@@ -2,6 +2,9 @@ import Foundation
22import PromiseKit
33import PMKFoundation
44import Rainbow
5+ import SRP
6+ import Crypto
7+ import CommonCrypto
58
69public class Client {
710 private static let authTypes = [ " sa " , " hsa " , " non-sa " , " hsa2 " ]
@@ -20,6 +23,8 @@ public class Client {
2023 case invalidHashcash
2124 case missingSecurityCodeInfo
2225 case accountUsesHardwareKey
26+ case srpInvalidPublicKey
27+ case srpError( String )
2328
2429 public var errorDescription : String ? {
2530 switch self {
@@ -56,6 +61,90 @@ public class Client {
5661 }
5762 }
5863
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 " )
59148 public func login( accountName: String , password: String ) -> Promise < Void > {
60149 var serviceKey : String !
61150
@@ -264,6 +353,43 @@ public class Client {
264353 return . value( hashcash)
265354 }
266355 }
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+ }
267393}
268394
269395public extension Promise where T == ( data: Data , response: URLResponse ) {
@@ -363,3 +489,10 @@ enum SecurityCode {
363489 }
364490 }
365491}
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