@enbox/crypto 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.mjs +1 -1
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/algorithms/aes-ctr.js +1 -1
- package/dist/esm/algorithms/aes-gcm.js +34 -1
- package/dist/esm/algorithms/aes-gcm.js.map +1 -1
- package/dist/esm/algorithms/aes-kw.js +154 -0
- package/dist/esm/algorithms/aes-kw.js.map +1 -0
- package/dist/esm/algorithms/ecdsa.js +110 -1
- package/dist/esm/algorithms/ecdsa.js.map +1 -1
- package/dist/esm/algorithms/eddsa.js +90 -1
- package/dist/esm/algorithms/eddsa.js.map +1 -1
- package/dist/esm/algorithms/hkdf.js +53 -0
- package/dist/esm/algorithms/hkdf.js.map +1 -0
- package/dist/esm/algorithms/pbkdf2.js +55 -0
- package/dist/esm/algorithms/pbkdf2.js.map +1 -0
- package/dist/esm/algorithms/sha-2.js +1 -1
- package/dist/esm/algorithms/x25519.js +125 -0
- package/dist/esm/algorithms/x25519.js.map +1 -0
- package/dist/esm/cose/cbor.js +35 -0
- package/dist/esm/cose/cbor.js.map +1 -0
- package/dist/esm/cose/cose-key.js +312 -0
- package/dist/esm/cose/cose-key.js.map +1 -0
- package/dist/esm/cose/cose-sign1.js +283 -0
- package/dist/esm/cose/cose-sign1.js.map +1 -0
- package/dist/esm/cose/eat.js +254 -0
- package/dist/esm/cose/eat.js.map +1 -0
- package/dist/esm/crypto-error.js +4 -0
- package/dist/esm/crypto-error.js.map +1 -1
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/local-key-manager.js +6 -1
- package/dist/esm/local-key-manager.js.map +1 -1
- package/dist/esm/primitives/ecies-secp256k1.js +79 -0
- package/dist/esm/primitives/ecies-secp256k1.js.map +1 -0
- package/dist/esm/primitives/x25519.js +9 -16
- package/dist/esm/primitives/x25519.js.map +1 -1
- package/dist/esm/utils.js +30 -0
- package/dist/esm/utils.js.map +1 -1
- package/dist/types/algorithms/aes-ctr.d.ts +1 -1
- package/dist/types/algorithms/aes-gcm.d.ts +23 -3
- package/dist/types/algorithms/aes-gcm.d.ts.map +1 -1
- package/dist/types/algorithms/aes-kw.d.ts +129 -0
- package/dist/types/algorithms/aes-kw.d.ts.map +1 -0
- package/dist/types/algorithms/ecdsa.d.ts +48 -3
- package/dist/types/algorithms/ecdsa.d.ts.map +1 -1
- package/dist/types/algorithms/eddsa.d.ts +48 -3
- package/dist/types/algorithms/eddsa.d.ts.map +1 -1
- package/dist/types/algorithms/hkdf.d.ts +35 -0
- package/dist/types/algorithms/hkdf.d.ts.map +1 -0
- package/dist/types/algorithms/pbkdf2.d.ts +35 -0
- package/dist/types/algorithms/pbkdf2.d.ts.map +1 -0
- package/dist/types/algorithms/sha-2.d.ts +1 -1
- package/dist/types/algorithms/x25519.d.ts +76 -0
- package/dist/types/algorithms/x25519.d.ts.map +1 -0
- package/dist/types/cose/cbor.d.ts +30 -0
- package/dist/types/cose/cbor.d.ts.map +1 -0
- package/dist/types/cose/cose-key.d.ts +106 -0
- package/dist/types/cose/cose-key.d.ts.map +1 -0
- package/dist/types/cose/cose-sign1.d.ts +195 -0
- package/dist/types/cose/cose-sign1.d.ts.map +1 -0
- package/dist/types/cose/eat.d.ts +203 -0
- package/dist/types/cose/eat.d.ts.map +1 -0
- package/dist/types/crypto-error.d.ts +4 -0
- package/dist/types/crypto-error.d.ts.map +1 -1
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/local-key-manager.d.ts +4 -4
- package/dist/types/local-key-manager.d.ts.map +1 -1
- package/dist/types/primitives/ecies-secp256k1.d.ts +53 -0
- package/dist/types/primitives/ecies-secp256k1.d.ts.map +1 -0
- package/dist/types/primitives/x25519.d.ts +9 -16
- package/dist/types/primitives/x25519.d.ts.map +1 -1
- package/dist/types/types/crypto-api.d.ts +52 -4
- package/dist/types/types/crypto-api.d.ts.map +1 -1
- package/dist/types/types/key-converter.d.ts +37 -15
- package/dist/types/types/key-converter.d.ts.map +1 -1
- package/dist/types/types/key-deriver.d.ts +41 -0
- package/dist/types/types/key-deriver.d.ts.map +1 -1
- package/dist/types/types/key-io.d.ts +37 -0
- package/dist/types/types/key-io.d.ts.map +1 -1
- package/dist/types/types/params-direct.d.ts +17 -0
- package/dist/types/types/params-direct.d.ts.map +1 -1
- package/dist/types/types/params-kms.d.ts +55 -0
- package/dist/types/types/params-kms.d.ts.map +1 -1
- package/dist/types/utils.d.ts +19 -0
- package/dist/types/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +3 -3
- package/package.json +12 -14
- package/src/algorithms/aes-ctr.ts +1 -1
- package/src/algorithms/aes-gcm.ts +38 -2
- package/src/algorithms/aes-kw.ts +182 -0
- package/src/algorithms/ecdsa.ts +132 -1
- package/src/algorithms/eddsa.ts +108 -1
- package/src/algorithms/hkdf.ts +54 -0
- package/src/algorithms/pbkdf2.ts +57 -0
- package/src/algorithms/sha-2.ts +1 -1
- package/src/algorithms/x25519.ts +153 -0
- package/src/cose/cbor.ts +36 -0
- package/src/cose/cose-key.ts +344 -0
- package/src/cose/cose-sign1.ts +473 -0
- package/src/cose/eat.ts +368 -0
- package/src/crypto-error.ts +6 -0
- package/src/index.ts +10 -0
- package/src/local-key-manager.ts +9 -4
- package/src/primitives/ecies-secp256k1.ts +113 -0
- package/src/primitives/x25519.ts +9 -16
- package/src/types/crypto-api.ts +124 -6
- package/src/types/key-converter.ts +33 -7
- package/src/types/key-deriver.ts +49 -0
- package/src/types/key-io.ts +40 -0
- package/src/types/params-direct.ts +21 -0
- package/src/types/params-kms.ts +67 -0
- package/src/utils.ts +53 -0
- package/dist/browser.js +0 -60
- package/dist/browser.js.map +0 -7
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import type { Jwk } from '../jose/jwk.js';
|
|
2
|
+
|
|
3
|
+
import { Cbor } from './cbor.js';
|
|
4
|
+
import { Ed25519 } from '../primitives/ed25519.js';
|
|
5
|
+
import { Secp256r1 } from '../primitives/secp256r1.js';
|
|
6
|
+
import { CoseAlgorithm, CoseKey } from './cose-key.js';
|
|
7
|
+
import { CryptoError, CryptoErrorCode } from '../crypto-error.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* COSE_Sign1 protected header parameters.
|
|
11
|
+
*
|
|
12
|
+
* The protected header is integrity-protected by inclusion in the Sig_structure.
|
|
13
|
+
* At minimum, it MUST contain the algorithm identifier.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9052#section-4 | RFC 9052, Section 4}
|
|
16
|
+
*/
|
|
17
|
+
export interface CoseSign1ProtectedHeader {
|
|
18
|
+
/** Algorithm identifier (label 1). Required. */
|
|
19
|
+
alg: CoseAlgorithm;
|
|
20
|
+
|
|
21
|
+
/** Content type (label 3). */
|
|
22
|
+
contentType?: string | number;
|
|
23
|
+
|
|
24
|
+
/** Key ID (label 4). */
|
|
25
|
+
kid?: Uint8Array;
|
|
26
|
+
|
|
27
|
+
/** Additional header parameters. */
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* COSE_Sign1 unprotected header parameters.
|
|
33
|
+
*
|
|
34
|
+
* These parameters are NOT integrity-protected.
|
|
35
|
+
*
|
|
36
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9052#section-4 | RFC 9052, Section 4}
|
|
37
|
+
*/
|
|
38
|
+
export interface CoseSign1UnprotectedHeader {
|
|
39
|
+
/** Key ID (label 4). */
|
|
40
|
+
kid?: Uint8Array;
|
|
41
|
+
|
|
42
|
+
/** Additional header parameters. */
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parameters for creating a COSE_Sign1 structure.
|
|
48
|
+
*/
|
|
49
|
+
export interface CoseSign1CreateParams {
|
|
50
|
+
/** The signing key in JWK format. Must contain the private key (`d`). */
|
|
51
|
+
key: Jwk;
|
|
52
|
+
|
|
53
|
+
/** The payload to sign. */
|
|
54
|
+
payload: Uint8Array;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Protected header parameters. If omitted, the algorithm is inferred from the key
|
|
58
|
+
* and a minimal protected header `{ alg }` is used.
|
|
59
|
+
*/
|
|
60
|
+
protectedHeader?: CoseSign1ProtectedHeader;
|
|
61
|
+
|
|
62
|
+
/** Unprotected header parameters. */
|
|
63
|
+
unprotectedHeader?: CoseSign1UnprotectedHeader;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* External additional authenticated data (external_aad).
|
|
67
|
+
* Included in the Sig_structure but not in the COSE_Sign1 message itself.
|
|
68
|
+
* Defaults to empty bytes.
|
|
69
|
+
*/
|
|
70
|
+
externalAad?: Uint8Array;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* If true, the payload is detached (not included in the COSE_Sign1 serialization).
|
|
74
|
+
* The payload field in the CBOR array will be `null`.
|
|
75
|
+
*/
|
|
76
|
+
detachedPayload?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Parameters for verifying a COSE_Sign1 structure.
|
|
81
|
+
*/
|
|
82
|
+
export interface CoseSign1VerifyParams {
|
|
83
|
+
/** The COSE_Sign1 CBOR-encoded message to verify. */
|
|
84
|
+
coseSign1: Uint8Array;
|
|
85
|
+
|
|
86
|
+
/** The public key in JWK format for verification. */
|
|
87
|
+
key: Jwk;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* External additional authenticated data (external_aad).
|
|
91
|
+
* Must match the value used during signing.
|
|
92
|
+
* Defaults to empty bytes.
|
|
93
|
+
*/
|
|
94
|
+
externalAad?: Uint8Array;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Detached payload. Required if the COSE_Sign1 was created with `detachedPayload: true`.
|
|
98
|
+
*/
|
|
99
|
+
payload?: Uint8Array;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Decoded COSE_Sign1 structure.
|
|
104
|
+
*/
|
|
105
|
+
export interface CoseSign1Decoded {
|
|
106
|
+
/** The protected header parameters (decoded from CBOR). */
|
|
107
|
+
protectedHeader: CoseSign1ProtectedHeader;
|
|
108
|
+
|
|
109
|
+
/** The raw protected header bytes (needed for signature verification). */
|
|
110
|
+
protectedHeaderBytes: Uint8Array;
|
|
111
|
+
|
|
112
|
+
/** The unprotected header parameters. */
|
|
113
|
+
unprotectedHeader: Map<number, unknown>;
|
|
114
|
+
|
|
115
|
+
/** The payload (null if detached). */
|
|
116
|
+
payload: Uint8Array | null;
|
|
117
|
+
|
|
118
|
+
/** The signature. */
|
|
119
|
+
signature: Uint8Array;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* COSE header label constants (RFC 9052, Section 3.1).
|
|
124
|
+
*/
|
|
125
|
+
enum CoseHeaderLabel {
|
|
126
|
+
/** Algorithm identifier */
|
|
127
|
+
Alg = 1,
|
|
128
|
+
/** Critical headers */
|
|
129
|
+
Crit = 2,
|
|
130
|
+
/** Content type */
|
|
131
|
+
ContentType = 3,
|
|
132
|
+
/** Key ID */
|
|
133
|
+
Kid = 4,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* CBOR tag for COSE_Sign1 (RFC 9052, Section 4.2).
|
|
138
|
+
*/
|
|
139
|
+
// const COSE_SIGN1_TAG = 18;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* COSE_Sign1 implementation per RFC 9052.
|
|
143
|
+
*
|
|
144
|
+
* Provides creation, verification, and decoding of COSE_Sign1 (single-signer)
|
|
145
|
+
* signed messages. This is the CBOR-based counterpart to JOSE/JWS and is used
|
|
146
|
+
* in TEE attestation (EAT tokens), CWT, and other COSE-based protocols.
|
|
147
|
+
*
|
|
148
|
+
* Supported algorithms:
|
|
149
|
+
* - EdDSA (Ed25519) — CoseAlgorithm.EdDSA (-8)
|
|
150
|
+
* - ES256 (P-256 / secp256r1 with SHA-256) — CoseAlgorithm.ES256 (-7)
|
|
151
|
+
*
|
|
152
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9052#section-4.3 | RFC 9052, Section 4.3}
|
|
153
|
+
*/
|
|
154
|
+
export class CoseSign1 {
|
|
155
|
+
/**
|
|
156
|
+
* Creates a COSE_Sign1 message.
|
|
157
|
+
*
|
|
158
|
+
* Constructs the `Sig_structure1` to-be-signed bytes per RFC 9052 Section 4.4,
|
|
159
|
+
* signs them with the provided key, and returns the CBOR-encoded COSE_Sign1 array:
|
|
160
|
+
*
|
|
161
|
+
* ```
|
|
162
|
+
* COSE_Sign1 = [
|
|
163
|
+
* protected : bstr, ; CBOR-encoded protected header
|
|
164
|
+
* unprotected : map, ; unprotected header parameters
|
|
165
|
+
* payload : bstr / nil, ; payload (nil if detached)
|
|
166
|
+
* signature : bstr ; signature
|
|
167
|
+
* ]
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @param params - The parameters for creating the COSE_Sign1 message.
|
|
171
|
+
* @returns The CBOR-encoded COSE_Sign1 message.
|
|
172
|
+
* @throws {CryptoError} If the algorithm is not supported or signing fails.
|
|
173
|
+
*
|
|
174
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9052#section-4.3 | RFC 9052, Section 4.3}
|
|
175
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9052#section-4.4 | RFC 9052, Section 4.4}
|
|
176
|
+
*/
|
|
177
|
+
public static async create(params: CoseSign1CreateParams): Promise<Uint8Array> {
|
|
178
|
+
const {
|
|
179
|
+
key,
|
|
180
|
+
payload,
|
|
181
|
+
externalAad = new Uint8Array(0),
|
|
182
|
+
detachedPayload = false,
|
|
183
|
+
} = params;
|
|
184
|
+
|
|
185
|
+
// Build the protected header.
|
|
186
|
+
const alg = params.protectedHeader?.alg ?? CoseKey.algorithmFromJwk(key);
|
|
187
|
+
const protectedHeaderMap = CoseSign1.buildProtectedHeaderMap(
|
|
188
|
+
params.protectedHeader ?? { alg }
|
|
189
|
+
);
|
|
190
|
+
const protectedHeaderBytes = Cbor.encode(protectedHeaderMap);
|
|
191
|
+
|
|
192
|
+
// Build the unprotected header.
|
|
193
|
+
const unprotectedHeaderMap = params.unprotectedHeader !== undefined
|
|
194
|
+
? CoseSign1.buildUnprotectedHeaderMap(params.unprotectedHeader)
|
|
195
|
+
: new Map<number, unknown>();
|
|
196
|
+
|
|
197
|
+
// Construct the Sig_structure1 (to-be-signed bytes).
|
|
198
|
+
const sigStructure = CoseSign1.buildSigStructure1(
|
|
199
|
+
protectedHeaderBytes, externalAad, payload
|
|
200
|
+
);
|
|
201
|
+
const toBeSigned = Cbor.encode(sigStructure);
|
|
202
|
+
|
|
203
|
+
// Sign the Sig_structure1 bytes.
|
|
204
|
+
const signature = await CoseSign1.signBytes(alg, key, toBeSigned);
|
|
205
|
+
|
|
206
|
+
// Assemble the COSE_Sign1 array.
|
|
207
|
+
const coseSign1Array = [
|
|
208
|
+
protectedHeaderBytes,
|
|
209
|
+
unprotectedHeaderMap,
|
|
210
|
+
detachedPayload ? null : payload,
|
|
211
|
+
signature,
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
return Cbor.encode(coseSign1Array);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Verifies a COSE_Sign1 message.
|
|
219
|
+
*
|
|
220
|
+
* Decodes the CBOR-encoded message, reconstructs the `Sig_structure1`, and verifies
|
|
221
|
+
* the signature using the provided public key.
|
|
222
|
+
*
|
|
223
|
+
* @param params - The parameters for verifying the COSE_Sign1 message.
|
|
224
|
+
* @returns `true` if the signature is valid, `false` otherwise.
|
|
225
|
+
* @throws {CryptoError} If the message is malformed or the algorithm is not supported.
|
|
226
|
+
*
|
|
227
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9052#section-4.4 | RFC 9052, Section 4.4}
|
|
228
|
+
*/
|
|
229
|
+
public static async verify(params: CoseSign1VerifyParams): Promise<boolean> {
|
|
230
|
+
const {
|
|
231
|
+
coseSign1,
|
|
232
|
+
key,
|
|
233
|
+
externalAad = new Uint8Array(0),
|
|
234
|
+
} = params;
|
|
235
|
+
|
|
236
|
+
// Decode the COSE_Sign1 message.
|
|
237
|
+
const decoded = CoseSign1.decode(coseSign1);
|
|
238
|
+
|
|
239
|
+
// Resolve the payload (from message or detached parameter).
|
|
240
|
+
const payload = decoded.payload ?? params.payload ?? null;
|
|
241
|
+
if (payload === null) {
|
|
242
|
+
throw new CryptoError(
|
|
243
|
+
CryptoErrorCode.InvalidCoseSign1,
|
|
244
|
+
'CoseSign1: payload is detached but no payload was provided for verification'
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Reconstruct the Sig_structure1.
|
|
249
|
+
const sigStructure = CoseSign1.buildSigStructure1(
|
|
250
|
+
decoded.protectedHeaderBytes, externalAad, payload
|
|
251
|
+
);
|
|
252
|
+
const toBeSigned = Cbor.encode(sigStructure);
|
|
253
|
+
|
|
254
|
+
// Extract the algorithm from the protected header.
|
|
255
|
+
const alg = decoded.protectedHeader.alg;
|
|
256
|
+
|
|
257
|
+
// Verify the signature.
|
|
258
|
+
return CoseSign1.verifyBytes(alg, key, toBeSigned, decoded.signature);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Decodes a CBOR-encoded COSE_Sign1 message into its constituent parts.
|
|
263
|
+
*
|
|
264
|
+
* The COSE_Sign1 structure is a CBOR array of four elements:
|
|
265
|
+
* ```
|
|
266
|
+
* [protected, unprotected, payload, signature]
|
|
267
|
+
* ```
|
|
268
|
+
*
|
|
269
|
+
* The message may optionally be wrapped in CBOR tag 18.
|
|
270
|
+
*
|
|
271
|
+
* @param coseSign1 - The CBOR-encoded COSE_Sign1 message.
|
|
272
|
+
* @returns The decoded COSE_Sign1 components.
|
|
273
|
+
* @throws {CryptoError} If the message does not conform to COSE_Sign1 structure.
|
|
274
|
+
*/
|
|
275
|
+
public static decode(coseSign1: Uint8Array): CoseSign1Decoded {
|
|
276
|
+
let decoded: unknown;
|
|
277
|
+
try {
|
|
278
|
+
decoded = Cbor.decode(coseSign1);
|
|
279
|
+
} catch {
|
|
280
|
+
throw new CryptoError(
|
|
281
|
+
CryptoErrorCode.InvalidCoseSign1,
|
|
282
|
+
'CoseSign1: failed to decode CBOR'
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Handle CBOR Tagged value (tag 18 for COSE_Sign1).
|
|
287
|
+
// The `cborg` library decodes tagged values as `Tagged` objects with `tag` and `value` properties.
|
|
288
|
+
if (decoded !== null && typeof decoded === 'object' && 'tag' in (decoded as Record<string, unknown>)) {
|
|
289
|
+
const tagged = decoded as { tag: number; value: unknown };
|
|
290
|
+
if (tagged.tag === 18) {
|
|
291
|
+
decoded = tagged.value;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Validate the COSE_Sign1 array structure.
|
|
296
|
+
if (!Array.isArray(decoded) || decoded.length !== 4) {
|
|
297
|
+
throw new CryptoError(
|
|
298
|
+
CryptoErrorCode.InvalidCoseSign1,
|
|
299
|
+
'CoseSign1: expected a CBOR array of 4 elements [protected, unprotected, payload, signature]'
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const [protectedHeaderBytes, unprotectedHeaderMap, payload, signature] = decoded as [
|
|
304
|
+
Uint8Array, Map<number, unknown>, Uint8Array | null, Uint8Array
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
// Validate element types.
|
|
308
|
+
if (!(protectedHeaderBytes instanceof Uint8Array)) {
|
|
309
|
+
throw new CryptoError(
|
|
310
|
+
CryptoErrorCode.InvalidCoseSign1,
|
|
311
|
+
'CoseSign1: protected header must be a byte string'
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!(signature instanceof Uint8Array)) {
|
|
316
|
+
throw new CryptoError(
|
|
317
|
+
CryptoErrorCode.InvalidCoseSign1,
|
|
318
|
+
'CoseSign1: signature must be a byte string'
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Decode the protected header.
|
|
323
|
+
let protectedHeaderMap: Map<number, unknown>;
|
|
324
|
+
if (protectedHeaderBytes.length === 0) {
|
|
325
|
+
protectedHeaderMap = new Map();
|
|
326
|
+
} else {
|
|
327
|
+
try {
|
|
328
|
+
protectedHeaderMap = Cbor.decode<Map<number, unknown>>(protectedHeaderBytes);
|
|
329
|
+
} catch {
|
|
330
|
+
throw new CryptoError(
|
|
331
|
+
CryptoErrorCode.InvalidCoseSign1,
|
|
332
|
+
'CoseSign1: failed to decode protected header CBOR'
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Extract the algorithm from the protected header.
|
|
338
|
+
const alg = protectedHeaderMap.get(CoseHeaderLabel.Alg);
|
|
339
|
+
if (alg === undefined || typeof alg !== 'number') {
|
|
340
|
+
throw new CryptoError(
|
|
341
|
+
CryptoErrorCode.InvalidCoseSign1,
|
|
342
|
+
'CoseSign1: protected header must contain an algorithm identifier (label 1)'
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Build the typed protected header.
|
|
347
|
+
const protectedHeader: CoseSign1ProtectedHeader = { alg: alg as CoseAlgorithm };
|
|
348
|
+
|
|
349
|
+
const contentType = protectedHeaderMap.get(CoseHeaderLabel.ContentType);
|
|
350
|
+
if (contentType !== undefined) {
|
|
351
|
+
protectedHeader.contentType = contentType as string | number;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const kid = protectedHeaderMap.get(CoseHeaderLabel.Kid);
|
|
355
|
+
if (kid !== undefined) {
|
|
356
|
+
protectedHeader.kid = kid as Uint8Array;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
protectedHeader,
|
|
361
|
+
protectedHeaderBytes,
|
|
362
|
+
unprotectedHeader : unprotectedHeaderMap instanceof Map ? unprotectedHeaderMap : new Map(),
|
|
363
|
+
payload : payload instanceof Uint8Array ? payload : null,
|
|
364
|
+
signature,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Builds the Sig_structure1 array for COSE_Sign1 signing and verification.
|
|
370
|
+
*
|
|
371
|
+
* ```
|
|
372
|
+
* Sig_structure1 = [
|
|
373
|
+
* context : "Signature1",
|
|
374
|
+
* body_protected : bstr,
|
|
375
|
+
* external_aad : bstr,
|
|
376
|
+
* payload : bstr
|
|
377
|
+
* ]
|
|
378
|
+
* ```
|
|
379
|
+
*
|
|
380
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc9052#section-4.4 | RFC 9052, Section 4.4}
|
|
381
|
+
*/
|
|
382
|
+
private static buildSigStructure1(
|
|
383
|
+
protectedHeaderBytes: Uint8Array,
|
|
384
|
+
externalAad: Uint8Array,
|
|
385
|
+
payload: Uint8Array,
|
|
386
|
+
): unknown[] {
|
|
387
|
+
return [
|
|
388
|
+
'Signature1', // context string
|
|
389
|
+
protectedHeaderBytes, // body_protected
|
|
390
|
+
externalAad, // external_aad
|
|
391
|
+
payload, // payload
|
|
392
|
+
];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Converts a {@link CoseSign1ProtectedHeader} to a CBOR Map with integer labels.
|
|
397
|
+
*/
|
|
398
|
+
private static buildProtectedHeaderMap(header: CoseSign1ProtectedHeader): Map<number, unknown> {
|
|
399
|
+
const map = new Map<number, unknown>();
|
|
400
|
+
|
|
401
|
+
map.set(CoseHeaderLabel.Alg, header.alg);
|
|
402
|
+
|
|
403
|
+
if (header.contentType !== undefined) {
|
|
404
|
+
map.set(CoseHeaderLabel.ContentType, header.contentType);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (header.kid !== undefined) {
|
|
408
|
+
map.set(CoseHeaderLabel.Kid, header.kid);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return map;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Converts a {@link CoseSign1UnprotectedHeader} to a CBOR Map with integer labels.
|
|
416
|
+
*/
|
|
417
|
+
private static buildUnprotectedHeaderMap(header: CoseSign1UnprotectedHeader): Map<number, unknown> {
|
|
418
|
+
const map = new Map<number, unknown>();
|
|
419
|
+
|
|
420
|
+
if (header.kid !== undefined) {
|
|
421
|
+
map.set(CoseHeaderLabel.Kid, header.kid);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return map;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Signs the to-be-signed bytes with the appropriate algorithm.
|
|
429
|
+
*/
|
|
430
|
+
private static async signBytes(
|
|
431
|
+
alg: CoseAlgorithm,
|
|
432
|
+
key: Jwk,
|
|
433
|
+
data: Uint8Array,
|
|
434
|
+
): Promise<Uint8Array> {
|
|
435
|
+
switch (alg) {
|
|
436
|
+
case CoseAlgorithm.EdDSA:
|
|
437
|
+
return Ed25519.sign({ key, data });
|
|
438
|
+
|
|
439
|
+
case CoseAlgorithm.ES256:
|
|
440
|
+
return Secp256r1.sign({ key, data });
|
|
441
|
+
|
|
442
|
+
default:
|
|
443
|
+
throw new CryptoError(
|
|
444
|
+
CryptoErrorCode.AlgorithmNotSupported,
|
|
445
|
+
`CoseSign1: signing algorithm ${alg} is not supported`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Verifies a signature over the to-be-signed bytes with the appropriate algorithm.
|
|
452
|
+
*/
|
|
453
|
+
private static async verifyBytes(
|
|
454
|
+
alg: CoseAlgorithm,
|
|
455
|
+
key: Jwk,
|
|
456
|
+
data: Uint8Array,
|
|
457
|
+
signature: Uint8Array,
|
|
458
|
+
): Promise<boolean> {
|
|
459
|
+
switch (alg) {
|
|
460
|
+
case CoseAlgorithm.EdDSA:
|
|
461
|
+
return Ed25519.verify({ key, signature, data });
|
|
462
|
+
|
|
463
|
+
case CoseAlgorithm.ES256:
|
|
464
|
+
return Secp256r1.verify({ key, signature, data });
|
|
465
|
+
|
|
466
|
+
default:
|
|
467
|
+
throw new CryptoError(
|
|
468
|
+
CryptoErrorCode.AlgorithmNotSupported,
|
|
469
|
+
`CoseSign1: verification algorithm ${alg} is not supported`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|