func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) { req := addJSONRequest{ Data: data, } var resp addChainResponse _, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp) if err != nil { return nil, err } rawLogID, err := base64.StdEncoding.DecodeString(resp.ID) if err != nil { return nil, err } rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature) if err != nil { return nil, err } ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature)) if err != nil { return nil, err } var logID ct.SHA256Hash copy(logID[:], rawLogID) 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. func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) { var resp getSTHResponse if err = c.fetchAndParse(c.uri+GetSTHPath, &resp); err != nil { return } sth = &ct.SignedTreeHead{ TreeSize: resp.TreeSize, Timestamp: resp.Timestamp, } rawRootHash, err := base64.StdEncoding.DecodeString(resp.SHA256RootHash) if err != nil { return nil, fmt.Errorf("invalid base64 encoding in sha256_root_hash: %v", err) } if len(rawRootHash) != sha256.Size { return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(rawRootHash)) } copy(sth.SHA256RootHash[:], rawRootHash) rawSignature, err := base64.StdEncoding.DecodeString(resp.TreeHeadSignature) if err != nil { return nil, errors.New("invalid base64 encoding in tree_head_signature") } ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature)) if err != nil { return nil, err } // TODO(alcutter): Verify signature sth.TreeHeadSignature = *ds return }
// 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 addChainResponse var req addChainRequest for _, link := range chain { req.Chain = append(req.Chain, link) } _, err := c.PostAndParseWithRetry(ctx, path, &req, &resp) if err != nil { return nil, err } ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature)) if err != nil { return nil, err } var logID ct.SHA256Hash copy(logID[:], resp.ID) sct := &ct.SignedCertificateTimestamp{ SCTVersion: resp.SCTVersion, LogID: logID, Timestamp: resp.Timestamp, Extensions: ct.CTExtensions(resp.Extensions), Signature: *ds} err = c.VerifySCTSignature(*sct, ctype, chain) if err != nil { return nil, err } return sct, nil }
// GetSTH retrieves the current STH from the log. // Returns a populated SignedTreeHead, or a non-nil error. func (c *LogClient) GetSTH(ctx context.Context) (sth *ct.SignedTreeHead, err error) { var resp getSTHResponse _, err = c.GetAndParse(ctx, GetSTHPath, nil, &resp) if err != nil { return } sth = &ct.SignedTreeHead{ TreeSize: resp.TreeSize, Timestamp: resp.Timestamp, } if len(resp.SHA256RootHash) != sha256.Size { return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(resp.SHA256RootHash)) } copy(sth.SHA256RootHash[:], resp.SHA256RootHash) ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.TreeHeadSignature)) if err != nil { return nil, err } sth.TreeHeadSignature = *ds err = c.VerifySTHSignature(*sth) if err != nil { return nil, err } return }
// 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, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { var resp addChainResponse var req addChainRequest for _, link := range chain { req.Chain = append(req.Chain, link) } httpStatus := "Unknown" backoffSeconds := 0 done := false for !done { if backoffSeconds > 0 { log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds) } err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds)) if err != nil { return nil, err } if backoffSeconds > 0 { backoffSeconds = 0 } httpResp, _, err := c.postAndParse(c.uri+path, &req, &resp) if err != nil { backoffSeconds = 10 continue } switch { case httpResp.StatusCode == 200: done = true case httpResp.StatusCode == 408: // request timeout, retry immediately case httpResp.StatusCode == 503: // Retry backoffSeconds = 10 if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" { if seconds, err := strconv.Atoi(retryAfter); err == nil { backoffSeconds = seconds } } default: return nil, fmt.Errorf("got HTTP Status %s", httpResp.Status) } httpStatus = httpResp.Status } ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature)) if err != nil { return nil, err } var logID ct.SHA256Hash copy(logID[:], resp.ID) return &ct.SignedCertificateTimestamp{ SCTVersion: resp.SCTVersion, LogID: logID, Timestamp: resp.Timestamp, Extensions: ct.CTExtensions(resp.Extensions), Signature: *ds}, nil }
func TestGetSTHWorks(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/ct/v1/get-sth" { t.Fatalf("Incorrect URL path: %s", r.URL.Path) } fmt.Fprintf(w, `{"tree_size": %d, "timestamp": %d, "sha256_root_hash": "%s", "tree_head_signature": "%s"}`, ValidSTHResponseTreeSize, int64(ValidSTHResponseTimestamp), ValidSTHResponseSHA256RootHash, ValidSTHResponseTreeHeadSignature) })) defer ts.Close() client, err := New(ts.URL, &http.Client{}, jsonclient.Options{}) if err != nil { t.Fatal(err) } sth, err := client.GetSTH(context.Background()) if err != nil { t.Fatal(err) } if sth.TreeSize != ValidSTHResponseTreeSize { t.Fatal("Invalid tree size") } if sth.Timestamp != ValidSTHResponseTimestamp { t.Fatal("Invalid Timestamp") } if sth.SHA256RootHash.Base64String() != ValidSTHResponseSHA256RootHash { t.Fatal("Invalid SHA256RootHash") } expectedRawSignature, err := base64.StdEncoding.DecodeString(ValidSTHResponseTreeHeadSignature) if err != nil { t.Fatal("Couldn't b64 decode 'correct' STH signature!") } expectedDS, err := ct.UnmarshalDigitallySigned(bytes.NewReader(expectedRawSignature)) if err != nil { t.Fatalf("Couldn't unmarshal DigitallySigned: %v", err) } if sth.TreeHeadSignature.Algorithm.Hash != expectedDS.Algorithm.Hash { t.Fatalf("Invalid TreeHeadSignature.Algorithm.Hash: expected %v, got %v", sth.TreeHeadSignature.Algorithm.Hash, expectedDS.Algorithm.Hash) } if sth.TreeHeadSignature.Algorithm.Signature != expectedDS.Algorithm.Signature { t.Fatalf("Invalid TreeHeadSignature.Algorithm.Signature: expected %v, got %v", sth.TreeHeadSignature.Algorithm.Signature, expectedDS.Algorithm.Signature) } if bytes.Compare(sth.TreeHeadSignature.Signature, expectedDS.Signature) != 0 { t.Fatalf("Invalid TreeHeadSignature.Signature: expected %v, got %v", sth.TreeHeadSignature.Signature, expectedDS.Signature) } }
// AddJSON submits arbitrary data to to XJSON server. func (c *LogClient) AddJSON(ctx context.Context, data interface{}) (*ct.SignedCertificateTimestamp, error) { req := addJSONRequest{ Data: data, } var resp addChainResponse _, err := c.PostAndParse(ctx, AddJSONPath, &req, &resp) if err != nil { return nil, err } ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature)) if err != nil { return nil, err } var logID ct.SHA256Hash copy(logID[:], 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. func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) { var resp getSTHResponse if err = fetchAndParse(context.TODO(), c.httpClient, c.uri+GetSTHPath, &resp); err != nil { return } sth = &ct.SignedTreeHead{ TreeSize: resp.TreeSize, Timestamp: resp.Timestamp, } if len(resp.SHA256RootHash) != sha256.Size { return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(resp.SHA256RootHash)) } copy(sth.SHA256RootHash[:], resp.SHA256RootHash) ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.TreeHeadSignature)) if err != nil { return nil, err } // TODO(alcutter): Verify signature sth.TreeHeadSignature = *ds return }
// 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 addChainResponse var req addChainRequest for _, link := range chain { req.Chain = append(req.Chain, link) } httpStatus := "Unknown" // Retry after 1s, 2s, 4s, 8s, 16s, 32s, 64s, 128s, 128s, .... maxInterval := 128.0 backoffInterval := 1.0 backoffSeconds := 0 loop: for { if backoffSeconds > 0 { log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds) } err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds)) if err != nil { return nil, err } if backoffSeconds > 0 { backoffSeconds = 0 } httpResp, _, err := c.postAndParse(c.uri+path, &req, &resp) if err != nil { backoffSeconds = int(backoffInterval) if backoffInterval < maxInterval { backoffInterval *= 2.0 } continue } switch { case httpResp.StatusCode == 200: break loop case httpResp.StatusCode == 408: // request timeout, retry immediately case httpResp.StatusCode == 503: // Retry backoffSeconds = int(backoffInterval) if backoffInterval < maxInterval { backoffInterval *= 2.0 } if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" { if seconds, err := strconv.Atoi(retryAfter); err == nil { backoffSeconds = seconds } } default: return nil, fmt.Errorf("got HTTP Status %s", httpResp.Status) } httpStatus = httpResp.Status } ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature)) if err != nil { return nil, err } var logID ct.SHA256Hash copy(logID[:], resp.ID) sct := &ct.SignedCertificateTimestamp{ SCTVersion: resp.SCTVersion, LogID: logID, Timestamp: resp.Timestamp, Extensions: ct.CTExtensions(resp.Extensions), Signature: *ds} err = c.VerifySCTSignature(*sct, ctype, chain) if err != nil { return nil, err } return sct, nil }