@amritk/nish-aarch64-linux 0.13.0 → 0.15.0
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/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +156 -0
- package/runtime/nish.h +89 -0
- package/runtime/nish.mjs +58 -0
- package/runtime/runtime-host.c +178 -0
- package/runtime/runtime-net.c +528 -0
- package/runtime/runtime-os.c +15 -0
- package/runtime/shim.mjs +137 -0
- package/scripts/build.sh +9 -7
- package/std/README.md +80 -0
- package/std/crypto/LICENSE-bearssl +21 -0
- package/std/crypto/LICENSE-fiat-crypto +21 -0
- package/std/crypto/aes.ts +899 -0
- package/std/crypto/base64url.ts +145 -0
- package/std/crypto/chacha20poly1305.ts +698 -0
- package/std/crypto/ct.ts +64 -0
- package/std/crypto/hkdf.ts +118 -0
- package/std/crypto/hmac.ts +155 -0
- package/std/crypto/p256.ts +4337 -0
- package/std/crypto/sha256.ts +444 -0
- package/std/crypto/sha512.ts +510 -0
- package/std/crypto/x25519.ts +509 -0
- package/std/crypto/x509.ts +1214 -0
|
@@ -0,0 +1,1214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/crypto/x509` — DER, PEM and the X.509 certificates a WebTransport or
|
|
3
|
+
* TLS server holds: read a P-256 private key and a certificate chain out of
|
|
4
|
+
* PEM, and mint the short-lived self-signed ECDSA P-256 certificate a browser
|
|
5
|
+
* pins by its SHA-256.
|
|
6
|
+
*
|
|
7
|
+
* import { x509MintSelfSigned, x509CertificateHash, derToPem } from "nish/crypto/x509";
|
|
8
|
+
*
|
|
9
|
+
* const serial: u8[] = new Array<u8>(16);
|
|
10
|
+
* crypto.getRandomValues(serial);
|
|
11
|
+
* serial[0] = serial[0] & toU8(0x7f); // positive, so it stays 16 octets
|
|
12
|
+
* const der: u8[] | null = x509MintSelfSigned(priv, "localhost", toI64(Date.now()), 14, serial);
|
|
13
|
+
* // x509CertificateHash(der) is what serverCertificateHashes names;
|
|
14
|
+
* // derToPem(der, "CERTIFICATE") is the file a TLS stack loads.
|
|
15
|
+
*
|
|
16
|
+
* Written for this module from ITU-T X.690 (DER), RFC 5280 (the certificate
|
|
17
|
+
* profile), RFC 7468 (PEM's textual encoding), RFC 5915 and RFC 5208/5958 (the
|
|
18
|
+
* SEC1 and PKCS#8 private-key structures), RFC 5480 (the P-256 key's
|
|
19
|
+
* identifiers) and RFC 5758 (ecdsa-with-SHA256).
|
|
20
|
+
*
|
|
21
|
+
* **DER is read strictly.** X.690 §10 leaves one encoding per value, and a
|
|
22
|
+
* reader that accepts a second spelling gives a certificate two identities
|
|
23
|
+
* while its hash — which is what a browser pins — names only one. So a length
|
|
24
|
+
* must be definite and minimal (no `0x80`, no long form below 128, no leading
|
|
25
|
+
* zero length octet), an INTEGER must be minimal in two's complement, a tag
|
|
26
|
+
* must fit in one octet, every element must end inside its parent, and a
|
|
27
|
+
* certificate must end where its outer SEQUENCE does. Anything else answers
|
|
28
|
+
* `null`, never a panic. The reader looks at what this module uses — the
|
|
29
|
+
* version, serial, algorithms, names as opaque bytes, validity, the subject
|
|
30
|
+
* public key and the signature — and checks the order of what follows the key
|
|
31
|
+
* (`[1]`, `[2]`, `[3]`) without parsing extensions.
|
|
32
|
+
*
|
|
33
|
+
* **PEM is RFC 7468's strict grammar**, with standard base64 (`+` and `/`,
|
|
34
|
+
* padded with `=`), not `base64url`: 64 characters a line and a shorter last
|
|
35
|
+
* one, a `-----END` label equal to its `-----BEGIN`, no headers (so an
|
|
36
|
+
* RFC 1421 encrypted key is refused), and canonical base64 whose unused low
|
|
37
|
+
* bits are zero. Text between blocks is ignored, as RFC 7468 §5.2 allows, and
|
|
38
|
+
* so are blocks with another label; a line may end in CRLF.
|
|
39
|
+
*
|
|
40
|
+
* **The minted certificate** is what the WebTransport specification's
|
|
41
|
+
* `serverCertificateHashes` accepts (W3C WebTransport, "custom certificate
|
|
42
|
+
* requirements"): X.509 v3, an ECDSA P-256 key signed with ecdsa-with-SHA256,
|
|
43
|
+
* and a validity period of at most fourteen days — `notAfter - notBefore`,
|
|
44
|
+
* which is what Chromium compares, so `days` is 1 to 14. Subject and issuer
|
|
45
|
+
* are the same single common name, as a UTF8String. It carries **no
|
|
46
|
+
* extensions**: the specification requires none, a browser checking a pinned
|
|
47
|
+
* hash does no path building for basicConstraints or keyUsage to inform, and
|
|
48
|
+
* every extension would be one more field whose absence is part of the
|
|
49
|
+
* contract a hash pins. OpenSSL verifies it as its own trust anchor all the
|
|
50
|
+
* same (`tests/link/crypto_x509`'s header has the commands). The serial and
|
|
51
|
+
* the time are parameters so the golden certificate is deterministic; a caller
|
|
52
|
+
* passes `Date.now()` and random bytes.
|
|
53
|
+
*
|
|
54
|
+
* **Constant time.** A private key is the one secret that passes through here:
|
|
55
|
+
* it is base64-decoded from PEM, lifted out of its DER, and handed to
|
|
56
|
+
* `nish/crypto/p256`. Base64 maps a character to its sextet and back by
|
|
57
|
+
* arithmetic on range masks, as `nish/crypto/base64url` does, and folds a bad
|
|
58
|
+
* character into one word read after the whole text, so neither a branch nor a
|
|
59
|
+
* table index depends on the key's bytes. PEM's framing is found by comparing
|
|
60
|
+
* the start of each line with the fixed `-----BEGIN ` and `-----END `, which no
|
|
61
|
+
* base64 character matches, so those comparisons end on a line's first byte
|
|
62
|
+
* whatever the key is. The DER reader branches on tags and
|
|
63
|
+
* lengths, which are the key's public framing, and copies its 32 octets with a
|
|
64
|
+
* fixed loop. Signing is `p256SignSha256`, and checking a key against the
|
|
65
|
+
* public key stored beside it compares public values. None of this is under
|
|
66
|
+
* the disassembly check; it is discipline.
|
|
67
|
+
*
|
|
68
|
+
* Private names carry the `x509` prefix because a `std/` module's private
|
|
69
|
+
* functions share the importing program's flat symbol namespace
|
|
70
|
+
* (`docs/wp26-stdlib.md` §3e).
|
|
71
|
+
*/
|
|
72
|
+
import { p256PublicKey, p256SignSha256, p256VerifySha256 } from "nish/crypto/p256"
|
|
73
|
+
import { sha256 } from "nish/crypto/sha256"
|
|
74
|
+
|
|
75
|
+
/** The longest validity `x509MintSelfSigned` writes, in days: WebTransport's two weeks. */
|
|
76
|
+
export const X509_MAX_DAYS: i32 = 14
|
|
77
|
+
|
|
78
|
+
/** Longest serial number, in content octets of its INTEGER (RFC 5280 §4.1.2.2). */
|
|
79
|
+
export const X509_MAX_SERIAL: i32 = 20
|
|
80
|
+
|
|
81
|
+
/** The longest common name minted, in bytes (RFC 5280 Appendix A's `ub-common-name`). */
|
|
82
|
+
const X509_MAX_COMMON_NAME: i32 = 64
|
|
83
|
+
|
|
84
|
+
/** What a time field reads as when it is not a valid RFC 5280 time: before any year the reader accepts. */
|
|
85
|
+
const X509_NO_TIME: i64 = -1000000000000
|
|
86
|
+
|
|
87
|
+
const X509_MS_PER_DAY: i64 = 86400000
|
|
88
|
+
|
|
89
|
+
// DER tags (X.690 §8, and the context tags RFC 5280 gives them).
|
|
90
|
+
const X509_TAG_INTEGER: i32 = 0x02
|
|
91
|
+
const X509_TAG_BIT_STRING: i32 = 0x03
|
|
92
|
+
const X509_TAG_OCTET_STRING: i32 = 0x04
|
|
93
|
+
const X509_TAG_OID: i32 = 0x06
|
|
94
|
+
const X509_TAG_UTF8_STRING: i32 = 0x0c
|
|
95
|
+
const X509_TAG_UTC_TIME: i32 = 0x17
|
|
96
|
+
const X509_TAG_GENERALIZED_TIME: i32 = 0x18
|
|
97
|
+
const X509_TAG_SEQUENCE: i32 = 0x30
|
|
98
|
+
const X509_TAG_SET: i32 = 0x31
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* One certificate, as `x509ParseCertificate` and `x509ParseChain` read it.
|
|
102
|
+
* The byte fields are copies, so the certificate outlives the text it was read
|
|
103
|
+
* from. Times are milliseconds since 1970-01-01 UTC, as `Date.now()` counts.
|
|
104
|
+
*/
|
|
105
|
+
export class X509Certificate {
|
|
106
|
+
/** The whole certificate, as its SHA-256 is taken. */
|
|
107
|
+
der: u8[]
|
|
108
|
+
/** The DER of `tbsCertificate`, the bytes the signature covers. */
|
|
109
|
+
tbs: u8[]
|
|
110
|
+
/** The serial number's INTEGER contents, two's complement as encoded. */
|
|
111
|
+
serial: u8[]
|
|
112
|
+
/** The issuer Name's DER, whole, to compare with another certificate's `subject`. */
|
|
113
|
+
issuer: u8[]
|
|
114
|
+
/** The subject Name's DER, whole. */
|
|
115
|
+
subject: u8[]
|
|
116
|
+
/** The subjectPublicKey BIT STRING's octets: for a P-256 key, the 65-byte uncompressed point. */
|
|
117
|
+
publicKey: u8[]
|
|
118
|
+
/** The signatureValue BIT STRING's octets: for ECDSA, the DER `SEQUENCE { r, s }`. */
|
|
119
|
+
signature: u8[]
|
|
120
|
+
/** The first instant the certificate is valid, inclusive (RFC 5280 §4.1.2.5). */
|
|
121
|
+
notBefore: i64 = 0
|
|
122
|
+
/** The last instant it is valid, inclusive. */
|
|
123
|
+
notAfter: i64 = 0
|
|
124
|
+
/** Whether the key is id-ecPublicKey on prime256v1 (RFC 5480 §2.1.1), so `publicKey` is a P-256 point. */
|
|
125
|
+
p256: boolean = false
|
|
126
|
+
/** Whether the signature algorithm is ecdsa-with-SHA256 with its parameters absent (RFC 5758 §3.2). */
|
|
127
|
+
ecdsaSha256: boolean = false
|
|
128
|
+
|
|
129
|
+
constructor() {
|
|
130
|
+
this.der = []
|
|
131
|
+
this.tbs = []
|
|
132
|
+
this.serial = []
|
|
133
|
+
this.issuer = []
|
|
134
|
+
this.subject = []
|
|
135
|
+
this.publicKey = []
|
|
136
|
+
this.signature = []
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// DER, reading (X.690 §8 and §10)
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
/** One element's identifier octet and where its contents lie: `der[start .. end)`. */
|
|
145
|
+
class X509DerElement {
|
|
146
|
+
tag: i32
|
|
147
|
+
/** Where the identifier octet is, so the whole element can be copied. */
|
|
148
|
+
at: i32
|
|
149
|
+
start: i32
|
|
150
|
+
end: i32
|
|
151
|
+
|
|
152
|
+
constructor(tag: i32, at: i32, start: i32, end: i32) {
|
|
153
|
+
this.tag = tag
|
|
154
|
+
this.at = at
|
|
155
|
+
this.start = start
|
|
156
|
+
this.end = end
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The element whose identifier octet is at `der[at]`, or `null` unless its
|
|
162
|
+
* header and contents lie in `der[at .. limit)` and its length is DER's: definite
|
|
163
|
+
* (§10.1) and minimal (§8.1.3.5 note 2 with §10.1 — long form only from 128,
|
|
164
|
+
* with no leading zero octet). A tag needing more than one octet is refused,
|
|
165
|
+
* since nothing in a certificate or a key uses one; lengths past 2^31 - 1 are
|
|
166
|
+
* refused before they can overflow.
|
|
167
|
+
*/
|
|
168
|
+
const x509DerRead = (der: u8[], at: i32, limit: i32): X509DerElement | null => {
|
|
169
|
+
if (at < 0 || limit > toI32(der.length) || at > limit - 2) {
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
const tag: i32 = toI32(der[at])
|
|
173
|
+
if ((tag & 0x1f) === 0x1f) {
|
|
174
|
+
return null
|
|
175
|
+
}
|
|
176
|
+
let length: i32 = toI32(der[at + 1])
|
|
177
|
+
let start: i32 = at + 2
|
|
178
|
+
if (length >= 0x80) {
|
|
179
|
+
const count: i32 = length & 0x7f
|
|
180
|
+
// 0x80 alone is BER's indefinite length, which DER forbids.
|
|
181
|
+
if (count < 1 || count > 4 || count > limit - start) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
if (toI32(der[start]) === 0 || (count === 4 && toI32(der[start]) >= 0x80)) {
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
length = 0
|
|
188
|
+
for (let k: i32 = 0; k < count; k++) {
|
|
189
|
+
length = (length << 8) | toI32(der[start + k])
|
|
190
|
+
}
|
|
191
|
+
if (length < 0x80) {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
start = start + count
|
|
195
|
+
}
|
|
196
|
+
if (length > limit - start) {
|
|
197
|
+
return null
|
|
198
|
+
}
|
|
199
|
+
return new X509DerElement(tag, at, start, start + length)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** `x509DerRead`, answering `null` also when the element's tag is not `tag`. */
|
|
203
|
+
const x509DerExpect = (der: u8[], at: i32, limit: i32, tag: i32): X509DerElement | null => {
|
|
204
|
+
const e: X509DerElement | null = x509DerRead(der, at, limit)
|
|
205
|
+
if (e === null || e.tag !== tag) {
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
return e
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The identifier octet at `der[at]` when `at` is inside `der[.. limit)`, else
|
|
213
|
+
* `-1`: how an optional element is looked for.
|
|
214
|
+
*/
|
|
215
|
+
const x509DerPeek = (der: u8[], at: i32, limit: i32): i32 => {
|
|
216
|
+
if (at < 0 || at >= limit || at >= toI32(der.length)) {
|
|
217
|
+
return -1
|
|
218
|
+
}
|
|
219
|
+
return toI32(der[at])
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** A copy of `der[from .. to)`. */
|
|
223
|
+
const x509DerSlice = (der: u8[], from: i32, to: i32): u8[] => {
|
|
224
|
+
const out: u8[] = new Array<u8>(to - from)
|
|
225
|
+
for (let k: i32 = 0; k < toI32(out.length); k++) {
|
|
226
|
+
const i: i32 = from + k
|
|
227
|
+
// Always true for the callers' ranges, and what lets the load drop its check.
|
|
228
|
+
if (i >= 0 && i < toI32(der.length)) {
|
|
229
|
+
out[k] = der[i]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return out
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** A copy of the element whole, identifier and length octets included. */
|
|
236
|
+
const x509DerWhole = (der: u8[], e: X509DerElement): u8[] => x509DerSlice(der, e.at, e.end)
|
|
237
|
+
|
|
238
|
+
/** Whether two byte strings are equal. Both are public here. */
|
|
239
|
+
const x509BytesEqual = (a: u8[], b: u8[]): boolean => {
|
|
240
|
+
if (toI32(b.length) !== toI32(a.length)) {
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
for (let k: i32 = 0; k < toI32(a.length); k++) {
|
|
244
|
+
if (k < toI32(b.length) && a[k] !== b[k]) {
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Whether `e`'s contents are exactly `expected`, as an OID is recognised by its encoding. */
|
|
252
|
+
const x509DerContentsAre = (der: u8[], e: X509DerElement, expected: u8[]): boolean => {
|
|
253
|
+
const n: i32 = toI32(expected.length)
|
|
254
|
+
if (e.end - e.start !== n) {
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
257
|
+
for (let k: i32 = 0; k < n; k++) {
|
|
258
|
+
if (der[e.start + k] !== expected[k]) {
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Whether an INTEGER's contents are DER's (X.690 §8.3.2): at least one octet,
|
|
267
|
+
* and the first nine bits neither all zero nor all one, which would make the
|
|
268
|
+
* first octet redundant.
|
|
269
|
+
*/
|
|
270
|
+
const x509DerIntegerMinimal = (der: u8[], e: X509DerElement): boolean => {
|
|
271
|
+
const n: i32 = e.end - e.start
|
|
272
|
+
if (n < 1) {
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
if (n === 1) {
|
|
276
|
+
return true
|
|
277
|
+
}
|
|
278
|
+
const b0: i32 = toI32(der[e.start])
|
|
279
|
+
const b1: i32 = toI32(der[e.start + 1])
|
|
280
|
+
return !((b0 === 0 && b1 < 0x80) || (b0 === 0xff && b1 >= 0x80))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The value of a one-octet, non-negative INTEGER such as a version, or `-1`. */
|
|
284
|
+
const x509DerSmallInteger = (der: u8[], e: X509DerElement): i32 => {
|
|
285
|
+
if (e.tag !== X509_TAG_INTEGER || e.end - e.start !== 1 || toI32(der[e.start]) >= 0x80) {
|
|
286
|
+
return -1
|
|
287
|
+
}
|
|
288
|
+
return toI32(der[e.start])
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The octets of a BIT STRING that has no unused bits, or `null`. Keys and
|
|
293
|
+
* signatures are whole octets, so the leading unused-bits count must be 0.
|
|
294
|
+
*/
|
|
295
|
+
const x509DerBitStringOctets = (der: u8[], e: X509DerElement): u8[] | null => {
|
|
296
|
+
if (e.tag !== X509_TAG_BIT_STRING || e.end - e.start < 1 || toI32(der[e.start]) !== 0) {
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
return x509DerSlice(der, e.start + 1, e.end)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** `count` decimal digits at `der[at ..)` as a number, or `-1` if any is not a digit. */
|
|
303
|
+
const x509DerDigits = (der: u8[], at: i32, count: i32): i32 => {
|
|
304
|
+
let value: i32 = 0
|
|
305
|
+
for (let k: i32 = 0; k < count; k++) {
|
|
306
|
+
const d: i32 = toI32(der[at + k]) - 48
|
|
307
|
+
if (d < 0 || d > 9) {
|
|
308
|
+
return -1
|
|
309
|
+
}
|
|
310
|
+
value = value * 10 + d
|
|
311
|
+
}
|
|
312
|
+
return value
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Whether `year` is a leap year of the Gregorian calendar. */
|
|
316
|
+
const x509IsLeapYear = (year: i32): boolean => (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
|
|
317
|
+
|
|
318
|
+
/** Days in `month` (1 to 12) of `year`. */
|
|
319
|
+
const x509DaysInMonth = (year: i32, month: i32): i32 => {
|
|
320
|
+
if (month === 2) {
|
|
321
|
+
return x509IsLeapYear(year) ? 29 : 28
|
|
322
|
+
}
|
|
323
|
+
return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Days from 1970-01-01 to `year`-`month`-`day`: the days of the years between,
|
|
328
|
+
* counted by the leap-year rule over whole years before each, and then the
|
|
329
|
+
* months of the year itself. `year` is at least 1.
|
|
330
|
+
*/
|
|
331
|
+
const x509DaysFromCivil = (year: i32, month: i32, day: i32): i32 => {
|
|
332
|
+
const y: i32 = year - 1
|
|
333
|
+
const before: i32 = 365 * y + y / 4 - y / 100 + y / 400
|
|
334
|
+
// 1969 * 365 + 492 - 19 + 4: the same count for 1970.
|
|
335
|
+
let days: i32 = before - 719162
|
|
336
|
+
for (let m: i32 = 1; m < month; m++) {
|
|
337
|
+
days = days + x509DaysInMonth(year, m)
|
|
338
|
+
}
|
|
339
|
+
return days + day - 1
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* A UTCTime or GeneralizedTime in the forms RFC 5280 §4.1.2.5 allows —
|
|
344
|
+
* `YYMMDDHHMMSSZ` and `YYYYMMDDHHMMSSZ`, in UTC, with seconds and without a
|
|
345
|
+
* fraction — as milliseconds since 1970, or `X509_NO_TIME`. A two-digit year
|
|
346
|
+
* of 50 or more is 19YY and below 50 is 20YY (§4.1.2.5.1). Both forms are
|
|
347
|
+
* accepted for any year from 1950, though §4.1.2.5 asks issuers to use UTCTime
|
|
348
|
+
* through 2049; the fields must name a real date and a time before 24:00:00.
|
|
349
|
+
*/
|
|
350
|
+
const x509DerTime = (der: u8[], e: X509DerElement): i64 => {
|
|
351
|
+
const n: i32 = e.end - e.start
|
|
352
|
+
let at: i32 = e.start
|
|
353
|
+
let year: i32 = 0
|
|
354
|
+
if (e.tag === X509_TAG_UTC_TIME && n === 13) {
|
|
355
|
+
year = x509DerDigits(der, at, 2)
|
|
356
|
+
if (year >= 0) {
|
|
357
|
+
year = year + (year >= 50 ? 1900 : 2000)
|
|
358
|
+
}
|
|
359
|
+
at = at + 2
|
|
360
|
+
} else if (e.tag === X509_TAG_GENERALIZED_TIME && n === 15) {
|
|
361
|
+
year = x509DerDigits(der, at, 4)
|
|
362
|
+
at = at + 4
|
|
363
|
+
} else {
|
|
364
|
+
return X509_NO_TIME
|
|
365
|
+
}
|
|
366
|
+
const month: i32 = x509DerDigits(der, at, 2)
|
|
367
|
+
const day: i32 = x509DerDigits(der, at + 2, 2)
|
|
368
|
+
const hour: i32 = x509DerDigits(der, at + 4, 2)
|
|
369
|
+
const minute: i32 = x509DerDigits(der, at + 6, 2)
|
|
370
|
+
const second: i32 = x509DerDigits(der, at + 8, 2)
|
|
371
|
+
if (toI32(der[at + 10]) !== 90 || year < 1950 || month < 1 || month > 12) {
|
|
372
|
+
return X509_NO_TIME
|
|
373
|
+
}
|
|
374
|
+
if (day < 1 || day > x509DaysInMonth(year, month) || hour < 0 || hour > 23) {
|
|
375
|
+
return X509_NO_TIME
|
|
376
|
+
}
|
|
377
|
+
if (minute < 0 || minute > 59 || second < 0 || second > 59) {
|
|
378
|
+
return X509_NO_TIME
|
|
379
|
+
}
|
|
380
|
+
const seconds: i32 = hour * 3600 + minute * 60 + second
|
|
381
|
+
return toI64(x509DaysFromCivil(year, month, day)) * X509_MS_PER_DAY + toI64(seconds) * 1000
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* The OID's contents octets (X.690 §8.19): the first two arcs as one
|
|
386
|
+
* subidentifier `40 * a + b`, then each arc in base 128, big-endian, with the
|
|
387
|
+
* top bit set on every octet but its last.
|
|
388
|
+
*/
|
|
389
|
+
const x509DerOidContents = (arcs: i32[]): u8[] => {
|
|
390
|
+
const out: u8[] = []
|
|
391
|
+
for (let i: i32 = 1; i < toI32(arcs.length); i++) {
|
|
392
|
+
const arc: i32 = i === 1 ? arcs[0] * 40 + arcs[1] : arcs[i]
|
|
393
|
+
let shift: i32 = 28
|
|
394
|
+
while (shift > 0 && arc >> shift === 0) {
|
|
395
|
+
shift = shift - 7
|
|
396
|
+
}
|
|
397
|
+
while (shift > 0) {
|
|
398
|
+
out.push(toU8(((arc >> shift) & 0x7f) | 0x80))
|
|
399
|
+
shift = shift - 7
|
|
400
|
+
}
|
|
401
|
+
out.push(toU8(arc & 0x7f))
|
|
402
|
+
}
|
|
403
|
+
return out
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** ecdsa-with-SHA256, 1.2.840.10045.4.3.2 (RFC 5758 §3.2). */
|
|
407
|
+
const x509OidEcdsaSha256 = (): u8[] => x509DerOidContents([1, 2, 840, 10045, 4, 3, 2])
|
|
408
|
+
|
|
409
|
+
/** id-ecPublicKey, 1.2.840.10045.2.1 (RFC 5480 §2.1.1). */
|
|
410
|
+
const x509OidEcPublicKey = (): u8[] => x509DerOidContents([1, 2, 840, 10045, 2, 1])
|
|
411
|
+
|
|
412
|
+
/** secp256r1, also called prime256v1 and P-256: 1.2.840.10045.3.1.7 (RFC 5480 §2.1.1.1). */
|
|
413
|
+
const x509OidPrime256v1 = (): u8[] => x509DerOidContents([1, 2, 840, 10045, 3, 1, 7])
|
|
414
|
+
|
|
415
|
+
/** id-at-commonName, 2.5.4.3 (RFC 5280 Appendix A). */
|
|
416
|
+
const x509OidCommonName = (): u8[] => x509DerOidContents([2, 5, 4, 3])
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Whether `der[start .. end)` is exactly one OID element equal to `oid` and
|
|
420
|
+
* nothing after it.
|
|
421
|
+
*/
|
|
422
|
+
const x509DerIsOnlyOid = (der: u8[], start: i32, end: i32, oid: u8[]): boolean => {
|
|
423
|
+
const e: X509DerElement | null = x509DerExpect(der, start, end, X509_TAG_OID)
|
|
424
|
+
return e !== null && e.end === end && x509DerContentsAre(der, e, oid)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Whether an AlgorithmIdentifier SEQUENCE is `{ oid, params }` with `params`
|
|
429
|
+
* the named-curve OID given, or `{ oid }` alone when `params` is empty.
|
|
430
|
+
*/
|
|
431
|
+
const x509DerIsAlgorithm = (der: u8[], alg: X509DerElement, oid: u8[], params: u8[]): boolean => {
|
|
432
|
+
if (alg.tag !== X509_TAG_SEQUENCE) {
|
|
433
|
+
return false
|
|
434
|
+
}
|
|
435
|
+
const id: X509DerElement | null = x509DerExpect(der, alg.start, alg.end, X509_TAG_OID)
|
|
436
|
+
if (id === null || !x509DerContentsAre(der, id, oid)) {
|
|
437
|
+
return false
|
|
438
|
+
}
|
|
439
|
+
if (toI32(params.length) === 0) {
|
|
440
|
+
return id.end === alg.end
|
|
441
|
+
}
|
|
442
|
+
return x509DerIsOnlyOid(der, id.end, alg.end, params)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* A minimal, non-negative INTEGER right-aligned into `out[off .. off + 32)`,
|
|
447
|
+
* or `false` when it is negative, not minimal or wider than 32 octets once a
|
|
448
|
+
* sign octet is set aside.
|
|
449
|
+
*/
|
|
450
|
+
const x509DerUnsignedInto = (der: u8[], e: X509DerElement, out: u8[], off: i32): boolean => {
|
|
451
|
+
if (!x509DerIntegerMinimal(der, e) || toI32(der[e.start]) >= 0x80) {
|
|
452
|
+
return false
|
|
453
|
+
}
|
|
454
|
+
let start: i32 = e.start
|
|
455
|
+
if (toI32(der[start]) === 0 && e.end - start > 1) {
|
|
456
|
+
start = start + 1
|
|
457
|
+
}
|
|
458
|
+
const n: i32 = e.end - start
|
|
459
|
+
if (n > 32) {
|
|
460
|
+
return false
|
|
461
|
+
}
|
|
462
|
+
for (let k: i32 = 0; k < n; k++) {
|
|
463
|
+
out[off + 32 - n + k] = der[start + k]
|
|
464
|
+
}
|
|
465
|
+
return true
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* `r || s`, 32 octets each, out of an ECDSA-Sig-Value `SEQUENCE { INTEGER r,
|
|
470
|
+
* INTEGER s }` (RFC 5480 §2.2 by way of RFC 3279 §2.2.3), or `null` unless it
|
|
471
|
+
* is exactly one with both integers minimal, positive and below 2^256.
|
|
472
|
+
*/
|
|
473
|
+
const x509DerSignatureRS = (sig: u8[]): u8[] | null => {
|
|
474
|
+
const total: i32 = toI32(sig.length)
|
|
475
|
+
const seq: X509DerElement | null = x509DerExpect(sig, 0, total, X509_TAG_SEQUENCE)
|
|
476
|
+
if (seq === null || seq.end !== total) {
|
|
477
|
+
return null
|
|
478
|
+
}
|
|
479
|
+
const r: X509DerElement | null = x509DerExpect(sig, seq.start, seq.end, X509_TAG_INTEGER)
|
|
480
|
+
if (r === null) {
|
|
481
|
+
return null
|
|
482
|
+
}
|
|
483
|
+
const s: X509DerElement | null = x509DerExpect(sig, r.end, seq.end, X509_TAG_INTEGER)
|
|
484
|
+
if (s === null || s.end !== seq.end) {
|
|
485
|
+
return null
|
|
486
|
+
}
|
|
487
|
+
const out: u8[] = new Array<u8>(64)
|
|
488
|
+
if (!x509DerUnsignedInto(sig, r, out, 0) || !x509DerUnsignedInto(sig, s, out, 32)) {
|
|
489
|
+
return null
|
|
490
|
+
}
|
|
491
|
+
return out
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// DER, writing
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
|
|
498
|
+
/** One element: `tag`, the minimal definite length of `contents` (X.690 §10.1), and `contents`. */
|
|
499
|
+
const x509DerTlv = (tag: i32, contents: u8[]): u8[] => {
|
|
500
|
+
const n: i32 = toI32(contents.length)
|
|
501
|
+
const out: u8[] = [toU8(tag)]
|
|
502
|
+
if (n < 0x80) {
|
|
503
|
+
out.push(toU8(n))
|
|
504
|
+
} else {
|
|
505
|
+
let count: i32 = 0
|
|
506
|
+
for (let v: i32 = n; v > 0; v = v >> 8) {
|
|
507
|
+
count = count + 1
|
|
508
|
+
}
|
|
509
|
+
out.push(toU8(0x80 | count))
|
|
510
|
+
for (let k: i32 = count - 1; k >= 0; k--) {
|
|
511
|
+
out.push(toU8((n >> (8 * k)) & 0xff))
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
for (let k: i32 = 0; k < toI32(contents.length); k++) {
|
|
515
|
+
out.push(contents[k])
|
|
516
|
+
}
|
|
517
|
+
return out
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** `parts` end to end. */
|
|
521
|
+
const x509DerConcat = (parts: u8[][]): u8[] => {
|
|
522
|
+
const out: u8[] = []
|
|
523
|
+
for (const part of parts) {
|
|
524
|
+
for (let k: i32 = 0; k < toI32(part.length); k++) {
|
|
525
|
+
out.push(part[k])
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return out
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** A constructed element around `parts`: a SEQUENCE, a SET or an explicit context tag. */
|
|
532
|
+
const x509DerWrap = (tag: i32, parts: u8[][]): u8[] => x509DerTlv(tag, x509DerConcat(parts))
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* The INTEGER holding the non-negative big-endian `magnitude`: its leading
|
|
536
|
+
* zero octets dropped, and one put back when the top bit would read as a sign
|
|
537
|
+
* (X.690 §8.3). Zero is the single octet `00`.
|
|
538
|
+
*/
|
|
539
|
+
const x509DerUnsignedInteger = (magnitude: u8[]): u8[] => {
|
|
540
|
+
const contents: u8[] = []
|
|
541
|
+
for (let k: i32 = 0; k < toI32(magnitude.length); k++) {
|
|
542
|
+
const octet: i32 = toI32(magnitude[k])
|
|
543
|
+
// The first significant octet: a sign octet in front of it if its top bit is set.
|
|
544
|
+
if (toI32(contents.length) === 0 && octet >= 0x80) {
|
|
545
|
+
contents.push(0)
|
|
546
|
+
}
|
|
547
|
+
if (toI32(contents.length) > 0 || octet !== 0) {
|
|
548
|
+
contents.push(toU8(octet))
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (toI32(contents.length) === 0) {
|
|
552
|
+
contents.push(0)
|
|
553
|
+
}
|
|
554
|
+
return x509DerTlv(X509_TAG_INTEGER, contents)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** A BIT STRING of whole `octets`: the unused-bits count 0, then the octets. */
|
|
558
|
+
const x509DerBitString = (octets: u8[]): u8[] => {
|
|
559
|
+
const contents: u8[] = [0]
|
|
560
|
+
for (let k: i32 = 0; k < toI32(octets.length); k++) {
|
|
561
|
+
contents.push(octets[k])
|
|
562
|
+
}
|
|
563
|
+
return x509DerTlv(X509_TAG_BIT_STRING, contents)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** `value` as `width` decimal digits, pushed onto `out`. */
|
|
567
|
+
const x509PushDigits = (out: u8[], value: i32, width: i32): void => {
|
|
568
|
+
let scale: i32 = 1
|
|
569
|
+
for (let k: i32 = 1; k < width; k++) {
|
|
570
|
+
scale = scale * 10
|
|
571
|
+
}
|
|
572
|
+
for (let k: i32 = 0; k < width; k++) {
|
|
573
|
+
out.push(toU8(((value / scale) % 10) + 48))
|
|
574
|
+
scale = scale / 10
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* The Time for `seconds` after 1970 (not negative, at most the last second of
|
|
580
|
+
* 9999), as RFC 5280 §4.1.2.5 has an issuer write it: UTCTime through 2049,
|
|
581
|
+
* GeneralizedTime from 2050, both in UTC with seconds.
|
|
582
|
+
*/
|
|
583
|
+
const x509DerTimeAt = (seconds: i64): u8[] => {
|
|
584
|
+
let days: i32 = toI32(seconds / 86400)
|
|
585
|
+
const rest: i32 = toI32(seconds % 86400)
|
|
586
|
+
let year: i32 = 1970
|
|
587
|
+
while (days >= (x509IsLeapYear(year) ? 366 : 365)) {
|
|
588
|
+
days = days - (x509IsLeapYear(year) ? 366 : 365)
|
|
589
|
+
year = year + 1
|
|
590
|
+
}
|
|
591
|
+
let month: i32 = 1
|
|
592
|
+
while (days >= x509DaysInMonth(year, month)) {
|
|
593
|
+
days = days - x509DaysInMonth(year, month)
|
|
594
|
+
month = month + 1
|
|
595
|
+
}
|
|
596
|
+
const text: u8[] = []
|
|
597
|
+
const utc: boolean = year < 2050
|
|
598
|
+
x509PushDigits(text, utc ? year % 100 : year, utc ? 2 : 4)
|
|
599
|
+
x509PushDigits(text, month, 2)
|
|
600
|
+
x509PushDigits(text, days + 1, 2)
|
|
601
|
+
x509PushDigits(text, rest / 3600, 2)
|
|
602
|
+
x509PushDigits(text, (rest / 60) % 60, 2)
|
|
603
|
+
x509PushDigits(text, rest % 60, 2)
|
|
604
|
+
text.push(90)
|
|
605
|
+
return x509DerTlv(utc ? X509_TAG_UTC_TIME : X509_TAG_GENERALIZED_TIME, text)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/** The bytes of `text`, as the language holds a string. */
|
|
609
|
+
const x509Bytes = (text: string): u8[] => {
|
|
610
|
+
const out: u8[] = []
|
|
611
|
+
const n: i32 = toI32(text.length)
|
|
612
|
+
for (let k: i32 = 0; k < n; k++) {
|
|
613
|
+
out.push(toU8(toI32(text.charCodeAt(k))))
|
|
614
|
+
}
|
|
615
|
+
return out
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ---------------------------------------------------------------------------
|
|
619
|
+
// PEM (RFC 7468) and standard base64 (RFC 4648 §4)
|
|
620
|
+
// ---------------------------------------------------------------------------
|
|
621
|
+
|
|
622
|
+
/** All ones when `lo <= c <= hi`, else zero, without a branch; `c` is a byte or a sextet. */
|
|
623
|
+
const x509RangeMask = (c: i32, lo: i32, hi: i32): i32 => ((lo - 1 - c) & (c - hi - 1)) >> 31
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* The base64 character for the sextet `v`: `'A' + v`, stepped by the gap to
|
|
627
|
+
* each later range it has reached — 26 lands on `a`, 52 on `0`, 62 on `+` and
|
|
628
|
+
* 63 on `/`.
|
|
629
|
+
*/
|
|
630
|
+
const x509Base64CharOf = (v: i32): i32 =>
|
|
631
|
+
65 +
|
|
632
|
+
v +
|
|
633
|
+
(x509RangeMask(v, 26, 63) & 6) -
|
|
634
|
+
(x509RangeMask(v, 52, 63) & 75) -
|
|
635
|
+
(x509RangeMask(v, 62, 63) & 15) +
|
|
636
|
+
(x509RangeMask(v, 63, 63) & 3)
|
|
637
|
+
|
|
638
|
+
/** The sextet the byte `c` stands for in base64, or `-1` when it is not in the alphabet. */
|
|
639
|
+
const x509Base64SextetOf = (c: i32): i32 => {
|
|
640
|
+
const upper: i32 = x509RangeMask(c, 65, 90)
|
|
641
|
+
const lower: i32 = x509RangeMask(c, 97, 122)
|
|
642
|
+
const digit: i32 = x509RangeMask(c, 48, 57)
|
|
643
|
+
const plus: i32 = x509RangeMask(c, 43, 43)
|
|
644
|
+
const slash: i32 = x509RangeMask(c, 47, 47)
|
|
645
|
+
const value: i32 = (upper & (c - 65)) | (lower & (c - 71)) | (digit & (c + 4)) | (plus & 62) | (slash & 63)
|
|
646
|
+
return value | ~(upper | lower | digit | plus | slash)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** `data` in padded base64, as one line. */
|
|
650
|
+
const x509Base64Encode = (data: u8[]): string => {
|
|
651
|
+
const parts: string[] = []
|
|
652
|
+
let acc: i32 = 0
|
|
653
|
+
let bits: i32 = 0
|
|
654
|
+
for (let k: i32 = 0; k < toI32(data.length); k++) {
|
|
655
|
+
acc = ((acc << 8) | toI32(data[k])) & 0xfff
|
|
656
|
+
bits = bits + 8
|
|
657
|
+
while (bits >= 6) {
|
|
658
|
+
bits = bits - 6
|
|
659
|
+
parts.push(String.fromCharCode(x509Base64CharOf((acc >> bits) & 63)))
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (bits > 0) {
|
|
663
|
+
parts.push(String.fromCharCode(x509Base64CharOf((acc << (6 - bits)) & 63)))
|
|
664
|
+
}
|
|
665
|
+
// One `=` for a final group of two bytes, two for one byte.
|
|
666
|
+
const tail: i32 = toI32(data.length) % 3
|
|
667
|
+
if (tail > 0) {
|
|
668
|
+
parts.push(tail === 1 ? "==" : "=")
|
|
669
|
+
}
|
|
670
|
+
return parts.join("")
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* The bytes of padded base64 `text`, or `null` unless it is the one canonical
|
|
675
|
+
* spelling of them: a whole number of four-character groups, `=` only as the
|
|
676
|
+
* last one or two characters, every other character in the alphabet, and the
|
|
677
|
+
* bits the padding leaves over all zero (RFC 4648 §3.5). Where the padding is
|
|
678
|
+
* depends on the length alone; the characters themselves are read by masks and
|
|
679
|
+
* judged once, after the last.
|
|
680
|
+
*/
|
|
681
|
+
const x509Base64Decode = (text: string): u8[] | null => {
|
|
682
|
+
const n: i32 = toI32(text.length)
|
|
683
|
+
if (n === 0 || (n & 3) !== 0) {
|
|
684
|
+
return null
|
|
685
|
+
}
|
|
686
|
+
let pad: i32 = 0
|
|
687
|
+
if (toI32(text.charCodeAt(n - 1)) === 61) {
|
|
688
|
+
pad = toI32(text.charCodeAt(n - 2)) === 61 ? 2 : 1
|
|
689
|
+
}
|
|
690
|
+
const chars: i32 = n - pad
|
|
691
|
+
const out: u8[] = new Array<u8>((n >> 2) * 3 - pad)
|
|
692
|
+
const outLen: i32 = toI32(out.length)
|
|
693
|
+
let bad: i32 = 0
|
|
694
|
+
let acc: i32 = 0
|
|
695
|
+
let bits: i32 = 0
|
|
696
|
+
let j: i32 = 0
|
|
697
|
+
for (let k: i32 = 0; k < toI32(text.length); k++) {
|
|
698
|
+
// The padding is past `chars`, and is not data; which positions those are
|
|
699
|
+
// is a fact about the length.
|
|
700
|
+
if (k < chars) {
|
|
701
|
+
const v: i32 = x509Base64SextetOf(toI32(text.charCodeAt(k)))
|
|
702
|
+
bad = bad | (v >> 31)
|
|
703
|
+
acc = ((acc << 6) | (v & 63)) & 0xfff
|
|
704
|
+
bits = bits + 6
|
|
705
|
+
if (bits >= 8) {
|
|
706
|
+
bits = bits - 8
|
|
707
|
+
// Always true — `out` was sized from the same length — and written so
|
|
708
|
+
// the store keeps no bounds check.
|
|
709
|
+
if (j >= 0 && j < outLen) {
|
|
710
|
+
out[j] = toU8((acc >> bits) & 0xff)
|
|
711
|
+
}
|
|
712
|
+
j = j + 1
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
bad = bad | (acc & ((1 << bits) - 1))
|
|
717
|
+
if (bad !== 0) {
|
|
718
|
+
return null
|
|
719
|
+
}
|
|
720
|
+
return out
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** Whether `text` starts with `prefix`. */
|
|
724
|
+
const x509StartsWith = (text: string, prefix: string): boolean => {
|
|
725
|
+
const n: i32 = toI32(prefix.length)
|
|
726
|
+
return toI32(text.length) >= n && text.substring(0, n) === prefix
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Whether `text` ends with `suffix`. */
|
|
730
|
+
const x509EndsWith = (text: string, suffix: string): boolean => {
|
|
731
|
+
const n: i32 = toI32(text.length)
|
|
732
|
+
const m: i32 = toI32(suffix.length)
|
|
733
|
+
return n >= m && text.substring(n - m, n) === suffix
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* `der` as a PEM block (RFC 7468 §2): `-----BEGIN label-----`, the base64 of
|
|
738
|
+
* the DER in lines of 64 characters, and `-----END label-----`, each line
|
|
739
|
+
* ending in a line feed.
|
|
740
|
+
*/
|
|
741
|
+
export const derToPem = (der: u8[], label: string): string => {
|
|
742
|
+
const body: string = x509Base64Encode(der)
|
|
743
|
+
const n: i32 = toI32(body.length)
|
|
744
|
+
const lines: string[] = [`-----BEGIN ${label}-----`]
|
|
745
|
+
for (let at: i32 = 0; at < n; at = at + 64) {
|
|
746
|
+
lines.push(body.substring(at, at + 64 < n ? at + 64 : n))
|
|
747
|
+
}
|
|
748
|
+
lines.push(`-----END ${label}-----`)
|
|
749
|
+
lines.push("")
|
|
750
|
+
return lines.join("\n")
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* The DER of every PEM block in `pem` whose label is exactly `label`, in the
|
|
755
|
+
* order they appear, or `null` when there is none or any block is malformed:
|
|
756
|
+
* an unterminated block, an END whose label is not its BEGIN's, a line longer
|
|
757
|
+
* than 64 characters or a short line before the last, a header line, or
|
|
758
|
+
* base64 that is not canonical. Blocks with other labels must be as well
|
|
759
|
+
* formed, and are skipped; so is text outside the blocks.
|
|
760
|
+
*/
|
|
761
|
+
export const pemToDer = (pem: string, label: string): u8[][] | null => {
|
|
762
|
+
const out: u8[][] = []
|
|
763
|
+
const n: i32 = toI32(pem.length)
|
|
764
|
+
// The label of the block being read, or "" outside one; its body lines so far.
|
|
765
|
+
let inside: string = ""
|
|
766
|
+
let body: string[] = []
|
|
767
|
+
let shortSeen: boolean = false
|
|
768
|
+
let lineStart: i32 = 0
|
|
769
|
+
while (lineStart < n) {
|
|
770
|
+
let lineEnd: i32 = lineStart
|
|
771
|
+
while (lineEnd >= 0 && lineEnd < toI32(pem.length) && toI32(pem.charCodeAt(lineEnd)) !== 10) {
|
|
772
|
+
lineEnd = lineEnd + 1
|
|
773
|
+
}
|
|
774
|
+
const next: i32 = lineEnd + 1
|
|
775
|
+
if (lineEnd > lineStart && toI32(pem.charCodeAt(lineEnd - 1)) === 13) {
|
|
776
|
+
lineEnd = lineEnd - 1
|
|
777
|
+
}
|
|
778
|
+
const line: string = pem.slice(lineStart, lineEnd)
|
|
779
|
+
lineStart = next
|
|
780
|
+
const length: i32 = toI32(line.length)
|
|
781
|
+
if (inside === "") {
|
|
782
|
+
if (x509StartsWith(line, "-----BEGIN ") && x509EndsWith(line, "-----") && length > 16) {
|
|
783
|
+
inside = line.substring(11, length - 5)
|
|
784
|
+
body = []
|
|
785
|
+
shortSeen = false
|
|
786
|
+
} else if (x509StartsWith(line, "-----END ")) {
|
|
787
|
+
return null
|
|
788
|
+
}
|
|
789
|
+
} else if (x509StartsWith(line, "-----END ")) {
|
|
790
|
+
if (line !== `-----END ${inside}-----`) {
|
|
791
|
+
return null
|
|
792
|
+
}
|
|
793
|
+
if (inside === label) {
|
|
794
|
+
const der: u8[] | null = x509Base64Decode(body.join(""))
|
|
795
|
+
if (der === null) {
|
|
796
|
+
return null
|
|
797
|
+
}
|
|
798
|
+
out.push(der)
|
|
799
|
+
}
|
|
800
|
+
inside = ""
|
|
801
|
+
} else {
|
|
802
|
+
if (shortSeen || length < 1 || length > 64) {
|
|
803
|
+
return null
|
|
804
|
+
}
|
|
805
|
+
shortSeen = length < 64
|
|
806
|
+
body.push(line)
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (inside !== "" || toI32(out.length) === 0) {
|
|
810
|
+
return null
|
|
811
|
+
}
|
|
812
|
+
return out
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// ---------------------------------------------------------------------------
|
|
816
|
+
// Private keys (RFC 5915, RFC 5208 / RFC 5958)
|
|
817
|
+
// ---------------------------------------------------------------------------
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* The 32-byte scalar of an ECPrivateKey (RFC 5915 §3) filling `der[start ..
|
|
821
|
+
* end)`: version 1, the key as an OCTET STRING of exactly 32 octets, the
|
|
822
|
+
* optional `[0]` parameters, which must name prime256v1 and are required when
|
|
823
|
+
* `needCurve` (a bare SEC1 key has nowhere else to say its curve), and the
|
|
824
|
+
* optional `[1]` public key, which must be the key's own. `null` otherwise,
|
|
825
|
+
* or when the scalar is 0 or not below n.
|
|
826
|
+
*/
|
|
827
|
+
const x509Sec1Key = (der: u8[], start: i32, end: i32, needCurve: boolean): u8[] | null => {
|
|
828
|
+
const seq: X509DerElement | null = x509DerExpect(der, start, end, X509_TAG_SEQUENCE)
|
|
829
|
+
if (seq === null || seq.end !== end) {
|
|
830
|
+
return null
|
|
831
|
+
}
|
|
832
|
+
const version: X509DerElement | null = x509DerRead(der, seq.start, seq.end)
|
|
833
|
+
if (version === null || x509DerSmallInteger(der, version) !== 1) {
|
|
834
|
+
return null
|
|
835
|
+
}
|
|
836
|
+
const key: X509DerElement | null = x509DerExpect(der, version.end, seq.end, X509_TAG_OCTET_STRING)
|
|
837
|
+
if (key === null || key.end - key.start !== 32) {
|
|
838
|
+
return null
|
|
839
|
+
}
|
|
840
|
+
const priv: u8[] = x509DerSlice(der, key.start, key.end)
|
|
841
|
+
let at: i32 = key.end
|
|
842
|
+
let curve: boolean = false
|
|
843
|
+
let stored: u8[] | null = null
|
|
844
|
+
if (x509DerPeek(der, at, seq.end) === 0xa0) {
|
|
845
|
+
const params: X509DerElement | null = x509DerRead(der, at, seq.end)
|
|
846
|
+
if (params === null || !x509DerIsOnlyOid(der, params.start, params.end, x509OidPrime256v1())) {
|
|
847
|
+
return null
|
|
848
|
+
}
|
|
849
|
+
curve = true
|
|
850
|
+
at = params.end
|
|
851
|
+
}
|
|
852
|
+
if (x509DerPeek(der, at, seq.end) === 0xa1) {
|
|
853
|
+
const wrapper: X509DerElement | null = x509DerRead(der, at, seq.end)
|
|
854
|
+
if (wrapper === null) {
|
|
855
|
+
return null
|
|
856
|
+
}
|
|
857
|
+
const bits: X509DerElement | null = x509DerExpect(der, wrapper.start, wrapper.end, X509_TAG_BIT_STRING)
|
|
858
|
+
if (bits === null || bits.end !== wrapper.end) {
|
|
859
|
+
return null
|
|
860
|
+
}
|
|
861
|
+
stored = x509DerBitStringOctets(der, bits)
|
|
862
|
+
if (stored === null) {
|
|
863
|
+
return null
|
|
864
|
+
}
|
|
865
|
+
at = wrapper.end
|
|
866
|
+
}
|
|
867
|
+
if (at !== seq.end || (needCurve && !curve)) {
|
|
868
|
+
return null
|
|
869
|
+
}
|
|
870
|
+
// Refuses 0 and n and above, and gives the point to hold `stored` against.
|
|
871
|
+
const pub: u8[] | null = p256PublicKey(priv)
|
|
872
|
+
if (pub === null) {
|
|
873
|
+
return null
|
|
874
|
+
}
|
|
875
|
+
if (stored !== null && !x509BytesEqual(stored, pub)) {
|
|
876
|
+
return null
|
|
877
|
+
}
|
|
878
|
+
return priv
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* The scalar of a PKCS#8 PrivateKeyInfo (RFC 5208 §5) or OneAsymmetricKey
|
|
883
|
+
* (RFC 5958 §2): version 0 or 1, the algorithm id-ecPublicKey with the
|
|
884
|
+
* prime256v1 curve, and an ECPrivateKey inside the OCTET STRING, followed by
|
|
885
|
+
* the optional `[0]` attributes and — in version 1 — `[1]` public key, which
|
|
886
|
+
* are skipped.
|
|
887
|
+
*/
|
|
888
|
+
const x509Pkcs8Key = (der: u8[]): u8[] | null => {
|
|
889
|
+
const total: i32 = toI32(der.length)
|
|
890
|
+
const seq: X509DerElement | null = x509DerExpect(der, 0, total, X509_TAG_SEQUENCE)
|
|
891
|
+
if (seq === null || seq.end !== total) {
|
|
892
|
+
return null
|
|
893
|
+
}
|
|
894
|
+
const version: X509DerElement | null = x509DerRead(der, seq.start, seq.end)
|
|
895
|
+
if (version === null) {
|
|
896
|
+
return null
|
|
897
|
+
}
|
|
898
|
+
const v: i32 = x509DerSmallInteger(der, version)
|
|
899
|
+
if (v !== 0 && v !== 1) {
|
|
900
|
+
return null
|
|
901
|
+
}
|
|
902
|
+
const alg: X509DerElement | null = x509DerRead(der, version.end, seq.end)
|
|
903
|
+
if (alg === null || !x509DerIsAlgorithm(der, alg, x509OidEcPublicKey(), x509OidPrime256v1())) {
|
|
904
|
+
return null
|
|
905
|
+
}
|
|
906
|
+
const key: X509DerElement | null = x509DerExpect(der, alg.end, seq.end, X509_TAG_OCTET_STRING)
|
|
907
|
+
if (key === null) {
|
|
908
|
+
return null
|
|
909
|
+
}
|
|
910
|
+
let at: i32 = key.end
|
|
911
|
+
if (x509DerPeek(der, at, seq.end) === 0xa0) {
|
|
912
|
+
const attributes: X509DerElement | null = x509DerRead(der, at, seq.end)
|
|
913
|
+
if (attributes === null) {
|
|
914
|
+
return null
|
|
915
|
+
}
|
|
916
|
+
at = attributes.end
|
|
917
|
+
}
|
|
918
|
+
if (v === 1 && x509DerPeek(der, at, seq.end) === 0x81) {
|
|
919
|
+
const pub: X509DerElement | null = x509DerRead(der, at, seq.end)
|
|
920
|
+
if (pub === null) {
|
|
921
|
+
return null
|
|
922
|
+
}
|
|
923
|
+
at = pub.end
|
|
924
|
+
}
|
|
925
|
+
if (at !== seq.end) {
|
|
926
|
+
return null
|
|
927
|
+
}
|
|
928
|
+
return x509Sec1Key(der, key.start, key.end, false)
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* The 32-byte P-256 private key in `pem`: one `EC PRIVATE KEY` block (SEC1,
|
|
933
|
+
* RFC 5915, which must name its curve) or one `PRIVATE KEY` block (unencrypted
|
|
934
|
+
* PKCS#8, RFC 5208 or RFC 5958), and not both. `null` for no such block or
|
|
935
|
+
* more than one, for any curve but P-256, for a scalar of 0 or n or more, for
|
|
936
|
+
* a stored public key that is not the key's own, and for anything malformed.
|
|
937
|
+
* An `ENCRYPTED PRIVATE KEY` is a different label, and answers `null`.
|
|
938
|
+
*/
|
|
939
|
+
export const x509ParseP256PrivateKey = (pem: string): u8[] | null => {
|
|
940
|
+
const sec1: u8[][] | null = pemToDer(pem, "EC PRIVATE KEY")
|
|
941
|
+
const pkcs8: u8[][] | null = pemToDer(pem, "PRIVATE KEY")
|
|
942
|
+
if (sec1 !== null && pkcs8 === null && toI32(sec1.length) === 1) {
|
|
943
|
+
const der: u8[] = sec1[0]
|
|
944
|
+
return x509Sec1Key(der, 0, toI32(der.length), true)
|
|
945
|
+
}
|
|
946
|
+
if (pkcs8 !== null && sec1 === null && toI32(pkcs8.length) === 1) {
|
|
947
|
+
return x509Pkcs8Key(pkcs8[0])
|
|
948
|
+
}
|
|
949
|
+
return null
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// ---------------------------------------------------------------------------
|
|
953
|
+
// Certificates (RFC 5280 §4.1)
|
|
954
|
+
// ---------------------------------------------------------------------------
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* `der` read as one Certificate (RFC 5280 §4.1), or `null` unless it is
|
|
958
|
+
* exactly one in DER: `SEQUENCE { tbsCertificate, signatureAlgorithm,
|
|
959
|
+
* signatureValue }`, where tbsCertificate holds an explicit version of v2 or
|
|
960
|
+
* v3 or none (DER omits v1, the default), a minimal serial, an algorithm equal
|
|
961
|
+
* to the outer one byte for byte (§4.1.1.2), issuer and subject Names, a
|
|
962
|
+
* Validity of two valid times, a SubjectPublicKeyInfo whose key is whole
|
|
963
|
+
* octets, then only the optional `[1]` and `[2]` unique identifiers (v2 and
|
|
964
|
+
* v3) and `[3]` extensions (v3), in that order. Any algorithm and key type is
|
|
965
|
+
* read; `p256` and `ecdsaSha256` say whether they are this module's.
|
|
966
|
+
*/
|
|
967
|
+
export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
968
|
+
const total: i32 = toI32(der.length)
|
|
969
|
+
const cert: X509DerElement | null = x509DerExpect(der, 0, total, X509_TAG_SEQUENCE)
|
|
970
|
+
if (cert === null || cert.end !== total) {
|
|
971
|
+
return null
|
|
972
|
+
}
|
|
973
|
+
const tbs: X509DerElement | null = x509DerExpect(der, cert.start, cert.end, X509_TAG_SEQUENCE)
|
|
974
|
+
if (tbs === null) {
|
|
975
|
+
return null
|
|
976
|
+
}
|
|
977
|
+
const outerAlg: X509DerElement | null = x509DerExpect(der, tbs.end, cert.end, X509_TAG_SEQUENCE)
|
|
978
|
+
if (outerAlg === null) {
|
|
979
|
+
return null
|
|
980
|
+
}
|
|
981
|
+
const sigValue: X509DerElement | null = x509DerExpect(der, outerAlg.end, cert.end, X509_TAG_BIT_STRING)
|
|
982
|
+
if (sigValue === null || sigValue.end !== cert.end) {
|
|
983
|
+
return null
|
|
984
|
+
}
|
|
985
|
+
const signature: u8[] | null = x509DerBitStringOctets(der, sigValue)
|
|
986
|
+
if (signature === null) {
|
|
987
|
+
return null
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
let at: i32 = tbs.start
|
|
991
|
+
let version: i32 = 0
|
|
992
|
+
if (x509DerPeek(der, at, tbs.end) === 0xa0) {
|
|
993
|
+
const explicit: X509DerElement | null = x509DerRead(der, at, tbs.end)
|
|
994
|
+
if (explicit === null) {
|
|
995
|
+
return null
|
|
996
|
+
}
|
|
997
|
+
const inner: X509DerElement | null = x509DerRead(der, explicit.start, explicit.end)
|
|
998
|
+
if (inner === null || inner.end !== explicit.end) {
|
|
999
|
+
return null
|
|
1000
|
+
}
|
|
1001
|
+
version = x509DerSmallInteger(der, inner)
|
|
1002
|
+
if (version !== 1 && version !== 2) {
|
|
1003
|
+
return null
|
|
1004
|
+
}
|
|
1005
|
+
at = explicit.end
|
|
1006
|
+
}
|
|
1007
|
+
const serial: X509DerElement | null = x509DerExpect(der, at, tbs.end, X509_TAG_INTEGER)
|
|
1008
|
+
if (serial === null || !x509DerIntegerMinimal(der, serial)) {
|
|
1009
|
+
return null
|
|
1010
|
+
}
|
|
1011
|
+
const alg: X509DerElement | null = x509DerExpect(der, serial.end, tbs.end, X509_TAG_SEQUENCE)
|
|
1012
|
+
if (alg === null || !x509BytesEqual(x509DerWhole(der, alg), x509DerWhole(der, outerAlg))) {
|
|
1013
|
+
return null
|
|
1014
|
+
}
|
|
1015
|
+
const issuer: X509DerElement | null = x509DerExpect(der, alg.end, tbs.end, X509_TAG_SEQUENCE)
|
|
1016
|
+
if (issuer === null) {
|
|
1017
|
+
return null
|
|
1018
|
+
}
|
|
1019
|
+
const validity: X509DerElement | null = x509DerExpect(der, issuer.end, tbs.end, X509_TAG_SEQUENCE)
|
|
1020
|
+
if (validity === null) {
|
|
1021
|
+
return null
|
|
1022
|
+
}
|
|
1023
|
+
const notBefore: X509DerElement | null = x509DerRead(der, validity.start, validity.end)
|
|
1024
|
+
if (notBefore === null) {
|
|
1025
|
+
return null
|
|
1026
|
+
}
|
|
1027
|
+
const notAfter: X509DerElement | null = x509DerRead(der, notBefore.end, validity.end)
|
|
1028
|
+
if (notAfter === null || notAfter.end !== validity.end) {
|
|
1029
|
+
return null
|
|
1030
|
+
}
|
|
1031
|
+
const subject: X509DerElement | null = x509DerExpect(der, validity.end, tbs.end, X509_TAG_SEQUENCE)
|
|
1032
|
+
if (subject === null) {
|
|
1033
|
+
return null
|
|
1034
|
+
}
|
|
1035
|
+
const spki: X509DerElement | null = x509DerExpect(der, subject.end, tbs.end, X509_TAG_SEQUENCE)
|
|
1036
|
+
if (spki === null) {
|
|
1037
|
+
return null
|
|
1038
|
+
}
|
|
1039
|
+
const keyAlg: X509DerElement | null = x509DerExpect(der, spki.start, spki.end, X509_TAG_SEQUENCE)
|
|
1040
|
+
if (keyAlg === null) {
|
|
1041
|
+
return null
|
|
1042
|
+
}
|
|
1043
|
+
const keyBits: X509DerElement | null = x509DerExpect(der, keyAlg.end, spki.end, X509_TAG_BIT_STRING)
|
|
1044
|
+
if (keyBits === null || keyBits.end !== spki.end) {
|
|
1045
|
+
return null
|
|
1046
|
+
}
|
|
1047
|
+
const publicKey: u8[] | null = x509DerBitStringOctets(der, keyBits)
|
|
1048
|
+
if (publicKey === null) {
|
|
1049
|
+
return null
|
|
1050
|
+
}
|
|
1051
|
+
at = spki.end
|
|
1052
|
+
// [1] issuerUniqueID and [2] subjectUniqueID are IMPLICIT BIT STRINGs (so
|
|
1053
|
+
// primitive, 0x81 and 0x82) from v2; [3] extensions is explicit (0xa3), v3.
|
|
1054
|
+
const trailing: i32[] = [0x81, 0x82, 0xa3]
|
|
1055
|
+
for (let i: i32 = 0; i < 3; i++) {
|
|
1056
|
+
if (x509DerPeek(der, at, tbs.end) === trailing[i]) {
|
|
1057
|
+
const e: X509DerElement | null = x509DerRead(der, at, tbs.end)
|
|
1058
|
+
if (e === null || version < (i === 2 ? 2 : 1)) {
|
|
1059
|
+
return null
|
|
1060
|
+
}
|
|
1061
|
+
at = e.end
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (at !== tbs.end) {
|
|
1065
|
+
return null
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const before: i64 = x509DerTime(der, notBefore)
|
|
1069
|
+
const after: i64 = x509DerTime(der, notAfter)
|
|
1070
|
+
if (before === X509_NO_TIME || after === X509_NO_TIME) {
|
|
1071
|
+
return null
|
|
1072
|
+
}
|
|
1073
|
+
const out = new X509Certificate()
|
|
1074
|
+
out.der = x509DerSlice(der, 0, total)
|
|
1075
|
+
out.tbs = x509DerWhole(der, tbs)
|
|
1076
|
+
out.serial = x509DerSlice(der, serial.start, serial.end)
|
|
1077
|
+
out.issuer = x509DerWhole(der, issuer)
|
|
1078
|
+
out.subject = x509DerWhole(der, subject)
|
|
1079
|
+
out.publicKey = publicKey
|
|
1080
|
+
out.p256 = x509DerIsAlgorithm(der, keyAlg, x509OidEcPublicKey(), x509OidPrime256v1())
|
|
1081
|
+
out.notBefore = before
|
|
1082
|
+
out.notAfter = after
|
|
1083
|
+
out.signature = signature
|
|
1084
|
+
out.ecdsaSha256 = x509DerIsAlgorithm(der, outerAlg, x509OidEcdsaSha256(), [])
|
|
1085
|
+
return out
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Every `CERTIFICATE` block in `pem`, in order — a server's chain, leaf first
|
|
1090
|
+
* as TLS sends it — or `null` when there is none, or when any block is not
|
|
1091
|
+
* PEM `pemToDer` accepts or a certificate `x509ParseCertificate` accepts.
|
|
1092
|
+
*/
|
|
1093
|
+
export const x509ParseChain = (pem: string): X509Certificate[] | null => {
|
|
1094
|
+
const blocks: u8[][] | null = pemToDer(pem, "CERTIFICATE")
|
|
1095
|
+
if (blocks === null) {
|
|
1096
|
+
return null
|
|
1097
|
+
}
|
|
1098
|
+
const out: X509Certificate[] = []
|
|
1099
|
+
for (const der of blocks) {
|
|
1100
|
+
const cert: X509Certificate | null = x509ParseCertificate(der)
|
|
1101
|
+
if (cert === null) {
|
|
1102
|
+
return null
|
|
1103
|
+
}
|
|
1104
|
+
out.push(cert)
|
|
1105
|
+
}
|
|
1106
|
+
return out
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Whether `cert`'s signature is a valid ecdsa-with-SHA256 signature of its TBS
|
|
1111
|
+
* bytes under `issuer`'s P-256 key — a self-signed certificate passes itself
|
|
1112
|
+
* as both. `false` for any other algorithm or key, and for a signature that is
|
|
1113
|
+
* not a DER ECDSA-Sig-Value. Only the signature: names, times and extensions
|
|
1114
|
+
* are the caller's policy.
|
|
1115
|
+
*/
|
|
1116
|
+
export const x509VerifySignature = (cert: X509Certificate, issuer: X509Certificate): boolean => {
|
|
1117
|
+
if (!cert.ecdsaSha256 || !issuer.p256) {
|
|
1118
|
+
return false
|
|
1119
|
+
}
|
|
1120
|
+
const rs: u8[] | null = x509DerSignatureRS(cert.signature)
|
|
1121
|
+
if (rs === null) {
|
|
1122
|
+
return false
|
|
1123
|
+
}
|
|
1124
|
+
return p256VerifySha256(issuer.publicKey, cert.tbs, rs)
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* A self-signed X.509 v3 certificate, in DER, for the P-256 key `priv`: subject
|
|
1129
|
+
* and issuer `CN=commonName`, valid from `notBeforeMs` (milliseconds since
|
|
1130
|
+
* 1970, rounded down to the second) for exactly `days` days, serial number
|
|
1131
|
+
* `serial`, and signed with ecdsa-with-SHA256 — see the module comment for why
|
|
1132
|
+
* it has no extensions. `null` when `days` is below 1 or above
|
|
1133
|
+
* `X509_MAX_DAYS`, `notBeforeMs` is negative or the validity would end after
|
|
1134
|
+
* 9999, `commonName` is empty or longer than 64 bytes, `priv` is not a P-256
|
|
1135
|
+
* private key, or `serial` — big-endian, leading zeros ignored — is zero or
|
|
1136
|
+
* needs more than `X509_MAX_SERIAL` octets as a positive INTEGER (clear the top
|
|
1137
|
+
* bit of 20 random bytes, or use fewer).
|
|
1138
|
+
*/
|
|
1139
|
+
export const x509MintSelfSigned = (
|
|
1140
|
+
priv: u8[],
|
|
1141
|
+
commonName: string,
|
|
1142
|
+
notBeforeMs: i64,
|
|
1143
|
+
days: i32,
|
|
1144
|
+
serial: u8[]
|
|
1145
|
+
): u8[] | null => {
|
|
1146
|
+
if (days < 1 || days > X509_MAX_DAYS || notBeforeMs < 0) {
|
|
1147
|
+
return null
|
|
1148
|
+
}
|
|
1149
|
+
const start: i64 = notBeforeMs / 1000
|
|
1150
|
+
const end: i64 = start + toI64(days) * 86400
|
|
1151
|
+
// 9999-12-31T23:59:59Z, the last second RFC 5280's times can spell.
|
|
1152
|
+
if (end > 253402300799) {
|
|
1153
|
+
return null
|
|
1154
|
+
}
|
|
1155
|
+
const name: u8[] = x509Bytes(commonName)
|
|
1156
|
+
const nameLength: i32 = toI32(name.length)
|
|
1157
|
+
if (nameLength < 1 || nameLength > X509_MAX_COMMON_NAME) {
|
|
1158
|
+
return null
|
|
1159
|
+
}
|
|
1160
|
+
const serialDer: u8[] = x509DerUnsignedInteger(serial)
|
|
1161
|
+
// Tag, length, and at most twenty content octets, of which at least one is not zero.
|
|
1162
|
+
let nonzero: i32 = 0
|
|
1163
|
+
for (let k: i32 = 0; k < toI32(serial.length); k++) {
|
|
1164
|
+
nonzero = nonzero | toI32(serial[k])
|
|
1165
|
+
}
|
|
1166
|
+
if (nonzero === 0 || toI32(serialDer.length) - 2 > X509_MAX_SERIAL) {
|
|
1167
|
+
return null
|
|
1168
|
+
}
|
|
1169
|
+
const pub: u8[] | null = p256PublicKey(priv)
|
|
1170
|
+
if (pub === null) {
|
|
1171
|
+
return null
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const algorithm: u8[] = x509DerWrap(X509_TAG_SEQUENCE, [x509DerTlv(X509_TAG_OID, x509OidEcdsaSha256())])
|
|
1175
|
+
const rdn: u8[] = x509DerWrap(X509_TAG_SET, [
|
|
1176
|
+
x509DerWrap(X509_TAG_SEQUENCE, [
|
|
1177
|
+
x509DerTlv(X509_TAG_OID, x509OidCommonName()),
|
|
1178
|
+
x509DerTlv(X509_TAG_UTF8_STRING, name),
|
|
1179
|
+
]),
|
|
1180
|
+
])
|
|
1181
|
+
const distinguished: u8[] = x509DerWrap(X509_TAG_SEQUENCE, [rdn])
|
|
1182
|
+
const spki: u8[] = x509DerWrap(X509_TAG_SEQUENCE, [
|
|
1183
|
+
x509DerWrap(X509_TAG_SEQUENCE, [
|
|
1184
|
+
x509DerTlv(X509_TAG_OID, x509OidEcPublicKey()),
|
|
1185
|
+
x509DerTlv(X509_TAG_OID, x509OidPrime256v1()),
|
|
1186
|
+
]),
|
|
1187
|
+
x509DerBitString(pub),
|
|
1188
|
+
])
|
|
1189
|
+
const tbs: u8[] = x509DerWrap(X509_TAG_SEQUENCE, [
|
|
1190
|
+
// [0] EXPLICIT Version: v3 is the INTEGER 2.
|
|
1191
|
+
x509DerWrap(0xa0, [x509DerTlv(X509_TAG_INTEGER, [2])]),
|
|
1192
|
+
serialDer,
|
|
1193
|
+
algorithm,
|
|
1194
|
+
distinguished,
|
|
1195
|
+
x509DerWrap(X509_TAG_SEQUENCE, [x509DerTimeAt(start), x509DerTimeAt(end)]),
|
|
1196
|
+
distinguished,
|
|
1197
|
+
spki,
|
|
1198
|
+
])
|
|
1199
|
+
const rs: u8[] | null = p256SignSha256(priv, tbs)
|
|
1200
|
+
if (rs === null) {
|
|
1201
|
+
return null
|
|
1202
|
+
}
|
|
1203
|
+
const r: u8[] = x509DerSlice(rs, 0, 32)
|
|
1204
|
+
const s: u8[] = x509DerSlice(rs, 32, 64)
|
|
1205
|
+
const sig: u8[] = x509DerWrap(X509_TAG_SEQUENCE, [x509DerUnsignedInteger(r), x509DerUnsignedInteger(s)])
|
|
1206
|
+
return x509DerWrap(X509_TAG_SEQUENCE, [tbs, algorithm, x509DerBitString(sig)])
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* The SHA-256 of a certificate's DER: the value WebTransport's
|
|
1211
|
+
* `serverCertificateHashes` pins, and what `openssl x509 -outform DER |
|
|
1212
|
+
* sha256sum` prints.
|
|
1213
|
+
*/
|
|
1214
|
+
export const x509CertificateHash = (der: u8[]): u8[] => sha256(der)
|