| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // 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 ct holds core types and utilities for Certificate Transparency. |
| package ct |
| |
| import ( |
| "crypto/sha256" |
| "encoding/base64" |
| "encoding/json" |
| "fmt" |
| |
| "github.com/google/certificate-transparency-go/tls" |
| "github.com/google/certificate-transparency-go/x509" |
| ) |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // The following structures represent those outlined in RFC6962; any section |
| // numbers mentioned refer to that RFC. |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // LogEntryType represents the LogEntryType enum from section 3.1: |
| // enum { x509_entry(0), precert_entry(1), (65535) } LogEntryType; |
| type LogEntryType tls.Enum // tls:"maxval:65535" |
| |
| // LogEntryType constants from section 3.1. |
| const ( |
| X509LogEntryType LogEntryType = 0 |
| PrecertLogEntryType LogEntryType = 1 |
| XJSONLogEntryType LogEntryType = 0x8000 // Experimental. Don't rely on this! |
| ) |
| |
| func (e LogEntryType) String() string { |
| switch e { |
| case X509LogEntryType: |
| return "X509LogEntryType" |
| case PrecertLogEntryType: |
| return "PrecertLogEntryType" |
| case XJSONLogEntryType: |
| return "XJSONLogEntryType" |
| default: |
| return fmt.Sprintf("UnknownEntryType(%d)", e) |
| } |
| } |
| |
| // RFC6962 section 2.1 requires a prefix byte on hash inputs for second preimage resistance. |
| const ( |
| TreeLeafPrefix = byte(0x00) |
| TreeNodePrefix = byte(0x01) |
| ) |
| |
| // MerkleLeafType represents the MerkleLeafType enum from section 3.4: |
| // enum { timestamped_entry(0), (255) } MerkleLeafType; |
| type MerkleLeafType tls.Enum // tls:"maxval:255" |
| |
| // TimestampedEntryLeafType is the only defined MerkleLeafType constant from section 3.4. |
| const TimestampedEntryLeafType MerkleLeafType = 0 // Entry type for an SCT |
| |
| func (m MerkleLeafType) String() string { |
| switch m { |
| case TimestampedEntryLeafType: |
| return "TimestampedEntryLeafType" |
| default: |
| return fmt.Sprintf("UnknownLeafType(%d)", m) |
| } |
| } |
| |
| // Version represents the Version enum from section 3.2: |
| // enum { v1(0), (255) } Version; |
| type Version tls.Enum // tls:"maxval:255" |
| |
| // CT Version constants from section 3.2. |
| const ( |
| V1 Version = 0 |
| ) |
| |
| func (v Version) String() string { |
| switch v { |
| case V1: |
| return "V1" |
| default: |
| return fmt.Sprintf("UnknownVersion(%d)", v) |
| } |
| } |
| |
| // SignatureType differentiates STH signatures from SCT signatures, see section 3.2. |
| // enum { certificate_timestamp(0), tree_hash(1), (255) } SignatureType; |
| type SignatureType tls.Enum // tls:"maxval:255" |
| |
| // SignatureType constants from section 3.2. |
| const ( |
| CertificateTimestampSignatureType SignatureType = 0 |
| TreeHashSignatureType SignatureType = 1 |
| ) |
| |
| func (st SignatureType) String() string { |
| switch st { |
| case CertificateTimestampSignatureType: |
| return "CertificateTimestamp" |
| case TreeHashSignatureType: |
| return "TreeHash" |
| default: |
| return fmt.Sprintf("UnknownSignatureType(%d)", st) |
| } |
| } |
| |
| // ASN1Cert type for holding the raw DER bytes of an ASN.1 Certificate |
| // (section 3.1). |
| type ASN1Cert struct { |
| Data []byte `tls:"minlen:1,maxlen:16777215"` |
| } |
| |
| // LogID holds the hash of the Log's public key (section 3.2). |
| // TODO(pphaneuf): Users should be migrated to the one in the logid package. |
| type LogID struct { |
| KeyID [sha256.Size]byte |
| } |
| |
| // PreCert represents a Precertificate (section 3.2). |
| type PreCert struct { |
| IssuerKeyHash [sha256.Size]byte |
| TBSCertificate []byte `tls:"minlen:1,maxlen:16777215"` // DER-encoded TBSCertificate |
| } |
| |
| // CTExtensions is a representation of the raw bytes of any CtExtension |
| // structure (see section 3.2). |
| // nolint: golint |
| type CTExtensions []byte // tls:"minlen:0,maxlen:65535"` |
| |
| // MerkleTreeNode represents an internal node in the CT tree. |
| type MerkleTreeNode []byte |
| |
| // ConsistencyProof represents a CT consistency proof (see sections 2.1.2 and |
| // 4.4). |
| type ConsistencyProof []MerkleTreeNode |
| |
| // AuditPath represents a CT inclusion proof (see sections 2.1.1 and 4.5). |
| type AuditPath []MerkleTreeNode |
| |
| // LeafInput represents a serialized MerkleTreeLeaf structure. |
| type LeafInput []byte |
| |
| // DigitallySigned is a local alias for tls.DigitallySigned so that we can |
| // attach a MarshalJSON method. |
| type DigitallySigned tls.DigitallySigned |
| |
| // FromBase64String populates the DigitallySigned structure from the base64 data passed in. |
| // Returns an error if the base64 data is invalid. |
| func (d *DigitallySigned) FromBase64String(b64 string) error { |
| raw, err := base64.StdEncoding.DecodeString(b64) |
| if err != nil { |
| return fmt.Errorf("failed to unbase64 DigitallySigned: %v", err) |
| } |
| var ds tls.DigitallySigned |
| if rest, err := tls.Unmarshal(raw, &ds); err != nil { |
| return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err) |
| } else if len(rest) > 0 { |
| return fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)) |
| } |
| *d = DigitallySigned(ds) |
| return nil |
| } |
| |
| // Base64String returns the base64 representation of the DigitallySigned struct. |
| func (d DigitallySigned) Base64String() (string, error) { |
| b, err := tls.Marshal(d) |
| if err != nil { |
| return "", err |
| } |
| return base64.StdEncoding.EncodeToString(b), nil |
| } |
| |
| // MarshalJSON implements the json.Marshaller interface. |
| func (d DigitallySigned) MarshalJSON() ([]byte, error) { |
| b64, err := d.Base64String() |
| if err != nil { |
| return []byte{}, err |
| } |
| return []byte(`"` + b64 + `"`), nil |
| } |
| |
| // UnmarshalJSON implements the json.Unmarshaler interface. |
| func (d *DigitallySigned) UnmarshalJSON(b []byte) error { |
| var content string |
| if err := json.Unmarshal(b, &content); err != nil { |
| return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err) |
| } |
| return d.FromBase64String(content) |
| } |
| |
| // LogEntry represents the (parsed) contents of an entry in a CT log. This is described |
| // in section 3.1, but note that this structure does *not* match the TLS structure |
| // defined there (the TLS structure is never used directly in RFC6962). |
| type LogEntry struct { |
| Index int64 |
| Leaf MerkleTreeLeaf |
| // Exactly one of the following three fields should be non-empty. |
| X509Cert *x509.Certificate // Parsed X.509 certificate |
| Precert *Precertificate // Extracted precertificate |
| JSONData []byte |
| |
| // Chain holds the issuing certificate chain, starting with the |
| // issuer of the leaf certificate / pre-certificate. |
| Chain []ASN1Cert |
| } |
| |
| // PrecertChainEntry holds an precertificate together with a validation chain |
| // for it; see section 3.1. |
| type PrecertChainEntry struct { |
| PreCertificate ASN1Cert `tls:"minlen:1,maxlen:16777215"` |
| CertificateChain []ASN1Cert `tls:"minlen:0,maxlen:16777215"` |
| } |
| |
| // CertificateChain holds a chain of certificates, as returned as extra data |
| // for get-entries (section 4.6). |
| type CertificateChain struct { |
| Entries []ASN1Cert `tls:"minlen:0,maxlen:16777215"` |
| } |
| |
| // JSONDataEntry holds arbitrary data. |
| type JSONDataEntry struct { |
| Data []byte `tls:"minlen:0,maxlen:1677215"` |
| } |
| |
| // SHA256Hash represents the output from the SHA256 hash function. |
| type SHA256Hash [sha256.Size]byte |
| |
| // FromBase64String populates the SHA256 struct with the contents of the base64 data passed in. |
| func (s *SHA256Hash) FromBase64String(b64 string) error { |
| bs, err := base64.StdEncoding.DecodeString(b64) |
| if err != nil { |
| return fmt.Errorf("failed to unbase64 LogID: %v", err) |
| } |
| if len(bs) != sha256.Size { |
| return fmt.Errorf("invalid SHA256 length, expected 32 but got %d", len(bs)) |
| } |
| copy(s[:], bs) |
| return nil |
| } |
| |
| // Base64String returns the base64 representation of this SHA256Hash. |
| func (s SHA256Hash) Base64String() string { |
| return base64.StdEncoding.EncodeToString(s[:]) |
| } |
| |
| // MarshalJSON implements the json.Marshaller interface for SHA256Hash. |
| func (s SHA256Hash) MarshalJSON() ([]byte, error) { |
| return []byte(`"` + s.Base64String() + `"`), nil |
| } |
| |
| // UnmarshalJSON implements the json.Unmarshaller interface. |
| func (s *SHA256Hash) UnmarshalJSON(b []byte) error { |
| var content string |
| if err := json.Unmarshal(b, &content); err != nil { |
| return fmt.Errorf("failed to unmarshal SHA256Hash: %v", err) |
| } |
| return s.FromBase64String(content) |
| } |
| |
| // SignedTreeHead represents the structure returned by the get-sth CT method |
| // after base64 decoding; see sections 3.5 and 4.3. |
| type SignedTreeHead struct { |
| Version Version `json:"sth_version"` // The version of the protocol to which the STH conforms |
| TreeSize uint64 `json:"tree_size"` // The number of entries in the new tree |
| Timestamp uint64 `json:"timestamp"` // The time at which the STH was created |
| SHA256RootHash SHA256Hash `json:"sha256_root_hash"` // The root hash of the log's Merkle tree |
| TreeHeadSignature DigitallySigned `json:"tree_head_signature"` // Log's signature over a TLS-encoded TreeHeadSignature |
| LogID SHA256Hash `json:"log_id"` // The SHA256 hash of the log's public key |
| } |
| |
| // TreeHeadSignature holds the data over which the signature in an STH is |
| // generated; see section 3.5 |
| type TreeHeadSignature struct { |
| Version Version `tls:"maxval:255"` |
| SignatureType SignatureType `tls:"maxval:255"` // == TreeHashSignatureType |
| Timestamp uint64 |
| TreeSize uint64 |
| SHA256RootHash SHA256Hash |
| } |
| |
| // SignedCertificateTimestamp represents the structure returned by the |
| // add-chain and add-pre-chain methods after base64 decoding; see sections |
| // 3.2, 4.1 and 4.2. |
| type SignedCertificateTimestamp struct { |
| SCTVersion Version `tls:"maxval:255"` |
| LogID LogID |
| Timestamp uint64 |
| Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` |
| Signature DigitallySigned // Signature over TLS-encoded CertificateTimestamp |
| } |
| |
| // CertificateTimestamp is the collection of data that the signature in an |
| // SCT is over; see section 3.2. |
| type CertificateTimestamp struct { |
| SCTVersion Version `tls:"maxval:255"` |
| SignatureType SignatureType `tls:"maxval:255"` |
| Timestamp uint64 |
| EntryType LogEntryType `tls:"maxval:65535"` |
| X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"` |
| PrecertEntry *PreCert `tls:"selector:EntryType,val:1"` |
| JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"` |
| Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` |
| } |
| |
| func (s SignedCertificateTimestamp) String() string { |
| return fmt.Sprintf("{Version:%d LogId:%s Timestamp:%d Extensions:'%s' Signature:%v}", s.SCTVersion, |
| base64.StdEncoding.EncodeToString(s.LogID.KeyID[:]), |
| s.Timestamp, |
| s.Extensions, |
| s.Signature) |
| } |
| |
| // TimestampedEntry is part of the MerkleTreeLeaf structure; see section 3.4. |
| type TimestampedEntry struct { |
| Timestamp uint64 |
| EntryType LogEntryType `tls:"maxval:65535"` |
| X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"` |
| PrecertEntry *PreCert `tls:"selector:EntryType,val:1"` |
| JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"` |
| Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` |
| } |
| |
| // MerkleTreeLeaf represents the deserialized structure of the hash input for the |
| // leaves of a log's Merkle tree; see section 3.4. |
| type MerkleTreeLeaf struct { |
| Version Version `tls:"maxval:255"` |
| LeafType MerkleLeafType `tls:"maxval:255"` |
| TimestampedEntry *TimestampedEntry `tls:"selector:LeafType,val:0"` |
| } |
| |
| // Precertificate represents the parsed CT Precertificate structure. |
| type Precertificate struct { |
| // DER-encoded pre-certificate as originally added, which includes a |
| // poison extension and a signature generated over the pre-cert by |
| // the pre-cert issuer (which might differ from the issuer of the final |
| // cert, see RFC6962 s3.1). |
| Submitted ASN1Cert |
| // SHA256 hash of the issuing key |
| IssuerKeyHash [sha256.Size]byte |
| // Parsed TBSCertificate structure, held in an x509.Certificate for convenience. |
| TBSCertificate *x509.Certificate |
| } |
| |
| // X509Certificate returns the X.509 Certificate contained within the |
| // MerkleTreeLeaf. |
| func (m *MerkleTreeLeaf) X509Certificate() (*x509.Certificate, error) { |
| if m.TimestampedEntry.EntryType != X509LogEntryType { |
| return nil, fmt.Errorf("cannot call X509Certificate on a MerkleTreeLeaf that is not an X509 entry") |
| } |
| return x509.ParseCertificate(m.TimestampedEntry.X509Entry.Data) |
| } |
| |
| // Precertificate returns the X.509 Precertificate contained within the MerkleTreeLeaf. |
| // |
| // The returned precertificate is embedded in an x509.Certificate, but is in the |
| // form stored internally in the log rather than the original submitted form |
| // (i.e. it does not include the poison extension and any changes to reflect the |
| // final certificate's issuer have been made; see x509.BuildPrecertTBS). |
| func (m *MerkleTreeLeaf) Precertificate() (*x509.Certificate, error) { |
| if m.TimestampedEntry.EntryType != PrecertLogEntryType { |
| return nil, fmt.Errorf("cannot call Precertificate on a MerkleTreeLeaf that is not a precert entry") |
| } |
| return x509.ParseTBSCertificate(m.TimestampedEntry.PrecertEntry.TBSCertificate) |
| } |
| |
| // APIEndpoint is a string that represents one of the Certificate Transparency |
| // Log API endpoints. |
| type APIEndpoint string |
| |
| // Certificate Transparency Log API endpoints; see section 4. |
| // WARNING: Should match the URI paths without the "/ct/v1/" prefix. If |
| // changing these constants, may need to change those too. |
| const ( |
| AddChainStr APIEndpoint = "add-chain" |
| AddPreChainStr APIEndpoint = "add-pre-chain" |
| GetSTHStr APIEndpoint = "get-sth" |
| GetEntriesStr APIEndpoint = "get-entries" |
| GetProofByHashStr APIEndpoint = "get-proof-by-hash" |
| GetSTHConsistencyStr APIEndpoint = "get-sth-consistency" |
| GetRootsStr APIEndpoint = "get-roots" |
| GetEntryAndProofStr APIEndpoint = "get-entry-and-proof" |
| ) |
| |
| // URI paths for Log requests; see section 4. |
| // WARNING: Should match the API endpoints, with the "/ct/v1/" prefix. If |
| // changing these constants, may need to change those too. |
| const ( |
| AddChainPath = "/ct/v1/add-chain" |
| AddPreChainPath = "/ct/v1/add-pre-chain" |
| GetSTHPath = "/ct/v1/get-sth" |
| GetEntriesPath = "/ct/v1/get-entries" |
| GetProofByHashPath = "/ct/v1/get-proof-by-hash" |
| GetSTHConsistencyPath = "/ct/v1/get-sth-consistency" |
| GetRootsPath = "/ct/v1/get-roots" |
| GetEntryAndProofPath = "/ct/v1/get-entry-and-proof" |
| |
| AddJSONPath = "/ct/v1/add-json" // Experimental addition |
| ) |
| |
| // AddChainRequest represents the JSON request body sent to the add-chain and |
| // add-pre-chain POST methods from sections 4.1 and 4.2. |
| type AddChainRequest struct { |
| Chain [][]byte `json:"chain"` |
| } |
| |
| // AddChainResponse represents the JSON response to the add-chain and |
| // add-pre-chain POST methods. |
| // An SCT represents a Log's promise to integrate a [pre-]certificate into the |
| // log within a defined period of time. |
| type AddChainResponse struct { |
| SCTVersion Version `json:"sct_version"` // SCT structure version |
| ID []byte `json:"id"` // Log ID |
| Timestamp uint64 `json:"timestamp"` // Timestamp of issuance |
| Extensions string `json:"extensions"` // Holder for any CT extensions |
| Signature []byte `json:"signature"` // Log signature for this SCT |
| } |
| |
| // AddJSONRequest represents the JSON request body sent to the add-json POST method. |
| // The corresponding response re-uses AddChainResponse. |
| // This is an experimental addition not covered by RFC6962. |
| type AddJSONRequest struct { |
| Data interface{} `json:"data"` |
| } |
| |
| // GetSTHResponse respresents the JSON response to the get-sth GET method from section 4.3. |
| type GetSTHResponse struct { |
| TreeSize uint64 `json:"tree_size"` // Number of certs in the current tree |
| Timestamp uint64 `json:"timestamp"` // Time that the tree was created |
| SHA256RootHash []byte `json:"sha256_root_hash"` // Root hash of the tree |
| TreeHeadSignature []byte `json:"tree_head_signature"` // Log signature for this STH |
| } |
| |
| // ToSignedTreeHead creates a SignedTreeHead from the GetSTHResponse. |
| func (r *GetSTHResponse) ToSignedTreeHead() (*SignedTreeHead, error) { |
| sth := SignedTreeHead{ |
| TreeSize: r.TreeSize, |
| Timestamp: r.Timestamp, |
| } |
| |
| if len(r.SHA256RootHash) != sha256.Size { |
| return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(r.SHA256RootHash)) |
| } |
| copy(sth.SHA256RootHash[:], r.SHA256RootHash) |
| |
| var ds DigitallySigned |
| if rest, err := tls.Unmarshal(r.TreeHeadSignature, &ds); err != nil { |
| return nil, fmt.Errorf("tls.Unmarshal(): %s", err) |
| } else if len(rest) > 0 { |
| return nil, fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)) |
| } |
| sth.TreeHeadSignature = ds |
| |
| return &sth, nil |
| } |
| |
| // GetSTHConsistencyResponse represents the JSON response to the get-sth-consistency |
| // GET method from section 4.4. (The corresponding GET request has parameters 'first' and |
| // 'second'.) |
| type GetSTHConsistencyResponse struct { |
| Consistency [][]byte `json:"consistency"` |
| } |
| |
| // GetProofByHashResponse represents the JSON response to the get-proof-by-hash GET |
| // method from section 4.5. (The corresponding GET request has parameters 'hash' |
| // and 'tree_size'.) |
| type GetProofByHashResponse struct { |
| LeafIndex int64 `json:"leaf_index"` // The 0-based index of the end entity corresponding to the "hash" parameter. |
| AuditPath [][]byte `json:"audit_path"` // An array of base64-encoded Merkle Tree nodes proving the inclusion of the chosen certificate. |
| } |
| |
| // LeafEntry represents a leaf in the Log's Merkle tree, as returned by the get-entries |
| // GET method from section 4.6. |
| type LeafEntry struct { |
| // LeafInput is a TLS-encoded MerkleTreeLeaf |
| LeafInput []byte `json:"leaf_input"` |
| // ExtraData holds (unsigned) extra data, normally the cert validation chain. |
| ExtraData []byte `json:"extra_data"` |
| } |
| |
| // GetEntriesResponse respresents the JSON response to the get-entries GET method |
| // from section 4.6. |
| type GetEntriesResponse struct { |
| Entries []LeafEntry `json:"entries"` // the list of returned entries |
| } |
| |
| // GetRootsResponse represents the JSON response to the get-roots GET method from section 4.7. |
| type GetRootsResponse struct { |
| Certificates []string `json:"certificates"` |
| } |
| |
| // GetEntryAndProofResponse represents the JSON response to the get-entry-and-proof |
| // GET method from section 4.8. (The corresponding GET request has parameters 'leaf_index' |
| // and 'tree_size'.) |
| type GetEntryAndProofResponse struct { |
| LeafInput []byte `json:"leaf_input"` // the entry itself |
| ExtraData []byte `json:"extra_data"` // any chain provided when the entry was added to the log |
| AuditPath [][]byte `json:"audit_path"` // the corresponding proof |
| } |