Skip to content

Commit 5ca67e1

Browse files
committed
verify that leaves have been built properly
1 parent 0631fd9 commit 5ca67e1

File tree

1 file changed

+113
-14
lines changed

1 file changed

+113
-14
lines changed

cmd/client/main.go

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package main
22

33
import (
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

2632
var (
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

3343
func 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

Comments
 (0)