@@ -2,6 +2,9 @@ import Foundation
2
2
import PromiseKit
3
3
import PMKFoundation
4
4
import Rainbow
5
+ import SRP
6
+ import Crypto
7
+ import CommonCrypto
5
8
6
9
public class Client {
7
10
private static let authTypes = [ " sa " , " hsa " , " non-sa " , " hsa2 " ]
@@ -20,6 +23,8 @@ public class Client {
20
23
case invalidHashcash
21
24
case missingSecurityCodeInfo
22
25
case accountUsesHardwareKey
26
+ case srpInvalidPublicKey
27
+ case srpError( String )
23
28
24
29
public var errorDescription : String ? {
25
30
switch self {
@@ -56,6 +61,97 @@ public class Client {
56
61
}
57
62
}
58
63
64
+ /// SRPLogin - Secure Remote Password
65
+ /// https://tools.ietf.org/html/rfc2945
66
+ /// Forked from https://github.com/adam-fowler/swift-srp that provides the algorithm
67
+ public func srpLogin( accountName: String , password: String ) -> Promise < Void > {
68
+ var serviceKey : String !
69
+ let client = SRPClient ( configuration: SRPConfiguration < SHA256 > ( . N2048) )
70
+ let clientKeys = client. generateKeys ( )
71
+ let a = clientKeys. public
72
+
73
+ // Get the Service Key needed from olympus session needed in headers
74
+ return firstly { ( ) -> Promise < ( data: Data , response: URLResponse ) > in
75
+ Current . network. dataTask ( with: URLRequest . itcServiceKey)
76
+ }
77
+ . then { ( data, _) -> Promise < ( serviceKey: String , hashcash: String ) > in
78
+ struct ServiceKeyResponse : Decodable {
79
+ let authServiceKey : String ?
80
+ }
81
+
82
+ let response = try JSONDecoder ( ) . decode ( ServiceKeyResponse . self, from: data)
83
+ serviceKey = response. authServiceKey
84
+
85
+ /// Load a hashcash of the account name
86
+ return self . loadHashcash ( accountName: accountName, serviceKey: serviceKey) . map { ( serviceKey, $0) }
87
+ }
88
+ . then { ( serviceKey, hashcash) -> Promise < ( serviceKey: String , hashcash: String , data: Data ) > in
89
+ /// Call the SRP /init endpoint to start the login
90
+ return Current . network. dataTask ( with: URLRequest . SRPInit ( serviceKey: serviceKey, a: Data ( a. bytes) . base64EncodedString ( ) , accountName: accountName) ) . map { ( serviceKey, hashcash, $0. data) }
91
+ }
92
+ . then { ( serviceKey, hashcash, data) -> Promise < ( data: Data , response: URLResponse ) > in
93
+ let srpInit = try JSONDecoder ( ) . decode ( ServerSRPInitResponse . self, from: data)
94
+
95
+ guard let decodedB = Data ( base64Encoded: srpInit. b) else {
96
+ throw Error . srpInvalidPublicKey
97
+ }
98
+ guard let decodedSalt = Data ( base64Encoded: srpInit. salt) else {
99
+ throw Error . srpInvalidPublicKey
100
+ }
101
+
102
+ let iterations = srpInit. iteration
103
+
104
+ do {
105
+ guard let encryptedPassword = self . pbkdf2 ( password: password, saltData: decodedSalt, keyByteCount: 32 , prf: CCPseudoRandomAlgorithm ( kCCPRFHmacAlgSHA256) , rounds: iterations) else {
106
+ throw Error . srpInvalidPublicKey
107
+ }
108
+
109
+ let sharedSecret = try client. calculateSharedSecret ( password: encryptedPassword, salt: [ UInt8] ( decodedSalt) , clientKeys: clientKeys, serverPublicKey: . init( [ UInt8] ( decodedB) ) )
110
+
111
+ let m1 = client. calculateClientProof ( username: accountName, salt: [ UInt8] ( decodedSalt) , clientPublicKey: a, serverPublicKey: . init( [ UInt8] ( decodedB) ) , sharedSecret: . init( sharedSecret. bytes) )
112
+ let m2 = client. calculateServerProof ( clientPublicKey: a, clientProof: m1, sharedSecret: . init( [ UInt8] ( sharedSecret. bytes) ) )
113
+
114
+ /// call the /complete endpoint passing in the hashcash, servicekey, and the calculated proof.
115
+ return Current . network. dataTask ( with: URLRequest . SRPComplete ( serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit. c, m1: Data ( m1) . base64EncodedString ( ) , m2: Data ( m2) . base64EncodedString ( ) ) )
116
+ } catch {
117
+ throw Error . srpError ( error. localizedDescription)
118
+ }
119
+ }
120
+ . then { ( data, response) -> Promise < Void > in
121
+ struct SignInResponse : Decodable {
122
+ let authType : String ?
123
+ let serviceErrors : [ ServiceError ] ?
124
+
125
+ struct ServiceError : Decodable , CustomStringConvertible {
126
+ let code : String
127
+ let message : String
128
+
129
+ var description : String {
130
+ return " \( code) : \( message) "
131
+ }
132
+ }
133
+ }
134
+
135
+ let httpResponse = response as! HTTPURLResponse
136
+ let responseBody = try JSONDecoder ( ) . decode ( SignInResponse . self, from: data)
137
+
138
+ switch httpResponse. statusCode {
139
+ case 200 :
140
+ return Current . network. dataTask ( with: URLRequest . olympusSession) . asVoid ( )
141
+ case 401 :
142
+ throw Error . invalidUsernameOrPassword ( username: accountName)
143
+ case 409 :
144
+ return self . handleTwoStepOrFactor ( data: data, response: response, serviceKey: serviceKey)
145
+ case 412 where Client . authTypes. contains ( responseBody. authType ?? " " ) :
146
+ throw Error . appleIDAndPrivacyAcknowledgementRequired
147
+ default :
148
+ throw Error . unexpectedSignInResponse ( statusCode: httpResponse. statusCode,
149
+ message: responseBody. serviceErrors? . map { $0. description } . joined ( separator: " , " ) )
150
+ }
151
+ }
152
+ }
153
+
154
+ @available ( * , deprecated, message: " Please use srpLogin " )
59
155
public func login( accountName: String , password: String ) -> Promise < Void > {
60
156
var serviceKey : String !
61
157
@@ -264,6 +360,43 @@ public class Client {
264
360
return . value( hashcash)
265
361
}
266
362
}
363
+
364
+ private func sha256( data : Data ) -> Data {
365
+ var hash = [ UInt8] ( repeating: 0 , count: Int ( CC_SHA256_DIGEST_LENGTH) )
366
+ data. withUnsafeBytes {
367
+ _ = CC_SHA256 ( $0. baseAddress, CC_LONG ( data. count) , & hash)
368
+ }
369
+ return Data ( hash)
370
+ }
371
+
372
+ private func pbkdf2( password: String , saltData: Data , keyByteCount: Int , prf: CCPseudoRandomAlgorithm , rounds: Int ) -> Data ? {
373
+ guard let passwordData = password. data ( using: . utf8) else { return nil }
374
+ let hashedPasswordData = sha256 ( data: passwordData)
375
+
376
+ var derivedKeyData = Data ( repeating: 0 , count: keyByteCount)
377
+ let derivedCount = derivedKeyData. count
378
+ let derivationStatus : Int32 = derivedKeyData. withUnsafeMutableBytes { derivedKeyBytes in
379
+ let keyBuffer : UnsafeMutablePointer < UInt8 > =
380
+ derivedKeyBytes. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
381
+ return saltData. withUnsafeBytes { saltBytes -> Int32 in
382
+ let saltBuffer : UnsafePointer < UInt8 > = saltBytes. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
383
+ return hashedPasswordData. withUnsafeBytes { hashedPasswordBytes -> Int32 in
384
+ let passwordBuffer : UnsafePointer < UInt8 > = hashedPasswordBytes. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
385
+ return CCKeyDerivationPBKDF (
386
+ CCPBKDFAlgorithm ( kCCPBKDF2) ,
387
+ passwordBuffer,
388
+ hashedPasswordData. count,
389
+ saltBuffer,
390
+ saltData. count,
391
+ prf,
392
+ UInt32 ( rounds) ,
393
+ keyBuffer,
394
+ derivedCount)
395
+ }
396
+ }
397
+ }
398
+ return derivationStatus == kCCSuccess ? derivedKeyData : nil
399
+ }
267
400
}
268
401
269
402
public extension Promise where T == ( data: Data , response: URLResponse ) {
@@ -363,3 +496,10 @@ enum SecurityCode {
363
496
}
364
497
}
365
498
}
499
+
500
+ public struct ServerSRPInitResponse : Decodable {
501
+ let iteration : Int
502
+ let salt : String
503
+ let b : String
504
+ let c : String
505
+ }
0 commit comments