add pkcs11

This commit is contained in:
microshine 2022-05-24 00:09:26 +03:00
parent 387f0d928d
commit 1578e6ceeb
56 changed files with 4975 additions and 0 deletions

11
packages/pkcs11/README.md Normal file
View File

@ -0,0 +1,11 @@
# `@peculiar/webcrypto-pkcs11`
> TODO: description
## Usage
```
const webcryptoPkcs11 = require('@peculiar/webcrypto-pkcs11');
// TODO: DEMONSTRATE API
```

View File

@ -0,0 +1,38 @@
{
"name": "@peculiar/webcrypto-pkcs11",
"version": "3.0.0",
"description": "Now Im the model of a modern major general / The venerated Virginian veteran whose men are all / Lining up, to put me up on a pedestal / Writin letters to relatives / Embellishin my elegance and eloquence / But the elephant is in the room / The truth is in ya face when ya hear the British cannons go / BOOM",
"keywords": [],
"author": "microshine <microshine@mail.ru>",
"homepage": "https://github.com/PeculiarVentures/webcrypto/tree/master/packages/webcrypto-pkcs11#readme",
"license": "ISC",
"main": "lib/webcrypto-pkcs11.js",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/PeculiarVentures/webcrypto.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"bugs": {
"url": "https://github.com/PeculiarVentures/webcrypto/issues"
},
"dependencies": {
"@peculiar/webcrypto-core": "^3.0.0",
"@peculiar/webcrypto-types": "^3.0.0",
"@peculiar/x509": "^1.6.4",
"graphene-pk11": "^2.3.0",
"pvtsutils": "^1.3.2",
"tslib": "^2.4.0"
}
}

View File

@ -0,0 +1,44 @@
import * as graphene from "graphene-pk11";
import { CryptoKey } from "./key";
export class Assert {
/**
* Throws exception whenever data is not an instance of Session
* @param data
* @throws TypeError
*/
public static isSession(data: any): asserts data is graphene.Session {
if (!(data instanceof graphene.Session)) {
throw new TypeError("PKCS#11 session is not initialized");
}
}
/**
* Throws exception whenever data is not an instance of Module
* @param data
* @throws TypeError
*/
public static isModule(data: any): asserts data is graphene.Module {
if (!(data instanceof graphene.Module)) {
throw new TypeError("PKCS#11 module is not initialized");
}
}
/**
* Throws exception whenever data is not an instance of PKCS#11 CryptoKey
* @param data
* @throws TypeError
*/
public static isCryptoKey(data: any): asserts data is CryptoKey {
if (!(data instanceof CryptoKey)) {
throw new TypeError("Object is not an instance of PKCS#11 CryptoKey");
}
}
public static requiredParameter(parameter: any, parameterName: string): asserts parameter {
if (!parameter) {
throw new Error(`Absent mandatory parameter \"${parameterName}\"`);
}
}
}

View File

@ -0,0 +1,244 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import * as pvtsutils from "pvtsutils";
import * as certs from "./certs";
import { Crypto } from "./crypto";
import { Pkcs11Object } from "./p11_object";
const TEMPLATES = [
{ class: graphene.ObjectClass.CERTIFICATE, certType: graphene.CertificateType.X_509, token: true },
{ class: graphene.ObjectClass.DATA, token: true, label: "X509 Request" },
];
export interface IGetValue {
/**
* Returns item blob
* @param key Object identifier
*/
getValue(key: string): Promise<ArrayBuffer | null>;
}
export class CertificateStorage implements types.CryptoCertificateStorage, IGetValue {
protected crypto: Crypto;
constructor(crypto: Crypto) {
this.crypto = crypto;
}
public async getValue(key: string): Promise<ArrayBuffer | null> {
const storageObject = this.getItemById(key);
if (storageObject instanceof graphene.X509Certificate) {
const x509Object = storageObject.toType<graphene.X509Certificate>();
const x509 = new certs.X509Certificate(this.crypto);
x509.p11Object = x509Object;
return x509.exportCert();
} else if (storageObject instanceof graphene.Data) {
const x509Object = storageObject.toType<graphene.Data>();
const x509request = new certs.X509CertificateRequest(this.crypto);
x509request.p11Object = x509Object;
return x509request.exportCert();
}
return null;
}
public indexOf(item: types.CryptoCertificate): Promise<string | null>;
public async indexOf(item: certs.CryptoCertificate) {
if (item instanceof certs.CryptoCertificate && item.p11Object?.token) {
return certs.CryptoCertificate.getID(item.p11Object);
}
return null;
}
public async keys() {
const keys: string[] = [];
TEMPLATES.forEach((template) => {
this.crypto.session!.find(template, (obj) => {
const item = obj.toType<any>();
const id = certs.CryptoCertificate.getID(item);
keys.push(id);
});
});
return keys;
}
public async clear() {
const objects: graphene.SessionObject[] = [];
TEMPLATES.forEach((template) => {
this.crypto.session!.find(template, (obj) => {
objects.push(obj);
});
});
objects.forEach((obj) => {
obj.destroy();
});
}
public async hasItem(item: types.CryptoCertificate) {
if (!(item instanceof certs.CryptoCertificate)) {
throw new TypeError(`Parameter 'item' is not of type 'CryptoCertificate'`);
}
const sessionObject = this.getItemById(item.id);
return !!sessionObject;
}
public getItem(index: string): Promise<types.CryptoCertificate>;
public getItem(index: string, algorithm: types.ImportAlgorithms, keyUsages: types.KeyUsage[]): Promise<types.CryptoCertificate>;
public async getItem(index: string, algorithm?: types.Algorithm, usages?: types.KeyUsage[]): Promise<types.CryptoCertificate> {
const storageObject = this.getItemById(index);
if (storageObject instanceof graphene.X509Certificate) {
const x509Object = storageObject.toType<graphene.X509Certificate>();
const x509 = new certs.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.Data) {
const x509Object = storageObject.toType<graphene.Data>();
const x509request = new certs.X509CertificateRequest(this.crypto);
x509request.p11Object = x509Object;
if (algorithm && usages) {
await x509request.exportKey(algorithm, usages);
} else {
await x509request.exportKey();
}
return x509request;
} else {
// @ts-ignore
return null;
}
}
public async removeItem(key: string) {
const sessionObject = this.getItemById(key);
if (sessionObject) {
sessionObject.destroy();
}
}
public async setItem(data: types.CryptoCertificate): Promise<string>;
public async setItem(data: certs.CryptoCertificate) {
if (!(data instanceof certs.CryptoCertificate)) {
throw new Error("Incoming data is not PKCS#11 CryptoCertificate");
}
Pkcs11Object.assertStorage(data.p11Object);
// don't copy object from token
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 certs.CryptoCertificate.getID(obj.toType<any>());
} else {
return data.id;
}
}
public exportCert(format: types.CryptoCertificateFormat, item: types.CryptoCertificate): Promise<ArrayBuffer | string>;
public exportCert(format: "raw", item: types.CryptoCertificate): Promise<ArrayBuffer>;
public exportCert(format: "pem", item: types.CryptoCertificate): Promise<string>;
public async exportCert(format: types.CryptoCertificateFormat, cert: certs.CryptoCertificate): Promise<ArrayBuffer | string> {
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}`);
}
}
public async importCert(format: types.CryptoCertificateFormat, data: types.BufferSource | string, algorithm: certs.Pkcs11ImportAlgorithms, keyUsages: types.KeyUsage[]): Promise<types.CryptoCertificate>;
public async importCert(format: "raw", data: types.BufferSource, algorithm: certs.Pkcs11ImportAlgorithms, keyUsages: types.KeyUsage[]): Promise<types.CryptoCertificate>;
public async importCert(format: "pem", data: string, algorithm: certs.Pkcs11ImportAlgorithms, keyUsages: types.KeyUsage[]): Promise<types.CryptoCertificate>;
public async importCert(format: types.CryptoCertificateFormat, data: types.BufferSource | string, algorithm: certs.Pkcs11ImportAlgorithms, usages: types.KeyUsage[]): Promise<certs.CryptoCertificate> {
let rawData: ArrayBuffer;
let rawType: types.CryptoCertificateType | null = null;
//#region Check
switch (format) {
case "pem":
if (typeof data !== "string") {
throw new TypeError("data: Is not type string");
}
if (core.PemConverter.isCertificate(data)) {
rawType = "x509";
} else if (core.PemConverter.isCertificateRequest(data)) {
rawType = "request";
} else {
throw new core.OperationError("data: Is not correct PEM data. Must be Certificate or Certificate Request");
}
rawData = core.PemConverter.toArrayBuffer(data);
break;
case "raw":
if (!pvtsutils.BufferSourceConverter.isBufferSource(data)) {
throw new TypeError("data: Is not type ArrayBuffer or ArrayBufferView");
}
rawData = pvtsutils.BufferSourceConverter.toArrayBuffer(data);
break;
default:
throw new TypeError("format: Is invalid value. Must be 'raw', 'pem'");
}
//#endregion
switch (rawType) {
case "x509": {
const x509 = new certs.X509Certificate(this.crypto);
await x509.importCert(Buffer.from(rawData), algorithm, usages);
return x509;
}
case "request": {
const request = new certs.X509CertificateRequest(this.crypto);
await request.importCert(Buffer.from(rawData), algorithm, usages);
return request;
}
default: {
try {
const x509 = new certs.X509Certificate(this.crypto);
await x509.importCert(Buffer.from(rawData), algorithm, usages);
return x509;
} catch {
// nothing
}
try {
const request = new certs.X509CertificateRequest(this.crypto);
await request.importCert(Buffer.from(rawData), algorithm, usages);
return request;
} catch {
// nothing
}
throw new core.OperationError("Cannot parse Certificate or Certificate Request from incoming ASN1");
}
}
}
protected getItemById(id: string): graphene.SessionObject | null {
let object: graphene.SessionObject | null = null;
TEMPLATES.forEach((template) => {
this.crypto.session!.find(template, (obj) => {
const item = obj.toType<any>();
if (id === certs.CryptoCertificate.getID(item)) {
object = item;
return false;
}
});
});
return object;
}
}

View File

@ -0,0 +1,75 @@
import * as graphene from "graphene-pk11";
import * as types from "@peculiar/webcrypto-types";
import { Crypto } from "../crypto";
import { CryptoKey } from "../key";
import { Pkcs11Object } from "../p11_object";
import { Pkcs11Params } from "../types";
export interface Pkcs11CryptoCertificate extends CryptoCertificate {
token: boolean;
sensitive: boolean;
label: string;
}
export type Pkcs11ImportAlgorithms = types.ImportAlgorithms & Pkcs11Params;
export abstract class CryptoCertificate extends Pkcs11Object implements Pkcs11CryptoCertificate {
public crypto: Crypto;
public static getID(p11Object: graphene.Storage) {
let type: string | undefined;
let id: Buffer | undefined;
if (p11Object instanceof graphene.Data) {
type = "request";
id = p11Object.objectId;
} else if (p11Object instanceof graphene.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")}`;
}
public get id() {
Pkcs11Object.assertStorage(this.p11Object);
return CryptoCertificate.getID(this.p11Object);
}
public type: types.CryptoCertificateType = "x509";
public publicKey!: CryptoKey;
public get token() {
try {
Pkcs11Object.assertStorage(this.p11Object);
return this.p11Object.token;
} catch { /* nothing */ }
return false;
}
public get sensitive() {
return false;
}
public get label() {
try {
Pkcs11Object.assertStorage(this.p11Object);
return this.p11Object.label;
} catch { /* nothing */ }
return "";
}
public constructor(crypto: Crypto) {
super();
this.crypto = crypto;
}
public abstract importCert(data: Buffer, algorithm: Pkcs11ImportAlgorithms, keyUsages: string[]): Promise<void>;
public abstract exportCert(): Promise<ArrayBuffer>;
public abstract exportKey(): Promise<CryptoKey>;
public abstract exportKey(algorithm: types.Algorithm, usages: types.KeyUsage[]): Promise<CryptoKey>;
}

View File

@ -0,0 +1,114 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as x509 from "@peculiar/x509";
import * as graphene from "graphene-pk11";
import * as pvtsutils from "pvtsutils";
import { CryptoKey } from "../key";
import { Pkcs11Object } from "../p11_object";
import { CryptoCertificate, Pkcs11ImportAlgorithms } from "./cert";
export class X509CertificateRequest extends CryptoCertificate implements types.CryptoX509CertificateRequest {
public get subjectName() {
return this.getData()?.subject;
}
public override type: "request" = "request";
public override p11Object?: graphene.Data;
public csr?: x509.Pkcs10CertificateRequest;
public get value(): ArrayBuffer {
Pkcs11Object.assertStorage(this.p11Object);
return new Uint8Array(this.p11Object.value).buffer as ArrayBuffer;
}
/**
* Creates new CertificateRequest in PKCS11 session
* @param data
* @param algorithm
* @param keyUsages
*/
public async importCert(data: Buffer, algorithm: Pkcs11ImportAlgorithms, keyUsages: types.KeyUsage[]) {
const array = new Uint8Array(data).buffer as ArrayBuffer;
this.parse(array);
const { token, label, sensitive, ...keyAlg } = algorithm; // remove custom attrs for key
this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto) as CryptoKey;
const hashSPKI = this.publicKey.p11Object.id;
const template = this.crypto.templateBuilder.build({
action: "import",
type: "request",
attributes: {
id: hashSPKI,
label: algorithm.label || "X509 Request",
token: !!(algorithm.token),
},
});
// set data attributes
template.value = Buffer.from(data);
this.p11Object = this.crypto.session.create(template).toType<graphene.Data>();
}
public async exportCert() {
return this.value;
}
public toJSON() {
return {
publicKey: this.publicKey.toJSON(),
subjectName: this.subjectName,
type: this.type,
value: pvtsutils.Convert.ToBase64Url(this.value),
};
}
public async exportKey(): Promise<CryptoKey>;
public async exportKey(algorithm: types.Algorithm, usages: types.KeyUsage[]): Promise<CryptoKey>;
public async exportKey(algorithm?: types.Algorithm, usages?: types.KeyUsage[]) {
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) as CryptoKey;
} else {
this.publicKey = await this.getData().publicKey.export(this.crypto) as CryptoKey;
}
}
}
return this.publicKey;
}
protected parse(data: ArrayBuffer) {
this.csr = new x509.Pkcs10CertificateRequest(data);
}
/**
* returns parsed ASN1 value
*/
protected getData() {
if (!this.csr) {
this.parse(this.value);
}
return this.csr!;
}
}

View File

@ -0,0 +1,3 @@
export * from "./cert"
export * from "./csr"
export * from "./x509"

View File

@ -0,0 +1,144 @@
import * as asn1Schema from "@peculiar/asn1-schema";
import * as asnX509 from "@peculiar/asn1-x509";
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as x509 from "@peculiar/x509";
import * as graphene from "graphene-pk11";
import * as pvtsutils from "pvtsutils";
import { CryptoKey } from "../key";
import { Pkcs11Object } from "../p11_object";
import { CryptoCertificate, Pkcs11ImportAlgorithms } from "./cert";
export class X509Certificate extends CryptoCertificate implements types.CryptoX509Certificate {
public get serialNumber() {
return this.getData().serialNumber;
}
public get notBefore() {
return this.getData().notBefore;
}
public get notAfter() {
return this.getData().notAfter;
}
public get issuerName() {
return this.getData().issuer;
}
public get subjectName() {
return this.getData().subject;
}
public override type: "x509" = "x509";
public get value(): ArrayBuffer {
Pkcs11Object.assertStorage(this.p11Object);
return new Uint8Array(this.p11Object.value).buffer;
}
public override p11Object?: graphene.X509Certificate;
protected x509?: x509.X509Certificate;
public async importCert(data: Buffer, algorithm: Pkcs11ImportAlgorithms, keyUsages: types.KeyUsage[]) {
const array = new Uint8Array(data);
this.parse(array.buffer as ArrayBuffer);
const { token, label, sensitive, ...keyAlg } = algorithm; // remove custom attrs for key
this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto);
const hashSPKI = this.publicKey.p11Object.id;
const certLabel = this.getName();
const template = this.crypto.templateBuilder.build({
action: "import",
type: "x509",
attributes: {
id: hashSPKI,
label: algorithm.label || certLabel,
token: !!(algorithm.token),
},
});
// set X509 attributes
template.value = Buffer.from(data);
const asn = asn1Schema.AsnConvert.parse(data, asnX509.Certificate);
template.serial = Buffer.from(asn1Schema.AsnConvert.serialize(core.asn1.AsnIntegerArrayBufferConverter.toASN(asn.tbsCertificate.serialNumber)));
template.subject = Buffer.from(asn1Schema.AsnConvert.serialize(asn.tbsCertificate.subject));
template.issuer = Buffer.from(asn1Schema.AsnConvert.serialize(asn.tbsCertificate.issuer));
this.p11Object = this.crypto.session.create(template).toType<graphene.X509Certificate>();
}
public async exportCert() {
return this.value;
}
public 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.Convert.ToBase64Url(this.value),
};
}
public async exportKey(): Promise<CryptoKey>;
public async exportKey(algorithm: types.Algorithm, usages: types.KeyUsage[]): Promise<CryptoKey>;
public async exportKey(algorithm?: types.Algorithm, usages?: types.KeyUsage[]) {
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;
}
protected parse(data: ArrayBuffer) {
this.x509 = new x509.X509Certificate(data);
}
/**
* returns parsed ASN1 value
*/
protected getData() {
if (!this.x509) {
this.parse(this.value);
}
return this.x509!;
}
/**
* Returns name from subject of the certificate
*/
protected getName() {
const name = new x509.Name(this.subjectName).toJSON();
for (const item of name) {
const commonName = item.CN;
if (commonName && commonName.length > 0) { // CN
return commonName[0];
}
}
return this.subjectName;
}
}

View File

@ -0,0 +1,3 @@
// We have to use SHA-1 algorithm instead of SHA-2
// OS X security uses SHA-1 for SecKeyItem's ID generation (kSecAttrApplicationLabel | kSecAttrPublicKeyHash)
export const ID_DIGEST = "SHA-1";

View File

