@ndn/keychain
Advanced tools
Comparing version 0.0.20191223-beta.1 to 0.0.20200606
@@ -0,0 +0,0 @@ export declare namespace TT { |
@@ -1,3 +0,3 @@ | ||
import { Component, Data } from "@ndn/packet"; | ||
import { CertificateName, PrivateKey, PublicKey } from "../mod"; | ||
import { Component, Data, Name } from "@ndn/packet"; | ||
import { PrivateKey, PublicKey } from "../key/mod"; | ||
import { ValidityPeriod } from "./mod"; | ||
@@ -10,19 +10,26 @@ /** | ||
readonly data: Data; | ||
readonly certName: CertificateName; | ||
readonly validity: ValidityPeriod; | ||
get name(): import("@ndn/packet").Name; | ||
static fromData(data: Data): Certificate; | ||
private constructor(); | ||
get name(): Name; | ||
get issuer(): Name | undefined; | ||
get isSelfSigned(): boolean; | ||
/** Public key in SubjectPublicKeyInfo binary format. */ | ||
get publicKey(): Uint8Array; | ||
constructor(data: Data); | ||
get publicKeySpki(): Uint8Array; | ||
/** Load public key. */ | ||
loadPublicKey(): Promise<PublicKey>; | ||
private publicKey?; | ||
} | ||
export declare namespace Certificate { | ||
interface BuildOptions { | ||
name: CertificateName; | ||
name: Name; | ||
freshness?: number; | ||
validity: ValidityPeriod; | ||
publicKey: Uint8Array; | ||
publicKeySpki: Uint8Array; | ||
signer: PrivateKey; | ||
} | ||
export function build({ name, freshness, validity, publicKey, signer, }: BuildOptions): Promise<Certificate>; | ||
interface IssueOptions extends Omit<BuildOptions, "name" | "publicKey" | "signer"> { | ||
function build({ name, freshness, validity, publicKeySpki, signer, }: BuildOptions): Promise<Certificate>; | ||
interface IssueOptions { | ||
freshness?: number; | ||
validity: ValidityPeriod; | ||
issuerId: Component; | ||
@@ -32,10 +39,10 @@ issuerPrivateKey: PrivateKey; | ||
} | ||
export function issue(options: IssueOptions): Promise<Certificate>; | ||
interface SelfSignOptions extends Omit<IssueOptions, "validity" | "issuerId" | "issuerPrivateKey"> { | ||
function issue(options: IssueOptions): Promise<Certificate>; | ||
interface SelfSignOptions { | ||
freshness?: number; | ||
validity?: ValidityPeriod; | ||
privateKey: PrivateKey; | ||
publicKey: PublicKey; | ||
} | ||
export function selfSign(options: SelfSignOptions): Promise<Certificate>; | ||
export function loadPublicKey(cert: Certificate): Promise<PublicKey>; | ||
export {}; | ||
function selfSign(options: SelfSignOptions): Promise<Certificate>; | ||
} |
@@ -1,5 +0,19 @@ | ||
import { Version } from "@ndn/naming-convention2"; | ||
import { Component, Data, LLSign, SigInfo } from "@ndn/packet"; | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
import { Data, SigInfo } from "@ndn/packet"; | ||
/// #if false | ||
const assert = __importDefault(require("minimalistic-assert")).default; | ||
/* | ||
/// #else | ||
import assert from "minimalistic-assert"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
import { loadSpki } from "../key/load.js"; | ||
import { CertificateName, KeyName } from "../mod.js"; | ||
import { PublicKey } from "../key/mod.js"; | ||
import * as CertNaming from "../naming.js"; | ||
import { ContentTypeKEY } from "./an.js"; | ||
@@ -12,45 +26,68 @@ import { ValidityPeriod } from "./mod.js"; | ||
export class Certificate { | ||
constructor(data) { | ||
constructor(data, validity) { | ||
this.data = data; | ||
this.certName = CertificateName.from(data.name); | ||
if (this.data.contentType !== ContentTypeKEY) { | ||
this.validity = validity; | ||
} | ||
static fromData(data) { | ||
const { name, contentType, sigInfo } = data; | ||
if (!CertNaming.isCertName(name)) { | ||
throw new Error(`${name} is not a certificate name`); | ||
} | ||
if (contentType !== ContentTypeKEY) { | ||
throw new Error("ContentType must be KEY"); | ||
} | ||
const si = data.sigInfo; | ||
if (typeof si === "undefined") { | ||
if (!sigInfo) { | ||
throw new Error("SigInfo is missing"); | ||
} | ||
const validity = ValidityPeriod.get(si); | ||
const validity = ValidityPeriod.get(sigInfo); | ||
if (typeof validity === "undefined") { | ||
throw new Error("ValidityPeriod is missing"); | ||
} | ||
this.validity = validity; | ||
const cert = new Certificate(data, validity); | ||
return cert; | ||
} | ||
get name() { return this.data.name; } | ||
get issuer() { | ||
var _a, _b; | ||
return (_b = (_a = this.data.sigInfo) === null || _a === void 0 ? void 0 : _a.keyLocator) === null || _b === void 0 ? void 0 : _b.name; | ||
} | ||
get isSelfSigned() { | ||
var _a, _b; | ||
return (_b = (_a = this.issuer) === null || _a === void 0 ? void 0 : _a.isPrefixOf(this.name)) !== null && _b !== void 0 ? _b : false; | ||
} | ||
/** Public key in SubjectPublicKeyInfo binary format. */ | ||
get publicKey() { return this.data.content; } | ||
get publicKeySpki() { | ||
return this.data.content; | ||
} | ||
/** Load public key. */ | ||
async loadPublicKey() { | ||
if (!this.publicKey) { | ||
this.publicKey = await loadSpki(CertNaming.toKeyName(this.name), this.publicKeySpki); | ||
} | ||
return this.publicKey; | ||
} | ||
} | ||
const DEFAULT_FRESHNESS = 3600000; | ||
(function (Certificate) { | ||
async function build({ name, freshness = DEFAULT_FRESHNESS, validity, publicKey, signer, }) { | ||
const data = new Data(name.toName(), Data.ContentType(ContentTypeKEY), Data.FreshnessPeriod(freshness)); | ||
const si = new SigInfo(); | ||
ValidityPeriod.set(si, validity); | ||
data.sigInfo = si; | ||
data.content = publicKey; | ||
signer.sign(data); | ||
await data[LLSign.PROCESS](); | ||
return new Certificate(data); | ||
async function build({ name, freshness = DEFAULT_FRESHNESS, validity, publicKeySpki, signer, }) { | ||
assert(CertNaming.isCertName(name)); | ||
const data = new Data(name, Data.ContentType(ContentTypeKEY), Data.FreshnessPeriod(freshness)); | ||
data.sigInfo = new SigInfo(); | ||
ValidityPeriod.set(data.sigInfo, validity); | ||
data.content = publicKeySpki; | ||
await signer.sign(data); | ||
return Certificate.fromData(data); | ||
} | ||
Certificate.build = build; | ||
async function issue(options) { | ||
if (!PublicKey.isExportable(options.publicKey)) { | ||
throw new Error("publicKey is not exportable"); | ||
} | ||
const { issuerPrivateKey: pvt, issuerId, publicKey: pub } = options; | ||
const kn = KeyName.from(pub.name); | ||
const cn = new CertificateName(kn.subjectName, kn.keyId, issuerId, Version.create(Date.now())); | ||
const publicKey = await pub.exportAsSpki(); | ||
const opts = { ...options, name: cn, publicKey, signer: pvt }; | ||
return await build(opts); | ||
const name = CertNaming.makeCertName(pub.name, { issuerId }); | ||
const publicKeySpki = await pub.exportAsSpki(); | ||
const opts = { ...options, name, publicKeySpki, signer: pvt }; | ||
return build(opts); | ||
} | ||
Certificate.issue = issue; | ||
const SELF_ISSUER = Component.from("self"); | ||
async function selfSign(options) { | ||
@@ -64,12 +101,8 @@ const { privateKey: { name: pvtName }, publicKey: { name: pubName } } = options; | ||
...options, | ||
issuerId: SELF_ISSUER, | ||
issuerId: CertNaming.ISSUER_SELF, | ||
issuerPrivateKey: options.privateKey, | ||
}; | ||
return await issue(opts); | ||
return issue(opts); | ||
} | ||
Certificate.selfSign = selfSign; | ||
async function loadPublicKey(cert) { | ||
return await loadSpki(cert.certName.toKeyName().toName(), cert.publicKey); | ||
} | ||
Certificate.loadPublicKey = loadPublicKey; | ||
})(Certificate || (Certificate = {})); |
export * from "./validity-period"; | ||
export * from "./certificate"; |
@@ -6,11 +6,17 @@ import { SigInfo } from "@ndn/packet"; | ||
static decodeFrom(decoder: Decoder): ValidityPeriod; | ||
notBefore: Date; | ||
notAfter: Date; | ||
constructor(); | ||
constructor(notBefore: Date, notAfter: Date); | ||
constructor(notBefore: ValidityPeriod.TimestampInput, notAfter: ValidityPeriod.TimestampInput); | ||
notBefore: number; | ||
notAfter: number; | ||
encodeTo(encoder: Encoder): void; | ||
/** Determine whether dt is within validity period. */ | ||
includes(dt: Date): boolean; | ||
/** Determine whether the specified timestamp is within validity period. */ | ||
includes(t?: ValidityPeriod.TimestampInput): boolean; | ||
/** Determine whether this validity period equals another. */ | ||
equals({ notBefore, notAfter }: ValidityPeriod): boolean; | ||
/** Compute the intersection of this and other validity periods. */ | ||
intersect(...validityPeriods: ValidityPeriod[]): ValidityPeriod; | ||
toString(): string; | ||
} | ||
export declare namespace ValidityPeriod { | ||
type TimestampInput = number | Date; | ||
const MAX: ValidityPeriod; | ||
@@ -17,0 +23,0 @@ function daysFromNow(n: number): ValidityPeriod; |
import { SigInfo } from "@ndn/packet"; | ||
import { EvDecoder, Extension } from "@ndn/tlv"; | ||
import { EvDecoder, Extension, toUtf8 } from "@ndn/tlv"; | ||
import { TT } from "./an.js"; | ||
const timestampRe = /^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$/; | ||
function decodeTimestamp(value) { | ||
const str = new TextDecoder().decode(value); | ||
function toTimestamp(input) { | ||
if (typeof input === "object") { | ||
return input.getTime(); | ||
} | ||
return input; | ||
} | ||
const timestampRe = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})$/; | ||
function decodeTimestamp(str) { | ||
const match = timestampRe.exec(str); | ||
@@ -11,7 +16,8 @@ if (!match) { | ||
} | ||
const [, y, m, d, h, i, s] = match.map((c) => parseInt(c, 10)); | ||
return new Date(Date.UTC(y, m - 1, d, h, i, s)); | ||
const [y, m, d, h, i, s] = match.slice(1).map((c) => Number.parseInt(c, 10)); | ||
return Date.UTC(y, m - 1, d, h, i, s); | ||
} | ||
function encodeTimestamp(d) { | ||
const str = [ | ||
function encodeTimestampString(timestamp) { | ||
const d = new Date(timestamp); | ||
return [ | ||
d.getUTCFullYear().toString().padStart(4, "0"), | ||
@@ -25,12 +31,14 @@ (d.getUTCMonth() + 1).toString().padStart(2, "0"), | ||
].join(""); | ||
return new TextEncoder().encode(str); | ||
} | ||
function encodeTimestamp(timestamp) { | ||
return toUtf8(encodeTimestampString(timestamp)); | ||
} | ||
const EVD = new EvDecoder("ValidityPeriod", TT.ValidityPeriod) | ||
.add(TT.NotBefore, (t, { value }) => t.notBefore = decodeTimestamp(value)) | ||
.add(TT.NotAfter, (t, { value }) => t.notAfter = decodeTimestamp(value)); | ||
.add(TT.NotBefore, (t, { text }) => t.notBefore = decodeTimestamp(text), { required: true }) | ||
.add(TT.NotAfter, (t, { text }) => t.notAfter = decodeTimestamp(text), { required: true }); | ||
/** Certificate validity period. */ | ||
export class ValidityPeriod { | ||
constructor(arg1, arg2) { | ||
this.notBefore = (arg1 !== null && arg1 !== void 0 ? arg1 : new Date(0)); | ||
this.notAfter = (arg2 !== null && arg2 !== void 0 ? arg2 : new Date(0)); | ||
constructor(notBefore = 0, notAfter = 0) { | ||
this.notBefore = toTimestamp(notBefore); | ||
this.notAfter = toTimestamp(notAfter); | ||
} | ||
@@ -43,7 +51,19 @@ static decodeFrom(decoder) { | ||
} | ||
/** Determine whether dt is within validity period. */ | ||
includes(dt) { | ||
const t = dt.getTime(); | ||
return this.notBefore.getTime() <= t && t <= this.notAfter.getTime(); | ||
/** Determine whether the specified timestamp is within validity period. */ | ||
includes(t = Date.now()) { | ||
t = toTimestamp(t); | ||
return this.notBefore <= t && t <= this.notAfter; | ||
} | ||
/** Determine whether this validity period equals another. */ | ||
equals({ notBefore, notAfter }) { | ||
return this.notBefore === notBefore && | ||
this.notAfter === notAfter; | ||
} | ||
/** Compute the intersection of this and other validity periods. */ | ||
intersect(...validityPeriods) { | ||
return new ValidityPeriod(Math.max(this.notBefore, ...validityPeriods.map(({ notBefore }) => notBefore)), Math.min(this.notAfter, ...validityPeriods.map(({ notAfter }) => notAfter))); | ||
} | ||
toString() { | ||
return `${encodeTimestampString(this.notBefore)}-${encodeTimestampString(this.notAfter)}`; | ||
} | ||
} | ||
@@ -60,5 +80,5 @@ SigInfo.registerExtension({ | ||
(function (ValidityPeriod) { | ||
ValidityPeriod.MAX = new ValidityPeriod(new Date(540109800000), new Date(253402300799000)); | ||
ValidityPeriod.MAX = new ValidityPeriod(540109800000, 253402300799000); | ||
function daysFromNow(n) { | ||
const notBefore = new Date(); | ||
const notBefore = Date.now(); | ||
const notAfter = new Date(notBefore); | ||
@@ -65,0 +85,0 @@ notAfter.setUTCDate(notAfter.getUTCDate() + n); |
export * from "./public-key"; | ||
export * from "./private-key"; | ||
export declare type EcCurve = "P-256" | "P-384" | "P-521"; | ||
export declare const EC_CURVES: ReadonlyArray<EcCurve>; | ||
export declare const EC_CURVES: readonly EcCurve[]; |
import { Name, NameLike } from "@ndn/packet"; | ||
import { EcCurve, EcPublicKey, KeyChain } from "../../mod"; | ||
import { PrivateKeyBase } from "../private-key"; | ||
import { StoredKey } from "../save"; | ||
import { makeGenParams } from "./internal"; | ||
import { PrivateKey } from "../base"; | ||
import { LoadedKey, StoredKey } from "../save"; | ||
import { makeGenParams } from "./algo"; | ||
/** ECDSA private key. */ | ||
export declare class EcPrivateKey extends PrivateKeyBase { | ||
export declare class EcPrivateKey extends PrivateKey { | ||
readonly curve: EcCurve; | ||
@@ -18,6 +18,6 @@ private readonly key; | ||
type: string; | ||
curve: EcCurve; | ||
curve: "P-256" | "P-384" | "P-521"; | ||
}; | ||
function generate(nameInput: NameLike, curve: EcCurve, keyChain?: KeyChain): Promise<[EcPrivateKey, EcPublicKey]>; | ||
function loadFromStored(name: Name, stored: StoredKey): Promise<[EcPrivateKey, EcPublicKey]>; | ||
function loadFromStored(name: Name, stored: StoredKey, extractable?: boolean): Promise<LoadedKey>; | ||
} |
@@ -0,12 +1,35 @@ | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
import { SigType } from "@ndn/packet"; | ||
import { fromHex, toHex } from "@ndn/tlv"; | ||
/// #if false | ||
const asn1 = __importDefault(require("@root/asn1")).default; | ||
/* | ||
/// #else | ||
import * as asn1 from "@root/asn1"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
/// #if false | ||
const assert = __importDefault(require("minimalistic-assert")).default; | ||
/* | ||
/// #else | ||
import assert from "minimalistic-assert"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
import { EcPublicKey } from "../../mod.js"; | ||
import { PrivateKey } from "../base.js"; | ||
import { crypto } from "../platform/mod.js"; | ||
import { PrivateKeyBase } from "../private-key.js"; | ||
import { generateKey } from "../save.js"; | ||
import { makeGenParams, SIGN_PARAMS, sigRawToDer } from "./internal.js"; | ||
import { EC_POINT_SIZE, makeGenParams, SIGN_PARAMS } from "./algo.js"; | ||
/** ECDSA private key. */ | ||
export class EcPrivateKey extends PrivateKeyBase { | ||
export class EcPrivateKey extends PrivateKey { | ||
constructor(name, curve, key) { | ||
super(name, SigType.Sha256WithEcdsa, name); | ||
super(name, SigType.Sha256WithEcdsa); | ||
this.curve = curve; | ||
@@ -16,6 +39,16 @@ this.key = key; | ||
async llSign(input) { | ||
const rawSig = await crypto.subtle.sign(SIGN_PARAMS, this.key, input); | ||
return sigRawToDer(new Uint8Array(rawSig), this.curve); | ||
const raw = new Uint8Array(await crypto.subtle.sign(SIGN_PARAMS, this.key, input)); | ||
const pointSize = EC_POINT_SIZE[this.curve]; | ||
return fromHex(asn1.Any("30", asn1.UInt(toUintHex(raw, 0, pointSize)), asn1.UInt(toUintHex(raw, pointSize, 2 * pointSize)))); | ||
} | ||
} | ||
function toUintHex(array, start, end) { | ||
let msb; | ||
for (msb = start; msb < end; ++msb) { | ||
if (array[msb]) { | ||
break; | ||
} | ||
} | ||
return toHex(array.slice(msb, end)); | ||
} | ||
(function (EcPrivateKey) { | ||
@@ -36,24 +69,26 @@ EcPrivateKey.makeWebCryptoImportParams = makeGenParams; | ||
EcPrivateKey.generate = generate; | ||
async function loadFromStored(name, stored) { | ||
async function loadFromStored(name, stored, extractable = false) { | ||
assert.equal(stored.type, EcPrivateKey.STORED_TYPE); | ||
const { curve } = stored; | ||
let pvt; | ||
let pub; | ||
if (stored.isJwk) { | ||
const { curve, isJwk, pvt, pub } = stored; | ||
let cryptoPvt; | ||
let cryptoPub; | ||
if (isJwk) { | ||
const params = makeGenParams(curve); | ||
[pvt, pub] = await Promise.all([ | ||
crypto.subtle.importKey("jwk", stored.pvt, params, false, ["sign"]), | ||
crypto.subtle.importKey("jwk", stored.pub, params, true, ["verify"]), | ||
[cryptoPvt, cryptoPub] = await Promise.all([ | ||
crypto.subtle.importKey("jwk", pvt, params, extractable, ["sign"]), | ||
crypto.subtle.importKey("jwk", pub, params, true, ["verify"]), | ||
]); | ||
} | ||
else { | ||
pvt = stored.pvt; | ||
pub = stored.pub; | ||
cryptoPvt = pvt; | ||
cryptoPub = pub; | ||
} | ||
return [ | ||
new EcPrivateKey(name, curve, pvt), | ||
new EcPublicKey(name, curve, pub), | ||
]; | ||
return { | ||
cryptoPvt, | ||
cryptoPub, | ||
privateKey: new EcPrivateKey(name, curve, cryptoPvt), | ||
publicKey: new EcPublicKey(name, curve, cryptoPub), | ||
}; | ||
} | ||
EcPrivateKey.loadFromStored = loadFromStored; | ||
})(EcPrivateKey || (EcPrivateKey = {})); |
@@ -1,16 +0,15 @@ | ||
import { Name, SigInfo } from "@ndn/packet"; | ||
import { DERElement } from "asn1-ts"; | ||
import { PublicKeyBase } from "../public-key"; | ||
import { Name } from "@ndn/packet"; | ||
import * as asn1 from "@root/asn1"; | ||
import { PublicKey } from "../base"; | ||
import { EcCurve } from "./mod"; | ||
/** ECDSA public key. */ | ||
export declare class EcPublicKey extends PublicKeyBase { | ||
export declare class EcPublicKey extends PublicKey implements PublicKey.Exportable { | ||
readonly curve: EcCurve; | ||
readonly key: CryptoKey; | ||
constructor(name: Name, curve: EcCurve, key: CryptoKey); | ||
protected llVerify(input: Uint8Array, sig: Uint8Array): Promise<void>; | ||
exportAsSpki(): Promise<Uint8Array>; | ||
protected doMatch(si: SigInfo): boolean; | ||
protected llVerify(input: Uint8Array, sig: Uint8Array): Promise<void>; | ||
} | ||
export declare namespace EcPublicKey { | ||
function importSpki(name: Name, spki: Uint8Array, der: DERElement): Promise<EcPublicKey>; | ||
function importSpki(name: Name, spki: Uint8Array, der: asn1.ElementBuffer): Promise<EcPublicKey>; | ||
} |
@@ -1,13 +0,42 @@ | ||
import { Name, SigType } from "@ndn/packet"; | ||
import { ASN1UniversalType } from "asn1-ts"; | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
import { SigType, Verifier } from "@ndn/packet"; | ||
import { toHex } from "@ndn/tlv"; | ||
/// #if false | ||
const asn1 = __importDefault(require("@root/asn1")).default; | ||
/* | ||
/// #else | ||
import * as asn1 from "@root/asn1"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
import { PublicKey } from "../base.js"; | ||
import { crypto } from "../platform/mod.js"; | ||
import { PublicKeyBase } from "../public-key.js"; | ||
import { makeGenParams, sigDerToRaw, SIGN_PARAMS } from "./internal.js"; | ||
import { EC_POINT_SIZE, makeGenParams, SIGN_PARAMS } from "./algo.js"; | ||
/** ECDSA public key. */ | ||
export class EcPublicKey extends PublicKeyBase { | ||
export class EcPublicKey extends PublicKey { | ||
constructor(name, curve, key) { | ||
super(name, SigType.Sha256WithEcdsa, name); | ||
super(name, SigType.Sha256WithEcdsa); | ||
this.curve = curve; | ||
this.key = key; | ||
} | ||
async llVerify(input, sig) { | ||
var _a, _b; | ||
const pointSize = EC_POINT_SIZE[this.curve]; | ||
const der = asn1.parseVerbose(sig); | ||
const r = (_a = der.children) === null || _a === void 0 ? void 0 : _a[0].value; | ||
const s = (_b = der.children) === null || _b === void 0 ? void 0 : _b[1].value; | ||
if (!r || !s || r.byteLength > pointSize || s.byteLength > pointSize) { | ||
Verifier.throwOnBadSig(false); | ||
} | ||
const raw = new Uint8Array(2 * pointSize); | ||
raw.set(r, pointSize - r.byteLength); | ||
raw.set(s, 2 * pointSize - s.byteLength); | ||
const ok = await crypto.subtle.verify(SIGN_PARAMS, this.key, raw, input); | ||
Verifier.throwOnBadSig(ok); | ||
} | ||
async exportAsSpki() { | ||
@@ -17,22 +46,14 @@ const spki = await crypto.subtle.exportKey("spki", this.key); | ||
} | ||
doMatch(si) { | ||
// TODO match KeyDigest | ||
return si.keyLocator instanceof Name && si.keyLocator.isPrefixOf(this.name); | ||
} | ||
async llVerify(input, sig) { | ||
const rawSig = sigDerToRaw(sig, this.curve); | ||
const ok = await crypto.subtle.verify(SIGN_PARAMS, this.key, rawSig, input); | ||
PublicKeyBase.throwOnIncorrectSig(ok); | ||
} | ||
} | ||
function determineEcCurve(der) { | ||
const { sequence: [{ sequence: [, paramsDer] },], } = der; | ||
if (paramsDer.tagNumber === ASN1UniversalType.objectIdentifier) { | ||
const namedCurveOid = paramsDer.objectIdentifier.dotDelimitedNotation; | ||
var _a, _b; | ||
const params = (_b = (_a = der.children) === null || _a === void 0 ? void 0 : _a[0].children) === null || _b === void 0 ? void 0 : _b[1]; | ||
if (params && params.type === 0x06 && params.value) { | ||
const namedCurveOid = toHex(params.value); | ||
switch (namedCurveOid) { | ||
case "1.2.840.10045.3.1.7": | ||
case "2A8648CE3D030107": // 1.2.840.10045.3.1.7 | ||
return "P-256"; | ||
case "1.3.132.0.34": | ||
case "2B81040022": // 1.3.132.0.34 | ||
return "P-384"; | ||
case "1.3.132.0.35": | ||
case "2B81040023": // 1.3.132.0.35 | ||
return "P-521"; | ||
@@ -39,0 +60,0 @@ } |
import { Name } from "@ndn/packet"; | ||
import { PublicKey } from "./mod"; | ||
import { PrivateKey } from "./private-key"; | ||
import { StoredKey } from "./save"; | ||
export declare function loadFromStored(name: Name, stored: StoredKey): Promise<[PrivateKey, PublicKey]>; | ||
import { LoadedKey, StoredKey } from "./save"; | ||
export declare function loadFromStored(name: Name, stored: StoredKey, extractable?: boolean): Promise<LoadedKey>; | ||
export declare function loadSpki(name: Name, spki: Uint8Array): Promise<PublicKey>; |
@@ -1,12 +0,26 @@ | ||
import { DERElement } from "asn1-ts"; | ||
import { HmacKey } from "./hmac.js"; | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
import { toHex } from "@ndn/tlv"; | ||
/// #if false | ||
const asn1 = __importDefault(require("@root/asn1")).default; | ||
/* | ||
/// #else | ||
import * as asn1 from "@root/asn1"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
import { HmacKey } from "./hmac/mod.js"; | ||
import { EcPrivateKey, EcPublicKey, RsaPrivateKey, RsaPublicKey } from "./mod.js"; | ||
export async function loadFromStored(name, stored) { | ||
export async function loadFromStored(name, stored, extractable = false) { | ||
switch (stored.type) { | ||
case EcPrivateKey.STORED_TYPE: | ||
return EcPrivateKey.loadFromStored(name, stored); | ||
return EcPrivateKey.loadFromStored(name, stored, extractable); | ||
case RsaPrivateKey.STORED_TYPE: | ||
return RsaPrivateKey.loadFromStored(name, stored); | ||
return RsaPrivateKey.loadFromStored(name, stored, extractable); | ||
case HmacKey.STORED_TYPE: | ||
return HmacKey.loadFromStored(name, stored); | ||
return HmacKey.loadFromStored(name, stored, extractable); | ||
} | ||
@@ -16,9 +30,13 @@ throw new Error(`unknown stored type ${stored.type}`); | ||
export async function loadSpki(name, spki) { | ||
const der = new DERElement(); | ||
der.fromBytes(spki); | ||
const { sequence: [{ sequence: [{ objectIdentifier: { dotDelimitedNotation: algoOid } }] },], } = der; | ||
var _a, _b; | ||
const der = asn1.parseVerbose(spki); | ||
const algo = (_b = (_a = der.children) === null || _a === void 0 ? void 0 : _a[0].children) === null || _b === void 0 ? void 0 : _b[0]; | ||
if (!(algo && algo.type === 0x06 && algo.value)) { | ||
throw new Error("SubjectPublicKeyInfo.algorithm.algorithm not found"); | ||
} | ||
const algoOid = toHex(algo.value); | ||
switch (algoOid) { | ||
case "1.2.840.10045.2.1": | ||
case "2A8648CE3D0201": // 1.2.840.10045.2.1 | ||
return EcPublicKey.importSpki(name, spki, der); | ||
case "1.2.840.113549.1.1.1": | ||
case "2A864886F70D010101": // 1.2.840.113549.1.1.1 | ||
return RsaPublicKey.importSpki(name, spki); | ||
@@ -25,0 +43,0 @@ } |
export { crypto as KeyChainImplWebCrypto } from "./platform/mod"; | ||
export { PrivateKey } from "./private-key"; | ||
export { PublicKey } from "./public-key"; | ||
export * from "./digest"; | ||
export * from "./base"; | ||
export * from "./rsa/mod"; | ||
export * from "./ec/mod"; | ||
export * from "./hmac"; | ||
export * from "./hmac/mod"; | ||
export { saveKey } from "./save"; |
export { crypto as KeyChainImplWebCrypto } from "./platform/mod.js"; | ||
export { PrivateKey } from "./private-key.js"; | ||
export { PublicKey } from "./public-key.js"; | ||
export * from "./digest.js"; | ||
export * from "./base.js"; | ||
export * from "./rsa/mod.js"; | ||
export * from "./ec/mod.js"; | ||
export * from "./hmac.js"; | ||
export * from "./hmac/mod.js"; | ||
export { saveKey } from "./save.js"; |
export declare const crypto: Crypto; | ||
export declare function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean; |
@@ -1,12 +0,1 @@ | ||
export const crypto = self.crypto; | ||
// https://codahale.com/a-lesson-in-timing-attacks/ | ||
export function timingSafeEqual(a, b) { | ||
if (a.byteLength !== b.byteLength) { | ||
return false; | ||
} | ||
let result = 0; | ||
for (let i = 0; i < a.byteLength; ++i) { | ||
result |= a[i] ^ b[i]; | ||
} | ||
return result === 0; | ||
} | ||
export const crypto = globalThis.crypto; |
export declare const crypto: Crypto; | ||
export declare function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean; |
@@ -0,9 +1,15 @@ | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
/// #if false | ||
const { Crypto: peculiarCrypto } = require("@peculiar/webcrypto"); | ||
/* | ||
/// #else | ||
import { Crypto as peculiarCrypto } from "@peculiar/webcrypto"; | ||
import { timingSafeEqual as nodeTimingSafeEqual } from "crypto"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
export const crypto = new peculiarCrypto(); // export as DOM Crypto type | ||
export function timingSafeEqual(a, b) { | ||
if (a.byteLength !== b.byteLength) { | ||
return false; | ||
} | ||
return nodeTimingSafeEqual(a, b); | ||
} |
export declare const ALGO = "RSASSA-PKCS1-v1_5"; | ||
export declare const IMPORT_PARAMS: RsaHashedImportParams; | ||
export declare const GEN_PARAMS: Pick<RsaHashedKeyGenParams, "hash" | "publicExponent" | "name">; | ||
export declare const GEN_PARAMS: Omit<RsaHashedKeyGenParams, "modulusLength">; |
export * from "./public-key"; | ||
export * from "./private-key"; | ||
export declare type RsaModulusLength = 1024 | 2048 | 4096; | ||
export declare const RSA_MODULUS_LENGTHS: ReadonlyArray<RsaModulusLength>; | ||
export declare const RSA_MODULUS_LENGTHS: readonly RsaModulusLength[]; |
import { Name, NameLike } from "@ndn/packet"; | ||
import { KeyChain, RsaModulusLength, RsaPublicKey } from "../../mod"; | ||
import { PrivateKeyBase } from "../private-key"; | ||
import { StoredKey } from "../save"; | ||
import { PrivateKey } from "../base"; | ||
import { LoadedKey, StoredKey } from "../save"; | ||
/** RSA private key. */ | ||
export declare class RsaPrivateKey extends PrivateKeyBase { | ||
export declare class RsaPrivateKey extends PrivateKey { | ||
private readonly key; | ||
@@ -18,3 +18,3 @@ constructor(name: Name, key: CryptoKey); | ||
function generate(nameInput: NameLike, modulusLength: RsaModulusLength, keyChain?: KeyChain): Promise<[RsaPrivateKey, RsaPublicKey]>; | ||
function loadFromStored(name: Name, stored: StoredKey): Promise<[RsaPrivateKey, RsaPublicKey]>; | ||
function loadFromStored(name: Name, { type, isJwk, pvt, pub }: StoredKey, extractable?: boolean): Promise<LoadedKey>; | ||
} |
@@ -0,12 +1,25 @@ | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
import { SigType } from "@ndn/packet"; | ||
/// #if false | ||
const assert = __importDefault(require("minimalistic-assert")).default; | ||
/* | ||
/// #else | ||
import assert from "minimalistic-assert"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
import { RsaPublicKey } from "../../mod.js"; | ||
import { PrivateKey } from "../base.js"; | ||
import { crypto } from "../platform/mod.js"; | ||
import { PrivateKeyBase } from "../private-key.js"; | ||
import { generateKey } from "../save.js"; | ||
import { ALGO, GEN_PARAMS, IMPORT_PARAMS } from "./internal.js"; | ||
/** RSA private key. */ | ||
export class RsaPrivateKey extends PrivateKeyBase { | ||
export class RsaPrivateKey extends PrivateKey { | ||
constructor(name, key) { | ||
super(name, SigType.Sha256WithRsa, name); | ||
super(name, SigType.Sha256WithRsa); | ||
this.key = key; | ||
@@ -37,22 +50,24 @@ } | ||
RsaPrivateKey.generate = generate; | ||
async function loadFromStored(name, stored) { | ||
assert.equal(stored.type, RsaPrivateKey.STORED_TYPE); | ||
let pvt; | ||
let pub; | ||
if (stored.isJwk) { | ||
[pvt, pub] = await Promise.all([ | ||
crypto.subtle.importKey("jwk", stored.pvt, IMPORT_PARAMS, false, ["sign"]), | ||
crypto.subtle.importKey("jwk", stored.pub, IMPORT_PARAMS, true, ["verify"]), | ||
async function loadFromStored(name, { type, isJwk, pvt, pub }, extractable = false) { | ||
assert.equal(type, RsaPrivateKey.STORED_TYPE); | ||
let cryptoPvt; | ||
let cryptoPub; | ||
if (isJwk) { | ||
[cryptoPvt, cryptoPub] = await Promise.all([ | ||
crypto.subtle.importKey("jwk", pvt, IMPORT_PARAMS, false, ["sign"]), | ||
crypto.subtle.importKey("jwk", pub, IMPORT_PARAMS, true, ["verify"]), | ||
]); | ||
} | ||
else { | ||
pvt = stored.pvt; | ||
pub = stored.pub; | ||
cryptoPvt = pvt; | ||
cryptoPub = pub; | ||
} | ||
return [ | ||
new RsaPrivateKey(name, pvt), | ||
new RsaPublicKey(name, pub), | ||
]; | ||
return { | ||
cryptoPvt, | ||
cryptoPub, | ||
privateKey: new RsaPrivateKey(name, cryptoPvt), | ||
publicKey: new RsaPublicKey(name, cryptoPub), | ||
}; | ||
} | ||
RsaPrivateKey.loadFromStored = loadFromStored; | ||
})(RsaPrivateKey || (RsaPrivateKey = {})); |
@@ -1,10 +0,9 @@ | ||
import { Name, SigInfo } from "@ndn/packet"; | ||
import { PublicKeyBase } from "../public-key"; | ||
import { Name } from "@ndn/packet"; | ||
import { PublicKey } from "../base"; | ||
/** RSA public key. */ | ||
export declare class RsaPublicKey extends PublicKeyBase { | ||
export declare class RsaPublicKey extends PublicKey implements PublicKey.Exportable { | ||
readonly key: CryptoKey; | ||
constructor(name: Name, key: CryptoKey); | ||
protected llVerify(input: Uint8Array, sig: Uint8Array): Promise<void>; | ||
exportAsSpki(): Promise<Uint8Array>; | ||
protected doMatch(si: SigInfo): boolean; | ||
protected llVerify(input: Uint8Array, sig: Uint8Array): Promise<void>; | ||
} | ||
@@ -11,0 +10,0 @@ export declare namespace RsaPublicKey { |
@@ -1,11 +0,15 @@ | ||
import { Name, SigType } from "@ndn/packet"; | ||
import { SigType, Verifier } from "@ndn/packet"; | ||
import { PublicKey } from "../base.js"; | ||
import { crypto } from "../platform/mod.js"; | ||
import { PublicKeyBase } from "../public-key.js"; | ||
import { ALGO, IMPORT_PARAMS } from "./internal.js"; | ||
/** RSA public key. */ | ||
export class RsaPublicKey extends PublicKeyBase { | ||
export class RsaPublicKey extends PublicKey { | ||
constructor(name, key) { | ||
super(name, SigType.Sha256WithRsa, name); | ||
super(name, SigType.Sha256WithRsa); | ||
this.key = key; | ||
} | ||
async llVerify(input, sig) { | ||
const ok = await crypto.subtle.verify(ALGO, this.key, sig, input); | ||
Verifier.throwOnBadSig(ok); | ||
} | ||
async exportAsSpki() { | ||
@@ -15,10 +19,2 @@ const spki = await crypto.subtle.exportKey("spki", this.key); | ||
} | ||
doMatch(si) { | ||
// TODO match KeyDigest | ||
return si.keyLocator instanceof Name && si.keyLocator.isPrefixOf(this.name); | ||
} | ||
async llVerify(input, sig) { | ||
const ok = await crypto.subtle.verify(ALGO, this.key, sig, input); | ||
PublicKeyBase.throwOnIncorrectSig(ok); | ||
} | ||
} | ||
@@ -25,0 +21,0 @@ (function (RsaPublicKey) { |
import { Name, NameLike } from "@ndn/packet"; | ||
import { KeyChain } from "../mod"; | ||
import { KeyChain } from "../store/mod"; | ||
import { PrivateKey, PublicKey } from "./base"; | ||
export interface StoredKey { | ||
@@ -9,2 +10,8 @@ type: string; | ||
} | ||
export interface LoadedKey { | ||
cryptoPvt: CryptoKey; | ||
cryptoPub?: CryptoKey; | ||
privateKey: PrivateKey; | ||
publicKey: PublicKey; | ||
} | ||
export declare function saveKey<T extends { | ||
@@ -11,0 +18,0 @@ type: string; |
@@ -1,7 +0,7 @@ | ||
import { KeyName } from "../mod.js"; | ||
import { Name } from "@ndn/packet"; | ||
import * as CertNaming from "../naming.js"; | ||
import { crypto } from "./platform/mod.js"; | ||
export async function saveKey(nameInput, type, algo, keyChain, makeKeys) { | ||
var _a; | ||
const name = KeyName.create(nameInput).toName(); | ||
const needJwk = ((_a = keyChain) === null || _a === void 0 ? void 0 : _a.canSCloneKeys) === false; | ||
const name = CertNaming.makeKeyName(new Name(nameInput)); | ||
const needJwk = (keyChain === null || keyChain === void 0 ? void 0 : keyChain.canSCloneKeys) === false; | ||
const pvtOrPair = await makeKeys(needJwk, crypto); | ||
@@ -36,3 +36,3 @@ let pvt; | ||
} | ||
keyChain.insertKey(name, stored); | ||
await keyChain.insertKey(name, stored); | ||
} | ||
@@ -39,0 +39,0 @@ return [name, pvt, pub]; |
@@ -1,4 +0,4 @@ | ||
export * from "./name"; | ||
export * as CertNaming from "./naming"; | ||
export * from "./key/mod"; | ||
export * from "./cert/mod"; | ||
export * from "./store/mod"; |
@@ -1,4 +0,5 @@ | ||
export * from "./name.js"; | ||
import * as CertNaming_1 from "./naming.js"; | ||
export { CertNaming_1 as CertNaming }; | ||
export * from "./key/mod.js"; | ||
export * from "./cert/mod.js"; | ||
export * from "./store/mod.js"; |
@@ -7,2 +7,3 @@ import { Name } from "@ndn/packet"; | ||
} | ||
/** Certificate store where backend supports JSON only. */ | ||
export declare class JsonCertStore extends StoreBase<Item> implements CertStore { | ||
@@ -9,0 +10,0 @@ get(name: Name): Promise<Certificate>; |
@@ -5,2 +5,3 @@ import { Data } from "@ndn/packet"; | ||
import { StoreBase } from "./store-base.js"; | ||
/** Certificate store where backend supports JSON only. */ | ||
export class JsonCertStore extends StoreBase { | ||
@@ -10,3 +11,3 @@ async get(name) { | ||
const wire = Buffer.from(item.certBase64, "base64"); | ||
return new Certificate(new Decoder(wire).decode(Data)); | ||
return Certificate.fromData(new Decoder(wire).decode(Data)); | ||
} | ||
@@ -13,0 +14,0 @@ async insert(cert) { |
@@ -1,8 +0,14 @@ | ||
import { Name } from "@ndn/packet"; | ||
import { StoredKey } from "../key/save"; | ||
import { PrivateKey, PublicKey } from "../mod"; | ||
import type { Name } from "@ndn/packet"; | ||
import { loadFromStored as loadFromStored_ } from "../key/load"; | ||
import { PrivateKey, PublicKey } from "../key/mod"; | ||
import { StoredKey as StoredKey_ } from "../key/save"; | ||
import { StoreBase } from "./store-base"; | ||
export declare class KeyStore extends StoreBase<StoredKey> { | ||
/** Storage of private keys. */ | ||
export declare class KeyStore extends StoreBase<KeyStore.StoredKey> { | ||
get(name: Name): Promise<[PrivateKey, PublicKey]>; | ||
insert(name: Name, stored: StoredKey): Promise<void>; | ||
insert(name: Name, stored: KeyStore.StoredKey): Promise<void>; | ||
} | ||
export declare namespace KeyStore { | ||
type StoredKey = StoredKey_; | ||
const loadFromStored: typeof loadFromStored_; | ||
} |
@@ -1,7 +0,9 @@ | ||
import { loadFromStored } from "../key/load.js"; | ||
import { loadFromStored as loadFromStored_ } from "../key/load.js"; | ||
import { StoreBase } from "./store-base.js"; | ||
/** Storage of private keys. */ | ||
export class KeyStore extends StoreBase { | ||
async get(name) { | ||
const stored = await this.getImpl(name); | ||
return await loadFromStored(name, stored); | ||
const { privateKey, publicKey } = await KeyStore.loadFromStored(name, stored); | ||
return [privateKey, publicKey]; | ||
} | ||
@@ -12,1 +14,4 @@ async insert(name, stored) { | ||
} | ||
(function (KeyStore) { | ||
KeyStore.loadFromStored = loadFromStored_; | ||
})(KeyStore || (KeyStore = {})); |
@@ -1,25 +0,63 @@ | ||
import { Name } from "@ndn/packet"; | ||
import { StoredKey } from "../key/save"; | ||
import { Certificate, PrivateKey, PublicKey } from "../mod"; | ||
import { Name, Signer } from "@ndn/packet"; | ||
import { Certificate } from "../cert/mod"; | ||
import { PrivateKey, PublicKey } from "../key/mod"; | ||
import { CertStore, KeyStore } from "./mod"; | ||
export declare class KeyChain { | ||
private readonly keys; | ||
private readonly certs; | ||
constructor(keys: KeyStore, certs: CertStore); | ||
/** Return whether KeyStore can structure-clone CryptoKey objects. */ | ||
get canSCloneKeys(): boolean; | ||
listKeys(prefix?: Name): Promise<Name[]>; | ||
getKeyPair(name: Name): Promise<[PrivateKey, PublicKey]>; | ||
/** Storage of own private keys and certificates. */ | ||
export declare abstract class KeyChain { | ||
/** | ||
* Return whether KeyStore supports structured clone of CryptoKey. | ||
* If this returns false, StoredKey must contain JsonWebKey instead of CryptoKey. | ||
*/ | ||
abstract readonly canSCloneKeys: boolean; | ||
/** List keys, filtered by name prefix. */ | ||
abstract listKeys(prefix?: Name): Promise<Name[]>; | ||
/** Retrieve key pair by key name. */ | ||
abstract getKeyPair(name: Name): Promise<[PrivateKey, PublicKey]>; | ||
/** Retrieve private key by key name. */ | ||
getPrivateKey(name: Name): Promise<PrivateKey>; | ||
/** Retrieve public key by key name. */ | ||
getPublicKey(name: Name): Promise<PublicKey>; | ||
insertKey(name: Name, stored: StoredKey): Promise<void>; | ||
deleteKey(name: Name): Promise<void>; | ||
listCerts(prefix?: Name): Promise<Name[]>; | ||
getCert(name: Name): Promise<Certificate>; | ||
insertCert(cert: Certificate): Promise<void>; | ||
deleteCert(name: Name): Promise<void>; | ||
/** Insert key pair. */ | ||
abstract insertKey(name: Name, stored: KeyStore.StoredKey): Promise<void>; | ||
/** Delete key pair and associated certificates. */ | ||
abstract deleteKey(name: Name): Promise<void>; | ||
/** List certificates, filtered by name prefix. */ | ||
abstract listCerts(prefix?: Name): Promise<Name[]>; | ||
/** Retrieve certificate by cert name. */ | ||
abstract getCert(name: Name): Promise<Certificate>; | ||
/** Insert certificate; key must exist. */ | ||
abstract insertCert(cert: Certificate): Promise<void>; | ||
/** Delete certificate. */ | ||
abstract deleteCert(name: Name): Promise<void>; | ||
/** | ||
* Create a signer from keys and certificates in the KeyChain. | ||
* @param name subject name, key name, or certificate name. | ||
* @param fallback invoked when no matching key or certificate is found. | ||
* | ||
* @li If name is a certificate name, sign with the corresponding private key, | ||
* and use the specified certificate name as KeyLocator. | ||
* @li If name is a key name, sign with the specified private key. | ||
* If a non-self-signed certificate exists for this key, use the certificate name as KeyLocator. | ||
* Otherwise, use the key name as KeyLocator. | ||
* @li If name is neither certificate name nor key name, it is interpreted as a subject name. | ||
* A non-self-signed certificate of this subject name is preferred. | ||
* If such a certificate does not exist, use any key of this subject name. | ||
* @li If prefixMatch is true, name can also be interpreted as a prefix of the subject name. | ||
*/ | ||
getSigner<Fallback extends Signer = never>(name: Name, { prefixMatch, fallback, }?: { | ||
prefixMatch?: boolean; | ||
fallback?: Signer | ((name: Name, keyChain: KeyChain, err?: Error) => Promise<Fallback>); | ||
}): Promise<Signer | Fallback>; | ||
private findSignerCertName; | ||
} | ||
export declare namespace KeyChain { | ||
/** | ||
* Open a persistent keychain. | ||
* @param locator in Node.js, a filesystem directory; in browser, a database name. | ||
*/ | ||
function open(locator: string): KeyChain; | ||
/** Open a keychain from given KeyStore and CertStore. */ | ||
function open(keys: KeyStore, certs: CertStore): KeyChain; | ||
/** Create an in-memory ephemeral keychain. */ | ||
function createTemp(): KeyChain; | ||
} |
import { Name } from "@ndn/packet"; | ||
import * as CertNaming from "../naming.js"; | ||
import { KeyStore, SCloneCertStore } from "./mod.js"; | ||
import { openStores } from "./platform/mod.js"; | ||
import { MemoryStoreImpl } from "./store-impl.js"; | ||
/** Storage of own private keys and certificates. */ | ||
export class KeyChain { | ||
/** Retrieve private key by key name. */ | ||
async getPrivateKey(name) { | ||
return (await this.getKeyPair(name))[0]; | ||
} | ||
/** Retrieve public key by key name. */ | ||
async getPublicKey(name) { | ||
return (await this.getKeyPair(name))[1]; | ||
} | ||
/** | ||
* Create a signer from keys and certificates in the KeyChain. | ||
* @param name subject name, key name, or certificate name. | ||
* @param fallback invoked when no matching key or certificate is found. | ||
* | ||
* @li If name is a certificate name, sign with the corresponding private key, | ||
* and use the specified certificate name as KeyLocator. | ||
* @li If name is a key name, sign with the specified private key. | ||
* If a non-self-signed certificate exists for this key, use the certificate name as KeyLocator. | ||
* Otherwise, use the key name as KeyLocator. | ||
* @li If name is neither certificate name nor key name, it is interpreted as a subject name. | ||
* A non-self-signed certificate of this subject name is preferred. | ||
* If such a certificate does not exist, use any key of this subject name. | ||
* @li If prefixMatch is true, name can also be interpreted as a prefix of the subject name. | ||
*/ | ||
async getSigner(name, { prefixMatch = false, fallback = (name, keyChain, err) => Promise.reject(new Error(`signer ${name} not found ${err}`)), } = {}) { | ||
const invokeFallback = (err) => { | ||
if (typeof fallback === "function") { | ||
return fallback(name, this, err); | ||
} | ||
return fallback; | ||
}; | ||
if (CertNaming.isCertName(name)) { | ||
let key; | ||
try { | ||
key = await this.getPrivateKey(CertNaming.toKeyName(name)); | ||
} | ||
catch (err) { | ||
return invokeFallback(err); | ||
} | ||
return key.withKeyLocator(name); | ||
} | ||
if (CertNaming.isKeyName(name)) { | ||
let key; | ||
let certName; | ||
try { | ||
[key, certName] = await Promise.all([ | ||
this.getPrivateKey(name), | ||
this.findSignerCertName(name, ({ keyName }) => name.equals(keyName)), | ||
]); | ||
} | ||
catch (err) { | ||
return invokeFallback(err); | ||
} | ||
return certName ? key.withKeyLocator(certName) : key; | ||
} | ||
const certName = await this.findSignerCertName(name, ({ subjectName }) => prefixMatch || name.equals(subjectName)); | ||
if (certName) { | ||
const key = await this.getPrivateKey(CertNaming.toKeyName(certName)); | ||
return key.withKeyLocator(certName); | ||
} | ||
let keyNames = await this.listKeys(name); | ||
if (!prefixMatch) { | ||
keyNames = keyNames.filter((keyName) => { | ||
const { subjectName } = CertNaming.parseKeyName(keyName); | ||
return name.equals(subjectName); | ||
}); | ||
} | ||
if (keyNames.length > 0) { | ||
return this.getPrivateKey(keyNames[0]); | ||
} | ||
return invokeFallback(); | ||
} | ||
async findSignerCertName(prefix, filter) { | ||
const certNames = (await this.listCerts(prefix)).filter((certName) => { | ||
const parsed = CertNaming.parseCertName(certName); | ||
return !parsed.issuerId.equals(CertNaming.ISSUER_SELF) && filter(parsed); | ||
}); | ||
return certNames.length === 0 ? undefined : certNames[0]; | ||
} | ||
} | ||
class KeyChainImpl extends KeyChain { | ||
constructor(keys, certs) { | ||
super(); | ||
this.keys = keys; | ||
this.certs = certs; | ||
} | ||
/** Return whether KeyStore can structure-clone CryptoKey objects. */ | ||
get canSCloneKeys() { return this.keys.canSClone; } | ||
@@ -16,10 +98,4 @@ async listKeys(prefix = new Name()) { | ||
async getKeyPair(name) { | ||
return await this.keys.get(name); | ||
return this.keys.get(name); | ||
} | ||
async getPrivateKey(name) { | ||
return (await this.getKeyPair(name))[0]; | ||
} | ||
async getPublicKey(name) { | ||
return (await this.getKeyPair(name))[1]; | ||
} | ||
async insertKey(name, stored) { | ||
@@ -37,7 +113,6 @@ await this.keys.insert(name, stored); | ||
async getCert(name) { | ||
return await this.certs.get(name); | ||
return this.certs.get(name); | ||
} | ||
async insertCert(cert) { | ||
const keyName = cert.certName.toKeyName().toName(); | ||
await this.getKeyPair(keyName); // ensure key exists | ||
await this.getKeyPair(CertNaming.toKeyName(cert.name)); // ensure key exists | ||
await this.certs.insert(cert); | ||
@@ -50,11 +125,15 @@ } | ||
(function (KeyChain) { | ||
function open(locator) { | ||
const [pvts, certs] = openStores(locator); | ||
return new KeyChain(pvts, certs); | ||
function open(arg1, arg2) { | ||
if (typeof arg1 === "string") { | ||
const [pvts, certs] = openStores(arg1); | ||
return new KeyChainImpl(pvts, certs); | ||
} | ||
return new KeyChainImpl(arg1, arg2); | ||
} | ||
KeyChain.open = open; | ||
/** Create an in-memory ephemeral keychain. */ | ||
function createTemp() { | ||
return new KeyChain(new KeyStore(new MemoryStoreImpl()), new SCloneCertStore(new MemoryStoreImpl())); | ||
return new KeyChainImpl(new KeyStore(new MemoryStoreImpl()), new SCloneCertStore(new MemoryStoreImpl())); | ||
} | ||
KeyChain.createTemp = createTemp; | ||
})(KeyChain || (KeyChain = {})); |
@@ -1,2 +0,2 @@ | ||
export { CertStore } from "./store-base"; | ||
export type { CertStore } from "./store-base"; | ||
export * from "./key-store"; | ||
@@ -3,0 +3,0 @@ export * from "./json-cert-store"; |
@@ -0,0 +0,0 @@ import { CertStore, KeyStore } from "../mod"; |
@@ -0,2 +1,15 @@ | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
/// #if false | ||
const { del: idbDel, get: idbGet, keys: idbKeys, set: idbSet, Store: idbStore } = require("idb-keyval"); | ||
/* | ||
/// #else | ||
import { del as idbDel, get as idbGet, keys as idbKeys, set as idbSet, Store as idbStore } from "idb-keyval"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
import { KeyStore, SCloneCertStore } from "../mod.js"; | ||
@@ -3,0 +16,0 @@ export class IdbStoreImpl { |
@@ -0,0 +0,0 @@ import { CertStore, KeyStore } from "../mod"; |
@@ -0,2 +1,15 @@ | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
/// #if false | ||
const Store = __importDefault(require("data-store")).default; | ||
/* | ||
/// #else | ||
import Store from "data-store"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
import { JsonCertStore, KeyStore } from "../mod.js"; | ||
@@ -24,2 +37,3 @@ export class FileStoreImpl { | ||
erase(key) { | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete this.store.data[key]; | ||
@@ -26,0 +40,0 @@ this.store.save(); |
@@ -7,2 +7,3 @@ import { Name } from "@ndn/packet"; | ||
} | ||
/** Certificate store where backend supports structured clone. */ | ||
export declare class SCloneCertStore extends StoreBase<Item> implements CertStore { | ||
@@ -9,0 +10,0 @@ get(name: Name): Promise<Certificate>; |
@@ -5,6 +5,7 @@ import { Data } from "@ndn/packet"; | ||
import { StoreBase } from "./store-base.js"; | ||
/** Certificate store where backend supports structured clone. */ | ||
export class SCloneCertStore extends StoreBase { | ||
async get(name) { | ||
const { certBuffer } = await this.getImpl(name); | ||
return new Certificate(new Decoder(certBuffer).decode(Data)); | ||
return Certificate.fromData(new Decoder(certBuffer).decode(Data)); | ||
} | ||
@@ -11,0 +12,0 @@ async insert(cert) { |
@@ -16,5 +16,6 @@ import { Name } from "@ndn/packet"; | ||
} | ||
export interface CertStore extends StoreBase<unknown> { | ||
get(name: Name): Promise<Certificate>; | ||
insert(cert: Certificate): Promise<void>; | ||
/** Storage of certificates. */ | ||
export interface CertStore extends StoreBase<any> { | ||
get: (name: Name) => Promise<Certificate>; | ||
insert: (cert: Certificate) => Promise<void>; | ||
} |
@@ -0,4 +1,17 @@ | ||
/// #if false | ||
import { createRequire } from "module"; | ||
const require = createRequire(import.meta.url); | ||
const { __importDefault } = require("tslib"); | ||
/// #endif | ||
import { Name } from "@ndn/packet"; | ||
import { fromHex, toHex } from "@ndn/tlv"; | ||
/// #if false | ||
const throat = __importDefault(require("throat")).default; | ||
/* | ||
/// #else | ||
import throat from "throat"; | ||
/// #endif | ||
/// #if false | ||
*/ | ||
/// #endif | ||
export class StoreBase { | ||
@@ -5,0 +18,0 @@ constructor(impl) { |
@@ -12,6 +12,6 @@ /** | ||
readonly storableKind: StorableKind; | ||
list(): Promise<string[]>; | ||
get(key: string): Promise<T>; | ||
insert(key: string, value: T): Promise<void>; | ||
erase(key: string): Promise<void>; | ||
list: () => Promise<string[]>; | ||
get: (key: string) => Promise<T>; | ||
insert: (key: string, value: T) => Promise<void>; | ||
erase: (key: string) => Promise<void>; | ||
} | ||
@@ -18,0 +18,0 @@ export declare class MemoryStoreImpl<T> implements StoreImpl<T> { |
{ | ||
"name": "@ndn/keychain", | ||
"version": "0.0.20191223-beta.1", | ||
"version": "0.0.20200606", | ||
"description": "NDNts: Key Chain", | ||
@@ -22,3 +22,5 @@ "keywords": [ | ||
}, | ||
"sideEffects": false, | ||
"sideEffects": [ | ||
"**/validity-period.[jt]s" | ||
], | ||
"homepage": "https://yoursunny.com/p/NDNts/", | ||
@@ -31,10 +33,10 @@ "repository": { | ||
"dependencies": { | ||
"@ndn/naming-convention2": "0.0.20191223-beta.1", | ||
"@ndn/packet": "0.0.20191223-beta.1", | ||
"@ndn/tlv": "0.0.20191223-beta.1", | ||
"@ndn/naming-convention2": "0.0.20200606", | ||
"@ndn/packet": "0.0.20200606", | ||
"@ndn/tlv": "0.0.20200606", | ||
"@root/asn1": "^1.0.0", | ||
"@types/minimalistic-assert": "^1.0.0", | ||
"@peculiar/webcrypto": "^1.0.22", | ||
"applymixins": "^1.1.0", | ||
"asn1-ts": "^2.10.4", | ||
"data-store": "github:jonschlinkert/data-store#b263df0455ad0c6a0a608fe30ed090d096c84487", | ||
"@types/root__asn1": "gist:521816e1294bba33a1ffc367cb141490", | ||
"@peculiar/webcrypto": "^1.1.1", | ||
"data-store": "jonschlinkert/data-store#4.1.0", | ||
"minimalistic-assert": "^1.0.1", | ||
@@ -41,0 +43,0 @@ "idb-keyval": "^3.2.0", |
@@ -11,3 +11,3 @@ # @ndn/keychain | ||
* [X] DigestSha256 | ||
* [X] DigestSha256 (in `@ndn/packet` package) | ||
* [X] signing and verification | ||
@@ -26,12 +26,14 @@ * [X] SignatureSha256WithRsa (RSASSA-PKCS1-v1\_5) | ||
The `PrivateKey` interface contains signing operators. | ||
The `PublicKey` interface contains verification operators. | ||
For asymmetric crypto algorithms, they are implemented by distinct classes. | ||
For symmetric crypto algorithms, they are generally implemented by the same class. | ||
Both Interest and Data are signable. | ||
`PrivateKey.prototype.sign` function assigns SignatureInfo field and returns immediately, deferring crypto signing operation in `pkt[LLSign.PENDING]`. | ||
When the packet is being transmitted, `L3Face` would trigger crypto signing during encoding. | ||
In case the signature is needed immediately, invoke `await pkt[LLSign.PROCESS]()` to execute crypto signing. | ||
* [X] sign Interest | ||
* [X] put certificate name in KeyLocator | ||
* [ ] generate SigNonce, SigTime, SigSeqNum | ||
* [X] verify Interest | ||
* [X] check ParametersSha256DigestComponent | ||
* [ ] check SigNonce, SigTime, SigSeqNum | ||
* [X] sign Data | ||
* [X] put certificate name in KeyLocator | ||
* [X] verify Data | ||
The implementation uses [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). | ||
@@ -46,3 +48,3 @@ | ||
`Certificate` class provides basic operations with [NDN Certificate Format 2.0](https://named-data.net/doc/ndn-cxx/0.6.6/specs/certificate-format.html). | ||
`Certificate` class provides basic operations with [NDN Certificate Format 2.0](https://named-data.net/doc/ndn-cxx/0.7.0/specs/certificate-format.html). | ||
@@ -71,3 +73,3 @@ * [X] generate self-signed certificate | ||
* In Firefox, ECDSA private keys cannot be saved in persistent keychain, due to [Mozilla Bug 1545813](https://bugzilla.mozilla.org/show_bug.cgi?id=1545813). | ||
* In Firefox, persistent keychain is unusable in a Private Browsing window, due to [Mozilla Bug 781982](https://bugzilla.mozilla.org/show_bug.cgi?id=781982). | ||
* In iOS, ECDSA P-521 curve is not supported. | ||
* In Firefox, persistent keychain is unusable in a Private Browsing window, due to [Mozilla Bug 781982](https://bugzilla.mozilla.org/show_bug.cgi?id=1639542). | ||
* In iOS and macOS Safari, ECDSA P-521 curve is not supported. |
GitHub dependency
Supply chain riskContains a dependency which resolves to a GitHub URL. Dependencies fetched from GitHub specifiers are not immutable can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
GitHub dependency
Supply chain riskContains a dependency which resolves to a GitHub URL. Dependencies fetched from GitHub specifiers are not immutable can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
68875
1732
72
66
12
2
+ Added@root/asn1@^1.0.0
+ Added@types/root__asn1@gist:521816e1294bba33a1ffc367cb141490
+ Added@ndn/naming-convention2@0.0.20200606(transitive)
+ Added@ndn/packet@0.0.20200606(transitive)
+ Added@ndn/tlv@0.0.20200606(transitive)
+ Added@root/asn1@1.0.2(transitive)
+ Added@root/encoding@1.0.1(transitive)
+ Added@types/root__asn1@1.0.5(transitive)
- Removedapplymixins@^1.1.0
- Removedasn1-ts@^2.10.4
- Removed@ndn/naming-convention2@0.0.20191223-beta.1(transitive)
- Removed@ndn/packet@0.0.20191223-beta.1(transitive)
- Removed@ndn/tlv@0.0.20191223-beta.1(transitive)
- Removedapplymixins@1.1.0(transitive)
- Removedasn1-ts@2.11.2(transitive)
Updated@ndn/packet@0.0.20200606
Updated@ndn/tlv@0.0.20200606
Updated@peculiar/webcrypto@^1.1.1