-
Notifications
You must be signed in to change notification settings - Fork 8
/
ck.ts
129 lines (109 loc) · 3.86 KB
/
ck.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { AESCBC, type CryptoAlgorithm } from "@ndn/keychain";
import { Timestamp } from "@ndn/naming-convention2";
import { type Component, Data, type LLDecrypt, type Name, type Signer } from "@ndn/packet";
import { Decoder, Encoder, EvDecoder } from "@ndn/tlv";
import { crypto } from "@ndn/util";
import { DefaultFreshness, Keyword, TT } from "./an";
import { type KeyEncryptionKey, makeNameInternal as makeKekName, parseNameInternal as parseKekName } from "./kek";
const EVD = new EvDecoder<ContentKey.Fields>("ContentKey", TT.EncryptedContent)
.add(TT.EncryptedPayload, (t, { value }) => t.encryptedKey = value, { required: true });
/** NAC content key. */
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class ContentKey {
/** Parse content key from Data packet. */
public static async fromData(data: Data): Promise<ContentKey> {
return new ContentKey(data);
}
private constructor(public readonly data: Data) {
Object.assign(this, ContentKey.parseName(data.name));
EVD.decode(this, new Decoder(data.content));
}
public get locator(): Name {
return ContentKey.makeLocator(this);
}
public get name(): Name {
return this.data.name;
}
public async loadKey(decrypter: LLDecrypt.Key): Promise<CryptoAlgorithm.GeneratedSecretKey> {
const { plaintext } = await decrypter.llDecrypt({ ciphertext: this.encryptedKey });
return AESCBC.cryptoGenerate({ importRaw: plaintext }, false);
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export interface ContentKey extends Readonly<ContentKey.NameParts>, Readonly<ContentKey.Fields> {}
export namespace ContentKey {
export interface LocatorParts {
ckPrefix: Name;
ckId: Component;
}
export interface NameParts extends LocatorParts, KeyEncryptionKey.NameParts {
}
export interface Fields {
encryptedKey: Uint8Array;
}
/**
* Parse content key locator name.
* In an encrypted application packet, it appears in EncryptedPayload.Name field.
*/
export function parseLocator(name: Name): LocatorParts {
if (!name.get(-2)?.equals(Keyword.CK)) {
throw new Error("bad CK locator");
}
return {
ckPrefix: name.getPrefix(-2),
ckId: name.get(-1)!,
};
}
/** Create content key locator name. */
export function makeLocator({ ckPrefix, ckId }: LocatorParts): Name {
return ckPrefix.append(Keyword.CK, ckId);
}
/** Parse content key Data name. */
export function parseName(name: Name): NameParts {
let pos = name.length - 1;
for (; pos >= 0; --pos) {
if (name.get(pos)!.equals(Keyword.ENCRYPTED_BY)) {
break;
}
}
if (pos < 0) {
throw new Error("bad CK name");
}
return {
...parseLocator(name.getPrefix(pos)),
...parseKekName(name.slice(pos + 1), Keyword.KEK, "CK"),
};
}
/** Create content key Data name. */
export function makeName(parts: NameParts): Name {
return makeLocator(parts).append(Keyword.ENCRYPTED_BY, ...makeKekName(parts, Keyword.KEK).comps);
}
export interface Options {
kek: KeyEncryptionKey;
/** AES-CBC secret key. */
key: CryptoAlgorithm.GeneratedSecretKey;
ckPrefix: Name;
ckId?: Component;
signer: Signer;
}
/** Create content key packet. */
export async function build({
kek,
key,
ckPrefix,
ckId = Timestamp.create(Date.now()),
signer,
}: Options): Promise<ContentKey> {
const secret = new Uint8Array(await crypto.subtle.exportKey("raw", key.secretKey));
const { ciphertext } = await kek.encrypter.llEncrypt({ plaintext: secret });
const data = new Data();
data.name = makeName({ ...kek, ckPrefix, ckId });
data.freshnessPeriod = DefaultFreshness;
data.content = Encoder.encode([
TT.EncryptedContent,
[TT.EncryptedPayload, ciphertext],
]);
await signer.sign(data);
return ContentKey.fromData(data);
}
}