diff --git a/hack/vendor.sh b/hack/vendor.sh index 34fe33ea20..6e83043f26 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -151,7 +151,7 @@ clone git github.com/docker/swarmkit 3b221eb0391d34ae0b9dac65df02b5b64de6dff2 clone git github.com/golang/mock bd3c8e81be01eef76d4b503f5e687d2d1354d2d9 clone git github.com/gogo/protobuf v0.3 clone git github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a -clone git github.com/google/certificate-transparency 0f6e3d1d1ba4d03fdaab7cd716f36255c2e48341 +clone git github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e clone git golang.org/x/crypto 3fbbcd23f1cb824e69491a5930cfeff09b12f4d2 https://github.com/golang/crypto.git clone git golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb https://github.com/golang/time.git clone git github.com/mreiferson/go-httpclient 63fe23f7434723dc904c901043af07931f293c47 diff --git a/vendor/src/github.com/google/certificate-transparency/go/client/getentries.go b/vendor/src/github.com/google/certificate-transparency/go/client/getentries.go new file mode 100644 index 0000000000..32be65c92f --- /dev/null +++ b/vendor/src/github.com/google/certificate-transparency/go/client/getentries.go @@ -0,0 +1,88 @@ +package client + +import ( + "bytes" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + ct "github.com/google/certificate-transparency/go" + "golang.org/x/net/context" +) + +// LeafEntry respresents a JSON leaf entry. +type LeafEntry struct { + LeafInput []byte `json:"leaf_input"` + ExtraData []byte `json:"extra_data"` +} + +// GetEntriesResponse respresents the JSON response to the CT get-entries method. +type GetEntriesResponse struct { + Entries []LeafEntry `json:"entries"` // the list of returned entries +} + +// GetRawEntries exposes the /ct/v1/get-entries result with only the JSON parsing done. +func GetRawEntries(ctx context.Context, httpClient *http.Client, logURL string, start, end int64) (*GetEntriesResponse, error) { + if end < 0 { + return nil, errors.New("end should be >= 0") + } + if end < start { + return nil, errors.New("start should be <= end") + } + + baseURL, err := url.Parse(strings.TrimRight(logURL, "/") + GetEntriesPath) + if err != nil { + return nil, err + } + + baseURL.RawQuery = url.Values{ + "start": []string{strconv.FormatInt(start, 10)}, + "end": []string{strconv.FormatInt(end, 10)}, + }.Encode() + + var resp GetEntriesResponse + err = fetchAndParse(context.TODO(), httpClient, baseURL.String(), &resp) + if err != nil { + return nil, err + } + + return &resp, nil +} + +// GetEntries attempts to retrieve the entries in the sequence [|start|, |end|] from the CT log server. (see section 4.6.) +// Returns a slice of LeafInputs or a non-nil error. +func (c *LogClient) GetEntries(start, end int64) ([]ct.LogEntry, error) { + resp, err := GetRawEntries(context.TODO(), c.httpClient, c.uri, start, end) + if err != nil { + return nil, err + } + entries := make([]ct.LogEntry, len(resp.Entries)) + for index, entry := range resp.Entries { + leaf, err := ct.ReadMerkleTreeLeaf(bytes.NewBuffer(entry.LeafInput)) + if err != nil { + return nil, err + } + entries[index].Leaf = *leaf + + var chain []ct.ASN1Cert + switch leaf.TimestampedEntry.EntryType { + case ct.X509LogEntryType: + chain, err = ct.UnmarshalX509ChainArray(entry.ExtraData) + + case ct.PrecertLogEntryType: + chain, err = ct.UnmarshalPrecertChainArray(entry.ExtraData) + + default: + return nil, fmt.Errorf("saw unknown entry type: %v", leaf.TimestampedEntry.EntryType) + } + if err != nil { + return nil, err + } + entries[index].Chain = chain + entries[index].Index = start + int64(index) + } + return entries, nil +} diff --git a/vendor/src/github.com/google/certificate-transparency/go/client/logclient.go b/vendor/src/github.com/google/certificate-transparency/go/client/logclient.go index 3c568a50bf..7b3dbbc234 100644 --- a/vendor/src/github.com/google/certificate-transparency/go/client/logclient.go +++ b/vendor/src/github.com/google/certificate-transparency/go/client/logclient.go @@ -13,26 +13,30 @@ import ( "io/ioutil" "log" "net/http" + "net/url" "strconv" "time" - "github.com/google/certificate-transparency/go" + ct "github.com/google/certificate-transparency/go" "golang.org/x/net/context" ) // 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" + GetSTHConsistencyPath = "/ct/v1/get-sth-consistency" ) // LogClient represents a client for a given CT Log instance type LogClient struct { - uri string // the base URI of the log. e.g. http://ct.googleapis/pilot - httpClient *http.Client // used to interact with the log via HTTP + uri string // the base URI of the log. e.g. http://ct.googleapis/pilot + httpClient *http.Client // used to interact with the log via HTTP + verifier *ct.SignatureVerifier // nil if no public key for log available } ////////////////////////////////////////////////////////////////////////////////// @@ -43,7 +47,7 @@ type LogClient struct { // addChainRequest represents the JSON request body sent to the add-chain CT // method. type addChainRequest struct { - Chain []string `json:"chain"` + Chain [][]byte `json:"chain"` } // addChainResponse represents the JSON response to the add-chain CT method. @@ -51,13 +55,13 @@ type addChainRequest struct { // log within a defined period of time. type addChainResponse struct { SCTVersion ct.Version `json:"sct_version"` // SCT structure version - ID string `json:"id"` // Log ID + ID []byte `json:"id"` // Log ID Timestamp uint64 `json:"timestamp"` // Timestamp of issuance Extensions string `json:"extensions"` // Holder for any CT extensions - Signature string `json:"signature"` // Log signature for this SCT + Signature []byte `json:"signature"` // Log signature for this SCT } -// addJSONRequest represents the JSON request body sent ot the add-json CT +// addJSONRequest represents the JSON request body sent to the add-json CT // method. type addJSONRequest struct { Data interface{} `json:"data"` @@ -67,24 +71,13 @@ type addJSONRequest struct { type getSTHResponse struct { TreeSize uint64 `json:"tree_size"` // Number of certs in the current tree Timestamp uint64 `json:"timestamp"` // Time that the tree was created - SHA256RootHash string `json:"sha256_root_hash"` // Root hash of the tree - TreeHeadSignature string `json:"tree_head_signature"` // Log signature for this STH + SHA256RootHash []byte `json:"sha256_root_hash"` // Root hash of the tree + TreeHeadSignature []byte `json:"tree_head_signature"` // Log signature for this STH } -// base64LeafEntry respresents a Base64 encoded leaf entry -type base64LeafEntry struct { - LeafInput string `json:"leaf_input"` - ExtraData string `json:"extra_data"` -} - -// getEntriesReponse respresents the JSON response to the CT get-entries method -type getEntriesResponse struct { - Entries []base64LeafEntry `json:"entries"` // the list of returned entries -} - -// getConsistencyProofResponse represents the JSON response to the CT get-consistency-proof method +// getConsistencyProofResponse represents the JSON response to the get-consistency-proof CT method type getConsistencyProofResponse struct { - Consistency []string `json:"consistency"` + Consistency [][]byte `json:"consistency"` } // getAuditProofResponse represents the JSON response to the CT get-audit-proof method @@ -105,6 +98,12 @@ type getEntryAndProofResponse struct { AuditPath []string `json:"audit_path"` // the corresponding proof } +// GetProofByHashResponse represents the JSON response to the CT get-proof-by-hash method. +type GetProofByHashResponse struct { + LeafIndex int64 `json:"leaf_index"` // The 0-based index of the end entity corresponding to the "hash" parameter. + AuditPath [][]byte `json:"audit_path"` // An array of base64-encoded Merkle Tree nodes proving the inclusion of the chosen certificate. +} + // 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 @@ -116,29 +115,56 @@ func New(uri string, hc *http.Client) *LogClient { return &LogClient{uri: uri, httpClient: hc} } -// Makes a HTTP call to |uri|, and attempts to parse the response as a JSON -// representation of the structure in |res|. +// NewWithPubKey constructs a new LogClient instance that includes public +// key information for the log; this instance will check signatures on +// responses from the log. +func NewWithPubKey(uri string, hc *http.Client, pemEncodedKey string) (*LogClient, error) { + pubkey, _, rest, err := ct.PublicKeyFromPEM([]byte(pemEncodedKey)) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, errors.New("extra data found after PEM key decoded") + } + + verifier, err := ct.NewSignatureVerifier(pubkey) + if err != nil { + return nil, err + } + + if hc == nil { + hc = new(http.Client) + } + return &LogClient{uri: uri, httpClient: hc, verifier: verifier}, nil +} + +// Makes a HTTP call to |uri|, and attempts to parse the response as a +// JSON representation of the structure in |res|. Uses |ctx| to +// control the HTTP call (so it can have a timeout or be cancelled by +// the caller), and |httpClient| to make the actual HTTP call. // Returns a non-nil |error| if there was a problem. -func (c *LogClient) fetchAndParse(uri string, res interface{}) error { - req, err := http.NewRequest("GET", uri, nil) +func fetchAndParse(ctx context.Context, httpClient *http.Client, uri string, res interface{}) error { + req, err := http.NewRequest(http.MethodGet, uri, nil) if err != nil { return err } - resp, err := c.httpClient.Do(req) - var body []byte - if resp != nil { - body, err = ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - return err - } - } + req.Cancel = ctx.Done() + resp, err := httpClient.Do(req) if err != nil { return err } - if err = json.Unmarshal(body, &res); err != nil { + defer resp.Body.Close() + // Make sure everything is read, so http.Client can reuse the connection. + defer ioutil.ReadAll(resp.Body) + + if resp.StatusCode != 200 { + return fmt.Errorf("got HTTP Status %s", resp.Status) + } + + if err := json.NewDecoder(resp.Body).Decode(res); err != nil { return err } + return nil } @@ -150,7 +176,7 @@ func (c *LogClient) postAndParse(uri string, req interface{}, res interface{}) ( if err != nil { return nil, "", err } - httpReq, err := http.NewRequest("POST", uri, bytes.NewReader(postBody)) + httpReq, err := http.NewRequest(http.MethodPost, uri, bytes.NewReader(postBody)) if err != nil { return nil, "", err } @@ -194,11 +220,11 @@ func backoffForRetry(ctx context.Context, d time.Duration) error { // 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) { +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, base64.StdEncoding.EncodeToString(link)) + req.Chain = append(req.Chain, link) } httpStatus := "Unknown" backoffSeconds := 0 @@ -214,7 +240,7 @@ func (c *LogClient) addChainWithRetry(ctx context.Context, path string, chain [] if backoffSeconds > 0 { backoffSeconds = 0 } - httpResp, errorBody, err := c.postAndParse(c.uri+path, &req, &resp) + httpResp, _, err := c.postAndParse(c.uri+path, &req, &resp) if err != nil { backoffSeconds = 10 continue @@ -233,49 +259,48 @@ func (c *LogClient) addChainWithRetry(ctx context.Context, path string, chain [] } } default: - return nil, fmt.Errorf("got HTTP Status %s: %s", httpResp.Status, errorBody) + return nil, fmt.Errorf("got HTTP Status %s", httpResp.Status) } httpStatus = httpResp.Status } - 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)) + ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature)) if err != nil { return nil, err } + var logID ct.SHA256Hash - copy(logID[:], rawLogID) - return &ct.SignedCertificateTimestamp{ + copy(logID[:], resp.ID) + sct := &ct.SignedCertificateTimestamp{ SCTVersion: resp.SCTVersion, LogID: logID, Timestamp: resp.Timestamp, Extensions: ct.CTExtensions(resp.Extensions), - Signature: *ds}, nil + Signature: *ds} + err = c.VerifySCTSignature(*sct, ctype, chain) + if err != nil { + return nil, err + } + return sct, nil } // AddChain adds the (DER represented) X509 |chain| to the log. func (c *LogClient) AddChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { - return c.addChainWithRetry(nil, AddChainPath, chain) + return c.addChainWithRetry(nil, ct.X509LogEntryType, AddChainPath, chain) } // AddPreChain adds the (DER represented) Precertificate |chain| to the log. func (c *LogClient) AddPreChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { - return c.addChainWithRetry(nil, AddPreChainPath, chain) + return c.addChainWithRetry(nil, ct.PrecertLogEntryType, AddPreChainPath, chain) } // AddChainWithContext adds the (DER represented) X509 |chain| to the log and // fails if the provided context expires before the chain is submitted. func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) { - return c.addChainWithRetry(ctx, AddChainPath, chain) + return c.addChainWithRetry(ctx, ct.X509LogEntryType, AddChainPath, chain) } +// AddJSON submits arbitrary data to to XJSON server. func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) { req := addJSONRequest{ Data: data, @@ -285,20 +310,12 @@ func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, e 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)) + ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.Signature)) if err != nil { return nil, err } var logID ct.SHA256Hash - copy(logID[:], rawLogID) + copy(logID[:], resp.ID) return &ct.SignedCertificateTimestamp{ SCTVersion: resp.SCTVersion, LogID: logID, @@ -311,7 +328,7 @@ func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, e // 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 { + if err = fetchAndParse(context.TODO(), c.httpClient, c.uri+GetSTHPath, &resp); err != nil { return } sth = &ct.SignedTreeHead{ @@ -319,69 +336,77 @@ func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) { 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(resp.SHA256RootHash) != sha256.Size { + return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(resp.SHA256RootHash)) } - 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) + copy(sth.SHA256RootHash[:], resp.SHA256RootHash) - 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)) + ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(resp.TreeHeadSignature)) if err != nil { return nil, err } - // TODO(alcutter): Verify signature sth.TreeHeadSignature = *ds + err = c.VerifySTHSignature(*sth) + if err != nil { + return nil, err + } return } -// GetEntries attempts to retrieve the entries in the sequence [|start|, |end|] from the CT -// log server. (see section 4.6.) -// Returns a slice of LeafInputs or a non-nil error. -func (c *LogClient) GetEntries(start, end int64) ([]ct.LogEntry, error) { - if end < 0 { - return nil, errors.New("end should be >= 0") +// VerifySTHSignature checks the signature in sth, returning any error encountered or nil if verification is +// successful. +func (c *LogClient) VerifySTHSignature(sth ct.SignedTreeHead) error { + if c.verifier == nil { + // Can't verify signatures without a verifier + return nil } - if end < start { - return nil, errors.New("start should be <= end") + return c.verifier.VerifySTHSignature(sth) +} + +// VerifySCTSignature checks the signature in sct for the given LogEntryType, with associated certificate chain. +func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype ct.LogEntryType, certData []ct.ASN1Cert) error { + if c.verifier == nil { + // Can't verify signatures without a verifier + return nil } - var resp getEntriesResponse - err := c.fetchAndParse(fmt.Sprintf("%s%s?start=%d&end=%d", c.uri, GetEntriesPath, start, end), &resp) - if err != nil { + + if ctype == ct.PrecertLogEntryType { + // TODO(drysdale): cope with pre-certs, which need to have the + // following fields set: + // leaf.PrecertEntry.TBSCertificate + // leaf.PrecertEntry.IssuerKeyHash (SHA-256 of issuer's public key) + return errors.New("SCT verification for pre-certificates unimplemented") + } + // Build enough of a Merkle tree leaf for the verifier to work on. + leaf := ct.MerkleTreeLeaf{ + Version: sct.SCTVersion, + LeafType: ct.TimestampedEntryLeafType, + TimestampedEntry: ct.TimestampedEntry{ + Timestamp: sct.Timestamp, + EntryType: ctype, + X509Entry: certData[0], + Extensions: sct.Extensions}} + entry := ct.LogEntry{Leaf: leaf} + return c.verifier.VerifySCTSignature(sct, entry) +} + +// GetSTHConsistency retrieves the consistency proof between two snapshots. +func (c *LogClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) { + u := fmt.Sprintf("%s%s?first=%d&second=%d", c.uri, GetSTHConsistencyPath, first, second) + var resp getConsistencyProofResponse + if err := fetchAndParse(ctx, c.httpClient, u, &resp); err != nil { return nil, err } - 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 - } - entries[index].Leaf = *leaf - chainBytes, err := base64.StdEncoding.DecodeString(entry.ExtraData) - - var chain []ct.ASN1Cert - switch leaf.TimestampedEntry.EntryType { - case ct.X509LogEntryType: - chain, err = ct.UnmarshalX509ChainArray(chainBytes) - - case ct.PrecertLogEntryType: - chain, err = ct.UnmarshalPrecertChainArray(chainBytes) - - default: - return nil, fmt.Errorf("saw unknown entry type: %v", leaf.TimestampedEntry.EntryType) - } - if err != nil { - return nil, err - } - entries[index].Chain = chain - entries[index].Index = start + int64(index) - } - return entries, nil + return resp.Consistency, nil +} + +// GetProofByHash returns an audit path for the hash of an SCT. +func (c *LogClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*GetProofByHashResponse, error) { + b64Hash := url.QueryEscape(base64.StdEncoding.EncodeToString(hash)) + u := fmt.Sprintf("%s%s?tree_size=%d&hash=%v", c.uri, GetProofByHashPath, treeSize, b64Hash) + var resp GetProofByHashResponse + if err := fetchAndParse(ctx, c.httpClient, u, &resp); err != nil { + return nil, err + } + return &resp, nil } diff --git a/vendor/src/github.com/google/certificate-transparency/go/serialization.go b/vendor/src/github.com/google/certificate-transparency/go/serialization.go index fd59040c6f..b83c901178 100644 --- a/vendor/src/github.com/google/certificate-transparency/go/serialization.go +++ b/vendor/src/github.com/google/certificate-transparency/go/serialization.go @@ -6,9 +6,11 @@ import ( "crypto" "encoding/asn1" "encoding/binary" + "encoding/json" "errors" "fmt" "io" + "strings" ) // Variable size structure prefix-header byte lengths @@ -18,6 +20,7 @@ const ( ExtensionsLengthBytes = 2 CertificateChainLengthBytes = 3 SignatureLengthBytes = 2 + JSONLengthBytes = 3 ) // Max lengths @@ -144,6 +147,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) } @@ -151,6 +158,41 @@ func ReadTimestampedEntryInto(r io.Reader, t *TimestampedEntry) error { return nil } +// SerializeTimestampedEntry writes timestamped entry to Writer. +// In case of error, w may contain garbage. +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: + // TODO: Pending google/certificate-transparency#1243, replace + // with ObjectHash once supported by CT server. + //jsonhash := objecthash.CommonJSONHash(string(t.JSONData)) + if err := writeVarBytes(w, []byte(t.JSONData), 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. @@ -299,6 +341,29 @@ func serializeV1CertSCTSignatureInput(timestamp uint64, cert ASN1Cert, ext CTExt return buf.Bytes(), nil } +func serializeV1JSONSCTSignatureInput(timestamp uint64, j []byte) ([]byte, error) { + var buf bytes.Buffer + if err := binary.Write(&buf, binary.BigEndian, V1); err != nil { + return nil, err + } + if err := binary.Write(&buf, binary.BigEndian, CertificateTimestampSignatureType); err != nil { + return nil, err + } + if err := binary.Write(&buf, binary.BigEndian, timestamp); err != nil { + return nil, err + } + if err := binary.Write(&buf, binary.BigEndian, XJSONLogEntryType); err != nil { + return nil, err + } + if err := writeVarBytes(&buf, j, JSONLengthBytes); err != nil { + return nil, err + } + if err := writeVarBytes(&buf, nil, ExtensionsLengthBytes); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + func serializeV1PrecertSCTSignatureInput(timestamp uint64, issuerKeyHash [issuerKeyHashLength]byte, tbs []byte, ext CTExtensions) ([]byte, error) { if err := checkCertificateFormat(tbs); err != nil { return nil, err @@ -345,6 +410,8 @@ func serializeV1SCTSignatureInput(sct SignedCertificateTimestamp, entry LogEntry return serializeV1PrecertSCTSignatureInput(sct.Timestamp, entry.Leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash, entry.Leaf.TimestampedEntry.PrecertEntry.TBSCertificate, entry.Leaf.TimestampedEntry.Extensions) + case XJSONLogEntryType: + return serializeV1JSONSCTSignatureInput(sct.Timestamp, entry.Leaf.TimestampedEntry.JSONData) default: return nil, fmt.Errorf("unknown TimestampedEntryLeafType %s", entry.Leaf.TimestampedEntry.EntryType) } @@ -459,6 +526,7 @@ func deserializeSCTV1(r io.Reader, sct *SignedCertificateTimestamp) error { return nil } +// DeserializeSCT reads an SCT from Reader. func DeserializeSCT(r io.Reader) (*SignedCertificateTimestamp, error) { var sct SignedCertificateTimestamp if err := binary.Read(r, binary.BigEndian, &sct.SCTVersion); err != nil { @@ -561,3 +629,63 @@ func SerializeSCTList(scts []SignedCertificateTimestamp) ([]byte, error) { } return asn1.Marshal(buf.Bytes()) // transform to Octet String } + +// SerializeMerkleTreeLeaf writes MerkleTreeLeaf to Writer. +// In case of error, w may contain garbage. +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 +} + +// CreateX509MerkleTreeLeaf generates a MerkleTreeLeaf for an X509 cert +func CreateX509MerkleTreeLeaf(cert ASN1Cert, timestamp uint64) *MerkleTreeLeaf { + return &MerkleTreeLeaf{ + Version: V1, + LeafType: TimestampedEntryLeafType, + TimestampedEntry: TimestampedEntry{ + Timestamp: timestamp, + EntryType: X509LogEntryType, + X509Entry: cert, + }, + } +} + +// CreateJSONMerkleTreeLeaf creates the merkle tree leaf for json data. +func CreateJSONMerkleTreeLeaf(data interface{}, timestamp uint64) *MerkleTreeLeaf { + jsonData, err := json.Marshal(AddJSONRequest{Data: data}) + if err != nil { + return nil + } + // Match the JSON serialization implemented by json-c + jsonStr := strings.Replace(string(jsonData), ":", ": ", -1) + jsonStr = strings.Replace(jsonStr, ",", ", ", -1) + jsonStr = strings.Replace(jsonStr, "{", "{ ", -1) + jsonStr = strings.Replace(jsonStr, "}", " }", -1) + jsonStr = strings.Replace(jsonStr, "/", `\/`, -1) + // TODO: Pending google/certificate-transparency#1243, replace with + // ObjectHash once supported by CT server. + + return &MerkleTreeLeaf{ + Version: V1, + LeafType: TimestampedEntryLeafType, + TimestampedEntry: TimestampedEntry{ + Timestamp: timestamp, + EntryType: XJSONLogEntryType, + JSONData: []byte(jsonStr), + }, + } +} diff --git a/vendor/src/github.com/google/certificate-transparency/go/types.go b/vendor/src/github.com/google/certificate-transparency/go/types.go index 8a63e98e60..507e624a1d 100644 --- a/vendor/src/github.com/google/certificate-transparency/go/types.go +++ b/vendor/src/github.com/google/certificate-transparency/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 @@ -238,6 +241,7 @@ type LogEntry struct { Leaf MerkleTreeLeaf X509Cert *x509.Certificate Precert *Precertificate + JSONData []byte Chain []ASN1Cert } @@ -313,6 +317,7 @@ type TimestampedEntry struct { Timestamp uint64 EntryType LogEntryType X509Entry ASN1Cert + JSONData []byte PrecertEntry PreCert Extensions CTExtensions } @@ -361,3 +366,9 @@ func (e sctError) Error() string { return "unknown error" } } + +// AddJSONRequest represents the JSON request body sent ot the add-json CT +// method. +type AddJSONRequest struct { + Data interface{} `json:"data"` +} diff --git a/vendor/src/github.com/google/certificate-transparency/go/x509/root_unix.go b/vendor/src/github.com/google/certificate-transparency/go/x509/root_unix.go index 324f855b13..a5bd19e821 100755 --- a/vendor/src/github.com/google/certificate-transparency/go/x509/root_unix.go +++ b/vendor/src/github.com/google/certificate-transparency/go/x509/root_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build dragonfly freebsd linux openbsd netbsd +// +build dragonfly freebsd linux openbsd netbsd solaris package x509