11import Foundation
22import Combine
3+ import SRP
4+ import Crypto
5+ import CommonCrypto
36
47public class Client {
58 private static let authTypes = [ " sa " , " hsa " , " non-sa " , " hsa2 " ]
@@ -8,6 +11,144 @@ public class Client {
811
912 // MARK: - Login
1013
14+ public func srpLogin( accountName: String , password: String ) -> AnyPublisher < AuthenticationState , Swift . Error > {
15+ var serviceKey : String !
16+
17+ let config = SRPConfiguration < SHA256 > ( . N2048)
18+ let client = SRPClient ( configuration: config)
19+ let clientKeys = client. generateKeys ( )
20+
21+ return Current . network. dataTask ( with: URLRequest . itcServiceKey)
22+ . map ( \. data)
23+ . decode ( type: ServiceKeyResponse . self, decoder: JSONDecoder ( ) )
24+ . flatMap { serviceKeyResponse -> AnyPublisher < ( String , String ) , Swift . Error > in
25+ serviceKey = serviceKeyResponse. authServiceKey
26+
27+ // Fixes issue https://github.com/RobotsAndPencils/XcodesApp/issues/360
28+ // On 2023-02-23, Apple added a custom implementation of hashcash to their auth flow
29+ // Without this addition, Apple ID's would get set to locked
30+ return self . loadHashcash ( accountName: accountName, serviceKey: serviceKey)
31+ . map { return ( serviceKey, $0) }
32+ . eraseToAnyPublisher ( )
33+ }
34+ . flatMap { ( serviceKey, hashcash) -> AnyPublisher < ( String , String , ServerSRPInitResponse ) , Swift . Error > in
35+
36+ return Current . network. dataTask ( with: URLRequest . SRPInit ( serviceKey: serviceKey, a: clientKeys. private. hex, accountName: accountName) )
37+ . map ( \. data)
38+ . decode ( type: ServerSRPInitResponse . self, decoder: JSONDecoder ( ) )
39+ . map { return ( serviceKey, hashcash, $0) }
40+ . eraseToAnyPublisher ( )
41+ }
42+ . flatMap { ( serviceKey, hashcash, srpInit) -> AnyPublisher < URLSession . DataTaskPublisher . Output , Swift . Error > in
43+
44+ guard let decodedB = Data ( base64Encoded: srpInit. b) else {
45+ return Fail ( error: AuthenticationError . srpInvalidPublicKey)
46+ . eraseToAnyPublisher ( )
47+ }
48+
49+ guard let decodedSalt = Data ( base64Encoded: srpInit. salt) else {
50+ return Fail ( error: AuthenticationError . srpInvalidPublicKey)
51+ . eraseToAnyPublisher ( )
52+ }
53+
54+ let iterations = srpInit. iteration
55+ let serverPublic = SRPKey ( [ UInt8] ( decodedB) )
56+
57+ guard let encryptedPassword = self . pbkdf2 ( password: password, saltData: decodedSalt, keyByteCount: 32 , prf: CCPseudoRandomAlgorithm ( kCCPRFHmacAlgSHA256) , rounds: iterations) else {
58+ return Fail ( error: AuthenticationError . srpInvalidPublicKey)
59+ . eraseToAnyPublisher ( )
60+ }
61+
62+
63+ let encryptedPasswordArray = encryptedPassword. hexEncodedString ( )
64+
65+ print ( " EncryptedPassword: \( encryptedPasswordArray) " )
66+ print ( " EncryptedPassword: \( [ UInt8] ( encryptedPassword) ) " )
67+ do {
68+
69+ // this calculates "S"
70+ let clientSharedSecret = try client. calculateSharedSecret (
71+ encryptedPassword: encryptedPasswordArray,
72+ salt: [ UInt8] ( decodedSalt) ,
73+ clientKeys: clientKeys,
74+ serverPublicKey: serverPublic
75+ )
76+ print ( " SharedSecret: \( clientSharedSecret) " )
77+
78+ let m1 = client. calculateClientProof (
79+ username: accountName,
80+ salt: [ UInt8] ( decodedSalt) ,
81+ clientPublicKey: clientKeys. public,
82+ serverPublicKey: serverPublic,
83+ sharedSecret: clientSharedSecret
84+ )
85+
86+ let m2 = client. serverProof ( clientProof: m1, clientKeys: clientKeys, sharedSecret: clientSharedSecret)
87+
88+
89+ print ( " M1: \( Data ( m1) . base64EncodedString ( ) ) " )
90+ print ( " M2: \( Data ( m2) . base64EncodedString ( ) ) " )
91+
92+ return Current . network. dataTask ( with: URLRequest . SRPComplete ( serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit. c, m1: Data ( m1) . base64EncodedString ( ) , m2: Data ( m2) . base64EncodedString ( ) ) )
93+ . mapError { $0 as Swift . Error }
94+ . eraseToAnyPublisher ( )
95+ } catch {
96+ print ( " Error: calculateSharedSecret \( error) " )
97+ return Fail ( error: AuthenticationError . srpInvalidPublicKey)
98+ . eraseToAnyPublisher ( )
99+ }
100+ }
101+ . flatMap { result -> AnyPublisher < AuthenticationState , Swift . Error > in
102+ let ( data, response) = result
103+ return Just ( data)
104+ . decode ( type: SignInResponse . self, decoder: JSONDecoder ( ) )
105+ . flatMap { responseBody -> AnyPublisher < AuthenticationState , Swift . Error > in
106+ let httpResponse = response as! HTTPURLResponse
107+
108+ switch httpResponse. statusCode {
109+ case 200 :
110+ return Current . network. dataTask ( with: URLRequest . olympusSession)
111+ . map { _ in AuthenticationState . authenticated }
112+ . mapError { $0 as Swift . Error }
113+ . eraseToAnyPublisher ( )
114+ case 401 :
115+ return Fail ( error: AuthenticationError . invalidUsernameOrPassword ( username: accountName) )
116+ . eraseToAnyPublisher ( )
117+ case 403 :
118+ let errorMessage = responseBody. serviceErrors? . first? . description. replacingOccurrences ( of: " -20209: " , with: " " ) ?? " "
119+ return Fail ( error: AuthenticationError . accountLocked ( errorMessage) )
120+ . eraseToAnyPublisher ( )
121+ case 409 :
122+ return self . handleTwoStepOrFactor ( data: data, response: response, serviceKey: serviceKey)
123+ case 412 where Client . authTypes. contains ( responseBody. authType ?? " " ) :
124+ return Fail ( error: AuthenticationError . appleIDAndPrivacyAcknowledgementRequired)
125+ . eraseToAnyPublisher ( )
126+ default :
127+ return Fail ( error: AuthenticationError . unexpectedSignInResponse ( statusCode: httpResponse. statusCode,
128+ message: responseBody. serviceErrors? . map { $0. description } . joined ( separator: " , " ) ) )
129+ . eraseToAnyPublisher ( )
130+ }
131+ }
132+ . eraseToAnyPublisher ( )
133+ }
134+ // .map(\.data)
135+ // .decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder())
136+ //
137+ //
138+ //
139+ // .flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in
140+ // return ("")
141+ // }
142+ // .flatMap { serverResponse -> AnyPublisher<AuthenticationState, Error> in
143+ // print(serverResponse)
144+ // return Fail(error: AuthenticationError.accountUsesTwoStepAuthentication)
145+ // .eraseToAnyPublisher()
146+ // }
147+ . mapError { $0 as Swift . Error }
148+ . eraseToAnyPublisher ( )
149+ }
150+
151+
11152 public func login( accountName: String , password: String ) -> AnyPublisher < AuthenticationState , Swift . Error > {
12153 var serviceKey : String !
13154
@@ -257,6 +398,32 @@ public class Client {
257398 . mapError { $0 as Error }
258399 . eraseToAnyPublisher ( )
259400 }
401+
402+ private func pbkdf2( password: String , saltData: Data , keyByteCount: Int , prf: CCPseudoRandomAlgorithm , rounds: Int ) -> Data ? {
403+ guard let passwordData = password. data ( using: . utf8) else { return nil }
404+
405+ var derivedKeyData = Data ( repeating: 0 , count: keyByteCount)
406+ let derivedCount = derivedKeyData. count
407+ let derivationStatus : Int32 = derivedKeyData. withUnsafeMutableBytes { derivedKeyBytes in
408+ let keyBuffer : UnsafeMutablePointer < UInt8 > =
409+ derivedKeyBytes. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
410+ return saltData. withUnsafeBytes { saltBytes -> Int32 in
411+ let saltBuffer : UnsafePointer < UInt8 > = saltBytes. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
412+ return CCKeyDerivationPBKDF (
413+ CCPBKDFAlgorithm ( kCCPBKDF2) ,
414+ password,
415+ passwordData. count,
416+ saltBuffer,
417+ saltData. count,
418+ prf,
419+ UInt32 ( rounds) ,
420+ keyBuffer,
421+ derivedCount)
422+ }
423+ }
424+ return derivationStatus == kCCSuccess ? derivedKeyData : nil
425+ }
426+
260427}
261428
262429// MARK: - Types
@@ -282,6 +449,7 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
282449 case notDeveloperAppleId
283450 case notAuthorized
284451 case invalidResult( resultString: String ? )
452+ case srpInvalidPublicKey
285453
286454 public var errorDescription : String ? {
287455 switch self {
@@ -316,6 +484,8 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
316484 return " You are not authorized. Please Sign in with your Apple ID first. "
317485 case let . invalidResult( resultString) :
318486 return resultString ?? " If you continue to have problems, please submit a bug report in the Help menu. "
487+ case . srpInvalidPublicKey:
488+ return " Invalid Key "
319489 }
320490 }
321491}
@@ -495,3 +665,23 @@ public struct AppleProvider: Decodable, Equatable {
495665public struct AppleUser : Decodable , Equatable {
496666 public let fullName : String
497667}
668+
669+ public struct ServerSRPInitResponse : Decodable {
670+ let iteration : Int
671+ let salt : String
672+ let b : String
673+ let c : String
674+ }
675+
676+
677+
678+ extension String {
679+ func base64ToU8Array( ) -> Data {
680+ return Data ( base64Encoded: self ) ?? Data ( )
681+ }
682+ }
683+ extension Data {
684+ func hexEncodedString( ) -> String {
685+ return map { String ( format: " %02hhx " , $0) } . joined ( )
686+ }
687+ }
0 commit comments