@sphereon/ssi-sdk-ext.key-utils
Advanced tools
Comparing version 0.26.1-feature.SPRIND.116.9 to 0.26.1-feature.SPRIND.116.44
@@ -25,9 +25,10 @@ "use strict"; | ||
const ssi_types_1 = require("@sphereon/ssi-types"); | ||
const functions_1 = require("./functions"); | ||
function coseKeyToJwk(coseKey) { | ||
const { x5chain, key_ops, crv, alg, baseIV, kty } = coseKey, rest = __rest(coseKey, ["x5chain", "key_ops", "crv", "alg", "baseIV", "kty"]); | ||
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, rest), { kty: coseToJoseKty(kty) }), (crv && { crv: coseToJoseCurve(crv) })), (key_ops && { key_ops: key_ops.map(coseToJoseKeyOperation) })), (alg && { alg: coseToJoseSignatureAlg(alg) })), (baseIV && { iv: baseIV })), (x5chain && { x5c: x5chain })); | ||
return (0, functions_1.removeNulls)(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, rest), { kty: coseToJoseKty(kty) }), (crv && { crv: coseToJoseCurve(crv) })), (key_ops && { key_ops: key_ops.map(coseToJoseKeyOperation) })), (alg && { alg: coseToJoseSignatureAlg(alg) })), (baseIV && { iv: baseIV })), (x5chain && { x5c: x5chain }))); | ||
} | ||
function jwkToCoseKey(jwk) { | ||
const { x5c, key_ops, crv, alg, iv, kty } = jwk, rest = __rest(jwk, ["x5c", "key_ops", "crv", "alg", "iv", "kty"]); | ||
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, rest), { kty: joseToCoseKty(kty) }), (crv && { crv: joseToCoseCurve(crv) })), (key_ops && { key_ops: key_ops.map(joseToCoseKeyOperation) })), (alg && { alg: joseToCoseSignatureAlg(alg) })), (iv && { baseIV: iv })), (x5c && { x5chain: x5c })); | ||
return (0, functions_1.removeNulls)(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, rest), { kty: joseToCoseKty(kty) }), (crv && { crv: joseToCoseCurve(crv) })), (key_ops && { key_ops: key_ops.map(joseToCoseKeyOperation) })), (alg && { alg: joseToCoseSignatureAlg(alg) })), (iv && { baseIV: iv })), (x5c && { x5chain: x5c }))); | ||
} | ||
@@ -34,0 +35,0 @@ function coseToJoseKty(kty) { |
@@ -0,3 +1,4 @@ | ||
import { Hasher } from '@sphereon/ssi-types'; | ||
import { SupportedEncodings } from 'uint8arrays/to-string'; | ||
export type HashAlgorithm = 'SHA-256' | 'SHA-512'; | ||
export type HashAlgorithm = 'SHA-256' | 'SHA-384' | 'SHA-512'; | ||
export type TDigestMethod = (input: string, encoding?: SupportedEncodings) => string; | ||
@@ -9,2 +10,3 @@ export declare const digestMethodParams: (hashAlgorithm: HashAlgorithm) => { | ||
}; | ||
export declare const shaHasher: Hasher; | ||
//# sourceMappingURL=digest-methods.d.ts.map |
@@ -26,20 +26,31 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.digestMethodParams = void 0; | ||
const sha256_1 = require("@stablelib/sha256"); | ||
const sha512_1 = require("@stablelib/sha512"); | ||
exports.shaHasher = exports.digestMethodParams = void 0; | ||
const sha256_1 = require("@noble/hashes/sha256"); | ||
const sha512_1 = require("@noble/hashes/sha512"); | ||
const u8a = __importStar(require("uint8arrays")); | ||
const digestMethodParams = (hashAlgorithm) => { | ||
if (hashAlgorithm === 'SHA-256') { | ||
return { hashAlgorithm: 'SHA-256', digestMethod: sha256DigestMethod, hash: sha256_1.hash }; | ||
return { hashAlgorithm: 'SHA-256', digestMethod: sha256DigestMethod, hash: sha256_1.sha256 }; | ||
} | ||
else if (hashAlgorithm === 'SHA-384') { | ||
return { hashAlgorithm: 'SHA-384', digestMethod: sha384DigestMethod, hash: sha512_1.sha384 }; | ||
} | ||
else { | ||
return { hashAlgorithm: 'SHA-512', digestMethod: sha512DigestMethod, hash: sha512_1.hash }; | ||
return { hashAlgorithm: 'SHA-512', digestMethod: sha512DigestMethod, hash: sha512_1.sha512 }; | ||
} | ||
}; | ||
exports.digestMethodParams = digestMethodParams; | ||
const shaHasher = (input, alg) => { | ||
const hashAlgorithm = alg.includes('384') ? 'SHA-384' : alg.includes('512') ? 'SHA-512' : 'SHA-256'; | ||
return (0, exports.digestMethodParams)(hashAlgorithm).hash(u8a.fromString(input, 'utf-8')); | ||
}; | ||
exports.shaHasher = shaHasher; | ||
const sha256DigestMethod = (input, encoding = 'base16') => { | ||
return u8a.toString((0, sha256_1.hash)(u8a.fromString(input, 'utf-8')), encoding); | ||
return u8a.toString((0, sha256_1.sha256)(u8a.fromString(input, 'utf-8')), encoding); | ||
}; | ||
const sha384DigestMethod = (input, encoding = 'base16') => { | ||
return u8a.toString((0, sha512_1.sha384)(u8a.fromString(input, 'utf-8')), encoding); | ||
}; | ||
const sha512DigestMethod = (input, encoding = 'base16') => { | ||
return u8a.toString((0, sha512_1.hash)(u8a.fromString(input, 'utf-8')), encoding); | ||
return u8a.toString((0, sha512_1.sha512)(u8a.fromString(input, 'utf-8')), encoding); | ||
}; | ||
@@ -46,0 +57,0 @@ /* |
@@ -60,2 +60,9 @@ import { JoseSignatureAlgorithm, JWK } from '@sphereon/ssi-types'; | ||
/** | ||
* Convert a JWK to a raw hex key. | ||
* Currently supports `RSA` and `EC` keys. Extendable for other key types. | ||
* @param jwk - The JSON Web Key object. | ||
* @returns A string representing the key in raw hexadecimal format. | ||
*/ | ||
export declare const jwkToRawHexKey: (jwk: JWK) => Promise<string>; | ||
/** | ||
* Determines the use param based upon the key/signature type or supplied use value. | ||
@@ -80,8 +87,16 @@ * | ||
export declare const keyTypeFromCryptographicSuite: (args: KeyTypeFromCryptographicSuiteArgs) => TKeyType; | ||
export declare function verifySignatureWithSubtle({ data, signature, key, crypto: cryptoArg, }: { | ||
export declare function removeNulls<T>(obj: T | any): any; | ||
export declare const globalCrypto: (setGlobal: boolean, suppliedCrypto?: Crypto) => Crypto; | ||
export declare const sanitizedJwk: (input: JWK | JsonWebKey) => JWK; | ||
/** | ||
* | ||
*/ | ||
export declare function verifyRawSignature({ data, signature, key: inputKey, opts, }: { | ||
data: Uint8Array; | ||
signature: Uint8Array; | ||
key: JsonWebKey; | ||
crypto?: Crypto; | ||
key: JWK; | ||
opts?: { | ||
signatureAlg?: JoseSignatureAlgorithm; | ||
}; | ||
}): Promise<boolean>; | ||
//# sourceMappingURL=functions.d.ts.map |
@@ -38,12 +38,25 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.keyTypeFromCryptographicSuite = exports.signatureAlgorithmFromKeyType = exports.signatureAlgorithmFromKey = exports.hexStringFromUint8Array = exports.toRawCompressedHexPublicKey = exports.isRawCompressedPublicKey = exports.asn1DerToRawPublicKey = exports.isAsn1Der = exports.padLeft = exports.jwkDetermineUse = exports.toJwk = exports.toJwkFromKey = exports.calculateJwkThumbprint = exports.toBase64url = exports.calculateJwkThumbprintForKey = exports.generatePrivateKeyHex = exports.getKms = exports.logger = void 0; | ||
exports.sanitizedJwk = exports.globalCrypto = exports.keyTypeFromCryptographicSuite = exports.signatureAlgorithmFromKeyType = exports.signatureAlgorithmFromKey = exports.hexStringFromUint8Array = exports.toRawCompressedHexPublicKey = exports.isRawCompressedPublicKey = exports.asn1DerToRawPublicKey = exports.isAsn1Der = exports.padLeft = exports.jwkDetermineUse = exports.jwkToRawHexKey = exports.toJwk = exports.toJwkFromKey = exports.calculateJwkThumbprint = exports.toBase64url = exports.calculateJwkThumbprintForKey = exports.generatePrivateKeyHex = exports.getKms = exports.logger = void 0; | ||
exports.importProvidedOrGeneratedKey = importProvidedOrGeneratedKey; | ||
exports.verifySignatureWithSubtle = verifySignatureWithSubtle; | ||
exports.removeNulls = removeNulls; | ||
exports.verifyRawSignature = verifyRawSignature; | ||
const random_1 = require("@ethersproject/random"); | ||
// Do not change these require statements to imports before we change to ESM. Breaks external CJS packages depending on this module | ||
const bls12_381_1 = require("@noble/curves/bls12-381"); | ||
const ed25519_1 = require("@noble/curves/ed25519"); | ||
const p256_1 = require("@noble/curves/p256"); | ||
const p384_1 = require("@noble/curves/p384"); | ||
const p521_1 = require("@noble/curves/p521"); | ||
const secp256k1_1 = require("@noble/curves/secp256k1"); | ||
const sha256_1 = require("@noble/hashes/sha256"); | ||
const sha512_1 = require("@noble/hashes/sha512"); | ||
const ssi_sdk_ext_x509_utils_1 = require("@sphereon/ssi-sdk-ext.x509-utils"); | ||
const ssi_types_1 = require("@sphereon/ssi-types"); | ||
const ed25519_1 = require("@stablelib/ed25519"); | ||
const ed25519_2 = require("@stablelib/ed25519"); | ||
const debug_1 = __importDefault(require("debug")); | ||
const elliptic_1 = __importDefault(require("elliptic")); | ||
const rsa = __importStar(require("micro-rsa-dsa-dh/rsa.js")); | ||
const u8a = __importStar(require("uint8arrays")); | ||
const digest_methods_1 = require("./digest-methods"); | ||
const jwk_jcs_1 = require("./jwk-jcs"); | ||
const types_1 = require("./types"); | ||
@@ -75,3 +88,3 @@ exports.logger = ssi_types_1.Loggers.DEFAULT.get('sphereon:key-utils'); | ||
case 'Ed25519': { | ||
const keyPairEd25519 = (0, ed25519_1.generateKeyPair)(); | ||
const keyPairEd25519 = (0, ed25519_2.generateKeyPair)(); | ||
return u8a.toString(keyPairEd25519.secretKey, 'base16'); | ||
@@ -173,3 +186,4 @@ } | ||
const calculateJwkThumbprint = (args) => { | ||
const { jwk, digestAlgorithm = 'sha256' } = args; | ||
const { digestAlgorithm = 'sha256' } = args; | ||
const jwk = (0, exports.sanitizedJwk)(args.jwk); | ||
let components; | ||
@@ -246,6 +260,90 @@ switch (jwk.kty) { | ||
} | ||
return jwk; | ||
return (0, exports.sanitizedJwk)(jwk); | ||
}; | ||
exports.toJwk = toJwk; | ||
/** | ||
* Convert a JWK to a raw hex key. | ||
* Currently supports `RSA` and `EC` keys. Extendable for other key types. | ||
* @param jwk - The JSON Web Key object. | ||
* @returns A string representing the key in raw hexadecimal format. | ||
*/ | ||
const jwkToRawHexKey = (jwk) => __awaiter(void 0, void 0, void 0, function* () { | ||
// TODO: Probably makes sense to have an option to do the same for private keys | ||
jwk = (0, exports.sanitizedJwk)(jwk); | ||
if (jwk.kty === 'RSA') { | ||
return rsaJwkToRawHexKey(jwk); | ||
} | ||
else if (jwk.kty === 'EC') { | ||
return ecJwkToRawHexKey(jwk); | ||
} | ||
else if (jwk.kty === 'OKP') { | ||
return okpJwkToRawHexKey(jwk); | ||
} | ||
else if (jwk.kty === 'oct') { | ||
return octJwkToRawHexKey(jwk); | ||
} | ||
else { | ||
throw new Error(`Unsupported key type: ${jwk.kty}`); | ||
} | ||
}); | ||
exports.jwkToRawHexKey = jwkToRawHexKey; | ||
/** | ||
* Convert an RSA JWK to a raw hex key. | ||
* @param jwk - The RSA JWK object. | ||
* @returns A string representing the RSA key in raw hexadecimal format. | ||
*/ | ||
function rsaJwkToRawHexKey(jwk) { | ||
jwk = (0, exports.sanitizedJwk)(jwk); | ||
if (!jwk.n || !jwk.e) { | ||
throw new Error("RSA JWK must contain 'n' and 'e' properties."); | ||
} | ||
// We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string | ||
const modulus = u8a.fromString(jwk.n.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url'); // 'n' is the modulus | ||
const exponent = u8a.fromString(jwk.e.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url'); // 'e' is the exponent | ||
return u8a.toString(modulus, 'hex') + u8a.toString(exponent, 'hex'); | ||
} | ||
/** | ||
* Convert an EC JWK to a raw hex key. | ||
* @param jwk - The EC JWK object. | ||
* @returns A string representing the EC key in raw hexadecimal format. | ||
*/ | ||
function ecJwkToRawHexKey(jwk) { | ||
jwk = (0, exports.sanitizedJwk)(jwk); | ||
if (!jwk.x || !jwk.y) { | ||
throw new Error("EC JWK must contain 'x' and 'y' properties."); | ||
} | ||
// We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string | ||
const x = u8a.fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url'); | ||
const y = u8a.fromString(jwk.y.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url'); | ||
return '04' + u8a.toString(x, 'hex') + u8a.toString(y, 'hex'); | ||
} | ||
/** | ||
* Convert an EC JWK to a raw hex key. | ||
* @param jwk - The EC JWK object. | ||
* @returns A string representing the EC key in raw hexadecimal format. | ||
*/ | ||
function okpJwkToRawHexKey(jwk) { | ||
jwk = (0, exports.sanitizedJwk)(jwk); | ||
if (!jwk.x) { | ||
throw new Error("OKP JWK must contain 'x' property."); | ||
} | ||
// We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string | ||
const x = u8a.fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url'); | ||
return u8a.toString(x, 'hex'); | ||
} | ||
/** | ||
* Convert an octet JWK to a raw hex key. | ||
* @param jwk - The octet JWK object. | ||
* @returns A string representing the octet key in raw hexadecimal format. | ||
*/ | ||
function octJwkToRawHexKey(jwk) { | ||
jwk = (0, exports.sanitizedJwk)(jwk); | ||
if (!jwk.k) { | ||
throw new Error("Octet JWK must contain 'k' property."); | ||
} | ||
// We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string | ||
const key = u8a.fromString(jwk.k.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url'); | ||
return u8a.toString(key, 'hex'); | ||
} | ||
/** | ||
* Determines the use param based upon the key/signature type or supplied use value. | ||
@@ -301,3 +399,3 @@ * | ||
const pubPoint = keyPair.getPublic(); | ||
return Object.assign(Object.assign(Object.assign({ alg: ssi_types_1.JoseSignatureAlgorithm.ES256K }, (use !== undefined && { use })), { kty: ssi_types_1.JwkKeyType.EC, crv: ssi_types_1.JoseCurve.secp256k1, x: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(pubPoint.getX().toString('hex'), 'base64url'), y: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(pubPoint.getY().toString('hex'), 'base64url') }), ((opts === null || opts === void 0 ? void 0 : opts.isPrivateKey) && { d: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(keyPair.getPrivate('hex'), 'base64url') })); | ||
return (0, exports.sanitizedJwk)(Object.assign(Object.assign(Object.assign({ alg: ssi_types_1.JoseSignatureAlgorithm.ES256K }, (use !== undefined && { use })), { kty: ssi_types_1.JwkKeyType.EC, crv: ssi_types_1.JoseCurve.secp256k1, x: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(pubPoint.getX().toString('hex'), 'base64url'), y: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(pubPoint.getY().toString('hex'), 'base64url') }), ((opts === null || opts === void 0 ? void 0 : opts.isPrivateKey) && { d: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(keyPair.getPrivate('hex'), 'base64url') }))); | ||
}; | ||
@@ -324,3 +422,3 @@ /** | ||
const pubPoint = keyPair.getPublic(); | ||
return Object.assign(Object.assign(Object.assign({ alg: ssi_types_1.JoseSignatureAlgorithm.ES256 }, (use !== undefined && { use })), { kty: ssi_types_1.JwkKeyType.EC, crv: ssi_types_1.JoseCurve.P_256, x: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(pubPoint.getX().toString('hex'), 'base64url'), y: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(pubPoint.getY().toString('hex'), 'base64url') }), ((opts === null || opts === void 0 ? void 0 : opts.isPrivateKey) && { d: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(keyPair.getPrivate('hex'), 'base64url') })); | ||
return (0, exports.sanitizedJwk)(Object.assign(Object.assign(Object.assign({ alg: ssi_types_1.JoseSignatureAlgorithm.ES256 }, (use !== undefined && { use })), { kty: ssi_types_1.JwkKeyType.EC, crv: ssi_types_1.JoseCurve.P_256, x: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(pubPoint.getX().toString('hex'), 'base64url'), y: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(pubPoint.getY().toString('hex'), 'base64url') }), ((opts === null || opts === void 0 ? void 0 : opts.isPrivateKey) && { d: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(keyPair.getPrivate('hex'), 'base64url') }))); | ||
}; | ||
@@ -337,14 +435,27 @@ /** | ||
const { use } = opts !== null && opts !== void 0 ? opts : {}; | ||
return Object.assign(Object.assign({ alg: ssi_types_1.JoseSignatureAlgorithm.EdDSA }, (use !== undefined && { use })), { kty: ssi_types_1.JwkKeyType.OKP, crv: (_a = opts === null || opts === void 0 ? void 0 : opts.crv) !== null && _a !== void 0 ? _a : ssi_types_1.JoseCurve.Ed25519, x: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(publicKeyHex, 'base64url') }); | ||
return (0, exports.sanitizedJwk)(Object.assign(Object.assign({ alg: ssi_types_1.JoseSignatureAlgorithm.EdDSA }, (use !== undefined && { use })), { kty: ssi_types_1.JwkKeyType.OKP, crv: (_a = opts === null || opts === void 0 ? void 0 : opts.crv) !== null && _a !== void 0 ? _a : ssi_types_1.JoseCurve.Ed25519, x: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(publicKeyHex, 'base64url') })); | ||
}; | ||
const toRSAJwk = (publicKeyHex, opts) => { | ||
var _a, _b, _c; | ||
const { key } = opts !== null && opts !== void 0 ? opts : {}; | ||
var _a, _b; | ||
const meta = (_a = opts === null || opts === void 0 ? void 0 : opts.key) === null || _a === void 0 ? void 0 : _a.meta; | ||
if ((meta === null || meta === void 0 ? void 0 : meta.publicKeyJwk) || (meta === null || meta === void 0 ? void 0 : meta.publicKeyPEM)) { | ||
if (meta === null || meta === void 0 ? void 0 : meta.publicKeyJwk) { | ||
return meta.publicKeyJwk; | ||
} | ||
const publicKeyPEM = (_b = meta === null || meta === void 0 ? void 0 : meta.publicKeyPEM) !== null && _b !== void 0 ? _b : (0, ssi_sdk_ext_x509_utils_1.hexToPEM)(publicKeyHex, 'public'); | ||
return (0, ssi_sdk_ext_x509_utils_1.PEMToJwk)(publicKeyPEM, 'public'); | ||
} | ||
// exponent (e) is 5 chars long, rest is modulus (n) | ||
// const publicKey = publicKeyHex | ||
// assertProperKeyLength(publicKey, [2048, 3072, 4096]) | ||
if ((_a = key === null || key === void 0 ? void 0 : key.meta) === null || _a === void 0 ? void 0 : _a.publicKeyJwk) { | ||
return key.meta.publicKeyJwk; | ||
} | ||
const publicKeyPEM = (_c = (_b = key === null || key === void 0 ? void 0 : key.meta) === null || _b === void 0 ? void 0 : _b.publicKeyPEM) !== null && _c !== void 0 ? _c : (0, ssi_sdk_ext_x509_utils_1.hexToPEM)(publicKeyHex, 'public'); | ||
return (0, ssi_sdk_ext_x509_utils_1.PEMToJwk)(publicKeyPEM, 'public'); | ||
const exponent = publicKeyHex.slice(-5); | ||
const modulus = publicKeyHex.slice(0, -5); | ||
// const modulusBitLength = (modulus.length / 2) * 8 | ||
// const alg = modulusBitLength === 2048 ? JoseSignatureAlgorithm.RS256 : modulusBitLength === 3072 ? JoseSignatureAlgorithm.RS384 : modulusBitLength === 4096 ? JoseSignatureAlgorithm.RS512 : undefined | ||
return (0, exports.sanitizedJwk)({ | ||
kty: 'RSA', | ||
n: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(modulus, 'base64url'), | ||
e: (0, ssi_sdk_ext_x509_utils_1.hexToBase64)(exponent, 'base64url'), | ||
// ...(alg && { alg }), | ||
}); | ||
}; | ||
@@ -477,2 +588,6 @@ const padLeft = (args) => { | ||
return ssi_types_1.JoseSignatureAlgorithm.ES256; | ||
case 'Secp384r1': | ||
return ssi_types_1.JoseSignatureAlgorithm.ES384; | ||
case 'Secp521r1': | ||
return ssi_types_1.JoseSignatureAlgorithm.ES512; | ||
case 'Secp256k1': | ||
@@ -487,5 +602,16 @@ return ssi_types_1.JoseSignatureAlgorithm.ES256K; | ||
const keyTypeFromCryptographicSuite = (args) => { | ||
const { suite } = args; | ||
switch (suite) { | ||
const { crv, kty, alg } = args; | ||
switch (alg) { | ||
case 'RSASSA-PSS': | ||
case 'RS256': | ||
case 'RS384': | ||
case 'RS512': | ||
case 'PS256': | ||
case 'PS384': | ||
case 'PS512': | ||
return 'RSA'; | ||
} | ||
switch (crv) { | ||
case 'EdDSA': | ||
case 'Ed25519': | ||
case 'Ed25519Signature2018': | ||
@@ -498,29 +624,143 @@ case 'Ed25519Signature2020': | ||
case 'ECDSA': | ||
case 'P-256': | ||
return 'Secp256r1'; | ||
case 'ES384': | ||
case 'P-384': | ||
return 'Secp384r1'; | ||
case 'ES512': | ||
case 'P-521': | ||
return 'Secp521r1'; | ||
case 'EcdsaSecp256k1Signature2019': | ||
case 'secp256k1': | ||
case 'ES256K': | ||
return 'Secp256k1'; | ||
default: | ||
throw new Error(`Cryptographic suite '${suite}' not supported`); | ||
} | ||
if (kty) { | ||
return kty; | ||
} | ||
throw new Error(`Cryptographic suite '${crv}' not supported`); | ||
}; | ||
exports.keyTypeFromCryptographicSuite = keyTypeFromCryptographicSuite; | ||
function verifySignatureWithSubtle(_a) { | ||
return __awaiter(this, arguments, void 0, function* ({ data, signature, key, crypto: cryptoArg, }) { | ||
var _b; | ||
let { alg, crv } = key; | ||
if (alg === 'ES256' || !alg) { | ||
alg = 'ECDSA'; | ||
function removeNulls(obj) { | ||
Object.keys(obj).forEach((key) => { | ||
if (obj[key] && typeof obj[key] === 'object') | ||
removeNulls(obj[key]); | ||
else if (obj[key] == null) | ||
delete obj[key]; | ||
}); | ||
return obj; | ||
} | ||
const globalCrypto = (setGlobal, suppliedCrypto) => { | ||
var _a, _b; | ||
let webcrypto; | ||
if (typeof suppliedCrypto !== 'undefined') { | ||
webcrypto = suppliedCrypto; | ||
} | ||
else if (typeof crypto !== 'undefined') { | ||
webcrypto = crypto; | ||
} | ||
else if (typeof global.crypto !== 'undefined') { | ||
webcrypto = global.crypto; | ||
} | ||
else if (typeof ((_b = (_a = global.window) === null || _a === void 0 ? void 0 : _a.crypto) === null || _b === void 0 ? void 0 : _b.subtle) !== 'undefined') { | ||
webcrypto = global.window.crypto; | ||
} | ||
else { | ||
webcrypto = require('crypto'); | ||
} | ||
if (setGlobal) { | ||
global.crypto = webcrypto; | ||
} | ||
return webcrypto; | ||
}; | ||
exports.globalCrypto = globalCrypto; | ||
const sanitizedJwk = (input) => { | ||
const inputJwk = typeof input['toJsonDTO'] === 'function' ? input['toJsonDTO']() : Object.assign({}, input); // KMP code can expose this. It converts a KMP JWK with mangled names into a clean JWK | ||
const jwk = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, inputJwk), (inputJwk.x && { x: base64ToBase64Url(inputJwk.x) })), (inputJwk.y && { y: base64ToBase64Url(inputJwk.y) })), (inputJwk.d && { d: base64ToBase64Url(inputJwk.d) })), (inputJwk.n && { n: base64ToBase64Url(inputJwk.n) })), (inputJwk.e && { e: base64ToBase64Url(inputJwk.e) })), (inputJwk.k && { k: base64ToBase64Url(inputJwk.k) })); | ||
return removeNulls(jwk); | ||
}; | ||
exports.sanitizedJwk = sanitizedJwk; | ||
const base64ToBase64Url = (input) => { | ||
return input.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); | ||
}; | ||
/** | ||
* | ||
*/ | ||
function verifyRawSignature(_a) { | ||
return __awaiter(this, arguments, void 0, function* ({ data, signature, key: inputKey, opts, }) { | ||
var _b, _c; | ||
/** | ||
* Converts a Base64URL-encoded JWK property to a BigInt. | ||
* @param jwkProp - The Base64URL-encoded string. | ||
* @returns The BigInt representation of the decoded value. | ||
*/ | ||
function jwkPropertyToBigInt(jwkProp) { | ||
// Decode Base64URL to Uint8Array | ||
const byteArray = u8a.fromString(jwkProp, 'base64url'); | ||
// Convert Uint8Array to hexadecimal string and then to BigInt | ||
const hex = u8a.toString(byteArray, 'hex'); | ||
return BigInt(`0x${hex}`); | ||
} | ||
const subtle = (_b = cryptoArg === null || cryptoArg === void 0 ? void 0 : cryptoArg.subtle) !== null && _b !== void 0 ? _b : crypto.subtle; | ||
const publicKey = yield subtle.importKey('jwk', key, { | ||
name: alg, | ||
namedCurve: crv, | ||
}, true, ['verify']); | ||
return subtle.verify({ | ||
name: alg, | ||
hash: 'SHA-256', // fixme; make arg | ||
}, publicKey, signature, data); | ||
try { | ||
(0, debug_1.default)(`verifyRawSignature for: ${inputKey}`); | ||
const jwk = (0, exports.sanitizedJwk)(inputKey); | ||
(0, jwk_jcs_1.validateJwk)(jwk, { crvOptional: true }); | ||
const keyType = (0, exports.keyTypeFromCryptographicSuite)({ crv: jwk.crv, kty: jwk.kty, alg: jwk.alg }); | ||
const publicKeyHex = yield (0, exports.jwkToRawHexKey)(jwk); | ||
// TODO: We really should look at the signature alg first if provided! From key type should be the last resort | ||
switch (keyType) { | ||
case 'Secp256k1': | ||
return secp256k1_1.secp256k1.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true }); | ||
case 'Secp256r1': | ||
return p256_1.p256.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true }); | ||
case 'Secp384r1': | ||
return p384_1.p384.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true }); | ||
case 'Secp521r1': | ||
return p521_1.p521.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true }); | ||
case 'Ed25519': | ||
return ed25519_1.ed25519.verify(signature, data, u8a.fromString(publicKeyHex, 'hex')); | ||
case 'Bls12381G1': | ||
case 'Bls12381G2': | ||
return bls12_381_1.bls12_381.verify(signature, data, u8a.fromString(publicKeyHex, 'hex')); | ||
case 'RSA': { | ||
const signatureAlgorithm = (_c = (_b = opts === null || opts === void 0 ? void 0 : opts.signatureAlg) !== null && _b !== void 0 ? _b : jwk.alg) !== null && _c !== void 0 ? _c : ssi_types_1.JoseSignatureAlgorithm.PS256; | ||
const hashAlg = signatureAlgorithm === (ssi_types_1.JoseSignatureAlgorithm.RS512 || ssi_types_1.JoseSignatureAlgorithm.PS512) | ||
? sha512_1.sha512 | ||
: signatureAlgorithm === (ssi_types_1.JoseSignatureAlgorithm.RS384 || ssi_types_1.JoseSignatureAlgorithm.PS384) | ||
? sha512_1.sha384 | ||
: sha256_1.sha256; | ||
switch (signatureAlgorithm) { | ||
case ssi_types_1.JoseSignatureAlgorithm.RS256: | ||
return rsa.PKCS1_SHA256.verify({ | ||
n: jwkPropertyToBigInt(jwk.n), | ||
e: jwkPropertyToBigInt(jwk.e), | ||
}, data, signature); | ||
case ssi_types_1.JoseSignatureAlgorithm.RS384: | ||
return rsa.PKCS1_SHA384.verify({ | ||
n: jwkPropertyToBigInt(jwk.n), | ||
e: jwkPropertyToBigInt(jwk.e), | ||
}, data, signature); | ||
case ssi_types_1.JoseSignatureAlgorithm.RS512: | ||
return rsa.PKCS1_SHA512.verify({ | ||
n: jwkPropertyToBigInt(jwk.n), | ||
e: jwkPropertyToBigInt(jwk.e), | ||
}, data, signature); | ||
case ssi_types_1.JoseSignatureAlgorithm.PS256: | ||
case ssi_types_1.JoseSignatureAlgorithm.PS384: | ||
case ssi_types_1.JoseSignatureAlgorithm.PS512: | ||
return rsa.PSS(hashAlg, rsa.mgf1(hashAlg)).verify({ | ||
n: jwkPropertyToBigInt(jwk.n), | ||
e: jwkPropertyToBigInt(jwk.e), | ||
}, data, signature); | ||
} | ||
} | ||
} | ||
throw Error(`Unsupported key type for signature validation: ${keyType}`); | ||
} | ||
catch (error) { | ||
exports.logger.error(`Error: ${error}`); | ||
throw error; | ||
} | ||
}); | ||
} | ||
//# sourceMappingURL=functions.js.map |
@@ -0,3 +1,16 @@ | ||
import { JWK } from '@sphereon/ssi-types'; | ||
import type { ByteView } from 'multiformats/codecs/interface'; | ||
/** | ||
* Checks if the JWK is valid. It must contain all the required members. | ||
* | ||
* @see https://www.rfc-editor.org/rfc/rfc7518#section-6 | ||
* @see https://www.rfc-editor.org/rfc/rfc8037#section-2 | ||
* | ||
* @param jwk - The JWK to check. | ||
* @param opts | ||
*/ | ||
export declare function validateJwk(jwk: any, opts?: { | ||
crvOptional?: boolean; | ||
}): void; | ||
/** | ||
* Extracts the required members of the JWK and canonicalizes it. | ||
@@ -8,24 +21,3 @@ * | ||
*/ | ||
export declare function minimalJwk(jwk: any): { | ||
crv: any; | ||
kty: any; | ||
x: any; | ||
y: any; | ||
e?: undefined; | ||
n?: undefined; | ||
} | { | ||
crv: any; | ||
kty: any; | ||
x: any; | ||
y?: undefined; | ||
e?: undefined; | ||
n?: undefined; | ||
} | { | ||
e: any; | ||
kty: any; | ||
n: any; | ||
crv?: undefined; | ||
x?: undefined; | ||
y?: undefined; | ||
}; | ||
export declare function minimalJwk(jwk: any): JWK; | ||
/** | ||
@@ -32,0 +24,0 @@ * Encodes a JWK into a Uint8Array. Only the required JWK members are encoded. |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.validateJwk = validateJwk; | ||
exports.minimalJwk = minimalJwk; | ||
@@ -11,3 +9,2 @@ exports.jwkJcsEncode = jwkJcsEncode; | ||
const web_encoding_1 = require("web-encoding"); | ||
const lodash_isplainobject_1 = __importDefault(require("lodash.isplainobject")); | ||
const textEncoder = new web_encoding_1.TextEncoder(); | ||
@@ -20,4 +17,8 @@ const textDecoder = new web_encoding_1.TextDecoder(); | ||
* @param description - Description of the value to check. | ||
* @param optional | ||
*/ | ||
function check(value, description) { | ||
function check(value, description, optional = false) { | ||
if (optional && !value) { | ||
return; | ||
} | ||
if (typeof value !== 'string' || !value) { | ||
@@ -32,5 +33,5 @@ throw new Error(`${description} missing or invalid`); | ||
*/ | ||
function validatePlainObject(value) { | ||
if (!(0, lodash_isplainobject_1.default)(value)) { | ||
throw new Error('JWK must be an object'); | ||
function assertObject(value) { | ||
if (!value || typeof value !== 'object') { | ||
throw new Error('Value must be an object'); | ||
} | ||
@@ -45,5 +46,8 @@ } | ||
* @param jwk - The JWK to check. | ||
* @param opts | ||
*/ | ||
function validateJwk(jwk) { | ||
validatePlainObject(jwk); | ||
function validateJwk(jwk, opts) { | ||
assertObject(jwk); | ||
const { crvOptional = false } = opts !== null && opts !== void 0 ? opts : {}; | ||
check(jwk.kty, '"kty" (Key Type) Parameter', false); | ||
// Check JWK required members based on the key type | ||
@@ -55,3 +59,3 @@ switch (jwk.kty) { | ||
case 'EC': | ||
check(jwk.crv, '"crv" (Curve) Parameter'); | ||
check(jwk.crv, '"crv" (Curve) Parameter', crvOptional); | ||
check(jwk.x, '"x" (X Coordinate) Parameter'); | ||
@@ -64,3 +68,3 @@ check(jwk.y, '"y" (Y Coordinate) Parameter'); | ||
case 'OKP': | ||
check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter'); | ||
check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter', crvOptional); // Shouldn't this one always be true as crv is not always present? | ||
check(jwk.x, '"x" (Public Key) Parameter'); | ||
@@ -90,5 +94,5 @@ break; | ||
case 'EC': | ||
return { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }; | ||
return Object.assign(Object.assign({}, (jwk.crv && { crv: jwk.crv })), { kty: jwk.kty, x: jwk.x, y: jwk.y }); | ||
case 'OKP': | ||
return { crv: jwk.crv, kty: jwk.kty, x: jwk.x }; | ||
return Object.assign(Object.assign({}, (jwk.crv && { crv: jwk.crv })), { kty: jwk.kty, x: jwk.x }); | ||
case 'RSA': | ||
@@ -95,0 +99,0 @@ return { e: jwk.e, kty: jwk.kty, n: jwk.n }; |
import { IKey, MinimalImportableKey } from '@veramo/core'; | ||
export declare const JWK_JCS_PUB_NAME: "jwk_jcs-pub"; | ||
export declare const JWK_JCS_PUB_PREFIX = 60241; | ||
export type TKeyType = 'Ed25519' | 'Secp256k1' | 'Secp256r1' | 'X25519' | 'Bls12381G1' | 'Bls12381G2' | 'RSA'; | ||
export type TKeyType = 'Ed25519' | 'Secp256k1' | 'Secp256r1' | 'Secp384r1' | 'Secp521r1' | 'X25519' | 'Bls12381G1' | 'Bls12381G2' | 'RSA'; | ||
export declare enum Key { | ||
@@ -31,3 +31,3 @@ Ed25519 = "Ed25519", | ||
key?: Partial<MinimalImportableKey>; | ||
type?: TKeyType; | ||
type?: Exclude<TKeyType, 'Secp384r1' | 'Secp521r1'>; | ||
use?: JwkKeyUse; | ||
@@ -43,4 +43,6 @@ x509?: X509Opts; | ||
export type KeyTypeFromCryptographicSuiteArgs = { | ||
suite: string; | ||
crv?: string; | ||
kty?: string; | ||
alg?: string; | ||
}; | ||
//# sourceMappingURL=key-util-types.d.ts.map |
{ | ||
"name": "@sphereon/ssi-sdk-ext.key-utils", | ||
"description": "Sphereon SSI-SDK plugin for key creation.", | ||
"version": "0.26.1-feature.SPRIND.116.9+9afc487", | ||
"version": "0.26.1-feature.SPRIND.116.44+f1862bf", | ||
"source": "src/index.ts", | ||
@@ -14,7 +14,7 @@ "main": "dist/index.js", | ||
"@ethersproject/random": "^5.7.0", | ||
"@sphereon/ssi-sdk-ext.x509-utils": "0.26.1-feature.SPRIND.116.9+9afc487", | ||
"@noble/curves": "1.7.0", | ||
"@noble/hashes": "1.6.1", | ||
"@sphereon/ssi-sdk-ext.x509-utils": "0.26.1-feature.SPRIND.116.44+f1862bf", | ||
"@sphereon/ssi-types": "0.30.2-feature.SDK.41.oidf.support.286", | ||
"@stablelib/ed25519": "^1.0.3", | ||
"@stablelib/sha256": "^1.0.1", | ||
"@stablelib/sha512": "^1.0.1", | ||
"@trust/keyto": "^1.0.1", | ||
@@ -27,2 +27,3 @@ "@veramo/core": "4.2.0", | ||
"lodash.isplainobject": "^4.0.6", | ||
"micro-rsa-dsa-dh": "^0.1.0", | ||
"multiformats": "^9.9.0", | ||
@@ -54,3 +55,3 @@ "uint8arrays": "^3.1.1", | ||
], | ||
"gitHead": "9afc4871e65ef851cd4c479ace388c0651243dca" | ||
"gitHead": "f1862bf57b3488fffaad2222174ed6927e5e3a05" | ||
} |
@@ -17,6 +17,7 @@ import { | ||
} from '@sphereon/ssi-types' | ||
import { removeNulls } from './functions' | ||
export function coseKeyToJwk(coseKey: ICoseKeyJson): JWK { | ||
const { x5chain, key_ops, crv, alg, baseIV, kty, ...rest } = coseKey | ||
return { | ||
return removeNulls({ | ||
...rest, | ||
@@ -29,3 +30,3 @@ kty: coseToJoseKty(kty), | ||
...(x5chain && { x5c: x5chain }), | ||
} satisfies JWK | ||
}) satisfies JWK | ||
} | ||
@@ -35,3 +36,3 @@ | ||
const { x5c, key_ops, crv, alg, iv, kty, ...rest } = jwk | ||
return { | ||
return removeNulls({ | ||
...rest, | ||
@@ -44,3 +45,3 @@ kty: joseToCoseKty(kty), | ||
...(x5c && { x5chain: x5c }), | ||
} satisfies ICoseKeyJson | ||
} satisfies ICoseKeyJson) | ||
} | ||
@@ -47,0 +48,0 @@ |
@@ -1,7 +0,8 @@ | ||
import { hash as sha256 } from '@stablelib/sha256' | ||
import { hash as sha512 } from '@stablelib/sha512' | ||
import { sha256 } from '@noble/hashes/sha256' | ||
import { sha384, sha512 } from '@noble/hashes/sha512' | ||
import { Hasher } from '@sphereon/ssi-types' | ||
import * as u8a from 'uint8arrays' | ||
import { SupportedEncodings } from 'uint8arrays/to-string' | ||
export type HashAlgorithm = 'SHA-256' | 'SHA-512' | ||
export type HashAlgorithm = 'SHA-256' | 'SHA-384' | 'SHA-512' | ||
export type TDigestMethod = (input: string, encoding?: SupportedEncodings) => string | ||
@@ -14,2 +15,4 @@ | ||
return { hashAlgorithm: 'SHA-256', digestMethod: sha256DigestMethod, hash: sha256 } | ||
} else if (hashAlgorithm === 'SHA-384') { | ||
return { hashAlgorithm: 'SHA-384', digestMethod: sha384DigestMethod, hash: sha384 } | ||
} else { | ||
@@ -20,2 +23,7 @@ return { hashAlgorithm: 'SHA-512', digestMethod: sha512DigestMethod, hash: sha512 } | ||
export const shaHasher: Hasher = (input: string, alg: string): Uint8Array => { | ||
const hashAlgorithm: HashAlgorithm = alg.includes('384') ? 'SHA-384' : alg.includes('512') ? 'SHA-512' : 'SHA-256' | ||
return digestMethodParams(hashAlgorithm).hash(u8a.fromString(input, 'utf-8')) | ||
} | ||
const sha256DigestMethod = (input: string, encoding: SupportedEncodings = 'base16'): string => { | ||
@@ -25,2 +33,6 @@ return u8a.toString(sha256(u8a.fromString(input, 'utf-8')), encoding) | ||
const sha384DigestMethod = (input: string, encoding: SupportedEncodings = 'base16'): string => { | ||
return u8a.toString(sha384(u8a.fromString(input, 'utf-8')), encoding) | ||
} | ||
const sha512DigestMethod = (input: string, encoding: SupportedEncodings = 'base16'): string => { | ||
@@ -27,0 +39,0 @@ return u8a.toString(sha512(u8a.fromString(input, 'utf-8')), encoding) |
import { randomBytes } from '@ethersproject/random' | ||
// Do not change these require statements to imports before we change to ESM. Breaks external CJS packages depending on this module | ||
import { bls12_381 } from '@noble/curves/bls12-381' | ||
import { ed25519 } from '@noble/curves/ed25519' | ||
import { p256 } from '@noble/curves/p256' | ||
import { p384 } from '@noble/curves/p384' | ||
import { p521 } from '@noble/curves/p521' | ||
import { secp256k1 } from '@noble/curves/secp256k1' | ||
import { sha256 } from '@noble/hashes/sha256' | ||
import { sha384, sha512 } from '@noble/hashes/sha512' | ||
import { generateRSAKeyAsPEM, hexToBase64, hexToPEM, PEMToJwk, privateKeyHexFromPEM } from '@sphereon/ssi-sdk-ext.x509-utils' | ||
import { JoseCurve, JoseSignatureAlgorithm, JwkKeyType, JWK, Loggers } from '@sphereon/ssi-types' | ||
import { JoseCurve, JoseSignatureAlgorithm, JWK, JwkKeyType, Loggers } from '@sphereon/ssi-types' | ||
import { generateKeyPair as generateSigningKeyPair } from '@stablelib/ed25519' | ||
import { IAgentContext, IKey, IKeyManager, ManagedKeyInfo, MinimalImportableKey } from '@veramo/core' | ||
import debug from 'debug' | ||
import { JsonWebKey } from 'did-resolver' | ||
import elliptic from 'elliptic' | ||
import * as rsa from 'micro-rsa-dsa-dh/rsa.js' | ||
import * as u8a from 'uint8arrays' | ||
import { digestMethodParams } from './digest-methods' | ||
import { validateJwk } from './jwk-jcs' | ||
import { | ||
@@ -171,3 +183,4 @@ ENC_KEY_ALGS, | ||
export const calculateJwkThumbprint = (args: { jwk: JWK; digestAlgorithm?: 'sha256' | 'sha512' }): string => { | ||
const { jwk, digestAlgorithm = 'sha256' } = args | ||
const { digestAlgorithm = 'sha256' } = args | ||
const jwk = sanitizedJwk(args.jwk) | ||
let components | ||
@@ -255,6 +268,98 @@ switch (jwk.kty) { | ||
} | ||
return jwk | ||
return sanitizedJwk(jwk) | ||
} | ||
/** | ||
* Convert a JWK to a raw hex key. | ||
* Currently supports `RSA` and `EC` keys. Extendable for other key types. | ||
* @param jwk - The JSON Web Key object. | ||
* @returns A string representing the key in raw hexadecimal format. | ||
*/ | ||
export const jwkToRawHexKey = async (jwk: JWK): Promise<string> => { | ||
// TODO: Probably makes sense to have an option to do the same for private keys | ||
jwk = sanitizedJwk(jwk) | ||
if (jwk.kty === 'RSA') { | ||
return rsaJwkToRawHexKey(jwk) | ||
} else if (jwk.kty === 'EC') { | ||
return ecJwkToRawHexKey(jwk) | ||
} else if (jwk.kty === 'OKP') { | ||
return okpJwkToRawHexKey(jwk) | ||
} else if (jwk.kty === 'oct') { | ||
return octJwkToRawHexKey(jwk) | ||
} else { | ||
throw new Error(`Unsupported key type: ${jwk.kty}`) | ||
} | ||
} | ||
/** | ||
* Convert an RSA JWK to a raw hex key. | ||
* @param jwk - The RSA JWK object. | ||
* @returns A string representing the RSA key in raw hexadecimal format. | ||
*/ | ||
function rsaJwkToRawHexKey(jwk: JsonWebKey): string { | ||
jwk = sanitizedJwk(jwk) | ||
if (!jwk.n || !jwk.e) { | ||
throw new Error("RSA JWK must contain 'n' and 'e' properties.") | ||
} | ||
// We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string | ||
const modulus = u8a.fromString(jwk.n.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') // 'n' is the modulus | ||
const exponent = u8a.fromString(jwk.e.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') // 'e' is the exponent | ||
return u8a.toString(modulus, 'hex') + u8a.toString(exponent, 'hex') | ||
} | ||
/** | ||
* Convert an EC JWK to a raw hex key. | ||
* @param jwk - The EC JWK object. | ||
* @returns A string representing the EC key in raw hexadecimal format. | ||
*/ | ||
function ecJwkToRawHexKey(jwk: JsonWebKey): string { | ||
jwk = sanitizedJwk(jwk) | ||
if (!jwk.x || !jwk.y) { | ||
throw new Error("EC JWK must contain 'x' and 'y' properties.") | ||
} | ||
// We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string | ||
const x = u8a.fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') | ||
const y = u8a.fromString(jwk.y.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') | ||
return '04' + u8a.toString(x, 'hex') + u8a.toString(y, 'hex') | ||
} | ||
/** | ||
* Convert an EC JWK to a raw hex key. | ||
* @param jwk - The EC JWK object. | ||
* @returns A string representing the EC key in raw hexadecimal format. | ||
*/ | ||
function okpJwkToRawHexKey(jwk: JsonWebKey): string { | ||
jwk = sanitizedJwk(jwk) | ||
if (!jwk.x) { | ||
throw new Error("OKP JWK must contain 'x' property.") | ||
} | ||
// We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string | ||
const x = u8a.fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') | ||
return u8a.toString(x, 'hex') | ||
} | ||
/** | ||
* Convert an octet JWK to a raw hex key. | ||
* @param jwk - The octet JWK object. | ||
* @returns A string representing the octet key in raw hexadecimal format. | ||
*/ | ||
function octJwkToRawHexKey(jwk: JsonWebKey): string { | ||
jwk = sanitizedJwk(jwk) | ||
if (!jwk.k) { | ||
throw new Error("Octet JWK must contain 'k' property.") | ||
} | ||
// We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string | ||
const key = u8a.fromString(jwk.k.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') | ||
return u8a.toString(key, 'hex') | ||
} | ||
/** | ||
* Determines the use param based upon the key/signature type or supplied use value. | ||
@@ -315,3 +420,3 @@ * | ||
return { | ||
return sanitizedJwk({ | ||
alg: JoseSignatureAlgorithm.ES256K, | ||
@@ -324,3 +429,3 @@ ...(use !== undefined && { use }), | ||
...(opts?.isPrivateKey && { d: hexToBase64(keyPair.getPrivate('hex'), 'base64url') }), | ||
} | ||
}) | ||
} | ||
@@ -348,3 +453,3 @@ | ||
const pubPoint = keyPair.getPublic() | ||
return { | ||
return sanitizedJwk({ | ||
alg: JoseSignatureAlgorithm.ES256, | ||
@@ -357,3 +462,3 @@ ...(use !== undefined && { use }), | ||
...(opts?.isPrivateKey && { d: hexToBase64(keyPair.getPrivate('hex'), 'base64url') }), | ||
} | ||
}) | ||
} | ||
@@ -376,3 +481,3 @@ | ||
const { use } = opts ?? {} | ||
return { | ||
return sanitizedJwk({ | ||
alg: JoseSignatureAlgorithm.EdDSA, | ||
@@ -383,16 +488,29 @@ ...(use !== undefined && { use }), | ||
x: hexToBase64(publicKeyHex, 'base64url'), | ||
} | ||
}) | ||
} | ||
const toRSAJwk = (publicKeyHex: string, opts?: { use?: JwkKeyUse; key?: IKey | MinimalImportableKey }): JWK => { | ||
const { key } = opts ?? {} | ||
const meta = opts?.key?.meta | ||
if (meta?.publicKeyJwk || meta?.publicKeyPEM) { | ||
if (meta?.publicKeyJwk) { | ||
return meta.publicKeyJwk as JWK | ||
} | ||
const publicKeyPEM = meta?.publicKeyPEM ?? hexToPEM(publicKeyHex, 'public') | ||
return PEMToJwk(publicKeyPEM, 'public') as JWK | ||
} | ||
// exponent (e) is 5 chars long, rest is modulus (n) | ||
// const publicKey = publicKeyHex | ||
// assertProperKeyLength(publicKey, [2048, 3072, 4096]) | ||
const exponent = publicKeyHex.slice(-5) | ||
const modulus = publicKeyHex.slice(0, -5) | ||
// const modulusBitLength = (modulus.length / 2) * 8 | ||
if (key?.meta?.publicKeyJwk) { | ||
return key.meta.publicKeyJwk as JWK | ||
} | ||
const publicKeyPEM = key?.meta?.publicKeyPEM ?? hexToPEM(publicKeyHex, 'public') | ||
return PEMToJwk(publicKeyPEM, 'public') as JWK | ||
// const alg = modulusBitLength === 2048 ? JoseSignatureAlgorithm.RS256 : modulusBitLength === 3072 ? JoseSignatureAlgorithm.RS384 : modulusBitLength === 4096 ? JoseSignatureAlgorithm.RS512 : undefined | ||
return sanitizedJwk({ | ||
kty: 'RSA', | ||
n: hexToBase64(modulus, 'base64url'), | ||
e: hexToBase64(exponent, 'base64url'), | ||
// ...(alg && { alg }), | ||
}) | ||
} | ||
@@ -538,2 +656,6 @@ | ||
return JoseSignatureAlgorithm.ES256 | ||
case 'Secp384r1': | ||
return JoseSignatureAlgorithm.ES384 | ||
case 'Secp521r1': | ||
return JoseSignatureAlgorithm.ES512 | ||
case 'Secp256k1': | ||
@@ -548,5 +670,18 @@ return JoseSignatureAlgorithm.ES256K | ||
export const keyTypeFromCryptographicSuite = (args: KeyTypeFromCryptographicSuiteArgs): TKeyType => { | ||
const { suite } = args | ||
switch (suite) { | ||
const { crv, kty, alg } = args | ||
switch (alg) { | ||
case 'RSASSA-PSS': | ||
case 'RS256': | ||
case 'RS384': | ||
case 'RS512': | ||
case 'PS256': | ||
case 'PS384': | ||
case 'PS512': | ||
return 'RSA' | ||
} | ||
switch (crv) { | ||
case 'EdDSA': | ||
case 'Ed25519': | ||
case 'Ed25519Signature2018': | ||
@@ -559,48 +694,178 @@ case 'Ed25519Signature2020': | ||
case 'ECDSA': | ||
case 'P-256': | ||
return 'Secp256r1' | ||
case 'ES384': | ||
case 'P-384': | ||
return 'Secp384r1' | ||
case 'ES512': | ||
case 'P-521': | ||
return 'Secp521r1' | ||
case 'EcdsaSecp256k1Signature2019': | ||
case 'secp256k1': | ||
case 'ES256K': | ||
return 'Secp256k1' | ||
default: | ||
throw new Error(`Cryptographic suite '${suite}' not supported`) | ||
} | ||
if (kty) { | ||
return kty as TKeyType | ||
} | ||
throw new Error(`Cryptographic suite '${crv}' not supported`) | ||
} | ||
export async function verifySignatureWithSubtle({ | ||
export function removeNulls<T>(obj: T | any) { | ||
Object.keys(obj).forEach((key) => { | ||
if (obj[key] && typeof obj[key] === 'object') removeNulls(obj[key]) | ||
else if (obj[key] == null) delete obj[key] | ||
}) | ||
return obj | ||
} | ||
export const globalCrypto = (setGlobal: boolean, suppliedCrypto?: Crypto): Crypto => { | ||
let webcrypto: Crypto | ||
if (typeof suppliedCrypto !== 'undefined') { | ||
webcrypto = suppliedCrypto | ||
} else if (typeof crypto !== 'undefined') { | ||
webcrypto = crypto | ||
} else if (typeof global.crypto !== 'undefined') { | ||
webcrypto = global.crypto | ||
} else if (typeof global.window?.crypto?.subtle !== 'undefined') { | ||
webcrypto = global.window.crypto | ||
} else { | ||
webcrypto = require('crypto') as Crypto | ||
} | ||
if (setGlobal) { | ||
global.crypto = webcrypto | ||
} | ||
return webcrypto | ||
} | ||
export const sanitizedJwk = (input: JWK | JsonWebKey): JWK => { | ||
const inputJwk = typeof input['toJsonDTO'] === 'function' ? input['toJsonDTO']() : {...input} as JWK // KMP code can expose this. It converts a KMP JWK with mangled names into a clean JWK | ||
const jwk = { | ||
...inputJwk, | ||
...(inputJwk.x && { x: base64ToBase64Url(inputJwk.x as string) }), | ||
...(inputJwk.y && { y: base64ToBase64Url(inputJwk.y as string) }), | ||
...(inputJwk.d && { d: base64ToBase64Url(inputJwk.d as string) }), | ||
...(inputJwk.n && { n: base64ToBase64Url(inputJwk.n as string) }), | ||
...(inputJwk.e && { e: base64ToBase64Url(inputJwk.e as string) }), | ||
...(inputJwk.k && { k: base64ToBase64Url(inputJwk.k as string) }), | ||
} as JWK | ||
return removeNulls(jwk) | ||
} | ||
const base64ToBase64Url = (input: string): string => { | ||
return input.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') | ||
} | ||
/** | ||
* | ||
*/ | ||
export async function verifyRawSignature({ | ||
data, | ||
signature, | ||
key, | ||
crypto: cryptoArg, | ||
key: inputKey, | ||
opts, | ||
}: { | ||
data: Uint8Array | ||
signature: Uint8Array | ||
key: JsonWebKey | ||
crypto?: Crypto | ||
key: JWK | ||
opts?: { | ||
signatureAlg?: JoseSignatureAlgorithm | ||
} | ||
}) { | ||
let { alg, crv } = key | ||
if (alg === 'ES256' || !alg) { | ||
alg = 'ECDSA' | ||
/** | ||
* Converts a Base64URL-encoded JWK property to a BigInt. | ||
* @param jwkProp - The Base64URL-encoded string. | ||
* @returns The BigInt representation of the decoded value. | ||
*/ | ||
function jwkPropertyToBigInt(jwkProp: string): bigint { | ||
// Decode Base64URL to Uint8Array | ||
const byteArray = u8a.fromString(jwkProp, 'base64url') | ||
// Convert Uint8Array to hexadecimal string and then to BigInt | ||
const hex = u8a.toString(byteArray, 'hex') | ||
return BigInt(`0x${hex}`) | ||
} | ||
const subtle = cryptoArg?.subtle ?? crypto.subtle | ||
const publicKey = await subtle.importKey( | ||
'jwk', | ||
key, | ||
{ | ||
name: alg, | ||
namedCurve: crv, | ||
} as EcKeyImportParams, | ||
true, | ||
['verify'] | ||
) | ||
try { | ||
debug(`verifyRawSignature for: ${inputKey}`) | ||
const jwk = sanitizedJwk(inputKey) | ||
validateJwk(jwk, { crvOptional: true }) | ||
const keyType = keyTypeFromCryptographicSuite({ crv: jwk.crv, kty: jwk.kty, alg: jwk.alg }) | ||
const publicKeyHex = await jwkToRawHexKey(jwk) | ||
return subtle.verify( | ||
{ | ||
name: alg as string, | ||
hash: 'SHA-256', // fixme; make arg | ||
}, | ||
publicKey, | ||
signature, | ||
data | ||
) | ||
// TODO: We really should look at the signature alg first if provided! From key type should be the last resort | ||
switch (keyType) { | ||
case 'Secp256k1': | ||
return secp256k1.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true }) | ||
case 'Secp256r1': | ||
return p256.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true }) | ||
case 'Secp384r1': | ||
return p384.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true }) | ||
case 'Secp521r1': | ||
return p521.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true }) | ||
case 'Ed25519': | ||
return ed25519.verify(signature, data, u8a.fromString(publicKeyHex, 'hex')) | ||
case 'Bls12381G1': | ||
case 'Bls12381G2': | ||
return bls12_381.verify(signature, data, u8a.fromString(publicKeyHex, 'hex')) | ||
case 'RSA': { | ||
const signatureAlgorithm = opts?.signatureAlg ?? jwk.alg as JoseSignatureAlgorithm | undefined ?? JoseSignatureAlgorithm.PS256 | ||
const hashAlg = | ||
signatureAlgorithm === (JoseSignatureAlgorithm.RS512 || JoseSignatureAlgorithm.PS512) | ||
? sha512 | ||
: signatureAlgorithm === (JoseSignatureAlgorithm.RS384 || JoseSignatureAlgorithm.PS384) | ||
? sha384 | ||
: sha256 | ||
switch (signatureAlgorithm) { | ||
case JoseSignatureAlgorithm.RS256: | ||
return rsa.PKCS1_SHA256.verify( | ||
{ | ||
n: jwkPropertyToBigInt(jwk.n!), | ||
e: jwkPropertyToBigInt(jwk.e!), | ||
}, | ||
data, | ||
signature | ||
) | ||
case JoseSignatureAlgorithm.RS384: | ||
return rsa.PKCS1_SHA384.verify( | ||
{ | ||
n: jwkPropertyToBigInt(jwk.n!), | ||
e: jwkPropertyToBigInt(jwk.e!), | ||
}, | ||
data, | ||
signature | ||
) | ||
case JoseSignatureAlgorithm.RS512: | ||
return rsa.PKCS1_SHA512.verify( | ||
{ | ||
n: jwkPropertyToBigInt(jwk.n!), | ||
e: jwkPropertyToBigInt(jwk.e!), | ||
}, | ||
data, | ||
signature | ||
) | ||
case JoseSignatureAlgorithm.PS256: | ||
case JoseSignatureAlgorithm.PS384: | ||
case JoseSignatureAlgorithm.PS512: | ||
return rsa.PSS(hashAlg, rsa.mgf1(hashAlg)).verify( | ||
{ | ||
n: jwkPropertyToBigInt(jwk.n!), | ||
e: jwkPropertyToBigInt(jwk.e!), | ||
}, | ||
data, | ||
signature | ||
) | ||
} | ||
} | ||
} | ||
throw Error(`Unsupported key type for signature validation: ${keyType}`) | ||
} catch (error: any) { | ||
logger.error(`Error: ${error}`) | ||
throw error | ||
} | ||
} |
@@ -0,4 +1,4 @@ | ||
import { JWK } from '@sphereon/ssi-types' | ||
import type { ByteView } from 'multiformats/codecs/interface' | ||
import { TextDecoder, TextEncoder } from 'web-encoding' | ||
import isPlainObject from 'lodash.isplainobject' | ||
import type { ByteView } from 'multiformats/codecs/interface' | ||
@@ -13,4 +13,8 @@ const textEncoder = new TextEncoder() | ||
* @param description - Description of the value to check. | ||
* @param optional | ||
*/ | ||
function check(value: unknown, description: string) { | ||
function check(value: unknown, description: string, optional: boolean = false) { | ||
if (optional && !value) { | ||
return | ||
} | ||
if (typeof value !== 'string' || !value) { | ||
@@ -26,5 +30,5 @@ throw new Error(`${description} missing or invalid`) | ||
*/ | ||
function validatePlainObject(value: unknown) { | ||
if (!isPlainObject(value)) { | ||
throw new Error('JWK must be an object') | ||
function assertObject(value: unknown) { | ||
if (!value || typeof value !== 'object') { | ||
throw new Error('Value must be an object') | ||
} | ||
@@ -40,5 +44,9 @@ } | ||
* @param jwk - The JWK to check. | ||
* @param opts | ||
*/ | ||
function validateJwk(jwk: any) { | ||
validatePlainObject(jwk) | ||
export function validateJwk(jwk: any, opts?: { crvOptional?: boolean }) { | ||
assertObject(jwk) | ||
const { crvOptional = false } = opts ?? {} | ||
check(jwk.kty, '"kty" (Key Type) Parameter', false) | ||
// Check JWK required members based on the key type | ||
@@ -50,3 +58,3 @@ switch (jwk.kty) { | ||
case 'EC': | ||
check(jwk.crv, '"crv" (Curve) Parameter') | ||
check(jwk.crv, '"crv" (Curve) Parameter', crvOptional) | ||
check(jwk.x, '"x" (X Coordinate) Parameter') | ||
@@ -59,3 +67,3 @@ check(jwk.y, '"y" (Y Coordinate) Parameter') | ||
case 'OKP': | ||
check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter') | ||
check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter', crvOptional) // Shouldn't this one always be true as crv is not always present? | ||
check(jwk.x, '"x" (Public Key) Parameter') | ||
@@ -81,3 +89,3 @@ break | ||
*/ | ||
export function minimalJwk(jwk: any) { | ||
export function minimalJwk(jwk: any): JWK { | ||
// "default" case is not needed | ||
@@ -87,5 +95,5 @@ // eslint-disable-next-line default-case | ||
case 'EC': | ||
return { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y } | ||
return { ...(jwk.crv && { crv: jwk.crv }), kty: jwk.kty, x: jwk.x, y: jwk.y } | ||
case 'OKP': | ||
return { crv: jwk.crv, kty: jwk.kty, x: jwk.x } | ||
return { ...(jwk.crv && { crv: jwk.crv }), kty: jwk.kty, x: jwk.x } | ||
case 'RSA': | ||
@@ -92,0 +100,0 @@ return { e: jwk.e, kty: jwk.kty, n: jwk.n } |
@@ -6,3 +6,3 @@ import { IKey, MinimalImportableKey } from '@veramo/core' | ||
export type TKeyType = 'Ed25519' | 'Secp256k1' | 'Secp256r1' | 'X25519' | 'Bls12381G1' | 'Bls12381G2' | 'RSA' | ||
export type TKeyType = 'Ed25519' | 'Secp256k1' | 'Secp256r1' | 'Secp384r1' | 'Secp521r1' | 'X25519' | 'Bls12381G1' | 'Bls12381G2' | 'RSA' | ||
@@ -40,3 +40,3 @@ export enum Key { | ||
key?: Partial<MinimalImportableKey> // Optional key to import with only privateKeyHex mandatory. If not specified a key with random kid will be created | ||
type?: TKeyType // The key type. Defaults to Secp256k1 | ||
type?: Exclude<TKeyType, 'Secp384r1' | 'Secp521r1'> // The key type. Defaults to Secp256k1. The exclude is there as we do not support it yet for key generation | ||
use?: JwkKeyUse // The key use | ||
@@ -60,3 +60,5 @@ x509?: X509Opts | ||
export type KeyTypeFromCryptographicSuiteArgs = { | ||
suite: string | ||
crv?: string | ||
kty?: string | ||
alg?: string | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
177625
2835
18
+ Added@noble/curves@1.7.0
+ Added@noble/hashes@1.6.1
+ Addedmicro-rsa-dsa-dh@^0.1.0
+ Added@noble/hashes@1.4.0(transitive)
+ Addedmicro-rsa-dsa-dh@0.1.0(transitive)
- Removed@stablelib/sha256@^1.0.1
- Removed@stablelib/sha512@^1.0.1
Updated@sphereon/ssi-sdk-ext.x509-utils@0.26.1-feature.SPRIND.116.44+f1862bf