diff --git a/Package.resolved b/Package.resolved index 50f2ab0..eea24ec 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/apple/swift-atomics.git", "state": { "branch": null, - "revision": "919eb1d83e02121cdb434c7bfc1f0c66ef17febe", - "version": "1.0.2" + "revision": "ff3d2212b6b093db7f177d0855adbc4ef9c5f036", + "version": "1.0.3" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/apple/swift-collections.git", "state": { "branch": null, - "revision": "f504716c27d2e5d4144fa4794b12129301d17729", - "version": "1.0.3" + "revision": "937e904258d22af6e447a0b72c0bc67583ef64a2", + "version": "1.0.4" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "e855380cb5234e96b760d93e0bfdc403e381e928", - "version": "2.45.0" + "revision": "45167b8006448c79dda4b7bd604e07a034c15c49", + "version": "2.48.0" } }, { @@ -60,8 +60,8 @@ "repositoryURL": "https://github.com/Joannis/swift-nio-ssh.git", "state": { "branch": null, - "revision": "b6bd90eb55df00e07e2d62be2492d2a0690a9a30", - "version": "0.2.1" + "revision": "d5fc603de485eca5a1e657361e3a8452875d108b", + "version": "0.3.0" } } ] diff --git a/Package.swift b/Package.swift index 911cd31..3b56fe5 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( ), ], dependencies: [ - .package(name: "swift-nio-ssh", url: "https://github.com/Joannis/swift-nio-ssh.git", from: "0.2.1"), + .package(name: "swift-nio-ssh", url: "https://github.com/Joannis/swift-nio-ssh.git", "0.3.0" ..< "0.4.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), .package(url: "https://github.com/attaswift/BigInt.git", from: "5.2.0"), .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "2.1.0"), diff --git a/Sources/Citadel/Algorithms/AES.swift b/Sources/Citadel/Algorithms/AES.swift index c179f91..fc974fb 100644 --- a/Sources/Citadel/Algorithms/AES.swift +++ b/Sources/Citadel/Algorithms/AES.swift @@ -20,43 +20,83 @@ enum CitadelError: Error { } public final class AES128CTR: NIOSSHTransportProtection { - public static let macName: String? = "hmac-sha1" + private enum Mac { + case sha1, sha256, sha512 + } + + public static let macNames = [ + "hmac-sha1", + "hmac-sha2-256", + "hmac-sha2-512" + ] public static let cipherBlockSize = 16 public static let cipherName = "aes128-ctr" + public var macBytes: Int { + keySizes.macKeySize + } - public static let keySizes = ExpectedKeySizes( - ivSize: 16, - encryptionKeySize: 16, // 128 bits - macKeySize: 20 // HAMC-SHA-1 - ) + public static func keySizes(forMac mac: String?) throws -> ExpectedKeySizes { + let macKeySize: Int + + switch mac { + case "hmac-sha1": + macKeySize = Insecure.SHA1.byteCount + case "hmac-sha2-256": + macKeySize = SHA256.byteCount + case "hmac-sha2-512": + macKeySize = SHA512.byteCount + default: + throw CitadelError.invalidMac + } + + return ExpectedKeySizes( + ivSize: 16, + encryptionKeySize: 16, // 128 bits + macKeySize: macKeySize + ) + } - public let macBytes = 20 // HAMC-SHA-1 private var keys: NIOSSHSessionKeys private var decryptionContext: UnsafeMutablePointer private var encryptionContext: UnsafeMutablePointer + private let mac: Mac + private let keySizes: ExpectedKeySizes - public init(initialKeys: NIOSSHSessionKeys) throws { + public init(initialKeys: NIOSSHSessionKeys, mac: String?) throws { + let keySizes = try Self.keySizes(forMac: mac) + guard - initialKeys.outboundEncryptionKey.bitCount == Self.keySizes.encryptionKeySize * 8, - initialKeys.inboundEncryptionKey.bitCount == Self.keySizes.encryptionKeySize * 8 + initialKeys.outboundEncryptionKey.bitCount == keySizes.encryptionKeySize * 8, + initialKeys.inboundEncryptionKey.bitCount == keySizes.encryptionKeySize * 8 else { throw CitadelError.invalidKeySize } + + switch mac { + case "hmac-sha1": + self.mac = .sha1 + case "hmac-sha2-256": + self.mac = .sha256 + case "hmac-sha2-512": + self.mac = .sha512 + default: + throw CitadelError.invalidMac + } self.keys = initialKeys - + self.keySizes = keySizes self.encryptionContext = CCryptoBoringSSL_EVP_CIPHER_CTX_new() self.decryptionContext = CCryptoBoringSSL_EVP_CIPHER_CTX_new() let outboundEncryptionKey = initialKeys.outboundEncryptionKey.withUnsafeBytes { buffer -> [UInt8] in let outboundEncryptionKey = Array(buffer.bindMemory(to: UInt8.self)) - assert(outboundEncryptionKey.count == Self.keySizes.encryptionKeySize) + assert(outboundEncryptionKey.count == keySizes.encryptionKeySize) return outboundEncryptionKey } let inboundEncryptionKey = initialKeys.inboundEncryptionKey.withUnsafeBytes { buffer -> [UInt8] in let inboundEncryptionKey = Array(buffer.bindMemory(to: UInt8.self)) - assert(inboundEncryptionKey.count == Self.keySizes.encryptionKeySize) + assert(inboundEncryptionKey.count == keySizes.encryptionKeySize) return inboundEncryptionKey } @@ -83,8 +123,8 @@ public final class AES128CTR: NIOSSHTransportProtection { public func updateKeys(_ newKeys: NIOSSHSessionKeys) throws { guard - newKeys.outboundEncryptionKey.bitCount == Self.keySizes.encryptionKeySize * 8, - newKeys.inboundEncryptionKey.bitCount == Self.keySizes.encryptionKeySize * 8 + newKeys.outboundEncryptionKey.bitCount == keySizes.encryptionKeySize * 8, + newKeys.inboundEncryptionKey.bitCount == keySizes.encryptionKeySize * 8 else { throw CitadelError.invalidKeySize } @@ -93,13 +133,13 @@ public final class AES128CTR: NIOSSHTransportProtection { let outboundEncryptionKey = newKeys.outboundEncryptionKey.withUnsafeBytes { buffer -> [UInt8] in let outboundEncryptionKey = Array(buffer.bindMemory(to: UInt8.self)) - assert(outboundEncryptionKey.count == Self.keySizes.encryptionKeySize) + assert(outboundEncryptionKey.count == keySizes.encryptionKeySize) return outboundEncryptionKey } let inboundEncryptionKey = newKeys.inboundEncryptionKey.withUnsafeBytes { buffer -> [UInt8] in let inboundEncryptionKey = Array(buffer.bindMemory(to: UInt8.self)) - assert(inboundEncryptionKey.count == Self.keySizes.encryptionKeySize) + assert(inboundEncryptionKey.count == keySizes.encryptionKeySize) return inboundEncryptionKey } @@ -151,12 +191,23 @@ public final class AES128CTR: NIOSSHTransportProtection { } public func decryptAndVerifyRemainingPacket(_ source: inout ByteBuffer, sequenceNumber: UInt32) throws -> ByteBuffer { + switch mac { + case .sha1: + return try _decryptAndVerifyRemainingPacket(&source, hash: Insecure.SHA1.self, sequenceNumber: sequenceNumber) + case .sha256: + return try _decryptAndVerifyRemainingPacket(&source, hash: SHA256.self, sequenceNumber: sequenceNumber) + case .sha512: + return try _decryptAndVerifyRemainingPacket(&source, hash: SHA512.self, sequenceNumber: sequenceNumber) + } + } + + internal func _decryptAndVerifyRemainingPacket(_ source: inout ByteBuffer, hash: H.Type, sequenceNumber: UInt32) throws -> ByteBuffer { // The first 4 bytes are the length. The last 16 are the tag. Everything else is ciphertext. We expect // that the ciphertext is a clean multiple of the block size, and to be non-zero. guard var plaintext = source.readBytes(length: 16), - let ciphertext = source.readBytes(length: source.readableBytes - macBytes), - let macHash = source.readBytes(length: macBytes), + let ciphertext = source.readBytes(length: source.readableBytes - keySizes.macKeySize), + let macHash = source.readBytes(length: keySizes.macKeySize), ciphertext.count % Self.cipherBlockSize == 0 else { // The only way this fails is if the payload doesn't match this encryption scheme. @@ -195,7 +246,7 @@ public final class AES128CTR: NIOSSHTransportProtection { } func test(sequenceNumber: UInt32) -> Bool { - var hmac = Crypto.HMAC(key: keys.inboundMACKey) + var hmac = Crypto.HMAC(key: keys.inboundMACKey) withUnsafeBytes(of: sequenceNumber.bigEndian) { buffer in hmac.update(data: buffer) } @@ -227,6 +278,22 @@ public final class AES128CTR: NIOSSHTransportProtection { _ packet: NIOSSHEncryptablePayload, to outboundBuffer: inout ByteBuffer, sequenceNumber: UInt32 + ) throws { + switch mac { + case .sha1: + try _encryptPacket(packet, to: &outboundBuffer, hashFunction: Insecure.SHA1.self, sequenceNumber: sequenceNumber) + case .sha256: + try _encryptPacket(packet, to: &outboundBuffer, hashFunction: SHA256.self, sequenceNumber: sequenceNumber) + case .sha512: + try _encryptPacket(packet, to: &outboundBuffer, hashFunction: SHA512.self, sequenceNumber: sequenceNumber) + } + } + + internal func _encryptPacket( + _ packet: NIOSSHEncryptablePayload, + to outboundBuffer: inout ByteBuffer, + hashFunction: H.Type, + sequenceNumber: UInt32 ) throws { // Keep track of where the length is going to be written. let packetLengthIndex = outboundBuffer.writerIndex @@ -280,7 +347,7 @@ public final class AES128CTR: NIOSSHTransportProtection { let plaintext = outboundBuffer.getBytes(at: packetLengthIndex, length: encryptedBufferSize)! assert(plaintext.count % Self.cipherBlockSize == 0) - var hmac = Crypto.HMAC(key: keys.outboundMACKey) + var hmac = Crypto.HMAC(key: keys.outboundMACKey) withUnsafeBytes(of: sequenceNumber.bigEndian) { buffer in hmac.update(data: buffer) } diff --git a/Sources/Citadel/BCrypt.swift b/Sources/Citadel/BCrypt.swift index c371c7a..4fc60ff 100644 --- a/Sources/Citadel/BCrypt.swift +++ b/Sources/Citadel/BCrypt.swift @@ -4,7 +4,7 @@ import Foundation import Crypto // Because we don't want to bundle our own SHA512 implementation with BCrypt, we're providing it to the C library -enum SHA512 { +enum _SHA512 { static let didInit: Bool = { citadel_set_crypto_hash_sha512 { output, input, inputLength in CCryptoBoringSSL_EVP_Digest(input, Int(inputLength), output, nil, CCryptoBoringSSL_EVP_sha512(), nil) diff --git a/Sources/Citadel/OpenSSHKey.swift b/Sources/Citadel/OpenSSHKey.swift index a98ea41..dbffc38 100644 --- a/Sources/Citadel/OpenSSHKey.swift +++ b/Sources/Citadel/OpenSSHKey.swift @@ -259,7 +259,7 @@ enum OpenSSH { throw KeyError.missingDecryptionKey } - guard SHA512.didInit else { + guard _SHA512.didInit else { fatalError("Internal library error") }