Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
create bundles with only leaf certificates
hand verification of certs in both situations
- only untrusted certs (current behavior)
- full cert chain to fulcio root (legacy)

also move SigningCert object, it was a pointless
wrapper around CertPath

Signed-off-by: Appu Goundan <[email protected]>
  • Loading branch information
loosebazooka committed Sep 18, 2023
1 parent 031c839 commit 8f45e9b
Show file tree
Hide file tree
Showing 18 changed files with 307 additions and 452 deletions.
28 changes: 7 additions & 21 deletions fuzzing/src/main/java/fuzzing/FulcioVerifierFuzzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import dev.sigstore.fulcio.client.FulcioVerificationException;
import dev.sigstore.fulcio.client.FulcioVerifier;
import dev.sigstore.fulcio.client.SigningCertificate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
Expand All @@ -34,33 +34,19 @@
public class FulcioVerifierFuzzer {
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
try {
int[] intArray = data.consumeInts(data.consumeInt(1, 10));

var cas = Tuf.certificateAuthoritiesFrom(data);
var ctLogs = Tuf.transparencyLogsFrom(data);

byte[] byteArray = data.consumeRemainingAsBytes();
List<Certificate> certList = new ArrayList<Certificate>();
List<Certificate> certList = new ArrayList<>();
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));
CertPath sc = cf.generateCertPath(certList);
FulcioVerifier fv = FulcioVerifier.newFulcioVerifier(cas, ctLogs);

