Skip to content

Commit

Permalink
Merge pull request #726 from sigstore/fulcio-interface
Browse files Browse the repository at this point in the history
Put RekorClient and FulcioClient behind interfaces
  • Loading branch information
loosebazooka authored May 29, 2024
2 parents 07de1dc + 8dbb3dc commit 0e154bf
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 262 deletions.
6 changes: 4 additions & 2 deletions sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import dev.sigstore.encryption.signers.Signers;
import dev.sigstore.fulcio.client.CertificateRequest;
import dev.sigstore.fulcio.client.FulcioClient;
import dev.sigstore.fulcio.client.FulcioClientGrpc;
import dev.sigstore.fulcio.client.FulcioVerificationException;
import dev.sigstore.fulcio.client.FulcioVerifier;
import dev.sigstore.fulcio.client.UnsupportedAlgorithmException;
Expand All @@ -39,6 +40,7 @@
import dev.sigstore.oidc.client.OidcToken;
import dev.sigstore.rekor.client.HashedRekordRequest;
import dev.sigstore.rekor.client.RekorClient;
import dev.sigstore.rekor.client.RekorClientHttp;
import dev.sigstore.rekor.client.RekorParseException;
import dev.sigstore.rekor.client.RekorResponse;
import dev.sigstore.rekor.client.RekorVerificationException;
Expand Down Expand Up @@ -216,9 +218,9 @@ public KeylessSigner build()
Preconditions.checkNotNull(oidcIdentities);
Preconditions.checkNotNull(signer);
Preconditions.checkNotNull(minSigningCertificateLifetime);
var fulcioClient = FulcioClient.builder().setUri(fulcioUri).build();
var fulcioClient = FulcioClientGrpc.builder().setUri(fulcioUri).build();
var fulcioVerifier = FulcioVerifier.newFulcioVerifier(trustedRoot);
var rekorClient = RekorClient.builder().setUri(rekorUri).build();
var rekorClient = RekorClientHttp.builder().setUri(rekorUri).build();
var rekorVerifier = RekorVerifier.newRekorVerifier(trustedRoot);
return new KeylessSigner(
fulcioClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,135 +15,15 @@
*/
package dev.sigstore.fulcio.client;

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.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;
import dev.sigstore.fulcio.v2.PublicKeyRequest;
import dev.sigstore.http.GrpcChannels;
import dev.sigstore.http.HttpParams;
import dev.sigstore.http.ImmutableHttpParams;
import java.io.ByteArrayInputStream;
import java.net.URI;
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;

/** A client to communicate with a fulcio service instance over gRPC. */
public class FulcioClient {

public static final URI PUBLIC_GOOD_URI = URI.create("https://fulcio.sigstore.dev");
public static final URI STAGING_URI = URI.create("https://fulcio.sigstage.dev");

private final HttpParams httpParams;
private final URI uri;

public static Builder builder() {
return new Builder();
}

private FulcioClient(HttpParams httpParams, URI uri) {
this.uri = uri;
this.httpParams = httpParams;
}

public static class Builder {
private URI uri = PUBLIC_GOOD_URI;
private HttpParams httpParams = ImmutableHttpParams.builder().build();

private Builder() {}

/** Configure the http properties, see {@link HttpParams}. */
public Builder setHttpParams(HttpParams httpParams) {
this.httpParams = httpParams;
return this;
}

/** Base url of the remote fulcio instance. */
public Builder setUri(URI uri) {
this.uri = uri;
return this;
}

public FulcioClient build() {
return new FulcioClient(httpParams, uri);
}
}

/**
* Request a signing certificate from fulcio.
*
* @param request certificate request parameters
* @return a {@link CertPath} from fulcio
*/
public CertPath signingCertificate(CertificateRequest request)
throws InterruptedException, CertificateException {
// TODO: 1. If we want to reduce the cost of creating channels/connections, we could try
// to make a new connection once per batch of fulcio requests, but we're not really
// at that point yet.
// TODO: 2. getUri().getAuthority() is potentially prone to error if we don't get a good URI
var channel = GrpcChannels.newManagedChannel(uri.getAuthority(), httpParams);

try {
var client = CAGrpc.newBlockingStub(channel);
var credentials = Credentials.newBuilder().setOidcIdentityToken(request.getIdToken()).build();

String pemEncodedPublicKey =
"-----BEGIN PUBLIC KEY-----\n"
+ Base64.getEncoder().encodeToString(request.getPublicKey().getEncoded())
+ "\n-----END PUBLIC KEY-----";
var publicKeyRequest =
PublicKeyRequest.newBuilder()
.setPublicKey(
PublicKey.newBuilder()
.setAlgorithm(request.getPublicKeyAlgorithm())
.setContent(pemEncodedPublicKey)
.build())
.setProofOfPossession(ByteString.copyFrom(request.getProofOfPossession()))
.build();
var req =
CreateSigningCertificateRequest.newBuilder()
.setCredentials(credentials)
.setPublicKeyRequest(publicKeyRequest)
.build();

var certs =
client
.withDeadlineAfter(httpParams.getTimeout(), TimeUnit.SECONDS)
.createSigningCertificate(req);

if (certs.getCertificateCase() == SIGNED_CERTIFICATE_DETACHED_SCT) {
throw new CertificateException("Detached SCTs are not supported");
}
return decodeCerts(certs.getSignedCertificateEmbeddedSct().getChain());
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
/** A client to communicate with a fulcio service instance. */
public interface FulcioClient {
URI PUBLIC_GOOD_URI = URI.create("https://fulcio.sigstore.dev");
URI STAGING_URI = URI.create("https://fulcio.sigstage.dev");

@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);
}
CertPath signingCertificate(CertificateRequest request)
throws InterruptedException, CertificateException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* 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 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.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;
import dev.sigstore.fulcio.v2.PublicKeyRequest;
import dev.sigstore.http.GrpcChannels;
import dev.sigstore.http.HttpParams;
import dev.sigstore.http.ImmutableHttpParams;
import java.io.ByteArrayInputStream;
import java.net.URI;
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;

/** A client to communicate with a fulcio service instance over gRPC. */
public class FulcioClientGrpc implements FulcioClient {

private final HttpParams httpParams;
private final URI uri;

public static Builder builder() {
return new Builder();
}

private FulcioClientGrpc(HttpParams httpParams, URI uri) {
this.uri = uri;
this.httpParams = httpParams;
}

public static class Builder {
private URI uri = FulcioClient.PUBLIC_GOOD_URI;
private HttpParams httpParams = ImmutableHttpParams.builder().build();

private Builder() {}

/** Configure the http properties, see {@link HttpParams}. */
public Builder setHttpParams(HttpParams httpParams) {
this.httpParams = httpParams;
return this;
}

/** Base url of the remote fulcio instance. */
public Builder setUri(URI uri) {
this.uri = uri;
return this;
}

public FulcioClientGrpc build() {
return new FulcioClientGrpc(httpParams, uri);
}
}

/**
* Request a signing certificate from fulcio.
*
* @param request certificate request parameters
* @return a {@link CertPath} from fulcio
*/
@Override
public CertPath signingCertificate(CertificateRequest request)
throws InterruptedException, CertificateException {
// TODO: 1. If we want to reduce the cost of creating channels/connections, we could try
// to make a new connection once per batch of fulcio requests, but we're not really
// at that point yet.
// TODO: 2. getUri().getAuthority() is potentially prone to error if we don't get a good URI
var channel = GrpcChannels.newManagedChannel(uri.getAuthority(), httpParams);

try {
var client = CAGrpc.newBlockingStub(channel);
var credentials = Credentials.newBuilder().setOidcIdentityToken(request.getIdToken()).build();

String pemEncodedPublicKey =
"-----BEGIN PUBLIC KEY-----\n"
+ Base64.getEncoder().encodeToString(request.getPublicKey().getEncoded())
+ "\n-----END PUBLIC KEY-----";
var publicKeyRequest =
PublicKeyRequest.newBuilder()
.setPublicKey(
PublicKey.newBuilder()
.setAlgorithm(request.getPublicKeyAlgorithm())
.setContent(pemEncodedPublicKey)
.build())
.setProofOfPossession(ByteString.copyFrom(request.getProofOfPossession()))
.build();
var req =
CreateSigningCertificateRequest.newBuilder()
.setCredentials(credentials)
.setPublicKeyRequest(publicKeyRequest)
.build();

var certs =
client
.withDeadlineAfter(httpParams.getTimeout(), TimeUnit.SECONDS)
.createSigningCertificate(req);

if (certs.getCertificateCase() == SIGNED_CERTIFICATE_DETACHED_SCT) {
throw new CertificateException("Detached SCTs are not supported");
}
return decodeCerts(certs.getSignedCertificateEmbeddedSct().getChain());
} 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 0e154bf

Please sign in to comment.