From 8bc998d6f57645f13217324b55051db292e8e221 Mon Sep 17 00:00:00 2001 From: Appu Goundan Date: Thu, 7 Apr 2022 18:40:01 -0400 Subject: [PATCH] Add sct and cert validations - Rename CertificateResponse to SigningCertificate - Add more testing around parsing - Add example fulcio/ctfe public keys and sct/cert examples for testing - Exceptions are still kinda just passed along Signed-off-by: Appu Goundan --- README.md | 2 +- build.gradle.kts | 1 + .../fulcio/client/CertificateRequest.java | 14 ++ .../fulcio/client/CertificateRequests.java | 33 ----- .../client/{Client.java => FulcioClient.java} | 46 ++----- ...se.java => FulcioValidationException.java} | 23 +--- .../fulcio/client/FulcioValidator.java | 120 ++++++++++++++++++ .../fulcio/client/SigningCertificate.java | 120 ++++++++++++++++++ ...{ClientTest.java => FulcioClientTest.java} | 10 +- .../fulcio/client/FulcioValidatorTest.java | 67 ++++++++++ .../fulcio/client/SigningCertificateTest.java | 42 ++++++ .../dev/sigstore/testing/FakeCTLogServer.java | 19 ++- .../dev/sigstore/samples/sct/valid/cert.pem | 26 ++++ .../dev/sigstore/samples/sct/valid/ctfe.pub | 4 + .../sigstore/samples/sct/valid/fulcio.crt.pem | 13 ++ .../dev/sigstore/samples/sct/valid/sct.base64 | 1 + 16 files changed, 448 insertions(+), 93 deletions(-) delete mode 100644 src/main/java/dev/sigstore/fulcio/client/CertificateRequests.java rename src/main/java/dev/sigstore/fulcio/client/{Client.java => FulcioClient.java} (70%) rename src/main/java/dev/sigstore/fulcio/client/{CertificateResponse.java => FulcioValidationException.java} (58%) create mode 100644 src/main/java/dev/sigstore/fulcio/client/FulcioValidator.java create mode 100644 src/main/java/dev/sigstore/fulcio/client/SigningCertificate.java rename src/test/java/dev/sigstore/fulcio/client/{ClientTest.java => FulcioClientTest.java} (90%) create mode 100644 src/test/java/dev/sigstore/fulcio/client/FulcioValidatorTest.java create mode 100644 src/test/java/dev/sigstore/fulcio/client/SigningCertificateTest.java create mode 100644 src/test/resources/dev/sigstore/samples/sct/valid/cert.pem create mode 100644 src/test/resources/dev/sigstore/samples/sct/valid/ctfe.pub create mode 100644 src/test/resources/dev/sigstore/samples/sct/valid/fulcio.crt.pem create mode 100644 src/test/resources/dev/sigstore/samples/sct/valid/sct.base64 diff --git a/README.md b/README.md index 333312cac..b5da97678 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ byte[] signed = signature.sign(); CertificateRequest cReq = new CertificateRequest(keys.getPublic(), signed); // ask fulcio for a signing cert chain for our public key -CertificateResponse cResp = fulcioClient.SigningCert(cReq, token); +SigningCertificate signingCert = fulcioClient.SigningCert(cReq, token); // sign something with our private key, throw it away and save the cert with the artifact ``` diff --git a/build.gradle.kts b/build.gradle.kts index 2d6a8eafc..fa1d8d0f5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation("com.google.api-client:google-api-client-gson:1.31.5") implementation("com.google.code.gson:gson:2.8.9") + implementation("org.conscrypt:conscrypt-openjdk-uber:2.5.2") // contains library code for all platforms testImplementation("junit:junit:4.12") testImplementation("com.nimbusds:oauth2-oidc-sdk:6.21.2") diff --git a/src/main/java/dev/sigstore/fulcio/client/CertificateRequest.java b/src/main/java/dev/sigstore/fulcio/client/CertificateRequest.java index 150993a4c..e73bf1fba 100644 --- a/src/main/java/dev/sigstore/fulcio/client/CertificateRequest.java +++ b/src/main/java/dev/sigstore/fulcio/client/CertificateRequest.java @@ -15,8 +15,10 @@ */ package dev.sigstore.fulcio.client; +import dev.sigstore.json.GsonSupplier; import java.security.PublicKey; import java.util.Collections; +import java.util.HashMap; import java.util.List; public class CertificateRequest { @@ -49,4 +51,16 @@ public PublicKey getPublicKey() { public byte[] getSignedEmailAddress() { return signedEmailAddress; } + + public String toJsonPayload() { + HashMap key = new HashMap<>(); + key.put("content", getPublicKey().getEncoded()); + key.put("algorithm", getPublicKey().getAlgorithm()); + + HashMap data = new HashMap<>(); + data.put("publicKey", key); + data.put("signedEmailAddress", getSignedEmailAddress()); + + return new GsonSupplier().get().toJson(data); + } } diff --git a/src/main/java/dev/sigstore/fulcio/client/CertificateRequests.java b/src/main/java/dev/sigstore/fulcio/client/CertificateRequests.java deleted file mode 100644 index 42824e7d5..000000000 --- a/src/main/java/dev/sigstore/fulcio/client/CertificateRequests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2022 The Sigstore Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.sigstore.fulcio.client; - -import dev.sigstore.json.GsonSupplier; -import java.util.HashMap; - -public class CertificateRequests { - public static String toJsonPayload(CertificateRequest cr) { - HashMap key = new HashMap<>(); - key.put("content", cr.getPublicKey().getEncoded()); - key.put("algorithm", cr.getPublicKey().getAlgorithm()); - - HashMap data = new HashMap<>(); - data.put("publicKey", key); - data.put("signedEmailAddress", cr.getSignedEmailAddress()); - - return new GsonSupplier().get().toJson(data); - } -} diff --git a/src/main/java/dev/sigstore/fulcio/client/Client.java b/src/main/java/dev/sigstore/fulcio/client/FulcioClient.java similarity index 70% rename from src/main/java/dev/sigstore/fulcio/client/Client.java rename to src/main/java/dev/sigstore/fulcio/client/FulcioClient.java index 5c2386eb6..7f27db704 100644 --- a/src/main/java/dev/sigstore/fulcio/client/Client.java +++ b/src/main/java/dev/sigstore/fulcio/client/FulcioClient.java @@ -17,21 +17,15 @@ import com.google.api.client.http.*; import com.google.api.client.http.apache.v2.ApacheHttpTransport; -import com.google.api.client.util.PemReader; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStreamReader; import java.net.URI; import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Base64; import java.util.concurrent.TimeUnit; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.HttpClientBuilder; +import org.conscrypt.ct.SerializationException; -public class Client { +public class FulcioClient { public static final String PUBLIC_FULCIO_SERVER = "https://fulcio.sigstore.dev"; public static final String SIGNING_CERT_PATH = "/api/v1/signingCert"; public static final String DEFAULT_USER_AGENT = "fulcioJavaClient/0.0.1"; @@ -41,11 +35,11 @@ public class Client { private final URI serverUrl; private final String userAgent; - public static Builder Builder() { + public static Builder builder() { return new Builder(); } - private Client(HttpTransport httpTransport, URI serverUrl, String userAgent) { + private FulcioClient(HttpTransport httpTransport, URI serverUrl, String userAgent) { this.httpTransport = httpTransport; this.serverUrl = serverUrl; this.userAgent = userAgent; @@ -85,19 +79,19 @@ public Builder setUseSSLVerification(boolean enable) { return this; } - public Client build() { + public FulcioClient build() { HttpClientBuilder hcb = ApacheHttpTransport.newDefaultHttpClientBuilder(); hcb.setConnectionTimeToLive(timeout, TimeUnit.SECONDS); if (!useSSLVerification) { hcb = hcb.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); } HttpTransport httpTransport = new ApacheHttpTransport(hcb.build()); - return new Client(httpTransport, serverUrl, userAgent); + return new FulcioClient(httpTransport, serverUrl, userAgent); } } - public CertificateResponse SigningCert(CertificateRequest cr, String bearerToken) - throws IOException, CertificateException { + public SigningCertificate SigningCert(CertificateRequest cr, String bearerToken) + throws IOException, CertificateException, SerializationException { URI fulcioEndpoint = serverUrl.resolve(SIGNING_CERT_PATH); HttpRequest req = @@ -105,8 +99,7 @@ public CertificateResponse SigningCert(CertificateRequest cr, String bearerToken .createRequestFactory() .buildPostRequest( new GenericUrl(fulcioEndpoint), - ByteArrayContent.fromString( - "application/json", CertificateRequests.toJsonPayload(cr))); + ByteArrayContent.fromString("application/json", cr.toJsonPayload())); req.getHeaders().setAccept("application/pem-certificate-chain"); req.getHeaders().setAuthorization("Bearer " + bearerToken); @@ -122,26 +115,7 @@ public CertificateResponse SigningCert(CertificateRequest cr, String bearerToken if (sctHeader == null) { throw new IOException("no signed certificate timestamps were found in response from Fulcio"); } - byte[] sct = Base64.getDecoder().decode(sctHeader); - System.out.println(new String(sct)); - - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - ArrayList certList = new ArrayList<>(); - PemReader pemReader = new PemReader(new InputStreamReader(resp.getContent())); - while (true) { - PemReader.Section section = pemReader.readNextSection(); - if (section == null) { - break; - } - - byte[] certBytes = section.getBase64DecodedBytes(); - certList.add((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certBytes))); - } - if (certList.isEmpty()) { - throw new IOException("no certificates were found in response from Fulcio"); - } - - return new CertificateResponse(cf.generateCertPath(certList), sct); + return SigningCertificate.newSigningCertificate(resp.getContent(), sctHeader); } } diff --git a/src/main/java/dev/sigstore/fulcio/client/CertificateResponse.java b/src/main/java/dev/sigstore/fulcio/client/FulcioValidationException.java similarity index 58% rename from src/main/java/dev/sigstore/fulcio/client/CertificateResponse.java rename to src/main/java/dev/sigstore/fulcio/client/FulcioValidationException.java index 79f38e185..7573d6ad6 100644 --- a/src/main/java/dev/sigstore/fulcio/client/CertificateResponse.java +++ b/src/main/java/dev/sigstore/fulcio/client/FulcioValidationException.java @@ -15,25 +15,16 @@ */ package dev.sigstore.fulcio.client; -import java.security.cert.CertPath; -import javax.annotation.Nullable; - -public class CertificateResponse { - private final CertPath certPath; - - // TODO: This could be saved potentially as a more concrete type - @Nullable private final byte[] sct; - - public CertificateResponse(CertPath certPath, @Nullable byte[] sct) { - this.certPath = certPath; - this.sct = sct; +public class FulcioValidationException extends Exception { + public FulcioValidationException(String message) { + super(message); } - public CertPath getCertPath() { - return certPath; + public FulcioValidationException(String message, Throwable cause) { + super(message, cause); } - public byte[] getSct() { - return sct; + public FulcioValidationException(Throwable cause) { + super(cause); } } diff --git a/src/main/java/dev/sigstore/fulcio/client/FulcioValidator.java b/src/main/java/dev/sigstore/fulcio/client/FulcioValidator.java new file mode 100644 index 000000000..a14bba403 --- /dev/null +++ b/src/main/java/dev/sigstore/fulcio/client/FulcioValidator.java @@ -0,0 +1,120 @@ +/* + * Copyright 2022 The Sigstore Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.sigstore.fulcio.client; + +import com.google.api.client.util.PemReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Collections; +import java.util.Date; +import org.conscrypt.ct.CTLogInfo; +import org.conscrypt.ct.CertificateEntry; +import org.conscrypt.ct.VerifiedSCT; + +public class FulcioValidator { + private final CTLogInfo ctLogInfo; + private final TrustAnchor fulcioRoot; + + public static FulcioValidator NewFulcioValidator(byte[] ctfePublicKey, byte[] fulcioRoot) + throws InvalidKeySpecException, NoSuchAlgorithmException, CertificateException, IOException, + InvalidAlgorithmParameterException { + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + PemReader pemReader = + new PemReader(new InputStreamReader(new ByteArrayInputStream(ctfePublicKey))); + PemReader.Section section = pemReader.readNextSection(); + if (pemReader.readNextSection() != null) { + throw new InvalidKeySpecException( + "ctfe public key must be only a single PEM encoded public key"); + } + EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(section.getBase64DecodedBytes()); + PublicKey ctfePublicKeyObj = keyFactory.generatePublic(publicKeySpec); + + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + X509Certificate fulcioRootObj = + (X509Certificate) + certificateFactory.generateCertificate(new ByteArrayInputStream(fulcioRoot)); + + TrustAnchor fulcioRootTrustAnchor = new TrustAnchor(fulcioRootObj, null); + // this should throw the InvalidAlgorithmException a bit earlier + new PKIXParameters(Collections.singleton(fulcioRootTrustAnchor)); + + // TODO: this ctloginfo probably needs to be appropriately filled out + CTLogInfo ctLogInfo = + new CTLogInfo(ctfePublicKeyObj, "fulcio ct log", "intentionally-nonsense"); + return new FulcioValidator(ctLogInfo, fulcioRootTrustAnchor); + } + + private FulcioValidator(CTLogInfo ctLogInfo, TrustAnchor fulcioRoot) { + this.ctLogInfo = ctLogInfo; + this.fulcioRoot = fulcioRoot; + } + + public void validateSct(SigningCertificate sc) + throws CertificateEncodingException, FulcioValidationException { + if (!(sc.getLeafCertificate() instanceof X509Certificate)) { + throw new RuntimeException("Encountered non X509 Certificate when validating SCT"); + } + CertificateEntry ce = + CertificateEntry.createForX509Certificate((X509Certificate) sc.getLeafCertificate()); + + // a result is returned here, but I don't know what to do with it yet + VerifiedSCT.Status status = ctLogInfo.verifySingleSCT(sc.getSct(), ce); + if (status != VerifiedSCT.Status.VALID) { + throw new FulcioValidationException("SCT could not be verified because " + status.toString()); + } + } + + public void validateCertChain(SigningCertificate sc) throws FulcioValidationException { + CertPathValidator cpv; + try { + cpv = CertPathValidator.getInstance("PKIX"); + } catch (NoSuchAlgorithmException e) { + // no PKIX, we probably shouldn't be here, but this seems to be a system library error + // not a program control flow issue + throw new RuntimeException(e); + } + + PKIXParameters pkixParams; + try { + pkixParams = new PKIXParameters(Collections.singleton(fulcioRoot)); + } catch (InvalidAlgorithmParameterException e) { + // this should have been checked when generating a validator instance + throw new RuntimeException(e); + } + pkixParams.setRevocationEnabled(false); + + // these certs are only valid for 15 minutes, so find a time in the validity period + Date dateInValidityPeriod = + new Date(((X509Certificate) sc.getLeafCertificate()).getNotBefore().getTime() + 1000); + pkixParams.setDate(dateInValidityPeriod); + + try { + // a result is returned here, but I don't know what to do with it yet + cpv.validate(sc.getCertPath(), pkixParams); + } catch (CertPathValidatorException | InvalidAlgorithmParameterException ve) { + throw new FulcioValidationException(ve); + } + } +} diff --git a/src/main/java/dev/sigstore/fulcio/client/SigningCertificate.java b/src/main/java/dev/sigstore/fulcio/client/SigningCertificate.java new file mode 100644 index 000000000..b7dd7d1dc --- /dev/null +++ b/src/main/java/dev/sigstore/fulcio/client/SigningCertificate.java @@ -0,0 +1,120 @@ +/* + * Copyright 2022 The Sigstore Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.sigstore.fulcio.client; + +import com.google.api.client.util.PemReader; +import com.google.common.annotations.VisibleForTesting; +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import dev.sigstore.json.GsonSupplier; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.cert.*; +import java.util.ArrayList; +import java.util.Base64; +import org.conscrypt.ct.DigitallySigned; +import org.conscrypt.ct.SerializationException; +import org.conscrypt.ct.SignedCertificateTimestamp; + +/** Response from Fulcio that includes a certPath and an SCT */ +public class SigningCertificate { + + private final CertPath certPath; + private final SignedCertificateTimestamp sct; + + static SigningCertificate newSigningCertificate(InputStream certs, String sctHeader) + throws CertificateException, IOException, SerializationException { + CertPath certPath = decodeCerts(certs); + SignedCertificateTimestamp sct = decodeSCT(sctHeader); + return new SigningCertificate(certPath, sct); + } + + @VisibleForTesting + static CertPath decodeCerts(InputStream content) throws CertificateException, IOException { + PemReader pemReader = new PemReader(new InputStreamReader(content)); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + ArrayList certList = new ArrayList<>(); + while (true) { + PemReader.Section section = pemReader.readNextSection(); + if (section == null) { + break; + } + + byte[] certBytes = section.getBase64DecodedBytes(); + certList.add((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certBytes))); + } + if (certList.isEmpty()) { + throw new IOException("no certificates were found in response from Fulcio"); + } + + return cf.generateCertPath(certList); + } + + @VisibleForTesting + static SignedCertificateTimestamp decodeSCT(String sctHeader) throws SerializationException { + byte[] sct = Base64.getDecoder().decode(sctHeader); + Gson gson = new GsonSupplier().get(); + return gson.fromJson(new InputStreamReader(new ByteArrayInputStream(sct)), SctJson.class) + .toSct(); + } + + private static class SctJson { + private int sct_version; + private byte[] id; + private long timestamp; + private byte[] extensions; + private byte[] signature; + + public SignedCertificateTimestamp toSct() throws JsonParseException, SerializationException { + if (sct_version != 0) { + throw new JsonParseException( + "Invalid SCT version:" + sct_version + ", only 0 (V1) is allowed"); + } + if (extensions.length != 0) { + throw new JsonParseException( + "SCT has extensions that cannot be handled by client:" + new String(extensions)); + } + + DigitallySigned digiSig = DigitallySigned.decode(signature); + return new SignedCertificateTimestamp( + SignedCertificateTimestamp.Version.V1, + id, + timestamp, + extensions, + digiSig, + SignedCertificateTimestamp.Origin.OCSP_RESPONSE); + } + } + + private SigningCertificate(CertPath certPath, SignedCertificateTimestamp sct) { + this.certPath = certPath; + this.sct = sct; + } + + public CertPath getCertPath() { + return certPath; + } + + public Certificate getLeafCertificate() { + return certPath.getCertificates().get(0); + } + + SignedCertificateTimestamp getSct() { + return sct; + } +} diff --git a/src/test/java/dev/sigstore/fulcio/client/ClientTest.java b/src/test/java/dev/sigstore/fulcio/client/FulcioClientTest.java similarity index 90% rename from src/test/java/dev/sigstore/fulcio/client/ClientTest.java rename to src/test/java/dev/sigstore/fulcio/client/FulcioClientTest.java index b28c863ed..b3a4a6a8c 100644 --- a/src/test/java/dev/sigstore/fulcio/client/ClientTest.java +++ b/src/test/java/dev/sigstore/fulcio/client/FulcioClientTest.java @@ -29,7 +29,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; -public class ClientTest { +public class FulcioClientTest { @Rule public TemporaryFolder testRoot = new TemporaryFolder(); @Test @@ -44,7 +44,7 @@ public void testSigningCert() throws Exception { // start fulcio client with config from oidc server try (FulcioWrapper fulcioServer = FulcioWrapper.startNewServer(fulcioConfig, ctLogServer.getURI().toString())) { - Client c = Client.Builder().setServerUrl(fulcioServer.getURI()).build(); + FulcioClient c = FulcioClient.builder().setServerUrl(fulcioServer.getURI()).build(); // create a "subject" and sign it with the oidc server key (signed JWT) String subject = FakeOIDCServer.USER; @@ -65,11 +65,11 @@ public void testSigningCert() throws Exception { CertificateRequest cReq = new CertificateRequest(keys.getPublic(), signed); // ask fulcio for a signing cert - CertificateResponse cResp = c.SigningCert(cReq, token); + SigningCertificate sc = c.SigningCert(cReq, token); // some pretty basic assertions - Assert.assertTrue(cResp.getCertPath().getCertificates().size() > 0); - Assert.assertTrue(cResp.getSct().length > 0); + Assert.assertTrue(sc.getCertPath().getCertificates().size() > 0); + Assert.assertNotNull(sc.getSct()); } } } diff --git a/src/test/java/dev/sigstore/fulcio/client/FulcioValidatorTest.java b/src/test/java/dev/sigstore/fulcio/client/FulcioValidatorTest.java new file mode 100644 index 000000000..12ccf489e --- /dev/null +++ b/src/test/java/dev/sigstore/fulcio/client/FulcioValidatorTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2022 The Sigstore Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.sigstore.fulcio.client; + +import com.google.common.io.ByteSource; +import com.google.common.io.Resources; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import org.conscrypt.ct.SerializationException; +import org.junit.Before; +import org.junit.Test; + +public class FulcioValidatorTest { + + private SigningCertificate sc; + private FulcioValidator fv; + + @Before + public void setup() + throws IOException, SerializationException, CertificateException, InvalidKeySpecException, + NoSuchAlgorithmException, InvalidAlgorithmParameterException { + String sctBase64 = + Resources.toString( + Resources.getResource("dev/sigstore/samples/sct/valid/sct.base64"), + Charset.defaultCharset()); + ByteSource certs = + Resources.asByteSource(Resources.getResource("dev/sigstore/samples/sct/valid/cert.pem")); + + sc = SigningCertificate.newSigningCertificate(certs.openStream(), sctBase64); + + byte[] fulcioRoot = + Resources.toByteArray( + Resources.getResource("dev/sigstore/samples/sct/valid/fulcio.crt.pem")); + byte[] ctfePub = + Resources.toByteArray(Resources.getResource("dev/sigstore/samples/sct/valid/ctfe.pub")); + + fv = FulcioValidator.NewFulcioValidator(ctfePub, fulcioRoot); + } + + @Test + public void testValidateCertChain() throws FulcioValidationException { + fv.validateCertChain(sc); + } + + @Test + public void testValidateSct() throws CertificateEncodingException, FulcioValidationException { + fv.validateSct(sc); + } +} diff --git a/src/test/java/dev/sigstore/fulcio/client/SigningCertificateTest.java b/src/test/java/dev/sigstore/fulcio/client/SigningCertificateTest.java new file mode 100644 index 000000000..dafcdf998 --- /dev/null +++ b/src/test/java/dev/sigstore/fulcio/client/SigningCertificateTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 The Sigstore Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.sigstore.fulcio.client; + +import com.google.common.io.ByteSource; +import com.google.common.io.Resources; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import org.conscrypt.ct.SerializationException; +import org.junit.Test; + +public class SigningCertificateTest { + @Test + public void TestDecode() + throws SerializationException, IOException, CertificateException, InvalidKeySpecException, + NoSuchAlgorithmException { + String sctBase64 = + Resources.toString( + Resources.getResource("dev/sigstore/samples/sct/valid/sct.base64"), + Charset.defaultCharset()); + ByteSource certs = + Resources.asByteSource(Resources.getResource("dev/sigstore/samples/sct/valid/cert.pem")); + + SigningCertificate sc = SigningCertificate.newSigningCertificate(certs.openStream(), sctBase64); + } +} diff --git a/src/test/java/dev/sigstore/testing/FakeCTLogServer.java b/src/test/java/dev/sigstore/testing/FakeCTLogServer.java index 78fb23d1c..bb10199c8 100644 --- a/src/test/java/dev/sigstore/testing/FakeCTLogServer.java +++ b/src/test/java/dev/sigstore/testing/FakeCTLogServer.java @@ -23,6 +23,10 @@ import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -52,9 +56,20 @@ public static FakeCTLogServer startNewServer() throws IOException, JOSEException public void handleSctRequest(HttpExchange t) throws IOException { // we dont really care about the input, we're are not testing fulcio, just the api Map content = new HashMap<>(); - content.put("sct_version", 1); - content.put("id", "test_id"); + content.put("sct_version", 0); + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + content.put("id", digest.digest("test_id".getBytes(StandardCharsets.UTF_8))); + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } content.put("timestamp", System.currentTimeMillis()); + // this is a signature stolen from a valid sct, but will not verify + content.put( + "signature", + Base64.getDecoder() + .decode( + "BAMARjBEAiBwHMgDtObhrT8wkWid01FXlqvXz1tsRei64siSuwZp7gIgdyRBYHatNaOezI/AW57lKkUffra4cKOGdO+oHKBJARI=")); String resp = new GsonSupplier().get().toJson(content); t.sendResponseHeaders(200, resp.length()); diff --git a/src/test/resources/dev/sigstore/samples/sct/valid/cert.pem b/src/test/resources/dev/sigstore/samples/sct/valid/cert.pem new file mode 100644 index 000000000..2a7b67c5b --- /dev/null +++ b/src/test/resources/dev/sigstore/samples/sct/valid/cert.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIICDDCCAZOgAwIBAgIUAIvUkAVYOwDVHphHIn5N5NzIYSIwCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MjA0MTUxODE5MDlaFw0yMjA0MTUxODI5MDhaMAAwWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAARghXRYr4TadV+uo3OsmalnfR/ecBv1XN+wox1JnGP98hQGOwH9KlmB +JGgJAqseoT1E1bHuJdjd1rFYjaz40epbo4HAMIG9MA4GA1UdDwEB/wQEAwIHgDAT +BgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSKGqxF ++53UeDOrzntfWQitFvTqWzAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF ++jAdBgNVHREBAf8EEzARgQ9hcHB1QGdvb2dsZS5jb20wKQYKKwYBBAGDvzABAQQb +aHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMAoGCCqGSM49BAMDA2cAMGQCMEQl +cTYSVsDN7CEbdVuHzkFuyAyex3rmpeN7+PKxE3EwaXqNbAP2UNwzfhS8W/Gh6AIw +JwY+/cXCVdouT9J9nU6lJJiT59v+7HBpC7NLqn4mR36UyjEgCR8TSLWv1P5Jcw0+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl +LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 +XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex +X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j +YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY +wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ +KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM +WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 +TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/src/test/resources/dev/sigstore/samples/sct/valid/ctfe.pub b/src/test/resources/dev/sigstore/samples/sct/valid/ctfe.pub new file mode 100644 index 000000000..1bb1488c9 --- /dev/null +++ b/src/test/resources/dev/sigstore/samples/sct/valid/ctfe.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3Pyu +dDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/src/test/resources/dev/sigstore/samples/sct/valid/fulcio.crt.pem b/src/test/resources/dev/sigstore/samples/sct/valid/fulcio.crt.pem new file mode 100644 index 000000000..133b6e8b6 --- /dev/null +++ b/src/test/resources/dev/sigstore/samples/sct/valid/fulcio.crt.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl +LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 +XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex +X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j +YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY +wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ +KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM +WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 +TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ +-----END CERTIFICATE----- diff --git a/src/test/resources/dev/sigstore/samples/sct/valid/sct.base64 b/src/test/resources/dev/sigstore/samples/sct/valid/sct.base64 new file mode 100644 index 000000000..a4afab0fb --- /dev/null +++ b/src/test/resources/dev/sigstore/samples/sct/valid/sct.base64 @@ -0,0 +1 @@ +eyJzY3RfdmVyc2lvbiI6MCwiaWQiOiJDR0NTOENoUy8yaEYwZEZySjRTY1JXY1lyQlk5d3pqU2JlYThJZ1kyYjNJPSIsInRpbWVzdGFtcCI6MTY1MDA0Njc0OTgwNiwiZXh0ZW5zaW9ucyI6IiIsInNpZ25hdHVyZSI6IkJBTUFSekJGQWlFQWxMS093dlo3bG9VdHQ4YUdxY01XL3dWS2N5cWQ2NzRZN29zKzZ5S25xU29DSUNsM2tQNXl4ajNxN3NLbDdxM21EKzdaZlUrMTZkUTF0QUJiQlNBdnZZN2QifQ== \ No newline at end of file