Skip to content

Commit

Permalink
chore(release): 1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Apr 26, 2023
1 parent 1053717 commit 8b6527f
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 3 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [1.2.0](https://github.com/panva/dpop/compare/v1.1.0...v1.2.0) (2023-04-26)


### Features

* release process with provenance ([1053717](https://github.com/panva/dpop/commit/10537177ffc4a1411a2d1d1df94f119d60323ed8))

## [1.1.0](https://github.com/panva/dpop/compare/v1.0.0...v1.1.0) (2022-09-28)


Expand Down
92 changes: 92 additions & 0 deletions build/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
export declare type JsonObject = {
[Key in string]?: JsonValue;
};
export declare type JsonArray = JsonValue[];
export declare type JsonPrimitive = string | number | boolean | null;
export declare type JsonValue = JsonPrimitive | JsonObject | JsonArray;
/**
* Supported JWS `alg` Algorithm identifiers.
*
* @example PS256 CryptoKey algorithm
* ```ts
* interface Ps256Algorithm extends RsaHashedKeyAlgorithm {
* name: 'RSA-PSS'
* hash: { name: 'SHA-256' }
* }
* ```
*
* @example CryptoKey algorithm for the `ES256` JWS Algorithm Identifier
* ```ts
* interface Es256Algorithm extends EcKeyAlgorithm {
* name: 'ECDSA'
* namedCurve: 'P-256'
* }
* ```
*
* @example CryptoKey algorithm for the `RS256` JWS Algorithm Identifier
* ```ts
* interface Rs256Algorithm extends RsaHashedKeyAlgorithm {
* name: 'RSASSA-PKCS1-v1_5'
* hash: { name: 'SHA-256' }
* }
* ```
*
* @example CryptoKey algorithm for the `EdDSA` JWS Algorithm Identifier (Experimental)
*
* Runtime support for this algorithm is very limited, it depends on the [Secure Curves in the Web
* Cryptography API](https://wicg.github.io/webcrypto-secure-curves/) proposal which is yet to be
* widely adopted. If the proposal changes this implementation will follow up with a minor release.
*
* ```ts
* interface EdDSAAlgorithm extends KeyAlgorithm {
* name: 'Ed25519'
* }
* ```
*/
export declare type JWSAlgorithm = 'PS256' | 'ES256' | 'RS256' | 'EdDSA';
export interface KeyPair extends CryptoKeyPair {
/**
* Private
* {@link https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey CryptoKey}
* instance to sign the DPoP Proof JWT with.
*
* Its algorithm must be compatible with a supported
* {@link JWSAlgorithm JWS `alg` Algorithm}.
*/
privateKey: CryptoKey;
/**
* The public key corresponding to {@link DPoPOptions.privateKey}
*/
publicKey: CryptoKey;
}
/**
* Generates a unique DPoP Proof JWT.
*
* @param keypair
* @param htu The HTTP URI (without query and fragment parts) of the request
* @param htm The HTTP method of the request
* @param nonce Server-provided nonce.
* @param accessToken Associated access token's value.
* @param additional Any additional claims.
*/
export default function DPoP(keypair: KeyPair, htu: string, htm: string, nonce?: string, accessToken?: string, additional?: Record<string, JsonValue>): Promise<string>;
export interface GenerateKeyPairOptions {
/**
* Indicates whether or not the private key may be exported.
* Default is `false`.
*/
extractable?: boolean;
/**
* (RSA algorithms only) The length, in bits, of the RSA modulus.
* Default is `2048`.
*/
modulusLength?: number;
}
/**
* Generates a
* {@link https://developer.mozilla.org/en-US/docs/Web/API/CryptoKeyPair CryptoKeyPair}
* for a given JWS `alg` Algorithm identifier.
*
* @param alg Supported JWS `alg` Algorithm identifier.
*/
export declare function generateKeyPair(alg: JWSAlgorithm, options?: GenerateKeyPairOptions): Promise<CryptoKeyPair>;
199 changes: 199 additions & 0 deletions build/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function buf(input) {
if (typeof input === 'string') {
return encoder.encode(input);
}
return decoder.decode(input);
}
function checkRsaKeyAlgorithm(algorithm) {
if (typeof algorithm.modulusLength !== 'number' || algorithm.modulusLength < 2048) {
throw new OperationProcessingError(`${algorithm.name} modulusLength must be at least 2048 bits`);
}
}
function subtleAlgorithm(key) {
switch (key.algorithm.name) {
case 'ECDSA':
return { name: key.algorithm.name, hash: 'SHA-256' };
case 'RSA-PSS':
checkRsaKeyAlgorithm(key.algorithm);
return {
name: key.algorithm.name,
saltLength: 256 >> 3,
};
case 'RSASSA-PKCS1-v1_5':
checkRsaKeyAlgorithm(key.algorithm);
return { name: key.algorithm.name };
case 'Ed25519':
return { name: key.algorithm.name };
}
throw new UnsupportedOperationError();
}
async function jwt(header, claimsSet, key) {
if (key.usages.includes('sign') === false) {
throw new TypeError('private CryptoKey instances used for signing assertions must include "sign" in their "usages"');
}
const input = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(claimsSet)))}`;
const signature = b64u(await crypto.subtle.sign(subtleAlgorithm(key), key, buf(input)));
return `${input}.${signature}`;
}
const CHUNK_SIZE = 0x8000;
function encodeBase64Url(input) {
if (input instanceof ArrayBuffer) {
input = new Uint8Array(input);
}
const arr = [];
for (let i = 0; i < input.byteLength; i += CHUNK_SIZE) {
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
}
return btoa(arr.join('')).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
}
function b64u(input) {
return encodeBase64Url(input);
}
function randomBytes() {
return b64u(crypto.getRandomValues(new Uint8Array(32)));
}
class UnsupportedOperationError extends Error {
constructor(message) {
super(message ?? 'operation not supported');
this.name = this.constructor.name;
Error.captureStackTrace?.(this, this.constructor);
}
}
class OperationProcessingError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace?.(this, this.constructor);
}
}
function psAlg(key) {
switch (key.algorithm.hash.name) {
case 'SHA-256':
return 'PS256';
default:
throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name');
}
}
function rsAlg(key) {
switch (key.algorithm.hash.name) {
case 'SHA-256':
return 'RS256';
default:
throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name');
}
}
function esAlg(key) {
switch (key.algorithm.namedCurve) {
case 'P-256':
return 'ES256';
default:
throw new UnsupportedOperationError('unsupported EcKeyAlgorithm namedCurve');
}
}
function determineJWSAlgorithm(key) {
switch (key.algorithm.name) {
case 'RSA-PSS':
return psAlg(key);
case 'RSASSA-PKCS1-v1_5':
return rsAlg(key);
case 'ECDSA':
return esAlg(key);
case 'Ed25519':
return 'EdDSA';
default:
throw new UnsupportedOperationError('unsupported CryptoKey algorithm name');
}
}
function isCryptoKey(key) {
return key instanceof CryptoKey;
}
function isPrivateKey(key) {
return isCryptoKey(key) && key.type === 'private';
}
function isPublicKey(key) {
return isCryptoKey(key) && key.type === 'public';
}
function epochTime() {
return Math.floor(Date.now() / 1000);
}
export default async function DPoP(keypair, htu, htm, nonce, accessToken, additional) {
const privateKey = keypair?.privateKey;
const publicKey = keypair?.publicKey;
if (!isPrivateKey(privateKey)) {
throw new TypeError('"keypair.privateKey" must be a private CryptoKey');
}
if (!isPublicKey(publicKey)) {
throw new TypeError('"keypair.publicKey" must be a public CryptoKey');
}
if (publicKey.extractable !== true) {
throw new TypeError('"keypair.publicKey.extractable" must be true');
}
if (typeof htu !== 'string') {
throw new TypeError('"htu" must be a string');
}
if (typeof htm !== 'string') {
throw new TypeError('"htm" must be a string');
}
if (nonce !== undefined && typeof nonce !== 'string') {
throw new TypeError('"nonce" must be a string or undefined');
}
if (accessToken !== undefined && typeof accessToken !== 'string') {
throw new TypeError('"accessToken" must be a string or undefined');
}
if (additional !== undefined &&
(typeof additional !== 'object' || typeof additional === null || Array.isArray(additional))) {
throw new TypeError('"additional" must be an object');
}
return jwt({
alg: determineJWSAlgorithm(privateKey),
typ: 'dpop+jwt',
jwk: await publicJwk(publicKey),
}, {
...additional,
iat: epochTime(),
jti: randomBytes(),
htm,
nonce,
htu,
ath: accessToken ? b64u(await crypto.subtle.digest('SHA-256', buf(accessToken))) : undefined,
}, privateKey);
}
async function publicJwk(key) {
const { kty, e, n, x, y, crv } = await crypto.subtle.exportKey('jwk', key);
return { kty, crv, e, n, x, y };
}
export async function generateKeyPair(alg, options) {
let algorithm;
if (typeof alg !== 'string' || alg.length === 0) {
throw new TypeError('"alg" must be a non-empty string');
}
switch (alg) {
case 'PS256':
algorithm = {
name: 'RSA-PSS',
hash: 'SHA-256',
modulusLength: options?.modulusLength ?? 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
};
break;
case 'RS256':
algorithm = {
name: 'RSASSA-PKCS1-v1_5',
hash: 'SHA-256',
modulusLength: options?.modulusLength ?? 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
};
break;
case 'ES256':
algorithm = { name: 'ECDSA', namedCurve: 'P-256' };
break;
case 'EdDSA':
algorithm = { name: 'Ed25519' };
break;
default:
throw new UnsupportedOperationError();
}
return (crypto.subtle.generateKey(algorithm, options?.extractable ?? false, ['sign', 'verify']));
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dpop",
"version": "1.1.0",
"version": "1.2.0",
"description": "DPoP for Web Platform API JavaScript runtimes",
"keywords": [
"dpop",
Expand Down

0 comments on commit 8b6527f

Please sign in to comment.