@@ -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,90 @@ public class Client {
56
61
}
57
62
}
58
63
64
+ public func srpLogin( accountName: String , password: String ) -> Promise < Void > {
65
+ var serviceKey : String !
66
+ let client = SRPClient ( configuration: SRPConfiguration < SHA256 > ( . N2048) )
67
+ let clientKeys = client. generateKeys ( )
68
+ let a = clientKeys. public
69
+
70
+ return firstly { ( ) -> Promise < ( data: Data , response: URLResponse ) > in
71
+ Current . network. dataTask ( with: URLRequest . itcServiceKey)
72
+ }
73
+ . then { ( data, _) -> Promise < ( serviceKey: String , hashcash: String ) > in
74
+ struct ServiceKeyResponse : Decodable {
75
+ let authServiceKey : String ?
76
+ }
77
+
78
+ let response = try JSONDecoder ( ) . decode ( ServiceKeyResponse . self, from: data)
79
+ serviceKey = response. authServiceKey
80
+
81
+ return self . loadHashcash ( accountName: accountName, serviceKey: serviceKey) . map { ( serviceKey, $0) }
82
+ }
83
+ . then { ( serviceKey, hashcash) -> Promise < ( serviceKey: String , hashcash: String , data: Data ) > in
84
+ return Current . network. dataTask ( with: URLRequest . SRPInit ( serviceKey: serviceKey, a: Data ( a. bytes) . base64EncodedString ( ) , accountName: accountName) ) . map { ( serviceKey, hashcash, $0. data) }
85
+ }
86
+ . then { ( serviceKey, hashcash, data) -> Promise < ( data: Data , response: URLResponse ) > in
87
+ let srpInit = try JSONDecoder ( ) . decode ( ServerSRPInitResponse . self, from: data)
88
+
89
+ guard let decodedB = Data ( base64Encoded: srpInit. b) else {
90
+ throw Error . srpInvalidPublicKey
91
+ }
92
+ guard let decodedSalt = Data ( base64Encoded: srpInit. salt) else {
93
+ throw Error . srpInvalidPublicKey
94
+ }
95
+
96
+ let iterations = srpInit. iteration
97
+
98
+ do {
99
+ guard let encryptedPassword = self . pbkdf2 ( password: password, saltData: decodedSalt, keyByteCount: 32 , prf: CCPseudoRandomAlgorithm ( kCCPRFHmacAlgSHA256) , rounds: iterations) else {
100
+ throw Error . srpInvalidPublicKey
101
+ }
102
+
103
+ let sharedSecret = try client. calculateSharedSecret ( password: encryptedPassword, salt: [ UInt8] ( decodedSalt) , clientKeys: clientKeys, serverPublicKey: . init( [ UInt8] ( decodedB) ) )
104
+
105
+ let m1 = client. calculateClientProof ( username: accountName, salt: [ UInt8] ( decodedSalt) , clientPublicKey: a, serverPublicKey: . init( [ UInt8] ( decodedB) ) , sharedSecret: . init( sharedSecret. bytes) )
106
+ let m2 = client. calculateServerProof ( clientPublicKey: a, clientProof: m1, sharedSecret: . init( [ UInt8] ( sharedSecret. bytes) ) )
107
+
108
+ return Current . network. dataTask ( with: URLRequest . SRPComplete ( serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit. c, m1: Data ( m1) . base64EncodedString ( ) , m2: Data ( m2) . base64EncodedString ( ) ) )
109
+ } catch {
110
+ throw Error . srpError ( error. localizedDescription)
111
+ }
112
+ }
113
+ . then { ( data, response) -> Promise < Void > in
114
+ struct SignInResponse : Decodable {
115
+ let authType : String ?
116
+ let serviceErrors : [ ServiceError ] ?
117
+
118
+ struct ServiceError : Decodable , CustomStringConvertible {
119
+ let code : String
120
+ let message : String
121
+
122
+ var description : String {
123
+ return " \( code) : \( message) "
124
+ }
125
+ }
126
+ }
127
+
128
+ let httpResponse = response as! HTTPURLResponse
129
+ let responseBody = try JSONDecoder ( ) . decode ( SignInResponse . self, from: data)
130
+
131
+ switch httpResponse. statusCode {
132
+ case 200 :
133
+ return Current . network. dataTask ( with: URLRequest . olympusSession) . asVoid ( )
134
+ case 401 :
135
+ throw Error . invalidUsernameOrPassword ( username: accountName)
136
+ case 409 :
137
+ return self . handleTwoStepOrFactor ( data: data, response: response, serviceKey: serviceKey)
138
+ case 412 where Client . authTypes. contains ( responseBody. authType ?? " " ) :
139
+ throw Error . appleIDAndPrivacyAcknowledgementRequired
140
+ default :
141
+ throw Error . unexpectedSignInResponse ( statusCode: httpResponse. statusCode,
142
+ message: responseBody. serviceErrors? . map { $0. description } . joined ( separator: " , " ) )
143
+ }
144
+ }
145
+ }
146
+
147
+ @available ( * , deprecated, message: " Please use srpLogin " )
59
148
public func login( accountName: String , password: String ) -> Promise < Void > {
60
149
var serviceKey : String !
61
150
@@ -264,6 +353,43 @@ public class Client {
264
353
return . value( hashcash)
265
354
}
266
355
}
356
+
357
+ private func sha256( data : Data ) -> Data {
358
+ var hash = [ UInt8] ( repeating: 0 , count: Int ( CC_SHA256_DIGEST_LENGTH) )
359
+ data. withUnsafeBytes {
360
+ _ = CC_SHA256 ( $0. baseAddress, CC_LONG ( data. count) , & hash)
361
+ }
362
+ return Data ( hash)
363
+ }
364
+
365
+ private func pbkdf2( password: String , saltData: Data , keyByteCount: Int , prf: CCPseudoRandomAlgorithm , rounds: Int ) -> Data ? {
366
+ guard let passwordData = password. data ( using: . utf8) else { return nil }
367
+ let hashedPasswordData = sha256 ( data: passwordData)
368
+
369
+ var derivedKeyData = Data ( repeating: 0 , count: keyByteCount)
370
+ let derivedCount = derivedKeyData. count
371
+ let derivationStatus : Int32 = derivedKeyData. withUnsafeMutableBytes { derivedKeyBytes in
372
+ let keyBuffer : UnsafeMutablePointer < UInt8 > =
373
+ derivedKeyBytes. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
374
+ return saltData. withUnsafeBytes { saltBytes -> Int32 in
375
+ let saltBuffer : UnsafePointer < UInt8 > = saltBytes. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
376
+ return hashedPasswordData. withUnsafeBytes { hashedPasswordBytes -> Int32 in
377
+ let passwordBuffer : UnsafePointer < UInt8 > = hashedPasswordBytes. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
378
+ return CCKeyDerivationPBKDF (
379
+ CCPBKDFAlgorithm ( kCCPBKDF2) ,
380
+ passwordBuffer,
381
+ hashedPasswordData. count,
382
+ saltBuffer,
383
+ saltData. count,
384
+ prf,
385
+ UInt32 ( rounds) ,
386
+ keyBuffer,
387
+ derivedCount)
388
+ }
389
+ }
390
+ }
391
+ return derivationStatus == kCCSuccess ? derivedKeyData : nil
392
+ }
267
393
}
268
394
269
395
public extension Promise where T == ( data: Data , response: URLResponse ) {
@@ -363,3 +489,10 @@ enum SecurityCode {
363
489
}
364
490
}
365
491
}
492
+
493
+ public struct ServerSRPInitResponse : Decodable {
494
+ let iteration : Int
495
+ let salt : String
496
+ let b : String
497
+ let c : String
498
+ }
0 commit comments