From 6055bf0a998fec4608f3ae96d53d9508d457b2d5 Mon Sep 17 00:00:00 2001 From: Luke Valenta Date: Fri, 21 Feb 2025 17:16:28 -0500 Subject: [PATCH 1/2] Add support for evidence - Rename QueuedAssertion to AssertionRequest, add Evidence field, and move from 'ca' package to 'mtc' package to be able to use private 'unmarshal' function. - Update /ca/queue to accept an AssertionRequest instead of an Assertion - Update index to include offset in evidence file. - Add 'mtc inspect evidence' option --- ca/ca.go | 148 ++++++++++++++++----------------------- ca/index.go | 39 +++++++---- cmd/mtc/main.go | 133 +++++++++++++++++++++++++---------- go.mod | 2 +- go.sum | 8 +++ http/server.go | 37 +++------- mtc.go | 179 +++++++++++++++++++++++++++++++++++++++++++++++- utils.go | 4 ++ 8 files changed, 383 insertions(+), 167 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index 7df4d1f..a4183a4 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -21,11 +21,8 @@ import ( "golang.org/x/crypto/cryptobyte" ) -const csLen = 32 - var ( - ErrChecksumInvalid = errors.New("Invalid checksum") - ErrClosed = errors.New("Handle is closed") + ErrClosed = errors.New("Handle is closed") ) type NewOpts struct { @@ -55,71 +52,6 @@ type Handle struct { batchNumbersCache []uint32 // cache for existing batches } -type QueuedAssertion struct { - Checksum []byte - Assertion mtc.Assertion -} - -func (a *QueuedAssertion) UnmarshalBinary(data []byte) error { - var ( - s cryptobyte.String = cryptobyte.String(data) - checksum [csLen]byte - ) - if !s.CopyBytes(checksum[:]) { - return mtc.ErrTruncated - } - - a.Checksum = make([]byte, csLen) - copy(a.Checksum, checksum[:]) - - checksum2 := sha256.Sum256([]byte(s)) - if !bytes.Equal(checksum2[:], checksum[:]) { - return ErrChecksumInvalid - } - - if err := a.Assertion.UnmarshalBinary([]byte(s)); err != nil { - return err - } - - return nil -} - -func (a *QueuedAssertion) marshalAndCheckAssertion() ([]byte, error) { - buf, err := a.Assertion.MarshalBinary() - if err != nil { - return nil, err - } - - checksum2 := sha256.Sum256([]byte(buf)) - if a.Checksum == nil { - a.Checksum = checksum2[:] - } else if !bytes.Equal(checksum2[:], a.Checksum) { - return nil, ErrChecksumInvalid - } - - return buf, nil -} - -// If set, checks whether the Checksum is correct. If not set, sets the -// Checksum to the correct value. -func (a *QueuedAssertion) Check() error { - _, err := a.marshalAndCheckAssertion() - return err -} - -func (a *QueuedAssertion) MarshalBinary() ([]byte, error) { - var b cryptobyte.Builder - - buf, err := a.marshalAndCheckAssertion() - if err != nil { - return nil, err - } - b.AddBytes(a.Checksum) - b.AddBytes(buf) - - return b.Bytes() -} - func (ca *Handle) Params() mtc.CAParams { return ca.params } @@ -165,7 +97,7 @@ func (h *Handle) dropQueue() error { // // For each entry, if checksum is not nil, makes sure the assertion // matches the checksum -func (h *Handle) QueueMultiple(it func(yield func(qa QueuedAssertion) error) error) error { +func (h *Handle) QueueMultiple(it func(yield func(a mtc.AssertionRequest) error) error) error { if h.closed { return ErrClosed } @@ -177,8 +109,8 @@ func (h *Handle) QueueMultiple(it func(yield func(qa QueuedAssertion) error) err defer w.Close() bw := bufio.NewWriter(w) - if err := it(func(qa QueuedAssertion) error { - buf, err := qa.MarshalBinary() + if err := it(func(a mtc.AssertionRequest) error { + buf, err := a.MarshalBinary() if err != nil { return err } @@ -208,14 +140,9 @@ func (h *Handle) QueueMultiple(it func(yield func(qa QueuedAssertion) error) err // Queue assertion for publication. // // If checksum is not nil, makes sure assertion matches the checksum. -func (h *Handle) Queue(a mtc.Assertion, checksum []byte) error { - return h.QueueMultiple(func(yield func(qa QueuedAssertion) error) error { - return yield( - QueuedAssertion{ - Checksum: checksum, - Assertion: a, - }, - ) +func (h *Handle) Queue(a mtc.AssertionRequest) error { + return h.QueueMultiple(func(yield func(a mtc.AssertionRequest) error) error { + return yield(a) }) } @@ -387,7 +314,7 @@ func (h *Handle) listBatchRange() (mtc.BatchRange, error) { } // Calls f on each assertion queued to be published. -func (h *Handle) WalkQueue(f func(QueuedAssertion) error) error { +func (h *Handle) WalkQueue(f func(mtc.AssertionRequest) error) error { r, err := os.OpenFile(h.queuePath(), os.O_RDONLY, 0) if err != nil { return fmt.Errorf("Opening queue: %w", err) @@ -400,7 +327,7 @@ func (h *Handle) WalkQueue(f func(QueuedAssertion) error) error { var ( prefix [2]byte aLen uint16 - qa QueuedAssertion + a mtc.AssertionRequest ) _, err := io.ReadFull(br, prefix[:]) if err == io.EOF { @@ -418,12 +345,12 @@ func (h *Handle) WalkQueue(f func(QueuedAssertion) error) error { return fmt.Errorf("Reading queue: %w", err) } - err = qa.UnmarshalBinary(buf) + err = a.UnmarshalBinary(buf) if err != nil { return fmt.Errorf("Parsing queue: %w", err) } - err = f(qa) + err = f(a) if err != nil { return err } @@ -760,6 +687,7 @@ func (h *Handle) issueBatch(number uint32, empty bool) error { "tree", "signed-validity-window", "abridged-assertions", + "evidence", "index", }, ) @@ -884,7 +812,7 @@ func (h *Handle) issueBatchTo(dir string, batch mtc.Batch, empty bool) error { prevHeads = w.ValidityWindow.TreeHeads } - // Read queue and write abridged-assertions + // Read queue and write abridged-assertions and evidence aasPath := gopath.Join(dir, "abridged-assertions") aasW, err := os.Create(aasPath) if err != nil { @@ -893,23 +821,48 @@ func (h *Handle) issueBatchTo(dir string, batch mtc.Batch, empty bool) error { defer aasW.Close() aasBW := bufio.NewWriter(aasW) + evPath := gopath.Join(dir, "evidence") + evW, err := os.Create(evPath) + if err != nil { + return fmt.Errorf("creating %s: %w", evPath, err) + } + defer evW.Close() + evBW := bufio.NewWriter(evW) + if !empty { - err = h.WalkQueue(func(qa QueuedAssertion) error { - aa := qa.Assertion.Abridge() + err = h.WalkQueue(func(a mtc.AssertionRequest) error { + aa := a.Assertion.Abridge() buf, err := aa.MarshalBinary() if err != nil { - return fmt.Errorf("Marshalling assertion %x: %w", qa.Checksum, err) + return fmt.Errorf("Marshalling assertion %x: %w", a.Checksum, err) } _, err = aasBW.Write(buf) if err != nil { return fmt.Errorf( "Writing assertion %x to %s: %w", - qa.Checksum, + a.Checksum, aasPath, err, ) } + + ev := a.Evidence + buf, err = ev.MarshalBinary() + if err != nil { + return fmt.Errorf("Marshalling evidence %x: %w", a.Checksum, err) + } + + _, err = evBW.Write(buf) + if err != nil { + return fmt.Errorf( + "Writing evidence %x to %s: %w", + a.Checksum, + evPath, + err, + ) + } + return nil }) if err != nil { @@ -932,6 +885,21 @@ func (h *Handle) issueBatchTo(dir string, batch mtc.Batch, empty bool) error { } defer aasR.Close() + err = evBW.Flush() + if err != nil { + return fmt.Errorf("flushing %s: %w", evPath, err) + } + + err = evW.Close() + if err != nil { + return fmt.Errorf("closing %s: %w", evPath, err) + } + evR, err := os.OpenFile(evPath, os.O_RDONLY, 0) + if err != nil { + return fmt.Errorf("opening %s: %w", evPath, err) + } + defer evR.Close() + // Compute tree tree, err := batch.ComputeTree(bufio.NewReader(aasR)) if err != nil { @@ -970,7 +938,7 @@ func (h *Handle) issueBatchTo(dir string, batch mtc.Batch, empty bool) error { defer indexW.Close() - err = ComputeIndex(aasR, indexW) + err = ComputeIndex(aasR, evR, indexW) if err != nil { return fmt.Errorf("computing %s to start: %w", indexPath, err) } diff --git a/ca/index.go b/ca/index.go index 85a2aef..81964b9 100644 --- a/ca/index.go +++ b/ca/index.go @@ -1,17 +1,18 @@ package ca -// Functions to work with the batches' index file into abridged-assertions. +// Functions to work with the batches' index file into abridged-assertions and evidence. // -// The index file consists of 48 byte entries, sorted by key. +// The index file consists of 56 byte entries, sorted by key. // -// +--------------+--------------+---------------+ -// | 32-byte key | uint64 seqno | uint64 offset | -// +--------------+--------------+---------------+ +// +--------------+--------------+---------------+------------------------+ +// | 32-byte key | uint64 seqno | uint64 offset | uint64 evidence_offset | +// +--------------+--------------+---------------+------------------------+ // // Each entry corresponds to an AbridgedAssertion. The key is its key, -// the seqno is the sequence number within the abridged-assertions list, and -// the offset is the byte-offset in the abridged-assertions file. -// offset and seqno are encoded big endian. +// the seqno is the sequence number within the abridged-assertions list, +// the offset is the byte-offset in the abridged-assertions file, and +// the evidence_offset is the byte-offset in the evidence file. +// offset, evidence_offset, and seqno are encoded big endian. // // This allows quick lookups by key using interpolation search. // @@ -154,19 +155,21 @@ func (h *Index) Search(hash []byte) (*IndexSearchResult, error) { } type indexEntry struct { - key [mtc.HashLen]byte - seqno uint64 - offset uint64 + key [mtc.HashLen]byte + seqno uint64 + offset uint64 + evidenceOffset uint64 } -// Reads a stream of AbridgedAssertions from r, and writes the index to w. -func ComputeIndex(r io.Reader, w io.Writer) error { +// Reads a streams of AbridgedAssertions Evidence from aaReader and evReader, +// and writes the index to w. +func ComputeIndex(aaReader, evReader io.Reader, w io.Writer) error { // First compute keys seqno := uint64(0) entries := []indexEntry{} var key [mtc.HashLen]byte - err := mtc.UnmarshalAbridgedAssertions(r, func(offset int, + err := mtc.UnmarshalAbridgedAssertions(aaReader, func(offset int, aa *mtc.AbridgedAssertion) error { err := aa.Key(key[:]) if err != nil { @@ -181,6 +184,13 @@ func ComputeIndex(r io.Reader, w io.Writer) error { return nil }) + seqno = uint64(0) + err = mtc.UnmarshalEvidenceEntries(evReader, func(offset int, _ *mtc.Evidence) error { + entries[seqno].evidenceOffset = uint64(offset) + seqno++ + return nil + }) + if err != nil { return fmt.Errorf("computing keys: %w", err) } @@ -204,6 +214,7 @@ func ComputeIndex(r io.Reader, w io.Writer) error { b.AddBytes(entry.key[:]) b.AddUint64(entry.seqno) b.AddUint64(entry.offset) + b.AddUint64(entry.evidenceOffset) buf, _ := b.Bytes() _, err = bw.Write(buf) diff --git a/cmd/mtc/main.go b/cmd/mtc/main.go index 88b38d2..15b36eb 100644 --- a/cmd/mtc/main.go +++ b/cmd/mtc/main.go @@ -118,21 +118,21 @@ func assertionFlags(inFile bool) []cli.Flag { return ret } -func assertionFromFlags(cc *cli.Context) (*ca.QueuedAssertion, error) { - qa, err := assertionFromFlagsUnchecked(cc) +func assertionFromFlags(cc *cli.Context) (*mtc.AssertionRequest, error) { + a, err := assertionFromFlagsUnchecked(cc) if err != nil { return nil, err } - err = qa.Check() + err = a.Check() if err != nil { return nil, err } - return qa, nil + return a, nil } -func assertionFromFlagsUnchecked(cc *cli.Context) (*ca.QueuedAssertion, error) { +func assertionFromFlagsUnchecked(cc *cli.Context) (*mtc.AssertionRequest, error) { var ( checksum []byte err error @@ -184,7 +184,7 @@ func assertionFromFlagsUnchecked(cc *cli.Context) (*ca.QueuedAssertion, error) { ) } - return &ca.QueuedAssertion{ + return &mtc.AssertionRequest{ Assertion: a, Checksum: checksum, }, nil @@ -192,6 +192,7 @@ func assertionFromFlagsUnchecked(cc *cli.Context) (*ca.QueuedAssertion, error) { var ( a mtc.Assertion + e mtc.Evidence scheme mtc.SignatureScheme ) @@ -251,6 +252,9 @@ func assertionFromFlagsUnchecked(cc *cli.Context) (*ca.QueuedAssertion, error) { if err != nil { return nil, fmt.Errorf("from-x509: %s", err) } + + e.Type = mtc.X509ChainEvidenceType + e.Info = mtc.X509ChainEvidenceInfo(certs) } // Setting any claim will overwrite those suggested by the @@ -339,14 +343,15 @@ func assertionFromFlagsUnchecked(cc *cli.Context) (*ca.QueuedAssertion, error) { a.Subject = subj } - return &ca.QueuedAssertion{ + return &mtc.AssertionRequest{ Assertion: a, + Evidence: e, Checksum: checksum, }, nil } func handleCaQueue(cc *cli.Context) error { - qa, err := assertionFromFlags(cc) + a, err := assertionFromFlags(cc) if err != nil { return err } @@ -357,17 +362,17 @@ func handleCaQueue(cc *cli.Context) error { } defer h.Close() - return h.QueueMultiple(func(yield func(qa ca.QueuedAssertion) error) error { + return h.QueueMultiple(func(yield func(a mtc.AssertionRequest) error) error { for i := 0; i < cc.Int("debug-repeat"); i++ { - qa2 := *qa + a2 := *a if cc.Bool("debug-vary") { - qa2.Checksum = nil - qa2.Assertion.Claims.DNS = append( - qa2.Assertion.Claims.DNS, + a2.Checksum = nil + a2.Assertion.Claims.DNS = append( + a2.Assertion.Claims.DNS, fmt.Sprintf("%d.example.com", i), ) } - if err := yield(qa2); err != nil { + if err := yield(a2); err != nil { return err } } @@ -376,12 +381,12 @@ func handleCaQueue(cc *cli.Context) error { } func handleNewAssertion(cc *cli.Context) error { - qa, err := assertionFromFlags(cc) + a, err := assertionFromFlags(cc) if err != nil { return err } - buf, err := qa.Assertion.MarshalBinary() + buf, err := a.MarshalBinary() if err != nil { return err } @@ -390,7 +395,7 @@ func handleNewAssertion(cc *cli.Context) error { return err } - fmt.Fprintf(os.Stderr, "checksum: %x\n", qa.Checksum) + fmt.Fprintf(os.Stderr, "checksum: %x\n", a.Checksum) return nil } @@ -412,12 +417,12 @@ func handleCaCert(cc *cli.Context) error { } defer h.Close() - qa, err := assertionFromFlags(cc) + a, err := assertionFromFlags(cc) if err != nil { return err } - cert, err := h.CertificateFor(qa.Assertion) + cert, err := h.CertificateFor(a.Assertion) if err != nil { return err } @@ -443,13 +448,13 @@ func handleCaShowQueue(cc *cli.Context) error { count := 0 - err = h.WalkQueue(func(qa ca.QueuedAssertion) error { + err = h.WalkQueue(func(ar mtc.AssertionRequest) error { count++ - a := qa.Assertion + a := ar.Assertion cs := a.Claims subj := a.Subject w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - fmt.Fprintf(w, "checksum\t%x\n", qa.Checksum) + fmt.Fprintf(w, "checksum\t%x\n", ar.Checksum) fmt.Fprintf(w, "subject_type\t%s\n", subj.Type()) switch subj := subj.(type) { case *mtc.TLSSubject: @@ -469,6 +474,7 @@ func handleCaShowQueue(cc *cli.Context) error { if len(cs.IPv6) != 0 { fmt.Fprintf(w, "ip6\t%s\n", cs.IPv6) } + writeEvidence(w, ar.Evidence) w.Flush() fmt.Printf("\n") return nil @@ -614,9 +620,10 @@ func handleInspectIndex(cc *cli.Context) error { } var ( - key []byte - seqno uint64 - offset uint64 + key []byte + seqno uint64 + offset uint64 + evidenceOffset uint64 ) s := cryptobyte.String(buf) @@ -624,11 +631,11 @@ func handleInspectIndex(cc *cli.Context) error { total := 0 fmt.Printf("%64s %7s %7s\n", "key", "seqno", "offset") for !s.Empty() { - if !s.ReadBytes(&key, 32) || !s.ReadUint64(&seqno) || !s.ReadUint64(&offset) { + if !s.ReadBytes(&key, 32) || !s.ReadUint64(&seqno) || !s.ReadUint64(&offset) || !s.ReadUint64(&evidenceOffset) { return errors.New("truncated") } - fmt.Printf("%x %7d %7d\n", key, seqno, offset) + fmt.Printf("%x %7d %7d %7d\n", key, seqno, offset, evidenceOffset) total++ } @@ -681,6 +688,28 @@ func writeAssertion(w *tabwriter.Writer, a mtc.Assertion) { } } +func writeEvidence(w *tabwriter.Writer, e mtc.Evidence) { + + fmt.Fprintf(w, "evidence\t") + switch e.Type { + case mtc.EmptyEvidenceType: + fmt.Fprintf(w, "empty\n") + case mtc.X509ChainEvidenceType: + fmt.Fprintf(w, "x509_chain\n") + for i, cert := range e.Info.(mtc.X509ChainEvidenceInfo) { + fmt.Fprintf(w, " certificate\t%d\n", i) + fmt.Fprintf(w, " subject\t%s\n", cert.Subject.String()) + fmt.Fprintf(w, " issuer\t%s\n", cert.Issuer.String()) + fmt.Fprintf(w, " serial_no\t%x\n", cert.SerialNumber) + fmt.Fprintf(w, " not_before\t%s\n", cert.NotBefore) + fmt.Fprintf(w, " not_after\t%s\n", cert.NotAfter) + } + default: + fmt.Fprintf(w, "unknown\n") + fmt.Fprintf(w, " raw\t%x", e.Info.(mtc.UnknownEvidenceInfo)) + } +} + func handleInspectCert(cc *cli.Context) error { buf, err := inspectGetBuf(cc) if err != nil { @@ -752,24 +781,52 @@ func handleInspectCert(cc *cli.Context) error { return nil } -func handleInspectAssertion(cc *cli.Context) error { +func handleInspectAssertionRequest(cc *cli.Context) error { buf, err := inspectGetBuf(cc) if err != nil { return err } - var a mtc.Assertion + var a mtc.AssertionRequest err = a.UnmarshalBinary(buf) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - writeAssertion(w, a) + fmt.Fprintf(w, "checksum\t%x\n", a.Checksum) + writeAssertion(w, a.Assertion) + writeEvidence(w, a.Evidence) w.Flush() return nil } +func handleInspectEvidence(cc *cli.Context) error { + + r, err := inspectGetReader(cc) + if err != nil { + return err + } + defer r.Close() + + count := 0 + err = mtc.UnmarshalEvidenceEntries( + bufio.NewReader(r), + func(_ int, e *mtc.Evidence) error { + count++ + w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) + writeEvidence(w, *e) + w.Flush() + return nil + }, + ) + if err != nil { + return err + } + fmt.Printf("Total number of evidence entries: %d\n", count) + return nil +} + func handleInspectAbridgedAssertions(cc *cli.Context) error { r, err := inspectGetReader(cc) if err != nil { @@ -934,7 +991,7 @@ func main() { assertionFlags(true), &cli.StringFlag{ Name: "out-file", - Usage: "path to write assertion to", + Usage: "path to write cert to", Aliases: []string{"o"}, }, ), @@ -975,8 +1032,14 @@ func main() { }, { Name: "assertion", - Usage: "parses an assertion", - Action: handleInspectAssertion, + Usage: "parses an assertion request", + Action: handleInspectAssertionRequest, + ArgsUsage: "[path]", + }, + { + Name: "evidence", + Usage: "parses batch's evidence file", + Action: handleInspectEvidence, ArgsUsage: "[path]", }, { @@ -1008,13 +1071,13 @@ func main() { }, { Name: "new-assertion", - Usage: "creates a new assertion", + Usage: "creates a new assertion request", Action: handleNewAssertion, Flags: append( assertionFlags(false), &cli.StringFlag{ Name: "out-file", - Usage: "path to write assertion to", + Usage: "path to write assertion request to", Aliases: []string{"o"}, }, ), diff --git a/go.mod b/go.mod index 3503818..1419683 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( golang.org/x/exp v0.0.0-20240119083558-1b970713d09a ) -require go.etcd.io/bbolt v1.4.0 // indirect +require go.etcd.io/bbolt v1.4.0 require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect diff --git a/go.sum b/go.sum index 719a345..3d41e81 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,16 @@ github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7q github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= @@ -20,3 +26,5 @@ golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/http/server.go b/http/server.go index 87face3..fc8d976 100644 --- a/http/server.go +++ b/http/server.go @@ -2,7 +2,6 @@ package http import ( "context" - "encoding/hex" "errors" "fmt" "io" @@ -45,21 +44,11 @@ func (s *Server) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) } -func assertionFromRequestUnchecked(r *http.Request) (*ca.QueuedAssertion, error) { +func assertionFromRequestUnchecked(r *http.Request) (*mtc.AssertionRequest, error) { var ( - checksum []byte - err error + a mtc.AssertionRequest ) - - checksumParam := r.URL.Query().Get("checksum") - if checksumParam != "" { - checksum, err = hex.DecodeString(checksumParam) - if err != nil { - return nil, err - } - } - var a mtc.Assertion switch r.Method { case http.MethodPost: body, err := io.ReadAll(r.Body) @@ -67,7 +56,6 @@ func assertionFromRequestUnchecked(r *http.Request) (*ca.QueuedAssertion, error) return nil, err } defer r.Body.Close() - err = a.UnmarshalBinary(body) if err != nil { return nil, err @@ -76,24 +64,21 @@ func assertionFromRequestUnchecked(r *http.Request) (*ca.QueuedAssertion, error) return nil, fmt.Errorf("unsupported HTTP method: %v", r.Method) } - return &ca.QueuedAssertion{ - Assertion: a, - Checksum: checksum, - }, nil + return &a, nil } -func assertionFromRequest(r *http.Request) (*ca.QueuedAssertion, error) { - qa, err := assertionFromRequestUnchecked(r) +func assertionFromRequest(r *http.Request) (*mtc.AssertionRequest, error) { + a, err := assertionFromRequestUnchecked(r) if err != nil { return nil, err } - err = qa.Check() + err = a.Check() if err != nil { return nil, err } - return qa, nil + return a, nil } func handleCaQueue(path string) func(w http.ResponseWriter, r *http.Request) { @@ -104,13 +89,13 @@ func handleCaQueue(path string) func(w http.ResponseWriter, r *http.Request) { return } defer h.Close() - qa, err := assertionFromRequest(r) + a, err := assertionFromRequest(r) if err != nil { http.Error(w, "invalid assertion", http.StatusBadRequest) return } - err = h.Queue(qa.Assertion, qa.Checksum) + err = h.Queue(*a) if err != nil { http.Error(w, "failed to queue assertion", http.StatusInternalServerError) return @@ -127,13 +112,13 @@ func handleCaCert(path string) func(w http.ResponseWriter, r *http.Request) { return } defer h.Close() - qa, err := assertionFromRequest(r) + a, err := assertionFromRequest(r) if err != nil { http.Error(w, "invalid assertion", http.StatusBadRequest) return } - cert, err := h.CertificateFor(qa.Assertion) + cert, err := h.CertificateFor(a.Assertion) if err != nil { http.Error(w, "failed to get certificate for assertion", http.StatusBadRequest) return diff --git a/mtc.go b/mtc.go index fcfba30..bd3c90c 100644 --- a/mtc.go +++ b/mtc.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto" "crypto/sha256" + "crypto/x509" "errors" "fmt" "io" @@ -53,6 +54,24 @@ type Claims struct { Unknown []UnknownClaim } +type EvidenceType uint16 + +const ( + EmptyEvidenceType EvidenceType = iota + X509ChainEvidenceType +) + +type Evidence struct { + Type EvidenceType + Info EvidenceInfo +} + +type EvidenceInfo any + +type X509ChainEvidenceInfo []*x509.Certificate + +type UnknownEvidenceInfo []byte + // Represents a claim we do not how to interpret. type UnknownClaim struct { Type ClaimType @@ -100,6 +119,12 @@ type AbridgedAssertion struct { Claims Claims } +type AssertionRequest struct { + Checksum []byte + Assertion Assertion + Evidence Evidence +} + // Copy of tls.SignatureScheme to prevent cycling dependencies type SignatureScheme uint16 @@ -899,6 +924,80 @@ func (a *AbridgedAssertion) unmarshal(s *cryptobyte.String) error { return nil } +func (a *AssertionRequest) UnmarshalBinary(data []byte) error { + var ( + s cryptobyte.String = cryptobyte.String(data) + ) + a.Checksum = make([]byte, sha256.Size) + if !s.CopyBytes(a.Checksum) { + return ErrTruncated + } + + err := a.Assertion.unmarshal(&s) + if err != nil { + return err + } + + err = a.Evidence.unmarshal(&s) + if err != nil { + return err + } + + if !s.Empty() { + return ErrExtraBytes + } + + err = a.Check() + if err != nil { + return err + } + + return nil +} + +func (a *AssertionRequest) marshalAndCheckAssertion() ([]byte, error) { + buf, err := a.Assertion.MarshalBinary() + if err != nil { + return nil, err + } + + checksum2 := sha256.Sum256([]byte(buf)) + if a.Checksum == nil { + a.Checksum = checksum2[:] + } else if !bytes.Equal(checksum2[:], a.Checksum) { + return nil, ErrChecksumInvalid + } + + return buf, nil +} + +// If set, checks whether the Checksum is correct. If not set, sets the +// Checksum to the correct value. +func (a *AssertionRequest) Check() error { + // TODO validate evidence + _, err := a.marshalAndCheckAssertion() + return err +} + +func (a *AssertionRequest) MarshalBinary() ([]byte, error) { + var b cryptobyte.Builder + + buf, err := a.marshalAndCheckAssertion() + if err != nil { + return nil, err + } + b.AddBytes(a.Checksum) + b.AddBytes(buf) + + buf, err = a.Evidence.MarshalBinary() + if err != nil { + return nil, err + } + b.AddBytes(buf) + + return b.Bytes() +} + func (t *Tree) LeafCount() uint64 { return t.nLeaves } @@ -1468,7 +1567,7 @@ func (c *Claims) MarshalBinary() ([]byte, error) { return nil, err } - for i := 0; i < len(c.Unknown); i++ { + for i := range len(c.Unknown) { claim := c.Unknown[i] if i == 0 { if claim.Type <= Ipv6ClaimType { @@ -1489,6 +1588,84 @@ func (c *Claims) MarshalBinary() ([]byte, error) { return b.Bytes() } +func (e *Evidence) UnmarshalBinary(data []byte) error { + *e = Evidence{} + s := cryptobyte.String(data) + err := e.unmarshal(&s) + if err != nil { + return err + } + if !s.Empty() { + return ErrExtraBytes + } + return nil +} + +func (e *Evidence) unmarshal(s *cryptobyte.String) error { + + var ( + evidenceInfo cryptobyte.String + evidenceType EvidenceType + ) + + if !s.ReadUint16((*uint16)(&evidenceType)) || + !s.ReadUint24LengthPrefixed(&evidenceInfo) { + return ErrTruncated + } + e.Type = evidenceType + switch e.Type { + case EmptyEvidenceType: + if !evidenceInfo.Empty() { + return errors.New("non-empty info for empty evidence") + } + case X509ChainEvidenceType: + chain, err := x509.ParseCertificates([]byte(evidenceInfo)) + if err != nil { + return errors.New("failed to parse X.509 chain") + } + e.Info = X509ChainEvidenceInfo(chain) + default: + unknownInfo := make([]byte, len(evidenceInfo)) + evidenceInfo.CopyBytes(unknownInfo) + e.Info = UnknownEvidenceInfo(unknownInfo) + } + + return nil +} + +func (e *Evidence) MarshalBinary() ([]byte, error) { + var b cryptobyte.Builder + + b.AddUint16(uint16(e.Type)) + b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { + switch e.Type { + case EmptyEvidenceType: + case X509ChainEvidenceType: + for _, cert := range e.Info.(X509ChainEvidenceInfo) { + b.AddBytes(cert.Raw) + } + default: + b.AddBytes(e.Info.(UnknownEvidenceInfo)) + } + }) + + return b.Bytes() +} + +// Unmarshals Evidence entries from r and calls f for each, with +// the offset in the stream as first argument, and the Evidence +// as second argument. +// +// Returns early on error. +func UnmarshalEvidenceEntries(r io.Reader, + f func(int, *Evidence) error) error { + return unmarshal(r, f) +} + +func (e *Evidence) maxSize() int { + return (65535+2)*2 + 2 +} + func NewMerkleTreeProof(batch *Batch, index uint64, path []byte) *MerkleTreeProof { return &MerkleTreeProof{ anchor: batch.Anchor(), diff --git a/utils.go b/utils.go index 185bd53..5e74d22 100644 --- a/utils.go +++ b/utils.go @@ -16,6 +16,10 @@ var ( // ErrExtraBytes is a parsing error returned when there are extraneous // bytes at the end of, or within, the data. ErrExtraBytes = errors.New("Unexpected extra (internal) bytes") + + // ErrChecksumInvalid is an error returned when a checksum does not + // match the corresponding data. + ErrChecksumInvalid = errors.New("Invalid checksum") ) type unmarshaler interface { From 9267f5f1fcb450dc341c32267795885209cff767 Mon Sep 17 00:00:00 2001 From: Luke Valenta Date: Tue, 25 Feb 2025 10:53:42 -0500 Subject: [PATCH 2/2] Address Bas' feedback and fix some bugs - Use 'ar' for AssertionRequests to reduce risk of variable shadowing. - bugfix: fix range when iterating over batches. Previously, there was no lower limit to the loop. And since we're iterating backwards with a uint32, keep the upper limit to avoid integer underflow. - Finish adding index support for evidence, so you can now `mtc ca evidence` will properly check the evidence file for an assertion's evidence. - Include Evidence in the AssertionRequest checksum. - Use 'assertion-request' instead of 'assertion' for cli flags. --- ca/ca.go | 109 +++++++++++++++++++++++++++++++++++------- ca/index.go | 2 + cmd/mtc/main.go | 123 +++++++++++++++++++++++++++++++----------------- http/server.go | 14 +++--- mtc.go | 55 ++++++++++++---------- 5 files changed, 210 insertions(+), 93 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index a4183a4..d08c750 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -47,6 +47,7 @@ type Handle struct { indices map[uint32]*Index aas map[uint32]*os.File + evs map[uint32]*os.File trees map[uint32]*Tree batchNumbersCache []uint32 // cache for existing batches @@ -69,6 +70,10 @@ func (ca *Handle) Close() error { r.Close() } + for _, r := range ca.evs { + r.Close() + } + for _, t := range ca.trees { t.Close() } @@ -97,7 +102,7 @@ func (h *Handle) dropQueue() error { // // For each entry, if checksum is not nil, makes sure the assertion // matches the checksum -func (h *Handle) QueueMultiple(it func(yield func(a mtc.AssertionRequest) error) error) error { +func (h *Handle) QueueMultiple(it func(yield func(ar mtc.AssertionRequest) error) error) error { if h.closed { return ErrClosed } @@ -109,8 +114,8 @@ func (h *Handle) QueueMultiple(it func(yield func(a mtc.AssertionRequest) error) defer w.Close() bw := bufio.NewWriter(w) - if err := it(func(a mtc.AssertionRequest) error { - buf, err := a.MarshalBinary() + if err := it(func(ar mtc.AssertionRequest) error { + buf, err := ar.MarshalBinary() if err != nil { return err } @@ -140,9 +145,9 @@ func (h *Handle) QueueMultiple(it func(yield func(a mtc.AssertionRequest) error) // Queue assertion for publication. // // If checksum is not nil, makes sure assertion matches the checksum. -func (h *Handle) Queue(a mtc.AssertionRequest) error { - return h.QueueMultiple(func(yield func(a mtc.AssertionRequest) error) error { - return yield(a) +func (h *Handle) Queue(ar mtc.AssertionRequest) error { + return h.QueueMultiple(func(yield func(ar mtc.AssertionRequest) error) error { + return yield(ar) }) } @@ -154,6 +159,7 @@ func Open(path string) (*Handle, error) { path: path, indices: make(map[uint32]*Index), aas: make(map[uint32]*os.File), + evs: make(map[uint32]*os.File), trees: make(map[uint32]*Tree), } if err := h.lock(); err != nil { @@ -209,6 +215,10 @@ func (h Handle) aaPath(number uint32) string { return gopath.Join(h.batchPath(number), "abridged-assertions") } +func (h Handle) evPath(number uint32) string { + return gopath.Join(h.batchPath(number), "evidence") +} + func (h Handle) batchPath(number uint32) string { return gopath.Join(h.batchesPath(), fmt.Sprintf("%d", number)) } @@ -327,7 +337,7 @@ func (h *Handle) WalkQueue(f func(mtc.AssertionRequest) error) error { var ( prefix [2]byte aLen uint16 - a mtc.AssertionRequest + ar mtc.AssertionRequest ) _, err := io.ReadFull(br, prefix[:]) if err == io.EOF { @@ -345,12 +355,12 @@ func (h *Handle) WalkQueue(f func(mtc.AssertionRequest) error) error { return fmt.Errorf("Reading queue: %w", err) } - err = a.UnmarshalBinary(buf) + err = ar.UnmarshalBinary(buf) if err != nil { return fmt.Errorf("Parsing queue: %w", err) } - err = f(a) + err = f(ar) if err != nil { return err } @@ -416,6 +426,14 @@ func (h *Handle) closeBatch(batch uint32) error { delete(h.aas, batch) } + if r, ok := h.evs[batch]; ok { + err := r.Close() + if err != nil { + return fmt.Errorf("closing evidence for %d: %w", batch, err) + } + delete(h.evs, batch) + } + if r, ok := h.trees[batch]; ok { err := r.Close() if err != nil { @@ -472,10 +490,25 @@ func (ca *Handle) aaFileFor(batch uint32) (*os.File, error) { return r, nil } +// Returns file handle to evidence file for the given batch. +func (ca *Handle) evFileFor(batch uint32) (*os.File, error) { + if r, ok := ca.evs[batch]; ok { + return r, nil + } + + r, err := os.Open(ca.evPath(batch)) + if err != nil { + return nil, err + } + + return r, nil +} + type keySearchResult struct { Batch uint32 SequenceNumber uint64 Offset uint64 + EvidenceOffset uint64 } // Returns the certificate for an issued assertion @@ -516,7 +549,46 @@ func (ca *Handle) CertificateFor(a mtc.Assertion) (*mtc.BikeshedCertificate, err }, nil } -// Search for AbridgedAssertions's batch/seqno/offset by key. +var errShortCircuit = errors.New("short circuit") + +// Returns the evidence for an issued assertion +func (ca *Handle) EvidenceFor(a mtc.Assertion) (*mtc.Evidence, error) { + aa := a.Abridge() + var key [mtc.HashLen]byte + err := aa.Key(key[:]) + if err != nil { + return nil, err + } + res, err := ca.aaByKey(key[:]) + if err != nil { + return nil, fmt.Errorf("searching by key: %w", err) + } + + if res == nil { + return nil, fmt.Errorf("no assertion with key %x on record", key) + } + + var ev *mtc.Evidence + evFile, err := ca.evFileFor(res.Batch) + if err != nil { + return nil, err + } + _, err = evFile.Seek(int64(res.EvidenceOffset), 0) + if err != nil { + return nil, err + } + err = mtc.UnmarshalEvidenceEntries(evFile, func(_ int, ev2 *mtc.Evidence) error { + ev = ev2 + return errShortCircuit + }) + if err != errShortCircuit { + return nil, err + } + + return ev, nil +} + +// Search for AbridgedAssertions's batch/seqno/offset/evidence_offset by key. func (ca *Handle) aaByKey(key []byte) (*keySearchResult, error) { batches, err := ca.listBatchRange() if err != nil { @@ -527,7 +599,7 @@ func (ca *Handle) aaByKey(key []byte) (*keySearchResult, error) { return nil, nil } - for batch := batches.End - 1; batch <= batches.End; batch-- { + for batch := batches.End - 1; batch >= batches.Begin && batch <= batches.End; batch-- { res, err := ca.aaByKeyIn(batch, key) if err != nil { return nil, fmt.Errorf("Searching in batch %d: %w", batch, err) @@ -537,6 +609,7 @@ func (ca *Handle) aaByKey(key []byte) (*keySearchResult, error) { Batch: batch, SequenceNumber: res.SequenceNumber, Offset: res.Offset, + EvidenceOffset: res.EvidenceOffset, }, nil } } @@ -830,34 +903,34 @@ func (h *Handle) issueBatchTo(dir string, batch mtc.Batch, empty bool) error { evBW := bufio.NewWriter(evW) if !empty { - err = h.WalkQueue(func(a mtc.AssertionRequest) error { - aa := a.Assertion.Abridge() + err = h.WalkQueue(func(ar mtc.AssertionRequest) error { + aa := ar.Assertion.Abridge() buf, err := aa.MarshalBinary() if err != nil { - return fmt.Errorf("Marshalling assertion %x: %w", a.Checksum, err) + return fmt.Errorf("Marshalling assertion %x: %w", ar.Checksum, err) } _, err = aasBW.Write(buf) if err != nil { return fmt.Errorf( "Writing assertion %x to %s: %w", - a.Checksum, + ar.Checksum, aasPath, err, ) } - ev := a.Evidence + ev := ar.Evidence buf, err = ev.MarshalBinary() if err != nil { - return fmt.Errorf("Marshalling evidence %x: %w", a.Checksum, err) + return fmt.Errorf("Marshalling evidence %x: %w", ar.Checksum, err) } _, err = evBW.Write(buf) if err != nil { return fmt.Errorf( "Writing evidence %x to %s: %w", - a.Checksum, + ar.Checksum, evPath, err, ) diff --git a/ca/index.go b/ca/index.go index 81964b9..db6e643 100644 --- a/ca/index.go +++ b/ca/index.go @@ -41,6 +41,7 @@ type Index struct { type IndexSearchResult struct { SequenceNumber uint64 Offset uint64 + EvidenceOffset uint64 } // Opens an index @@ -139,6 +140,7 @@ func (h *Index) Search(hash []byte) (*IndexSearchResult, error) { ss := cryptobyte.String(val[:]) ss.ReadUint64(&ret.SequenceNumber) ss.ReadUint64(&ret.Offset) + ss.ReadUint64(&ret.EvidenceOffset) return &ret, nil case -1: // tmp < needle a.Set(&tmp) diff --git a/cmd/mtc/main.go b/cmd/mtc/main.go index 15b36eb..2d1df44 100644 --- a/cmd/mtc/main.go +++ b/cmd/mtc/main.go @@ -46,9 +46,10 @@ func writeToFileOrStdout(path string, buf []byte) error { return nil } -// Flags used to create or specify an assertion. Used in `mtc ca queue'. +// Flags used to create or specify an assertion request. +// Used in `mtc ca queue' and 'mtc ca cert'. // Includes the in-file flag, if inFile is true. -func assertionFlags(inFile bool) []cli.Flag { +func assertionRequestFlags(inFile bool) []cli.Flag { ret := []cli.Flag{ &cli.StringSliceFlag{ Name: "dns", @@ -110,7 +111,7 @@ func assertionFlags(inFile bool) []cli.Flag { Name: "in-file", Category: "Assertion", Aliases: []string{"i"}, - Usage: "Read assertion from the given file", + Usage: "Read assertion request from the given file", }, ) } @@ -118,21 +119,21 @@ func assertionFlags(inFile bool) []cli.Flag { return ret } -func assertionFromFlags(cc *cli.Context) (*mtc.AssertionRequest, error) { - a, err := assertionFromFlagsUnchecked(cc) +func assertionRequestFromFlags(cc *cli.Context) (*mtc.AssertionRequest, error) { + ar, err := assertionRequestFromFlagsUnchecked(cc) if err != nil { return nil, err } - err = a.Check() + err = ar.Check() if err != nil { return nil, err } - return a, nil + return ar, nil } -func assertionFromFlagsUnchecked(cc *cli.Context) (*mtc.AssertionRequest, error) { +func assertionRequestFromFlagsUnchecked(cc *cli.Context) (*mtc.AssertionRequest, error) { var ( checksum []byte err error @@ -150,7 +151,7 @@ func assertionFromFlagsUnchecked(cc *cli.Context) (*mtc.AssertionRequest, error) assertionBuf, err := os.ReadFile(assertionPath) if err != nil { return nil, fmt.Errorf( - "reading assertion %s: %w", + "reading assertion request %s: %w", assertionPath, err, ) @@ -174,20 +175,16 @@ func assertionFromFlagsUnchecked(cc *cli.Context) (*mtc.AssertionRequest, error) } } - var a mtc.Assertion - err = a.UnmarshalBinary(assertionBuf) + var ar mtc.AssertionRequest + err = ar.UnmarshalBinary(assertionBuf) if err != nil { return nil, fmt.Errorf( - "parsing assertion %s: %w", + "parsing assertion request %s: %w", assertionPath, err, ) } - - return &mtc.AssertionRequest{ - Assertion: a, - Checksum: checksum, - }, nil + return &ar, nil } var ( @@ -351,7 +348,7 @@ func assertionFromFlagsUnchecked(cc *cli.Context) (*mtc.AssertionRequest, error) } func handleCaQueue(cc *cli.Context) error { - a, err := assertionFromFlags(cc) + ar, err := assertionRequestFromFlags(cc) if err != nil { return err } @@ -362,17 +359,17 @@ func handleCaQueue(cc *cli.Context) error { } defer h.Close() - return h.QueueMultiple(func(yield func(a mtc.AssertionRequest) error) error { + return h.QueueMultiple(func(yield func(ar mtc.AssertionRequest) error) error { for i := 0; i < cc.Int("debug-repeat"); i++ { - a2 := *a + ar2 := *ar if cc.Bool("debug-vary") { - a2.Checksum = nil - a2.Assertion.Claims.DNS = append( - a2.Assertion.Claims.DNS, + ar2.Checksum = nil + ar2.Assertion.Claims.DNS = append( + ar2.Assertion.Claims.DNS, fmt.Sprintf("%d.example.com", i), ) } - if err := yield(a2); err != nil { + if err := yield(ar2); err != nil { return err } } @@ -380,13 +377,13 @@ func handleCaQueue(cc *cli.Context) error { }) } -func handleNewAssertion(cc *cli.Context) error { - a, err := assertionFromFlags(cc) +func handleNewAssertionRequest(cc *cli.Context) error { + ar, err := assertionRequestFromFlags(cc) if err != nil { return err } - buf, err := a.MarshalBinary() + buf, err := ar.MarshalBinary() if err != nil { return err } @@ -395,8 +392,6 @@ func handleNewAssertion(cc *cli.Context) error { return err } - fmt.Fprintf(os.Stderr, "checksum: %x\n", a.Checksum) - return nil } @@ -417,12 +412,12 @@ func handleCaCert(cc *cli.Context) error { } defer h.Close() - a, err := assertionFromFlags(cc) + ar, err := assertionRequestFromFlags(cc) if err != nil { return err } - cert, err := h.CertificateFor(a.Assertion) + cert, err := h.CertificateFor(ar.Assertion) if err != nil { return err } @@ -439,6 +434,35 @@ func handleCaCert(cc *cli.Context) error { return nil } +func handleCaEvidence(cc *cli.Context) error { + h, err := ca.Open(cc.String("ca-path")) + if err != nil { + return err + } + defer h.Close() + + ar, err := assertionRequestFromFlags(cc) + if err != nil { + return err + } + + ev, err := h.EvidenceFor(ar.Assertion) + if err != nil { + return err + } + + buf, err := ev.MarshalBinary() + if err != nil { + return err + } + + if err := writeToFileOrStdout(cc.String("out-file"), buf); err != nil { + return err + } + + return nil +} + func handleCaShowQueue(cc *cli.Context) error { h, err := ca.Open(cc.String("ca-path")) if err != nil { @@ -482,7 +506,7 @@ func handleCaShowQueue(cc *cli.Context) error { if err != nil { return err } - fmt.Printf("Total number of assertions in queue: %d\n", count) + fmt.Printf("Total number of assertion requests in queue: %d\n", count) return nil } @@ -787,16 +811,16 @@ func handleInspectAssertionRequest(cc *cli.Context) error { return err } - var a mtc.AssertionRequest - err = a.UnmarshalBinary(buf) + var ar mtc.AssertionRequest + err = ar.UnmarshalBinary(buf) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - fmt.Fprintf(w, "checksum\t%x\n", a.Checksum) - writeAssertion(w, a.Assertion) - writeEvidence(w, a.Evidence) + fmt.Fprintf(w, "checksum\t%x\n", ar.Checksum) + writeAssertion(w, ar.Assertion) + writeEvidence(w, ar.Evidence) w.Flush() return nil } @@ -969,7 +993,7 @@ func main() { Usage: "queue assertion for issuance", Action: handleCaQueue, Flags: append( - assertionFlags(true), + assertionRequestFlags(true), &cli.IntFlag{ Name: "debug-repeat", Category: "Debug", @@ -988,7 +1012,7 @@ func main() { Usage: "creates certificate for an issued assertion", Action: handleCaCert, Flags: append( - assertionFlags(true), + assertionRequestFlags(true), &cli.StringFlag{ Name: "out-file", Usage: "path to write cert to", @@ -996,6 +1020,19 @@ func main() { }, ), }, + { + Name: "evidence", + Usage: "fetches evidence for an issued assertion", + Action: handleCaEvidence, + Flags: append( + assertionRequestFlags(true), + &cli.StringFlag{ + Name: "out-file", + Usage: "path to write evidence to", + Aliases: []string{"o"}, + }, + ), + }, { Name: "serve", Usage: "start CA server", @@ -1031,7 +1068,7 @@ func main() { ArgsUsage: "[path]", }, { - Name: "assertion", + Name: "assertion-request", Usage: "parses an assertion request", Action: handleInspectAssertionRequest, ArgsUsage: "[path]", @@ -1070,11 +1107,11 @@ func main() { }, }, { - Name: "new-assertion", + Name: "new-assertion-request", Usage: "creates a new assertion request", - Action: handleNewAssertion, + Action: handleNewAssertionRequest, Flags: append( - assertionFlags(false), + assertionRequestFlags(false), &cli.StringFlag{ Name: "out-file", Usage: "path to write assertion request to", diff --git a/http/server.go b/http/server.go index fc8d976..986b027 100644 --- a/http/server.go +++ b/http/server.go @@ -45,9 +45,8 @@ func (s *Server) Shutdown(ctx context.Context) error { } func assertionFromRequestUnchecked(r *http.Request) (*mtc.AssertionRequest, error) { - var ( - a mtc.AssertionRequest + ar mtc.AssertionRequest ) switch r.Method { case http.MethodPost: @@ -56,29 +55,28 @@ func assertionFromRequestUnchecked(r *http.Request) (*mtc.AssertionRequest, erro return nil, err } defer r.Body.Close() - err = a.UnmarshalBinary(body) + err = ar.UnmarshalBinary(body) if err != nil { return nil, err } + return &ar, nil default: return nil, fmt.Errorf("unsupported HTTP method: %v", r.Method) } - - return &a, nil } func assertionFromRequest(r *http.Request) (*mtc.AssertionRequest, error) { - a, err := assertionFromRequestUnchecked(r) + ar, err := assertionFromRequestUnchecked(r) if err != nil { return nil, err } - err = a.Check() + err = ar.Check() if err != nil { return nil, err } - return a, nil + return ar, nil } func handleCaQueue(path string) func(w http.ResponseWriter, r *http.Request) { diff --git a/mtc.go b/mtc.go index bd3c90c..45df3b9 100644 --- a/mtc.go +++ b/mtc.go @@ -924,21 +924,21 @@ func (a *AbridgedAssertion) unmarshal(s *cryptobyte.String) error { return nil } -func (a *AssertionRequest) UnmarshalBinary(data []byte) error { +func (ar *AssertionRequest) UnmarshalBinary(data []byte) error { var ( s cryptobyte.String = cryptobyte.String(data) ) - a.Checksum = make([]byte, sha256.Size) - if !s.CopyBytes(a.Checksum) { + ar.Checksum = make([]byte, sha256.Size) + if !s.CopyBytes(ar.Checksum) { return ErrTruncated } - err := a.Assertion.unmarshal(&s) + err := ar.Assertion.unmarshal(&s) if err != nil { return err } - err = a.Evidence.unmarshal(&s) + err = ar.Evidence.unmarshal(&s) if err != nil { return err } @@ -947,7 +947,7 @@ func (a *AssertionRequest) UnmarshalBinary(data []byte) error { return ErrExtraBytes } - err = a.Check() + err = ar.Check() if err != nil { return err } @@ -955,44 +955,51 @@ func (a *AssertionRequest) UnmarshalBinary(data []byte) error { return nil } -func (a *AssertionRequest) marshalAndCheckAssertion() ([]byte, error) { - buf, err := a.Assertion.MarshalBinary() +func (ar *AssertionRequest) marshalAndCheckAssertionRequest() ([]byte, error) { + var b cryptobyte.Builder + + buf, err := ar.Assertion.MarshalBinary() if err != nil { return nil, err } + b.AddBytes(buf) - checksum2 := sha256.Sum256([]byte(buf)) - if a.Checksum == nil { - a.Checksum = checksum2[:] - } else if !bytes.Equal(checksum2[:], a.Checksum) { + buf, err = ar.Evidence.MarshalBinary() + if err != nil { + return nil, err + } + b.AddBytes(buf) + + checksumBytes, err := b.Bytes() + if err != nil { + return nil, err + } + checksum2 := sha256.Sum256(checksumBytes) + if ar.Checksum == nil { + ar.Checksum = checksum2[:] + } else if !bytes.Equal(checksum2[:], ar.Checksum) { return nil, ErrChecksumInvalid } - return buf, nil + return b.Bytes() } // If set, checks whether the Checksum is correct. If not set, sets the // Checksum to the correct value. -func (a *AssertionRequest) Check() error { +func (ar *AssertionRequest) Check() error { // TODO validate evidence - _, err := a.marshalAndCheckAssertion() + _, err := ar.marshalAndCheckAssertionRequest() return err } -func (a *AssertionRequest) MarshalBinary() ([]byte, error) { +func (ar *AssertionRequest) MarshalBinary() ([]byte, error) { var b cryptobyte.Builder - buf, err := a.marshalAndCheckAssertion() - if err != nil { - return nil, err - } - b.AddBytes(a.Checksum) - b.AddBytes(buf) - - buf, err = a.Evidence.MarshalBinary() + buf, err := ar.marshalAndCheckAssertionRequest() if err != nil { return nil, err } + b.AddBytes(ar.Checksum) b.AddBytes(buf) return b.Bytes()