Skip to content

Commit 0df20f6

Browse files
authored
Merge pull request #36173 from nextcloud/enh/openssl-seal-custom
Use a PHP implementation of openssl_seal that allows to use modern ciphers
2 parents 6466c8e + f2912ce commit 0df20f6

1 file changed

Lines changed: 108 additions & 9 deletions

File tree

apps/encryption/lib/Crypto/Crypt.php

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
* @author Björn Schießle <[email protected]>
77
* @author Christoph Wurst <[email protected]>
88
* @author Clark Tomlinson <[email protected]>
9+
* @author Côme Chilliet <[email protected]>
910
* @author Joas Schilling <[email protected]>
11+
* @author Kevin Niehage <[email protected]>
1012
* @author Lukas Reschke <[email protected]>
1113
* @author Morris Jobke <[email protected]>
1214
* @author Roeland Jago Douma <[email protected]>
@@ -40,6 +42,7 @@
4042
use OCP\IL10N;
4143
use OCP\ILogger;
4244
use OCP\IUserSession;
45+
use phpseclib\Crypt\RC4;
4346

4447
/**
4548
* Class Crypt provides the encryption implementation of the default Nextcloud
@@ -517,12 +520,9 @@ public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $ciph
517520
/**
518521
* check for valid signature
519522
*
520-
* @param string $data
521-
* @param string $passPhrase
522-
* @param string $expectedSignature
523523
* @throws GenericEncryptionException
524524
*/
525-
private function checkSignature($data, $passPhrase, $expectedSignature) {
525+
private function checkSignature(string $data, string $passPhrase, string $expectedSignature): void {
526526
$enforceSignature = !$this->config->getSystemValueBool('encryption_skip_signature_check', false);
527527

528528
$signature = $this->createSignature($data, $passPhrase);
@@ -695,9 +695,9 @@ public function generateFileKey() {
695695
}
696696

697697
/**
698-
* @param $encKeyFile
699-
* @param $shareKey
700-
* @param $privateKey
698+
* @param string $encKeyFile
699+
* @param string $shareKey
700+
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $privateKey
701701
* @return string
702702
* @throws MultiKeyDecryptException
703703
*/
@@ -706,7 +706,8 @@ public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
706706
throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
707707
}
708708

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

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

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

753+
/**
754+
* returns the value of $useLegacyBase64Encoding
755+
*
756+
* @return bool
757+
*/
752758
public function useLegacyBase64Encoding(): bool {
753759
return $this->useLegacyBase64Encoding;
754760
}
761+
762+
/**
763+
* Uses phpseclib RC4 implementation
764+
*/
765+
private function rc4Decrypt(string $data, string $secret): string {
766+
$rc4 = new RC4();
767+
/** @psalm-suppress InternalMethod */
768+
$rc4->setKey($secret);
769+
770+
return $rc4->decrypt($data);
771+
}
772+
773+
/**
774+
* Uses phpseclib RC4 implementation
775+
*/
776+
private function rc4Encrypt(string $data, string $secret): string {
777+
$rc4 = new RC4();
778+
/** @psalm-suppress InternalMethod */
779+
$rc4->setKey($secret);
780+
781+
return $rc4->encrypt($data);
782+
}
783+
784+
/**
785+
* Custom implementation of openssl_open()
786+
*
787+
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
788+
* @throws DecryptionFailedException
789+
*/
790+
private function opensslOpen(string $data, string &$output, string $encrypted_key, $private_key, string $cipher_algo): bool {
791+
$result = false;
792+
793+
// check if RC4 is used
794+
if (strcasecmp($cipher_algo, "rc4") === 0) {
795+
// decrypt the intermediate key with RSA
796+
if (openssl_private_decrypt($encrypted_key, $intermediate, $private_key, OPENSSL_PKCS1_PADDING)) {
797+
// decrypt the file key with the intermediate key
798+
// using our own RC4 implementation
799+
$output = $this->rc4Decrypt($data, $intermediate);
800+
$result = (strlen($output) === strlen($data));
801+
}
802+
} else {
803+
throw new DecryptionFailedException('Unsupported cipher '.$cipher_algo);
804+
}
805+
806+
return $result;
807+
}
808+
809+
/**
810+
* Custom implementation of openssl_seal()
811+
*
812+
* @throws EncryptionFailedException
813+
*/
814+
private function opensslSeal(string $data, string &$sealed_data, array &$encrypted_keys, array $public_key, string $cipher_algo): int|false {
815+
$result = false;
816+
817+
// check if RC4 is used
818+
if (strcasecmp($cipher_algo, "rc4") === 0) {
819+
// make sure that there is at least one public key to use
820+
if (count($public_key) >= 1) {
821+
// generate the intermediate key
822+
$intermediate = openssl_random_pseudo_bytes(16, $strong_result);
823+
824+
// check if we got strong random data
825+
if ($strong_result) {
826+
// encrypt the file key with the intermediate key
827+
// using our own RC4 implementation
828+
$sealed_data = $this->rc4Encrypt($data, $intermediate);
829+
if (strlen($sealed_data) === strlen($data)) {
830+
// prepare the encrypted keys
831+
$encrypted_keys = [];
832+
833+
// iterate over the public keys and encrypt the intermediate
834+
// for each of them with RSA
835+
foreach ($public_key as $tmp_key) {
836+
if (openssl_public_encrypt($intermediate, $tmp_output, $tmp_key, OPENSSL_PKCS1_PADDING)) {
837+
$encrypted_keys[] = $tmp_output;
838+
}
839+
}
840+
841+
// set the result if everything worked fine
842+
if (count($public_key) === count($encrypted_keys)) {
843+
$result = strlen($sealed_data);
844+
}
845+
}
846+
}
847+
}
848+
} else {
849+
throw new EncryptionFailedException('Unsupported cipher '.$cipher_algo);
850+
}
851+
852+
return $result;
853+
}
755854
}

0 commit comments

Comments
 (0)