RekorClientHttp.java
/*
* 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 com.google.api.client.util.Preconditions;
import dev.sigstore.http.HttpClients;
import dev.sigstore.http.HttpParams;
import dev.sigstore.trustroot.Service;
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 = HttpParams.builder().build();
private Service service;
private Builder() {}
/** Configure the http properties, see {@link HttpParams}. */
public Builder setHttpParams(HttpParams httpParams) {
this.httpParams = httpParams;
return this;
}
/** Service information for a remote rekor instance. */
public Builder setService(Service service) {
this.service = service;
return this;
}
public RekorClientHttp build() {
Preconditions.checkNotNull(service);
return new RekorClientHttp(httpParams, service.getUrl());
}
}
@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<RekorEntry> getEntry(HashedRekordRequest hashedRekordRequest)
throws IOException, RekorParseException {
return getEntry(hashedRekordRequest.computeUUID());
}
@Override
public Optional<RekorEntry> 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<String> searchEntry(
String email, String hash, String publicKeyFormat, String publicKeyContent)
throws IOException {
URI rekorSearchEndpoint = uri.resolve(REKOR_INDEX_SEARCH_PATH);
HashMap<String, Object> publicKeyParams = null;
if (publicKeyContent != null) {
publicKeyParams = new HashMap<>();
publicKeyParams.put("format", publicKeyFormat);
publicKeyParams.put("content", publicKeyContent);
}
var data = new HashMap<String, Object>();
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));
}
}