diff --git a/packages/jades/CHANGELOG.md b/packages/jades/CHANGELOG.md new file mode 100644 index 0000000..943498a --- /dev/null +++ b/packages/jades/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.2](https://github.com/lukasjhan/sd-jwt-vc-dm-owf/compare/v0.1.1...v0.1.2) (2025-02-09) + +**Note:** Version bump only for package sd-jwt-jades + + + + + +## 0.1.1 (2025-02-09) + +**Note:** Version bump only for package jades diff --git a/packages/jades/README.md b/packages/jades/README.md new file mode 100644 index 0000000..d24ac7c --- /dev/null +++ b/packages/jades/README.md @@ -0,0 +1,210 @@ +# SD JWT VC + JAdES Typescript + +> ⚠️ **Platform Support**: This package currently supports Node.js environments only. + +Typescript implementation of SD JWT VCDM profile. + +A library that integrates SD-JWT with W3C Verifiable Credentials Data Model and implements JAdES digital signature standards. + +## Features + +### JAdES Digital Signature Integration + +Implements JAdES (JSON Advanced Electronic Signatures) standard for SD-JWT with support for the following signature profiles: + +- **B-B (Basic - Baseline)**: Basic signature format +- **B-T (Basic with Time)**: Signatures with timestamp +- **B-LT (Basic Long-Term)**: Signatures with validation data for long-term preservation +- **B-LTA (Basic Long-Term with Archive timestamps)**: Long-term preservation with periodic timestamp renewal + +## Installation + +```bash +pnpm add @sd-jwt/jades +``` + +## Usage + +### B-B + +```typescript +import { JAdES, parseCerts, createKidFromCert } from '@sd-jwt/jades'; +import * as fs from 'fs'; +import { createPrivateKey } from 'node:crypto'; + +(async () => { + const jades = new JAdES.Sign({ data: 'data 1', target: 'data 2' }); + + const certPem = fs.readFileSync('./fixtures/certificate.crt', 'utf-8'); + const certs = parseCerts(certPem); + const kid = createKidFromCert(certs[0]); + + const keyPem = fs.readFileSync('./fixtures/private.pem', 'utf-8'); + const privateKey = createPrivateKey(keyPem); + + await jades + .setProtectedHeader({ + alg: 'RS256', + typ: 'dc+sd-jwt', + }) + .setX5c(certs) + .setDisclosureFrame({ + _sd: ['data'], + }) + .setSignedAt() + .sign(privateKey, kid); + + const serialized = jades.toJSON(); + console.log(serialized); +})(); +``` + +### B-T + +```typescript +import { JAdES, parseCerts, createKidFromCert } from '@sd-jwt/jades'; +import * as fs from 'fs'; +import { createPrivateKey } from 'node:crypto'; + +(async () => { + const jades = new JAdES.Sign({ data: 'data 1', target: 'data 2' }); + + const certPem = fs.readFileSync('./fixtures/certificate.crt', 'utf-8'); + const certs = parseCerts(certPem); + const kid = createKidFromCert(certs[0]); + + const keyPem = fs.readFileSync('./fixtures/private.pem', 'utf-8'); + const privateKey = createPrivateKey(keyPem); + + await jades + .setProtectedHeader({ + alg: 'RS256', + typ: 'dc+sd-jwt', + }) + .setX5c(certs) + .setDisclosureFrame({ + _sd: ['data'], + }) + .setSignedAt() + .setUnprotectedHeader({ + etsiU: [ + { + sigTst: { + tstTokens: [ + { + val: 'Base64-encoded RFC 3161 Timestamp Token', + }, + ], + }, + }, + ], + }) + .sign(privateKey, kid); + + const serialized = jades.toJSON(); + console.log(serialized); +})(); +``` + +### B-LT + +```typescript +import { JAdES, parseCerts, createKidFromCert } from '@sd-jwt/jades'; +import * as fs from 'fs'; +import { createPrivateKey } from 'node:crypto'; + +(async () => { + const jades = new JAdES.Sign({ data: 'data 1', target: 'data 2' }); + + const certPem = fs.readFileSync('./fixtures/certificate.crt', 'utf-8'); + const certs = parseCerts(certPem); + const kid = createKidFromCert(certs[0]); + + const keyPem = fs.readFileSync('./fixtures/private.pem', 'utf-8'); + const privateKey = createPrivateKey(keyPem); + + await jades + .setProtectedHeader({ + alg: 'RS256', + typ: 'dc+sd-jwt', + }) + .setX5c(certs) + .setDisclosureFrame({ + _sd: ['data'], + }) + .setSignedAt() + .setUnprotectedHeader({ + etsiU: [ + { + sigTst: { + tstTokens: [ + { + val: 'Base64-encoded RFC 3161 Timestamp Token', + }, + ], + }, + }, + { + xVals: [ + { x509Cert: 'Base64-encoded Trust Anchor' }, + { x509Cert: 'Base64-encoded CA Certificate' }, + ], + }, + { + rVals: { + crlVals: ['Base64-encoded CRL'], + ocspVals: ['Base64-encoded OCSP Response'], + }, + }, + ], + }) + .sign(privateKey, kid); + + const serialized = jades.toJSON(); + console.log(serialized); +})(); +``` + +### B-LTA + +```typescript +import { JAdES, parseCerts, createKidFromCert } from '@sd-jwt/jades'; +import * as fs from 'fs'; +import { createPrivateKey } from 'node:crypto'; + +(async () => { + const jades = new JAdES.Sign({ data: 'data 1', target: 'data 2' }); + + const certPem = fs.readFileSync('./fixtures/certificate.crt', 'utf-8'); + const certs = parseCerts(certPem); + const kid = createKidFromCert(certs[0]); + + const keyPem = fs.readFileSync('./fixtures/private.pem', 'utf-8'); + const privateKey = createPrivateKey(keyPem); + + await jades + .setProtectedHeader({ + alg: 'RS256', + typ: 'dc+sd-jwt', + }) + .setX5c(certs) + .setDisclosureFrame({ + _sd: ['data'], + }) + .setSignedAt() + .sign(privateKey, kid); + + const serialized = jades.toJSON(); + console.log(serialized); +})(); +``` + +## License + +Apache License 2.0 + +## References + +- [SD-JWT VC Data Model](https://github.com/danielfett/sd-jwt-vc-dm) +- [OpenID4VC HAIP Profile](https://github.com/openid/oid4vc-haip/pull/147/files#diff-762ef65fd82909517226ac1bb7e8855792bb57021abc1637c15b8557154dbbf1) +- [ETSI TS 119 182-1 - JAdES Baseline Signatures](https://www.etsi.org/deliver/etsi_ts/119100_119199/11918201/01.02.01_60/ts_11918201v010201p.pdf) diff --git a/packages/jades/package.json b/packages/jades/package.json new file mode 100644 index 0000000..ad1d09f --- /dev/null +++ b/packages/jades/package.json @@ -0,0 +1,55 @@ +{ + "name": "sd-jwt-jades", + "version": "0.1.2", + "description": "JADES implementation in typescript", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf **/dist && tsup", + "lint": "biome lint ./src", + "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov", + "test:node": "vitest run ./src/test/*.spec.ts", + "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom", + "test:cov": "vitest run --coverage" + }, + "keywords": [ + "jades", + "sd-jwt", + "jwt" + ], + "engines": { + "node": ">=16" + }, + "author": "Lukas.J.Han ", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "sourceMap": true, + "splitting": false, + "clean": true, + "dts": true, + "format": [ + "cjs", + "esm" + ] + }, + "dependencies": { + "@sd-jwt/core": "workspace:*", + "@sd-jwt/crypto-nodejs": "workspace:*", + "@sd-jwt/types": "workspace:*", + "@sd-jwt/utils": "workspace:*", + "asn1js": "^3.0.5" + } +} diff --git a/packages/jades/src/constant.ts b/packages/jades/src/constant.ts new file mode 100644 index 0000000..5abc1fb --- /dev/null +++ b/packages/jades/src/constant.ts @@ -0,0 +1,30 @@ +import { constants } from 'node:crypto'; + +export const ALGORITHMS = { + // RSA + RS256: { hash: 'sha256', padding: constants.RSA_PKCS1_PADDING }, + RS384: { hash: 'sha384', padding: constants.RSA_PKCS1_PADDING }, + RS512: { hash: 'sha512', padding: constants.RSA_PKCS1_PADDING }, + + // RSA-PSS + PS256: { hash: 'sha256', padding: constants.RSA_PKCS1_PSS_PADDING }, + PS384: { hash: 'sha384', padding: constants.RSA_PKCS1_PSS_PADDING }, + PS512: { hash: 'sha512', padding: constants.RSA_PKCS1_PSS_PADDING }, + + // ECDSA + ES256: { hash: 'sha256', namedCurve: 'P-256' }, + ES384: { hash: 'sha384', namedCurve: 'P-384' }, + ES512: { hash: 'sha512', namedCurve: 'P-521' }, + + // EdDSA + EdDSA: { curves: ['ed25519', 'ed448'] }, +}; + +export enum CommitmentOIDs { + proofOfOrigin = '1.2.840.113549.1.9.16.6.1', + proofOfReceipt = '1.2.840.113549.1.9.16.6.2', + proofOfDelivery = '1.2.840.113549.1.9.16.6.3', + proofOfSender = '1.2.840.113549.1.9.16.6.4', + proofOfApproval = '1.2.840.113549.1.9.16.6.5', + proofOfCreation = '1.2.840.113549.1.9.16.6.6', +} diff --git a/packages/jades/src/index.ts b/packages/jades/src/index.ts new file mode 100644 index 0000000..b2ca8db --- /dev/null +++ b/packages/jades/src/index.ts @@ -0,0 +1,12 @@ +import { Sign } from './sign'; +export * from './type'; +export * from './constant'; +export * from './utils'; +import { Present } from './present'; +import { JWTVerifier } from './verify'; + +export const JAdES = { + Sign, + Present, + Verify: JWTVerifier, +}; diff --git a/packages/jades/src/present.ts b/packages/jades/src/present.ts new file mode 100644 index 0000000..9ba3192 --- /dev/null +++ b/packages/jades/src/present.ts @@ -0,0 +1,40 @@ +import { SDJwtGeneralJSONInstance } from '@sd-jwt/core'; +import { digest, generateSalt } from '@sd-jwt/crypto-nodejs'; +import type { PresentationFrame } from '@sd-jwt/types'; +import type { GeneralJWS } from './type'; +import { getGeneralJSONFromJWSToken } from './utils'; + +export const Present = { + async present>( + credential: GeneralJWS | string, + presentationFrame?: PresentationFrame, + options?: Record, + ): Promise { + // Initialize the SD JWT instance with proper configuration + const sdJwtInstance = new SDJwtGeneralJSONInstance({ + hashAlg: 'sha-256', + hasher: digest, + saltGenerator: generateSalt, + }); + + // Convert string to GeneralJSON if needed + const generalJsonCredential = getGeneralJSONFromJWSToken(credential); + + // If there are no disclosures, return the credential as is + // This prevents errors from the core library when handling credentials without SD claims + if ( + !generalJsonCredential.disclosures || + generalJsonCredential.disclosures.length === 0 + ) { + return generalJsonCredential.toJson(); + } + + // Use the instance's present method for the core SD-JWT functionality + const presentedCredential = await sdJwtInstance.present( + generalJsonCredential, + presentationFrame, + ); + + return presentedCredential.toJson(); + }, +}; diff --git a/packages/jades/src/sign.ts b/packages/jades/src/sign.ts new file mode 100644 index 0000000..db83106 --- /dev/null +++ b/packages/jades/src/sign.ts @@ -0,0 +1,394 @@ +import type { DisclosureFrame } from '@sd-jwt/types'; +import { + type KeyObject, + type X509Certificate, + createHash, + createSign, +} from 'node:crypto'; +import { base64urlEncode } from '@sd-jwt/utils'; +import { ALGORITHMS } from './constant'; +import { GeneralJSON, SDJwtGeneralJSONInstance } from '@sd-jwt/core'; +import { digest, generateSalt } from '@sd-jwt/crypto-nodejs'; +import type { + Alg, + CommitmentOption, + GeneralJWS, + ProtectedHeader, + SigD, + UnprotectedHeader, +} from './type'; + +export class Sign> { + private serialized?: GeneralJWS; + + private protectedHeader: Partial; + + // TODO: implement + // unprotected header + private header: UnprotectedHeader; + + private disclosureFrame: DisclosureFrame | undefined; + + /** + * If payload is empty, the data of payload will be empty string. + * This is the Detached JWS Payload described in TS 119 182-1 v1.2.1 section 5.2.8 + * The sigD header must be present when the payload is empty. + */ + constructor(private readonly payload?: T) { + this.protectedHeader = {}; + this.header = {}; + } + + private async appendSignature(key: KeyObject, kid: string) { + if (this.serialized === undefined) { + throw new Error('Signature must be appended to serialized'); + } + + if ( + !this.protectedHeader.alg || + (this.protectedHeader.alg as Alg | 'none') === 'none' + ) { + throw new Error('alg must be set and not "none"'); + } + + if (this.payload === undefined) { + const encodedProtectedHeader = base64urlEncode( + JSON.stringify({ ...this.protectedHeader, kid }), + ); + const encodedPayload = ''; + const protectedData = `${encodedProtectedHeader}.${encodedPayload}`; + + const signature = JWTSigner.sign( + this.protectedHeader.alg, + protectedData, + key, + ); + this.serialized.signatures.push({ + protected: protectedData, + signature, + header: this.header, + }); + return this; + } + + const generalJSON = GeneralJSON.fromSerialized(this.serialized); + const signer = (data: string) => { + if (!this.protectedHeader.alg) + throw new Error('alg must be set when signing'); + return JWTSigner.sign(this.protectedHeader.alg, data, key); + }; + await generalJSON.addSignature({ ...this.protectedHeader, kid }, signer); + const serialized = generalJSON.toJson(); + this.serialized = serialized; + return this; + } + + private async createSignature(key: KeyObject, kid: string) { + if ( + !this.protectedHeader.alg || + (this.protectedHeader.alg as Alg | 'none') === 'none' + ) { + throw new Error('alg must be set and not "none"'); + } + + if (this.payload === undefined) { + /** + * If the payload is empty, It uses Detached JWS Payload described in TS 119 182-1 v1.2.1 section 5.2.8 + * So Create manual signature here. + */ + + const encodedProtectedHeader = base64urlEncode( + JSON.stringify({ ...this.protectedHeader, kid }), + ); + const encodedPayload = ''; + const protectedData = `${encodedProtectedHeader}.${encodedPayload}`; + + const signature = JWTSigner.sign( + this.protectedHeader.alg, + protectedData, + key, + ); + + this.serialized = { + payload: '', + signatures: [ + { + protected: encodedProtectedHeader, + signature, + header: this.header, + }, + ], + }; + return this; + } + + /** + * Create a General JWS Payload with SD-JWT library. + */ + + const sdjwtInstance = new SDJwtGeneralJSONInstance({ + hashAlg: 'sha-256', + signAlg: this.protectedHeader.alg, + hasher: digest, + saltGenerator: generateSalt, + }); + + const disclosureFrame = this.disclosureFrame; + + const generalJSON = await sdjwtInstance.issue( + this.payload, + disclosureFrame, + { + sigs: [ + { + alg: this.protectedHeader.alg, + kid: kid, + header: this.protectedHeader, + signer: (data: string) => { + if (!this.protectedHeader.alg) + throw new Error('alg must be set when signing'); + return JWTSigner.sign(this.protectedHeader.alg, data, key); + }, + }, + ], + }, + ); + + const serialized = generalJSON.toJson(); + this.serialized = { + payload: serialized.payload, + signatures: serialized.signatures.map((sig) => ({ + ...sig, + header: { + ...sig.header, + ...this.header, + }, + })), + }; + + return this; + } + + async sign(key: KeyObject, kid: string) { + if ( + !this.protectedHeader.alg || + (this.protectedHeader.alg as Alg | 'none') === 'none' + ) { + throw new Error('alg must be set and not "none"'); + } + + this.validateCertificateHeaders(); + + if (this.serialized !== undefined) { + return this.appendSignature(key, kid); + } + + return this.createSignature(key, kid); + } + + setProtectedHeader(header: ProtectedHeader) { + if (!header.alg || (header.alg as Alg | 'none') === 'none') { + throw new Error('alg must be set and not "none"'); + } + this.protectedHeader = header; + return this; + } + + setDisclosureFrame(frame: DisclosureFrame) { + this.disclosureFrame = frame; + return this; + } + + setB64(b64: boolean) { + if (b64) { + this.protectedHeader.b64 = undefined; + } else { + this.protectedHeader.b64 = false; + } + return this; + } + + setIssuedAt(sec?: number) { + this.protectedHeader.iat = sec ?? Math.floor(Date.now() / 1000); + return this; + } + + setSignedAt(sec?: number) { + this.protectedHeader.signedAt = sec ?? Math.floor(Date.now() / 1000); + return this; + } + + setSigD(sigd: SigD) { + this.protectedHeader.sigD = sigd; + /** + * TS 119 182-1 v1.2.1 section 5.1.10 + * + * If the sigD header parameter is present with its member set to + "http://uri.etsi.org/19182/HttpHeaders" then the b64 header parameter shall be present and set to + "false". + */ + if (sigd.mId === 'http://uri.etsi.org/19182/HttpHeaders') { + this.setB64(false); + } + return this; + } + + setJti(jti: string) { + this.protectedHeader.jti = jti; + return this; + } + + setX5u(uri: string) { + this.protectedHeader.x5u = uri; + return this; + } + + setX5c(certs: X509Certificate[]) { + this.protectedHeader.x5c = certs.map((cert) => cert.raw.toString('base64')); + return this; + } + + setX5tS256(cert: X509Certificate) { + this.protectedHeader['x5t#256'] = createHash('sha-256') + .update(new Uint8Array(cert.raw)) + .digest('base64url'); + return this; + } + + setX5tSo(cert: X509Certificate) { + this.protectedHeader['x5t#o'] = { + digAlg: 'sha-512', + digVal: createHash('sha-512') + .update(new Uint8Array(cert.raw)) + .digest('base64url'), + }; + return this; + } + + setX5ts(certs: X509Certificate[]) { + if (certs.length < 2) { + throw new Error( + 'at least 2 certificates are required, use setX5tSo instead', + ); + } + this.protectedHeader['x5t#s'] = certs.map((cert) => ({ + digAlg: 'sha-512', + digVal: createHash('sha-512') + .update(new Uint8Array(cert.raw)) + .digest('base64url'), + })); + return this; + } + + setCty(cty: string) { + this.protectedHeader.cty = cty; + return this; + } + + setCommitment(option: CommitmentOption) { + this.protectedHeader.srCms = option; + return this; + } + + setUnprotectedHeader(header: UnprotectedHeader) { + this.header = header; + return this; + } + + private validateCertificateHeaders() { + const hasCertHeader = !!( + this.protectedHeader['x5t#S256'] || + this.protectedHeader.x5c || + this.protectedHeader['x5t#o'] || + this.protectedHeader.sigX5ts + ); + if (!hasCertHeader) { + throw new Error( + 'JAdES signature requires at least one certificate header', + ); + } + } + + toJSON() { + if (!this.serialized) { + throw new Error('Not signed yet'); + } + return this.serialized; + } +} + +const JWTSigner = { + sign(alg: Alg, signingInput: string, privateKey: KeyObject) { + const signature = JWTSigner.createSignature(alg, signingInput, privateKey); + return signature; + }, + + createSignature(alg: Alg, signingInput: string, privateKey: KeyObject) { + switch (alg) { + case 'RS256': + case 'RS384': + case 'RS512': + case 'PS256': + case 'PS384': + case 'PS512': { + const option = ALGORITHMS[alg]; + return JWTSigner.createRSASignature(signingInput, privateKey, option); + } + case 'ES256': + case 'ES384': + case 'ES512': { + const option = ALGORITHMS[alg]; + return JWTSigner.createECDSASignature(signingInput, privateKey, option); + } + case 'EdDSA': { + const option = ALGORITHMS[alg]; + return JWTSigner.createEdDSASignature(signingInput, privateKey, option); + } + default: + } + throw new Error(`Unsupported algorithm: ${alg}`); + }, + + createRSASignature( + signingInput: string, + privateKey: KeyObject, + options: { hash: string; padding: number }, + ) { + const signer = createSign(options.hash); + signer.update(signingInput); + const signature = signer.sign({ + key: privateKey, + padding: options.padding, + }); + return signature.toString('base64url'); + }, + + createECDSASignature( + signingInput: string, + privateKey: KeyObject, + options: { hash: string; namedCurve: string }, + ) { + const signer = createSign(options.hash); + signer.update(signingInput); + + const signature = signer.sign({ + key: privateKey, + dsaEncoding: 'ieee-p1363', + }); + + return signature.toString('base64url'); + }, + + createEdDSASignature( + signingInput: string, + privateKey: KeyObject, + options: { curves: string[] }, + ) { + const signer = createSign(options.curves[0]); + signer.update(signingInput); + const signature = signer.sign({ + key: privateKey, + }); + return signature.toString('base64url'); + }, +}; diff --git a/packages/jades/src/test/fixtures/certificate.crt b/packages/jades/src/test/fixtures/certificate.crt new file mode 100644 index 0000000..a950c7b --- /dev/null +++ b/packages/jades/src/test/fixtures/certificate.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdTCCAl0CFH4VXnYSKJitiYAQ0ygvLIQQtaUAMA0GCSqGSIb3DQEBCwUAMHcx +CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDDAKBgNVBAoM +A09XRjEMMAoGA1UECwwDTGFiMRQwEgYDVQQDDAtTRC1KV1QgVkNETTEcMBoGCSqG +SIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0yNTAyMjIwNjMyMjhaFw0yNjAyMjIw +NjMyMjhaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0Yx +DDAKBgNVBAoMA09XRjEMMAoGA1UECwwDTGFiMRQwEgYDVQQDDAtTRC1KV1QgVkNE +TTEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBALdwnKQEtn0CCExhJ6jjtQC/Vi2o5bzhJVAvL2DZHvZc +Y+c2YM57sJ3wQiNYiEa1ESSnxEN9+2+NrQ2YHKMVz3nmihO0yBCom0HiSuW6MSE5 +uSLRVNA2VMfnPcNV6ZoKhmALe/awVubz1IZf36BBXcJW6KOsXwYBfibV4DF57n6j +FvYRSq6FLPekKDdJaayJqtfPLmvSeyhfHdm2b2yfrCdkwgKejIj4TlCQUdMxckGM +hA1HWr//UvPR0V2fhh6+dZ1DjbPLEQ4LZAA2YImnIrmKN/TrZCSx8AGb5+YTc1dx +OJjeO273UaCXMTqV1bC3z7ixFSHoqxIbSSjTtHX68LkCAwEAATANBgkqhkiG9w0B +AQsFAAOCAQEAJG5mB2AlvsiE88uOQycXyf65Ivz5/U0+ezrAIXKW5cB0AOIwtpaO +lGUmJovE8RM4D3VUEhjIFuuGKJlYszU1I9TPUlS1PwaMIPkirvRzFbYf0q1SxeI+ +0meMNu9OtLRDhLMZZtefNCrWerAhd7qEo63KWbig3/F3Vm620opVUqVlVP8VnY5F +HBT1ILcPNlnnd9FCBDvweuUilCafN8KHq7N23d+zKE73ayD2y0K34drGam1RnZMg +Lv7ewCz5BybD8rGcDL/txPE4K/5Ueo4o5p4GqfYuwcFkS5qiMhlAm/d+Kgai6uWP +dM/gjo5ijrGWSkQwYSH5j8R+tP5cR7TS2A== +-----END CERTIFICATE----- diff --git a/packages/jades/src/test/fixtures/certificate.csr b/packages/jades/src/test/fixtures/certificate.csr new file mode 100644 index 0000000..2012188 --- /dev/null +++ b/packages/jades/src/test/fixtures/certificate.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICvDCCAaQCAQAwdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEMMAoGA1UECgwDT1dGMQwwCgYDVQQLDANMYWIxFDASBgNVBAMMC1NELUpX +VCBWQ0RNMRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt3CcpAS2fQIITGEnqOO1AL9WLajlvOElUC8v +YNke9lxj5zZgznuwnfBCI1iIRrURJKfEQ337b42tDZgcoxXPeeaKE7TIEKibQeJK +5boxITm5ItFU0DZUx+c9w1XpmgqGYAt79rBW5vPUhl/foEFdwlboo6xfBgF+JtXg +MXnufqMW9hFKroUs96QoN0lprImq188ua9J7KF8d2bZvbJ+sJ2TCAp6MiPhOUJBR +0zFyQYyEDUdav/9S89HRXZ+GHr51nUONs8sRDgtkADZgiaciuYo39OtkJLHwAZvn +5hNzV3E4mN47bvdRoJcxOpXVsLfPuLEVIeirEhtJKNO0dfrwuQIDAQABoAAwDQYJ +KoZIhvcNAQELBQADggEBABhDDhEySkKuJ024D9f4Lg5XrnNGKYrvAzznFLSepkez +osceeXVrcRSFU20M9OIjcbYpLLXCMaW92kdt6EKOBd8OBjoY4VPTCfUCGNu6I5NW +dNXZEos720ENFny/eB8L2LpVykVqdYXXbhlqCWGiOXkuFRos12R9riOFHsNmFdQa +tO4HpA1dssj5qsUOT+TBn6NUQ6iDBn9U+A/hdJ5NZFbKYewyw2/pQ1A97iZMPBLQ +wwm+2IJHxAZZFR2DTX68NL+OMPyqrUKNmo2kyvmoG7JSEXtzW5ZfB+kZfIuqtgMn +I8o7JjVd18bLaazeMv3EHgL8/apIfeXuvqid0RBDf1U= +-----END CERTIFICATE REQUEST----- diff --git a/packages/jades/src/test/fixtures/pkijs-test-cert.crt b/packages/jades/src/test/fixtures/pkijs-test-cert.crt new file mode 100644 index 0000000..2b95211 --- /dev/null +++ b/packages/jades/src/test/fixtures/pkijs-test-cert.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIDrDCCAmCgAwIBAgIICTX8CVCpVZcwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEFAKEcMBoG +CSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgMIGQMYGNMIGKBgNVBAMegYIAMAA2ADQAMABjAGYA +NQAyADMAMgA4ADkAZQBiAGYAMgBiADkAOAA4ADIAYwA2AGIAYQAxADgAYQAxAGIAOQBkAGMAYwAwAGEA +MABmADMAMABiADcAOAA2ADIAZABjADgAMgAxADEAZQA4ADAAZAAyAGIANAA1AGEANQBmAGEANQBkMB4X +DTIxMTAxNDEwMzMxOVoXDTIxMTAxNTEwMzMxOVowgZAxgY0wgYoGA1UEAx6BggAwADYANAAwAGMAZgA1 +ADIAMwAyADgAOQBlAGIAZgAyAGIAOQA4ADgAMgBjADYAYgBhADEAOABhADEAYgA5AGQAYwBjADAAYQAw +AGYAMwAwAGIANwA4ADYAMgBkAGMAOAAyADEAMQBlADgAMABkADIAYgA0ADUAYQA1AGYAYQA1AGQwWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAToW17Szlrc7F6JXyn3HggMkHx1TluBrteZ0WAQHV31u4yiLaaR +70atxlhCdMaTpFey+lnnjfSns3TipH47meUOo2swaTAPBgNVHRMBAf8EBTADAgEAMCsGA1UdIwQkMCKA +IGQM9SMonr8rmILGuhihudzAoPMLeGLcghHoDStFpfpdMCkGA1UdDgQiBCC4UlB2jJfY9+OEU+7mFsXS +qF+Cg/gKWUSxfD/jHpQ1XDBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcN +AQEIMA0GCWCGSAFlAwQCAQUAogMCASADggEBAFT2wAXEKwmK0YgFWhX/QdWUAG4mlvcxqF+Re+UyW0/k +hfHKhgKP/z+CWdAKm1DD668rf7nQo4lQH3o8F3ksK3sTqTi5UXDB3S7xWnv1YFh73oQep3aDfKzpccLm +kFMUFatMJZmd+3N9uav5IA8TIIkFCqDVB59X9OCGvNubRZA+5q41b7TovTA04WBpiUxCWtKWJtArcU1I +hmu2w50768pQp9adVJCy7byQIzA1VE4g+85srzEiML2ICC1AVm25OzNs73nkDtdivZF81Wk1qheN0m57 +NgeGVBTBKS4YLMeiMowMXJKoFnFqwyv+0JL0ZeFCh0al2RF+FDk86b1rykY= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/packages/jades/src/test/fixtures/private.pem b/packages/jades/src/test/fixtures/private.pem new file mode 100644 index 0000000..4b81731 --- /dev/null +++ b/packages/jades/src/test/fixtures/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC3cJykBLZ9AghM +YSeo47UAv1YtqOW84SVQLy9g2R72XGPnNmDOe7Cd8EIjWIhGtREkp8RDfftvja0N +mByjFc955ooTtMgQqJtB4krlujEhObki0VTQNlTH5z3DVemaCoZgC3v2sFbm89SG +X9+gQV3CVuijrF8GAX4m1eAxee5+oxb2EUquhSz3pCg3SWmsiarXzy5r0nsoXx3Z +tm9sn6wnZMICnoyI+E5QkFHTMXJBjIQNR1q//1Lz0dFdn4YevnWdQ42zyxEOC2QA +NmCJpyK5ijf062QksfABm+fmE3NXcTiY3jtu91GglzE6ldWwt8+4sRUh6KsSG0ko +07R1+vC5AgMBAAECgf9gCq9NuSn0fHnKts8EYhHRSdOqR3bN3EZgI+3VAsdd399d +aooAgI7eZmw6SH3d3JnQW31u6dOyaYNtGqXjq+WF5K4Q53w+TaIvOlQNe5kbTDci +LkP90HRfzu32TvxMO2mn346YxJh27Ha20uHThbsRHoCd27s56cFegs8rXJHwmKD9 +vOopbrM31lFYVIv2w92UMI2tzyVh/dt6Li/bE2x+SfdboHg3bKOzb2J54ArdApAj +qomp0UaZLFcEoidKwXs7A7c3coDu0edXNlDYiQiU3T51O5IHLVLu7ydsfHY0nfBo +bW06RovbCcUWZuVg4URvsrqJjYa9yqSOFhMa1AECgYEA20pEg+Qs71X8Yf8wcmf6 +H78W5iU8IKhvtaZhTzgmd31AwwH4aYi3F7FqrzBrXOpDVdhQHUJw0xRogzu9oTqX +Mgi3JU1jibk4EZSoM1r8dlvjOILC2Bev1vweGQoUadObhGYzlMJ5jWDhnKapSaqd +8Ko6gpSe6shrIaL+LEK46QECgYEA1iX5ZSCmLTyL8pCAT6Wg3ij5/MOyw7Diycu9 +aj4oc/GMnM3bX1ZHWIHYf/r2wJeNQKOwO6bFLWU09mrk8M/Th9spYgmmhOLh5S5n +7s5vDtMetFd2nFxPCLQD+dmw8muOHzx4q8oHJLTiSembuRKwasWRcVB1fsluWq9J +64kzj7kCgYEAgVFyxjSxKxMSGthawZz/q+kMn/wlUT0DY6QSjp0WUjDTGCz5SYMO +NjKyL03mgozD2MQPurGLx26pk3qPgZ0DvMXW+3B/2qAXqIZ6Co8Fgub9QD4cmUKe +t7FGLo37K81WUOXUVU26UN1pLvA5tO+g5GP+zTLH5Lz8MIHmUkOmuAECgYBvoKfZ +8STuW6tPq8dWvS2X4KA/A/wBSglNy2hXI0OO/JChhCUgEydpmXWzcKyDQ/ybfzf0 +QEB0I1dyQ9JHPO402Sc/NWyZxHKgNopyzyTRA3fz5cusQenozX1tFe7IzwvseGcm +lU8X8Tor8Rv9YyKW5ZmlCZ3Vk0Z0+v57Sq+X8QKBgQCJS9Bj9QspFleC6InX1gIJ +C97iiQ2sk5nAd0UlV1z1/p8/KySPBNIEAXqpKbey8N/T7qEhwM7TJscMez4fo1i4 +qwU4mEfAZJ6QqOQ/PgOJl07ozVHrHcU+EYthiV46uwbDP5xPoccxxX7fEYpTDAqX +9Qmw4wgGHXIJbLfcIBp8hw== +-----END PRIVATE KEY----- diff --git a/packages/jades/src/test/fixtures/public.pem b/packages/jades/src/test/fixtures/public.pem new file mode 100644 index 0000000..13556c4 --- /dev/null +++ b/packages/jades/src/test/fixtures/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt3CcpAS2fQIITGEnqOO1 +AL9WLajlvOElUC8vYNke9lxj5zZgznuwnfBCI1iIRrURJKfEQ337b42tDZgcoxXP +eeaKE7TIEKibQeJK5boxITm5ItFU0DZUx+c9w1XpmgqGYAt79rBW5vPUhl/foEFd +wlboo6xfBgF+JtXgMXnufqMW9hFKroUs96QoN0lprImq188ua9J7KF8d2bZvbJ+s +J2TCAp6MiPhOUJBR0zFyQYyEDUdav/9S89HRXZ+GHr51nUONs8sRDgtkADZgiaci +uYo39OtkJLHwAZvn5hNzV3E4mN47bvdRoJcxOpXVsLfPuLEVIeirEhtJKNO0dfrw +uQIDAQAB +-----END PUBLIC KEY----- diff --git a/packages/jades/src/test/index.spec.ts b/packages/jades/src/test/index.spec.ts new file mode 100644 index 0000000..aa8f191 --- /dev/null +++ b/packages/jades/src/test/index.spec.ts @@ -0,0 +1,7 @@ +import { describe, expect, test } from 'vitest'; + +describe('Test#1', () => { + test('Test#1', () => { + expect(1).toBe(1); + }); +}); diff --git a/packages/jades/src/test/present.spec.ts b/packages/jades/src/test/present.spec.ts new file mode 100644 index 0000000..c725982 --- /dev/null +++ b/packages/jades/src/test/present.spec.ts @@ -0,0 +1,187 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { + type X509Certificate, + createPrivateKey, + type KeyObject, +} from 'node:crypto'; +import { Sign } from '../sign'; +import { Present } from '../present'; +import { parseCerts } from '../utils'; +import { GeneralJSON, SDJwtGeneralJSONInstance } from '@sd-jwt/core'; +import { digest } from '@sd-jwt/crypto-nodejs'; +import { JWTVerifier } from '../verify'; +import type { GeneralJWS } from '../type'; + +describe('Present', () => { + let testCert: X509Certificate[]; + let privateKey: KeyObject; + let signedCredentialJson: GeneralJWS; + let signedCredentialJsonWithoutSD: GeneralJWS; + + // Create a credential to use in tests + beforeAll(async () => { + // Load test certificates and keys + const certPath = path.join(__dirname, 'fixtures', 'certificate.crt'); + const certPem = fs.readFileSync(certPath, 'utf-8'); + testCert = parseCerts(certPem); + + const keyPath = path.join(__dirname, 'fixtures', 'private.pem'); + const keyPem = fs.readFileSync(keyPath, 'utf-8'); + privateKey = createPrivateKey(keyPem); + + // Create a test credential with selective disclosure + const payload = { + vct: 'https://credentials.example.com/drivers_license', + iss: 'https://dmv.example.gov', + iat: 1683000000, + exp: 1793000000, + given_name: 'Jane', + family_name: 'Doe', + license_number: 'DL123456789', + license_class: 'C', + address: { + street_address: '456 Oak Ave', + locality: 'Springfield', + region: 'State', + country: 'US', + }, + birthdate: '1985-05-15', + }; + + const sign = new Sign(payload); + const result = await sign + .setProtectedHeader({ + alg: 'RS256', + typ: 'jades', + }) + .setX5c(testCert) + .setDisclosureFrame({ + _sd: ['given_name', 'family_name', 'license_number', 'license_class'], + }) + .sign(privateKey, 'test-kid'); + + signedCredentialJson = result.toJSON(); + + const sign2 = new Sign(payload); + const result2 = await sign2 + .setProtectedHeader({ + alg: 'RS256', + typ: 'jades', + }) + .setX5c(testCert) + .setDisclosureFrame({ + _sd: [], + }) + .sign(privateKey, 'test-kid'); + + signedCredentialJsonWithoutSD = result2.toJSON(); + }); + + describe('present method', () => { + it('should create a presentation with selective disclosure', async () => { + // Present with only names disclosed + const presentationFrame = { + given_name: true, + family_name: true, + }; + + const presentedCredentialJson = await Present.present( + signedCredentialJson, + presentationFrame, + ); + + // Convert the JSON result back to GeneralJSON for verification + const presentedCredential = GeneralJSON.fromSerialized( + presentedCredentialJson, + ); + + // Verify the presented credential + const instance = new SDJwtGeneralJSONInstance({ + hasher: digest, + verifier: JWTVerifier.verifier, + }); + + const verifiedData = await instance.verify(presentedCredential); + + expect(verifiedData).toBeDefined(); + expect(verifiedData.payload).toHaveProperty('address'); + expect(verifiedData.payload).toHaveProperty('birthdate'); + expect(verifiedData.payload).toHaveProperty('given_name'); + expect(verifiedData.payload).toHaveProperty('family_name'); + expect(verifiedData.payload).not.toHaveProperty('license_number'); + expect(verifiedData.payload).not.toHaveProperty('license_class'); + }); + + it('should handle JSON input as a string', async () => { + // Present with only name and license_class disclosed + const presentationFrame = { + given_name: true, + family_name: true, + license_class: true, + }; + + const jsonString = JSON.stringify(signedCredentialJson, null, 2); + + const presentedCredentialJson = await Present.present( + jsonString, + presentationFrame, + ); + + // Convert the JSON result back to GeneralJSON for verification + const presentedCredential = GeneralJSON.fromSerialized( + presentedCredentialJson, + ); + + // Verify the presented credential + const instance = new SDJwtGeneralJSONInstance({ + hasher: digest, + verifier: JWTVerifier.verifier, + }); + + const verifiedData = await instance.verify(presentedCredential); + + expect(verifiedData).toBeDefined(); + expect(verifiedData.payload).toHaveProperty('given_name'); + expect(verifiedData.payload).toHaveProperty('family_name'); + expect(verifiedData.payload).toHaveProperty('license_class'); + expect(verifiedData.payload).not.toHaveProperty('license_number'); + }); + + it('should create a presentation without SD', async () => { + const presentedCredentialJson = await Present.present( + signedCredentialJsonWithoutSD, + ); + + // Verify the result is returned correctly + expect(presentedCredentialJson).toBeDefined(); + + // Verify that the presented credential is the same as the original + // (compare key properties since we're getting back a plain object now) + expect(presentedCredentialJson.payload).toEqual( + signedCredentialJsonWithoutSD.payload, + ); + expect(presentedCredentialJson.signatures).toEqual( + signedCredentialJsonWithoutSD.signatures, + ); + }); + + it('should handle JSON without SD input as a string', async () => { + const jsonString = JSON.stringify(signedCredentialJsonWithoutSD, null, 2); + + const presentedCredentialJson = await Present.present(jsonString); + + // Verify the result is returned correctly + expect(presentedCredentialJson).toBeDefined(); + + // Verify that the presented credential has the same structure as the original + expect(presentedCredentialJson.payload).toEqual( + signedCredentialJsonWithoutSD.payload, + ); + expect(presentedCredentialJson.signatures).toEqual( + signedCredentialJsonWithoutSD.signatures, + ); + }); + }); +}); diff --git a/packages/jades/src/test/sign.spec.ts b/packages/jades/src/test/sign.spec.ts new file mode 100644 index 0000000..dcd4355 --- /dev/null +++ b/packages/jades/src/test/sign.spec.ts @@ -0,0 +1,293 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { + type X509Certificate, + createPrivateKey, + type KeyObject, +} from 'node:crypto'; +import { Sign } from '../sign'; +import type { DisclosureFrame } from '@sd-jwt/types'; +import type { ProtectedHeader } from '../type'; +import { base64urlDecode } from '@sd-jwt/utils'; +import { parseCerts } from '../utils'; +import { GeneralJSON, SDJwtGeneralJSONInstance } from '@sd-jwt/core'; +import { digest } from '@sd-jwt/crypto-nodejs'; +import { JWTVerifier } from '../verify'; + +describe('Sign', () => { + let testCert: X509Certificate[]; + let privateKey: KeyObject; + + beforeAll(() => { + const certPath = path.join(__dirname, 'fixtures', 'certificate.crt'); + const certPem = fs.readFileSync(certPath, 'utf-8'); + testCert = parseCerts(certPem); + + const keyPath = path.join(__dirname, 'fixtures', 'private.pem'); + const keyPem = fs.readFileSync(keyPath, 'utf-8'); + privateKey = createPrivateKey(keyPem); + }); + + describe('constructor', () => { + it('should create instance with payload', () => { + const payload = { test: 'value' }; + const sign = new Sign(payload); + expect(sign).toBeInstanceOf(Sign); + }); + + it('should create instance without payload', () => { + const sign = new Sign(); + expect(sign).toBeInstanceOf(Sign); + }); + }); + + describe('setProtectedHeader', () => { + it('should set valid protected header', () => { + const sign = new Sign({ test: 'value' }); + const header: ProtectedHeader = { + alg: 'ES256', + typ: 'JWT', + }; + const result = sign.setProtectedHeader(header); + expect(result).toBe(sign); // Should return this for chaining + }); + + it('should throw error when alg is none', () => { + const sign = new Sign({ test: 'value' }); + const header = { + alg: 'none', + typ: 'JWT', + }; + expect(() => + sign.setProtectedHeader(header as ProtectedHeader), + ).toThrow(); + }); + + it('should throw error when alg is missing', () => { + const sign = new Sign({ test: 'value' }); + const header = { + typ: 'JWT', + }; + expect(() => + sign.setProtectedHeader(header as ProtectedHeader), + ).toThrow(); + }); + }); + + describe('setDisclosureFrame', () => { + it('should set disclosure frame', () => { + const sign = new Sign({ test: 'value' }); + const frame: DisclosureFrame<{ test: string }> = { + _sd: ['test'], + }; + const result = sign.setDisclosureFrame(frame); + expect(result).toBe(sign); // Should return this for chaining + }); + }); + + describe('setB64', () => { + it('should set b64 to undefined when true', () => { + const sign = new Sign({ test: 'value' }); + const result = sign.setB64(true); + // @ts-expect-error accessing private field for testing + expect(sign.protectedHeader.b64).toBeUndefined(); + expect(result).toBe(sign); + }); + + it('should set b64 to false when false', () => { + const sign = new Sign({ test: 'value' }); + const result = sign.setB64(false); + // @ts-expect-error accessing private field for testing + expect(sign.protectedHeader.b64).toBe(false); + expect(result).toBe(sign); + }); + }); + + describe('setIssuedAt', () => { + it('should set custom issued at time', () => { + const sign = new Sign({ test: 'value' }); + const timestamp = Math.floor(Date.now() / 1000); + const result = sign.setIssuedAt(timestamp); + // @ts-expect-error accessing private field for testing + expect(sign.protectedHeader.iat).toBe(timestamp); + expect(result).toBe(sign); + }); + + it('should set current time when no timestamp provided', () => { + const sign = new Sign({ test: 'value' }); + const before = Math.floor(Date.now() / 1000); + const result = sign.setIssuedAt(); + // @ts-expect-error accessing private field for testing + const iat = sign.protectedHeader.iat as number; + const after = Math.floor(Date.now() / 1000); + + expect(iat).toBeGreaterThanOrEqual(before); + expect(iat).toBeLessThanOrEqual(after); + expect(result).toBe(sign); + }); + }); + + describe('sign', () => { + it('should throw when alg is not set', async () => { + const sign = new Sign({ test: 'value' }); + await expect(sign.sign({} as KeyObject, 'kid')).rejects.toThrow(); + }); + + it('should sign payload with RS256', async () => { + const payload = { test: 'value' }; + const sign = new Sign(payload); + sign + .setProtectedHeader({ + alg: 'RS256', + typ: 'JWT', + }) + .setX5c(testCert); + + const result = await sign.sign(privateKey, 'test-kid'); + // @ts-expect-error accessing private field for testing + const serialized = result.serialized; + + expect(serialized).toBeDefined(); + expect(serialized?.signatures).toHaveLength(1); + expect(serialized?.signatures[0].protected).toBeDefined(); + expect(serialized?.signatures[0].signature).toBeDefined(); + + // Verify the protected header + const protectedHeader = JSON.parse( + base64urlDecode(serialized?.signatures[0].protected ?? '').toString(), + ); + expect(protectedHeader.alg).toBe('RS256'); + expect(protectedHeader.kid).toBe('test-kid'); + expect(protectedHeader.x5c).toBeDefined(); + expect(protectedHeader.x5c).toHaveLength(1); + }); + + it('should create detached signature when payload is undefined', async () => { + const sign = new Sign(); + sign + .setProtectedHeader({ + alg: 'RS256', + typ: 'JWT', + }) + .setX5c(testCert); + + const result = await sign.sign(privateKey, 'test-kid'); + // @ts-expect-error accessing private field for testing + const serialized = result.serialized; + + expect(serialized).toBeDefined(); + expect(serialized?.payload).toBe(''); + expect(serialized?.signatures).toHaveLength(1); + expect(serialized?.signatures[0].protected).toBeDefined(); + expect(serialized?.signatures[0].signature).toBeDefined(); + }); + + it('should append multiple signatures', async () => { + const payload = { test: 'value' }; + const sign = new Sign(payload); + sign + .setProtectedHeader({ + alg: 'RS256', + typ: 'JWT', + }) + .setX5c(testCert); + + await sign.sign(privateKey, 'kid1'); + await sign.sign(privateKey, 'kid2'); + + // @ts-expect-error accessing private field for testing + const serialized = sign.serialized; + + expect(serialized?.signatures).toHaveLength(2); + expect(serialized?.signatures[0].protected).toBeDefined(); + expect(serialized?.signatures[1].protected).toBeDefined(); + + // Verify different kids + const header1 = JSON.parse( + base64urlDecode(serialized?.signatures[0].protected ?? '').toString(), + ); + const header2 = JSON.parse( + base64urlDecode(serialized?.signatures[1].protected ?? '').toString(), + ); + expect(header1.kid).toBe('kid1'); + expect(header2.kid).toBe('kid2'); + expect(header1.x5c).toBeDefined(); + expect(header2.x5c).toBeDefined(); + }); + + it('should sign with disclosure frame', async () => { + const payload = { test: 'value', sensitive: 'data' }; + const sign = new Sign(payload); + const result = await sign + .setProtectedHeader({ + alg: 'RS256', + typ: 'JWT', + }) + .setX5c(testCert) + .setDisclosureFrame({ + _sd: ['sensitive'], + }) + .sign(privateKey, 'test-kid'); + + // @ts-expect-error accessing private field for testing + const serialized = result.serialized; + + expect(serialized).toBeDefined(); + expect(serialized?.signatures).toHaveLength(1); + + // The payload should contain _sd array with hash + const decodedPayload = JSON.parse( + base64urlDecode(serialized?.payload ?? '').toString(), + ); + expect(decodedPayload._sd).toBeDefined(); + expect(Array.isArray(decodedPayload._sd)).toBe(true); + expect(decodedPayload.test).toBe('value'); + expect(decodedPayload.sensitive).toBeUndefined(); + }); + }); + + describe('verify', () => { + it('verify signed JAdES', async () => { + const payload = { test: 'value', sensitive: 'data' }; + const sign = new Sign(payload); + const result = await sign + .setProtectedHeader({ + alg: 'RS256', + typ: 'JWT', + }) + .setX5c(testCert) + .setDisclosureFrame({ + _sd: ['sensitive'], + }) + .sign(privateKey, 'test-kid'); + + const serialized = result.toJSON(); + + expect(serialized).toBeDefined(); + const instance = new SDJwtGeneralJSONInstance({ + hasher: digest, + verifier: JWTVerifier.verifier, + }); + const verifiedData = await instance.verify( + GeneralJSON.fromSerialized(serialized), + ); + expect(verifiedData).toBeDefined(); + expect(verifiedData.payload).toEqual(payload); + }); + }); + + describe('error cases', () => { + it('should throw when attempting to append signature without serialization', async () => { + const sign = new Sign({ test: 'value' }); + // @ts-expect-error: Testing private method + await expect(sign.appendSignature(null, 'kid')).rejects.toThrow(); + }); + + it('should throw when alg is not set', async () => { + const sign = new Sign({ test: 'value' }); + // @ts-expect-error: Testing private method + await expect(sign.createSignature(null, 'kid')).rejects.toThrow(); + }); + }); +}); diff --git a/packages/jades/src/test/utils.spec.ts b/packages/jades/src/test/utils.spec.ts new file mode 100644 index 0000000..74918aa --- /dev/null +++ b/packages/jades/src/test/utils.spec.ts @@ -0,0 +1,31 @@ +import { X509Certificate } from 'node:crypto'; + +import { describe, it, expect, beforeAll } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { createKidFromCert } from '../utils'; + +describe('createKidFromCert', () => { + let testCert: X509Certificate; + + beforeAll(() => { + const certPath = path.join(__dirname, 'fixtures', 'pkijs-test-cert.crt'); + const certPem = fs.readFileSync(certPath, 'utf-8'); + testCert = new X509Certificate(certPem); + }); + + it('should create a valid base64 encoded kid', () => { + const kid = createKidFromCert(testCert); + + console.log(kid); + // Check if output is defined + expect(kid).toBeDefined(); + // Check if output is base64 encoded + expect(() => Buffer.from(kid, 'base64')).not.toThrow(); + }); + + it('should throw for invalid certificate', () => { + const invalidCert = {} as X509Certificate; + expect(() => createKidFromCert(invalidCert)).toThrow(); + }); +}); diff --git a/packages/jades/src/test/verify.spec.ts b/packages/jades/src/test/verify.spec.ts new file mode 100644 index 0000000..b539c67 --- /dev/null +++ b/packages/jades/src/test/verify.spec.ts @@ -0,0 +1,70 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { + type X509Certificate, + createPrivateKey, + type KeyObject, +} from 'node:crypto'; +import { Sign } from '../sign'; +import { parseCerts } from '../utils'; +import { JWTVerifier } from '../verify'; +import type { GeneralJWS } from '../type'; + +describe('Verify', () => { + let testCert: X509Certificate[]; + let privateKey: KeyObject; + let signedCredentialJson: GeneralJWS; + + const payload = { + vct: 'https://credentials.example.com/drivers_license', + iss: 'https://dmv.example.gov', + iat: 1683000000, + exp: 1793000000, + given_name: 'Jane', + family_name: 'Doe', + license_number: 'DL123456789', + license_class: 'C', + address: { + street_address: '456 Oak Ave', + locality: 'Springfield', + region: 'State', + country: 'US', + }, + birthdate: '1985-05-15', + }; + + // Create a credential to use in tests + beforeAll(async () => { + // Load test certificates and keys + const certPath = path.join(__dirname, 'fixtures', 'certificate.crt'); + const certPem = fs.readFileSync(certPath, 'utf-8'); + testCert = parseCerts(certPem); + + const keyPath = path.join(__dirname, 'fixtures', 'private.pem'); + const keyPem = fs.readFileSync(keyPath, 'utf-8'); + privateKey = createPrivateKey(keyPem); + + const sign = new Sign(payload); + const result = await sign + .setProtectedHeader({ + alg: 'RS256', + typ: 'jades', + }) + .setX5c(testCert) + .setDisclosureFrame({ + _sd: ['given_name', 'family_name', 'license_number', 'license_class'], + }) + .sign(privateKey, 'test-kid'); + + signedCredentialJson = result.toJSON(); + }); + + describe('verify method', () => { + it('should verify a credential', async () => { + const verifiedData = await JWTVerifier.verify(signedCredentialJson); + expect(verifiedData).toBeDefined(); + expect(verifiedData.payload).toEqual(payload); + }); + }); +}); diff --git a/packages/jades/src/type.ts b/packages/jades/src/type.ts new file mode 100644 index 0000000..c39e5cd --- /dev/null +++ b/packages/jades/src/type.ts @@ -0,0 +1,79 @@ +import type { ALGORITHMS, CommitmentOIDs } from './constant'; + +export type ProtectedHeader = { + alg: Alg; + typ?: string; + + // TODO: define other headers + [key: string]: unknown; +}; + +export type SigD = { + mId: string; + pars: [string, string]; + hash: string; +}; + +export type Alg = keyof typeof ALGORITHMS; + +export type GeneralJWS = { + payload: string; + signatures: Array<{ + protected: string; + signature: string; + + /** + * This is a optional unprotected header. + * + */ + header: { + disclosures?: Array; + kid?: string; + kb_jwt?: string; + + /** + * TODO: add JAdES unprotected header + */ + etsiU?: EtsiU; + }; + }>; +}; + +export type CommitmentOption = Array<{ + commId: string | CommitmentOIDs; + commQuals?: Array>; +}>; + +export type UnprotectedHeader = { + etsiU?: EtsiU; +}; + +export type EtsiU = + | [sigTst] // B-T profile + | [sigTst, XVal, rVal] // B-LT profile + | [sigTst, XVal, rVal, ArcTst]; // B-LTA profile + +export type TstToken = { + tstTokens: Array<{ val: string }>; +}; + +export type sigTst = { + sigTst: TstToken; +}; + +export type XVal = { + xVals: Array<{ x509Cert: string }>; +}; + +export type rVal = { + rVals: { + crlVals: Array; + ocspVals: Array; + }; +}; + +export type ArcTst = { + arcTst: TstToken & { + canonAlg: string; + }; +}; diff --git a/packages/jades/src/utils.ts b/packages/jades/src/utils.ts new file mode 100644 index 0000000..f4c327b --- /dev/null +++ b/packages/jades/src/utils.ts @@ -0,0 +1,97 @@ +import { X509Certificate } from 'node:crypto'; +import { Sequence, CharacterString, Integer } from 'asn1js'; +import { GeneralJSON } from '@sd-jwt/core'; +import type { GeneralJWS } from './type'; +import { SDJWTException } from '@sd-jwt/utils'; + +export const parseCerts = (chainPem: string): X509Certificate[] => { + return chainPem + .split(/(?=-----BEGIN CERTIFICATE-----)/g) + .filter((cert) => cert.trim().length > 0) + .map((cert) => new X509Certificate(cert)); +}; + +/** + * Creates a JAdES-compliant kid from X.509 certificate + * According to TS 119 182-1 v1.2.1 section 5.1.4 + */ +export const createKidFromCert = (cert: X509Certificate) => { + /* + + KID = base64url(derEncodeSequence(IssuerSerial)) + + IssuerSerial ::= SEQUENCE { + issuer GeneralNames, + serialNumber CertificateSerialNumber + } + + GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName + + GeneralName ::= CHOICE { + otherName [0] OtherName, + rfc822Name [1] IA5String, + dNSName [2] IA5String, + x400Address [3] ORAddress, + directoryName [4] Name, + ediPartyName [5] EDIPartyName, + uniformResourceIdentifier [6] IA5String, + iPAddress [7] OCTET STRING, + registeredID [8] OBJECT IDENTIFIER + } + + CertificateSerialNumber ::= INTEGER + + issuer + contains the issuer name of the certificate. For non-attribute + certificates, the issuer MUST contain only the issuer name from + the certificate encoded in the directoryName choice of + GeneralNames. For attribute certificates, the issuer MUST contain + the issuer name field from the attribute certificate. + + serialNumber + holds the serial number that uniquely identifies the certificate + for the issuer. + */ + + // Get issuer and serial from certificate + const issuer = cert.issuer; + const serialNumber = cert.serialNumber; + + // Create an instance of the Sequence ASN.1 class + const sequence = new Sequence({ + value: [ + // issuer + new CharacterString({ + value: issuer, + }), + // serialNumber + new Integer({ + // Passing the serial number as a string is not supported by the library + valueHex: new Uint8Array(Buffer.from(serialNumber, 'hex')), + }), + ], + }); + + // DER-encode the sequence + const derEncoded = sequence.toBER(false); + + // Return the base64 encoding of the DER-encoded sequence + return Buffer.from(derEncoded).toString('base64'); +}; + +export const getGeneralJSONFromJWSToken = ( + credential: GeneralJWS | string, +): GeneralJSON => { + if (typeof credential === 'string') { + try { + const parsed = JSON.parse(credential); + return GeneralJSON.fromSerialized(parsed); + } catch (error) { + throw new SDJWTException( + 'Invalid credential format: not a valid JSON', + error, + ); + } + } + return GeneralJSON.fromSerialized(credential); +}; diff --git a/packages/jades/src/verify.ts b/packages/jades/src/verify.ts new file mode 100644 index 0000000..3f50a0a --- /dev/null +++ b/packages/jades/src/verify.ts @@ -0,0 +1,98 @@ +import { createVerify, X509Certificate } from 'node:crypto'; +import type { GeneralJWS } from './type'; +import { SDJwtGeneralJSONInstance } from '@sd-jwt/core'; +import { digest } from '@sd-jwt/crypto-nodejs'; +import { getGeneralJSONFromJWSToken } from './utils'; + +export const JWTVerifier = { + async verify(credential: GeneralJWS | string, requiredClaimKeys?: string[]) { + const instance = new SDJwtGeneralJSONInstance({ + hasher: digest, + verifier: JWTVerifier.verifier, + }); + + const generalJsonCredential = getGeneralJSONFromJWSToken(credential); + + const verifiedData = await instance.verify( + generalJsonCredential, + requiredClaimKeys, + ); + return verifiedData; + }, + + verifier(data: string, signatureB64: string): boolean { + try { + const [headerB64, payloadB64] = data.split('.'); + + const headerStr = Buffer.from(headerB64, 'base64url').toString('utf-8'); + const header = JSON.parse(headerStr); + + if (!header.x5c || !Array.isArray(header.x5c)) { + throw new Error('x5c certificate chain is missing in header'); + } + + const isValid = JWTVerifier.verifySig( + data, + signatureB64, + header.x5c, + header.alg, + ); + + return isValid; + } catch (error) { + return false; + } + }, + + getVerifyAlgorithm(jwtAlg: string): string { + const algorithmMap: Record = { + RS256: 'SHA256', + RS384: 'SHA384', + RS512: 'SHA512', + ES256: 'SHA256', + ES384: 'SHA384', + ES512: 'SHA512', + PS256: 'SHA256', + PS384: 'SHA384', + PS512: 'SHA512', + }; + + const algorithm = algorithmMap[jwtAlg]; + if (!algorithm) { + throw new Error(`Unsupported JWT algorithm: ${jwtAlg}`); + } + + return algorithm; + }, + + verifySig( + data: string, + sig: string, + x5c: string[], + algorithm: string, + ): boolean { + try { + if (!x5c || x5c.length === 0) { + console.error('x5c certificate chain is missing'); + return false; + } + + const certDer = Buffer.from(x5c[0], 'base64'); + const cert = new X509Certificate(new Uint8Array(certDer)); + const publicKey = cert.publicKey; + + const signatureBytes = Buffer.from(sig, 'base64url'); + const signatureUint8Array = new Uint8Array(signatureBytes); + + const cryptoAlgorithm = JWTVerifier.getVerifyAlgorithm(algorithm); + + const verifier = createVerify(cryptoAlgorithm); + verifier.update(data); + + return verifier.verify(publicKey, signatureUint8Array); + } catch (error) { + console.error('JWT verification error:', error); + return false; + } + }, +}; diff --git a/packages/jades/tsconfig.json b/packages/jades/tsconfig.json new file mode 100644 index 0000000..de46450 --- /dev/null +++ b/packages/jades/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/dist", "**/*.spec.ts"] +} diff --git a/packages/jades/vitest.config.mts b/packages/jades/vitest.config.mts new file mode 100644 index 0000000..5842dff --- /dev/null +++ b/packages/jades/vitest.config.mts @@ -0,0 +1,4 @@ +// vite.config.ts +import { allEnvs } from '../../vitest.shared'; + +export default allEnvs; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c47e8a6..52c8258 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,6 +183,24 @@ importers: specifier: workspace:* version: link:../node-crypto + packages/jades: + dependencies: + '@sd-jwt/core': + specifier: workspace:* + version: link:../core + '@sd-jwt/crypto-nodejs': + specifier: workspace:* + version: link:../node-crypto + '@sd-jwt/types': + specifier: workspace:* + version: link:../types + '@sd-jwt/utils': + specifier: workspace:* + version: link:../utils + asn1js: + specifier: ^3.0.5 + version: 3.0.6 + packages/jwt-status-list: dependencies: '@sd-jwt/types': @@ -698,10 +716,6 @@ packages: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.1.2': - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} @@ -916,131 +930,66 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rollup/rollup-android-arm-eabi@4.34.9': - resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm-eabi@4.43.0': resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.34.9': - resolution: {integrity: sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==} - cpu: [arm64] - os: [android] - '@rollup/rollup-android-arm64@4.43.0': resolution: {integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.34.9': - resolution: {integrity: sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-arm64@4.43.0': resolution: {integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.34.9': - resolution: {integrity: sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.43.0': resolution: {integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.34.9': - resolution: {integrity: sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.43.0': resolution: {integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.34.9': - resolution: {integrity: sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.43.0': resolution: {integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.34.9': - resolution: {integrity: sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.43.0': resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.34.9': - resolution: {integrity: sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.43.0': resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.34.9': - resolution: {integrity: sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.43.0': resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.34.9': - resolution: {integrity: sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-musl@4.43.0': resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.34.9': - resolution: {integrity: sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==} - cpu: [loong64] - os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.43.0': resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': - resolution: {integrity: sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==} - cpu: [ppc64] - os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.34.9': - resolution: {integrity: sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==} - cpu: [riscv64] - os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.43.0': resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} cpu: [riscv64] @@ -1051,61 +1000,31 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.34.9': - resolution: {integrity: sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==} - cpu: [s390x] - os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.43.0': resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.34.9': - resolution: {integrity: sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-gnu@4.43.0': resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.34.9': - resolution: {integrity: sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-musl@4.43.0': resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.34.9': - resolution: {integrity: sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.43.0': resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.34.9': - resolution: {integrity: sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.43.0': resolution: {integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.34.9': - resolution: {integrity: sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.43.0': resolution: {integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==} cpu: [x64] @@ -1189,9 +1108,6 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} @@ -1392,6 +1308,10 @@ packages: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} engines: {node: '>=8'} + asn1js@3.0.6: + resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==} + engines: {node: '>=12.0.0'} + assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -1652,10 +1572,6 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1899,10 +1815,6 @@ packages: debug: optional: true - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -1997,11 +1909,6 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -2297,10 +2204,6 @@ packages: resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} engines: {node: '>=8'} - jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} - jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -2455,10 +2358,6 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - lru-cache@10.2.0: - resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} - engines: {node: 14 || >=16.14} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2610,10 +2509,6 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} - minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} - engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -2941,10 +2836,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} - path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -3072,6 +2963,13 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -3187,11 +3085,6 @@ packages: engines: {node: '>=14'} hasBin: true - rollup@4.34.9: - resolution: {integrity: sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rollup@4.43.0: resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3541,6 +3434,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.3.5: resolution: {integrity: sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==} engines: {node: '>=18'} @@ -4150,7 +4046,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.3': dependencies: - '@jridgewell/set-array': 1.1.2 + '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 @@ -4162,8 +4058,6 @@ snapshots: '@jridgewell/resolve-uri@3.1.1': {} - '@jridgewell/set-array@1.1.2': {} - '@jridgewell/set-array@1.2.1': {} '@jridgewell/sourcemap-codec@1.4.15': {} @@ -4178,7 +4072,7 @@ snapshots: '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@lerna/create@8.1.2(encoding@0.1.13)(typescript@5.4.5)': dependencies: @@ -4295,7 +4189,7 @@ snapshots: '@npmcli/git@5.0.4': dependencies: '@npmcli/promise-spawn': 7.0.1 - lru-cache: 10.2.0 + lru-cache: 10.4.3 npm-pick-manifest: 9.0.0 proc-log: 3.0.0 promise-inflight: 1.0.1 @@ -4478,120 +4372,63 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/rollup-android-arm-eabi@4.34.9': - optional: true - '@rollup/rollup-android-arm-eabi@4.43.0': optional: true - '@rollup/rollup-android-arm64@4.34.9': - optional: true - '@rollup/rollup-android-arm64@4.43.0': optional: true - '@rollup/rollup-darwin-arm64@4.34.9': - optional: true - '@rollup/rollup-darwin-arm64@4.43.0': optional: true - '@rollup/rollup-darwin-x64@4.34.9': - optional: true - '@rollup/rollup-darwin-x64@4.43.0': optional: true - '@rollup/rollup-freebsd-arm64@4.34.9': - optional: true - '@rollup/rollup-freebsd-arm64@4.43.0': optional: true - '@rollup/rollup-freebsd-x64@4.34.9': - optional: true - '@rollup/rollup-freebsd-x64@4.43.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.9': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.43.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.9': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.43.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.9': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.43.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.34.9': - optional: true - '@rollup/rollup-linux-arm64-musl@4.43.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.9': - optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.43.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': - optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.34.9': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.43.0': optional: true '@rollup/rollup-linux-riscv64-musl@4.43.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.34.9': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.43.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.34.9': - optional: true - '@rollup/rollup-linux-x64-gnu@4.43.0': optional: true - '@rollup/rollup-linux-x64-musl@4.34.9': - optional: true - '@rollup/rollup-linux-x64-musl@4.43.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.34.9': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.43.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.34.9': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.43.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.34.9': - optional: true - '@rollup/rollup-win32-x64-msvc@4.43.0': optional: true @@ -4674,8 +4511,6 @@ snapshots: '@types/cookie@0.6.0': {} - '@types/estree@1.0.6': {} - '@types/estree@1.0.7': {} '@types/estree@1.0.8': {} @@ -4873,6 +4708,12 @@ snapshots: arrify@2.0.1: {} + asn1js@3.0.6: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.3 + tslib: 2.8.1 + assertion-error@1.1.0: {} async@3.2.5: {} @@ -4956,8 +4797,8 @@ snapshots: '@npmcli/fs': 3.1.0 fs-minipass: 3.0.3 glob: 10.4.5 - lru-cache: 10.2.0 - minipass: 7.0.4 + lru-cache: 10.4.3 + minipass: 7.1.2 minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 @@ -5164,12 +5005,6 @@ snapshots: create-require@1.1.1: {} - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -5347,7 +5182,7 @@ snapshots: execa@5.0.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -5422,11 +5257,6 @@ snapshots: follow-redirects@1.15.5: {} - foreground-child@3.1.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -5452,7 +5282,7 @@ snapshots: fs-minipass@3.0.3: dependencies: - minipass: 7.0.4 + minipass: 7.1.2 fs.realpath@1.0.0: {} @@ -5524,14 +5354,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.3.10: - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.3 - minipass: 7.0.4 - path-scurry: 1.10.1 - glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -5563,7 +5385,7 @@ snapshots: fs.realpath: 1.0.0 minimatch: 8.0.4 minipass: 4.2.8 - path-scurry: 1.10.1 + path-scurry: 1.11.1 globby@11.1.0: dependencies: @@ -5617,7 +5439,7 @@ snapshots: hosted-git-info@7.0.1: dependencies: - lru-cache: 10.2.0 + lru-cache: 10.4.3 html-encoding-sniffer@4.0.0: dependencies: @@ -5831,12 +5653,6 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - jackspeak@2.3.6: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -6089,8 +5905,6 @@ snapshots: dependencies: get-func-name: 2.0.2 - lru-cache@10.2.0: {} - lru-cache@10.4.3: {} lru-cache@6.0.0: @@ -6150,7 +5964,7 @@ snapshots: cacache: 18.0.2 http-cache-semantics: 4.1.1 is-lambda: 1.0.1 - minipass: 7.0.4 + minipass: 7.1.2 minipass-fetch: 3.0.4 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 @@ -6237,11 +6051,11 @@ snapshots: minipass-collect@2.0.1: dependencies: - minipass: 7.0.4 + minipass: 7.1.2 minipass-fetch@3.0.4: dependencies: - minipass: 7.0.4 + minipass: 7.1.2 minipass-sized: 1.0.3 minizlib: 2.1.2 optionalDependencies: @@ -6272,8 +6086,6 @@ snapshots: minipass@5.0.0: {} - minipass@7.0.4: {} - minipass@7.1.2: {} minizlib@2.1.2: @@ -6466,7 +6278,7 @@ snapshots: npm-registry-fetch@16.1.0: dependencies: make-fetch-happen: 13.0.0 - minipass: 7.0.4 + minipass: 7.1.2 minipass-fetch: 3.0.4 minipass-json-stream: 1.0.1 minizlib: 2.1.2 @@ -6648,7 +6460,7 @@ snapshots: '@npmcli/run-script': 7.0.2 cacache: 18.0.2 fs-minipass: 3.0.3 - minipass: 7.0.4 + minipass: 7.1.2 npm-package-arg: 11.0.1 npm-packlist: 8.0.2 npm-pick-manifest: 9.0.0 @@ -6706,11 +6518,6 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.10.1: - dependencies: - lru-cache: 10.2.0 - minipass: 7.1.2 - path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 @@ -6799,6 +6606,12 @@ snapshots: punycode@2.3.1: {} + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.3: {} + querystringify@2.2.0: {} queue-microtask@1.2.3: {} @@ -6816,7 +6629,7 @@ snapshots: read-package-json@6.0.4: dependencies: - glob: 10.3.10 + glob: 10.4.5 json-parse-even-better-errors: 3.0.1 normalize-package-data: 5.0.0 npm-normalize-package-bin: 3.0.1 @@ -6916,31 +6729,6 @@ snapshots: dependencies: glob: 9.3.5 - rollup@4.34.9: - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.9 - '@rollup/rollup-android-arm64': 4.34.9 - '@rollup/rollup-darwin-arm64': 4.34.9 - '@rollup/rollup-darwin-x64': 4.34.9 - '@rollup/rollup-freebsd-arm64': 4.34.9 - '@rollup/rollup-freebsd-x64': 4.34.9 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.9 - '@rollup/rollup-linux-arm-musleabihf': 4.34.9 - '@rollup/rollup-linux-arm64-gnu': 4.34.9 - '@rollup/rollup-linux-arm64-musl': 4.34.9 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.9 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.9 - '@rollup/rollup-linux-riscv64-gnu': 4.34.9 - '@rollup/rollup-linux-s390x-gnu': 4.34.9 - '@rollup/rollup-linux-x64-gnu': 4.34.9 - '@rollup/rollup-linux-x64-musl': 4.34.9 - '@rollup/rollup-win32-arm64-msvc': 4.34.9 - '@rollup/rollup-win32-ia32-msvc': 4.34.9 - '@rollup/rollup-win32-x64-msvc': 4.34.9 - fsevents: 2.3.3 - rollup@4.43.0: dependencies: '@types/estree': 1.0.7 @@ -7101,7 +6889,7 @@ snapshots: ssri@10.0.5: dependencies: - minipass: 7.0.4 + minipass: 7.1.2 ssri@9.0.1: dependencies: @@ -7347,6 +7135,8 @@ snapshots: tslib@2.6.2: {} + tslib@2.8.1: {} + tsup@8.3.5(postcss@8.5.6)(typescript@5.4.5): dependencies: bundle-require: 5.1.0(esbuild@0.24.2) @@ -7359,7 +7149,7 @@ snapshots: picocolors: 1.1.1 postcss-load-config: 6.0.1(postcss@8.5.6) resolve-from: 5.0.0 - rollup: 4.34.9 + rollup: 4.43.0 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tinyexec: 0.3.2