add pkcs11
This commit is contained in:
parent
387f0d928d
commit
1578e6ceeb
|
@ -0,0 +1,11 @@
|
|||
# `@peculiar/webcrypto-pkcs11`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const webcryptoPkcs11 = require('@peculiar/webcrypto-pkcs11');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "@peculiar/webcrypto-pkcs11",
|
||||
"version": "3.0.0",
|
||||
"description": "Now I’m 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"
|
||||
}
|
||||
}
|
|
@ -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}\"`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>;
|
||||
|
||||
}
|
|
@ -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!;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./cert"
|
||||
export * from "./csr"
|
||||
export * from "./x509"
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./aes_cbc";
|
||||
export * from "./aes_ecb";
|
||||
export * from "./aes_gcm";
|
||||
export * from "./key";
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./ec_dh";
|
||||
export * from "./ec_dsa";
|
||||
export * from "./key";
|
||||
export * from "./crypto";
|
|
@ -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*/ }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./hmac";
|
||||
export * from "./key";
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export * from "./aes";
|
||||
export * from "./ec";
|
||||
export * from "./hmac";
|
||||
export * from "./rsa";
|
||||
export * from "./sha";
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export * from "./crypto";
|
||||
export * from "./rsa-pss";
|
||||
export * from "./rsa-oaep";
|
||||
export * from "./rsa-ssa";
|
||||
export * from "./key";
|
|
@ -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*/ }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./sha_1";
|
||||
export * from "./sha_256";
|
||||
export * from "./sha_384";
|
||||
export * from "./sha_512";
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { Sha1Provider } from "./sha_1";
|
||||
|
||||
export class Sha256Provider extends Sha1Provider {
|
||||
public override name = "SHA-256";
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { Sha1Provider } from "./sha_1";
|
||||
|
||||
export class Sha384Provider extends Sha1Provider {
|
||||
public override name = "SHA-384";
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { Sha1Provider } from "./sha_1";
|
||||
|
||||
export class Sha512Provider extends Sha1Provider {
|
||||
public override name = "SHA-512";
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 { }
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -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]);
|
||||
});
|
||||
|
||||
});
|
|
@ -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();
|
||||
});
|
|
@ -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"]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -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(","), "");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
|
@ -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");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -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"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
Reference in New Issue