logclient.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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. // 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 an error that occurred when processing a response from a server,
  53. // and also includes key details from the http.Response that triggered the error.
  54. type RspError struct {
  55. Err error
  56. StatusCode int
  57. Body []byte
  58. }
  59. // Error formats the RspError instance, focusing on the error.
  60. func (e RspError) Error() string {
  61. return e.Err.Error()
  62. }
  63. // Attempts to add |chain| to the log, using the api end-point specified by
  64. // |path|. If provided context expires before submission is complete an
  65. // error will be returned.
  66. func (c *LogClient) addChainWithRetry(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
  67. var resp ct.AddChainResponse
  68. var req ct.AddChainRequest
  69. for _, link := range chain {
  70. req.Chain = append(req.Chain, link.Data)
  71. }
  72. httpRsp, body, err := c.PostAndParseWithRetry(ctx, path, &req, &resp)
  73. if err != nil {
  74. if httpRsp != nil {
  75. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  76. }
  77. return nil, err
  78. }
  79. var ds ct.DigitallySigned
  80. if rest, err := tls.Unmarshal(resp.Signature, &ds); err != nil {
  81. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  82. } else if len(rest) > 0 {
  83. return nil, RspError{
  84. Err: fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)),
  85. StatusCode: httpRsp.StatusCode,
  86. Body: body,
  87. }
  88. }
  89. exts, err := base64.StdEncoding.DecodeString(resp.Extensions)
  90. if err != nil {
  91. return nil, RspError{
  92. Err: fmt.Errorf("invalid base64 data in Extensions (%q): %v", resp.Extensions, err),
  93. StatusCode: httpRsp.StatusCode,
  94. Body: body,
  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. // AddChain adds the (DER represented) X509 |chain| to the log.
  112. func (c *LogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
  113. return c.addChainWithRetry(ctx, ct.X509LogEntryType, ct.AddChainPath, chain)
  114. }
  115. // AddPreChain adds the (DER represented) Precertificate |chain| to the log.
  116. func (c *LogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
  117. return c.addChainWithRetry(ctx, ct.PrecertLogEntryType, ct.AddPreChainPath, chain)
  118. }
  119. // AddJSON submits arbitrary data to to XJSON server.
  120. func (c *LogClient) AddJSON(ctx context.Context, data interface{}) (*ct.SignedCertificateTimestamp, error) {
  121. req := ct.AddJSONRequest{Data: data}
  122. var resp ct.AddChainResponse
  123. httpRsp, body, err := c.PostAndParse(ctx, ct.AddJSONPath, &req, &resp)
  124. if err != nil {
  125. if httpRsp != nil {
  126. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  127. }
  128. return nil, err
  129. }
  130. var ds ct.DigitallySigned
  131. if rest, err := tls.Unmarshal(resp.Signature, &ds); err != nil {
  132. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  133. } else if len(rest) > 0 {
  134. return nil, RspError{
  135. Err: fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)),
  136. StatusCode: httpRsp.StatusCode,
  137. Body: body,
  138. }
  139. }
  140. var logID ct.LogID
  141. copy(logID.KeyID[:], resp.ID)
  142. return &ct.SignedCertificateTimestamp{
  143. SCTVersion: resp.SCTVersion,
  144. LogID: logID,
  145. Timestamp: resp.Timestamp,
  146. Extensions: ct.CTExtensions(resp.Extensions),
  147. Signature: ds,
  148. }, nil
  149. }
  150. // GetSTH retrieves the current STH from the log.
  151. // Returns a populated SignedTreeHead, or a non-nil error (which may be of type
  152. // RspError if a raw http.Response is available).
  153. func (c *LogClient) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) {
  154. var resp ct.GetSTHResponse
  155. httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHPath, nil, &resp)
  156. if err != nil {
  157. if httpRsp != nil {
  158. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  159. }
  160. return nil, err
  161. }
  162. sth, err := resp.ToSignedTreeHead()
  163. if err != nil {
  164. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  165. }
  166. if err := c.VerifySTHSignature(*sth); err != nil {
  167. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  168. }
  169. return sth, nil
  170. }
  171. // VerifySTHSignature checks the signature in sth, returning any error encountered or nil if verification is
  172. // successful.
  173. func (c *LogClient) VerifySTHSignature(sth ct.SignedTreeHead) error {
  174. if c.Verifier == nil {
  175. // Can't verify signatures without a verifier
  176. return nil
  177. }
  178. return c.Verifier.VerifySTHSignature(sth)
  179. }
  180. // VerifySCTSignature checks the signature in sct for the given LogEntryType, with associated certificate chain.
  181. func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype ct.LogEntryType, certData []ct.ASN1Cert) error {
  182. if c.Verifier == nil {
  183. // Can't verify signatures without a verifier
  184. return nil
  185. }
  186. leaf, err := ct.MerkleTreeLeafFromRawChain(certData, ctype, sct.Timestamp)
  187. if err != nil {
  188. return fmt.Errorf("failed to build MerkleTreeLeaf: %v", err)
  189. }
  190. entry := ct.LogEntry{Leaf: *leaf}
  191. return c.Verifier.VerifySCTSignature(sct, entry)
  192. }
  193. // GetSTHConsistency retrieves the consistency proof between two snapshots.
  194. func (c *LogClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) {
  195. base10 := 10
  196. params := map[string]string{
  197. "first": strconv.FormatUint(first, base10),
  198. "second": strconv.FormatUint(second, base10),
  199. }
  200. var resp ct.GetSTHConsistencyResponse
  201. httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHConsistencyPath, params, &resp)
  202. if err != nil {
  203. if httpRsp != nil {
  204. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  205. }
  206. return nil, err
  207. }
  208. return resp.Consistency, nil
  209. }
  210. // GetProofByHash returns an audit path for the hash of an SCT.
  211. func (c *LogClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error) {
  212. b64Hash := base64.StdEncoding.EncodeToString(hash)
  213. base10 := 10
  214. params := map[string]string{
  215. "tree_size": strconv.FormatUint(treeSize, base10),
  216. "hash": b64Hash,
  217. }
  218. var resp ct.GetProofByHashResponse
  219. httpRsp, body, err := c.GetAndParse(ctx, ct.GetProofByHashPath, params, &resp)
  220. if err != nil {
  221. if httpRsp != nil {
  222. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  223. }
  224. return nil, err
  225. }
  226. return &resp, nil
  227. }
  228. // GetAcceptedRoots retrieves the set of acceptable root certificates for a log.
  229. func (c *LogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
  230. var resp ct.GetRootsResponse
  231. httpRsp, body, err := c.GetAndParse(ctx, ct.GetRootsPath, nil, &resp)
  232. if err != nil {
  233. if httpRsp != nil {
  234. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  235. }
  236. return nil, err
  237. }
  238. var roots []ct.ASN1Cert
  239. for _, cert64 := range resp.Certificates {
  240. cert, err := base64.StdEncoding.DecodeString(cert64)
  241. if err != nil {
  242. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  243. }
  244. roots = append(roots, ct.ASN1Cert{Data: cert})
  245. }
  246. return roots, nil
  247. }
  248. // GetEntryAndProof returns a log entry and audit path for the index of a leaf.
  249. func (c *LogClient) GetEntryAndProof(ctx context.Context, index, treeSize uint64) (*ct.GetEntryAndProofResponse, error) {
  250. base10 := 10
  251. params := map[string]string{
  252. "leaf_index": strconv.FormatUint(index, base10),
  253. "tree_size": strconv.FormatUint(treeSize, base10),
  254. }
  255. var resp ct.GetEntryAndProofResponse
  256. httpRsp, body, err := c.GetAndParse(ctx, ct.GetEntryAndProofPath, params, &resp)
  257. if err != nil {
  258. if httpRsp != nil {
  259. return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
  260. }
  261. return nil, err
  262. }
  263. return &resp, nil
  264. }