From 1055df471814958cfc6c2070e2e4ea432f13c55d Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Wed, 7 Jul 2021 17:46:24 +0200 Subject: [PATCH] Split up unlock method so that the KEK can be retrieved/used --- README.md | 12 +++++ .../CryptomatorCryptoLib/MasterkeyFile.swift | 23 ++++++++- .../MasterkeyFileTests.swift | 50 ++++++++++++++++++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 89ca03a..b15c518 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,18 @@ let pepper = ... // optional let masterkey = try masterkeyFile.unlock(passphrase: passphrase, pepper: pepper) ``` +The unlock process can also be performed in two steps: + +```swift +let masterkeyFile = ... +let passphrase = ... +let pepper = ... // optional +let kek = try masterkeyFile.deriveKey(passphrase: passphrase, pepper: pepper) +let masterkey = try masterkeyFile.unlock(kek: kek) +``` + +This is useful if you'd like to derive the key in an extra step since the function is memory-intensive (using scrypt). The result can then be used elsewhere, e.g. in a memory-restricted process. + #### Lock For persisting the masterkey, use this method to export its encrypted/wrapped masterkey and other metadata as JSON data. diff --git a/Sources/CryptomatorCryptoLib/MasterkeyFile.swift b/Sources/CryptomatorCryptoLib/MasterkeyFile.swift index 4a20e78..19aaa31 100644 --- a/Sources/CryptomatorCryptoLib/MasterkeyFile.swift +++ b/Sources/CryptomatorCryptoLib/MasterkeyFile.swift @@ -75,7 +75,18 @@ public class MasterkeyFile { - Returns: A masterkey with the unwrapped keys. */ public func unlock(passphrase: String, pepper: [UInt8] = [UInt8]()) throws -> Masterkey { - // derive keys: + let kek = try deriveKey(passphrase: passphrase, pepper: pepper) + return try unlock(kek: kek) + } + + /** + Derives a KEK from the given passphrase and the params from this masterkey file using scrypt. + + - Parameter passphrase: The passphrase used during key derivation. + - Parameter pepper: An optional application-specific pepper added to the scrypt's salt. Defaults to empty byte array. + - Returns: A 256-bit key derived from passphrase using scrypt. + */ + public func deriveKey(passphrase: String, pepper: [UInt8] = [UInt8]()) throws -> [UInt8] { let pw = [UInt8](passphrase.precomposedStringWithCanonicalMapping.utf8) let salt = [UInt8](Data(base64Encoded: content.scryptSalt)!) var kek = [UInt8](repeating: 0x00, count: kCCKeySizeAES256) @@ -83,6 +94,16 @@ public class MasterkeyFile { guard scryptResult == 0 else { throw MasterkeyFileError.keyDerivationFailed } + return kek + } + + /** + Unwraps the stored encryption and MAC keys with the given KEK. + + - Parameter kek: The KEK for unwrapping the keys from this masterkey file. + - Returns: A masterkey with the unwrapped keys. + */ + public func unlock(kek: [UInt8]) throws -> Masterkey { guard let wrappedMasterKey = Data(base64Encoded: content.primaryMasterKey) else { throw MasterkeyFileError.malformedMasterkeyFile("invalid base64 data in primaryMasterKey") } diff --git a/Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift b/Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift index 2ec13e4..0057323 100644 --- a/Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift +++ b/Tests/CryptomatorCryptoLibTests/MasterkeyFileTests.swift @@ -33,7 +33,7 @@ class MasterkeyFileTests: XCTestCase { XCTAssertEqual("cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=", masterkeyFile.content.versionMac) } - func testUnlock() throws { + func testUnlockWithPassphrase() throws { let expectedKey = [UInt8](repeating: 0x00, count: 32) let data = """ { @@ -142,6 +142,54 @@ class MasterkeyFileTests: XCTestCase { } } + func testDeriveKey() throws { + let expectedKey: [UInt8] = [ + 0x8C, 0xF4, 0xA0, 0x4E, 0xC8, 0x45, 0xF4, 0x28, + 0xB2, 0xF9, 0xF9, 0xE1, 0xD9, 0xDF, 0x08, 0xD2, + 0x62, 0x11, 0xD9, 0xAF, 0xE2, 0xF5, 0x5F, 0xDE, + 0xDF, 0xCB, 0xB5, 0xE7, 0x5A, 0xEF, 0x34, 0xF9 + ] + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + let kek = try masterkeyFile.deriveKey(passphrase: "asd", pepper: [UInt8]()) + XCTAssertEqual(expectedKey, kek) + } + + func testUnlockWithKEK() throws { + let expectedKey = [UInt8](repeating: 0x00, count: 32) + let data = """ + { + "version": 7, + "scryptSalt": "AAAAAAAAAAA=", + "scryptCostParam": 2, + "scryptBlockSize": 8, + "primaryMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "hmacMasterKey": "mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==", + "versionMac": "cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=" + } + """.data(using: .utf8)! + let masterkeyFile = try MasterkeyFile.withContentFromData(data: data) + let kek: [UInt8] = [ + 0x8C, 0xF4, 0xA0, 0x4E, 0xC8, 0x45, 0xF4, 0x28, + 0xB2, 0xF9, 0xF9, 0xE1, 0xD9, 0xDF, 0x08, 0xD2, + 0x62, 0x11, 0xD9, 0xAF, 0xE2, 0xF5, 0x5F, 0xDE, + 0xDF, 0xCB, 0xB5, 0xE7, 0x5A, 0xEF, 0x34, 0xF9 + ] + let masterkey = try masterkeyFile.unlock(kek: kek) + XCTAssertEqual(expectedKey, masterkey.aesMasterKey) + XCTAssertEqual(expectedKey, masterkey.macMasterKey) + } + func testLock() throws { let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) let content = try MasterkeyFile.lock(masterkey: masterkey, vaultVersion: 7, passphrase: "asd", pepper: [UInt8](), scryptCostParam: 2, cryptoSupport: CryptoSupportMock())