Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -25,33 +25,33 @@ public enum COSEAlgorithmIdentifier: Int, RawRepresentable, CaseIterable, Encoda
/// 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
/// 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
}
}
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)
}
}
}
82 changes: 82 additions & 0 deletions Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// TestRSAKeyPair.swift
// swift-webauthn
//
// Created by David Scrève on 27/01/2025.
//

import Foundation
import Crypto
import WebAuthn
import _CryptoExtras


struct TestRSAKeyPair {
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 signature(data: Data) throws -> _RSA.Signing.RSASignature {
let privateKey = try _RSA.Signing.PrivateKey(pemRepresentation: privateKeyPEM)
let rsaSignature = try privateKey.signature(for: data,padding:_RSA.Signing.Padding.insecurePKCS1v1_5)
return rsaSignature
}

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

return [UInt8](signature)
}
}
}
Loading