@@ -76,8 +76,11 @@ public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilenam
7676 * - https://docs.sigstore.dev/logging/verify-release/
7777 * - https://github.com/secure-systems-lab/dsse/blob/master/protocol.md#protocol
7878 */
79- $ this ->assertCertificateClaims ($ attestation );
80- $ output ->writeln ('# ' . $ attestationIndex . ': Certificate facts verified. ' , OutputInterface::VERBOSITY_VERBOSE );
79+ $ this ->assertCertificateSignedByTrustedRoot ($ attestation );
80+ $ output ->writeln ('# ' . $ attestationIndex . ': Certificate was signed by a trusted root. ' , OutputInterface::VERBOSITY_VERBOSE );
81+
82+ $ this ->assertCertificateExtensionClaims ($ attestation );
83+ $ output ->writeln ('# ' . $ attestationIndex . ': Certificate extension claims match. ' , OutputInterface::VERBOSITY_VERBOSE );
8184
8285 $ this ->assertDigestFromAttestationMatchesActual ($ pharFilename , $ attestation );
8386 $ output ->writeln ('# ' . $ attestationIndex . ': Payload digest matches downloaded file. ' , OutputInterface::VERBOSITY_VERBOSE );
@@ -89,39 +92,9 @@ public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilenam
8992 $ output ->writeln ('<info>✅ Verified the new PIE version (using fallback verification)</info> ' );
9093 }
9194
92- private function assertCertificateClaims (Attestation $ attestation ): void
95+ private function assertCertificateSignedByTrustedRoot (Attestation $ attestation ): void
9396 {
9497 $ attestationCertificateInfo = openssl_x509_parse ($ attestation ->certificate );
95- Assert::isArray ($ attestationCertificateInfo ['extensions ' ]);
96-
97- /**
98- * See {@link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#136141572641--fulcio} for details
99- * on the Fulcio extension keys; note the values are DER-encoded strings; the ASN.1 tag is UTF8String (0x0C).
100- *
101- * First up, check the extension values are what we expect; these are hard-coded, as we don't expect them
102- * to change unless the namespace/repo name change, etc.
103- */
104- foreach (self ::ATTESTATION_CERTIFICATE_EXPECTED_EXTENSION_VALUES as $ extension => $ expectedValue ) {
105- Assert::keyExists ($ attestationCertificateInfo ['extensions ' ], $ extension );
106- Assert::stringNotEmpty ($ attestationCertificateInfo ['extensions ' ][$ extension ]);
107- $ actualValue = $ attestationCertificateInfo ['extensions ' ][$ extension ];
108-
109- // First character (the ASN.1 tag) is expected to be UTF8String (0x0C)
110- if (ord ($ actualValue [0 ]) !== 0x0C ) {
111- throw FailedToVerifyRelease::fromMismatchingExtensionValues ($ extension , $ expectedValue , $ actualValue );
112- }
113-
114- /**
115- * Second character is expected to be the length of the actual value
116- * as long as they are less than 127 bytes (short form)
117- *
118- * @link https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/basic-encoding-rules.html#Lengths
119- */
120- $ derDecodedValue = substr ($ actualValue , 2 , ord ($ actualValue [1 ]));
121- if ($ derDecodedValue !== $ expectedValue ) {
122- throw FailedToVerifyRelease::fromMismatchingExtensionValues ($ extension , $ expectedValue , $ derDecodedValue );
123- }
124- }
12598
12699 // @todo process in place to make sure this gets updated frequently enough: gh attestation trusted-root > resources/trusted-root.jsonl
127100 $ trustedRootJsonLines = explode ("\n" , trim (file_get_contents ($ this ->trustedRootFilePath )));
@@ -141,7 +114,11 @@ private function assertCertificateClaims(Attestation $attestation): void
141114 $ decoded = json_decode ($ jsonLine , true );
142115
143116 // No certificate authorities defined in this JSON line, skip it...
144- if (! is_array ($ decoded ) || ! array_key_exists ('certificateAuthorities ' , $ decoded )) {
117+ if (
118+ ! is_array ($ decoded )
119+ || ! array_key_exists ('certificateAuthorities ' , $ decoded )
120+ || ! is_array ($ decoded ['certificateAuthorities ' ])
121+ ) {
145122 continue ;
146123 }
147124
@@ -208,6 +185,46 @@ private function assertCertificateClaims(Attestation $attestation): void
208185 throw FailedToVerifyRelease::fromNoIssuerCertificateInTrustedRoot ($ attestationCertificateInfo ['issuer ' ]);
209186 }
210187
188+ private function assertCertificateExtensionClaims (Attestation $ attestation ): void
189+ {
190+ $ attestationCertificateInfo = openssl_x509_parse ($ attestation ->certificate );
191+ Assert::isArray ($ attestationCertificateInfo ['extensions ' ]);
192+
193+ /**
194+ * See {@link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#136141572641--fulcio} for details
195+ * on the Fulcio extension keys; note the values are DER-encoded strings; the ASN.1 tag is UTF8String (0x0C).
196+ *
197+ * Check the extension values are what we expect; these are hard-coded, as we don't expect them
198+ * to change unless the namespace/repo name change, etc.
199+ */
200+ foreach (self ::ATTESTATION_CERTIFICATE_EXPECTED_EXTENSION_VALUES as $ extension => $ expectedValue ) {
201+ Assert::keyExists ($ attestationCertificateInfo ['extensions ' ], $ extension );
202+ Assert::stringNotEmpty ($ attestationCertificateInfo ['extensions ' ][$ extension ]);
203+ $ actualValue = $ attestationCertificateInfo ['extensions ' ][$ extension ];
204+
205+ // First character (the ASN.1 tag) is expected to be UTF8String (0x0C)
206+ if (ord ($ actualValue [0 ]) !== 0x0C ) {
207+ throw FailedToVerifyRelease::fromMismatchingExtensionValues ($ extension , $ expectedValue , $ actualValue );
208+ }
209+
210+ /**
211+ * Second character is expected to be the length of the actual value
212+ * as long as they are less than 127 bytes (short form)
213+ *
214+ * @link https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/basic-encoding-rules.html#Lengths
215+ */
216+ $ expectedValueLength = ord ($ actualValue [1 ]);
217+ if (strlen ($ actualValue ) !== 2 + $ expectedValueLength ) {
218+ throw FailedToVerifyRelease::fromInvalidDerEncodedStringLength ($ actualValue , 2 + $ expectedValueLength );
219+ }
220+
221+ $ derDecodedValue = substr ($ actualValue , 2 , $ expectedValueLength );
222+ if ($ derDecodedValue !== $ expectedValue ) {
223+ throw FailedToVerifyRelease::fromMismatchingExtensionValues ($ extension , $ expectedValue , $ derDecodedValue );
224+ }
225+ }
226+ }
227+
211228 private function verifyDsseEnvelopeSignature (ReleaseMetadata $ releaseMetadata , int $ attestationIndex , Attestation $ attestation ): void
212229 {
213230 if (! extension_loaded ('openssl ' )) {
0 commit comments