From 9fe9cfecfdd0ab80d4ff13be975b0e6c67605c3b Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 27 Nov 2024 19:19:36 +0100 Subject: [PATCH] fix(migration): Decrypt ownCloud secrets v2 Signed-off-by: Christoph Wurst --- lib/private/Security/Crypto.php | 32 ++++++++++++++++++++++++++++--- tests/lib/Security/CryptoTest.php | 13 +++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lib/private/Security/Crypto.php b/lib/private/Security/Crypto.php index aeeafcc271c52..fe2524503ea1d 100644 --- a/lib/private/Security/Crypto.php +++ b/lib/private/Security/Crypto.php @@ -146,6 +146,25 @@ private function decryptWithoutSecret(string $authenticatedCiphertext, string $p throw new Exception('Authenticated ciphertext could not be decoded.'); } + /* + * Rearrange arguments for legacy ownCloud migrations + * + * The original scheme consistent of three parts. Nextcloud added a + * fourth at the end as "2" or later "3", ownCloud added "v2" at the + * beginning. + */ + $originalParts = $parts; + $isOwnCloudV2Migration = $partCount === 4 && $originalParts[0] === 'v2'; + if ($isOwnCloudV2Migration) { + $parts = [ + $parts[1], + $parts[2], + $parts[3], + '2' + ]; + } + + // Convert hex-encoded values to binary $ciphertext = $this->hex2bin($parts[0]); $iv = $parts[1]; $hmac = $this->hex2bin($parts[2]); @@ -156,7 +175,7 @@ private function decryptWithoutSecret(string $authenticatedCiphertext, string $p $iv = $this->hex2bin($iv); } - if ($version === '3') { + if ($version === '3' || $isOwnCloudV2Migration) { $keyMaterial = hash_hkdf('sha512', $password); $encryptionKey = substr($keyMaterial, 0, 32); $hmacKey = substr($keyMaterial, 32); @@ -165,8 +184,15 @@ private function decryptWithoutSecret(string $authenticatedCiphertext, string $p $this->cipher->setPassword($encryptionKey); $this->cipher->setIV($iv); - if (!hash_equals($this->calculateHMAC($parts[0] . $parts[1], $hmacKey), $hmac)) { - throw new Exception('HMAC does not match.'); + if ($isOwnCloudV2Migration) { + // ownCloud uses the binary IV for HMAC calculation + if (!hash_equals($this->calculateHMAC($parts[0] . $iv, $hmacKey), $hmac)) { + throw new Exception('HMAC does not match.'); + } + } else { + if (!hash_equals($this->calculateHMAC($parts[0] . $parts[1], $hmacKey), $hmac)) { + throw new Exception('HMAC does not match.'); + } } $result = $this->cipher->decrypt($ciphertext); diff --git a/tests/lib/Security/CryptoTest.php b/tests/lib/Security/CryptoTest.php index bdbad8b26108d..80a2bf85d8a2c 100644 --- a/tests/lib/Security/CryptoTest.php +++ b/tests/lib/Security/CryptoTest.php @@ -89,6 +89,19 @@ public function testVersion2CiphertextDecryptsToCorrectPlaintext() { ); } + /** + * Test data taken from https://github.com/owncloud/core/blob/9deb8196b20354c8de0cd720ad4d18d52ccc96d8/tests/lib/Security/CryptoTest.php#L56-L60 + */ + public function testOcVersion2CiphertextDecryptsToCorrectPlaintext() { + $this->assertSame( + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.', + $this->crypto->decrypt( + 'v2|d57dbe4d1317cdf19d4ddc2df807f6b5d63ab1e119c46590ce54bae56a9cd3969168c4ec1600ac9758dd7e7afb9c4c962dd23072c1463add1d9c77c467723b37bb768ef00e3c50898e59247cbb59ce56b74ce5990648ffe9e40d0e95076c27a785bdcf32c219ea4ad5c316b1f12f48c1|6bd21db258a5e406a2c288a444de195f|a19111a4cf1a11ee95fc1734699c20964eaa05bb007e1cecc4cc6872f827a4b7deedc977c13b138d728d68116aa3d82f9673e20c7e447a9788aa3be994b67cd6', + 'ThisIsAVeryS3cur3P4ssw0rd' + ) + ); + } + public function testVersion3CiphertextDecryptsToCorrectPlaintext() { $this->assertSame( 'Another plaintext value that will be encrypted with version 3. It addresses the related key issue. Old ciphertexts should be decrypted properly, but only use the better version for encryption.',