diff --git a/Package.resolved b/Package.resolved index fde8e45..1b00ec2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/apple/swift-collections.git", "state": { "branch": null, - "revision": "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", - "version": "1.1.0" + "revision": "671108c96644956dddcd89dd59c203dcdb36cec7", + "version": "1.1.4" } }, { @@ -60,8 +60,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "359c461e5561d22c6334828806cc25d759ca7aa6", - "version": "2.65.0" + "revision": "914081701062b11e3bb9e21accc379822621995e", + "version": "2.76.1" } }, { @@ -78,8 +78,8 @@ "repositoryURL": "https://github.com/apple/swift-system.git", "state": { "branch": null, - "revision": "025bcb1165deab2e20d4eaba79967ce73013f496", - "version": "1.2.1" + "revision": "c8a44d836fe7913603e246acab7c528c2e780168", + "version": "1.4.0" } } ] diff --git a/Sources/Citadel/Algorithms/DH-Helpers.swift b/Sources/Citadel/Algorithms/DH-Helpers.swift index 8b818f6..cbadf38 100644 --- a/Sources/Citadel/Algorithms/DH-Helpers.swift +++ b/Sources/Citadel/Algorithms/DH-Helpers.swift @@ -5,43 +5,6 @@ import NIO import NIOSSH import Crypto -let generator2: [UInt8] = [ 0x02 ] -let dh14PublicExponent: [UInt8] = [ 0x01, 0x00, 0x01 ] -let dh14p: [UInt8] = [ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, - 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, - 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, - 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, - 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, - 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, - 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, - 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, - 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, - 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, - 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, - 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, - 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, - 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, - 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, - 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, - 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, - 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, - 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, - 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, - 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, - 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, - 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, - 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, - 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, - 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, - 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, - 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, - 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, - 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF -] - extension SymmetricKey { /// Creates a symmetric key by truncating a given digest. static func truncatingDigest(_ digest: D, length: Int) -> SymmetricKey { diff --git a/Sources/Citadel/Algorithms/DiffieHellman.swift b/Sources/Citadel/Algorithms/DiffieHellman.swift new file mode 100644 index 0000000..a1f5786 --- /dev/null +++ b/Sources/Citadel/Algorithms/DiffieHellman.swift @@ -0,0 +1,313 @@ +import CCryptoBoringSSL +import Foundation +import BigInt +import NIO +import NIOSSH +import Crypto + +public enum DiffieHellman { + public enum Group14: DiffieHellman.Group { + public static let groupName = "group14" + public static let generator: [UInt8] = [ 0x02 ] + public static let publicExponent: [UInt8] = [ 0x01, 0x00, 0x01 ] + public static let prime: [UInt8] = [ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, + 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, + 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, + 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, + 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, + 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, + 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, + 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, + 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, + 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, + 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, + 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, + 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + ] + } + + public protocol Group { + static var groupName: String { get } + static var generator: [UInt8] { get } + static var publicExponent: [UInt8] { get } + static var prime: [UInt8] { get } + } + + public protocol HashFunction: Crypto.HashFunction { + static var dhName: String { get } + static var rsaName: String { get } + } + + public struct KeyExchange: NIOSSHKeyExchangeAlgorithmProtocol { + public static var keyExchangeInitMessageId: UInt8 { 30 } + public static var keyExchangeReplyMessageId: UInt8 { 31 } + + public static var keyExchangeAlgorithmNames: [Substring] { + ["diffie-hellman-\(Group.groupName)-\(Hash.dhName)"] + } + + private var previousSessionIdentifier: ByteBuffer? + private var ourRole: SSHConnectionRole + private var theirKey: RSA.PublicKey? + private var sharedSecret: Data? + public let ourKey: RSA.PrivateKey + + private struct _KeyExchangeResult { + var sessionID: ByteBuffer + var exchangeHash: Hash.Digest + var keys: NIOSSHSessionKeys + } + + public init(ourRole: SSHConnectionRole, previousSessionIdentifier: ByteBuffer?) { + self.ourRole = ourRole + self.previousSessionIdentifier = previousSessionIdentifier + self.ourKey = RSA.PrivateKey() + } + + public func initiateKeyExchangeClientSide(allocator: ByteBufferAllocator) -> ByteBuffer { + var buffer = allocator.buffer(capacity: 256) + + buffer.writeBignum(ourKey._publicKey.modulus) + return buffer + } + + public mutating func completeKeyExchangeServerSide( + clientKeyExchangeMessage message: ByteBuffer, + serverHostKey: NIOSSHPrivateKey, + initialExchangeBytes: inout ByteBuffer, + allocator: ByteBufferAllocator, + expectedKeySizes: ExpectedKeySizes + ) throws -> (KeyExchangeResult, NIOSSHKeyExchangeServerReply) { + // With that, we have enough to finalize the key exchange. + let kexResult = try self.finalizeKeyExchange( + theirKeyBytes: message, + initialExchangeBytes: &initialExchangeBytes, + serverHostKey: serverHostKey.publicKey, + allocator: allocator, + expectedKeySizes: expectedKeySizes + ) + + // We should now sign the exchange hash. + let exchangeHashSignature = try serverHostKey.sign(digest: kexResult.exchangeHash) + + // Ok, time to write the final message. We need to write our public key into it. + // The largest key we're likely to end up with here is 256 bytes. + var publicKeyBytes = allocator.buffer(capacity: 256) + _ = self.ourKey.publicKey.write(to: &publicKeyBytes) + + // Now we have all we need. + let responseMessage = NIOSSHKeyExchangeServerReply( + hostKey: serverHostKey.publicKey, + publicKey: publicKeyBytes, + signature: exchangeHashSignature + ) + + return (KeyExchangeResult(sessionID: kexResult.sessionID, keys: kexResult.keys), responseMessage) + } + + public mutating func receiveServerKeyExchangePayload(serverKeyExchangeMessage: NIOSSHKeyExchangeServerReply, initialExchangeBytes: inout ByteBuffer, allocator: ByteBufferAllocator, expectedKeySizes: ExpectedKeySizes) throws -> KeyExchangeResult { + let kexResult = try self.finalizeKeyExchange(theirKeyBytes: serverKeyExchangeMessage.publicKey, + initialExchangeBytes: &initialExchangeBytes, + serverHostKey: serverKeyExchangeMessage.hostKey, + allocator: allocator, + expectedKeySizes: expectedKeySizes) + + // We can now verify signature over the exchange hash. + guard serverKeyExchangeMessage.hostKey.isValidSignature(serverKeyExchangeMessage.signature, for: kexResult.exchangeHash) else { + throw CitadelError.invalidSignature + } + + // Great, all done here. + return KeyExchangeResult( + sessionID: kexResult.sessionID, + keys: kexResult.keys + ) + } + + private mutating func finalizeKeyExchange(theirKeyBytes f: ByteBuffer, + initialExchangeBytes: inout ByteBuffer, + serverHostKey: NIOSSHPublicKey, + allocator: ByteBufferAllocator, + expectedKeySizes: ExpectedKeySizes) throws -> _KeyExchangeResult { + let f = f.getBytes(at: 0, length: f.readableBytes)! + + let serverPublicKey = CCryptoBoringSSL_BN_bin2bn(f, f.count, nil)! + defer { CCryptoBoringSSL_BN_free(serverPublicKey) } + let secret = CCryptoBoringSSL_BN_new()! + let serverHostKeyBN = CCryptoBoringSSL_BN_new() + defer { CCryptoBoringSSL_BN_free(serverHostKeyBN) } + + var buffer = ByteBuffer() + serverHostKey.write(to: &buffer) + buffer.readWithUnsafeReadableBytes { buffer in + let buffer = buffer.bindMemory(to: UInt8.self) + CCryptoBoringSSL_BN_bin2bn(buffer.baseAddress!, buffer.count, serverHostKeyBN) + return buffer.count + } + + let ctx = CCryptoBoringSSL_BN_CTX_new() + defer { CCryptoBoringSSL_BN_CTX_free(ctx) } + + let group = CCryptoBoringSSL_BN_bin2bn(Group.prime, Group.prime.count, nil) + defer { CCryptoBoringSSL_BN_free(group) } + + guard CCryptoBoringSSL_BN_mod_exp( + secret, + serverPublicKey, + ourKey.privateExponent, + group, + ctx + ) == 1 else { + throw CitadelError.cryptographicError + } + + var sharedSecret = [UInt8]() + sharedSecret.reserveCapacity(Int(CCryptoBoringSSL_BN_num_bytes(secret))) + CCryptoBoringSSL_BN_bn2bin(secret, &sharedSecret) + + self.sharedSecret = Data(sharedSecret) + + func hexEncodedString(array: [UInt8]) -> String { + return array.map { String(format: "%02hhx", $0) }.joined() + } + + //var offset = initialExchangeBytes.writerIndex + initialExchangeBytes.writeCompositeSSHString { + serverHostKey.write(to: &$0) + } + + //offset = initialExchangeBytes.writerIndex + switch self.ourRole { + case .client: + initialExchangeBytes.writeMPBignum(ourKey._publicKey.modulus) + //offset = initialExchangeBytes.writerIndex + initialExchangeBytes.writeMPBignum(serverPublicKey) + case .server: + initialExchangeBytes.writeMPBignum(serverPublicKey) + initialExchangeBytes.writeMPBignum(ourKey._publicKey.modulus) + } + + // Ok, now finalize the exchange hash. If we don't have a previous session identifier at this stage, we do now! + initialExchangeBytes.writeMPBignum(secret) + + let exchangeHash = Hash.hash(data: initialExchangeBytes.readableBytesView) + + let sessionID: ByteBuffer + if let previousSessionIdentifier = self.previousSessionIdentifier { + sessionID = previousSessionIdentifier + } else { + var hashBytes = allocator.buffer(capacity: Hash.Digest.byteCount) + hashBytes.writeContiguousBytes(exchangeHash) + sessionID = hashBytes + } + + // Now we can generate the keys. + let keys = self.generateKeys(secret: secret, exchangeHash: exchangeHash, sessionID: sessionID, expectedKeySizes: expectedKeySizes) + + // All done! + return _KeyExchangeResult(sessionID: sessionID, exchangeHash: exchangeHash, keys: keys) + } + + private func generateKeys(secret: UnsafeMutablePointer, exchangeHash: Hash.Digest, sessionID: ByteBuffer, expectedKeySizes: ExpectedKeySizes) -> NIOSSHSessionKeys { + // Cool, now it's time to generate the keys. In my ideal world I'd have a mechanism to handle this digest securely, but this is + // not available in CryptoKit so we're going to spill these keys all over the heap and the stack. This isn't ideal, but I don't + // think the risk is too bad. + // + // We generate these as follows: + // + // - Initial IV client to server: HASH(K || H || "A" || session_id) + // (Here K is encoded as mpint and "A" as byte and session_id as raw + // data. "A" means the single character A, ASCII 65). + // - Initial IV server to client: HASH(K || H || "B" || session_id) + // - Encryption key client to server: HASH(K || H || "C" || session_id) + // - Encryption key server to client: HASH(K || H || "D" || session_id) + // - Integrity key client to server: HASH(K || H || "E" || session_id) + // - Integrity key server to client: HASH(K || H || "F" || session_id) + + func calculateSha1SymmetricKey(letter: UInt8, expectedKeySize size: Int) -> SymmetricKey { + SymmetricKey(data: calculateSha1Key(letter: letter, expectedKeySize: size)) + } + + func calculateSha1Key(letter: UInt8, expectedKeySize size: Int) -> [UInt8] { + var result = [UInt8]() + var hashInput = ByteBuffer() + + while result.count < size { + hashInput.moveWriterIndex(to: 0) + hashInput.writeMPBignum(secret) + hashInput.writeBytes(exchangeHash) + + if !result.isEmpty { + hashInput.writeBytes(result) + } else { + hashInput.writeInteger(letter) + hashInput.writeBytes(sessionID.readableBytesView) + } + + result += Insecure.SHA1.hash(data: hashInput.readableBytesView) + } + + result.removeLast(result.count - size) + return result + } + + switch self.ourRole { + case .client: + return NIOSSHSessionKeys( + initialInboundIV: calculateSha1Key(letter: UInt8(ascii: "B"), expectedKeySize: expectedKeySizes.ivSize), + initialOutboundIV: calculateSha1Key(letter: UInt8(ascii: "A"), expectedKeySize: expectedKeySizes.ivSize), + inboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "D"), expectedKeySize: expectedKeySizes.encryptionKeySize), + outboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "C"), expectedKeySize: expectedKeySizes.encryptionKeySize), + inboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "F"), expectedKeySize: expectedKeySizes.macKeySize), + outboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "E"), expectedKeySize: expectedKeySizes.macKeySize)) + case .server: + return NIOSSHSessionKeys( + initialInboundIV: calculateSha1Key(letter: UInt8(ascii: "A"), expectedKeySize: expectedKeySizes.ivSize), + initialOutboundIV: calculateSha1Key(letter: UInt8(ascii: "B"), expectedKeySize: expectedKeySizes.ivSize), + inboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "C"), expectedKeySize: expectedKeySizes.encryptionKeySize), + outboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "D"), expectedKeySize: expectedKeySizes.encryptionKeySize), + inboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "E"), expectedKeySize: expectedKeySizes.macKeySize), + outboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "F"), expectedKeySize: expectedKeySizes.macKeySize)) + } + } + } +} + +extension Insecure.SHA1: DiffieHellman.HashFunction { + public static var dhName: String { "sha1" } + public static var rsaName: String { "ssh-rsa" } +} + +extension SHA256: DiffieHellman.HashFunction { + public static var dhName: String { "sha256" } + public static var rsaName: String { "rsa-sha2-256" } +} + +extension SHA512: DiffieHellman.HashFunction { + public static var dhName: String { "sha512" } + public static var rsaName: String { "rsa-sha2-512" } +} + +public typealias DiffieHellmanGroup14Sha1 = DiffieHellman.KeyExchange +public typealias DiffieHellmanGroup14Sha256 = DiffieHellman.KeyExchange +public typealias DiffieHellmanGroup14Sha512 = DiffieHellman.KeyExchange diff --git a/Sources/Citadel/Algorithms/DiffieHellmanGroup14Sha1.swift b/Sources/Citadel/Algorithms/DiffieHellmanGroup14Sha1.swift deleted file mode 100644 index 22ee0e7..0000000 --- a/Sources/Citadel/Algorithms/DiffieHellmanGroup14Sha1.swift +++ /dev/null @@ -1,239 +0,0 @@ -import CCryptoBoringSSL -import Foundation -import BigInt -import NIO -import NIOSSH -import Crypto - -public struct DiffieHellmanGroup14Sha1: NIOSSHKeyExchangeAlgorithmProtocol { - public static let keyExchangeInitMessageId: UInt8 = 30 - public static let keyExchangeReplyMessageId: UInt8 = 31 - - public static let keyExchangeAlgorithmNames: [Substring] = ["diffie-hellman-group14-sha1"] - - private var previousSessionIdentifier: ByteBuffer? - private var ourRole: SSHConnectionRole - private var theirKey: Insecure.RSA.PublicKey? - private var sharedSecret: Data? - public let ourKey: Insecure.RSA.PrivateKey - public static var ourKey: Insecure.RSA.PrivateKey? - - private struct _KeyExchangeResult { - var sessionID: ByteBuffer - var exchangeHash: Insecure.SHA1.Digest - var keys: NIOSSHSessionKeys - } - - public init(ourRole: SSHConnectionRole, previousSessionIdentifier: ByteBuffer?) { - self.ourRole = ourRole - self.previousSessionIdentifier = previousSessionIdentifier - self.ourKey = Self.ourKey ?? Insecure.RSA.PrivateKey() - } - - public func initiateKeyExchangeClientSide(allocator: ByteBufferAllocator) -> ByteBuffer { - var buffer = allocator.buffer(capacity: 256) - - buffer.writeBignum(ourKey._publicKey.modulus) - return buffer - } - - public mutating func completeKeyExchangeServerSide( - clientKeyExchangeMessage message: ByteBuffer, - serverHostKey: NIOSSHPrivateKey, - initialExchangeBytes: inout ByteBuffer, - allocator: ByteBufferAllocator, - expectedKeySizes: ExpectedKeySizes - ) throws -> (KeyExchangeResult, NIOSSHKeyExchangeServerReply) { - // With that, we have enough to finalize the key exchange. - let kexResult = try self.finalizeKeyExchange( - theirKeyBytes: message, - initialExchangeBytes: &initialExchangeBytes, - serverHostKey: serverHostKey.publicKey, - allocator: allocator, - expectedKeySizes: expectedKeySizes - ) - - // We should now sign the exchange hash. - let exchangeHashSignature = try serverHostKey.sign(digest: kexResult.exchangeHash) - - // Ok, time to write the final message. We need to write our public key into it. - // The largest key we're likely to end up with here is 256 bytes. - var publicKeyBytes = allocator.buffer(capacity: 256) - _ = self.ourKey.publicKey.write(to: &publicKeyBytes) - - // Now we have all we need. - let responseMessage = NIOSSHKeyExchangeServerReply( - hostKey: serverHostKey.publicKey, - publicKey: publicKeyBytes, - signature: exchangeHashSignature - ) - - return (KeyExchangeResult(sessionID: kexResult.sessionID, keys: kexResult.keys), responseMessage) - } - - public mutating func receiveServerKeyExchangePayload(serverKeyExchangeMessage: NIOSSHKeyExchangeServerReply, initialExchangeBytes: inout ByteBuffer, allocator: ByteBufferAllocator, expectedKeySizes: ExpectedKeySizes) throws -> KeyExchangeResult { - let kexResult = try self.finalizeKeyExchange(theirKeyBytes: serverKeyExchangeMessage.publicKey, - initialExchangeBytes: &initialExchangeBytes, - serverHostKey: serverKeyExchangeMessage.hostKey, - allocator: allocator, - expectedKeySizes: expectedKeySizes) - - // We can now verify signature over the exchange hash. - guard serverKeyExchangeMessage.hostKey.isValidSignature(serverKeyExchangeMessage.signature, for: kexResult.exchangeHash) else { - throw CitadelError.invalidSignature - } - - // Great, all done here. - return KeyExchangeResult( - sessionID: kexResult.sessionID, - keys: kexResult.keys - ) - } - - private mutating func finalizeKeyExchange(theirKeyBytes f: ByteBuffer, - initialExchangeBytes: inout ByteBuffer, - serverHostKey: NIOSSHPublicKey, - allocator: ByteBufferAllocator, - expectedKeySizes: ExpectedKeySizes) throws -> _KeyExchangeResult { - let f = f.getBytes(at: 0, length: f.readableBytes)! - - let serverPublicKey = CCryptoBoringSSL_BN_bin2bn(f, f.count, nil)! - defer { CCryptoBoringSSL_BN_free(serverPublicKey) } - let secret = CCryptoBoringSSL_BN_new()! - let serverHostKeyBN = CCryptoBoringSSL_BN_new() - defer { CCryptoBoringSSL_BN_free(serverHostKeyBN) } - - var buffer = ByteBuffer() - serverHostKey.write(to: &buffer) - buffer.readWithUnsafeReadableBytes { buffer in - let buffer = buffer.bindMemory(to: UInt8.self) - CCryptoBoringSSL_BN_bin2bn(buffer.baseAddress!, buffer.count, serverHostKeyBN) - return buffer.count - } - - let ctx = CCryptoBoringSSL_BN_CTX_new() - defer { CCryptoBoringSSL_BN_CTX_free(ctx) } - - let group = CCryptoBoringSSL_BN_bin2bn(dh14p, dh14p.count, nil) - defer { CCryptoBoringSSL_BN_free(group) } - - guard CCryptoBoringSSL_BN_mod_exp( - secret, - serverPublicKey, - ourKey.privateExponent, - group, - ctx - ) == 1 else { - throw CitadelError.cryptographicError - } - - var sharedSecret = [UInt8]() - sharedSecret.reserveCapacity(Int(CCryptoBoringSSL_BN_num_bytes(secret))) - CCryptoBoringSSL_BN_bn2bin(secret, &sharedSecret) - - self.sharedSecret = Data(sharedSecret) - - func hexEncodedString(array: [UInt8]) -> String { - return array.map { String(format: "%02hhx", $0) }.joined() - } - - //var offset = initialExchangeBytes.writerIndex - initialExchangeBytes.writeCompositeSSHString { - serverHostKey.write(to: &$0) - } - - //offset = initialExchangeBytes.writerIndex - switch self.ourRole { - case .client: - initialExchangeBytes.writeMPBignum(ourKey._publicKey.modulus) - //offset = initialExchangeBytes.writerIndex - initialExchangeBytes.writeMPBignum(serverPublicKey) - case .server: - initialExchangeBytes.writeMPBignum(serverPublicKey) - initialExchangeBytes.writeMPBignum(ourKey._publicKey.modulus) - } - - // Ok, now finalize the exchange hash. If we don't have a previous session identifier at this stage, we do now! - initialExchangeBytes.writeMPBignum(secret) - - let exchangeHash = Insecure.SHA1.hash(data: initialExchangeBytes.readableBytesView) - - let sessionID: ByteBuffer - if let previousSessionIdentifier = self.previousSessionIdentifier { - sessionID = previousSessionIdentifier - } else { - var hashBytes = allocator.buffer(capacity: Insecure.SHA1.byteCount) - hashBytes.writeContiguousBytes(exchangeHash) - sessionID = hashBytes - } - - // Now we can generate the keys. - let keys = self.generateKeys(secret: secret, exchangeHash: exchangeHash, sessionID: sessionID, expectedKeySizes: expectedKeySizes) - - // All done! - return _KeyExchangeResult(sessionID: sessionID, exchangeHash: exchangeHash, keys: keys) - } - - private func generateKeys(secret: UnsafeMutablePointer, exchangeHash: Insecure.SHA1.Digest, sessionID: ByteBuffer, expectedKeySizes: ExpectedKeySizes) -> NIOSSHSessionKeys { - // Cool, now it's time to generate the keys. In my ideal world I'd have a mechanism to handle this digest securely, but this is - // not available in CryptoKit so we're going to spill these keys all over the heap and the stack. This isn't ideal, but I don't - // think the risk is too bad. - // - // We generate these as follows: - // - // - Initial IV client to server: HASH(K || H || "A" || session_id) - // (Here K is encoded as mpint and "A" as byte and session_id as raw - // data. "A" means the single character A, ASCII 65). - // - Initial IV server to client: HASH(K || H || "B" || session_id) - // - Encryption key client to server: HASH(K || H || "C" || session_id) - // - Encryption key server to client: HASH(K || H || "D" || session_id) - // - Integrity key client to server: HASH(K || H || "E" || session_id) - // - Integrity key server to client: HASH(K || H || "F" || session_id) - - func calculateSha1SymmetricKey(letter: UInt8, expectedKeySize size: Int) -> SymmetricKey { - SymmetricKey(data: calculateSha1Key(letter: letter, expectedKeySize: size)) - } - - func calculateSha1Key(letter: UInt8, expectedKeySize size: Int) -> [UInt8] { - var result = [UInt8]() - var hashInput = ByteBuffer() - - while result.count < size { - hashInput.moveWriterIndex(to: 0) - hashInput.writeMPBignum(secret) - hashInput.writeBytes(exchangeHash) - - if !result.isEmpty { - hashInput.writeBytes(result) - } else { - hashInput.writeInteger(letter) - hashInput.writeBytes(sessionID.readableBytesView) - } - - result += Insecure.SHA1.hash(data: hashInput.readableBytesView) - } - - result.removeLast(result.count - size) - return result - } - - switch self.ourRole { - case .client: - return NIOSSHSessionKeys( - initialInboundIV: calculateSha1Key(letter: UInt8(ascii: "B"), expectedKeySize: expectedKeySizes.ivSize), - initialOutboundIV: calculateSha1Key(letter: UInt8(ascii: "A"), expectedKeySize: expectedKeySizes.ivSize), - inboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "D"), expectedKeySize: expectedKeySizes.encryptionKeySize), - outboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "C"), expectedKeySize: expectedKeySizes.encryptionKeySize), - inboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "F"), expectedKeySize: expectedKeySizes.macKeySize), - outboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "E"), expectedKeySize: expectedKeySizes.macKeySize)) - case .server: - return NIOSSHSessionKeys( - initialInboundIV: calculateSha1Key(letter: UInt8(ascii: "A"), expectedKeySize: expectedKeySizes.ivSize), - initialOutboundIV: calculateSha1Key(letter: UInt8(ascii: "B"), expectedKeySize: expectedKeySizes.ivSize), - inboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "C"), expectedKeySize: expectedKeySizes.encryptionKeySize), - outboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "D"), expectedKeySize: expectedKeySizes.encryptionKeySize), - inboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "E"), expectedKeySize: expectedKeySizes.macKeySize), - outboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "F"), expectedKeySize: expectedKeySizes.macKeySize)) - } - } -} diff --git a/Sources/Citadel/Algorithms/DiffieHellmanGroup14Sha256.swift b/Sources/Citadel/Algorithms/DiffieHellmanGroup14Sha256.swift deleted file mode 100644 index d18693e..0000000 --- a/Sources/Citadel/Algorithms/DiffieHellmanGroup14Sha256.swift +++ /dev/null @@ -1,239 +0,0 @@ -import CCryptoBoringSSL -import Foundation -import BigInt -import NIO -import NIOSSH -import Crypto - -public struct DiffieHellmanGroup14Sha256: NIOSSHKeyExchangeAlgorithmProtocol { - public static let keyExchangeInitMessageId: UInt8 = 30 - public static let keyExchangeReplyMessageId: UInt8 = 31 - - public static let keyExchangeAlgorithmNames: [Substring] = ["diffie-hellman-group14-sha256"] - - private var previousSessionIdentifier: ByteBuffer? - private var ourRole: SSHConnectionRole - private var theirKey: Insecure.RSA.PublicKey? - private var sharedSecret: Data? - public let ourKey: Insecure.RSA.PrivateKey - public static var ourKey: Insecure.RSA.PrivateKey? - - private struct _KeyExchangeResult { - var sessionID: ByteBuffer - var exchangeHash: SHA256.Digest - var keys: NIOSSHSessionKeys - } - - public init(ourRole: SSHConnectionRole, previousSessionIdentifier: ByteBuffer?) { - self.ourRole = ourRole - self.previousSessionIdentifier = previousSessionIdentifier - self.ourKey = Self.ourKey ?? Insecure.RSA.PrivateKey() - } - - public func initiateKeyExchangeClientSide(allocator: ByteBufferAllocator) -> ByteBuffer { - var buffer = allocator.buffer(capacity: 256) - - buffer.writeBignum(ourKey._publicKey.modulus) - return buffer - } - - public mutating func completeKeyExchangeServerSide( - clientKeyExchangeMessage message: ByteBuffer, - serverHostKey: NIOSSHPrivateKey, - initialExchangeBytes: inout ByteBuffer, - allocator: ByteBufferAllocator, - expectedKeySizes: ExpectedKeySizes - ) throws -> (KeyExchangeResult, NIOSSHKeyExchangeServerReply) { - // With that, we have enough to finalize the key exchange. - let kexResult = try self.finalizeKeyExchange( - theirKeyBytes: message, - initialExchangeBytes: &initialExchangeBytes, - serverHostKey: serverHostKey.publicKey, - allocator: allocator, - expectedKeySizes: expectedKeySizes - ) - - // We should now sign the exchange hash. - let exchangeHashSignature = try serverHostKey.sign(digest: kexResult.exchangeHash) - - // Ok, time to write the final message. We need to write our public key into it. - // The largest key we're likely to end up with here is 256 bytes. - var publicKeyBytes = allocator.buffer(capacity: 256) - _ = self.ourKey.publicKey.write(to: &publicKeyBytes) - - // Now we have all we need. - let responseMessage = NIOSSHKeyExchangeServerReply( - hostKey: serverHostKey.publicKey, - publicKey: publicKeyBytes, - signature: exchangeHashSignature - ) - - return (KeyExchangeResult(sessionID: kexResult.sessionID, keys: kexResult.keys), responseMessage) - } - - public mutating func receiveServerKeyExchangePayload(serverKeyExchangeMessage: NIOSSHKeyExchangeServerReply, initialExchangeBytes: inout ByteBuffer, allocator: ByteBufferAllocator, expectedKeySizes: ExpectedKeySizes) throws -> KeyExchangeResult { - let kexResult = try self.finalizeKeyExchange(theirKeyBytes: serverKeyExchangeMessage.publicKey, - initialExchangeBytes: &initialExchangeBytes, - serverHostKey: serverKeyExchangeMessage.hostKey, - allocator: allocator, - expectedKeySizes: expectedKeySizes) - - // We can now verify signature over the exchange hash. - guard serverKeyExchangeMessage.hostKey.isValidSignature(serverKeyExchangeMessage.signature, for: kexResult.exchangeHash) else { - throw CitadelError.invalidSignature - } - - // Great, all done here. - return KeyExchangeResult( - sessionID: kexResult.sessionID, - keys: kexResult.keys - ) - } - - private mutating func finalizeKeyExchange(theirKeyBytes f: ByteBuffer, - initialExchangeBytes: inout ByteBuffer, - serverHostKey: NIOSSHPublicKey, - allocator: ByteBufferAllocator, - expectedKeySizes: ExpectedKeySizes) throws -> _KeyExchangeResult { - let f = f.getBytes(at: 0, length: f.readableBytes)! - - let serverPublicKey = CCryptoBoringSSL_BN_bin2bn(f, f.count, nil)! - defer { CCryptoBoringSSL_BN_free(serverPublicKey) } - let secret = CCryptoBoringSSL_BN_new()! - let serverHostKeyBN = CCryptoBoringSSL_BN_new() - defer { CCryptoBoringSSL_BN_free(serverHostKeyBN) } - - var buffer = ByteBuffer() - serverHostKey.write(to: &buffer) - buffer.readWithUnsafeReadableBytes { buffer in - let buffer = buffer.bindMemory(to: UInt8.self) - CCryptoBoringSSL_BN_bin2bn(buffer.baseAddress!, buffer.count, serverHostKeyBN) - return buffer.count - } - - let ctx = CCryptoBoringSSL_BN_CTX_new() - defer { CCryptoBoringSSL_BN_CTX_free(ctx) } - - let group = CCryptoBoringSSL_BN_bin2bn(dh14p, dh14p.count, nil) - defer { CCryptoBoringSSL_BN_free(group) } - - guard CCryptoBoringSSL_BN_mod_exp( - secret, - serverPublicKey, - ourKey.privateExponent, - group, - ctx - ) == 1 else { - throw CitadelError.cryptographicError - } - - var sharedSecret = [UInt8]() - sharedSecret.reserveCapacity(Int(CCryptoBoringSSL_BN_num_bytes(secret))) - CCryptoBoringSSL_BN_bn2bin(secret, &sharedSecret) - - self.sharedSecret = Data(sharedSecret) - - func hexEncodedString(array: [UInt8]) -> String { - return array.map { String(format: "%02hhx", $0) }.joined() - } - - //var offset = initialExchangeBytes.writerIndex - initialExchangeBytes.writeCompositeSSHString { - serverHostKey.write(to: &$0) - } - - //offset = initialExchangeBytes.writerIndex - switch self.ourRole { - case .client: - initialExchangeBytes.writeMPBignum(ourKey._publicKey.modulus) - //offset = initialExchangeBytes.writerIndex - initialExchangeBytes.writeMPBignum(serverPublicKey) - case .server: - initialExchangeBytes.writeMPBignum(serverPublicKey) - initialExchangeBytes.writeMPBignum(ourKey._publicKey.modulus) - } - - // Ok, now finalize the exchange hash. If we don't have a previous session identifier at this stage, we do now! - initialExchangeBytes.writeMPBignum(secret) - - let exchangeHash = SHA256.hash(data: initialExchangeBytes.readableBytesView) - - let sessionID: ByteBuffer - if let previousSessionIdentifier = self.previousSessionIdentifier { - sessionID = previousSessionIdentifier - } else { - var hashBytes = allocator.buffer(capacity: SHA256.byteCount) - hashBytes.writeContiguousBytes(exchangeHash) - sessionID = hashBytes - } - - // Now we can generate the keys. - let keys = self.generateKeys(secret: secret, exchangeHash: exchangeHash, sessionID: sessionID, expectedKeySizes: expectedKeySizes) - - // All done! - return _KeyExchangeResult(sessionID: sessionID, exchangeHash: exchangeHash, keys: keys) - } - - private func generateKeys(secret: UnsafeMutablePointer, exchangeHash: SHA256.Digest, sessionID: ByteBuffer, expectedKeySizes: ExpectedKeySizes) -> NIOSSHSessionKeys { - // Cool, now it's time to generate the keys. In my ideal world I'd have a mechanism to handle this digest securely, but this is - // not available in CryptoKit so we're going to spill these keys all over the heap and the stack. This isn't ideal, but I don't - // think the risk is too bad. - // - // We generate these as follows: - // - // - Initial IV client to server: HASH(K || H || "A" || session_id) - // (Here K is encoded as mpint and "A" as byte and session_id as raw - // data. "A" means the single character A, ASCII 65). - // - Initial IV server to client: HASH(K || H || "B" || session_id) - // - Encryption key client to server: HASH(K || H || "C" || session_id) - // - Encryption key server to client: HASH(K || H || "D" || session_id) - // - Integrity key client to server: HASH(K || H || "E" || session_id) - // - Integrity key server to client: HASH(K || H || "F" || session_id) - - func calculateSha1SymmetricKey(letter: UInt8, expectedKeySize size: Int) -> SymmetricKey { - SymmetricKey(data: calculateSha256Key(letter: letter, expectedKeySize: size)) - } - - func calculateSha256Key(letter: UInt8, expectedKeySize size: Int) -> [UInt8] { - var result = [UInt8]() - var hashInput = ByteBuffer() - - while result.count < size { - hashInput.moveWriterIndex(to: 0) - hashInput.writeMPBignum(secret) - hashInput.writeBytes(exchangeHash) - - if !result.isEmpty { - hashInput.writeBytes(result) - } else { - hashInput.writeInteger(letter) - hashInput.writeBytes(sessionID.readableBytesView) - } - - result += SHA256.hash(data: hashInput.readableBytesView) - } - - result.removeLast(result.count - size) - return result - } - - switch self.ourRole { - case .client: - return NIOSSHSessionKeys( - initialInboundIV: calculateSha256Key(letter: UInt8(ascii: "B"), expectedKeySize: expectedKeySizes.ivSize), - initialOutboundIV: calculateSha256Key(letter: UInt8(ascii: "A"), expectedKeySize: expectedKeySizes.ivSize), - inboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "D"), expectedKeySize: expectedKeySizes.encryptionKeySize), - outboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "C"), expectedKeySize: expectedKeySizes.encryptionKeySize), - inboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "F"), expectedKeySize: expectedKeySizes.macKeySize), - outboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "E"), expectedKeySize: expectedKeySizes.macKeySize)) - case .server: - return NIOSSHSessionKeys( - initialInboundIV: calculateSha256Key(letter: UInt8(ascii: "A"), expectedKeySize: expectedKeySizes.ivSize), - initialOutboundIV: calculateSha256Key(letter: UInt8(ascii: "B"), expectedKeySize: expectedKeySizes.ivSize), - inboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "C"), expectedKeySize: expectedKeySizes.encryptionKeySize), - outboundEncryptionKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "D"), expectedKeySize: expectedKeySizes.encryptionKeySize), - inboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "E"), expectedKeySize: expectedKeySizes.macKeySize), - outboundMACKey: calculateSha1SymmetricKey(letter: UInt8(ascii: "F"), expectedKeySize: expectedKeySizes.macKeySize)) - } - } -} diff --git a/Sources/Citadel/Algorithms/RSA.swift b/Sources/Citadel/Algorithms/RSA.swift index f466541..9cf29ad 100644 --- a/Sources/Citadel/Algorithms/RSA.swift +++ b/Sources/Citadel/Algorithms/RSA.swift @@ -6,15 +6,28 @@ import CCryptoBoringSSL import Foundation import Crypto +public enum RSA {} + extension Insecure { - public enum RSA {} + public typealias RSA = Citadel.RSA } -extension Insecure.RSA { +extension RSA { public final class PublicKey: NIOSSHPublicKeyProtocol { - public static let publicKeyPrefix = "ssh-rsa" - public static let keyExchangeAlgorithms = ["diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1"] - + public static var publicKeyPrefix: String { Hash.rsaName } + public static var keyExchangeAlgorithms: [String] { + switch Hash.self { + case is Insecure.SHA1.Type: + return ["diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1"] + case is SHA256.Type: + return ["diffie-hellman-group1-sha256", "diffie-hellman-group14-sha256"] + case is SHA512.Type: + return ["diffie-hellman-group1-sha512", "diffie-hellman-group14-sha512"] + default: + return [] + } + } + // PublicExponent e internal let publicExponent: UnsafeMutablePointer @@ -104,11 +117,11 @@ extension Insecure.RSA { return writtenBytes } - static func read(consuming buffer: inout ByteBuffer) throws -> Insecure.RSA.PublicKey { + static func read(consuming buffer: inout ByteBuffer) throws -> PublicKey { try read(from: &buffer) } - public static func read(from buffer: inout ByteBuffer) throws -> Insecure.RSA.PublicKey { + public static func read(from buffer: inout ByteBuffer) throws -> PublicKey { guard var publicExponent = buffer.readSSHBuffer(), var modulus = buffer.readSSHBuffer() @@ -138,8 +151,8 @@ extension Insecure.RSA { } public struct Signature: ContiguousBytes, NIOSSHSignatureProtocol { - public static let signaturePrefix = "ssh-rsa" - + public static var signaturePrefix: String { Hash.rsaName } + public let rawRepresentation: Data public init(rawRepresentation: D) where D : DataProtocol { @@ -165,8 +178,9 @@ extension Insecure.RSA { } public final class PrivateKey: NIOSSHPrivateKeyProtocol { - public static let keyPrefix = "ssh-rsa" - + + public static var keyPrefix: String { Hash.rsaName } + // Private Exponent internal let privateExponent: UnsafeMutablePointer @@ -185,29 +199,33 @@ extension Insecure.RSA { deinit { CCryptoBoringSSL_BN_free(privateExponent) } - - public init(bits: Int = 2047, publicExponent e: BigUInt = 65537) { + + public init() { let privateKey = CCryptoBoringSSL_BN_new()! let publicKey = CCryptoBoringSSL_BN_new()! - let group = CCryptoBoringSSL_BN_bin2bn(dh14p, dh14p.count, nil)! - let generator = CCryptoBoringSSL_BN_bin2bn(generator2, generator2.count, nil)! + let group = CCryptoBoringSSL_BN_bin2bn(Group.prime, Group.prime.count, nil)! + let generator = CCryptoBoringSSL_BN_bin2bn(Group.generator, Group.generator.count, nil)! let bignumContext = CCryptoBoringSSL_BN_CTX_new() - + CCryptoBoringSSL_BN_rand(privateKey, 256 * 8 - 1, 0, /*-1*/BN_RAND_BOTTOM_ANY) CCryptoBoringSSL_BN_mod_exp(publicKey, generator, privateKey, group, bignumContext) - let eBytes = Array(e.serialize()) + let eBytes = Group.publicExponent let e = CCryptoBoringSSL_BN_bin2bn(eBytes, eBytes.count, nil)! - + CCryptoBoringSSL_BN_CTX_free(bignumContext) CCryptoBoringSSL_BN_free(generator) CCryptoBoringSSL_BN_free(group) - + self.privateExponent = privateKey - self._publicKey = .init( + self._publicKey = PublicKey( publicExponent: e, modulus: publicKey ) } + + public convenience init(bits: Int = 2047, publicExponent e: BigUInt = 65537) { + self.init() + } public func signature(for message: D) throws -> Signature { let context = CCryptoBoringSSL_RSA_new() @@ -275,7 +293,7 @@ extension Insecure.RSA { let ctx = CCryptoBoringSSL_BN_CTX_new() defer { CCryptoBoringSSL_BN_CTX_free(ctx) } - let group = CCryptoBoringSSL_BN_bin2bn(dh14p, dh14p.count, nil)! + let group = CCryptoBoringSSL_BN_bin2bn(Group.prime, Group.prime.count, nil)! defer { CCryptoBoringSSL_BN_free(group) } CCryptoBoringSSL_BN_mod_exp( secret, @@ -322,43 +340,6 @@ extension BigUInt { } } -extension BigUInt { - public static let diffieHellmanGroup14 = BigUInt(Data([ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, - 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, - 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, - 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, - 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, - 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, - 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, - 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, - 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, - 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, - 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, - 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, - 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, - 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, - 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, - 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, - 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, - 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, - 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, - 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, - 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, - 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, - 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, - 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, - 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, - 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, - 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, - 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, - 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, - 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - ] as [UInt8])) -} - extension ByteBuffer { @discardableResult mutating func readPositiveMPInt() -> BigUInt? { diff --git a/Sources/Citadel/Client.swift b/Sources/Citadel/Client.swift index 881bbb3..a268bea 100644 --- a/Sources/Citadel/Client.swift +++ b/Sources/Citadel/Client.swift @@ -101,7 +101,12 @@ public struct SSHAlgorithms { ]) algorithms.publicKeyAlgorihtms = .add([ - (Insecure.RSA.PublicKey.self, Insecure.RSA.Signature.self), +// (Insecure.RSA.PublicKey.self, Insecure.RSA.Signature.self), + (RSA.PublicKey.self, RSA.Signature.self), +// (Insecure.RSA.PublicKey.self, Insecure.RSA.Signature.self), + (RSA.PublicKey.self, RSA.Signature.self), +// (Insecure.RSA.PublicKey.self, Insecure.RSA.Signature.self), + (RSA.PublicKey.self, RSA.Signature.self), ]) return algorithms diff --git a/Sources/Citadel/OpenSSHKey.swift b/Sources/Citadel/OpenSSHKey.swift index a681234..8efd15d 100644 --- a/Sources/Citadel/OpenSSHKey.swift +++ b/Sources/Citadel/OpenSSHKey.swift @@ -22,7 +22,7 @@ protocol OpenSSHPrivateKey: ByteBufferConvertible { associatedtype PublicKey: ByteBufferConvertible } -extension Insecure.RSA.PrivateKey: ByteBufferConvertible { +extension RSA.PrivateKey: ByteBufferConvertible { static func read(consuming buffer: inout ByteBuffer) throws -> Self { guard let nBytesLength = buffer.readInteger(as: UInt32.self), diff --git a/Sources/Citadel/SSHAuthenticationMethod.swift b/Sources/Citadel/SSHAuthenticationMethod.swift index 2647d81..8cdb6b0 100644 --- a/Sources/Citadel/SSHAuthenticationMethod.swift +++ b/Sources/Citadel/SSHAuthenticationMethod.swift @@ -39,7 +39,10 @@ public final class SSHAuthenticationMethod: NIOSSHClientUserAuthenticationDelega /// - Parameters: /// - username: The username to authenticate with. /// - privateKey: The private key to authenticate with. - public static func rsa(username: String, privateKey: Insecure.RSA.PrivateKey) -> SSHAuthenticationMethod { + public static func rsa( + username: String, + privateKey: RSA.PrivateKey + ) -> SSHAuthenticationMethod { return SSHAuthenticationMethod(username: username, offer: .privateKey(.init(privateKey: .init(custom: privateKey)))) } diff --git a/Sources/Citadel/SSHCert.swift b/Sources/Citadel/SSHCert.swift index 881862a..80b1b68 100644 --- a/Sources/Citadel/SSHCert.swift +++ b/Sources/Citadel/SSHCert.swift @@ -76,15 +76,15 @@ extension Curve25519.Signing.PrivateKey: OpenSSHPrivateKey { } } -extension Insecure.RSA.PublicKey: ByteBufferConvertible { +extension RSA.PublicKey: ByteBufferConvertible { func write(to buffer: inout ByteBuffer) { let _: Int = self.write(to: &buffer) } } -extension Insecure.RSA.PrivateKey: OpenSSHPrivateKey { - typealias PublicKey = Insecure.RSA.PublicKey - +extension RSA.PrivateKey: OpenSSHPrivateKey { + typealias PublicKey = RSA.PublicKey + static var publicKeyPrefix: String { "ssh-rsa" } static var privateKeyPrefix: String { "ssh-rsa" } static var keyType: OpenSSH.KeyType { .sshRSA } @@ -106,8 +106,8 @@ extension Insecure.RSA.PrivateKey: OpenSSHPrivateKey { /// - key: The OpenSSH private key string. /// - decryptionKey: The key to decrypt the private key with, if any. public convenience init(sshRsa key: String, decryptionKey: Data? = nil) throws { - let privateKey = try OpenSSH.PrivateKey.init(string: key, decryptionKey: decryptionKey).privateKey - let publicKey = privateKey.publicKey as! Insecure.RSA.PublicKey + let privateKey = try OpenSSH.PrivateKey.PrivateKey>.init(string: key, decryptionKey: decryptionKey).privateKey + let publicKey = privateKey.publicKey as! RSA.PublicKey // Copy, so that our values stored in `privateKey` aren't freed when exciting the initializers scope let modulus = CCryptoBoringSSL_BN_new()! diff --git a/Tests/CitadelTests/Citadel2Tests.swift b/Tests/CitadelTests/Citadel2Tests.swift index 8c077ec..1a2ae1d 100644 --- a/Tests/CitadelTests/Citadel2Tests.swift +++ b/Tests/CitadelTests/Citadel2Tests.swift @@ -194,7 +194,7 @@ final class Citadel2Tests: XCTestCase { let clientPublicKey = clientPrivateKey.publicKey let server = try await SSHServer.host( host: "0.0.0.0", - port: 2222, + port: 2223, hostKeys: [ .init(p521Key: P521.Signing.PrivateKey()) ], @@ -206,7 +206,7 @@ final class Citadel2Tests: XCTestCase { let client = try await SSHClient.connect( host: "127.0.0.1", - port: 2222, + port: 2223, authenticationMethod: SSHAuthenticationMethod.p521( username: "Joannis", privateKey: clientKey @@ -280,57 +280,4 @@ final class Citadel2Tests: XCTestCase { try await client.close() } - - @available(macOS 15.0, *) - func testStdinStream() async throws { - guard - let host = ProcessInfo.processInfo.environment["SSH_HOST"], - let _port = ProcessInfo.processInfo.environment["SSH_PORT"], - let port = Int(_port), - let username = ProcessInfo.processInfo.environment["SSH_USERNAME"], - let password = ProcessInfo.processInfo.environment["SSH_PASSWORD"] - else { - throw XCTSkip() - } - - let client = try await SSHClient.connect( - host: host, - port: port, - authenticationMethod: .passwordBased(username: username, password: password), - hostKeyValidator: .acceptAnything(), - reconnect: .never - ) - - try await client.withTTY { inbound, outbound in - try await outbound.write(ByteBuffer(string: "cat")) - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - var i = UInt8.min - for try await value in inbound { - switch value { - case .stdout(let value): - for byte in value.readableBytesView { - XCTAssertEqual(byte, i) - i = i &+ 1 - } - case .stderr: - XCTFail("Unexpected stderr") - } - } - } - - group.addTask { - for i: UInt8 in .min ... .max { - let value = ByteBufferAllocator().buffer(integer: i) - try await outbound.write(value) - } - } - - try await group.next() - group.cancelAll() - } - } - - try await client.close() - } }