@int-loop/core 0.1.0 → 0.3.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/dist/anchor.d.ts +70 -0
- package/dist/anchor.js +201 -0
- package/dist/comprehension.d.ts +12 -0
- package/dist/comprehension.js +81 -0
- package/dist/controls.d.ts +9 -0
- package/dist/controls.js +19 -0
- package/dist/der.d.ts +53 -0
- package/dist/der.js +246 -0
- package/dist/evidence-pack.d.ts +11 -2
- package/dist/evidence-pack.js +24 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/report.d.ts +92 -0
- package/dist/report.js +443 -0
- package/dist/schema/ledger.d.ts +3 -1
- package/dist/schema/ledger.js +2 -0
- package/dist/schema/policy.d.ts +3 -0
- package/dist/schema/policy.js +4 -0
- package/dist/schema/request.d.ts +1 -0
- package/dist/schema/request.js +2 -0
- package/dist/tsa-roots.d.ts +9 -0
- package/dist/tsa-roots.js +150 -0
- package/dist/tsp.d.ts +37 -0
- package/dist/tsp.js +228 -0
- package/dist/x509.d.ts +93 -0
- package/dist/x509.js +329 -0
- package/package.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Timestamp authority roots that int-loop and int-loop-verify trust by default. An auditor
|
|
2
|
+
// can replace this list with their own (int-loop-verify --tsa-roots roots.pem).
|
|
3
|
+
//
|
|
4
|
+
// Provenance, checked 2026-11-09 (SHA-256 fingerprints):
|
|
5
|
+
// FreeTSA Root CA a6379e7cecc05faa3cbf076013d745e327bbbaa38c0b9af22469d4701d18aabc
|
|
6
|
+
// from https://freetsa.org/files/cacert.pem, identical to the root FreeTSA sends
|
|
7
|
+
// DigiCert Trusted Root G4 552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988
|
|
8
|
+
// from the macOS system root store
|
|
9
|
+
// Sectigo Public Time Stamping Root R46
|
|
10
|
+
// 4941b001b8a97e961b7817c9d9e960ec4b056bfc915a8c1aabf6ef6b3ac046a5
|
|
11
|
+
// from the macOS system root store
|
|
12
|
+
//
|
|
13
|
+
// tests/core/tsp.test.ts checks each fingerprint, so a change here cannot go unnoticed.
|
|
14
|
+
import { fromPem } from "./der.js";
|
|
15
|
+
export const KNOWN_TSA_ROOTS = [
|
|
16
|
+
{
|
|
17
|
+
id: "freetsa",
|
|
18
|
+
name: "FreeTSA Root CA",
|
|
19
|
+
sha256: "a6379e7cecc05faa3cbf076013d745e327bbbaa38c0b9af22469d4701d18aabc",
|
|
20
|
+
pem: `
|
|
21
|
+
-----BEGIN CERTIFICATE-----
|
|
22
|
+
MIIH/zCCBeegAwIBAgIJAMHphhYNqOmAMA0GCSqGSIb3DQEBDQUAMIGVMREwDwYD
|
|
23
|
+
VQQKEwhGcmVlIFRTQTEQMA4GA1UECxMHUm9vdCBDQTEYMBYGA1UEAxMPd3d3LmZy
|
|
24
|
+
ZWV0c2Eub3JnMSIwIAYJKoZIhvcNAQkBFhNidXNpbGV6YXNAZ21haWwuY29tMRIw
|
|
25
|
+
EAYDVQQHEwlXdWVyemJ1cmcxDzANBgNVBAgTBkJheWVybjELMAkGA1UEBhMCREUw
|
|
26
|
+
HhcNMTYwMzEzMDE1MjEzWhcNNDEwMzA3MDE1MjEzWjCBlTERMA8GA1UEChMIRnJl
|
|
27
|
+
ZSBUU0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9y
|
|
28
|
+
ZzEiMCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJ
|
|
29
|
+
V3VlcnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFMIICIjANBgkq
|
|
30
|
+
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtgKODjAy8REQ2WTNqUudAnjhlCrpE6ql
|
|
31
|
+
mQfNppeTmVvZrH4zutn+NwTaHAGpjSGv4/WRpZ1wZ3BRZ5mPUBZyLgq0YrIfQ5Fx
|
|
32
|
+
0s/MRZPzc1r3lKWrMR9sAQx4mN4z11xFEO529L0dFJjPF9MD8Gpd2feWzGyptlel
|
|
33
|
+
b+PqT+++fOa2oY0+NaMM7l/xcNHPOaMz0/2olk0i22hbKeVhvokPCqhFhzsuhKsm
|
|
34
|
+
q4Of/o+t6dI7sx5h0nPMm4gGSRhfq+z6BTRgCrqQG2FOLoVFgt6iIm/BnNffUr7V
|
|
35
|
+
DYd3zZmIwFOj/H3DKHoGik/xK3E82YA2ZulVOFRW/zj4ApjPa5OFbpIkd0pmzxzd
|
|
36
|
+
EcL479hSA9dFiyVmSxPtY5ze1P+BE9bMU1PScpRzw8MHFXxyKqW13Qv7LWw4sbk3
|
|
37
|
+
SciB7GACbQiVGzgkvXG6y85HOuvWNvC5GLSiyP9GlPB0V68tbxz4JVTRdw/Xn/XT
|
|
38
|
+
FNzRBM3cq8lBOAVt/PAX5+uFcv1S9wFE8YjaBfWCP1jdBil+c4e+0tdywT2oJmYB
|
|
39
|
+
BF/kEt1wmGwMmHunNEuQNzh1FtJY54hbUfiWi38mASE7xMtMhfj/C4SvapiDN837
|
|
40
|
+
gYaPfs8x3KZxbX7C3YAsFnJinlwAUss1fdKar8Q/YVs7H/nU4c4Ixxxz4f67fcVq
|
|
41
|
+
M2ITKentbCMCAwEAAaOCAk4wggJKMAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQD
|
|
42
|
+
AgHGMB0GA1UdDgQWBBT6VQ2MNGZRQ0z357OnbJWveuaklzCBygYDVR0jBIHCMIG/
|
|
43
|
+
gBT6VQ2MNGZRQ0z357OnbJWveuakl6GBm6SBmDCBlTERMA8GA1UEChMIRnJlZSBU
|
|
44
|
+
U0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9yZzEi
|
|
45
|
+
MCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJV3Vl
|
|
46
|
+
cnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFggkAwemGFg2o6YAw
|
|
47
|
+
MwYDVR0fBCwwKjAooCagJIYiaHR0cDovL3d3dy5mcmVldHNhLm9yZy9yb290X2Nh
|
|
48
|
+
LmNybDCBzwYDVR0gBIHHMIHEMIHBBgorBgEEAYHyJAEBMIGyMDMGCCsGAQUFBwIB
|
|
49
|
+
FidodHRwOi8vd3d3LmZyZWV0c2Eub3JnL2ZyZWV0c2FfY3BzLmh0bWwwMgYIKwYB
|
|
50
|
+
BQUHAgEWJmh0dHA6Ly93d3cuZnJlZXRzYS5vcmcvZnJlZXRzYV9jcHMucGRmMEcG
|
|
51
|
+
CCsGAQUFBwICMDsaOUZyZWVUU0EgdHJ1c3RlZCB0aW1lc3RhbXBpbmcgU29mdHdh
|
|
52
|
+
cmUgYXMgYSBTZXJ2aWNlIChTYWFTKTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUH
|
|
53
|
+
MAGGG2h0dHA6Ly93d3cuZnJlZXRzYS5vcmc6MjU2MDANBgkqhkiG9w0BAQ0FAAOC
|
|
54
|
+
AgEAaK9+v5OFYu9M6ztYC+L69sw1omdyli89lZAfpWMMh9CRmJhM6KBqM/ipwoLt
|
|
55
|
+
nxyxGsbCPhcQjuTvzm+ylN6VwTMmIlVyVSLKYZcdSjt/eCUN+41K7sD7GVmxZBAF
|
|
56
|
+
ILnBDmTGJmLkrU0KuuIpj8lI/E6Z6NnmuP2+RAQSHsfBQi6sssnXMo4HOW5gtPO7
|
|
57
|
+
gDrUpVXID++1P4XndkoKn7Svw5n0zS9fv1hxBcYIHPPQUze2u30bAQt0n0iIyRLz
|
|
58
|
+
aWuhtpAtd7ffwEbASgzB7E+NGF4tpV37e8KiA2xiGSRqT5ndu28fgpOY87gD3ArZ
|
|
59
|
+
DctZvvTCfHdAS5kEO3gnGGeZEVLDmfEsv8TGJa3AljVa5E40IQDsUXpQLi8G+UC4
|
|
60
|
+
1DWZu8EVT4rnYaCw1VX7ShOR1PNCCvjb8S8tfdudd9zhU3gEB0rxdeTy1tVbNLXW
|
|
61
|
+
99y90xcwr1ZIDUwM/xQ/noO8FRhm0LoPC73Ef+J4ZBdrvWwauF3zJe33d4ibxEcb
|
|
62
|
+
8/pz5WzFkeixYM2nsHhqHsBKw7JPouKNXRnl5IAE1eFmqDyC7G/VT7OF669xM6hb
|
|
63
|
+
Ut5G21JE4cNK6NNucS+fzg1JPX0+3VhsYZjj7D5uljRvQXrJ8iHgr/M6j2oLHvTA
|
|
64
|
+
I2MLdq2qjZFDOCXsxBxJpbmLGBx9ow6ZerlUxzws2AWv2pk=
|
|
65
|
+
-----END CERTIFICATE-----
|
|
66
|
+
`,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "digicert-g4",
|
|
70
|
+
name: "DigiCert Trusted Root G4",
|
|
71
|
+
sha256: "552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988",
|
|
72
|
+
pem: `
|
|
73
|
+
-----BEGIN CERTIFICATE-----
|
|
74
|
+
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
|
|
104
|
+
-----END CERTIFICATE-----
|
|
105
|
+
`,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "sectigo-r46",
|
|
109
|
+
name: "Sectigo Public Time Stamping Root R46",
|
|
110
|
+
sha256: "4941b001b8a97e961b7817c9d9e960ec4b056bfc915a8c1aabf6ef6b3ac046a5",
|
|
111
|
+
pem: `
|
|
112
|
+
-----BEGIN CERTIFICATE-----
|
|
113
|
+
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
|
|
142
|
+
XftzTXTX48FRiJ6PA9o=
|
|
143
|
+
-----END CERTIFICATE-----
|
|
144
|
+
`,
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
/** The default roots, as DER. */
|
|
148
|
+
export function knownTsaRootsDer() {
|
|
149
|
+
return KNOWN_TSA_ROOTS.flatMap((r) => fromPem(r.pem));
|
|
150
|
+
}
|
package/dist/tsp.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** A TimeStampReq for a SHA-256 digest, asking for the signing certificate to be included. */
|
|
2
|
+
export declare function buildTimestampRequest(digest: Uint8Array, nonce: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
3
|
+
export type TimestampReply = {
|
|
4
|
+
granted: true;
|
|
5
|
+
token: Uint8Array<ArrayBuffer>;
|
|
6
|
+
} | {
|
|
7
|
+
granted: false;
|
|
8
|
+
reason: string;
|
|
9
|
+
};
|
|
10
|
+
/** Reads a TimeStampResp. A refusal is a result, not an error; malformed bytes throw. */
|
|
11
|
+
export declare function parseTimestampReply(bytes: Uint8Array): TimestampReply;
|
|
12
|
+
export type TimestampExpectation = {
|
|
13
|
+
/** The SHA-256 digest that should have been stamped. */
|
|
14
|
+
digest: Uint8Array;
|
|
15
|
+
/** The nonce sent with the request, when checking a fresh reply. */
|
|
16
|
+
nonce?: Uint8Array;
|
|
17
|
+
/** Trusted TSA roots, as DER certificates. */
|
|
18
|
+
roots: readonly Uint8Array[];
|
|
19
|
+
};
|
|
20
|
+
export type TimestampInfo = {
|
|
21
|
+
/** The stamped time, as the TSA states it. */
|
|
22
|
+
gen_time: string;
|
|
23
|
+
serial: string;
|
|
24
|
+
policy: string;
|
|
25
|
+
/** Signing certificate first, trusted root last. */
|
|
26
|
+
chain: string[];
|
|
27
|
+
/** SHA-256 (hex) of the trusted root the chain ends at: which authority vouches for the time. */
|
|
28
|
+
root_sha256: string;
|
|
29
|
+
};
|
|
30
|
+
export type TimestampResult = ({
|
|
31
|
+
ok: true;
|
|
32
|
+
} & TimestampInfo) | {
|
|
33
|
+
ok: false;
|
|
34
|
+
reason: string;
|
|
35
|
+
};
|
|
36
|
+
/** Verifies a TimeStampToken. Never throws: anything malformed is a failure. */
|
|
37
|
+
export declare function verifyTimestampToken(token: Uint8Array, expect: TimestampExpectation): Promise<TimestampResult>;
|
package/dist/tsp.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// RFC 3161 timestamps: building a request, reading the reply and verifying the token.
|
|
2
|
+
//
|
|
3
|
+
// A token is a CMS SignedData whose content is a TSTInfo: the hash that was stamped, the
|
|
4
|
+
// time, a serial number and the nonce we sent. Verifying it means checking the hash and
|
|
5
|
+
// nonce, the CMS signature over the signed attributes, the binding of the signing
|
|
6
|
+
// certificate (ESS signingCertificate), the certificate's timestamping purpose, and a
|
|
7
|
+
// chain to a trusted root valid at the stamped time. Pure: the nonce is passed in.
|
|
8
|
+
import { bytesEqual, concatBytes, toHex } from "./hash.js";
|
|
9
|
+
import { children, contextTag, encodeBoolean, encodeNull, encodeOctetString, encodeOid, encodeSequence, encodeSmallInt, encodeUnsignedInteger, expectTag, readDer, readIntegerBytes, readOctetString, readOid, readSmallInt, readTime, seq, TAG, } from "./der.js";
|
|
10
|
+
import { buildChain, extendedKeyUsage, HASH_BY_OID, keyUsageAllows, OID, parseCertificate, subjectKeyIdentifier, verifySignature } from "./x509.js";
|
|
11
|
+
const CMS = {
|
|
12
|
+
signedData: "1.2.840.113549.1.7.2",
|
|
13
|
+
tstInfo: "1.2.840.113549.1.9.16.1.4",
|
|
14
|
+
contentType: "1.2.840.113549.1.9.3",
|
|
15
|
+
messageDigest: "1.2.840.113549.1.9.4",
|
|
16
|
+
signingCertificate: "1.2.840.113549.1.9.16.2.12",
|
|
17
|
+
signingCertificateV2: "1.2.840.113549.1.9.16.2.47",
|
|
18
|
+
};
|
|
19
|
+
const KEY_USAGE_DIGITAL_SIGNATURE = 0;
|
|
20
|
+
/** Real TSAs send three or four; more is not a token worth reading. */
|
|
21
|
+
const MAX_CERTIFICATES = 8;
|
|
22
|
+
const KEY_USAGE_NON_REPUDIATION = 1;
|
|
23
|
+
/** A TimeStampReq for a SHA-256 digest, asking for the signing certificate to be included. */
|
|
24
|
+
export function buildTimestampRequest(digest, nonce) {
|
|
25
|
+
if (digest.length !== 32)
|
|
26
|
+
throw new Error("expected a 32-byte SHA-256 digest");
|
|
27
|
+
return encodeSequence(encodeSmallInt(1), encodeSequence(encodeSequence(encodeOid(OID.sha256), encodeNull()), encodeOctetString(digest)), encodeUnsignedInteger(nonce), encodeBoolean(true));
|
|
28
|
+
}
|
|
29
|
+
/** Reads a TimeStampResp. A refusal is a result, not an error; malformed bytes throw. */
|
|
30
|
+
export function parseTimestampReply(bytes) {
|
|
31
|
+
const [statusInfo, token, ...extra] = seq(readDer(bytes), "TimeStampResp");
|
|
32
|
+
if (extra.length)
|
|
33
|
+
throw new Error("TimeStampResp has trailing elements");
|
|
34
|
+
const [status, ...rest] = seq(statusInfo, "PKIStatusInfo");
|
|
35
|
+
const code = readSmallInt(status, "PKIStatus");
|
|
36
|
+
if (code !== 0 && code !== 1) {
|
|
37
|
+
const text = rest[0]?.tag === TAG.SEQUENCE ? children(rest[0]).map((s) => new TextDecoder().decode(s.content)).join("; ") : "";
|
|
38
|
+
return { granted: false, reason: `the TSA refused the request (status ${code}${text ? `: ${text}` : ""})` };
|
|
39
|
+
}
|
|
40
|
+
if (!token)
|
|
41
|
+
throw new Error("the TSA granted the request but sent no token");
|
|
42
|
+
return { granted: true, token: token.raw };
|
|
43
|
+
}
|
|
44
|
+
/** Verifies a TimeStampToken. Never throws: anything malformed is a failure. */
|
|
45
|
+
export async function verifyTimestampToken(token, expect) {
|
|
46
|
+
try {
|
|
47
|
+
return await verifyUnsafe(token, expect);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return { ok: false, reason: `malformed timestamp token: ${err instanceof Error ? err.message : String(err)}` };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function verifyUnsafe(token, expect) {
|
|
54
|
+
const fail = (reason) => ({ ok: false, reason });
|
|
55
|
+
// ContentInfo { id-signedData, [0] EXPLICIT SignedData }
|
|
56
|
+
const [ctype, wrapped, ...ciExtra] = seq(readDer(token), "ContentInfo");
|
|
57
|
+
if (ciExtra.length || readOid(ctype) !== CMS.signedData)
|
|
58
|
+
return fail("not a CMS SignedData");
|
|
59
|
+
const [signedData, ...wrapExtra] = children(expectTag(wrapped, contextTag(0), "SignedData wrapper"));
|
|
60
|
+
if (wrapExtra.length)
|
|
61
|
+
return fail("SignedData wrapper has trailing elements");
|
|
62
|
+
const sd = seq(signedData, "SignedData");
|
|
63
|
+
const encap = seq(sd[2], "encapContentInfo");
|
|
64
|
+
if (encap.length !== 2)
|
|
65
|
+
return fail("encapContentInfo must carry exactly the TSTInfo");
|
|
66
|
+
if (readOid(encap[0]) !== CMS.tstInfo)
|
|
67
|
+
return fail("the signed content is not a TSTInfo");
|
|
68
|
+
const [eContentEl] = children(expectTag(encap[1], contextTag(0), "eContent wrapper"));
|
|
69
|
+
const eContent = readOctetString(eContentEl, "eContent");
|
|
70
|
+
const certificates = [];
|
|
71
|
+
let k = 3;
|
|
72
|
+
if (sd[k]?.tag === contextTag(0)) {
|
|
73
|
+
const certs = children(sd[k++]);
|
|
74
|
+
if (certs.length > MAX_CERTIFICATES)
|
|
75
|
+
return fail(`the token carries ${certs.length} certificates; at most ${MAX_CERTIFICATES} are read`);
|
|
76
|
+
certificates.push(...certs.map((c) => parseCertificate(c.raw)));
|
|
77
|
+
}
|
|
78
|
+
if (sd[k]?.tag === contextTag(1))
|
|
79
|
+
k++; // CRLs: not used
|
|
80
|
+
const signerInfos = seq(sd[k++], "signerInfos", TAG.SET);
|
|
81
|
+
if (k !== sd.length)
|
|
82
|
+
return fail("SignedData has trailing elements");
|
|
83
|
+
if (signerInfos.length !== 1)
|
|
84
|
+
return fail(`expected one signer, found ${signerInfos.length}`);
|
|
85
|
+
// TSTInfo
|
|
86
|
+
const tst = parseTstInfo(eContent);
|
|
87
|
+
if (tst.imprintAlg !== OID.sha256)
|
|
88
|
+
return fail("the stamped hash is not SHA-256");
|
|
89
|
+
if (!bytesEqual(tst.imprint, expect.digest))
|
|
90
|
+
return fail("the timestamp is for a different hash");
|
|
91
|
+
if (expect.nonce !== undefined && (tst.nonce === null || !bytesEqual(tst.nonce, readIntegerBytes(readDer(encodeUnsignedInteger(expect.nonce)))))) {
|
|
92
|
+
return fail("the nonce does not match the request");
|
|
93
|
+
}
|
|
94
|
+
// SignerInfo
|
|
95
|
+
const si = seq(signerInfos[0], "SignerInfo");
|
|
96
|
+
const sid = si[1];
|
|
97
|
+
const digestAlg = readOid(seq(si[2], "digestAlgorithm")[0]);
|
|
98
|
+
const hash = HASH_BY_OID[digestAlg];
|
|
99
|
+
if (!hash || hash === "SHA-1")
|
|
100
|
+
return fail(`unsupported digest algorithm ${digestAlg}`);
|
|
101
|
+
const signedAttrsEl = expectTag(si[3], contextTag(0), "signedAttrs");
|
|
102
|
+
const sigAlg = readOid(seq(si[4], "signatureAlgorithm")[0]);
|
|
103
|
+
const signature = readOctetString(si[5], "signature");
|
|
104
|
+
if (si.length > 7 || (si.length === 7 && si[6].tag !== contextTag(1)))
|
|
105
|
+
return fail("SignerInfo has unexpected elements");
|
|
106
|
+
const attrs = new Map();
|
|
107
|
+
for (const attr of children(signedAttrsEl)) {
|
|
108
|
+
const [type, values] = seq(attr, "attribute");
|
|
109
|
+
const oid = readOid(type);
|
|
110
|
+
const vs = seq(values, "attribute values", TAG.SET);
|
|
111
|
+
if (attrs.has(oid) || vs.length !== 1)
|
|
112
|
+
return fail(`signed attribute ${oid} must appear once with one value`);
|
|
113
|
+
attrs.set(oid, vs[0]);
|
|
114
|
+
}
|
|
115
|
+
if (readOid(attrs.get(CMS.contentType), "contentType attribute") !== CMS.tstInfo)
|
|
116
|
+
return fail("contentType attribute is not TSTInfo");
|
|
117
|
+
const contentDigest = new Uint8Array(await crypto.subtle.digest(hash, eContent));
|
|
118
|
+
if (!bytesEqual(readOctetString(attrs.get(CMS.messageDigest), "messageDigest attribute"), contentDigest))
|
|
119
|
+
return fail("messageDigest does not match the TSTInfo");
|
|
120
|
+
// The signer's certificate: identified by the SignerInfo and bound by ESS signingCertificate.
|
|
121
|
+
const signer = certificates.find((c) => matchesSid(c, sid));
|
|
122
|
+
if (!signer)
|
|
123
|
+
return fail("the token does not include the signing certificate");
|
|
124
|
+
const essProblem = await checkSigningCertificate(signer, attrs);
|
|
125
|
+
if (essProblem)
|
|
126
|
+
return fail(essProblem);
|
|
127
|
+
// The signature covers the DER of the signed attributes, re-tagged as a SET.
|
|
128
|
+
const signed = concatBytes(Uint8Array.of(TAG.SET), signedAttrsEl.raw.subarray(1));
|
|
129
|
+
let valid = false;
|
|
130
|
+
try {
|
|
131
|
+
valid = await verifySignature(signer.spki, sigAlg, signature, signed, hash);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
return fail(`signature cannot be checked: ${err instanceof Error ? err.message : String(err)}`);
|
|
135
|
+
}
|
|
136
|
+
if (!valid)
|
|
137
|
+
return fail("the TSA's signature does not verify");
|
|
138
|
+
// RFC 3161 2.3: the signing certificate is for timestamping only, and says so critically.
|
|
139
|
+
const eku = extendedKeyUsage(signer);
|
|
140
|
+
if (!eku || !eku.critical || eku.purposes.length !== 1 || eku.purposes[0] !== OID.kpTimeStamping) {
|
|
141
|
+
return fail(`${signer.name} is not a certificate for timestamping only (critical extended key usage id-kp-timeStamping)`);
|
|
142
|
+
}
|
|
143
|
+
if (!keyUsageAllows(signer, KEY_USAGE_DIGITAL_SIGNATURE) && !keyUsageAllows(signer, KEY_USAGE_NON_REPUDIATION)) {
|
|
144
|
+
return fail(`${signer.name} is not allowed to make signatures`);
|
|
145
|
+
}
|
|
146
|
+
const roots = expect.roots.map((r) => parseCertificate(r));
|
|
147
|
+
const chain = await buildChain(signer, certificates.filter((c) => c !== signer), roots, tst.genTime);
|
|
148
|
+
if (!chain.ok)
|
|
149
|
+
return fail(`the TSA's certificate does not chain to a trusted root: ${chain.reason}`);
|
|
150
|
+
const rootHash = toHex(new Uint8Array(await crypto.subtle.digest("SHA-256", chain.anchor.raw)));
|
|
151
|
+
return { ok: true, gen_time: new Date(tst.genTime).toISOString(), serial: toHex(tst.serial), policy: tst.policy, chain: chain.path, root_sha256: rootHash };
|
|
152
|
+
}
|
|
153
|
+
function parseTstInfo(bytes) {
|
|
154
|
+
const t = seq(readDer(bytes), "TSTInfo");
|
|
155
|
+
if (readSmallInt(t[0], "TSTInfo version") !== 1)
|
|
156
|
+
throw new Error("unsupported TSTInfo version");
|
|
157
|
+
const policy = readOid(t[1], "policy");
|
|
158
|
+
const [alg, hashed, ...imprintExtra] = seq(t[2], "messageImprint");
|
|
159
|
+
if (imprintExtra.length)
|
|
160
|
+
throw new Error("messageImprint has trailing elements");
|
|
161
|
+
const algParts = seq(alg, "hashAlgorithm");
|
|
162
|
+
if (algParts.length > 2 || (algParts[1] && algParts[1].tag !== TAG.NULL))
|
|
163
|
+
throw new Error("unexpected hashAlgorithm parameters");
|
|
164
|
+
const imprintAlg = readOid(algParts[0]);
|
|
165
|
+
const imprint = readOctetString(hashed, "hashedMessage");
|
|
166
|
+
const serial = readIntegerBytes(t[3], "serialNumber");
|
|
167
|
+
const genTime = readTime(expectTag(t[4], TAG.GENERALIZED_TIME, "genTime"), "genTime");
|
|
168
|
+
let nonce = null;
|
|
169
|
+
for (const el of t.slice(5)) {
|
|
170
|
+
if (el.tag === TAG.INTEGER) {
|
|
171
|
+
if (nonce !== null)
|
|
172
|
+
throw new Error("TSTInfo has two nonces");
|
|
173
|
+
nonce = readIntegerBytes(el, "nonce");
|
|
174
|
+
}
|
|
175
|
+
// A critical extension we do not understand might change what the token means.
|
|
176
|
+
if (el.tag === contextTag(1)) {
|
|
177
|
+
for (const ext of children(el)) {
|
|
178
|
+
const parts = seq(ext, "TSTInfo extension");
|
|
179
|
+
if (parts.length === 3 && parts[1].tag === TAG.BOOLEAN && parts[1].content[0] === 0xff)
|
|
180
|
+
throw new Error("TSTInfo has a critical extension");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// accuracy, ordering and the tsa name do not affect what is checked here
|
|
184
|
+
}
|
|
185
|
+
return { policy, imprintAlg, imprint, serial, genTime, nonce };
|
|
186
|
+
}
|
|
187
|
+
/** SignerIdentifier: IssuerAndSerialNumber, or [0] SubjectKeyIdentifier. */
|
|
188
|
+
function matchesSid(cert, sid) {
|
|
189
|
+
if (sid.tag === TAG.SEQUENCE) {
|
|
190
|
+
const [issuer, serial] = children(sid);
|
|
191
|
+
return bytesEqual(issuer.raw, cert.issuer) && bytesEqual(readIntegerBytes(serial), cert.serial);
|
|
192
|
+
}
|
|
193
|
+
if (sid.tag === contextTag(0, false)) {
|
|
194
|
+
const ski = subjectKeyIdentifier(cert);
|
|
195
|
+
return ski !== null && bytesEqual(ski, sid.content);
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
/** RFC 2634 / RFC 5035: the signed attributes name the signing certificate by hash. At least one form is required. */
|
|
200
|
+
async function checkSigningCertificate(signer, attrs) {
|
|
201
|
+
const v2 = attrs.get(CMS.signingCertificateV2);
|
|
202
|
+
const v1 = attrs.get(CMS.signingCertificate);
|
|
203
|
+
if (!v1 && !v2)
|
|
204
|
+
return "the token does not bind its signing certificate (no ESS signingCertificate attribute)";
|
|
205
|
+
if (v2) {
|
|
206
|
+
const [certs] = seq(v2, "SigningCertificateV2");
|
|
207
|
+
const first = seq(certs, "ESSCertIDv2 list")[0];
|
|
208
|
+
const parts = seq(first, "ESSCertIDv2");
|
|
209
|
+
let alg = OID.sha256;
|
|
210
|
+
let i = 0;
|
|
211
|
+
if (parts[0]?.tag === TAG.SEQUENCE)
|
|
212
|
+
alg = readOid(seq(parts[i++], "hashAlgorithm")[0]);
|
|
213
|
+
const hashName = HASH_BY_OID[alg];
|
|
214
|
+
if (!hashName)
|
|
215
|
+
return `unsupported signingCertificateV2 hash ${alg}`;
|
|
216
|
+
const actual = new Uint8Array(await crypto.subtle.digest(hashName, signer.raw));
|
|
217
|
+
if (!bytesEqual(readOctetString(parts[i], "certHash"), actual))
|
|
218
|
+
return "signingCertificateV2 names a different certificate";
|
|
219
|
+
}
|
|
220
|
+
if (v1) {
|
|
221
|
+
const [certs] = seq(v1, "SigningCertificate");
|
|
222
|
+
const first = seq(certs, "ESSCertID list")[0];
|
|
223
|
+
const actual = new Uint8Array(await crypto.subtle.digest("SHA-1", signer.raw));
|
|
224
|
+
if (!bytesEqual(readOctetString(seq(first, "ESSCertID")[0], "certHash"), actual))
|
|
225
|
+
return "signingCertificate names a different certificate";
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
package/dist/x509.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export declare const OID: {
|
|
2
|
+
readonly rsaEncryption: "1.2.840.113549.1.1.1";
|
|
3
|
+
readonly sha256WithRSA: "1.2.840.113549.1.1.11";
|
|
4
|
+
readonly sha384WithRSA: "1.2.840.113549.1.1.12";
|
|
5
|
+
readonly sha512WithRSA: "1.2.840.113549.1.1.13";
|
|
6
|
+
readonly ecPublicKey: "1.2.840.10045.2.1";
|
|
7
|
+
readonly ecdsaWithSHA256: "1.2.840.10045.4.3.2";
|
|
8
|
+
readonly ecdsaWithSHA384: "1.2.840.10045.4.3.3";
|
|
9
|
+
readonly ecdsaWithSHA512: "1.2.840.10045.4.3.4";
|
|
10
|
+
readonly p256: "1.2.840.10045.3.1.7";
|
|
11
|
+
readonly p384: "1.3.132.0.34";
|
|
12
|
+
readonly p521: "1.3.132.0.35";
|
|
13
|
+
readonly sha1: "1.3.14.3.2.26";
|
|
14
|
+
readonly sha256: "2.16.840.1.101.3.4.2.1";
|
|
15
|
+
readonly sha384: "2.16.840.1.101.3.4.2.2";
|
|
16
|
+
readonly sha512: "2.16.840.1.101.3.4.2.3";
|
|
17
|
+
readonly commonName: "2.5.4.3";
|
|
18
|
+
readonly organisation: "2.5.4.10";
|
|
19
|
+
readonly subjectKeyIdentifier: "2.5.29.14";
|
|
20
|
+
readonly keyUsage: "2.5.29.15";
|
|
21
|
+
readonly subjectAltName: "2.5.29.17";
|
|
22
|
+
readonly basicConstraints: "2.5.29.19";
|
|
23
|
+
readonly crlDistributionPoints: "2.5.29.31";
|
|
24
|
+
readonly certificatePolicies: "2.5.29.32";
|
|
25
|
+
readonly authorityKeyIdentifier: "2.5.29.35";
|
|
26
|
+
readonly extKeyUsage: "2.5.29.37";
|
|
27
|
+
readonly authorityInfoAccess: "1.3.6.1.5.5.7.1.1";
|
|
28
|
+
readonly kpTimeStamping: "1.3.6.1.5.5.7.3.8";
|
|
29
|
+
readonly anyExtendedKeyUsage: "2.5.29.37.0";
|
|
30
|
+
};
|
|
31
|
+
export type HashName = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512";
|
|
32
|
+
export declare const HASH_BY_OID: Record<string, HashName>;
|
|
33
|
+
export type Certificate = {
|
|
34
|
+
raw: Uint8Array<ArrayBuffer>;
|
|
35
|
+
tbs: Uint8Array<ArrayBuffer>;
|
|
36
|
+
signatureAlg: string;
|
|
37
|
+
signature: Uint8Array<ArrayBuffer>;
|
|
38
|
+
serial: Uint8Array<ArrayBuffer>;
|
|
39
|
+
issuer: Uint8Array<ArrayBuffer>;
|
|
40
|
+
subject: Uint8Array<ArrayBuffer>;
|
|
41
|
+
/** A readable form of the subject, for reports: CN, else O. */
|
|
42
|
+
name: string;
|
|
43
|
+
notBefore: number;
|
|
44
|
+
notAfter: number;
|
|
45
|
+
spki: Uint8Array<ArrayBuffer>;
|
|
46
|
+
extensions: Map<string, {
|
|
47
|
+
critical: boolean;
|
|
48
|
+
value: Uint8Array<ArrayBuffer>;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
export declare function parseCertificate(bytes: Uint8Array): Certificate;
|
|
52
|
+
/** basicConstraints: whether this is a CA, and how many intermediate CAs may follow it (null: no limit). */
|
|
53
|
+
export declare function basicConstraints(cert: Certificate): {
|
|
54
|
+
ca: boolean;
|
|
55
|
+
pathLen: number | null;
|
|
56
|
+
};
|
|
57
|
+
export declare function isCa(cert: Certificate): boolean;
|
|
58
|
+
/** Whether the key usage extension allows `bit`. An absent extension permits every use. */
|
|
59
|
+
export declare function keyUsageAllows(cert: Certificate, bit: number): boolean;
|
|
60
|
+
export declare function extendedKeyUsage(cert: Certificate): {
|
|
61
|
+
critical: boolean;
|
|
62
|
+
purposes: string[];
|
|
63
|
+
} | null;
|
|
64
|
+
export declare function subjectKeyIdentifier(cert: Certificate): Uint8Array<ArrayBuffer> | null;
|
|
65
|
+
/**
|
|
66
|
+
* Verifies `signature` over `data` with the key in `spki`. `alg` is a signature algorithm OID,
|
|
67
|
+
* or rsaEncryption / ecPublicKey with the hash given separately (as CMS signer infos do).
|
|
68
|
+
*/
|
|
69
|
+
export declare function verifySignature(spki: Uint8Array<ArrayBuffer>, alg: string, signature: Uint8Array, data: Uint8Array<ArrayBuffer>, hash?: HashName): Promise<boolean>;
|
|
70
|
+
/** X.509 and CMS carry ECDSA signatures as DER SEQUENCE { r, s }; WebCrypto wants r || s. */
|
|
71
|
+
export declare function ecdsaDerToRaw(der: Uint8Array, size: number): Uint8Array<ArrayBuffer>;
|
|
72
|
+
export type ChainResult = {
|
|
73
|
+
ok: true;
|
|
74
|
+
path: string[];
|
|
75
|
+
anchor: Certificate;
|
|
76
|
+
} | {
|
|
77
|
+
ok: false;
|
|
78
|
+
reason: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Builds a chain from `leaf` to one of `anchors`, through `intermediates`, with every
|
|
82
|
+
* certificate, the anchor included, valid at `at` (the timestamp's own time). The anchor is
|
|
83
|
+
* reached when a certificate's issuer has an anchor's name and the anchor's key verifies
|
|
84
|
+
* it, or when a certificate has an anchor's name and key itself (a cross-signed copy).
|
|
85
|
+
*
|
|
86
|
+
* Every issuer, the anchor included, must be a CA allowed to sign certificates, within its
|
|
87
|
+
* path length constraint, and either without an extended key usage or with one that allows
|
|
88
|
+
* timestamping. The search is bounded: at most MAX_CHAIN deep, each (name, key) once per
|
|
89
|
+
* path, and MAX_SIGNATURE_CHECKS signatures in all.
|
|
90
|
+
*/
|
|
91
|
+
export declare function buildChain(leaf: Certificate, intermediates: readonly Certificate[], anchors: readonly Certificate[], at: number): Promise<ChainResult>;
|
|
92
|
+
/** SHA-256 fingerprint, base64url, for reports and pinning. */
|
|
93
|
+
export declare function certificateFingerprint(cert: Certificate): Promise<string>;
|