diff --git a/.changeset/rotten-goats-repeat.md b/.changeset/rotten-goats-repeat.md new file mode 100644 index 000000000..3ef1f6bd8 --- /dev/null +++ b/.changeset/rotten-goats-repeat.md @@ -0,0 +1,5 @@ +--- +'@sigstore/sign': patch +--- + +Fix for selecting correct OIDC claim when calculating proof-of-possession for Fulcio diff --git a/package-lock.json b/package-lock.json index 0f328b7e2..1845cc944 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13035,7 +13035,7 @@ }, "packages/bundle": { "name": "@sigstore/bundle", - "version": "3.1.0", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" @@ -13046,18 +13046,18 @@ }, "packages/cli": { "name": "@sigstore/cli", - "version": "0.8.2", + "version": "0.9.0", "license": "Apache-2.0", "dependencies": { "@oclif/color": "^1.0.13", "@oclif/core": "^4", "@oclif/plugin-help": "^6", - "@sigstore/bundle": "^3.0.0", - "@sigstore/oci": "^0.5.0", - "@sigstore/sign": "^3.0.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/oci": "^0.6.0", + "@sigstore/sign": "^4.0.0", "open": "^8.4.2", "openid-client": "^5.7.0", - "sigstore": "^3.0.0" + "sigstore": "^4.0.0" }, "bin": { "sigstore": "bin/run" @@ -13074,20 +13074,20 @@ }, "packages/client": { "name": "sigstore", - "version": "3.1.0", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" + "@sigstore/sign": "^4.0.0", + "@sigstore/tuf": "^4.0.0", + "@sigstore/verify": "^3.0.0" }, "devDependencies": { "@sigstore/jest": "^0.0.0", - "@sigstore/mock": "^0.10.0", - "@sigstore/rekor-types": "^3.0.0", + "@sigstore/mock": "^0.11.0", + "@sigstore/rekor-types": "^4.0.0", "@tufjs/repo-mock": "^3.0.1", "@types/make-fetch-happen": "^10.0.4" }, @@ -13097,15 +13097,15 @@ }, "packages/conformance": { "name": "@sigstore/conformance", - "version": "0.5.0", + "version": "0.6.0", "dependencies": { "@oclif/core": "^4", - "@sigstore/bundle": "^3.1.0", + "@sigstore/bundle": "^4.0.0", "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0", + "@sigstore/tuf": "^4.0.0", + "@sigstore/verify": "^3.0.0", "elliptic": "^6.6.1", - "sigstore": "^3.1.0" + "sigstore": "^4.0.0" }, "bin": { "sigstore": "bin/run" @@ -13121,7 +13121,7 @@ }, "packages/core": { "name": "@sigstore/core", - "version": "2.0.0", + "version": "3.0.0", "license": "Apache-2.0", "engines": { "node": "^20.17.0 || >=22.9.0" @@ -13145,7 +13145,7 @@ }, "packages/mock": { "name": "@sigstore/mock", - "version": "0.10.0", + "version": "0.11.0", "license": "Apache-2.0", "dependencies": { "@peculiar/webcrypto": "^1.5.0", @@ -13160,7 +13160,7 @@ "pvutils": "^1.1.3" }, "devDependencies": { - "@sigstore/rekor-types": "^3.0.0", + "@sigstore/rekor-types": "^4.0.0", "make-fetch-happen": "^15.0.0" }, "engines": { @@ -13169,11 +13169,11 @@ }, "packages/mock-server": { "name": "@sigstore/mock-server", - "version": "0.2.2", + "version": "0.3.0", "dependencies": { "@oclif/color": "^1.0.13", "@oclif/core": "^4", - "@sigstore/mock": "^0.10.0", + "@sigstore/mock": "^0.11.0", "@tufjs/repo-mock": "^3.0.1", "express": "5.1.0" }, @@ -13191,7 +13191,7 @@ }, "packages/oci": { "name": "@sigstore/oci", - "version": "0.5.0", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "make-fetch-happen": "^15.0.0", @@ -13206,7 +13206,7 @@ }, "packages/rekor-types": { "name": "@sigstore/rekor-types", - "version": "3.0.0", + "version": "4.0.0", "license": "Apache-2.0", "devDependencies": { "json-schema-to-typescript": "^15.0.4", @@ -13218,11 +13218,11 @@ }, "packages/sign": { "name": "@sigstore/sign", - "version": "3.1.0", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", "@sigstore/protobuf-specs": "^0.5.0", "make-fetch-happen": "^15.0.0", "proc-log": "^5.0.0", @@ -13230,8 +13230,8 @@ }, "devDependencies": { "@sigstore/jest": "^0.0.0", - "@sigstore/mock": "^0.10.0", - "@sigstore/rekor-types": "^3.0.0", + "@sigstore/mock": "^0.11.0", + "@sigstore/rekor-types": "^4.0.0", "@types/make-fetch-happen": "^10.0.4", "@types/promise-retry": "^1.1.6" }, @@ -13241,7 +13241,7 @@ }, "packages/tuf": { "name": "@sigstore/tuf", - "version": "3.1.1", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", @@ -13258,11 +13258,11 @@ }, "packages/verify": { "name": "@sigstore/verify", - "version": "2.1.1", + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { diff --git a/packages/sign/src/__tests__/util/oidc.test.ts b/packages/sign/src/__tests__/util/oidc.test.ts index 62460e77a..5ab4272c8 100644 --- a/packages/sign/src/__tests__/util/oidc.test.ts +++ b/packages/sign/src/__tests__/util/oidc.test.ts @@ -16,10 +16,11 @@ limitations under the License. import { extractJWTSubject } from '../../util/oidc'; describe('extractJWTSubject', () => { - describe('when the JWT is issued by accounts.google.com', () => { + describe('when the JWT has a verified email', () => { const payload = { iss: 'https://accounts.google.com', email: 'foo@bar.com', + email_verified: true, }; const jwt = `.${Buffer.from(JSON.stringify(payload)).toString('base64')}.`; @@ -29,20 +30,23 @@ describe('extractJWTSubject', () => { }); }); - describe('when the JWT is issued by sigstore.dev', () => { + describe('when the JWT has an unverified email', () => { const payload = { iss: 'https://oauth2.sigstore.dev/auth', email: 'foo@bar.com', + email_verified: false, }; const jwt = `.${Buffer.from(JSON.stringify(payload)).toString('base64')}.`; - it('should return the email address', () => { - expect(extractJWTSubject(jwt)).toBe(payload.email); + it('throws an error', () => { + expect(() => extractJWTSubject(jwt)).toThrow( + 'JWT email not verified by issuer' + ); }); }); - describe('when the JWT is a generic JWT', () => { + describe('when the JWT has a sub claim', () => { const payload = { iss: 'https://example.com', sub: 'foo@bar.com', @@ -53,4 +57,15 @@ describe('extractJWTSubject', () => { expect(extractJWTSubject(jwt)).toBe(payload.sub); }); }); + + describe('when the JWT has neither an email nor a sub claim', () => { + const payload = { + iss: 'https://example.com', + }; + const jwt = `.${Buffer.from(JSON.stringify(payload)).toString('base64')}.`; + + it('throws an error', () => { + expect(() => extractJWTSubject(jwt)).toThrow('JWT subject not found'); + }); + }); }); diff --git a/packages/sign/src/util/oidc.ts b/packages/sign/src/util/oidc.ts index c653ff0f0..2c2ebad72 100644 --- a/packages/sign/src/util/oidc.ts +++ b/packages/sign/src/util/oidc.ts @@ -19,17 +19,23 @@ type JWTSubject = { iss: string; sub: string; email: string; + email_verified: boolean; }; export function extractJWTSubject(jwt: string): string { const parts = jwt.split('.', 3); const payload: JWTSubject = JSON.parse(enc.base64Decode(parts[1])); - switch (payload.iss) { - case 'https://accounts.google.com': - case 'https://oauth2.sigstore.dev/auth': - return payload.email; - default: - return payload.sub; + if (payload.email) { + if (!payload.email_verified) { + throw new Error('JWT email not verified by issuer'); + } + return payload.email; + } + + if (payload.sub) { + return payload.sub; + } else { + throw new Error('JWT subject not found'); } }