From 8dbb3dcd491e48aaf0ba4053ae6d272d9e45ecab Mon Sep 17 00:00:00 2001 From: Appu Goundan Date: Wed, 29 May 2024 12:59:43 -0400 Subject: [PATCH] Put RekorClient and FulcioClient behind interfaces This lets them be a little more extensible if the underlying communication mechanism needs to change (grpc, http) Signed-off-by: Appu Goundan --- .../main/java/dev/sigstore/KeylessSigner.java | 6 +- .../sigstore/fulcio/client/FulcioClient.java | 132 +-------------- .../fulcio/client/FulcioClientGrpc.java | 147 +++++++++++++++++ .../sigstore/rekor/client/RekorClient.java | 148 +++-------------- .../rekor/client/RekorClientHttp.java | 156 ++++++++++++++++++ .../rekor/client/RekorEntryFetcher.java | 4 +- ...entTest.java => FulcioClientGrpcTest.java} | 8 +- ...ientTest.java => RekorClientHttpTest.java} | 4 +- 8 files changed, 343 insertions(+), 262 deletions(-) create mode 100644 sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClientGrpc.java create mode 100644 sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClientHttp.java rename sigstore-java/src/test/java/dev/sigstore/fulcio/client/{FulcioClientTest.java => FulcioClientGrpcTest.java} (95%) rename sigstore-java/src/test/java/dev/sigstore/rekor/client/{RekorClientTest.java => RekorClientHttpTest.java} (98%) diff --git a/sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java b/sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java index eb5846a1..1a773465 100644 --- a/sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java +++ b/sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java @@ -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; @@ -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; @@ -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, diff --git a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClient.java b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClient.java index 40d8cd2e..5526b512 100644 --- a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClient.java +++ b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClient.java @@ -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(); - 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; } diff --git a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClientGrpc.java b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClientGrpc.java new file mode 100644 index 00000000..043c8bd4 --- /dev/null +++ b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClientGrpc.java @@ -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(); + 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); + } +} diff --git a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClient.java b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClient.java index cad493fe..da6a5ac7 100644 --- a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClient.java +++ b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClient.java @@ -15,66 +15,15 @@ */ package dev.sigstore.rekor.client; -import static dev.sigstore.json.GsonSupplier.GSON; - -import com.google.api.client.http.ByteArrayContent; -import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpResponseException; -import dev.sigstore.http.HttpClients; -import dev.sigstore.http.HttpParams; -import dev.sigstore.http.ImmutableHttpParams; import java.io.IOException; import java.net.URI; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Optional; /** A client to communicate with a rekor service instance. */ -public class RekorClient { - public static final URI PUBLIC_GOOD_URI = URI.create("https://rekor.sigstore.dev"); - public static final URI STAGING_URI = URI.create("https://rekor.sigstage.dev"); - - public static final String REKOR_ENTRIES_PATH = "/api/v1/log/entries"; - public static final String REKOR_INDEX_SEARCH_PATH = "/api/v1/index/retrieve"; - - private final HttpParams httpParams; - private final URI uri; - - public static RekorClient.Builder builder() { - return new RekorClient.Builder(); - } - - private RekorClient(HttpParams httpParams, URI uri) { - this.uri = uri; - this.httpParams = httpParams; - } - - public static class Builder { - private HttpParams httpParams = ImmutableHttpParams.builder().build(); - private URI uri = PUBLIC_GOOD_URI; - - private Builder() {} - - /** Configure the http properties, see {@link HttpParams}, {@link ImmutableHttpParams}. */ - public Builder setHttpParams(HttpParams httpParams) { - this.httpParams = httpParams; - return this; - } - - /** Base url of the remote rekor instance. */ - public Builder setUri(URI uri) { - this.uri = uri; - return this; - } - - public RekorClient build() { - return new RekorClient(httpParams, uri); - } - } +public interface RekorClient { + URI PUBLIC_GOOD_URI = URI.create("https://rekor.sigstore.dev"); + URI STAGING_URI = URI.create("https://rekor.sigstage.dev"); /** * Put a new hashedrekord entry on the Rekor log. @@ -82,54 +31,25 @@ public RekorClient build() { * @param hashedRekordRequest the request to send to rekor * @return a {@link RekorResponse} with information about the log entry */ - public RekorResponse putEntry(HashedRekordRequest hashedRekordRequest) - throws IOException, RekorParseException { - URI rekorPutEndpoint = uri.resolve(REKOR_ENTRIES_PATH); - - HttpRequest req = - HttpClients.newRequestFactory(httpParams) - .buildPostRequest( - new GenericUrl(rekorPutEndpoint), - ByteArrayContent.fromString( - "application/json", hashedRekordRequest.toJsonPayload())); - req.getHeaders().set("Accept", "application/json"); - req.getHeaders().set("Content-Type", "application/json"); + RekorResponse putEntry(HashedRekordRequest hashedRekordRequest) + throws IOException, RekorParseException; - HttpResponse resp = req.execute(); - if (resp.getStatusCode() != 201) { - throw new IOException( - String.format( - Locale.ROOT, - "bad response from rekor @ '%s' : %s", - rekorPutEndpoint, - resp.parseAsString())); - } - - URI rekorEntryUri = uri.resolve(resp.getHeaders().getLocation()); - String entry = resp.parseAsString(); - return RekorResponse.newRekorResponse(rekorEntryUri, entry); - } - - public Optional getEntry(HashedRekordRequest hashedRekordRequest) - throws IOException, RekorParseException { - return getEntry(hashedRekordRequest.computeUUID()); - } + /** + * Get an entry from the log + * + * @param hashedRekordRequest the entry to find + * @return the entry if found on the log, empty otherwise + */ + Optional getEntry(HashedRekordRequest hashedRekordRequest) + throws IOException, RekorParseException; - public Optional getEntry(String UUID) throws IOException, RekorParseException { - URI getEntryURI = uri.resolve(REKOR_ENTRIES_PATH + "/" + UUID); - HttpRequest req = - HttpClients.newRequestFactory(httpParams).buildGetRequest(new GenericUrl(getEntryURI)); - req.getHeaders().set("Accept", "application/json"); - HttpResponse response; - try { - response = req.execute(); - } catch (HttpResponseException e) { - if (e.getStatusCode() == 404) return Optional.empty(); - throw e; - } - return Optional.of( - RekorResponse.newRekorResponse(getEntryURI, response.parseAsString()).getEntry()); - } + /** + * Get an entry from the log + * + * @param UUID the uuid of the log entry + * @return the entry if found on the log, empty otherwise + */ + Optional getEntry(String UUID) throws IOException, RekorParseException; /** * Returns a list of UUIDs for matching entries for the given search parameters. @@ -139,31 +59,7 @@ public Optional getEntry(String UUID) throws IOException, RekorParse * @param publicKeyFormat format of public key (one of 'pgp','x509','minisign', 'ssh', 'tuf') * @param publicKeyContent public key base64 encoded content */ - public List searchEntry( + List searchEntry( String email, String hash, String publicKeyFormat, String publicKeyContent) - throws IOException { - URI rekorSearchEndpoint = uri.resolve(REKOR_INDEX_SEARCH_PATH); - - HashMap publicKeyParams = null; - if (publicKeyContent != null) { - publicKeyParams = new HashMap<>(); - publicKeyParams.put("format", publicKeyFormat); - publicKeyParams.put("content", publicKeyContent); - } - var data = new HashMap(); - data.put("email", email); - data.put("hash", hash); - data.put("publicKey", publicKeyParams); - - String contentString = GSON.get().toJson(data); - HttpRequest req = - HttpClients.newRequestFactory(httpParams) - .buildPostRequest( - new GenericUrl(rekorSearchEndpoint), - ByteArrayContent.fromString("application/json", contentString)); - req.getHeaders().set("Accept", "application/json"); - req.getHeaders().set("Content-Type", "application/json"); - var response = req.execute(); - return Arrays.asList(GSON.get().fromJson(response.parseAsString(), String[].class)); - } + throws IOException; } diff --git a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClientHttp.java b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClientHttp.java new file mode 100644 index 00000000..921b6b68 --- /dev/null +++ b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClientHttp.java @@ -0,0 +1,156 @@ +/* + * 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.rekor.client; + +import static dev.sigstore.json.GsonSupplier.GSON; + +import com.google.api.client.http.ByteArrayContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import dev.sigstore.http.HttpClients; +import dev.sigstore.http.HttpParams; +import dev.sigstore.http.ImmutableHttpParams; +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +/** A client to communicate with a rekor service instance over http. */ +public class RekorClientHttp implements RekorClient { + public static final String REKOR_ENTRIES_PATH = "/api/v1/log/entries"; + public static final String REKOR_INDEX_SEARCH_PATH = "/api/v1/index/retrieve"; + + private final HttpParams httpParams; + private final URI uri; + + public static RekorClientHttp.Builder builder() { + return new RekorClientHttp.Builder(); + } + + private RekorClientHttp(HttpParams httpParams, URI uri) { + this.uri = uri; + this.httpParams = httpParams; + } + + public static class Builder { + private HttpParams httpParams = ImmutableHttpParams.builder().build(); + private URI uri = RekorClient.PUBLIC_GOOD_URI; + + private Builder() {} + + /** Configure the http properties, see {@link HttpParams}, {@link ImmutableHttpParams}. */ + public Builder setHttpParams(HttpParams httpParams) { + this.httpParams = httpParams; + return this; + } + + /** Base url of the remote rekor instance. */ + public Builder setUri(URI uri) { + this.uri = uri; + return this; + } + + public RekorClientHttp build() { + return new RekorClientHttp(httpParams, uri); + } + } + + @Override + public RekorResponse putEntry(HashedRekordRequest hashedRekordRequest) + throws IOException, RekorParseException { + URI rekorPutEndpoint = uri.resolve(REKOR_ENTRIES_PATH); + + HttpRequest req = + HttpClients.newRequestFactory(httpParams) + .buildPostRequest( + new GenericUrl(rekorPutEndpoint), + ByteArrayContent.fromString( + "application/json", hashedRekordRequest.toJsonPayload())); + req.getHeaders().set("Accept", "application/json"); + req.getHeaders().set("Content-Type", "application/json"); + + HttpResponse resp = req.execute(); + if (resp.getStatusCode() != 201) { + throw new IOException( + String.format( + Locale.ROOT, + "bad response from rekor @ '%s' : %s", + rekorPutEndpoint, + resp.parseAsString())); + } + + URI rekorEntryUri = uri.resolve(resp.getHeaders().getLocation()); + String entry = resp.parseAsString(); + return RekorResponse.newRekorResponse(rekorEntryUri, entry); + } + + @Override + public Optional getEntry(HashedRekordRequest hashedRekordRequest) + throws IOException, RekorParseException { + return getEntry(hashedRekordRequest.computeUUID()); + } + + @Override + public Optional getEntry(String UUID) throws IOException, RekorParseException { + URI getEntryURI = uri.resolve(REKOR_ENTRIES_PATH + "/" + UUID); + HttpRequest req = + HttpClients.newRequestFactory(httpParams).buildGetRequest(new GenericUrl(getEntryURI)); + req.getHeaders().set("Accept", "application/json"); + HttpResponse response; + try { + response = req.execute(); + } catch (HttpResponseException e) { + if (e.getStatusCode() == 404) return Optional.empty(); + throw e; + } + return Optional.of( + RekorResponse.newRekorResponse(getEntryURI, response.parseAsString()).getEntry()); + } + + @Override + public List searchEntry( + String email, String hash, String publicKeyFormat, String publicKeyContent) + throws IOException { + URI rekorSearchEndpoint = uri.resolve(REKOR_INDEX_SEARCH_PATH); + + HashMap publicKeyParams = null; + if (publicKeyContent != null) { + publicKeyParams = new HashMap<>(); + publicKeyParams.put("format", publicKeyFormat); + publicKeyParams.put("content", publicKeyContent); + } + var data = new HashMap(); + data.put("email", email); + data.put("hash", hash); + data.put("publicKey", publicKeyParams); + + String contentString = GSON.get().toJson(data); + HttpRequest req = + HttpClients.newRequestFactory(httpParams) + .buildPostRequest( + new GenericUrl(rekorSearchEndpoint), + ByteArrayContent.fromString("application/json", contentString)); + req.getHeaders().set("Accept", "application/json"); + req.getHeaders().set("Content-Type", "application/json"); + var response = req.execute(); + return Arrays.asList(GSON.get().fromJson(response.parseAsString(), String[].class)); + } +} diff --git a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorEntryFetcher.java b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorEntryFetcher.java index f7282cef..997320e7 100644 --- a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorEntryFetcher.java +++ b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorEntryFetcher.java @@ -72,8 +72,8 @@ public static RekorEntryFetcher fromTrustedRoot(TrustedRootProvider trustedRootP trustedRoot.getTLogs().stream() .map(TransparencyLog::getBaseUrl) .distinct() - .map(uri -> RekorClient.builder().setUri(uri).build()) - .collect(Collectors.toList()); + .map(uri -> RekorClientHttp.builder().setUri(uri).build()) + .collect(Collectors.toList()); return new RekorEntryFetcher(rekorClients); } diff --git a/sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioClientTest.java b/sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioClientGrpcTest.java similarity index 95% rename from sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioClientTest.java rename to sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioClientGrpcTest.java index 984ad808..8375319d 100644 --- a/sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioClientTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioClientGrpcTest.java @@ -29,7 +29,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -public class FulcioClientTest { +public class FulcioClientGrpcTest { @Test @ExtendWith({FakeCTLogServer.class, MockOAuth2ServerExtension.class, FulcioWrapper.class}) @@ -48,7 +48,7 @@ public void testSigningCert( // ask fulcio for a signing cert var client = - FulcioClient.builder() + FulcioClientGrpc.builder() .setHttpParams(ImmutableHttpParams.builder().allowInsecureConnections(true).build()) .setUri(fulcioWrapper.getGrpcURI2()) .build(); @@ -78,7 +78,7 @@ public void testSigningCert_NoSct( // ask fulcio for a signing cert var client = - FulcioClient.builder() + FulcioClientGrpc.builder() .setHttpParams(ImmutableHttpParams.builder().allowInsecureConnections(true).build()) .setUri(fulcioWrapper.getGrpcURI2()) .build(); @@ -94,7 +94,7 @@ public void testDecode_embeddedGrpc() throws Exception { Resources.toString( Resources.getResource("dev/sigstore/samples/fulcio-response/valid/certWithSct.pem"), StandardCharsets.UTF_8)); - var signingCert = FulcioClient.builder().build().decodeCerts(certs); + var signingCert = FulcioClientGrpc.builder().build().decodeCerts(certs); Assertions.assertTrue( Certificates.getEmbeddedSCTs(Certificates.getLeaf(signingCert)).isPresent()); Assertions.assertEquals(3, signingCert.getCertificates().size()); diff --git a/sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorClientTest.java b/sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorClientHttpTest.java similarity index 98% rename from sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorClientTest.java rename to sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorClientHttpTest.java index ee8fa5f3..adcaf67a 100644 --- a/sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorClientTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorClientHttpTest.java @@ -40,7 +40,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -public class RekorClientTest { +public class RekorClientHttpTest { private static RekorClient client; private static HashedRekordRequest req; @@ -49,7 +49,7 @@ public class RekorClientTest { @BeforeAll public static void setupClient() throws Exception { // this tests directly against rekor in prod, it's a bit hard to bring up a rekor instance - client = RekorClient.builder().build(); + client = RekorClientHttp.builder().build(); req = createdRekorRequest(); resp = client.putEntry(req); }