@sphereon/ssi-sdk.mdl-mdoc
Version:
260 lines • 14.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.X509CallbackService = exports.CoseCryptoService = void 0;
const kmp_mdoc_core_1 = require("@sphereon/kmp-mdoc-core");
const ssi_sdk_ext_key_utils_1 = require("@sphereon/ssi-sdk-ext.key-utils");
const ssi_sdk_ext_x509_utils_1 = require("@sphereon/ssi-sdk-ext.x509-utils");
const crypto = __importStar(require("crypto"));
const pkijs_1 = require("pkijs");
const u8a = __importStar(require("uint8arrays"));
var CoseKeyCbor = kmp_mdoc_core_1.com.sphereon.crypto.cose.CoseKeyCbor;
var CoseJoseKeyMappingService = kmp_mdoc_core_1.com.sphereon.crypto.CoseJoseKeyMappingService;
var DefaultCallbacks = kmp_mdoc_core_1.com.sphereon.crypto.DefaultCallbacks;
var SignatureAlgorithm = kmp_mdoc_core_1.com.sphereon.crypto.generic.SignatureAlgorithm;
var KeyInfo = kmp_mdoc_core_1.com.sphereon.crypto.KeyInfo;
var ResolvedKeyInfo = kmp_mdoc_core_1.com.sphereon.crypto.ResolvedKeyInfo;
var DateTimeUtils = kmp_mdoc_core_1.com.sphereon.kmp.DateTimeUtils;
var decodeFrom = kmp_mdoc_core_1.com.sphereon.kmp.decodeFrom;
var encodeTo = kmp_mdoc_core_1.com.sphereon.kmp.encodeTo;
var Encoding = kmp_mdoc_core_1.com.sphereon.kmp.Encoding;
class CoseCryptoService {
constructor(context) {
this.context = context;
}
setContext(context) {
this.context = context;
}
signAsync(input, requireX5Chain) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
if (!this.context) {
throw Error('No context provided. Please provide a context with the setContext method or constructor');
}
const { keyInfo, alg, value } = input;
let kmsKeyRef = (_a = keyInfo.kmsKeyRef) !== null && _a !== void 0 ? _a : undefined;
if (!kmsKeyRef) {
const key = keyInfo.key;
if (key == null) {
return Promise.reject(Error('No key present in keyInfo. This implementation cannot sign without a key!'));
}
const resolvedKeyInfo = ResolvedKeyInfo.Static.fromKeyInfo(keyInfo, key);
const jwkKeyInfo = CoseJoseKeyMappingService.toResolvedJwkKeyInfo(resolvedKeyInfo);
const kid = (_c = (_b = jwkKeyInfo.kid) !== null && _b !== void 0 ? _b : (0, ssi_sdk_ext_key_utils_1.calculateJwkThumbprint)({ jwk: jwkKeyInfo.key.toJsonDTO() })) !== null && _c !== void 0 ? _c : jwkKeyInfo.key.getKidAsString(true);
if (!kid) {
return Promise.reject(Error('No kid present and not kmsKeyRef provided'));
}
kmsKeyRef = kid;
}
const result = yield this.context.agent.keyManagerSign({
algorithm: alg.jose.value,
data: encodeTo(value, Encoding.UTF8),
encoding: 'utf-8',
keyRef: kmsKeyRef,
});
return decodeFrom(result, Encoding.UTF8);
});
}
verify1Async(input, keyInfo, requireX5Chain) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
const getCertAndKey = (x5c) => __awaiter(this, void 0, void 0, function* () {
if (requireX5Chain && (!x5c || x5c.length === 0)) {
// We should not be able to get here anyway, as the MLD-mdoc library already validated at this point. But let's make sure
return Promise.reject(new Error(`No x5chain was present in the CoseSign headers!`));
}
// TODO: According to the IETF spec there should be a x5t in case the x5chain is in the protected headers. In the Funke this does not seem to be done/used!
issuerCert = x5c ? (0, ssi_sdk_ext_x509_utils_1.pemOrDerToX509Certificate)(x5c[0]) : undefined;
let issuerJwk;
if (issuerCert) {
const info = yield (0, ssi_sdk_ext_x509_utils_1.getCertificateInfo)(issuerCert);
issuerJwk = info.publicKeyJWK;
}
return { issuerCert, issuerJwk };
});
const coseKeyInfo = CoseJoseKeyMappingService.toCoseKeyInfo(keyInfo);
if ((_a = coseKeyInfo === null || coseKeyInfo === void 0 ? void 0 : coseKeyInfo.key) === null || _a === void 0 ? void 0 : _a.d) {
throw Error('Do not use private keys to verify!');
}
else if (!((_b = input.payload) === null || _b === void 0 ? void 0 : _b.value)) {
return Promise.reject(Error('Signature validation without payload not supported'));
}
const sign1Json = input.toJson(); // Let's make it a bit easier on ourselves, instead of working with CBOR
const coseAlg = sign1Json.protectedHeader.alg;
if (!coseAlg) {
return Promise.reject(Error('No alg protected header present'));
}
let issuerCert;
let issuerCoseKey;
let kid = (_d = (_c = coseKeyInfo === null || coseKeyInfo === void 0 ? void 0 : coseKeyInfo.kid) !== null && _c !== void 0 ? _c : sign1Json.protectedHeader.kid) !== null && _d !== void 0 ? _d : (_e = sign1Json.unprotectedHeader) === null || _e === void 0 ? void 0 : _e.kid;
// Please note this method does not perform chain validation. The MDL-MSO_MDOC library already performed this before this step
const x5c = (_j = (_g = (_f = coseKeyInfo === null || coseKeyInfo === void 0 ? void 0 : coseKeyInfo.key) === null || _f === void 0 ? void 0 : _f.getX509CertificateChain()) !== null && _g !== void 0 ? _g : (_h = sign1Json.protectedHeader) === null || _h === void 0 ? void 0 : _h.x5chain) !== null && _j !== void 0 ? _j : (_k = sign1Json.unprotectedHeader) === null || _k === void 0 ? void 0 : _k.x5chain;
if (!coseKeyInfo || !(coseKeyInfo === null || coseKeyInfo === void 0 ? void 0 : coseKeyInfo.key) || ((_l = coseKeyInfo === null || coseKeyInfo === void 0 ? void 0 : coseKeyInfo.key) === null || _l === void 0 ? void 0 : _l.x5chain)) {
const certAndKey = yield getCertAndKey(x5c);
issuerCoseKey = certAndKey.issuerJwk ? CoseJoseKeyMappingService.toCoseKey(certAndKey.issuerJwk) : undefined;
issuerCert = certAndKey.issuerCert;
}
if (!issuerCoseKey) {
if (!(coseKeyInfo === null || coseKeyInfo === void 0 ? void 0 : coseKeyInfo.key)) {
return Promise.reject(Error(`Either a x5c needs to be in the headers, or you need to provide a key for verification`));
}
if (kid === null) {
kid = coseKeyInfo.key.getKidAsString(false);
}
issuerCoseKey = CoseKeyCbor.Static.fromDTO(coseKeyInfo.key);
}
const issuerCoseKeyInfo = new KeyInfo(kid, issuerCoseKey, coseKeyInfo.opts, coseKeyInfo.keyVisibility, (_m = issuerCoseKey.getSignatureAlgorithm()) !== null && _m !== void 0 ? _m : coseKeyInfo.signatureAlgorithm, x5c, coseKeyInfo.kmsKeyRef, coseKeyInfo.kms, (_o = coseKeyInfo.keyType) !== null && _o !== void 0 ? _o : issuerCoseKey.getKty());
const recalculatedToBeSigned = input.toBeSignedJson(issuerCoseKeyInfo, SignatureAlgorithm.Static.fromCose(coseAlg));
const key = CoseJoseKeyMappingService.toJoseJwk(issuerCoseKeyInfo.key).toJsonDTO();
const valid = yield (0, ssi_sdk_ext_key_utils_1.verifyRawSignature)({
data: u8a.fromString(recalculatedToBeSigned.base64UrlValue, 'base64url'),
signature: u8a.fromString(sign1Json.signature, 'base64url'),
key,
});
return {
name: 'mdoc',
critical: true,
error: !valid,
message: `Signature of '${issuerCert ? (0, ssi_sdk_ext_x509_utils_1.getSubjectDN)(issuerCert).DN : kid}' was ${valid ? '' : 'in'}valid`,
keyInfo: issuerCoseKeyInfo,
};
});
}
resolvePublicKeyAsync(keyInfo) {
if (keyInfo.key) {
return Promise.resolve(CoseJoseKeyMappingService.toResolvedKeyInfo(keyInfo, keyInfo.key));
}
return Promise.reject(Error('No key present in keyInfo. This implementation cannot resolve public keys on its own currently!'));
}
}
exports.CoseCryptoService = CoseCryptoService;
/**
* This class can be used for X509 validations.
* Either have an instance per trustedCerts and verification invocation or use a single instance and provide the trusted certs in the method argument
*
* The class is also registered with the low-level mDL/mdoc Kotlin Multiplatform library
* Next to the specific function for the library it exports a more powerful version of the same verification method as well
*/
class X509CallbackService {
constructor(trustedCerts) {
this.setTrustedCerts = (trustedCertsInPEM) => {
this._trustedCerts = trustedCertsInPEM === null || trustedCertsInPEM === void 0 ? void 0 : trustedCertsInPEM.map((cert) => {
if (cert.includes('CERTIFICATE')) {
// PEM
return cert;
}
return (0, ssi_sdk_ext_x509_utils_1.derToPEM)(cert);
});
};
this.getTrustedCerts = () => this._trustedCerts;
this.setTrustedCerts(trustedCerts);
}
/**
* A more powerful version of the method below. Allows to verify at a specific time and returns more information
* @param chain
* @param trustAnchors
* @param verificationTime
*/
verifyCertificateChain(_a) {
return __awaiter(this, arguments, void 0, function* ({ chain, trustAnchors = this.getTrustedCerts(), verificationTime, opts, }) {
return yield (0, ssi_sdk_ext_x509_utils_1.validateX509CertificateChain)({
chain,
trustAnchors,
verificationTime,
opts,
});
});
}
/**
* This method is the implementation used within the mDL/Mdoc library
*/
verifyCertificateChainJS(chainDER, chainPEM, trustedCerts, verificationProfile, verificationTime) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const verificationAt = verificationTime !== null && verificationTime !== void 0 ? verificationTime : DateTimeUtils.Static.DEFAULT.dateTimeLocal();
let chain = [];
if (chainDER && chainDER.length > 0) {
chain = chainDER.map((der) => Uint8Array.from(der));
}
if (chainPEM && chainPEM.length > 0) {
chain = (chain !== null && chain !== void 0 ? chain : []).concat(chainPEM);
}
const result = yield (0, ssi_sdk_ext_x509_utils_1.validateX509CertificateChain)({
chain: chain, // The function will handle an empty array
trustAnchors: trustedCerts !== null && trustedCerts !== void 0 ? trustedCerts : this.getTrustedCerts(),
verificationTime: new Date(verificationAt.toEpochSeconds().toULong() * 1000),
opts: { trustRootWhenNoAnchors: true },
});
const cert = result.certificateChain ? result.certificateChain[result.certificateChain.length - 1] : undefined;
return {
publicKey: cert === null || cert === void 0 ? void 0 : cert.publicKeyJWK, // fixme
publicKeyAlgorithm: (_a = cert === null || cert === void 0 ? void 0 : cert.publicKeyJWK) === null || _a === void 0 ? void 0 : _a.alg,
name: 'x.509',
critical: result.critical,
message: result.message,
error: result.error,
verificationTime: verificationAt,
};
});
}
}
exports.X509CallbackService = X509CallbackService;
const defaultCryptoEngine = () => {
if (typeof self !== 'undefined') {
if ('crypto' in self) {
let engineName = 'webcrypto';
if ('webkitSubtle' in self.crypto) {
engineName = 'safari';
}
// @ts-ignore
(0, pkijs_1.setEngine)(engineName, new pkijs_1.CryptoEngine({ name: engineName, crypto: crypto }));
}
}
else if (typeof crypto !== 'undefined' && 'webcrypto' in crypto) {
const name = 'NodeJS ^15';
const nodeCrypto = crypto.webcrypto;
// @ts-ignore
(0, pkijs_1.setEngine)(name, new pkijs_1.CryptoEngine({ name, crypto: nodeCrypto }));
}
else {
// @ts-ignore
const name = 'crypto';
(0, pkijs_1.setEngine)(name, new pkijs_1.CryptoEngine({ name, crypto: (0, ssi_sdk_ext_key_utils_1.globalCrypto)(false) }));
}
};
defaultCryptoEngine();
// We register the services with the mDL/mdoc library. Please note that the context is not passed in, meaning we cannot sign by default.
DefaultCallbacks.setCoseCryptoDefault(new CoseCryptoService());
DefaultCallbacks.setX509Default(new X509CallbackService());
//# sourceMappingURL=index.js.map