diff --git a/Package.swift b/Package.swift index 8c9ec3f..0f5d80b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version: 6.0 //===----------------------------------------------------------------------===// // // This source file is part of the Swift WebAuthn open source project @@ -36,15 +36,13 @@ let package = Package( .product(name: "Crypto", package: "swift-crypto"), .product(name: "_CryptoExtras", package: "swift-crypto"), .product(name: "Logging", package: "swift-log"), - ], - swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")] + ] ), .testTarget( name: "WebAuthnTests", dependencies: [ .target(name: "WebAuthn") - ], - swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")] + ] ) ] ) diff --git a/Sources/WebAuthn/Ceremonies/Authentication/AuthenticationCredential.swift b/Sources/WebAuthn/Ceremonies/Authentication/AuthenticationCredential.swift index 88dc884..6237cc6 100644 --- a/Sources/WebAuthn/Ceremonies/Authentication/AuthenticationCredential.swift +++ b/Sources/WebAuthn/Ceremonies/Authentication/AuthenticationCredential.swift @@ -35,7 +35,7 @@ public struct AuthenticationCredential: Sendable { } extension AuthenticationCredential: Decodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(URLEncodedBase64.self, forKey: .id) diff --git a/Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift b/Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift index 979ba08..cf9580b 100644 --- a/Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift +++ b/Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift @@ -49,7 +49,7 @@ public struct AuthenticatorAssertionResponse: Sendable { } extension AuthenticatorAssertionResponse: Decodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) clientDataJSON = try container.decodeBytesFromURLEncodedBase64(forKey: .clientDataJSON) diff --git a/Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift b/Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift index e142aa3..81340ed 100644 --- a/Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift +++ b/Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift @@ -45,7 +45,7 @@ public struct PublicKeyCredentialRequestOptions: Encodable, Sendable { // let extensions: [String: Any] - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(challenge.base64URLEncodedString(), forKey: .challenge) @@ -107,7 +107,7 @@ public struct PublicKeyCredentialDescriptor: Equatable, Encodable, Sendable { self.transports = transports } - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(type, forKey: .type) diff --git a/Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift b/Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift index 0d45439..52999cd 100644 --- a/Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift +++ b/Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift @@ -30,7 +30,7 @@ public struct AuthenticatorAttestationResponse: Sendable { } extension AuthenticatorAttestationResponse: Decodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) clientDataJSON = try container.decodeBytesFromURLEncodedBase64(forKey: .clientDataJSON) diff --git a/Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift b/Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift index a069c4a..9fdfabf 100644 --- a/Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift +++ b/Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift @@ -47,7 +47,7 @@ public struct PublicKeyCredentialCreationOptions: Encodable, Sendable { /// supported. public let attestation: AttestationConveyancePreference - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(challenge.base64URLEncodedString(), forKey: .challenge) @@ -142,7 +142,7 @@ public struct PublicKeyCredentialUserEntity: Encodable, Sendable { self.displayName = displayName } - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id.base64URLEncodedString(), forKey: .id) diff --git a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift index fe8a88f..2bc3f9f 100644 --- a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift +++ b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift @@ -32,7 +32,7 @@ public struct RegistrationCredential: Sendable { } extension RegistrationCredential: Decodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(URLEncodedBase64.self, forKey: .id) diff --git a/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorAttestationGloballyUniqueID.swift b/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorAttestationGloballyUniqueID.swift index a9bef10..e2b252a 100644 --- a/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorAttestationGloballyUniqueID.swift +++ b/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorAttestationGloballyUniqueID.swift @@ -64,12 +64,12 @@ public struct AuthenticatorAttestationGloballyUniqueID: Hashable, Sendable { public typealias AAGUID = AuthenticatorAttestationGloballyUniqueID extension AuthenticatorAttestationGloballyUniqueID: Codable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() id = try container.decode(UUID.self) } - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(id) } diff --git a/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift b/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift index 103de56..e039ca6 100644 --- a/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift +++ b/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift @@ -27,10 +27,10 @@ struct AuthenticatorData: Equatable, Sendable { } extension AuthenticatorData { - init(bytes: [UInt8]) throws { + init(bytes: [UInt8]) throws(WebAuthnError) { let minAuthDataLength = 37 guard bytes.count >= minAuthDataLength else { - throw WebAuthnError.authDataTooShort + throw .authDataTooShort } let relyingPartyIDHash = Array(bytes[..<32]) @@ -44,7 +44,7 @@ extension AuthenticatorData { if flags.attestedCredentialData { let minAttestedAuthLength = 37 + AAGUID.size + 2 guard bytes.count > minAttestedAuthLength else { - throw WebAuthnError.attestedCredentialDataMissing + throw .attestedCredentialDataMissing } let (data, length) = try Self.parseAttestedData(bytes) attestedCredentialData = data @@ -52,21 +52,21 @@ extension AuthenticatorData { // For assertion signatures, the AT flag MUST NOT be set and the attestedCredentialData MUST NOT be included. } else { if !flags.extensionDataIncluded && bytes.count != minAuthDataLength { - throw WebAuthnError.attestedCredentialFlagNotSet + throw .attestedCredentialFlagNotSet } } var extensionData: [UInt8]? if flags.extensionDataIncluded { guard remainingCount != 0 else { - throw WebAuthnError.extensionDataMissing + throw .extensionDataMissing } extensionData = Array(bytes[(bytes.count - remainingCount)...]) remainingCount -= extensionData!.count } guard remainingCount == 0 else { - throw WebAuthnError.leftOverBytesInAuthenticatorData + throw .leftOverBytesInAuthenticatorData } self.relyingPartyIDHash = relyingPartyIDHash @@ -81,10 +81,10 @@ extension AuthenticatorData { /// /// This is assumed to take place after the first 37 bytes of `data`, which is always of fixed size. /// - SeeAlso: [WebAuthn Level 3 Editor's Draft §6.5.1. Attested Credential Data]( https://w3c.github.io/webauthn/#sctn-attested-credential-data) - private static func parseAttestedData(_ data: [UInt8]) throws -> (AttestedCredentialData, Int) { + private static func parseAttestedData(_ data: [UInt8]) throws(WebAuthnError) -> (AttestedCredentialData, Int) { /// **aaguid** (16): The AAGUID of the authenticator. guard let aaguid = AAGUID(bytes: data[37..<(37 + AAGUID.size)]) // Bytes [37-52] - else { throw WebAuthnError.attestedCredentialDataMissing } + else { throw .attestedCredentialDataMissing } /// **credentialIdLength** (2): Byte length L of credentialId, 16-bit unsigned big-endian integer. Value MUST be ≤ 1023. let idLengthBytes = data[53..<55] // Length is 2 bytes @@ -92,20 +92,22 @@ extension AuthenticatorData { let idLength = UInt16(bigEndianBytes: idLengthData) guard idLength <= 1023 - else { throw WebAuthnError.credentialIDTooLong } + else { throw .credentialIDTooLong } let credentialIDEndIndex = Int(idLength) + 55 guard data.count >= credentialIDEndIndex - else { throw WebAuthnError.credentialIDTooShort } + else { throw .credentialIDTooShort } /// **credentialId** (L): Credential ID let credentialID = data[55.. UInt8 { - if slice.count < 1 { throw CBORError.unfinishedSequence } + func popByte() throws(CBORError) -> UInt8 { + if slice.count < 1 { throw .unfinishedSequence } return slice.removeFirst() } - func popBytes(_ n: Int) throws -> ArraySlice { - if slice.count < n { throw CBORError.unfinishedSequence } + func popBytes(_ n: Int) throws(CBORError) -> ArraySlice { + if slice.count < n { throw .unfinishedSequence } let result = slice.prefix(n) slice = slice.dropFirst(n) return result diff --git a/Sources/WebAuthn/Ceremonies/Shared/CollectedClientData.swift b/Sources/WebAuthn/Ceremonies/Shared/CollectedClientData.swift index 2726cb2..88ca7f3 100644 --- a/Sources/WebAuthn/Ceremonies/Shared/CollectedClientData.swift +++ b/Sources/WebAuthn/Ceremonies/Shared/CollectedClientData.swift @@ -34,11 +34,11 @@ public struct CollectedClientData: Codable, Hashable, Sendable { public let challenge: URLEncodedBase64 public let origin: String - func verify(storedChallenge: [UInt8], ceremonyType: CeremonyType, relyingPartyOrigin: String) throws { - guard type == ceremonyType else { throw CollectedClientDataVerifyError.ceremonyTypeDoesNotMatch } + func verify(storedChallenge: [UInt8], ceremonyType: CeremonyType, relyingPartyOrigin: String) throws(CollectedClientDataVerifyError) { + guard type == ceremonyType else { throw .ceremonyTypeDoesNotMatch } guard challenge == storedChallenge.base64URLEncodedString() else { - throw CollectedClientDataVerifyError.challengeDoesNotMatch + throw .challengeDoesNotMatch } - guard origin == relyingPartyOrigin else { throw CollectedClientDataVerifyError.originDoesNotMatch } + guard origin == relyingPartyOrigin else { throw .originDoesNotMatch } } } diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift index 307b0ce..dccc84d 100644 --- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift +++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift @@ -108,7 +108,7 @@ struct EC2PublicKey: PublicKey, Sendable { self.yCoordinate = yCoordinate } - init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws { + init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws(WebAuthnError) { self.algorithm = algorithm // Curve is key -1 - or -0 for SwiftCBOR @@ -117,18 +117,18 @@ struct EC2PublicKey: PublicKey, Sendable { guard let curveRaw = publicKeyObject[COSEKey.crv.cbor], case let .unsignedInt(curve) = curveRaw, let coseCurve = COSECurve(rawValue: curve) else { - throw WebAuthnError.invalidCurve + throw .invalidCurve } self.curve = coseCurve guard let xCoordRaw = publicKeyObject[COSEKey.x.cbor], case let .byteString(xCoordinateBytes) = xCoordRaw else { - throw WebAuthnError.invalidXCoordinate + throw .invalidXCoordinate } xCoordinate = xCoordinateBytes guard let yCoordRaw = publicKeyObject[COSEKey.y.cbor], case let .byteString(yCoordinateBytes) = yCoordRaw else { - throw WebAuthnError.invalidYCoordinate + throw .invalidYCoordinate } yCoordinate = yCoordinateBytes } @@ -167,18 +167,18 @@ struct RSAPublicKeyData: PublicKey, Sendable { var rawRepresentation: [UInt8] { n + e } - init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws { + init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws(WebAuthnError) { self.algorithm = algorithm guard let nRaw = publicKeyObject[COSEKey.n.cbor], case let .byteString(nBytes) = nRaw else { - throw WebAuthnError.invalidModulus + throw .invalidModulus } n = nBytes guard let eRaw = publicKeyObject[COSEKey.e.cbor], case let .byteString(eBytes) = eRaw else { - throw WebAuthnError.invalidExponent + throw .invalidExponent } e = eBytes } @@ -213,17 +213,17 @@ struct OKPPublicKey: PublicKey, Sendable { let curve: UInt64 let xCoordinate: [UInt8] - init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws { + init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws(WebAuthnError) { self.algorithm = algorithm // Curve is key -1, or NegativeInt 0 for SwiftCBOR guard let curveRaw = publicKeyObject[.negativeInt(0)], case let .unsignedInt(curve) = curveRaw else { - throw WebAuthnError.invalidCurve + throw .invalidCurve } self.curve = curve // X Coordinate is key -2, or NegativeInt 1 for SwiftCBOR guard let xCoordRaw = publicKeyObject[.negativeInt(1)], case let .byteString(xCoordinateBytes) = xCoordRaw else { - throw WebAuthnError.invalidXCoordinate + throw .invalidXCoordinate } xCoordinate = xCoordinateBytes } diff --git a/Sources/WebAuthn/Helpers/Base64Utilities.swift b/Sources/WebAuthn/Helpers/Base64Utilities.swift index 126af5c..7d3d882 100644 --- a/Sources/WebAuthn/Helpers/Base64Utilities.swift +++ b/Sources/WebAuthn/Helpers/Base64Utilities.swift @@ -26,12 +26,12 @@ public struct EncodedBase64: ExpressibleByStringLiteral, Codable, Hashable, Equa self.init(value) } - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() self.base64 = try container.decode(String.self) } - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(self.base64) } diff --git a/Sources/WebAuthn/WebAuthnError.swift b/Sources/WebAuthn/WebAuthnError.swift index f9b942a..f4dce7b 100644 --- a/Sources/WebAuthn/WebAuthnError.swift +++ b/Sources/WebAuthn/WebAuthnError.swift @@ -51,6 +51,7 @@ public struct WebAuthnError: Error, Hashable, Sendable { case leftOverBytesInAuthenticatorData case credentialIDTooLong case credentialIDTooShort + case invalidPublicKeyLength // MARK: CredentialPublicKey case badPublicKeyBytes @@ -110,6 +111,7 @@ public struct WebAuthnError: Error, Hashable, Sendable { public static let leftOverBytesInAuthenticatorData = Self(reason: .leftOverBytesInAuthenticatorData) public static let credentialIDTooLong = Self(reason: .credentialIDTooLong) public static let credentialIDTooShort = Self(reason: .credentialIDTooShort) + public static let invalidPublicKeyLength = Self(reason: .invalidPublicKeyLength) // MARK: CredentialPublicKey public static let badPublicKeyBytes = Self(reason: .badPublicKeyBytes) diff --git a/Sources/WebAuthn/WebAuthnManager.swift b/Sources/WebAuthn/WebAuthnManager.swift index 1da063b..299089b 100644 --- a/Sources/WebAuthn/WebAuthnManager.swift +++ b/Sources/WebAuthn/WebAuthnManager.swift @@ -139,7 +139,7 @@ public struct WebAuthnManager: Sendable { timeout: Duration? = .seconds(60), allowCredentials: [PublicKeyCredentialDescriptor]? = nil, userVerification: UserVerificationRequirement = .preferred - ) throws -> PublicKeyCredentialRequestOptions { + ) -> PublicKeyCredentialRequestOptions { let challenge = challengeGenerator.generate() return PublicKeyCredentialRequestOptions( diff --git a/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift b/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift index 52f3752..2474c03 100644 --- a/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift +++ b/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift @@ -35,7 +35,7 @@ final class WebAuthnManagerAuthenticationTests: XCTestCase { func testBeginAuthentication() async throws { let allowCredentials: [PublicKeyCredentialDescriptor] = [.init(type: .publicKey, id: [1, 0, 2, 30])] - let options = try webAuthnManager.beginAuthentication( + let options = webAuthnManager.beginAuthentication( timeout: .seconds(1234), allowCredentials: allowCredentials, userVerification: .preferred diff --git a/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift b/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift index 0102045..71019ff 100644 --- a/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift +++ b/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift @@ -99,7 +99,7 @@ final class WebAuthnManagerIntegrationTests: XCTestCase { id: [UInt8](URLEncodedBase64(credential.id).urlDecoded.decoded!) )] - let authenticationOptions = try webAuthnManager.beginAuthentication( + let authenticationOptions = webAuthnManager.beginAuthentication( timeout: authenticationTimeout, allowCredentials: rememberedCredentials, userVerification: userVerification