123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- // Copyright 2014 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- // Package client is a CT log client implementation and contains types and code
- // for interacting with RFC6962-compliant CT Log instances.
- // See http://tools.ietf.org/html/rfc6962 for details
- package client
- import (
- "context"
- "encoding/base64"
- "fmt"
- "net/http"
- "strconv"
- ct "github.com/google/certificate-transparency-go"
- "github.com/google/certificate-transparency-go/jsonclient"
- "github.com/google/certificate-transparency-go/tls"
- )
- // LogClient represents a client for a given CT Log instance
- type LogClient struct {
- jsonclient.JSONClient
- }
- // CheckLogClient is an interface that allows (just) checking of various log contents.
- type CheckLogClient interface {
- BaseURI() string
- GetSTH(context.Context) (*ct.SignedTreeHead, error)
- GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error)
- GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error)
- }
- // New constructs a new LogClient instance.
- // |uri| is the base URI of the CT log instance to interact with, e.g.
- // https://ct.googleapis.com/pilot
- // |hc| is the underlying client to be used for HTTP requests to the CT log.
- // |opts| can be used to provide a custom logger interface and a public key
- // for signature verification.
- func New(uri string, hc *http.Client, opts jsonclient.Options) (*LogClient, error) {
- logClient, err := jsonclient.New(uri, hc, opts)
- if err != nil {
- return nil, err
- }
- return &LogClient{*logClient}, err
- }
- // RspError represents an error that occurred when processing a response from a server,
- // and also includes key details from the http.Response that triggered the error.
- type RspError struct {
- Err error
- StatusCode int
- Body []byte
- }
- // Error formats the RspError instance, focusing on the error.
- func (e RspError) Error() string {
- return e.Err.Error()
- }
- // Attempts to add |chain| to the log, using the api end-point specified by
- // |path|. If provided context expires before submission is complete an
- // error will be returned.
- func (c *LogClient) addChainWithRetry(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
- var resp ct.AddChainResponse
- var req ct.AddChainRequest
- for _, link := range chain {
- req.Chain = append(req.Chain, link.Data)
- }
- httpRsp, body, err := c.PostAndParseWithRetry(ctx, path, &req, &resp)
- if err != nil {
- if httpRsp != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return nil, err
- }
- var ds ct.DigitallySigned
- if rest, err := tls.Unmarshal(resp.Signature, &ds); err != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- } else if len(rest) > 0 {
- return nil, RspError{
- Err: fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)),
- StatusCode: httpRsp.StatusCode,
- Body: body,
- }
- }
- exts, err := base64.StdEncoding.DecodeString(resp.Extensions)
- if err != nil {
- return nil, RspError{
- Err: fmt.Errorf("invalid base64 data in Extensions (%q): %v", resp.Extensions, err),
- StatusCode: httpRsp.StatusCode,
- Body: body,
- }
- }
- var logID ct.LogID
- copy(logID.KeyID[:], resp.ID)
- sct := &ct.SignedCertificateTimestamp{
- SCTVersion: resp.SCTVersion,
- LogID: logID,
- Timestamp: resp.Timestamp,
- Extensions: ct.CTExtensions(exts),
- Signature: ds,
- }
- if err := c.VerifySCTSignature(*sct, ctype, chain); err != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return sct, nil
- }
- // AddChain adds the (DER represented) X509 |chain| to the log.
- func (c *LogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
- return c.addChainWithRetry(ctx, ct.X509LogEntryType, ct.AddChainPath, chain)
- }
- // AddPreChain adds the (DER represented) Precertificate |chain| to the log.
- func (c *LogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
- return c.addChainWithRetry(ctx, ct.PrecertLogEntryType, ct.AddPreChainPath, chain)
- }
- // AddJSON submits arbitrary data to to XJSON server.
- func (c *LogClient) AddJSON(ctx context.Context, data interface{}) (*ct.SignedCertificateTimestamp, error) {
- req := ct.AddJSONRequest{Data: data}
- var resp ct.AddChainResponse
- httpRsp, body, err := c.PostAndParse(ctx, ct.AddJSONPath, &req, &resp)
- if err != nil {
- if httpRsp != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return nil, err
- }
- var ds ct.DigitallySigned
- if rest, err := tls.Unmarshal(resp.Signature, &ds); err != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- } else if len(rest) > 0 {
- return nil, RspError{
- Err: fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)),
- StatusCode: httpRsp.StatusCode,
- Body: body,
- }
- }
- var logID ct.LogID
- copy(logID.KeyID[:], resp.ID)
- return &ct.SignedCertificateTimestamp{
- SCTVersion: resp.SCTVersion,
- LogID: logID,
- Timestamp: resp.Timestamp,
- Extensions: ct.CTExtensions(resp.Extensions),
- Signature: ds,
- }, nil
- }
- // GetSTH retrieves the current STH from the log.
- // Returns a populated SignedTreeHead, or a non-nil error (which may be of type
- // RspError if a raw http.Response is available).
- func (c *LogClient) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) {
- var resp ct.GetSTHResponse
- httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHPath, nil, &resp)
- if err != nil {
- if httpRsp != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return nil, err
- }
- sth, err := resp.ToSignedTreeHead()
- if err != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- if err := c.VerifySTHSignature(*sth); err != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return sth, nil
- }
- // VerifySTHSignature checks the signature in sth, returning any error encountered or nil if verification is
- // successful.
- func (c *LogClient) VerifySTHSignature(sth ct.SignedTreeHead) error {
- if c.Verifier == nil {
- // Can't verify signatures without a verifier
- return nil
- }
- return c.Verifier.VerifySTHSignature(sth)
- }
- // VerifySCTSignature checks the signature in sct for the given LogEntryType, with associated certificate chain.
- func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype ct.LogEntryType, certData []ct.ASN1Cert) error {
- if c.Verifier == nil {
- // Can't verify signatures without a verifier
- return nil
- }
- leaf, err := ct.MerkleTreeLeafFromRawChain(certData, ctype, sct.Timestamp)
- if err != nil {
- return fmt.Errorf("failed to build MerkleTreeLeaf: %v", err)
- }
- entry := ct.LogEntry{Leaf: *leaf}
- return c.Verifier.VerifySCTSignature(sct, entry)
- }
- // GetSTHConsistency retrieves the consistency proof between two snapshots.
- func (c *LogClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) {
- base10 := 10
- params := map[string]string{
- "first": strconv.FormatUint(first, base10),
- "second": strconv.FormatUint(second, base10),
- }
- var resp ct.GetSTHConsistencyResponse
- httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHConsistencyPath, params, &resp)
- if err != nil {
- if httpRsp != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return nil, err
- }
- return resp.Consistency, nil
- }
- // GetProofByHash returns an audit path for the hash of an SCT.
- func (c *LogClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error) {
- b64Hash := base64.StdEncoding.EncodeToString(hash)
- base10 := 10
- params := map[string]string{
- "tree_size": strconv.FormatUint(treeSize, base10),
- "hash": b64Hash,
- }
- var resp ct.GetProofByHashResponse
- httpRsp, body, err := c.GetAndParse(ctx, ct.GetProofByHashPath, params, &resp)
- if err != nil {
- if httpRsp != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return nil, err
- }
- return &resp, nil
- }
- // GetAcceptedRoots retrieves the set of acceptable root certificates for a log.
- func (c *LogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
- var resp ct.GetRootsResponse
- httpRsp, body, err := c.GetAndParse(ctx, ct.GetRootsPath, nil, &resp)
- if err != nil {
- if httpRsp != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return nil, err
- }
- var roots []ct.ASN1Cert
- for _, cert64 := range resp.Certificates {
- cert, err := base64.StdEncoding.DecodeString(cert64)
- if err != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- roots = append(roots, ct.ASN1Cert{Data: cert})
- }
- return roots, nil
- }
- // GetEntryAndProof returns a log entry and audit path for the index of a leaf.
- func (c *LogClient) GetEntryAndProof(ctx context.Context, index, treeSize uint64) (*ct.GetEntryAndProofResponse, error) {
- base10 := 10
- params := map[string]string{
- "leaf_index": strconv.FormatUint(index, base10),
- "tree_size": strconv.FormatUint(treeSize, base10),
- }
- var resp ct.GetEntryAndProofResponse
- httpRsp, body, err := c.GetAndParse(ctx, ct.GetEntryAndProofPath, params, &resp)
- if err != nil {
- if httpRsp != nil {
- return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
- }
- return nil, err
- }
- return &resp, nil
- }
|