Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ dns [example.com]
ip4 [198.51.100.60]

proof_type merkle_tree_sha256
CA OID 62253.12.15
CA TAI 62253.12.15
Batch number 0
index 1
recomputed tree head 043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345
Expand All @@ -429,6 +429,22 @@ authentication path

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

### Verify certificate

To automate this, there is the `mtc verify` command that takes
a certificate, the CA parameters, and a signed validity window.

```
$ mtc verify -ca-params www/mtc/v04b/ca-params -validity-window www/mtc/v04b/batches/1/validity-window my-cert
$ echo $?
0
```

Status code 0 means verification succeeded.

For transparency, you should not get the signed validity window directly
from the CA, but rather from one or more mirrors (see below).

### Run CA as server

An Merkle Tree CA can be run just from the commandline, but it's often
Expand Down Expand Up @@ -476,7 +492,7 @@ dns [example.com]
ip4 [198.51.100.60]

proof_type merkle_tree_sha256
CA OID 62253.12.15
CA TAI 62253.12.15
Batch number 0
index 1
recomputed tree head 043bc6b0e49a085f2370b2e0f0876d154c2e8d8fe049077dbad118a363580345
Expand Down
151 changes: 114 additions & 37 deletions cmd/mtc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,20 @@ func writeEvidenceList(w *tabwriter.Writer, el mtc.EvidenceList) error {
return nil
}

func handleVerify(cc *cli.Context) error {
return handleCert(cc, false)
}

func handleInspectCert(cc *cli.Context) error {
return handleCert(cc, true)
}

