Skip to content

Commit

Permalink
Merge pull request #73 from PeculiarVentures:always-auth
Browse files Browse the repository at this point in the history
Support always authenticate for private keys
  • Loading branch information
microshine committed Jul 28, 2022
2 parents 462187f + 9a6445c commit 2a44d4f
Show file tree
Hide file tree
Showing 16 changed files with 492 additions and 174 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Expand Up @@ -4,7 +4,7 @@ on: [push, pull_request]

jobs:
build:
runs-on: macos-10.15
runs-on: macos-12

strategy:
matrix:
Expand Down
16 changes: 8 additions & 8 deletions package.json
Expand Up @@ -27,11 +27,11 @@
"url": "https://github.com/PeculiarVentures/node-webcrypto-p11.git"
},
"dependencies": {
"@peculiar/asn1-schema": "^2.1.9",
"@peculiar/asn1-x509": "^2.1.9",
"@peculiar/asn1-schema": "^2.2.0",
"@peculiar/asn1-x509": "^2.2.0",
"@peculiar/json-schema": "^1.1.12",
"@peculiar/x509": "^1.7.0",
"graphene-pk11": "^2.3.0",
"@peculiar/x509": "^1.8.1",
"graphene-pk11": "^2.3.2",
"pkcs11js": "^1.3.0",
"pvtsutils": "^1.3.2",
"tslib": "^2.4.0",
Expand Down Expand Up @@ -59,15 +59,15 @@
"devDependencies": {
"@peculiar/webcrypto-test": "^1.0.7",
"@types/mocha": "^9.1.1",
"@types/node": "^17.0.44",
"@types/node": "^18.6.1",
"mocha": "^10.0.0",
"nyc": "^15.1.0",
"rollup": "^2.75.6",
"rollup": "^2.77.0",
"rollup-plugin-dts": "^4.2.2",
"rollup-plugin-typescript2": "^0.32.1",
"ts-node": "^10.8.1",
"ts-node": "^10.9.1",
"tslint": "^6.1.3",
"typescript": "^4.7.3"
"typescript": "^4.7.4"
},
"funding": {
"type": "github",
Expand Down
1 change: 1 addition & 0 deletions src/crypto.ts
Expand Up @@ -51,6 +51,7 @@ export class Crypto extends core.Crypto implements core.CryptoStorages, types.IS
private initialized: boolean;

public templateBuilder: types.ITemplateBuilder = new TemplateBuilder();
public onAlwaysAuthenticate?: types.AlwaysAuthenticateHandle;

/**
* Creates an instance of WebCrypto.
Expand Down
11 changes: 9 additions & 2 deletions src/key.ts
Expand Up @@ -2,7 +2,7 @@
import * as core from "webcrypto-core";

import * as graphene from "graphene-pk11";
import { Pkcs11KeyAlgorithm } from "./types";
import { AlwaysAuthenticateParams, Pkcs11KeyAlgorithm } from "./types";

export interface ITemplatePair {
privateKey: graphene.ITemplate;
Expand All @@ -16,7 +16,7 @@ export interface CryptoKeyJson<T extends Pkcs11KeyAlgorithm = Pkcs11KeyAlgorithm
extractable: boolean;
}

export class CryptoKey<T extends Pkcs11KeyAlgorithm = Pkcs11KeyAlgorithm> extends core.CryptoKey {
export class CryptoKey<T extends Pkcs11KeyAlgorithm = Pkcs11KeyAlgorithm> extends core.CryptoKey implements AlwaysAuthenticateParams {

public static defaultKeyAlgorithm(): Pkcs11KeyAlgorithm {
const alg: Pkcs11KeyAlgorithm = {
Expand Down Expand Up @@ -49,6 +49,12 @@ export class CryptoKey<T extends Pkcs11KeyAlgorithm = Pkcs11KeyAlgorithm> extend
public id: string;
public p11Object: graphene.Key | graphene.SecretKey | graphene.PublicKey | graphene.PrivateKey;

/**
* If `true`, the user has to supply the PIN for each use (sign or decrypt) with the key. Use `crypto.onAlwaysAuthenticate` handler to customize this behavior.
* @since v2.6.0
*/
public alwaysAuthenticate?: boolean | undefined;

public override type: KeyType = "secret";
public override extractable: boolean = false;
public override algorithm: T;
Expand Down Expand Up @@ -109,6 +115,7 @@ export class CryptoKey<T extends Pkcs11KeyAlgorithm = Pkcs11KeyAlgorithm> extend
protected initPrivateKey(key: graphene.PrivateKey): void {
this.p11Object = key;
this.type = "private";
this.alwaysAuthenticate = key.alwaysAuthenticate;
try {
// Yubico throws CKR_ATTRIBUTE_TYPE_INVALID
this.extractable = key.extractable;
Expand Down
9 changes: 7 additions & 2 deletions src/mechs/ec/crypto.ts
Expand Up @@ -32,6 +32,9 @@ export class EcCrypto implements types.IContainer {
sensitive: algorithm.sensitive,
extractable,
usages: keyUsages,
};
if (algorithm.alwaysAuthenticate) {
attrs.alwaysAuthenticate = true;
}
const privateTemplate = this.createTemplate({
action: "generate",
Expand Down Expand Up @@ -158,6 +161,7 @@ export class EcCrypto implements types.IContainer {
}

protected importJwkPrivateKey(jwk: JsonWebKey, algorithm: types.Pkcs11EcKeyImportParams, extractable: boolean, keyUsages: KeyUsage[]): EcCryptoKey {

const template = this.createTemplate({
action: "import",
type: "private",
Expand All @@ -166,6 +170,7 @@ export class EcCrypto implements types.IContainer {
token: algorithm.token,
sensitive: algorithm.sensitive,
label: algorithm.label,
alwaysAuthenticate: algorithm.alwaysAuthenticate,
extractable,
usages: keyUsages,
},
Expand Down Expand Up @@ -200,8 +205,8 @@ export class EcCrypto implements types.IContainer {
if (namedCurve.name === "curve25519") {
pointEc = utils.b64UrlDecode(jwk.x!);
} else {
const point = core.EcUtils.encodePoint({ x: utils.b64UrlDecode(jwk.x!), y: utils.b64UrlDecode(jwk.y!) }, namedCurve.size)
const derPoint = asn1Schema.AsnConvert.serialize(new asn1Schema.OctetString(point))
const point = core.EcUtils.encodePoint({ x: utils.b64UrlDecode(jwk.x!), y: utils.b64UrlDecode(jwk.y!) }, namedCurve.size);
const derPoint = asn1Schema.AsnConvert.serialize(new asn1Schema.OctetString(point));
pointEc = Buffer.from(derPoint);
}
template.pointEC = pointEc;
Expand Down
29 changes: 21 additions & 8 deletions src/mechs/ec/ec_dsa.ts
Expand Up @@ -2,6 +2,7 @@ import * as core from "webcrypto-core";

import { CryptoKey } from "../../key";
import * as types from "../../types";
import { alwaysAuthenticate } from "../../utils";

import { EcCrypto } from "./crypto";
import { EcCryptoKey } from "./key";
Expand Down Expand Up @@ -33,14 +34,26 @@ export class EcdsaProvider extends core.EcdsaProvider implements types.IContaine
}

public async onSign(algorithm: 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 Algorithm).name, buf);
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 Algorithm).name, buf);
}
const signer = this.container.session.createSign(mechanism, key.key);
try {
await alwaysAuthenticate(key, this.container);
} catch (e) {
try {
// call C_SignFinal to close the active state
signer.once(buf);
} catch {
// nothing
}
this.container.session.createSign(mechanism, key.key).once(buf, (err, data2) => {
throw e;
}
return new Promise<ArrayBuffer>((resolve, reject) => {
signer.once(buf, (err, data2) => {
if (err) {
reject(err);
} else {
Expand Down Expand Up @@ -84,7 +97,7 @@ export class EcdsaProvider extends core.EcdsaProvider implements types.IContaine
}
}

protected wc2pk11(alg: EcdsaParams, keyAlg: KeyAlgorithm): { name: string, params: null } {
protected wc2pk11(alg: EcdsaParams, keyAlg: KeyAlgorithm): { name: string, params: null; } {
let algName: string;
const hashAlg = (alg.hash as Algorithm).name.toUpperCase();
switch (hashAlg) {
Expand Down
9 changes: 7 additions & 2 deletions src/mechs/rsa/crypto.ts
Expand Up @@ -10,7 +10,7 @@ import * as utils from "../../utils";

import { RsaCryptoKey } from "./key";

const HASH_PREFIXES: { [alg: string]: Buffer } = {
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]),
Expand All @@ -36,7 +36,11 @@ export class RsaCrypto implements types.IContainer {
sensitive: algorithm.sensitive,
extractable,
usages: keyUsages,
};
if (algorithm.alwaysAuthenticate) {
attrs.alwaysAuthenticate = true;
}

const privateTemplate = this.createTemplate({
action: "generate",
type: "private",
Expand Down Expand Up @@ -95,7 +99,7 @@ export class RsaCrypto implements types.IContainer {
const jwk = await this.exportJwkPublicKey(key);
const spki = this.jwk2spki(jwk);
const asn = asnSchema.AsnConvert.parse(spki, core.asn1.PublicKeyInfo);
return asn.publicKey
return asn.publicKey;
}
default:
throw new core.OperationError("format: Must be 'raw', 'jwk', 'pkcs8' or 'spki'");
Expand Down Expand Up @@ -241,6 +245,7 @@ export class RsaCrypto implements types.IContainer {
sensitive: algorithm.sensitive,
label: algorithm.label,
extractable,
alwaysAuthenticate: algorithm.alwaysAuthenticate,
usages: keyUsages
},
});
Expand Down
37 changes: 25 additions & 12 deletions src/mechs/rsa/rsa-oaep.ts
Expand Up @@ -3,6 +3,7 @@ import * as core from "webcrypto-core";

import { CryptoKey } from "../../key";
import * as types from "../../types";
import { alwaysAuthenticate } from "../../utils";

import { RsaCrypto } from "./crypto";
import { RsaCryptoKey } from "./key";
Expand Down Expand Up @@ -47,18 +48,30 @@ export class RsaOaepProvider extends core.RsaOaepProvider implements types.ICont
}

public async onDecrypt(algorithm: 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);
}
});
const buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm);
const context = Buffer.alloc((key.algorithm).modulusLength >> 3);

const decipher = this.container.session.createDecipher(mechanism, key.key);
try {
await alwaysAuthenticate(key, this.container);
} catch (e) {
try {
// call C_SignFinal to close the active state
decipher.once(buf, context);
} catch {
// nothing
}
throw e;
}
return new Promise<ArrayBuffer>((resolve, reject) => {
decipher.once(buf, context, (err, data2) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(data2).buffer);
}
});
});
}

Expand Down
29 changes: 21 additions & 8 deletions src/mechs/rsa/rsa-pss.ts
Expand Up @@ -3,6 +3,7 @@ import * as core from "webcrypto-core";

import { CryptoKey } from "../../key";
import * as types from "../../types";
import { alwaysAuthenticate } from "../../utils";

import { RsaCrypto } from "./crypto";
import { RsaCryptoKey } from "./key";
Expand Down Expand Up @@ -31,14 +32,26 @@ export class RsaPssProvider extends core.RsaPssProvider implements types.IContai
}

public async onSign(algorithm: 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 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);
let buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm as 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);
}
const signer = this.container.session.createSign(mechanism, key.key);
try {
await alwaysAuthenticate(key, this.container);
} catch (e) {
try {
// call C_SignFinal to close the active state
signer.once(buf);
} catch {
// nothing
}
this.container.session.createSign(mechanism, key.key).once(buf, (err, data2) => {
throw e;
}
return new Promise<ArrayBuffer>((resolve, reject) => {
signer.once(buf, (err, data2) => {
if (err) {
reject(err);
} else {
Expand Down Expand Up @@ -82,7 +95,7 @@ export class RsaPssProvider extends core.RsaPssProvider implements types.IContai
}
}

protected wc2pk11(alg: RsaPssParams, keyAlg: RsaHashedKeyAlgorithm): { name: string, params: graphene.IParams } {
protected wc2pk11(alg: RsaPssParams, keyAlg: RsaHashedKeyAlgorithm): { name: string, params: graphene.IParams; } {
let mech: string;
let param: graphene.RsaPssParams;
const saltLen = alg.saltLength;
Expand Down
29 changes: 21 additions & 8 deletions src/mechs/rsa/rsa-ssa.ts
Expand Up @@ -2,6 +2,7 @@ import * as core from "webcrypto-core";

import { CryptoKey } from "../../key";
import * as types from "../../types";
import { alwaysAuthenticate } from "../../utils";

import { RsaCrypto } from "./crypto";
import { RsaCryptoKey } from "./key";
Expand Down Expand Up @@ -30,14 +31,26 @@ export class RsaSsaProvider extends core.RsaSsaProvider implements types.IContai
}

public async onSign(algorithm: 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 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);
let buf = Buffer.from(data);
const mechanism = this.wc2pk11(algorithm, key.algorithm as 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);
}
const signer = this.container.session.createSign(mechanism, key.key);
try {
await alwaysAuthenticate(key, this.container);
} catch (e) {
try {
// call C_SignFinal to close the active state
signer.once(buf);
} catch {
// nothing
}
this.container.session.createSign(mechanism, key.key).once(buf, (err, data2) => {
throw e;
}
return new Promise<ArrayBuffer>((resolve, reject) => {
signer.once(buf, (err, data2) => {
if (err) {
reject(err);
} else {
Expand Down Expand Up @@ -81,7 +94,7 @@ export class RsaSsaProvider extends core.RsaSsaProvider implements types.IContai
}
}

protected wc2pk11(alg: Algorithm, keyAlg: RsaHashedKeyAlgorithm): { name: string, params: null } {
protected wc2pk11(alg: Algorithm, keyAlg: RsaHashedKeyAlgorithm): { name: string, params: null; } {
let res: string;
switch (keyAlg.hash.name.toUpperCase()) {
case "SHA-1":
Expand Down
5 changes: 4 additions & 1 deletion src/template_builder.ts
Expand Up @@ -22,7 +22,7 @@ export class TemplateBuilder implements types.ITemplateBuilder {
}
} else {
if (attributes.label) {
template.label = attributes.label
template.label = attributes.label;
}
if (attributes.id) {
template.id = Buffer.from(pvtsutils.BufferSourceConverter.toArrayBuffer(attributes.id));
Expand All @@ -48,6 +48,9 @@ export class TemplateBuilder implements types.ITemplateBuilder {
decrypt,
unwrap,
});
if (attributes.alwaysAuthenticate) {
template.alwaysAuth = true;
}
break;
case "public":
Object.assign<types.ITemplate, types.ITemplate>(template, {
Expand Down

0 comments on commit 2a44d4f

Please sign in to comment.