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