logclient.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. // Package client is a CT log client implementation and contains types and code
  2. // for interacting with RFC6962-compliant CT Log instances.
  3. // See http://tools.ietf.org/html/rfc6962 for details
  4. package client
  5. import (
  6. "bytes"
  7. "crypto/sha256"
  8. "encoding/base64"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "io/ioutil"
  13. "log"
  14. "net/http"
  15. "net/url"
  16. "strconv"
  17. "time"
  18. ct "github.com/google/certificate-transparency/go"
  19. "golang.org/x/net/context"
  20. )
  21. // URI paths for CT Log endpoints
  22. const (
  23. AddChainPath = "/ct/v1/add-chain"
  24. AddPreChainPath = "/ct/v1/add-pre-chain"
  25. AddJSONPath = "/ct/v1/add-json"
  26. GetSTHPath = "/ct/v1/get-sth"
  27. GetEntriesPath = "/ct/v1/get-entries"
  28. GetProofByHashPath = "/ct/v1/get-proof-by-hash"
  29. GetSTHConsistencyPath = "/ct/v1/get-sth-consistency"
  30. )
  31. // LogClient represents a client for a given CT Log instance
  32. type LogClient struct {
  33. uri string // the base URI of the log. e.g. http://ct.googleapis/pilot
  34. httpClient *http.Client // used to interact with the log via HTTP
  35. verifier *ct.SignatureVerifier // nil if no public key for log available
  36. }
  37. //////////////////////////////////////////////////////////////////////////////////
  38. // JSON structures follow.
  39. // These represent the structures returned by the CT Log server.
  40. //////////////////////////////////////////////////////////////////////////////////
  41. // addChainRequest represents the JSON request body sent to the add-chain CT
  42. // method.
  43. type addChainRequest struct {
  44. Chain [][]byte `json:"chain"`
  45. }
  46. // addChainResponse represents the JSON response to the add-chain CT method.
  47. // An SCT represents a Log's promise to integrate a [pre-]certificate into the
  48. // log within a defined period of time.
  49. type addChainResponse struct {
  50. SCTVersion ct.Version `json:"sct_version"` // SCT structure version
  51. ID []byte `json:"id"` // Log ID
  52. Timestamp uint64 `json:"timestamp"` // Timestamp of issuance
  53. Extensions string `json:"extensions"` // Holder for any CT extensions
  54. Signature []byte `json:"signature"` // Log signature for this SCT
  55. }
  56. // addJSONRequest represents the JSON request body sent to the add-json CT
  57. // method.
  58. type addJSONRequest struct {
  59. Data interface{} `json:"data"`
  60. }
  61. // getSTHResponse respresents the JSON response to the get-sth CT method
  62. type getSTHResponse struct {
  63. TreeSize uint64 `json:"tree_size"` // Number of certs in the current tree
  64. Timestamp uint64 `json:"timestamp"` // Time that the tree was created
  65. SHA256RootHash []byte `json:"sha256_root_hash"` // Root hash of the tree
  66. TreeHeadSignature []byte `json:"tree_head_signature"` // Log signature for this STH
  67. }
  68. // getConsistencyProofResponse represents the JSON response to the get-consistency-proof CT method
  69. type getConsistencyProofResponse struct {
  70. Consistency [][]byte `json:"consistency"`
  71. }
  72. // getAuditProofResponse represents the JSON response to the CT get-audit-proof method
  73. type getAuditProofResponse struct {
  74. Hash []string `json:"hash"` // the hashes which make up the proof
  75. TreeSize uint64 `json:"tree_size"` // the tree size against which this proof is constructed
  76. }
  77. // getAcceptedRootsResponse represents the JSON response to the CT get-roots method.
  78. type getAcceptedRootsResponse struct {
  79. Certificates []string `json:"certificates"`
  80. }
  81. // getEntryAndProodReponse represents the JSON response to the CT get-entry-and-proof method
  82. type getEntryAndProofResponse struct {
  83. LeafInput string `json:"leaf_input"` // the entry itself
  84. ExtraData string `json:"extra_data"` // any chain provided when the entry was added to the log
  85. AuditPath []string `json:"audit_path"` // the corresponding proof
  86. }
  87. // GetProofByHashResponse represents the JSON response to the CT get-proof-by-hash method.
  88. type GetProofByHashResponse struct {
  89. LeafIndex int64 `json:"leaf_index"` // The 0-based index of the end entity corresponding to the "hash" parameter.
  90. AuditPath [][]byte `json:"audit_path"` // An array of base64-encoded Merkle Tree nodes proving the inclusion of the chosen certificate.
  91. }
  92. // New constructs a new LogClient instance.
  93. // |uri| is the base URI of the CT log instance to interact with, e.g.
  94. // http://ct.googleapis.com/pilot
  95. // |hc| is the underlying client to be used for HTTP requests to the CT log.
  96. func New(uri string, hc *http.Client) *LogClient {
  97. if hc == nil {
  98. hc = new(http.Client)
  99. }
  100. return &LogClient{uri: uri, httpClient: hc}
  101. }
  102. // NewWithPubKey constructs a new LogClient instance that includes public
  103. // key information for the log; this instance will check signatures on
  104. // responses from the log.
  105. func NewWithPubKey(uri string, hc *http.Client, pemEncodedKey string) (*LogClient, error) {
  106. pubkey, _, rest, err := ct.PublicKeyFromPEM([]byte(pemEncodedKey))
  107. if err != nil {
  108. return nil, err
  109. }
  110. if len(rest) > 0 {
  111. return nil, errors.New("extra data found after PEM key decoded")
  112. }
  113. verifier, err := ct.NewSignatureVerifier(pubkey)
  114. if err != nil {
  115. return nil, err
  116. }
  117. if hc == nil {
  118. hc = new(http.Client)
  119. }
  120. return &LogClient{uri: uri, httpClient: hc, verifier: verifier}, nil
  121. }
  122. // Makes a HTTP call to |uri|, and attempts to parse the response as a
  123. // JSON representation of the structure in |res|. Uses |ctx| to
  124. // control the HTTP call (so it can have a timeout or be cancelled by
  125. // the caller), and |httpClient| to make the actual HTTP call.
  126. // Returns a non-nil |error| if there was a problem.
  127. func fetchAndParse(ctx context.Context, httpClient *http.Client, uri string, res interface{}) error {
  128. req, err := http.NewRequest(http.MethodGet, uri, nil)
  129. if err != nil {
  130. return err
  131. }
  132. req.Cancel = ctx.Done()
  133. resp, err := httpClient.Do(req)
  134. if err != nil {
  135. return err
  136. }
  137. defer resp.Body.Close()
  138. // Make sure everything is read, so http.Client can reuse the connection.
  139. defer ioutil.ReadAll(resp.Body)
  140. if resp.StatusCode != 200 {
  141. return fmt.Errorf("got HTTP Status %s", resp.Status)
  142. }
  143. if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
  144. return err
  145. }
  146. return nil
  147. }
  148. // Makes a HTTP POST call to |uri|, and attempts to parse the response as a JSON
  149. // representation of the structure in |res|.
  150. // Returns a non-nil |error| if there was a problem.
  151. func (c *LogClient) postAndParse(uri string, req interface{}, res interface{}) (*http.Response, string, error) {
  152. postBody, err := json.Marshal(req)
  153. if err != nil {
  154. return nil, "", err
  155. }
  156. httpReq, err := http.NewRequest(http.MethodPost, uri, bytes.NewReader(postBody))
  157. if err != nil {
  158. return nil, "", err
  159. }
  160. httpReq.Header.Set("Content-Type", "application/json")
  161. resp, err := c.httpClient.Do(httpReq)
  162. // Read all of the body, if there is one, so that the http.Client can do
  163. // Keep-Alive:
  164. var body []byte
  165. if resp != nil {
  166. body, err = ioutil.ReadAll(resp.Body)
  167. resp.Body.Close()
  168. }
  169. if err != nil {
  170. return resp, string(body), err
  171. }
  172. if resp.StatusCode == 200 {
  173. if err != nil {
  174. return resp, string(body), err
  175. }
  176. if err = json.Unmarshal(body, &res); err != nil {
  177. return resp, string(body), err
  178. }
  179. }
  180. return resp, string(body), nil
  181. }
  182. func backoffForRetry(ctx context.Context, d time.Duration) error {
  183. backoffTimer := time.NewTimer(d)
  184. if ctx != nil {
  185. select {
  186. case <-ctx.Done():
  187. return ctx.Err()
  188. case <-backoffTimer.C:
  189. }
  190. } else {
  191. <-backoffTimer.C
  192. }
  193. return nil
  194. }
  195. // Attempts to add |chain| to the log, using the api end-point specified by
  196. // |path|. If provided context expires before submission is complete an
  197. // error will be returned.
  198. func (c *LogClient) addChainWithRetry(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
  199. var resp addChainResponse
  200. var req addChainRequest
  201. for _, link := range chain {
  202. req.Chain = append(req.Chain, link)
  203. }
  204. httpStatus := "Unknown"
  205. backoffSeconds := 0
  206. done := false
  207. for !done {
  208. if backoffSeconds > 0 {
  209. log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds)
  210. }
  211. err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds))
  212. if err != nil {
  213. return nil, err
  214. }
  215. if backoffSeconds > 0 {
  216. backoffSeconds = 0
  217. }
  218. httpResp, _, err := c.postAndParse(c.uri+path, &req, &resp)
  219. if err != nil {
  220. backoffSeconds = 10
  221. continue
  222. }
  223. switch {
  224. case httpResp.StatusCode == 200:
  225. done = true
  226. case httpResp.StatusCode == 408:
  227. // request timeout, retry immediately
  228. case httpResp.StatusCode == 503:
  229. // Retry
  230. backoffSeconds = 10
  231. if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" {
  232. if seconds, err := strconv.Atoi(retryAfter); err == nil {
  233. backoffSeconds = seconds
  234. }
  235. }
  236. default:
  237. return nil, fmt.Errorf("got HTTP Status %s", httpResp.Status)
  238. }
  239. httpStatus = httpResp.Status
  240. }
  241. ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature))
  242. if err != nil {
  243. return nil, err
  244. }
  245. var logID ct.SHA256Hash
  246. copy(logID[:], resp.ID)
  247. sct := &ct.SignedCertificateTimestamp{
  248. SCTVersion: resp.SCTVersion,
  249. LogID: logID,
  250. Timestamp: resp.Timestamp,
  251. Extensions: ct.CTExtensions(resp.Extensions),
  252. Signature: *ds}
  253. err = c.VerifySCTSignature(*sct, ctype, chain)
  254. if err != nil {
  255. return nil, err
  256. }
  257. return sct, nil
  258. }
  259. // AddChain adds the (DER represented) X509 |chain| to the log.
  260. func (c *LogClient) AddChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
  261. return c.addChainWithRetry(nil, ct.X509LogEntryType, AddChainPath, chain)
  262. }
  263. // AddPreChain adds the (DER represented) Precertificate |chain| to the log.
  264. func (c *LogClient) AddPreChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
  265. return c.addChainWithRetry(nil, ct.PrecertLogEntryType, AddPreChainPath, chain)
  266. }
  267. // AddChainWithContext adds the (DER represented) X509 |chain| to the log and
  268. // fails if the provided context expires before the chain is submitted.
  269. func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
  270. return c.addChainWithRetry(ctx, ct.X509LogEntryType, AddChainPath, chain)
  271. }
  272. // AddJSON submits arbitrary data to to XJSON server.
  273. func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) {
  274. req := addJSONRequest{
  275. Data: data,
  276. }
  277. var resp addChainResponse
  278. _, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp)
  279. if err != nil {
  280. return nil, err
  281. }
  282. ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature))
  283. if err != nil {
  284. return nil, err
  285. }
  286. var logID ct.SHA256Hash
  287. copy(logID[:], resp.ID)
  288. return &ct.SignedCertificateTimestamp{
  289. SCTVersion: resp.SCTVersion,
  290. LogID: logID,
  291. Timestamp: resp.Timestamp,
  292. Extensions: ct.CTExtensions(resp.Extensions),
  293. Signature: *ds}, nil
  294. }
  295. // GetSTH retrieves the current STH from the log.
  296. // Returns a populated SignedTreeHead, or a non-nil error.
  297. func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) {
  298. var resp getSTHResponse
  299. if err = fetchAndParse(context.TODO(), c.httpClient, c.uri+GetSTHPath, &resp); err != nil {
  300. return
  301. }
  302. sth = &ct.SignedTreeHead{
  303. TreeSize: resp.TreeSize,
  304. Timestamp: resp.Timestamp,
  305. }
  306. if len(resp.SHA256RootHash) != sha256.Size {
  307. return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(resp.SHA256RootHash))
  308. }
  309. copy(sth.SHA256RootHash[:], resp.SHA256RootHash)
  310. ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.TreeHeadSignature))
  311. if err != nil {
  312. return nil, err
  313. }
  314. sth.TreeHeadSignature = *ds
  315. err = c.VerifySTHSignature(*sth)
  316. if err != nil {
  317. return nil, err
  318. }
  319. return
  320. }
  321. // VerifySTHSignature checks the signature in sth, returning any error encountered or nil if verification is
  322. // successful.
  323. func (c *LogClient) VerifySTHSignature(sth ct.SignedTreeHead) error {
  324. if c.verifier == nil {
  325. // Can't verify signatures without a verifier
  326. return nil
  327. }
  328. return c.verifier.VerifySTHSignature(sth)
  329. }
  330. // VerifySCTSignature checks the signature in sct for the given LogEntryType, with associated certificate chain.
  331. func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype ct.LogEntryType, certData []ct.ASN1Cert) error {
  332. if c.verifier == nil {
  333. // Can't verify signatures without a verifier
  334. return nil
  335. }
  336. if ctype == ct.PrecertLogEntryType {
  337. // TODO(drysdale): cope with pre-certs, which need to have the
  338. // following fields set:
  339. // leaf.PrecertEntry.TBSCertificate
  340. // leaf.PrecertEntry.IssuerKeyHash (SHA-256 of issuer's public key)
  341. return errors.New("SCT verification for pre-certificates unimplemented")
  342. }
  343. // Build enough of a Merkle tree leaf for the verifier to work on.
  344. leaf := ct.MerkleTreeLeaf{
  345. Version: sct.SCTVersion,
  346. LeafType: ct.TimestampedEntryLeafType,
  347. TimestampedEntry: ct.TimestampedEntry{
  348. Timestamp: sct.Timestamp,
  349. EntryType: ctype,
  350. X509Entry: certData[0],
  351. Extensions: sct.Extensions}}
  352. entry := ct.LogEntry{Leaf: leaf}
  353. return c.verifier.VerifySCTSignature(sct, entry)
  354. }
  355. // GetSTHConsistency retrieves the consistency proof between two snapshots.
  356. func (c *LogClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) {
  357. u := fmt.Sprintf("%s%s?first=%d&second=%d", c.uri, GetSTHConsistencyPath, first, second)
  358. var resp getConsistencyProofResponse
  359. if err := fetchAndParse(ctx, c.httpClient, u, &resp); err != nil {
  360. return nil, err
  361. }
  362. return resp.Consistency, nil
  363. }
  364. // GetProofByHash returns an audit path for the hash of an SCT.
  365. func (c *LogClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*GetProofByHashResponse, error) {
  366. b64Hash := url.QueryEscape(base64.StdEncoding.EncodeToString(hash))
  367. u := fmt.Sprintf("%s%s?tree_size=%d&hash=%v", c.uri, GetProofByHashPath, treeSize, b64Hash)
  368. var resp GetProofByHashResponse
  369. if err := fetchAndParse(ctx, c.httpClient, u, &resp); err != nil {
  370. return nil, err
  371. }
  372. return &resp, nil
  373. }