From 1e7611f48486b7165dfc8651506bdb20624653b9 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Wed, 23 Mar 2022 17:13:17 +0100 Subject: [PATCH 01/13] Update README.md [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b6b31c..b2866d7 100644 --- a/README.md +++ b/README.md @@ -202,4 +202,4 @@ Help us keep Cryptomator open and inclusive. Please read and follow our [Code of ## License -Distributed under the AGPLv3. See the LICENSE file for more info. +This project is dual-licensed under the AGPLv3 for FOSS projects as well as a commercial license derived from the LGPL for independent software vendors and resellers. If you want to use this library in applications that are *not* licensed under the AGPL, feel free to contact our [sales team](https://cryptomator.org/enterprise/). From ed3a6d0c70b9ca4c766c8a4d7433eecbd7f6bb9d Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Thu, 31 Mar 2022 15:55:35 +0200 Subject: [PATCH 02/13] Updated process script --- Scripts/process.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/process.sh b/Scripts/process.sh index 9a0da33..9ab497b 100755 --- a/Scripts/process.sh +++ b/Scripts/process.sh @@ -26,8 +26,8 @@ function process_output() { } if [ "$staged_mode" = true ]; then - process_output "SwiftFormat" "python ./Scripts/git-format-staged.py -f 'swiftformat stdin --stdinpath \"{}\" --quiet' '*.swift'" - process_output "SwiftLint" "python ./Scripts/git-format-staged.py --no-write -f 'swiftlint --use-stdin --quiet >&2' '*.swift'" + process_output "SwiftFormat" "python3 ./Scripts/git-format-staged.py -f 'swiftformat stdin --stdinpath \"{}\" --quiet' '*.swift'" + process_output "SwiftLint" "python3 ./Scripts/git-format-staged.py --no-write -f 'swiftlint --use-stdin --quiet >&2' '*.swift'" if [[ "$final_status" -gt 0 ]]; then printf '\nChanges werde made or are required. Please review the output above for further details.\n' fi From a66dc2b2b23111095123cd1eaad49468af4bb386 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 20 May 2022 11:26:35 +0200 Subject: [PATCH 03/13] increase deployment target to iOS 13 --- CryptomatorCryptoLib.xcodeproj/project.pbxproj | 6 ++++-- Package.swift | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CryptomatorCryptoLib.xcodeproj/project.pbxproj b/CryptomatorCryptoLib.xcodeproj/project.pbxproj index a545370..0f7d0ec 100644 --- a/CryptomatorCryptoLib.xcodeproj/project.pbxproj +++ b/CryptomatorCryptoLib.xcodeproj/project.pbxproj @@ -528,7 +528,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -588,7 +588,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -612,6 +612,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Sources/CryptomatorCryptoLib/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -636,6 +637,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Sources/CryptomatorCryptoLib/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Package.swift b/Package.swift index db6c710..05a4d30 100644 --- a/Package.swift +++ b/Package.swift @@ -13,8 +13,8 @@ import PackageDescription let package = Package( name: "CryptomatorCryptoLib", platforms: [ - .iOS(.v9), - .macOS(.v10_12) + .iOS(.v13), + .macOS(.v10_15) ], products: [ .library(name: "CryptomatorCryptoLib", targets: ["CryptomatorCryptoLib"]) From a0152343f093ebba279ded1742c1675dc36d662a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 20 May 2022 11:28:38 +0200 Subject: [PATCH 04/13] move CTR-specific test cases to specialized test class --- .../project.pbxproj | 4 + Sources/CryptomatorCryptoLib/Cryptor.swift | 4 +- .../CryptorTests.swift | 116 +++++++----------- .../CtrCryptorTests.swift | 70 +++++++++++ 4 files changed, 118 insertions(+), 76 deletions(-) create mode 100644 Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift diff --git a/CryptomatorCryptoLib.xcodeproj/project.pbxproj b/CryptomatorCryptoLib.xcodeproj/project.pbxproj index 0f7d0ec..4c8a71e 100644 --- a/CryptomatorCryptoLib.xcodeproj/project.pbxproj +++ b/CryptomatorCryptoLib.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 9E9BB81624558DFF00F9FF51 /* MasterkeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */; }; 9EB822C1248AF82200879838 /* AesCtr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB822C0248AF82200879838 /* AesCtr.swift */; }; 9EB822C3248AF9C500879838 /* AesCtrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB822C2248AF9C500879838 /* AesCtrTests.swift */; }; + 9EBEC947283782E6002210DE /* CtrCryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEC946283782E6002210DE /* CtrCryptorTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -97,6 +98,7 @@ 9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterkeyTests.swift; sourceTree = ""; }; 9EB822C0248AF82200879838 /* AesCtr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtr.swift; sourceTree = ""; }; 9EB822C2248AF9C500879838 /* AesCtrTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtrTests.swift; sourceTree = ""; }; + 9EBEC946283782E6002210DE /* CtrCryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CtrCryptorTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -171,6 +173,7 @@ 9EB822C2248AF9C500879838 /* AesCtrTests.swift */, 9E44EEA724599C7800A37B01 /* AesSivTests.swift */, 9E35C4EA24576A3D0006E50C /* CryptorTests.swift */, + 9EBEC946283782E6002210DE /* CtrCryptorTests.swift */, 74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */, 74A5B57525A869DD002D10F7 /* MasterkeyFileTests.swift */, 9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */, @@ -443,6 +446,7 @@ files = ( 74A5B57E25A86A69002D10F7 /* CryptoSupportMock.swift in Sources */, 9E44EEA92459AB1500A37B01 /* AesSivTests.swift in Sources */, + 9EBEC947283782E6002210DE /* CtrCryptorTests.swift in Sources */, 9EB822C3248AF9C500879838 /* AesCtrTests.swift in Sources */, 74A5B57625A869DD002D10F7 /* MasterkeyFileTests.swift in Sources */, 9E9BB81624558DFF00F9FF51 /* MasterkeyTests.swift in Sources */, diff --git a/Sources/CryptomatorCryptoLib/Cryptor.swift b/Sources/CryptomatorCryptoLib/Cryptor.swift index 3dae122..7274f3c 100644 --- a/Sources/CryptomatorCryptoLib/Cryptor.swift +++ b/Sources/CryptomatorCryptoLib/Cryptor.swift @@ -66,8 +66,8 @@ public class Cryptor { return contentCryptor.nonceLen + fileHeaderPayloadSize + contentCryptor.tagLen } - private let cleartextChunkSize = 32 * 1024 - private var ciphertextChunkSize: Int { + public let cleartextChunkSize = 32 * 1024 + public var ciphertextChunkSize: Int { return contentCryptor.nonceLen + cleartextChunkSize + contentCryptor.tagLen } diff --git a/Tests/CryptomatorCryptoLibTests/CryptorTests.swift b/Tests/CryptomatorCryptoLibTests/CryptorTests.swift index bdd77ef..5f3f91d 100644 --- a/Tests/CryptomatorCryptoLibTests/CryptorTests.swift +++ b/Tests/CryptomatorCryptoLibTests/CryptorTests.swift @@ -10,17 +10,19 @@ import XCTest @testable import CryptomatorCryptoLib class CryptorTests: XCTestCase { + var contentCryptor: ContentCryptor! var cryptor: Cryptor! var tmpDirURL: URL! - override func setUpWithError() throws { - let aesKey = [UInt8](repeating: 0x55, count: 32) - let macKey = [UInt8](repeating: 0x77, count: 32) - let masterkey = Masterkey.createFromRaw(aesMasterKey: aesKey, macMasterKey: macKey) - let cryptoSupport = CryptoSupportMock() - let contentCryptor = CtrThenHmacContentCryptor(macKey: macKey, cryptoSupport: cryptoSupport) - cryptor = Cryptor(masterkey: masterkey, cryptoSupport: cryptoSupport, contentCryptor: contentCryptor) + override class var defaultTestSuite: XCTestSuite { + // Return empty `XCTestSuite` so that no tests from this "abstract" `XCTestCase` is run. + // Make sure to override this in subclasses so that the implemented test case can run. + return XCTestSuite(name: "InterfaceTests Excluded") + } + func setUpWithError(masterkey: Masterkey, cryptoSupport: CryptoSupport, contentCryptor: ContentCryptor) throws { + self.contentCryptor = contentCryptor + cryptor = Cryptor(masterkey: masterkey, cryptoSupport: cryptoSupport, contentCryptor: contentCryptor) tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tmpDirURL, withIntermediateDirectories: true) } @@ -59,50 +61,6 @@ class CryptorTests: XCTestCase { } } - func testCreateHeader() throws { - let header = try cryptor.createHeader() - XCTAssertEqual([UInt8](repeating: 0xF0, count: 16), header.nonce) - XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), header.contentKey) - } - - func testEncryptHeader() throws { - let header = try cryptor.createHeader() - let encrypted = try cryptor.encryptHeader(header) - let expected: [UInt8] = [ - 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, - 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, - 0x0D, 0x91, 0xF2, 0x9C, 0xC6, 0x35, 0xD7, 0x5E, - 0x1E, 0x42, 0x23, 0x1E, 0xC7, 0x90, 0x57, 0xE3, - 0x8D, 0x98, 0xF3, 0x58, 0x07, 0x2C, 0x9F, 0x03, - 0xBC, 0xEA, 0x5A, 0x98, 0x3B, 0x68, 0x62, 0x89, - 0x3E, 0xBC, 0x5E, 0x5E, 0x27, 0x39, 0xCB, 0x8E, - 0xD4, 0x27, 0x61, 0x06, 0x8E, 0x7F, 0x3A, 0x4E, - 0xC7, 0x9F, 0x4D, 0x3E, 0x20, 0x57, 0xDC, 0xE4, - 0x65, 0xA5, 0xFF, 0x93, 0xC2, 0x7B, 0xD2, 0xB8, - 0x3F, 0xE3, 0xD0, 0x8C, 0xB3, 0x92, 0xED, 0x96 - ] - XCTAssertEqual(expected, encrypted) - } - - func testDecryptHeader() throws { - let ciphertext: [UInt8] = [ - 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, - 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, - 0x0D, 0x91, 0xF2, 0x9C, 0xC6, 0x35, 0xD7, 0x5E, - 0x1E, 0x42, 0x23, 0x1E, 0xC7, 0x90, 0x57, 0xE3, - 0x8D, 0x98, 0xF3, 0x58, 0x07, 0x2C, 0x9F, 0x03, - 0xBC, 0xEA, 0x5A, 0x98, 0x3B, 0x68, 0x62, 0x89, - 0x3E, 0xBC, 0x5E, 0x5E, 0x27, 0x39, 0xCB, 0x8E, - 0xD4, 0x27, 0x61, 0x06, 0x8E, 0x7F, 0x3A, 0x4E, - 0xC7, 0x9F, 0x4D, 0x3E, 0x20, 0x57, 0xDC, 0xE4, - 0x65, 0xA5, 0xFF, 0x93, 0xC2, 0x7B, 0xD2, 0xB8, - 0x3F, 0xE3, 0xD0, 0x8C, 0xB3, 0x92, 0xED, 0x96 - ] - let decrypted = try cryptor.decryptHeader(ciphertext) - XCTAssertEqual([UInt8](repeating: 0xF0, count: 16), decrypted.nonce) - XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), decrypted.contentKey) - } - func testEncryptAndDecryptContent() throws { let originalData = Data(repeating: 0x0F, count: 65 * 1024) let originalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) @@ -128,7 +86,7 @@ class CryptorTests: XCTestCase { } func testEncryptAndDecryptSingleChunk() throws { - let nonce = [UInt8](repeating: 0x00, count: 16) + let nonce = [UInt8](repeating: 0x00, count: contentCryptor.nonceLen) let filekey = [UInt8](repeating: 0x00, count: 32) let cleartext = [UInt8]("hello world".data(using: .ascii)!) @@ -139,47 +97,57 @@ class CryptorTests: XCTestCase { } func testCalculateCiphertextSize() { + let overheadPerChunk = contentCryptor.nonceLen + contentCryptor.tagLen + XCTAssertEqual(0, cryptor.calculateCiphertextSize(0)) - XCTAssertEqual(1 + 48, cryptor.calculateCiphertextSize(1)) - XCTAssertEqual(32 * 1024 - 1 + 48, cryptor.calculateCiphertextSize(32 * 1024 - 1)) - XCTAssertEqual(32 * 1024 + 48, cryptor.calculateCiphertextSize(32 * 1024)) + XCTAssertEqual(1 + overheadPerChunk, cryptor.calculateCiphertextSize(1)) + XCTAssertEqual(32 * 1024 - 1 + overheadPerChunk, cryptor.calculateCiphertextSize(32 * 1024 - 1)) + XCTAssertEqual(32 * 1024 + overheadPerChunk, cryptor.calculateCiphertextSize(32 * 1024)) - XCTAssertEqual(32 * 1024 + 1 + 48 * 2, cryptor.calculateCiphertextSize(32 * 1024 + 1)) - XCTAssertEqual(32 * 1024 + 2 + 48 * 2, cryptor.calculateCiphertextSize(32 * 1024 + 2)) - XCTAssertEqual(64 * 1024 - 1 + 48 * 2, cryptor.calculateCiphertextSize(64 * 1024 - 1)) - XCTAssertEqual(64 * 1024 + 48 * 2, cryptor.calculateCiphertextSize(64 * 1024)) + XCTAssertEqual(32 * 1024 + 1 + overheadPerChunk * 2, cryptor.calculateCiphertextSize(32 * 1024 + 1)) + XCTAssertEqual(32 * 1024 + 2 + overheadPerChunk * 2, cryptor.calculateCiphertextSize(32 * 1024 + 2)) + XCTAssertEqual(64 * 1024 - 1 + overheadPerChunk * 2, cryptor.calculateCiphertextSize(64 * 1024 - 1)) + XCTAssertEqual(64 * 1024 + overheadPerChunk * 2, cryptor.calculateCiphertextSize(64 * 1024)) - XCTAssertEqual(64 * 1024 + 1 + 48 * 3, cryptor.calculateCiphertextSize(64 * 1024 + 1)) + XCTAssertEqual(64 * 1024 + 1 + overheadPerChunk * 3, cryptor.calculateCiphertextSize(64 * 1024 + 1)) } func testCalculateCleartextSize() throws { + let overheadPerChunk = contentCryptor.nonceLen + contentCryptor.tagLen + XCTAssertEqual(0, try cryptor.calculateCleartextSize(0)) - XCTAssertEqual(1, try cryptor.calculateCleartextSize(1 + 48)) - XCTAssertEqual(32 * 1024 - 1, try cryptor.calculateCleartextSize(32 * 1024 - 1 + 48)) - XCTAssertEqual(32 * 1024, try cryptor.calculateCleartextSize(32 * 1024 + 48)) + XCTAssertEqual(1, try cryptor.calculateCleartextSize(1 + overheadPerChunk)) + XCTAssertEqual(32 * 1024 - 1, try cryptor.calculateCleartextSize(32 * 1024 - 1 + overheadPerChunk)) + XCTAssertEqual(32 * 1024, try cryptor.calculateCleartextSize(32 * 1024 + overheadPerChunk)) - XCTAssertEqual(32 * 1024 + 1, try cryptor.calculateCleartextSize(32 * 1024 + 1 + 48 * 2)) - XCTAssertEqual(32 * 1024 + 2, try cryptor.calculateCleartextSize(32 * 1024 + 2 + 48 * 2)) - XCTAssertEqual(64 * 1024 - 1, try cryptor.calculateCleartextSize(64 * 1024 - 1 + 48 * 2)) - XCTAssertEqual(64 * 1024, try cryptor.calculateCleartextSize(64 * 1024 + 48 * 2)) + XCTAssertEqual(32 * 1024 + 1, try cryptor.calculateCleartextSize(32 * 1024 + 1 + overheadPerChunk * 2)) + XCTAssertEqual(32 * 1024 + 2, try cryptor.calculateCleartextSize(32 * 1024 + 2 + overheadPerChunk * 2)) + XCTAssertEqual(64 * 1024 - 1, try cryptor.calculateCleartextSize(64 * 1024 - 1 + overheadPerChunk * 2)) + XCTAssertEqual(64 * 1024, try cryptor.calculateCleartextSize(64 * 1024 + overheadPerChunk * 2)) - XCTAssertEqual(64 * 1024 + 1, try cryptor.calculateCleartextSize(64 * 1024 + 1 + 48 * 3)) + XCTAssertEqual(64 * 1024 + 1, try cryptor.calculateCleartextSize(64 * 1024 + 1 + overheadPerChunk * 3)) } func testCalculateCleartextSizeWithInvalidCiphertextSize() throws { XCTAssertThrowsError(try cryptor.calculateCleartextSize(1), "invalid ciphertext size") { error in XCTAssertEqual(.invalidParameter("Method not defined for input value 1"), error as? CryptoError) } - XCTAssertThrowsError(try cryptor.calculateCleartextSize(48), "invalid ciphertext size") { error in - XCTAssertEqual(.invalidParameter("Method not defined for input value 48"), error as? CryptoError) + + let emptyPayload = contentCryptor.nonceLen + contentCryptor.tagLen + XCTAssertThrowsError(try cryptor.calculateCleartextSize(emptyPayload), "invalid ciphertext size") { error in + XCTAssertEqual(.invalidParameter("Method not defined for input value \(emptyPayload)"), error as? CryptoError) } - XCTAssertThrowsError(try cryptor.calculateCleartextSize(32 * 1024 + 1 + 48), "invalid ciphertext size") { error in - XCTAssertEqual(.invalidParameter("Method not defined for input value 32817"), error as? CryptoError) + + let oneChunkPlusOneByte = cryptor.ciphertextChunkSize + 1 + XCTAssertThrowsError(try cryptor.calculateCleartextSize(oneChunkPlusOneByte), "invalid ciphertext size") { error in + XCTAssertEqual(.invalidParameter("Method not defined for input value \(oneChunkPlusOneByte)"), error as? CryptoError) } - XCTAssertThrowsError(try cryptor.calculateCleartextSize(32 * 1024 + 48 * 2), "invalid ciphertext size") { error in - XCTAssertEqual(.invalidParameter("Method not defined for input value 32864"), error as? CryptoError) + + let oneChunkPlusEmptySecondChunk = cryptor.ciphertextChunkSize + contentCryptor.nonceLen + contentCryptor.tagLen + XCTAssertThrowsError(try cryptor.calculateCleartextSize(oneChunkPlusEmptySecondChunk), "invalid ciphertext size") { error in + XCTAssertEqual(.invalidParameter("Method not defined for input value \(oneChunkPlusEmptySecondChunk)"), error as? CryptoError) } } } diff --git a/Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift b/Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift new file mode 100644 index 0000000..df25577 --- /dev/null +++ b/Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift @@ -0,0 +1,70 @@ +// +// CryptorTests.swift +// CryptomatorCryptoLibTests +// +// Created by Sebastian Stenzel on 27.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import XCTest +@testable import CryptomatorCryptoLib + +class CtrCryptorTest: CryptorTests { + override class var defaultTestSuite: XCTestSuite { + return XCTestSuite(forTestCaseClass: CtrCryptorTest.self) + } + + override func setUpWithError() throws { + let aesKey = [UInt8](repeating: 0x55, count: 32) + let macKey = [UInt8](repeating: 0x77, count: 32) + let masterkey = Masterkey.createFromRaw(aesMasterKey: aesKey, macMasterKey: macKey) + let cryptoSupport = CryptoSupportMock() + let contentCryptor = CtrThenHmacContentCryptor(macKey: macKey, cryptoSupport: cryptoSupport) + + try super.setUpWithError(masterkey: masterkey, cryptoSupport: cryptoSupport, contentCryptor: contentCryptor) + } + + func testCreateHeader() throws { + let header = try cryptor.createHeader() + XCTAssertEqual([UInt8](repeating: 0xF0, count: 16), header.nonce) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), header.contentKey) + } + + func testEncryptHeader() throws { + let header = try cryptor.createHeader() + let encrypted = try cryptor.encryptHeader(header) + let expected: [UInt8] = [ + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x0D, 0x91, 0xF2, 0x9C, 0xC6, 0x35, 0xD7, 0x5E, + 0x1E, 0x42, 0x23, 0x1E, 0xC7, 0x90, 0x57, 0xE3, + 0x8D, 0x98, 0xF3, 0x58, 0x07, 0x2C, 0x9F, 0x03, + 0xBC, 0xEA, 0x5A, 0x98, 0x3B, 0x68, 0x62, 0x89, + 0x3E, 0xBC, 0x5E, 0x5E, 0x27, 0x39, 0xCB, 0x8E, + 0xD4, 0x27, 0x61, 0x06, 0x8E, 0x7F, 0x3A, 0x4E, + 0xC7, 0x9F, 0x4D, 0x3E, 0x20, 0x57, 0xDC, 0xE4, + 0x65, 0xA5, 0xFF, 0x93, 0xC2, 0x7B, 0xD2, 0xB8, + 0x3F, 0xE3, 0xD0, 0x8C, 0xB3, 0x92, 0xED, 0x96 + ] + XCTAssertEqual(expected, encrypted) + } + + func testDecryptHeader() throws { + let ciphertext: [UInt8] = [ + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x0D, 0x91, 0xF2, 0x9C, 0xC6, 0x35, 0xD7, 0x5E, + 0x1E, 0x42, 0x23, 0x1E, 0xC7, 0x90, 0x57, 0xE3, + 0x8D, 0x98, 0xF3, 0x58, 0x07, 0x2C, 0x9F, 0x03, + 0xBC, 0xEA, 0x5A, 0x98, 0x3B, 0x68, 0x62, 0x89, + 0x3E, 0xBC, 0x5E, 0x5E, 0x27, 0x39, 0xCB, 0x8E, + 0xD4, 0x27, 0x61, 0x06, 0x8E, 0x7F, 0x3A, 0x4E, + 0xC7, 0x9F, 0x4D, 0x3E, 0x20, 0x57, 0xDC, 0xE4, + 0x65, 0xA5, 0xFF, 0x93, 0xC2, 0x7B, 0xD2, 0xB8, + 0x3F, 0xE3, 0xD0, 0x8C, 0xB3, 0x92, 0xED, 0x96 + ] + let decrypted = try cryptor.decryptHeader(ciphertext) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 16), decrypted.nonce) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), decrypted.contentKey) + } +} From 406ac20460275689e2fdf969f05a646e9bd43e48 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 20 May 2022 11:29:55 +0200 Subject: [PATCH 05/13] read nonce length from ContentCryptor --- Sources/CryptomatorCryptoLib/Cryptor.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CryptomatorCryptoLib/Cryptor.swift b/Sources/CryptomatorCryptoLib/Cryptor.swift index 7274f3c..17a76b0 100644 --- a/Sources/CryptomatorCryptoLib/Cryptor.swift +++ b/Sources/CryptomatorCryptoLib/Cryptor.swift @@ -154,7 +154,7 @@ public class Cryptor { // MARK: - File Header Encryption and Decryption func createHeader() throws -> FileHeader { - let nonce = try cryptoSupport.createRandomBytes(size: kCCBlockSizeAES128) + let nonce = try cryptoSupport.createRandomBytes(size: contentCryptor.nonceLen) let contentKey = try cryptoSupport.createRandomBytes(size: kCCKeySizeAES256) return FileHeader(nonce: nonce, contentKey: contentKey) } @@ -301,7 +301,7 @@ public class Cryptor { } func encryptSingleChunk(_ chunk: [UInt8], chunkNumber: UInt64, headerNonce: [UInt8], fileKey: [UInt8]) throws -> [UInt8] { - let chunkNonce = try cryptoSupport.createRandomBytes(size: kCCBlockSizeAES128) + let chunkNonce = try cryptoSupport.createRandomBytes(size: contentCryptor.nonceLen) return try contentCryptor.encrypt(chunk, key: fileKey, nonce: chunkNonce, ad: headerNonce, chunkNumber.bigEndian.byteArray()) } From a20478e698065f2521559db45840210d62df722d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 20 May 2022 11:30:36 +0200 Subject: [PATCH 06/13] Add GCM ContentCryptor --- .../project.pbxproj | 4 + .../CryptomatorCryptoLib/ContentCryptor.swift | 26 +++++++ .../GcmCryptorTests.swift | 75 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift diff --git a/CryptomatorCryptoLib.xcodeproj/project.pbxproj b/CryptomatorCryptoLib.xcodeproj/project.pbxproj index 4c8a71e..77817eb 100644 --- a/CryptomatorCryptoLib.xcodeproj/project.pbxproj +++ b/CryptomatorCryptoLib.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 9EB822C1248AF82200879838 /* AesCtr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB822C0248AF82200879838 /* AesCtr.swift */; }; 9EB822C3248AF9C500879838 /* AesCtrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB822C2248AF9C500879838 /* AesCtrTests.swift */; }; 9EBEC947283782E6002210DE /* CtrCryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEC946283782E6002210DE /* CtrCryptorTests.swift */; }; + 9EBEC94928378308002210DE /* GcmCryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEC94828378308002210DE /* GcmCryptorTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -99,6 +100,7 @@ 9EB822C0248AF82200879838 /* AesCtr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtr.swift; sourceTree = ""; }; 9EB822C2248AF9C500879838 /* AesCtrTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtrTests.swift; sourceTree = ""; }; 9EBEC946283782E6002210DE /* CtrCryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CtrCryptorTests.swift; sourceTree = ""; }; + 9EBEC94828378308002210DE /* GcmCryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GcmCryptorTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -174,6 +176,7 @@ 9E44EEA724599C7800A37B01 /* AesSivTests.swift */, 9E35C4EA24576A3D0006E50C /* CryptorTests.swift */, 9EBEC946283782E6002210DE /* CtrCryptorTests.swift */, + 9EBEC94828378308002210DE /* GcmCryptorTests.swift */, 74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */, 74A5B57525A869DD002D10F7 /* MasterkeyFileTests.swift */, 9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */, @@ -451,6 +454,7 @@ 74A5B57625A869DD002D10F7 /* MasterkeyFileTests.swift in Sources */, 9E9BB81624558DFF00F9FF51 /* MasterkeyTests.swift in Sources */, 9E35C4EB24576A3D0006E50C /* CryptorTests.swift in Sources */, + 9EBEC94928378308002210DE /* GcmCryptorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/CryptomatorCryptoLib/ContentCryptor.swift b/Sources/CryptomatorCryptoLib/ContentCryptor.swift index ffc1014..1f7c311 100644 --- a/Sources/CryptomatorCryptoLib/ContentCryptor.swift +++ b/Sources/CryptomatorCryptoLib/ContentCryptor.swift @@ -7,6 +7,7 @@ // import CommonCrypto +import CryptoKit import Foundation protocol ContentCryptor { @@ -35,6 +36,31 @@ protocol ContentCryptor { func decrypt(_ chunk: [UInt8], key: [UInt8], ad: [UInt8]...) throws -> [UInt8] } +class GcmContentCryptor: ContentCryptor { + let nonceLen = 12 // 96 bit + let tagLen = 16 // 128 bit + + func encrypt(_ chunk: [UInt8], key keyBytes: [UInt8], nonce nonceBytes: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + let concatAd = ad.reduce([], +) + let key = SymmetricKey(data: keyBytes) + let nonce = try AES.GCM.Nonce(data: nonceBytes) + let encrypted = try AES.GCM.seal(chunk, using: key, nonce: nonce, authenticating: concatAd) + + return [UInt8](encrypted.nonce + encrypted.ciphertext + encrypted.tag) + } + + func decrypt(_ chunk: [UInt8], key keyBytes: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + assert(chunk.count >= nonceLen + tagLen, "ciphertext chunk must at least contain nonce + tag") + + let concatAd = ad.reduce([], +) + let key = SymmetricKey(data: keyBytes) + let encrypted = try AES.GCM.SealedBox(combined: chunk) + let decrypted = try AES.GCM.open(encrypted, using: key, authenticating: concatAd) + + return [UInt8](decrypted) + } +} + class CtrThenHmacContentCryptor: ContentCryptor { let nonceLen = kCCBlockSizeAES128 let tagLen = Int(CC_SHA256_DIGEST_LENGTH) diff --git a/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift b/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift new file mode 100644 index 0000000..5c69033 --- /dev/null +++ b/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift @@ -0,0 +1,75 @@ +// +// CryptorTests.swift +// CryptomatorCryptoLibTests +// +// Created by Sebastian Stenzel on 27.04.20. +// Copyright © 2020 Skymatic GmbH. All rights reserved. +// + +import XCTest +@testable import CryptomatorCryptoLib + +class GcmCryptorTests: CryptorTests { + override class var defaultTestSuite: XCTestSuite { + return XCTestSuite(forTestCaseClass: GcmCryptorTests.self) + } + + override func setUpWithError() throws { + let aesKey = [UInt8](repeating: 0x55, count: 32) + let macKey = [UInt8](repeating: 0x77, count: 32) + let masterkey = Masterkey.createFromRaw(aesMasterKey: aesKey, macMasterKey: macKey) + let cryptoSupport = CryptoSupportMock() + let contentCryptor = GcmContentCryptor() + + try super.setUpWithError(masterkey: masterkey, cryptoSupport: cryptoSupport, contentCryptor: contentCryptor) + } + + func testCreateHeader() throws { + let header = try cryptor.createHeader() + XCTAssertEqual([UInt8](repeating: 0xF0, count: 12), header.nonce) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), header.contentKey) + } + + func testEncryptHeader() throws { + let header = try cryptor.createHeader() + let encrypted = try cryptor.encryptHeader(header) + + // echo -n "///////////w8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8A==" | base64 --decode \ + // | openssl enc -aes-256-gcm -K 5555555555555555555555555555555555555555555555555555555555555555 -iv F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0 -a + let expected: [UInt8] = [ + // nonce + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, + // ciphertext + 0x1C, 0x87, 0x19, 0xF0, 0x31, 0x22, 0x86, 0x8F, + 0xDB, 0x9D, 0x97, 0x03, 0xA0, 0x86, 0x08, 0xD5, + 0x88, 0x58, 0x96, 0xC2, 0xE6, 0x60, 0x4B, 0xB9, + 0xEA, 0x64, 0x31, 0xD4, 0xA0, 0x5D, 0x47, 0x6F, + 0xE4, 0x1F, 0x32, 0x31, 0xF2, 0xC0, 0x61, 0x1F, + // tag + 0x6D, 0x42, 0x98, 0x82, 0x43, 0xF2, 0x1F, 0x43, + 0xF6, 0x44, 0xFD, 0x6D, 0xF7, 0xA9, 0x3F, 0x0B + ] + XCTAssertEqual(expected, encrypted) + } + + func testDecryptHeader() throws { + let ciphertext: [UInt8] = [ + // nonce + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, + // ciphertext + 0x1C, 0x87, 0x19, 0xF0, 0x31, 0x22, 0x86, 0x8F, + 0xDB, 0x9D, 0x97, 0x03, 0xA0, 0x86, 0x08, 0xD5, + 0x88, 0x58, 0x96, 0xC2, 0xE6, 0x60, 0x4B, 0xB9, + 0xEA, 0x64, 0x31, 0xD4, 0xA0, 0x5D, 0x47, 0x6F, + 0xE4, 0x1F, 0x32, 0x31, 0xF2, 0xC0, 0x61, 0x1F, + // tag + 0x6D, 0x42, 0x98, 0x82, 0x43, 0xF2, 0x1F, 0x43, + 0xF6, 0x44, 0xFD, 0x6D, 0xF7, 0xA9, 0x3F, 0x0B + ] + let decrypted = try cryptor.decryptHeader(ciphertext) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 12), decrypted.nonce) + XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), decrypted.contentKey) + } +} From 5c5b99df60c7674b1364525a1487d3516d661d9a Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 20 May 2022 12:16:29 +0200 Subject: [PATCH 07/13] Updated deployment targets and README [ci skip] --- CryptomatorCryptoLib.xcodeproj/project.pbxproj | 6 ++---- README.md | 9 +++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CryptomatorCryptoLib.xcodeproj/project.pbxproj b/CryptomatorCryptoLib.xcodeproj/project.pbxproj index 77817eb..30a058f 100644 --- a/CryptomatorCryptoLib.xcodeproj/project.pbxproj +++ b/CryptomatorCryptoLib.xcodeproj/project.pbxproj @@ -537,7 +537,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -597,7 +597,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200 -Xfrontend -warn-long-function-bodies=200"; @@ -620,7 +620,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Sources/CryptomatorCryptoLib/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -645,7 +644,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Sources/CryptomatorCryptoLib/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/README.md b/README.md index b2866d7..5b8ce6b 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ For more information on the Cryptomator encryption scheme, visit the security ar ## Requirements -- iOS 9.0 or higher -- macOS 10.12 or higher +- iOS 13.0 or higher +- macOS 10.15 or higher ## Installation @@ -178,8 +178,9 @@ Please read our [contribution guide](.github/CONTRIBUTING.md), if you would like In general, the following preference is used to choose the implementation of cryptographic primitives: -1. Apple Swift Crypto (HMAC) -2. Apple CommonCrypto (AES-CTR, RFC 3394 Key Derivation) +1. Apple CryptoKit (AES-GCM) +2. Apple Swift Crypto (HMAC) +3. Apple CommonCrypto (AES-CTR, RFC 3394 Key Derivation) This project uses [SwiftFormat](https://github.com/nicklockwood/SwiftFormat) and [SwiftLint](https://github.com/realm/SwiftLint) to enforce code style and conventions. Install these tools if you haven't already. From 35e8a6946201aecc60b912a6d929b6668674f4d2 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 20 May 2022 14:38:04 +0200 Subject: [PATCH 08/13] Made cryptor scheme configurable during cryptor init --- README.md | 7 +++++-- Sources/CryptomatorCryptoLib/Cryptor.swift | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5b8ce6b..063c389 100644 --- a/README.md +++ b/README.md @@ -121,13 +121,16 @@ try MasterkeyFile.changePassphrase(masterkeyFileData: masterkeyFileData, oldPass #### Constructor -Create a cryptor by providing a masterkey. +Create a cryptor by providing a masterkey and a scheme (e.g., `.sivGcm`). ```swift let masterkey = ... -let cryptor = Cryptor(masterkey: masterkey) +let scheme = ... +let cryptor = Cryptor(masterkey: masterkey, scheme: scheme) ``` +Make sure that the data you're working with is compatible with the provided scheme. + #### Path Encryption and Decryption Encrypt the directory ID in order to determine the encrypted directory URL. diff --git a/Sources/CryptomatorCryptoLib/Cryptor.swift b/Sources/CryptomatorCryptoLib/Cryptor.swift index 17a76b0..2ff5396 100644 --- a/Sources/CryptomatorCryptoLib/Cryptor.swift +++ b/Sources/CryptomatorCryptoLib/Cryptor.swift @@ -49,6 +49,11 @@ public extension InputStream { } } +public enum CryptorScheme { + case sivCtrMac + case sivGcm +} + public enum FileNameEncoding: String { case base64url case base32 @@ -81,9 +86,15 @@ public class Cryptor { self.contentCryptor = contentCryptor } - public convenience init(masterkey: Masterkey) { + public convenience init(masterkey: Masterkey, scheme: CryptorScheme) { let cryptoSupport = CryptoSupport() - let contentCryptor = CtrThenHmacContentCryptor(macKey: masterkey.macMasterKey, cryptoSupport: cryptoSupport) + let contentCryptor: ContentCryptor + switch scheme { + case .sivCtrMac: + contentCryptor = CtrThenHmacContentCryptor(macKey: masterkey.macMasterKey, cryptoSupport: cryptoSupport) + case .sivGcm: + contentCryptor = GcmContentCryptor() + } self.init(masterkey: masterkey, cryptoSupport: cryptoSupport, contentCryptor: contentCryptor) } From 4811a24a996066341f451bf9dc0420701a170f81 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 20 May 2022 14:38:44 +0200 Subject: [PATCH 09/13] Cleaned up project --- CryptomatorCryptoLib.xcodeproj/project.pbxproj | 2 +- Sources/CryptomatorCryptoLib/Cryptor.swift | 4 ++-- Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift | 2 +- Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CryptomatorCryptoLib.xcodeproj/project.pbxproj b/CryptomatorCryptoLib.xcodeproj/project.pbxproj index 30a058f..1c522af 100644 --- a/CryptomatorCryptoLib.xcodeproj/project.pbxproj +++ b/CryptomatorCryptoLib.xcodeproj/project.pbxproj @@ -175,9 +175,9 @@ 9EB822C2248AF9C500879838 /* AesCtrTests.swift */, 9E44EEA724599C7800A37B01 /* AesSivTests.swift */, 9E35C4EA24576A3D0006E50C /* CryptorTests.swift */, + 74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */, 9EBEC946283782E6002210DE /* CtrCryptorTests.swift */, 9EBEC94828378308002210DE /* GcmCryptorTests.swift */, - 74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */, 74A5B57525A869DD002D10F7 /* MasterkeyFileTests.swift */, 9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */, ); diff --git a/Sources/CryptomatorCryptoLib/Cryptor.swift b/Sources/CryptomatorCryptoLib/Cryptor.swift index 2ff5396..1cccf14 100644 --- a/Sources/CryptomatorCryptoLib/Cryptor.swift +++ b/Sources/CryptomatorCryptoLib/Cryptor.swift @@ -71,8 +71,8 @@ public class Cryptor { return contentCryptor.nonceLen + fileHeaderPayloadSize + contentCryptor.tagLen } - public let cleartextChunkSize = 32 * 1024 - public var ciphertextChunkSize: Int { + private let cleartextChunkSize = 32 * 1024 + var ciphertextChunkSize: Int { return contentCryptor.nonceLen + cleartextChunkSize + contentCryptor.tagLen } diff --git a/Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift b/Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift index df25577..b8c6ff4 100644 --- a/Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift +++ b/Tests/CryptomatorCryptoLibTests/CtrCryptorTests.swift @@ -1,5 +1,5 @@ // -// CryptorTests.swift +// CtrCryptorTests.swift // CryptomatorCryptoLibTests // // Created by Sebastian Stenzel on 27.04.20. diff --git a/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift b/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift index 5c69033..76b5372 100644 --- a/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift +++ b/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift @@ -1,9 +1,9 @@ // -// CryptorTests.swift +// GcmCryptorTests.swift // CryptomatorCryptoLibTests // -// Created by Sebastian Stenzel on 27.04.20. -// Copyright © 2020 Skymatic GmbH. All rights reserved. +// Created by Sebastian Stenzel on 20.05.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. // import XCTest From 79df1114218f84ae1ed117e98fc1cca7dae37288 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 20 May 2022 14:58:15 +0200 Subject: [PATCH 10/13] Made some adjustments to CryptorScheme so that it's usable in cloud-access-swift --- Sources/CryptomatorCryptoLib/Cryptor.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/CryptomatorCryptoLib/Cryptor.swift b/Sources/CryptomatorCryptoLib/Cryptor.swift index 1cccf14..240d513 100644 --- a/Sources/CryptomatorCryptoLib/Cryptor.swift +++ b/Sources/CryptomatorCryptoLib/Cryptor.swift @@ -49,9 +49,9 @@ public extension InputStream { } } -public enum CryptorScheme { - case sivCtrMac - case sivGcm +public enum CryptorScheme: String, Codable { + case sivCtrMac = "SIV_CTRMAC" + case sivGcm = "SIV_GCM" } public enum FileNameEncoding: String { From 0976141d53548bd1c07cee5bc08159852c9ff654 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 20 May 2022 15:09:53 +0200 Subject: [PATCH 11/13] Adjusted access control of cleartextChunkSize --- Sources/CryptomatorCryptoLib/Cryptor.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CryptomatorCryptoLib/Cryptor.swift b/Sources/CryptomatorCryptoLib/Cryptor.swift index 240d513..786c2df 100644 --- a/Sources/CryptomatorCryptoLib/Cryptor.swift +++ b/Sources/CryptomatorCryptoLib/Cryptor.swift @@ -71,7 +71,7 @@ public class Cryptor { return contentCryptor.nonceLen + fileHeaderPayloadSize + contentCryptor.tagLen } - private let cleartextChunkSize = 32 * 1024 + let cleartextChunkSize = 32 * 1024 var ciphertextChunkSize: Int { return contentCryptor.nonceLen + cleartextChunkSize + contentCryptor.tagLen } From e7f83ae0f530a6265474d85af5339e9b051ca71e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 24 May 2022 09:49:20 +0200 Subject: [PATCH 12/13] allow different AD order for GCM and CTR+HMAC --- .../CryptomatorCryptoLib/ContentCryptor.swift | 59 +++++++++++++++---- Sources/CryptomatorCryptoLib/Cryptor.swift | 8 +-- .../GcmCryptorTests.swift | 28 +++++++++ 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/Sources/CryptomatorCryptoLib/ContentCryptor.swift b/Sources/CryptomatorCryptoLib/ContentCryptor.swift index 1f7c311..bb4545f 100644 --- a/Sources/CryptomatorCryptoLib/ContentCryptor.swift +++ b/Sources/CryptomatorCryptoLib/ContentCryptor.swift @@ -23,7 +23,7 @@ protocol ContentCryptor { - Parameter ad: Associated data, which needs to be authenticated during decryption. - Returns: Nonce/IV + ciphertext + MAC/tag, as a concatenated byte array. */ - func encrypt(_ chunk: [UInt8], key: [UInt8], nonce: [UInt8], ad: [UInt8]...) throws -> [UInt8] + func encrypt(_ chunk: [UInt8], key: [UInt8], nonce: [UInt8], ad: [UInt8]) throws -> [UInt8] /** Decrypts one single chunk of encrypted data. @@ -33,29 +33,60 @@ protocol ContentCryptor { - Parameter ad: Associated data, which needs to be authenticated during decryption. - Returns: The original cleartext. */ - func decrypt(_ chunk: [UInt8], key: [UInt8], ad: [UInt8]...) throws -> [UInt8] + func decrypt(_ chunk: [UInt8], key: [UInt8], ad: [UInt8]) throws -> [UInt8] + + /** + Constructs the associated data which will be authenticated during encryption/decryption of a single chunk + + - Parameter chunkNumber: The index of the chunk (starting at 0), preventing swapping of chunks + - Parameter headerNonce: The nonce used in the file header, binding the chunk to this particular file. + - Returns: The combined associated data. + */ + func ad(chunkNumber: UInt64, headerNonce: [UInt8]) -> [UInt8] +} + +extension ContentCryptor { + func encryptHeader(_ header: [UInt8], key: [UInt8], nonce: [UInt8]) throws -> [UInt8] { + return try encrypt(header, key: key, nonce: nonce, ad: []) + } + + func decryptHeader(_ header: [UInt8], key: [UInt8]) throws -> [UInt8] { + return try decrypt(header, key: key, ad: []) + } + + func encryptChunk(_ chunk: [UInt8], chunkNumber: UInt64, chunkNonce: [UInt8], fileKey: [UInt8], headerNonce: [UInt8]) throws -> [UInt8] { + let ad = ad(chunkNumber: chunkNumber, headerNonce: headerNonce) + return try encrypt(chunk, key: fileKey, nonce: chunkNonce, ad: ad) + } + + func decryptChunk(_ chunk: [UInt8], chunkNumber: UInt64, fileKey: [UInt8], headerNonce: [UInt8]) throws -> [UInt8] { + let ad = ad(chunkNumber: chunkNumber, headerNonce: headerNonce) + return try decrypt(chunk, key: fileKey, ad: ad) + } } class GcmContentCryptor: ContentCryptor { let nonceLen = 12 // 96 bit let tagLen = 16 // 128 bit - func encrypt(_ chunk: [UInt8], key keyBytes: [UInt8], nonce nonceBytes: [UInt8], ad: [UInt8]...) throws -> [UInt8] { - let concatAd = ad.reduce([], +) + func ad(chunkNumber: UInt64, headerNonce: [UInt8]) -> [UInt8] { + return chunkNumber.bigEndian.byteArray() + headerNonce + } + + func encrypt(_ chunk: [UInt8], key keyBytes: [UInt8], nonce nonceBytes: [UInt8], ad: [UInt8]) throws -> [UInt8] { let key = SymmetricKey(data: keyBytes) let nonce = try AES.GCM.Nonce(data: nonceBytes) - let encrypted = try AES.GCM.seal(chunk, using: key, nonce: nonce, authenticating: concatAd) + let encrypted = try AES.GCM.seal(chunk, using: key, nonce: nonce, authenticating: ad) return [UInt8](encrypted.nonce + encrypted.ciphertext + encrypted.tag) } - func decrypt(_ chunk: [UInt8], key keyBytes: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + func decrypt(_ chunk: [UInt8], key keyBytes: [UInt8], ad: [UInt8]) throws -> [UInt8] { assert(chunk.count >= nonceLen + tagLen, "ciphertext chunk must at least contain nonce + tag") - let concatAd = ad.reduce([], +) let key = SymmetricKey(data: keyBytes) let encrypted = try AES.GCM.SealedBox(combined: chunk) - let decrypted = try AES.GCM.open(encrypted, using: key, authenticating: concatAd) + let decrypted = try AES.GCM.open(encrypted, using: key, authenticating: ad) return [UInt8](decrypted) } @@ -73,13 +104,17 @@ class CtrThenHmacContentCryptor: ContentCryptor { self.cryptoSupport = cryptoSupport } - func encrypt(_ chunk: [UInt8], key: [UInt8], nonce: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + func ad(chunkNumber: UInt64, headerNonce: [UInt8]) -> [UInt8] { + return headerNonce + chunkNumber.bigEndian.byteArray() + } + + func encrypt(_ chunk: [UInt8], key: [UInt8], nonce: [UInt8], ad: [UInt8]) throws -> [UInt8] { let ciphertext = try AesCtr.compute(key: key, iv: nonce, data: chunk) let mac = computeHmac(ciphertext, nonce: nonce, ad: ad) return nonce + ciphertext + mac } - func decrypt(_ chunk: [UInt8], key: [UInt8], ad: [UInt8]...) throws -> [UInt8] { + func decrypt(_ chunk: [UInt8], key: [UInt8], ad: [UInt8]) throws -> [UInt8] { assert(chunk.count >= nonceLen + tagLen, "ciphertext chunk must at least contain nonce + tag") // decompose chunk: @@ -98,8 +133,8 @@ class CtrThenHmacContentCryptor: ContentCryptor { return try AesCtr.compute(key: key, iv: chunkNonce, data: ciphertext) } - private func computeHmac(_ ciphertext: [UInt8], nonce: [UInt8], ad: [[UInt8]]) -> [UInt8] { - let data = ad.reduce([UInt8](), +) + nonce + ciphertext + private func computeHmac(_ ciphertext: [UInt8], nonce: [UInt8], ad: [UInt8]) -> [UInt8] { + let data = ad + nonce + ciphertext var mac = [UInt8](repeating: 0x00, count: tagLen) CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), macKey, macKey.count, data, data.count, &mac) return mac diff --git a/Sources/CryptomatorCryptoLib/Cryptor.swift b/Sources/CryptomatorCryptoLib/Cryptor.swift index 786c2df..732813f 100644 --- a/Sources/CryptomatorCryptoLib/Cryptor.swift +++ b/Sources/CryptomatorCryptoLib/Cryptor.swift @@ -172,12 +172,12 @@ public class Cryptor { func encryptHeader(_ header: FileHeader) throws -> [UInt8] { let cleartext = [UInt8](repeating: 0xFF, count: fileHeaderLegacyPayloadSize) + header.contentKey - return try contentCryptor.encrypt(cleartext, key: masterkey.aesMasterKey, nonce: header.nonce) + return try contentCryptor.encryptHeader(cleartext, key: masterkey.aesMasterKey, nonce: header.nonce) } func decryptHeader(_ header: [UInt8]) throws -> FileHeader { let nonce = [UInt8](header[0 ..< contentCryptor.nonceLen]) - let cleartext = try contentCryptor.decrypt(header, key: masterkey.aesMasterKey) + let cleartext = try contentCryptor.decryptHeader(header, key: masterkey.aesMasterKey) let contentKey = [UInt8](cleartext[fileHeaderLegacyPayloadSize...]) return FileHeader(nonce: nonce, contentKey: contentKey) } @@ -313,11 +313,11 @@ public class Cryptor { func encryptSingleChunk(_ chunk: [UInt8], chunkNumber: UInt64, headerNonce: [UInt8], fileKey: [UInt8]) throws -> [UInt8] { let chunkNonce = try cryptoSupport.createRandomBytes(size: contentCryptor.nonceLen) - return try contentCryptor.encrypt(chunk, key: fileKey, nonce: chunkNonce, ad: headerNonce, chunkNumber.bigEndian.byteArray()) + return try contentCryptor.encryptChunk(chunk, chunkNumber: chunkNumber, chunkNonce: chunkNonce, fileKey: fileKey, headerNonce: headerNonce) } func decryptSingleChunk(_ chunk: [UInt8], chunkNumber: UInt64, headerNonce: [UInt8], fileKey: [UInt8]) throws -> [UInt8] { - return try contentCryptor.decrypt(chunk, key: fileKey, ad: headerNonce, chunkNumber.bigEndian.byteArray()) + return try contentCryptor.decryptChunk(chunk, chunkNumber: chunkNumber, fileKey: fileKey, headerNonce: headerNonce) } // MARK: - File Size Calculation diff --git a/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift b/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift index 76b5372..683569b 100644 --- a/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift +++ b/Tests/CryptomatorCryptoLibTests/GcmCryptorTests.swift @@ -72,4 +72,32 @@ class GcmCryptorTests: CryptorTests { XCTAssertEqual([UInt8](repeating: 0xF0, count: 12), decrypted.nonce) XCTAssertEqual([UInt8](repeating: 0xF0, count: 32), decrypted.contentKey) } + + func testDecryptSingleChunk() throws { + let headerNonce: [UInt8] = [ + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55 + ] + let fileKey: [UInt8] = [ + 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77 + ] + let ciphertext: [UInt8] = [ + // nonce + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, + // payload + 0x52, 0xC5, 0xEE, 0x8D, 0x7F, 0xB4, 0x4E, 0xF2, + 0x8A, 0xEC, 0x55, + // tag + 0x3C, 0xC7, 0x02, 0x65, 0xE5, 0x35, 0x2C, 0xB5, + 0xA0, 0x9A, 0x43, 0xAE, 0x0F, 0x5C, 0xA1, 0x5D + ] + + let cleartext = try cryptor.decryptSingleChunk(ciphertext, chunkNumber: 0, headerNonce: headerNonce, fileKey: fileKey) + + XCTAssertEqual([UInt8]("hello world".utf8), cleartext) + } } From 820dea696d900d52ab4c5d4d4237f86a507d6889 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 24 May 2022 15:12:49 +0200 Subject: [PATCH 13/13] Preparing 1.1.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 063c389..d733576 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ For more information on the Cryptomator encryption scheme, visit the security ar You can use [Swift Package Manager](https://swift.org/package-manager/ "Swift Package Manager"). ```swift -.package(url: "https://github.com/cryptomator/cryptolib-swift.git", .upToNextMinor(from: "1.0.0")) +.package(url: "https://github.com/cryptomator/cryptolib-swift.git", .upToNextMinor(from: "1.1.0")) ``` ## Usage