diff --git a/go/client/logclient.go b/go/client/logclient.go index b668a312e..a87c30186 100644 --- a/go/client/logclient.go +++ b/go/client/logclient.go @@ -13,6 +13,7 @@ import ( "io/ioutil" "log" "net/http" + "net/url" "strconv" "time" @@ -23,11 +24,12 @@ import ( // URI paths for CT Log endpoints const ( - AddChainPath = "/ct/v1/add-chain" - AddPreChainPath = "/ct/v1/add-pre-chain" - AddJSONPath = "/ct/v1/add-json" - GetSTHPath = "/ct/v1/get-sth" - GetEntriesPath = "/ct/v1/get-entries" + AddChainPath = "/ct/v1/add-chain" + AddPreChainPath = "/ct/v1/add-pre-chain" + AddJSONPath = "/ct/v1/add-json" + GetSTHPath = "/ct/v1/get-sth" + GetEntriesPath = "/ct/v1/get-entries" + GetProofByHashPath = "/ct/v1/get-proof-by-hash" ) // LogClient represents a client for a given CT Log instance @@ -58,12 +60,6 @@ type addChainResponse struct { Signature string `json:"signature"` // Log signature for this SCT } -// addJSONRequest represents the JSON request body sent ot the add-json CT -// method. -type addJSONRequest struct { - Data interface{} `json:"data"` -} - // getSTHResponse respresents the JSON response to the get-sth CT method type getSTHResponse struct { TreeSize uint64 `json:"tree_size"` // Number of certs in the current tree @@ -106,6 +102,13 @@ type getEntryAndProofResponse struct { AuditPath []string `json:"audit_path"` // the corresponding proof } +type getProofByHashResponse struct { + LeafIndex int `json:"leaf_index"` // The 0-based index of the end entity corresponding to the "hash" parameter. + AuditPath []string `json:"audit_path"` // An array of base64-encoded Merkle Tree nodes proving the inclusion of the chosen certificate. + Success bool `json:"success"` + ErrorMessage string `json:"error_message"` +} + // New constructs a new LogClient instance. // |uri| is the base URI of the CT log instance to interact with, e.g. // http://ct.googleapis.com/pilot @@ -283,10 +286,7 @@ func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert return c.addChainWithRetry(ctx, AddChainPath, chain) } -func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) { - req := addJSONRequest{ - Data: data, - } +func (c *LogClient) AddJSON(req interface{}) (*ct.SignedCertificateTimestamp, error) { var resp addChainResponse _, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp) if err != nil { @@ -366,6 +366,7 @@ func (c *LogClient) GetEntries(start, end int64) ([]ct.LogEntry, error) { entries := make([]ct.LogEntry, len(resp.Entries)) for index, entry := range resp.Entries { leafBytes, err := base64.StdEncoding.DecodeString(entry.LeafInput) + leaf, err := ct.ReadMerkleTreeLeaf(bytes.NewBuffer(leafBytes)) if err != nil { return nil, err @@ -381,6 +382,8 @@ func (c *LogClient) GetEntries(start, end int64) ([]ct.LogEntry, error) { case ct.PrecertLogEntryType: chain, err = ct.UnmarshalPrecertChainArray(chainBytes) + case ct.XJSONLogEntryType: + // Do nothing. XJSON isn't a cert. default: return nil, fmt.Errorf("saw unknown entry type: %v", leaf.TimestampedEntry.EntryType) } @@ -392,3 +395,27 @@ func (c *LogClient) GetEntries(start, end int64) ([]ct.LogEntry, error) { } return entries, nil } + +func (c *LogClient) GetProofByHash(hash []byte, treeSize uint64) (ct.AuditPath, error) { + var resp getProofByHashResponse + b64Hash := url.QueryEscape(base64.StdEncoding.EncodeToString(hash)) + u := fmt.Sprintf("%s%s?tree_size=%d&hash=%v", c.uri, GetProofByHashPath, treeSize, b64Hash) + log.Printf("%v", u) + err := c.fetchAndParse(u, &resp) + if err != nil { + return nil, err + } + if resp.ErrorMessage != "" { + return nil, fmt.Errorf("%v", resp.ErrorMessage) + } + + path := make(ct.AuditPath, len(resp.AuditPath)) + for i, p := range resp.AuditPath { + node, err := base64.StdEncoding.DecodeString(p) + if err != nil { + return nil, err + } + path[i] = node + } + return path, nil +} diff --git a/go/client/logclient_test.go b/go/client/logclient_test.go index 2419b1c43..b1fbc946e 100644 --- a/go/client/logclient_test.go +++ b/go/client/logclient_test.go @@ -32,8 +32,45 @@ const ( CertEntryExtraDataB64 = "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" SubmissionCertB64 = "MIIEijCCA3KgAwIBAgICEk0wDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgY2Fja2xpbmcgY3J5cHRvZ3JhcGhlciBmYWtlIFJPT1QwHhcNMTUxMDIxMjAxMTUyWhcNMjAxMDE5MjAxMTUyWjAfMR0wGwYDVQQDExRoYXBweSBoYWNrZXIgZmFrZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIKR3maBcUSsncXYzQT13D5Nr+Z3mLxMMh3TUdt6sACmqbJ0btRlgXfMtNLM2OU1I6a3Ju+tIZSdn2v21JBwvxUzpZQ4zy2cimIiMQDZCQHJwzC9GZn8HaW091iz9H0Go3A7WDXwYNmsdLNRi00o14UjoaVqaPsYrZWvRKaIRqaU0hHmS0AWwQSvN/93iMIXuyiwywmkwKbWnnxCQ/gsctKFUtcNrwEx9Wgj6KlhwDTyI1QWSBbxVYNyUgPFzKxrSmwMO0yNff7ho+QT9x5+Y/7XE59S4Mc4ZXxcXKew/gSlN9U5mvT+D2BhDtkCupdfsZNCQWp27A+b/DmrFI9NqsCAwEAAaOCAcIwggG+MBIGA1UdEwEB/wQIMAYBAf8CAQAwQwYDVR0eBDwwOqE4MAaCBC5taWwwCocIAAAAAAAAAAAwIocgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDgYDVR0PAQH/BAQDAgGGMH8GCCsGAQUFBwEBBHMwcTAyBggrBgEFBQcwAYYmaHR0cDovL2lzcmcudHJ1c3RpZC5vY3NwLmlkZW50cnVzdC5jb20wOwYIKwYBBQUHMAKGL2h0dHA6Ly9hcHBzLmlkZW50cnVzdC5jb20vcm9vdHMvZHN0cm9vdGNheDMucDdjMB8GA1UdIwQYMBaAFOmkP+6epeby1dd5YDyTpi4kjpeqMFQGA1UdIARNMEswCAYGZ4EMAQIBMD8GCysGAQQBgt8TAQEBMDAwLgYIKwYBBQUHAgEWImh0dHA6Ly9jcHMucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5pZGVudHJ1c3QuY29tL0RTVFJPT1RDQVgzQ1JMLmNybDAdBgNVHQ4EFgQU+3hPEvlgFYMsnxd/NBmzLjbqQYkwDQYJKoZIhvcNAQELBQADggEBAA0YAeLXOklx4hhCikUUl+BdnFfn1g0W5AiQLVNIOL6PnqXu0wjnhNyhqdwnfhYMnoy4idRh4lB6pz8Gf9pnlLd/DnWSV3gS+/I/mAl1dCkKby6H2V790e6IHmIK2KYm3jm+U++FIdGpBdsQTSdmiX/rAyuxMDM0adMkNBwTfQmZQCz6nGHw1QcSPZMvZpsC8SkvekzxsjF1otOrMUPNPQvtTWrVx8GlR2qfx/4xbQa1v2frNvFBCmO59goz+jnWvfTtj2NjwDZ7vlMBsPm16dbKYC840uvRoZjxqsdc3ChCZjqimFqlNG/xoPA8+dTicZzCXE9ijPIcvW6y1aa3bGw=" + AddJSONResp = `{ + "sct_version":0, + "id":"KHYaGJAn++880NYaAY12sFBXKcenQRvMvfYE9F1CYVM=", + "timestamp":1337, + "extensions":"", + "signature":"BAMARjBEAiAIc21J5ZbdKZHw5wLxCP+MhBEsV5+nfvGyakOIv6FOvAIgWYMZb6Pw///uiNM7QTg2Of1OqmK1GbeGuEl9VJN8v8c=" + }` + ProofByHashResp = ` + { + "leaf_index": 3, + "audit_path": [ + "pMumx96PIUB3TX543ljlpQ/RgZRqitRfykupIZrXq0Q=", + "5s2NQWkjmesu+Kqgp70TCwVLwq8obpHw/JyMGwN56pQ=", + "7VelXijfmGFSl62BWIsG8LRmxJGBq9XP8FxmszuT2Cg=" + ] + }` ) +// Create a test CT server. +func CtServer(t *testing.T) *httptest.Server { + hs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.URL.Path == "/ct/v1/get-sth": + fmt.Fprintf(w, `{"tree_size": %d, "timestamp": %d, "sha256_root_hash": "%s", "tree_head_signature": "%s"}`, + ValidSTHResponseTreeSize, + int64(ValidSTHResponseTimestamp), + ValidSTHResponseSHA256RootHash, + ValidSTHResponseTreeHeadSignature) + + case r.URL.Path == "/ct/v1/add-json": + w.Write([]byte(AddJSONResp)) + case r.URL.Path == "/ct/v1/get-proof-by-hash": + w.Write([]byte(ProofByHashResp)) + default: + t.Fatalf("Incorrect URL path: %s", r.URL.Path) + } + })) + return hs +} func TestGetEntriesWorks(t *testing.T) { positiveDecimalNumber := regexp.MustCompile("[0-9]+") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -214,3 +251,33 @@ func TestAddJSON(t *testing.T) { } } } + +func TestGetProofByHash(t *testing.T) { + hs := CtServer(t) + defer hs.Close() + + c := New(hs.URL) + + tests := []struct { + hash []byte + treesize uint64 + }{ + { + []byte{0x4A, 0x9E, 0x8E, 0xDB, 0xE5, 0xCE, 0x2D, 0x2D, + 0xA6, 0x9D, 0x48, 0x3E, 0xDB, 0x45, 0x18, 0x66, + 0x75, 0xD4, 0xBE, 0x37, 0xB6, 0x49, 0xD4, 0x09, + 0x23, 0xB1, 0x56, 0xA7, 0xD1, 0x27, 0x74, 0x63}, // random SHA256 output + 5, + }, + } + + for _, tc := range tests { + auditPath, err := c.GetProofByHash(tc.hash, tc.treesize) + if err != nil { + t.Errorf("GetProofByHash(%h, %v): %v", tc.hash, tc.treesize, err) + } + if got := len(auditPath); got < 1 { + t.Errorf("len(GetProofByHash(%h, %v)): %v, want > 1", tc.hash, tc.treesize, got) + } + } +} diff --git a/go/serialization.go b/go/serialization.go index 56e0ec940..ae64d7f7b 100644 --- a/go/serialization.go +++ b/go/serialization.go @@ -5,9 +5,12 @@ import ( "container/list" "crypto" "encoding/binary" + "encoding/json" "errors" "fmt" "io" + + "github.com/benlaurie/objecthash/go/objecthash" ) // Variable size structure prefix-header byte lengths @@ -17,6 +20,7 @@ const ( ExtensionsLengthBytes = 2 CertificateChainLengthBytes = 3 SignatureLengthBytes = 2 + JSONLengthBytes = 3 ) // Max lengths @@ -80,7 +84,7 @@ func readVarBytes(r io.Reader, numLenBytes int) ([]byte, error) { return nil, err } data := make([]byte, l) - if n, err := io.ReadFull(r, data); err != nil { + if n, err := io.ReadFull(r, data); err != nil { if err == io.EOF || err == io.ErrUnexpectedEOF { return nil, fmt.Errorf("short read: expected %d but got %d", l, n) } @@ -123,10 +127,10 @@ func readASN1CertList(r io.Reader, totalLenBytes int, elementLenBytes int) ([]AS // Returns a non-nil error if there was a problem. func ReadTimestampedEntryInto(r io.Reader, t *TimestampedEntry) error { var err error - if err = binary.Read(r, binary.BigEndian, &t.Timestamp); err != nil { + if err := binary.Read(r, binary.BigEndian, &t.Timestamp); err != nil { return err } - if err = binary.Read(r, binary.BigEndian, &t.EntryType); err != nil { + if err := binary.Read(r, binary.BigEndian, &t.EntryType); err != nil { return err } switch t.EntryType { @@ -141,6 +145,10 @@ func ReadTimestampedEntryInto(r io.Reader, t *TimestampedEntry) error { if t.PrecertEntry.TBSCertificate, err = readVarBytes(r, PreCertificateLengthBytes); err != nil { return err } + case XJSONLogEntryType: + if t.JSONData, err = readVarBytes(r, JSONLengthBytes); err != nil { + return err + } default: return fmt.Errorf("unknown EntryType: %d", t.EntryType) } @@ -148,6 +156,37 @@ func ReadTimestampedEntryInto(r io.Reader, t *TimestampedEntry) error { return nil } +func SerializeTimestampedEntry(w io.Writer, t *TimestampedEntry) error { + if err := binary.Write(w, binary.BigEndian, t.Timestamp); err != nil { + return err + } + if err := binary.Write(w, binary.BigEndian, t.EntryType); err != nil { + return err + } + switch t.EntryType { + case X509LogEntryType: + if err := writeVarBytes(w, t.X509Entry, CertificateLengthBytes); err != nil { + return err + } + case PrecertLogEntryType: + if err := binary.Write(w, binary.BigEndian, t.PrecertEntry.IssuerKeyHash); err != nil { + return err + } + if err := writeVarBytes(w, t.PrecertEntry.TBSCertificate, PreCertificateLengthBytes); err != nil { + return err + } + case XJSONLogEntryType: + jsonhash := objecthash.CommonJSONHash(string(t.JSONData)) + if err := writeVarBytes(w, jsonhash[:], JSONLengthBytes); err != nil { + return err + } + default: + return fmt.Errorf("unknown EntryType: %d", t.EntryType) + } + writeVarBytes(w, t.Extensions, ExtensionsLengthBytes) + return nil +} + // ReadMerkleTreeLeaf parses the byte-stream representation of a MerkleTreeLeaf // and returns a pointer to a new MerkleTreeLeaf structure containing the // parsed data. @@ -174,6 +213,25 @@ func ReadMerkleTreeLeaf(r io.Reader) (*MerkleTreeLeaf, error) { return &m, nil } +func SerializeMerkleTreeLeaf(w io.Writer, m *MerkleTreeLeaf) error { + if m.Version != V1 { + return fmt.Errorf("unknown Version %d", m.Version) + } + if err := binary.Write(w, binary.BigEndian, m.Version); err != nil { + return err + } + if m.LeafType != TimestampedEntryLeafType { + return fmt.Errorf("unknown LeafType %d", m.LeafType) + } + if err := binary.Write(w, binary.BigEndian, m.LeafType); err != nil { + return err + } + if err := SerializeTimestampedEntry(w, &m.TimestampedEntry); err != nil { + return err + } + return nil +} + // UnmarshalX509ChainArray unmarshalls the contents of the "chain:" entry in a // GetEntries response in the case where the entry refers to an X509 leaf. func UnmarshalX509ChainArray(b []byte) ([]ASN1Cert, error) { @@ -337,9 +395,12 @@ func serializeV1SCTSignatureInput(sct SignedCertificateTimestamp, entry LogEntry } switch entry.Leaf.TimestampedEntry.EntryType { case X509LogEntryType: - return serializeV1CertSCTSignatureInput(sct.Timestamp, entry.Leaf.TimestampedEntry.X509Entry, entry.Leaf.TimestampedEntry.Extensions) + return serializeV1CertSCTSignatureInput(sct.Timestamp, + entry.Leaf.TimestampedEntry.X509Entry, + entry.Leaf.TimestampedEntry.Extensions) case PrecertLogEntryType: - return serializeV1PrecertSCTSignatureInput(sct.Timestamp, entry.Leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash, + return serializeV1PrecertSCTSignatureInput(sct.Timestamp, + entry.Leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash, entry.Leaf.TimestampedEntry.PrecertEntry.TBSCertificate, entry.Leaf.TimestampedEntry.Extensions) default: @@ -509,3 +570,21 @@ func SerializeSTHSignatureInput(sth SignedTreeHead) ([]byte, error) { return nil, fmt.Errorf("unsupported STH version %d", sth.Version) } } + +func SerializeV1SCTMerkleTreeLeafJSON(w io.Writer, sct *SignedCertificateTimestamp, data interface{}) error { + jsonData, err := json.Marshal(data) + if err != nil { + return nil + } + m := &MerkleTreeLeaf{ + Version: sct.SCTVersion, + LeafType: TimestampedEntryLeafType, + TimestampedEntry: TimestampedEntry{ + Timestamp: sct.Timestamp, + EntryType: XJSONLogEntryType, + JSONData: jsonData, + Extensions: sct.Extensions, + }, + } + return SerializeMerkleTreeLeaf(w, m) +} diff --git a/go/signatures.go b/go/signatures.go index 600db2454..0af785c19 100644 --- a/go/signatures.go +++ b/go/signatures.go @@ -12,6 +12,7 @@ import ( "errors" "flag" "fmt" + "hash" "log" "math/big" ) @@ -129,3 +130,16 @@ func (s SignatureVerifier) VerifySTHSignature(sth SignedTreeHead) error { } return s.verifySignature(sthData, sth.TreeHeadSignature) } + +func V1LeafHash() hash.Hash { + // V1 leaf hash is 0x00 || serialized proto. + h := sha256.New() + h.Write([]byte{0x00}) + return h +} + +func JSONV1LeafHash(sct *SignedCertificateTimestamp, data interface{}) []byte { + h := V1LeafHash() + SerializeV1SCTMerkleTreeLeafJSON(h, sct, data) + return h.Sum(nil) +} diff --git a/go/types.go b/go/types.go index 8a63e98e6..20a77d5e2 100644 --- a/go/types.go +++ b/go/types.go @@ -28,6 +28,8 @@ func (e LogEntryType) String() string { return "X509LogEntryType" case PrecertLogEntryType: return "PrecertLogEntryType" + case XJSONLogEntryType: + return "XJSONLogEntryType" } panic(fmt.Sprintf("No string defined for LogEntryType constant value %d", e)) } @@ -36,6 +38,7 @@ func (e LogEntryType) String() string { const ( X509LogEntryType LogEntryType = 0 PrecertLogEntryType LogEntryType = 1 + XJSONLogEntryType LogEntryType = 0x8000 // Experimental. Don't rely on this! ) // MerkleLeafType represents the MerkleLeafType enum from section 3.4 of the @@ -198,6 +201,7 @@ func (d *DigitallySigned) FromBase64String(b64 string) error { return fmt.Errorf("failed to unbase64 DigitallySigned: %v", err) } ds, err := UnmarshalDigitallySigned(bytes.NewReader(raw)) + if err != nil { return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err) } @@ -238,6 +242,7 @@ type LogEntry struct { Leaf MerkleTreeLeaf X509Cert *x509.Certificate Precert *Precertificate + JSONData []byte Chain []ASN1Cert } @@ -314,6 +319,7 @@ type TimestampedEntry struct { EntryType LogEntryType X509Entry ASN1Cert PrecertEntry PreCert + JSONData []byte Extensions CTExtensions }