@@ -42,218 +42,9 @@ let dh14p: [UInt8] = [
42
42
0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF
43
43
]
44
44
45
- public struct DiffieHellmanGroup14Sha1 : NIOSSHKeyExchangeAlgorithmProtocol {
46
- public static let keyExchangeInitMessageId : UInt8 = 30
47
- public static let keyExchangeReplyMessageId : UInt8 = 31
48
-
49
- public static let keyExchangeAlgorithmNames : [ Substring ] = [ " diffie-hellman-group14-sha1 " ]
50
-
51
- private var previousSessionIdentifier : ByteBuffer ?
52
- private var ourRole : SSHConnectionRole
53
- private var theirKey : Insecure . RSA . PublicKey ?
54
- private var sharedSecret : Data ?
55
- public let ourKey : Insecure . RSA . PrivateKey
56
- public static var ourKey : Insecure . RSA . PrivateKey ?
57
-
58
- private struct _KeyExchangeResult {
59
- var sessionID : ByteBuffer
60
- var exchangeHash : Insecure . SHA1 . Digest
61
- var keys : NIOSSHSessionKeys
62
- }
63
-
64
- public init ( ourRole: SSHConnectionRole , previousSessionIdentifier: ByteBuffer ? ) {
65
- self . ourRole = ourRole
66
- self . previousSessionIdentifier = previousSessionIdentifier
67
- self . ourKey = Self . ourKey ?? Insecure . RSA. PrivateKey ( )
68
- }
69
-
70
- public func initiateKeyExchangeClientSide( allocator: ByteBufferAllocator ) -> ByteBuffer {
71
- var buffer = allocator. buffer ( capacity: 256 )
72
-
73
- buffer. writeBignum ( ourKey. _publicKey. modulus)
74
- return buffer
75
- }
76
-
77
- public mutating func completeKeyExchangeServerSide(
78
- clientKeyExchangeMessage message: ByteBuffer ,
79
- serverHostKey: NIOSSHPrivateKey ,
80
- initialExchangeBytes: inout ByteBuffer ,
81
- allocator: ByteBufferAllocator ,
82
- expectedKeySizes: ExpectedKeySizes
83
- ) throws -> ( KeyExchangeResult , NIOSSHKeyExchangeServerReply ) {
84
- throw CitadelError . unsupported
85
- }
86
-
87
- public mutating func receiveServerKeyExchangePayload( serverKeyExchangeMessage: NIOSSHKeyExchangeServerReply , initialExchangeBytes: inout ByteBuffer , allocator: ByteBufferAllocator , expectedKeySizes: ExpectedKeySizes ) throws -> KeyExchangeResult {
88
- let kexResult = try self . finalizeKeyExchange ( theirKeyBytes: serverKeyExchangeMessage. publicKey,
89
- initialExchangeBytes: & initialExchangeBytes,
90
- serverHostKey: serverKeyExchangeMessage. hostKey,
91
- allocator: allocator,
92
- expectedKeySizes: expectedKeySizes)
93
-
94
- // We can now verify signature over the exchange hash.
95
- guard serverKeyExchangeMessage. hostKey. isValidSignature ( serverKeyExchangeMessage. signature, for: kexResult. exchangeHash) else {
96
- throw CitadelError . invalidSignature
97
- }
98
-
99
- // Great, all done here.
100
- return KeyExchangeResult (
101
- sessionID: kexResult. sessionID,
102
- keys: kexResult. keys
103
- )
104
- }
105
-
106
- private mutating func finalizeKeyExchange( theirKeyBytes f: ByteBuffer ,
107
- initialExchangeBytes: inout ByteBuffer ,
108
- serverHostKey: NIOSSHPublicKey ,
109
- allocator: ByteBufferAllocator ,
110
- expectedKeySizes: ExpectedKeySizes ) throws -> _KeyExchangeResult {
111
- let f = f. getBytes ( at: 0 , length: f. readableBytes) !
112
-
113
- let serverPublicKey = CCryptoBoringSSL_BN_bin2bn ( f, f. count, nil ) !
114
- defer { CCryptoBoringSSL_BN_free ( serverPublicKey) }
115
- let secret = CCryptoBoringSSL_BN_new ( ) !
116
- let serverHostKeyBN = CCryptoBoringSSL_BN_new ( )
117
- defer { CCryptoBoringSSL_BN_free ( serverHostKeyBN) }
118
-
119
- var buffer = ByteBuffer ( )
120
- serverHostKey. write ( to: & buffer)
121
- buffer. readWithUnsafeReadableBytes { buffer in
122
- let buffer = buffer. bindMemory ( to: UInt8 . self)
123
- CCryptoBoringSSL_BN_bin2bn ( buffer. baseAddress!, buffer. count, serverHostKeyBN)
124
- return buffer. count
125
- }
126
-
127
- let ctx = CCryptoBoringSSL_BN_CTX_new ( )
128
- defer { CCryptoBoringSSL_BN_CTX_free ( ctx) }
129
-
130
- let group = CCryptoBoringSSL_BN_bin2bn ( dh14p, dh14p. count, nil )
131
- defer { CCryptoBoringSSL_BN_free ( group) }
132
-
133
- guard CCryptoBoringSSL_BN_mod_exp (
134
- secret,
135
- serverPublicKey,
136
- ourKey. privateExponent,
137
- group,
138
- ctx
139
- ) == 1 else {
140
- throw CitadelError . cryptographicError
141
- }
142
-
143
- var sharedSecret = [ UInt8] ( )
144
- sharedSecret. reserveCapacity ( Int ( CCryptoBoringSSL_BN_num_bytes ( secret) ) )
145
- CCryptoBoringSSL_BN_bn2bin ( secret, & sharedSecret)
146
-
147
- self . sharedSecret = Data ( sharedSecret)
148
-
149
- func hexEncodedString( array: [ UInt8 ] ) -> String {
150
- return array. map { String ( format: " %02hhx " , $0) } . joined ( )
151
- }
152
-
153
- //var offset = initialExchangeBytes.writerIndex
154
- initialExchangeBytes. writeCompositeSSHString {
155
- serverHostKey. write ( to: & $0)
156
- }
157
-
158
- //offset = initialExchangeBytes.writerIndex
159
- switch self . ourRole {
160
- case . client:
161
- initialExchangeBytes. writeMPBignum ( ourKey. _publicKey. modulus)
162
- //offset = initialExchangeBytes.writerIndex
163
- initialExchangeBytes. writeMPBignum ( serverPublicKey)
164
- case . server:
165
- initialExchangeBytes. writeMPBignum ( serverPublicKey)
166
- initialExchangeBytes. writeMPBignum ( ourKey. _publicKey. modulus)
167
- }
168
-
169
- // Ok, now finalize the exchange hash. If we don't have a previous session identifier at this stage, we do now!
170
- initialExchangeBytes. writeMPBignum ( secret)
171
-
172
- let exchangeHash = Insecure . SHA1. hash ( data: initialExchangeBytes. readableBytesView)
173
-
174
- let sessionID : ByteBuffer
175
- if let previousSessionIdentifier = self . previousSessionIdentifier {
176
- sessionID = previousSessionIdentifier
177
- } else {
178
- var hashBytes = allocator. buffer ( capacity: Insecure . SHA1. byteCount)
179
- hashBytes. writeContiguousBytes ( exchangeHash)
180
- sessionID = hashBytes
181
- }
182
-
183
- // Now we can generate the keys.
184
- let keys = self . generateKeys ( secret: secret, exchangeHash: exchangeHash, sessionID: sessionID, expectedKeySizes: expectedKeySizes)
185
-
186
- // All done!
187
- return _KeyExchangeResult ( sessionID: sessionID, exchangeHash: exchangeHash, keys: keys)
188
- }
189
-
190
- private func generateKeys( secret: UnsafeMutablePointer < BIGNUM > , exchangeHash: Insecure . SHA1 . Digest , sessionID: ByteBuffer , expectedKeySizes: ExpectedKeySizes ) -> NIOSSHSessionKeys {
191
- // 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
192
- // 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
193
- // think the risk is too bad.
194
- //
195
- // We generate these as follows:
196
- //
197
- // - Initial IV client to server: HASH(K || H || "A" || session_id)
198
- // (Here K is encoded as mpint and "A" as byte and session_id as raw
199
- // data. "A" means the single character A, ASCII 65).
200
- // - Initial IV server to client: HASH(K || H || "B" || session_id)
201
- // - Encryption key client to server: HASH(K || H || "C" || session_id)
202
- // - Encryption key server to client: HASH(K || H || "D" || session_id)
203
- // - Integrity key client to server: HASH(K || H || "E" || session_id)
204
- // - Integrity key server to client: HASH(K || H || "F" || session_id)
205
-
206
- func calculateSha1SymmetricKey( letter: UInt8 , expectedKeySize size: Int ) -> SymmetricKey {
207
- SymmetricKey ( data: calculateSha1Key ( letter: letter, expectedKeySize: size) )
208
- }
209
-
210
- func calculateSha1Key( letter: UInt8 , expectedKeySize size: Int ) -> [ UInt8 ] {
211
- var result = [ UInt8] ( )
212
- var hashInput = ByteBuffer ( )
213
-
214
- while result. count < size {
215
- hashInput. moveWriterIndex ( to: 0 )
216
- hashInput. writeMPBignum ( secret)
217
- hashInput. writeBytes ( exchangeHash)
218
-
219
- if !result. isEmpty {
220
- hashInput. writeBytes ( result)
221
- } else {
222
- hashInput. writeInteger ( letter)
223
- hashInput. writeBytes ( sessionID. readableBytesView)
224
- }
225
-
226
- result += Insecure . SHA1. hash ( data: hashInput. readableBytesView)
227
- }
228
-
229
- result. removeLast ( result. count - size)
230
- return result
231
- }
232
-
233
- switch self . ourRole {
234
- case . client:
235
- return NIOSSHSessionKeys (
236
- initialInboundIV: calculateSha1Key ( letter: UInt8 ( ascii: " B " ) , expectedKeySize: expectedKeySizes. ivSize) ,
237
- initialOutboundIV: calculateSha1Key ( letter: UInt8 ( ascii: " A " ) , expectedKeySize: expectedKeySizes. ivSize) ,
238
- inboundEncryptionKey: calculateSha1SymmetricKey ( letter: UInt8 ( ascii: " D " ) , expectedKeySize: expectedKeySizes. encryptionKeySize) ,
239
- outboundEncryptionKey: calculateSha1SymmetricKey ( letter: UInt8 ( ascii: " C " ) , expectedKeySize: expectedKeySizes. encryptionKeySize) ,
240
- inboundMACKey: calculateSha1SymmetricKey ( letter: UInt8 ( ascii: " F " ) , expectedKeySize: expectedKeySizes. macKeySize) ,
241
- outboundMACKey: calculateSha1SymmetricKey ( letter: UInt8 ( ascii: " E " ) , expectedKeySize: expectedKeySizes. macKeySize) )
242
- case . server:
243
- return NIOSSHSessionKeys (
244
- initialInboundIV: calculateSha1Key ( letter: UInt8 ( ascii: " A " ) , expectedKeySize: expectedKeySizes. ivSize) ,
245
- initialOutboundIV: calculateSha1Key ( letter: UInt8 ( ascii: " B " ) , expectedKeySize: expectedKeySizes. ivSize) ,
246
- inboundEncryptionKey: calculateSha1SymmetricKey ( letter: UInt8 ( ascii: " C " ) , expectedKeySize: expectedKeySizes. encryptionKeySize) ,
247
- outboundEncryptionKey: calculateSha1SymmetricKey ( letter: UInt8 ( ascii: " D " ) , expectedKeySize: expectedKeySizes. encryptionKeySize) ,
248
- inboundMACKey: calculateSha1SymmetricKey ( letter: UInt8 ( ascii: " E " ) , expectedKeySize: expectedKeySizes. macKeySize) ,
249
- outboundMACKey: calculateSha1SymmetricKey ( letter: UInt8 ( ascii: " F " ) , expectedKeySize: expectedKeySizes. macKeySize) )
250
- }
251
- }
252
- }
253
-
254
45
extension SymmetricKey {
255
46
/// Creates a symmetric key by truncating a given digest.
256
- fileprivate static func truncatingDigest< D: Digest > ( _ digest: D , length: Int ) -> SymmetricKey {
47
+ static func truncatingDigest< D: Digest > ( _ digest: D , length: Int ) -> SymmetricKey {
257
48
assert ( length <= D . byteCount)
258
49
return digest. withUnsafeBytes { bodyPtr in
259
50
SymmetricKey ( data: UnsafeRawBufferPointer ( rebasing: bodyPtr. prefix ( length) ) )
@@ -262,7 +53,7 @@ extension SymmetricKey {
262
53
}
263
54
264
55
extension HashFunction {
265
- fileprivate mutating func update( byte: UInt8 ) {
56
+ mutating func update( byte: UInt8 ) {
266
57
withUnsafeBytes ( of: byte) { bytePtr in
267
58
assert ( bytePtr. count == 1 , " Why is this 8 bit integer so large? " )
268
59
self . update ( bufferPointer: bytePtr)
@@ -299,7 +90,7 @@ extension ByteBuffer {
299
90
var size = ( bignum. bitWidth + 7 ) / 8
300
91
writeWithUnsafeMutableBytes ( minimumWritableBytes: Int ( size + 1 ) ) { buffer in
301
92
let buffer = buffer. bindMemory ( to: UInt8 . self)
302
-
93
+
303
94
buffer. baseAddress!. pointee = 0
304
95
305
96
let serialized = Array ( bignum. serialize ( ) )
@@ -356,7 +147,7 @@ extension HashFunction {
356
147
fileprivate mutating func updateAsMPInt( sharedSecret: Data ) {
357
148
sharedSecret. withUnsafeBytes { secretBytesPtr in
358
149
var secretBytesPtr = secretBytesPtr [ ... ]
359
-
150
+
360
151
// Here we treat this shared secret as an mpint by just treating these bytes as an unsigned
361
152
// fixed-length integer in network byte order, as suggested by draft-ietf-curdle-ssh-curves-08,
362
153
// and "prepending" it with a 32-bit length field. Note that instead of prepending, we just make
@@ -385,11 +176,11 @@ extension HashFunction {
385
176
}
386
177
let numberOfZeroBytes = firstNonZeroByteIndex - secretBytesPtr. startIndex
387
178
let topBitOfFirstNonZeroByteIsSet = secretBytesPtr [ firstNonZeroByteIndex] & 0x80 == 0x80
388
-
179
+
389
180
// We need to hash a few extra bytes: specifically, we need a 4 byte length in network byte order,
390
181
// and maybe a fifth as a zero byte.
391
182
var lengthHelper = SharedSecretLengthHelper ( )
392
-
183
+
393
184
switch ( numberOfZeroBytes, topBitOfFirstNonZeroByteIsSet) {
394
185
case ( 0 , false ) :
395
186
// This is the easy case, we just treat the whole thing as the body.
@@ -410,7 +201,7 @@ extension HashFunction {
410
201
lengthHelper. length = UInt8 ( secretBytesPtr. count)
411
202
lengthHelper. useExtraZeroByte = false
412
203
}
413
-
204
+
414
205
// Now generate the hash.
415
206
lengthHelper. update ( hasher: & self )
416
207
self . update ( bufferPointer: UnsafeRawBufferPointer ( rebasing: secretBytesPtr) )
@@ -429,10 +220,10 @@ private struct SharedSecretLengthHelper {
429
220
// 32 bytes long (before the mpint transformation), we only ever actually need to modify one of these bytes:
430
221
// the 4th.
431
222
private var backingBytes = ( UInt8 ( 0 ) , UInt8 ( 0 ) , UInt8 ( 0 ) , UInt8 ( 0 ) , UInt8 ( 0 ) )
432
-
223
+
433
224
/// Whether we should hash an extra zero byte.
434
225
var useExtraZeroByte : Bool = false
435
-
226
+
436
227
/// The length to encode.
437
228
var length : UInt8 {
438
229
get {
@@ -442,27 +233,27 @@ private struct SharedSecretLengthHelper {
442
233
self . backingBytes. 3 = newValue
443
234
}
444
235
}
445
-
236
+
446
237
// Remove the elementwise initializer.
447
238
init ( ) { }
448
-
239
+
449
240
func update< Hasher: HashFunction > ( hasher: inout Hasher ) {
450
241
withUnsafeBytes ( of: self . backingBytes) { bytesPtr in
451
242
precondition ( bytesPtr. count == 5 )
452
-
243
+
453
244
let bytesToHash : UnsafeRawBufferPointer
454
245
if self . useExtraZeroByte {
455
246
bytesToHash = bytesPtr
456
247
} else {
457
248
bytesToHash = UnsafeRawBufferPointer ( rebasing: bytesPtr. prefix ( 4 ) )
458
249
}
459
-
250
+
460
251
hasher. update ( bufferPointer: bytesToHash)
461
252
}
462
253
}
463
254
}
464
255
465
- fileprivate extension ByteBuffer {
256
+ extension ByteBuffer {
466
257
/// Many functions in SSH write composite data structures into an SSH string. This is a tricky thing to express
467
258
/// without confining all of those functions to writing strings directly, which is pretty uncool. Instead, we can
468
259
/// wrap the body into this function, which will take the returned total length and use that as the string length.
@@ -471,7 +262,7 @@ fileprivate extension ByteBuffer {
471
262
// Reserve 4 bytes for the length.
472
263
let originalWriterIndex = self . writerIndex
473
264
self . moveWriterIndex ( forwardBy: 4 )
474
-
265
+
475
266
var writtenLength : Int
476
267
do {
477
268
writtenLength = try compositeFunction ( & self )
@@ -480,7 +271,7 @@ fileprivate extension ByteBuffer {
480
271
self . moveWriterIndex ( to: originalWriterIndex)
481
272
throw error
482
273
}
483
-
274
+
484
275
// Ok, now we're going to write the length.
485
276
writtenLength += self . setInteger ( UInt32 ( writtenLength) , at: originalWriterIndex)
486
277
return writtenLength
0 commit comments