node-webcrypto-p11
Version:
A WebCrypto Polyfill built on PKCS11
1,408 lines (1,387 loc) • 120 kB
JavaScript
/*!
* The MIT License (MIT)
*
* Copyright (c) 2020 Peculiar Ventures, LLC
*
* See the full version of the license https://github.com/PeculiarVentures/node-webcrypto-p11/blob/master/LICENSE
*/
'use strict';
var tslib = require('tslib');
var core = require('webcrypto-core');
var graphene = require('graphene-pk11');
var pkcs11 = require('pkcs11js');
var pvtsutils = require('pvtsutils');
var x509 = require('@peculiar/x509');
var asn1Schema = require('@peculiar/asn1-schema');
var asnX509 = require('@peculiar/asn1-x509');
var crypto = require('crypto');
var jsonSchema = require('@peculiar/json-schema');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var core__namespace = /*#__PURE__*/_interopNamespaceDefault(core);
var graphene__namespace = /*#__PURE__*/_interopNamespaceDefault(graphene);
var pkcs11__namespace = /*#__PURE__*/_interopNamespaceDefault(pkcs11);
var pvtsutils__namespace = /*#__PURE__*/_interopNamespaceDefault(pvtsutils);
var x509__namespace = /*#__PURE__*/_interopNamespaceDefault(x509);
var asn1Schema__namespace = /*#__PURE__*/_interopNamespaceDefault(asn1Schema);
var asnX509__namespace = /*#__PURE__*/_interopNamespaceDefault(asnX509);
var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
var jsonSchema__namespace = /*#__PURE__*/_interopNamespaceDefault(jsonSchema);
class CryptoKey extends core__namespace.CryptoKey {
static defaultKeyAlgorithm() {
const alg = {
label: "",
name: "",
sensitive: false,
token: false,
};
return alg;
}
static getID(p11Key) {
let name;
switch (p11Key.class) {
case graphene__namespace.ObjectClass.PRIVATE_KEY:
name = "private";
break;
case graphene__namespace.ObjectClass.PUBLIC_KEY:
name = "public";
break;
case graphene__namespace.ObjectClass.SECRET_KEY:
name = "secret";
break;
default:
throw new Error(`Unsupported Object type '${graphene__namespace.ObjectClass[p11Key.class]}'`);
}
return `${name}-${p11Key.handle.toString("hex")}-${p11Key.id.toString("hex")}`;
}
get key() {
return this.p11Object.toType();
}
constructor(key, alg, usages) {
super();
this.type = "secret";
this.extractable = false;
this.usages = [];
this.p11Object = key;
switch (key.class) {
case graphene__namespace.ObjectClass.PUBLIC_KEY:
this.initPublicKey(key.toType());
break;
case graphene__namespace.ObjectClass.PRIVATE_KEY:
this.initPrivateKey(key.toType());
break;
case graphene__namespace.ObjectClass.SECRET_KEY:
this.initSecretKey(key.toType());
break;
default:
throw new core__namespace.CryptoError(`Wrong incoming session object '${graphene__namespace.ObjectClass[key.class]}'`);
}
const { name, ...defaultAlg } = CryptoKey.defaultKeyAlgorithm();
this.algorithm = { ...alg, ...defaultAlg };
this.id = CryptoKey.getID(key);
if (usages) {
this.usages = usages;
}
try {
this.algorithm.label = key.label;
}
catch { }
try {
this.algorithm.token = key.token;
}
catch { }
try {
if (key instanceof graphene__namespace.PrivateKey || key instanceof graphene__namespace.SecretKey) {
this.algorithm.sensitive = key.get("sensitive");
}
}
catch { }
this.onAssign();
}
toJSON() {
return {
algorithm: this.algorithm,
type: this.type,
usages: this.usages,
extractable: this.extractable,
};
}
initPrivateKey(key) {
this.p11Object = key;
this.type = "private";
this.alwaysAuthenticate = key.alwaysAuthenticate;
try {
this.extractable = key.extractable;
}
catch {
this.extractable = false;
}
this.usages = [];
if (key.decrypt) {
this.usages.push("decrypt");
}
if (key.derive) {
this.usages.push("deriveKey");
this.usages.push("deriveBits");
}
if (key.sign) {
this.usages.push("sign");
}
if (key.unwrap) {
this.usages.push("unwrapKey");
}
}
initPublicKey(key) {
this.p11Object = key;
this.type = "public";
this.extractable = true;
if (key.encrypt) {
this.usages.push("encrypt");
}
if (key.verify) {
this.usages.push("verify");
}
if (key.wrap) {
this.usages.push("wrapKey");
}
}
initSecretKey(key) {
this.p11Object = key;
this.type = "secret";
try {
this.extractable = key.extractable;
}
catch {
this.extractable = false;
}
if (key.sign) {
this.usages.push("sign");
}
if (key.verify) {
this.usages.push("verify");
}
if (key.encrypt) {
this.usages.push("encrypt");
}
if (key.decrypt) {
this.usages.push("decrypt");
}
if (key.wrap) {
this.usages.push("wrapKey");
}
if (key.unwrap) {
this.usages.push("unwrapKey");
}
if (key.derive) {
this.usages.push("deriveKey");
this.usages.push("deriveBits");
}
}
onAssign() {
}
}
class Assert {
static isSession(data) {
if (!(data instanceof graphene__namespace.Session)) {
throw new TypeError("PKCS#11 session is not initialized");
}
}
static isModule(data) {
if (!(data instanceof graphene__namespace.Module)) {
throw new TypeError("PKCS#11 module is not initialized");
}
}
static isCryptoKey(data) {
if (!(data instanceof CryptoKey)) {
throw new TypeError("Object is not an instance of PKCS#11 CryptoKey");
}
}
static requiredParameter(parameter, parameterName) {
if (!parameter) {
throw new Error(`Absent mandatory parameter "${parameterName}"`);
}
}
}
class Pkcs11Object {
static assertStorage(obj) {
if (!obj) {
throw new TypeError("PKCS#11 object is empty");
}
}
constructor(object) {
this.p11Object = object;
}
}
class CryptoCertificate extends Pkcs11Object {
static getID(p11Object) {
let type;
let id;
if (p11Object instanceof graphene__namespace.Data) {
type = "request";
id = p11Object.objectId;
}
else if (p11Object instanceof graphene__namespace.X509Certificate) {
type = "x509";
id = p11Object.id;
}
if (!type || !id) {
throw new Error("Unsupported PKCS#11 object");
}
return `${type}-${p11Object.handle.toString("hex")}-${id.toString("hex")}`;
}
get id() {
Pkcs11Object.assertStorage(this.p11Object);
return CryptoCertificate.getID(this.p11Object);
}
get token() {
try {
Pkcs11Object.assertStorage(this.p11Object);
return this.p11Object.token;
}
catch { }
return false;
}
get sensitive() {
return false;
}
get label() {
try {
Pkcs11Object.assertStorage(this.p11Object);
return this.p11Object.label;
}
catch { }
return "";
}
constructor(crypto) {
super();
this.type = "x509";
this.crypto = crypto;
}
async computeID() {
let id = this.publicKey.p11Object.id;
const indexes = await this.crypto.keyStorage.keys();
if (!indexes.some(o => o.split("-")[2] === id.toString("hex"))) {
let certKeyRaw;
try {
certKeyRaw = await this.crypto.subtle.exportKey("spki", this.publicKey);
}
catch {
return id;
}
for (const index of indexes) {
const [type] = index.split("-");
if (type !== "public") {
continue;
}
let keyRaw;
try {
const key = await this.crypto.keyStorage.getItem(index);
keyRaw = await this.crypto.subtle.exportKey("spki", key);
if (pvtsutils__namespace.BufferSourceConverter.isEqual(keyRaw, certKeyRaw)) {
id = key.p11Object.id;
break;
}
}
catch {
continue;
}
}
}
return id;
}
}
class ParserError extends Error {
constructor(message) {
super(message);
this.name = "ParserError";
}
}
class X509CertificateRequest extends CryptoCertificate {
constructor() {
super(...arguments);
this.type = "request";
}
get subjectName() {
var _a;
return (_a = this.getData()) === null || _a === void 0 ? void 0 : _a.subject;
}
get value() {
Pkcs11Object.assertStorage(this.p11Object);
return new Uint8Array(this.p11Object.value).buffer;
}
async importCert(data, algorithm, keyUsages) {
const array = new Uint8Array(data).buffer;
this.parse(array);
const { token, label, sensitive, ...keyAlg } = algorithm;
this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto);
const id = await this.computeID();
const template = this.crypto.templateBuilder.build({
action: "import",
type: "request",
attributes: {
id,
label: algorithm.label || "X509 Request",
token: !!(algorithm.token),
},
});
template.value = Buffer.from(data);
this.p11Object = this.crypto.session.create(template).toType();
}
async exportCert() {
return this.value;
}
toJSON() {
return {
publicKey: this.publicKey.toJSON(),
subjectName: this.subjectName,
type: this.type,
value: pvtsutils__namespace.Convert.ToBase64Url(this.value),
};
}
async exportKey(algorithm, usages) {
if (!this.publicKey) {
const publicKeyID = this.id.replace(/\w+-\w+-/i, "");
const keyIndexes = await this.crypto.keyStorage.keys();
for (const keyIndex of keyIndexes) {
const parts = keyIndex.split("-");
if (parts[0] === "public" && parts[2] === publicKeyID) {
if (algorithm && usages) {
this.publicKey = await this.crypto.keyStorage.getItem(keyIndex, algorithm, true, usages);
}
else {
this.publicKey = await this.crypto.keyStorage.getItem(keyIndex);
}
break;
}
}
if (!this.publicKey) {
if (algorithm && usages) {
this.publicKey = await this.getData().publicKey.export(algorithm, usages, this.crypto);
}
else {
this.publicKey = await this.getData().publicKey.export(this.crypto);
}
}
}
return this.publicKey;
}
parse(data) {
try {
this.csr = new x509__namespace.Pkcs10CertificateRequest(data);
}
catch {
throw new ParserError("Cannot parse PKCS10 certificate request");
}
}
getData() {
if (!this.csr) {
this.parse(this.value);
}
return this.csr;
}
}
class X509Certificate extends CryptoCertificate {
constructor() {
super(...arguments);
this.type = "x509";
}
get serialNumber() {
return this.getData().serialNumber;
}
get notBefore() {
return this.getData().notBefore;
}
get notAfter() {
return this.getData().notAfter;
}
get issuerName() {
return this.getData().issuer;
}
get subjectName() {
return this.getData().subject;
}
get value() {
Pkcs11Object.assertStorage(this.p11Object);
return new Uint8Array(this.p11Object.value).buffer;
}
async importCert(data, algorithm, keyUsages) {
const array = new Uint8Array(data);
this.parse(array.buffer);
const { token, label, sensitive, ...keyAlg } = algorithm;
this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto);
const id = await this.computeID();
const certLabel = this.getName();
const template = this.crypto.templateBuilder.build({
action: "import",
type: "x509",
attributes: {
id,
label: algorithm.label || certLabel,
token: !!(algorithm.token),
},
});
template.value = Buffer.from(data);
const asn = asn1Schema__namespace.AsnConvert.parse(data, asnX509__namespace.Certificate);
template.serial = Buffer.from(asn1Schema__namespace.AsnConvert.serialize(asn1Schema__namespace.AsnIntegerArrayBufferConverter.toASN(asn.tbsCertificate.serialNumber)));
template.subject = Buffer.from(asn1Schema__namespace.AsnConvert.serialize(asn.tbsCertificate.subject));
template.issuer = Buffer.from(asn1Schema__namespace.AsnConvert.serialize(asn.tbsCertificate.issuer));
this.p11Object = this.crypto.session.create(template).toType();
}
async exportCert() {
return this.value;
}
toJSON() {
return {
publicKey: this.publicKey.toJSON(),
notBefore: this.notBefore,
notAfter: this.notAfter,
subjectName: this.subjectName,
issuerName: this.issuerName,
serialNumber: this.serialNumber,
type: this.type,
value: pvtsutils__namespace.Convert.ToBase64Url(this.value),
};
}
async exportKey(algorithm, usages) {
if (!this.publicKey) {
const publicKeyID = this.id.replace(/\w+-\w+-/i, "");
const keyIndexes = await this.crypto.keyStorage.keys();
for (const keyIndex of keyIndexes) {
const parts = keyIndex.split("-");
if (parts[0] === "public" && parts[2] === publicKeyID) {
if (algorithm && usages) {
this.publicKey = await this.crypto.keyStorage.getItem(keyIndex, algorithm, usages);
}
else {
this.publicKey = await this.crypto.keyStorage.getItem(keyIndex);
}
break;
}
}
if (!this.publicKey) {
if (algorithm && usages) {
this.publicKey = await this.getData().publicKey.export(algorithm, usages, this.crypto);
}
else {
this.publicKey = await this.getData().publicKey.export(this.crypto);
}
}
}
return this.publicKey;
}
parse(data) {
try {
this.x509 = new x509__namespace.X509Certificate(data);
}
catch {
throw new ParserError("Cannot parse X509 certificate");
}
}
getData() {
if (!this.x509) {
this.parse(this.value);
}
return this.x509;
}
getName() {
const name = new x509__namespace.Name(this.subjectName).toJSON();
for (const item of name) {
const commonName = item.CN;
if (commonName && commonName.length > 0) {
return commonName[0];
}
}
return this.subjectName;
}
}
const TEMPLATES = [
{ class: graphene__namespace.ObjectClass.CERTIFICATE, certType: graphene__namespace.CertificateType.X_509, token: true },
{ class: graphene__namespace.ObjectClass.DATA, token: true, label: "X509 Request" },
];
class CertificateStorage {
constructor(crypto) {
this.crypto = crypto;
}
async getValue(key) {
const storageObject = this.getItemById(key);
if (storageObject instanceof graphene__namespace.X509Certificate) {
const x509Object = storageObject.toType();
const x509 = new X509Certificate(this.crypto);
x509.p11Object = x509Object;
return x509.exportCert();
}
else if (storageObject instanceof graphene__namespace.Data) {
const x509Object = storageObject.toType();
const x509request = new X509CertificateRequest(this.crypto);
x509request.p11Object = x509Object;
return x509request.exportCert();
}
return null;
}
async indexOf(item) {
var _a;
if (item instanceof CryptoCertificate && ((_a = item.p11Object) === null || _a === void 0 ? void 0 : _a.token)) {
return CryptoCertificate.getID(item.p11Object);
}
return null;
}
async keys() {
const keys = [];
TEMPLATES.forEach((template) => {
this.crypto.session.find(template, (obj) => {
const item = obj.toType();
const id = CryptoCertificate.getID(item);
keys.push(id);
});
});
return keys;
}
async clear() {
const objects = [];
TEMPLATES.forEach((template) => {
this.crypto.session.find(template, (obj) => {
objects.push(obj);
});
});
objects.forEach((obj) => {
obj.destroy();
});
}
async hasItem(item) {
const sessionObject = this.getItemById(item.id);
return !!sessionObject;
}
async getItem(index, algorithm, usages) {
const storageObject = this.getItemById(index);
if (storageObject instanceof graphene__namespace.X509Certificate) {
const x509Object = storageObject.toType();
const x509 = new X509Certificate(this.crypto);
x509.p11Object = x509Object;
if (algorithm && usages) {
await x509.exportKey(algorithm, usages);
}
else {
await x509.exportKey();
}
return x509;
}
else if (storageObject instanceof graphene__namespace.Data) {
const x509Object = storageObject.toType();
const x509request = new X509CertificateRequest(this.crypto);
x509request.p11Object = x509Object;
if (algorithm && usages) {
await x509request.exportKey(algorithm, usages);
}
else {
await x509request.exportKey();
}
return x509request;
}
else {
return null;
}
}
async removeItem(key) {
const sessionObject = this.getItemById(key);
if (sessionObject) {
sessionObject.destroy();
}
}
async setItem(data) {
if (!(data instanceof CryptoCertificate)) {
throw new Error("Incoming data is not PKCS#11 CryptoCertificate");
}
Pkcs11Object.assertStorage(data.p11Object);
if (!data.p11Object.token) {
const template = this.crypto.templateBuilder.build({
action: "copy",
type: data.type,
attributes: {
token: true,
}
});
const obj = this.crypto.session.copy(data.p11Object, template);
return CryptoCertificate.getID(obj.toType());
}
else {
return data.id;
}
}
async exportCert(format, cert) {
switch (format) {
case "pem": {
throw Error("PEM format is not implemented");
}
case "raw": {
return cert.exportCert();
}
default:
throw new Error(`Unsupported format in use ${format}`);
}
}
async importCert(format, data, algorithm, usages) {
let rawData;
let rawType = null;
switch (format) {
case "pem":
if (typeof data !== "string") {
throw new TypeError("data: Is not type string");
}
if (core__namespace.PemConverter.isCertificate(data)) {
rawType = "x509";
}
else if (core__namespace.PemConverter.isCertificateRequest(data)) {
rawType = "request";
}
else {
throw new core__namespace.OperationError("data: Is not correct PEM data. Must be Certificate or Certificate Request");
}
rawData = core__namespace.PemConverter.toArrayBuffer(data);
break;
case "raw":
if (!pvtsutils__namespace.BufferSourceConverter.isBufferSource(data)) {
throw new TypeError("data: Is not type ArrayBuffer or ArrayBufferView");
}
rawData = pvtsutils__namespace.BufferSourceConverter.toArrayBuffer(data);
break;
default:
throw new TypeError("format: Is invalid value. Must be 'raw', 'pem'");
}
switch (rawType) {
case "x509": {
const x509 = new X509Certificate(this.crypto);
await x509.importCert(Buffer.from(rawData), algorithm, usages);
return x509;
}
case "request": {
const request = new X509CertificateRequest(this.crypto);
await request.importCert(Buffer.from(rawData), algorithm, usages);
return request;
}
default: {
try {
const x509 = new X509Certificate(this.crypto);
await x509.importCert(Buffer.from(rawData), algorithm, usages);
return x509;
}
catch (e) {
if (!(e instanceof ParserError)) {
throw e;
}
}
try {
const request = new X509CertificateRequest(this.crypto);
await request.importCert(Buffer.from(rawData), algorithm, usages);
return request;
}
catch (e) {
if (!(e instanceof ParserError)) {
throw e;
}
}
throw new core__namespace.OperationError("Cannot parse Certificate or Certificate Request from incoming ASN1");
}
}
}
getItemById(id) {
let object = null;
TEMPLATES.forEach((template) => {
this.crypto.session.find(template, (obj) => {
const item = obj.toType();
if (id === CryptoCertificate.getID(item)) {
object = item;
return false;
}
});
});
return object;
}
}
const ID_DIGEST = "SHA-1";
function GUID() {
return crypto__namespace.randomBytes(20);
}
function b64UrlDecode(b64url) {
return Buffer.from(pvtsutils__namespace.Convert.FromBase64Url(b64url));
}
function prepareData(data) {
return Buffer.from(pvtsutils__namespace.BufferSourceConverter.toArrayBuffer(data));
}
function isHashedAlgorithm(data) {
return data instanceof Object
&& "name" in data
&& "hash" in data;
}
function isCryptoKeyPair(data) {
return data instanceof Object
&& "privateKey" in data
&& "publicKey" in data;
}
function prepareAlgorithm(algorithm) {
if (typeof algorithm === "string") {
return {
name: algorithm,
};
}
if (isHashedAlgorithm(algorithm)) {
const preparedAlgorithm = { ...algorithm };
preparedAlgorithm.hash = prepareAlgorithm(algorithm.hash);
return preparedAlgorithm;
}
return { ...algorithm };
}
function digest(algorithm, data) {
const hash = crypto__namespace.createHash(algorithm.replace("-", ""));
hash.update(prepareData(Buffer.from(pvtsutils__namespace.BufferSourceConverter.toArrayBuffer(data))));
return hash.digest();
}
function calculateProviderID(slot) {
const str = slot.manufacturerID + slot.slotDescription + slot.getToken().serialNumber + slot.handle.toString("hex");
return digest(ID_DIGEST, Buffer.from(str)).toString("hex");
}
function getProviderInfo(slot) {
const slots = slot.module.getSlots(true);
let index = -1;
for (let i = 0; i < slots.length; i++) {
if (slots.items(i).handle.equals(slot.handle)) {
index = i;
break;
}
}
const token = slot.getToken();
const provider = {
id: calculateProviderID(slot),
slot: index,
name: token.label,
reader: slot.slotDescription,
serialNumber: slot.getToken().serialNumber,
algorithms: [],
isRemovable: !!(slot.flags & graphene__namespace.SlotFlag.REMOVABLE_DEVICE),
isHardware: !!(slot.flags & graphene__namespace.SlotFlag.HW_SLOT),
};
const algorithms = slot.getMechanisms();
for (let i = 0; i < algorithms.length; i++) {
const algorithm = algorithms.tryGetItem(i);
if (!algorithm) {
continue;
}
let algName = "";
switch (algorithm.name) {
case "SHA_1":
algName = "SHA-1";
break;
case "SHA256":
algName = "SHA-256";
break;
case "SHA384":
algName = "SHA-384";
break;
case "SHA512":
algName = "SHA-512";
break;
case "RSA_PKCS":
case "SHA1_RSA_PKCS":
case "SHA256_RSA_PKCS":
case "SHA384_RSA_PKCS":
case "SHA512_RSA_PKCS":
algName = "RSASSA-PKCS1-v1_5";
break;
case "SHA1_RSA_PSS":
case "SHA256_RSA_PSS":
case "SHA384_RSA_PSS":
case "SHA512_RSA_PSS":
algName = "RSA-PSS";
break;
case "SHA1_RSA_PKCS_PSS":
case "SHA256_RSA_PKCS_PSS":
case "SHA384_RSA_PKCS_PSS":
case "SHA512_RSA_PKCS_PSS":
algName = "RSA-PSS";
break;
case "RSA_PKCS_OAEP":
algName = "RSA-OAEP";
break;
case "ECDSA":
case "ECDSA_SHA1":
case "ECDSA_SHA256":
case "ECDSA_SHA384":
case "ECDSA_SHA512":
algName = "ECDSA";
break;
case "ECDH1_DERIVE":
algName = "ECDH";
break;
case "AES_CBC_PAD":
algName = "AES-CBC";
break;
case "AES_ECB":
case "AES_ECB_PAD":
algName = "AES-ECB";
break;
case "AES_GCM_PAD":
algName = "AES-GCM";
break;
case "AES_KEY_WRAP_PAD":
algName = "AES-KW";
break;
}
if (algName && !provider.algorithms.some((alg) => alg === algName)) {
provider.algorithms.push(algName);
}
}
return provider;
}
async function alwaysAuthenticate(key, container, operation) {
if (key.key instanceof graphene__namespace.PrivateKey && key.key.alwaysAuthenticate) {
if (!container.onAlwaysAuthenticate) {
throw new core__namespace.CryptoError("Crypto key requires re-authentication, but Crypto doesn't have 'onAlwaysAuthenticate' method");
}
const pin = await container.onAlwaysAuthenticate(key, container, operation);
if (pin) {
container.session.login(pin, graphene__namespace.UserType.CONTEXT_SPECIFIC);
}
}
}
class AesCryptoKey extends CryptoKey {
onAssign() {
this.algorithm.length = this.key.get("valueLen") << 3;
}
}
class AesCrypto {
constructor(container) {
this.container = container;
}
async generateKey(algorithm, extractable, keyUsages) {
return new Promise((resolve, reject) => {
const template = this.container.templateBuilder.build({
action: "generate",
type: "secret",
attributes: {
id: GUID(),
label: algorithm.label || `AES-${algorithm.length}`,
token: algorithm.token,
sensitive: algorithm.sensitive,
extractable,
usages: keyUsages,
},
});
template.keyType = graphene__namespace.KeyType.AES;
template.valueLen = algorithm.length >> 3;
this.container.session.generateKey(graphene__namespace.KeyGenMechanism.AES, template, (err, aesKey) => {
try {
if (err) {
reject(new core__namespace.CryptoError(`Aes: Can not generate new key\n${err.message}`));
}
else {
if (!aesKey) {
throw new Error("Cannot get key from callback function");
}
resolve(new AesCryptoKey(aesKey, algorithm));
}
}
catch (e) {
reject(e);
}
});
});
}
async exportKey(format, key) {
const template = key.key.getAttribute({ value: null, valueLen: null });
switch (format.toLowerCase()) {
case "jwk": {
const aes = /AES-(\w+)/.exec(key.algorithm.name)[1];
const jwk = {
kty: "oct",
k: pvtsutils__namespace.Convert.ToBase64Url(template.value),
alg: `A${template.valueLen * 8}${aes}`,
ext: true,
key_ops: key.usages,
};
return jwk;
}
case "raw":
return new Uint8Array(template.value).buffer;
default:
throw new core__namespace.OperationError("format: Must be 'jwk' or 'raw'");
}
}
async importKey(format, keyData, algorithm, extractable, keyUsages) {
const formatLower = format.toLowerCase();
if (formatLower !== "jwk" && formatLower !== "raw") {
throw new core__namespace.OperationError("format: Must be 'jwk' or 'raw'");
}
if (formatLower === "jwk") {
const jwk = keyData;
if (!jwk.k) {
throw new core__namespace.OperationError("jwk.k: Cannot get required property");
}
keyData = pvtsutils__namespace.Convert.FromBase64Url(jwk.k);
}
const value = keyData;
if (value.byteLength !== 16 && value.byteLength !== 24 && value.byteLength !== 32) {
throw new core__namespace.OperationError("keyData: Is wrong key length");
}
const aesAlg = {
...AesCryptoKey.defaultKeyAlgorithm(),
...algorithm,
length: value.byteLength * 8,
};
const template = this.container.templateBuilder.build({
action: "import",
type: "secret",
attributes: {
id: GUID(),
label: algorithm.label || `AES-${aesAlg.length}`,
token: algorithm.token,
sensitive: algorithm.sensitive,
extractable,
usages: keyUsages,
},
});
template.keyType = graphene__namespace.KeyType.AES;
template.value = Buffer.from(value);
const sessionObject = this.container.session.create(template);
const key = new AesCryptoKey(sessionObject.toType(), aesAlg);
return key;
}
async encrypt(padding, algorithm, key, data) {
if (padding) {
const blockLength = 16;
const mod = blockLength - (data.byteLength % blockLength);
const pad = Buffer.alloc(mod);
pad.fill(mod);
data = Buffer.concat([Buffer.from(data), pad]);
}
return new Promise((resolve, reject) => {
const enc = Buffer.alloc(this.getOutputBufferSize(key.algorithm, true, data.byteLength));
const mechanism = this.wc2pk11(algorithm);
this.container.session.createCipher(mechanism, key.key)
.once(Buffer.from(data), enc, (err, data2) => {
if (err) {
reject(err);
}
else {
resolve(new Uint8Array(data2).buffer);
}
});
});
}
async decrypt(padding, algorithm, key, data) {
const dec = await new Promise((resolve, reject) => {
const buf = Buffer.alloc(this.getOutputBufferSize(key.algorithm, false, data.length));
const mechanism = this.wc2pk11(algorithm);
this.container.session.createDecipher(mechanism, key.key)
.once(Buffer.from(data), buf, (err, data2) => {
if (err) {
reject(err);
}
else {
resolve(data2);
}
});
});
if (padding) {
const paddingLength = dec[dec.length - 1];
const res = new Uint8Array(dec.slice(0, dec.length - paddingLength));
return res.buffer;
}
else {
return new Uint8Array(dec).buffer;
}
}
isAesGCM(algorithm) {
return algorithm.name.toUpperCase() === "AES-GCM";
}
isAesCBC(algorithm) {
return algorithm.name.toUpperCase() === "AES-CBC";
}
isAesECB(algorithm) {
return algorithm.name.toUpperCase() === "AES-ECB";
}
wc2pk11(algorithm) {
const session = this.container.session;
if (this.isAesGCM(algorithm)) {
const aad = algorithm.additionalData ? prepareData(algorithm.additionalData) : undefined;
let AesGcmParamsClass = graphene__namespace.AesGcmParams;
if (session.slot.module.cryptokiVersion.major >= 2 &&
session.slot.module.cryptokiVersion.minor >= 40) {
AesGcmParamsClass = graphene__namespace.AesGcm240Params;
}
const params = new AesGcmParamsClass(prepareData(algorithm.iv), aad, algorithm.tagLength || 128);
return { name: "AES_GCM", params };
}
else if (this.isAesCBC(algorithm)) {
return { name: "AES_CBC_PAD", params: prepareData(algorithm.iv) };
}
else if (this.isAesECB(algorithm)) {
return { name: "AES_ECB", params: null };
}
else {
throw new core__namespace.OperationError("Unrecognized algorithm name");
}
}
getOutputBufferSize(keyAlg, enc, dataSize) {
const len = keyAlg.length >> 3;
if (enc) {
return (Math.ceil(dataSize / len) * len) + len;
}
else {
return dataSize;
}
}
}
class AesCbcProvider extends core__namespace.AesCbcProvider {
constructor(container) {
super();
this.container = container;
this.crypto = new AesCrypto(container);
}
async onGenerateKey(algorithm, extractable, keyUsages) {
const key = await this.crypto.generateKey({ ...algorithm, name: this.name }, extractable, keyUsages);
return key;
}
async onEncrypt(algorithm, key, data) {
return this.crypto.encrypt(false, algorithm, key, new Uint8Array(data));
}
async onDecrypt(algorithm, key, data) {
return this.crypto.decrypt(false, algorithm, key, new Uint8Array(data));
}
async onExportKey(format, key) {
return this.crypto.exportKey(format, key);
}
async onImportKey(format, keyData, algorithm, extractable, keyUsages) {
return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
}
checkCryptoKey(key, keyUsage) {
super.checkCryptoKey(key, keyUsage);
Assert.isCryptoKey(key);
}
}
class AesEcbProvider extends core__namespace.ProviderCrypto {
constructor(container) {
super();
this.container = container;
this.name = "AES-ECB";
this.usages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"];
this.crypto = new AesCrypto(container);
}
async onGenerateKey(algorithm, extractable, keyUsages) {
const key = await this.crypto.generateKey({ ...algorithm, name: this.name }, extractable, keyUsages);
return key;
}
async onEncrypt(algorithm, key, data) {
return this.crypto.encrypt(true, algorithm, key, new Uint8Array(data));
}
async onDecrypt(algorithm, key, data) {
return this.crypto.decrypt(true, algorithm, key, new Uint8Array(data));
}
async onExportKey(format, key) {
return this.crypto.exportKey(format, key);
}
async onImportKey(format, keyData, algorithm, extractable, keyUsages) {
return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
}
checkCryptoKey(key, keyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof CryptoKey)) {
throw new TypeError("key: Is not a PKCS11 CryptoKey");
}
}
}
class AesGcmProvider extends core__namespace.AesGcmProvider {
constructor(container) {
super();
this.container = container;
this.crypto = new AesCrypto(container);
}
async onGenerateKey(algorithm, extractable, keyUsages) {
const key = await this.crypto.generateKey({ ...algorithm, name: this.name }, extractable, keyUsages);
return key;
}
async onEncrypt(algorithm, key, data) {
return this.crypto.encrypt(false, algorithm, key, new Uint8Array(data));
}
async onDecrypt(algorithm, key, data) {
return this.crypto.decrypt(false, algorithm, key, new Uint8Array(data));
}
async onExportKey(format, key) {
return this.crypto.exportKey(format, key);
}
async onImportKey(format, keyData, algorithm, extractable, keyUsages) {
return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
}
checkCryptoKey(key, keyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof CryptoKey)) {
throw new TypeError("key: Is not a PKCS11 CryptoKey");
}
}
}
class EcCryptoKey extends CryptoKey {
onAssign() {
if (!this.algorithm.namedCurve) {
try {
const paramsECDSA = asn1Schema__namespace.AsnConvert.parse(this.key.get("paramsECDSA"), core__namespace.asn1.ObjectIdentifier);
try {
const pointEC = core__namespace.EcCurves.get(paramsECDSA.value);
this.algorithm.namedCurve = pointEC.name;
}
catch {
this.algorithm.namedCurve = paramsECDSA.value;
}
}
catch { }
}
}
}
const id_ecPublicKey = "1.2.840.10045.2.1";
class EcCrypto {
constructor(container) {
this.container = container;
this.publicKeyUsages = ["verify"];
this.privateKeyUsages = ["sign", "deriveKey", "deriveBits"];
}
async generateKey(algorithm, extractable, keyUsages) {
return new Promise((resolve, reject) => {
const attrs = {
id: GUID(),
label: algorithm.label,
token: algorithm.token,
sensitive: algorithm.sensitive,
extractable,
usages: keyUsages,
};
if (algorithm.alwaysAuthenticate) {
attrs.alwaysAuthenticate = true;
}
const privateTemplate = this.createTemplate({
action: "generate",
type: "private",
attributes: attrs,
});
const publicTemplate = this.createTemplate({
action: "generate",
type: "public",
attributes: attrs,
});
publicTemplate.paramsEC = Buffer.from(core__namespace.EcCurves.get(algorithm.namedCurve).raw);
this.container.session.generateKeyPair(graphene__namespace.KeyGenMechanism.EC, publicTemplate, privateTemplate, (err, keys) => {
try {
if (err) {
reject(err);
}
else {
if (!keys) {
throw new Error("Cannot get keys from callback function");
}
const wcKeyPair = {
privateKey: new EcCryptoKey(keys.privateKey, algorithm),
publicKey: new EcCryptoKey(keys.publicKey, algorithm),
};
resolve(wcKeyPair);
}
}
catch (e) {
reject(e);
}
});
});
}
async exportKey(format, key) {
switch (format.toLowerCase()) {
case "jwk": {
if (key.type === "private") {
return this.exportJwkPrivateKey(key);
}
else {
return this.exportJwkPublicKey(key);
}
}
case "pkcs8": {
const jwk = await this.exportJwkPrivateKey(key);
return this.jwk2pkcs(jwk);
}
case "spki": {
const jwk = await this.exportJwkPublicKey(key);
return this.jwk2spki(jwk);
}
case "raw": {
const jwk = await this.exportJwkPublicKey(key);
if (key.algorithm.namedCurve === "X25519") {
return pvtsutils__namespace.Convert.FromBase64Url(jwk.x);
}
else {
const publicKey = jsonSchema__namespace.JsonParser.fromJSON(jwk, { targetSchema: core__namespace.asn1.EcPublicKey });
return publicKey.value;
}
}
default:
throw new core__namespace.OperationError("format: Must be 'jwk', 'raw', pkcs8' or 'spki'");
}
}
async importKey(format, keyData, algorithm, extractable, keyUsages) {
switch (format.toLowerCase()) {
case "jwk": {
const jwk = keyData;
if (jwk.d) {
return this.importJwkPrivateKey(jwk, algorithm, extractable, keyUsages);
}
else {
return this.importJwkPublicKey(jwk, algorithm, extractable, keyUsages);
}
}
case "spki": {
const jwk = this.spki2jwk(keyData);
return this.importJwkPublicKey(jwk, algorithm, extractable, keyUsages);
}
case "pkcs8": {
const jwk = this.pkcs2jwk(keyData);
return this.importJwkPrivateKey(jwk, algorithm, extractable, keyUsages);
}
case "raw": {
const curve = core__namespace.EcCurves.get(algorithm.namedCurve);
const ecPoint = core__namespace.EcUtils.decodePoint(keyData, curve.size);
const jwk = {
kty: "EC",
crv: algorithm.namedCurve,
x: pvtsutils__namespace.Convert.ToBase64Url(ecPoint.x),
};
if (ecPoint.y) {
jwk.y = pvtsutils__namespace.Convert.ToBase64Url(ecPoint.y);
}
return this.importJwkPublicKey(jwk, algorithm, extractable, keyUsages);
}
default:
throw new core__namespace.OperationError("format: Must be 'jwk', 'raw', 'pkcs8' or 'spki'");
}
}
getAlgorithm(p11AlgorithmName) {
const mechanisms = this.container.session.slot.getMechanisms();
let EC;
for (let i = 0; i < mechanisms.length; i++) {
const mechanism = mechanisms.tryGetItem(i);
if (mechanism && (mechanism.name === p11AlgorithmName || mechanism.name === "ECDSA")) {
EC = mechanism.name;
}
}
if (!EC) {
throw new Error(`Cannot get PKCS11 EC mechanism by name '${p11AlgorithmName}'`);
}
return EC;
}
prepareData(hashAlgorithm, data) {
return digest(hashAlgorithm.replace("-", ""), data);
}
importJwkPrivateKey(jwk, algorithm, extractable, keyUsages) {
const template = this.createTemplate({
action: "import",
type: "private",
attributes: {
id: GUID(),
token: algorithm.token,
sensitive: algorithm.sensitive,
label: algorithm.label,
alwaysAuthenticate: algorithm.alwaysAuthenticate,
extractable,
usages: keyUsages,
},
});
template.paramsEC = Buffer.from(core__namespace.EcCurves.get(algorithm.namedCurve).raw);
template.value = b64UrlDecode(jwk.d);
const p11key = this.container.session.create(template).toType();
return new EcCryptoKey(p11key, algorithm);
}
importJwkPublicKey(jwk, algorithm, extractable, keyUsages) {
const namedCurve = core__namespace.EcCurves.get(algorithm.namedCurve);
const template = this.createTemplate({
action: "import",
type: "public",
attributes: {
id: GUID(),
token: algorithm.token,
label: algorithm.label,
extractable,
usages: keyUsages,
}
});
template.paramsEC = Buffer.from(namedCurve.raw);
let pointEc;
if (namedCurve.name === "curve25519") {
pointEc = b64UrlDecode(jwk.x);
}
else {
const point = core__namespace.EcUtils.encodePoint({ x: b64UrlDecode(jwk.x), y: b64UrlDecode(jwk.y) }, namedCurve.size);
const derPoint = asn1Schema__namespace.AsnConvert.serialize(new asn1Schema__namespace.OctetString(point));
pointEc = Buffer.from(derPoint);
}
template.pointEC = pointEc;
const p11key = this.container.session.create(template).toType();
return new EcCryptoKey(p11key, algorithm);
}
exportJwkPublicKey(key) {
const pkey = key.key.getAttribute({
pointEC: null,
});
const curve = core__namespace.EcCurves.get(key.algorithm.namedCurve);
const p11PointEC = pkey.pointEC;
if (!p11PointEC) {
throw new Error("Cannot get required ECPoint attribute");
}
const derEcPoint = asn1Schema__namespace.AsnConvert.parse(p11PointEC, asn1Schema__namespace.OctetString);
const ecPoint = core__namespace.EcUtils.decodePoint(derEcPoint, curve.size);
const jwk = {
kty: "EC",
crv: key.algorithm.namedCurve,
ext: true,
key_ops: key.usages,
x: pvtsutils__namespace.Convert.ToBase64Url(ecPoint.x),
};
if (curve.name !== "curve25519") {
jwk.y = pvtsutils__namespace.Convert.ToBase64Url(ecPoint.y);
}
return jwk;
}
async exportJwkPrivateKey(key) {
const pkey = key.key.getAttribute({
value: null,
});
const jwk = {
kty: "EC",
crv: key.algorithm.namedCurve,
ext: true,
key_ops: key.usages,
d: pvtsutils__namespace.Convert.ToBase64Url(pkey.value),
};
return jwk;
}
createTemplate(params) {
const template = this.container.templateBuilder.build({
...params,
attributes: {
...params.attributes,
label: params.attributes.label || "EC",
},
});
template.keyType = graphene__namespace.KeyType.EC;
return template;
}
spki2jwk(raw) {
const keyInfo = asn1Schema__namespace.AsnParser.parse(raw, core__namespace.asn1.PublicKeyInfo);
if (keyInfo.publicKeyAlgorithm.algorithm !== id_ecPublicKey) {
throw new Error("SPKI is not EC public key");
}
const namedCurveId = asn1Schema__namespace.AsnParser.parse(keyInfo.publicKeyAlgorithm.paramet