Skip to content

Commit

Permalink
Merge branch 'release/1.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
tobihagemann committed May 24, 2022
2 parents ea36366 + 820dea6 commit 6e5dbea
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 106 deletions.
16 changes: 12 additions & 4 deletions CryptomatorCryptoLib.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
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 */; };
9EBEC94928378308002210DE /* GcmCryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEC94828378308002210DE /* GcmCryptorTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -97,6 +99,8 @@
9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterkeyTests.swift; sourceTree = "<group>"; };
9EB822C0248AF82200879838 /* AesCtr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtr.swift; sourceTree = "<group>"; };
9EB822C2248AF9C500879838 /* AesCtrTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesCtrTests.swift; sourceTree = "<group>"; };
9EBEC946283782E6002210DE /* CtrCryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CtrCryptorTests.swift; sourceTree = "<group>"; };
9EBEC94828378308002210DE /* GcmCryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GcmCryptorTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -172,6 +176,8 @@
9E44EEA724599C7800A37B01 /* AesSivTests.swift */,
9E35C4EA24576A3D0006E50C /* CryptorTests.swift */,
74A5B57D25A86A69002D10F7 /* CryptoSupportMock.swift */,
9EBEC946283782E6002210DE /* CtrCryptorTests.swift */,
9EBEC94828378308002210DE /* GcmCryptorTests.swift */,
74A5B57525A869DD002D10F7 /* MasterkeyFileTests.swift */,
9E9BB81524558DFF00F9FF51 /* MasterkeyTests.swift */,
);
Expand Down Expand Up @@ -443,10 +449,12 @@
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 */,
9E35C4EB24576A3D0006E50C /* CryptorTests.swift in Sources */,
9EBEC94928378308002210DE /* GcmCryptorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -528,8 +536,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -588,8 +596,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MACOSX_DEPLOYMENT_TARGET = 10.12;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
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";
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -178,8 +181,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.

Expand All @@ -202,4 +206,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/).
4 changes: 2 additions & 2 deletions Scripts/process.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 67 additions & 6 deletions Sources/CryptomatorCryptoLib/ContentCryptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import CommonCrypto
import CryptoKit
import Foundation

protocol ContentCryptor {
Expand All @@ -22,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.
Expand All @@ -32,7 +33,63 @@ 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 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: ad)

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 key = SymmetricKey(data: keyBytes)
let encrypted = try AES.GCM.SealedBox(combined: chunk)
let decrypted = try AES.GCM.open(encrypted, using: key, authenticating: ad)

return [UInt8](decrypted)
}
}

class CtrThenHmacContentCryptor: ContentCryptor {
Expand All @@ -47,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:
Expand All @@ -72,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
Expand Down
31 changes: 21 additions & 10 deletions Sources/CryptomatorCryptoLib/Cryptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public extension InputStream {
}
}

public enum CryptorScheme: String, Codable {
case sivCtrMac = "SIV_CTRMAC"
case sivGcm = "SIV_GCM"
}

public enum FileNameEncoding: String {
case base64url
case base32
Expand All @@ -66,8 +71,8 @@ public class Cryptor {
return contentCryptor.nonceLen + fileHeaderPayloadSize + contentCryptor.tagLen
}

private let cleartextChunkSize = 32 * 1024
private var ciphertextChunkSize: Int {
let cleartextChunkSize = 32 * 1024
var ciphertextChunkSize: Int {
return contentCryptor.nonceLen + cleartextChunkSize + contentCryptor.tagLen
}

Expand All @@ -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)
}

Expand Down Expand Up @@ -154,19 +165,19 @@ 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)
}

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)
}
Expand Down Expand Up @@ -301,12 +312,12 @@ public class Cryptor {
}

func encryptSingleChunk(_ chunk: [UInt8], chunkNumber: UInt64, headerNonce: [UInt8], fileKey: [UInt8]) throws -> [UInt8] {
let chunkNonce = try cryptoSupport.createRandomBytes(size: kCCBlockSizeAES128)
return try contentCryptor.encrypt(chunk, key: fileKey, nonce: chunkNonce, ad: headerNonce, chunkNumber.bigEndian.byteArray())
let chunkNonce = try cryptoSupport.createRandomBytes(size: contentCryptor.nonceLen)
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
Expand Down
Loading

0 comments on commit 6e5dbea

Please sign in to comment.