Skip to content

Commit

Permalink
Merge pull request #638 from abiligiri/SRPLogin_fix
Browse files Browse the repository at this point in the history
SRP Login works now
  • Loading branch information
MattKiazyk authored Oct 29, 2024
2 parents 2ed84ef + 42c2c6b commit 29bf770
Show file tree
Hide file tree
Showing 19 changed files with 345 additions and 1,378 deletions.
14 changes: 5 additions & 9 deletions Xcodes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objectVersion = 54;
objects = {

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

/* Begin PBXFileReference section */
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyPinView.swift; sourceTree = "<group>"; };
332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyTouchView.swift; sourceTree = "<group>"; };
36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreferencePane.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -352,6 +354,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */,
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */,
CABFA9E42592F08E00380FEE /* Version in Frameworks */,
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
Expand Down Expand Up @@ -416,6 +419,7 @@
CA538A12255A4F7C00E64DD7 /* Frameworks */ = {
isa = PBXGroup;
children = (
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -815,7 +819,6 @@
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */,
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */,
E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */,
);
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -1484,13 +1487,6 @@
};
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "xcodes-srp";
};
/* End XCLocalSwiftPackageReference section */

/* Begin XCRemoteSwiftPackageReference section */
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */ = {
isa = XCRemoteSwiftPackageReference;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
}
},
{
"package": "BigInt",
"repositoryURL": "https://github.com/attaswift/BigInt",
"package": "big-num",
"repositoryURL": "https://github.com/adam-fowler/big-num",
"state": {
"branch": null,
"revision": "793a7fac0bfc318e85994bf6900652e827aef33e",
"version": "5.4.1"
"revision": "5c5511ad06aeb2b97d0868f7394e14a624bfb1c7",
"version": "2.0.2"
}
},
{
Expand Down Expand Up @@ -118,6 +118,15 @@
"version": "1.1.7"
}
},
{
"package": "swift-srp",
"repositoryURL": "https://github.com/xcodesOrg/swift-srp",
"state": {
"branch": "main",
"revision": "543aa0122a0257b992f6c7d62d18a26e3dffb8fe",
"version": null
}
},
{
"package": "SwiftSoup",
"repositoryURL": "https://github.com/scinfu/SwiftSoup",
Expand Down
8 changes: 5 additions & 3 deletions Xcodes/AppleAPI/Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -12,13 +12,15 @@ let package = Package(
name: "AppleAPI",
targets: ["AppleAPI"]),
],
dependencies: [],
dependencies: [
.package(url: "https://github.com/xcodesOrg/swift-srp", branch: "main")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "AppleAPI",
dependencies: []),
dependencies: [.product(name: "SRP", package: "swift-srp")]),
.testTarget(
name: "AppleAPITests",
dependencies: ["AppleAPI"]),
Expand Down
139 changes: 34 additions & 105 deletions Xcodes/AppleAPI/Sources/AppleAPI/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ public class Client {
public func srpLogin(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
var serviceKey: String!

let client = SRPClient<SHA256>(username: accountName, password: password)
let a = client.startAuthentication()

let client = SRPClient(configuration: SRPConfiguration<SHA256>(.N2048))
let clientKeys = client.generateKeys()
let a = clientKeys.public

return Current.network.dataTask(with: URLRequest.itcServiceKey)
.map(\.data)
.decode(type: ServiceKeyResponse.self, decoder: JSONDecoder())
Expand All @@ -33,15 +34,13 @@ public class Client {
}
.flatMap { (serviceKey, hashcash) -> AnyPublisher<(String, String, ServerSRPInitResponse), Swift.Error> in

return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: a.base64EncodedString(), accountName: accountName))
return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: Data(a.bytes).base64EncodedString(), accountName: accountName))
.map(\.data)
.decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder())
.map { return (serviceKey, hashcash, $0) }
.eraseToAnyPublisher()
}
.flatMap { (serviceKey, hashcash, srpInit) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in
print("SRP INIT REsponse: \(srpInit)")

guard let decodedB = Data(base64Encoded: srpInit.b) else {
return Fail(error: AuthenticationError.srpInvalidPublicKey)
.eraseToAnyPublisher()
Expand All @@ -55,29 +54,20 @@ public class Client {
let iterations = srpInit.iteration

do {

guard let encryptedPassword = self.pbkdf2(password: password, saltData: decodedSalt, keyByteCount: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds: iterations) else {
return Fail(error: AuthenticationError.srpInvalidPublicKey)
.eraseToAnyPublisher()
}

// let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, isEncryptedPassword: true, encryptedPassword: encryptedPassword.hexEncodedString())
let encryptedPasswordString = String(data: encryptedPassword, encoding: .utf8)
let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, isEncryptedPassword: true, encryptedPassword: encryptedPasswordString)

guard let m2 = client.HAMK else {
return Fail(error: AuthenticationError.srpInvalidPublicKey)
.eraseToAnyPublisher()
}
let sharedSecret = try client.calculateSharedSecret(password: encryptedPassword, salt: [UInt8](decodedSalt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB)))

print("m1: \(m1.base64EncodedString())")
print("m2: \(m2.base64EncodedString())")
return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: m1.base64EncodedString(), m2: m2.base64EncodedString()))
let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](decodedSalt), clientPublicKey: a, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes))
let m2 = client.calculateServerProof(clientPublicKey: a, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes)))

