Skip to content

Commit 8b8ea3b

Browse files
authored
fix: skip registry key check for keyless (Sigstore/Fulcio) attestations (#454)
fix: skip registry key check for keyless (Sigstore/Fulcio) attestations Attestations signed with keyless Sigstore/Fulcio have no keyid and embed the signing certificate directly in the bundle. The existing guard unconditionally required matching registry keys, causing EMISSINGSIGNATUREKEY for registries that only use keyless signing. Only throw when there are keyed attestations that can't be matched. ## References <!-- Examples: Related to #0 Depends on #0 Blocked by #0 Fixes #0 Closes #0 -->
1 parent 18d36e6 commit 8b8ea3b

2 files changed

Lines changed: 51 additions & 8 deletions

File tree

lib/registry.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,10 @@ class RegistryFetcher extends Fetcher {
256256
const attestationKeyIds = bundles.map((b) => b.keyid).filter((k) => !!k)
257257
const attestationRegistryKeys = (this.registryKeys || [])
258258
.filter(key => attestationKeyIds.includes(key.keyid))
259-
if (!attestationRegistryKeys.length) {
259+
// Only require registry keys when there are keyed attestations.
260+
// Keyless (Sigstore/Fulcio) attestations embed their signing
261+
// certificate in the bundle and don't need registry keys.
262+
if (attestationKeyIds.length > 0 && !attestationRegistryKeys.length) {
260263
throw Object.assign(new Error(
261264
`${mani._id} has attestations but no corresponding public key(s) can be found`
262265
), { code: 'EMISSINGSIGNATUREKEY' })

test/registry.js

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -661,14 +661,54 @@ t.test('verifyAttestations no attestation with keyid', async t => {
661661
}],
662662
})
663663

664-
return t.rejects(
665-
f.manifest(),
666-
// eslint-disable-next-line max-len
667-
/sigstore@0\.4\.0 has attestations but no corresponding public key\(s\) can be found/,
668-
{
669-
code: 'EMISSINGSIGNATUREKEY',
670-
}
664+
// Keyless attestations (no keyid) should not require registry keys
665+
const mani = await f.manifest()
666+
t.ok(mani._attestations)
667+
t.ok(mani._integrity)
668+
})
669+
670+
t.test('verifyAttestations keyless without registry keys', async t => {
671+
tnock(t, 'https://registry.npmjs.org')
672+
.get('/sigstore')
673+
.reply(200, {
674+
_id: 'sigstore',
675+
_rev: 'deadbeef',
676+
name: 'sigstore',
677+
'dist-tags': { latest: '0.4.0' },
678+
versions: {
679+
'0.4.0': {
680+
name: 'sigstore',
681+
version: '0.4.0',
682+
dist: {
683+
// eslint-disable-next-line max-len
684+
integrity: 'sha512-KCwMX6k20mQyFkNYG2XT3lwK9u1P36wS9YURFd85zCXPrwrSLZCEh7/vMBFNYcJXRiBtGDS+T4/RZZF493zABA==',
685+
// eslint-disable-next-line max-len
686+
attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/sigstore@0.4.0', provenance: { predicateType: 'https://slsa.dev/provenance/v0.2' } },
687+
},
688+
},
689+
},
690+
})
691+
692+
const fixture = fs.readFileSync(
693+
path.join(__dirname, 'fixtures', 'sigstore/no-keyid-attestations.json'),
694+
'utf8'
671695
)
696+
697+
tnock(t, 'https://registry.npmjs.org')
698+
.get('/-/npm/v1/attestations/sigstore@0.4.0')
699+
.reply(200, JSON.parse(fixture))
700+
701+
// Keyless (Sigstore/Fulcio) attestations embed the signing certificate
702+
// in the bundle and should verify without any registry keys at all
703+
const f = new MockedRegistryFetcher('sigstore@0.4.0', {
704+
registry: 'https://registry.npmjs.org',
705+
cache,
706+
verifyAttestations: true,
707+
})
708+
709+
const mani = await f.manifest()
710+
t.ok(mani._attestations)
711+
t.ok(mani._integrity)
672712
})
673713

674714
t.test('verifyAttestations valid attestations', async t => {

0 commit comments

Comments
 (0)