@freedomofpress/sigstore-browser 0.1.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/.github/workflows/conformance.yml +29 -0
- package/.github/workflows/publish.yml +45 -0
- package/README.md +10 -0
- package/dist/asn1/error.d.ts +5 -0
- package/dist/asn1/error.d.ts.map +1 -0
- package/dist/asn1/error.js +20 -0
- package/dist/asn1/error.js.map +1 -0
- package/dist/asn1/index.d.ts +2 -0
- package/dist/asn1/index.d.ts.map +1 -0
- package/dist/asn1/index.js +17 -0
- package/dist/asn1/index.js.map +1 -0
- package/dist/asn1/length.d.ts +4 -0
- package/dist/asn1/length.d.ts.map +1 -0
- package/dist/asn1/length.js +59 -0
- package/dist/asn1/length.js.map +1 -0
- package/dist/asn1/obj.d.ts +15 -0
- package/dist/asn1/obj.d.ts.map +1 -0
- package/dist/asn1/obj.js +153 -0
- package/dist/asn1/obj.js.map +1 -0
- package/dist/asn1/parse.d.ts +7 -0
- package/dist/asn1/parse.d.ts.map +1 -0
- package/dist/asn1/parse.js +118 -0
- package/dist/asn1/parse.js.map +1 -0
- package/dist/asn1/tag.d.ts +17 -0
- package/dist/asn1/tag.d.ts.map +1 -0
- package/dist/asn1/tag.js +86 -0
- package/dist/asn1/tag.js.map +1 -0
- package/dist/bundle.d.ts +87 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +2 -0
- package/dist/bundle.js.map +1 -0
- package/dist/canonicalize.d.ts +2 -0
- package/dist/canonicalize.d.ts.map +1 -0
- package/dist/canonicalize.js +64 -0
- package/dist/canonicalize.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +217 -0
- package/dist/cli.js.map +1 -0
- package/dist/crypto.d.ts +5 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +260 -0
- package/dist/crypto.js.map +1 -0
- package/dist/dsse.d.ts +2 -0
- package/dist/dsse.d.ts.map +1 -0
- package/dist/dsse.js +42 -0
- package/dist/dsse.js.map +1 -0
- package/dist/encoding.d.ts +17 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +112 -0
- package/dist/encoding.js.map +1 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +79 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +104 -0
- package/dist/interfaces.d.ts.map +1 -0
- package/dist/interfaces.js +26 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/oid.d.ts +5 -0
- package/dist/oid.d.ts.map +1 -0
- package/dist/oid.js +25 -0
- package/dist/oid.js.map +1 -0
- package/dist/pem.d.ts +3 -0
- package/dist/pem.d.ts.map +1 -0
- package/dist/pem.js +41 -0
- package/dist/pem.js.map +1 -0
- package/dist/rfc3161/error.d.ts +3 -0
- package/dist/rfc3161/error.d.ts.map +1 -0
- package/dist/rfc3161/error.js +18 -0
- package/dist/rfc3161/error.js.map +1 -0
- package/dist/rfc3161/index.d.ts +2 -0
- package/dist/rfc3161/index.d.ts.map +1 -0
- package/dist/rfc3161/index.js +17 -0
- package/dist/rfc3161/index.js.map +1 -0
- package/dist/rfc3161/timestamp.d.ts +36 -0
- package/dist/rfc3161/timestamp.d.ts.map +1 -0
- package/dist/rfc3161/timestamp.js +203 -0
- package/dist/rfc3161/timestamp.js.map +1 -0
- package/dist/rfc3161/tstinfo.d.ts +13 -0
- package/dist/rfc3161/tstinfo.d.ts.map +1 -0
- package/dist/rfc3161/tstinfo.js +47 -0
- package/dist/rfc3161/tstinfo.js.map +1 -0
- package/dist/sigstore.d.ts +33 -0
- package/dist/sigstore.d.ts.map +1 -0
- package/dist/sigstore.js +531 -0
- package/dist/sigstore.js.map +1 -0
- package/dist/stream.d.ts +22 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +114 -0
- package/dist/stream.js.map +1 -0
- package/dist/timestamp/tsa.d.ts +24 -0
- package/dist/timestamp/tsa.d.ts.map +1 -0
- package/dist/timestamp/tsa.js +150 -0
- package/dist/timestamp/tsa.js.map +1 -0
- package/dist/tlog/body.d.ts +8 -0
- package/dist/tlog/body.d.ts.map +1 -0
- package/dist/tlog/body.js +45 -0
- package/dist/tlog/body.js.map +1 -0
- package/dist/tlog/checkpoint.d.ts +23 -0
- package/dist/tlog/checkpoint.d.ts.map +1 -0
- package/dist/tlog/checkpoint.js +187 -0
- package/dist/tlog/checkpoint.js.map +1 -0
- package/dist/tlog/dsse.d.ts +4 -0
- package/dist/tlog/dsse.d.ts.map +1 -0
- package/dist/tlog/dsse.js +85 -0
- package/dist/tlog/dsse.js.map +1 -0
- package/dist/tlog/hashedrekord.d.ts +4 -0
- package/dist/tlog/hashedrekord.d.ts.map +1 -0
- package/dist/tlog/hashedrekord.js +63 -0
- package/dist/tlog/hashedrekord.js.map +1 -0
- package/dist/tlog/intoto.d.ts +4 -0
- package/dist/tlog/intoto.d.ts.map +1 -0
- package/dist/tlog/intoto.js +50 -0
- package/dist/tlog/intoto.js.map +1 -0
- package/dist/tlog/merkle.d.ts +3 -0
- package/dist/tlog/merkle.d.ts.map +1 -0
- package/dist/tlog/merkle.js +112 -0
- package/dist/tlog/merkle.js.map +1 -0
- package/dist/trust/tuf-root.json +1 -0
- package/dist/trust/tuf.d.ts +95 -0
- package/dist/trust/tuf.d.ts.map +1 -0
- package/dist/trust/tuf.js +150 -0
- package/dist/trust/tuf.js.map +1 -0
- package/dist/x509/cert.d.ts +52 -0
- package/dist/x509/cert.d.ts.map +1 -0
- package/dist/x509/cert.js +256 -0
- package/dist/x509/cert.js.map +1 -0
- package/dist/x509/chain.d.ts +20 -0
- package/dist/x509/chain.d.ts.map +1 -0
- package/dist/x509/chain.js +141 -0
- package/dist/x509/chain.js.map +1 -0
- package/dist/x509/ext.d.ts +48 -0
- package/dist/x509/ext.d.ts.map +1 -0
- package/dist/x509/ext.js +164 -0
- package/dist/x509/ext.js.map +1 -0
- package/dist/x509/index.d.ts +4 -0
- package/dist/x509/index.d.ts.map +1 -0
- package/dist/x509/index.js +19 -0
- package/dist/x509/index.js.map +1 -0
- package/dist/x509/sct.d.ts +25 -0
- package/dist/x509/sct.d.ts.map +1 -0
- package/dist/x509/sct.js +123 -0
- package/dist/x509/sct.js.map +1 -0
- package/eslint.config.mjs +31 -0
- package/package.json +39 -0
- package/src/asn1/error.ts +18 -0
- package/src/asn1/index.ts +16 -0
- package/src/asn1/length.ts +71 -0
- package/src/asn1/obj.ts +184 -0
- package/src/asn1/parse.ts +146 -0
- package/src/asn1/tag.ts +105 -0
- package/src/bundle.ts +99 -0
- package/src/canonicalize.ts +61 -0
- package/src/cli.ts +285 -0
- package/src/crypto.ts +310 -0
- package/src/dsse.ts +49 -0
- package/src/encoding.ts +141 -0
- package/src/errors.ts +82 -0
- package/src/index.browser.test.ts +219 -0
- package/src/index.ts +14 -0
- package/src/interfaces.ts +118 -0
- package/src/oid.ts +28 -0
- package/src/pem.ts +48 -0
- package/src/rfc3161/error.ts +16 -0
- package/src/rfc3161/index.ts +17 -0
- package/src/rfc3161/timestamp.ts +270 -0
- package/src/rfc3161/tstinfo.ts +80 -0
- package/src/sigstore.ts +728 -0
- package/src/stream.ts +141 -0
- package/src/timestamp/tsa.ts +205 -0
- package/src/tlog/body.ts +65 -0
- package/src/tlog/checkpoint.ts +248 -0
- package/src/tlog/dsse.ts +147 -0
- package/src/tlog/hashedrekord.ts +130 -0
- package/src/tlog/intoto.ts +102 -0
- package/src/tlog/merkle.ts +152 -0
- package/src/trust/README.md +38 -0
- package/src/trust/tuf-root.json +1 -0
- package/src/trust/tuf.ts +223 -0
- package/src/types/tuf-browser.d.ts +13 -0
- package/src/x509/cert.ts +319 -0
- package/src/x509/chain.ts +212 -0
- package/src/x509/ext.ts +212 -0
- package/src/x509/index.ts +19 -0
- package/src/x509/sct.ts +154 -0
- package/tsconfig.json +18 -0
- package/vite.config.playwright.ts +26 -0
- package/vite.config.ts +18 -0
package/src/asn1/obj.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2023 The Sigstore Authors.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { toArrayBuffer } from "../encoding.js";
|
|
17
|
+
import { ByteStream } from "../stream.js";
|
|
18
|
+
import { ASN1ParseError, ASN1TypeError } from "./error.js";
|
|
19
|
+
import { decodeLength, encodeLength } from "./length.js";
|
|
20
|
+
import {
|
|
21
|
+
parseBitString,
|
|
22
|
+
parseBoolean,
|
|
23
|
+
parseInteger,
|
|
24
|
+
parseOID,
|
|
25
|
+
parseTime,
|
|
26
|
+
} from "./parse.js";
|
|
27
|
+
import { ASN1Tag } from "./tag.js";
|
|
28
|
+
|
|
29
|
+
export class ASN1Obj {
|
|
30
|
+
readonly tag: ASN1Tag;
|
|
31
|
+
readonly subs: ASN1Obj[];
|
|
32
|
+
readonly value: Uint8Array;
|
|
33
|
+
|
|
34
|
+
constructor(tag: ASN1Tag, value: Uint8Array, subs: ASN1Obj[]) {
|
|
35
|
+
this.tag = tag;
|
|
36
|
+
this.value = value;
|
|
37
|
+
this.subs = subs;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Constructs an ASN.1 object from a Buffer of DER-encoded bytes.
|
|
41
|
+
public static parseBuffer(buf: Uint8Array): ASN1Obj {
|
|
42
|
+
return parseStream(new ByteStream(toArrayBuffer(buf)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public toDER(): Uint8Array {
|
|
46
|
+
const valueStream = new ByteStream();
|
|
47
|
+
|
|
48
|
+
if (this.subs.length > 0) {
|
|
49
|
+
for (const sub of this.subs) {
|
|
50
|
+
valueStream.appendView(sub.toDER());
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
valueStream.appendView(this.value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const value = valueStream.buffer;
|
|
57
|
+
|
|
58
|
+
// Concat tag/length/value
|
|
59
|
+
const obj = new ByteStream();
|
|
60
|
+
obj.appendChar(this.tag.toDER());
|
|
61
|
+
obj.appendView(encodeLength(value.length));
|
|
62
|
+
obj.appendView(value);
|
|
63
|
+
|
|
64
|
+
return obj.buffer;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
68
|
+
// Convenience methods for parsing ASN.1 primitives into JS types
|
|
69
|
+
|
|
70
|
+
// Returns the ASN.1 object's value as a boolean. Throws an error if the
|
|
71
|
+
// object is not a boolean.
|
|
72
|
+
public toBoolean(): boolean {
|
|
73
|
+
if (!this.tag.isBoolean()) {
|
|
74
|
+
throw new ASN1TypeError("not a boolean");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return parseBoolean(this.value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Returns the ASN.1 object's value as a BigInt. Throws an error if the
|
|
81
|
+
// object is not an integer.
|
|
82
|
+
public toInteger(): bigint {
|
|
83
|
+
if (!this.tag.isInteger()) {
|
|
84
|
+
throw new ASN1TypeError("not an integer");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return parseInteger(this.value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Returns the ASN.1 object's value as an OID string. Throws an error if the
|
|
91
|
+
// object is not an OID.
|
|
92
|
+
public toOID(): string {
|
|
93
|
+
if (!this.tag.isOID()) {
|
|
94
|
+
throw new ASN1TypeError("not an OID");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return parseOID(this.value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Returns the ASN.1 object's value as a Date. Throws an error if the object
|
|
101
|
+
// is not either a UTCTime or a GeneralizedTime.
|
|
102
|
+
public toDate(): Date {
|
|
103
|
+
switch (true) {
|
|
104
|
+
case this.tag.isUTCTime():
|
|
105
|
+
return parseTime(this.value, true);
|
|
106
|
+
case this.tag.isGeneralizedTime():
|
|
107
|
+
return parseTime(this.value, false);
|
|
108
|
+
default:
|
|
109
|
+
throw new ASN1TypeError("not a date");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Returns the ASN.1 object's value as a number[] where each number is the
|
|
114
|
+
// value of a bit in the bit string. Throws an error if the object is not a
|
|
115
|
+
// bit string.
|
|
116
|
+
public toBitString(): number[] {
|
|
117
|
+
if (!this.tag.isBitString()) {
|
|
118
|
+
throw new ASN1TypeError("not a bit string");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return parseBitString(this.value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
126
|
+
// Internal stream parsing functions
|
|
127
|
+
|
|
128
|
+
function parseStream(stream: ByteStream): ASN1Obj {
|
|
129
|
+
// Parse tag, length, and value from stream
|
|
130
|
+
const tag = new ASN1Tag(stream.getUint8());
|
|
131
|
+
const len = decodeLength(stream);
|
|
132
|
+
const value = stream.slice(stream.position, len);
|
|
133
|
+
|
|
134
|
+
const start = stream.position;
|
|
135
|
+
|
|
136
|
+
let subs: ASN1Obj[] = [];
|
|
137
|
+
// If the object is constructed, parse its children. Sometimes, children
|
|
138
|
+
// are embedded in OCTESTRING objects, so we need to check those
|
|
139
|
+
// for children as well.
|
|
140
|
+
if (tag.constructed) {
|
|
141
|
+
subs = collectSubs(stream, len);
|
|
142
|
+
} else if (tag.isOctetString()) {
|
|
143
|
+
// Attempt to parse children of OCTETSTRING objects. If anything fails,
|
|
144
|
+
// assume the object is not constructed and treat as primitive.
|
|
145
|
+
try {
|
|
146
|
+
subs = collectSubs(stream, len);
|
|
147
|
+
} catch {
|
|
148
|
+
// Fail silently and treat as primitive
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// If there are no children, move stream cursor to the end of the object
|
|
153
|
+
if (subs.length === 0) {
|
|
154
|
+
stream.seek(start + len);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return new ASN1Obj(tag, value, subs);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function collectSubs(stream: ByteStream, len: number): ASN1Obj[] {
|
|
161
|
+
// Calculate end of object content
|
|
162
|
+
const end = stream.position + len;
|
|
163
|
+
|
|
164
|
+
// Make sure there are enough bytes left in the stream. This should never
|
|
165
|
+
// happen, cause it'll get caught when the stream is sliced in parseStream.
|
|
166
|
+
// Leaving as an extra check just in case.
|
|
167
|
+
/* istanbul ignore if */
|
|
168
|
+
if (end > stream.length) {
|
|
169
|
+
throw new ASN1ParseError("invalid length");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Parse all children
|
|
173
|
+
const subs: ASN1Obj[] = [];
|
|
174
|
+
while (stream.position < end) {
|
|
175
|
+
subs.push(parseStream(stream));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// When we're done parsing children, we should be at the end of the object
|
|
179
|
+
if (stream.position !== end) {
|
|
180
|
+
throw new ASN1ParseError("invalid length");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return subs;
|
|
184
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2023 The Sigstore Authors.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Uint8ArrayToString } from "../encoding.js";
|
|
18
|
+
|
|
19
|
+
const RE_TIME_SHORT_YEAR =
|
|
20
|
+
/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/;
|
|
21
|
+
const RE_TIME_LONG_YEAR =
|
|
22
|
+
/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/;
|
|
23
|
+
|
|
24
|
+
// Parse a BigInt from the DER-encoded buffer
|
|
25
|
+
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-integer
|
|
26
|
+
export function parseInteger(buf: Uint8Array): bigint {
|
|
27
|
+
let pos = 0;
|
|
28
|
+
const end = buf.length;
|
|
29
|
+
let val = buf[pos];
|
|
30
|
+
const neg = val > 0x7f;
|
|
31
|
+
|
|
32
|
+
// Consume any padding bytes
|
|
33
|
+
const pad = neg ? 0xff : 0x00;
|
|
34
|
+
while (val == pad && ++pos < end) {
|
|
35
|
+
val = buf[pos];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Calculate remaining bytes to read
|
|
39
|
+
const len = end - pos;
|
|
40
|
+
|
|
41
|
+
if (len === 0) return BigInt(neg ? -1 : 0);
|
|
42
|
+
|
|
43
|
+
// Handle two's complement for negative numbers
|
|
44
|
+
val = neg ? val - 256 : val;
|
|
45
|
+
|
|
46
|
+
// Parse remaining bytes
|
|
47
|
+
let n = BigInt(val);
|
|
48
|
+
for (let i = pos + 1; i < end; ++i) {
|
|
49
|
+
n = n * BigInt(256) + BigInt(buf[i]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return n;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Parse an ASCII string from the DER-encoded buffer
|
|
56
|
+
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-basic-types#boolean
|
|
57
|
+
export function parseStringASCII(buf: Uint8Array): string {
|
|
58
|
+
return Uint8ArrayToString(buf);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Parse a Date from the DER-encoded buffer
|
|
62
|
+
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.5.1
|
|
63
|
+
export function parseTime(buf: Uint8Array, shortYear: boolean): Date {
|
|
64
|
+
const timeStr = parseStringASCII(buf);
|
|
65
|
+
|
|
66
|
+
// Parse the time string into matches - captured groups start at index 1
|
|
67
|
+
const m = shortYear
|
|
68
|
+
? RE_TIME_SHORT_YEAR.exec(timeStr)
|
|
69
|
+
: RE_TIME_LONG_YEAR.exec(timeStr);
|
|
70
|
+
|
|
71
|
+
if (!m) {
|
|
72
|
+
throw new Error("invalid time");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Translate dates with a 2-digit year to 4 digits per the spec
|
|
76
|
+
if (shortYear) {
|
|
77
|
+
let year = Number(m[1]);
|
|
78
|
+
year += year >= 50 ? 1900 : 2000;
|
|
79
|
+
m[1] = year.toString();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Translate to ISO8601 format and parse
|
|
83
|
+
return new Date(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}Z`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Parse an OID from the DER-encoded buffer
|
|
87
|
+
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier
|
|
88
|
+
export function parseOID(buf: Uint8Array): string {
|
|
89
|
+
let pos = 0;
|
|
90
|
+
const end = buf.length;
|
|
91
|
+
|
|
92
|
+
// Consume first byte which encodes the first two OID components
|
|
93
|
+
let n = buf[pos++];
|
|
94
|
+
const first = Math.floor(n / 40);
|
|
95
|
+
const second = n % 40;
|
|
96
|
+
|
|
97
|
+
let oid = `${first}.${second}`;
|
|
98
|
+
|
|
99
|
+
// Consume remaining bytes
|
|
100
|
+
let val = 0;
|
|
101
|
+
for (; pos < end; ++pos) {
|
|
102
|
+
n = buf[pos];
|
|
103
|
+
val = (val << 7) + (n & 0x7f);
|
|
104
|
+
|
|
105
|
+
// If the left-most bit is NOT set, then this is the last byte in the
|
|
106
|
+
// sequence and we can add the value to the OID and reset the accumulator
|
|
107
|
+
if ((n & 0x80) === 0) {
|
|
108
|
+
oid += `.${val}`;
|
|
109
|
+
val = 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return oid;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Parse a boolean from the DER-encoded buffer
|
|
117
|
+
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-basic-types#boolean
|
|
118
|
+
export function parseBoolean(buf: Uint8Array): boolean {
|
|
119
|
+
return buf[0] !== 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Parse a bit string from the DER-encoded buffer
|
|
123
|
+
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-bit-string
|
|
124
|
+
export function parseBitString(buf: Uint8Array): number[] {
|
|
125
|
+
// First byte tell us how many unused bits are in the last byte
|
|
126
|
+
const unused = buf[0];
|
|
127
|
+
|
|
128
|
+
const start = 1;
|
|
129
|
+
const end = buf.length;
|
|
130
|
+
const bits = [];
|
|
131
|
+
|
|
132
|
+
for (let i = start; i < end; ++i) {
|
|
133
|
+
const byte = buf[i];
|
|
134
|
+
|
|
135
|
+
// The skip value is only used for the last byte
|
|
136
|
+
const skip = i === end - 1 ? unused : 0;
|
|
137
|
+
|
|
138
|
+
// Iterate over each bit in the byte (most significant first)
|
|
139
|
+
for (let j = 7; j >= skip; --j) {
|
|
140
|
+
// Read the bit and add it to the bit string
|
|
141
|
+
bits.push((byte >> j) & 0x01);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return bits;
|
|
146
|
+
}
|
package/src/asn1/tag.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2023 The Sigstore Authors.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { ASN1ParseError } from "./error.js";
|
|
17
|
+
|
|
18
|
+
const UNIVERSAL_TAG = {
|
|
19
|
+
BOOLEAN: 0x01,
|
|
20
|
+
INTEGER: 0x02,
|
|
21
|
+
BIT_STRING: 0x03,
|
|
22
|
+
OCTET_STRING: 0x04,
|
|
23
|
+
OBJECT_IDENTIFIER: 0x06,
|
|
24
|
+
SEQUENCE: 0x10,
|
|
25
|
+
SET: 0x11,
|
|
26
|
+
PRINTABLE_STRING: 0x13,
|
|
27
|
+
UTC_TIME: 0x17,
|
|
28
|
+
GENERALIZED_TIME: 0x18,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const TAG_CLASS = {
|
|
32
|
+
UNIVERSAL: 0x00,
|
|
33
|
+
APPLICATION: 0x01,
|
|
34
|
+
CONTEXT_SPECIFIC: 0x02,
|
|
35
|
+
PRIVATE: 0x03,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-encoded-tag-bytes
|
|
39
|
+
export class ASN1Tag {
|
|
40
|
+
readonly number: number;
|
|
41
|
+
readonly constructed: boolean;
|
|
42
|
+
readonly class: number;
|
|
43
|
+
|
|
44
|
+
constructor(enc: number) {
|
|
45
|
+
// Bits 0 through 4 are the tag number
|
|
46
|
+
this.number = enc & 0x1f;
|
|
47
|
+
|
|
48
|
+
// Bit 5 is the constructed bit
|
|
49
|
+
this.constructed = (enc & 0x20) === 0x20;
|
|
50
|
+
|
|
51
|
+
// Bit 6 & 7 are the class
|
|
52
|
+
this.class = enc >> 6;
|
|
53
|
+
|
|
54
|
+
if (this.number === 0x1f) {
|
|
55
|
+
throw new ASN1ParseError("long form tags not supported");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (this.class === TAG_CLASS.UNIVERSAL && this.number === 0x00) {
|
|
59
|
+
throw new ASN1ParseError("unsupported tag 0x00");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public isUniversal(): boolean {
|
|
64
|
+
return this.class === TAG_CLASS.UNIVERSAL;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public isContextSpecific(num?: number): boolean {
|
|
68
|
+
const res = this.class === TAG_CLASS.CONTEXT_SPECIFIC;
|
|
69
|
+
return num !== undefined ? res && this.number === num : res;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public isBoolean(): boolean {
|
|
73
|
+
return this.isUniversal() && this.number === UNIVERSAL_TAG.BOOLEAN;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public isInteger(): boolean {
|
|
77
|
+
return this.isUniversal() && this.number === UNIVERSAL_TAG.INTEGER;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public isBitString(): boolean {
|
|
81
|
+
return this.isUniversal() && this.number === UNIVERSAL_TAG.BIT_STRING;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public isOctetString(): boolean {
|
|
85
|
+
return this.isUniversal() && this.number === UNIVERSAL_TAG.OCTET_STRING;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public isOID(): boolean {
|
|
89
|
+
return (
|
|
90
|
+
this.isUniversal() && this.number === UNIVERSAL_TAG.OBJECT_IDENTIFIER
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public isUTCTime(): boolean {
|
|
95
|
+
return this.isUniversal() && this.number === UNIVERSAL_TAG.UTC_TIME;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public isGeneralizedTime(): boolean {
|
|
99
|
+
return this.isUniversal() && this.number === UNIVERSAL_TAG.GENERALIZED_TIME;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public toDER(): number {
|
|
103
|
+
return this.number | (this.constructed ? 0x20 : 0x00) | (this.class << 6);
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/bundle.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
type SigstoreBundleBase = {
|
|
2
|
+
mediaType: string;
|
|
3
|
+
verificationMaterial: VerificationMaterial;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type SigstoreBundle =
|
|
7
|
+
| (SigstoreBundleBase & { messageSignature: MessageSignature; dsseEnvelope?: never })
|
|
8
|
+
| (SigstoreBundleBase & { messageSignature?: never; dsseEnvelope: DSSEEnvelope });
|
|
9
|
+
|
|
10
|
+
export interface VerificationMaterial {
|
|
11
|
+
certificate?: Certificate;
|
|
12
|
+
x509CertificateChain?: X509CertificateChain;
|
|
13
|
+
tlogEntries: TLogEntry[];
|
|
14
|
+
timestampVerificationData?: TimestampVerificationData;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Certificate {
|
|
18
|
+
rawBytes: string; // Base64-encoded certificate bytes
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface X509CertificateChain {
|
|
22
|
+
certificates: Certificate[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TLogEntry {
|
|
26
|
+
logIndex: string;
|
|
27
|
+
logId: LogId;
|
|
28
|
+
kindVersion: KindVersion;
|
|
29
|
+
integratedTime: string | null; // UNIX timestamp (can be null for Rekor v2 bundles)
|
|
30
|
+
inclusionPromise?: InclusionPromise;
|
|
31
|
+
inclusionProof?: InclusionProof;
|
|
32
|
+
canonicalizedBody: string; // Base64-encoded JSON body of the log entry
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LogId {
|
|
36
|
+
keyId: string; // Base64-encoded key ID
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface KindVersion {
|
|
40
|
+
kind: string;
|
|
41
|
+
version: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface InclusionPromise {
|
|
45
|
+
signedEntryTimestamp: string; // Base64-encoded signature over the entry
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface InclusionProof {
|
|
49
|
+
logIndex: string;
|
|
50
|
+
rootHash: string; // Base64-encoded root hash of the Merkle tree
|
|
51
|
+
treeSize: string; // Number of entries in the Merkle tree
|
|
52
|
+
hashes: string[]; // Base64-encoded sibling hashes in the Merkle tree
|
|
53
|
+
checkpoint: Checkpoint;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface Checkpoint {
|
|
57
|
+
envelope: string; // Signed envelope from the transparency log
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MessageSignature {
|
|
61
|
+
messageDigest: MessageDigest;
|
|
62
|
+
signature: string; // Base64-encoded signature over the message digest
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MessageDigest {
|
|
66
|
+
algorithm: string; // Hashing algorithm, e.g., "SHA2_256"
|
|
67
|
+
digest: string; // Base64-encoded message digest
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface DSSEEnvelope {
|
|
71
|
+
payload: string;
|
|
72
|
+
payloadType: string;
|
|
73
|
+
signatures: DSSESignature[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface DSSESignature {
|
|
77
|
+
sig: string;
|
|
78
|
+
keyid?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface TimestampVerificationData {
|
|
82
|
+
rfc3161Timestamps: RFC3161Timestamp[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface RFC3161Timestamp {
|
|
86
|
+
signedTimestamp: string; // Base64-encoded RFC3161 SignedData
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface InTotoStatement {
|
|
90
|
+
_type: string;
|
|
91
|
+
subject: InTotoSubject[];
|
|
92
|
+
predicateType: string;
|
|
93
|
+
predicate: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface InTotoSubject {
|
|
97
|
+
name: string;
|
|
98
|
+
digest: Record<string, string>;
|
|
99
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// From https://github.com/theupdateframework/tuf-js/blob/38d537ea883e8bb38ee6ab17b5f59ee479d0eab2/packages/canonical-json/lib/index.js
|
|
2
|
+
|
|
3
|
+
const COMMA = ",";
|
|
4
|
+
const COLON = ":";
|
|
5
|
+
const LEFT_SQUARE_BRACKET = "[";
|
|
6
|
+
const RIGHT_SQUARE_BRACKET = "]";
|
|
7
|
+
const LEFT_CURLY_BRACKET = "{";
|
|
8
|
+
const RIGHT_CURLY_BRACKET = "}";
|
|
9
|
+
|
|
10
|
+
function canonicalizeString(string: string): string {
|
|
11
|
+
const escapedString = string.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
12
|
+
return '"' + escapedString + '"';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Recursively encodes the supplied object according to the canonical JSON form
|
|
16
|
+
// as specified at http://wiki.laptop.org/go/Canonical_JSON. It's a restricted
|
|
17
|
+
// dialect of JSON in which keys are lexically sorted, floats are not allowed,
|
|
18
|
+
// and only double quotes and backslashes are escaped.
|
|
19
|
+
export function canonicalize(object: object): string {
|
|
20
|
+
const buffer: string[] = [];
|
|
21
|
+
if (typeof object === "string") {
|
|
22
|
+
buffer.push(canonicalizeString(object));
|
|
23
|
+
} else if (typeof object === "boolean") {
|
|
24
|
+
buffer.push(JSON.stringify(object));
|
|
25
|
+
} else if (Number.isInteger(object)) {
|
|
26
|
+
buffer.push(JSON.stringify(object));
|
|
27
|
+
} else if (object === null) {
|
|
28
|
+
buffer.push(JSON.stringify(object));
|
|
29
|
+
} else if (Array.isArray(object)) {
|
|
30
|
+
buffer.push(LEFT_SQUARE_BRACKET);
|
|
31
|
+
let first = true;
|
|
32
|
+
object.forEach((element) => {
|
|
33
|
+
if (!first) {
|
|
34
|
+
buffer.push(COMMA);
|
|
35
|
+
}
|
|
36
|
+
first = false;
|
|
37
|
+
buffer.push(canonicalize(element));
|
|
38
|
+
});
|
|
39
|
+
buffer.push(RIGHT_SQUARE_BRACKET);
|
|
40
|
+
} else if (typeof object === "object") {
|
|
41
|
+
buffer.push(LEFT_CURLY_BRACKET);
|
|
42
|
+
let first = true;
|
|
43
|
+
Object.keys(object)
|
|
44
|
+
.sort()
|
|
45
|
+
.forEach((property) => {
|
|
46
|
+
if (!first) {
|
|
47
|
+
buffer.push(COMMA);
|
|
48
|
+
}
|
|
49
|
+
first = false;
|
|
50
|
+
buffer.push(canonicalizeString(property));
|
|
51
|
+
buffer.push(COLON);
|
|
52
|
+
// eslint-disable-next-line
|
|
53
|
+
buffer.push(canonicalize((object as any)[property])); // 'any' as a fallback
|
|
54
|
+
});
|
|
55
|
+
buffer.push(RIGHT_CURLY_BRACKET);
|
|
56
|
+
} else {
|
|
57
|
+
throw new TypeError("cannot encode " + object);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return buffer.join("");
|
|
61
|
+
}
|