Skip to content

Commit 29bf770

Browse files
authored
Merge pull request #638 from abiligiri/SRPLogin_fix
SRP Login works now
2 parents 2ed84ef + 42c2c6b commit 29bf770

19 files changed

Lines changed: 345 additions & 1378 deletions

File tree

Xcodes.xcodeproj/project.pbxproj

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 60;
6+
objectVersion = 54;
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F5B88F2CCF09B900705E2F /* CryptoKit.framework */; };
1011
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */ = {isa = PBXBuildFile; productRef = 334A932B2CA885A400A5E079 /* LibFido2Swift */; };
1112
3328073F2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */; };
1213
332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */; };
@@ -196,6 +197,7 @@
196197
/* End PBXCopyFilesBuildPhase section */
197198

198199
/* Begin PBXFileReference section */
200+
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
199201
3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyPinView.swift; sourceTree = "<group>"; };
200202
332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyTouchView.swift; sourceTree = "<group>"; };
201203
36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreferencePane.swift; sourceTree = "<group>"; };
@@ -352,6 +354,7 @@
352354
isa = PBXFrameworksBuildPhase;
353355
buildActionMask = 2147483647;
354356
files = (
357+
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */,
355358
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */,
356359
CABFA9E42592F08E00380FEE /* Version in Frameworks */,
357360
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
@@ -416,6 +419,7 @@
416419
CA538A12255A4F7C00E64DD7 /* Frameworks */ = {
417420
isa = PBXGroup;
418421
children = (
422+
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */,
419423
);
420424
name = Frameworks;
421425
sourceTree = "<group>";
@@ -815,7 +819,6 @@
815819
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
816820
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */,
817821
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */,
818-
E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */,
819822
);
820823
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
821824
projectDirPath = "";
@@ -1484,13 +1487,6 @@
14841487
};
14851488
/* End XCConfigurationList section */
14861489

1487-
/* Begin XCLocalSwiftPackageReference section */
1488-
E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */ = {
1489-
isa = XCLocalSwiftPackageReference;
1490-
relativePath = "xcodes-srp";
1491-
};
1492-
/* End XCLocalSwiftPackageReference section */
1493-
14941490
/* Begin XCRemoteSwiftPackageReference section */
14951491
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */ = {
14961492
isa = XCRemoteSwiftPackageReference;

Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 13 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Xcodes/AppleAPI/Package.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.3
1+
// swift-tools-version:5.7
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
@@ -12,13 +12,15 @@ let package = Package(
1212
name: "AppleAPI",
1313
targets: ["AppleAPI"]),
1414
],
15-
dependencies: [],
15+
dependencies: [
16+
.package(url: "https://github.com/xcodesOrg/swift-srp", branch: "main")
17+
],
1618
targets: [
1719
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
1820
// Targets can depend on other targets in this package, and on products in packages this package depends on.
1921
.target(
2022
name: "AppleAPI",
21-
dependencies: []),
23+
dependencies: [.product(name: "SRP", package: "swift-srp")]),
2224
.testTarget(
2325
name: "AppleAPITests",
2426
dependencies: ["AppleAPI"]),

Xcodes/AppleAPI/Sources/AppleAPI/Client.swift