@ -0,0 +1,184 @@
// Core
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import * as pkcs11 from "pkcs11js";
import { Assert } from "./assert";
import { CertificateStorage } from "./cert_storage";
import { KeyStorage } from "./key_storage";
import { SubtleCrypto } from "./subtle";
import { CryptoParams, ISessionContainer, ITemplateBuilder, ProviderInfo } from "./types";
import { getProviderInfo } from "./utils";
import { TemplateBuilder } from "./template_builder";
/**
* PKCS11 with WebCrypto Interface
*/
export class Crypto extends core.Crypto implements types.CryptoStorages, ISessionContainer {
public info?: ProviderInfo;
public subtle: SubtleCrypto;
public keyStorage: KeyStorage;
public certStorage: CertificateStorage;
public isReadWrite: boolean;
public isLoggedIn: boolean;
public isLoginRequired: boolean;
/**
* PKCS11 Slot
* @internal
*/
public slot: graphene.Slot;
/**
* PKCS11 Token
* @internal
*/
public token: graphene.Token;
#session?: graphene.Session;
/**
* PKCS11 token
* @internal
*/
public get session() {
Assert.isSession(this.#session);
return this.#session;
}
protected name?: string;
private initialized: boolean;
public templateBuilder: ITemplateBuilder = new TemplateBuilder();
/**
* Creates an instance of WebCrypto.
* @param props PKCS11 module init parameters
*/
constructor(props: CryptoParams) {
super();
const mod = graphene.Module.load(props.library, props.name || props.library);
this.name = props.name;
try {
if (props.libraryParameters) {
mod.initialize({
libraryParameters: props.libraryParameters,
});
} else {
mod.initialize();
}
} catch (e) {
if (!(e instanceof pkcs11.Pkcs11Error && e.code === pkcs11.CKR_CRYPTOKI_ALREADY_INITIALIZED)) {
throw e;
}
}
this.initialized = true;
const slotIndex = props.slot || 0;
const slots = mod.getSlots(true);
if (!(0 <= slotIndex && slotIndex < slots.length)) {
throw new core.CryptoError(`Slot by index ${props.slot} is not found`);
}
this.slot = slots.items(slotIndex);
this.token = this.slot.getToken();
this.isLoginRequired = !!(this.token.flags & graphene.TokenFlag.LOGIN_REQUIRED);
this.isLoggedIn = !this.isLoginRequired;
this.isReadWrite = !!props.readWrite;
this.open(props.readWrite);
if (props.pin && this.isLoginRequired) {
this.login(props.pin);
}
for (const i in props.vendors!) {
graphene.Mechanism.vendor(props.vendors![i]);
}
this.subtle = new SubtleCrypto(this);
this.keyStorage = new KeyStorage(this);
this.certStorage = new CertificateStorage(this);
}
public open(rw?: boolean) {
let flags = graphene.SessionFlag.SERIAL_SESSION;
if (rw) {
flags |= graphene.SessionFlag.RW_SESSION;
}
this.#session = this.slot.open(flags);
this.info = getProviderInfo(this.slot);
if (this.info && this.name) {
this.info.name = this.name;
}
}
public reset() {
if (this.isLoggedIn && this.isLoginRequired) {
this.logout();
}
this.session.close();
this.open(this.isReadWrite);
}
public login(pin: string) {
if (!this.isLoginRequired) {
return;
}
try {
this.session.login(pin);
} catch (error) {
if (!(error instanceof pkcs11.Pkcs11Error && error.code === pkcs11.CKR_USER_ALREADY_LOGGED_IN)) {
throw error;
}
}
this.isLoggedIn = true;
}
public logout() {
if (!this.isLoginRequired) {
return;
}
try {
this.session.logout();
} catch (error) {
if (!(error instanceof pkcs11.Pkcs11Error && error.code === pkcs11.CKR_USER_NOT_LOGGED_IN)) {
throw error;
}
}
this.isLoggedIn = false;
}
/**
* Generates cryptographically random values
* @param array Initialize array
*/
// Based on: https://github.com/KenanY/get-random-values
public getRandomValues<T extends ArrayBufferView | null>(array: T): T {
if (!ArrayBuffer.isView(array)) {
throw new TypeError("Failed to execute 'getRandomValues' on 'Crypto': parameter 1 is not of type 'ArrayBufferView'");
}
if (array.byteLength > 65536) {
throw new core.CryptoError(`Failed to execute 'getRandomValues' on 'Crypto': The ArrayBufferView's byte length (${array.byteLength}) exceeds the number of bytes of entropy available via this API (65536).`);
}
const bytes = new Uint8Array(this.session.generateRandom(array.byteLength));
(array as unknown as Uint8Array).set(bytes);
return array;
}
/**
* Close PKCS11 module
*/
public close() {
if (this.initialized) {
this.session.logout();
this.session.close();
this.slot.module.finalize();
this.slot.module.close();
}
}
}

View File

@ -0,0 +1,7 @@
export { Crypto } from "./crypto";
export { SubtleCrypto } from "./subtle";
export { CryptoKey } from "./key";
export { CryptoCertificate, X509Certificate, X509CertificateRequest } from "./certs";
export { KeyStorage } from "./key_storage";
export { CertificateStorage } from "./cert_storage";
export * from "./template_builder";

177
packages/pkcs11/src/key.ts Normal file
View File

@ -0,0 +1,177 @@
// Core
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import { KeyType } from "crypto";
import * as graphene from "graphene-pk11";
import { Pkcs11KeyAlgorithm } from "./types";
export interface ITemplatePair {
privateKey: graphene.ITemplate;
publicKey: graphene.ITemplate;
}
export class CryptoKey<T extends Pkcs11KeyAlgorithm = Pkcs11KeyAlgorithm> extends core.CryptoKey {
public static defaultKeyAlgorithm() {
const alg: Pkcs11KeyAlgorithm = {
label: "",
name: "",
sensitive: false,
token: false,
};
return alg;
}
public static getID(p11Key: graphene.Key) {
let name: string;
switch (p11Key.class) {
case graphene.ObjectClass.PRIVATE_KEY:
name = "private";
break;
case graphene.ObjectClass.PUBLIC_KEY:
name = "public";
break;
case graphene.ObjectClass.SECRET_KEY:
name = "secret";
break;
default:
throw new Error(`Unsupported Object type '${graphene.ObjectClass[p11Key.class]}'`);
}
return `${name}-${p11Key.handle.toString("hex")}-${p11Key.id.toString("hex")}`;
}
public id: string;
public p11Object: graphene.Key | graphene.SecretKey | graphene.PublicKey | graphene.PrivateKey;
public override algorithm: T;
public get key(): graphene.Key {
return this.p11Object.toType<graphene.Key>();
}
constructor(key: graphene.Key, alg: T | types.KeyAlgorithm, usages?: types.KeyUsage[]) {
super();
this.p11Object = key;
switch (key.class) {
case graphene.ObjectClass.PUBLIC_KEY:
this.initPublicKey(key.toType<graphene.PublicKey>());
break;
case graphene.ObjectClass.PRIVATE_KEY:
this.initPrivateKey(key.toType<graphene.PrivateKey>());
break;
case graphene.ObjectClass.SECRET_KEY:
this.initSecretKey(key.toType<graphene.SecretKey>());
break;
default:
throw new core.CryptoError(`Wrong incoming session object '${graphene.ObjectClass[key.class]}'`);
}
const { name, ...defaultAlg } = CryptoKey.defaultKeyAlgorithm();
this.algorithm = { ...alg, ...defaultAlg } as T;
this.id = CryptoKey.getID(key);
if (usages) {
this.usages = usages;
}
try {
this.algorithm.label = key.label;
} catch { /*nothing*/ }
try {
this.algorithm.token = key.token;
} catch { /*nothing*/ }
try {
if (key instanceof graphene.PrivateKey || key instanceof graphene.SecretKey) {
this.algorithm.sensitive = key.get("sensitive");
}
} catch { /*nothing*/ }
this.onAssign();
}
public toJSON() {
return {
algorithm: this.algorithm,
type: this.type,
usages: this.usages,
extractable: this.extractable,
};
}
protected initPrivateKey(key: graphene.PrivateKey) {
this.p11Object = key;
this.type = "private";
try {
// Yubico throws CKR_ATTRIBUTE_TYPE_INVALID
this.extractable = key.extractable;
} catch (e) {
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");
}
}
protected initPublicKey(key: graphene.PublicKey) {
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");
}
}
protected initSecretKey(key: graphene.SecretKey) {
this.p11Object = key;
this.type = "secret";
try {
// Yubico throws CKR_ATTRIBUTE_TYPE_INVALID
this.extractable = key.extractable;
} catch (e) {
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");
}
}
protected onAssign() {
// nothing
}
}

View File

@ -0,0 +1,200 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import { Crypto } from "./crypto";
import { CryptoKey } from "./key";
import { AesCryptoKey, EcCryptoKey, HmacCryptoKey, RsaCryptoKey } from "./mechs";
import { Pkcs11KeyAlgorithm } from "./types";
import * as utils from "./utils";
const OBJECT_TYPES = [graphene.ObjectClass.PRIVATE_KEY, graphene.ObjectClass.PUBLIC_KEY, graphene.ObjectClass.SECRET_KEY];
export class KeyStorage implements types.CryptoKeyStorage {
protected crypto: Crypto;
constructor(crypto: Crypto) {
this.crypto = crypto;
}
public async keys() {
const keys: string[] = [];
OBJECT_TYPES.forEach((objectClass) => {
this.crypto.session!.find({ class: objectClass, token: true }, (obj) => {
const item = obj.toType<any>();
keys.push(CryptoKey.getID(item));
});
});
return keys;
}
public async indexOf(item: CryptoKey) {
if (item instanceof CryptoKey && item.key.token) {
return CryptoKey.getID(item.key);
}
return null;
}
public async clear() {
const keys: graphene.SessionObject[] = [];
OBJECT_TYPES.forEach((objectClass) => {
this.crypto.session!.find({ class: objectClass, token: true }, (obj) => {
keys.push(obj);
});
});
keys.forEach((key) => {
key.destroy();
});
}
public async getItem(key: string): Promise<CryptoKey>;
/** @deprecated Use getItem(index, algorithm, extractable, keyUsages) */
public async getItem(key: string, algorithm: types.Algorithm, usages: types.KeyUsage[]): Promise<CryptoKey>;
public async getItem(index: string, algorithm: types.ImportAlgorithms, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey>;
public async getItem(key: string, ...args: any[]) {
const subjectObject = this.getItemById(key);
if (subjectObject) {
const p11Key = subjectObject.toType<graphene.SecretKey>();
let alg: Pkcs11KeyAlgorithm | undefined;
let algorithm: types.Algorithm | undefined;
let usages: types.KeyUsage[] | undefined;
if (typeof args[0] === "object" && typeof args[1] === "boolean" && Array.isArray(args[2])) {
algorithm = args[0];
usages = args[2];
} else if (typeof args[0] === "object" && Array.isArray(args[1])) {
algorithm = args[0];
usages = args[1];
}
if (algorithm) {
alg = {
...utils.prepareAlgorithm(algorithm),
token: false,
sensitive: false,
label: "",
};
} else {
alg = {
name: "",
token: false,
sensitive: false,
label: "",
};
switch (p11Key.type) {
case graphene.KeyType.RSA: {
if (p11Key.sign || p11Key.verify) {
alg.name = "RSASSA-PKCS1-v1_5";
} else {
alg.name = "RSA-OAEP";
}
(alg as any).hash = { name: "SHA-256" };
break;
}
case graphene.KeyType.EC: {
if (p11Key.sign || p11Key.verify) {
alg.name = "ECDSA";
} else {
alg.name = "ECDH";
}
break;
}
case graphene.KeyType.GENERIC_SECRET:
case graphene.KeyType.AES: {
if (p11Key.sign || p11Key.verify) {
alg.name = "HMAC";
} else {
alg.name = "AES-CBC";
}
break;
}
default:
throw new Error(`Unsupported type of key '${graphene.KeyType[p11Key.type] || p11Key.type}'`);
}
}
let CryptoKeyClass: typeof CryptoKey;
switch (alg.name.toUpperCase()) {
case "RSASSA-PKCS1-V1_5":
case "RSA-PSS":
case "RSA-OAEP":
CryptoKeyClass = RsaCryptoKey as typeof CryptoKey;
break;
case "ECDSA":
case "ECDH":
CryptoKeyClass = EcCryptoKey as typeof CryptoKey;
break;
case "HMAC":
CryptoKeyClass = HmacCryptoKey as typeof CryptoKey;
break;
case "AES-CBC":
case "AES-ECB":
case "AES-GCM":
CryptoKeyClass = AesCryptoKey as typeof CryptoKey;
break;
default:
CryptoKeyClass = CryptoKey;
}
const key = new CryptoKeyClass(p11Key, alg, usages);
if (typeof args[1] === "boolean") {
key.extractable = args[1];
}
return key;
} else {
return null;
}
}
public async removeItem(key: string) {
const sessionObject = this.getItemById(key);
if (sessionObject) {
sessionObject.destroy();
}
}
public async setItem(data: types.CryptoKey): Promise<string> {
if (!(data instanceof CryptoKey)) {
throw new core.CryptoError("Parameter 1 is not P11CryptoKey");
}
const p11Key = data as CryptoKey;
// don't copy object from token
const hasItem = await this.hasItem(data);
if (!(hasItem && p11Key.key.token)) {
const template = this.crypto.templateBuilder.build({
action: "copy",
type: p11Key.type,
attributes: {
token: true,
}
});
const obj = this.crypto.session.copy(p11Key.key, template);
return CryptoKey.getID(obj.toType<any>());
} else {
return data.id;
}
}
public async hasItem(key: CryptoKey) {
const item = this.getItemById(key.id);
return !!item;
}
protected getItemById(id: string): graphene.SessionObject | null {
let key: graphene.SessionObject | null = null;
OBJECT_TYPES.forEach((objectClass) => {
this.crypto.session!.find({ class: objectClass, token: true }, (obj) => {
const item = obj.toType<any>();
if (id === CryptoKey.getID(item)) {
key = item;
return false;
}
});
});
return key;
}
}

View File

@ -0,0 +1,50 @@
import * as types from "@peculiar/webcrypto-types";
import * as core from "@peculiar/webcrypto-core";
import { Assert } from "../../assert";
import { CryptoKey } from "../../key";
import { IContainer, ISessionContainer } from "../../types";
import { AesCrypto } from "./crypto";
import { AesCryptoKey } from "./key";
export class AesCbcProvider extends core.AesCbcProvider implements IContainer {
public crypto: AesCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new AesCrypto(container);
}
public override async onGenerateKey(algorithm: types.AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[], ...args: any[]): Promise<core.CryptoKey> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
keyUsages);
return key;
}
public override async onEncrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return this.crypto.encrypt(false, algorithm, key, new Uint8Array(data));
}
public override async onDecrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return this.crypto.decrypt(false, algorithm, key, new Uint8Array(data));
}
public override async onExportKey(format: types.KeyFormat, key: AesCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return this.crypto.exportKey(format, key);
}
public override async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.Algorithm, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
Assert.isCryptoKey(key);
}
}

View File

@ -0,0 +1,52 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import { CryptoKey } from "../../key";
import { IContainer, ISessionContainer, Pkcs11AesKeyGenParams, Pkcs11KeyImportParams } from "../../types";
import { AesCrypto } from "./crypto";
import { AesCryptoKey } from "./key";
export class AesEcbProvider extends core.ProviderCrypto implements IContainer {
public name = "AES-ECB";
public usages: types.KeyUsage[] = ["encrypt", "decrypt", "wrapKey", "unwrapKey"];
public crypto: AesCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new AesCrypto(container);
}
public override async onGenerateKey(algorithm: Pkcs11AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
keyUsages);
return key;
}
public override async onEncrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return this.crypto.encrypt(true, algorithm, key, data);
}
public override async onDecrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return this.crypto.decrypt(true, algorithm, key, data);
}
public override async onExportKey(format: types.KeyFormat, key: AesCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return this.crypto.exportKey(format, key);
}
public override async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: Pkcs11KeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof CryptoKey)) {
throw new TypeError("key: Is not a PKCS11 CryptoKey");
}
}
}

View File

@ -0,0 +1,52 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import { CryptoKey } from "../../key";
import { IContainer, ISessionContainer, Pkcs11KeyImportParams } from "../../types";
import { Pkcs11AesKeyGenParams } from "../../types";
import { AesCrypto } from "./crypto";
import { AesCryptoKey } from "./key";
export class AesGcmProvider extends core.AesGcmProvider implements IContainer {
public crypto: AesCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new AesCrypto(container);
}
public override async onGenerateKey(algorithm: Pkcs11AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
keyUsages);
return key;
}
public override async onEncrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return this.crypto.encrypt(false, algorithm, key, new Uint8Array(data));
}
public override async onDecrypt(algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return this.crypto.decrypt(false, algorithm, key, new Uint8Array(data));
}
public override async onExportKey(format: types.KeyFormat, key: AesCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return this.crypto.exportKey(format, key);
}
public override async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: Pkcs11KeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof CryptoKey)) {
throw new TypeError("key: Is not a PKCS11 CryptoKey");
}
}
}

View File

