Skip to content
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

Integer Byte Casting #68

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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 @@ -14,7 +14,7 @@

// Contains the new public key created by the authenticator.
struct AttestedCredentialData: Equatable {
let aaguid: [UInt8]
let authenticatorAttestationGUID: AAGUID
let credentialID: [UInt8]
let publicKey: [UInt8]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the WebAuthn Swift open source project
//
// Copyright (c) 2024 the WebAuthn Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

/// A globally unique ID identifying an authenticator.
///
/// Each authenticator has an Authenticator Attestation Globally Unique Identifier or **AAGUID**, which is a 128-bit identifier indicating the type (e.g. make and model) of the authenticator. The AAGUID MUST be chosen by its maker to be identical across all substantially identical authenticators made by that maker, and different (with high probability) from the AAGUIDs of all other types of authenticators. The AAGUID for a given type of authenticator SHOULD be randomly generated to ensure this.
///
/// The Relying Party MAY use the AAGUID to infer certain properties of the authenticator, such as certification level and strength of key protection, using information from other sources. The Relying Party MAY use the AAGUID to attempt to identify the maker of the authenticator without requesting and verifying attestation, but the AAGUID is not provably authentic without attestation.
/// - SeeAlso: [WebAuthn Leven 3 Editor's Draft §6. WebAuthn Authenticator Model](https://w3c.github.io/webauthn/#aaguid)
public struct AuthenticatorAttestationGloballyUniqueID: Hashable, Sendable {
/// The underlying UUID for the authenticator.
public let id: UUID

/// Initialize an AAGUID with a UUID.
@inlinable
public init(uuid: UUID) {
self.id = uuid
}

/// Initialize an AAGUID with a byte sequence.
///
/// This sequence must be of length ``AuthenticatorAttestationGloballyUniqueID/size``.
@inlinable
public init?(bytes: some BidirectionalCollection<UInt8>) {
let uuidSize = MemoryLayout<uuid_t>.size
assert(uuidSize == Self.size, "Size of uuid_t (\(uuidSize)) does not match Self.size (\(Self.size))!")
guard bytes.count == uuidSize else { return nil }
self.init(uuid: UUID(uuid: bytes.casting()))
}

/// Initialize an AAGUID with a string-based UUID.
@inlinable
public init?(uuidString: String) {
guard let uuid = UUID(uuidString: uuidString)
else { return nil }

self.init(uuid: uuid)
}

/// Access the AAGUID as an encoded byte sequence.
@inlinable
public var bytes: [UInt8] { withUnsafeBytes(of: id) { Array($0) } }

/// The identifier of an anonymized authenticator, set to a byte sequence of 16 zeros.
public static let anonymous = AuthenticatorAttestationGloballyUniqueID(bytes: Array(repeating: 0, count: Self.size))!

/// The byte length of an encoded identifer.
public static let size: Int = 16
}

/// A shorthand for an ``AuthenticatorAttestationGloballyUniqueID``
public typealias AAGUID = AuthenticatorAttestationGloballyUniqueID

extension AuthenticatorAttestationGloballyUniqueID: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
id = try container.decode(UUID.self)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(id)
}
}
14 changes: 7 additions & 7 deletions Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ extension AuthenticatorData {

let relyingPartyIDHash = Array(bytes[..<32])
let flags = AuthenticatorFlags(bytes[32])
let counter: UInt32 = Data(bytes[33..<37]).toInteger(endian: .big)
let counter = UInt32(bigEndianBytes: bytes[33..<37])

var remainingCount = bytes.count - minAuthDataLength

var attestedCredentialData: AttestedCredentialData?
// For attestation signatures, the authenticator MUST set the AT flag and include the attestedCredentialData.
if flags.attestedCredentialData {
let minAttestedAuthLength = 55
let minAttestedAuthLength = 37 + AAGUID.size + 2
guard bytes.count > minAttestedAuthLength else {
throw WebAuthnError.attestedCredentialDataMissing
}
Expand Down Expand Up @@ -84,13 +84,13 @@ extension AuthenticatorData {
/// - SeeAlso: [WebAuthn Level 3 Editor's Draft §6.5.1. Attested Credential Data]( https://w3c.github.io/webauthn/#sctn-attested-credential-data)
private static func parseAttestedData(_ data: [UInt8]) throws -> (AttestedCredentialData, Int) {
/// **aaguid** (16): The AAGUID of the authenticator.
let aaguidLength = 16
let aaguid = data[37..<(37 + aaguidLength)] // To byte at index 52
guard let aaguid = AAGUID(bytes: data[37..<(37 + AAGUID.size)]) // Bytes [37-52]
else { throw WebAuthnError.attestedCredentialDataMissing }

/// **credentialIdLength** (2): Byte length L of credentialId, 16-bit unsigned big-endian integer. Value MUST be ≤ 1023.
let idLengthBytes = data[53..<55] // Length is 2 bytes
let idLengthData = Data(idLengthBytes)
let idLength: UInt16 = idLengthData.toInteger(endian: .big)
let idLength = UInt16(bigEndianBytes: idLengthData)

guard idLength <= 1023
else { throw WebAuthnError.credentialIDTooLong }
Expand All @@ -110,13 +110,13 @@ extension AuthenticatorData {
let publicKeyBytes = data[credentialIDEndIndex..<(data.count - inputStream.remainingBytes)]

let data = AttestedCredentialData(
aaguid: Array(aaguid),
authenticatorAttestationGUID: aaguid,
credentialID: Array(credentialID),
publicKey: Array(publicKeyBytes)
)

/// `2` is the size of **credentialIdLength**
let length = data.aaguid.count + 2 + data.credentialID.count + data.publicKey.count
let length = AAGUID.size + 2 + data.credentialID.count + data.publicKey.count

return (data, length)
}
Expand Down
49 changes: 49 additions & 0 deletions Sources/WebAuthn/Helpers/ByteCasting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the WebAuthn Swift open source project
//
// Copyright (c) 2024 the WebAuthn Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

extension BidirectionalCollection where Element == UInt8 {
/// Cast a byte sequence into a trivial type like a primitive or a tuple of primitives.
///
/// - Note: It is up to the caller to verify the receiver's size before casting it.
@inlinable
func casting<R>() -> R {
precondition(self.count == MemoryLayout<R>.size, "self.count (\(self.count)) does not match MemoryLayout<R>.size (\(MemoryLayout<R>.size))")

let result = self.withContiguousStorageIfAvailable({
$0.withUnsafeBytes { $0.loadUnaligned(as: R.self) }
}) ?? Array(self).withUnsafeBytes {
$0.loadUnaligned(as: R.self)
}

return result
}
}

extension FixedWidthInteger {
/// Initialize a fixed width integer from a contiguous sequence of Bytes representing a big endian type.
/// - Parameter bigEndianBytes: The Bytes to interpret as a big endian integer.
@inlinable
init(bigEndianBytes: some BidirectionalCollection<UInt8>) {
self.init(bigEndian: bigEndianBytes.casting())
}

/// Initialize a fixed width integer from a contiguous sequence of Bytes representing a little endian type.
/// - Parameter bigEndianBytes: The Bytes to interpret as a little endian integer.
@inlinable
init(littleEndianBytes: some BidirectionalCollection<UInt8>) {
self.init(littleEndian: littleEndianBytes.casting())
}
}
34 changes: 0 additions & 34 deletions Sources/WebAuthn/Helpers/Numbers+Bytes.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the WebAuthn Swift open source project
//
// Copyright (c) 2024 the WebAuthn Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import XCTest
@testable import WebAuthn

final class AuthenticatorAttestationGloballyUniqueIDTests: XCTestCase {
func testByteCoding() throws {
let aaguid = AuthenticatorAttestationGloballyUniqueID(bytes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
XCTAssertNotNil(aaguid)
XCTAssertEqual(aaguid?.bytes, [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f])
XCTAssertEqual(aaguid?.id, UUID(uuid: (0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f)))
XCTAssertEqual(aaguid, AuthenticatorAttestationGloballyUniqueID(uuid: UUID(uuid: (0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f))))
XCTAssertEqual(aaguid, AuthenticatorAttestationGloballyUniqueID(uuidString: "00010203-0405-0607-0809-0A0B0C0D0E0F" ))
}

func testInvalidByteDecoding() throws {
XCTAssertNil(AuthenticatorAttestationGloballyUniqueID(bytes: []))
XCTAssertNil(AuthenticatorAttestationGloballyUniqueID(bytes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]))
XCTAssertNil(AuthenticatorAttestationGloballyUniqueID(bytes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]))
}
}
6 changes: 2 additions & 4 deletions Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ struct TestAuthDataBuilder {
.flags(0b11000101)
.counter([0b00000000, 0b00000000, 0b00000000, 0b00000000])
.attestedCredData(
aaguid: [UInt8](repeating: 0, count: 16),
credentialIDLength: [0b00000000, 0b00000001],
credentialID: [0b00000001],
credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
Expand Down Expand Up @@ -110,18 +109,17 @@ struct TestAuthDataBuilder {
return temp
}

/// aaguid length = 16
/// credentialIDLength length = 2
/// credentialID length = credentialIDLength
/// credentialPublicKey = variable
func attestedCredData(
aaguid: [UInt8] = [UInt8](repeating: 0, count: 16),
authenticatorAttestationGUID: AAGUID = .anonymous,
credentialIDLength: [UInt8] = [0b00000000, 0b00000001],
credentialID: [UInt8] = [0b00000001],
credentialPublicKey: [UInt8]
) -> Self {
var temp = self
temp.wrapped.attestedCredData = aaguid + credentialIDLength + credentialID + credentialPublicKey
temp.wrapped.attestedCredData = authenticatorAttestationGUID.bytes + credentialIDLength + credentialID + credentialPublicKey
return temp
}

Expand Down
2 changes: 0 additions & 2 deletions Tests/WebAuthnTests/WebAuthnManagerRegistrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ final class WebAuthnManagerRegistrationTests: XCTestCase {
TestAuthDataBuilder()
.validMock()
.attestedCredData(
aaguid: Array(repeating: 0, count: 16),
credentialIDLength: [0b000_00011, 0b1111_1111],
credentialID: Array(repeating: 0, count: 1023),
credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
Expand All @@ -320,7 +319,6 @@ final class WebAuthnManagerRegistrationTests: XCTestCase {
TestAuthDataBuilder()
.validMock()
.attestedCredData(
aaguid: Array(repeating: 0, count: 16),
credentialIDLength: [0b000_00100, 0b0000_0000],
credentialID: Array(repeating: 0, count: 1024),
credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
Expand Down