1// Copyright 2014 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 client is a CT log client implementation and contains types and code
16// for interacting with RFC6962-compliant CT Log instances.
17// See http://tools.ietf.org/html/rfc6962 for details
18package client
19
20import (
21	"context"
22	"encoding/base64"
23	"fmt"
24	"net/http"
25	"strconv"
26
27	ct "github.com/google/certificate-transparency-go"
28	"github.com/google/certificate-transparency-go/jsonclient"
29	"github.com/google/certificate-transparency-go/tls"
30)
31
32// LogClient represents a client for a given CT Log instance
33type LogClient struct {
34	jsonclient.JSONClient
35}
36
37// CheckLogClient is an interface that allows (just) checking of various log contents.
38type CheckLogClient interface {
39	BaseURI() string
40	GetSTH(context.Context) (*ct.SignedTreeHead, error)
41	GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error)
42	GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error)
43}
44
45// New constructs a new LogClient instance.
46// |uri| is the base URI of the CT log instance to interact with, e.g.
47// https://ct.googleapis.com/pilot
48// |hc| is the underlying client to be used for HTTP requests to the CT log.
49// |opts| can be used to provide a custom logger interface and a public key
50// for signature verification.
51func New(uri string, hc *http.Client, opts jsonclient.Options) (*LogClient, error) {
52	logClient, err := jsonclient.New(uri, hc, opts)
53	if err != nil {
54		return nil, err
55	}
56	return &LogClient{*logClient}, err
57}
58
59// RspError represents an error that occurred when processing a response from  a server,
60// and also includes key details from the http.Response that triggered the error.
61type RspError struct {
62	Err        error
63	StatusCode int
64	Body       []byte
65}
66
67// Error formats the RspError instance, focusing on the error.
68func (e RspError) Error() string {
69	return e.Err.Error()
70}
71
72// Attempts to add |chain| to the log, using the api end-point specified by
73// |path|. If provided context expires before submission is complete an
74// error will be returned.
75func (c *LogClient) addChainWithRetry(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
76	var resp ct.AddChainResponse
77	var req ct.AddChainRequest
78	for _, link := range chain {
79		req.Chain = append(req.Chain, link.Data)
80	}
81
82	httpRsp, body, err := c.PostAndParseWithRetry(ctx, path, &req, &resp)
83	if err != nil {
84		if httpRsp != nil {
85			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
86		}
87		return nil, err
88	}
89
90	var ds ct.DigitallySigned
91	if rest, err := tls.Unmarshal(resp.Signature, &ds); err != nil {
92		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
93	} else if len(rest) > 0 {
94		return nil, RspError{
95			Err:        fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)),
96			StatusCode: httpRsp.StatusCode,
97			Body:       body,
98		}
99	}
100
101	exts, err := base64.StdEncoding.DecodeString(resp.Extensions)
102	if err != nil {
103		return nil, RspError{
104			Err:        fmt.Errorf("invalid base64 data in Extensions (%q): %v", resp.Extensions, err),
105			StatusCode: httpRsp.StatusCode,
106			Body:       body,
107		}
108	}
109
110	var logID ct.LogID
111	copy(logID.KeyID[:], resp.ID)
112	sct := &ct.SignedCertificateTimestamp{
113		SCTVersion: resp.SCTVersion,
114		LogID:      logID,
115		Timestamp:  resp.Timestamp,
116		Extensions: ct.CTExtensions(exts),
117		Signature:  ds,
118	}
119	if err := c.VerifySCTSignature(*sct, ctype, chain); err != nil {
120		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
121	}
122	return sct, nil
123}
124
125// AddChain adds the (DER represented) X509 |chain| to the log.
126func (c *LogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
127	return c.addChainWithRetry(ctx, ct.X509LogEntryType, ct.AddChainPath, chain)
128}
129
130// AddPreChain adds the (DER represented) Precertificate |chain| to the log.
131func (c *LogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
132	return c.addChainWithRetry(ctx, ct.PrecertLogEntryType, ct.AddPreChainPath, chain)
133}
134
135// AddJSON submits arbitrary data to to XJSON server.
136func (c *LogClient) AddJSON(ctx context.Context, data interface{}) (*ct.SignedCertificateTimestamp, error) {
137	req := ct.AddJSONRequest{Data: data}
138	var resp ct.AddChainResponse
139	httpRsp, body, err := c.PostAndParse(ctx, ct.AddJSONPath, &req, &resp)
140	if err != nil {
141		if httpRsp != nil {
142			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
143		}
144		return nil, err
145	}
146	var ds ct.DigitallySigned
147	if rest, err := tls.Unmarshal(resp.Signature, &ds); err != nil {
148		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
149	} else if len(rest) > 0 {
150		return nil, RspError{
151			Err:        fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)),
152			StatusCode: httpRsp.StatusCode,
153			Body:       body,
154		}
155	}
156	var logID ct.LogID
157	copy(logID.KeyID[:], resp.ID)
158	return &ct.SignedCertificateTimestamp{
159		SCTVersion: resp.SCTVersion,
160		LogID:      logID,
161		Timestamp:  resp.Timestamp,
162		Extensions: ct.CTExtensions(resp.Extensions),
163		Signature:  ds,
164	}, nil
165}
166
167// GetSTH retrieves the current STH from the log.
168// Returns a populated SignedTreeHead, or a non-nil error (which may be of type
169// RspError if a raw http.Response is available).
170func (c *LogClient) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) {
171	var resp ct.GetSTHResponse
172	httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHPath, nil, &resp)
173	if err != nil {
174		if httpRsp != nil {
175			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
176		}
177		return nil, err
178	}
179
180	sth, err := resp.ToSignedTreeHead()
181	if err != nil {
182		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
183	}
184
185	if err := c.VerifySTHSignature(*sth); err != nil {
186		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
187	}
188	return sth, nil
189}
190
191// VerifySTHSignature checks the signature in sth, returning any error encountered or nil if verification is
192// successful.
193func (c *LogClient) VerifySTHSignature(sth ct.SignedTreeHead) error {
194	if c.Verifier == nil {
195		// Can't verify signatures without a verifier
196		return nil
197	}
198	return c.Verifier.VerifySTHSignature(sth)
199}
200
201// VerifySCTSignature checks the signature in sct for the given LogEntryType, with associated certificate chain.
202func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype ct.LogEntryType, certData []ct.ASN1Cert) error {
203	if c.Verifier == nil {
204		// Can't verify signatures without a verifier
205		return nil
206	}
207	leaf, err := ct.MerkleTreeLeafFromRawChain(certData, ctype, sct.Timestamp)
208	if err != nil {
209		return fmt.Errorf("failed to build MerkleTreeLeaf: %v", err)
210	}
211	entry := ct.LogEntry{Leaf: *leaf}
212	return c.Verifier.VerifySCTSignature(sct, entry)
213}
214
215// GetSTHConsistency retrieves the consistency proof between two snapshots.
216func (c *LogClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) {
217	base10 := 10
218	params := map[string]string{
219		"first":  strconv.FormatUint(first, base10),
220		"second": strconv.FormatUint(second, base10),
221	}
222	var resp ct.GetSTHConsistencyResponse
223	httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHConsistencyPath, params, &resp)
224	if err != nil {
225		if httpRsp != nil {
226			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
227		}
228		return nil, err
229	}
230	return resp.Consistency, nil
231}
232
233// GetProofByHash returns an audit path for the hash of an SCT.
234func (c *LogClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error) {
235	b64Hash := base64.StdEncoding.EncodeToString(hash)
236	base10 := 10
237	params := map[string]string{
238		"tree_size": strconv.FormatUint(treeSize, base10),
239		"hash":      b64Hash,
240	}
241	var resp ct.GetProofByHashResponse
242	httpRsp, body, err := c.GetAndParse(ctx, ct.GetProofByHashPath, params, &resp)
243	if err != nil {
244		if httpRsp != nil {
245			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
246		}
247		return nil, err
248	}
249	return &resp, nil
250}
251
252// GetAcceptedRoots retrieves the set of acceptable root certificates for a log.
253func (c *LogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
254	var resp ct.GetRootsResponse
255	httpRsp, body, err := c.GetAndParse(ctx, ct.GetRootsPath, nil, &resp)
256	if err != nil {
257		if httpRsp != nil {
258			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
259		}
260		return nil, err
261	}
262	var roots []ct.ASN1Cert
263	for _, cert64 := range resp.Certificates {
264		cert, err := base64.StdEncoding.DecodeString(cert64)
265		if err != nil {
266			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
267		}
268		roots = append(roots, ct.ASN1Cert{Data: cert})
269	}
270	return roots, nil
271}
272
273// GetEntryAndProof returns a log entry and audit path for the index of a leaf.
274func (c *LogClient) GetEntryAndProof(ctx context.Context, index, treeSize uint64) (*ct.GetEntryAndProofResponse, error) {
275	base10 := 10
276	params := map[string]string{
277		"leaf_index": strconv.FormatUint(index, base10),
278		"tree_size":  strconv.FormatUint(treeSize, base10),
279	}
280	var resp ct.GetEntryAndProofResponse
281	httpRsp, body, err := c.GetAndParse(ctx, ct.GetEntryAndProofPath, params, &resp)
282	if err != nil {
283		if httpRsp != nil {
284			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
285		}
286		return nil, err
287	}
288	return &resp, nil
289}
290