// Handles `mtc verify' and `mtc inspect cert'
func handleCert(cc *cli.Context, inspect bool) error {
if !inspect && !cc.IsSet("validity-window") {
return errors.New("-validity-window must be set")
}

buf, err := inspectGetBuf(cc)
if err != nil {
return err
Expand All @@ -902,56 +915,95 @@ func handleInspectCert(cc *cli.Context) error {
return err
}

w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
writeAssertion(w, c.Assertion)
fmt.Fprintf(w, "\n")
tai := c.Proof.TrustAnchorIdentifier()
fmt.Fprintf(w, "proof_type\t%v\n", tai.ProofType(&caStore))
if !tai.Issuer.Equal(&params.Issuer) {
return fmt.Errorf(
"Issuer in certificate (%s) does not match provided CA (%s)",
tai.Issuer,
params.Issuer,
)
}

var (
vw *mtc.SignedValidityWindow
verifyResult error
)
if cc.IsSet("validity-window") {
vwPath := cc.String("validity-window")
vwBuf, err := os.ReadFile(vwPath)
if err != nil {
return fmt.Errorf("Reading %s: %w", vwPath, err)
}

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

switch proof := c.Proof.(type) {
case *mtc.MerkleTreeProof:
fmt.Fprintf(w, "index\t%d\n", proof.Index())
verifyResult = c.Verify(mtc.VerifyOptions{
ValidityWindow: &vw.ValidityWindow,
CA: params,
})
}

switch proof := c.Proof.(type) {
case *mtc.MerkleTreeProof:
path := proof.Path()
batch := &mtc.Batch{
CA: params,
Number: tai.BatchNumber,
if inspect {
w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
writeAssertion(w, c.Assertion)
fmt.Fprintf(w, "\n")

fmt.Fprintf(w, "proof_type\t%v\n", params.ProofType)
fmt.Fprintf(w, "CA TAI\t%s\n", tai.Issuer)
fmt.Fprintf(w, "Batch number\t%d\n", tai.BatchNumber)

if vw != nil {
vrs := "✅"
if verifyResult != nil {
vrs = verifyResult.Error()
}

fmt.Fprintf(w, "Verification result\t%s\n", vrs)
}

if !tai.Issuer.Equal(&params.Issuer) {
return fmt.Errorf(
"IssuerId doesn't match: %s ≠ %s",
params.Issuer,
tai.Issuer,
switch proof := c.Proof.(type) {
case *mtc.MerkleTreeProof:
fmt.Fprintf(w, "index\t%d\n", proof.Index())
path := proof.Path()
batch := &mtc.Batch{
CA: params,
Number: tai.BatchNumber,
}

if !tai.Issuer.Equal(&params.Issuer) {
return fmt.Errorf(
"IssuerId doesn't match: %s ≠ %s",
params.Issuer,
tai.Issuer,
)
}
be := mtc.NewBatchEntry(c.Assertion, proof.NotAfter())
head, err := batch.ComputeTreeHeadFromAuthenticationPath(
proof.Index(),
path,
&be,
)
}
be := mtc.NewBatchEntry(c.Assertion, proof.NotAfter())
head, err := batch.ComputeTreeHeadFromAuthenticationPath(
proof.Index(),
path,
&be,
)
if err != nil {
return fmt.Errorf("computing tree head: %w", err)
}
if err != nil {
return fmt.Errorf("computing tree head: %w", err)
}

fmt.Fprintf(w, "recomputed tree head\t%x\n", head)
fmt.Fprintf(w, "recomputed tree head\t%x\n", head)

w.Flush()
fmt.Printf("authentication path\n")
for i := 0; i < len(path)/mtc.HashLen; i++ {
fmt.Printf(" %x\n", path[i*mtc.HashLen:(i+1)*mtc.HashLen])
w.Flush()
fmt.Printf("authentication path\n")
for i := 0; i < len(path)/mtc.HashLen; i++ {
fmt.Printf(" %x\n", path[i*mtc.HashLen:(i+1)*mtc.HashLen])
}
}

w.Flush()
return nil
}

w.Flush()
return nil
return verifyResult
}

func handleInspectAssertionRequest(cc *cli.Context) error {
Expand Down Expand Up @@ -1319,6 +1371,13 @@ func main() {
Usage: "parses a certificate",
Action: handleInspectCert,
ArgsUsage: "[path]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "validity-window",
Usage: "path to signed validity window to verify against",
Aliases: []string{"w"},
},
},
},
{
Name: "umbilical-certificates",
Expand Down Expand Up @@ -1355,6 +1414,24 @@ func main() {
},
),
},
{
Name: "verify",
Usage: "verifies a merkle tree certificate",
Action: handleVerify,
ArgsUsage: "[path]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "ca-params",
Usage: "path to CA parameters",
Aliases: []string{"p"},
},
&cli.StringFlag{
Name: "validity-window",
Usage: "path to trusted signed validity window",
Aliases: []string{"w"},
},
},
},
},
Before: func(cc *cli.Context) error {
if path := cc.String("cpuprofile"); path != "" {
Expand Down
26 changes: 18 additions & 8 deletions mtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,13 @@ func (c *BikeshedCertificate) UnmarshalBinary(data []byte, caStore CAStore) erro
if notAfter >= 1<<63 {
return errors.New("timestamp too large")
}
switch tai.ProofType(caStore) {

params := caStore.Lookup(tai.Issuer)
if params == nil {
return fmt.Errorf("unknown CA with TAI %s", tai)
}

switch params.ProofType {
case MerkleTreeProofType:
proof := &MerkleTreeProof{
notAfter: time.Unix(int64(notAfter), 0),
Expand Down Expand Up @@ -1965,15 +1971,19 @@ func NewMerkleTreeProof(batch *Batch, index uint64, notAfter time.Time,
}

type CAStore interface {
Lookup(oid RelativeOID) CAParams
Lookup(oid RelativeOID) *CAParams
}

type LocalCAStore struct {
store map[string]CAParams
}

func (s *LocalCAStore) Lookup(oid RelativeOID) CAParams {
return s.store[oid.String()]
func (s *LocalCAStore) Lookup(oid RelativeOID) *CAParams {
ret, ok := s.store[oid.String()]
if !ok {
return nil
}
return &ret
}

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

type RelativeOID []byte

func (tai *TrustAnchorIdentifier) ProofType(store CAStore) ProofType {
return store.Lookup(tai.Issuer).ProofType
}

func (oid RelativeOID) segments() []uint32 {
var res []uint32
cur := uint32(0)
Expand Down Expand Up @@ -2127,6 +2133,10 @@ func (tai TrustAnchorIdentifier) MarshalBinary() ([]byte, error) {
return b.Bytes()
}

func (tai TrustAnchorIdentifier) String() string {
return fmt.Sprintf("%s.%d", tai.Issuer, tai.BatchNumber)
}

func (tai *TrustAnchorIdentifier) unmarshal(s *cryptobyte.String) error {
var oidBytes []byte
if !copyUint8LengthPrefixed(s, &oidBytes) || len(oidBytes) == 0 {
Expand Down