logclient.go 7.9 KB

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