Skip to content

Commit 4c9fb31

Browse files
committed
keychain: KeyChainExternal
1 parent c1f138d commit 4c9fb31

File tree

3 files changed

+111
-47
lines changed

3 files changed

+111
-47
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type { Name } from "@ndn/packet";
2+
import type { Promisable } from "type-fest";
3+
4+
import type { Certificate } from "../certificate";
5+
import type { CryptoAlgorithm } from "../key/mod";
6+
import { KeyStore } from "./key-store";
7+
import { KeyChain, KeyChainSerialized } from "./keychain";
8+
9+
/**
10+
* KeyChain adapter that copies from an external KeyChain.
11+
*/
12+
export abstract class KeyChainExternal extends KeyChainSerialized {
13+
private readonly insertKeyLoader: KeyStore.Loader;
14+
private cached?: KeyChain;
15+
16+
protected constructor(
17+
protected readonly algoList: readonly CryptoAlgorithm[],
18+
public override readonly needJwk = true,
19+
) {
20+
super();
21+
this.insertKeyLoader = new KeyStore.Loader(needJwk, algoList);
22+
}
23+
24+
/** Copy the external KeyChain to `dest`. */
25+
protected abstract copyTo(dest: KeyChain): Promisable<KeyChain>;
26+
27+
private async load() {
28+
return (this.cached ??= await this.copyTo(KeyChain.createTemp(this.algoList)));
29+
}
30+
31+
protected override async sListKeys(prefix: Name): Promise<Name[]> {
32+
const keyChain = await this.load();
33+
return keyChain.listKeys(prefix);
34+
}
35+
36+
protected override async sGetKeyPair(name: Name): Promise<KeyChain.KeyPair> {
37+
const keyChain = await this.load();
38+
return keyChain.getKeyPair(name);
39+
}
40+
41+
protected override async sInsertKey(name: Name, stored: KeyStore.StoredKey): Promise<void> {
42+
const keyPair = await this.insertKeyLoader.loadKey(name, stored);
43+
try {
44+
await this.eInsertKey(keyPair);
45+
} finally {
46+
delete this.cached;
47+
}
48+
}
49+
50+
/** Insert a key pair in external KeyChain. */
51+
protected abstract eInsertKey(keyPair: KeyStore.KeyPair): Promisable<void>;
52+
53+
protected override async sDeleteKey(name: Name): Promise<void> {
54+
try {
55+
await this.eDeleteKey(name);
56+
} finally {
57+
delete this.cached;
58+
}
59+
}
60+
61+
/** Delete a key pair in external KeyChain. */
62+
protected abstract eDeleteKey(name: Name): Promisable<void>;
63+
64+
protected override async sListCerts(prefix: Name): Promise<Name[]> {
65+
const keyChain = await this.load();
66+
return keyChain.listCerts(prefix);
67+
}
68+
69+
protected override async sGetCert(name: Name): Promise<Certificate> {
70+
const keyChain = await this.load();
71+
return keyChain.getCert(name);
72+
}
73+
74+
protected override async sInsertCert(cert: Certificate): Promise<void> {
75+
try {
76+
await this.eInsertCert(cert);
77+
} finally {
78+
delete this.cached;
79+
}
80+
}
81+
82+
/** Insert a certificate in external KeyChain. */
83+
protected abstract eInsertCert(cert: Certificate): Promisable<void>;
84+
85+
protected override async sDeleteCert(name: Name): Promise<void> {
86+
try {
87+
await this.eDeleteCert(name);
88+
} finally {
89+
delete this.cached;
90+
}
91+
}
92+
93+
/** Delete a certificate in external KeyChain. */
94+
protected abstract eDeleteCert(name: Name): Promisable<void>;
95+
}

pkg/keychain/src/store/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./key-store";
22
export * from "./cert-store";
33
export * from "./keychain";
4+
export * from "./keychain-external";

pkg/ndnsec/src/keychain_node.ts

Lines changed: 15 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { Certificate, CertNaming, type CryptoAlgorithm, ECDSA, KeyChain, KeyChainSerialized, KeyStore, RSA, RSAOAEP } from "@ndn/keychain";
1+
import { Certificate, CertNaming, ECDSA, type KeyChain, KeyChainExternal, type KeyStore, RSA, RSAOAEP } from "@ndn/keychain";
22
import { Component, Data, Name, NameMap, ValidityPeriod } from "@ndn/packet";
33
import { type Decodable, Decoder, Encoder } from "@ndn/tlv";
4+
import { assert } from "@ndn/util";
45
import { execa, execaSync } from "execa";
56

67
import { SafeBag } from "./safe-bag";
@@ -12,7 +13,7 @@ const ALGO_LIST = [ECDSA, RSA, RSAOAEP];
1213
let ndnsecInstalled: boolean | undefined;
1314

