Skip to content

Commit 201c7dd

Browse files
Add mtc verify (#70)
Add mtc verify Cf #57 Co-authored-by: Luke Valenta <lvalenta@cloudflare.com>
1 parent 69f9819 commit 201c7dd

File tree

3 files changed

+150
-47
lines changed

3 files changed

+150
-47
lines changed

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ dns [example.com]
445445
ip4 [198.51.100.60]
446446
447447
proof_type merkle_tree_sha256
448-
CA OID 62253.12.15
448+
CA TAI 62253.12.15
449449
Batch number 0
450450
index 1
451451
recomputed tree head 043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345
@@ -455,6 +455,22 @@ authentication path
455455

456456
This is indeed the root of batch `0`, and so this certificate is valid.
457457

458+
### Verify certificate
459+
460+
To automate this, there is the `mtc verify` command that takes
461+
a certificate, the CA parameters, and a signed validity window.
462+
463+
```
464+
$ mtc verify -ca-params www/mtc/v04b/ca-params -validity-window www/mtc/v04b/batches/1/validity-window my-cert
465+
$ echo $?
466+
0
467+
```
468+
469+
Status code 0 means verification succeeded.
470+
471+
For transparency, you should not get the signed validity window directly
472+
from the CA, but rather from one or more mirrors (see below).
473+
458474
### Run CA as server
459475

460476
An Merkle Tree CA can be run just from the commandline, but it's often
@@ -502,7 +518,7 @@ dns [example.com]
502518
ip4 [198.51.100.60]
503519
504520
proof_type merkle_tree_sha256
505-
CA OID 62253.12.15
521+
CA TAI 62253.12.15
506522
Batch number 0
507523
index 1
508524
recomputed tree head 043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345

cmd/mtc/main.go

Lines changed: 114 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,20 @@ func writeEvidenceList(w *tabwriter.Writer, el mtc.EvidenceList) error {
886886
return nil
887887
}
888888

889+
func handleVerify(cc *cli.Context) error {
890+
return handleCert(cc, false)
891+
}
892+
889893
func handleInspectCert(cc *cli.Context) error {
894+
return handleCert(cc, true)
895+
}
896+
897+
// Handles `mtc verify' and `mtc inspect cert'
898+
func handleCert(cc *cli.Context, inspect bool) error {
899+
if !inspect && !cc.IsSet("validity-window") {
900+
return errors.New("-validity-window must be set")
901+
}
902+
890903
buf, err := inspectGetBuf(cc)
891904
if err != nil {
892905
return err
@@ -905,56 +918,95 @@ func handleInspectCert(cc *cli.Context) error {
905918
return err
906919
}
907920

908-
w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
909-
writeAssertion(w, c.Assertion)
910-
fmt.Fprintf(w, "\n")
911921
tai := c.Proof.TrustAnchorIdentifier()
912-
fmt.Fprintf(w, "proof_type\t%v\n", tai.ProofType(&caStore))
922+
if !tai.Issuer.Equal(&params.Issuer) {
923+
return fmt.Errorf(
924+
"Issuer in certificate (%s) does not match provided CA (%s)",
925+
tai.Issuer,
926+
params.Issuer,
927+
)
928+
}
929+
930+
var (
931+
vw *mtc.SignedValidityWindow
932+
verifyResult error
933+
)
934+
if cc.IsSet("validity-window") {
935+
vwPath := cc.String("validity-window")
936+
vwBuf, err := os.ReadFile(vwPath)
937+
if err != nil {
938+
return fmt.Errorf("Reading %s: %w", vwPath, err)
939+
}
913940

914-
fmt.Fprintf(w, "CA OID\t%s\n", tai.Issuer)
915-
fmt.Fprintf(w, "Batch number\t%d\n", tai.BatchNumber)
941+
vw = new(mtc.SignedValidityWindow)
942+
if err := vw.UnmarshalBinary(vwBuf, params); err != nil {
943+
return fmt.Errorf("Parsing %s: %w", vwPath, err)
944+
}
916945

917-
switch proof := c.Proof.(type) {
918-
case *mtc.MerkleTreeProof:
919-
fmt.Fprintf(w, "index\t%d\n", proof.Index())
946+
verifyResult = c.Verify(mtc.VerifyOptions{
947+
ValidityWindow: &vw.ValidityWindow,
948+
CA: params,
949+
})
920950
}
921951

922-
switch proof := c.Proof.(type) {
923-
case *mtc.MerkleTreeProof:
924-
path := proof.Path()
925-
batch := &mtc.Batch{
926-
CA: params,
927-
Number: tai.BatchNumber,
952+
if inspect {
953+
w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
954+
writeAssertion(w, c.Assertion)
955+
fmt.Fprintf(w, "\n")
956+
957+
fmt.Fprintf(w, "proof_type\t%v\n", params.ProofType)
958+
fmt.Fprintf(w, "CA TAI\t%s\n", tai.Issuer)
959+
fmt.Fprintf(w, "Batch number\t%d\n", tai.BatchNumber)
960+
961+
if vw != nil {
962+
vrs := "✅"
963+
if verifyResult != nil {
964+
vrs = verifyResult.Error()
965+
}
966+
967+
fmt.Fprintf(w, "Verification result\t%s\n", vrs)
928968
}
929969

930-
if !tai.Issuer.Equal(&params.Issuer) {
931-
return fmt.Errorf(
932-
"IssuerId doesn't match: %s ≠ %s",
933-
params.Issuer,
934-
tai.Issuer,
970+
switch proof := c.Proof.(type) {
971+
case *mtc.MerkleTreeProof:
972+
fmt.Fprintf(w, "index\t%d\n", proof.Index())
973+
path := proof.Path()
974+
batch := &mtc.Batch{
975+
CA: params,
976+
Number: tai.BatchNumber,
977+
}
978+
979+
if !tai.Issuer.Equal(&params.Issuer) {
980+
return fmt.Errorf(
981+
"IssuerId doesn't match: %s ≠ %s",
982+
params.Issuer,
983+
tai.Issuer,
984+
)
985+
}
986+
be := mtc.NewBatchEntry(c.Assertion, proof.NotAfter())
987+
head, err := batch.ComputeTreeHeadFromAuthenticationPath(
988+
proof.Index(),
989+
path,
990+
&be,
935991
)
936-
}
937-
be := mtc.NewBatchEntry(c.Assertion, proof.NotAfter())
938-
head, err := batch.ComputeTreeHeadFromAuthenticationPath(
939-
proof.Index(),
940-
path,
941-
&be,
942-
)
943-
if err != nil {
944-
return fmt.Errorf("computing tree head: %w", err)
945-
}
992+
if err != nil {
993+
return fmt.Errorf("computing tree head: %w", err)
994+
}
946995

947-
fmt.Fprintf(w, "recomputed tree head\t%x\n", head)
996+
fmt.Fprintf(w, "recomputed tree head\t%x\n", head)
948997

949-
w.Flush()
950-
fmt.Printf("authentication path\n")
951-
for i := 0; i < len(path)/mtc.HashLen; i++ {
952-
fmt.Printf(" %x\n", path[i*mtc.HashLen:(i+1)*mtc.HashLen])
998+
w.Flush()
999+
fmt.Printf("authentication path\n")
1000+
for i := 0; i < len(path)/mtc.HashLen; i++ {
1001+
fmt.Printf(" %x\n", path[i*mtc.HashLen:(i+1)*mtc.HashLen])
1002+
}
9531003
}
1004+
1005+
w.Flush()
1006+
return nil
9541007
}
9551008

956-
w.Flush()
957-
return nil
1009+
return verifyResult
9581010
}
9591011

9601012
func handleInspectAssertionRequest(cc *cli.Context) error {
@@ -1322,6 +1374,13 @@ func main() {
13221374
Usage: "parses a certificate",
13231375
Action: handleInspectCert,
13241376
ArgsUsage: "[path]",
1377+
Flags: []cli.Flag{
1378+
&cli.StringFlag{
1379+
Name: "validity-window",
1380+
Usage: "path to signed validity window to verify against",
1381+
Aliases: []string{"w"},
1382+
},
1383+
},
13251384
},
13261385
{
13271386
Name: "umbilical-certificates",
@@ -1358,6 +1417,24 @@ func main() {
13581417
},
13591418
),
13601419
},
1420+
{
1421+
Name: "verify",
1422+
Usage: "verifies a merkle tree certificate",
1423+
Action: handleVerify,
1424+
ArgsUsage: "[path]",
1425+
Flags: []cli.Flag{
1426+
&cli.StringFlag{
1427+
Name: "ca-params",
1428+
Usage: "path to CA parameters",
1429+
Aliases: []string{"p"},
1430+
},
1431+
&cli.StringFlag{
1432+
Name: "validity-window",
1433+
Usage: "path to trusted signed validity window",
1434+
Aliases: []string{"w"},
1435+
},
1436+
},
1437+
},
13611438
},
13621439
Before: func(cc *cli.Context) error {
13631440
if path := cc.String("cpuprofile"); path != "" {

mtc.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,13 @@ func (c *BikeshedCertificate) UnmarshalBinary(data []byte, caStore CAStore) erro
429429
if notAfter >= 1<<63 {
430430
return errors.New("timestamp too large")
431431
}
432-
switch tai.ProofType(caStore) {
432+
433+
params := caStore.Lookup(tai.Issuer)
434+
if params == nil {
435+
return fmt.Errorf("unknown CA with TAI %s", tai)
436+
}
437+
438+
switch params.ProofType {
433439
case MerkleTreeProofType:
434440
proof := &MerkleTreeProof{
435441
notAfter: time.Unix(int64(notAfter), 0),
@@ -1965,15 +1971,19 @@ func NewMerkleTreeProof(batch *Batch, index uint64, notAfter time.Time,
19651971
}
19661972

19671973
type CAStore interface {
1968-
Lookup(oid RelativeOID) CAParams
1974+
Lookup(oid RelativeOID) *CAParams
19691975
}
19701976

19711977
type LocalCAStore struct {
19721978
store map[string]CAParams
19731979
}
19741980

1975-
func (s *LocalCAStore) Lookup(oid RelativeOID) CAParams {
1976-
return s.store[oid.String()]
1981+
func (s *LocalCAStore) Lookup(oid RelativeOID) *CAParams {
1982+
ret, ok := s.store[oid.String()]
1983+
if !ok {
1984+
return nil
1985+
}
1986+
return &ret
19771987
}
19781988

19791989
func (s *LocalCAStore) Add(params CAParams) {
@@ -1994,10 +2004,6 @@ type TrustAnchorIdentifier struct {
19942004

19952005
type RelativeOID []byte
19962006

1997-
func (tai *TrustAnchorIdentifier) ProofType(store CAStore) ProofType {
1998-
return store.Lookup(tai.Issuer).ProofType
1999-
}
2000-
20012007
func (oid RelativeOID) segments() []uint32 {
20022008
var res []uint32
20032009
cur := uint32(0)
@@ -2127,6 +2133,10 @@ func (tai TrustAnchorIdentifier) MarshalBinary() ([]byte, error) {
21272133
return b.Bytes()
21282134
}
21292135

2136+
func (tai TrustAnchorIdentifier) String() string {
2137+
return fmt.Sprintf("%s.%d", tai.Issuer, tai.BatchNumber)
2138+
}
2139+
21302140
func (tai *TrustAnchorIdentifier) unmarshal(s *cryptobyte.String) error {
21312141
var oidBytes []byte
21322142
if !copyUint8LengthPrefixed(s, &oidBytes) || len(oidBytes) == 0 {

0 commit comments

Comments
 (0)