...

Source file src/github.com/google/certificate-transparency-go/client/logclient.go

Documentation: github.com/google/certificate-transparency-go/client

     1  // Copyright 2014 Google LLC. 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
    18  package client
    19  
    20  import (
    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
    33  type LogClient struct {
    34  	jsonclient.JSONClient
    35  }
    36  
    37  // CheckLogClient is an interface that allows (just) checking of various log contents.
    38  type 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.
    51  func 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 a server error including HTTP information.
    60  type RspError = jsonclient.RspError
    61  
    62  // Attempts to add |chain| to the log, using the api end-point specified by
    63  // |path|. If provided context expires before submission is complete an
    64  // error will be returned.
    65  func (c *LogClient) addChainWithRetry(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
    66  	var resp ct.AddChainResponse
    67  	var req ct.AddChainRequest
    68  	for _, link := range chain {
    69  		req.Chain = append(req.Chain, link.Data)
    70  	}
    71  
    72  	httpRsp, body, err := c.PostAndParseWithRetry(ctx, path, &req, &resp)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	var ds ct.DigitallySigned
    78  	if rest, err := tls.Unmarshal(resp.Signature, &ds); err != nil {
    79  		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
    80  	} else if len(rest) > 0 {
    81  		return nil, RspError{
    82  			Err:        fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)),
    83  			StatusCode: httpRsp.StatusCode,
    84  			Body:       body,
    85  		}
    86  	}
    87  
    88  	exts, err := base64.StdEncoding.DecodeString(resp.Extensions)
    89  	if err != nil {
    90  		return nil, RspError{
    91  			Err:        fmt.Errorf("invalid base64 data in Extensions (%q): %v", resp.Extensions, err),
    92  			StatusCode: httpRsp.StatusCode,
    93  			Body:       body,
    94  		}
    95  	}
    96  
    97  	var logID ct.LogID
    98  	copy(logID.KeyID[:], resp.ID)
    99  	sct := &ct.SignedCertificateTimestamp{
   100  		SCTVersion: resp.SCTVersion,
   101  		LogID:      logID,
   102  		Timestamp:  resp.Timestamp,
   103  		Extensions: ct.CTExtensions(exts),
   104  		Signature:  ds,
   105  	}
   106  	if err := c.VerifySCTSignature(*sct, ctype, chain); err != nil {
   107  		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
   108  	}
   109  	return sct, nil
   110  }
   111  
   112  // AddChain adds the (DER represented) X509 |chain| to the log.
   113  func (c *LogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
   114  	return c.addChainWithRetry(ctx, ct.X509LogEntryType, ct.AddChainPath, chain)
   115  }
   116  
   117  // AddPreChain adds the (DER represented) Precertificate |chain| to the log.
   118  func (c *LogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
   119  	return c.addChainWithRetry(ctx, ct.PrecertLogEntryType, ct.AddPreChainPath, chain)
   120  }
   121  
   122  // GetSTH retrieves the current STH from the log.
   123  // Returns a populated SignedTreeHead, or a non-nil error (which may be of type
   124  // RspError if a raw http.Response is available).
   125  func (c *LogClient) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) {
   126  	var resp ct.GetSTHResponse
   127  	httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHPath, nil, &resp)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	sth, err := resp.ToSignedTreeHead()
   133  	if err != nil {
   134  		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
   135  	}
   136  
   137  	if err := c.VerifySTHSignature(*sth); err != nil {
   138  		return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
   139  	}
   140  	return sth, nil
   141  }
   142  
   143  // VerifySTHSignature checks the signature in sth, returning any error encountered or nil if verification is
   144  // successful.
   145  func (c *LogClient) VerifySTHSignature(sth ct.SignedTreeHead) error {
   146  	if c.Verifier == nil {
   147  		// Can't verify signatures without a verifier
   148  		return nil
   149  	}
   150  	return c.Verifier.VerifySTHSignature(sth)
   151  }
   152  
   153  // VerifySCTSignature checks the signature in sct for the given LogEntryType, with associated certificate chain.
   154  func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype ct.LogEntryType, certData []ct.ASN1Cert) error {
   155  	if c.Verifier == nil {
   156  		// Can't verify signatures without a verifier
   157  		return nil
   158  	}
   159  	leaf, err := ct.MerkleTreeLeafFromRawChain(certData, ctype, sct.Timestamp)
   160  	if err != nil {
   161  		return fmt.Errorf("failed to build MerkleTreeLeaf: %v", err)
   162  	}
   163  	entry := ct.LogEntry{Leaf: *leaf}
   164  	return c.Verifier.VerifySCTSignature(sct, entry)
   165  }
   166  
   167  // GetSTHConsistency retrieves the consistency proof between two snapshots.
   168  func (c *LogClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) {
   169  	base10 := 10
   170  	params := map[string]string{
   171  		"first":  strconv.FormatUint(first, base10),
   172  		"second": strconv.FormatUint(second, base10),
   173  	}
   174  	var resp ct.GetSTHConsistencyResponse
   175  	if _, _, err := c.GetAndParse(ctx, ct.GetSTHConsistencyPath, params, &resp); err != nil {
   176  		return nil, err
   177  	}
   178  	return resp.Consistency, nil
   179  }
   180  
   181  // GetProofByHash returns an audit path for the hash of an SCT.
   182  func (c *LogClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error) {
   183  	b64Hash := base64.StdEncoding.EncodeToString(hash)
   184  	base10 := 10
   185  	params := map[string]string{
   186  		"tree_size": strconv.FormatUint(treeSize, base10),
   187  		"hash":      b64Hash,
   188  	}
   189  	var resp ct.GetProofByHashResponse
   190  	if _, _, err := c.GetAndParse(ctx, ct.GetProofByHashPath, params, &resp); err != nil {
   191  		return nil, err
   192  	}
   193  	return &resp, nil
   194  }
   195  
   196  // GetAcceptedRoots retrieves the set of acceptable root certificates for a log.
   197  func (c *LogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
   198  	var resp ct.GetRootsResponse
   199  	httpRsp, body, err := c.GetAndParse(ctx, ct.GetRootsPath, nil, &resp)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	var roots []ct.ASN1Cert
   204  	for _, cert64 := range resp.Certificates {
   205  		cert, err := base64.StdEncoding.DecodeString(cert64)
   206  		if err != nil {
   207  			return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
   208  		}
   209  		roots = append(roots, ct.ASN1Cert{Data: cert})
   210  	}
   211  	return roots, nil
   212  }
   213  
   214  // GetEntryAndProof returns a log entry and audit path for the index of a leaf.
   215  func (c *LogClient) GetEntryAndProof(ctx context.Context, index, treeSize uint64) (*ct.GetEntryAndProofResponse, error) {
   216  	base10 := 10
   217  	params := map[string]string{
   218  		"leaf_index": strconv.FormatUint(index, base10),
   219  		"tree_size":  strconv.FormatUint(treeSize, base10),
   220  	}
   221  	var resp ct.GetEntryAndProofResponse
   222  	if _, _, err := c.GetAndParse(ctx, ct.GetEntryAndProofPath, params, &resp); err != nil {
   223  		return nil, err
   224  	}
   225  	return &resp, nil
   226  }
   227  

View as plain text