@ -0,0 +1,223 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import { Convert } from "pvtsutils";
import { CryptoKey } from "../../key";
import { GUID, prepareData } from "../../utils";
import { IContainer, ISessionContainer, Pkcs11AesKeyAlgorithm, Pkcs11AesKeyGenParams, Pkcs11KeyImportParams } from "../../types";
import { AesCryptoKey } from "./key";
export class AesCrypto implements IContainer {
constructor(public container: ISessionContainer) {
}
public async generateKey(algorithm: Pkcs11AesKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
return new Promise<CryptoKey>((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.KeyType.AES;
template.valueLen = algorithm.length >> 3;
// PKCS11 generation
this.container.session.generateKey(graphene.KeyGenMechanism.AES, template, (err, aesKey) => {
try {
if (err) {
reject(new core.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);
}
});
});
}
public async exportKey(format: string, key: AesCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
const template = key.key.getAttribute({ value: null, valueLen: null });
switch (format.toLowerCase()) {
case "jwk":
const aes: string = /AES-(\w+)/.exec(key.algorithm.name!)![1];
const jwk: types.JsonWebKey = {
kty: "oct",
k: 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;
break;
default:
throw new core.OperationError("format: Must be 'jwk' or 'raw'");
}
}
public async importKey(format: string, keyData: types.JsonWebKey | ArrayBuffer, algorithm: Pkcs11KeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
// get key value
let value: ArrayBuffer;
switch (format.toLowerCase()) {
case "jwk":
const jwk = keyData as types.JsonWebKey;
if (!jwk.k) {
throw new core.OperationError("jwk.k: Cannot get required property");
}
keyData = Convert.FromBase64Url(jwk.k);
case "raw":
value = keyData as ArrayBuffer;
switch (value.byteLength) {
case 16:
case 24:
case 32:
break;
default:
throw new core.OperationError("keyData: Is wrong key length");
}
break;
default:
throw new core.OperationError("format: Must be 'jwk' or 'raw'");
}
// prepare key algorithm
const aesAlg: Pkcs11AesKeyAlgorithm = {
...AesCryptoKey.defaultKeyAlgorithm(),
...algorithm,
length: value.byteLength * 8,
};
const template: graphene.ITemplate = 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.KeyType.AES;
template.value = Buffer.from(value);
// create session object
const sessionObject = this.container.session.create(template);
const key = new AesCryptoKey(sessionObject.toType<graphene.SecretKey>(), aesAlg);
return key;
}
public async encrypt(padding: boolean, algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
// add padding if needed
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<ArrayBuffer>((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);
}
});
});
}
public async decrypt(padding: boolean, algorithm: types.Algorithm, key: AesCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
const dec = await new Promise<Buffer>((resolve, reject) => {
const buf = Buffer.alloc(this.getOutputBufferSize(key.algorithm, false, data.byteLength));
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) {
// Remove 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;
}
}
protected isAesGCM(algorithm: types.Algorithm): algorithm is types.AesGcmParams {
return algorithm.name.toUpperCase() === "AES-GCM";
}
protected isAesCBC(algorithm: types.Algorithm): algorithm is types.AesCbcParams {
return algorithm.name.toUpperCase() === "AES-CBC";
}
protected isAesECB(algorithm: types.Algorithm): algorithm is types.Algorithm {
return algorithm.name.toUpperCase() === "AES-ECB";
}
protected wc2pk11(algorithm: types.Algorithm) {
const session = this.container.session;
if (this.isAesGCM(algorithm)) {
const aad = algorithm.additionalData ? prepareData(algorithm.additionalData) : undefined;
let AesGcmParamsClass = graphene.AesGcmParams;
if (session.slot.module.cryptokiVersion.major >= 2 &&
session.slot.module.cryptokiVersion.minor >= 40) {
AesGcmParamsClass = graphene.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.OperationError("Unrecognized algorithm name");
}
}
/**
* Returns a size of output buffer of enc/dec operation
* @param keyAlg key algorithm
* @param enc type of operation
* `true` - encryption operation
* `false` - decryption operation
* @param dataSize size of incoming data
*/
protected getOutputBufferSize(keyAlg: Pkcs11AesKeyAlgorithm, enc: boolean, dataSize: number): number {
const len = keyAlg.length >> 3;
if (enc) {
return (Math.ceil(dataSize / len) * len) + len;
} else {
return dataSize;
}
}
}

View File

@ -0,0 +1,4 @@
export * from "./aes_cbc";
export * from "./aes_ecb";
export * from "./aes_gcm";
export * from "./key";

View File

@ -0,0 +1,10 @@
import { CryptoKey } from "../../key";
import { Pkcs11AesKeyAlgorithm } from "../../types";
export class AesCryptoKey extends CryptoKey<Pkcs11AesKeyAlgorithm> {
protected override onAssign() {
this.algorithm.length = this.key.get("valueLen") << 3;
}
}

View File

@ -0,0 +1,353 @@
import * as asn1Schema from "@peculiar/asn1-schema";
import * as jsonSchema from "@peculiar/json-schema";
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import * as pvtsutils from "pvtsutils";
import { Assert } from "../../assert";
import { CryptoKey } from "../../key";
import { IContainer, ISessionContainer, Pkcs11Attributes, ITemplateBuildParameters, ITemplate, Pkcs11EcKeyGenParams, Pkcs11EcKeyImportParams } from "../../types";
import { GUID, digest, b64UrlDecode } from "../../utils";
import { EcCryptoKey } from "./key";
const id_ecPublicKey = "1.2.840.10045.2.1";
export class EcCrypto implements IContainer {
public publicKeyUsages = ["verify"];
public privateKeyUsages = ["sign", "deriveKey", "deriveBits"];
public constructor(public container: ISessionContainer) {
}
public async generateKey(algorithm: Pkcs11EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
return new Promise<types.CryptoKeyPair>((resolve, reject) => {
// Create PKCS#11 templates
const attrs: Pkcs11Attributes = {
id: GUID(),
label: algorithm.label,
token: algorithm.token,
sensitive: algorithm.sensitive,
extractable,
usages: keyUsages,
};
const privateTemplate = this.createTemplate({
action: "generate",
type: "private",
attributes: attrs,
});
const publicTemplate = this.createTemplate({
action: "generate",
type: "public",
attributes: attrs,
});
// EC params
publicTemplate.paramsEC = Buffer.from(core.EcCurves.get(algorithm.namedCurve).raw);
// PKCS11 generation
this.container.session.generateKeyPair(graphene.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 as any);
}
} catch (e) {
reject(e);
}
});
});
}
public async exportKey(format: types.KeyFormat, key: EcCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
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": {
// export subjectPublicKey BIT_STRING value
const jwk = await this.exportJwkPublicKey(key);
if ((key.algorithm as types.EcKeyGenParams).namedCurve === "X25519") {
return pvtsutils.Convert.FromBase64Url(jwk.x!);
} else {
const publicKey = jsonSchema.JsonParser.fromJSON(jwk, { targetSchema: core.asn1.EcPublicKey });
return publicKey.value;
}
}
default:
throw new core.OperationError("format: Must be 'jwk', 'raw', pkcs8' or 'spki'");
}
}
public async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: Pkcs11EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
switch (format.toLowerCase()) {
case "jwk": {
const jwk: any = 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 as ArrayBuffer);
return this.importJwkPublicKey(jwk, algorithm, extractable, keyUsages);
}
case "pkcs8": {
const jwk = this.pkcs2jwk(keyData as ArrayBuffer);
return this.importJwkPrivateKey(jwk, algorithm, extractable, keyUsages);
}
case "raw": {
const curve = core.EcCurves.get(algorithm.namedCurve);
const ecPoint = core.EcUtils.decodePoint(keyData as Uint8Array, curve.size);
const jwk: types.JsonWebKey = {
kty: "EC",
crv: algorithm.namedCurve,
x: pvtsutils.Convert.ToBase64Url(ecPoint.x),
};
if (ecPoint.y) {
jwk.y = pvtsutils.Convert.ToBase64Url(ecPoint.y);
}
return this.importJwkPublicKey(jwk, algorithm, extractable, keyUsages);
}
default:
throw new core.OperationError("format: Must be 'jwk', 'raw', 'pkcs8' or 'spki'");
}
}
public getAlgorithm(p11AlgorithmName: string | number) {
const mechanisms = this.container.session.slot.getMechanisms();
let EC: string | undefined;
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;
}
public prepareData(hashAlgorithm: string, data: Buffer) {
// use nodejs crypto for digest calculating
return digest(hashAlgorithm.replace("-", ""), data);
}
protected importJwkPrivateKey(jwk: types.JsonWebKey, algorithm: Pkcs11EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]) {
const template = this.createTemplate({
action: "import",
type: "private",
attributes: {
id: GUID(),
token: algorithm.token,
sensitive: algorithm.sensitive,
label: algorithm.label,
extractable,
usages: keyUsages,
},
});
// Set EC private key attributes
template.paramsEC = Buffer.from(core.EcCurves.get(algorithm.namedCurve).raw);
template.value = b64UrlDecode(jwk.d!);
const p11key = this.container.session.create(template).toType<graphene.Key>();
return new EcCryptoKey(p11key, algorithm);
}
protected importJwkPublicKey(jwk: types.JsonWebKey, algorithm: Pkcs11EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]) {
const namedCurve = core.EcCurves.get(algorithm.namedCurve);
const template = this.createTemplate({
action: "import",
type: "public",
attributes: {
id: GUID(),
token: algorithm.token,
label: algorithm.label,
extractable,
usages: keyUsages,
}
});
// Set EC public key attributes
template.paramsEC = Buffer.from(namedCurve.raw);;
let pointEc: Buffer;
if (namedCurve.name === "curve25519") {
pointEc = b64UrlDecode(jwk.x!);
} else {
const point = core.EcUtils.encodePoint({ x: b64UrlDecode(jwk.x!), y: b64UrlDecode(jwk.y!) }, namedCurve.size);
const derPoint = asn1Schema.AsnConvert.serialize(new asn1Schema.OctetString(point));
pointEc = Buffer.from(derPoint);
}
template.pointEC = pointEc;
const p11key = this.container.session.create(template).toType<graphene.Key>();
return new EcCryptoKey(p11key, algorithm);
}
protected exportJwkPublicKey(key: EcCryptoKey) {
const pkey: graphene.ITemplate = key.key.getAttribute({
pointEC: null,
});
const curve = core.EcCurves.get(key.algorithm.namedCurve);
// Parse DER-encoded of ANSI X9.62 ECPoint value ''Q''
const p11PointEC = pkey.pointEC;
if (!p11PointEC) {
throw new Error("Cannot get required ECPoint attribute");
}
const derEcPoint = asn1Schema.AsnConvert.parse(p11PointEC, asn1Schema.OctetString);
const ecPoint = core.EcUtils.decodePoint(derEcPoint, curve.size);
const jwk: types.JsonWebKey = {
kty: "EC",
crv: key.algorithm.namedCurve,
ext: true,
key_ops: key.usages,
x: pvtsutils.Convert.ToBase64Url(ecPoint.x),
};
if (curve.name !== "curve25519") {
jwk.y = pvtsutils.Convert.ToBase64Url(ecPoint.y!);
}
return jwk;
}
protected async exportJwkPrivateKey(key: EcCryptoKey) {
const pkey: graphene.ITemplate = key.key.getAttribute({
value: null,
});
const jwk: types.JsonWebKey = {
kty: "EC",
crv: (key.algorithm as types.EcKeyGenParams).namedCurve,
ext: true,
key_ops: key.usages,
d: pvtsutils.Convert.ToBase64Url(pkey.value!),
};
return jwk;
}
/**
* Creates PKCS11 template
* @param params
*/
protected createTemplate(params: ITemplateBuildParameters): ITemplate {
const template = this.container.templateBuilder.build({
...params,
attributes: {
...params.attributes,
label: params.attributes.label || "EC",
},
});
template.keyType = graphene.KeyType.EC;
return template;
}
protected spki2jwk(raw: ArrayBuffer): types.JsonWebKey {
const keyInfo = asn1Schema.AsnParser.parse(raw, core.asn1.PublicKeyInfo);
if (keyInfo.publicKeyAlgorithm.algorithm !== id_ecPublicKey) {
throw new Error("SPKI is not EC public key");
}
const namedCurveId = asn1Schema.AsnParser.parse(keyInfo.publicKeyAlgorithm.parameters!, core.asn1.ObjectIdentifier);
const namedCurve = core.EcCurves.get(namedCurveId.value);
const ecPublicKey = new core.asn1.EcPublicKey(keyInfo.publicKey);
const json = jsonSchema.JsonSerializer.toJSON(ecPublicKey);
return {
kty: "EC",
crv: namedCurve.name,
...json,
};
}
protected jwk2pkcs(jwk: types.JsonWebKey): ArrayBuffer {
Assert.requiredParameter(jwk.crv, "crv");
const namedCurve = core.EcCurves.get(jwk.crv);
const ecPrivateKey = jsonSchema.JsonParser.fromJSON(jwk, { targetSchema: core.asn1.EcPrivateKey });
const keyInfo = new core.asn1.PrivateKeyInfo();
keyInfo.privateKeyAlgorithm = new core.asn1.AlgorithmIdentifier();
keyInfo.privateKeyAlgorithm.algorithm = id_ecPublicKey;
keyInfo.privateKeyAlgorithm.parameters = namedCurve.raw;
keyInfo.privateKey = asn1Schema.AsnSerializer.serialize(ecPrivateKey);
return asn1Schema.AsnSerializer.serialize(keyInfo);
}
protected getCoordinate(b64: string, coordinateLength: number) {
const buf = pvtsutils.Convert.FromBase64Url(b64);
const offset = coordinateLength - buf.byteLength;
const res = new Uint8Array(coordinateLength);
res.set(new Uint8Array(buf), offset);
return res.buffer as ArrayBuffer;
}
protected jwk2spki(jwk: types.JsonWebKey) {
if (!jwk.crv) {
throw new Error("Absent mandatory parameter \"crv\"");
}
const namedCurve = core.EcCurves.get(jwk.crv);
const ecPublicKey = jsonSchema.JsonParser.fromJSON(jwk, { targetSchema: core.asn1.EcPublicKey });
const keyInfo = new core.asn1.PublicKeyInfo();
keyInfo.publicKeyAlgorithm.algorithm = id_ecPublicKey;
keyInfo.publicKeyAlgorithm.parameters = namedCurve.raw;
keyInfo.publicKey = ecPublicKey.value;
return asn1Schema.AsnSerializer.serialize(keyInfo);
}
protected pkcs2jwk(raw: ArrayBuffer): types.JsonWebKey {
const keyInfo = asn1Schema.AsnParser.parse(raw, core.asn1.PrivateKeyInfo);
if (keyInfo.privateKeyAlgorithm.algorithm !== id_ecPublicKey) {
throw new Error("PKCS8 is not EC private key");
}
if (!keyInfo.privateKeyAlgorithm.parameters) {
throw new Error("Cannot get required Named curve parameters from ASN.1 PrivateKeyInfo structure");
}
const namedCurveId = asn1Schema.AsnParser.parse(keyInfo.privateKeyAlgorithm.parameters!, core.asn1.ObjectIdentifier);
const namedCurve = core.EcCurves.get(namedCurveId.value);
const ecPrivateKey = asn1Schema.AsnParser.parse(keyInfo.privateKey, core.asn1.EcPrivateKey);
const json = jsonSchema.JsonSerializer.toJSON(ecPrivateKey);
return {
kty: "EC",
crv: namedCurve.name,
...json,
};
}
}

View File

@ -0,0 +1,108 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import { CryptoKey } from "../../key";
import { IContainer, ISessionContainer, Pkcs11EcKeyGenParams } from "../../types";
import { EcCrypto } from "./crypto";
import { EcCryptoKey } from "./key";
export class EcdhProvider extends core.EcdhProvider implements IContainer {
public override namedCurves = core.EcCurves.names;
public override usages: types.ProviderKeyPairUsage = {
privateKey: ["sign", "deriveKey", "deriveBits"],
publicKey: ["verify"],
};
public crypto: EcCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new EcCrypto(container);
}
public override async onGenerateKey(algorithm: Pkcs11EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
keyUsages);
return key;
}
public override async onExportKey(format: types.KeyFormat, key: EcCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return this.crypto.exportKey(format, key);
}
public override async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
const key = await this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
return key;
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof EcCryptoKey)) {
throw new TypeError("key: Is not EC CryptoKey");
}
}
public async onDeriveBits(algorithm: types.EcdhKeyDeriveParams, baseKey: EcCryptoKey, length: number): Promise<ArrayBuffer> {
return new Promise<ArrayBuffer>((resolve, reject) => {
let valueLen = 256;
switch (baseKey.algorithm.namedCurve) {
case "P-256":
case "K-256":
valueLen = 256;
break;
case "P-384":
valueLen = 384;
break;
case "P-521":
valueLen = 534;
break;
}
// TODO Use template provider
const template: graphene.ITemplate = {
token: false,
sensitive: false,
class: graphene.ObjectClass.SECRET_KEY,
keyType: graphene.KeyType.GENERIC_SECRET,
extractable: true,
encrypt: true,
decrypt: true,
valueLen: valueLen >> 3,
};
// derive key
const ecPoint = (algorithm.public as EcCryptoKey).key.getAttribute({ pointEC: null }).pointEC!;
this.container.session.deriveKey(
{
name: "ECDH1_DERIVE",
params: new graphene.EcdhParams(
graphene.EcKdf.NULL,
null as any,
ecPoint, // CKA_EC_POINT
),
},
baseKey.key,
template,
(err, key) => {
if (err) {
reject(err);
} else {
if (!key) {
throw new Error("Cannot get key from callback function");
}
const secretKey = key.toType<graphene.SecretKey>();
const value = secretKey.getAttribute({ value: null }).value as Buffer;
resolve(new Uint8Array(value.slice(0, length >> 3)).buffer);
}
});
});
}
}

View File

@ -0,0 +1,112 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import { CryptoKey } from "../../key";
import { IContainer, ISessionContainer, Pkcs11EcKeyGenParams, Pkcs11EcKeyImportParams } from "../../types";
import { EcCrypto } from "./crypto";
import { EcCryptoKey } from "./key";
export class EcdsaProvider extends core.EcdsaProvider implements IContainer {
public override namedCurves = core.EcCurves.names;
public override usages: types.ProviderKeyPairUsage = {
privateKey: ["sign", "deriveKey", "deriveBits"],
publicKey: ["verify"],
};
public crypto: EcCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new EcCrypto(container);
}
public override async onGenerateKey(algorithm: Pkcs11EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
keyUsages);
return key;
}
public override async onSign(algorithm: types.EcdsaParams, key: EcCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new Promise<ArrayBuffer>((resolve, reject) => {
let buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, algorithm);
mechanism.name = this.crypto.getAlgorithm(mechanism.name);
if (mechanism.name === "ECDSA") {
buf = this.crypto.prepareData((algorithm.hash as types.Algorithm).name, buf);
}
this.container.session.createSign(mechanism, key.key).once(buf, (err, data2) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(data2).buffer);
}
});
});
}
public override async onVerify(algorithm: types.EcdsaParams, key: EcCryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
let buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, algorithm);
mechanism.name = this.crypto.getAlgorithm(mechanism.name);
if (mechanism.name === "ECDSA") {
buf = this.crypto.prepareData((algorithm.hash as types.Algorithm).name, buf);
}
this.container.session.createVerify(mechanism, key.key).once(buf, Buffer.from(signature), (err, data2) => {
if (err) {
reject(err);
} else {
resolve(data2);
}
});
});
}
public override async onExportKey(format: types.KeyFormat, key: EcCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return this.crypto.exportKey(format, key);
}
public override async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: Pkcs11EcKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
const key = await this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
return key;
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof EcCryptoKey)) {
throw new TypeError("key: Is not EC CryptoKey");
}
}
protected wc2pk11(alg: types.EcdsaParams, keyAlg: types.KeyAlgorithm): { name: string, params: null; } {
let algName: string;
const hashAlg = (alg.hash as types.Algorithm).name.toUpperCase();
switch (hashAlg) {
case "SHA-1":
algName = "ECDSA_SHA1";
break;
case "SHA-224":
algName = "ECDSA_SHA224";
break;
case "SHA-256":
algName = "ECDSA_SHA256";
break;
case "SHA-384":
algName = "ECDSA_SHA384";
break;
case "SHA-512":
algName = "ECDSA_SHA512";
break;
default:
throw new core.OperationError(`Cannot create PKCS11 mechanism from algorithm '${hashAlg}'`);
}
return { name: algName, params: null };
}
}

