Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
Create bundles with only leaf certs.
Handle verification of certs in both situations
- containing only untrusted certs (current)
- contain full cert chain (legacy)

Signed-off-by: Appu Goundan <[email protected]>
  • Loading branch information
loosebazooka committed Sep 18, 2023
1 parent ce3d3a1 commit 6a44406
Show file tree
Hide file tree
Showing 15 changed files with 174 additions and 199 deletions.
6 changes: 3 additions & 3 deletions fuzzing/src/main/java/fuzzing/FulcioVerifierFuzzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
var cas = Tuf.certificateAuthoritiesFrom(data);
var ctLogs = Tuf.transparencyLogsFrom(data);

byte[] byteArray = data.consumeRemainingAsBytes();
List<Certificate> certList = new ArrayList<Certificate>();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
certList.add(cf.generateCertificate(new ByteArrayInputStream(byteArray)));
certList.add(cf.generateCertificate(new ByteArrayInputStream(byteArray)));
certList.add(cf.generateCertificate(new ByteArrayInputStream(data.consumeBytes(10240))));
certList.add(
cf.generateCertificate(new ByteArrayInputStream(data.consumeRemainingAsBytes())));

SigningCertificate sc = SigningCertificate.from(cf.generateCertPath(certList));
FulcioVerifier fv = FulcioVerifier.newFulcioVerifier(cas, ctLogs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ public interface KeylessSignature {
/** The sha256 hash digest of the artifact */
byte[] getDigest();

/** The full certificate chain provided by fulcio for the public key used to sign the artifact */
/**
* The partial certificate chain provided by fulcio for the public key and identity used to sign
* the artifact, this should NOT contain the trusted root or any trusted intermediates. But users
* of this object should understand that older signatures may include the full chain.
*/
CertPath getCertPath();

/** The signature over the artifact */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ public List<KeylessSignature> sign(List<byte[]> artifactDigests)
rekorVerifier.verifyEntry(rekorResponse.getEntry());

result.add(
ImmutableKeylessSignature.builder()
KeylessSignature.builder()
.digest(artifactDigest)
.certPath(signingCert.getCertPath())
.signature(signature)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@

import java.util.List;
import java.util.Map;
import org.immutables.value.Value;
import org.immutables.value.Value.Default;
import org.immutables.value.Value.Immutable;

@Value.Immutable
@Immutable
public interface KeylessVerificationRequest {
KeylessSignature getKeylessSignature();

VerificationOptions getVerificationOptions();
@Default
default VerificationOptions getVerificationOptions() {
return VerificationOptions.builder().isOnline(true).build();
}

@Value.Immutable
@Immutable
interface VerificationOptions {
boolean isOnline();

Expand All @@ -36,7 +40,7 @@ static ImmutableVerificationOptions.Builder builder() {
}
}

@Value.Immutable
@Immutable
interface CertificateIdentity {
String getIssuer();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import com.google.protobuf.ByteString;
import com.google.protobuf.util.JsonFormat;
import dev.sigstore.ImmutableKeylessSignature;
import dev.sigstore.KeylessSignature;
import dev.sigstore.encryption.certificates.Certificates;
import dev.sigstore.proto.bundle.v1.Bundle;
Expand Down Expand Up @@ -210,7 +209,7 @@ static KeylessSignature readBundle(Reader jsonReader) throws BundleParseExceptio
+ " is supported");
}
try {
return ImmutableKeylessSignature.builder()
return KeylessSignature.builder()
.digest(bundle.getMessageSignature().getMessageDigest().getDigest().toByteArray())
.certPath(
toCertPath(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;

public class Certificates {

private static final String SCT_X509_OID = "1.3.6.1.4.1.11129.2.4.2";

/** Convert a certificate to a PEM encoded certificate. */
public static String toPemString(Certificate cert) throws IOException {
var certWriter = new StringWriter();
Expand Down Expand Up @@ -148,4 +151,55 @@ public static CertPath appendCertPath(CertPath root, Certificate certificate)
.build();
return cf.generateCertPath(certs);
}

/**
* Trims a parent CertPath from a provided CertPath. This is intended to be used to trim trusted
* root and intermediates from a full CertPath to reveal just the untrusted parts which can be
* distributed as part of a signature tuple or bundle.
*
* @param certPath a certificate path to trim from
* @param trimPath the parent portion of the path to trim
* @return a trimmed path
* @throws IllegalArgumentException if the trimPath is not a parent of the certPath or if they are
* the same length
* @throws CertificateException if an error occurs during CertPath construction
*/
public static CertPath trimCertPath(CertPath certPath, CertPath trimPath)
throws CertificateException {
var certs = certPath.getCertificates();
var trim = trimPath.getCertificates();
if (trim.size() >= certs.size()) {
throw new IllegalArgumentException("Cannot trim larger certificate chain from shorter chain");
}
if (!certs.subList(certs.size() - trim.size(), certs.size()).equals(trim)) {
throw new IllegalArgumentException("trim path was not the parent of the provider chain");
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return cf.generateCertPath(certs.subList(0, certs.size() - trim.size()));
}

/**
* Find and return any SCTs embedded in a certificate.
*
* @param certificate the certificate with embedded scts
* @return a byte array containing any number of embedded scts
*/
public static Optional<byte[]> getEmbeddedSCTs(X509Certificate certificate) {
return Optional.ofNullable(certificate.getExtensionValue(SCT_X509_OID));
}

/**
* Check if a certificate is self-signed.
*/
public static boolean isSelfSigned(X509Certificate certificate) {
return (certificate.getIssuerDN().equals(certificate.getSubjectDN()));
}

/**
* Check if the root of a CertPath is self-signed
*/
public static boolean isSelfSigned(CertPath certPath) {
X509Certificate cert = (X509Certificate) certPath.getCertificates().get(certPath.getCertificates().size() - 1);
return isSelfSigned(cert);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ public SigningCertificate signingCertificate(CertificateRequest request)
if (certs.getCertificateCase() == SIGNED_CERTIFICATE_DETACHED_SCT) {
throw new CertificateException("Detached SCTs are not supported");
}
return SigningCertificate.newSigningCertificate(certs.getSignedCertificateEmbeddedSct());
return SigningCertificate.newSigningCertificate(
certs.getSignedCertificateEmbeddedSct(), certificateAuthority.getCertPath());
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.PKIXParameters;
Expand Down Expand Up @@ -90,16 +91,13 @@ private FulcioVerifier(
}

@VisibleForTesting
void verifySct(SigningCertificate signingCertificate, CertPath rebuiltCert)
void verifySct(CertPath rebuiltCert)
throws FulcioVerificationException {
if (ctLogs.size() == 0) {
throw new FulcioVerificationException("No ct logs were provided to verifier");
}

if (signingCertificate.getDetachedSct().isPresent()) {
throw new FulcioVerificationException(
"Detached SCTs are not supported for validating certificates");
} else if (signingCertificate.getEmbeddedSct().isPresent()) {
if (Certificates.getEmbeddedSCTs((X509Certificate) rebuiltCert.getCertificates().get(0)).isPresent()) {
verifyEmbeddedScts(rebuiltCert);
} else {
throw new FulcioVerificationException("No valid SCTs were found during verification");
Expand Down Expand Up @@ -147,7 +145,7 @@ private void verifyEmbeddedScts(CertPath rebuiltCert) throws FulcioVerificationE
* @param signingCertificate containing the certificate chain
* @throws FulcioVerificationException if verification fails for any reason
*/
public void verifySigningCertificate(SigningCertificate signingCertificate)
public void verifySigningCertificate(Certificate signingCertificate)
throws FulcioVerificationException, IOException {
CertPath reconstructedCert = reconstructValidCertPath(signingCertificate);
verifySct(signingCertificate, reconstructedCert);
Expand All @@ -158,7 +156,7 @@ public void verifySigningCertificate(SigningCertificate signingCertificate)
* certificate path combining the provided un-trusted certs and a known set of trusted and
* intermediate certs.
*/
CertPath reconstructValidCertPath(SigningCertificate signingCertificate)
CertPath reconstructValidCertPath(CertPath certPath)
throws FulcioVerificationException {
CertPathValidator cpv;
try {
Expand All @@ -170,7 +168,7 @@ CertPath reconstructValidCertPath(SigningCertificate signingCertificate)
e);
}

var leaf = signingCertificate.getLeafCertificate();
var leaf = (X509Certificate) certPath.getCertificates().get(0);
var validCAs = cas.find(leaf.getNotBefore().toInstant());

if (validCAs.size() == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,21 @@
*/
package dev.sigstore.fulcio.client;

import static dev.sigstore.json.GsonSupplier.GSON;

import com.google.api.client.util.PemReader;
import com.google.common.annotations.VisibleForTesting;
import com.google.gson.JsonParseException;
import dev.sigstore.encryption.certificates.transparency.DigitallySigned;
import dev.sigstore.encryption.certificates.transparency.SerializationException;
import dev.sigstore.encryption.certificates.transparency.SignedCertificateTimestamp;
import dev.sigstore.encryption.certificates.Certificates;
import dev.sigstore.fulcio.v2.CertificateChain;
import dev.sigstore.fulcio.v2.SigningCertificateDetachedSCT;
import dev.sigstore.fulcio.v2.SigningCertificateEmbeddedSCT;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.security.cert.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;

/**
* Response from Fulcio that includes a Certificate Chain and a Signed Certificate Timestamp (SCT).
* This should only be constructed by fulcio clients.
*
* <p>An SCT is not required for all instances of fulcio, however the public good instance of fulcio
* should probably always include one. SCT can be associated with a certificate in two modes:
Expand All @@ -50,38 +41,23 @@
* x509 certificates (this is the most common form, and we should expect this moving forward)
*/
public class SigningCertificate {
private static final String SCT_X509_OID = "1.3.6.1.4.1.11129.2.4.2";

private final CertPath certPath;
@Nullable private final SignedCertificateTimestamp sct;

public static SigningCertificate from(CertPath certPath) {
static SigningCertificate from(CertPath certPath) {
return new SigningCertificate(certPath);
}

static SigningCertificate newSigningCertificate(String certs, @Nullable String sctHeader)
throws CertificateException, IOException, SerializationException {
static SigningCertificate newSigningCertificate(String certs)
throws CertificateException, IOException {
CertPath certPath = decodeCerts(certs);
if (sctHeader != null) {
SignedCertificateTimestamp sct =
decodeSCT(new String(Base64.getDecoder().decode(sctHeader), StandardCharsets.UTF_8));
return new SigningCertificate(certPath, sct);
}
return new SigningCertificate(certPath, null);
}

static SigningCertificate newSigningCertificate(SigningCertificateDetachedSCT signingCertificate)
throws CertificateException, SerializationException {
SignedCertificateTimestamp sct = null;
if (!signingCertificate.getSignedCertificateTimestamp().isEmpty()) {
sct = decodeSCT(signingCertificate.getSignedCertificateTimestamp().toStringUtf8());
}
return new SigningCertificate(decodeCerts(signingCertificate.getChain()), sct);
return new SigningCertificate(certPath);
}

static SigningCertificate newSigningCertificate(SigningCertificateEmbeddedSCT signingCertificate)
static SigningCertificate newSigningCertificate(
SigningCertificateEmbeddedSCT signingCertificate, CertPath caCertPath)
throws CertificateException {
return new SigningCertificate(decodeCerts(signingCertificate.getChain()));
return new SigningCertificate(
Certificates.trimCertPath(decodeCerts(signingCertificate.getChain()), caCertPath));
}

@VisibleForTesting
Expand Down Expand Up @@ -121,61 +97,8 @@ static CertPath decodeCerts(String content) throws CertificateException, IOExcep
return cf.generateCertPath(certList);
}

@VisibleForTesting
static SignedCertificateTimestamp decodeSCT(String sctJson) throws SerializationException {
return GSON.get().fromJson(sctJson, SctJson.class).toSct();
}

/** Returns true if the signing certificate contains scts embedded in X509 extensions. */
boolean hasEmbeddedSct() {
return getLeafCertificate().getExtensionValue(SCT_X509_OID) != null;
}

/**
* Returns scts if present, or empty if not. The returned byte array may contain any number of
* embedded scts.
*/
Optional<byte[]> getEmbeddedSct() {
return Optional.ofNullable(getLeafCertificate().getExtensionValue(SCT_X509_OID));
}

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, StandardCharsets.UTF_8));
}

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;
}

private SigningCertificate(CertPath certPath) {
this.certPath = certPath;
this.sct = null;
}

public CertPath getCertPath() {
Expand All @@ -190,8 +113,4 @@ public List<X509Certificate> getCertificates() {
public X509Certificate getLeafCertificate() {
return (X509Certificate) certPath.getCertificates().get(0);
}

Optional<SignedCertificateTimestamp> getDetachedSct() {
return Optional.ofNullable(sct);
}
}
Loading

0 comments on commit 6a44406

Please sign in to comment.