@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/encoding.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
export function toArrayBuffer(view: ArrayBufferView): ArrayBuffer {
|
|
17
|
+
const { byteLength } = view;
|
|
18
|
+
|
|
19
|
+
if (byteLength === 0) {
|
|
20
|
+
return new ArrayBuffer(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { buffer, byteOffset } = view;
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
buffer instanceof ArrayBuffer &&
|
|
27
|
+
byteOffset === 0 &&
|
|
28
|
+
byteLength === buffer.byteLength
|
|
29
|
+
) {
|
|
30
|
+
return buffer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const clone = new Uint8Array(byteLength);
|
|
34
|
+
clone.set(new Uint8Array(buffer, byteOffset, byteLength));
|
|
35
|
+
return clone.buffer;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function base64ToUint8Array(base64: string): Uint8Array {
|
|
39
|
+
const binaryString = atob(base64);
|
|
40
|
+
const length = binaryString.length;
|
|
41
|
+
const bytes = new Uint8Array(length);
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < length; i++) {
|
|
44
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return bytes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function base64UrlToUint8Array(base64url: string): Uint8Array {
|
|
51
|
+
let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
|
|
52
|
+
while (base64.length % 4 !== 0) {
|
|
53
|
+
base64 += '=';
|
|
54
|
+
}
|
|
55
|
+
return base64ToUint8Array(base64);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function Uint8ArrayToBase64(uint8Array: Uint8Array): string {
|
|
59
|
+
let binaryString = "";
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < uint8Array.length; i++) {
|
|
62
|
+
binaryString += String.fromCharCode(uint8Array[i]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return btoa(binaryString);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function hexToUint8Array(hex: string): Uint8Array {
|
|
69
|
+
if (hex.length % 2 !== 0) {
|
|
70
|
+
throw new Error("Hex string must have an even length");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const length = hex.length / 2;
|
|
74
|
+
const uint8Array = new Uint8Array(length);
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < length; i++) {
|
|
77
|
+
uint8Array[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return uint8Array;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function Uint8ArrayToHex(uint8Array: Uint8Array): string {
|
|
84
|
+
let hexString = "";
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < uint8Array.length; i++) {
|
|
87
|
+
let hex = uint8Array[i].toString(16);
|
|
88
|
+
if (hex.length === 1) {
|
|
89
|
+
hex = "0" + hex;
|
|
90
|
+
}
|
|
91
|
+
hexString += hex;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return hexString;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function stringToUint8Array(str: string): Uint8Array {
|
|
98
|
+
// Defaults to utf-8, but utf-8 is ascii compatible
|
|
99
|
+
const encoder = new TextEncoder();
|
|
100
|
+
return encoder.encode(str);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function Uint8ArrayToString(uint8Array: Uint8Array): string {
|
|
104
|
+
const decoder = new TextDecoder("utf-8");
|
|
105
|
+
return decoder.decode(uint8Array);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function readBigInt64BE(
|
|
109
|
+
uint8Array: Uint8Array,
|
|
110
|
+
offset?: number,
|
|
111
|
+
): bigint {
|
|
112
|
+
if (offset === undefined) {
|
|
113
|
+
offset = 0;
|
|
114
|
+
}
|
|
115
|
+
const hex = Uint8ArrayToHex(uint8Array.slice(offset, offset + 8));
|
|
116
|
+
return BigInt(`0x${hex}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function base64Encode(str: string): string {
|
|
120
|
+
return Uint8ArrayToBase64(stringToUint8Array(str));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function base64Decode(str: string): string {
|
|
124
|
+
return Uint8ArrayToString(base64ToUint8Array(str));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Constant-time equality comparison for Uint8Arrays
|
|
129
|
+
* Prevents timing attacks by always comparing all bytes
|
|
130
|
+
*/
|
|
131
|
+
export function uint8ArrayEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
132
|
+
if (a.byteLength !== b.byteLength) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let result = 0;
|
|
137
|
+
for (let i = 0; i < a.byteLength; i++) {
|
|
138
|
+
result |= a[i] ^ b[i];
|
|
139
|
+
}
|
|
140
|
+
return result === 0;
|
|
141
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Base class for all verification errors
|
|
19
|
+
* Provides structured error handling with error codes
|
|
20
|
+
*/
|
|
21
|
+
export class VerificationError extends Error {
|
|
22
|
+
constructor(public code: string, message: string) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'VerificationError';
|
|
25
|
+
Object.setPrototypeOf(this, VerificationError.prototype);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown when timestamp verification fails
|
|
31
|
+
*/
|
|
32
|
+
export class TimestampError extends VerificationError {
|
|
33
|
+
constructor(message: string) {
|
|
34
|
+
super('TIMESTAMP_ERROR', message);
|
|
35
|
+
this.name = 'TimestampError';
|
|
36
|
+
Object.setPrototypeOf(this, TimestampError.prototype);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Error thrown when certificate verification fails
|
|
42
|
+
*/
|
|
43
|
+
export class CertificateError extends VerificationError {
|
|
44
|
+
constructor(message: string) {
|
|
45
|
+
super('CERTIFICATE_ERROR', message);
|
|
46
|
+
this.name = 'CertificateError';
|
|
47
|
+
Object.setPrototypeOf(this, CertificateError.prototype);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Error thrown when transparency log verification fails
|
|
53
|
+
*/
|
|
54
|
+
export class TLogError extends VerificationError {
|
|
55
|
+
constructor(message: string) {
|
|
56
|
+
super('TLOG_ERROR', message);
|
|
57
|
+
this.name = 'TLogError';
|
|
58
|
+
Object.setPrototypeOf(this, TLogError.prototype);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Error thrown when signature verification fails
|
|
64
|
+
*/
|
|
65
|
+
export class SignatureError extends VerificationError {
|
|
66
|
+
constructor(message: string) {
|
|
67
|
+
super('SIGNATURE_ERROR', message);
|
|
68
|
+
this.name = 'SignatureError';
|
|
69
|
+
Object.setPrototypeOf(this, SignatureError.prototype);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Error thrown when policy verification fails
|
|
75
|
+
*/
|
|
76
|
+
export class PolicyError extends VerificationError {
|
|
77
|
+
constructor(message: string) {
|
|
78
|
+
super('POLICY_ERROR', message);
|
|
79
|
+
this.name = 'PolicyError';
|
|
80
|
+
Object.setPrototypeOf(this, PolicyError.prototype);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { SigstoreVerifier } from "./sigstore.js";
|
|
3
|
+
|
|
4
|
+
describe("Sigstore Browser Integration Tests", () => {
|
|
5
|
+
it("should initialize SigstoreVerifier in browser", () => {
|
|
6
|
+
const verifier = new SigstoreVerifier();
|
|
7
|
+
expect(verifier).toBeDefined();
|
|
8
|
+
expect(verifier).toBeInstanceOf(SigstoreVerifier);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should have crypto.subtle available for ECDSA P-256 verification", async () => {
|
|
12
|
+
expect(globalThis.crypto).toBeDefined();
|
|
13
|
+
expect(globalThis.crypto.subtle).toBeDefined();
|
|
14
|
+
|
|
15
|
+
// Test ECDSA P-256 which is what Sigstore uses
|
|
16
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
17
|
+
{
|
|
18
|
+
name: "ECDSA",
|
|
19
|
+
namedCurve: "P-256",
|
|
20
|
+
},
|
|
21
|
+
true,
|
|
22
|
+
["sign", "verify"]
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const data = new TextEncoder().encode("test");
|
|
26
|
+
const signature = await crypto.subtle.sign(
|
|
27
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
28
|
+
keyPair.privateKey,
|
|
29
|
+
data
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const valid = await crypto.subtle.verify(
|
|
33
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
34
|
+
keyPair.publicKey,
|
|
35
|
+
signature,
|
|
36
|
+
data
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(valid).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should have crypto.subtle available for ECDSA P-384 verification", async () => {
|
|
43
|
+
// Test ECDSA P-384 which is also used by Sigstore
|
|
44
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
45
|
+
{
|
|
46
|
+
name: "ECDSA",
|
|
47
|
+
namedCurve: "P-384",
|
|
48
|
+
},
|
|
49
|
+
true,
|
|
50
|
+
["sign", "verify"]
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const data = new TextEncoder().encode("test");
|
|
54
|
+
const signature = await crypto.subtle.sign(
|
|
55
|
+
{ name: "ECDSA", hash: "SHA-384" },
|
|
56
|
+
keyPair.privateKey,
|
|
57
|
+
data
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const valid = await crypto.subtle.verify(
|
|
61
|
+
{ name: "ECDSA", hash: "SHA-384" },
|
|
62
|
+
keyPair.publicKey,
|
|
63
|
+
signature,
|
|
64
|
+
data
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(valid).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should support SHA-256 hashing in browser", async () => {
|
|
71
|
+
const data = new TextEncoder().encode("test data");
|
|
72
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
73
|
+
|
|
74
|
+
expect(hash).toBeInstanceOf(ArrayBuffer);
|
|
75
|
+
expect(hash.byteLength).toBe(32);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should support SHA-384 hashing in browser", async () => {
|
|
79
|
+
const data = new TextEncoder().encode("test data");
|
|
80
|
+
const hash = await crypto.subtle.digest("SHA-384", data);
|
|
81
|
+
|
|
82
|
+
expect(hash).toBeInstanceOf(ArrayBuffer);
|
|
83
|
+
expect(hash.byteLength).toBe(48);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should support SHA-512 hashing in browser", async () => {
|
|
87
|
+
const data = new TextEncoder().encode("test data");
|
|
88
|
+
const hash = await crypto.subtle.digest("SHA-512", data);
|
|
89
|
+
|
|
90
|
+
expect(hash).toBeInstanceOf(ArrayBuffer);
|
|
91
|
+
expect(hash.byteLength).toBe(64);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should support X.509 certificate key import (SPKI format)", async () => {
|
|
95
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
96
|
+
{
|
|
97
|
+
name: "ECDSA",
|
|
98
|
+
namedCurve: "P-256",
|
|
99
|
+
},
|
|
100
|
+
true,
|
|
101
|
+
["sign", "verify"]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const exported = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
|
105
|
+
const imported = await crypto.subtle.importKey(
|
|
106
|
+
"spki",
|
|
107
|
+
exported,
|
|
108
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
109
|
+
true,
|
|
110
|
+
["verify"]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(imported).toBeDefined();
|
|
114
|
+
expect(imported.type).toBe("public");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should support RSA-PSS for signature verification", async () => {
|
|
118
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
119
|
+
{
|
|
120
|
+
name: "RSA-PSS",
|
|
121
|
+
modulusLength: 2048,
|
|
122
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
123
|
+
hash: "SHA-256",
|
|
124
|
+
},
|
|
125
|
+
true,
|
|
126
|
+
["sign", "verify"]
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const data = new TextEncoder().encode("test");
|
|
130
|
+
const signature = await crypto.subtle.sign(
|
|
131
|
+
{
|
|
132
|
+
name: "RSA-PSS",
|
|
133
|
+
saltLength: 32,
|
|
134
|
+
},
|
|
135
|
+
keyPair.privateKey,
|
|
136
|
+
data
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const valid = await crypto.subtle.verify(
|
|
140
|
+
{
|
|
141
|
+
name: "RSA-PSS",
|
|
142
|
+
saltLength: 32,
|
|
143
|
+
},
|
|
144
|
+
keyPair.publicKey,
|
|
145
|
+
signature,
|
|
146
|
+
data
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(valid).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should support TextEncoder/TextDecoder for UTF-8", () => {
|
|
153
|
+
const encoder = new TextEncoder();
|
|
154
|
+
const decoder = new TextDecoder();
|
|
155
|
+
|
|
156
|
+
const text = "Hello Sigstore 🔐";
|
|
157
|
+
const encoded = encoder.encode(text);
|
|
158
|
+
const decoded = decoder.decode(encoded);
|
|
159
|
+
|
|
160
|
+
expect(decoded).toBe(text);
|
|
161
|
+
expect(encoded).toBeInstanceOf(Uint8Array);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should support base64 encoding/decoding (atob/btoa)", () => {
|
|
165
|
+
const text = "Hello Sigstore";
|
|
166
|
+
const base64 = btoa(text);
|
|
167
|
+
const decoded = atob(base64);
|
|
168
|
+
|
|
169
|
+
expect(decoded).toBe(text);
|
|
170
|
+
expect(base64).toBe("SGVsbG8gU2lnc3RvcmU=");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should support Uint8Array operations", () => {
|
|
174
|
+
const arr1 = new Uint8Array([1, 2, 3, 4, 5]);
|
|
175
|
+
const arr2 = new Uint8Array([6, 7, 8, 9, 10]);
|
|
176
|
+
|
|
177
|
+
const combined = new Uint8Array(arr1.length + arr2.length);
|
|
178
|
+
combined.set(arr1, 0);
|
|
179
|
+
combined.set(arr2, arr1.length);
|
|
180
|
+
|
|
181
|
+
expect(combined.length).toBe(10);
|
|
182
|
+
expect(combined[0]).toBe(1);
|
|
183
|
+
expect(combined[5]).toBe(6);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should support ArrayBuffer operations", () => {
|
|
187
|
+
const buffer = new ArrayBuffer(32);
|
|
188
|
+
const view = new Uint8Array(buffer);
|
|
189
|
+
|
|
190
|
+
view[0] = 255;
|
|
191
|
+
view[31] = 128;
|
|
192
|
+
|
|
193
|
+
expect(buffer.byteLength).toBe(32);
|
|
194
|
+
expect(view[0]).toBe(255);
|
|
195
|
+
expect(view[31]).toBe(128);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should support DataView for byte manipulation", () => {
|
|
199
|
+
const buffer = new ArrayBuffer(8);
|
|
200
|
+
const view = new DataView(buffer);
|
|
201
|
+
|
|
202
|
+
view.setUint32(0, 0x12345678, false); // big-endian
|
|
203
|
+
view.setUint32(4, 0x9abcdef0, false);
|
|
204
|
+
|
|
205
|
+
expect(view.getUint32(0, false)).toBe(0x12345678);
|
|
206
|
+
expect(view.getUint8(0)).toBe(0x12);
|
|
207
|
+
expect(view.getUint8(1)).toBe(0x34);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should support JSON parsing of complex structures", () => {
|
|
211
|
+
const json = '{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.3","verificationMaterial":{"tlogEntries":[{"logIndex":"123"}]}}';
|
|
212
|
+
const parsed = JSON.parse(json);
|
|
213
|
+
|
|
214
|
+
expect(parsed.mediaType).toBeDefined();
|
|
215
|
+
expect(parsed.verificationMaterial).toBeDefined();
|
|
216
|
+
expect(parsed.verificationMaterial.tlogEntries).toBeInstanceOf(Array);
|
|
217
|
+
expect(parsed.verificationMaterial.tlogEntries[0].logIndex).toBe("123");
|
|
218
|
+
});
|
|
219
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { SigstoreVerifier } from "./sigstore.js";
|
|
2
|
+
export type { SigstoreVerifierOptions } from "./sigstore.js";
|
|
3
|
+
export type { SigstoreBundle, TLogEntry, VerificationMaterial, MessageSignature, TimestampVerificationData, RFC3161Timestamp } from "./bundle.js";
|
|
4
|
+
export type { TrustedRoot, Sigstore, RawTimestampAuthority } from "./interfaces.js";
|
|
5
|
+
export {
|
|
6
|
+
VerificationError,
|
|
7
|
+
TimestampError,
|
|
8
|
+
CertificateError,
|
|
9
|
+
TLogError,
|
|
10
|
+
SignatureError,
|
|
11
|
+
PolicyError,
|
|
12
|
+
} from "./errors.js";
|
|
13
|
+
export { verifyRFC3161Timestamp, verifyBundleTimestamp } from "./timestamp/tsa.js";
|
|
14
|
+
export { TrustedRootProvider, type TrustedRootProviderOptions } from "./trust/tuf.js";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { X509Certificate } from "./x509/index.js";
|
|
2
|
+
|
|
3
|
+
export enum SigstoreRoots {
|
|
4
|
+
certificateAuthorities = "certificateAuthorities",
|
|
5
|
+
ctlogs = "ctlogs",
|
|
6
|
+
timestampAuthorities = "timestampAuthorities",
|
|
7
|
+
tlogs = "tlogs",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type RawTimestampAuthorities = RawTimestampAuthority[];
|
|
11
|
+
|
|
12
|
+
export interface TrustedRoot {
|
|
13
|
+
mediaType: string;
|
|
14
|
+
tlogs: RawLogs;
|
|
15
|
+
certificateAuthorities: RawCAs;
|
|
16
|
+
ctlogs: RawLogs;
|
|
17
|
+
timestampAuthorities: RawTimestampAuthorities;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RawTimestampAuthority {
|
|
21
|
+
subject: {
|
|
22
|
+
organization: string;
|
|
23
|
+
commonName: string;
|
|
24
|
+
};
|
|
25
|
+
certChain: {
|
|
26
|
+
certificates: {
|
|
27
|
+
rawBytes: string;
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
validFor: {
|
|
31
|
+
start: string;
|
|
32
|
+
end?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CTLog {
|
|
37
|
+
logID: Uint8Array;
|
|
38
|
+
publicKey: CryptoKey;
|
|
39
|
+
validFor: {
|
|
40
|
+
start: Date;
|
|
41
|
+
end: Date;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RekorKeyInfo {
|
|
46
|
+
publicKey: CryptoKey;
|
|
47
|
+
logId: Uint8Array;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CertAuthority {
|
|
51
|
+
certChain: X509Certificate[];
|
|
52
|
+
validFor: {
|
|
53
|
+
start: Date;
|
|
54
|
+
end: Date;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface Sigstore {
|
|
59
|
+
rekor: RekorKeyInfo | undefined;
|
|
60
|
+
ctlogs: CTLog[];
|
|
61
|
+
certificateAuthorities: CertAuthority[];
|
|
62
|
+
timestampAuthorities: CertAuthority[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface RawLog {
|
|
66
|
+
baseUrl: string;
|
|
67
|
+
hashAlgorithm: string;
|
|
68
|
+
publicKey: {
|
|
69
|
+
rawBytes: string;
|
|
70
|
+
keyDetails: string;
|
|
71
|
+
validFor: {
|
|
72
|
+
start: string;
|
|
73
|
+
end?: string;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
logId: {
|
|
77
|
+
keyId: string;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type RawLogs = RawLog[];
|
|
82
|
+
|
|
83
|
+
export interface RawCA {
|
|
84
|
+
subject: {
|
|
85
|
+
organization: string;
|
|
86
|
+
commonName: string;
|
|
87
|
+
};
|
|
88
|
+
uri: string;
|
|
89
|
+
certChain: {
|
|
90
|
+
certificates: {
|
|
91
|
+
rawBytes: string;
|
|
92
|
+
}[];
|
|
93
|
+
};
|
|
94
|
+
validFor: {
|
|
95
|
+
start: string;
|
|
96
|
+
end?: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type RawCAs = RawCA[];
|
|
101
|
+
|
|
102
|
+
export enum KeyTypes {
|
|
103
|
+
Ecdsa = "ECDSA",
|
|
104
|
+
Ed25519 = "Ed25519",
|
|
105
|
+
RSA = "RSA",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export enum EcdsaTypes {
|
|
109
|
+
P256 = "P-256",
|
|
110
|
+
P384 = "P-384",
|
|
111
|
+
P521 = "P-521",
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export enum HashAlgorithms {
|
|
115
|
+
SHA256 = "SHA-256",
|
|
116
|
+
SHA384 = "SHA-384",
|
|
117
|
+
SHA512 = "SHA-512",
|
|
118
|
+
}
|
package/src/oid.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// From https://github.com/sigstore/sigstore-js/blob/main/packages/core/src/oid.ts
|
|
2
|
+
|
|
3
|
+
export const ECDSA_SIGNATURE_ALGOS: Record<string, string> = {
|
|
4
|
+
"1.2.840.10045.4.3.1": "sha224",
|
|
5
|
+
"1.2.840.10045.4.3.2": "sha256",
|
|
6
|
+
"1.2.840.10045.4.3.3": "sha384",
|
|
7
|
+
"1.2.840.10045.4.3.4": "sha512",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// RSA signature algorithms
|
|
11
|
+
export const RSA_SIGNATURE_ALGOS: Record<string, string> = {
|
|
12
|
+
"1.2.840.113549.1.1.11": "sha256", // sha256WithRSAEncryption
|
|
13
|
+
"1.2.840.113549.1.1.12": "sha384", // sha384WithRSAEncryption
|
|
14
|
+
"1.2.840.113549.1.1.13": "sha512", // sha512WithRSAEncryption
|
|
15
|
+
"1.2.840.113549.1.1.5": "sha1", // sha1WithRSAEncryption
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SHA2_HASH_ALGOS: Record<string, string> = {
|
|
19
|
+
"2.16.840.1.101.3.4.2.1": "sha256",
|
|
20
|
+
"2.16.840.1.101.3.4.2.2": "sha384",
|
|
21
|
+
"2.16.840.1.101.3.4.2.3": "sha512",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const ECDSA_CURVE_NAMES: Record<string, string> = {
|
|
25
|
+
"1.2.840.10045.3.1.7": "secp256r1",
|
|
26
|
+
"1.3.132.0.34": "secp384r1",
|
|
27
|
+
"1.3.132.0.35": "secp521r1",
|
|
28
|
+
};
|
package/src/pem.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
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 { base64ToUint8Array, Uint8ArrayToBase64 } from "./encoding.js";
|
|
18
|
+
|
|
19
|
+
const PEM_HEADER = /-----BEGIN (.*)-----/;
|
|
20
|
+
const PEM_FOOTER = /-----END (.*)-----/;
|
|
21
|
+
|
|
22
|
+
export function toDER(certificate: string): Uint8Array {
|
|
23
|
+
let der = "";
|
|
24
|
+
|
|
25
|
+
certificate.split("\n").forEach((line) => {
|
|
26
|
+
if (line.match(PEM_HEADER) || line.match(PEM_FOOTER)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
der += line;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return base64ToUint8Array(der);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Translates a DER-encoded buffer into a PEM-encoded string. Standard PEM
|
|
37
|
+
// encoding dictates that each certificate should have a trailing newline after
|
|
38
|
+
// the footer.
|
|
39
|
+
export function fromDER(certificate: Uint8Array, type = "CERTIFICATE"): string {
|
|
40
|
+
// Base64-encode the certificate.
|
|
41
|
+
const der = Uint8ArrayToBase64(certificate);
|
|
42
|
+
// Split the certificate into lines of 64 characters.
|
|
43
|
+
const lines = der.match(/.{1,64}/g) || "";
|
|
44
|
+
|
|
45
|
+
return [`-----BEGIN ${type}-----`, ...lines, `-----END ${type}-----`]
|
|
46
|
+
.join("\n")
|
|
47
|
+
.concat("\n");
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
export class RFC3161TimestampVerificationError extends Error {}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
export { RFC3161Timestamp } from "./timestamp.js";
|