View File

@ -0,0 +1,4 @@
export * from "./ec_dh";
export * from "./ec_dsa";
export * from "./key";
export * from "./crypto";

View File

@ -0,0 +1,22 @@
import { AsnConvert } from "@peculiar/asn1-schema";
import * as core from "@peculiar/webcrypto-core";
import { CryptoKey } from "../../key";
import { Pkcs11EcKeyAlgorithm } from "../../types";
export class EcCryptoKey extends CryptoKey<Pkcs11EcKeyAlgorithm> {
protected override onAssign() {
if (!this.algorithm.namedCurve) {
try {
const paramsECDSA = AsnConvert.parse(this.key.get("paramsECDSA"), core.asn1.ObjectIdentifier);
try {
const pointEC = core.EcCurves.get(paramsECDSA.value);
this.algorithm.namedCurve = pointEC.name;
} catch {
this.algorithm.namedCurve = paramsECDSA.value;
}
} catch { /*nothing*/ }
}
}
}

View File

@ -0,0 +1,190 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import * as pvtsutils from "pvtsutils";
import { CryptoKey } from "../../key";
import {
IContainer, ISessionContainer, ITemplateBuildParameters, ITemplate,
Pkcs11HmacKeyAlgorithm, Pkcs11HmacKeyGenParams, Pkcs11HmacKeyImportParams,
} from "../../types";
import { GUID } from "../../utils";
import { HmacCryptoKey } from "./key";
export class HmacProvider extends core.HmacProvider implements IContainer {
constructor(public container: ISessionContainer) {
super();
}
public async onGenerateKey(algorithm: Pkcs11HmacKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
return new Promise<CryptoKey>((resolve, reject) => {
const length = (algorithm.length || this.getDefaultLength((algorithm.hash as types.Algorithm).name)) >> 3 << 3;
algorithm = { ...algorithm, name: this.name, length };
const template = this.createTemplate({
action: "generate",
type: "secret",
attributes: {
token: algorithm.token,
sensitive: algorithm.sensitive,
label: algorithm.label,
extractable,
usages: keyUsages
},
});
template.valueLen = length >> 3;
// PKCS11 generation
this.container.session.generateKey(graphene.KeyGenMechanism.GENERIC_SECRET, template, (err, key) => {
try {
if (err) {
reject(new core.CryptoError(`HMAC: Cannot generate new key\n${err.message}`));
} else {
if (!key) {
throw new Error("Cannot get key from callback function");
}
resolve(new HmacCryptoKey(key, algorithm));
}
} catch (e) {
reject(e);
}
});
});
}
public override async onSign(algorithm: types.Algorithm, key: HmacCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new Promise<ArrayBuffer>((resolve, reject) => {
const mechanism = this.wc2pk11(algorithm, key.algorithm);
this.container.session.createSign(mechanism, key.key).once(Buffer.from(data), (err, data2) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(data2).buffer);
}
});
});
}
public override async onVerify(algorithm: types.Algorithm, key: HmacCryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
const mechanism = this.wc2pk11(algorithm, key.algorithm);
this.container.session.createVerify(mechanism, key.key).once(Buffer.from(data), Buffer.from(signature), (err, ok) => {
if (err) {
reject(err);
} else {
resolve(ok);
}
});
});
}
public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: Pkcs11HmacKeyImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
// get key value
let value: ArrayBuffer;
switch (format.toLowerCase()) {
case "jwk":
const jwk = keyData as types.JsonWebKey;
if (!jwk.k) {
throw new core.OperationError("jwk.k: Cannot get required property");
}
keyData = pvtsutils.Convert.FromBase64Url(jwk.k);
case "raw":
value = keyData as ArrayBuffer;
break;
default:
throw new core.OperationError("format: Must be 'jwk' or 'raw'");
}
// prepare key algorithm
const hmacAlg = {
...algorithm,
name: this.name,
length: value.byteLength * 8 || this.getDefaultLength((algorithm as any).hash.name),
} as Pkcs11HmacKeyAlgorithm;
const template: graphene.ITemplate = this.createTemplate({
action: "import",
type: "secret",
attributes: {
token: algorithm.token,
sensitive: algorithm.sensitive,
label: algorithm.label,
extractable,
usages: keyUsages
},
});
template.value = Buffer.from(value);
// create session object
const sessionObject = this.container.session.create(template);
const key = new HmacCryptoKey(sessionObject.toType<graphene.SecretKey>(), hmacAlg);
return key;
}
public async onExportKey(format: types.KeyFormat, key: HmacCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
const template = key.key.getAttribute({ value: null });
switch (format.toLowerCase()) {
case "jwk":
const jwk: types.JsonWebKey = {
kty: "oct",
k: pvtsutils.Convert.ToBase64Url(template.value!),
alg: `HS${key.algorithm.hash.name.replace("SHA-", "")}`,
ext: true,
key_ops: key.usages,
};
return jwk;
case "raw":
return new Uint8Array(template.value!).buffer;
break;
default:
throw new core.OperationError("format: Must be 'jwk' or 'raw'");
}
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof HmacCryptoKey)) {
throw new TypeError("key: Is not HMAC CryptoKey");
}
}
protected createTemplate(params: ITemplateBuildParameters): ITemplate {
const template = this.container.templateBuilder.build({
...params,
attributes: {
...params.attributes,
id: GUID(),
label: params.attributes.label || "HMAC",
},
});
template.keyType = graphene.KeyType.GENERIC_SECRET;
return template;
}
protected wc2pk11(alg: types.Algorithm, keyAlg: types.HmacKeyAlgorithm): graphene.IAlgorithm {
let res: string;
switch (keyAlg.hash.name.toUpperCase()) {
case "SHA-1":
res = "SHA_1_HMAC";
break;
case "SHA-224":
res = "SHA224_HMAC";
break;
case "SHA-256":
res = "SHA256_HMAC";
break;
case "SHA-384":
res = "SHA384_HMAC";
break;
case "SHA-512":
res = "SHA512_HMAC";
break;
default:
throw new core.OperationError(`Cannot create PKCS11 mechanism from algorithm '${keyAlg.hash.name}'`);
}
return { name: res, params: null };
}
}

View File

@ -0,0 +1,2 @@
export * from "./hmac";
export * from "./key";

View File

@ -0,0 +1,10 @@
import { CryptoKey } from "../../key";
import { Pkcs11HmacKeyAlgorithm } from "../../types";
export class HmacCryptoKey extends CryptoKey<Pkcs11HmacKeyAlgorithm> {
protected override onAssign() {
this.algorithm.length = this.key.get("valueLen") << 3;
}
}

View File

@ -0,0 +1,5 @@
export * from "./aes";
export * from "./ec";
export * from "./hmac";
export * from "./rsa";
export * from "./sha";

View File

@ -0,0 +1,364 @@
import * as asnSchema from "@peculiar/asn1-schema";
import * as jsonSchema from "@peculiar/json-schema";
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import * as pvtsutils from "pvtsutils";
import { CryptoKey } from "../../key";
import {
IContainer, ISessionContainer,
Pkcs11RsaHashedKeyGenParams, Pkcs11Attributes, Pkcs11RsaHashedImportParams,
ITemplateBuildParameters, ITemplate,
} from "../../types";
import { GUID, digest, b64UrlDecode } from "../../utils";
import { RsaCryptoKey } from "./key";
const HASH_PREFIXES: { [alg: string]: Buffer; } = {
"sha-1": Buffer.from([0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14]),
"sha-256": Buffer.from([0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20]),
"sha-384": Buffer.from([0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30]),
"sha-512": Buffer.from([0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40]),
};
export class RsaCrypto implements IContainer {
public publicKeyUsages = ["verify", "encrypt", "wrapKey"];
public privateKeyUsages = ["sign", "decrypt", "unwrapKey"];
public constructor(public container: ISessionContainer) { }
public async generateKey(algorithm: Pkcs11RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
const size = algorithm.modulusLength;
const exp = Buffer.from(algorithm.publicExponent);
// Create PKCS#11 templates
const attrs: Pkcs11Attributes = {
id: GUID(),
label: algorithm.label,
token: algorithm.token,
sensitive: algorithm.sensitive,
extractable,
usages: keyUsages,
};
const privateTemplate = this.createTemplate({
action: "generate",
type: "private",
attributes: attrs,
});
const publicTemplate = this.createTemplate({
action: "generate",
type: "public",
attributes: attrs,
});
// Set RSA params
publicTemplate.publicExponent = exp;
publicTemplate.modulusBits = size;
// PKCS11 generation
return new Promise<types.CryptoKeyPair>((resolve, reject) => {
this.container.session.generateKeyPair(graphene.KeyGenMechanism.RSA, publicTemplate, privateTemplate, (err, keys) => {
try {
if (err) {
reject(new core.CryptoError(`Rsa: Can not generate new key\n${err.message}`));
} else {
if (!keys) {
throw new Error("Cannot get keys from callback function");
}
const wcKeyPair = {
privateKey: new RsaCryptoKey(keys.privateKey, algorithm),
publicKey: new RsaCryptoKey(keys.publicKey, algorithm),
};
resolve(wcKeyPair as any);
}
} catch (e) {
reject(e);
}
});
});
}
public async exportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
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);
const spki = this.jwk2spki(jwk);
const asn = asnSchema.AsnConvert.parse(spki, core.asn1.PublicKeyInfo);
return asn.publicKey;
}
default:
throw new core.OperationError("format: Must be 'raw', 'jwk', 'pkcs8' or 'spki'");
}
}
public async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
switch (format.toLowerCase()) {
case "jwk":
const jwk: any = keyData;
if (jwk.d) {
return this.importJwkPrivateKey(jwk, algorithm as types.RsaHashedKeyGenParams, extractable, keyUsages);
} else {
return this.importJwkPublicKey(jwk, algorithm as types.RsaHashedKeyGenParams, extractable, keyUsages);
}
case "spki": {
const raw = new Uint8Array(keyData as Uint8Array).buffer as ArrayBuffer;
const jwk = this.spki2jwk(raw);
return this.importJwkPublicKey(jwk, algorithm as types.RsaHashedKeyGenParams, extractable, keyUsages);
}
case "pkcs8": {
const raw = new Uint8Array(keyData as Uint8Array).buffer as ArrayBuffer;
const jwk = this.pkcs2jwk(raw);
return this.importJwkPrivateKey(jwk, algorithm as types.RsaHashedKeyGenParams, extractable, keyUsages);
}
default:
throw new core.OperationError("format: Must be 'jwk', 'pkcs8' or 'spki'");
}
}
public getAlgorithm(wcAlgorithmName: string, p11AlgorithmName: string) {
const DEFAULT_RSA = wcAlgorithmName === "RSASSA-PKCS1-v1_5" ? "RSA_PKCS"
: wcAlgorithmName === "RSA-PSS" ? "RSA_PKCS_PSS"
: wcAlgorithmName === "RSA-OAEP" ? "RSA_PKCS_OAEP" : "RSA_PKCS";
const mechanisms = this.container.session.slot.getMechanisms();
let RSA: string | undefined;
for (let i = 0; i < mechanisms.length; i++) {
const mechanism = mechanisms.tryGetItem(i);
if (mechanism && (mechanism.name === p11AlgorithmName || mechanism.name === DEFAULT_RSA)) {
RSA = mechanism.name;
}
}
if (!RSA) {
throw new Error(`Cannot get PKCS11 RSA mechanism by name '${p11AlgorithmName}'`);
}
return RSA;
}
public prepareData(hashAlgorithm: string, data: Buffer) {
// use nodejs crypto for digest calculating
const hash = digest(hashAlgorithm.replace("-", ""), data);
// enveloping hash
const hashPrefix = HASH_PREFIXES[hashAlgorithm.toLowerCase()];
if (!hashPrefix) {
throw new Error(`Cannot get prefix for hash '${hashAlgorithm}'`);
}
return Buffer.concat([hashPrefix, hash]);
}
protected jwkAlgName(algorithm: types.RsaHashedKeyAlgorithm) {
switch (algorithm.name.toUpperCase()) {
case "RSA-OAEP":
const mdSize = /(\d+)$/.exec(algorithm.hash.name)![1];
return `RSA-OAEP${mdSize !== "1" ? `-${mdSize}` : ""}`;
case "RSASSA-PKCS1-V1_5":
return `RS${/(\d+)$/.exec(algorithm.hash.name)![1]}`;
case "RSA-PSS":
return `PS${/(\d+)$/.exec(algorithm.hash.name)![1]}`;
default:
throw new core.OperationError("algorithm: Is not recognized");
}
}
protected async exportJwkPublicKey(key: RsaCryptoKey) {
const pkey: graphene.ITemplate = key.key.getAttribute({
publicExponent: null,
modulus: null,
});
// Remove padding
pkey.publicExponent = pkey.publicExponent!.length > 3
? pkey.publicExponent!.slice(pkey.publicExponent!.length - 3)
: pkey.publicExponent;
const alg = this.jwkAlgName(key.algorithm as types.RsaHashedKeyAlgorithm);
const jwk: types.JsonWebKey = {
kty: "RSA",
alg,
ext: true,
key_ops: key.usages,
e: pvtsutils.Convert.ToBase64Url(pkey.publicExponent!),
n: pvtsutils.Convert.ToBase64Url(pkey.modulus!),
};
return jwk;
}
protected async exportJwkPrivateKey(key: RsaCryptoKey) {
const pkey: graphene.ITemplate = key.key.getAttribute({
publicExponent: null,
modulus: null,
privateExponent: null,
prime1: null,
prime2: null,
exp1: null,
exp2: null,
coefficient: null,
});
// Remove padding
pkey.publicExponent = pkey.publicExponent!.length > 3
? pkey.publicExponent!.slice(pkey.publicExponent!.length - 3)
: pkey.publicExponent;
const alg = this.jwkAlgName(key.algorithm as types.RsaHashedKeyAlgorithm);
const jwk: types.JsonWebKey = {
kty: "RSA",
alg,
ext: true,
key_ops: key.usages,
e: pvtsutils.Convert.ToBase64Url(pkey.publicExponent!),
n: pvtsutils.Convert.ToBase64Url(pkey.modulus!),
d: pvtsutils.Convert.ToBase64Url(pkey.privateExponent!),
p: pvtsutils.Convert.ToBase64Url(pkey.prime1!),
q: pvtsutils.Convert.ToBase64Url(pkey.prime2!),
dp: pvtsutils.Convert.ToBase64Url(pkey.exp1!),
dq: pvtsutils.Convert.ToBase64Url(pkey.exp2!),
qi: pvtsutils.Convert.ToBase64Url(pkey.coefficient!),
};
return jwk;
}
protected importJwkPrivateKey(jwk: types.JsonWebKey, algorithm: Pkcs11RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]) {
const template = this.createTemplate({
action: "import",
type: "private",
attributes: {
id: GUID(),
token: algorithm.token,
sensitive: algorithm.sensitive,
label: algorithm.label,
extractable,
usages: keyUsages
},
});
// Set RSA private key attributes
template.publicExponent = b64UrlDecode(jwk.e!);
template.modulus = b64UrlDecode(jwk.n!);
template.privateExponent = b64UrlDecode(jwk.d!);
template.prime1 = b64UrlDecode(jwk.p!);
template.prime2 = b64UrlDecode(jwk.q!);
template.exp1 = b64UrlDecode(jwk.dp!);
template.exp2 = b64UrlDecode(jwk.dq!);
template.coefficient = b64UrlDecode(jwk.qi!);
const p11key = this.container.session.create(template).toType<graphene.PrivateKey>();
return new RsaCryptoKey(p11key, algorithm);
}
protected importJwkPublicKey(jwk: types.JsonWebKey, algorithm: Pkcs11RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]) {
const template = this.createTemplate({
action: "import",
type: "public",
attributes: {
id: GUID(),
token: algorithm.token,
label: algorithm.label,
extractable,
usages: keyUsages
},
});
// Set RSA public key attributes
template.publicExponent = b64UrlDecode(jwk.e!);
template.modulus = b64UrlDecode(jwk.n!);
const p11key = this.container.session.create(template).toType<graphene.PublicKey>();
return new RsaCryptoKey(p11key, algorithm);
}
/**
* Creates PKCS11 template
* @param params
*/
protected createTemplate(params: ITemplateBuildParameters): ITemplate {
const template = this.container.templateBuilder.build({
...params,
attributes: {
...params.attributes,
label: params.attributes.label || "RSA",
}
});
template.keyType = graphene.KeyType.RSA;
return template;
}
protected jwk2spki(jwk: types.JsonWebKey) {
const key = jsonSchema.JsonParser.fromJSON(jwk, { targetSchema: core.asn1.RsaPublicKey });
const keyInfo = new core.asn1.PublicKeyInfo();
keyInfo.publicKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1";
keyInfo.publicKeyAlgorithm.parameters = null;
keyInfo.publicKey = asnSchema.AsnSerializer.serialize(key);
return asnSchema.AsnSerializer.serialize(keyInfo);
}
protected jwk2pkcs(jwk: types.JsonWebKey) {
const key = jsonSchema.JsonParser.fromJSON(jwk, { targetSchema: core.asn1.RsaPrivateKey });
const keyInfo = new core.asn1.PrivateKeyInfo();
keyInfo.privateKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1";
keyInfo.privateKeyAlgorithm.parameters = null;
keyInfo.privateKey = asnSchema.AsnSerializer.serialize(key);
return asnSchema.AsnSerializer.serialize(keyInfo);
}
protected pkcs2jwk(raw: ArrayBuffer): types.JsonWebKey {
const keyInfo = asnSchema.AsnParser.parse(raw, core.asn1.PrivateKeyInfo);
if (keyInfo.privateKeyAlgorithm.algorithm !== "1.2.840.113549.1.1.1") {
throw new Error("PKCS8 is not RSA private key");
}
const key = asnSchema.AsnParser.parse(keyInfo.privateKey, core.asn1.RsaPrivateKey);
const json = jsonSchema.JsonSerializer.toJSON(key);
return {
kty: "RSA",
...json,
};
}
protected spki2jwk(raw: ArrayBuffer): types.JsonWebKey {
const keyInfo = asnSchema.AsnParser.parse(raw, core.asn1.PublicKeyInfo);
if (keyInfo.publicKeyAlgorithm.algorithm !== "1.2.840.113549.1.1.1") {
throw new Error("PKCS8 is not RSA private key");
}
const key = asnSchema.AsnParser.parse(keyInfo.publicKey, core.asn1.RsaPublicKey);
const json = jsonSchema.JsonSerializer.toJSON(key);
return {
kty: "RSA",
...json,
};
}
}

