SigstoreTrustedRoot.java

/*
 * Copyright 2023 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.trustroot;

import com.google.api.client.util.Lists;
import com.google.common.base.Strings;
import dev.sigstore.json.ProtoJson;
import dev.sigstore.proto.trustroot.v1.TrustedRoot;
import dev.sigstore.proto.trustroot.v1.TrustedRootOrBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.stream.Collectors;
import org.immutables.value.Value.Immutable;

@Immutable
public interface SigstoreTrustedRoot {

  /** A list of certificate authorities associated with this trustedroot. */
  List<CertificateAuthority> getCAs();

  /** A list of binary transparency logs associated with this trustedroot. */
  List<TransparencyLog> getTLogs();

  /** A list of certificate transparency logs associated with this trustedroot. */
  List<TransparencyLog> getCTLogs();

  /** A list of timestamping authorities associated with this trustroot. */
  List<CertificateAuthority> getTSAs();

  /** Parse the trusted root from an input stream and close the stream */
  static SigstoreTrustedRoot from(InputStream json) throws SigstoreConfigurationException {
    var trustedRootBuilder = TrustedRoot.newBuilder();
    try (var reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {
      ProtoJson.parser().merge(reader, trustedRootBuilder);
    } catch (IOException ex) {
      throw new SigstoreConfigurationException("Could not parse trusted root", ex);
    }
    return from(trustedRootBuilder);
  }

  /** Create an instance from a parsed proto definition of a trustedroot. */
  static SigstoreTrustedRoot from(TrustedRootOrBuilder proto)
      throws SigstoreConfigurationException {
    if (!Strings.isNullOrEmpty(proto.getMediaType())
        && !proto
            .getMediaType()
            .equals("application/vnd.dev.sigstore.trustedroot+json;version=0.1")) {
      throw new SigstoreConfigurationException(
          "Unsupported trusted root mediaType: " + proto.getMediaType());
    }
    List<CertificateAuthority> cas = Lists.newArrayList();
    for (var certAuthority : proto.getCertificateAuthoritiesList()) {
      try {
        cas.add(CertificateAuthority.from(certAuthority));
      } catch (CertificateException ce) {
        throw new SigstoreConfigurationException("Could not parse certificate in trusted root", ce);
      }
    }

    List<TransparencyLog> tlogs =
        proto.getTlogsList().stream().map(TransparencyLog::from).collect(Collectors.toList());

    List<TransparencyLog> ctlogs =
        proto.getCtlogsList().stream().map(TransparencyLog::from).collect(Collectors.toList());

    List<CertificateAuthority> tsas = Lists.newArrayList();
    for (var timestampAuthority : proto.getTimestampAuthoritiesList()) {
      try {
        tsas.add(CertificateAuthority.from(timestampAuthority));
      } catch (CertificateException ce) {
        throw new SigstoreConfigurationException("Could not parse TSAs in trusted root", ce);
      }
    }

    return ImmutableSigstoreTrustedRoot.builder()
        .cAs(cas)
        .tLogs(tlogs)
        .cTLogs(ctlogs)
        .tSAs(tsas)
        .build();
  }
}