1415
/** Access ndn-cxx KeyChain. */
15-
export class NdnsecKeyChain extends KeyChainSerialized {
16+
export class NdnsecKeyChain extends KeyChainExternal {
1617
/**
1718
* Whether current environment supports ndn-cxx KeyChain.
1819
*
@@ -29,7 +30,7 @@ export class NdnsecKeyChain extends KeyChainSerialized {
2930
home,
3031
importOptions,
3132
}: NdnsecKeyChain.Options = {}) {
32-
super();
33+
super(ALGO_LIST);
3334
if (pibLocator && tpmLocator) {
3435
this.env.NDN_CLIENT_PIB = pibLocator;
3536
this.env.NDN_CLIENT_TPM = tpmLocator;
@@ -39,11 +40,8 @@ export class NdnsecKeyChain extends KeyChainSerialized {
3940
this.importOptions = importOptions;
4041
}
4142

42-
public override readonly needJwk = true;
4343
private readonly env: NodeJS.ProcessEnv = { NDN_NAME_ALT_URI: "0" };
4444
private readonly importOptions?: SafeBag.ImportOptions;
45-
private cached?: KeyChain;
46-
private readonly insertKeyLoader = new KeyStore.Loader(true, ALGO_LIST);
4745

4846
private async invokeNdnsec(argv: readonly string[], input?: Uint8Array): Promise<{
4947
readonly lines: string[];
@@ -64,7 +62,7 @@ export class NdnsecKeyChain extends KeyChainSerialized {
6462
}
6563

6664
/** Copy keys and certificates to another keychain. */
67-
public async copyTo(dest: KeyChain): Promise<KeyChain> {
65+
public override async copyTo(dest: KeyChain): Promise<KeyChain> {
6866
const { lines } = await this.invokeNdnsec(["list", "-c"]);
6967
const keyCerts = new NameMap<Name[]>();
7068
for (const line of lines) {
@@ -97,60 +95,30 @@ export class NdnsecKeyChain extends KeyChainSerialized {
9795
return dest;
9896
}
9997

100-
private async load() {
101-
return (this.cached ??= await this.copyTo(KeyChain.createTemp(ALGO_LIST)));
102-
}
103-
104-
protected override async sListKeys(prefix: Name): Promise<Name[]> {
105-
const keyChain = await this.load();
106-
return keyChain.listKeys(prefix);
107-
}
108-
109-
protected override async sGetKeyPair(name: Name): Promise<KeyChain.KeyPair> {
110-
const keyChain = await this.load();
111-
return keyChain.getKeyPair(name);
112-
}
113-
114-
protected override async sInsertKey(name: Name, stored: KeyStore.StoredKey): Promise<void> {
115-
const keyPair = await this.insertKeyLoader.loadKey(name, stored);
116-
98+
protected override async eInsertKey({ publicKey, signer, pvt }: KeyStore.KeyPair): Promise<void> {
11799
const selfSigned = await Certificate.issue({
118-
publicKey: keyPair.publicKey,
100+
publicKey,
119101
validity: ValidityPeriod.MAX,
120-
issuerPrivateKey: keyPair.signer,
102+
issuerPrivateKey: signer,
121103
issuerId: IMPORTING_ISSUER,
122104
});
123-
const pkcs8 = new Uint8Array(await crypto.subtle.exportKey(
124-
"pkcs8", (keyPair.pvt as CryptoAlgorithm.PrivateKey).privateKey));
105+
assert("privateKey" in pvt);
106+
const pkcs8 = new Uint8Array(await crypto.subtle.exportKey("pkcs8", pvt.privateKey));
125107

126108
const safeBag = await SafeBag.create(selfSigned, pkcs8, PASSPHRASE);
127109
await this.invokeNdnsec(["import", "-P", PASSPHRASE, "-i-"], Encoder.encode(safeBag));
128-
delete this.cached;
129110
}
130111

131-
protected override async sDeleteKey(name: Name): Promise<void> {
112+
protected override async eDeleteKey(name: Name): Promise<void> {
132113
await this.invokeNdnsec(["delete", "-k", name.toString()]);
133-
delete this.cached;
134-
}
135-
136-
protected override async sListCerts(prefix: Name): Promise<Name[]> {
137-
const keyChain = await this.load();
138-
return keyChain.listCerts(prefix);
139-
}
140-
141-
protected override async sGetCert(name: Name): Promise<Certificate> {
142-
const keyChain = await this.load();
143-
return keyChain.getCert(name);
144114
}
145115

146-
protected override async sInsertCert(cert: Certificate): Promise<void> {
116+
protected override async eInsertCert(cert: Certificate): Promise<void> {
147117
await this.invokeNdnsec(["cert-install", "-K", "-f-"], Encoder.encode(cert.data));
148-
delete this.cached;
149118
}
150119

151-
protected override async sDeleteCert(name: Name): Promise<void> {
120+
protected override async eDeleteCert(name: Name): Promise<void> {
152121
await this.invokeNdnsec(["delete", "-c", name.toString()]);
153-
delete this.cached;
154122
}
155123
}
156124

@@ -162,7 +130,7 @@ export namespace NdnsecKeyChain {
162130
*
163131
* @remarks
164132
* This must be specified together with `.tpmLocator`.
165-
* @see {@link https://docs.named-data.net/ndn-cxx/0.8.1/manpages/ndn-client.conf.html#key-management}
133+
* @see {@link https://docs.named-data.net/ndn-cxx/0.9.0/manpages/ndn-client.conf.html#key-management}
166134
*/
167135
pibLocator?: string;
168136

@@ -171,7 +139,7 @@ export namespace NdnsecKeyChain {
171139
*
172140
* @remarks
173141
* This must be specified together with `.pibLocator`.
174-
* @see {@link https://docs.named-data.net/ndn-cxx/0.8.1/manpages/ndn-client.conf.html#key-management}
142+
* @see {@link https://docs.named-data.net/ndn-cxx/0.9.0/manpages/ndn-client.conf.html#key-management}
175143
*/
176144
tpmLocator?: string;
177145

0 commit comments

Comments
 (0)