1// Copyright 2018 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 15package ctutil 16 17import ( 18 "context" 19 "crypto/sha256" 20 "fmt" 21 "net/http" 22 "strings" 23 "sync" 24 "time" 25 26 ct "github.com/google/certificate-transparency-go" 27 "github.com/google/certificate-transparency-go/client" 28 "github.com/google/certificate-transparency-go/dnsclient" 29 "github.com/google/certificate-transparency-go/jsonclient" 30 "github.com/google/certificate-transparency-go/loglist" 31 "github.com/google/certificate-transparency-go/x509" 32 "github.com/google/trillian/merkle" 33 "github.com/google/trillian/merkle/rfc6962" 34) 35 36// LogInfo holds the objects needed to perform per-log verification and 37// validation of SCTs. 38type LogInfo struct { 39 Description string 40 Client client.CheckLogClient 41 MMD time.Duration 42 Verifier *ct.SignatureVerifier 43 PublicKey []byte 44 45 mu sync.RWMutex 46 lastSTH *ct.SignedTreeHead 47} 48 49// NewLogInfo builds a LogInfo object based on a log list entry. 50func NewLogInfo(log *loglist.Log, hc *http.Client) (*LogInfo, error) { 51 url := log.URL 52 if !strings.HasPrefix(url, "https://") { 53 url = "https://" + url 54 } 55 lc, err := client.New(url, hc, jsonclient.Options{PublicKeyDER: log.Key}) 56 if err != nil { 57 return nil, fmt.Errorf("failed to create client for log %q: %v", log.Description, err) 58 } 59 return newLogInfo(log, lc) 60} 61 62// NewLogInfoOverDNSWrapper builds a LogInfo object that accesses logs via DNS, based on a log list entry. 63// The inert http.Client argument allows this variant to be used interchangeably with NewLogInfo. 64func NewLogInfoOverDNSWrapper(log *loglist.Log, _ *http.Client) (*LogInfo, error) { 65 return NewLogInfoOverDNS(log) 66} 67 68// NewLogInfoOverDNS builds a LogInfo object that accesses logs via DNS, based on a log list entry. 69func NewLogInfoOverDNS(log *loglist.Log) (*LogInfo, error) { 70 if log.DNSAPIEndpoint == "" { 71 return nil, fmt.Errorf("no available DNS endpoint for log %q", log.Description) 72 } 73 dc, err := dnsclient.New(log.DNSAPIEndpoint, jsonclient.Options{PublicKeyDER: log.Key}) 74 if err != nil { 75 return nil, fmt.Errorf("failed to create DNS client for log %q: %v", log.Description, err) 76 } 77 return newLogInfo(log, dc) 78} 79 80func newLogInfo(log *loglist.Log, lc client.CheckLogClient) (*LogInfo, error) { 81 logKey, err := x509.ParsePKIXPublicKey(log.Key) 82 if err != nil { 83 return nil, fmt.Errorf("failed to parse public key data for log %q: %v", log.Description, err) 84 } 85 verifier, err := ct.NewSignatureVerifier(logKey) 86 if err != nil { 87 return nil, fmt.Errorf("failed to build verifier log %q: %v", log.Description, err) 88 } 89 mmd := time.Duration(log.MaximumMergeDelay) * time.Second 90 return &LogInfo{ 91 Description: log.Description, 92 Client: lc, 93 MMD: mmd, 94 Verifier: verifier, 95 PublicKey: log.Key, 96 }, nil 97} 98 99// LogInfoByHash holds LogInfo objects index by the SHA-256 hash of the log's public key. 100type LogInfoByHash map[[sha256.Size]byte]*LogInfo 101 102// LogInfoByKeyHash builds a map of LogInfo objects indexed by their key hashes. 103func LogInfoByKeyHash(ll *loglist.LogList, hc *http.Client) (LogInfoByHash, error) { 104 return logInfoByKeyHash(ll, hc, NewLogInfo) 105} 106 107// LogInfoByKeyHashOverDNS builds a map of LogInfo objects (for access over DNS) indexed by their key hashes. 108func LogInfoByKeyHashOverDNS(ll *loglist.LogList, hc *http.Client) (LogInfoByHash, error) { 109 return logInfoByKeyHash(ll, hc, NewLogInfoOverDNSWrapper) 110} 111 112func logInfoByKeyHash(ll *loglist.LogList, hc *http.Client, infoFactory func(*loglist.Log, *http.Client) (*LogInfo, error)) (map[[sha256.Size]byte]*LogInfo, error) { 113 result := make(map[[sha256.Size]byte]*LogInfo) 114 for _, log := range ll.Logs { 115 h := sha256.Sum256(log.Key) 116 li, err := infoFactory(&log, hc) 117 if err != nil { 118 return nil, err 119 } 120 result[h] = li 121 } 122 return result, nil 123} 124 125// LastSTH returns the last STH known for the log. 126func (li *LogInfo) LastSTH() *ct.SignedTreeHead { 127 li.mu.RLock() 128 defer li.mu.RUnlock() 129 return li.lastSTH 130} 131 132// SetSTH sets the last STH known for the log. 133func (li *LogInfo) SetSTH(sth *ct.SignedTreeHead) { 134 li.mu.Lock() 135 defer li.mu.Unlock() 136 li.lastSTH = sth 137} 138 139// VerifySCTSignature checks the signature in the SCT matches the given leaf (adjusted for the 140// timestamp in the SCT) and log. 141func (li *LogInfo) VerifySCTSignature(sct ct.SignedCertificateTimestamp, leaf ct.MerkleTreeLeaf) error { 142 leaf.TimestampedEntry.Timestamp = sct.Timestamp 143 if err := li.Verifier.VerifySCTSignature(sct, ct.LogEntry{Leaf: leaf}); err != nil { 144 return fmt.Errorf("failed to verify SCT signature from log %q: %v", li.Description, err) 145 } 146 return nil 147} 148 149// VerifyInclusionLatest checks that the given Merkle tree leaf, adjusted for the provided timestamp, 150// is present in the latest known tree size of the log. If no tree size for the log is known, it will 151// be queried. On success, returns the index of the leaf in the log. 152func (li *LogInfo) VerifyInclusionLatest(ctx context.Context, leaf ct.MerkleTreeLeaf, timestamp uint64) (int64, error) { 153 sth := li.LastSTH() 154 if sth == nil { 155 var err error 156 sth, err = li.Client.GetSTH(ctx) 157 if err != nil { 158 return -1, fmt.Errorf("failed to get current STH for %q log: %v", li.Description, err) 159 } 160 li.SetSTH(sth) 161 } 162 return li.VerifyInclusionAt(ctx, leaf, timestamp, sth.TreeSize, sth.SHA256RootHash[:]) 163} 164 165// VerifyInclusion checks that the given Merkle tree leaf, adjusted for the provided timestamp, 166// is present in the current tree size of the log. On success, returns the index of the leaf 167// in the log. 168func (li *LogInfo) VerifyInclusion(ctx context.Context, leaf ct.MerkleTreeLeaf, timestamp uint64) (int64, error) { 169 sth, err := li.Client.GetSTH(ctx) 170 if err != nil { 171 return -1, fmt.Errorf("failed to get current STH for %q log: %v", li.Description, err) 172 } 173 li.SetSTH(sth) 174 return li.VerifyInclusionAt(ctx, leaf, timestamp, sth.TreeSize, sth.SHA256RootHash[:]) 175} 176 177// VerifyInclusionAt checks that the given Merkle tree leaf, adjusted for the provided timestamp, 178// is present in the given tree size & root hash of the log. On success, returns the index of the 179// leaf in the log. 180func (li *LogInfo) VerifyInclusionAt(ctx context.Context, leaf ct.MerkleTreeLeaf, timestamp, treeSize uint64, rootHash []byte) (int64, error) { 181 leaf.TimestampedEntry.Timestamp = timestamp 182 leafHash, err := ct.LeafHashForLeaf(&leaf) 183 if err != nil { 184 return -1, fmt.Errorf("failed to create leaf hash: %v", err) 185 } 186 187 rsp, err := li.Client.GetProofByHash(ctx, leafHash[:], treeSize) 188 if err != nil { 189 return -1, fmt.Errorf("failed to GetProofByHash(sct,size=%d): %v", treeSize, err) 190 } 191 192 verifier := merkle.NewLogVerifier(rfc6962.DefaultHasher) 193 if err := verifier.VerifyInclusionProof(rsp.LeafIndex, int64(treeSize), rsp.AuditPath, rootHash, leafHash[:]); err != nil { 194 return -1, fmt.Errorf("failed to verify inclusion proof at size %d: %v", treeSize, err) 195 } 196 return rsp.LeafIndex, nil 197} 198