Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: V1 of the trust root and the verification input. #5

Merged
merged 20 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
988 changes: 892 additions & 96 deletions gen/pb-go/common/v1/sigstore_common.pb.go

Large diffs are not rendered by default.

205 changes: 71 additions & 134 deletions gen/pb-go/rekor/v1/sigstore_rekor.pb.go

Large diffs are not rendered by default.

469 changes: 469 additions & 0 deletions gen/pb-go/trustroot/v1/sigstore_trustroot.pb.go

Large diffs are not rendered by default.

1,018 changes: 1,018 additions & 0 deletions gen/pb-go/verification/v1/sigstore_verification.pb.go

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions protos/sigstore_common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
syntax = "proto3";
package dev.sigstore.common.v1;

import "google/protobuf/timestamp.proto";

option go_package = "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1";
option java_package = "dev.sigstore.proto.common.v1";
option java_multiple_files = true;
Expand All @@ -36,6 +38,36 @@ enum HashAlgorithm {
SHA2_512 = 2;
}

// Subset of known signature algorithms.
enum SignatureAlgorithm {
kommendorkapten marked this conversation as resolved.
Show resolved Hide resolved
SIGNATURE_ALGORITHM_UNSPECIFIED = 0;
ECDSA_P256_SHA_256 = 1; // See NIST FIPS 186-4
ECDSA_P256_HMAC_SHA_256 = 2; // See RFC6979
ED25519 = 3; // See RFC8032
RSA_PKCS1V5 = 4; // See RFC8017
RSA_PSS = 5; // See RFC8017
}

// Details of a specific public key, capturing the the key encoding method,
// and signature algorithm.
// To avoid the possibility of contradicting formats such as PKCS1 with
// ED25519 the valid permutations are listed as a linear set instead of a
// cartesian set (i.e one combined variable instead of two, one for encoding
// and one for the signature algorithm).
enum PublicKeyDetails {
KEY_FORMAT_UNSPECIFIED = 0;
// RSA
PKCS1_RSA_PKCS1V5 = 1; // See RFC8017
PKCS1_RSA_PSS = 2; // See RFC8017
PKIX_RSA_PKCS1V5 = 3;
PKIX_RSA_PSS = 4;
// ECDSA
PKIX_ECDSA_P256_SHA_256 = 5; // See NIST FIPS 186-4
PKIX_ECDSA_P256_HMAC_SHA_256 = 6; // See RFC6979
// Ed 25519
PKIX_ED25519 = 7; // See RFC8032
}

