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