diff --git a/Package.swift b/Package.swift index 0f5d80b..d46e8ed 100644 --- a/Package.swift +++ b/Package.swift @@ -24,7 +24,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/unrelentingtech/SwiftCBOR.git", from: "0.4.7"), - .package(url: "https://github.com/apple/swift-crypto.git", "2.0.0" ..< "4.0.0"), + .package(url: "https://github.com/apple/swift-crypto.git", "3.8.1" ..< "4.0.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin.git", from: "1.1.0") ], @@ -41,7 +41,9 @@ let package = Package( .testTarget( name: "WebAuthnTests", dependencies: [ - .target(name: "WebAuthn") + .target(name: "WebAuthn"), + .product(name: "Crypto", package: "swift-crypto"), + .product(name: "_CryptoExtras", package: "swift-crypto"), ] ) ] diff --git a/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift b/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift index 50ecee2..adf2c2d 100644 --- a/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift +++ b/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift @@ -19,39 +19,39 @@ import Crypto /// [https://www.w3.org/TR/webauthn/#biblio-iana-cose-algs-reg], for instance, -7 for "ES256" and -257 for "RS256". public enum COSEAlgorithmIdentifier: Int, RawRepresentable, CaseIterable, Encodable, Sendable { /// AlgES256 ECDSA with SHA-256 - case algES256 = -7 - /// AlgES384 ECDSA with SHA-384 - case algES384 = -35 - /// AlgES512 ECDSA with SHA-512 - case algES512 = -36 - - // We don't support RSA yet - - // /// AlgRS1 RSASSA-PKCS1-v1_5 with SHA-1 - // case algRS1 = -65535 - // /// AlgRS256 RSASSA-PKCS1-v1_5 with SHA-256 - // case algRS256 = -257 - // /// AlgRS384 RSASSA-PKCS1-v1_5 with SHA-384 - // case algRS384 = -258 - // /// AlgRS512 RSASSA-PKCS1-v1_5 with SHA-512 - // case algRS512 = -259 - // /// AlgPS256 RSASSA-PSS with SHA-256 - // case algPS256 = -37 - // /// AlgPS384 RSASSA-PSS with SHA-384 - // case algPS384 = -38 - // /// AlgPS512 RSASSA-PSS with SHA-512 - // case algPS512 = -39 - // // AlgEdDSA EdDSA - // case algEdDSA = -8 - - func hashAndCompare(data: Data, to compareHash: Data) -> Bool { - switch self { - case .algES256: - return SHA256.hash(data: data) == compareHash - case .algES384: - return SHA384.hash(data: data) == compareHash - case .algES512: - return SHA512.hash(data: data) == compareHash - } - } + case algES256 = -7 + /// AlgES384 ECDSA with SHA-384 + case algES384 = -35 + /// AlgES512 ECDSA with SHA-512 + case algES512 = -36 + + /// AlgRS1 RSASSA-PKCS1-v1_5 with SHA-1 + case algRS1 = -65535 + /// AlgRS256 RSASSA-PKCS1-v1_5 with SHA-256 + case algRS256 = -257 + /// AlgRS384 RSASSA-PKCS1-v1_5 with SHA-384 + case algRS384 = -258 + /// AlgRS512 RSASSA-PKCS1-v1_5 with SHA-512 + case algRS512 = -259 + /// AlgPS256 RSASSA-PSS with SHA-256 + case algPS256 = -37 + /// AlgPS384 RSASSA-PSS with SHA-384 + case algPS384 = -38 + /// AlgPS512 RSASSA-PSS with SHA-512 + case algPS512 = -39 + // /// AlgEdDSA EdDSA + // case algEdDSA = -8 + + func hashAndCompare(data: Data, to compareHash: Data) -> Bool { + switch self { + case .algES256, .algRS256, .algPS256: + return SHA256.hash(data: data) == compareHash + case .algES384, .algRS384, .algPS384: + return SHA384.hash(data: data) == compareHash + case .algES512, .algRS512, .algPS512: + return SHA512.hash(data: data) == compareHash + case .algRS1: + return Insecure.SHA1.hash(data: data) == compareHash + } + } } diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift index dccc84d..892490a 100644 --- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift +++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift @@ -76,8 +76,7 @@ enum CredentialPublicKey: Sendable { case .ellipticKey: self = try .ec2(EC2PublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm)) case .rsaKey: - throw WebAuthnError.unsupported - // self = try .rsa(RSAPublicKeyData(publicKeyObject: publicKeyObject, algorithm: algorithm)) + self = try .rsa(RSAPublicKeyData(publicKeyObject: publicKeyObject, algorithm: algorithm)) case .octetKey: throw WebAuthnError.unsupported // self = try .okp(OKPPublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm)) @@ -153,11 +152,12 @@ struct EC2PublicKey: PublicKey, Sendable { .isValidSignature(ecdsaSignature, for: data) else { throw WebAuthnError.invalidSignature } + default: + throw WebAuthnError.unsupportedCredentialPublicKeyAlgorithm } } } -/// Currently not in use struct RSAPublicKeyData: PublicKey, Sendable { let algorithm: COSEAlgorithmIdentifier // swiftlint:disable:next identifier_name @@ -184,26 +184,21 @@ struct RSAPublicKeyData: PublicKey, Sendable { } func verify(signature: some DataProtocol, data: some DataProtocol) throws { - throw WebAuthnError.unsupported - // let rsaSignature = _RSA.Signing.RSASignature(derRepresentation: signature) - - // var rsaPadding: _RSA.Signing.Padding - // switch algorithm { - // case .algRS1, .algRS256, .algRS384, .algRS512: - // rsaPadding = .insecurePKCS1v1_5 - // case .algPS256, .algPS384, .algPS512: - // rsaPadding = .PSS - // default: - // throw WebAuthnError.unsupportedCOSEAlgorithmForRSAPublicKey - // } - - // guard try _RSA.Signing.PublicKey(rawRepresentation: rawRepresentation).isValidSignature( - // rsaSignature, - // for: data, - // padding: rsaPadding - // ) else { - // throw WebAuthnError.invalidSignature - // } + let rsaSignature = _RSA.Signing.RSASignature(rawRepresentation: signature) + + var rsaPadding: _RSA.Signing.Padding + switch algorithm { + case .algRS1, .algRS256, .algRS384, .algRS512: + rsaPadding = .insecurePKCS1v1_5 + case .algPS256, .algPS384, .algPS512: + rsaPadding = .PSS + default: + throw WebAuthnError.unsupportedCOSEAlgorithmForRSAPublicKey + } + + let publicKey = try _RSA.Signing.PublicKey(n: n, e: e) + guard publicKey.isValidSignature(rsaSignature, for: data, padding: rsaPadding) + else { throw WebAuthnError.invalidSignature } } } diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift b/Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift index a60813a..3ca14c8 100644 --- a/Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift +++ b/Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift @@ -12,14 +12,21 @@ //===----------------------------------------------------------------------===// import WebAuthn -import SwiftCBOR +@preconcurrency import SwiftCBOR +import Testing // protocol AttestationObjectParameter: CBOR {} struct TestAttestationObject { var fmt: CBOR? var attStmt: CBOR? - var authData: CBOR? + var authData: AuthData = .none + + enum AuthData { + case structured(TestAuthData) + case cbor(CBOR) + case none + } var cborEncoded: [UInt8] { var attestationObject: [CBOR: CBOR] = [:] @@ -29,8 +36,12 @@ struct TestAttestationObject { if let attStmt { attestationObject[.utf8String("attStmt")] = attStmt } - if let authData { + switch authData { + case .structured(let authData): + attestationObject[.utf8String("authData")] = .byteString(authData.byteArrayRepresentation) + case .cbor(let authData): attestationObject[.utf8String("authData")] = authData + case .none: break } return [UInt8](CBOR.map(attestationObject).encode()) @@ -44,11 +55,22 @@ struct TestAttestationObjectBuilder { self.wrapped = wrapped } - func validMock() -> Self { + func keyAgnosticBase() -> Self { var temp = self temp.wrapped.fmt = .utf8String("none") temp.wrapped.attStmt = .map([:]) - temp.wrapped.authData = .byteString(TestAuthDataBuilder().validMock().build().byteArrayRepresentation) + return temp + } + + func validMockECDSA() -> Self { + var temp = self.keyAgnosticBase() + temp.wrapped.authData = .structured(TestAuthDataBuilder().validMockECDSA().build()) + return temp + } + + func validMockRSA() -> Self { + var temp = self.keyAgnosticBase() + temp.wrapped.authData = .structured(TestAuthDataBuilder().validMockRSA().build()) return temp } @@ -104,25 +126,38 @@ struct TestAttestationObjectBuilder { func invalidAuthData() -> Self { var temp = self - temp.wrapped.authData = .double(1) + temp.wrapped.authData = .cbor(.double(1)) return temp } func emptyAuthData() -> Self { var temp = self - temp.wrapped.authData = .byteString([]) + temp.wrapped.authData = .cbor(.byteString([])) return temp } func zeroAuthData(byteCount: Int) -> Self { var temp = self - temp.wrapped.authData = .byteString([UInt8](repeating: 0, count: byteCount)) + temp.wrapped.authData = .cbor(.byteString([UInt8](repeating: 0, count: byteCount))) return temp } func authData(_ builder: TestAuthDataBuilder) -> Self { var temp = self - temp.wrapped.authData = .byteString(builder.build().byteArrayRepresentation) + temp.wrapped.authData = .structured(builder.build()) + return temp + } + + func authData(builder: (TestAuthDataBuilder) -> TestAuthDataBuilder) -> Self { + var temp = self + switch temp.wrapped.authData { + case .structured(let testAuthData): + temp.wrapped.authData = .structured(builder(.init(wrapped: testAuthData)).build()) + case .cbor: + Issue.record("authData must be structured") + case .none: + temp.wrapped.authData = .structured(builder(.init()).build()) + } return temp } diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift b/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift index 05ba4ea..6f93912 100644 --- a/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift +++ b/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift @@ -58,7 +58,7 @@ struct TestAuthDataBuilder { build().byteArrayRepresentation.base64URLEncodedString() } - func validMock() -> Self { + func validMockECDSA() -> Self { self .relyingPartyIDHash(fromRelyingPartyID: "example.com") .flags(0b11000101) @@ -66,7 +66,20 @@ struct TestAuthDataBuilder { .attestedCredData( credentialIDLength: [0b00000000, 0b00000001], credentialID: [0b00000001], - credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray() + credentialPublicKey: TestCredentialPublicKeyBuilder().validMockECDSA().buildAsByteArray() + ) + .extensions([UInt8](repeating: 0, count: 20)) + } + + func validMockRSA() -> Self { + self + .relyingPartyIDHash(fromRelyingPartyID: "example.com") + .flags(0b11000101) + .counter([0b00000000, 0b00000000, 0b00000000, 0b00000000]) + .attestedCredData( + credentialIDLength: [0b00000000, 0b00000001], + credentialID: [0b00000001], + credentialPublicKey: TestCredentialPublicKeyBuilder().validMockRSA().buildAsByteArray() ) .extensions([UInt8](repeating: 0, count: 20)) } diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift b/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift index 7b6402f..0059dbf 100644 --- a/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift +++ b/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift @@ -12,15 +12,22 @@ //===----------------------------------------------------------------------===// @testable import WebAuthn -import SwiftCBOR +@preconcurrency import SwiftCBOR struct TestCredentialPublicKey { var kty: CBOR? var alg: CBOR? + // EC2, OKP var crv: CBOR? var xCoordinate: CBOR? + + //EC2 var yCoordinate: CBOR? + // RSA + var nCoordinate: CBOR? + var eCoordinate: CBOR? + var byteArrayRepresentation: [UInt8] { var value: [CBOR: CBOR] = [:] if let kty { @@ -38,6 +45,15 @@ struct TestCredentialPublicKey { if let yCoordinate { value[COSEKey.y.cbor] = yCoordinate } + + if let nCoordinate { + value[COSEKey.n.cbor] = nCoordinate + } + + if let eCoordinate { + value[COSEKey.e.cbor] = eCoordinate + } + return CBOR.map(value).encode() } } @@ -53,7 +69,7 @@ struct TestCredentialPublicKeyBuilder { return wrapped.byteArrayRepresentation } - func validMock() -> Self { + func validMockECDSA() -> Self { return self .kty(.ellipticKey) .crv(.p256) @@ -61,6 +77,15 @@ struct TestCredentialPublicKeyBuilder { .xCoordinate(TestECCKeyPair.publicKeyXCoordinate) .yCoordiante(TestECCKeyPair.publicKeyYCoordinate) } + + func validMockRSA() -> Self { + return self + .kty(.rsaKey) + .alg(.algRS256) + .nCoordinate(TestRSAKeyPair.publicKeyNCoordinate) + .eCoordiante(TestRSAKeyPair.publicKeyECoordinate) + } + func kty(_ kty: COSEKeyType) -> Self { var temp = self @@ -91,4 +116,16 @@ struct TestCredentialPublicKeyBuilder { temp.wrapped.yCoordinate = .byteString(yCoordinate) return temp } + + func nCoordinate(_ nCoordinate: [UInt8]) -> Self { + var temp = self + temp.wrapped.nCoordinate = .byteString(nCoordinate) + return temp + } + + func eCoordiante(_ eCoordinate: [UInt8]) -> Self { + var temp = self + temp.wrapped.eCoordinate = .byteString(eCoordinate) + return temp + } } diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift b/Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift index 49bb811..e6b2523 100644 --- a/Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift +++ b/Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift @@ -15,7 +15,7 @@ import Foundation import Crypto import WebAuthn -struct TestECCKeyPair { +struct TestECCKeyPair: TestSigner { static let privateKeyPEM = """ -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUC6oOLmd9F3Ak32L @@ -33,25 +33,33 @@ struct TestECCKeyPair { static let publicKeyXCoordinate = "9621b40922a4b52f513c1b6679b8d48f81019972c7f3c64d6c856de34e45a645".hexadecimal! static let publicKeyYCoordinate = "6637312afe0ae9a2bec08bcf4611e0e9081e6f120311a8986605d5d3b4b248f8".hexadecimal! - static func signature(data: Data) throws -> P256.Signing.ECDSASignature { + static func sign(data: Data) throws -> [UInt8] { let privateKey = try P256.Signing.PrivateKey(pemRepresentation: privateKeyPEM) - return try privateKey.signature(for: data) + return Array(try privateKey.signature(for: data).derRepresentation) } static var signature: [UInt8] { - let authenticatorData = TestAuthDataBuilder() - .validAuthenticationMock() - // .counter([0, 0, 0, 1]) - .buildAsBase64URLEncoded() - - // Create a signature. This part is usually performed by the authenticator - let clientData: Data = TestClientDataJSON(type: "webauthn.get").jsonData - let clientDataHash = SHA256.hash(data: clientData) - let rawAuthenticatorData = authenticatorData.urlDecoded.decoded! - let signatureBase = rawAuthenticatorData + clientDataHash - // swiftlint:disable:next force_try - let signature = try! TestECCKeyPair.signature(data: signatureBase).derRepresentation - - return [UInt8](signature) + get throws { + let authenticatorData = TestAuthDataBuilder() + .validAuthenticationMock() + .buildAsBase64URLEncoded() + + // Create a signature. This part is usually performed by the authenticator + let clientData: Data = TestClientDataJSON(type: "webauthn.get").jsonData + let clientDataHash = SHA256.hash(data: clientData) + let rawAuthenticatorData = authenticatorData.urlDecoded.decoded! + let signatureBase = rawAuthenticatorData + clientDataHash + + return try sign(data: signatureBase) + } } } + +extension TestKeyConfiguration { + static let ecdsa = TestKeyConfiguration( + signer: TestECCKeyPair.self, + credentialPublicKeyBuilder: TestCredentialPublicKeyBuilder().validMockECDSA(), + authDataBuilder: TestAuthDataBuilder().validMockECDSA(), + attestationObjectBuilder: TestAttestationObjectBuilder().validMockECDSA() + ) +} diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestKeyConfiguration.swift b/Tests/WebAuthnTests/Utils/TestModels/TestKeyConfiguration.swift new file mode 100644 index 0000000..7420e8b --- /dev/null +++ b/Tests/WebAuthnTests/Utils/TestModels/TestKeyConfiguration.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift WebAuthn open source project +// +// Copyright (c) 2025 the Swift WebAuthn project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +protocol TestSigner { + static func sign(data: Data) throws -> [UInt8] + + static var signature: [UInt8] { get throws } +} + +struct TestKeyConfiguration { + var signer: any TestSigner.Type + var credentialPublicKeyBuilder: TestCredentialPublicKeyBuilder + var authDataBuilder: TestAuthDataBuilder + var attestationObjectBuilder: TestAttestationObjectBuilder + + var credentialPublicKey: [UInt8] { + credentialPublicKeyBuilder.buildAsByteArray() + } + var attestationObject: [UInt8] { + attestationObjectBuilder.build().cborEncoded + } +} diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift b/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift new file mode 100644 index 0000000..fc35737 --- /dev/null +++ b/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift WebAuthn open source project +// +// Copyright (c) 2023 the Swift WebAuthn project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import Crypto +import WebAuthn +import _CryptoExtras + +struct TestRSAKeyPair: TestSigner { + static let privateKeyPEM = """ + -----BEGIN RSA PRIVATE KEY----- + MIIEpQIBAAKCAQEAngCfNRz1D1HvyvWxURSKpGtymY/qUOW0JfQ77jc8S6p1D/78 + w886pOdcPkfWQbR/qN7PbwfVDHFSJW1wbMSVdwwcUa9ELMpgQIqkLoBEjohWyAT2 + PGKfpEskSTZfq0K/CZ+ZZ4YwNNt/IH7mZhKGQHS5SHpgRAXJuATxQmt4vFSwBp+8 + aN4Wmbzl+S3w2vLY2JaEPT3rL0t5WNQa2QLhH4JWBpSywe0Jl1LxWj+gOZJdZJeN + c1dZtvwnhHXrwg0EjLILFf8V3GglWj8Gg6xuPo8+IQi+gjQEnDiOJpm7uhK4h7qZ + iK2FzUlu4PYm/4oha+LvK7IKcjjFgyAuwq6sKQIDAQABAoIBAEoE5JDPRgatTfb4 + 7t6bDvBD3eYOw6iuU5zMNB8/BSI1cq3RuLxKoqCKOm563ObfFkcYSnkrZCV2GROr + l1V9KsAgjku+HeQV0s2ppYybToKvYGhH2ssjMMKY6SDbNipXFIP/nrAe7wp0IbQp + fuoml3outHY9zkdPptZsilGhY2hmT6oAcoOt2ZWj8mITiQgxGzbT5vQBjkyppFMm + k5h+C64nS6EiJuJUUDUbvkD5hE+nHFr+165oPUmPCXGYGBiayGh8j9j7AHfGAdH3 + zSW+PWDMX9vApJZZauQ4FA7FDXzzFjiT3Xcqyl+yyqL/D5YA+GsSE3pbPZJcLJi2 + ZQ0ShbkCgYEAzhItmWbRILbXULoDfqSXYxW2XXt6Z0JFy3AaOfhbAHYUazraqByY + l0AKVMVp5cH9qk2Z/s/oqIcfsBa3P8H73yBRrDJuPQpcTWuT71qjGfgqQABqzInc + 71IB2F6WZ2Dnkh1uPczS+wiy8kv7Z4nM3hJhvgJ/ZGInQffYNSoiQisCgYEAxEjt + gTZPwXn9PyVmwW19CN1ZNR22nzTqMDZQZMvDYCbLcMEJ+Ls3TvVX5IA5hrGWGa6v + n18CdvAWebmLBww7w0FX2KF4Ug0+YGEOH15wg352dtBQ1Jx8fqeX+z+OoCjNVdiH + YDpTXd7xjcs2umotisH+vo6NHnQLuBnOcGc9ZPsCgYEAiiMtZhPCRIfMtlS7Wv3C + ba10Xh4T43xNhR5Utl+BwUFmVqtRQDhLIbjQNBtR7a6o+KykemessqxB1aykkpza + 1qu3lBMKSujTDyL6PA0qIJJ24Ahnj00rSVJT4lMlx47yLMSFze+rzpP6QOomUTXS + m1r/InxSIVyarGIUES95X5kCgYEAvnct0FZNaibfsSiv3z5JOBLh/4LHtRF5tjLe + LBD1kxXSD6Wh8XRppPq5wQcTyzoDtwQlcvaUw6kRhiifWcVrMHr1rUZyJNypDIjh + VVskvtQ2S/C0nrsCqzwhZDI2Sf+N0KF+K8gtIUe3CaqJfraNXroEYhCdq1FcFdck + 1Tm4/4UCgYEAj+raCSTOBazoE8Z+53WUJ8Y/ZrbqEc7y7ltl6FgbZLWArETglNCD + FmTawde5HZJza2x+BUJpy+31ChbaIctdu6O2tZZCa2FwdtAXf86ZJe0By4fhmK9v + m0Eq9qinAmFyVbkuIzqCJMGeC1FxUYIf/DkpAMOb/ACTyig+YFgFjdU= + -----END RSA PRIVATE KEY----- + """ + + static let publicKeyPEM = """ + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAngCfNRz1D1HvyvWxURSK + pGtymY/qUOW0JfQ77jc8S6p1D/78w886pOdcPkfWQbR/qN7PbwfVDHFSJW1wbMSV + dwwcUa9ELMpgQIqkLoBEjohWyAT2PGKfpEskSTZfq0K/CZ+ZZ4YwNNt/IH7mZhKG + QHS5SHpgRAXJuATxQmt4vFSwBp+8aN4Wmbzl+S3w2vLY2JaEPT3rL0t5WNQa2QLh + H4JWBpSywe0Jl1LxWj+gOZJdZJeNc1dZtvwnhHXrwg0EjLILFf8V3GglWj8Gg6xu + Po8+IQi+gjQEnDiOJpm7uhK4h7qZiK2FzUlu4PYm/4oha+LvK7IKcjjFgyAuwq6s + KQIDAQAB + -----END PUBLIC KEY----- + """ + static let publicKeyNCoordinate = [UInt8](try! _RSA.Signing.PublicKey(pemRepresentation: publicKeyPEM).getKeyPrimitives().modulus) + static let publicKeyECoordinate = [UInt8](try! _RSA.Signing.PublicKey(pemRepresentation: publicKeyPEM).getKeyPrimitives().publicExponent) + + static func sign(data: Data) throws -> [UInt8] { + let privateKey = try _RSA.Signing.PrivateKey(pemRepresentation: privateKeyPEM) + return Array(try privateKey.signature(for: data,padding:_RSA.Signing.Padding.insecurePKCS1v1_5).rawRepresentation) + } + + static var signature: [UInt8] { + get throws { + let authenticatorData = TestAuthDataBuilder() + .validAuthenticationMock() + .buildAsBase64URLEncoded() + + // Create a signature. This part is usually performed by the authenticator + let clientData: Data = TestClientDataJSON(type: "webauthn.get").jsonData + let clientDataHash = SHA256.hash(data: clientData) + let rawAuthenticatorData = authenticatorData.urlDecoded.decoded! + let signatureBase = rawAuthenticatorData + clientDataHash + + return try sign(data: signatureBase) + } + } +} + +extension TestKeyConfiguration { + static let rsa = TestKeyConfiguration( + signer: TestRSAKeyPair.self, + credentialPublicKeyBuilder: TestCredentialPublicKeyBuilder().validMockRSA(), + authDataBuilder: TestAuthDataBuilder().validMockRSA(), + attestationObjectBuilder: TestAttestationObjectBuilder().validMockRSA() + ) +} diff --git a/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift b/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift index 1dfd74f..5c3641a 100644 --- a/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift +++ b/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift @@ -50,57 +50,91 @@ struct WebAuthnManagerAuthenticationTests { #expect(options.userVerification == .preferred) } - @Test - func finishAuthenticationFailsIfCredentialTypeIsInvalid() throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationFailsIfCredentialTypeIsInvalid(keyConfiguration: TestKeyConfiguration) throws { #expect(throws: WebAuthnError.invalidAssertionCredentialType) { - try finishAuthentication(type: "invalid") + try finishAuthentication( + signature: keyConfiguration.signer.signature, + type: "invalid", + credentialPublicKey: keyConfiguration.credentialPublicKey + ) } } - @Test - func finishAuthenticationFailsIfClientDataJSONDecodingFails() throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationFailsIfClientDataJSONDecodingFails(keyConfiguration: TestKeyConfiguration) throws { #expect(throws: DecodingError.self) { - try finishAuthentication(clientDataJSON: [0]) + try finishAuthentication( + clientDataJSON: [0], + signature: keyConfiguration.signer.signature, + credentialPublicKey: keyConfiguration.credentialPublicKey + ) } } - @Test - func finishAuthenticationFailsIfCeremonyTypeDoesNotMatch() throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationFailsIfCeremonyTypeDoesNotMatch(keyConfiguration: TestKeyConfiguration) throws { var clientDataJSON = TestClientDataJSON() clientDataJSON.type = "webauthn.create" #expect(throws: CollectedClientData.CollectedClientDataVerifyError.ceremonyTypeDoesNotMatch) { - try finishAuthentication(clientDataJSON: clientDataJSON.jsonBytes) + try finishAuthentication( + clientDataJSON: clientDataJSON.jsonBytes, + signature: keyConfiguration.signer.signature, + credentialPublicKey: keyConfiguration.credentialPublicKey + ) } } - @Test - func finishAuthenticationFailsIfRelyingPartyIDHashDoesNotMatch() throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationFailsIfRelyingPartyIDHashDoesNotMatch(keyConfiguration: TestKeyConfiguration) throws { #expect(throws: WebAuthnError.relyingPartyIDHashDoesNotMatch) { try finishAuthentication( authenticatorData: TestAuthDataBuilder() .validAuthenticationMock() .relyingPartyIDHash(fromRelyingPartyID: "wrong-id.org") .build() - .byteArrayRepresentation + .byteArrayRepresentation, + signature: keyConfiguration.signer.signature, + credentialPublicKey: keyConfiguration.credentialPublicKey ) } } - @Test - func finishAuthenticationFailsIfUserPresentFlagIsNotSet() throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationFailsIfUserPresentFlagIsNotSet(keyConfiguration: TestKeyConfiguration) throws { #expect(throws: WebAuthnError.userPresentFlagNotSet) { try finishAuthentication( authenticatorData: TestAuthDataBuilder() .validAuthenticationMock() .flags(0b10000000) .build() - .byteArrayRepresentation + .byteArrayRepresentation, + signature: keyConfiguration.signer.signature, + credentialPublicKey: keyConfiguration.credentialPublicKey ) } } - @Test - func finishAuthenticationFailsIfUserIsNotVerified() throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationFailsIfUserIsNotVerified(keyConfiguration: TestKeyConfiguration) throws { #expect(throws: WebAuthnError.userVerifiedFlagNotSet) { try finishAuthentication( authenticatorData: TestAuthDataBuilder() @@ -108,13 +142,18 @@ struct WebAuthnManagerAuthenticationTests { .flags(0b10000001) .build() .byteArrayRepresentation, + signature: keyConfiguration.signer.signature, + credentialPublicKey: keyConfiguration.credentialPublicKey, requireUserVerification: true ) } } - @Test - func finishAuthenticationFailsIfCredentialCounterIsNotUpToDate() throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationFailsIfCredentialCounterIsNotUpToDate(keyConfiguration: TestKeyConfiguration) throws { #expect(throws: WebAuthnError.potentialReplayAttack) { try finishAuthentication( authenticatorData: TestAuthDataBuilder() @@ -122,13 +161,18 @@ struct WebAuthnManagerAuthenticationTests { .counter([0, 0, 0, 1]) // signCount = 1 .build() .byteArrayRepresentation, + signature: keyConfiguration.signer.signature, + credentialPublicKey: keyConfiguration.credentialPublicKey, credentialCurrentSignCount: 2 ) } } - @Test - func finishAuthenticationSucceeds() throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationSucceeds(keyConfiguration: TestKeyConfiguration) throws { let credentialID = TestConstants.mockCredentialID let oldSignCount: UInt32 = 0 @@ -146,13 +190,14 @@ struct WebAuthnManagerAuthenticationTests { let clientData = TestClientDataJSON(type: "webauthn.get").jsonBytes let clientDataHash = SHA256.hash(data: clientData) let signatureBase = Data(authenticatorData) + clientDataHash - let signature = try TestECCKeyPair.signature(data: signatureBase).derRepresentation + let signature = try keyConfiguration.signer.sign(data: signatureBase) let verifiedAuthentication = try finishAuthentication( credentialID: credentialID, clientDataJSON: clientData, authenticatorData: authenticatorData, signature: [UInt8](signature), + credentialPublicKey: keyConfiguration.credentialPublicKey, credentialCurrentSignCount: oldSignCount ) @@ -165,13 +210,13 @@ struct WebAuthnManagerAuthenticationTests { credentialID: [UInt8] = TestConstants.mockCredentialID, clientDataJSON: [UInt8] = TestClientDataJSON(type: "webauthn.get").jsonBytes, authenticatorData: [UInt8] = TestAuthDataBuilder().validAuthenticationMock().build().byteArrayRepresentation, - signature: [UInt8] = TestECCKeyPair.signature, + signature: [UInt8], userHandle: [UInt8]? = "36323638424436452d303831452d344331312d413743332d334444304146333345433134".hexadecimal!, attestationObject: [UInt8]? = nil, authenticatorAttachment: AuthenticatorAttachment? = .platform, type: CredentialType = .publicKey, expectedChallenge: [UInt8] = TestConstants.mockChallenge, - credentialPublicKey: [UInt8] = TestCredentialPublicKeyBuilder().validMock().buildAsByteArray(), + credentialPublicKey: [UInt8], credentialCurrentSignCount: UInt32 = 0, requireUserVerification: Bool = false ) throws -> VerifiedAuthentication { diff --git a/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift b/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift index 0c76582..579d7ac 100644 --- a/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift +++ b/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift @@ -18,8 +18,11 @@ import Crypto struct WebAuthnManagerIntegrationTests { // swiftlint:disable:next function_body_length - @Test - func registrationAndAuthenticationSucceeds() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func registrationAndAuthenticationSucceeds(keyConfiguration: TestKeyConfiguration) async throws { let configuration = WebAuthnManager.Configuration( relyingPartyID: "example.com", relyingPartyName: "Example RP", @@ -57,12 +60,12 @@ struct WebAuthnManagerIntegrationTests { // The following lines reflect what an authenticator normally produces let mockCredentialID = [UInt8](repeating: 1, count: 10) let mockClientDataJSON = TestClientDataJSON(challenge: mockChallenge.base64URLEncodedString()) - let mockCredentialPublicKey = TestCredentialPublicKeyBuilder().validMock().buildAsByteArray() - let mockAttestationObject = TestAttestationObjectBuilder().validMock().authData( - TestAuthDataBuilder().validMock() + let mockCredentialPublicKey = keyConfiguration.credentialPublicKey + let mockAttestationObject = keyConfiguration.attestationObjectBuilder + .authData { $0 .attestedCredData(credentialPublicKey: mockCredentialPublicKey) .noExtensionData() - ).build().cborEncoded + }.build().cborEncoded let registrationResponse = RegistrationCredential( id: mockCredentialID.base64URLEncodedString(), @@ -132,7 +135,7 @@ struct WebAuthnManagerIntegrationTests { ).jsonBytes let clientDataHash = SHA256.hash(data: clientData) let signatureBase = Data(authenticatorData + clientDataHash) - let signature = try TestECCKeyPair.signature(data: signatureBase).derRepresentation + let signature = try keyConfiguration.signer.sign(data: signatureBase) let authenticationCredential = AuthenticationCredential( id: mockCredentialID.base64URLEncodedString(), @@ -153,7 +156,7 @@ struct WebAuthnManagerIntegrationTests { let successfullAuthentication = try webAuthnManager.finishAuthentication( credential: authenticationCredential, expectedChallenge: mockChallenge, - credentialPublicKey: mockCredentialPublicKey, + credentialPublicKey: keyConfiguration.credentialPublicKey, credentialCurrentSignCount: oldSignCount, requireUserVerification: false ) diff --git a/Tests/WebAuthnTests/WebAuthnManagerRegistrationTests.swift b/Tests/WebAuthnTests/WebAuthnManagerRegistrationTests.swift index 51cdb5c..bf9ecbb 100644 --- a/Tests/WebAuthnTests/WebAuthnManagerRegistrationTests.swift +++ b/Tests/WebAuthnTests/WebAuthnManagerRegistrationTests.swift @@ -15,7 +15,6 @@ import Testing import SwiftCBOR -// swiftlint:disable:next type_body_length struct WebAuthnManagerRegistrationTests { var webAuthnManager: WebAuthnManager! @@ -54,48 +53,76 @@ struct WebAuthnManagerRegistrationTests { // MARK: - finishRegistration() - @Test - func finishRegistrationFailsIfCeremonyTypeDoesNotMatch() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfCeremonyTypeDoesNotMatch(keyConfiguration: TestKeyConfiguration) async throws { var clientDataJSON = TestClientDataJSON() clientDataJSON.type = "webauthn.get" await #expect(throws: CollectedClientData.CollectedClientDataVerifyError.ceremonyTypeDoesNotMatch) { - try await finishRegistration(clientDataJSON: clientDataJSON.jsonBytes) + try await finishRegistration( + clientDataJSON: clientDataJSON.jsonBytes, + attestationObject: keyConfiguration.attestationObject + ) } } - @Test - func finishRegistrationFailsIfChallengeDoesNotMatch() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfChallengeDoesNotMatch(keyConfiguration: TestKeyConfiguration) async throws { var clientDataJSON = TestClientDataJSON() clientDataJSON.challenge = [0, 2, 4].base64URLEncodedString() await #expect(throws: CollectedClientData.CollectedClientDataVerifyError.challengeDoesNotMatch) { try await finishRegistration( challenge: [UInt8]("definitely another challenge".utf8), - clientDataJSON: clientDataJSON.jsonBytes + clientDataJSON: clientDataJSON.jsonBytes, + attestationObject: keyConfiguration.attestationObject ) } } - @Test - func finishRegistrationFailsIfOriginDoesNotMatch() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfOriginDoesNotMatch(keyConfiguration: TestKeyConfiguration) async throws { var clientDataJSON = TestClientDataJSON() clientDataJSON.origin = "https://random-origin.org" // `webAuthnManager` is configured with origin = https://example.com await #expect(throws: CollectedClientData.CollectedClientDataVerifyError.originDoesNotMatch) { - try await finishRegistration(clientDataJSON: clientDataJSON.jsonBytes) + try await finishRegistration( + clientDataJSON: clientDataJSON.jsonBytes, + attestationObject: keyConfiguration.attestationObject + ) } } - @Test - func finishRegistrationFailsWithInvalidCredentialCreationType() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsWithInvalidCredentialCreationType(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.invalidCredentialCreationType) { - try await finishRegistration(type: "foo") + try await finishRegistration( + type: "foo", + attestationObject: keyConfiguration.attestationObject + ) } } - @Test - func finishRegistrationFailsIfClientDataJSONDecodingFails() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfClientDataJSONDecodingFails(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: DecodingError.self) { - try await finishRegistration(clientDataJSON: [0]) + try await finishRegistration( + clientDataJSON: [0], + attestationObject: keyConfiguration.attestationObject + ) } } @@ -106,12 +133,14 @@ struct WebAuthnManagerRegistrationTests { } } - @Test - func finishRegistrationFailsIfAuthDataIsInvalid() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfAuthDataIsInvalid(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.invalidAuthData) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() + attestationObject: keyConfiguration.attestationObjectBuilder .invalidAuthData() .build() .cborEncoded @@ -119,12 +148,14 @@ struct WebAuthnManagerRegistrationTests { } } - @Test - func finishRegistrationFailsIfFmtIsInvalid() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfFmtIsInvalid(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.invalidFmt) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() + attestationObject: keyConfiguration.attestationObjectBuilder .invalidFmt() .build() .cborEncoded @@ -132,12 +163,14 @@ struct WebAuthnManagerRegistrationTests { } } - @Test - func finishRegistrationFailsIfAttStmtIsMissing() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfAttStmtIsMissing(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.missingAttStmt) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() + attestationObject: keyConfiguration.attestationObjectBuilder .missingAttStmt() .build() .cborEncoded @@ -145,12 +178,14 @@ struct WebAuthnManagerRegistrationTests { } } - @Test - func finishRegistrationFailsIfAuthDataIsTooShort() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfAuthDataIsTooShort(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.authDataTooShort) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() + attestationObject: keyConfiguration.attestationObjectBuilder .zeroAuthData(byteCount: 36) .build() .cborEncoded @@ -158,111 +193,119 @@ struct WebAuthnManagerRegistrationTests { } } - @Test - func finishRegistrationFailsIfAttestedCredentialDataFlagIsSetButThereIsNoCredentialData() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfAttestedCredentialDataFlagIsSetButThereIsNoCredentialData(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.attestedCredentialDataMissing) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData( - TestAuthDataBuilder() - .validMock() - .flags(0b01000001) - .noAttestedCredentialData() - .noExtensionData() - ) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0 + .flags(0b01000001) + .noAttestedCredentialData() + .noExtensionData() + } .build() .cborEncoded ) } } - @Test - func finishRegistrationFailsIfAttestedCredentialDataFlagIsNotSetButThereIsCredentialData() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfAttestedCredentialDataFlagIsNotSetButThereIsCredentialData(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.attestedCredentialFlagNotSet) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData( - TestAuthDataBuilder() - .validMock() - .flags(0b00000001) - .attestedCredData(credentialPublicKey: []) - ) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0 + .flags(0b00000001) + .attestedCredData(credentialPublicKey: []) + } .build() .cborEncoded ) } } - @Test - func finishRegistrationFailsIfExtensionDataFlagIsSetButThereIsNoExtensionData() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfExtensionDataFlagIsSetButThereIsNoExtensionData(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.extensionDataMissing) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData(TestAuthDataBuilder().validMock().noExtensionData().flags(0b11000001)) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0.noExtensionData().flags(0b11000001) } .build() .cborEncoded ) } } - @Test - func finishRegistrationFailsIfCredentialIdIsTooShort() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfCredentialIdIsTooShort(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.credentialIDTooShort) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData( - TestAuthDataBuilder() - .validMock() - .attestedCredData( - credentialIDLength: [0b00000000, 0b00000010], // we expect length = 2 - credentialID: [255], // but only get length = 1 - credentialPublicKey: [] - ) - .noExtensionData() - ) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0 + .attestedCredData( + credentialIDLength: [0b00000000, 0b00000010], // we expect length = 2 + credentialID: [255], // but only get length = 1 + credentialPublicKey: [] + ) + .noExtensionData() + } .build() .cborEncoded ) } } - @Test - func finishRegistrationFailsIfRelyingPartyIDHashDoesNotMatch() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfRelyingPartyIDHashDoesNotMatch(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.relyingPartyIDHashDoesNotMatch) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData(TestAuthDataBuilder().validMock().relyingPartyIDHash(fromRelyingPartyID: "invalid-id.com")) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0.relyingPartyIDHash(fromRelyingPartyID: "invalid-id.com") } .build() .cborEncoded ) } } - @Test - func finishRegistrationFailsIfUserPresentFlagIsNotSet() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfUserPresentFlagIsNotSet(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.userPresentFlagNotSet) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData(TestAuthDataBuilder().validMock().flags(0b11000000)) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0.flags(0b11000000) } .build() .cborEncoded ) } } - @Test - func finishRegistrationFailsIfUserVerificationFlagIsNotSetButRequired() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfUserVerificationFlagIsNotSetButRequired(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.userVerificationRequiredButFlagNotSet) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData(TestAuthDataBuilder().validMock().flags(0b11000001)) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0.flags(0b11000001) } .build() .cborEncoded, requireUserVerification: true @@ -270,12 +313,14 @@ struct WebAuthnManagerRegistrationTests { } } - @Test - func finishRegistrationFailsIfAttFmtIsNoneButAttStmtIsIncluded() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfAttFmtIsNoneButAttStmtIsIncluded(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.attestationStatementMustBeEmpty) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() + attestationObject: keyConfiguration.attestationObjectBuilder .fmt("none") .attStmt(.double(123)) .build() @@ -285,28 +330,34 @@ struct WebAuthnManagerRegistrationTests { } } - @Test - func finishRegistrationFailsIfRawIDIsTooLong() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationFailsIfRawIDIsTooLong(keyConfiguration: TestKeyConfiguration) async throws { await #expect(throws: WebAuthnError.credentialRawIDTooLong) { - try await finishRegistration(rawID: [UInt8](repeating: 0, count: 1024)) + try await finishRegistration( + rawID: [UInt8](repeating: 0, count: 1024), + attestationObject: keyConfiguration.attestationObject + ) } } - @Test - func finishAuthenticationFailsIfCredentialIDTooLong() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishAuthenticationFailsIfCredentialIDTooLong(keyConfiguration: TestKeyConfiguration) async throws { /// This should succeed as it's on the border of being acceptable _ = try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData( - TestAuthDataBuilder() - .validMock() - .attestedCredData( - credentialIDLength: [0b000_00011, 0b1111_1111], - credentialID: Array(repeating: 0, count: 1023), - credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray() - ) - ) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0 + .attestedCredData( + credentialIDLength: [0b000_00011, 0b1111_1111], + credentialID: Array(repeating: 0, count: 1023), + credentialPublicKey: keyConfiguration.credentialPublicKey + ) + } .build() .cborEncoded ) @@ -314,33 +365,31 @@ struct WebAuthnManagerRegistrationTests { /// While this one should throw await #expect(throws: WebAuthnError.credentialIDTooLong) { try await finishRegistration( - attestationObject: TestAttestationObjectBuilder() - .validMock() - .authData( - TestAuthDataBuilder() - .validMock() - .attestedCredData( - credentialIDLength: [0b000_00100, 0b0000_0000], - credentialID: Array(repeating: 0, count: 1024), - credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray() - ) - ) + attestationObject: keyConfiguration.attestationObjectBuilder + .authData { $0 + .attestedCredData( + credentialIDLength: [0b000_00100, 0b0000_0000], + credentialID: Array(repeating: 0, count: 1024), + credentialPublicKey: keyConfiguration.credentialPublicKey + ) + } .build() .cborEncoded ) } } - @Test - func finishRegistrationSucceeds() async throws { + @Test(arguments: [ + TestKeyConfiguration.ecdsa, + TestKeyConfiguration.rsa, + ]) + func finishRegistrationSucceeds(keyConfiguration: TestKeyConfiguration) async throws { let credentialID: [UInt8] = [0, 1, 0, 1, 0, 1] - let credentialPublicKey: [UInt8] = TestCredentialPublicKeyBuilder().validMock().buildAsByteArray() - let authData = TestAuthDataBuilder() - .validMock() + let credentialPublicKey: [UInt8] = keyConfiguration.credentialPublicKey + let authData = keyConfiguration.authDataBuilder .attestedCredData(credentialPublicKey: credentialPublicKey) .noExtensionData() - let attestationObject = TestAttestationObjectBuilder() - .validMock() + let attestationObject = keyConfiguration.attestationObjectBuilder .authData(authData) .build() .cborEncoded @@ -370,7 +419,7 @@ struct WebAuthnManagerRegistrationTests { type: CredentialType = .publicKey, rawID: [UInt8] = "e0fac9350509f71748d83782ccaf6b4c1462c615c70e255da1344e40887c8fcd".hexadecimal!, clientDataJSON: [UInt8] = TestClientDataJSON().jsonBytes, - attestationObject: [UInt8] = TestAttestationObjectBuilder().validMock().build().cborEncoded, + attestationObject: [UInt8], requireUserVerification: Bool = false, confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true } ) async throws -> Credential {