11package main
22
33import (
4+ "bytes"
45 "context"
56 "crypto/x509"
67 "encoding/base64"
8+ "encoding/hex"
79 "encoding/pem"
810 "errors"
911 "flag"
@@ -16,21 +18,30 @@ import (
1618
1719 "github.com/transparency-dev/formats/log"
1820 tdnote "github.com/transparency-dev/formats/note"
21+ "github.com/transparency-dev/merkle/proof"
22+ "github.com/transparency-dev/merkle/rfc6962"
1923 "github.com/transparency-dev/tessera/api/layout"
24+ "github.com/transparency-dev/tessera/ctonly"
2025 "github.com/transparency-dev/tesseract/internal/client"
2126 "github.com/transparency-dev/tesseract/internal/types/staticct"
27+ "github.com/transparency-dev/tesseract/internal/x509util"
2228 "golang.org/x/mod/sumdb/note"
2329 "k8s.io/klog/v2"
2430)
2531
2632var (
27- monitoringURL = flag .String ("monitoring_url" , "" , "Base tlog-tiles URL" )
28- leafIndex = flag .String ("leaf_index" , "" , "The index of the leaf to fetch" )
29- origin = flag .String ("origin" , os .Getenv ("CT_LOG_ORIGIN" ), "Origin of the log, for checkpoints and the monitoring prefix. This is defaulted to the environment variable CT_LOG_ORIGIN" )
30- logPubKey = flag .String ("log_public_key" , os .Getenv ("CT_LOG_PUBLIC_KEY" ), "Public key for the log. This is defaulted to the environment variable CT_LOG_PUBLIC_KEY" )
33+ monitoringURL = flag .String ("monitoring_url" , "" , "Log monitoring URL." )
34+ leafIndex = flag .String ("leaf_index" , "" , "The index of the leaf to fetch." )
35+ origin = flag .String ("origin" , "" , "Origin of the log, for checkpoints and the monitoring prefix." )
36+ logPubKey = flag .String ("log_public_key" , "" , "Public key for the log, base64 encoded." )
37+ )
38+
39+ var (
40+ hasher = rfc6962 .DefaultHasher
3141)
3242
3343func main () {
44+ // Verify Flags
3445 klog .InitFlags (nil )
3546 flag .Parse ()
3647
@@ -49,16 +60,18 @@ func main() {
4960 if err != nil {
5061 klog .Exitf ("Invalid --monitoring_url %q: %v" , * monitoringURL , err )
5162 }
63+
64+ // Create client
5265 hc := & http.Client {
5366 Timeout : 30 * time .Second ,
5467 }
5568 fetcher , err := client .NewHTTPFetcher (logURL , hc )
5669 if err != nil {
5770 klog .Exitf ("Failed to create HTTP fetcher: %v" , err )
5871 }
59-
6072 ctx := context .Background ()
6173
74+ // Read Checkpoint
6275 cpRaw , err := fetcher .ReadCheckpoint (ctx )
6376 if err != nil {
6477 klog .Exitf ("Failed to fetch checkpoint: %v" , err )
@@ -76,6 +89,7 @@ func main() {
7689 klog .Exitf ("Leaf index %d is out of range for log size %d" , li , cp .Size )
7790 }
7891
92+ // Fetch entry
7993 bundleIndex := li / uint64 (layout .EntryBundleWidth )
8094 indexInBundle := li % uint64 (layout .EntryBundleWidth )
8195
@@ -94,21 +108,106 @@ func main() {
94108 klog .Exitf ("Failed to unmarshal entry: %v" , err )
95109 }
96110
97- certBytes := entry .Certificate
98- if entry .IsPrecert {
99- // For precertificates, the `Certificate` field holds the TBSCertificate.
100- // We need to wrap this in a `Certificate` structure to be able to parse it.
101- // This is a bit of a hack, but it's what the `x509` package expects.
102- cert , err := x509 .ParseCertificate (entry .Precertificate )
111+ // Check that the entry has been built properly
112+ e := ctonly.Entry {
113+ Timestamp : entry .Timestamp ,
114+ IsPrecert : entry .IsPrecert ,
115+ Certificate : entry .Certificate ,
116+ Precertificate : entry .Precertificate ,
117+ IssuerKeyHash : entry .IssuerKeyHash ,
118+ FingerprintsChain : entry .FingerprintsChain ,
119+ }
120+ var chain []* x509.Certificate
121+ if e .IsPrecert {
122+ cert , err := x509 .ParseCertificate (e .Precertificate )
123+ if err != nil {
124+ klog .Exitf ("Failed to parse precertificate: %v" , err )
125+ }
126+ chain = append (chain , cert )
127+ } else {
128+ cert , err := x509 .ParseCertificate (e .Certificate )
103129 if err != nil {
104130 klog .Exitf ("Failed to parse precertificate: %v" , err )
105131 }
106- certBytes = cert .Raw
132+ chain = append (chain , cert )
133+ }
134+ for i , hash := range entry .FingerprintsChain {
135+ iss , err := fetcher .ReadIssuer (ctx , hash [:])
136+ if err != nil {
137+ klog .Exitf ("Failed to fetch issuer number %d: %v" , i , err )
138+ }
139+ cert , err := x509 .ParseCertificate (iss )
140+ if err != nil {
141+ klog .Exitf ("Failed ot parse issuer number %d: %v" , i , err )
142+ }
143+ chain = append (chain , cert )
144+ }
145+ ee , err := x509util .EntryFromChain (chain , entry .IsPrecert , entry .Timestamp )
146+ if err != nil {
147+ klog .Exitf ("Failed to reconstruct entry from the leaf and issuers: %v" , err )
148+ }
149+
150+ var errs []error
151+ if e .Timestamp != ee .Timestamp {
152+ errs = append (errs , fmt .Errorf ("timestamp don't match: %d, %d" , e .Timestamp , ee .Timestamp ))
153+ }
154+ if e .IsPrecert != ee .IsPrecert {
155+ errs = append (errs , fmt .Errorf ("IsPrecert don't match: %t, %t" , e .IsPrecert , ee .IsPrecert ))
156+ }
157+ if ! bytes .Equal (e .Certificate , ee .Certificate ) {
158+ if e .IsPrecert {
159+ errs = append (errs , fmt .Errorf ("TBSCertificates don't match" ))
160+ } else {
161+ errs = append (errs , fmt .Errorf ("certificates don't match" ))
162+ }
163+ }
164+ if ! bytes .Equal (e .Precertificate , ee .Precertificate ) {
165+ errs = append (errs , fmt .Errorf ("precertificates don't match" ))
166+ }
167+ if ! bytes .Equal (e .IssuerKeyHash , ee .IssuerKeyHash ) {
168+ errs = append (errs , fmt .Errorf ("IssuerKeyHashes don't match, got %q, want %q" , hex .EncodeToString (e .IssuerKeyHash ), hex .EncodeToString (ee .IssuerKeyHash )))
169+ }
170+ if len (e .FingerprintsChain ) != len (ee .FingerprintsChain ) {
171+ errs = append (errs , fmt .Errorf ("lengths of fingerprints chains don't match: got %d, want %d" , len (e .FingerprintsChain ), len (ee .FingerprintsChain )))
172+ } else {
173+ for i := range e .FingerprintsChain {
174+ if ! bytes .Equal (e .FingerprintsChain [i ][:], ee .FingerprintsChain [i ][:]) {
175+ errs = append (errs , fmt .Errorf ("fingerprints %d don't match, got %q, want %q" , i , hex .EncodeToString (e .FingerprintsChain [i ][:]), hex .EncodeToString (ee .FingerprintsChain [i ][:])))
176+ }
177+ }
178+ }
179+ if len (errs ) > 0 {
180+ klog .Exitf ("Leaf entry not built properly: %v" , errors .Join (errs ... ))
181+ }
182+
183+ // TODO(phboneff): check that the chain is valid
184+ // TODO(phboneff): if this is an end cert and it has an SCT from this very log, check that SCT
185+
186+ // Build inclusion proof
187+ proofBuilder , err := client .NewProofBuilder (ctx , log.Checkpoint {
188+ Origin : * origin ,
189+ Size : cp .Size ,
190+ Hash : cp .Hash }, fetcher .ReadTile )
191+ if err != nil {
192+ klog .Exitf ("Failed to create proofBuilder: %v" , err )
193+ }
194+ mlh := e .MerkleLeafHash (entry .LeafIndex )
195+ ip , err := proofBuilder .InclusionProof (ctx , li )
196+ if err != nil {
197+ klog .Exitf ("Failed to build InclusionProof %v" , err )
198+ }
199+ if err := proof .VerifyInclusion (hasher , li , cp .Size , mlh , ip , cp .Hash ); err != nil {
200+ klog .Exitf ("Failed to verify inclusion of leaf %d in tree of size %d: %v" , li , cp .Size , err )
107201 }
108202
109203 pemBlock := & pem.Block {
110- Type : "CERTIFICATE" ,
111- Bytes : certBytes ,
204+ Type : "CERTIFICATE" ,
205+ Bytes : func () []byte {
206+ if entry .IsPrecert {
207+ return entry .Precertificate
208+ }
209+ return entry .Certificate
210+ }(),
112211 }
113212
114213 if err := pem .Encode (os .Stdout , pemBlock ); err != nil {
0 commit comments