-
-
Notifications
You must be signed in to change notification settings - Fork 32
RSA support and modularity add-ons #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ public struct AuthenticationCredential: Sendable { | |
public let type: CredentialType | ||
} | ||
|
||
extension AuthenticationCredential: Decodable { | ||
extension AuthenticationCredential: Codable { | ||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
|
||
|
@@ -44,6 +44,17 @@ extension AuthenticationCredential: Decodable { | |
authenticatorAttachment = try container.decodeIfPresent(AuthenticatorAttachment.self, forKey: .authenticatorAttachment) | ||
type = try container.decode(CredentialType.self, forKey: .type) | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
|
||
try container.encode(id, forKey: .id) | ||
try container.encode(rawID.base64URLEncodedString(), forKey: .rawID) | ||
try container.encode(response, forKey: .response) | ||
try container.encodeIfPresent(authenticatorAttachment, forKey: .authenticatorAttachment) | ||
try container.encode(type, forKey: .type) | ||
} | ||
Comment on lines
+48
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: formatting |
||
|
||
|
||
private enum CodingKeys: String, CodingKey { | ||
case id | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,7 +48,7 @@ public struct AuthenticatorAssertionResponse: Sendable { | |
public let attestationObject: [UInt8]? | ||
} | ||
|
||
extension AuthenticatorAssertionResponse: Decodable { | ||
extension AuthenticatorAssertionResponse: Codable { | ||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
|
||
|
@@ -58,6 +58,17 @@ extension AuthenticatorAssertionResponse: Decodable { | |
userHandle = try container.decodeBytesFromURLEncodedBase64IfPresent(forKey: .userHandle) | ||
attestationObject = try container.decodeBytesFromURLEncodedBase64IfPresent(forKey: .attestationObject) | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
|
||
try container.encode(clientDataJSON.base64URLEncodedString(), forKey: .clientDataJSON) | ||
try container.encode(authenticatorData.base64URLEncodedString(), forKey: .authenticatorData) | ||
try container.encode(signature.base64URLEncodedString(), forKey: .signature) | ||
try container.encodeIfPresent(userHandle?.base64URLEncodedString(),forKey: .userHandle) | ||
try container.encodeIfPresent(attestationObject?.base64URLEncodedString(),forKey: .attestationObject) | ||
} | ||
Comment on lines
+62
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: formatting |
||
|
||
|
||
private enum CodingKeys: String, CodingKey { | ||
case clientDataJSON | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,36 +12,38 @@ | |
//===----------------------------------------------------------------------===// | ||
|
||
import Foundation | ||
import SwiftOpenAPI | ||
|
||
/// The `PublicKeyCredentialRequestOptions` gets passed to the WebAuthn API (`navigator.credentials.get()`) | ||
/// | ||
/// When encoding using `Encodable`, the byte arrays are encoded as base64url. | ||
/// | ||
/// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options | ||
public struct PublicKeyCredentialRequestOptions: Encodable, Sendable { | ||
@OpenAPIDescriptable | ||
public struct PublicKeyCredentialRequestOptions: Codable, Sendable { | ||
/// A challenge that the authenticator signs, along with other data, when producing an authentication assertion | ||
/// | ||
/// When encoding using `Encodable` this is encoded as base64url. | ||
public let challenge: [UInt8] | ||
public var challenge: [UInt8] | ||
|
||
/// A time, in seconds, that the caller is willing to wait for the call to complete. This is treated as a | ||
/// hint, and may be overridden by the client. | ||
/// | ||
/// - Note: When encoded, this value is represented in milleseconds as a ``UInt32``. | ||
/// See https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options | ||
public let timeout: Duration? | ||
public var timeout: Duration? | ||
|
||
/// The ID of the Relying Party making the request. | ||
/// | ||
/// This is configured on ``WebAuthnManager`` before its ``WebAuthnManager/beginAuthentication(timeout:allowCredentials:userVerification:)`` method is called. | ||
/// - Note: When encoded, this field appears as `rpId` to match the expectations of `navigator.credentials.get()`. | ||
public let relyingPartyID: String | ||
public var relyingPartyID: String | ||
|
||
/// Optionally used by the client to find authenticators eligible for this authentication ceremony. | ||
public let allowCredentials: [PublicKeyCredentialDescriptor]? | ||
public var allowCredentials: [PublicKeyCredentialDescriptor]? | ||
|
||
/// Specifies whether the user should be verified during the authentication ceremony. | ||
public let userVerification: UserVerificationRequirement? | ||
public var userVerification: UserVerificationRequirement? | ||
|
||
// let extensions: [String: Any] | ||
|
||
|
@@ -50,15 +52,44 @@ public struct PublicKeyCredentialRequestOptions: Encodable, Sendable { | |
|
||
try container.encode(challenge.base64URLEncodedString(), forKey: .challenge) | ||
try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout) | ||
try container.encode(relyingPartyID, forKey: .rpID) | ||
try container.encode(relyingPartyID, forKey: .relyingPartyID) | ||
try container.encodeIfPresent(allowCredentials, forKey: .allowCredentials) | ||
try container.encodeIfPresent(userVerification, forKey: .userVerification) | ||
} | ||
|
||
public init(challenge: [UInt8], timeout: Duration?, relyingPartyID: String, allowCredentials: [PublicKeyCredentialDescriptor]?, userVerification: UserVerificationRequirement?) { | ||
self.challenge = challenge | ||
self.timeout = timeout | ||
self.relyingPartyID = relyingPartyID | ||
self.allowCredentials = allowCredentials | ||
self.userVerification = userVerification | ||
} | ||
|
||
public init(_ src : PublicKeyCredentialRequestOptions) { | ||
self.challenge=src.challenge | ||
self.timeout=src.timeout | ||
self.relyingPartyID=src.relyingPartyID | ||
self.allowCredentials=src.allowCredentials | ||
self.userVerification = src.userVerification | ||
} | ||
|
||
|
||
public init(from decoder: any Decoder) throws { | ||
let values = try decoder.container(keyedBy: CodingKeys.self) | ||
|
||
self.challenge = try values.decodeBytesFromURLEncodedBase64(forKey: .challenge) | ||
|
||
if let timeout = try values.decodeIfPresent(UInt32.self, forKey:.timeout) { | ||
self.timeout=Duration.milliseconds(timeout) | ||
} | ||
self.relyingPartyID=try values.decode(String.self, forKey:.relyingPartyID) | ||
self.allowCredentials=try values.decodeIfPresent([PublicKeyCredentialDescriptor].self,forKey: .allowCredentials) | ||
self.userVerification=try values.decodeIfPresent(UserVerificationRequirement.self,forKey: .userVerification) | ||
} | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case challenge | ||
case timeout | ||
case rpID = "rpId" | ||
case relyingPartyID = "rpId" | ||
case allowCredentials | ||
case userVerification | ||
} | ||
|
@@ -67,10 +98,10 @@ public struct PublicKeyCredentialRequestOptions: Encodable, Sendable { | |
/// Information about a generated credential. | ||
/// | ||
/// When encoding using `Encodable`, `id` is encoded as base64url. | ||
public struct PublicKeyCredentialDescriptor: Equatable, Encodable, Sendable { | ||
public struct PublicKeyCredentialDescriptor: Equatable, Codable, Sendable { | ||
/// Defines hints as to how clients might communicate with a particular authenticator in order to obtain an | ||
/// assertion for a specific credential | ||
public enum AuthenticatorTransport: String, Equatable, Encodable, Sendable { | ||
public enum AuthenticatorTransport: String, Equatable, Codable, Sendable { | ||
/// Indicates the respective authenticator can be contacted over removable USB. | ||
case usb | ||
/// Indicates the respective authenticator can be contacted over Near Field Communication (NFC). | ||
|
@@ -114,6 +145,14 @@ public struct PublicKeyCredentialDescriptor: Equatable, Encodable, Sendable { | |
try container.encode(id.base64URLEncodedString(), forKey: .id) | ||
try container.encodeIfPresent(transports, forKey: .transports) | ||
} | ||
|
||
public init(from decoder: any Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
let type = try container.decode(CredentialType.self,forKey: .type) | ||
let id = try container.decodeBytesFromURLEncodedBase64( forKey: .id) | ||
let transports = try container.decodeIfPresent([AuthenticatorTransport].self,forKey:.transports) ?? [] | ||
self.init(type: type, id:id, transports: transports) | ||
} | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case type | ||
|
@@ -124,7 +163,7 @@ public struct PublicKeyCredentialDescriptor: Equatable, Encodable, Sendable { | |
|
||
/// The Relying Party may require user verification for some of its operations but not for others, and may use this | ||
/// type to express its needs. | ||
public enum UserVerificationRequirement: String, Encodable, Sendable { | ||
public enum UserVerificationRequirement: String, Codable, Sendable { | ||
/// The Relying Party requires user verification for the operation and will fail the overall ceremony if the | ||
/// user wasn't verified. | ||
case required | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -12,40 +12,42 @@ | |||||||||||||||||||
//===----------------------------------------------------------------------===// | ||||||||||||||||||||
|
||||||||||||||||||||
import Foundation | ||||||||||||||||||||
import SwiftOpenAPI | ||||||||||||||||||||
|
||||||||||||||||||||
/// The `PublicKeyCredentialCreationOptions` gets passed to the WebAuthn API (`navigator.credentials.create()`) | ||||||||||||||||||||
/// | ||||||||||||||||||||
/// Generally this should not be created manually. Instead use `RelyingParty.beginRegistration()`. When encoding using | ||||||||||||||||||||
/// `Encodable` byte arrays are base64url encoded. | ||||||||||||||||||||
/// | ||||||||||||||||||||
/// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-makecredentialoptions | ||||||||||||||||||||
public struct PublicKeyCredentialCreationOptions: Encodable, Sendable { | ||||||||||||||||||||
@OpenAPIDescriptable | ||||||||||||||||||||
public struct PublicKeyCredentialCreationOptions: Codable, Sendable { | ||||||||||||||||||||
/// A byte array randomly generated by the Relying Party. Should be at least 16 bytes long to ensure sufficient | ||||||||||||||||||||
/// entropy. | ||||||||||||||||||||
/// | ||||||||||||||||||||
/// The Relying Party should store the challenge temporarily until the registration flow is complete. When | ||||||||||||||||||||
/// encoding using `Encodable`, the challenge is base64url encoded. | ||||||||||||||||||||
public let challenge: [UInt8] | ||||||||||||||||||||
public var challenge: [UInt8] | ||||||||||||||||||||
|
||||||||||||||||||||
/// Contains names and an identifier for the user account performing the registration | ||||||||||||||||||||
public let user: PublicKeyCredentialUserEntity | ||||||||||||||||||||
public var user: PublicKeyCredentialUserEntity | ||||||||||||||||||||
|
||||||||||||||||||||
/// Contains a name and an identifier for the Relying Party responsible for the request | ||||||||||||||||||||
public let relyingParty: PublicKeyCredentialRelyingPartyEntity | ||||||||||||||||||||
public var relyingParty: PublicKeyCredentialRelyingPartyEntity | ||||||||||||||||||||
|
||||||||||||||||||||
/// A list of key types and signature algorithms the Relying Party supports. Ordered from most preferred to least | ||||||||||||||||||||
/// preferred. | ||||||||||||||||||||
public let publicKeyCredentialParameters: [PublicKeyCredentialParameters] | ||||||||||||||||||||
public var publicKeyCredentialParameters: [PublicKeyCredentialParameters] | ||||||||||||||||||||
|
||||||||||||||||||||
/// A time, in seconds, that the caller is willing to wait for the call to complete. This is treated as a | ||||||||||||||||||||
/// hint, and may be overridden by the client. | ||||||||||||||||||||
/// | ||||||||||||||||||||
/// - Note: When encoded, this value is represented in milleseconds as a ``UInt32``. | ||||||||||||||||||||
public let timeout: Duration? | ||||||||||||||||||||
public var timeout: Duration? | ||||||||||||||||||||
|
||||||||||||||||||||
/// Sets the Relying Party's preference for attestation conveyance. At the time of writing only `none` is | ||||||||||||||||||||
/// supported. | ||||||||||||||||||||
public let attestation: AttestationConveyancePreference | ||||||||||||||||||||
public var attestation: AttestationConveyancePreference | ||||||||||||||||||||
|
||||||||||||||||||||
public func encode(to encoder: Encoder) throws { | ||||||||||||||||||||
var container = encoder.container(keyedBy: CodingKeys.self) | ||||||||||||||||||||
|
@@ -57,6 +59,29 @@ public struct PublicKeyCredentialCreationOptions: Encodable, Sendable { | |||||||||||||||||||
try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout) | ||||||||||||||||||||
try container.encode(attestation, forKey: .attestation) | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public init(from decoder: any Decoder) throws { | ||||||||||||||||||||
let values = try decoder.container(keyedBy: CodingKeys.self) | ||||||||||||||||||||
|
||||||||||||||||||||
self.challenge = try values.decodeBytesFromURLEncodedBase64(forKey: .challenge) | ||||||||||||||||||||
self.user = try values.decode(PublicKeyCredentialUserEntity.self, forKey: .user) | ||||||||||||||||||||
self.relyingParty = try values.decode(PublicKeyCredentialRelyingPartyEntity.self, forKey:.relyingParty) | ||||||||||||||||||||
self.publicKeyCredentialParameters = try values.decode([PublicKeyCredentialParameters].self, forKey:.publicKeyCredentialParameters) | ||||||||||||||||||||
if let timeout = try values.decodeIfPresent(UInt32.self,forKey:.timeout) { | ||||||||||||||||||||
self.timeout = Duration.milliseconds(timeout) | ||||||||||||||||||||
} | ||||||||||||||||||||
self.attestation = try values.decode(AttestationConveyancePreference.self, forKey:.attestation) | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public init(challenge: [UInt8],user: PublicKeyCredentialUserEntity,relyingParty: PublicKeyCredentialRelyingPartyEntity,publicKeyCredentialParameters: [PublicKeyCredentialParameters], | ||||||||||||||||||||
timeout: Duration?,attestation: AttestationConveyancePreference) { | ||||||||||||||||||||
Comment on lines
+76
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: formatting
Suggested change
|
||||||||||||||||||||
self.challenge = challenge | ||||||||||||||||||||
self.user = user | ||||||||||||||||||||
self.relyingParty = relyingParty | ||||||||||||||||||||
self.publicKeyCredentialParameters = publicKeyCredentialParameters | ||||||||||||||||||||
self.timeout = timeout | ||||||||||||||||||||
self.attestation = attestation | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
private enum CodingKeys: String, CodingKey { | ||||||||||||||||||||
case challenge | ||||||||||||||||||||
|
@@ -70,7 +95,7 @@ public struct PublicKeyCredentialCreationOptions: Encodable, Sendable { | |||||||||||||||||||
|
||||||||||||||||||||
// MARK: - Credential parameters | ||||||||||||||||||||
/// From §5.3 (https://w3c.github.io/TR/webauthn/#dictionary-credential-params) | ||||||||||||||||||||
public struct PublicKeyCredentialParameters: Equatable, Encodable, Sendable { | ||||||||||||||||||||
public struct PublicKeyCredentialParameters: Equatable, Codable, Sendable { | ||||||||||||||||||||
/// The type of credential to be created. At the time of writing always ``CredentialType/publicKey``. | ||||||||||||||||||||
public let type: CredentialType | ||||||||||||||||||||
/// The cryptographic signature algorithm with which the newly generated credential will be used, and thus also | ||||||||||||||||||||
|
@@ -87,6 +112,18 @@ public struct PublicKeyCredentialParameters: Equatable, Encodable, Sendable { | |||||||||||||||||||
self.type = type | ||||||||||||||||||||
self.alg = alg | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public init(from decoder: any Decoder) throws { | ||||||||||||||||||||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||||||||||||||||||||
let type = try container.decode(CredentialType.self,forKey: .type) | ||||||||||||||||||||
let alg = try container.decode(COSEAlgorithmIdentifier.self, forKey: .alg) | ||||||||||||||||||||
self.init(type:type,alg:alg) | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
private enum CodingKeys: String, CodingKey { | ||||||||||||||||||||
case type | ||||||||||||||||||||
case alg | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
extension Array where Element == PublicKeyCredentialParameters { | ||||||||||||||||||||
|
@@ -103,22 +140,31 @@ extension Array where Element == PublicKeyCredentialParameters { | |||||||||||||||||||
/// From §5.4.2 (https://www.w3.org/TR/webauthn/#sctn-rp-credential-params). | ||||||||||||||||||||
/// The PublicKeyCredentialRelyingPartyEntity dictionary is used to supply additional Relying Party attributes when | ||||||||||||||||||||
/// creating a new credential. | ||||||||||||||||||||
public struct PublicKeyCredentialRelyingPartyEntity: Encodable, Sendable { | ||||||||||||||||||||
public struct PublicKeyCredentialRelyingPartyEntity: Codable, Sendable { | ||||||||||||||||||||
/// A unique identifier for the Relying Party entity. | ||||||||||||||||||||
public let id: String | ||||||||||||||||||||
|
||||||||||||||||||||
/// A human-readable identifier for the Relying Party, intended only for display. For example, "ACME Corporation", | ||||||||||||||||||||
/// "Wonderful Widgets, Inc." or "ОАО Примертех". | ||||||||||||||||||||
public let name: String | ||||||||||||||||||||
|
||||||||||||||||||||
public init(_ src : PublicKeyCredentialRelyingPartyEntity) { | ||||||||||||||||||||
self.id = src.id | ||||||||||||||||||||
self.name = src.name | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public init(id : String, name : String) { | ||||||||||||||||||||
self.id = id | ||||||||||||||||||||
self.name = name | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/// From §5.4.3 (https://www.w3.org/TR/webauthn/#dictionary-user-credential-params) | ||||||||||||||||||||
/// The PublicKeyCredentialUserEntity dictionary is used to supply additional user account attributes when | ||||||||||||||||||||
/// creating a new credential. | ||||||||||||||||||||
/// | ||||||||||||||||||||
/// When encoding using `Encodable`, `id` is base64url encoded. | ||||||||||||||||||||
public struct PublicKeyCredentialUserEntity: Encodable, Sendable { | ||||||||||||||||||||
public struct PublicKeyCredentialUserEntity: Codable, Sendable { | ||||||||||||||||||||
/// Generated by the Relying Party, unique to the user account, and must not contain personally identifying | ||||||||||||||||||||
/// information about the user. | ||||||||||||||||||||
/// | ||||||||||||||||||||
|
@@ -149,6 +195,15 @@ public struct PublicKeyCredentialUserEntity: Encodable, Sendable { | |||||||||||||||||||
try container.encode(name, forKey: .name) | ||||||||||||||||||||
try container.encode(displayName, forKey: .displayName) | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public init(from decoder: any Decoder) throws { | ||||||||||||||||||||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||||||||||||||||||||
let id = try container.decodeBytesFromURLEncodedBase64(forKey: .id) | ||||||||||||||||||||
let name = try container.decode(String.self, forKey: .name) | ||||||||||||||||||||
let displayName = try container.decode(String.self, forKey: .displayName) | ||||||||||||||||||||
self.init(id: id, name: name, displayName: displayName) | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
private enum CodingKeys: String, CodingKey { | ||||||||||||||||||||
case id | ||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't feel appropriate to add to this package as it isn't guaranteed to be used with vapor or OpenAPI. As an alternative to removing it completely, could this instead live in a separate package that augments swift-webauthn for those who want this functionality.