gdc-common-utils-ts 1.0.5 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AesManager.d.ts +27 -0
- package/dist/AesManager.js +62 -0
- package/dist/CryptographyService.d.ts +76 -0
- package/dist/CryptographyService.js +403 -0
- package/dist/constants/Schemas.d.ts +45 -0
- package/dist/constants/Schemas.js +48 -0
- package/dist/constants/index.js +1 -0
- package/{src/constants/schemaorg.ts → dist/constants/schemaorg.d.ts} +24 -116
- package/dist/constants/schemaorg.js +185 -0
- package/dist/cryptoDecode.d.ts +3 -0
- package/dist/cryptoDecode.js +90 -0
- package/dist/cryptoEncode.d.ts +1 -0
- package/dist/cryptoEncode.js +30 -0
- package/dist/cryptography.abstract.d.ts +13 -0
- package/{src/cryptography.abstract.ts → dist/cryptography.abstract.js} +6 -14
- package/dist/hmac.d.ts +2 -0
- package/{src/hmac.ts → dist/hmac.js} +4 -7
- package/dist/index.js +3 -0
- package/{src/interfaces/Cryptography.types.ts → dist/interfaces/Cryptography.types.d.ts} +71 -71
- package/dist/interfaces/Cryptography.types.js +8 -0
- package/dist/interfaces/ICryptoHelper.d.ts +28 -0
- package/dist/interfaces/ICryptoHelper.js +3 -0
- package/dist/interfaces/ICryptography.d.ts +154 -0
- package/dist/interfaces/ICryptography.js +3 -0
- package/dist/interfaces/IWallet.d.ts +55 -0
- package/dist/interfaces/IWallet.js +3 -0
- package/dist/interfaces/MlDsa.d.ts +9 -0
- package/{src/interfaces/MlDsa.ts → dist/interfaces/MlDsa.js} +1 -5
- package/dist/interfaces/MlKem.d.ts +11 -0
- package/{src/interfaces/MlKem.ts → dist/interfaces/MlKem.js} +0 -5
- package/dist/models/aes.d.ts +85 -0
- package/dist/models/aes.js +10 -0
- package/dist/models/auth.d.ts +35 -0
- package/dist/models/auth.js +3 -0
- package/{src/models/bundle.ts → dist/models/bundle.d.ts} +41 -63
- package/dist/models/bundle.js +26 -0
- package/dist/models/clinical-sections.d.ts +36 -0
- package/dist/models/clinical-sections.en.d.ts +75 -0
- package/dist/models/clinical-sections.en.js +81 -0
- package/dist/models/clinical-sections.js +32 -0
- package/dist/models/comm.d.ts +44 -0
- package/dist/models/comm.js +4 -0
- package/{src/models/confidential-job.ts → dist/models/confidential-job.d.ts} +23 -45
- package/dist/models/confidential-job.js +20 -0
- package/dist/models/confidential-message.d.ts +97 -0
- package/dist/models/confidential-message.js +4 -0
- package/{src/models/confidential-storage.ts → dist/models/confidential-storage.d.ts} +35 -56
- package/dist/models/confidential-storage.js +3 -0
- package/{src/models/consent-rule.ts → dist/models/consent-rule.d.ts} +22 -42
- package/dist/models/consent-rule.js +21 -0
- package/{src/models/crypto.ts → dist/models/crypto.d.ts} +5 -13
- package/dist/models/crypto.js +3 -0
- package/dist/models/device-license.d.ts +133 -0
- package/dist/models/device-license.js +3 -0
- package/{src/models/did.ts → dist/models/did.d.ts} +21 -30
- package/dist/models/did.js +3 -0
- package/dist/models/fhir-documents.d.ts +101 -0
- package/dist/models/fhir-documents.js +3 -0
- package/{src/models/index.ts → dist/models/index.d.ts} +2 -0
- package/dist/models/index.js +33 -0
- package/dist/models/indexing.d.ts +11 -0
- package/dist/models/indexing.js +18 -0
- package/dist/models/interoperable-claims.d.ts +68 -0
- package/dist/models/interoperable-claims.js +105 -0
- package/dist/models/issue.d.ts +57 -0
- package/dist/models/issue.js +75 -0
- package/dist/models/jsonapi.d.ts +13 -0
- package/dist/models/jsonapi.js +3 -0
- package/{src/models/jwe.ts → dist/models/jwe.d.ts} +10 -22
- package/dist/models/jwe.js +3 -0
- package/{src/models/jwk.ts → dist/models/jwk.d.ts} +0 -11
- package/dist/models/jwk.js +3 -0
- package/{src/models/jws.ts → dist/models/jws.d.ts} +0 -7
- package/dist/models/jws.js +3 -0
- package/dist/models/jwt.d.ts +9 -0
- package/dist/models/jwt.js +3 -0
- package/dist/models/multibase58.d.ts +13 -0
- package/dist/models/multibase58.js +40 -0
- package/dist/models/oidc4ida.common.model.d.ts +33 -0
- package/dist/models/oidc4ida.common.model.js +3 -0
- package/dist/models/oidc4ida.document.model.d.ts +50 -0
- package/dist/models/oidc4ida.document.model.js +3 -0
- package/{src/models/oidc4ida.electronicRecord.model.ts → dist/models/oidc4ida.electronicRecord.model.d.ts} +18 -37
- package/dist/models/oidc4ida.electronicRecord.model.js +3 -0
- package/{src/models/oidc4ida.evidence.model.ts → dist/models/oidc4ida.evidence.model.d.ts} +17 -35
- package/dist/models/oidc4ida.evidence.model.js +5 -0
- package/dist/models/openid-device.d.ts +119 -0
- package/dist/models/openid-device.js +3 -0
- package/dist/models/operation-outcome.d.ts +26 -0
- package/dist/models/operation-outcome.js +3 -0
- package/{src/models/params.ts → dist/models/params.d.ts} +20 -29
- package/dist/models/params.js +3 -0
- package/dist/models/resource-document.d.ts +14 -0
- package/dist/models/resource-document.js +3 -0
- package/dist/models/response.d.ts +1 -0
- package/dist/models/response.js +3 -0
- package/dist/models/urlPath.d.ts +58 -0
- package/dist/models/urlPath.js +76 -0
- package/dist/models/verifiable-credential.d.ts +45 -0
- package/dist/models/verifiable-credential.js +8 -0
- package/dist/utils/actor.d.ts +18 -0
- package/dist/utils/actor.js +36 -0
- package/dist/utils/base-convert.d.ts +20 -0
- package/{src/utils/base-convert.ts → dist/utils/base-convert.js} +23 -36
- package/dist/utils/baseN.d.ts +35 -0
- package/dist/utils/baseN.js +174 -0
- package/dist/utils/bundle.d.ts +6 -0
- package/dist/utils/bundle.js +32 -0
- package/dist/utils/content.d.ts +55 -0
- package/{src/utils/content.ts → dist/utils/content.js} +4 -10
- package/dist/utils/did.d.ts +67 -0
- package/dist/utils/did.js +123 -0
- package/dist/utils/format-converter.d.ts +21 -0
- package/dist/utils/format-converter.js +109 -0
- package/dist/utils/index.js +13 -0
- package/dist/utils/jwt.d.ts +52 -0
- package/dist/utils/jwt.js +153 -0
- package/dist/utils/manager-error.d.ts +15 -0
- package/dist/utils/manager-error.js +23 -0
- package/dist/utils/multibase58.d.ts +13 -0
- package/dist/utils/multibase58.js +40 -0
- package/dist/utils/multibasehash.d.ts +8 -0
- package/{src/utils/multibasehash.ts → dist/utils/multibasehash.js} +8 -13
- package/dist/utils/normalize.d.ts +16 -0
- package/{src/utils/normalize.ts → dist/utils/normalize.js} +15 -18
- package/dist/utils/object-convert.d.ts +20 -0
- package/{src/utils/object-convert.ts → dist/utils/object-convert.js} +10 -16
- package/dist/utils/string-convert.d.ts +24 -0
- package/{src/utils/string-convert.ts → dist/utils/string-convert.js} +5 -14
- package/dist/utils/string-utils.d.ts +25 -0
- package/{src/utils/string-utils.ts → dist/utils/string-utils.js} +12 -16
- package/dist/utils/url.d.ts +27 -0
- package/{src/utils/url.ts → dist/utils/url.js} +6 -7
- package/package.json +56 -14
- package/PUBLISHING.md +0 -33
- package/__tests__/AesManager.test.ts +0 -53
- package/__tests__/CryptographyService.test.ts +0 -194
- package/__tests__/bundle.test.ts +0 -29
- package/__tests__/content.test.ts +0 -72
- package/__tests__/crypto-encode-decode.test.ts +0 -52
- package/__tests__/crypto-hmac.test.ts +0 -21
- package/__tests__/did-generateServiceId.errors.test.ts +0 -8
- package/__tests__/did-generateServiceId.test.ts +0 -18
- package/__tests__/models-clinical-sections.test.ts +0 -32
- package/__tests__/models-multibase58.test.ts +0 -33
- package/__tests__/multibase58.errors.test.ts +0 -7
- package/__tests__/multibase58.test.ts +0 -28
- package/__tests__/multibasehash.test.ts +0 -25
- package/__tests__/utils-actor.test.ts +0 -46
- package/__tests__/utils-base-convert.test.ts +0 -57
- package/__tests__/utils-baseN.test.ts +0 -40
- package/__tests__/utils-did-extra.test.ts +0 -33
- package/__tests__/utils-format-converter.test.ts +0 -87
- package/__tests__/utils-jwt.test.ts +0 -57
- package/__tests__/utils-manager-error.test.ts +0 -11
- package/__tests__/utils-normalize.test.ts +0 -15
- package/__tests__/utils-object-convert.test.ts +0 -38
- package/__tests__/utils-string-convert.test.ts +0 -20
- package/__tests__/utils-string-utils.test.ts +0 -25
- package/__tests__/utils-url.test.ts +0 -21
- package/babel.config.cjs +0 -5
- package/jest.config.ts +0 -47
- package/src/AesManager.ts +0 -82
- package/src/CryptographyService.ts +0 -461
- package/src/JweManager.ts.txt +0 -365
- package/src/KmsService.txt +0 -493
- package/src/constants/Schemas.ts +0 -61
- package/src/cryptoDecode.ts +0 -104
- package/src/cryptoEncode.ts +0 -36
- package/src/interfaces/ICryptoHelper.ts +0 -33
- package/src/interfaces/ICryptography.ts +0 -177
- package/src/interfaces/IWallet.ts +0 -62
- package/src/models/aes.ts +0 -93
- package/src/models/auth.ts +0 -38
- package/src/models/bundle.txt +0 -93
- package/src/models/clinical-sections.en.ts +0 -82
- package/src/models/clinical-sections.ts +0 -64
- package/src/models/comm.ts +0 -63
- package/src/models/confidential-message.ts +0 -137
- package/src/models/device-license.ts +0 -161
- package/src/models/indexing.ts +0 -20
- package/src/models/issue.ts +0 -85
- package/src/models/jsonapi.ts +0 -19
- package/src/models/jwt.ts +0 -15
- package/src/models/multibase58.ts +0 -46
- package/src/models/oidc4ida.common.model.ts +0 -39
- package/src/models/oidc4ida.document.model.ts +0 -61
- package/src/models/openid-device.ts +0 -146
- package/src/models/operation-outcome.ts +0 -34
- package/src/models/resource-document.ts +0 -21
- package/src/models/response.ts +0 -5
- package/src/models/urlPath.ts +0 -76
- package/src/models/verifiable-credential.ts +0 -52
- package/src/types/noble-hashes.d.ts +0 -4
- package/src/utils/actor.ts +0 -56
- package/src/utils/baseN.ts +0 -203
- package/src/utils/bundle.ts +0 -30
- package/src/utils/did.ts +0 -155
- package/src/utils/format-converter.ts +0 -119
- package/src/utils/jwt.ts +0 -165
- package/src/utils/manager-error.ts +0 -27
- package/src/utils/multibase58.ts +0 -46
- package/tsconfig.json +0 -15
- /package/{src/constants/index.ts → dist/constants/index.d.ts} +0 -0
- /package/{src/index.ts → dist/index.d.ts} +0 -0
- /package/{src/utils/index.ts → dist/utils/index.d.ts} +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ProtectedDataAES } from './models/aes';
|
|
2
|
+
/**
|
|
3
|
+
* Manages AES-GCM encryption and decryption using Node's native crypto module.
|
|
4
|
+
* This class handles the core cryptography and the base64url serialization required for JWE.
|
|
5
|
+
*/
|
|
6
|
+
export declare class AesManager {
|
|
7
|
+
private readonly ALGORITHM;
|
|
8
|
+
private readonly KEY_SIZE;
|
|
9
|
+
private readonly IV_GENERATION_SIZE;
|
|
10
|
+
private readonly TAG_SIZE_BYTES;
|
|
11
|
+
/**
|
|
12
|
+
* Encrypts a plaintext string and returns the components as base64url strings.
|
|
13
|
+
* @param plaintext The stringified data to encrypt (e.g. a stringified object or ASCII string from raw bytes)
|
|
14
|
+
* @param cekBytes The 32-byte Content Encryption Key.
|
|
15
|
+
* @param aad The Additional Authenticated Data string for integrity protection.
|
|
16
|
+
* @returns A promise resolving to the JWE-compatible encrypted components.
|
|
17
|
+
*/
|
|
18
|
+
encrypt(plaintext: string, cekBytes: Uint8Array, aad: string): Promise<ProtectedDataAES>;
|
|
19
|
+
/**
|
|
20
|
+
* Decrypts JWE-compatible encrypted components back to a plaintext string.
|
|
21
|
+
* @param encryptedData The object containing the base64url-encoded ciphertext, iv, and tag.
|
|
22
|
+
* @param cekBytes The 32-byte Content Encryption Key.
|
|
23
|
+
* @param aad The Additional Authenticated Data for integrity verification.
|
|
24
|
+
* @returns A promise resolving to the decrypted plaintext string.
|
|
25
|
+
*/
|
|
26
|
+
decrypt(encryptedData: ProtectedDataAES, cekBytes: Uint8Array, aad: string): Promise<string>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
// File: crypto-ts/AesManager.ts
|
|
3
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
|
4
|
+
import { Content } from './utils/content.js';
|
|
5
|
+
/**
|
|
6
|
+
* Manages AES-GCM encryption and decryption using Node's native crypto module.
|
|
7
|
+
* This class handles the core cryptography and the base64url serialization required for JWE.
|
|
8
|
+
*/
|
|
9
|
+
export class AesManager {
|
|
10
|
+
ALGORITHM = 'aes-256-gcm';
|
|
11
|
+
KEY_SIZE = 32; // 256-bit key
|
|
12
|
+
IV_GENERATION_SIZE = 12; // 96-bit IV, recommended by NIST for GCM
|
|
13
|
+
TAG_SIZE_BYTES = 16; // 128-bit authentication tag
|
|
14
|
+
/**
|
|
15
|
+
* Encrypts a plaintext string and returns the components as base64url strings.
|
|
16
|
+
* @param plaintext The stringified data to encrypt (e.g. a stringified object or ASCII string from raw bytes)
|
|
17
|
+
* @param cekBytes The 32-byte Content Encryption Key.
|
|
18
|
+
* @param aad The Additional Authenticated Data string for integrity protection.
|
|
19
|
+
* @returns A promise resolving to the JWE-compatible encrypted components.
|
|
20
|
+
*/
|
|
21
|
+
async encrypt(plaintext, cekBytes, aad) {
|
|
22
|
+
if (cekBytes.length !== this.KEY_SIZE) {
|
|
23
|
+
throw new Error(`Invalid key length: a ${this.KEY_SIZE}-byte key is required.`);
|
|
24
|
+
}
|
|
25
|
+
const iv = randomBytes(this.IV_GENERATION_SIZE);
|
|
26
|
+
const aadBytes = Content.stringToBytesUTF8(aad);
|
|
27
|
+
const cipher = createCipheriv(this.ALGORITHM, cekBytes, iv, {
|
|
28
|
+
authTagLength: this.TAG_SIZE_BYTES
|
|
29
|
+
});
|
|
30
|
+
cipher.setAAD(aadBytes);
|
|
31
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
32
|
+
const tag = cipher.getAuthTag();
|
|
33
|
+
return {
|
|
34
|
+
ciphertext: Content.bytesToRawBase64UrlSafe(ciphertext),
|
|
35
|
+
iv: Content.bytesToRawBase64UrlSafe(iv),
|
|
36
|
+
tag: Content.bytesToRawBase64UrlSafe(tag),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decrypts JWE-compatible encrypted components back to a plaintext string.
|
|
41
|
+
* @param encryptedData The object containing the base64url-encoded ciphertext, iv, and tag.
|
|
42
|
+
* @param cekBytes The 32-byte Content Encryption Key.
|
|
43
|
+
* @param aad The Additional Authenticated Data for integrity verification.
|
|
44
|
+
* @returns A promise resolving to the decrypted plaintext string.
|
|
45
|
+
*/
|
|
46
|
+
async decrypt(encryptedData, cekBytes, aad) {
|
|
47
|
+
if (cekBytes.length !== this.KEY_SIZE) {
|
|
48
|
+
throw new Error(`Invalid key length: a ${this.KEY_SIZE}-byte key is required.`);
|
|
49
|
+
}
|
|
50
|
+
const ciphertextBytes = Content.base64ToBytes(encryptedData.ciphertext);
|
|
51
|
+
const tagBytes = Content.base64ToBytes(encryptedData.tag);
|
|
52
|
+
const ivBytes = Content.base64ToBytes(encryptedData.iv);
|
|
53
|
+
const aadBytes = Content.stringToBytesUTF8(aad);
|
|
54
|
+
const decipher = createDecipheriv(this.ALGORITHM, cekBytes, ivBytes, {
|
|
55
|
+
authTagLength: this.TAG_SIZE_BYTES
|
|
56
|
+
});
|
|
57
|
+
decipher.setAuthTag(tagBytes);
|
|
58
|
+
decipher.setAAD(aadBytes);
|
|
59
|
+
const decryptedBytes = Buffer.concat([decipher.update(ciphertextBytes), decipher.final()]);
|
|
60
|
+
return Content.bytesToStringUTF8(decryptedBytes);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ICryptoHelper } from './interfaces/ICryptoHelper';
|
|
2
|
+
import { ICryptography } from './interfaces/ICryptography';
|
|
3
|
+
import { DataCompactJWT, JwtCompactParts } from './models/jwt';
|
|
4
|
+
import { JweObject } from './models/jwe';
|
|
5
|
+
import { MlkemPublicJwk, MldsaPublicJwk, PublicJwk, MlkemPrivateJwk, MldsaAlg, MlkemCurve } from './interfaces/Cryptography.types';
|
|
6
|
+
import { ProtectedDataAES } from './models/aes';
|
|
7
|
+
/**
|
|
8
|
+
* Implements the ICryptography interface, providing a complete suite of low-level,
|
|
9
|
+
* stateless cryptographic functions. This service is the "engine" of the security layer,
|
|
10
|
+
* orchestrating Post-Quantum and AES primitives.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CryptographyService implements ICryptography {
|
|
13
|
+
private aesManager;
|
|
14
|
+
private cryptoHelper;
|
|
15
|
+
private mlDsaModule;
|
|
16
|
+
private mlKemModule;
|
|
17
|
+
private readonly ML_KEM_SEED_SIZE;
|
|
18
|
+
private readonly ML_DSA_SEED_SIZE;
|
|
19
|
+
constructor(cryptoHelper: ICryptoHelper);
|
|
20
|
+
private loadMlDsa;
|
|
21
|
+
private loadMlKem;
|
|
22
|
+
digestString(data: string, algorithm: string): Promise<string>;
|
|
23
|
+
generateKeyPairMlKem(seedBytes?: Uint8Array, crv?: MlkemCurve): Promise<{
|
|
24
|
+
publicJWKey: MlkemPublicJwk & {
|
|
25
|
+
kid: string;
|
|
26
|
+
};
|
|
27
|
+
secretKeyBytes: Uint8Array;
|
|
28
|
+
}>;
|
|
29
|
+
generateKeyPairMlDsa(seedBytes?: Uint8Array, alg?: MldsaAlg): Promise<{
|
|
30
|
+
publicJWKey: MldsaPublicJwk & {
|
|
31
|
+
kid: string;
|
|
32
|
+
};
|
|
33
|
+
secretKeyBytes: Uint8Array;
|
|
34
|
+
}>;
|
|
35
|
+
encryptJwe(payload: object, protectedHeader: object, secretJWKey: MlkemPrivateJwk, recipientsJWKeys: MlkemPublicJwk[]): Promise<JweObject>;
|
|
36
|
+
encryptJweToCompact(payload: object | string, protectedHeader: object, secretJWKey: MlkemPrivateJwk, recipientJWKey: MlkemPublicJwk): Promise<string>;
|
|
37
|
+
decryptJwe(jwe: JweObject | string, secretKeyJwk: MlkemPrivateJwk): Promise<{
|
|
38
|
+
decryptedBytes: Uint8Array;
|
|
39
|
+
protectedHeader: object;
|
|
40
|
+
}>;
|
|
41
|
+
getRecipientKidsFromJwe(jwe: JweObject | string): string[];
|
|
42
|
+
signDataJws(payload: object, protectedHeader: object, secretKeyBytes: Uint8Array): Promise<JwtCompactParts>;
|
|
43
|
+
verifyJws(jws: JwtCompactParts | string, publicJwk: PublicJwk): Promise<boolean>;
|
|
44
|
+
verifyDetachedJws(payloadBytes: Uint8Array, detachedJws: string, publicJWKey: PublicJwk): Promise<boolean>;
|
|
45
|
+
encrypt(plaintext: string, cekBytes: Uint8Array, aad: string): Promise<ProtectedDataAES>;
|
|
46
|
+
decrypt(encryptedData: ProtectedDataAES, cekBytes: Uint8Array, aad: string): Promise<string>;
|
|
47
|
+
encapsulate(cekSeedBytes: Uint8Array, secretKeyBytes: Uint8Array, recipientPublicKeyBytes: Uint8Array): Promise<{
|
|
48
|
+
encapsulatedCekBytes: Uint8Array;
|
|
49
|
+
derivedCekBytes: Uint8Array;
|
|
50
|
+
}>;
|
|
51
|
+
decapsulate(encapsulatedBytes: Uint8Array, secretKeyBytes: Uint8Array): Promise<Uint8Array>;
|
|
52
|
+
signBytes(payloadBytes: Uint8Array, secretKeyBytes: Uint8Array, alg: MldsaAlg): Promise<Uint8Array>;
|
|
53
|
+
verifyBytes(signatureBytes: Uint8Array, dataBytes: Uint8Array, publicKey: PublicJwk): Promise<boolean>;
|
|
54
|
+
jwsToCompact(jws: DataCompactJWT): string;
|
|
55
|
+
parseCompactJws(jwsString: string): DataCompactJWT;
|
|
56
|
+
parseCompactJwe(jweString: string): JweObject;
|
|
57
|
+
/**
|
|
58
|
+
* Computes a JWK thumbprint using a specified hash algorithm.
|
|
59
|
+
* This implementation is platform-agnostic by using the injected ICryptoHelper.
|
|
60
|
+
*/
|
|
61
|
+
private _computeJwkThumbprint;
|
|
62
|
+
/**
|
|
63
|
+
* Creates a canonical string from a simple, flat JSON object as required by
|
|
64
|
+
* RFC 7638 for JWK thumbprints.
|
|
65
|
+
*/
|
|
66
|
+
private _canonicalizeForJwkThumbprint;
|
|
67
|
+
/**
|
|
68
|
+
* Extracts the Base JWK for thumbprint calculation per RFC 7638.
|
|
69
|
+
* This handles both Post-Quantum (OKP, AKP) and legacy (EC) key types.
|
|
70
|
+
*/
|
|
71
|
+
private _toBaseJwk;
|
|
72
|
+
/**
|
|
73
|
+
* Utility to convert a hex string to a Uint8Array.
|
|
74
|
+
*/
|
|
75
|
+
private _hexToBytes;
|
|
76
|
+
}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
// File: crypto-ts/CryptographyService.ts
|
|
3
|
+
import * as pako from 'pako';
|
|
4
|
+
import * as jwtUtils from './utils/jwt.js';
|
|
5
|
+
import { AesManager } from './AesManager.js';
|
|
6
|
+
import { Content } from './utils/content.js';
|
|
7
|
+
/**
|
|
8
|
+
* Implements the ICryptography interface, providing a complete suite of low-level,
|
|
9
|
+
* stateless cryptographic functions. This service is the "engine" of the security layer,
|
|
10
|
+
* orchestrating Post-Quantum and AES primitives.
|
|
11
|
+
*/
|
|
12
|
+
export class CryptographyService {
|
|
13
|
+
aesManager;
|
|
14
|
+
cryptoHelper;
|
|
15
|
+
mlDsaModule = null;
|
|
16
|
+
mlKemModule = null;
|
|
17
|
+
// Constants for seed sizes, as per @noble library requirements.
|
|
18
|
+
ML_KEM_SEED_SIZE = 64;
|
|
19
|
+
ML_DSA_SEED_SIZE = 32;
|
|
20
|
+
constructor(cryptoHelper) {
|
|
21
|
+
this.aesManager = new AesManager();
|
|
22
|
+
this.cryptoHelper = cryptoHelper;
|
|
23
|
+
}
|
|
24
|
+
async loadMlDsa() {
|
|
25
|
+
if (this.mlDsaModule)
|
|
26
|
+
return this.mlDsaModule;
|
|
27
|
+
try {
|
|
28
|
+
// Use explicit .js subpath to satisfy package exports in Metro/Node ESM.
|
|
29
|
+
const module = await import('@noble/post-quantum/ml-dsa.js');
|
|
30
|
+
this.mlDsaModule = module;
|
|
31
|
+
return module;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error('[CryptographyService] Missing dependency "@noble/post-quantum/ml-dsa.js". Install it for ML-DSA operations.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async loadMlKem() {
|
|
38
|
+
if (this.mlKemModule)
|
|
39
|
+
return this.mlKemModule;
|
|
40
|
+
try {
|
|
41
|
+
// Use explicit .js subpath to satisfy package exports in Metro/Node ESM.
|
|
42
|
+
const module = await import('@noble/post-quantum/ml-kem.js');
|
|
43
|
+
this.mlKemModule = module;
|
|
44
|
+
return module;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new Error('[CryptographyService] Missing dependency "@noble/post-quantum/ml-kem.js". Install it for ML-KEM operations.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
digestString(data, algorithm) {
|
|
51
|
+
return this.cryptoHelper.digestString(data, algorithm);
|
|
52
|
+
}
|
|
53
|
+
// --- Key Generation ---
|
|
54
|
+
async generateKeyPairMlKem(seedBytes, crv = 'ML-KEM-768') {
|
|
55
|
+
const mlKem = await this.loadMlKem();
|
|
56
|
+
let seed;
|
|
57
|
+
if (seedBytes && seedBytes.length === this.ML_KEM_SEED_SIZE) {
|
|
58
|
+
seed = seedBytes;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
seed = await this.cryptoHelper.getRandomBytes(this.ML_KEM_SEED_SIZE);
|
|
62
|
+
}
|
|
63
|
+
let keygenFn;
|
|
64
|
+
switch (crv) {
|
|
65
|
+
case 'ML-KEM-512':
|
|
66
|
+
keygenFn = mlKem.ml_kem512.keygen;
|
|
67
|
+
break;
|
|
68
|
+
case 'ML-KEM-1024':
|
|
69
|
+
keygenFn = mlKem.ml_kem1024.keygen;
|
|
70
|
+
break;
|
|
71
|
+
case 'ML-KEM-768':
|
|
72
|
+
default:
|
|
73
|
+
keygenFn = mlKem.ml_kem768.keygen;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
const { secretKey, publicKey: publicKeyBytes } = keygenFn(seed);
|
|
77
|
+
const pubJwkWithoutKid = {
|
|
78
|
+
kty: 'OKP', crv: crv, x: Content.bytesToRawBase64UrlSafe(publicKeyBytes),
|
|
79
|
+
};
|
|
80
|
+
const kid = await this._computeJwkThumbprint(pubJwkWithoutKid);
|
|
81
|
+
const publicKey = { ...pubJwkWithoutKid, kid };
|
|
82
|
+
return { publicJWKey: publicKey, secretKeyBytes: secretKey };
|
|
83
|
+
}
|
|
84
|
+
async generateKeyPairMlDsa(seedBytes, alg = 'ML-DSA-44') {
|
|
85
|
+
const mlDsa = await this.loadMlDsa();
|
|
86
|
+
let seed;
|
|
87
|
+
if (seedBytes && seedBytes.length === this.ML_DSA_SEED_SIZE) {
|
|
88
|
+
seed = seedBytes;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
seed = await this.cryptoHelper.getRandomBytes(this.ML_DSA_SEED_SIZE);
|
|
92
|
+
}
|
|
93
|
+
let keygenFn;
|
|
94
|
+
switch (alg) {
|
|
95
|
+
case 'ML-DSA-65':
|
|
96
|
+
keygenFn = mlDsa.ml_dsa65.keygen;
|
|
97
|
+
break;
|
|
98
|
+
case 'ML-DSA-87':
|
|
99
|
+
keygenFn = mlDsa.ml_dsa87.keygen;
|
|
100
|
+
break;
|
|
101
|
+
case 'ML-DSA-44':
|
|
102
|
+
default:
|
|
103
|
+
keygenFn = mlDsa.ml_dsa44.keygen;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
const { secretKey, publicKey: publicKeyBytes } = keygenFn(seed);
|
|
107
|
+
const pubJwkWithoutKid = {
|
|
108
|
+
kty: 'AKP', alg: alg, pub: Content.bytesToRawBase64UrlSafe(publicKeyBytes),
|
|
109
|
+
};
|
|
110
|
+
const kid = await this._computeJwkThumbprint(pubJwkWithoutKid);
|
|
111
|
+
const publicKey = { ...pubJwkWithoutKid, kid };
|
|
112
|
+
return { publicJWKey: publicKey, secretKeyBytes: secretKey };
|
|
113
|
+
}
|
|
114
|
+
// --- High-Level Workflows ---
|
|
115
|
+
async encryptJwe(payload, protectedHeader, secretJWKey, recipientsJWKeys) {
|
|
116
|
+
// ARCHITECTURAL NOTE: This implementation is currently only suitable for a single recipient.
|
|
117
|
+
// A Key Encapsulation Mechanism (KEM) derives a *different* shared secret for each recipient's public key.
|
|
118
|
+
// A true multi-recipient JWE requires a single Content Encryption Key (CEK) that is then
|
|
119
|
+
// encrypted (wrapped) for each recipient. This code uses the KEM-derived shared secret as the CEK.
|
|
120
|
+
// This must be refactored to a key-wrapping approach to support multiple recipients correctly.
|
|
121
|
+
if (recipientsJWKeys.length !== 1) {
|
|
122
|
+
// Temporarily throw until the architecture is fixed for multi-recipient.
|
|
123
|
+
throw new Error("CryptographyService.encryptJwe currently only supports a single recipient.");
|
|
124
|
+
}
|
|
125
|
+
const recipient = recipientsJWKeys[0];
|
|
126
|
+
const publicKeyBytes = Content.base64ToBytes(recipient.x);
|
|
127
|
+
// Per RFC 9278, we generate a random seed for the KEM. The KEM then derives both the
|
|
128
|
+
// final Content Encryption Key (CEK) and the encapsulated key from this seed.
|
|
129
|
+
const cekSeedBytes = await this.cryptoHelper.getRandomBytes(32);
|
|
130
|
+
const { derivedCekBytes, // This is the actual Content Encryption Key
|
|
131
|
+
encapsulatedCekBytes // This is the encrypted key for the recipient
|
|
132
|
+
} = await this.encapsulate(cekSeedBytes, secretJWKey.dBytes, publicKeyBytes);
|
|
133
|
+
// 2. Now, use the *derived* CEK to encrypt the payload with AES.
|
|
134
|
+
const protectedHeaderB64Url = Content.objectToRawBase64UrlSafe(protectedHeader);
|
|
135
|
+
let payloadBytes = Content.objectToBytes(payload);
|
|
136
|
+
let payloadString;
|
|
137
|
+
if (protectedHeader.zip === 'DEF') {
|
|
138
|
+
payloadBytes = pako.deflate(payloadBytes);
|
|
139
|
+
payloadString = Content.bytesToRawBase64UrlSafe(payloadBytes);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
payloadString = Content.bytesToStringASCII(payloadBytes);
|
|
143
|
+
}
|
|
144
|
+
const encrypted = await this.encrypt(payloadString, derivedCekBytes, protectedHeaderB64Url);
|
|
145
|
+
// 3. Assemble the JWE. The `encrypted_key` is the result of the KEM encapsulation.
|
|
146
|
+
const recipientData = [{
|
|
147
|
+
header: { alg: recipient.crv, kid: recipient.kid },
|
|
148
|
+
encrypted_key: Content.bytesToRawBase64UrlSafe(encapsulatedCekBytes),
|
|
149
|
+
}];
|
|
150
|
+
return {
|
|
151
|
+
protected: protectedHeaderB64Url,
|
|
152
|
+
recipients: recipientData,
|
|
153
|
+
iv: encrypted.iv,
|
|
154
|
+
ciphertext: encrypted.ciphertext,
|
|
155
|
+
tag: encrypted.tag,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async encryptJweToCompact(payload, protectedHeader, secretJWKey, recipientJWKey) {
|
|
159
|
+
// 1. Construct the complete, final protected header by merging the main and recipient headers.
|
|
160
|
+
const recipientHeader = { alg: recipientJWKey.crv, kid: recipientJWKey.kid };
|
|
161
|
+
const finalProtectedHeader = { ...protectedHeader, ...recipientHeader };
|
|
162
|
+
const protectedHeaderB64Url = Content.objectToRawBase64UrlSafe(finalProtectedHeader);
|
|
163
|
+
// 2. Perform KEM to derive the Content Encryption Key (CEK).
|
|
164
|
+
const publicKeyBytes = Content.base64ToBytes(recipientJWKey.x);
|
|
165
|
+
const cekSeedBytes = await this.cryptoHelper.getRandomBytes(32);
|
|
166
|
+
const { derivedCekBytes, encapsulatedCekBytes } = await this.encapsulate(cekSeedBytes, secretJWKey.dBytes, publicKeyBytes);
|
|
167
|
+
const encapsulatedKeyB64Url = Content.bytesToRawBase64UrlSafe(encapsulatedCekBytes);
|
|
168
|
+
// 3. Encrypt the payload using the derived CEK and the *final* protected header as AAD.
|
|
169
|
+
const payloadBytes = typeof payload === 'string'
|
|
170
|
+
? Content.stringToBytesUTF8(payload)
|
|
171
|
+
: Content.objectToBytes(payload);
|
|
172
|
+
if (finalProtectedHeader.zip === 'DEF') {
|
|
173
|
+
// Note: Compressing a compact JWS string is often inefficient, but supported.
|
|
174
|
+
const compressedPayload = pako.deflate(payloadBytes);
|
|
175
|
+
const payloadString = Content.bytesToRawBase64UrlSafe(compressedPayload);
|
|
176
|
+
const encrypted = await this.encrypt(payloadString, derivedCekBytes, protectedHeaderB64Url);
|
|
177
|
+
return `${protectedHeaderB64Url}.${encapsulatedKeyB64Url}.${encrypted.iv}.${encrypted.ciphertext}.${encrypted.tag}`;
|
|
178
|
+
}
|
|
179
|
+
const payloadString = Content.bytesToStringASCII(payloadBytes);
|
|
180
|
+
const encrypted = await this.encrypt(payloadString, derivedCekBytes, protectedHeaderB64Url);
|
|
181
|
+
// 4. Assemble the 5 parts of the compact JWE.
|
|
182
|
+
return `${protectedHeaderB64Url}.${encapsulatedKeyB64Url}.${encrypted.iv}.${encrypted.ciphertext}.${encrypted.tag}`;
|
|
183
|
+
}
|
|
184
|
+
async decryptJwe(jwe, secretKeyJwk) {
|
|
185
|
+
const jweObject = typeof jwe === 'string' ? this.parseCompactJwe(jwe) : jwe;
|
|
186
|
+
const recipient = jweObject.recipients.find(r => r.header?.kid === secretKeyJwk.kid);
|
|
187
|
+
if (!recipient || !recipient.encrypted_key) {
|
|
188
|
+
throw new Error(`JWE does not contain a recipient with kid=${secretKeyJwk.kid}`);
|
|
189
|
+
}
|
|
190
|
+
// Decapsulate to get the CEK
|
|
191
|
+
const encapsulatedKeyBytes = Content.base64ToBytes(recipient.encrypted_key);
|
|
192
|
+
const cekBytes = await this.decapsulate(encapsulatedKeyBytes, secretKeyJwk.dBytes);
|
|
193
|
+
// Decrypt the payload
|
|
194
|
+
const encryptedData = { ciphertext: jweObject.ciphertext, iv: jweObject.iv, tag: jweObject.tag };
|
|
195
|
+
const decryptedPayloadString = await this.decrypt(encryptedData, cekBytes, jweObject.protected);
|
|
196
|
+
// Handle decompression
|
|
197
|
+
const protectedHeader = Content.base64UrlSafeToJSON(jweObject.protected);
|
|
198
|
+
let decryptedBytes;
|
|
199
|
+
if (protectedHeader.zip === 'DEF') {
|
|
200
|
+
const compressedBytes = Content.base64ToBytes(decryptedPayloadString);
|
|
201
|
+
decryptedBytes = pako.inflate(compressedBytes);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
decryptedBytes = Content.stringToBytesUTF8(decryptedPayloadString);
|
|
205
|
+
}
|
|
206
|
+
return { decryptedBytes, protectedHeader };
|
|
207
|
+
}
|
|
208
|
+
getRecipientKidsFromJwe(jwe) {
|
|
209
|
+
const jweObject = typeof jwe === 'string' ? this.parseCompactJwe(jwe) : jwe;
|
|
210
|
+
if (!jweObject.recipients) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
return jweObject.recipients
|
|
214
|
+
.map(recipient => recipient.header?.kid)
|
|
215
|
+
.filter((kid) => !!kid);
|
|
216
|
+
}
|
|
217
|
+
async signDataJws(payload, protectedHeader, secretKeyBytes) {
|
|
218
|
+
const protectedHeaderB64Url = Content.objectToRawBase64UrlSafe(protectedHeader);
|
|
219
|
+
const payloadB64Url = await jwtUtils.encodePayload(payload);
|
|
220
|
+
const signingInput = `${protectedHeaderB64Url}.${payloadB64Url}`;
|
|
221
|
+
const signingInputBytes = Content.stringToBytesUTF8(signingInput);
|
|
222
|
+
// Infer algorithm from protected header
|
|
223
|
+
const alg = protectedHeader.alg;
|
|
224
|
+
if (!alg)
|
|
225
|
+
throw new Error("Protected header must contain 'alg' property for signing.");
|
|
226
|
+
const signatureBytes = await this.signBytes(signingInputBytes, secretKeyBytes, alg);
|
|
227
|
+
const jwsParts = {
|
|
228
|
+
protected: protectedHeaderB64Url,
|
|
229
|
+
payload: payloadB64Url,
|
|
230
|
+
signature: Content.bytesToRawBase64UrlSafe(signatureBytes),
|
|
231
|
+
};
|
|
232
|
+
return jwsParts;
|
|
233
|
+
}
|
|
234
|
+
async verifyJws(jws, publicJwk) {
|
|
235
|
+
const parts = typeof jws === 'string' ? jwtUtils.getPartsJWT(jws) : jws;
|
|
236
|
+
if (!parts)
|
|
237
|
+
throw new Error('Invalid Compact JWS format');
|
|
238
|
+
const signingInput = `${parts.protected}.${parts.payload}`;
|
|
239
|
+
const signingInputBytes = Content.stringToBytesUTF8(signingInput);
|
|
240
|
+
const signatureBytes = Content.base64ToBytes(parts.signature);
|
|
241
|
+
return this.verifyBytes(signatureBytes, signingInputBytes, publicJwk);
|
|
242
|
+
}
|
|
243
|
+
async verifyDetachedJws(payloadBytes, detachedJws, publicJWKey) {
|
|
244
|
+
const parts = detachedJws.split('..');
|
|
245
|
+
if (parts.length !== 2)
|
|
246
|
+
throw new Error("Invalid Detached JWS format");
|
|
247
|
+
const protectedHeaderB64Url = parts[0];
|
|
248
|
+
const signatureB64Url = parts[1];
|
|
249
|
+
const payloadB64Url = Content.bytesToRawBase64UrlSafe(payloadBytes);
|
|
250
|
+
const signingInput = `${protectedHeaderB64Url}.${payloadB64Url}`;
|
|
251
|
+
const signingInputBytes = Content.stringToBytesUTF8(signingInput);
|
|
252
|
+
const signatureBytes = Content.base64ToBytes(signatureB64Url);
|
|
253
|
+
return this.verifyBytes(signatureBytes, signingInputBytes, publicJWKey);
|
|
254
|
+
}
|
|
255
|
+
// --- Low-Level Primitives ---
|
|
256
|
+
encrypt(plaintext, cekBytes, aad) {
|
|
257
|
+
return this.aesManager.encrypt(plaintext, cekBytes, aad);
|
|
258
|
+
}
|
|
259
|
+
decrypt(encryptedData, cekBytes, aad) {
|
|
260
|
+
return this.aesManager.decrypt(encryptedData, cekBytes, aad);
|
|
261
|
+
}
|
|
262
|
+
async encapsulate(cekSeedBytes, secretKeyBytes, recipientPublicKeyBytes) {
|
|
263
|
+
// According to RFC 9278 (JWE with ML-KEM), a seed is used for the KEM encapsulation.
|
|
264
|
+
// The KEM then derives a shared secret from this seed. It is this *derived* shared secret
|
|
265
|
+
// that is used to encrypt the content, NOT the original seed.
|
|
266
|
+
// The `encapsulate` function from the noble library handles this correctly by accepting the
|
|
267
|
+
// seed as the second argument. It returns both the encapsulated key (`cipherText`)
|
|
268
|
+
// and the derived shared secret, which we must use as the actual AES key.
|
|
269
|
+
const mlKem = await this.loadMlKem();
|
|
270
|
+
const { sharedSecret, cipherText } = await mlKem.ml_kem768.encapsulate(recipientPublicKeyBytes, cekSeedBytes);
|
|
271
|
+
return { derivedCekBytes: sharedSecret, encapsulatedCekBytes: cipherText };
|
|
272
|
+
}
|
|
273
|
+
async decapsulate(encapsulatedBytes, secretKeyBytes) {
|
|
274
|
+
const mlKem = await this.loadMlKem();
|
|
275
|
+
return mlKem.ml_kem768.decapsulate(encapsulatedBytes, secretKeyBytes);
|
|
276
|
+
}
|
|
277
|
+
async signBytes(payloadBytes, secretKeyBytes, alg) {
|
|
278
|
+
const mlDsa = await this.loadMlDsa();
|
|
279
|
+
switch (alg) {
|
|
280
|
+
case 'ML-DSA-44': return mlDsa.ml_dsa44.sign(payloadBytes, secretKeyBytes);
|
|
281
|
+
case 'ML-DSA-65': return mlDsa.ml_dsa65.sign(payloadBytes, secretKeyBytes);
|
|
282
|
+
case 'ML-DSA-87': return mlDsa.ml_dsa87.sign(payloadBytes, secretKeyBytes);
|
|
283
|
+
default: throw new Error(`Unsupported ML-DSA algorithm: ${alg}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async verifyBytes(signatureBytes, dataBytes, publicKey) {
|
|
287
|
+
const mlDsa = await this.loadMlDsa();
|
|
288
|
+
const publicKeyBytes = Content.base64ToBytes(publicKey.pub || publicKey.x);
|
|
289
|
+
const alg = publicKey.alg;
|
|
290
|
+
if (!alg)
|
|
291
|
+
throw new Error("Public key must contain 'alg' property for verification.");
|
|
292
|
+
switch (alg) {
|
|
293
|
+
case 'ML-DSA-44': return mlDsa.ml_dsa44.verify(signatureBytes, dataBytes, publicKeyBytes);
|
|
294
|
+
case 'ML-DSA-65': return mlDsa.ml_dsa65.verify(signatureBytes, dataBytes, publicKeyBytes);
|
|
295
|
+
case 'ML-DSA-87': return mlDsa.ml_dsa87.verify(signatureBytes, dataBytes, publicKeyBytes);
|
|
296
|
+
default: throw new Error(`Unsupported ML-DSA algorithm: ${alg}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// --- Formatting & Parsing Utilities ---
|
|
300
|
+
jwsToCompact(jws) {
|
|
301
|
+
return `${jws.protected}.${jws.payload}.${jws.signature}`;
|
|
302
|
+
}
|
|
303
|
+
parseCompactJws(jwsString) {
|
|
304
|
+
if (jwsString.trim().startsWith('{')) {
|
|
305
|
+
const parsed = JSON.parse(jwsString);
|
|
306
|
+
if (!parsed.payload || !parsed.signatures || !parsed.signatures[0]) {
|
|
307
|
+
throw new Error("Invalid JWS JSON format");
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
payload: parsed.payload,
|
|
311
|
+
protected: parsed.signatures[0].protected,
|
|
312
|
+
signature: parsed.signatures[0].signature,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const parts = jwtUtils.getPartsJWT(jwsString);
|
|
316
|
+
if (!parts)
|
|
317
|
+
throw new Error("Invalid Compact JWS format");
|
|
318
|
+
const result = {
|
|
319
|
+
payload: Content.base64UrlSafeToJSON(parts.payload),
|
|
320
|
+
protected: Content.base64UrlSafeToJSON(parts.protected),
|
|
321
|
+
signature: Content.base64ToBytes(parts.signature),
|
|
322
|
+
};
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
parseCompactJwe(jweString) {
|
|
326
|
+
if (jweString.trim().startsWith('{')) {
|
|
327
|
+
return JSON.parse(jweString);
|
|
328
|
+
}
|
|
329
|
+
const parts = jweString.split('.');
|
|
330
|
+
if (parts.length !== 5)
|
|
331
|
+
throw new Error("Invalid Compact JWE format");
|
|
332
|
+
const protectedHeader = Content.base64UrlSafeToJSON(parts[0]);
|
|
333
|
+
// Compact JWE has no per-recipient header, but our model requires one.
|
|
334
|
+
// The 'kid' should be in the main protected header for decryption to work.
|
|
335
|
+
return {
|
|
336
|
+
protected: parts[0],
|
|
337
|
+
recipients: [{
|
|
338
|
+
header: { alg: protectedHeader.alg || '', kid: protectedHeader.kid || '' },
|
|
339
|
+
encrypted_key: parts[1]
|
|
340
|
+
}],
|
|
341
|
+
iv: parts[2],
|
|
342
|
+
ciphertext: parts[3],
|
|
343
|
+
tag: parts[4],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
// --- JWK Thumbprint Calculation (RFC 7638) ---
|
|
347
|
+
/**
|
|
348
|
+
* Computes a JWK thumbprint using a specified hash algorithm.
|
|
349
|
+
* This implementation is platform-agnostic by using the injected ICryptoHelper.
|
|
350
|
+
*/
|
|
351
|
+
async _computeJwkThumbprint(jwk, hash = "SHA-256") {
|
|
352
|
+
const baseJwk = this._toBaseJwk(jwk);
|
|
353
|
+
const canonical = this._canonicalizeForJwkThumbprint(baseJwk);
|
|
354
|
+
// Use the platform-agnostic digest method
|
|
355
|
+
const digestHex = await this.cryptoHelper.digestString(canonical, hash);
|
|
356
|
+
// The digestString returns a hex string, but thumbprints are Base64UrlSafe.
|
|
357
|
+
// We need to convert from hex to bytes, then bytes to Base64UrlSafe.
|
|
358
|
+
const digestBytes = this._hexToBytes(digestHex);
|
|
359
|
+
return Content.bytesToRawBase64UrlSafe(digestBytes);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Creates a canonical string from a simple, flat JSON object as required by
|
|
363
|
+
* RFC 7638 for JWK thumbprints.
|
|
364
|
+
*/
|
|
365
|
+
_canonicalizeForJwkThumbprint(obj) {
|
|
366
|
+
const keys = Object.keys(obj).sort();
|
|
367
|
+
const parts = keys.map(k => `"${k}":${JSON.stringify(obj[k])}`);
|
|
368
|
+
return `{${parts.join(",")}}`;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Extracts the Base JWK for thumbprint calculation per RFC 7638.
|
|
372
|
+
* This handles both Post-Quantum (OKP, AKP) and legacy (EC) key types.
|
|
373
|
+
*/
|
|
374
|
+
_toBaseJwk(jwk) {
|
|
375
|
+
if (jwk.kty === "OKP") {
|
|
376
|
+
const { crv, x } = jwk;
|
|
377
|
+
return { kty: "OKP", crv, x };
|
|
378
|
+
}
|
|
379
|
+
else if (jwk.kty === "AKP") {
|
|
380
|
+
const { alg, pub } = jwk;
|
|
381
|
+
return { kty: "AKP", alg, pub };
|
|
382
|
+
}
|
|
383
|
+
else if (jwk.kty === "EC") {
|
|
384
|
+
const { crv, x, y } = jwk;
|
|
385
|
+
const baseJwk = { kty: "EC", crv, x, y };
|
|
386
|
+
return baseJwk;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
const exhaustiveCheck = jwk;
|
|
390
|
+
throw new Error(`Unsupported key type for JWK thumbprint: ${exhaustiveCheck.kty}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Utility to convert a hex string to a Uint8Array.
|
|
395
|
+
*/
|
|
396
|
+
_hexToBytes(hex) {
|
|
397
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
398
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
399
|
+
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
|
400
|
+
}
|
|
401
|
+
return bytes;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines the types of form requests that can be sent in a batch.
|
|
3
|
+
*/
|
|
4
|
+
export declare const FormRequestType: {
|
|
5
|
+
readonly IndividualTerms: "IndividualTerms";
|
|
6
|
+
readonly PersonalIdentity: "PersonalIdentity";
|
|
7
|
+
};
|
|
8
|
+
export declare const Sector: {
|
|
9
|
+
readonly EMERGENCY: "emergency";
|
|
10
|
+
readonly HEALTH_CARE: "health-care";
|
|
11
|
+
readonly HEALTH_INSURANCE: "health-insurance";
|
|
12
|
+
readonly RESEARCH: "research";
|
|
13
|
+
};
|
|
14
|
+
export type Sector = typeof Sector[keyof typeof Sector];
|
|
15
|
+
export declare const Section: {
|
|
16
|
+
readonly REGISTRY: "registry";
|
|
17
|
+
readonly ENTITY: "entity";
|
|
18
|
+
readonly INDIVIDUAL: "individual";
|
|
19
|
+
readonly NETWORK: "network";
|
|
20
|
+
};
|
|
21
|
+
export type Section = typeof Section[keyof typeof Section];
|
|
22
|
+
export declare const Format: {
|
|
23
|
+
readonly SCHEMA: "org.schema";
|
|
24
|
+
readonly FHIR_API: "org.hl7.fhir.api";
|
|
25
|
+
};
|
|
26
|
+
export type Format = typeof Format[keyof typeof Format];
|
|
27
|
+
export declare const Resource: {
|
|
28
|
+
readonly PERSON: "Person";
|
|
29
|
+
readonly RELATED_PERSON: "RelatedPerson";
|
|
30
|
+
readonly EMPLOYEE: "Employee";
|
|
31
|
+
readonly EMPLOYEE_ROLE: "EmployeeRole";
|
|
32
|
+
readonly PRACTITIONER: "Practitioner";
|
|
33
|
+
readonly PRACTITIONER_ROLE: "PractitionerRole";
|
|
34
|
+
readonly ORGANIZATION: "Organization";
|
|
35
|
+
readonly LOCATION: "Location";
|
|
36
|
+
readonly GROUP: "Group";
|
|
37
|
+
};
|
|
38
|
+
export type Resource = typeof Resource[keyof typeof Resource];
|
|
39
|
+
export declare const JobAction: {
|
|
40
|
+
readonly BATCH: "_batch";
|
|
41
|
+
readonly CREATE: "_create";
|
|
42
|
+
readonly DISCOVERY: "_discovery";
|
|
43
|
+
};
|
|
44
|
+
export type JobAction = typeof JobAction[keyof typeof JobAction];
|
|
45
|
+
export declare const knownDomainsReversed: readonly ["org.schema", "org.hl7.fhir", "org.ilo.isco", "net.openid"];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// constants/Schemas.ts
|
|
2
|
+
// Based on the backend's src/models/schemaorg.ts
|
|
3
|
+
/**
|
|
4
|
+
* Defines the types of form requests that can be sent in a batch.
|
|
5
|
+
*/
|
|
6
|
+
export const FormRequestType = {
|
|
7
|
+
IndividualTerms: 'IndividualTerms',
|
|
8
|
+
PersonalIdentity: 'PersonalIdentity',
|
|
9
|
+
};
|
|
10
|
+
// --- Enums for URL construction and validation ---
|
|
11
|
+
export const Sector = {
|
|
12
|
+
EMERGENCY: 'emergency',
|
|
13
|
+
HEALTH_CARE: 'health-care',
|
|
14
|
+
HEALTH_INSURANCE: 'health-insurance',
|
|
15
|
+
RESEARCH: 'research',
|
|
16
|
+
};
|
|
17
|
+
export const Section = {
|
|
18
|
+
REGISTRY: 'registry',
|
|
19
|
+
ENTITY: 'entity',
|
|
20
|
+
INDIVIDUAL: 'individual',
|
|
21
|
+
NETWORK: 'network',
|
|
22
|
+
};
|
|
23
|
+
export const Format = {
|
|
24
|
+
SCHEMA: 'org.schema',
|
|
25
|
+
FHIR_API: 'org.hl7.fhir.api',
|
|
26
|
+
};
|
|
27
|
+
export const Resource = {
|
|
28
|
+
PERSON: 'Person',
|
|
29
|
+
RELATED_PERSON: 'RelatedPerson',
|
|
30
|
+
EMPLOYEE: 'Employee',
|
|
31
|
+
EMPLOYEE_ROLE: 'EmployeeRole',
|
|
32
|
+
PRACTITIONER: 'Practitioner',
|
|
33
|
+
PRACTITIONER_ROLE: 'PractitionerRole',
|
|
34
|
+
ORGANIZATION: 'Organization',
|
|
35
|
+
LOCATION: 'Location',
|
|
36
|
+
GROUP: 'Group',
|
|
37
|
+
};
|
|
38
|
+
export const JobAction = {
|
|
39
|
+
BATCH: '_batch',
|
|
40
|
+
CREATE: '_create',
|
|
41
|
+
DISCOVERY: '_discovery',
|
|
42
|
+
};
|
|
43
|
+
export const knownDomainsReversed = [
|
|
44
|
+
'org.schema',
|
|
45
|
+
'org.hl7.fhir',
|
|
46
|
+
'org.ilo.isco',
|
|
47
|
+
'net.openid',
|
|
48
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './schemaorg.js';
|