View File

@ -0,0 +1,5 @@
export * from "./crypto";
export * from "./rsa-pss";
export * from "./rsa-oaep";
export * from "./rsa-ssa";
export * from "./key";

View File

@ -0,0 +1,29 @@
import { CryptoKey } from "../../key";
import { Pkcs11RsaHashedKeyAlgorithm } from "../../types";
export class RsaCryptoKey extends CryptoKey<Pkcs11RsaHashedKeyAlgorithm> {
protected override onAssign() {
if (!this.algorithm.modulusLength) {
this.algorithm.modulusLength = 0;
try {
this.algorithm.modulusLength = this.key.get("modulus").length << 3;
} catch { /*nothing*/ }
}
if (!this.algorithm.publicExponent) {
this.algorithm.publicExponent = new Uint8Array(0);
try {
let publicExponent = this.key.get("publicExponent") as Buffer;
// Remove padding
publicExponent = publicExponent.length > 3
? publicExponent.slice(publicExponent.length - 3)
: publicExponent;
this.algorithm.publicExponent = new Uint8Array(publicExponent);
} catch { /*nothing*/ }
}
}
}

View File

@ -0,0 +1,111 @@
import * as graphene from "graphene-pk11";
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import { CryptoKey } from "../../key";
import {
IContainer, ISessionContainer,
Pkcs11RsaHashedImportParams, Pkcs11RsaHashedKeyAlgorithm, Pkcs11RsaHashedKeyGenParams,
} from "../../types";
import { RsaCrypto } from "./crypto";
import { RsaCryptoKey } from "./key";
export class RsaOaepProvider extends core.RsaOaepProvider implements IContainer {
public override usages: types.ProviderKeyPairUsage = {
privateKey: ["sign", "decrypt", "unwrapKey"],
publicKey: ["verify", "encrypt", "wrapKey"],
};
public crypto: RsaCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new RsaCrypto(container);
}
public async onGenerateKey(algorithm: Pkcs11RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
keyUsages);
return key;
}
public async onEncrypt(algorithm: types.RsaOaepParams, key: RsaCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm);
const context = Buffer.alloc((key.algorithm).modulusLength >> 3);
this.container.session.createCipher(mechanism, key.key)
.once(buf, context, (err, data2) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(data2).buffer);
}
});
});
}
public async onDecrypt(algorithm: types.RsaOaepParams, key: RsaCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm);
const context = Buffer.alloc((key.algorithm).modulusLength >> 3);
this.container.session.createDecipher(mechanism, key.key)
.once(buf, context, (err, data2) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(data2).buffer);
}
});
});
}
public async onExportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return this.crypto.exportKey(format, key);
}
public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: Pkcs11RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
const key = await this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
return key;
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof RsaCryptoKey)) {
throw new TypeError("key: Is not PKCS11 CryptoKey");
}
}
protected wc2pk11(alg: types.RsaOaepParams, keyAlg: Pkcs11RsaHashedKeyAlgorithm): graphene.IAlgorithm {
let params: graphene.RsaOaepParams;
const sourceData = alg.label ? Buffer.from((alg as types.RsaOaepParams).label as Uint8Array) : undefined;
switch (keyAlg.hash.name.toUpperCase()) {
case "SHA-1":
params = new graphene.RsaOaepParams(graphene.MechanismEnum.SHA1, graphene.RsaMgf.MGF1_SHA1, sourceData);
break;
case "SHA-224":
params = new graphene.RsaOaepParams(graphene.MechanismEnum.SHA224, graphene.RsaMgf.MGF1_SHA224, sourceData);
break;
case "SHA-256":
params = new graphene.RsaOaepParams(graphene.MechanismEnum.SHA256, graphene.RsaMgf.MGF1_SHA256, sourceData);
break;
case "SHA-384":
params = new graphene.RsaOaepParams(graphene.MechanismEnum.SHA384, graphene.RsaMgf.MGF1_SHA384, sourceData);
break;
case "SHA-512":
params = new graphene.RsaOaepParams(graphene.MechanismEnum.SHA512, graphene.RsaMgf.MGF1_SHA512, sourceData);
break;
default:
throw new core.OperationError(`Cannot create PKCS11 mechanism from algorithm '${keyAlg.hash.name}'`);
}
const res = { name: "RSA_PKCS_OAEP", params };
return res;
}
}

View File

@ -0,0 +1,116 @@
import * as graphene from "graphene-pk11";
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import { CryptoKey } from "../../key";
import { IContainer, ISessionContainer } from "../../types";
import { RsaCrypto } from "./crypto";
import { RsaCryptoKey } from "./key";
export class RsaPssProvider extends core.RsaPssProvider implements IContainer {
public override usages: types.ProviderKeyPairUsage = {
privateKey: ["sign", "decrypt", "unwrapKey"],
publicKey: ["verify", "encrypt", "wrapKey"],
};
public crypto: RsaCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new RsaCrypto(container);
}
public async onGenerateKey(algorithm: types.RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
keyUsages);
return key;
}
public async onSign(algorithm: types.RsaPssParams, key: RsaCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new Promise<ArrayBuffer>((resolve, reject) => {
let buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm as types.RsaHashedKeyAlgorithm);
mechanism.name = this.crypto.getAlgorithm(this.name, mechanism.name);
if (mechanism.name === "RSA_PKCS_PSS") {
buf = this.crypto.prepareData((key as any).algorithm.hash.name, buf);
}
this.container.session.createSign(mechanism, key.key).once(buf, (err, data2) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(data2).buffer);
}
});
});
}
public async onVerify(algorithm: types.RsaPssParams, key: RsaCryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
let buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm as types.RsaHashedKeyAlgorithm);
mechanism.name = this.crypto.getAlgorithm(this.name, mechanism.name);
if (mechanism.name === "RSA_PKCS_PSS") {
buf = this.crypto.prepareData((key as any).algorithm.hash.name, buf);
}
this.container.session.createVerify(mechanism, key.key).once(buf, Buffer.from(signature), (err, data2) => {
if (err) {
reject(err);
} else {
resolve(data2);
}
});
});
}
public async onExportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return this.crypto.exportKey(format, key);
}
public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
const key = await this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
return key;
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof RsaCryptoKey)) {
throw new TypeError("key: Is not PKCS11 CryptoKey");
}
}
protected wc2pk11(alg: types.RsaPssParams, keyAlg: types.RsaHashedKeyAlgorithm): { name: string, params: graphene.IParams; } {
let mech: string;
let param: graphene.RsaPssParams;
const saltLen = alg.saltLength;
switch (keyAlg.hash.name.toUpperCase()) {
case "SHA-1":
mech = "SHA1_RSA_PKCS_PSS";
param = new graphene.RsaPssParams(graphene.MechanismEnum.SHA1, graphene.RsaMgf.MGF1_SHA1, saltLen);
break;
case "SHA-224":
mech = "SHA224_RSA_PKCS_PSS";
param = new graphene.RsaPssParams(graphene.MechanismEnum.SHA224, graphene.RsaMgf.MGF1_SHA224, saltLen);
break;
case "SHA-256":
mech = "SHA256_RSA_PKCS_PSS";
param = new graphene.RsaPssParams(graphene.MechanismEnum.SHA256, graphene.RsaMgf.MGF1_SHA256, saltLen);
break;
case "SHA-384":
mech = "SHA384_RSA_PKCS_PSS";
param = new graphene.RsaPssParams(graphene.MechanismEnum.SHA384, graphene.RsaMgf.MGF1_SHA384, saltLen);
break;
case "SHA-512":
mech = "SHA512_RSA_PKCS_PSS";
param = new graphene.RsaPssParams(graphene.MechanismEnum.SHA512, graphene.RsaMgf.MGF1_SHA512, saltLen);
break;
default:
throw new core.OperationError(`Cannot create PKCS11 mechanism from algorithm '${keyAlg.hash.name}'`);
}
return { name: mech, params: param };
}
}

View File

@ -0,0 +1,108 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import { CryptoKey } from "../../key";
import { IContainer, ISessionContainer } from "../../types";
import { RsaCrypto } from "./crypto";
import { RsaCryptoKey } from "./key";
export class RsaSsaProvider extends core.RsaSsaProvider implements IContainer {
public override usages: types.ProviderKeyPairUsage = {
privateKey: ["sign", "decrypt", "unwrapKey"],
publicKey: ["verify", "encrypt", "wrapKey"],
};
public crypto: RsaCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new RsaCrypto(container);
}
public async onGenerateKey(algorithm: types.RsaHashedKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair> {
const key = await this.crypto.generateKey(
{ ...algorithm, name: this.name },
extractable,
keyUsages);
return key;
}
public async onSign(algorithm: types.Algorithm, key: RsaCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return new Promise<ArrayBuffer>((resolve, reject) => {
let buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm as types.RsaHashedKeyAlgorithm);
mechanism.name = this.crypto.getAlgorithm(this.name, mechanism.name);
if (mechanism.name === "RSA_PKCS") {
buf = this.crypto.prepareData((key as any).algorithm.hash.name, buf);
}
this.container.session.createSign(mechanism, key.key).once(buf, (err, data2) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(data2).buffer);
}
});
});
}
public async onVerify(algorithm: types.Algorithm, key: RsaCryptoKey, signature: ArrayBuffer, data: ArrayBuffer): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
let buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm as types.RsaHashedKeyAlgorithm);
mechanism.name = this.crypto.getAlgorithm(this.name, mechanism.name);
if (mechanism.name === "RSA_PKCS") {
buf = this.crypto.prepareData((key as any).algorithm.hash.name, buf);
}
this.container.session.createVerify(mechanism, key.key).once(buf, Buffer.from(signature), (err, data2) => {
if (err) {
reject(err);
} else {
resolve(data2);
}
});
});
}
public async onExportKey(format: types.KeyFormat, key: RsaCryptoKey): Promise<types.JsonWebKey | ArrayBuffer> {
return this.crypto.exportKey(format, key);
}
public async onImportKey(format: types.KeyFormat, keyData: types.JsonWebKey | ArrayBuffer, algorithm: types.RsaHashedImportParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
const key = await this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages);
return key;
}
public override checkCryptoKey(key: CryptoKey, keyUsage?: types.KeyUsage) {
super.checkCryptoKey(key, keyUsage);
if (!(key instanceof RsaCryptoKey)) {
throw new TypeError("key: Is not PKCS11 CryptoKey");
}
}
protected wc2pk11(alg: types.Algorithm, keyAlg: types.RsaHashedKeyAlgorithm): { name: string, params: null; } {
let res: string;
switch (keyAlg.hash.name.toUpperCase()) {
case "SHA-1":
res = "SHA1_RSA_PKCS";
break;
case "SHA-224":
res = "SHA224_RSA_PKCS";
break;
case "SHA-256":
res = "SHA256_RSA_PKCS";
break;
case "SHA-384":
res = "SHA384_RSA_PKCS";
break;
case "SHA-512":
res = "SHA512_RSA_PKCS";
break;
default:
throw new core.OperationError(`Cannot create PKCS11 mechanism from algorithm '${keyAlg.hash.name}'`);
}
return { name: res, params: null };
}
}

View File

@ -0,0 +1,27 @@
import * as types from "@peculiar/webcrypto-types";
import type * as graphene from "graphene-pk11";
import { IContainer, ISessionContainer } from "../../types";
export class ShaCrypto implements IContainer {
public constructor(public container: ISessionContainer) { }
public async digest(algorithm: types.Algorithm, data: ArrayBuffer) {
const p11Mech: graphene.IAlgorithm = {
name: algorithm.name.toUpperCase().replace("-", ""),
params: null,
};
return new Promise<ArrayBuffer>((resolve, reject) => {
this.container.session.createDigest(p11Mech).once(Buffer.from(data), (err, data) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(data).buffer);
}
});
});
}
}

View File

@ -0,0 +1,4 @@
export * from "./sha_1";
export * from "./sha_256";
export * from "./sha_384";
export * from "./sha_512";

View File

@ -0,0 +1,22 @@
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import { IContainer, ISessionContainer } from "../../types";
import { ShaCrypto } from "./crypto";
export class Sha1Provider extends core.ProviderCrypto implements IContainer {
public name = "SHA-1";
public usages: types.KeyUsage[] = [];
public crypto: ShaCrypto;
constructor(public container: ISessionContainer) {
super();
this.crypto = new ShaCrypto(container);
}
public override async onDigest(algorithm: types.Algorithm, data: ArrayBuffer): Promise<ArrayBuffer> {
return this.crypto.digest(algorithm, data);
}
}

View File

@ -0,0 +1,5 @@
import { Sha1Provider } from "./sha_1";
export class Sha256Provider extends Sha1Provider {
public override name = "SHA-256";
}

View File

@ -0,0 +1,5 @@
import { Sha1Provider } from "./sha_1";
export class Sha384Provider extends Sha1Provider {
public override name = "SHA-384";
}

View File

@ -0,0 +1,5 @@
import { Sha1Provider } from "./sha_1";
export class Sha512Provider extends Sha1Provider {
public override name = "SHA-512";
}

View File

@ -0,0 +1,17 @@
import { Storage } from "graphene-pk11";
export class Pkcs11Object {
public static assertStorage(obj: Storage | undefined): asserts obj is Storage {
if (!obj) {
throw new TypeError("PKCS#11 object is empty");
}
}
public p11Object?: Storage;
constructor(object?: Storage) {
this.p11Object = object;
}
}

View File

