@amritk/nish-x86_64-linux 0.20.0 → 0.21.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/INSTALL.md +22 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +2 -1
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +44 -0
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +261 -53
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +31 -11
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +83 -1
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +85 -8
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +1875 -0
- package/std/net/quic-listener.ts +20 -4
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/crypto/hmac.ts
CHANGED
|
@@ -46,12 +46,17 @@
|
|
|
46
46
|
* stores nothing of its own and an arena scope around it releases all it
|
|
47
47
|
* allocated (see "Caller-owned scratch" below).
|
|
48
48
|
*
|
|
49
|
-
*
|
|
49
|
+
* `pbkdf2HmacSha256` is PBKDF2 with HMAC-SHA-256 (RFC 8018 §5.2), the key
|
|
50
|
+
* derivation SCRAM-SHA-256 runs over a password: the password goes in as a
|
|
51
|
+
* `Secret<u8[]>` and the derived key comes out as one, and every U_j and T_i
|
|
52
|
+
* is wiped before it answers (see "PBKDF2" below).
|
|
53
|
+
*
|
|
54
|
+
* Written from RFC 2104 and RFC 8018, not ported from another implementation.
|
|
50
55
|
*/
|
|
51
56
|
import { SHA256_BLOCK, SHA256_SIZE, Sha256 } from "nish/crypto/sha256"
|
|
52
57
|
import { SHA384_SIZE, SHA512_BLOCK, SHA512_SIZE, Sha384, Sha512 } from "nish/crypto/sha512"
|
|
53
58
|
import { timingSafeEqual } from "nish/crypto/ct"
|
|
54
|
-
import { Secret, exposeWith, wipe } from "nish:secret"
|
|
59
|
+
import { Secret, exposeWith, secret, wipe } from "nish:secret"
|
|
55
60
|
|
|
56
61
|
/** RFC 2104 §2's `ipad`, the byte XORed into the key for the inner hash. */
|
|
57
62
|
const HMAC_IPAD: i32 = 0x36
|
|
@@ -661,3 +666,134 @@ export class HmacSha384Scratch {
|
|
|
661
666
|
hmacWipeSha384(this.outer)
|
|
662
667
|
}
|
|
663
668
|
}
|
|
669
|
+
|
|
670
|
+
// ---- PBKDF2 ----------------------------------------------------------------
|
|
671
|
+
//
|
|
672
|
+
// PBKDF2 with HMAC-SHA-256 as its PRF (RFC 8018 §5.2), the key derivation
|
|
673
|
+
// SCRAM-SHA-256 (RFC 7677, RFC 5802 §3 "Hi") runs over a password. The
|
|
674
|
+
// password keys an inner and an outer hasher once; every U_j after that is
|
|
675
|
+
// two compressions in a working hasher copied from them, so a count of 4096
|
|
676
|
+
// costs 4096 HMACs and never keys one again.
|
|
677
|
+
|
|
678
|
+
/** What `pbkdf2HmacSha256` passes through `exposeWith`, which carries one argument. */
|
|
679
|
+
interface Pbkdf2Request {
|
|
680
|
+
/** The salt, public: SCRAM's server sends it. */
|
|
681
|
+
salt: u8[]
|
|
682
|
+
/** The iteration count `c`, at least 1. */
|
|
683
|
+
iterations: i32
|
|
684
|
+
/** The derived key's length `dkLen` in bytes, at least 1. */
|
|
685
|
+
length: i32
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** The length of INT(i), the big-endian block index after the salt; typed, as `HMAC_FROM` is. */
|
|
689
|
+
const PBKDF2_INDEX: i32 = 4
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Ends the HMAC `work` holds, keyed as `keyedOuter` is on the outer side, and
|
|
693
|
+
* writes its 32-byte tag into `out`. Both digests `work` answers are wiped
|
|
694
|
+
* here, so they die with this call and its arena scope.
|
|
695
|
+
*/
|
|
696
|
+
const pbkdf2Finish = (keyedOuter: Sha256, work: Sha256, out: u8[]): void => {
|
|
697
|
+
const innerDigest: u8[] = work.digest()
|
|
698
|
+
hmacCopySha256(keyedOuter, work)
|
|
699
|
+
work.update(innerDigest, HMAC_FROM, SHA256_SIZE)
|
|
700
|
+
wipe(innerDigest)
|
|
701
|
+
const tag: u8[] = work.digest()
|
|
702
|
+
for (let i: i32 = 0; i < SHA256_SIZE && i < toI32(tag.length) && i < toI32(out.length); i += 1) {
|
|
703
|
+
out[i] = tag[i]
|
|
704
|
+
}
|
|
705
|
+
wipe(tag)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** `pbkdf2HmacSha256` on the exposed password. */
|
|
709
|
+
const pbkdf2HmacSha256Exposed = (password: u8[], request: Pbkdf2Request): u8[] | null => {
|
|
710
|
+
const salt: u8[] = request.salt
|
|
711
|
+
const iterations: i32 = request.iterations
|
|
712
|
+
const length: i32 = request.length
|
|
713
|
+
// Before any `toI32`, which would saturate under `--number-mode f64`.
|
|
714
|
+
if (password.length > 2147483647 || salt.length > 2147483647) {
|
|
715
|
+
return null
|
|
716
|
+
}
|
|
717
|
+
if (iterations < 1 || length < 1) {
|
|
718
|
+
return null
|
|
719
|
+
}
|
|
720
|
+
const keyedInner = new Sha256()
|
|
721
|
+
const keyedOuter = new Sha256()
|
|
722
|
+
hmacKeySha256(keyedInner, keyedOuter, password)
|
|
723
|
+
const work = new Sha256()
|
|
724
|
+
const u: u8[] = new Array<u8>(SHA256_SIZE)
|
|
725
|
+
const t: u8[] = new Array<u8>(SHA256_SIZE)
|
|
726
|
+
const index: u8[] = new Array<u8>(PBKDF2_INDEX)
|
|
727
|
+
const dk: u8[] = new Array<u8>(length)
|
|
728
|
+
// dkLen is an i32, so it never reaches RFC 8018's (2^32 - 1) * hLen bound,
|
|
729
|
+
// and the block index below never passes 2^26.
|
|
730
|
+
let block: i32 = 1
|
|
731
|
+
let done: i32 = 0
|
|
732
|
+
while (done < length) {
|
|
733
|
+
// U_1 = PRF(P, S || INT(i)), INT(i) four bytes, most significant first.
|
|
734
|
+
index[0] = toU8((block >> 24) & 0xff)
|
|
735
|
+
index[1] = toU8((block >> 16) & 0xff)
|
|
736
|
+
index[2] = toU8((block >> 8) & 0xff)
|
|
737
|
+
index[3] = toU8(block & 0xff)
|
|
738
|
+
hmacCopySha256(keyedInner, work)
|
|
739
|
+
work.update(salt, HMAC_FROM, toI32(salt.length))
|
|
740
|
+
work.update(index, HMAC_FROM, PBKDF2_INDEX)
|
|
741
|
+
pbkdf2Finish(keyedOuter, work, u)
|
|
742
|
+
for (let j: i32 = 0; j < SHA256_SIZE && j < toI32(t.length) && j < toI32(u.length); j += 1) {
|
|
743
|
+
t[j] = u[j]
|
|
744
|
+
}
|
|
745
|
+
// U_c = PRF(P, U_{c-1}); T_i = U_1 XOR ... XOR U_c.
|
|
746
|
+
for (let c: i32 = 1; c < iterations; c += 1) {
|
|
747
|
+
hmacCopySha256(keyedInner, work)
|
|
748
|
+
work.update(u, HMAC_FROM, SHA256_SIZE)
|
|
749
|
+
pbkdf2Finish(keyedOuter, work, u)
|
|
750
|
+
for (let j: i32 = 0; j < SHA256_SIZE && j < toI32(t.length) && j < toI32(u.length); j += 1) {
|
|
751
|
+
t[j] = t[j] ^ u[j]
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
const take: i32 = length - done < SHA256_SIZE ? length - done : SHA256_SIZE
|
|
755
|
+
for (let j: i32 = 0; j < take && j < toI32(t.length) && done + j < toI32(dk.length); j += 1) {
|
|
756
|
+
dk[done + j] = t[j]
|
|
757
|
+
}
|
|
758
|
+
done += take
|
|
759
|
+
block += 1
|
|
760
|
+
}
|
|
761
|
+
wipe(u)
|
|
762
|
+
wipe(t)
|
|
763
|
+
hmacWipeSha256(work)
|
|
764
|
+
hmacWipeSha256(keyedInner)
|
|
765
|
+
hmacWipeSha256(keyedOuter)
|
|
766
|
+
return dk
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* PBKDF2-HMAC-SHA256(P, S, c, dkLen) as RFC 8018 §5.2 defines it: the
|
|
771
|
+
* `dkLen`-byte key derived from the password `password` and the salt `salt`
|
|
772
|
+
* over `iterations` rounds, as a `Secret`.
|
|
773
|
+
*
|
|
774
|
+
* const salted: Secret<u8[]> | null = pbkdf2HmacSha256(password, salt, 4096, 32);
|
|
775
|
+
*
|
|
776
|
+
* Bad input answers `null` and never panics: an iteration count or a length
|
|
777
|
+
* below 1, or a password or salt longer than 2^31 - 1 bytes. The password is
|
|
778
|
+
* read only inside `exposeWith`, and every U_j, every T_i, both keyed hashers
|
|
779
|
+
* and the working one are wiped before the key is answered; the digests on
|
|
780
|
+
* the way are wiped in the call that made them. The time taken depends on
|
|
781
|
+
* `iterations`, `dkLen` and the password's length past a block, all of which
|
|
782
|
+
* RFC 8018 treats as public, and on nothing of the password's bytes. The time
|
|
783
|
+
* is linear in `iterations` and nothing here bounds it, so a caller that takes
|
|
784
|
+
* the count from an untrusted peer (a SCRAM server's `i=`) must cap it first:
|
|
785
|
+
* 2^31 - 1 rounds is about 2^32 compressions.
|
|
786
|
+
*/
|
|
787
|
+
export const pbkdf2HmacSha256 = (
|
|
788
|
+
password: Secret<u8[]>,
|
|
789
|
+
salt: u8[],
|
|
790
|
+
iterations: i32,
|
|
791
|
+
length: i32
|
|
792
|
+
): Secret<u8[]> | null => {
|
|
793
|
+
const request: Pbkdf2Request = { salt: salt, iterations: iterations, length: length }
|
|
794
|
+
const dk: u8[] | null = exposeWith(password, request, pbkdf2HmacSha256Exposed)
|
|
795
|
+
if (dk === null) {
|
|
796
|
+
return null
|
|
797
|
+
}
|
|
798
|
+
return secret(dk)
|
|
799
|
+
}
|
package/std/crypto/x509.ts
CHANGED
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
*
|
|
73
73
|
* **Constant time.** A private key is the one secret that passes through here:
|
|
74
74
|
* it is base64-decoded from PEM, lifted out of its DER, and handed to
|
|
75
|
-
* `nish/crypto/p256`. Base64
|
|
76
|
-
* arithmetic on range masks
|
|
77
|
-
* character into one word read after the whole text, so neither a branch
|
|
78
|
-
* table index depends on the key's bytes. PEM's framing is found by comparing
|
|
75
|
+
* `nish/crypto/p256`. Base64 is `nish/crypto/base64url`'s `base64StdPadDecode`,
|
|
76
|
+
* which maps a character to its sextet by arithmetic on range masks and folds a
|
|
77
|
+
* bad character into one word read after the whole text, so neither a branch
|
|
78
|
+
* nor a table index depends on the key's bytes. PEM's framing is found by comparing
|
|
79
79
|
* the start of each line with the fixed `-----BEGIN ` and `-----END `, which no
|
|
80
80
|
* base64 character matches, so those comparisons end on a line's first byte
|
|
81
81
|
* whatever the key is. The DER reader branches on tags and
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
* (`docs/wp26-stdlib.md` §3e).
|
|
97
97
|
*/
|
|
98
98
|
import { Secret, secret, wipe } from "nish:secret"
|
|
99
|
+
import { base64StdPadDecode, base64StdPadEncode } from "nish/crypto/base64url"
|
|
99
100
|
import { p256PublicKey, p256SignSha256, p256VerifySha256 } from "nish/crypto/p256"
|
|
100
101
|
import { sha256 } from "nish/crypto/sha256"
|
|
101
102
|
|
|
@@ -704,124 +705,9 @@ const x509Bytes = (text: string): u8[] => {
|
|
|
704
705
|
}
|
|
705
706
|
|
|
706
707
|
// ---------------------------------------------------------------------------
|
|
707
|
-
// PEM (RFC 7468)
|
|
708
|
+
// PEM (RFC 7468), over `nish/crypto/base64url`'s padded base64
|
|
708
709
|
// ---------------------------------------------------------------------------
|
|
709
710
|
|
|
710
|
-
/**
|
|
711
|
-
* All ones when `lo <= c <= hi`, else zero, without a branch; `c` is a byte or
|
|
712
|
-
* a sextet. The subtractions are `u32`'s, which wrap by definition, rather
|
|
713
|
-
* than checked `i32` ones, whose check would be a branch on a byte of what may
|
|
714
|
-
* be a private key's PEM; the sign bit `>> 31` reads is the same.
|
|
715
|
-
* `x509Base64CharOf` and `x509Base64SextetOf` sum in `u32` for the same reason.
|
|
716
|
-
*/
|
|
717
|
-
const x509RangeMask = (c: i32, lo: i32, hi: i32): i32 =>
|
|
718
|
-
toI32((toU32(lo) - 1 - toU32(c)) & (toU32(c) - toU32(hi) - 1)) >> 31
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* The base64 character for the sextet `v`: `'A' + v`, stepped by the gap to
|
|
722
|
-
* each later range it has reached — 26 lands on `a`, 52 on `0`, 62 on `+` and
|
|
723
|
-
* 63 on `/`.
|
|
724
|
-
*/
|
|
725
|
-
const x509Base64CharOf = (v: i32): i32 =>
|
|
726
|
-
toI32(
|
|
727
|
-
65 +
|
|
728
|
-
toU32(v) +
|
|
729
|
-
toU32(x509RangeMask(v, 26, 63) & 6) -
|
|
730
|
-
toU32(x509RangeMask(v, 52, 63) & 75) -
|
|
731
|
-
toU32(x509RangeMask(v, 62, 63) & 15) +
|
|
732
|
-
toU32(x509RangeMask(v, 63, 63) & 3)
|
|
733
|
-
)
|
|
734
|
-
|
|
735
|
-
/** The sextet the byte `c` stands for in base64, or `-1` when it is not in the alphabet. */
|
|
736
|
-
const x509Base64SextetOf = (c: i32): i32 => {
|
|
737
|
-
const upper: i32 = x509RangeMask(c, 65, 90)
|
|
738
|
-
const lower: i32 = x509RangeMask(c, 97, 122)
|
|
739
|
-
const digit: i32 = x509RangeMask(c, 48, 57)
|
|
740
|
-
const plus: i32 = x509RangeMask(c, 43, 43)
|
|
741
|
-
const slash: i32 = x509RangeMask(c, 47, 47)
|
|
742
|
-
const value: i32 =
|
|
743
|
-
(upper & toI32(toU32(c) - 65)) |
|
|
744
|
-
(lower & toI32(toU32(c) - 71)) |
|
|
745
|
-
(digit & toI32(toU32(c) + 4)) |
|
|
746
|
-
(plus & 62) |
|
|
747
|
-
(slash & 63)
|
|
748
|
-
return value | ~(upper | lower | digit | plus | slash)
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
/** `data` in padded base64, as one line. */
|
|
752
|
-
const x509Base64Encode = (data: u8[]): string => {
|
|
753
|
-
const parts: string[] = []
|
|
754
|
-
let acc: i32 = 0
|
|
755
|
-
let bits: i32 = 0
|
|
756
|
-
for (let k: i32 = 0; k < toI32(data.length); k++) {
|
|
757
|
-
acc = ((acc << 8) | toI32(data[k])) & 0xfff
|
|
758
|
-
bits = bits + 8
|
|
759
|
-
while (bits >= 6) {
|
|
760
|
-
bits = bits - 6
|
|
761
|
-
parts.push(String.fromCharCode(x509Base64CharOf((acc >> bits) & 63)))
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
if (bits > 0) {
|
|
765
|
-
parts.push(String.fromCharCode(x509Base64CharOf((acc << (6 - bits)) & 63)))
|
|
766
|
-
}
|
|
767
|
-
// One `=` for a final group of two bytes, two for one byte.
|
|
768
|
-
const tail: i32 = toI32(data.length) % 3
|
|
769
|
-
if (tail > 0) {
|
|
770
|
-
parts.push(tail === 1 ? "==" : "=")
|
|
771
|
-
}
|
|
772
|
-
return parts.join("")
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* The bytes of padded base64 `text`, or `null` unless it is the one canonical
|
|
777
|
-
* spelling of them: a whole number of four-character groups, `=` only as the
|
|
778
|
-
* last one or two characters, every other character in the alphabet, and the
|
|
779
|
-
* bits the padding leaves over all zero (RFC 4648 §3.5). Where the padding is
|
|
780
|
-
* depends on the length alone; the characters themselves are read by masks and
|
|
781
|
-
* judged once, after the last.
|
|
782
|
-
*/
|
|
783
|
-
const x509Base64Decode = (text: string): u8[] | null => {
|
|
784
|
-
const n: i32 = toI32(text.length)
|
|
785
|
-
if (n === 0 || (n & 3) !== 0) {
|
|
786
|
-
return null
|
|
787
|
-
}
|
|
788
|
-
let pad: i32 = 0
|
|
789
|
-
if (toI32(text.charCodeAt(n - 1)) === 61) {
|
|
790
|
-
pad = toI32(text.charCodeAt(n - 2)) === 61 ? 2 : 1
|
|
791
|
-
}
|
|
792
|
-
const chars: i32 = n - pad
|
|
793
|
-
const out: u8[] = new Array<u8>((n >> 2) * 3 - pad)
|
|
794
|
-
const outLen: i32 = toI32(out.length)
|
|
795
|
-
let bad: i32 = 0
|
|
796
|
-
let acc: i32 = 0
|
|
797
|
-
let bits: i32 = 0
|
|
798
|
-
let j: i32 = 0
|
|
799
|
-
for (let k: i32 = 0; k < toI32(text.length); k++) {
|
|
800
|
-
// The padding is past `chars`, and is not data; which positions those are
|
|
801
|
-
// is a fact about the length.
|
|
802
|
-
if (k < chars) {
|
|
803
|
-
const v: i32 = x509Base64SextetOf(toI32(text.charCodeAt(k)))
|
|
804
|
-
bad = bad | (v >> 31)
|
|
805
|
-
acc = ((acc << 6) | (v & 63)) & 0xfff
|
|
806
|
-
bits = bits + 6
|
|
807
|
-
if (bits >= 8) {
|
|
808
|
-
bits = bits - 8
|
|
809
|
-
// Always true — `out` was sized from the same length — and written so
|
|
810
|
-
// the store keeps no bounds check.
|
|
811
|
-
if (j >= 0 && j < outLen) {
|
|
812
|
-
out[j] = toU8((acc >> bits) & 0xff)
|
|
813
|
-
}
|
|
814
|
-
j = j + 1
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
bad = bad | (acc & ((1 << bits) - 1))
|
|
819
|
-
if (bad !== 0) {
|
|
820
|
-
return null
|
|
821
|
-
}
|
|
822
|
-
return out
|
|
823
|
-
}
|
|
824
|
-
|
|
825
711
|
/** Whether `text` starts with `prefix`. */
|
|
826
712
|
const x509StartsWith = (text: string, prefix: string): boolean => {
|
|
827
713
|
const n: i32 = toI32(prefix.length)
|
|
@@ -841,7 +727,7 @@ const x509EndsWith = (text: string, suffix: string): boolean => {
|
|
|
841
727
|
* ending in a line feed.
|
|
842
728
|
*/
|
|
843
729
|
export const derToPem = (der: u8[], label: string): string => {
|
|
844
|
-
const body: string =
|
|
730
|
+
const body: string = base64StdPadEncode(der)
|
|
845
731
|
const n: i32 = toI32(body.length)
|
|
846
732
|
const lines: string[] = [`-----BEGIN ${label}-----`]
|
|
847
733
|
for (let at: i32 = 0; at < n; at = at + 64) {
|
|
@@ -894,7 +780,10 @@ export const pemToDer = (pem: string, label: string): u8[][] | null => {
|
|
|
894
780
|
}
|
|
895
781
|
// Every block is decoded, whatever its label: a malformed block beside the
|
|
896
782
|
// one asked for is refused, not skipped (docs/security/crypto-x509.md, X509-1).
|
|
897
|
-
|
|
783
|
+
// An empty body is refused: a block with no DER is malformed, though
|
|
784
|
+
// the empty text is base64 for no bytes.
|
|
785
|
+
const text: string = body.join("")
|
|
786
|
+
const der: u8[] | null = text.length === 0 ? null : base64StdPadDecode(text)
|
|
898
787
|
if (der === null) {
|
|
899
788
|
return null
|
|
900
789
|
}
|