Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ciChecks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [20, 22]
deno-version: ['v2.1.x']
deno-version: ['v2.1.x', 'v2.4.x']

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ request new features, or to suggest changes to existing features.

Install the following before proceeding:

- **Deno v2.1.x**
- **Deno v2.4.x**

After pulling down the code, set up dependencies:

Expand Down
5 changes: 4 additions & 1 deletion packages/browser/src/methods/startAuthentication.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
AuthenticationExtensionsClientInputs,
AuthenticationExtensionsClientOutputs,
PublicKeyCredentialRequestOptionsJSON,
Uint8Array_,
} from '../types/index.ts';

import { _browserSupportsWebAuthnInternals } from '../helpers/browserSupportsWebAuthn.ts';
Expand Down Expand Up @@ -41,7 +42,9 @@ const goodOpts1: PublicKeyCredentialRequestOptionsJSON = {

// With UTF-8 challenge
const goodOpts2UTF8: PublicKeyCredentialRequestOptionsJSON = {
challenge: bufferToBase64URLString(new TextEncoder().encode('やれやれだぜ')),
challenge: bufferToBase64URLString(
(new TextEncoder().encode('やれやれだぜ') as Uint8Array_).buffer,
),
allowCredentials: [],
timeout: 1,
};
Expand Down
18 changes: 17 additions & 1 deletion packages/browser/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export interface AuthenticatorAssertionResponseJSON {
*/
export type WebAuthnCredential = {
id: Base64URLString;
publicKey: Uint8Array;
publicKey: Uint8Array_;
// Number of times this authenticator is expected to have been used
counter: number;
// From browser's `startRegistration()` -> RegistrationCredentialJSON.transports (API L2 and up)
Expand Down Expand Up @@ -293,3 +293,19 @@ export type AttestationFormat =
| 'tpm'
| 'apple'
| 'none';

/**
* Equivalent to `Uint8Array` before TypeScript 5.7, and `Uint8Array<ArrayBuffer>` in TypeScript 5.7
* and beyond.
*
* **Context**
*
* `Uint8Array` became a generic type in TypeScript 5.7, requiring types defined simply as
* `Uint8Array` to be refactored to `Uint8Array<ArrayBuffer>` starting in Deno 2.2. `Uint8Array` is
* _not_ generic in Deno 2.1.x and earlier, though, so this type helps bridge this gap.
*
* Inspired by Deno's std library:
*
* https://github.com/denoland/std/blob/b5a5fe4f96b91c1fe8dba5cc0270092dd11d3287/bytes/_types.ts#L11
*/
export type Uint8Array_ = ReturnType<Uint8Array['slice']>;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
AuthenticatorTransportFuture,
Base64URLString,
PublicKeyCredentialRequestOptionsJSON,
Uint8Array_,
} from '../types/index.ts';
import { isoBase64URL, isoUint8Array } from '../helpers/iso/index.ts';
import { generateChallenge } from '../helpers/generateChallenge.ts';
Expand All @@ -28,7 +29,7 @@ export async function generateAuthenticationOptions(
id: Base64URLString;
transports?: AuthenticatorTransportFuture[];
}[];
challenge?: string | Uint8Array;
challenge?: string | Uint8Array_;
timeout?: number;
userVerification?: 'required' | 'preferred' | 'discouraged';
extensions?: AuthenticationExtensionsClientInputs;
Expand Down
3 changes: 2 additions & 1 deletion packages/server/src/helpers/convertAAGUIDToString.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { isoUint8Array } from './iso/index.ts';
import type { Uint8Array_ } from '../types/index.ts';

/**
* Convert the aaguid buffer in authData into a UUID string
*/
export function convertAAGUIDToString(aaguid: Uint8Array): string {
export function convertAAGUIDToString(aaguid: Uint8Array_): string {
// Raw Hex: adce000235bcc60a648b0b25f1f05503
const hex = isoUint8Array.toHex(aaguid);

Expand Down
5 changes: 3 additions & 2 deletions packages/server/src/helpers/convertCOSEtoPKCS.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { isoCBOR, isoUint8Array } from './iso/index.ts';
import { COSEKEYS, COSEPublicKeyEC2 } from './cose.ts';
import { COSEKEYS, type COSEPublicKeyEC2 } from './cose.ts';
import type { Uint8Array_ } from '../types/index.ts';

/**
* Takes COSE-encoded public key and converts it to PKCS key
*/
export function convertCOSEtoPKCS(cosePublicKey: Uint8Array): Uint8Array {
export function convertCOSEtoPKCS(cosePublicKey: Uint8Array_): Uint8Array_ {
// This is a little sloppy, I'm using COSEPublicKeyEC2 since it could have both x and y, but when
// there's no y it means it's probably better typed as COSEPublicKeyOKP. I'll leave this for now
// and revisit it later if it ever becomes an actual problem.
Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/helpers/convertCertBufferToPEM.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Base64URLString } from '../types/index.ts';
import type { Base64URLString, Uint8Array_ } from '../types/index.ts';
import { isoBase64URL } from './iso/index.ts';

/**
* Convert buffer to an OpenSSL-compatible PEM text format.
*/
export function convertCertBufferToPEM(
certBuffer: Uint8Array | Base64URLString,
certBuffer: Uint8Array_ | Base64URLString,
): string {
let b64cert: string;

Expand Down
3 changes: 2 additions & 1 deletion packages/server/src/helpers/convertPEMToBytes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { isoBase64URL } from './iso/index.ts';
import type { Uint8Array_ } from '../types/index.ts';

/**
* Take a certificate in PEM format and convert it to bytes
*/
export function convertPEMToBytes(pem: string): Uint8Array {
export function convertPEMToBytes(pem: string): Uint8Array_ {
const certBase64 = pem
.replace('-----BEGIN CERTIFICATE-----', '')
.replace('-----END CERTIFICATE-----', '')
Expand Down
13 changes: 7 additions & 6 deletions packages/server/src/helpers/convertX509PublicKeyToCOSE.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { AsnParser } from '@peculiar/asn1-schema';
import { Certificate } from '@peculiar/asn1-x509';
import { ECParameters, id_ecPublicKey, id_secp256r1, id_secp384r1 } from '@peculiar/asn1-ecc';
import { id_rsaEncryption, RSAPublicKey } from '@peculiar/asn1-rsa';

import {
COSECRV,
COSEKEYS,
Expand All @@ -9,12 +11,11 @@ import {
COSEPublicKeyEC2,
COSEPublicKeyRSA,
} from './cose.ts';
import { RSAPublicKey } from '@peculiar/asn1-rsa';

import { mapX509SignatureAlgToCOSEAlg } from './mapX509SignatureAlgToCOSEAlg.ts';
import type { Uint8Array_ } from '../types/index.ts';

export function convertX509PublicKeyToCOSE(
x509Certificate: Uint8Array,
x509Certificate: Uint8Array_,
): COSEPublicKey {
let cosePublicKey: COSEPublicKey = new Map();

Expand Down Expand Up @@ -59,8 +60,8 @@ export function convertX509PublicKeyToCOSE(
subjectPublicKeyInfo.subjectPublicKey,
);

let x: Uint8Array;
let y: Uint8Array;
let x: Uint8Array_;
let y: Uint8Array_;
if (subjectPublicKey[0] === 0x04) {
// Public key is in "uncompressed form", so we can split the remaining bytes in half
let pointer = 1;
Expand All @@ -84,7 +85,7 @@ export function convertX509PublicKeyToCOSE(
coseEC2PubKey.set(COSEKEYS.y, y);

cosePublicKey = coseEC2PubKey;
} else if (publicKeyAlgorithmID === '1.2.840.113549.1.1.1') {
} else if (publicKeyAlgorithmID === id_rsaEncryption) {
/**
* RSA public key
*/
Expand Down
22 changes: 12 additions & 10 deletions packages/server/src/helpers/cose.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Uint8Array_ } from '../types/index.ts';

/**
* Fundamental values that are needed to discern the more specific COSE public key types below.
*
Expand Down Expand Up @@ -28,10 +30,10 @@ export type COSEPublicKey = {
export type COSEPublicKeyOKP = COSEPublicKey & {
// Getters
get(key: COSEKEYS.crv): number | undefined;
get(key: COSEKEYS.x): Uint8Array | undefined;
get(key: COSEKEYS.x): Uint8Array_ | undefined;
// Setters
set(key: COSEKEYS.crv, value: number): void;
set(key: COSEKEYS.x, value: Uint8Array): void;
set(key: COSEKEYS.x, value: Uint8Array_): void;
};

/**
Expand All @@ -40,24 +42,24 @@ export type COSEPublicKeyOKP = COSEPublicKey & {
export type COSEPublicKeyEC2 = COSEPublicKey & {
// Getters
get(key: COSEKEYS.crv): number | undefined;
get(key: COSEKEYS.x): Uint8Array | undefined;
get(key: COSEKEYS.y): Uint8Array | undefined;
get(key: COSEKEYS.x): Uint8Array_ | undefined;
get(key: COSEKEYS.y): Uint8Array_ | undefined;
// Setters
set(key: COSEKEYS.crv, value: number): void;
set(key: COSEKEYS.x, value: Uint8Array): void;
set(key: COSEKEYS.y, value: Uint8Array): void;
set(key: COSEKEYS.x, value: Uint8Array_): void;
set(key: COSEKEYS.y, value: Uint8Array_): void;
};

/**
* Values specific to RSA public keys
*/
export type COSEPublicKeyRSA = COSEPublicKey & {
// Getters
get(key: COSEKEYS.n): Uint8Array | undefined;
get(key: COSEKEYS.e): Uint8Array | undefined;
get(key: COSEKEYS.n): Uint8Array_ | undefined;
get(key: COSEKEYS.e): Uint8Array_ | undefined;
// Setters
set(key: COSEKEYS.n, value: Uint8Array): void;
set(key: COSEKEYS.e, value: Uint8Array): void;
set(key: COSEKEYS.n, value: Uint8Array_): void;
set(key: COSEKEYS.e, value: Uint8Array_): void;
};

/**
Expand Down
15 changes: 8 additions & 7 deletions packages/server/src/helpers/decodeAttestationObject.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { isoCBOR } from './iso/index.ts';
import type { Uint8Array_ } from '../types/index.ts';

/**
* Convert an AttestationObject buffer to a proper object
*
* @param base64AttestationObject Attestation Object buffer
*/
export function decodeAttestationObject(
attestationObject: Uint8Array,
attestationObject: Uint8Array_,
): AttestationObject {
return _decodeAttestationObjectInternals.stubThis(
isoCBOR.decodeFirst<AttestationObject>(attestationObject),
Expand All @@ -25,21 +26,21 @@ export type AttestationFormat =
export type AttestationObject = {
get(key: 'fmt'): AttestationFormat;
get(key: 'attStmt'): AttestationStatement;
get(key: 'authData'): Uint8Array;
get(key: 'authData'): Uint8Array_;
};

/**
* `AttestationStatement` will be an instance of `Map`, but these keys help make finite the list of
* possible values within it.
*/
export type AttestationStatement = {
get(key: 'sig'): Uint8Array | undefined;
get(key: 'x5c'): Uint8Array[] | undefined;
get(key: 'response'): Uint8Array | undefined;
get(key: 'sig'): Uint8Array_ | undefined;
get(key: 'x5c'): Uint8Array_[] | undefined;
get(key: 'response'): Uint8Array_ | undefined;
get(key: 'alg'): number | undefined;
get(key: 'ver'): string | undefined;
get(key: 'certInfo'): Uint8Array | undefined;
get(key: 'pubArea'): Uint8Array | undefined;
get(key: 'certInfo'): Uint8Array_ | undefined;
get(key: 'pubArea'): Uint8Array_ | undefined;
// `Map` properties
readonly size: number;
};
Expand Down
3 changes: 2 additions & 1 deletion packages/server/src/helpers/decodeAuthenticatorExtensions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { isoCBOR } from './iso/index.ts';
import type { Uint8Array_ } from '../types/index.ts';

/**
* Convert authenticator extension data buffer to a proper object
*
* @param extensionData Authenticator Extension Data buffer
*/
export function decodeAuthenticatorExtensions(
extensionData: Uint8Array,
extensionData: Uint8Array_,
): AuthenticationExtensionsAuthenticatorOutputs | undefined {
let toCBOR: Map<string, unknown>;
try {
Expand Down
5 changes: 3 additions & 2 deletions packages/server/src/helpers/decodeCredentialPublicKey.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { COSEPublicKey } from './cose.ts';
import type { COSEPublicKey } from './cose.ts';
import { isoCBOR } from './iso/index.ts';
import type { Uint8Array_ } from '../types/index.ts';

export function decodeCredentialPublicKey(
publicKey: Uint8Array,
publicKey: Uint8Array_,
): COSEPublicKey {
return _decodeCredentialPublicKeyInternals.stubThis(
isoCBOR.decodeFirst<COSEPublicKey>(publicKey),
Expand Down
5 changes: 3 additions & 2 deletions packages/server/src/helpers/generateChallenge.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { isoCrypto } from './iso/index.ts';
import type { Uint8Array_ } from '../types/index.ts';

/**
* Generate a suitably random value to be used as an attestation or assertion challenge
*/
export async function generateChallenge(): Promise<Uint8Array> {
export async function generateChallenge(): Promise<Uint8Array_> {
/**
* WebAuthn spec says that 16 bytes is a good minimum:
*
Expand All @@ -24,5 +25,5 @@ export async function generateChallenge(): Promise<Uint8Array> {
* @ignore Don't include this in docs output
*/
export const _generateChallengeInternals = {
stubThis: (value: Uint8Array) => value,
stubThis: (value: Uint8Array_) => value,
};
5 changes: 3 additions & 2 deletions packages/server/src/helpers/generateUserID.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { isoCrypto } from './iso/index.ts';
import type { Uint8Array_ } from '../types/index.ts';

/**
* Generate a suitably random value to be used as user ID
*/
export async function generateUserID(): Promise<Uint8Array> {
export async function generateUserID(): Promise<Uint8Array_> {
/**
* WebAuthn spec says user.id has a max length of 64 bytes. I prefer how 32 random bytes look
* after they're base64url-encoded so I'm choosing to go with that here.
Expand All @@ -20,5 +21,5 @@ export async function generateUserID(): Promise<Uint8Array> {
* @ignore Don't include this in docs output
*/
export const _generateUserIDInternals = {
stubThis: (value: Uint8Array) => value,
stubThis: (value: Uint8Array_) => value,
};
4 changes: 3 additions & 1 deletion packages/server/src/helpers/getCertificateInfo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { AsnParser } from '@peculiar/asn1-schema';
import { BasicConstraints, Certificate, id_ce_basicConstraints } from '@peculiar/asn1-x509';

import type { Uint8Array_ } from '../types/index.ts';

export type CertificateInfo = {
issuer: Issuer;
subject: Subject;
Expand Down Expand Up @@ -40,7 +42,7 @@ const issuerSubjectIDKey: { [key: string]: 'C' | 'O' | 'OU' | 'CN' } = {
* @param pemCertificate Result from call to `convertASN1toPEM(x5c[0])`
*/
export function getCertificateInfo(
leafCertBuffer: Uint8Array,
leafCertBuffer: Uint8Array_,
): CertificateInfo {
const x509 = AsnParser.parse(leafCertBuffer, Certificate);
const parsedCert = x509.tbsCertificate;
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/helpers/iso/isoBase64URL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import base64 from '@hexagon/base64';

import type { Base64URLString } from '../../types/index.ts';
import type { Base64URLString, Uint8Array_ } from '../../types/index.ts';

/**
* Decode from a Base64URL-encoded string to an ArrayBuffer. Best used when converting a
Expand All @@ -17,7 +17,7 @@ import type { Base64URLString } from '../../types/index.ts';
export function toBuffer(
base64urlString: string,
from: 'base64' | 'base64url' = 'base64url',
): Uint8Array {
): Uint8Array_ {
const _buffer = base64.toArrayBuffer(base64urlString, from === 'base64url');
return new Uint8Array(_buffer);
}
Expand All @@ -30,10 +30,10 @@ export function toBuffer(
* @param to (optional) The encoding to use, in case it's desirable to encode to base64 instead
*/
export function fromBuffer(
buffer: Uint8Array,
buffer: Uint8Array_,
to: 'base64' | 'base64url' = 'base64url',
): string {
return base64.fromArrayBuffer(buffer, to === 'base64url');
return base64.fromArrayBuffer(buffer.buffer, to === 'base64url');
}

/**
Expand Down
Loading