@ -0,0 +1,77 @@
import * as types from "@peculiar/webcrypto-types";
import * as core from "@peculiar/webcrypto-core";
import { ID_DIGEST } from "./const";
import { CryptoKey, CryptoKey as P11CryptoKey } from "./key";
import * as mechs from "./mechs";
import { IContainer, ISessionContainer } from "./types";
import * as utils from "./utils";
export class SubtleCrypto extends core.SubtleCrypto implements IContainer {
public constructor(public container: ISessionContainer) {
super();
//#region AES
this.providers.set(new mechs.AesCbcProvider(this.container));
this.providers.set(new mechs.AesEcbProvider(this.container));
this.providers.set(new mechs.AesGcmProvider(this.container));
//#endregion
// #region RSA
this.providers.set(new mechs.RsaSsaProvider(this.container));
this.providers.set(new mechs.RsaPssProvider(this.container));
this.providers.set(new mechs.RsaOaepProvider(this.container));
// #endregion
// #region EC
this.providers.set(new mechs.EcdsaProvider(this.container));
this.providers.set(new mechs.EcdhProvider(this.container));
// #endregion
//#region SHA
this.providers.set(new mechs.Sha1Provider(this.container));
this.providers.set(new mechs.Sha256Provider(this.container));
this.providers.set(new mechs.Sha384Provider(this.container));
this.providers.set(new mechs.Sha512Provider(this.container));
//#endregion
// #region HMAC
this.providers.set(new mechs.HmacProvider(this.container));
// #endregion
}
public override async generateKey(algorithm: types.RsaHashedKeyGenParams | types.EcKeyGenParams, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair>;
public override async generateKey(algorithm: types.AesKeyGenParams | types.HmacKeyGenParams | types.Pbkdf2Params, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey>;
public override async generateKey(algorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<types.CryptoKeyPair | types.CryptoKey> {
const keys = await super.generateKey(algorithm, extractable, keyUsages);
// Fix ID for generated key pair. It must be hash of public key raw
if (utils.isCryptoKeyPair(keys)) {
const publicKey = keys.publicKey as P11CryptoKey;
const privateKey = keys.privateKey as P11CryptoKey;
const raw = await this.exportKey("spki", publicKey);
const digest = utils.digest(ID_DIGEST, raw).slice(0, 16);
publicKey.key.id = digest;
publicKey.id = P11CryptoKey.getID(publicKey.key);
privateKey.key.id = digest;
privateKey.id = P11CryptoKey.getID(privateKey.key);
}
return keys;
}
public override async importKey(format: types.KeyFormat, keyData: types.JsonWebKey | types.BufferSource, algorithm: types.AlgorithmIdentifier, extractable: boolean, keyUsages: types.KeyUsage[]): Promise<CryptoKey> {
const key = await super.importKey(format, keyData, algorithm, extractable, keyUsages);
// Fix ID for generated key pair. It must be hash of public key raw
if (key.type === "public" && extractable) {
const publicKey = key as P11CryptoKey;
const raw = await this.exportKey("spki", publicKey);
const digest = utils.digest(ID_DIGEST, raw).slice(0, 16);
publicKey.key.id = digest;
publicKey.id = P11CryptoKey.getID(publicKey.key);
}
return key as CryptoKey;
}
}

View File

@ -0,0 +1,101 @@
import * as graphene from "graphene-pk11";
import { BufferSourceConverter } from "pvtsutils";
import * as types from "./types";
export class TemplateBuilder implements types.ITemplateBuilder {
public build(params: types.ITemplateBuildParameters): types.ITemplate {
const { attributes, action, type } = params;
const template: types.ITemplate = {
token: !!attributes.token,
};
if (action === "copy") {
if (type === "private") {
if (attributes.token) {
// TODO SafeNET 5110 token requires CKA_SENSITIVE:true and CKA_EXTRACTABLE:false
// Those values must be set in C_GenerateKeyPair, or C_CopyObject, or C_CreateObject
// Object.assign<types.ITemplate, types.ITemplate>(template, {
// sensitive: true,
// });
}
}
} else {
if (attributes.label) {
template.label = attributes.label
}
if (attributes.id) {
template.id = Buffer.from(BufferSourceConverter.toArrayBuffer(attributes.id));
}
const sign = attributes.usages?.includes("sign");
const verify = attributes.usages?.includes("verify");
const wrap = attributes.usages?.includes("wrapKey");
const unwrap = attributes.usages?.includes("unwrapKey");
const encrypt = unwrap || attributes.usages?.includes("encrypt");
const decrypt = wrap || attributes.usages?.includes("decrypt");
const derive = attributes.usages?.includes("deriveBits") || attributes.usages?.includes("deriveKey");
switch (type) {
case "private":
Object.assign<types.ITemplate, types.ITemplate>(template, {
class: graphene.ObjectClass.PRIVATE_KEY,
sensitive: !!attributes.sensitive,
private: true,
extractable: !!attributes.extractable,
derive,
sign,
decrypt,
unwrap,
});
break;
case "public":
Object.assign<types.ITemplate, types.ITemplate>(template, {
token: !!attributes.token,
class: graphene.ObjectClass.PUBLIC_KEY,
private: false,
derive,
verify,
encrypt,
wrap,
});
break;
case "secret":
Object.assign<types.ITemplate, types.ITemplate>(template, {
class: graphene.ObjectClass.SECRET_KEY,
sensitive: !!attributes.sensitive,
extractable: !!attributes.extractable,
derive,
sign,
verify,
decrypt,
encrypt,
unwrap,
wrap,
});
break;
case "request":
if (template.id) {
template.objectId = template.id;
delete template.id;
}
Object.assign<types.ITemplate, types.ITemplate>(template, {
class: graphene.ObjectClass.DATA,
application: "webcrypto-p11",
private: false,
});
break;
case "x509":
Object.assign<types.ITemplate, types.ITemplate>(template, {
class: graphene.ObjectClass.CERTIFICATE,
certType: graphene.CertificateType.X_509,
private: false,
});
break;
}
}
return template;
}
}

View File

@ -0,0 +1,122 @@
import * as types from "@peculiar/webcrypto-types";
import * as graphene from "graphene-pk11";
import { BufferSource } from "pvtsutils";
export type ITemplate = graphene.ITemplate;
export interface Pkcs11Attributes {
id?: BufferSource;
token?: boolean;
sensitive?: boolean;
label?: string;
extractable?: boolean;
usages?: types.KeyUsage[];
}
export type TemplateBuildType = "private" | "public" | "secret" | "x509" | "request";
export type TemplateBuildAction = "generate" | "import" | "copy";
export interface ITemplateBuildParameters {
type: TemplateBuildType;
action: TemplateBuildAction;
attributes: Pkcs11Attributes;
}
/**
* export interface of PKCS#11 template builder
*/
export interface ITemplateBuilder {
/**
* Returns a PKCS#11 template
* @param params Template build parameters
*/
build(params: ITemplateBuildParameters): ITemplate;
}
export interface ISessionContainer {
readonly session: graphene.Session;
templateBuilder: ITemplateBuilder;
}
export interface IContainer {
readonly container: ISessionContainer;
}
export interface CryptoParams {
/**
* Path to library
*/
library: string;
/**
* Name of PKCS11 module
*/
name?: string;
/**
* Index of slot
*/
slot?: number;
readWrite?: boolean;
/**
* PIN of slot
*/
pin?: string;
/**
* list of vendor json files
*/
vendors?: string[];
/**
* NSS library parameters
*/
libraryParameters?: string;
}
export interface ProviderInfo {
id: string;
name: string;
reader: string;
slot: number;
serialNumber: string;
algorithms: string[];
isRemovable: boolean;
isHardware: boolean;
}
export interface Pkcs11Params {
token?: boolean;
sensitive?: boolean;
label?: string;
}
export interface Pkcs11KeyGenParams extends types.Algorithm, Pkcs11Params { }
export interface Pkcs11AesKeyGenParams extends types.AesKeyGenParams, Pkcs11KeyGenParams { }
export interface Pkcs11HmacKeyGenParams extends types.HmacKeyGenParams, Pkcs11KeyGenParams { }
export interface Pkcs11EcKeyGenParams extends types.EcKeyGenParams, Pkcs11KeyGenParams { }
export interface Pkcs11RsaHashedKeyGenParams extends types.RsaHashedKeyGenParams, Pkcs11KeyGenParams { }
export interface Pkcs11KeyImportParams extends types.Algorithm, Pkcs11Params { }
export interface Pkcs11EcKeyImportParams extends types.EcKeyImportParams, Pkcs11KeyImportParams { }
export interface Pkcs11RsaHashedImportParams extends types.RsaHashedImportParams, Pkcs11KeyImportParams { }
export interface Pkcs11HmacKeyImportParams extends types.HmacImportParams, Pkcs11KeyImportParams { }
export interface Pkcs11AesKeyImportParams extends types.Algorithm, Pkcs11KeyImportParams { }
export interface Pkcs11KeyAlgorithm extends types.KeyAlgorithm {
token: boolean;
sensitive: boolean;
label: string;
}
export interface Pkcs11RsaHashedKeyAlgorithm extends types.RsaHashedKeyAlgorithm, Pkcs11KeyAlgorithm { }
export interface Pkcs11EcKeyAlgorithm extends types.EcKeyAlgorithm, Pkcs11KeyAlgorithm { }
export interface Pkcs11AesKeyAlgorithm extends types.AesKeyAlgorithm, Pkcs11KeyAlgorithm { }
export interface Pkcs11HmacKeyAlgorithm extends types.HmacKeyAlgorithm, Pkcs11KeyAlgorithm { }

View File

@ -0,0 +1,166 @@
import * as types from "@peculiar/webcrypto-types";
import * as crypto from "crypto";
import { Slot, SlotFlag } from "graphene-pk11";
import { BufferSourceConverter, Convert } from "pvtsutils";
import { ID_DIGEST } from "./const";
import { ProviderInfo } from "./types";
export interface HashedAlgorithm extends types.Algorithm {
hash: types.AlgorithmIdentifier;
}
export function GUID(): Buffer {
return crypto.randomBytes(20);
}
export function b64UrlDecode(b64url: string): Buffer {
return Buffer.from(Convert.FromBase64Url(b64url));
}
/**
* Converts BufferSource to Buffer
* @param data Array which must be prepared
*/
export function prepareData(data: types.BufferSource): Buffer {
return Buffer.from(BufferSourceConverter.toArrayBuffer(data));
}
export function isHashedAlgorithm(data: any): data is HashedAlgorithm {
return data instanceof Object
&& "name" in data
&& "hash" in data;
}
export function isCryptoKeyPair(data: any): data is types.CryptoKeyPair {
return data && data.privateKey && data.publicKey;
}
export function prepareAlgorithm(algorithm: types.AlgorithmIdentifier): types.Algorithm {
if (typeof algorithm === "string") {
return {
name: algorithm,
} as types.Algorithm;
}
if (isHashedAlgorithm(algorithm)) {
const preparedAlgorithm = { ...algorithm };
preparedAlgorithm.hash = prepareAlgorithm(algorithm.hash);
return preparedAlgorithm as HashedAlgorithm;
}
return { ...algorithm };
}
/**
* Calculates digest for given data
* @param algorithm
* @param data
*/
export function digest(algorithm: string, data: types.BufferSource): Buffer {
const hash = crypto.createHash(algorithm.replace("-", ""));
hash.update(prepareData(Buffer.from(BufferSourceConverter.toArrayBuffer(data))));
return hash.digest();
}
function calculateProviderID(slot: Slot) {
const str = slot.manufacturerID + slot.slotDescription + slot.getToken().serialNumber + slot.handle.toString("hex");
return digest(ID_DIGEST, Buffer.from(str)).toString("hex");
}
export function getProviderInfo(slot: Slot) {
// get index of 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: ProviderInfo = {
id: calculateProviderID(slot),
slot: index,
name: token.label,
reader: slot.slotDescription,
serialNumber: slot.getToken().serialNumber,
algorithms: [],
isRemovable: !!(slot.flags & SlotFlag.REMOVABLE_DEVICE),
isHardware: !!(slot.flags & 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;
default:
}
if (algName && !provider.algorithms.some((alg) => alg === algName)) {
provider.algorithms.push(algName);
}
}
return provider;
}

View File

@ -0,0 +1,44 @@
import * as assert from "assert";
import { AesCryptoKey } from "../src/mechs";
import { Pkcs11AesKeyGenParams, Pkcs11AesKeyImportParams } from "../src/types";
import { crypto } from "./config";
context("AES", () => {
context("token", () => {
it("generate", async () => {
const alg: Pkcs11AesKeyGenParams = {
name: "AES-CBC",
length: 128,
label: "custom",
token: true,
sensitive: true,
};
const key = await crypto.subtle.generateKey(alg, false, ["encrypt", "decrypt"]) as AesCryptoKey;
assert.strictEqual(key.algorithm.token, true);
assert.strictEqual(key.algorithm.label, alg.label);
assert.strictEqual(key.algorithm.sensitive, true);
});
it("import", async () => {
const alg: Pkcs11AesKeyImportParams = {
name: "AES-CBC",
label: "custom",
token: true,
sensitive: true,
};
const raw = Buffer.from("1234567890abcdef1234567809abcdef");
const key = await crypto.subtle.importKey("raw", raw, alg, false, ["encrypt", "decrypt"]) as AesCryptoKey;
assert.strictEqual(key.algorithm.token, true);
assert.strictEqual(key.algorithm.label, alg.label);
assert.strictEqual(key.algorithm.sensitive, true);
});
});
});

View File

@ -0,0 +1,255 @@
import * as x509 from "@peculiar/x509";
import * as core from "@peculiar/webcrypto-core";
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
import { X509Certificate, X509CertificateRequest } from "../src";
import { crypto } from "./config";
import { isNSS } from "./helper";
import { Pkcs11RsaHashedKeyAlgorithm } from "../src/types";
const X509_RAW = Buffer.from("308203A830820290A003020102020900FEDCE3010FC948FF300D06092A864886F70D01010505003034310B300906035504061302465231123010060355040A0C094468696D796F7469733111300F06035504030C084365727469676E61301E170D3037303632393135313330355A170D3237303632393135313330355A3034310B300906035504061302465231123010060355040A0C094468696D796F7469733111300F06035504030C084365727469676E6130820122300D06092A864886F70D01010105000382010F003082010A0282010100C868F1C9D6D6B3347526821EECB4BEEA5CE126ED114761E1A27C16784021E4609E5AC863E1C4B19692FF186D6923E12B62F7DDE2362F9107B948CF0EEC79B62CE7344B700825A33C871B19F281070F389019D311FE86B4F2D15E1E1E96CD806CCE3B3193B6F2A0D0A995127DA59ACC6BC884568A33A9E722155316F0CC17EC575FE9A20A9809DEE35F9C6FDC48E3850B155AA6BA9FAC48E309B2F7F432DE5E34BE1C785D425BCE0E228F4D90D77D3218B30B2C6ABF8E3F141189200E7714B53D940887F7251ED5B26000EC6F2A28256E2A3E186317253F3E442016F626C825AE054AB4E7632CF38C16537E5CFB111A08C146629F22B8F1C28D69DCFA3A5806DF0203010001A381BC3081B9300F0603551D130101FF040530030101FF301D0603551D0E041604141AEDFE413990B42459BE01F252D545F65A39DC1130640603551D23045D305B80141AEDFE413990B42459BE01F252D545F65A39DC11A138A4363034310B300906035504061302465231123010060355040A0C094468696D796F7469733111300F06035504030C084365727469676E61820900FEDCE3010FC948FF300E0603551D0F0101FF040403020106301106096086480186F8420101040403020007300D06092A864886F70D0101050500038201010085031E9271F642AFE1A3619EEBF3C00FF2A5D4DA95E6D6BE68363D7E6E1F4C8AEFD10F216D5EA55263CE12F8EF2ADA6FEB37FE1302C7CB3B3E226BDA612E7FD4723DDD30E11E4C40198C0FD79CD183307B9859DC7DC6B90C294CA133A2EB673A6584D396E2ED7645708FB52BDEF923D6496E3C14B5C69F351E50D0C18F6A70440262CBAE1D6841A7AA57E853AA07D206F6D514060B9103752C6C72B561959A0D8BB90DE7F5DF54CDDEE6D8D609089763E5C12EB0B74426C026C0AF55309E3BD5362A1904F45C1EFFCF2CB7FFD0FD874011D51123BB48C021A9A4282DFD15F8B04E2BF4305B21FC119134BE41EF7B9D9775FF9795C096582FEABB46D7BBE4D92E", "hex");
const X509_REQUEST_RAW = Buffer.from("308202BC308201A402003078310B3009060355040613025553311430120603550403130B6D792D737974652E6E6574311430120603550407130B53756E20416E746F6E696F311D301B060355040A13144D7920686F6D65206F7267616E697A6174696F6E310F300D06035504081306546573786173310D300B060355040B13044E6F6E6530820122300D06092A864886F70D01010105000382010F003082010A028201010092323A4560FF7FB0C022B6A9B72FE2F29F544AB8AAA4CFD1A1A71D9D0EB7B89CE85505DE15AC11785EDC5FFE45BC6B39E0688B7680FE1AFA42E36C50070AB52F01C1E86B139D10C9A0729CECDBF3CDF6FF538B6C2AE80498D6EAD5C90AC46131FD542C9EF0F400FCDA341E6CB61BA3C612D17A6CACB6415FBCFBF912E16BDCC3689C8C95BBE0C118884FC8A0F9597CB734B4C84A451FCB511BE6C7FDE0F45FE5B386CD32C675249012C3E2A0F18AB8DC880A960831943747E8C92F1972DDF8C18C59E07D59E98609B62B94FF88172D928D3B14FB8D66B4A6DE8B6DAE3AB6552F5CC8BFD1CF97DFB252EB551DBE2AF33826B3E26190ED48646556068196369DBB0203010001A000300D06092A864886F70D01010B050003820101001EBF4FF997C237C6001D4170BB8FCF64E3B3137D7746F4E08A3F884A127F235665EBBBB497FF8691AED2E1268728FFFF902ED577C86BDA86A59DFED036FEEAF7DE7B766F5AF1F7A08A7432C3B6F99C7223D0B76067A8D789B168F28E8FDEBD8D5F7EFFFE1F38EAAA0DB5BB1F861E9463B1299CC00E5329D24D8D0F049E650FEC4D62143651EBEDFF10795F0B1BC325EAC01951E2344FFD8850BF6A3FC1304FD4C4136CF27FE443A69B39F92F07A7F48BC8AC2AF3C9F3FD8236424DB838806F884677CCD122DE815C400E726A24B8A9E4D50FF75EFBCC2F8DCED7E88C4E727B1BAD84E0FA0F65A91D1D7FF54AF7279A33043ECAF205CDFACD05511E7E0641A970", "hex");
const X509_PEM = core.PemConverter.fromBufferSource(X509_RAW, "CERTIFICATE");
const X509_REQUEST_PEM = core.PemConverter.fromBufferSource(X509_REQUEST_RAW, "CERTIFICATE REQUEST");
(isNSS("CertStorage. NSS is readonly")
? context.skip
: context)
("Certificate storage", () => {
beforeEach(async () => {
let keys = await crypto.certStorage.keys();
if (keys.length) {
await crypto.certStorage.clear();
}
keys = await crypto.certStorage.keys();
assert.strictEqual(keys.length, 0);
});
context("indexOf", () => {
const vector: {
type: types.CryptoCertificateType;
data: string | types.BufferSource;
format: types.CryptoCertificateFormat;
}[] = [
{ type: "x509", data: X509_RAW, format: "raw" },
{ type: "request", data: X509_REQUEST_RAW, format: "raw" },
{ type: "x509", data: X509_PEM, format: "pem" },
{ type: "request", data: X509_REQUEST_PEM, format: "pem" },
];
vector.forEach((params) => {
it(`${params.type} ${params.format}`, async () => {
const cert = await crypto.certStorage.importCert(params.format, params.data, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
const index = await crypto.certStorage.setItem(cert);
const found = await crypto.certStorage.indexOf(cert);
assert.strictEqual(found, null);
const certByIndex = await crypto.certStorage.getItem(index, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
assert.strictEqual(!!certByIndex, true, "Cannot get cert item from storage");
});
});
});
context("importCert", () => {
it("x509", async () => {
const item = await crypto.certStorage.importCert("raw", X509_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]) as X509Certificate;
const json = item.toJSON();
assert.strictEqual(json.publicKey.algorithm.name, "RSASSA-PKCS1-v1_5");
assert.strictEqual((json.publicKey.algorithm as Pkcs11RsaHashedKeyAlgorithm).hash.name, "SHA-256");
assert.strictEqual(json.notBefore.toISOString(), "2007-06-29T15:13:05.000Z");
assert.strictEqual(json.notAfter.toISOString(), "2027-06-29T15:13:05.000Z");
assert.strictEqual(json.subjectName, "C=FR, O=Dhimyotis, CN=Certigna");
assert.strictEqual(json.issuerName, "C=FR, O=Dhimyotis, CN=Certigna");
assert.strictEqual(json.serialNumber, "00fedce3010fc948ff");
assert.strictEqual(json.type, "x509");
assert.strictEqual(item.label, "Certigna");
assert.strictEqual(item.token, false);
assert.strictEqual(item.sensitive, false);
});
it("x509 to token", async () => {
const item = await crypto.certStorage.importCert(
"raw",
X509_RAW,
{
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
token: true,
label: "custom",
} as types.RsaHashedImportParams,
["verify"]);
assert.ok(item instanceof X509Certificate);
assert.strictEqual(item.label, "custom");
assert.strictEqual(item.token, true);
assert.strictEqual(item.sensitive, false);
});
it("request", async () => {
const item = await crypto.certStorage.importCert("raw", X509_REQUEST_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" } as types.RsaHashedImportParams, ["verify"]) as X509CertificateRequest;
const json = item.toJSON();
assert.strictEqual(json.publicKey.algorithm.name, "RSASSA-PKCS1-v1_5");
assert.strictEqual((json.publicKey.algorithm as Pkcs11RsaHashedKeyAlgorithm).hash.name, "SHA-384");
assert.strictEqual(json.subjectName, "C=US, CN=my-syte.net, L=Sun Antonio, O=My home organization, ST=Tesxas, OU=None");
assert.strictEqual(json.type, "request");
assert.strictEqual(item.label, "X509 Request");
assert.strictEqual(item.token, false);
assert.strictEqual(item.sensitive, false);
});
it("request to token", async () => {
const item = await crypto.certStorage.importCert("raw", X509_REQUEST_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384", token: true, label: "custom" } as types.RsaHashedImportParams, ["verify"]) as X509CertificateRequest;
assert.strictEqual(item.label, "custom");
assert.strictEqual(item.token, true);
assert.strictEqual(item.sensitive, false);
});
it("wrong type throws error", async () => {
await assert.rejects(crypto.certStorage.importCert("wrong" as any, X509_REQUEST_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" } as types.RsaHashedImportParams, ["verify"]));
});
});
context("set/get item", () => {
it("x509", async () => {
const x509 = await crypto.certStorage.importCert("raw", X509_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
const index = await crypto.certStorage.setItem(x509);
const x5092 = await crypto.certStorage.getItem(index);
assert.strictEqual(!!x5092, true);
});
it("request", async () => {
const request = await crypto.certStorage.importCert("raw", X509_REQUEST_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
const index = await crypto.certStorage.setItem(request);
const request2 = await crypto.certStorage.getItem(index, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
assert.strictEqual(!!request2, true);
});
it("null", async () => {
const item = await crypto.certStorage.getItem("not exist");
assert.strictEqual(item, null);
});
it("set wrong object", async () => {
await assert.rejects(crypto.certStorage.setItem({} as any), Error);
});
});
context("get value", () => {
it("x509", async () => {
const x509 = await crypto.certStorage.importCert("raw", X509_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
const index = await crypto.certStorage.setItem(x509);
const raw = await crypto.certStorage.getValue(index);
assert.strictEqual(!!raw, true);
assert.strictEqual(raw!.byteLength > 0, true);
});
it("request", async () => {
const request = await crypto.certStorage.importCert("raw", X509_REQUEST_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
const index = await crypto.certStorage.setItem(request);
const raw = await crypto.certStorage.getValue(index);
assert.strictEqual(!!raw, true);
assert.strictEqual(raw!.byteLength > 0, true);
});
it("null", async () => {
const item = await crypto.certStorage.getItem("not exist");
assert.strictEqual(item, null);
});
it("set wrong object", async () => {
await assert.rejects(crypto.certStorage.setItem({} as any), Error);
});
});
it("removeItem", async () => {
let indexes = await crypto.certStorage.keys();
assert.strictEqual(indexes.length, 0);
const request = await crypto.certStorage.importCert("raw", X509_REQUEST_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
await crypto.certStorage.setItem(request);
const x509 = await crypto.certStorage.importCert("raw", X509_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
const x509Index = await crypto.certStorage.setItem(x509);
indexes = await crypto.certStorage.keys();
assert.strictEqual(indexes.length, 2);
await crypto.certStorage.removeItem(x509Index);
indexes = await crypto.certStorage.keys();
assert.strictEqual(indexes.length, 1);
});
it("exportCert", async () => {
const x509 = await crypto.certStorage.importCert("raw", X509_RAW, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" } as types.RsaHashedImportParams, ["verify"]);
const raw = await crypto.certStorage.exportCert("raw", x509);
assert.strictEqual(Buffer.from(raw).equals(X509_RAW), true);
});
it("test", async () => {
const alg = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048
};
const keys = await crypto.subtle.generateKey(
{
...alg,
token: true,
} as any,
false,
[
"sign",
"verify"
]);
const keyIndex = await crypto.keyStorage.setItem(keys.privateKey);
const cert = await x509.X509CertificateGenerator.createSelfSigned(
{
serialNumber: "01",
name: "CN=Test",
notBefore: new Date("2020/01/01"),
notAfter: new Date("2020/01/02"),
signingAlgorithm: alg,
keys,
extensions: [
new x509.BasicConstraintsExtension(true, 2, true),
new x509.ExtendedKeyUsageExtension(
["1.2.3.4.5.6.7", "2.3.4.5.6.7.8"],
true
),
new x509.KeyUsagesExtension(
x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign,
true
)
]
},
crypto,
);
const fortifyCert = await crypto.certStorage.importCert(
"raw",
cert.rawData,
{
...alg,
token: true,
},
["verify"]
);
const certIndex = await crypto.certStorage.setItem(fortifyCert);
assert.strictEqual(keyIndex.split("-")[2], certIndex.split("-")[2]);
});
});

View File

@ -0,0 +1,26 @@
import * as os from "os";
import { Crypto } from "../src";
export const config = process.env.PV_CRYPTO === "nss" ?
{
library: os.platform() === "darwin" ? "/usr/local/opt/nss/lib/libsoftokn3.dylib" : "/usr/lib/x86_64-linux-gnu/nss/libsoftokn3.so",
libraryParameters: `configdir='' certPrefix='' keyPrefix='' secmod='' flags=readOnly,noCertDB,noModDB,forceOpen,optimizeSpace`,
name: "NSS",
slot: 1,
readWrite: true,
}
:
{
library: "/usr/local/lib/softhsm/libsofthsm2.so",
name: "SoftHSMv2",
slot: 0,
readWrite: true,
pin: "12345",
};
console.log(`PKCS11 provider: ${config.name} at ${config.library}`);
export const crypto = new Crypto(config);
process.on("beforeExit", () => {
crypto.close();
});

View File

@ -0,0 +1,70 @@
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
import * as graphene from "graphene-pk11";
import { ITemplateBuilder, ITemplateBuildParameters, ITemplate, Pkcs11AesKeyAlgorithm } from "../src/types";
import { config, crypto } from "./config";
context("Crypto", () => {
it("get random values", () => {
const buf = new Uint8Array(16);
const check = Buffer.from(buf).toString("base64");
assert.notStrictEqual(Buffer.from(crypto.getRandomValues(buf)).toString("base64"), check, "Has no random values");
});
it("get random values with large buffer", () => {
const buf = new Uint8Array(65600);
assert.throws(() => {
crypto.getRandomValues(buf);
}, Error);
});
it("reset", () => {
const currentHandle = crypto.session.handle.toString("hex");
crypto.reset();
if (config.pin) {
crypto.login(config.pin);
}
const newHandle = crypto.session.handle.toString("hex");
assert.strictEqual(currentHandle !== newHandle, true, "handle of session wasn't changed");
});
context("custom template builder", () => {
class CustomTemplateBuilder implements ITemplateBuilder {
build(params: ITemplateBuildParameters): ITemplate {
return {
label: "CustomTemplate",
token: false,
sensitive: false,
class: graphene.ObjectClass.SECRET_KEY,
encrypt: true,
decrypt: false,
sign: false,
verify: false,
wrap: false,
unwrap: false,
derive: false,
};
}
}
const templateBuilder = crypto.templateBuilder;
before(() => {
crypto.templateBuilder = new CustomTemplateBuilder();
});
after(() => {
crypto.templateBuilder = templateBuilder;
});
it("create AES-CBC", async () => {
const key = await crypto.subtle.generateKey({ name: "AES-CBC", length: 128 } as types.AesKeyGenParams, true, ["encrypt", "decrypt"]);
assert.strictEqual((key.algorithm as Pkcs11AesKeyAlgorithm).label, "CustomTemplate");
assert.deepStrictEqual(key.usages, ["encrypt"]);
});
});
});

138
packages/pkcs11/test/ec.ts Normal file
View File

@ -0,0 +1,138 @@
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
import { Convert } from "pvtsutils";
import { EcCryptoKey } from "../src/mechs";
import { Pkcs11EcKeyGenParams } from "../src/types";
import { crypto } from "./config";
context("EC", () => {
context("token", () => {
it("generate", async () => {
const alg: Pkcs11EcKeyGenParams = {
name: "ECDSA",
namedCurve: "P-256",
label: "custom",
token: true,
sensitive: true,
};
const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);
const privateKey = keys.privateKey as EcCryptoKey;
assert.strictEqual(privateKey.algorithm.token, true);
assert.strictEqual(privateKey.algorithm.label, alg.label);
assert.strictEqual(privateKey.algorithm.sensitive, true);
const publicKey = keys.publicKey as EcCryptoKey;
assert.strictEqual(publicKey.algorithm.token, true);
assert.strictEqual(publicKey.algorithm.label, alg.label);
assert.strictEqual(publicKey.algorithm.sensitive, false);
});
it("import", async () => {
const alg: Pkcs11EcKeyGenParams = {
name: "ECDSA",
namedCurve: "P-256",
label: "custom",
token: true,
sensitive: true,
};
const spki = Convert.FromBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7MvmXG6zXIRe0q6S9IWJxqeiAl++411K6TGJKtAbs32jxVnLvWGR+QElM0CRs/Xgit5g1xGywroh0cN3cJBbA==");
const publicKey = await crypto.subtle.importKey("spki", spki, alg, false, ["verify"]) as EcCryptoKey;
assert.strictEqual(publicKey.algorithm.token, true);
assert.strictEqual(publicKey.algorithm.label, alg.label);
assert.strictEqual(publicKey.algorithm.sensitive, false);
});
});
context("Extra ECC named curves", () => {
const namedCurves = [
"brainpoolP160r1",
"brainpoolP160t1",
"brainpoolP192r1",
"brainpoolP192t1",
"brainpoolP224r1",
"brainpoolP224t1",
"brainpoolP256r1",
"brainpoolP256t1",
"brainpoolP320r1",
"brainpoolP320t1",
"brainpoolP384r1",
"brainpoolP384t1",
"brainpoolP512r1",
"brainpoolP512t1",
];
context("sign/verify + pkcs8/spki", () => {
const data = new Uint8Array(10);
namedCurves.forEach((namedCurve) => {
it(namedCurve, async () => {
const alg: types.EcKeyGenParams = { name: "ECDSA", namedCurve };
const signAlg = { ...alg, hash: "SHA-256" } as types.EcdsaParams;
const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const signature = await crypto.subtle.sign(signAlg, keys.privateKey, data);
const ok = await crypto.subtle.verify(signAlg, keys.publicKey, signature, data);
assert.ok(ok);
const pkcs8 = await crypto.subtle.exportKey("pkcs8", keys.privateKey);
const spki = await crypto.subtle.exportKey("spki", keys.publicKey);
const privateKey = await crypto.subtle.importKey("pkcs8", pkcs8, alg, true, ["sign"]);
const publicKey = await crypto.subtle.importKey("spki", spki, alg, true, ["verify"]);
const signature2 = await crypto.subtle.sign(signAlg, privateKey, data);
const ok2 = await crypto.subtle.verify(signAlg, keys.publicKey, signature2, data);
assert.ok(ok2);
const ok3 = await crypto.subtle.verify(signAlg, publicKey, signature, data);
assert.ok(ok3);
});
});
});
context("deriveBits + jwk", () => {
namedCurves.forEach((namedCurve) => {
const test = [
// Skip next curves, SoftHSM throws CKR_FUNCTION_FAILED
"brainpoolP160r1",
"brainpoolP160t1",
"brainpoolP192r1",
"brainpoolP192t1",
"brainpoolP224r1",
"brainpoolP224t1",
].includes(namedCurve)
? it.skip
: it;
test(namedCurve, async () => {
const alg: types.EcKeyGenParams = { name: "ECDH", namedCurve };
const keys = await crypto.subtle.generateKey(alg, true, ["deriveBits", "deriveKey"]);
const deriveAlg: types.EcdhKeyDeriveParams = { name: "ECDH", public: keys.publicKey };
const derivedBits = await crypto.subtle.deriveBits(deriveAlg, keys.privateKey, 128);
const privateJwk = await crypto.subtle.exportKey("jwk", keys.privateKey);
const publicJwk = await crypto.subtle.exportKey("jwk", keys.publicKey);
const privateKey = await crypto.subtle.importKey("jwk", privateJwk, alg, true, ["deriveBits"]);
const publicKey = await crypto.subtle.importKey("jwk", publicJwk, alg, true, []);
const derivedBits2 = await crypto.subtle.deriveBits({ name: "ECDH", public: keys.publicKey } as types.EcdhKeyDeriveParams, privateKey, 128);
const derivedBits3 = await crypto.subtle.deriveBits({ name: "ECDH", public: publicKey } as types.EcdhKeyDeriveParams, keys.privateKey, 128);
assert.strictEqual(Convert.ToHex(derivedBits2), Convert.ToHex(derivedBits));
assert.strictEqual(Convert.ToHex(derivedBits3), Convert.ToHex(derivedBits));
});
});
});
});
});

View File

@ -0,0 +1,30 @@
import { CryptoKey } from "../src";
import { config } from "./config";
/**
* Returns true if blobs from keys are equal
* @param a Crypto key
* @param b Crypto key
*/
export function isKeyEqual(a: CryptoKey, b: CryptoKey) {
if (a instanceof CryptoKey && b instanceof CryptoKey) {
return (a as any).data.equals((b as any).data);
}
return false;
}
function testManufacturer(manufacturerID: string, message: string) {
if (config.name === manufacturerID) {
console.warn(" \x1b[33mWARN:\x1b[0m Test is not supported for %s. %s", manufacturerID, message || "");
return true;
}
return false;
}
export function isSoftHSM(message: string) {
return testManufacturer("SoftHSMv2", message);
}
export function isNSS(message: string) {
return testManufacturer("NSS", message);
}

View File

@ -0,0 +1,45 @@
import * as assert from "assert";
import { HmacCryptoKey } from "../src/mechs";
import { Pkcs11HmacKeyGenParams, Pkcs11HmacKeyImportParams } from "../src/types";
import { crypto } from "./config";
context("HMAC", () => {
context("token", () => {
it("generate", async () => {
const alg: Pkcs11HmacKeyGenParams = {
name: "HMAC",
hash: "SHA-256",
label: "custom",
token: true,
sensitive: true,
};
const key = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);
assert.strictEqual(key.algorithm.token, true);
assert.strictEqual(key.algorithm.label, alg.label);
assert.strictEqual(key.algorithm.sensitive, true);
});
it("import", async () => {
const alg: Pkcs11HmacKeyImportParams = {
name: "HMAC",
hash: "SHA-256",
label: "custom",
token: true,
sensitive: true,
};
const raw = Buffer.from("1234567890abcdef1234567809abcdef");
const key = await crypto.subtle.importKey("raw", raw, alg, false, ["sign", "verify"]) as HmacCryptoKey;
assert.strictEqual(key.algorithm.token, true);
assert.strictEqual(key.algorithm.label, alg.label);
assert.strictEqual(key.algorithm.sensitive, true);
});
});
});

View File

@ -0,0 +1,328 @@
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
import { CryptoKey } from "../src";
import { Pkcs11RsaHashedKeyAlgorithm, Pkcs11EcKeyAlgorithm } from "../src/types";
import { crypto } from "./config";
import { isNSS } from "./helper";
(isNSS("KeyStorage. NSS is readonly")
? context.skip
: context)
("KeyStorage", () => {
beforeEach(async () => {
let keys = await crypto.keyStorage.keys();
if (keys.length) {
await crypto.keyStorage.clear();
}
keys = await crypto.keyStorage.keys();
assert.strictEqual(keys.length, 0);
});
context("indexOf", () => {
["privateKey", "publicKey"].forEach((type) => {
it(type, async () => {
const algorithm: types.RsaHashedKeyGenParams = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 1024,
};
const keys = await crypto.subtle.generateKey(algorithm, false, ["sign", "verify"]);
const key = (keys as any)[type] as CryptoKey;
const index = await crypto.keyStorage.setItem(key);
const found = await crypto.keyStorage.indexOf(key);
assert.strictEqual(found, null);
const keyByIndex = await crypto.keyStorage.getItem(index);
assert.strictEqual(keyByIndex.key.id.toString("hex"), key.key.id.toString("hex"));
});
});
});
context("set/get item", () => {
it("secret key", async () => {
let indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 0);
const algorithm: types.AesKeyGenParams = {
name: "AES-CBC",
length: 256,
};
const key = await crypto.subtle.generateKey(algorithm, true, ["encrypt", "decrypt"]) as CryptoKey;
assert.strictEqual(!!key, true, "Has no key value");
assert.strictEqual(key.algorithm.token, false);
assert.strictEqual(key.algorithm.label, "AES-256");
assert.strictEqual(key.algorithm.sensitive, false);
// Set key
const index = await crypto.keyStorage.setItem(key);
// Check indexes amount
indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1, "Wrong amount of indexes in storage");
assert.strictEqual(indexes[0], index, "Wrong index of item in storage");
// Get key
const aesKey = await crypto.keyStorage.getItem(index);
assert.strictEqual(!!aesKey, true);
assert.strictEqual(aesKey.key.id.toString("hex"), key.key.id.toString("hex"));
assert.strictEqual(aesKey.algorithm.token, true);
assert.strictEqual(aesKey.algorithm.label, "AES-256");
assert.strictEqual(aesKey.algorithm.sensitive, false);
});
it("public/private keys", async () => {
const indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 0);
const algorithm: types.RsaHashedKeyGenParams = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
};
const keys = await crypto.subtle.generateKey(algorithm, false, ["sign", "verify"]);
assert(keys, "Has no keys");
assert(keys.privateKey, "Has no private key");
assert(keys.publicKey, "Has no public key");
assert.strictEqual(keys.privateKey.extractable, false);
assert.strictEqual(keys.publicKey.extractable, true);
// Set keys
const privateKeyIndex = await crypto.keyStorage.setItem(keys.privateKey);
const publicKeyIndex = await crypto.keyStorage.setItem(keys.publicKey);
// Get keys
const privateKey = await crypto.keyStorage.getItem(privateKeyIndex);
assert(privateKey);
assert.strictEqual(privateKey.extractable, false);
const publicKey = await crypto.keyStorage.getItem(publicKeyIndex);
assert(publicKey);
assert.strictEqual(publicKey.extractable, true);
});
});
it("remove item", async () => {
const algorithm: types.RsaHashedKeyGenParams = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
};
const keys = await crypto.subtle.generateKey(algorithm, false, ["sign", "verify"]);
// Set keys to storage
await crypto.keyStorage.setItem(keys.publicKey);
await crypto.keyStorage.setItem(keys.privateKey);
// Check indexes amount
let indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 2);
// Remove first item
await crypto.keyStorage.removeItem(indexes[0]);
// Check indexes amount
indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1);
});
context("getItem", () => {
it("wrong key identity", async () => {
const key = await crypto.keyStorage.getItem("key not exist");
assert.strictEqual(key, null);
});
context("with algorithm", () => {
it("RSASSA-PKCS1-v1_5", async () => {
const algorithm: types.RsaHashedKeyGenParams = {
name: "RSA-PSS",
hash: "SHA-1",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
};
const keys = await crypto.subtle.generateKey(algorithm, true, ["sign", "verify", "encrypt", "decrypt"]);
// Set key to storage
const index = await crypto.keyStorage.setItem(keys.publicKey);
// Check indexes
const indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1);
// Get key from storage with default algorithm
const keyDefault = await crypto.keyStorage.getItem(index);
assert.strictEqual(keyDefault.algorithm.name, "RSASSA-PKCS1-v1_5");
assert.strictEqual((keyDefault.algorithm as Pkcs11RsaHashedKeyAlgorithm).hash.name, "SHA-256");
assert.deepStrictEqual(keyDefault.usages, ["encrypt", "verify"]);
// Get key from storage and set algorithm
const key = await crypto.keyStorage.getItem(
index,
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-512" } as types.RsaHashedImportParams,
false,
["verify"],
);
assert.strictEqual(key.algorithm.name, "RSASSA-PKCS1-v1_5");
assert.strictEqual((key.algorithm as Pkcs11RsaHashedKeyAlgorithm).hash.name, "SHA-512");
assert.strictEqual(key.extractable, false);
assert.deepStrictEqual(key.usages, ["verify"]);
});
context("with default algorithm", () => {
it("RSASSA-PKCS1-v1_5", async () => {
const keys = await crypto.subtle.generateKey(
{
name: "RSA-PSS",
hash: "SHA-1",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
} as types.RsaHashedKeyGenParams,
false,
["sign", "verify"],
);
// Set key to storage
const index = await crypto.keyStorage.setItem(keys.publicKey);
// Check indexes
const indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1);
// Get key from storage with default alg
const key = await crypto.keyStorage.getItem(index);
assert.strictEqual(key.algorithm.name, "RSASSA-PKCS1-v1_5");
assert.strictEqual((key.algorithm as Pkcs11RsaHashedKeyAlgorithm).hash.name, "SHA-256");
assert.strictEqual(key.usages.join(","), "verify");
});
it("ECDSA P-256", async () => {
const keys = await crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256",
} as types.EcKeyGenParams,
false,
["sign", "verify"],
);
// Set key to storage
const index = await crypto.keyStorage.setItem(keys.publicKey);
// Check indexes
const indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1);
// Get key from storage with default alg
const key = await crypto.keyStorage.getItem(index);
assert.strictEqual(key.algorithm.name, "ECDSA");
assert.strictEqual((key.algorithm as Pkcs11EcKeyAlgorithm).namedCurve, "P-256");
assert.strictEqual(key.usages.join(","), "verify");
});
it("ECDSA P-521", async () => {
const keys = await crypto.subtle.generateKey({
name: "ECDSA",
namedCurve: "P-521",
} as types.EcKeyGenParams,
false,
["sign", "verify"],
);
// Set key to storage
const index = await crypto.keyStorage.setItem(keys.publicKey);
// Check indexes
const indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1);
// Get key from storage with default alg
const key = await crypto.keyStorage.getItem(index);
assert.strictEqual(key.algorithm.name, "ECDSA");
assert.strictEqual((key.algorithm as Pkcs11EcKeyAlgorithm).namedCurve, "P-521");
assert.strictEqual(key.usages.join(","), "verify");
});
it("RSA-OAEP", async () => {
const keys = await crypto.subtle.generateKey({
name: "RSA-OAEP",
hash: "SHA-1",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
} as types.RsaHashedKeyGenParams,
false,
["encrypt", "decrypt"],
);
// Set key to storage
const index = await crypto.keyStorage.setItem(keys.publicKey);
// Check indexes
const indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1);
// Get key from storage we default alg
const key = await crypto.keyStorage.getItem(index);
assert.strictEqual(key.algorithm.name, "RSA-OAEP");
assert.strictEqual((key.algorithm as Pkcs11RsaHashedKeyAlgorithm).hash.name, "SHA-256");
assert.strictEqual(key.usages.join(","), "encrypt");
});
it("AES-CBC", async () => {
const aesKey = await crypto.subtle.generateKey({
name: "AES-CBC",
length: 256,
} as types.AesKeyGenParams,
false,
["encrypt", "decrypt"],
) as CryptoKey;
// Set key to storage
const index = await crypto.keyStorage.setItem(aesKey);
// Check indexes
const indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1);
// Get key from storage we default alg
const key = await crypto.keyStorage.getItem(index);
assert.strictEqual(key.algorithm.name, "AES-CBC");
assert.strictEqual(key.usages.join(","), "encrypt,decrypt");
});
});
it("ECDH", async () => {
const keys = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-384",
} as types.EcKeyGenParams,
false,
["deriveBits"],
);
// Set key to storage
const index = await crypto.keyStorage.setItem(keys.publicKey);
// Check indexes
const indexes = await crypto.keyStorage.keys();
assert.strictEqual(indexes.length, 1);
// Get key from storage we default alg
const key = await crypto.keyStorage.getItem(index);
assert.strictEqual(key.algorithm.name, "ECDH");
assert.strictEqual((key.algorithm as Pkcs11EcKeyAlgorithm).namedCurve, "P-384");
assert.strictEqual(key.usages.join(","), "");
});
});
});
});

