RekorResponse.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.common.reflect.TypeToken;
import com.google.gson.JsonSyntaxException;
import java.net.URI;
import java.util.Map;
import org.immutables.value.Value;
/**
* Representation of a rekor response with the log location, raw log string and parsed log
* information.
*/
@Value.Immutable
public interface RekorResponse {
/**
* Create a RekorResponse from raw http response information.
*
* <p>A raw http response looks something like:
*
* <pre>
* {
* "dbf7b3f960d0d5853f80dfc968779554a628b44a30a4c8a2084b5bd2f6970085": { // log uuid
* "body": "eyJhcGlWZX...UzBLIn19fX0=",
* "integratedTime": 1653410800,
* "logID": "d32f30a3...18723a1bea496",
* "logIndex": 52,
* "verification": {
* "signedEntryTimestamp": "MEYCIQCYufGO...Oc9UAqVb+dCCl"
* }
* }
* }
* </pre>
*
* @param entryLocation the entry location from the http headers
* @param rawResponse the body of the rekor response as a string
* @return an immutable {@link RekorResponse} instance
* @throws RekorParseException if the rawResponse doesn't parse directly to a single rekor entry
*/
static RekorResponse newRekorResponse(URI entryLocation, String rawResponse)
throws RekorParseException {
var type = new TypeToken<Map<String, RekorEntry>>() {}.getType();
Map<String, RekorEntry> entryMap;
try {
entryMap = GSON.get().fromJson(rawResponse, type);
} catch (JsonSyntaxException
| NullPointerException
| NumberFormatException
| StringIndexOutOfBoundsException ex) {
throw new RekorParseException("Rekor entry json could not be parsed: " + rawResponse, ex);
}
if (entryMap == null) {
throw new RekorParseException("Expecting a single rekor entry in response but found none");
}
if (entryMap.size() != 1) {
throw new RekorParseException(
"Expecting a single rekor entry in response but found: " + entryMap.size());
}
var entry = entryMap.entrySet().iterator().next();
if (entry == null || entry.getKey() == null || entry.getValue() == null) {
throw new RekorParseException(
"Expecting single rekor entry but found an invalid entry: " + rawResponse);
}
return ImmutableRekorResponse.builder()
.entryLocation(entryLocation)
.raw(rawResponse)
.uuid(entry.getKey())
.entry(entry.getValue())
.build();
}
/** Path to the rekor entry on the log. */
URI getEntryLocation();
/** Returns the {@link RekorEntry} representation of the entry on the log. */
RekorEntry getEntry();
/** Returns the log uuid of entry represented by {@link #getEntry()}. */
String getUuid();
/** Returns the raw response from the rekor request. */
String getRaw();
}