Skip to content

Commit 9b107ec

Browse files
committed
SRP Login works now
- Switch to use https://github.com/adam-fowler/swift-srp with some modifications that are local - Pad g value to equal size of N while calculating clientProof - Use SHA256(plain-text-password) while computing key using PBKDF2 - Added a unit test with some sample values
1 parent 2ed84ef commit 9b107ec

26 files changed

Lines changed: 778 additions & 676 deletions

File tree

Xcodes.xcodeproj/project.pbxproj

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
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,7 @@
815819
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
816820
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */,
817821
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */,
818-
E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */,
822+
15F5B8912CCF44B700705E2F /* XCLocalSwiftPackageReference "swift-srp-main" */,
819823
);
820824
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
821825
projectDirPath = "";
@@ -1485,9 +1489,9 @@
14851489
/* End XCConfigurationList section */
14861490

14871491
/* Begin XCLocalSwiftPackageReference section */
1488-
E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */ = {
1492+
15F5B8912CCF44B700705E2F /* XCLocalSwiftPackageReference "swift-srp-main" */ = {
14891493
isa = XCLocalSwiftPackageReference;
1490-
relativePath = "xcodes-srp";
1494+
relativePath = "swift-srp-main";
14911495
};
14921496
/* End XCLocalSwiftPackageReference section */
14931497

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

Lines changed: 4 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: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ let package = Package(
1212
name: "AppleAPI",
1313
targets: ["AppleAPI"]),
1414
],
15-
dependencies: [],
15+
dependencies: [
16+
.package(name: "swift-srp", path: "swift-srp-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: 38 additions & 27 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,7 +34,7 @@ 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) }
@@ -62,18 +63,16 @@ public class Client {
6263
}
6364

6465
// 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-
}
66+
let encryptedPasswordString = encryptedPassword.base64EncodedString()
67+
let sharedSecret = try client.calculateSharedSecret(password: encryptedPassword, salt: [UInt8](decodedSalt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB)))
68+
// let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, encryptedPassword: encryptedPasswordString)
7269

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()))
70+
let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](decodedSalt), clientPublicKey: a, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes))
71+
let m2 = client.calculateServerProof(clientPublicKey: a, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes)))
72+
print("m1: \(Data(m1).base64EncodedString())")
73+
print("m2: \(Data(m2).base64EncodedString())")
74+
75+
return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: Data(m1).base64EncodedString(), m2: Data(m2).base64EncodedString()))
7776
.mapError { $0 as Swift.Error }
7877
.eraseToAnyPublisher()
7978
} catch {
@@ -382,27 +381,39 @@ public class Client {
382381
.mapError { $0 as Error }
383382
.eraseToAnyPublisher()
384383
}
385-
384+
385+
func sha256(data : Data) -> Data {
386+
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
387+
data.withUnsafeBytes {
388+
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
389+
}
390+
return Data(hash)
391+
}
392+
386393
private func pbkdf2(password: String, saltData: Data, keyByteCount: Int, prf: CCPseudoRandomAlgorithm, rounds: Int) -> Data? {
387394
guard let passwordData = password.data(using: .utf8) else { return nil }
388-
395+
let hashedPasswordData = sha256(data: passwordData)
396+
389397
var derivedKeyData = Data(repeating: 0, count: keyByteCount)
390398
let derivedCount = derivedKeyData.count
391399
let derivationStatus: Int32 = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
392400
let keyBuffer: UnsafeMutablePointer<UInt8> =
393401
derivedKeyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
394402
return saltData.withUnsafeBytes { saltBytes -> Int32 in
395403
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)
404+
return hashedPasswordData.withUnsafeBytes { hashedPasswordBytes -> Int32 in
405+
let passwordBuffer: UnsafePointer<UInt8> = hashedPasswordBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
406+
return CCKeyDerivationPBKDF(
407+
CCPBKDFAlgorithm(kCCPBKDF2),
408+
passwordBuffer,
409+
hashedPasswordData.count,
410+
saltBuffer,
411+
saltData.count,
412+
prf,
413+
UInt32(rounds),
414+
keyBuffer,
415+
derivedCount)
416+
}
406417
}
407418
}
408419
return derivationStatus == kCCSuccess ? derivedKeyData : nil