View File

@ -0,0 +1,73 @@
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
import { RsaCryptoKey } from "../src/mechs";
import { Pkcs11RsaHashedImportParams, Pkcs11RsaHashedKeyGenParams } from "../src/types";
import { crypto } from "./config";
context("RSA", () => {
context("token", () => {
it("generate", async () => {
const alg: Pkcs11RsaHashedKeyGenParams = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
label: "custom",
token: true,
sensitive: true,
};
const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);
const privateKey = keys.privateKey as RsaCryptoKey;
assert.strictEqual(privateKey.algorithm.token, true);
assert.strictEqual(privateKey.algorithm.label, alg.label);
assert.strictEqual(privateKey.algorithm.sensitive, true);
const publicKey = keys.publicKey as RsaCryptoKey;
assert.strictEqual(publicKey.algorithm.token, true);
assert.strictEqual(publicKey.algorithm.label, alg.label);
assert.strictEqual(publicKey.algorithm.sensitive, false);
});
it("import", async () => {
const alg: Pkcs11RsaHashedImportParams = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
label: "custom",
token: true,
sensitive: true,
};
const jwk = {
alg: "RS256",
e: "AQAB",
ext: true,
key_ops: ["verify"],
kty: "RSA",
n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0",
};
const publicKey = await crypto.subtle.importKey("jwk", jwk, alg, true, ["verify"]) as RsaCryptoKey;
assert.strictEqual(publicKey.algorithm.token, true);
assert.strictEqual(publicKey.algorithm.label, alg.label);
assert.strictEqual(publicKey.algorithm.sensitive, false);
});
});
it("RSA 3072bits", async () => {
const alg: types.RsaHashedKeyGenParams = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 3072,
};
const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);
assert.strictEqual((keys.privateKey.algorithm as types.RsaHashedKeyAlgorithm).modulusLength, 3072);
});
});