// HashOutput captures a digest of a 'message' (generic octet sequence)
// and the corresponding hash algorithm used.
message HashOutput {
Expand All @@ -60,13 +92,35 @@ message MessageSignature {
bytes signature = 2;
}

// LogId captures the identity of a transparency log.
message LogId {
oneof id {
// The unique id of the log, represented as the SHA-256 hash
// of the log's public key, computed over the DER encoding.
// https://www.rfc-editor.org/rfc/rfc6962#section-3.2
bytes key_id = 1;
kommendorkapten marked this conversation as resolved.
Show resolved Hide resolved
// Currently not used but proposed by
// https://datatracker.ietf.org/doc/rfc9162/
ObjectIdentifier oid = 2;
}
}

// This message holds a RFC 3161 timestamp.
message RFC3161SignedTimestamp {
// Signed timestamp is the DER encoded TimeStampResponse.
// See https://www.rfc-editor.org/rfc/rfc3161.html#section-2.4.2
bytes signed_timestamp = 1;
}

message PublicKey {
// DER-encoded public key, encoding method is specified by the
// key_details attribute.
optional bytes raw_bytes = 1;
// Key encoding and signature algorithm to use for this key.
PublicKeyDetails key_details = 2;
// Optional validity period for this key.
optional TimeRange valid_for = 3;
kommendorkapten marked this conversation as resolved.
Show resolved Hide resolved
}

// PublicKeyIdentifier can be used to identify an (out of band) delivered
// key, to verify a signature.
Expand All @@ -82,11 +136,39 @@ message PublicKeyIdentifier {
string hint = 1;
}

// An ASN1. OBJECT IDENTIFIER
message ObjectIdentifier {
repeated int32 id = 1;
}

// An OID and the corresponding (byte) value.
message ObjectIdentifierValuePair {
ObjectIdentifier oid = 1;
bytes value = 2;
}

message DistinguishedName {
string organization = 1;
string common_name = 2;
}

message X509Certificate {
// DER-encoded X.509 certificate.
bytes raw_bytes = 1;
}

enum SubjectAlternativeNameType {
DNS = 0;
Email = 1;
URI = 2;
}

message SubjectAlternativeName {
SubjectAlternativeNameType type = 1;
// A regular expression describing the expected value for the SAN.
string value = 2;
}

// A chain of X.509 certificates.
message X509CertificateChain {
// The chain of certificates, with indices 0 to n.
Expand All @@ -105,3 +187,12 @@ message VerificationMaterial {
X509CertificateChain x509_certificate_chain = 2;
}
}

// The time range is half-open and does not include the end timestamp,
// i.e [start, end).
// End is optional to be able to capture a period that has started but
// has no known end.
message TimeRange {
google.protobuf.Timestamp start = 1;
optional google.protobuf.Timestamp end = 2;
}
13 changes: 3 additions & 10 deletions protos/sigstore_rekor.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
syntax = "proto3";
package dev.sigstore.rekor.v1;

import "sigstore_common.proto";

option go_package = "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1";
option java_package = "dev.sigstore.proto.rekor.v1";
option java_multiple_files = true;
Expand Down Expand Up @@ -76,15 +78,6 @@ message InclusionPromise {
bytes signed_entry_timestamp = 1;
}

// LogId captures the identity of a transparency log.
message LogId {
// The unique id of the log, represented as the SHA-256 hash
// of the log's public key, computed over the DER encoding.
// This is similar to how it works for certificate transparency logs:
// https://www.rfc-editor.org/rfc/rfc6962#section-3.2
bytes key_id = 1;
}

// TransparencyLogEntry captures all the details required from Rekor to
// reconstruct an entry, given that the payload is provided via other means.
// This type can easily be created from the existing response from Rekor.
Expand All @@ -97,7 +90,7 @@ message TransparencyLogEntry {
// The index of the entry in the log.
int64 log_index = 1;
// The unique identifier of the log.
LogId log_id = 2;
dev.sigstore.common.v1.LogId log_id = 2;
// The kind (type) and version of the object associated with this
// entry. These values are required to construct the entry during
// verification.
Expand Down
86 changes: 86 additions & 0 deletions protos/sigstore_trustroot.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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.

syntax = "proto3";
package dev.sigstore.trustroot.v1;

import "sigstore_common.proto";

option go_package = "github.com/sigstore/protobuf-specs/gen/pb-go/trustroot/v1";
option java_package = "dev.sigstore.proto.trustroot.v1";
option java_multiple_files = true;
option java_outer_classname = "TrustRootProto";

// TransparencyLogInstance describes the immutable parameters from a
// transparency log.
// See https://www.rfc-editor.org/rfc/rfc9162.html#name-log-parameters
// for more details.
// The incluced parameters are the minimal set required to identify a log,
// and verify an inclusion promise.
message TransparencyLogInstance {
kommendorkapten marked this conversation as resolved.
Show resolved Hide resolved
// The base URL at which can be used to URLs for the client.
string base_url = 1;
// The hash algorithm used for the Merkle Tree.
dev.sigstore.common.v1.HashAlgorithm hash_algorithm = 2;
// The public key used to verify signatures generated by the log.
// This attribute contains the signature algorithm used by the log.
dev.sigstore.common.v1.PublicKey public_key = 3;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect more than public_key being embedded in the TUF target? Today we only have the public_key in the TUF root, so just wondering if we're going to bake some of this information into our TUF targets? For example, we could serialize this into the TUF target for a given Rekor instance, so just wondering out loud. Jotted it down here, but applicable to CA below as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't say for sure, but I think so. As an example the base_url corresponds to the metadata field uri in the TUF target. We are talking about adding a time range for the log too, which would then populate the same fields in the linked public_key. The same reasoning goes for CA too of course.
This is not the complete set of attributes, some are missing still like the hash and signature algorithm for the log, or the distinguished name for a CA. As they are relevant from a verification perspective, we should think how such values are communicated. The distinguished may be part of the policy decision though.

// The unique identifier for this transparency log.
dev.sigstore.common.v1.LogId log_id = 4;
}

// CertificateAuthority enlists the information required to identify which
// CA to use and perform signature verification.
message CertificateAuthority {
// The root certificate MUST be self-signed, and so the subject and
// issuer are the same.
dev.sigstore.common.v1.DistinguishedName subject = 1;
// The URI at which the CA can be accessed.
string uri = 2;
// The certificate chain for this CA.
dev.sigstore.common.v1.X509CertificateChain cert_chain = 3;
// The time the *entire* chain was valid. This is at max the
// longest interval when *all* certificates in the chain were valid,
// but it MAY be shorter.
dev.sigstore.common.v1.TimeRange valid_for = 4;
}

// TrustedRoot describes the client's complete set of trusted entities.
// How the TrustedRoot is populated is not specified, but can be a
// combination of many sources such as TUF repositories, files on disk etc.
//
// The TrustedRoot is not meant to be used for any artifact verification, only
// to capture the complete/global set of trusted verification materials.
// When verifying an artifact, based on the artifact and policies, a selection
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking out loud if CA & TransparencyInstance has enough information in there to write policies against. Both of them have a URI / base_url and CA has DistinguishedName, but just curious if that's enough and that's what one would use to filter out the instances that should be used for verification.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess thinking that the one (only?) reason for having the TrustedRoot as a collection of all the everythings is that you can then pass it through some sort of policy machine which will distill it down to the verification materials before calling the actual verification with a subset of keys/authorities.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess thinking that the one (only?) reason for having the TrustedRoot as a collection of all the everythings is that you can then pass it through some sort of policy machine which will distill it down to the verification materials before calling the actual verification with a subset of keys/authorities.

Yes, that's the exact reasoning.

Based on the scenarios I'm aware of, we should have enough data to confidently distill what exact "instance" of a CA or log to use:

  • The name (log_id or distinguished name)
  • URI
  • Time range the service was active for

What could possibly cause some confusion is that if a client is relying on a TUF multi root setup, where multiple TUF roots exposes targets for the log or CA with identical data. But this is an explicit confusion as I see it, and so the precondition that the client trusts the TUF root may not hold any more, then all bets are off, as the TUF repository may serve arbitrary data. But I don't think of this as a real problem.

What the policy will protect against is e.g. where an artifact was signed with a certificate from the wrong CA given a policy, and for that I'd say we are good.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic. Again just thinking of how the layer above this (policy-controller) will sort out the details. But, I guess that's my problem :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working a bit on that too, I hope that I can share some details on this later today!

// of keys/authorities are expected to be extracted and provided to the
// verification function. This way the set of keys/authorities kan be kept to
// a minimal set by the policy to gain better control over what signatures
// that are allowed.
message TrustedRoot {
// A set of trusted Rekor servers.
repeated TransparencyLogInstance tlogs = 1;
// A set of trusted certificate authorites (e.g Fulcio), and any
// intermediate certificates they provide.
// If a CA is issuing multiple intermediate certificate, each
// combination shall be represented as separate chain. I.e, a single
// root cert may appear in multiple chains but with different
// intermediate and/or leaf certificates.
// The certificates are intended to be used for verifying artifact
// signatures.
repeated CertificateAuthority certificate_authorities = 2;
// A set of trusted certificate transparency logs.
repeated TransparencyLogInstance ctlogs = 3;
// A set of trusted timestamping authorities.
repeated CertificateAuthority timestamp_authorities = 4;
}
136 changes: 136 additions & 0 deletions protos/sigstore_verification.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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.

syntax = "proto3";
package dev.sigstore.verification.v1;

import "sigstore_common.proto";
import "sigstore_trustroot.proto";
import "sigstore_bundle.proto";

option go_package = "github.com/sigstore/protobuf-specs/gen/pb-go/verification/v1";
option java_package = "dev.sigstore.proto.verification.v1";
option java_multiple_files = true;
option java_outer_classname = "VerificationProto";

// The identity of a X.509 Certificate signer.
message CertificateIdentity {
// The X.509v3 issuer extension (OID 1.3.6.1.4.1.57264.1.1)
string issuer = 1;
dev.sigstore.common.v1.SubjectAlternativeName san = 2;
// An unordered list of OIDs that must be verified.
// All OID/values provided in this list MUST exactly match against
// the values in the certificate for verification to be successful.
repeated dev.sigstore.common.v1.ObjectIdentifierValuePair oids = 3;
}

message CertificateIdentities {
repeated CertificateIdentity identities = 1;
}

message PublicKeyIdentities {
repeated dev.sigstore.common.v1.PublicKey public_keys = 1;
}

// A light-weight set of options/policies for identifying trusted signers,
// used during verification of a single artifact.
message ArtifactVerificationOptions {
message TlogOptions {
// Number of transparency logs the entry must appear on.
int32 threshold = 1;
// Perform an online inclusion proof.
bool perform_online_verification = 2;
kommendorkapten marked this conversation as resolved.
Show resolved Hide resolved
// Disable verification for transparency logs.
bool disable = 3;
}
message CtlogOptions {
// The number of ct transparency logs the certificate must
// appear on.
int32 threshold = 1;
// Expect detached SCTs.
// This is not supported right now as we can't capture an
// detached SCT in the bundle.
bool detached_sct = 2;
// Disable ct transparency log verification
bool disable = 3;
}
message TimestampAuthorityOptions {
// The number of signed timestamps that are expected.
int32 threshold = 1;
// Disable signed timestamp verification.
bool disable = 2;
}

// At least one identity MUST be provided. Providing zero identities
kommendorkapten marked this conversation as resolved.
Show resolved Hide resolved
// is an error. If at least one provided identity is found as a
// signer, the verification is considered successful.
oneof signers {
CertificateIdentities certificate_identities = 1;
// To simplify verification implementation, the logic for
// bundle verification should be implemented as a
// higher-order function, where one of argument should be an
// interface over the set of trusted public keys, like this:
// `Verify(bytes artifact, bytes signature, string key_id)`.
// This way the caller is in full control of mapping the
// identified (or hinted) key in the bundle to one of the
// trusted keys, as this process is inherently application
// specific.
PublicKeyIdentities public_keys = 2;
}
// Optional options for artifact transparency log verification.
// If none is provided, the default verification options are:
// Threshold: 1
// Online verification: false
// Disable: false
optional TlogOptions tlog_options = 3;
// Optional options for certificate transparency log verification.
// If none is provided, the default verification options are:
// Threshold: 1
// Detached SCT: false
// Disable: false
optional CtlogOptions ctlog_options = 4;
// Optional options for certificate signed timestamp verification.
// If none is provided, the default verification options are:
// Threshold: 1
// Disable: false
optional TimestampAuthorityOptions tsa_options = 5;
}

message Artifact {
oneof data {
// Location of the artifact
string artifact_uri = 1;
// The raw bytes of the artifact
bytes artifact = 2;
}
}

// Input captures all that is needed to call the bundle verification method,
// to verify a single artifact referenced by the bundle.
message Input {
// The verification materials provided during a bundle verification.
// The running process is usually preloaded with a "global"
// dev.sisgtore.trustroot.TrustedRoot.v1 instance. Prior to
// verifying an artifact (i.e a bundle), and/or based on current
// policy, some selection is expected to happen, to filter out the
// exact certificate authority to use, which transparency logs are
// relevant etc. The result should b ecaptured in the
// `artifact_trust_root`.
dev.sigstore.trustroot.v1.TrustedRoot artifact_trust_root = 1;
ArtifactVerificationOptions artifact_verification_options = 2;
dev.sigstore.bundle.v1.Bundle bundle = 3;
// If the bundle contains a message signature, the artifact must be
// provided.
optional Artifact artifact = 4;
}