XcodesTests/AppStateUpdateTests.swift

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import Path
2+
import CryptoKit
23
import Version
34
@testable import Xcodes
45
import XCTest
6+
import CommonCrypto
7+
import BigNum
8+
import SRP
59

610
class AppStateUpdateTests: XCTestCase {
711
var subject: AppState!
@@ -258,4 +262,81 @@ class AppStateUpdateTests: XCTestCase {
258262
XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0")!, Version("12.3.0-RC")!])
259263
XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[], []])
260264
}
265+
266+
func sha256(data : Data) -> Data {
267+
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
268+
data.withUnsafeBytes {
269+
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
270+
}
271+
return Data(hash)
272+
}
273+
274+
func testSIRP() throws {
275+
/*
276+
Obtaind by running fastlane spaceauth --verbose -u anand.appleseed@example.com with some custom logging
277+
278+
Starting SIRP Apple ID login
279+
a: {"a":"VLEKLa+n2cyeYNWbECm45CuS4kCdCxodlTDGlW1FKaUyOrv/RbtN2sM0pVE12zI7k3VkocPC3rN5DZBIkahR6I8JHj/J97dtTvzsR+aNRWTYCT2HGP1PBI0QArp3eitAbFqTWI4+Zw+oOnV8+AYdH/wjbq7gOK4C4dvIHE+FzRwIlmguPb5qu2r47R9W3y1msVdoUGlFBOMOMb7Gyq7F0MaEIFH63lNzGomwq74mfss/cFqurd6fxU+Y7tdVTPZw1GWyBEPiXWpk8sNm2zE+S6zWo5tOsICprU75IC9galh1igfzN7VNe0SUFLNFTbFK+Bb1SFAOrAbBZOmyOG5uSQ==","accountName":"anand.appleseed@example.com","protocols":["s2k","s2k_fo"]}
280+
Received SIRP signin init response: {"iteration"=>20309, "salt"=>"fIjNflgqSJXACWwwvhDU+w==", "protocol"=>"s2k", "b"=>"PMbU75wwG6rDTySXn2ASWyfQuPoW5ham15SzIscpInwOPE2uk7sePsW4ra0dHcLDUMFQn/LgBggIKOo7YZ9hf1VReiAzXwSKSHdJHjHUURTC2eNpANGUPO1qzuXYgc/MP3MR+GipKHsz+KTLT+8wLjNaiCIHsL/7evJBMw9QqiwhyXlAIm5mGZfhdTVbGpLz2/QzrFmI6pUTrHpio6m1Q74DH3FBxxIeiIcuEdGdeVt9iUweowBRyf2woasTvSV1fbMQbl+lsWPwzt/a73+J30eOGFdSubqSVYh2pV0OLqRz7zPzJars12teCWUV+0WUIaxb14Mp7tlmqcTPuqZe9w==", "c"=>"d-533-eccbc4e9-9564-11ef-84a6-018111c8cc60:PRN"}
281+
encrypted_password: 40532b4de9353fc537dc62ee84eacebd7ecb5ec26efca98bd01b0380e302100f 32
282+
bb: 7672345903537871991962715758896796468138571329014139041563495295907370682045347022183702983061785424983278857706335295545994877883818377653653442007828499221881058994644619578239367613808278802931379172730746665773282250642455690715139985911303055104847971308813151718669484181874342088801251592138154023949370621963319928723678385968989085032385411532317797659749008135679504901238396934480214258070495365760319076978872181485178648397361564241555189629889320567561713407566532187413091018319494367244540399242523126294027225387432028960726767445027313453858210115810946641002311734776929442587065438110307439763191
283+
x: 726436461883978586175291668001486484510457416477927591386889224605776454162
284+
u: 49415306980415573732801389514223606278850554555635359953422678270536095422201
285+
private 23161374166158551996079451276150657702422963034121842124445818241826569345033578345120284496449280736328513302994568402583647660370960353252836732307301957364261384324957527103960720408713825427474127658415917826326829664923997096839970397226662116904369925262192683131695683487505523842260218490007066160096482662715246662018133837725691586205535995663334471723776536640973591229093933458552240634178864509015968350855952558520147559154646484379002445961375384929682566525908284011230815908584648931495968206840416022306138033496705677078512266958633477047047323620540878121579549170392075029336954975132431417099801
286+
S: 4f75b6ea99c2d7d121357cce80c75c8e1bf74a65e8f66f75f8f66a481301afb8bebf0e54a3fac4f8bfdd60c77d6e670c87968b341f62175e25eb1d4f496e4e6596e1a387f2840688a35002419b70115b7902a46544cc7b31eb4c909c0acaeb752835d1562a687c431421510ebc7535c007a2bd12a4f7696c8c96a75a491b1eb9189ade2bef23dd5b0bb962b4f03e7fba7f6ba6fe67ba34cc18647daf3e474876f85dac5a15eb51c99d1ed78783579ffd6c8b6911f72564d87dc8f76519c8fc1535b83743ed5f7d6b8461d7154ce2a874cbb45bf63018352b9b997fbafbd6b15eac2a544a801c0152470796f3b69a84a4a653e5186b30efeeb148ff3c32ab8e08
287+
K: c5207f707186a52f1adee41bf0a7bc41e51e6dffc25cdaeca8acb7de2710b20a
288+
hN: 65908899099613711898698321155848703477601840791750658211391687862255842366922
289+
hG: 23094592799618609623465742609366149076596436609130823198107630312273622653270
290+
hxor 73599884097654065452785151481733181870375477364472235597514429707329935690908
291+
response: {"accountName":"anand.appleseed@example.com","c":"d-533-eccbc4e9-9564-11ef-84a6-018111c8cc60:PRN","m1":"f/Bkq8gBTYxl7SyiRd4SXTyE/jM/g6E0mVyZIQDIsPg=","m2":"R2rgqC9cMAtWiXUImOrvs4oF+ccibf8KaFsZQ22WokM=","rememberMe":false}
292+
*/
293+
294+
let publicKey = Data(base64Encoded: "VLEKLa+n2cyeYNWbECm45CuS4kCdCxodlTDGlW1FKaUyOrv/RbtN2sM0pVE12zI7k3VkocPC3rN5DZBIkahR6I8JHj/J97dtTvzsR+aNRWTYCT2HGP1PBI0QArp3eitAbFqTWI4+Zw+oOnV8+AYdH/wjbq7gOK4C4dvIHE+FzRwIlmguPb5qu2r47R9W3y1msVdoUGlFBOMOMb7Gyq7F0MaEIFH63lNzGomwq74mfss/cFqurd6fxU+Y7tdVTPZw1GWyBEPiXWpk8sNm2zE+S6zWo5tOsICprU75IC9galh1igfzN7VNe0SUFLNFTbFK+Bb1SFAOrAbBZOmyOG5uSQ==".data(using: .utf8)!)
295+
296+
let clientKeys = SRPKeyPair(public: .init([UInt8](publicKey!)),
297+
private: .init(BigNum("23161374166158551996079451276150657702422963034121842124445818241826569345033578345120284496449280736328513302994568402583647660370960353252836732307301957364261384324957527103960720408713825427474127658415917826326829664923997096839970397226662116904369925262192683131695683487505523842260218490007066160096482662715246662018133837725691586205535995663334471723776536640973591229093933458552240634178864509015968350855952558520147559154646484379002445961375384929682566525908284011230815908584648931495968206840416022306138033496705677078512266958633477047047323620540878121579549170392075029336954975132431417099801")!))
298+
299+
let password = sha256(data: "example".data(using: .utf8)!)
300+
let salt = Data(base64Encoded: "fIjNflgqSJXACWwwvhDU+w==".data(using: .utf8)!)!
301+
let iterations: Int = 20309
302+
let derivedKeyLength: Int = 32
303+
var keyArray = Array<UInt8>(repeating: 0, count: derivedKeyLength)
304+
305+
let result:Int32 = keyArray.withUnsafeMutableBytes { keyBytes -> Int32 in
306+
let keyBuffer = UnsafeMutablePointer<UInt8>(keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self))
307+
return password.withUnsafeBytes { passwordDigestBytes -> Int32 in
308+
let passwordBuffer = UnsafePointer<UInt8>(passwordDigestBytes.baseAddress!.assumingMemoryBound(to: UInt8.self))
309+
return salt.withUnsafeBytes { saltBytes -> Int32 in
310+
let saltBuffer = UnsafePointer<UInt8>(saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self))
311+
return CCKeyDerivationPBKDF(
312+
CCPBKDFAlgorithm(kCCPBKDF2),
313+
passwordBuffer,
314+
password.count,
315+
saltBuffer,
316+
salt.count,
317+
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
318+
UInt32(iterations),
319+
keyBuffer,
320+
derivedKeyLength)
321+
322+
}
323+
}
324+
}
325+
326+
let expectedKey: [UInt8] = [0x40, 0x53, 0x2b, 0x4d, 0xe9, 0x35, 0x3f, 0xc5, 0x37, 0xdc, 0x62, 0xee, 0x84, 0xea, 0xce, 0xbd, 0x7e, 0xcb, 0x5e, 0xc2, 0x6e, 0xfc, 0xa9, 0x8b, 0xd0, 0x1b, 0x03, 0x80, 0xe3, 0x02, 0x10, 0x0f]
327+
328+
XCTAssertEqual(expectedKey, keyArray)
329+
330+
let decodedB = Data(base64Encoded: "PMbU75wwG6rDTySXn2ASWyfQuPoW5ham15SzIscpInwOPE2uk7sePsW4ra0dHcLDUMFQn/LgBggIKOo7YZ9hf1VReiAzXwSKSHdJHjHUURTC2eNpANGUPO1qzuXYgc/MP3MR+GipKHsz+KTLT+8wLjNaiCIHsL/7evJBMw9QqiwhyXlAIm5mGZfhdTVbGpLz2/QzrFmI6pUTrHpio6m1Q74DH3FBxxIeiIcuEdGdeVt9iUweowBRyf2woasTvSV1fbMQbl+lsWPwzt/a73+J30eOGFdSubqSVYh2pV0OLqRz7zPzJars12teCWUV+0WUIaxb14Mp7tlmqcTPuqZe9w==".data(using: .utf8)!)!
331+
332+
let client = SRPClient(configuration: SRPConfiguration<SHA256>(.N2048))
333+
let sharedSecret = try client.calculateSharedSecret(password: Data(keyArray), salt: [UInt8](salt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB)))
334+
335+
let accountName = "anand.appleseed@example.com"
336+
let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](salt), clientPublicKey: clientKeys.public, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes))
337+
let m2 = client.calculateServerProof(clientPublicKey: clientKeys.public, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes)))
338+
339+
XCTAssertEqual(Data(m1).base64EncodedString(), "f/Bkq8gBTYxl7SyiRd4SXTyE/jM/g6E0mVyZIQDIsPg=")
340+
XCTAssertEqual(Data(m2).base64EncodedString(), "R2rgqC9cMAtWiXUImOrvs4oF+ccibf8KaFsZQ22WokM=")
341+
}
261342
}

swift-srp-main/.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: adam-fowler
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
release:
11+
types: [published]
12+
workflow_dispatch:
13+
14+
jobs:
15+
macOS:
16+
runs-on: macOS-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 1
21+
- name: Build
22+
run: swift build
23+
- name: Run tests
24+
run: swift test
25+
26+
linux:
27+
runs-on: ubuntu-latest
28+
strategy:
29+
matrix:
30+
image: ['swift:5.9', 'swift:5.10', 'swift:6.0']
31+
container:
32+
image: ${{ matrix.image }}
33+
steps:
34+
- name: Checkout
35+
uses: actions/checkout@v4
36+
with:
37+
fetch-depth: 1
38+
- name: Test
39+
run: swift test --parallel --enable-code-coverage

swift-srp-main/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
/.build
3+
/.swiftpm
4+
/Packages
5+
/*.xcodeproj
6+
xcuserdata/
7+
Package.resolved

0 commit comments

Comments
 (0)