From 79bfd03947a25f4bfb67d1c54893be7c79ec77e2 Mon Sep 17 00:00:00 2001 From: Philip Harrison Date: Tue, 14 Feb 2023 13:39:17 +0000 Subject: [PATCH] feat: audit signatures verifies attestations Update `audit signatures` to also verify Sigstore attestations. Additional changes: - Adding error message to json error output as there are a lot of different failure cases with signature verification that would be hard to debug without this - Adding predicateType to json error output for attestations to diffentiate between provenance and publish attestations References: - Pacote changes: https://github.com/npm/pacote/pull/259 - RFC: https://github.com/npm/rfcs/pull/626 Signed-off-by: Philip Harrison --- lib/commands/audit.js | 84 +++-- package-lock.json | 2 + .../test/lib/commands/audit.js.test.cjs | 56 ++++ test/lib/commands/audit.js | 301 ++++++++++++++++++ .../sigstore/valid-sigstore-attestations.json | 94 ++++++ .../sigstore/valid-tuf-js-attestations.json | 94 ++++++ 6 files changed, 610 insertions(+), 21 deletions(-) create mode 100644 test/lib/fixtures/sigstore/valid-sigstore-attestations.json create mode 100644 test/lib/fixtures/sigstore/valid-tuf-js-attestations.json diff --git a/lib/commands/audit.js b/lib/commands/audit.js index 192b3b9663d6c..05830fff69c2e 100644 --- a/lib/commands/audit.js +++ b/lib/commands/audit.js @@ -24,7 +24,8 @@ class VerifySignatures { this.missing = [] this.checkedPackages = new Set() this.auditedWithKeysCount = 0 - this.verifiedCount = 0 + this.verifiedSignatureCount = 0 + this.verifiedAttestationCount = 0 this.exitCode = 0 } @@ -78,12 +79,25 @@ class VerifySignatures { this.npm.output(timing) this.npm.output('') - if (this.verifiedCount) { - const verifiedBold = this.npm.chalk.bold('verified') - if (this.verifiedCount === 1) { - this.npm.output(`${this.verifiedCount} package has a ${verifiedBold} registry signature`) + const verifiedBold = this.npm.chalk.bold('verified') + if (this.verifiedSignatureCount) { + if (this.verifiedSignatureCount === 1) { + /* eslint-disable-next-line max-len */ + this.npm.output(`${this.verifiedSignatureCount} package has a ${verifiedBold} registry signature`) + } else { + /* eslint-disable-next-line max-len */ + this.npm.output(`${this.verifiedSignatureCount} packages have ${verifiedBold} registry signatures`) + } + this.npm.output('') + } + + if (this.verifiedAttestationCount) { + if (this.verifiedAttestationCount === 1) { + /* eslint-disable-next-line max-len */ + this.npm.output(`${this.verifiedAttestationCount} package has a ${verifiedBold} attestation`) } else { - this.npm.output(`${this.verifiedCount} packages have ${verifiedBold} registry signatures`) + /* eslint-disable-next-line max-len */ + this.npm.output(`${this.verifiedAttestationCount} packages have ${verifiedBold} attestations`) } this.npm.output('') } @@ -110,19 +124,35 @@ class VerifySignatures { const invalidClr = this.npm.chalk.bold(this.npm.chalk.red('invalid')) // We can have either invalid signatures or invalid provenance const invalidSignatures = this.invalid.filter(i => i.code === 'EINTEGRITYSIGNATURE') - if (invalidSignatures.length === 1) { - this.npm.output(`1 package has an ${invalidClr} registry signature:`) - // } else if (invalidSignatures.length > 1) { - } else { - // TODO move this back to an else if once provenance attestation audit is added - /* eslint-disable-next-line max-len */ - this.npm.output(`${invalidSignatures.length} packages have ${invalidClr} registry signatures:`) + if (invalidSignatures.length) { + if (invalidSignatures.length === 1) { + this.npm.output(`1 package has an ${invalidClr} registry signature:`) + } else { + /* eslint-disable-next-line max-len */ + this.npm.output(`${invalidSignatures.length} packages have ${invalidClr} registry signatures:`) + } + this.npm.output('') + invalidSignatures.map(i => + this.npm.output(`${this.npm.chalk.red(`${i.name}@${i.version}`)} (${i.registry})`) + ) + this.npm.output('') } - this.npm.output('') - invalidSignatures.map(i => - this.npm.output(`${this.npm.chalk.red(`${i.name}@${i.version}`)} (${i.registry})`) - ) - this.npm.output('') + + const invalidAttestations = this.invalid.filter(i => i.code === 'EATTESTATIONVERIFY') + if (invalidAttestations.length) { + if (invalidAttestations.length === 1) { + this.npm.output(`1 package has an ${invalidClr} attestation:`) + } else { + /* eslint-disable-next-line max-len */ + this.npm.output(`${invalidAttestations.length} packages have ${invalidClr} attestations:`) + } + this.npm.output('') + invalidAttestations.map(i => + this.npm.output(`${this.npm.chalk.red(`${i.name}@${i.version}`)} (${i.registry})`) + ) + this.npm.output('') + } + if (invalid.length === 1) { /* eslint-disable-next-line max-len */ this.npm.output(`Someone might have tampered with this package since it was published on the registry!`) @@ -252,9 +282,11 @@ class VerifySignatures { const { _integrity: integrity, _signatures, + _attestations, _resolved: resolved, } = await pacote.manifest(`${name}@${version}`, { verifySignatures: true, + verifyAttestations: true, ...this.buildRegistryConfig(registry), ...this.npm.flatOptions, }) @@ -262,6 +294,7 @@ class VerifySignatures { const result = { integrity, signatures, + attestations: _attestations, resolved, } return result @@ -287,14 +320,14 @@ class VerifySignatures { } try { - const { integrity, signatures, resolved } = await this.verifySignatures( + const { integrity, signatures, attestations, resolved } = await this.verifySignatures( name, version, registry ) // Currently we only care about missing signatures on registries that provide a public key // We could make this configurable in the future with a strict/paranoid mode if (signatures.length) { - this.verifiedCount += 1 + this.verifiedSignatureCount += 1 } else if (keys.length) { this.missing.push({ integrity, @@ -305,10 +338,18 @@ class VerifySignatures { version, }) } + + // Track verified attestations separately to registry signatures, as all + // packages on registries with signing keys are expected to have registry + // signatures, but not all packages have provenance and publish attestations. + if (attestations) { + this.verifiedAttestationCount += 1 + } } catch (e) { - if (e.code === 'EINTEGRITYSIGNATURE') { + if (e.code === 'EINTEGRITYSIGNATURE' || e.code === 'EATTESTATIONVERIFY') { this.invalid.push({ code: e.code, + message: e.message, integrity: e.integrity, keyid: e.keyid, location, @@ -316,6 +357,7 @@ class VerifySignatures { registry, resolved: e.resolved, signature: e.signature, + predicateType: e.predicateType, type, version, }) diff --git a/package-lock.json b/package-lock.json index 7cd605442ce6a..794cb3cab661e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11143,6 +11143,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.0.0.tgz", "integrity": "sha512-e+qfbn/zf1+rCza/BhIA//Awmf0v1pa5HQS8Xk8iXrn9bgytytVLqYD0P7NSqZ6IELTgq+tcDvLPkQjNHyWLNg==", + "inBundle": true, "dependencies": { "make-fetch-happen": "^11.0.1", "tuf-js": "^1.0.0" @@ -14066,6 +14067,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.0.0.tgz", "integrity": "sha512-1dxsQwESDzACJjTdYHQ4wJ1f/of7jALWKfJEHSBWUQB/5UTJUx9SW6GHXp4mZ1KvdBRJCpGjssoPFGi4hvw8/A==", + "inBundle": true, "dependencies": { "make-fetch-happen": "^11.0.1", "minimatch": "^6.1.0" diff --git a/tap-snapshots/test/lib/commands/audit.js.test.cjs b/tap-snapshots/test/lib/commands/audit.js.test.cjs index c95e30b26783d..4fec8f86c5baa 100644 --- a/tap-snapshots/test/lib/commands/audit.js.test.cjs +++ b/tap-snapshots/test/lib/commands/audit.js.test.cjs @@ -53,6 +53,7 @@ exports[`test/lib/commands/audit.js TAP audit signatures json output with invali "invalid": [ { "code": "EINTEGRITYSIGNATURE", + "message": "kms-demo@1.0.0 has an invalid registry signature with keyid: SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA and signature: bogus", "integrity": "sha512-QqZ7VJ/8xPkS9s2IWB7Shj3qTJdcRyeXKbPQnsZjsPEwvutGv0EGeVchPcauoiDFJlGbZMFq5GDCurAGNSghJQ==", "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA", "location": "node_modules/kms-demo", @@ -76,11 +77,34 @@ exports[`test/lib/commands/audit.js TAP audit signatures json output with invali } ` +exports[`test/lib/commands/audit.js TAP audit signatures json output with invalid attestations > must match snapshot 1`] = ` +{ + "invalid": [ + { + "code": "EATTESTATIONVERIFY", + "message": "sigstore@1.0.0 failed to verify attestation: artifact signature verification failed", + "integrity": "sha512-e+qfbn/zf1+rCza/BhIA//Awmf0v1pa5HQS8Xk8iXrn9bgytytVLqYD0P7NSqZ6IELTgq+tcDvLPkQjNHyWLNg==", + "keyid": "", + "location": "node_modules/sigstore", + "name": "sigstore", + "registry": "https://registry.npmjs.org/", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.0.0.tgz", + "signature": "MEYCIQD10kAn3lC/1rJvXBtSDckbqkKEmz369gPDKb4lG4zMKQIhAP1+RhbMcASsfXhxpXKNCAjJb+3Av3Br95eKD7VL/BEB", + "predicateType": "https://slsa.dev/provenance/v0.2", + "type": "dependencies", + "version": "1.0.0" + } + ], + "missing": [] +} +` + exports[`test/lib/commands/audit.js TAP audit signatures json output with invalid signatures > must match snapshot 1`] = ` { "invalid": [ { "code": "EINTEGRITYSIGNATURE", + "message": "kms-demo@1.0.0 has an invalid registry signature with keyid: SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA and signature: bogus", "integrity": "sha512-QqZ7VJ/8xPkS9s2IWB7Shj3qTJdcRyeXKbPQnsZjsPEwvutGv0EGeVchPcauoiDFJlGbZMFq5GDCurAGNSghJQ==", "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA", "location": "node_modules/kms-demo", @@ -173,6 +197,17 @@ audited 1 package in xxx ` +exports[`test/lib/commands/audit.js TAP audit signatures with invalid attestations > must match snapshot 1`] = ` +audited 1 package in xxx + +1 package has an invalid attestation: + +sigstore@1.0.0 (https://registry.npmjs.org/) + +Someone might have tampered with this package since it was published on the registry! + +` + exports[`test/lib/commands/audit.js TAP audit signatures with invalid signatures > must match snapshot 1`] = ` audited 1 package in xxx @@ -203,6 +238,18 @@ audited 1 package in xxx kms-demo@1.0.0 (https://registry.npmjs.org/) ` +exports[`test/lib/commands/audit.js TAP audit signatures with multiple invalid attestations > must match snapshot 1`] = ` +audited 2 packages in xxx + +2 packages have invalid attestations: + +sigstore@1.0.0 (https://registry.npmjs.org/) +tuf-js@1.0.0 (https://registry.npmjs.org/) + +Someone might have tampered with these packages since they were published on the registry! + +` + exports[`test/lib/commands/audit.js TAP audit signatures with multiple invalid signatures > must match snapshot 1`] = ` audited 2 packages in xxx @@ -247,6 +294,15 @@ audited 2 packages in xxx async@1.1.1 (https://registry.npmjs.org/) ` +exports[`test/lib/commands/audit.js TAP audit signatures with valid attestations > must match snapshot 1`] = ` +audited 1 package in xxx + +1 package has a verified registry signature + +1 package has a verified attestation + +` + exports[`test/lib/commands/audit.js TAP audit signatures with valid signatures > must match snapshot 1`] = ` audited 1 package in xxx diff --git a/test/lib/commands/audit.js b/test/lib/commands/audit.js index fdb53c6aa4f57..5c82fa14de32c 100644 --- a/test/lib/commands/audit.js +++ b/test/lib/commands/audit.js @@ -310,6 +310,102 @@ t.test('audit signatures', async t => { }), } + const installWithValidAttestations = { + 'package.json': JSON.stringify({ + name: 'test-dep', + version: '1.0.0', + dependencies: { + sigstore: '1.0.0', + }, + }), + node_modules: { + sigstore: { + 'package.json': JSON.stringify({ + name: 'sigstore', + version: '1.0.0', + }), + }, + }, + 'package-lock.json': JSON.stringify({ + name: 'test-dep', + version: '1.0.0', + lockfileVersion: 2, + requires: true, + packages: { + '': { + name: 'test-dep', + version: '1.0.0', + dependencies: { + sigstore: '^1.0.0', + }, + }, + 'node_modules/sigstore': { + version: '1.0.0', + }, + }, + dependencies: { + sigstore: { + version: '1.0.0', + }, + }, + }), + } + + const installWithMultipleValidAttestations = { + 'package.json': JSON.stringify({ + name: 'test-dep', + version: '1.0.0', + dependencies: { + sigstore: '1.0.0', + 'tuf-js': '1.0.0', + }, + }), + node_modules: { + sigstore: { + 'package.json': JSON.stringify({ + name: 'sigstore', + version: '1.0.0', + }), + }, + 'tuf-js': { + 'package.json': JSON.stringify({ + name: 'tuf-js', + version: '1.0.0', + }), + }, + }, + 'package-lock.json': JSON.stringify({ + name: 'test-dep', + version: '1.0.0', + lockfileVersion: 2, + requires: true, + packages: { + '': { + name: 'test-dep', + version: '1.0.0', + dependencies: { + sigstore: '^1.0.0', + 'tuf-js': '^1.0.0', + }, + }, + 'node_modules/sigstore': { + version: '1.0.0', + }, + 'node_modules/tuf-js': { + version: '1.0.0', + }, + }, + dependencies: { + sigstore: { + version: '1.0.0', + }, + 'tuf-js': { + version: '1.0.0', + }, + }, + }), + } + const installWithAlias = { 'package.json': JSON.stringify({ name: 'test-dep', @@ -717,6 +813,44 @@ t.test('audit signatures', async t => { await registry.package({ manifest }) } + async function manifestWithValidAttestations ({ registry }) { + const manifest = registry.manifest({ + name: 'sigstore', + packuments: [{ + version: '1.0.0', + dist: { + // eslint-disable-next-line max-len + integrity: 'sha512-e+qfbn/zf1+rCza/BhIA//Awmf0v1pa5HQS8Xk8iXrn9bgytytVLqYD0P7NSqZ6IELTgq+tcDvLPkQjNHyWLNg==', + tarball: 'https://registry.npmjs.org/sigstore/-/sigstore-1.0.0.tgz', + // eslint-disable-next-line max-len + attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/sigstore@1.0.0', provenance: { predicateType: 'https://slsa.dev/provenance/v0.2' } }, + // eslint-disable-next-line max-len + signatures: [{ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA', sig: 'MEQCIBlpcHT68iWOpx8pJr3WUzD1EqQ7tb0CmY36ebbceR6IAiAVGRaxrFoyh0/5B7H1o4VFhfsHw9F8G+AxOZQq87q+lg==' }], + }, + }], + }) + await registry.package({ manifest }) + } + + async function manifestWithMultipleValidAttestations ({ registry }) { + const manifest = registry.manifest({ + name: 'tuf-js', + packuments: [{ + version: '1.0.0', + dist: { + // eslint-disable-next-line max-len + integrity: 'sha512-1dxsQwESDzACJjTdYHQ4wJ1f/of7jALWKfJEHSBWUQB/5UTJUx9SW6GHXp4mZ1KvdBRJCpGjssoPFGi4hvw8/A==', + tarball: 'https://registry.npmjs.org/tuf-js/-/tuf-js-1.0.0.tgz', + // eslint-disable-next-line max-len + attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/tuf-js@1.0.0', provenance: { predicateType: 'https://slsa.dev/provenance/v0.2' } }, + // eslint-disable-next-line max-len + signatures: [{ keyid: 'SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA', sig: 'MEYCIQDgGQeY2QLkLuoO9YxOqFZ+a6zYuaZpXhc77kUfdCUXDQIhAJp/vV+9Xg1bfM5YlTvKIH9agUEOu5T76+tQaHY2vZyO' }], + }, + }], + }) + await registry.package({ manifest }) + } + async function manifestWithInvalidSigs ({ registry, name = 'kms-demo', version = '1.0.0' }) { const manifest = registry.manifest({ name, @@ -1576,6 +1710,173 @@ t.test('audit signatures', async t => { t.matchSnapshot(joinedOutput()) }) + t.test('with valid attestations', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithValidAttestations, + mocks: { + pacote: t.mock('pacote', { + sigstore: { + sigstore: { verify: async () => true }, + }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + await manifestWithValidAttestations({ registry }) + const fixture = fs.readFileSync( + path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture) + registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS) + + await npm.exec('audit', ['signatures']) + + t.notOk(process.exitCode, 'should exit successfully') + t.match(joinedOutput(), /1 package has a verified attestation/) + t.matchSnapshot(joinedOutput()) + }) + + t.test('with multiple valid attestations', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithMultipleValidAttestations, + mocks: { + pacote: t.mock('pacote', { + sigstore: { + sigstore: { verify: async () => true }, + }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + await manifestWithValidAttestations({ registry }) + await manifestWithMultipleValidAttestations({ registry }) + const fixture1 = fs.readFileSync( + path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'), + 'utf8' + ) + const fixture2 = fs.readFileSync( + path.join(__dirname, '..', 'fixtures', 'sigstore/valid-tuf-js-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture1) + registry.nock.get('/-/npm/v1/attestations/tuf-js@1.0.0').reply(200, fixture2) + registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS) + + await npm.exec('audit', ['signatures']) + + t.notOk(process.exitCode, 'should exit successfully') + t.match(joinedOutput(), /2 packages have verified attestations/) + }) + + t.test('with invalid attestations', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithValidAttestations, + mocks: { + pacote: t.mock('pacote', { + sigstore: { + sigstore: { + verify: async () => { + throw new Error(`artifact signature verification failed`) + }, + }, + }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + await manifestWithValidAttestations({ registry }) + const fixture = fs.readFileSync( + path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture) + registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS) + + await npm.exec('audit', ['signatures']) + + t.equal(process.exitCode, 1, 'should exit with error') + t.match( + joinedOutput(), + '1 package has an invalid attestation' + ) + t.matchSnapshot(joinedOutput()) + }) + + t.test('json output with invalid attestations', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithValidAttestations, + config: { + json: true, + }, + mocks: { + pacote: t.mock('pacote', { + sigstore: { + sigstore: { + verify: async () => { + throw new Error(`artifact signature verification failed`) + }, + }, + }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + await manifestWithValidAttestations({ registry }) + const fixture = fs.readFileSync( + path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture) + registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS) + + await npm.exec('audit', ['signatures']) + + t.equal(process.exitCode, 1, 'should exit with error') + t.match(joinedOutput(), 'artifact signature verification failed') + t.matchSnapshot(joinedOutput()) + }) + + t.test('with multiple invalid attestations', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithMultipleValidAttestations, + mocks: { + pacote: t.mock('pacote', { + sigstore: { + sigstore: { + verify: async () => { + throw new Error(`artifact signature verification failed`) + }, + }, + }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + await manifestWithValidAttestations({ registry }) + await manifestWithMultipleValidAttestations({ registry }) + const fixture1 = fs.readFileSync( + path.join(__dirname, '..', 'fixtures', 'sigstore/valid-sigstore-attestations.json'), + 'utf8' + ) + const fixture2 = fs.readFileSync( + path.join(__dirname, '..', 'fixtures', 'sigstore/valid-tuf-js-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture1) + registry.nock.get('/-/npm/v1/attestations/tuf-js@1.0.0').reply(200, fixture2) + registry.nock.get('/-/npm/v1/keys').reply(200, VALID_REGISTRY_KEYS) + + await npm.exec('audit', ['signatures']) + + t.equal(process.exitCode, 1, 'should exit with error') + t.match( + joinedOutput(), + '2 packages have invalid attestations' + ) + t.matchSnapshot(joinedOutput()) + }) + t.test('workspaces', async t => { t.test('verifies registry deps and ignores local workspace deps', async t => { const { npm, joinedOutput } = await loadMockNpm(t, { diff --git a/test/lib/fixtures/sigstore/valid-sigstore-attestations.json b/test/lib/fixtures/sigstore/valid-sigstore-attestations.json new file mode 100644 index 0000000000000..ca0e19b8cfcd3 --- /dev/null +++ b/test/lib/fixtures/sigstore/valid-sigstore-attestations.json @@ -0,0 +1,94 @@ +{ + "attestations": [ + { + "predicateType": "https://slsa.dev/provenance/v0.2", + "bundle": { + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + { + "rawBytes": "MIIDmzCCAyGgAwIBAgIUce0wM1Ev1pqBXH9W1BbvEg9RopYwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjMwMjA5MTc1NjAwWhcNMjMwMjA5MTgwNjAwWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0SgHPgKy+HgMtXdqkkgY6Ji1v7wc+lxnnatY73cbKFwQzw+/x8288IwIz6y54dtznSXnjbzNMdNS0Q2rfMsMcaOCAkAwggI8MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUVVMaFO5X4Xzc/tiJCfm0WXa75QIwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wZAYDVR0RAQH/BFowWIZWaHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzLy5naXRodWIvd29ya2Zsb3dzL3B1Ymxpc2gueW1sQHJlZnMvdGFncy92MS4wLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAVBgorBgEEAYO/MAECBAdyZWxlYXNlMDYGCisGAQQBg78wAQMEKDA2NTI4OTk3YzNjOGFiOWY4NjRmYWQ5YTM2NTg0NDZhY2E3ZmQ4NmQwFQYKKwYBBAGDvzABBAQHcHVibGlzaDAiBgorBgEEAYO/MAEFBBRzaWdzdG9yZS9zaWdzdG9yZS1qczAeBgorBgEEAYO/MAEGBBByZWZzL3RhZ3MvdjEuMC4wMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGGN1HpGQAABAMARjBEAiB/E0+AFpKimPxI/TQDXeCa06+wtpwvLhyPrbHOQYu74gIgB/9fdZD+uHvUBHyptxaGoBxdJfUKEYx9nhaZw2LeZuwwCgYIKoZIzj0EAwMDaAAwZQIxAPW070C7IM0RrAU5rMpP25TFH/rfKvbvqRNnUoPfvIlA9q7Abe8BHIl97pTmf/5vJgIwbZ4myRXGWjB0LUyzplC2GX0kklVGYeqRM5xxsxAK0zwd/U7KjoFIlp/gkyLyiMHH" + }, + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "tlogEntries": [ + { + "logIndex": "12988397", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1675965360", + "inclusionPromise": { + "signedEntryTimestamp": "MEUCID8bfktgHysxMJAIXz6CqqKHGAYPp/X6FZrS9SDtKdbcAiEAg+0zUFNPJKEVX6m33aCU+CRBgWkDNOC8oE4jHoco4kw=" + }, + "inclusionProof": null, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJ0ZWtORFFYbEhaMEYzU1VKQlowbFZZMlV3ZDAweFJYWXhjSEZDV0VnNVZ6RkNZblpGWnpsU2IzQlpkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDAxcVFUVk5WR014VG1wQmQxZG9ZMDVOYWsxM1RXcEJOVTFVWjNkT2FrRjNWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVVd1UyZElVR2RMZVN0SVowMTBXR1J4YTJ0bldUWkthVEYyTjNkaksyeDRibTVoZEZrS056TmpZa3RHZDFGNmR5c3ZlRGd5T0RoSmQwbDZObmsxTkdSMGVtNVRXRzVxWW5wT1RXUk9VekJSTW5KbVRYTk5ZMkZQUTBGclFYZG5aMGs0VFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWV1ZrMWhDa1pQTlZnMFdIcGpMM1JwU2tObWJUQlhXR0UzTlZGSmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQxcEJXVVJXVWpCU1FWRklMMEpHYjNkWFNWcFhZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0FwYU0wNHdZak5LYkV4WGNIcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wSXhXVzE0Y0dNeVozVmxWekZ6VVVoS2JGcHVUWFprUjBadUNtTjVPVEpOVXpSM1RHcEJkMDlSV1V0TGQxbENRa0ZIUkhaNlFVSkJVVkZ5WVVoU01HTklUVFpNZVRrd1lqSjBiR0pwTldoWk0xSndZakkxZWt4dFpIQUtaRWRvTVZsdVZucGFXRXBxWWpJMU1GcFhOVEJNYlU1MllsUkJWa0puYjNKQ1owVkZRVmxQTDAxQlJVTkNRV1I1V2xkNGJGbFlUbXhOUkZsSFEybHpSd3BCVVZGQ1p6YzRkMEZSVFVWTFJFRXlUbFJKTkU5VWF6TlplazVxVDBkR2FVOVhXVFJPYWxKdFdWZFJOVmxVVFRKT1ZHY3dUa1JhYUZreVJUTmFiVkUwQ2s1dFVYZEdVVmxMUzNkWlFrSkJSMFIyZWtGQ1FrRlJTR05JVm1saVIyeDZZVVJCYVVKbmIzSkNaMFZGUVZsUEwwMUJSVVpDUWxKNllWZGtlbVJIT1hrS1dsTTVlbUZYWkhwa1J6bDVXbE14Y1dONlFXVkNaMjl5UW1kRlJVRlpUeTlOUVVWSFFrSkNlVnBYV25wTU0xSm9Xak5OZG1ScVJYVk5RelIzVFVsSFNncENaMjl5UW1kRlJVRmtXalZCWjFGRFFraHpSV1ZSUWpOQlNGVkJNMVF3ZDJGellraEZWRXBxUjFJMFkyMVhZek5CY1VwTFdISnFaVkJMTXk5b05IQjVDbWRET0hBM2J6UkJRVUZIUjA0eFNIQkhVVUZCUWtGTlFWSnFRa1ZCYVVJdlJUQXJRVVp3UzJsdFVIaEpMMVJSUkZobFEyRXdOaXQzZEhCM2RreG9lVkFLY21KSVQxRlpkVGMwWjBsblFpODVabVJhUkN0MVNIWlZRa2g1Y0hSNFlVZHZRbmhrU21aVlMwVlplRGx1YUdGYWR6Sk1aVnAxZDNkRFoxbEpTMjlhU1FwNmFqQkZRWGROUkdGQlFYZGFVVWw0UVZCWE1EY3dRemRKVFRCU2NrRlZOWEpOY0ZBeU5WUkdTQzl5Wmt0MlluWnhVazV1Vlc5UVpuWkpiRUU1Y1RkQkNtSmxPRUpJU1d3NU4zQlViV1l2TlhaS1owbDNZbG8wYlhsU1dFZFhha0l3VEZWNWVuQnNRekpIV0RCcmEyeFdSMWxsY1ZKTk5YaDRjM2hCU3pCNmQyUUtMMVUzUzJwdlJrbHNjQzluYTNsTWVXbE5TRWdLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89Iiwic2lnIjoiVFVWWlEwbFJSREV3YTBGdU0yeERMekZ5U25aWVFuUlRSR05yWW5GclMwVnRlak0yT1dkUVJFdGlOR3hITkhwTlMxRkphRUZRTVN0U2FHSk5ZMEZUYzJaWWFIaHdXRXRPUTBGcVNtSXJNMEYyTTBKeU9UVmxTMFEzVmt3dlFrVkMifV19LCJoYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNjI2OWQzNzQ2MzI0MjM5YzEzOGJkZTMzMDEzMTVlY2FkNmI4ZGM5YzcwY2RlYTBhODEyYjYzYTUzOGJmYzdlYyJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6Ijg0NWU5MTRlYmJhZTBkNmZmY2FlMmFmYjc3YzdkZWY0NzkzMDVjOWVlMzExMDE0MDJmZTQ3NWU2ZDIzZjExYzkifX19fQ==" + } + ], + "timestampVerificationData": null + }, + "dsseEnvelope": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJwa2c6bnBtL3NpZ3N0b3JlQDEuMC4wIiwiZGlnZXN0Ijp7InNoYTUxMiI6IjdiZWE5ZjZlN2ZmMzdmNWZhYjBiMzZiZjA2MTIwMGZmZjAzMDk5ZmQyZmQ2OTZiOTFkMDRiYzVlNGYyMjVlYjlmZDZlMGNhZGNhZDU0YmE5ODBmNDNmYjM1MmE5OWU4ODEwYjRlMGFiZWI1YzBlZjJjZjkxMDhjZDFmMjU4YjM2In19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MC4yIiwicHJlZGljYXRlIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9ucG0vY2xpL2doYUB2MCIsImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vbnBtL2NsaUA5LjQuMiJ9LCJpbnZvY2F0aW9uIjp7ImNvbmZpZ1NvdXJjZSI6eyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzQHJlZnMvdGFncy92MS4wLjAiLCJkaWdlc3QiOnsic2hhMSI6IjA2NTI4OTk3YzNjOGFiOWY4NjRmYWQ5YTM2NTg0NDZhY2E3ZmQ4NmQifSwiZW50cnlQb2ludCI6InNpZ3N0b3JlL3NpZ3N0b3JlLWpzLy5naXRodWIvd29ya2Zsb3dzL3B1Ymxpc2gueW1sQHJlZnMvdGFncy92MS4wLjAifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7IkdJVEhVQl9BQ1RPUl9JRCI6IjM5ODAyNyIsIkdJVEhVQl9FVkVOVF9OQU1FIjoicmVsZWFzZSIsIkdJVEhVQl9SRUYiOiJyZWZzL3RhZ3MvdjEuMC4wIiwiR0lUSFVCX1JFRl9UWVBFIjoidGFnIiwiR0lUSFVCX1JFUE9TSVRPUlkiOiJzaWdzdG9yZS9zaWdzdG9yZS1qcyIsIkdJVEhVQl9SRVBPU0lUT1JZX0lEIjoiNDk1NTc0NTU1IiwiR0lUSFVCX1JFUE9TSVRPUllfT1dORVJfSUQiOiI3MTA5NjM1MyIsIkdJVEhVQl9SVU5fQVRURU1QVCI6IjEiLCJHSVRIVUJfUlVOX0lEIjoiNDEzNzAyODgxNiIsIkdJVEhVQl9SVU5fTlVNQkVSIjoiOSIsIkdJVEhVQl9TSEEiOiIwNjUyODk5N2MzYzhhYjlmODY0ZmFkOWEzNjU4NDQ2YWNhN2ZkODZkIiwiR0lUSFVCX1dPUktGTE9XX1JFRiI6InNpZ3N0b3JlL3NpZ3N0b3JlLWpzLy5naXRodWIvd29ya2Zsb3dzL3B1Ymxpc2gueW1sQHJlZnMvdGFncy92MS4wLjAiLCJHSVRIVUJfV09SS0ZMT1dfU0hBIjoiMDY1Mjg5OTdjM2M4YWI5Zjg2NGZhZDlhMzY1ODQ0NmFjYTdmZDg2ZCJ9fSwibWV0YWRhdGEiOnsiYnVpbGRJbnZvY2F0aW9uSWQiOiI0MTM3MDI4ODE2LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6ZmFsc2UsImVudmlyb25tZW50IjpmYWxzZSwibWF0ZXJpYWxzIjpmYWxzZX0sInJlcHJvZHVjaWJsZSI6ZmFsc2V9LCJtYXRlcmlhbHMiOlt7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUvc2lnc3RvcmUtanMiLCJkaWdlc3QiOnsic2hhMSI6IjA2NTI4OTk3YzNjOGFiOWY4NjRmYWQ5YTM2NTg0NDZhY2E3ZmQ4NmQifX1dfX0=", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "sig": "MEYCIQD10kAn3lC/1rJvXBtSDckbqkKEmz369gPDKb4lG4zMKQIhAP1+RhbMcASsfXhxpXKNCAjJb+3Av3Br95eKD7VL/BEB", + "keyid": "" + } + ] + } + } + }, + { + "predicateType": "https://github.com/npm/attestation/tree/main/specs/publish/v0.1", + "bundle": { + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "publicKey": { + "hint": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + }, + "tlogEntries": [ + { + "logIndex": "12988399", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1675965362", + "inclusionPromise": { + "signedEntryTimestamp": "MEQCIEM1cnSu6sdPpTZvs2XKUGcyKR+ygz4VoOgxcXVLNy5cAiAVJEOd9duLUkVFTfz7fEJdOOEF6RA1xUe6IiPJeZZ5OQ==" + }, + "inclusionProof": null, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7ImtleWlkIjoiU0hBMjU2OmpsM2J3c3d1ODBQampva0NnaDBvMnc1YzJVNExoUUFFNTdnajljejFrekEiLCJwdWJsaWNLZXkiOiJMUzB0TFMxQ1JVZEpUaUJRVlVKTVNVTWdTMFZaTFMwdExTMEtUVVpyZDBWM1dVaExiMXBKZW1vd1EwRlJXVWxMYjFwSmVtb3dSRUZSWTBSUlowRkZNVTlzWWpONlRVRkdSbmhZUzBocFNXdFJUelZqU2pOWmFHdzFhVFpWVUhBclNXaDFkR1ZDU21KMVNHTkJOVlZ2WjB0dk1FVlhkR3hYZDFjMlMxTmhTMjlVVGtWWlREZEtiRU5SYVZadWEyaENhM1JWWjJjOVBRb3RMUzB0TFVWT1JDQlFWVUpNU1VNZ1MwVlpMUzB0TFMwPSIsInNpZyI6IlRVVlpRMGxSUkRremJXZG5VbTVzZVRkdVpWVjFkVGRGTHpWTVJUbHJNRTR6V0d0UmRXMDJSRzVFVkhaRGEwTk1ZMUZKYUVGT1dUY3llaXR1YTI4eVpFZFpObkJzSzBReGVTOUpWR2RPVEZwNGMwWlhRVmt3WTJocVYyczNVRlpKIn1dfSwiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjcyNzUyMjliMTk3YjllYWRlNDAyNjQ5Y2VmYTIzZGI5ZGM0N2FjNjVmOGExMzVjNDA5OTJhNmEwMDQyN2JhODYifSwicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2ZDM2OTdiZDVmYmVkNTZkZTYwYWU5Y2Y1MmY4NjA4Yjk2M2I2N2FjYTZkY2NhMzZmOTMwZTlhN2ZiNWEyYjkzIn19fX0=" + } + ], + "timestampVerificationData": null + }, + "dsseEnvelope": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJwa2c6bnBtL3NpZ3N0b3JlQDEuMC4wIiwiZGlnZXN0Ijp7InNoYTUxMiI6IjdiZWE5ZjZlN2ZmMzdmNWZhYjBiMzZiZjA2MTIwMGZmZjAzMDk5ZmQyZmQ2OTZiOTFkMDRiYzVlNGYyMjVlYjlmZDZlMGNhZGNhZDU0YmE5ODBmNDNmYjM1MmE5OWU4ODEwYjRlMGFiZWI1YzBlZjJjZjkxMDhjZDFmMjU4YjM2In19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9ucG0vYXR0ZXN0YXRpb24vdHJlZS9tYWluL3NwZWNzL3B1Ymxpc2gvdjAuMSIsInByZWRpY2F0ZSI6eyJuYW1lIjoic2lnc3RvcmUiLCJ2ZXJzaW9uIjoiMS4wLjAiLCJyZWdpc3RyeSI6Imh0dHBzOi8vcmVnaXN0cnkubnBtanMub3JnIn19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "sig": "MEYCIQD93mggRnly7neUuu7E/5LE9k0N3XkQum6DnDTvCkCLcQIhANY72z+nko2dGY6pl+D1y/ITgNLZxsFWAY0chjWk7PVI", + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + } + ] + } + } + } + ] +} diff --git a/test/lib/fixtures/sigstore/valid-tuf-js-attestations.json b/test/lib/fixtures/sigstore/valid-tuf-js-attestations.json new file mode 100644 index 0000000000000..6ed923aa66148 --- /dev/null +++ b/test/lib/fixtures/sigstore/valid-tuf-js-attestations.json @@ -0,0 +1,94 @@ +{ + "attestations": [ + { + "predicateType": "https://slsa.dev/provenance/v0.2", + "bundle": { + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + { + "rawBytes": "MIIDpTCCAy2gAwIBAgIUJ+i+7rcd4wcBtySOuMjY44WEmwowCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjMwMjA5MTczODI1WhcNMjMwMjA5MTc0ODI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFi++0ByDdgt/NKMD6tSL4xT4tGZZASdRjIzyujxOjpuDXGq08ZeD8eUf7gZCwCreaIX7rHAYaXvbQ3nA4N6ZmqOCAkwwggJIMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUGmX4ev4tH52Fw5NYiuFtCT6fA3YwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8waQYDVR0RAQH/BF8wXYZbaHR0cHM6Ly9naXRodWIuY29tL3RoZXVwZGF0ZWZyYW1ld29yay90dWYtanMvLmdpdGh1Yi93b3JrZmxvd3MvcHVibGlzaC55bWxAcmVmcy90YWdzL3YxLjAuMDA5BgorBgEEAYO/MAEBBCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMBUGCisGAQQBg78wAQIEB3JlbGVhc2UwNgYKKwYBBAGDvzABAwQoOTk2MjRjYzdjZTM5ODcyZTZiZDA4YTBkZjU5MjI5YTQ4NTYyZGJjYTAVBgorBgEEAYO/MAEEBAdwdWJsaXNoMCcGCisGAQQBg78wAQUEGXRoZXVwZGF0ZWZyYW1ld29yay90dWYtanMwHgYKKwYBBAGDvzABBgQQcmVmcy90YWdzL3YxLjAuMDCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABhjdB0GwAAAQDAEgwRgIhAMvULg3+C9MFZgTjWpEGUX5Jw5m5AizP/mlI/0/SOpRcAiEA4RI3jDcgP6Nwx2j++b1T7d7wPfdyFOueDcN3SURsdmUwCgYIKoZIzj0EAwMDZgAwYwIwOcN18RrQ26tEe1lDdQJi7XEtXA5fzKzWxkjo+Ll4HrLuAjWTiQvy6ABkzHL/oNzsAi8mWhNn5ncF3BCF0riMwy1h2Y3owpPilvL+wmdMOe59wzbycQ5AYbSLkY50amCFJQ==" + }, + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "tlogEntries": [ + { + "logIndex": "12987347", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1675964305", + "inclusionPromise": { + "signedEntryTimestamp": "MEUCIQCicS3b1znpUuLI8LqtewqsHkfJUNV9cUvomA2L+Ni+4AIgEN7IKk+J3P2F9wDtrgXqp1lObLKCJG/hnfIqtqw6Z2Y=" + }, + "inclusionProof": null, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJ3VkVORFFYa3laMEYzU1VKQlowbFZTaXRwS3pkeVkyUTBkMk5DZEhsVFQzVk5hbGswTkZkRmJYZHZkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDAxcVFUVk5WR042VDBSSk1WZG9ZMDVOYWsxM1RXcEJOVTFVWXpCUFJFa3hWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVWR2FTc3JNRUo1UkdSbmRDOU9TMDFFTm5SVFREUjRWRFIwUjFwYVFWTmtVbXBKZW5rS2RXcDRUMnB3ZFVSWVIzRXdPRnBsUkRobFZXWTNaMXBEZDBOeVpXRkpXRGR5U0VGWllWaDJZbEV6YmtFMFRqWmFiWEZQUTBGcmQzZG5aMHBKVFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWSGJWZzBDbVYyTkhSSU5USkdkelZPV1dsMVJuUkRWRFptUVROWmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQyRlJXVVJXVWpCU1FWRklMMEpHT0hkWVdWcGlZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETlNiMXBZVm5kYVIwWXdXbGRhZVFwWlZ6RnNaREk1ZVdGNU9UQmtWMWwwWVc1TmRreHRaSEJrUjJneFdXazVNMkl6U25KYWJYaDJaRE5OZG1OSVZtbGlSMng2WVVNMU5XSlhlRUZqYlZadENtTjVPVEJaVjJSNlRETlplRXhxUVhWTlJFRTFRbWR2Y2tKblJVVkJXVTh2VFVGRlFrSkRkRzlrU0ZKM1kzcHZka3d6VW5aaE1sWjFURzFHYW1SSGJIWUtZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUblppYmxKc1ltNVJkVmt5T1hSTlFsVkhRMmx6UjBGUlVVSm5OemgzUVZGSlJVSXpTbXhpUjFab1l6SlZkd3BPWjFsTFMzZFpRa0pCUjBSMmVrRkNRWGRSYjA5VWF6Sk5hbEpxV1hwa2FscFVUVFZQUkdONVdsUmFhVnBFUVRSWlZFSnJXbXBWTlUxcVNUVlpWRkUwQ2s1VVdYbGFSMHBxV1ZSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVWQ1FXUjNaRmRLYzJGWVRtOU5RMk5IUTJselIwRlJVVUpuTnpoM1FWRlZSVWRZVW04S1dsaFdkMXBIUmpCYVYxcDVXVmN4YkdReU9YbGhlVGt3WkZkWmRHRnVUWGRJWjFsTFMzZFpRa0pCUjBSMmVrRkNRbWRSVVdOdFZtMWplVGt3V1Zka2VncE1NMWw0VEdwQmRVMUVRMEpwZDFsTFMzZFpRa0pCU0ZkbFVVbEZRV2RTT1VKSWMwRmxVVUl6UVU0d09VMUhja2Q0ZUVWNVdYaHJaVWhLYkc1T2QwdHBDbE5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQUVVGQlFtaHFaRUl3UjNkQlFVRlJSRUZGWjNkU1owbG9RVTEyVlV4bk15dERPVTFHV21kVWFsZHdSVWNLVlZnMVNuYzFiVFZCYVhwUUwyMXNTUzh3TDFOUGNGSmpRV2xGUVRSU1NUTnFSR05uVURaT2QzZ3lhaXNyWWpGVU4yUTNkMUJtWkhsR1QzVmxSR05PTXdwVFZWSnpaRzFWZDBObldVbExiMXBKZW1vd1JVRjNUVVJhWjBGM1dYZEpkMDlqVGpFNFVuSlJNalowUldVeGJFUmtVVXBwTjFoRmRGaEJOV1o2UzNwWENuaHJhbThyVEd3MFNISk1kVUZxVjFScFVYWjVOa0ZDYTNwSVRDOXZUbnB6UVdrNGJWZG9UbTQxYm1OR00wSkRSakJ5YVUxM2VURm9NbGt6YjNkd1VHa0tiSFpNSzNkdFpFMVBaVFU1ZDNwaWVXTlJOVUZaWWxOTWExazFNR0Z0UTBaS1VUMDlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsiLCJzaWciOiJUVVZWUTBsQ1JtOUljbWhEU0dGWWFFbFFZakprV2pOU01FZHRORTR4VldkNmRrVnlkamxzWjNJdlZXRlJVbTFsUVdsRlFUaDFOVkpTUlRnMk5sbDRTV0Z2T1ZOc2NGZEhWVTV0Vld4cmNrZHdWWFZyTUVkSWNtSnBSSGR0YTBFOSJ9XX0sImhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI3OWIyN2U3Zjg3NmJhY2FiMWE2YjkxZDA2MWYxMTBhOWUxYjkxODZkN2M0YmI5ZjU5ZWMyODM5MTY5OTQzZmMxIn0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZDNjOWFhNjQxMjBhNWY0Y2Y2ZWY3MzFmMDNhNmRlNGFiZmVhNjUxYzhlOWQ4NzAyYTM4NTI2MzZjY2ZiZTc0ZSJ9fX19" + } + ], + "timestampVerificationData": null + }, + "dsseEnvelope": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJwa2c6bnBtL3R1Zi1qc0AxLjAuMCIsImRpZ2VzdCI6eyJzaGE1MTIiOiJkNWRjNmM0MzAxMTIwZjMwMDIyNjM0ZGQ2MDc0MzhjMDlkNWZmZTg3ZmI4YzAyZDYyOWYyNDQxZDIwNTY1MTAwN2ZlNTQ0Yzk1MzFmNTI1YmExODc1ZTllMjY2NzUyYWY3NDE0NDkwYTkxYTNiMmNhMGYxNDY4Yjg4NmZjM2NmYyJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInByZWRpY2F0ZSI6eyJidWlsZFR5cGUiOiJodHRwczovL2dpdGh1Yi5jb20vbnBtL2NsaS9naGFAdjAiLCJidWlsZGVyIjp7ImlkIjoiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS40LjIifSwiaW52b2NhdGlvbiI6eyJjb25maWdTb3VyY2UiOnsidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS90aGV1cGRhdGVmcmFtZXdvcmsvdHVmLWpzQHJlZnMvdGFncy92MS4wLjAiLCJkaWdlc3QiOnsic2hhMSI6Ijk5NjI0Y2M3Y2UzOTg3MmU2YmQwOGEwZGY1OTIyOWE0ODU2MmRiY2EifSwiZW50cnlQb2ludCI6InRoZXVwZGF0ZWZyYW1ld29yay90dWYtanMvLmdpdGh1Yi93b3JrZmxvd3MvcHVibGlzaC55bWxAcmVmcy90YWdzL3YxLjAuMCJ9LCJwYXJhbWV0ZXJzIjp7fSwiZW52aXJvbm1lbnQiOnsiR0lUSFVCX0FDVE9SX0lEIjoiMzk4MDI3IiwiR0lUSFVCX0VWRU5UX05BTUUiOiJyZWxlYXNlIiwiR0lUSFVCX1JFRiI6InJlZnMvdGFncy92MS4wLjAiLCJHSVRIVUJfUkVGX1RZUEUiOiJ0YWciLCJHSVRIVUJfUkVQT1NJVE9SWSI6InRoZXVwZGF0ZWZyYW1ld29yay90dWYtanMiLCJHSVRIVUJfUkVQT1NJVE9SWV9JRCI6IjUyOTQzMTAwNyIsIkdJVEhVQl9SRVBPU0lUT1JZX09XTkVSX0lEIjoiNDYzMzAyOCIsIkdJVEhVQl9SVU5fQVRURU1QVCI6IjEiLCJHSVRIVUJfUlVOX0lEIjoiNDEzNjkwMjkxOCIsIkdJVEhVQl9SVU5fTlVNQkVSIjoiMTAiLCJHSVRIVUJfU0hBIjoiOTk2MjRjYzdjZTM5ODcyZTZiZDA4YTBkZjU5MjI5YTQ4NTYyZGJjYSIsIkdJVEhVQl9XT1JLRkxPV19SRUYiOiJ0aGV1cGRhdGVmcmFtZXdvcmsvdHVmLWpzLy5naXRodWIvd29ya2Zsb3dzL3B1Ymxpc2gueW1sQHJlZnMvdGFncy92MS4wLjAiLCJHSVRIVUJfV09SS0ZMT1dfU0hBIjoiOTk2MjRjYzdjZTM5ODcyZTZiZDA4YTBkZjU5MjI5YTQ4NTYyZGJjYSJ9fSwibWV0YWRhdGEiOnsiYnVpbGRJbnZvY2F0aW9uSWQiOiI0MTM2OTAyOTE4LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6ZmFsc2UsImVudmlyb25tZW50IjpmYWxzZSwibWF0ZXJpYWxzIjpmYWxzZX0sInJlcHJvZHVjaWJsZSI6ZmFsc2V9LCJtYXRlcmlhbHMiOlt7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vdGhldXBkYXRlZnJhbWV3b3JrL3R1Zi1qcyIsImRpZ2VzdCI6eyJzaGExIjoiOTk2MjRjYzdjZTM5ODcyZTZiZDA4YTBkZjU5MjI5YTQ4NTYyZGJjYSJ9fV19fQ==", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "sig": "MEUCIBFoHrhCHaXhIPb2dZ3R0Gm4N1UgzvErv9lgr/UaQRmeAiEA8u5RRE866YxIao9SlpWGUNmUlkrGpUuk0GHrbiDwmkA=", + "keyid": "" + } + ] + } + } + }, + { + "predicateType": "https://github.com/npm/attestation/tree/main/specs/publish/v0.1", + "bundle": { + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "publicKey": { + "hint": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + }, + "tlogEntries": [ + { + "logIndex": "12987349", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1675964307", + "inclusionPromise": { + "signedEntryTimestamp": "MEUCIC4eHozZ9PAd5OmOCB0uzDT2gz3VWBN47+CyFavOrkL3AiEAzlK562Nf4B3yQdjztSDHpwOATAs4sJGh9WPGQ6u0hNQ=" + }, + "inclusionProof": null, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7ImtleWlkIjoiU0hBMjU2OmpsM2J3c3d1ODBQampva0NnaDBvMnc1YzJVNExoUUFFNTdnajljejFrekEiLCJwdWJsaWNLZXkiOiJMUzB0TFMxQ1JVZEpUaUJRVlVKTVNVTWdTMFZaTFMwdExTMEtUVVpyZDBWM1dVaExiMXBKZW1vd1EwRlJXVWxMYjFwSmVtb3dSRUZSWTBSUlowRkZNVTlzWWpONlRVRkdSbmhZUzBocFNXdFJUelZqU2pOWmFHdzFhVFpWVUhBclNXaDFkR1ZDU21KMVNHTkJOVlZ2WjB0dk1FVlhkR3hYZDFjMlMxTmhTMjlVVGtWWlREZEtiRU5SYVZadWEyaENhM1JWWjJjOVBRb3RMUzB0TFVWT1JDQlFWVUpNU1VNZ1MwVlpMUzB0TFMwPSIsInNpZyI6IlRVVlpRMGxSUkZaalVuUnZRVTh2VVZoWmMxUnVPRE5qTkhkcUwyZ3lXU3NyUVdsRlRXVk1ORUZrWkVkeWJGZHdZbWRKYUVGTE9WUTVWemg1Y21scFQzVnRWa1J1YW5KelRrSTJlakZQZDB0cmVuQjZSbkY1VDJKbVFVaDVkM0pUIn1dfSwiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjUzYzkxZWJhZDE5YzY0ODYxZTlmMWRkODBkYjhiNGQyMDFjZjYwMzI0NTk1NjUxZmVhYWVmNzQzMWE2ZGEwMGEifSwicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIwZTExMWI4MzUxNTQ0OTJlYjQwMjZiY2M4ZDUyZmUzZGNhMzc5ODRkMWE1NDQxZGU5ODg4NTdlODk1N2Q1YTgzIn19fX0=" + } + ], + "timestampVerificationData": null + }, + "dsseEnvelope": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJwa2c6bnBtL3R1Zi1qc0AxLjAuMCIsImRpZ2VzdCI6eyJzaGE1MTIiOiJkNWRjNmM0MzAxMTIwZjMwMDIyNjM0ZGQ2MDc0MzhjMDlkNWZmZTg3ZmI4YzAyZDYyOWYyNDQxZDIwNTY1MTAwN2ZlNTQ0Yzk1MzFmNTI1YmExODc1ZTllMjY2NzUyYWY3NDE0NDkwYTkxYTNiMmNhMGYxNDY4Yjg4NmZjM2NmYyJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2dpdGh1Yi5jb20vbnBtL2F0dGVzdGF0aW9uL3RyZWUvbWFpbi9zcGVjcy9wdWJsaXNoL3YwLjEiLCJwcmVkaWNhdGUiOnsibmFtZSI6InR1Zi1qcyIsInZlcnNpb24iOiIxLjAuMCIsInJlZ2lzdHJ5IjoiaHR0cHM6Ly9yZWdpc3RyeS5ucG1qcy5vcmcifX0=", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "sig": "MEYCIQDVcRtoAO/QXYsTn83c4wj/h2Y++AiEMeL4AddGrlWpbgIhAK9T9W8yriiOumVDnjrsNB6z1OwKkzpzFqyObfAHywrS", + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + } + ] + } + } + } + ] +}