Skip to content

Commit

Permalink
Merge pull request #36173 from nextcloud/enh/openssl-seal-custom
Browse files Browse the repository at this point in the history
Use a PHP implementation of openssl_seal that allows to use modern ciphers
  • Loading branch information
szaimen authored Mar 2, 2023
2 parents 6466c8e + f2912ce commit 0df20f6
Showing 1 changed file with 108 additions and 9 deletions.
117 changes: 108 additions & 9 deletions apps/encryption/lib/Crypto/Crypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Clark Tomlinson <fallen013@gmail.com>
* @author Côme Chilliet <come.chilliet@nextcloud.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Kevin Niehage <kevin@niehage.name>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
Expand Down Expand Up @@ -40,6 +42,7 @@
use OCP\IL10N;
use OCP\ILogger;
use OCP\IUserSession;
use phpseclib\Crypt\RC4;

/**
* Class Crypt provides the encryption implementation of the default Nextcloud
Expand Down Expand Up @@ -517,12 +520,9 @@ public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $ciph
/**
* check for valid signature
*
* @param string $data
* @param string $passPhrase
* @param string $expectedSignature
* @throws GenericEncryptionException
*/
private function checkSignature($data, $passPhrase, $expectedSignature) {
private function checkSignature(string $data, string $passPhrase, string $expectedSignature): void {
$enforceSignature = !$this->config->getSystemValueBool('encryption_skip_signature_check', false);

$signature = $this->createSignature($data, $passPhrase);
Expand Down Expand Up @@ -695,9 +695,9 @@ public function generateFileKey() {
}

/**
* @param $encKeyFile
* @param $shareKey
* @param $privateKey
* @param string $encKeyFile
* @param string $shareKey
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $privateKey
* @return string
* @throws MultiKeyDecryptException
*/
Expand All @@ -706,7 +706,8 @@ public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
}

if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey, 'RC4')) {
$plainContent = '';
if ($this->opensslOpen($encKeyFile, $plainContent, $shareKey, $privateKey, 'RC4')) {
return $plainContent;
} else {
throw new MultiKeyDecryptException('multikeydecrypt with share key failed:' . openssl_error_string());
Expand All @@ -731,7 +732,7 @@ public function multiKeyEncrypt($plainContent, array $keyFiles) {
$shareKeys = [];
$mappedShareKeys = [];

if (openssl_seal($plainContent, $sealed, $shareKeys, $keyFiles, 'RC4')) {
if ($this->opensslSeal($plainContent, $sealed, $shareKeys, $keyFiles, 'RC4')) {
$i = 0;

// Ensure each shareKey is labelled with its corresponding key id
Expand All @@ -749,7 +750,105 @@ public function multiKeyEncrypt($plainContent, array $keyFiles) {
}
}

/**
* returns the value of $useLegacyBase64Encoding
*
* @return bool
*/
public function useLegacyBase64Encoding(): bool {
return $this->useLegacyBase64Encoding;
}

/**
* Uses phpseclib RC4 implementation
*/
private function rc4Decrypt(string $data, string $secret): string {
$rc4 = new RC4();
/** @psalm-suppress InternalMethod */
$rc4->setKey($secret);

return $rc4->decrypt($data);
}

/**
* Uses phpseclib RC4 implementation
*/
private function rc4Encrypt(string $data, string $secret): string {
$rc4 = new RC4();
/** @psalm-suppress InternalMethod */
$rc4->setKey($secret);

return $rc4->encrypt($data);
}

/**
* Custom implementation of openssl_open()
*
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
* @throws DecryptionFailedException
*/
private function opensslOpen(string $data, string &$output, string $encrypted_key, $private_key, string $cipher_algo): bool {
$result = false;

// check if RC4 is used
if (strcasecmp($cipher_algo, "rc4") === 0) {
// decrypt the intermediate key with RSA
if (openssl_private_decrypt($encrypted_key, $intermediate, $private_key, OPENSSL_PKCS1_PADDING)) {
// decrypt the file key with the intermediate key
// using our own RC4 implementation
$output = $this->rc4Decrypt($data, $intermediate);
$result = (strlen($output) === strlen($data));
}
} else {
throw new DecryptionFailedException('Unsupported cipher '.$cipher_algo);
}

return $result;
}

/**
* Custom implementation of openssl_seal()
*
* @throws EncryptionFailedException
*/
private function opensslSeal(string $data, string &$sealed_data, array &$encrypted_keys, array $public_key, string $cipher_algo): int|false {
$result = false;

// check if RC4 is used
if (strcasecmp($cipher_algo, "rc4") === 0) {
// make sure that there is at least one public key to use
if (count($public_key) >= 1) {
// generate the intermediate key
$intermediate = openssl_random_pseudo_bytes(16, $strong_result);

// check if we got strong random data
if ($strong_result) {
// encrypt the file key with the intermediate key
// using our own RC4 implementation
$sealed_data = $this->rc4Encrypt($data, $intermediate);
if (strlen($sealed_data) === strlen($data)) {
// prepare the encrypted keys
$encrypted_keys = [];

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

// set the result if everything worked fine
if (count($public_key) === count($encrypted_keys)) {
$result = strlen($sealed_data);
}
}
}
}
} else {
throw new EncryptionFailedException('Unsupported cipher '.$cipher_algo);
}

return $result;
}
}

0 comments on commit 0df20f6

Please sign in to comment.