Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
45 changes: 23 additions & 22 deletions Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -153,6 +152,8 @@ struct EC2PublicKey: PublicKey, Sendable {
.isValidSignature(ecdsaSignature, for: data) else {
throw WebAuthnError.invalidSignature
}
default:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think we may want to list out the unsupported types here to catch new key sizes in the future, but the list may be a long one, so just a nit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm less worried about this. The want to throw an unsupported and any new supported ones will need to be added to the switch to make tests pass

throw WebAuthnError.unsupportedCredentialPublicKeyAlgorithm
}
}
}
Expand Down Expand Up @@ -184,26 +185,26 @@ 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
// }
//throw WebAuthnError.unsupported
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
}

guard try _RSA.Signing.PublicKey(n:n, e:e).isValidSignature(
rsaSignature,
for: data,
padding: rsaPadding)
else {
throw WebAuthnError.invalidSignature
}
}
}

Expand Down
12 changes: 10 additions & 2 deletions Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,19 @@ struct TestAttestationObjectBuilder {
self.wrapped = wrapped
}

func validMock() -> Self {
func validMockECDSA() -> Self {
var temp = self
temp.wrapped.fmt = .utf8String("none")
temp.wrapped.attStmt = .map([:])
temp.wrapped.authData = .byteString(TestAuthDataBuilder().validMock().build().byteArrayRepresentation)
temp.wrapped.authData = .byteString(TestAuthDataBuilder().validMockECDSA().build().byteArrayRepresentation)
return temp
}

func validMockRSA() -> Self {
var temp = self
temp.wrapped.fmt = .utf8String("none")
temp.wrapped.attStmt = .map([:])
temp.wrapped.authData = .byteString(TestAuthDataBuilder().validMockRSA().build().byteArrayRepresentation)
return temp
}

Expand Down
17 changes: 15 additions & 2 deletions Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,28 @@ struct TestAuthDataBuilder {
build().byteArrayRepresentation.base64URLEncodedString()
}

func validMock() -> Self {
func validMockECDSA() -> Self {
self
.relyingPartyIDHash(fromRelyingPartyID: "example.com")
.flags(0b11000101)
.counter([0b00000000, 0b00000000, 0b00000000, 0b00000000])
.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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ 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 {
Expand All @@ -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()
}
}
Expand All @@ -53,14 +69,23 @@ struct TestCredentialPublicKeyBuilder {
return wrapped.byteArrayRepresentation
}

func validMock() -> Self {
func validMockECDSA() -> Self {
return self
.kty(.ellipticKey)
.crv(.p256)
.alg(.algES256)
.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
Expand Down Expand Up @@ -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
}
}
35 changes: 18 additions & 17 deletions Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct TestECCKeyPair {
xk1shW3jTkWmRWY3MSr+CumivsCLz0YR4OkIHm8SAxGomGYF1dO0skj4
-----END PRIVATE KEY-----
"""

static let publicKeyPEM = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEliG0CSKktS9RPBtmebjUj4EBmXLH
Expand All @@ -32,26 +32,27 @@ struct TestECCKeyPair {
"""
static let publicKeyXCoordinate = "9621b40922a4b52f513c1b6679b8d48f81019972c7f3c64d6c856de34e45a645".hexadecimal!
static let publicKeyYCoordinate = "6637312afe0ae9a2bec08bcf4611e0e9081e6f120311a8986605d5d3b4b248f8".hexadecimal!

static func signature(data: Data) throws -> P256.Signing.ECDSASignature {
let privateKey = try P256.Signing.PrivateKey(pemRepresentation: privateKeyPEM)
return try privateKey.signature(for: data)
}

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
// swiftlint:disable:next force_try
let signature = try! TestECCKeyPair.signature(data: signatureBase).derRepresentation

return [UInt8](signature)
}
}
}
Loading