View File

@ -0,0 +1,88 @@
import * as types from "@peculiar/webcrypto-types";
import * as assert from "assert";
import * as graphene from "graphene-pk11";
import { ID_DIGEST } from "../src/const";
import { CryptoKey } from "../src/key";
import { crypto } from "./config";
context("Subtle", () => {
async function getId(publicKey: types.CryptoKey) {
const raw = await crypto.subtle.exportKey("spki", publicKey);
const hash = await (await crypto.subtle.digest(ID_DIGEST, raw)).slice(0, 16);
return Buffer.from(hash).toString("hex");
}
context("key must have id equals to SHA-1 of public key raw", () => {
context("generate key", () => {
before(async () => {
crypto.keyStorage.clear();
});
after(async () => {
crypto.keyStorage.clear();
});
[
{ name: "RSA-PSS", hash: "SHA-256", publicExponent: new Uint8Array([1, 0, 1]), modulusLength: 1024 },
{ name: "ECDSA", namedCurve: "P-256" },
].map((alg) => {
it(alg.name, async () => {
const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);
const id = await getId(keys.publicKey);
assert.strictEqual((keys.publicKey as CryptoKey).key.id.toString("hex"), id);
assert.strictEqual((keys.publicKey as CryptoKey).id.includes(id), true);
assert.strictEqual((keys.publicKey as CryptoKey).p11Object.token, false);
assert.strictEqual((keys.privateKey as CryptoKey).p11Object.token, false);
assert.strictEqual(((keys.privateKey as CryptoKey).p11Object as graphene.PrivateKey).sensitive, false);
});
});
context("pkcs11 attributes", () => {
[
{ name: "RSA-PSS", hash: "SHA-256", publicExponent: new Uint8Array([1, 0, 1]), modulusLength: 1024, token: true, sensitive: true, label: "RSA-PSS" },
{ name: "ECDSA", namedCurve: "P-256", token: true, sensitive: true, label: "ECDSA" },
].map((alg) => {
it(alg.name, async () => {
const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);
const id = await getId(keys.publicKey);
assert.strictEqual((keys.publicKey as CryptoKey).key.id.toString("hex"), id);
assert.strictEqual((keys.publicKey as CryptoKey).id.includes(id), true);
assert.strictEqual((keys.publicKey as CryptoKey).p11Object.token, true);
assert.strictEqual((keys.publicKey as CryptoKey).p11Object.label, alg.name);
assert.strictEqual((keys.privateKey as CryptoKey).p11Object.token, true);
assert.strictEqual(((keys.privateKey as CryptoKey).p11Object as graphene.PrivateKey).sensitive, true);
assert.strictEqual((keys.privateKey as CryptoKey).p11Object.label, alg.name);
});
});
});
});
context("import key", () => {
const spki = Buffer.from("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoZMMqyfA16N6bvloFHmalk/SGMisr3zSXFZdR8F9UkaY7hF13hHiQtwp2YO+1zd7jwYi1Y7SMA9iUrC+ap2OCw==", "base64");
it("extractable public key", async () => {
const key = await crypto.subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: "P-256" } as types.EcKeyImportParams, true, ["verify"]);
const id = await getId(key);
assert.strictEqual((key as CryptoKey).key.id.toString("hex"), id);
assert.strictEqual((key as CryptoKey).id.includes(id), true);
});
it("don't try to update id if key is not extractable", async () => {
const key = await crypto.subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: "P-256" } as types.EcKeyImportParams, false, ["verify"]);
assert.notStrictEqual((key as CryptoKey).key.id.toString("hex"), "69e4556056c8d300eff3d4523fc6515d9f833fe6");
});
});
});
});

View File

@ -0,0 +1,153 @@
import * as types from "@peculiar/webcrypto-types";
import * as test from "@peculiar/webcrypto-test";
import * as config from "./config";
import { isNSS, isSoftHSM } from "./helper";
function fixEcImport(item: test.ITestImportAction) {
if (item.name?.startsWith("JWK private key")) {
const jwk = item.data as types.JsonWebKey;
delete jwk.x;
delete jwk.y;
}
if (item.name?.startsWith("PKCS8 P-256")) {
item.data = Buffer.from("3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420895118e4e168dc9ee0d419d2c3f5845b2918fda96b84d9a91012f2ffb70d9ee1", "hex");
}
if (item.name?.startsWith("PKCS8 P-384")) {
item.data = Buffer.from("304e020100301006072a8648ce3d020106052b8104002204373035020101043098d7c6a318f0a02efe1a17552492884c11a079314d4cc9f92e1504905436072d61539fc7fd73371eeda4c80e3902c743", "hex");
}
if (item.name?.startsWith("PKCS8 P-521")) {
item.data = Buffer.from("3060020100301006072a8648ce3d020106052b81040023044930470201010442006c71a419f8a4e6ad25f99308ef475ba5319678acb5f9cde61bdf301e69e953e7766c0adc603397728aa0e4873fa679ad1efc6693e125df7bb75e880638d28f968b", "hex");
}
}
// Fix EC import tests.
// PKCS#11 doesn't return public key from private key
test.vectors.ECDSA.actions.import?.forEach(fixEcImport);
test.vectors.ECDH.actions.import?.forEach(fixEcImport);
test.vectors.ECDH.actions.deriveKey?.forEach((item) => {
if (item.name === "P-521 256") {
// module doesn't support AES-CTR
item.derivedKeyType.name = "AES-CBC";
}
});
// WebcryptoTest.check(config.crypto, [
// vectors.AES128CBC,
// ]);
test.WebcryptoTest.check(config.crypto, {
AES128KW: true,
AES192KW: true,
AES256KW: true,
RSAOAEP: true,
PBKDF2: true,
HKDF: true,
DESCBC: true,
DESEDE3CBC: true,
RSAESPKCS1: true,
AES128CMAC: true,
AES192CMAC: true,
AES256CMAC: true,
AES128CTR: true,
AES192CTR: true,
AES256CTR: true,
});
test.WebcryptoTest.add(config.crypto, {
name: "RSA-OAEP-SHA1",
actions: {
encrypt: [
{
skip: isNSS("RSA-OAEP-SHA1 throws CKR_DEVICE_ERROR"),
name: "without label",
algorithm: {
name: "RSA-OAEP",
} as types.Algorithm,
data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
encData: Buffer.from("MAKiRseL08AlR8Fmn1uVz/lDDdrDiRyI6KUW3mcE/0kxwW7/VizQJP+jiTSWyHexhQ+Sp0ugm6Doa/jahajuVf0aFkqJCcEKlSeMGvu4QdDc9tJzeNJVqSbPovFy60Criyjei4ganw2RQM2Umav//HfQEyqGTcyftMxXzkDDBQU=", "base64"),
key: {
publicKey: {
format: "jwk",
algorithm: { name: "RSA-OAEP", hash: "SHA-1" } as types.RsaHashedImportParams,
data: {
alg: "RSA-OAEP",
e: "AQAB",
ext: true,
key_ops: ["encrypt"],
kty: "RSA",
n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0",
},
extractable: true,
keyUsages: ["encrypt"],
},
privateKey: {
format: "jwk",
algorithm: { name: "RSA-OAEP", hash: "SHA-1" } as types.RsaHashedImportParams,
data: {
alg: "RSA-OAEP",
d: "AkeIWJywp9OfYsj0ECsKmhDVBw55ZL_yU-rbIrashQ_31P6gsc_0I-SVN1rd8Hz79OJ_rTY8ZRBZ4PIyFdPoyvuo5apHdAHH6riJKxDHWPxhE-ReNVEPSTiF1ry8DSe5zC7w9BLnH_QM8bkN4cOnvgqrg7EbrGWomAGJVvoRwOM",
dp: "pOolqL7HwnmWLn7GDX8zGkm0Q1IAj-ouBL7ZZbaTm3wETLtwu-dGsQheEdzP_mfL_CTiCAwGuQBcSItimD0DdQ",
dq: "FTSY59AnkgmB7TsErWNBE3xlVB_pMpE2xWyCBCz96gyDOUOFDz8vlSV-clhjawJeRd1n30nZOPSBtOHozhwZmQ",
e: "AQAB",
ext: true,
key_ops: ["decrypt"],
kty: "RSA",
n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0",
p: "6jFtmBJJQFIlQUXXZYIgvH70Y9a03oWKjNuF2veb5Zf09EtLNE86NpnIm463OnoHJPW0m8wHFXZZfcYVTIPR_w",
q: "0GttDMl1kIzSV2rNzGXpOS8tUqr5Lz0EtVZwIb9GJPMmJ0P3gZ801zEgZZ4-esU7cLUf-BSZEAmfnKA80G2jIw",
qi: "FByTxX4G2eXkk1xe0IuiEv7I5NS-CnFyp8iB4XLG0rabnfcIZFKpf__X0sNyVOAVo5-jJMuUYjCRTdaXNAWhkg",
},
extractable: true,
keyUsages: ["decrypt"],
},
},
},
{
skip: isSoftHSM("RSA-OAEP-SHA1 supports encryption without label only")
|| isNSS("RSA-OAEP-SHA1 throws CKR_DEVICE_ERROR"),
name: "with label",
algorithm: {
name: "RSA-OAEP",
label: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
} as types.RsaOaepParams,
data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
encData: Buffer.from("YLtmJDT8Y4Z2Y/VoGHUvhgs5kptNShFRUCcsKpUgI9A+YCYXL3K8fnEkbzO/Nkd4/0RsvfnmXkUJg3JdzPslwO1bOdlNsd2hRi0qi4cpxVmHDjuI3EHMb7FI3Pb9cF/kMFeEQzttpIDqh/UQJnoyh4d/RyZS1w37Vk0sNer7xw0=", "base64"),
key: {
publicKey: {
format: "jwk" as types.KeyFormat,
algorithm: { name: "RSA-OAEP", hash: "SHA-1" } as types.RsaHashedImportParams,
data: {
alg: "RSA-OAEP",
e: "AQAB",
ext: true,
key_ops: ["encrypt"],
kty: "RSA",
n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0",
},
extractable: true,
keyUsages: ["encrypt"],
},
privateKey: {
format: "jwk",
algorithm: { name: "RSA-OAEP", hash: "SHA-1" } as types.RsaHashedImportParams,
data: {
alg: "RSA-OAEP",
d: "AkeIWJywp9OfYsj0ECsKmhDVBw55ZL_yU-rbIrashQ_31P6gsc_0I-SVN1rd8Hz79OJ_rTY8ZRBZ4PIyFdPoyvuo5apHdAHH6riJKxDHWPxhE-ReNVEPSTiF1ry8DSe5zC7w9BLnH_QM8bkN4cOnvgqrg7EbrGWomAGJVvoRwOM",
dp: "pOolqL7HwnmWLn7GDX8zGkm0Q1IAj-ouBL7ZZbaTm3wETLtwu-dGsQheEdzP_mfL_CTiCAwGuQBcSItimD0DdQ",
dq: "FTSY59AnkgmB7TsErWNBE3xlVB_pMpE2xWyCBCz96gyDOUOFDz8vlSV-clhjawJeRd1n30nZOPSBtOHozhwZmQ",
e: "AQAB",
ext: true,
key_ops: ["decrypt"],
kty: "RSA",
n: "vqpvdxuyZ6rKYnWTj_ZzDBFZAAAlpe5hpoiYHqa2j5kK7v8U5EaPY2bLib9m4B40j-n3FV9xUCGiplWdqMJJKT-4PjGO5E3S4N9kjFhu57noYT7z7302J0sJXeoFbXxlgE-4G55Oxlm52ID2_RJesP5nzcGTriQwoRbrJP5OEt0",
p: "6jFtmBJJQFIlQUXXZYIgvH70Y9a03oWKjNuF2veb5Zf09EtLNE86NpnIm463OnoHJPW0m8wHFXZZfcYVTIPR_w",
q: "0GttDMl1kIzSV2rNzGXpOS8tUqr5Lz0EtVZwIb9GJPMmJ0P3gZ801zEgZZ4-esU7cLUf-BSZEAmfnKA80G2jIw",
qi: "FByTxX4G2eXkk1xe0IuiEv7I5NS-CnFyp8iB4XLG0rabnfcIZFKpf__X0sNyVOAVo5-jJMuUYjCRTdaXNAWhkg",
},
extractable: true,
keyUsages: ["decrypt"],
},
},
},
],
},
});