for (int choice : intArray) {
switch (choice % 4) {
case 0:
sc.getCertificates();
break;
case 1:
sc.getLeafCertificate();
break;
case 3:
fv.verifySigningCertificate(sc);
break;
}
}
fv.verifySigningCertificate(sc);
} catch (CertificateException
| FulcioVerificationException
| InvalidKeySpecException
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
18 changes: 10 additions & 8 deletions sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import dev.sigstore.fulcio.client.FulcioClient;
import dev.sigstore.fulcio.client.FulcioVerificationException;
import dev.sigstore.fulcio.client.FulcioVerifier;
import dev.sigstore.fulcio.client.SigningCertificate;
import dev.sigstore.fulcio.client.UnsupportedAlgorithmException;
import dev.sigstore.oidc.client.OidcClients;
import dev.sigstore.oidc.client.OidcException;
Expand All @@ -47,7 +46,9 @@
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.CertPath;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.time.Duration;
import java.util.ArrayList;
Expand Down Expand Up @@ -82,7 +83,7 @@ public class KeylessSigner implements AutoCloseable {

/** The code signing certificate from Fulcio. */
@GuardedBy("lock")
private @Nullable SigningCertificate signingCert;
private @Nullable CertPath signingCert;

/**
* Representation {@link #signingCert} in PEM bytes format. This is used to avoid serializing the
Expand Down Expand Up @@ -244,7 +245,7 @@ public List<KeylessSignature> sign(List<byte[]> artifactDigests)
// However, files might be large, and it might take time to talk to Rekor
// so we check the certificate expiration here.
renewSigningCertificate();
SigningCertificate signingCert;
CertPath signingCert;
byte[] signingCertPemBytes;
lock.readLock().lock();
try {
Expand All @@ -264,9 +265,9 @@ public List<KeylessSignature> sign(List<byte[]> artifactDigests)
rekorVerifier.verifyEntry(rekorResponse.getEntry());

result.add(
ImmutableKeylessSignature.builder()
KeylessSignature.builder()
.digest(artifactDigest)
.certPath(signingCert.getCertPath())
.certPath(signingCert)
.signature(signature)
.entry(rekorResponse.getEntry())
.build());
Expand All @@ -284,7 +285,8 @@ private void renewSigningCertificate()
if (signingCert != null) {
@SuppressWarnings("JavaUtilDate")
long lifetimeLeft =
signingCert.getLeafCertificate().getNotAfter().getTime() - System.currentTimeMillis();
((X509Certificate) signingCert.getCertificates().get(0)).getNotAfter().getTime()
- System.currentTimeMillis();
if (lifetimeLeft > minSigningCertificateLifetime.toMillis()) {
// The current certificate is fine, reuse it
return;
Expand All @@ -300,7 +302,7 @@ private void renewSigningCertificate()
signingCert = null;
signingCertPemBytes = null;
OidcToken tokenInfo = oidcClients.getIDToken();
SigningCertificate signingCert =
CertPath signingCert =
fulcioClient.signingCertificate(
CertificateRequest.newCertificateRequest(
signer.getPublicKey(),
Expand All @@ -311,7 +313,7 @@ private void renewSigningCertificate()
// allow that to be known
fulcioVerifier.verifySigningCertificate(signingCert);
this.signingCert = signingCert;
signingCertPemBytes = Certificates.toPemBytes(signingCert.getLeafCertificate());
signingCertPemBytes = Certificates.toPemBytes(signingCert);
} finally {
lock.writeLock().unlock();
}
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
5 changes: 2 additions & 3 deletions sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import dev.sigstore.fulcio.client.FulcioCertificateVerifier;
import dev.sigstore.fulcio.client.FulcioVerificationException;
import dev.sigstore.fulcio.client.FulcioVerifier;
import dev.sigstore.fulcio.client.SigningCertificate;
import dev.sigstore.rekor.client.HashedRekordRequest;
import dev.sigstore.rekor.client.RekorClient;
import dev.sigstore.rekor.client.RekorEntry;
Expand Down Expand Up @@ -142,8 +141,8 @@ public void verify(Path artifact, KeylessVerificationRequest request)
*/
public void verify(byte[] artifactDigest, KeylessVerificationRequest request)
throws KeylessVerificationException {
var signingCert = SigningCertificate.from(request.getKeylessSignature().getCertPath());
var leafCert = signingCert.getLeafCertificate();
var signingCert = request.getKeylessSignature().getCertPath();
var leafCert = Certificates.getLeaf(signingCert);

// this ensures the provided artifact digest matches what may have come from a bundle (in
// keyless signature)
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 @@ -137,15 +140,71 @@ public static CertPath toCertPath(Certificate certificate) throws CertificateExc
return cf.generateCertPath(Collections.singletonList(certificate));
}

/** Appends an X509Certificate to a {@link CertPath} as a leaf. */
public static CertPath appendCertPath(CertPath root, Certificate certificate)
throws CertificateException {
/** Appends an CertPath to another {@link CertPath} as children. */
public static CertPath append(CertPath parent, CertPath child) throws CertificateException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<Certificate> certs =
ImmutableList.<Certificate>builder()
.add(certificate)
.addAll(root.getCertificates())
.addAll(child.getCertificates())
.addAll(parent.getCertificates())
.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 parentPath the parent certPath to trim off the full certPath
* @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 trimParent(CertPath certPath, CertPath parentPath)
throws CertificateException {
if (!containsParent(certPath, parentPath)) {
throw new IllegalArgumentException("trim path was not the parent of the provider chain");
}
var certs = certPath.getCertificates();
var parent = parentPath.getCertificates();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return cf.generateCertPath(certs.subList(0, certs.size() - parent.size()));
}

/** Check if a parent certpath is the suffix of a certpath */
public static boolean containsParent(CertPath certPath, CertPath parentPath) {
var certs = certPath.getCertificates();
var parent = parentPath.getCertificates();
return parent.size() <= certs.size()
&& certs.subList(certs.size() - parent.size(), certs.size()).equals(parent);
}

/**
* 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(Certificate certificate) {
return Optional.ofNullable(((X509Certificate) certificate).getExtensionValue(SCT_X509_OID));
}

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

/** Check if the root of a CertPath is self-signed */
public static boolean isSelfSigned(CertPath certPath) {
return isSelfSigned(certPath.getCertificates().get(certPath.getCertificates().size() - 1));
}

public static X509Certificate getLeaf(CertPath certPath) {
return (X509Certificate) certPath.getCertificates().get(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

import static dev.sigstore.fulcio.v2.SigningCertificate.CertificateCase.SIGNED_CERTIFICATE_DETACHED_SCT;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.ByteString;
import dev.sigstore.encryption.certificates.Certificates;
import dev.sigstore.fulcio.v2.CAGrpc;
import dev.sigstore.fulcio.v2.CertificateChain;
import dev.sigstore.fulcio.v2.CreateSigningCertificateRequest;
import dev.sigstore.fulcio.v2.Credentials;
import dev.sigstore.fulcio.v2.PublicKey;
Expand All @@ -28,7 +31,13 @@
import dev.sigstore.http.ImmutableHttpParams;
import dev.sigstore.trustroot.CertificateAuthority;
import dev.sigstore.trustroot.SigstoreTrustedRoot;
import java.io.ByteArrayInputStream;
import java.security.cert.CertPath;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -80,9 +89,9 @@ public FulcioClient build() {
* Request a signing certificate from fulcio.
*
* @param request certificate request parameters
* @return a {@link SigningCertificate} from fulcio
* @return a {@link CertPath} from fulcio
*/
public SigningCertificate signingCertificate(CertificateRequest request)
public CertPath signingCertificate(CertificateRequest request)
throws InterruptedException, CertificateException {
if (!certificateAuthority.isCurrent()) {
throw new RuntimeException(
Expand Down Expand Up @@ -126,9 +135,27 @@ 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 Certificates.trimParent(
decodeCerts(certs.getSignedCertificateEmbeddedSct().getChain()),
certificateAuthority.getCertPath());
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}

@VisibleForTesting
CertPath decodeCerts(CertificateChain certChain) throws CertificateException {
var certificateFactory = CertificateFactory.getInstance("X.509");
var certs = new ArrayList<X509Certificate>();
if (certChain.getCertificatesCount() == 0) {
throw new CertificateParsingException(
"no valid PEM certificates were found in response from Fulcio");
}
for (var cert : certChain.getCertificatesList().asByteStringList()) {
certs.add(
(X509Certificate)
certificateFactory.generateCertificate(new ByteArrayInputStream(cert.toByteArray())));
}
return certificateFactory.generateCertPath(certs);
}
}
Loading

0 comments on commit 8f45e9b

Please sign in to comment.