Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting rid of openssl_seal and rc4 in server side encryption #37243

Merged
merged 6 commits into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 64 additions & 17 deletions apps/encryption/lib/Crypto/Crypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,9 @@ private function getOpenSSLConfig() {
}

/**
* @param string $plainContent
* @param string $passPhrase
* @param int $version
* @param int $position
* @return false|string
* @throws EncryptionFailedException
*/
public function symmetricEncryptFileContent($plainContent, $passPhrase, $version, $position) {
public function symmetricEncryptFileContent(string $plainContent, string $passPhrase, int $version, string $position): string|false {
if (!$plainContent) {
$this->logger->error('Encryption Library, symmetrical encryption failed no content given',
['app' => 'encryption']);
Expand Down Expand Up @@ -409,7 +404,7 @@ public function encryptPrivateKey($privateKey, $password, $uid = '') {
$privateKey,
$hash,
0,
0
'0'
);

return $encryptedKey;
Expand Down Expand Up @@ -537,12 +532,8 @@ private function checkSignature(string $data, string $passPhrase, string $expect

/**
* create signature
*
* @param string $data
* @param string $passPhrase
* @return string
*/
private function createSignature($data, $passPhrase) {
private function createSignature(string $data, string $passPhrase): string {
$passPhrase = hash('sha512', $passPhrase . 'a', true);
return hash_hmac('sha256', $data, $passPhrase);
}
Expand Down Expand Up @@ -695,13 +686,25 @@ public function generateFileKey() {
}

/**
* @param string $encKeyFile
* @param string $shareKey
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $privateKey
* @return string
* @throws MultiKeyDecryptException
*/
public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
public function multiKeyDecrypt(string $shareKey, $privateKey): string {
$plainContent = '';

// decrypt the intermediate key with RSA
if (openssl_private_decrypt($shareKey, $intermediate, $privateKey, OPENSSL_PKCS1_OAEP_PADDING)) {
return $intermediate;
} else {
throw new MultiKeyDecryptException('multikeydecrypt with share key failed:' . openssl_error_string());
}
}

/**
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $privateKey
* @throws MultiKeyDecryptException
*/
public function multiKeyDecryptLegacy(string $encKeyFile, string $shareKey, $privateKey): string {
if (!$encKeyFile) {
throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
}
Expand All @@ -714,13 +717,56 @@ public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
}
}

/**
* @param array<string,\OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string> $keyFiles
* @throws MultiKeyEncryptException
*/
public function multiKeyEncrypt(string $plainContent, array $keyFiles): array {
if (empty($plainContent)) {
throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content');
}

// Set empty vars to be set by openssl by reference
$shareKeys = [];
$mappedShareKeys = [];

// make sure that there is at least one public key to use
if (count($keyFiles) >= 1) {
// prepare the encrypted keys
$shareKeys = [];

// iterate over the public keys and encrypt the intermediate
// for each of them with RSA
foreach ($keyFiles as $tmp_key) {
if (openssl_public_encrypt($plainContent, $tmp_output, $tmp_key, OPENSSL_PKCS1_OAEP_PADDING)) {
$shareKeys[] = $tmp_output;
}
}

// set the result if everything worked fine
if (count($keyFiles) === count($shareKeys)) {
$i = 0;

// Ensure each shareKey is labelled with its corresponding key id
foreach ($keyFiles as $userId => $publicKey) {
$mappedShareKeys[$userId] = $shareKeys[$i];
$i++;
}

return $mappedShareKeys;
}
}
throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string());
}

/**
* @param string $plainContent
* @param array $keyFiles
* @return array
* @throws MultiKeyEncryptException
* @deprecated 27.0.0 use multiKeyEncrypt
*/
public function multiKeyEncrypt($plainContent, array $keyFiles) {
public function multiKeyEncryptLegacy($plainContent, array $keyFiles) {
// openssl_seal returns false without errors if plaincontent is empty
// so trigger our own error
if (empty($plainContent)) {
Expand Down Expand Up @@ -809,6 +855,7 @@ private function opensslOpen(string $data, string &$output, string $encrypted_ke
/**
* Custom implementation of openssl_seal()
*
* @deprecated 27.0.0 use multiKeyEncrypt
* @throws EncryptionFailedException
*/
private function opensslSeal(string $data, string &$sealed_data, array &$encrypted_keys, array $public_key, string $cipher_algo): int|false {
Expand Down
57 changes: 35 additions & 22 deletions apps/encryption/lib/Crypto/Encryption.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class Encryption implements IEncryptionModule {
/** @var int Current version of the file */
private $version = 0;

private bool $useLegacyFileKey = true;

/** @var array remember encryption signature version */
private static $rememberVersion = [];

Expand Down Expand Up @@ -182,6 +184,8 @@ public function begin($path, $user, $mode, array $header, array $accessList) {
$this->writeCache = '';
$this->useLegacyBase64Encoding = true;

$this->useLegacyFileKey = ($header['useLegacyFileKey'] ?? 'true') !== 'false';

if (isset($header['encoding'])) {
$this->useLegacyBase64Encoding = $header['encoding'] !== Crypt::BINARY_ENCODING_FORMAT;
}
Expand All @@ -195,13 +199,17 @@ public function begin($path, $user, $mode, array $header, array $accessList) {
}

if ($this->session->decryptAllModeActivated()) {
$encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path);
$shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid());
$this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
$shareKey,
$this->session->getDecryptAllKey());
if ($this->useLegacyFileKey) {
$encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path);
$this->fileKey = $this->crypt->multiKeyDecryptLegacy($encryptedFileKey,
$shareKey,
$this->session->getDecryptAllKey());
} else {
$this->fileKey = $this->crypt->multiKeyDecrypt($shareKey, $this->session->getDecryptAllKey());
}
} else {
$this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
$this->fileKey = $this->keyManager->getFileKey($this->path, $this->user, $this->useLegacyFileKey);
}

// always use the version from the original file, also part files
Expand Down Expand Up @@ -239,7 +247,11 @@ public function begin($path, $user, $mode, array $header, array $accessList) {
$this->cipher = $this->crypt->getLegacyCipher();
}

$result = ['cipher' => $this->cipher, 'signed' => 'true'];
$result = [
'cipher' => $this->cipher,
'signed' => 'true',
'useLegacyFileKey' => 'false',
];

if ($this->useLegacyBase64Encoding !== true) {
$result['encoding'] = Crypt::BINARY_ENCODING_FORMAT;
Expand All @@ -254,14 +266,14 @@ public function begin($path, $user, $mode, array $header, array $accessList) {
* buffer.
*
* @param string $path to the file
* @param int $position
* @param string $position
* @return string remained data which should be written to the file in case
* of a write operation
* @throws PublicKeyMissingException
* @throws \Exception
* @throws \OCA\Encryption\Exceptions\MultiKeyEncryptException
*/
public function end($path, $position = 0) {
public function end($path, $position = '0') {
$result = '';
if ($this->isWriteOperation) {
// in case of a part file we remember the new signature versions
Expand Down Expand Up @@ -296,10 +308,13 @@ public function end($path, $position = 0) {
}

$publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys, $this->getOwner($path));
$encryptedKeyfiles = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
$this->keyManager->setAllFileKeys($this->path, $encryptedKeyfiles);
$shareKeys = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
$this->keyManager->deleteLegacyFileKey($this->path);
foreach ($shareKeys as $uid => $keyFile) {
$this->keyManager->setShareKey($this->path, $uid, $keyFile);
}
}
return $result;
return $result ?: '';
}


Expand All @@ -315,7 +330,6 @@ public function encrypt($data, $position = 0) {
// If extra data is left over from the last round, make sure it
// is integrated into the next block
if ($this->writeCache) {

// Concat writeCache to start of $data
$data = $this->writeCache . $data;

Expand All @@ -327,15 +341,13 @@ public function encrypt($data, $position = 0) {
$encrypted = '';
// While there still remains some data to be processed & written
while (strlen($data) > 0) {

// Remaining length for this iteration, not of the
// entire file (may be greater than 8192 bytes)
$remainingLength = strlen($data);

// If data remaining to be written is less than the
// size of 1 unencrypted block
if ($remainingLength < $this->getUnencryptedBlockSize(true)) {

// Set writeCache to contents of $data
// The writeCache will be carried over to the
// next write round, and added to the start of
Expand All @@ -349,11 +361,10 @@ public function encrypt($data, $position = 0) {
// Clear $data ready for next round
$data = '';
} else {

// Read the chunk from the start of $data
$chunk = substr($data, 0, $this->getUnencryptedBlockSize(true));

$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position);
$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, (string)$position);

Check notice

Code scanning / Psalm

PossiblyFalseOperand

Cannot concatenate with a possibly false false|string

// Remove the chunk we just processed from
// $data, leaving only unprocessed data in $data
Expand Down Expand Up @@ -391,18 +402,18 @@ public function decrypt($data, $position = 0) {
* @param string $path path to the file which should be updated
* @param string $uid of the user who performs the operation
* @param array $accessList who has access to the file contains the key 'users' and 'public'
* @return boolean
* @return bool
*/
public function update($path, $uid, array $accessList) {
if (empty($accessList)) {
if (isset(self::$rememberVersion[$path])) {
$this->keyManager->setVersion($path, self::$rememberVersion[$path], new View());
unset(self::$rememberVersion[$path]);
}
return;
return false;
}

$fileKey = $this->keyManager->getFileKey($path, $uid);
$fileKey = $this->keyManager->getFileKey($path, $uid, null);

if (!empty($fileKey)) {
$publicKeys = [];
Expand All @@ -420,11 +431,13 @@ public function update($path, $uid, array $accessList) {

$publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->getOwner($path));

$encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
$shareKeys = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);

$this->keyManager->deleteAllFileKeys($path);

$this->keyManager->setAllFileKeys($path, $encryptedFileKey);
foreach ($shareKeys as $uid => $keyFile) {
$this->keyManager->setShareKey($this->path, $uid, $keyFile);
}
} else {
$this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
['file' => $path, 'app' => 'encryption']);
Expand Down Expand Up @@ -503,7 +516,7 @@ public function getUnencryptedBlockSize($signed = false) {
* @throws DecryptionFailedException
*/
public function isReadable($path, $uid) {
$fileKey = $this->keyManager->getFileKey($path, $uid);
$fileKey = $this->keyManager->getFileKey($path, $uid, null);
if (empty($fileKey)) {
$owner = $this->util->getOwner($path);
if ($owner !== $uid) {
Expand Down
32 changes: 23 additions & 9 deletions apps/encryption/lib/KeyManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
use OCP\Lock\ILockingProvider;

class KeyManager {

/**
* @var Session
*/
Expand Down Expand Up @@ -441,17 +440,21 @@ public function getPrivateKey($userId) {
/**
* @param string $path
* @param $uid
* @param ?bool $useLegacyFileKey null means try both
* @return string
*/
public function getFileKey($path, $uid) {
public function getFileKey(string $path, ?string $uid, ?bool $useLegacyFileKey): string {
if ($uid === '') {
$uid = null;
}
$publicAccess = is_null($uid);
$encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID);
$encryptedFileKey = '';
if ($useLegacyFileKey ?? true) {
$encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID);

if (empty($encryptedFileKey)) {
return '';
if (empty($encryptedFileKey) && $useLegacyFileKey) {
return '';
}
}

if ($this->util->isMasterKeyEnabled()) {
Expand All @@ -475,10 +478,17 @@ public function getFileKey($path, $uid) {
$privateKey = $this->session->getPrivateKey();
}

if ($encryptedFileKey && $shareKey && $privateKey) {
return $this->crypt->multiKeyDecrypt($encryptedFileKey,
$shareKey,
$privateKey);
if ($useLegacyFileKey ?? true) {
if ($encryptedFileKey && $shareKey && $privateKey) {
Fixed Show fixed Hide fixed
return $this->crypt->multiKeyDecryptLegacy($encryptedFileKey,
$shareKey,
$privateKey);
}
}
if (!($useLegacyFileKey ?? false)) {
if ($shareKey && $privateKey) {
return $this->crypt->multiKeyDecrypt($shareKey, $privateKey);
}
}

return '';
Expand Down Expand Up @@ -656,6 +666,10 @@ public function deleteAllFileKeys($path) {
return $this->keyStorage->deleteAllFileKeys($path);
}

public function deleteLegacyFileKey(string $path): bool {
return $this->keyStorage->deleteFileKey($path, $this->fileKeyId, Encryption::ID);
}

/**
* @param array $userIds
* @return array
Expand Down
Loading