Lines changed: 34 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ public class Client {
1515
public func srpLogin(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
1616
var serviceKey: String!
1717

18-
let client = SRPClient<SHA256>(username: accountName, password: password)
19-
let a = client.startAuthentication()
20-
18+
let client = SRPClient(configuration: SRPConfiguration<SHA256>(.N2048))
19+
let clientKeys = client.generateKeys()
20+
let a = clientKeys.public
21+
2122
return Current.network.dataTask(with: URLRequest.itcServiceKey)
2223
.map(\.data)
2324
.decode(type: ServiceKeyResponse.self, decoder: JSONDecoder())
@@ -33,15 +34,13 @@ public class Client {
3334
}
3435
.flatMap { (serviceKey, hashcash) -> AnyPublisher<(String, String, ServerSRPInitResponse), Swift.Error> in
3536

36-
return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: a.base64EncodedString(), accountName: accountName))
37+
return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: Data(a.bytes).base64EncodedString(), accountName: accountName))
3738
.map(\.data)
3839
.decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder())
3940
.map { return (serviceKey, hashcash, $0) }
4041
.eraseToAnyPublisher()
4142
}
4243
.flatMap { (serviceKey, hashcash, srpInit) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in
43-
print("SRP INIT REsponse: \(srpInit)")
44-
4544
guard let decodedB = Data(base64Encoded: srpInit.b) else {
4645
return Fail(error: AuthenticationError.srpInvalidPublicKey)
4746
.eraseToAnyPublisher()
@@ -55,29 +54,20 @@ public class Client {
5554
let iterations = srpInit.iteration
5655

5756
do {
58-
5957
guard let encryptedPassword = self.pbkdf2(password: password, saltData: decodedSalt, keyByteCount: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds: iterations) else {
6058
return Fail(error: AuthenticationError.srpInvalidPublicKey)
6159
.eraseToAnyPublisher()
6260
}
6361

64-
// let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, isEncryptedPassword: true, encryptedPassword: encryptedPassword.hexEncodedString())
65-
let encryptedPasswordString = String(data: encryptedPassword, encoding: .utf8)
66-
let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, isEncryptedPassword: true, encryptedPassword: encryptedPasswordString)
67-
68-
guard let m2 = client.HAMK else {
69-
return Fail(error: AuthenticationError.srpInvalidPublicKey)
70-
.eraseToAnyPublisher()
71-
}
62+
let sharedSecret = try client.calculateSharedSecret(password: encryptedPassword, salt: [UInt8](decodedSalt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB)))
7263

73-
print("m1: \(m1.base64EncodedString())")
74-
print("m2: \(m2.base64EncodedString())")
75-
76-
return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: m1.base64EncodedString(), m2: m2.base64EncodedString()))
64+
let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](decodedSalt), clientPublicKey: a, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes))
65+
let m2 = client.calculateServerProof(clientPublicKey: a, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes)))
66+
67+
return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: Data(m1).base64EncodedString(), m2: Data(m2).base64EncodedString()))
7768
.mapError { $0 as Swift.Error }
7869
.eraseToAnyPublisher()
7970
} catch {
80-
print("Error: calculateSharedSecret \(error)")
8171
return Fail(error: AuthenticationError.srpInvalidPublicKey)
8272
.eraseToAnyPublisher()
8373
}
@@ -115,79 +105,6 @@ public class Client {
115105
}
116106
.eraseToAnyPublisher()
117107
}
118-
// .map(\.data)
119-
// .decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder())
120-
//
121-
//
122-
//
123-
// .flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in
124-
// return ("")
125-
// }
126-
// .flatMap { serverResponse -> AnyPublisher<AuthenticationState, Error> in
127-
// print(serverResponse)
128-
// return Fail(error: AuthenticationError.accountUsesTwoStepAuthentication)
129-
// .eraseToAnyPublisher()
130-
// }
131-
.mapError { $0 as Swift.Error }
132-
.eraseToAnyPublisher()
133-
}
134-
135-
136-
public func login(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
137-
var serviceKey: String!
138-
139-
return Current.network.dataTask(with: URLRequest.itcServiceKey)
140-
.map(\.data)
141-
.decode(type: ServiceKeyResponse.self, decoder: JSONDecoder())
142-
.flatMap { serviceKeyResponse -> AnyPublisher<(String, String), Swift.Error> in
143-
serviceKey = serviceKeyResponse.authServiceKey
144-
145-
// Fixes issue https://github.com/RobotsAndPencils/XcodesApp/issues/360
146-
// On 2023-02-23, Apple added a custom implementation of hashcash to their auth flow
147-
// Without this addition, Apple ID's would get set to locked
148-
return self.loadHashcash(accountName: accountName, serviceKey: serviceKey)
149-
.map { return (serviceKey, $0)}
150-
.eraseToAnyPublisher()
151-
}
152-
.flatMap { (serviceKey, hashcash) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in
153-
154-
return Current.network.dataTask(with: URLRequest.signIn(serviceKey: serviceKey, accountName: accountName, password: password, hashcash: hashcash))
155-
.mapError { $0 as Swift.Error }
156-
.eraseToAnyPublisher()
157-
}
158-
.flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in
159-
let (data, response) = result
160-
return Just(data)
161-
.decode(type: SignInResponse.self, decoder: JSONDecoder())
162-
.flatMap { responseBody -> AnyPublisher<AuthenticationState, Swift.Error> in
163-
let httpResponse = response as! HTTPURLResponse
164-
165-
switch httpResponse.statusCode {
166-
case 200:
167-
return Current.network.dataTask(with: URLRequest.olympusSession)
168-
.map { _ in AuthenticationState.authenticated }
169-
.mapError { $0 as Swift.Error }
170-
.eraseToAnyPublisher()
171-
case 401:
172-
return Fail(error: AuthenticationError.invalidUsernameOrPassword(username: accountName))
173-
.eraseToAnyPublisher()
174-
case 403:
175-
let errorMessage = responseBody.serviceErrors?.first?.description.replacingOccurrences(of: "-20209: ", with: "") ?? ""
176-
return Fail(error: AuthenticationError.accountLocked(errorMessage))
177-
.eraseToAnyPublisher()
178-
case 409:
179-
return self.handleTwoStepOrFactor(data: data, response: response, serviceKey: serviceKey)
180-
case 412 where Client.authTypes.contains(responseBody.authType ?? ""):
181-
return Fail(error: AuthenticationError.appleIDAndPrivacyAcknowledgementRequired)
182-
.eraseToAnyPublisher()
183-
default:
184-
return Fail(error: AuthenticationError.unexpectedSignInResponse(statusCode: httpResponse.statusCode,
185-
message: responseBody.serviceErrors?.map { $0.description }.joined(separator: ", ")))
186-
.eraseToAnyPublisher()
187-
}
188-
}
189-
.eraseToAnyPublisher()
190-
}
191108
.mapError { $0 as Swift.Error }
192109
.eraseToAnyPublisher()
193110
}
@@ -382,27 +299,39 @@ public class Client {
382299
.mapError { $0 as Error }
383300
.eraseToAnyPublisher()
384301
}
385-
302+
303+
func sha256(data : Data) -> Data {
304+
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
305+
data.withUnsafeBytes {
306+
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
307+
}
308+
return Data(hash)
309+
}
310+
386311
private func pbkdf2(password: String, saltData: Data, keyByteCount: Int, prf: CCPseudoRandomAlgorithm, rounds: Int) -> Data? {
387312
guard let passwordData = password.data(using: .utf8) else { return nil }
388-
313+
let hashedPasswordData = sha256(data: passwordData)
314+
389315
var derivedKeyData = Data(repeating: 0, count: keyByteCount)
390316
let derivedCount = derivedKeyData.count
391317
let derivationStatus: Int32 = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
392318
let keyBuffer: UnsafeMutablePointer<UInt8> =
393319
derivedKeyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
394320
return saltData.withUnsafeBytes { saltBytes -> Int32 in
395321
let saltBuffer: UnsafePointer<UInt8> = saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
396-
return CCKeyDerivationPBKDF(
397-
CCPBKDFAlgorithm(kCCPBKDF2),
398-
password,
399-
passwordData.count,
400-
saltBuffer,
401-
saltData.count,
402-
prf,
403-
UInt32(rounds),
404-
keyBuffer,
405-
derivedCount)
322+
return hashedPasswordData.withUnsafeBytes { hashedPasswordBytes -> Int32 in
323+
let passwordBuffer: UnsafePointer<UInt8> = hashedPasswordBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
324+
return CCKeyDerivationPBKDF(
325+
CCPBKDFAlgorithm(kCCPBKDF2),
326+
passwordBuffer,
327+
hashedPasswordData.count,
328+
saltBuffer,
329+
saltData.count,
330+
prf,
331+
UInt32(rounds),
332+
keyBuffer,
333+
derivedCount)
334+
}
406335
}
407336
}
408337
return derivationStatus == kCCSuccess ? derivedKeyData : nil

0 commit comments

Comments
 (0)