Skip to content

Commit

Permalink
Support AES128-CTR with more MACs
Browse files Browse the repository at this point in the history
  • Loading branch information
Joannis committed Mar 15, 2023
1 parent ec938a6 commit bbe2e65
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 32 deletions.
16 changes: 8 additions & 8 deletions Package.resolved
Expand Up @@ -15,17 +15,17 @@
"repositoryURL": "https://github.com/apple/swift-atomics.git",
"state": {
"branch": null,
"revision": "919eb1d83e02121cdb434c7bfc1f0c66ef17febe",
"version": "1.0.2"
"revision": "ff3d2212b6b093db7f177d0855adbc4ef9c5f036",
"version": "1.0.3"
}
},
{
"package": "swift-collections",
"repositoryURL": "https://github.com/apple/swift-collections.git",
"state": {
"branch": null,
"revision": "f504716c27d2e5d4144fa4794b12129301d17729",
"version": "1.0.3"
"revision": "937e904258d22af6e447a0b72c0bc67583ef64a2",
"version": "1.0.4"
}
},
{
Expand All @@ -51,17 +51,17 @@
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "e855380cb5234e96b760d93e0bfdc403e381e928",
"version": "2.45.0"
"revision": "45167b8006448c79dda4b7bd604e07a034c15c49",
"version": "2.48.0"
}
},
{
"package": "swift-nio-ssh",
"repositoryURL": "https://github.com/Joannis/swift-nio-ssh.git",
"state": {
"branch": null,
"revision": "b6bd90eb55df00e07e2d62be2492d2a0690a9a30",
"version": "0.2.1"
"revision": "d5fc603de485eca5a1e657361e3a8452875d108b",
"version": "0.3.0"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Expand Up @@ -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"),
Expand Down
109 changes: 88 additions & 21 deletions Sources/Citadel/Algorithms/AES.swift
Expand Up @@ -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<EVP_CIPHER_CTX>
private var encryptionContext: UnsafeMutablePointer<EVP_CIPHER_CTX>
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
}

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

Expand Down Expand Up @@ -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<H: HashFunction>(_ 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.
Expand Down Expand Up @@ -195,7 +246,7 @@ public final class AES128CTR: NIOSSHTransportProtection {
}

func test(sequenceNumber: UInt32) -> Bool {
var hmac = Crypto.HMAC<Crypto.Insecure.SHA1>(key: keys.inboundMACKey)
var hmac = Crypto.HMAC<H>(key: keys.inboundMACKey)
withUnsafeBytes(of: sequenceNumber.bigEndian) { buffer in
hmac.update(data: buffer)
}
Expand Down Expand Up @@ -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<H: HashFunction>(
_ 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
Expand Down Expand Up @@ -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<Crypto.Insecure.SHA1>(key: keys.outboundMACKey)
var hmac = Crypto.HMAC<H>(key: keys.outboundMACKey)
withUnsafeBytes(of: sequenceNumber.bigEndian) { buffer in
hmac.update(data: buffer)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Citadel/BCrypt.swift
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Citadel/OpenSSHKey.swift
Expand Up @@ -259,7 +259,7 @@ enum OpenSSH {
throw KeyError.missingDecryptionKey
}

guard SHA512.didInit else {
guard _SHA512.didInit else {
fatalError("Internal library error")
}

Expand Down

0 comments on commit bbe2e65

Please sign in to comment.