1// Copyright 2015 Google Inc. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package ct holds core types and utilities for Certificate Transparency. 16package ct 17 18import ( 19 "crypto/sha256" 20 "encoding/base64" 21 "encoding/json" 22 "fmt" 23 24 "github.com/google/certificate-transparency-go/tls" 25 "github.com/google/certificate-transparency-go/x509" 26) 27 28/////////////////////////////////////////////////////////////////////////////// 29// The following structures represent those outlined in RFC6962; any section 30// numbers mentioned refer to that RFC. 31/////////////////////////////////////////////////////////////////////////////// 32 33// LogEntryType represents the LogEntryType enum from section 3.1: 34// enum { x509_entry(0), precert_entry(1), (65535) } LogEntryType; 35type LogEntryType tls.Enum // tls:"maxval:65535" 36 37// LogEntryType constants from section 3.1. 38const ( 39 X509LogEntryType LogEntryType = 0 40 PrecertLogEntryType LogEntryType = 1 41 XJSONLogEntryType LogEntryType = 0x8000 // Experimental. Don't rely on this! 42) 43 44func (e LogEntryType) String() string { 45 switch e { 46 case X509LogEntryType: 47 return "X509LogEntryType" 48 case PrecertLogEntryType: 49 return "PrecertLogEntryType" 50 case XJSONLogEntryType: 51 return "XJSONLogEntryType" 52 default: 53 return fmt.Sprintf("UnknownEntryType(%d)", e) 54 } 55} 56 57// RFC6962 section 2.1 requires a prefix byte on hash inputs for second preimage resistance. 58const ( 59 TreeLeafPrefix = byte(0x00) 60 TreeNodePrefix = byte(0x01) 61) 62 63// MerkleLeafType represents the MerkleLeafType enum from section 3.4: 64// enum { timestamped_entry(0), (255) } MerkleLeafType; 65type MerkleLeafType tls.Enum // tls:"maxval:255" 66 67// TimestampedEntryLeafType is the only defined MerkleLeafType constant from section 3.4. 68const TimestampedEntryLeafType MerkleLeafType = 0 // Entry type for an SCT 69 70func (m MerkleLeafType) String() string { 71 switch m { 72 case TimestampedEntryLeafType: 73 return "TimestampedEntryLeafType" 74 default: 75 return fmt.Sprintf("UnknownLeafType(%d)", m) 76 } 77} 78 79// Version represents the Version enum from section 3.2: 80// enum { v1(0), (255) } Version; 81type Version tls.Enum // tls:"maxval:255" 82 83// CT Version constants from section 3.2. 84const ( 85 V1 Version = 0 86) 87 88func (v Version) String() string { 89 switch v { 90 case V1: 91 return "V1" 92 default: 93 return fmt.Sprintf("UnknownVersion(%d)", v) 94 } 95} 96 97// SignatureType differentiates STH signatures from SCT signatures, see section 3.2. 98// enum { certificate_timestamp(0), tree_hash(1), (255) } SignatureType; 99type SignatureType tls.Enum // tls:"maxval:255" 100 101// SignatureType constants from section 3.2. 102const ( 103 CertificateTimestampSignatureType SignatureType = 0 104 TreeHashSignatureType SignatureType = 1 105) 106 107func (st SignatureType) String() string { 108 switch st { 109 case CertificateTimestampSignatureType: 110 return "CertificateTimestamp" 111 case TreeHashSignatureType: 112 return "TreeHash" 113 default: 114 return fmt.Sprintf("UnknownSignatureType(%d)", st) 115 } 116} 117 118// ASN1Cert type for holding the raw DER bytes of an ASN.1 Certificate 119// (section 3.1). 120type ASN1Cert struct { 121 Data []byte `tls:"minlen:1,maxlen:16777215"` 122} 123 124// LogID holds the hash of the Log's public key (section 3.2). 125// TODO(pphaneuf): Users should be migrated to the one in the logid package. 126type LogID struct { 127 KeyID [sha256.Size]byte 128} 129 130// PreCert represents a Precertificate (section 3.2). 131type PreCert struct { 132 IssuerKeyHash [sha256.Size]byte 133 TBSCertificate []byte `tls:"minlen:1,maxlen:16777215"` // DER-encoded TBSCertificate 134} 135 136// CTExtensions is a representation of the raw bytes of any CtExtension 137// structure (see section 3.2). 138// nolint: golint 139type CTExtensions []byte // tls:"minlen:0,maxlen:65535"` 140 141// MerkleTreeNode represents an internal node in the CT tree. 142type MerkleTreeNode []byte 143 144// ConsistencyProof represents a CT consistency proof (see sections 2.1.2 and 145// 4.4). 146type ConsistencyProof []MerkleTreeNode 147 148// AuditPath represents a CT inclusion proof (see sections 2.1.1 and 4.5). 149type AuditPath []MerkleTreeNode 150 151// LeafInput represents a serialized MerkleTreeLeaf structure. 152type LeafInput []byte 153 154// DigitallySigned is a local alias for tls.DigitallySigned so that we can 155// attach a MarshalJSON method. 156type DigitallySigned tls.DigitallySigned 157 158// FromBase64String populates the DigitallySigned structure from the base64 data passed in. 159// Returns an error if the base64 data is invalid. 160func (d *DigitallySigned) FromBase64String(b64 string) error { 161 raw, err := base64.StdEncoding.DecodeString(b64) 162 if err != nil { 163 return fmt.Errorf("failed to unbase64 DigitallySigned: %v", err) 164 } 165 var ds tls.DigitallySigned 166 if rest, err := tls.Unmarshal(raw, &ds); err != nil { 167 return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err) 168 } else if len(rest) > 0 { 169 return fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)) 170 } 171 *d = DigitallySigned(ds) 172 return nil 173} 174 175// Base64String returns the base64 representation of the DigitallySigned struct. 176func (d DigitallySigned) Base64String() (string, error) { 177 b, err := tls.Marshal(d) 178 if err != nil { 179 return "", err 180 } 181 return base64.StdEncoding.EncodeToString(b), nil 182} 183 184// MarshalJSON implements the json.Marshaller interface. 185func (d DigitallySigned) MarshalJSON() ([]byte, error) { 186 b64, err := d.Base64String() 187 if err != nil { 188 return []byte{}, err 189 } 190 return []byte(`"` + b64 + `"`), nil 191} 192 193// UnmarshalJSON implements the json.Unmarshaler interface. 194func (d *DigitallySigned) UnmarshalJSON(b []byte) error { 195 var content string 196 if err := json.Unmarshal(b, &content); err != nil { 197 return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err) 198 } 199 return d.FromBase64String(content) 200} 201 202// LogEntry represents the (parsed) contents of an entry in a CT log. This is described 203// in section 3.1, but note that this structure does *not* match the TLS structure 204// defined there (the TLS structure is never used directly in RFC6962). 205type LogEntry struct { 206 Index int64 207 Leaf MerkleTreeLeaf 208 // Exactly one of the following three fields should be non-empty. 209 X509Cert *x509.Certificate // Parsed X.509 certificate 210 Precert *Precertificate // Extracted precertificate 211 JSONData []byte 212 213 // Chain holds the issuing certificate chain, starting with the 214 // issuer of the leaf certificate / pre-certificate. 215 Chain []ASN1Cert 216} 217 218// PrecertChainEntry holds an precertificate together with a validation chain 219// for it; see section 3.1. 220type PrecertChainEntry struct { 221 PreCertificate ASN1Cert `tls:"minlen:1,maxlen:16777215"` 222 CertificateChain []ASN1Cert `tls:"minlen:0,maxlen:16777215"` 223} 224 225// CertificateChain holds a chain of certificates, as returned as extra data 226// for get-entries (section 4.6). 227type CertificateChain struct { 228 Entries []ASN1Cert `tls:"minlen:0,maxlen:16777215"` 229} 230 231// JSONDataEntry holds arbitrary data. 232type JSONDataEntry struct { 233 Data []byte `tls:"minlen:0,maxlen:1677215"` 234} 235 236// SHA256Hash represents the output from the SHA256 hash function. 237type SHA256Hash [sha256.Size]byte 238 239// FromBase64String populates the SHA256 struct with the contents of the base64 data passed in. 240func (s *SHA256Hash) FromBase64String(b64 string) error { 241 bs, err := base64.StdEncoding.DecodeString(b64) 242 if err != nil { 243 return fmt.Errorf("failed to unbase64 LogID: %v", err) 244 } 245 if len(bs) != sha256.Size { 246 return fmt.Errorf("invalid SHA256 length, expected 32 but got %d", len(bs)) 247 } 248 copy(s[:], bs) 249 return nil 250} 251 252// Base64String returns the base64 representation of this SHA256Hash. 253func (s SHA256Hash) Base64String() string { 254 return base64.StdEncoding.EncodeToString(s[:]) 255} 256 257// MarshalJSON implements the json.Marshaller interface for SHA256Hash. 258func (s SHA256Hash) MarshalJSON() ([]byte, error) { 259 return []byte(`"` + s.Base64String() + `"`), nil 260} 261 262// UnmarshalJSON implements the json.Unmarshaller interface. 263func (s *SHA256Hash) UnmarshalJSON(b []byte) error { 264 var content string 265 if err := json.Unmarshal(b, &content); err != nil { 266 return fmt.Errorf("failed to unmarshal SHA256Hash: %v", err) 267 } 268 return s.FromBase64String(content) 269} 270 271// SignedTreeHead represents the structure returned by the get-sth CT method 272// after base64 decoding; see sections 3.5 and 4.3. 273type SignedTreeHead struct { 274 Version Version `json:"sth_version"` // The version of the protocol to which the STH conforms 275 TreeSize uint64 `json:"tree_size"` // The number of entries in the new tree 276 Timestamp uint64 `json:"timestamp"` // The time at which the STH was created 277 SHA256RootHash SHA256Hash `json:"sha256_root_hash"` // The root hash of the log's Merkle tree 278 TreeHeadSignature DigitallySigned `json:"tree_head_signature"` // Log's signature over a TLS-encoded TreeHeadSignature 279 LogID SHA256Hash `json:"log_id"` // The SHA256 hash of the log's public key 280} 281 282// TreeHeadSignature holds the data over which the signature in an STH is 283// generated; see section 3.5 284type TreeHeadSignature struct { 285 Version Version `tls:"maxval:255"` 286 SignatureType SignatureType `tls:"maxval:255"` // == TreeHashSignatureType 287 Timestamp uint64 288 TreeSize uint64 289 SHA256RootHash SHA256Hash 290} 291 292// SignedCertificateTimestamp represents the structure returned by the 293// add-chain and add-pre-chain methods after base64 decoding; see sections 294// 3.2, 4.1 and 4.2. 295type SignedCertificateTimestamp struct { 296 SCTVersion Version `tls:"maxval:255"` 297 LogID LogID 298 Timestamp uint64 299 Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` 300 Signature DigitallySigned // Signature over TLS-encoded CertificateTimestamp 301} 302 303// CertificateTimestamp is the collection of data that the signature in an 304// SCT is over; see section 3.2. 305type CertificateTimestamp struct { 306 SCTVersion Version `tls:"maxval:255"` 307 SignatureType SignatureType `tls:"maxval:255"` 308 Timestamp uint64 309 EntryType LogEntryType `tls:"maxval:65535"` 310 X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"` 311 PrecertEntry *PreCert `tls:"selector:EntryType,val:1"` 312 JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"` 313 Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` 314} 315 316func (s SignedCertificateTimestamp) String() string { 317 return fmt.Sprintf("{Version:%d LogId:%s Timestamp:%d Extensions:'%s' Signature:%v}", s.SCTVersion, 318 base64.StdEncoding.EncodeToString(s.LogID.KeyID[:]), 319 s.Timestamp, 320 s.Extensions, 321 s.Signature) 322} 323 324// TimestampedEntry is part of the MerkleTreeLeaf structure; see section 3.4. 325type TimestampedEntry struct { 326 Timestamp uint64 327 EntryType LogEntryType `tls:"maxval:65535"` 328 X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"` 329 PrecertEntry *PreCert `tls:"selector:EntryType,val:1"` 330 JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"` 331 Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` 332} 333 334// MerkleTreeLeaf represents the deserialized structure of the hash input for the 335// leaves of a log's Merkle tree; see section 3.4. 336type MerkleTreeLeaf struct { 337 Version Version `tls:"maxval:255"` 338 LeafType MerkleLeafType `tls:"maxval:255"` 339 TimestampedEntry *TimestampedEntry `tls:"selector:LeafType,val:0"` 340} 341 342// Precertificate represents the parsed CT Precertificate structure. 343type Precertificate struct { 344 // DER-encoded pre-certificate as originally added, which includes a 345 // poison extension and a signature generated over the pre-cert by 346 // the pre-cert issuer (which might differ from the issuer of the final 347 // cert, see RFC6962 s3.1). 348 Submitted ASN1Cert 349 // SHA256 hash of the issuing key 350 IssuerKeyHash [sha256.Size]byte 351 // Parsed TBSCertificate structure, held in an x509.Certificate for convenience. 352 TBSCertificate *x509.Certificate 353} 354 355// X509Certificate returns the X.509 Certificate contained within the 356// MerkleTreeLeaf. 357func (m *MerkleTreeLeaf) X509Certificate() (*x509.Certificate, error) { 358 if m.TimestampedEntry.EntryType != X509LogEntryType { 359 return nil, fmt.Errorf("cannot call X509Certificate on a MerkleTreeLeaf that is not an X509 entry") 360 } 361 return x509.ParseCertificate(m.TimestampedEntry.X509Entry.Data) 362} 363 364// Precertificate returns the X.509 Precertificate contained within the MerkleTreeLeaf. 365// 366// The returned precertificate is embedded in an x509.Certificate, but is in the 367// form stored internally in the log rather than the original submitted form 368// (i.e. it does not include the poison extension and any changes to reflect the 369// final certificate's issuer have been made; see x509.BuildPrecertTBS). 370func (m *MerkleTreeLeaf) Precertificate() (*x509.Certificate, error) { 371 if m.TimestampedEntry.EntryType != PrecertLogEntryType { 372 return nil, fmt.Errorf("cannot call Precertificate on a MerkleTreeLeaf that is not a precert entry") 373 } 374 return x509.ParseTBSCertificate(m.TimestampedEntry.PrecertEntry.TBSCertificate) 375} 376 377// APIEndpoint is a string that represents one of the Certificate Transparency 378// Log API endpoints. 379type APIEndpoint string 380 381// Certificate Transparency Log API endpoints; see section 4. 382// WARNING: Should match the URI paths without the "/ct/v1/" prefix. If 383// changing these constants, may need to change those too. 384const ( 385 AddChainStr APIEndpoint = "add-chain" 386 AddPreChainStr APIEndpoint = "add-pre-chain" 387 GetSTHStr APIEndpoint = "get-sth" 388 GetEntriesStr APIEndpoint = "get-entries" 389 GetProofByHashStr APIEndpoint = "get-proof-by-hash" 390 GetSTHConsistencyStr APIEndpoint = "get-sth-consistency" 391 GetRootsStr APIEndpoint = "get-roots" 392 GetEntryAndProofStr APIEndpoint = "get-entry-and-proof" 393) 394 395// URI paths for Log requests; see section 4. 396// WARNING: Should match the API endpoints, with the "/ct/v1/" prefix. If 397// changing these constants, may need to change those too. 398const ( 399 AddChainPath = "/ct/v1/add-chain" 400 AddPreChainPath = "/ct/v1/add-pre-chain" 401 GetSTHPath = "/ct/v1/get-sth" 402 GetEntriesPath = "/ct/v1/get-entries" 403 GetProofByHashPath = "/ct/v1/get-proof-by-hash" 404 GetSTHConsistencyPath = "/ct/v1/get-sth-consistency" 405 GetRootsPath = "/ct/v1/get-roots" 406 GetEntryAndProofPath = "/ct/v1/get-entry-and-proof" 407 408 AddJSONPath = "/ct/v1/add-json" // Experimental addition 409) 410 411// AddChainRequest represents the JSON request body sent to the add-chain and 412// add-pre-chain POST methods from sections 4.1 and 4.2. 413type AddChainRequest struct { 414 Chain [][]byte `json:"chain"` 415} 416 417// AddChainResponse represents the JSON response to the add-chain and 418// add-pre-chain POST methods. 419// An SCT represents a Log's promise to integrate a [pre-]certificate into the 420// log within a defined period of time. 421type AddChainResponse struct { 422 SCTVersion Version `json:"sct_version"` // SCT structure version 423 ID []byte `json:"id"` // Log ID 424 Timestamp uint64 `json:"timestamp"` // Timestamp of issuance 425 Extensions string `json:"extensions"` // Holder for any CT extensions 426 Signature []byte `json:"signature"` // Log signature for this SCT 427} 428 429// AddJSONRequest represents the JSON request body sent to the add-json POST method. 430// The corresponding response re-uses AddChainResponse. 431// This is an experimental addition not covered by RFC6962. 432type AddJSONRequest struct { 433 Data interface{} `json:"data"` 434} 435 436// GetSTHResponse respresents the JSON response to the get-sth GET method from section 4.3. 437type GetSTHResponse struct { 438 TreeSize uint64 `json:"tree_size"` // Number of certs in the current tree 439 Timestamp uint64 `json:"timestamp"` // Time that the tree was created 440 SHA256RootHash []byte `json:"sha256_root_hash"` // Root hash of the tree 441 TreeHeadSignature []byte `json:"tree_head_signature"` // Log signature for this STH 442} 443 444// ToSignedTreeHead creates a SignedTreeHead from the GetSTHResponse. 445func (r *GetSTHResponse) ToSignedTreeHead() (*SignedTreeHead, error) { 446 sth := SignedTreeHead{ 447 TreeSize: r.TreeSize, 448 Timestamp: r.Timestamp, 449 } 450 451 if len(r.SHA256RootHash) != sha256.Size { 452 return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(r.SHA256RootHash)) 453 } 454 copy(sth.SHA256RootHash[:], r.SHA256RootHash) 455 456 var ds DigitallySigned 457 if rest, err := tls.Unmarshal(r.TreeHeadSignature, &ds); err != nil { 458 return nil, fmt.Errorf("tls.Unmarshal(): %s", err) 459 } else if len(rest) > 0 { 460 return nil, fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)) 461 } 462 sth.TreeHeadSignature = ds 463 464 return &sth, nil 465} 466 467// GetSTHConsistencyResponse represents the JSON response to the get-sth-consistency 468// GET method from section 4.4. (The corresponding GET request has parameters 'first' and 469// 'second'.) 470type GetSTHConsistencyResponse struct { 471 Consistency [][]byte `json:"consistency"` 472} 473 474// GetProofByHashResponse represents the JSON response to the get-proof-by-hash GET 475// method from section 4.5. (The corresponding GET request has parameters 'hash' 476// and 'tree_size'.) 477type GetProofByHashResponse struct { 478 LeafIndex int64 `json:"leaf_index"` // The 0-based index of the end entity corresponding to the "hash" parameter. 479 AuditPath [][]byte `json:"audit_path"` // An array of base64-encoded Merkle Tree nodes proving the inclusion of the chosen certificate. 480} 481 482// LeafEntry represents a leaf in the Log's Merkle tree, as returned by the get-entries 483// GET method from section 4.6. 484type LeafEntry struct { 485 // LeafInput is a TLS-encoded MerkleTreeLeaf 486 LeafInput []byte `json:"leaf_input"` 487 // ExtraData holds (unsigned) extra data, normally the cert validation chain. 488 ExtraData []byte `json:"extra_data"` 489} 490 491// GetEntriesResponse respresents the JSON response to the get-entries GET method 492// from section 4.6. 493type GetEntriesResponse struct { 494 Entries []LeafEntry `json:"entries"` // the list of returned entries 495} 496 497// GetRootsResponse represents the JSON response to the get-roots GET method from section 4.7. 498type GetRootsResponse struct { 499 Certificates []string `json:"certificates"` 500} 501 502// GetEntryAndProofResponse represents the JSON response to the get-entry-and-proof 503// GET method from section 4.8. (The corresponding GET request has parameters 'leaf_index' 504// and 'tree_size'.) 505type GetEntryAndProofResponse struct { 506 LeafInput []byte `json:"leaf_input"` // the entry itself 507 ExtraData []byte `json:"extra_data"` // any chain provided when the entry was added to the log 508 AuditPath [][]byte `json:"audit_path"` // the corresponding proof 509} 510