return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: Data(m1).base64EncodedString(), m2: Data(m2).base64EncodedString()))
.mapError { $0 as Swift.Error }
.eraseToAnyPublisher()
} catch {
print("Error: calculateSharedSecret \(error)")
return Fail(error: AuthenticationError.srpInvalidPublicKey)
.eraseToAnyPublisher()
}
Expand Down Expand Up @@ -115,79 +105,6 @@ public class Client {
}
.eraseToAnyPublisher()
}
// .map(\.data)
// .decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder())
//
//
//
// .flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in
// return ("")
// }
// .flatMap { serverResponse -> AnyPublisher<AuthenticationState, Error> in
// print(serverResponse)
// return Fail(error: AuthenticationError.accountUsesTwoStepAuthentication)
// .eraseToAnyPublisher()
// }
.mapError { $0 as Swift.Error }
.eraseToAnyPublisher()
}


public func login(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
var serviceKey: String!

return Current.network.dataTask(with: URLRequest.itcServiceKey)
.map(\.data)
.decode(type: ServiceKeyResponse.self, decoder: JSONDecoder())
.flatMap { serviceKeyResponse -> AnyPublisher<(String, String), Swift.Error> in
serviceKey = serviceKeyResponse.authServiceKey

// Fixes issue https://github.com/RobotsAndPencils/XcodesApp/issues/360
// On 2023-02-23, Apple added a custom implementation of hashcash to their auth flow
// Without this addition, Apple ID's would get set to locked
return self.loadHashcash(accountName: accountName, serviceKey: serviceKey)
.map { return (serviceKey, $0)}
.eraseToAnyPublisher()
}
.flatMap { (serviceKey, hashcash) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in

return Current.network.dataTask(with: URLRequest.signIn(serviceKey: serviceKey, accountName: accountName, password: password, hashcash: hashcash))
.mapError { $0 as Swift.Error }
.eraseToAnyPublisher()
}
.flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in
let (data, response) = result
return Just(data)
.decode(type: SignInResponse.self, decoder: JSONDecoder())
.flatMap { responseBody -> AnyPublisher<AuthenticationState, Swift.Error> in
let httpResponse = response as! HTTPURLResponse

switch httpResponse.statusCode {
case 200:
return Current.network.dataTask(with: URLRequest.olympusSession)
.map { _ in AuthenticationState.authenticated }
.mapError { $0 as Swift.Error }
.eraseToAnyPublisher()
case 401:
return Fail(error: AuthenticationError.invalidUsernameOrPassword(username: accountName))
.eraseToAnyPublisher()
case 403:
let errorMessage = responseBody.serviceErrors?.first?.description.replacingOccurrences(of: "-20209: ", with: "") ?? ""
return Fail(error: AuthenticationError.accountLocked(errorMessage))
.eraseToAnyPublisher()
case 409:
return self.handleTwoStepOrFactor(data: data, response: response, serviceKey: serviceKey)
case 412 where Client.authTypes.contains(responseBody.authType ?? ""):
return Fail(error: AuthenticationError.appleIDAndPrivacyAcknowledgementRequired)
.eraseToAnyPublisher()
default:
return Fail(error: AuthenticationError.unexpectedSignInResponse(statusCode: httpResponse.statusCode,
message: responseBody.serviceErrors?.map { $0.description }.joined(separator: ", ")))
.eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}
.mapError { $0 as Swift.Error }
.eraseToAnyPublisher()
}
Expand Down Expand Up @@ -382,27 +299,39 @@ public class Client {
.mapError { $0 as Error }
.eraseToAnyPublisher()
}


func sha256(data : Data) -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
return Data(hash)
}

private func pbkdf2(password: String, saltData: Data, keyByteCount: Int, prf: CCPseudoRandomAlgorithm, rounds: Int) -> Data? {
guard let passwordData = password.data(using: .utf8) else { return nil }

let hashedPasswordData = sha256(data: passwordData)

var derivedKeyData = Data(repeating: 0, count: keyByteCount)
let derivedCount = derivedKeyData.count
let derivationStatus: Int32 = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
let keyBuffer: UnsafeMutablePointer<UInt8> =
derivedKeyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
return saltData.withUnsafeBytes { saltBytes -> Int32 in
let saltBuffer: UnsafePointer<UInt8> = saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
return CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password,
passwordData.count,
saltBuffer,
saltData.count,
prf,
UInt32(rounds),
keyBuffer,
derivedCount)
return hashedPasswordData.withUnsafeBytes { hashedPasswordBytes -> Int32 in
let passwordBuffer: UnsafePointer<UInt8> = hashedPasswordBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
return CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
passwordBuffer,
hashedPasswordData.count,
saltBuffer,
saltData.count,
prf,
UInt32(rounds),
keyBuffer,
derivedCount)
}
}
}
return derivationStatus == kCCSuccess ? derivedKeyData : nil
Expand Down
Loading

0 comments on commit 29bf770

Please sign in to comment.