diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6092f4a..4b7e64a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.23' diff --git a/README.md b/README.md index 0183cdb..700ac27 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ For every batch, the CA signs that root together with all the roots Let's create an MTC CA. ``` -$ mtc ca new --batch-duration 5m --lifetime 1h my-mtc-ca ca.example.com/path +$ mtc ca new --batch-duration 5m --lifetime 1h 62253.12.15 ca.example.com/path ``` This creates a new MTC CA called `my-mtc-ca`, and puts the data in the @@ -137,7 +137,7 @@ is `ca-params`, which contains the information about the CA: ``` $ mtc inspect ca-params www/mtc/v1/ca-params -issuer_id my-mtc-ca +issuer 62253.12.15 start_time 1705677477 2024-01-19 16:17:57 +0100 CET batch_duration 300 5m0s life_time 3600 1h0m0s @@ -356,33 +356,27 @@ tree_heads[2] ab3cb1262fc084be0447c2b3d175d63f6ec2782dcc1443888b12f685976093d5 ### Creating a certificate -In MTC, a **certificate** is an assertion, together with the batch number, -`issuer_id` of the CA, and an authentication path in the Merkle tree. +In MTC, a **certificate** is an assertion, +together with the TrustAnchorIdentifier (consisting of an OID for the CA and the batch number), +and an authentication path in the Merkle tree. Let's create one for our initial assertion. ``` $ mtc ca cert -i my-assertion -o my-cert -$ mtc inspect cert my-cert +``` + +If we inspect the certificate, it can recompute the root from the authentication path and CA parameters: + +``` +$ mtc inspect -ca-params www/mtc/v1/ca-params cert my-cert subject_type TLS signature_scheme p256 public_key_hash a02a1758e4c9d6511dc02f59301b9f29e41762d3d769c87a22333497984a41ef dns [example.com] ip4 [198.51.100.60] -proof_type merkle_tree_sha256 -issuer_id my-mtc-ca -batch 0 -index 0 -authentication path - 00b17df8d909fd3e77005486a16ca00fdc9af38f92a23351359fd420d9f2ef78 -``` - -If we provide the `ca-params` to `mtc inspect`, it can recompute the root -from the authentication path: - -``` -$ mtc inspect -ca-params www/mtc/v1/ca-params cert my-cert -[…] +proof_type merkle_tree_sha256 +CA OID 62253.12.15 batch 0 index 0 recomputed root c005dcdb53c4e41befcf3a294b815d8b8aa0a260e9f10bfd4e4cb52eb3724aa3 diff --git a/ca/ca.go b/ca/ca.go index aa1edda..2ebb733 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -29,7 +29,7 @@ var ( ) type NewOpts struct { - IssuerId string + Issuer mtc.RelativeOID HttpServer string // Fields below are optional. @@ -991,7 +991,7 @@ func (h *Handle) issueBatchTo(dir string, batch mtc.Batch, empty bool) error { return nil } -// Creates a new Merkle Tree CA, and opens it. +// New creates a new Merkle Tree CA, and opens it. // // Call Handle.Close() when done. func New(path string, opts NewOpts) (*Handle, error) { @@ -1034,7 +1034,7 @@ func New(path string, opts NewOpts) (*Handle, error) { h.params.StartTime = uint64(time.Now().Unix()) h.params.HttpServer = opts.HttpServer - h.params.IssuerId = opts.IssuerId + h.params.Issuer = opts.Issuer if opts.SignatureScheme == 0 { opts.SignatureScheme = mtc.TLSDilitihium5r3 diff --git a/cmd/mtc/main.go b/cmd/mtc/main.go index ad14868..a68adbc 100644 --- a/cmd/mtc/main.go +++ b/cmd/mtc/main.go @@ -1,9 +1,9 @@ package main import ( + "errors" "github.com/bwesterb/mtc" "github.com/bwesterb/mtc/ca" - "github.com/urfave/cli/v2" "golang.org/x/crypto/cryptobyte" @@ -11,7 +11,6 @@ import ( "crypto/x509" "encoding/hex" "encoding/pem" - "errors" "fmt" "io" "net" @@ -400,10 +399,18 @@ func handleCaNew(cc *cli.Context) error { cli.ShowSubcommandHelp(cc) return errArgs } + + taiString := cc.Args().Get(0) + oid := mtc.RelativeOID{} + err := oid.UnmarshalText([]byte(taiString)) + if err != nil { + return err + } + h, err := ca.New( cc.String("ca-path"), ca.NewOpts{ - IssuerId: cc.Args().Get(0), + Issuer: oid, HttpServer: cc.Args().Get(1), BatchDuration: cc.Duration("batch-duration"), @@ -575,9 +582,16 @@ func handleInspectCert(cc *cli.Context) error { if err != nil { return err } + params, err := inspectGetCAParams(cc) + if err != nil { + return err + } + + caStore := mtc.LocalCAStore{} + caStore.Add(*params) var c mtc.BikeshedCertificate - err = c.UnmarshalBinary(buf) + err = c.UnmarshalBinary(buf, &caStore) if err != nil { return err } @@ -585,13 +599,11 @@ func handleInspectCert(cc *cli.Context) error { 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", c.Proof.TrustAnchor().ProofType()) + tai := c.Proof.TrustAnchorIdentifier() + fmt.Fprintf(w, "proof_type\t%v\n", tai.ProofType(&caStore)) - switch anch := c.Proof.TrustAnchor().(type) { - case *mtc.MerkleTreeTrustAnchor: - fmt.Fprintf(w, "issuer_id\t%s\n", anch.IssuerId()) - fmt.Fprintf(w, "batch\t%d\n", anch.BatchNumber()) - } + fmt.Fprintf(w, "CA OID\t%s\n", tai.Issuer) + fmt.Fprintf(w, "Batch number\t%d\n", tai.BatchNumber) switch proof := c.Proof.(type) { case *mtc.MerkleTreeProof: @@ -601,38 +613,30 @@ func handleInspectCert(cc *cli.Context) error { switch proof := c.Proof.(type) { case *mtc.MerkleTreeProof: path := proof.Path() + batch := &mtc.Batch{ + CA: params, + Number: tai.BatchNumber, + } - params, err := inspectGetCAParams(cc) - if err == nil { - anch := proof.TrustAnchor().(*mtc.MerkleTreeTrustAnchor) - - batch := &mtc.Batch{ - CA: params, - Number: anch.BatchNumber(), - } - - if anch.IssuerId() != params.IssuerId { - return fmt.Errorf( - "IssuerId doesn't match: %s ≠ %s", - params.IssuerId, - anch.IssuerId(), - ) - } - aa := c.Assertion.Abridge() - root, err := batch.ComputeRootFromAuthenticationPath( - proof.Index(), - path, - &aa, + if !tai.Issuer.Equal(¶ms.Issuer) { + return fmt.Errorf( + "IssuerId doesn't match: %s ≠ %s", + params.Issuer, + tai.Issuer, ) - if err != nil { - return fmt.Errorf("computing root: %w", err) - } - - fmt.Fprintf(w, "recomputed root\t%x\n", root) - } else if err != errNoCaParams { - return err + } + aa := c.Assertion.Abridge() + root, err := batch.ComputeRootFromAuthenticationPath( + proof.Index(), + path, + &aa, + ) + if err != nil { + return fmt.Errorf("computing root: %w", err) } + fmt.Fprintf(w, "recomputed root\t%x\n", root) + w.Flush() fmt.Printf("authentication path\n") for i := 0; i < len(path)/mtc.HashLen; i++ { @@ -721,7 +725,7 @@ func handleInspectCaParams(cc *cli.Context) error { return err } w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - fmt.Fprintf(w, "issuer_id\t%s\n", p.IssuerId) + fmt.Fprintf(w, "issuer\t%s\n", p.Issuer) fmt.Fprintf(w, "start_time\t%d\t%s\n", p.StartTime, time.Unix(int64(p.StartTime), 0)) fmt.Fprintf(w, "batch_duration\t%d\t%s\n", p.BatchDuration, @@ -764,7 +768,7 @@ func main() { Name: "new", Usage: "creates a new CA", Action: handleCaNew, - ArgsUsage: " ", + ArgsUsage: " ", Flags: []cli.Flag{ &cli.DurationFlag{ Name: "batch-duration", @@ -781,6 +785,12 @@ func main() { Aliases: []string{"s"}, Usage: "time to serve assertions", }, + &cli.StringFlag{ + Name: "ca-path", + Aliases: []string{"p"}, + Usage: "root directory to store CA files", + Value: ".", + }, }, }, { diff --git a/mtc.go b/mtc.go index e1562fe..7315baa 100644 --- a/mtc.go +++ b/mtc.go @@ -18,10 +18,11 @@ import ( "golang.org/x/crypto/cryptobyte" ) -// Public parameters of a Merkle Tree CA +// CAParams holds the public parameters of a Merkle Tree CA type CAParams struct { - IssuerId string + Issuer RelativeOID PublicKey Verifier + ProofType ProofType StartTime uint64 BatchDuration uint64 Lifetime uint64 @@ -133,34 +134,19 @@ const ( MerkleTreeProofType ProofType = iota ) -type TrustAnchor interface { - ProofType() ProofType - Info() []byte -} - -type MerkleTreeTrustAnchor struct { - issuerId string - batchNumber uint32 -} - -type UnknownTrustAnchor struct { - typ ProofType - info []byte -} - type Proof interface { - TrustAnchor() TrustAnchor + TrustAnchorIdentifier() TrustAnchorIdentifier Info() []byte } type MerkleTreeProof struct { - anchor *MerkleTreeTrustAnchor + anchor TrustAnchorIdentifier index uint64 path []byte } type UnknownProof struct { - anchor *UnknownTrustAnchor + anchor TrustAnchorIdentifier info []byte } @@ -174,41 +160,7 @@ type SignedValidityWindow struct { Signature []byte } -func (t *MerkleTreeTrustAnchor) ProofType() ProofType { - return MerkleTreeProofType -} - -func (t *MerkleTreeTrustAnchor) Info() []byte { - var b cryptobyte.Builder - b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte(t.issuerId)) - }) - b.AddUint32(t.batchNumber) - ret, err := b.Bytes() - if err != nil { - // Can only happen if issuerId is too long, but we checked for this. - panic(err) - } - return ret -} - -func (t *MerkleTreeTrustAnchor) BatchNumber() uint32 { - return t.batchNumber -} - -func (t *MerkleTreeTrustAnchor) IssuerId() string { - return t.issuerId -} - -func (t *UnknownTrustAnchor) ProofType() ProofType { - return t.typ -} - -func (t *UnknownTrustAnchor) Info() []byte { - return t.info -} - -func (p *MerkleTreeProof) TrustAnchor() TrustAnchor { +func (p *MerkleTreeProof) TrustAnchorIdentifier() TrustAnchorIdentifier { return p.anchor } @@ -234,7 +186,7 @@ func (p *MerkleTreeProof) Index() uint64 { return p.index } -func (p *UnknownProof) TrustAnchor() TrustAnchor { +func (p *UnknownProof) TrustAnchorIdentifier() TrustAnchorIdentifier { return p.anchor } @@ -326,7 +278,7 @@ func (t *Tree) NodeCount() uint { return TreeNodeCount(t.nLeaves) } -// Returns the number of nodes in the Merkle tree for a batch, which has +// TreeNodeCount returns the number of nodes in the Merkle tree for a batch, which has // nLeaves assertions. func TreeNodeCount(nLeaves uint64) uint { if nLeaves == 0 { @@ -369,52 +321,41 @@ func (c *BikeshedCertificate) MarshalBinary() ([]byte, error) { var b cryptobyte.Builder buf, err := c.Assertion.MarshalBinary() if err != nil { - return nil, fmt.Errorf("Failed to marshal Assertion: %w", err) + return nil, fmt.Errorf("failed to marshal Assertion: %w", err) } b.AddBytes(buf) - b.AddUint16(uint16(c.Proof.TrustAnchor().ProofType())) - b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes(c.Proof.TrustAnchor().Info()) - }) + + buf, err = c.Proof.TrustAnchorIdentifier().MarshalBinary() + b.AddBytes(buf) b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(c.Proof.Info()) }) return b.Bytes() } -func (c *BikeshedCertificate) UnmarshalBinary(data []byte) error { +func (c *BikeshedCertificate) UnmarshalBinary(data []byte, caStore CAStore) error { s := cryptobyte.String(data) err := c.Assertion.unmarshal(&s) if err != nil { - return fmt.Errorf("Failed to unmarshal Assertion: %w", err) + return fmt.Errorf("failed to unmarshal Assertion: %w", err) } var ( - typ ProofType - proofInfo cryptobyte.String - anchorInfo cryptobyte.String + proofInfo cryptobyte.String ) - if !s.ReadUint16((*uint16)(&typ)) || - !s.ReadUint8LengthPrefixed(&anchorInfo) || - !s.ReadUint16LengthPrefixed(&proofInfo) { + tai := TrustAnchorIdentifier{} + err = tai.unmarshal(&s) + if err != nil { + return fmt.Errorf("failed to unmarshal TrustAnchorIdentifier: %w", err) + } + if !s.ReadUint16LengthPrefixed(&proofInfo) { return ErrTruncated } if !s.Empty() { return ErrExtraBytes } - switch typ { + switch tai.ProofType(caStore) { case MerkleTreeProofType: - proof := &MerkleTreeProof{ - anchor: &MerkleTreeTrustAnchor{}, - } - var issuerId []byte - if !anchorInfo.ReadUint8LengthPrefixed((*cryptobyte.String)(&issuerId)) || - !anchorInfo.ReadUint32(&proof.anchor.batchNumber) { - return ErrTruncated - } - proof.anchor.issuerId = string(issuerId) - if !anchorInfo.Empty() { - return ErrExtraBytes - } + proof := &MerkleTreeProof{} if !proofInfo.ReadUint64(&proof.index) || !copyUint16LengthPrefixed(&proofInfo, &proof.path) { return ErrTruncated @@ -422,15 +363,13 @@ func (c *BikeshedCertificate) UnmarshalBinary(data []byte) error { if !proofInfo.Empty() { return ErrExtraBytes } + proof.anchor = tai c.Proof = proof return nil } c.Proof = &UnknownProof{ - anchor: &UnknownTrustAnchor{ - typ: typ, - info: []byte(anchorInfo), - }, - info: []byte(proofInfo), + anchor: tai, + info: []byte(proofInfo), } return nil } @@ -488,12 +427,13 @@ func (p *CAParams) MarshalBinary() ([]byte, error) { // TODO add struct to I-D var b cryptobyte.Builder b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte(p.IssuerId)) + b.AddBytes(p.Issuer) }) b.AddUint16(uint16(p.PublicKey.Scheme())) b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(p.PublicKey.Bytes()) }) + b.AddUint16(uint16(p.ProofType)) b.AddUint64(p.StartTime) b.AddUint64(p.BatchDuration) b.AddUint64(p.Lifetime) @@ -518,6 +458,7 @@ func (p *CAParams) UnmarshalBinary(data []byte) error { if !s.ReadUint8LengthPrefixed((*cryptobyte.String)(&issuerBuf)) || !s.ReadUint16((*uint16)(&sigScheme)) || !s.ReadUint16LengthPrefixed((*cryptobyte.String)(&pkBuf)) || + !s.ReadUint16((*uint16)(&p.ProofType)) || !s.ReadUint64(&p.StartTime) || !s.ReadUint64(&p.BatchDuration) || !s.ReadUint64(&p.Lifetime) || @@ -531,7 +472,7 @@ func (p *CAParams) UnmarshalBinary(data []byte) error { return ErrExtraBytes } - p.IssuerId = string(issuerBuf) + p.Issuer = issuerBuf p.HttpServer = string(httpServerBuf) p.PublicKey, err = UnmarshalVerifier(sigScheme, pkBuf) if err != nil { @@ -542,11 +483,14 @@ func (p *CAParams) UnmarshalBinary(data []byte) error { } func (p *CAParams) Validate() error { - if len(p.IssuerId) > 32 { - return errors.New("issuer_id must be 32 bytes or less") + // If the issuer uses the full 255 bytes, there can be at most 128 batches, + // as there is only a single byte left for encoding the batch. + // TODO Maybe reduce the maximum allowed size of the issuer OID. + if len(p.Issuer) > 255 { + return errors.New("issuer must be 255 bytes or less") } - if len(p.IssuerId) == 0 { - return errors.New("issuer_id can't be empty") + if len(p.Issuer) == 0 { + return errors.New("issuer can't be empty") } if p.Lifetime%p.BatchDuration != 0 { return errors.New("lifetime must be a multiple of batch_duration") @@ -639,8 +583,9 @@ func (w *SignedValidityWindow) MarshalBinary() ([]byte, error) { func (w *ValidityWindow) LabeledValdityWindow(ca *CAParams) ([]byte, error) { var b cryptobyte.Builder b.AddBytes([]byte("Merkle Tree Crts ValidityWindow\000")) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte(ca.IssuerId)) + b.AddBytes(ca.Issuer) }) buf, err := w.MarshalBinary() if err != nil { @@ -670,11 +615,12 @@ func (p *CAParams) newTreeHeads(prevHeads, root []byte) ([]byte, error) { return append(prevHeads[HashLen:len(prevHeads)], root...), nil } -func (batch *Batch) Anchor() *MerkleTreeTrustAnchor { - return &MerkleTreeTrustAnchor{ - issuerId: batch.CA.IssuerId, - batchNumber: batch.Number, +func (batch *Batch) Anchor() TrustAnchorIdentifier { + tai := TrustAnchorIdentifier{ + Issuer: batch.CA.Issuer, + BatchNumber: batch.Number, } + return tai } func (batch *Batch) SignValidityWindow(signer Signer, prevHeads []byte, @@ -1073,7 +1019,7 @@ func (batch *Batch) hashNode(out, left, right []byte, index uint64, b.AddUint8(1) b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte(batch.CA.IssuerId)) + b.AddBytes(batch.CA.Issuer) }) b.AddUint32(batch.Number) b.AddUint64(index) @@ -1096,7 +1042,7 @@ func (batch *Batch) hashEmpty(out []byte, index uint64, level uint8) error { var b cryptobyte.Builder b.AddUint8(0) b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte(batch.CA.IssuerId)) + b.AddBytes(batch.CA.Issuer) }) b.AddUint32(batch.Number) b.AddUint64(index) @@ -1188,7 +1134,7 @@ func (a *AbridgedAssertion) Hash(out []byte, batch *Batch, index uint64) error { var b cryptobyte.Builder b.AddUint8(2) b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte(batch.CA.IssuerId)) + b.AddBytes(batch.CA.Issuer) }) b.AddUint32(batch.Number) b.AddUint64(index) @@ -1527,11 +1473,53 @@ func NewMerkleTreeProof(batch *Batch, index uint64, path []byte) *MerkleTreeProo } } -// A Trust Anchor Identifier (TAI) is used to identify a CA, or a specific batch. +type CAStore interface { + 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) Add(params CAParams) { + if s.store == nil { + s.store = make(map[string]CAParams) + } + s.store[params.Issuer.String()] = params +} + +// A TrustAnchorIdentifier (TAI) is used to identify a CA, or a specific batch. // // TAI are OIDs relative to the Private Enterprise Numbers (PEN) -// arc 1.3.6.1.4.1. -type TrustAnchorIdentifier []byte +// arc 1.3.6.1.4.1. +type TrustAnchorIdentifier struct { + Issuer RelativeOID + BatchNumber uint32 +} + +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) + for i := 0; i < len(oid); i++ { + cur = (cur << 7) | uint32(oid[i]&0x7f) + + if oid[i]&0x80 == 0 { + res = append(res, cur) + cur = 0 + } + } + return res +} func (tai *TrustAnchorIdentifier) UnmarshalBinary(buf []byte) error { s := cryptobyte.String(buf) @@ -1545,39 +1533,27 @@ func (tai *TrustAnchorIdentifier) UnmarshalBinary(buf []byte) error { return nil } -func (tai TrustAnchorIdentifier) String() string { - if tai == nil { +func (oid RelativeOID) String() string { + if oid == nil { return "nil" } var buf bytes.Buffer - child := 0 - cur := uint64(0) - for i := 0; i < len(tai); i++ { - cur = (cur << 7) | uint64(tai[i]&0x7f) - - if tai[i]&0x80 == 0 { - if child != 0 { - fmt.Fprintf(&buf, ".") - } - fmt.Fprintf(&buf, "%d", cur) - cur = 0 - child++ + first := true + for _, s := range oid.segments() { + if !first { + _, _ = fmt.Fprintf(&buf, ".") } + first = false + _, _ = fmt.Fprintf(&buf, "%d", s) } - return buf.String() } -func (tai *TrustAnchorIdentifier) UnmarshalText(text []byte) error { - bits := strings.Split(string(text), ".") +func (oid *RelativeOID) FromSegments(segments []uint32) error { var buf bytes.Buffer - for i, bit := range bits { - v, err := strconv.ParseUint(bit, 10, 32) - if err != nil { - return fmt.Errorf("TrustAnchorIdentifier: subidentifier %d: %v", i, err) - } + for _, v := range segments { for j := 4; j >= 0; j-- { cur := v >> (j * 7) if cur != 0 || j == 0 { @@ -1589,49 +1565,99 @@ func (tai *TrustAnchorIdentifier) UnmarshalText(text []byte) error { } } } - *tai = buf.Bytes() - if len(*tai) > 255 { - return errors.New("TrustAnchorIdentifier: over 255 bytes") + *oid = buf.Bytes() + if len(*oid) > 255 { + return errors.New("OID: over 255 bytes") + } + return nil +} + +func (oid *RelativeOID) UnmarshalText(text []byte) error { + bits := strings.Split(string(text), ".") + var segments []uint32 + for i, bit := range bits { + v, err := strconv.ParseUint(bit, 10, 32) + if err != nil { + return fmt.Errorf("OID: subidentifier %d: %v", i, err) + } + segments = append(segments, uint32(v)) + } + err := oid.FromSegments(segments) + if err != nil { + return err + } + if len(*oid) > 255 { + return errors.New("OID: over 255 bytes") } return nil } func (tai TrustAnchorIdentifier) MarshalBinary() ([]byte, error) { - if tai == nil || len(tai) == 0 { - return nil, errors.New("Can't marshal uninitialized TrustAnchorIdentifier") + if tai.Issuer == nil || len(tai.Issuer) == 0 { + return nil, errors.New("can't marshal uninitialized TrustAnchorIdentifier") + } + batch := RelativeOID{} + err := batch.FromSegments([]uint32{tai.BatchNumber}) + if err != nil { + return nil, err } var b cryptobyte.Builder b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte(tai)) + b.AddBytes(tai.Issuer) + b.AddBytes(batch) }) return b.Bytes() } func (tai *TrustAnchorIdentifier) unmarshal(s *cryptobyte.String) error { - if !copyUint8LengthPrefixed(s, (*[]byte)(tai)) || len(*tai) == 0 { + var oidBytes []byte + if !copyUint8LengthPrefixed(s, &oidBytes) || len(oidBytes) == 0 { return ErrTruncated } cur := uint64(0) child := 0 - for i := 0; i < len(*tai); i++ { - if cur == 0 && (*tai)[i] == 0x80 { + for i := 0; i < len(oidBytes); i++ { + if cur == 0 && (oidBytes)[i] == 0x80 { return errors.New("TrustAnchorIdentifier: not normalized; starts with 0x80") } - cur = (cur << 7) | uint64((*tai)[i]&0x7f) + cur = (cur << 7) | uint64((oidBytes)[i]&0x7f) if cur > 0xffffffff { return fmt.Errorf("TrustAnchorIdentifier: overflow of sub-identifier %d", child) } - if (*tai)[i]&0x80 == 0 { + if (oidBytes)[i]&0x80 == 0 { cur = 0 child++ - } else if i == len(*tai)-1 { + } else if i == len(oidBytes)-1 { return errors.New("TrustAnchorIdentifier: ends on continuation") } } + oid := RelativeOID(oidBytes) + segments := oid.segments() + tai.BatchNumber = segments[len(segments)-1] + err := oid.FromSegments(segments[:len(segments)-1]) + if err != nil { + return err + } + tai.Issuer = oid return nil } + +func (oid *RelativeOID) Equal(rhs *RelativeOID) bool { + if rhs == nil { + return false + } + if len(*oid) == len(*rhs) { + for i, v := range *oid { + if v != (*rhs)[i] { + return false + } + } + return true + } + return false +} diff --git a/mtc_test.go b/mtc_test.go index 30e0e6b..90187d4 100644 --- a/mtc_test.go +++ b/mtc_test.go @@ -76,8 +76,10 @@ func createTestAssertion(i int, sub Subject) Assertion { } func createTestCA() *CAParams { + tai := RelativeOID{} + _ = tai.FromSegments([]uint32{10, 20, 300}) ret := CAParams{ - IssuerId: "example", + Issuer: tai, PublicKey: nil, StartTime: 0, BatchDuration: 1, @@ -319,36 +321,26 @@ func TestTAIParsing(t *testing.T) { text string hex string }{ - {"0", "0100"}, - {"32473", "0381fd59"}, - {"32473.1", "0481fd5901"}, + {"0", "00"}, + {"32473", "81fd59"}, + {"32473.1", "81fd5901"}, {"32473.4.40.400.4000.40000.400000.4000000.40000000.400000000.4000000000", - "2181fd59042883109f2082b84098b50081f492009389b40081bede88008ef3acd000"}, + "81fd59042883109f2082b84098b50081f492009389b40081bede88008ef3acd000"}, {"32473.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1", - "7c81fd5901010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"}, + "81fd5901010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"}, } { - var tai, tai2 TrustAnchorIdentifier - if err := tai.UnmarshalText([]byte(tc.text)); err != nil { + var oid RelativeOID + if err := oid.UnmarshalText([]byte(tc.text)); err != nil { t.Fatal(err) } - if tai.String() != tc.text { - t.Fatalf("%s ≠ %s", tc.text, tai.String()) + if oid.String() != tc.text { + t.Fatalf("%s ≠ %s", tc.text, oid.String()) } - bin2, err := tai.MarshalBinary() - if err != nil { - t.Fatal(err) - } - hex2 := hex.EncodeToString(bin2) + hex2 := hex.EncodeToString(oid) if hex2 != tc.hex { t.Fatalf("%s ≠ %s", tc.hex, hex2) } - if err := tai2.UnmarshalBinary(bin2); err != nil { - t.Fatal(err) - } - if !bytes.Equal(tai, tai2) { - t.Fatalf("%v ≠ %v", tai, tai2) - } } for _, tc := range []struct{ hex, errString string }{ @@ -367,12 +359,12 @@ func TestTAIParsing(t *testing.T) { } for _, tc := range []struct{ s, errString string }{ - {"12345678900", "TrustAnchorIdentifier: subidentifier 0: strconv.ParseUint: parsing \"12345678900\": value out of range"}, - {"1..1", "TrustAnchorIdentifier: subidentifier 1: strconv.ParseUint: parsing \"\": invalid syntax"}, - {"-1", "TrustAnchorIdentifier: subidentifier 0: strconv.ParseUint: parsing \"-1\": invalid syntax"}, + {"12345678900", "OID: subidentifier 0: strconv.ParseUint: parsing \"12345678900\": value out of range"}, + {"1..1", "OID: subidentifier 1: strconv.ParseUint: parsing \"\": invalid syntax"}, + {"-1", "OID: subidentifier 0: strconv.ParseUint: parsing \"-1\": invalid syntax"}, } { - var tai TrustAnchorIdentifier - if err := tai.UnmarshalText([]byte(tc.s)); err == nil || err.Error() != tc.errString { + var oid RelativeOID + if err := oid.UnmarshalText([]byte(tc.s)); err == nil || err.Error() != tc.errString { t.Fatalf("%s: %s ≠ %v", tc.s, tc.errString, err) } }