diff --git a/note/note.go b/note/note.go index adbadf8..149e2c2 100644 --- a/note/note.go +++ b/note/note.go @@ -6,11 +6,12 @@ package note import ( "crypto/ed25519" + "encoding/base64" "errors" "fmt" - "strings" "strconv" - "encoding/base64" + "strings" + "golang.org/x/mod/sumdb/note" ) @@ -29,13 +30,13 @@ const ( ) var ( - errSignerID = errors.New("malformed signer id") - errSignerAlg = errors.New("unknown signer algorithm") - errVerifierID = errors.New("malformed verifier id") - errVerifierAlg = errors.New("unknown verifier algorithm") - errInvalidHash = errors.New("invalid key hash") - errMalformedSig = errors.New("malformed signature") - errSignerHash = errors.New("invalid verifier hash") + errSignerID = errors.New("malformed signer id") + errSignerAlg = errors.New("unknown signer algorithm") + errVerifierID = errors.New("malformed verifier id") + errVerifierAlg = errors.New("unknown verifier algorithm") + errInvalidHash = errors.New("invalid key hash") + errMalformedSig = errors.New("malformed signature") + errSignerHash = errors.New("invalid verifier hash") ) // NewSigner returns a new Signer for keys with the following algorithms: @@ -171,5 +172,3 @@ func (v *verifier) KeyHash() uint32 { func (v *verifier) Verify(msg, sig []byte) bool { return v.v(msg, sig) } - - diff --git a/note/note_cosigv1.go b/note/note_cosigv1.go index db91309..4cd3fb0 100644 --- a/note/note_cosigv1.go +++ b/note/note_cosigv1.go @@ -13,6 +13,8 @@ import ( "encoding/binary" "errors" "fmt" + "math" + "math/bits" "strconv" "strings" "time" @@ -128,7 +130,7 @@ func NewMLDSASignerFromCrypto(name string, signer crypto.Signer) (SubtreeSigner, name: name, keyHash: s.hash, verifyNote: func(msg, sig []byte) bool { return verifyMLDSACosigV1(pubKey, name)(msg, sig) }, - verifySubtree: func(timestamp uint64, logOrigin string, start, end uint64, hash []byte, sig []byte) bool { + verifySubtree: func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool { return verifyMLDSACosigV1Subtree(pubKey, name)(logOrigin, start, end, hash, sig) }, } @@ -159,7 +161,7 @@ func NewMLDSAVerifier(vkey string) (SubtreeVerifier, error) { return nil, err } v.verifyNote = func(msg, sig []byte) bool { return verifyMLDSACosigV1(pubKey, name)(msg, sig) } - v.verifySubtree = func(timestamp uint64, logOrigin string, start, end uint64, hash []byte, sig []byte) bool { + v.verifySubtree = func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool { return verifyMLDSACosigV1Subtree(pubKey, name)(logOrigin, start, end, hash, sig) } return v, nil @@ -314,19 +316,34 @@ func VKeyToCosignatureV1(vkey string) (string, error) { return fmt.Sprintf("%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(pubKey)), nil } -// CoSigV1Timestamp extracts the embedded timestamp from a CoSigV1 signature. +// CoSigV1Timestamp is deprecated. +// +// Deprecated: use CosignatureTimestamp. func CoSigV1Timestamp(s note.Signature) (time.Time, error) { + t, err := CosignatureTimestamp(s) + return time.Unix(int64(t), 0).UTC(), err +} + +// CosignatureTimestamp returns the timestamp embedded in a signature from a CosignatureV1 signer. +func CosignatureTimestamp(s note.Signature) (uint64, error) { r, err := base64.StdEncoding.DecodeString(s.Base64) if err != nil { - return time.UnixMilli(0), errMalformedSig + return 0, errMalformedSig } const minSigSize = 64 // min(ed25519.SignatureSize, mldsa.MLDSA44SignatureSize) if len(r) < keyHashSize+timestampSize+minSigSize { - return time.UnixMilli(0), errVerifierAlg + return 0, errVerifierAlg } r = r[keyHashSize:] // Skip the hash // Next 8 bytes are the timestamp as Unix seconds-since-epoch: - return time.Unix(int64(binary.BigEndian.Uint64(r)), 0), nil + t := binary.BigEndian.Uint64(r) + + // SPEC: MUST NOT exceed 2^63-1 + if t > math.MaxInt64 { + return 0, errInvalidTimestamp + } + + return t, nil } // verifyEd25519CosigV1 returns a verify function based on key. @@ -405,14 +422,28 @@ func formatEd25519CosignatureV1(t uint64, msg []byte) ([]byte, error) { return fmt.Appendf(nil, "cosignature/v1\ntime %d\n%s", t, msg), nil } +var ( + errInvalidPayload = errors.New("invalid message payload") +) + func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin string, start, end uint64, hash []byte) ([]byte, error) { // SPEC: If start is not zero, timestamp MUST be zero. if start > 0 && timestamp > 0 { return nil, errInvalidTimestamp } - if len(logOrigin) > 255 || len(cosignerName) > 255 { + // SPEC: MUST NOT exceed 2^63-1 + if timestamp > math.MaxInt64 { + return nil, errInvalidTimestamp + } + if !isSubtreeValid(start, end) { + return nil, errInvalidPayload + } + if lo, lc := len(logOrigin), len(cosignerName); lo == 0 || lo > 255 || lc == 0 || lc > 255 { return nil, errSignerID } + if len(hash) != sha256.Size { + return nil, errInvalidPayload + } // The signed message is a binary TLS presentation encoding of the // following structure: @@ -429,7 +460,7 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s // See https://c2sp.org/tlog-cosignature for more details. const label = "subtree/v1\n\x00" - r := cryptobyte.NewFixedBuilder(make([]byte, 0, len(label)+(2+len(cosignerName))+8+(2+len(logOrigin))+8+8+32)) + r := cryptobyte.NewFixedBuilder(make([]byte, 0, len(label)+(1+len(cosignerName))+8+(1+len(logOrigin))+8+8+32)) r.AddBytes([]byte(label)) r.AddUint8(uint8(len(cosignerName))) r.AddBytes([]byte(cosignerName)) @@ -442,6 +473,38 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s return r.Bytes() } +func isSubtreeValid(start, end uint64) bool { + if start > end { + return false + } + if start == 0 { + return true + } + if start == end { + return true + } + + l := end - start + if l > math.MaxInt64 { + return false + } + + if bc := bitCeil(l); start&(bc-1) != 0 { + return false + } + + return true +} + +// bitCeil returns the smallest power of 2 larger than or equal to n. +// MUST NOT be used with n larger than uint64(1)<<63. +func bitCeil(n uint64) uint64 { + if n <= 1 { + return 1 + } + return uint64(1) << bits.Len64(n-1) +} + var ( errInvalidTimestamp = errors.New("invalid timestamp") ) @@ -450,7 +513,9 @@ var ( // provide access to a similarly capable verifier. type SubtreeSigner interface { note.Signer - SignSubtree(timestamp uint64, logOrigin string, start, end uint64, root []byte) ([]byte, error) + // SignSubtree returns a note-style signature line over the subtree described by the provided arguments. + SignSubtree(logOrigin string, start, end uint64, root []byte) ([]byte, error) + // Verifier returns a SubtreeVerifier instance which is able to verify signatures created by this signer instance. Verifier() SubtreeVerifier } @@ -466,15 +531,29 @@ type subtreeSigner struct { func (s *subtreeSigner) Name() string { return s.name } func (s *subtreeSigner) KeyHash() uint32 { return s.hash } func (s *subtreeSigner) Sign(msg []byte) ([]byte, error) { return s.signNote(msg) } -func (s *subtreeSigner) SignSubtree(timestamp uint64, logOrigin string, start, end uint64, root []byte) ([]byte, error) { - return s.signSubtree(timestamp, logOrigin, start, end, root) +func (s *subtreeSigner) SignSubtree(logOrigin string, start, end uint64, root []byte) ([]byte, error) { + stSig, err := s.signSubtree(0, logOrigin, start, end, root) + if err != nil { + return nil, err + } + // signSubtree gives us back timestamp||signature, so we need to prepend keyhash + // and format into a note signature. + sig := make([]byte, 0, keyHashSize+len(stSig)) + sig = binary.BigEndian.AppendUint32(sig, s.KeyHash()) + sig = append(sig, stSig...) + + res := fmt.Appendf(nil, "— %s %s\n", s.name, base64.StdEncoding.EncodeToString(sig)) + return res, nil } func (s *subtreeSigner) Verifier() SubtreeVerifier { return s.verifier } // SubtreeVerifier is a verifier that supports the verification of subtree signatures. type SubtreeVerifier interface { note.Verifier - VerifySubtree(timestamp uint64, logOrigin string, start, end uint64, hash []byte, sig []byte) bool + // VerifySubtree verifies note-formatted signatures over the provided subtree arguments. + // Note that sig MUST be a valid note-formatted signature line, starting with "— " and ending + // with a newline. + VerifySubtree(logOrigin string, start, end uint64, hash []byte, sig []byte) bool } // subtreeVerifier implements the note.Verifier interface to facilitate cosigning operations @@ -484,14 +563,44 @@ type subtreeVerifier struct { name string keyHash uint32 verifyNote func([]byte, []byte) bool - verifySubtree func(timestamp uint64, logOrigin string, start, end uint64, hash []byte, sig []byte) bool + verifySubtree func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool } func (v *subtreeVerifier) Name() string { return v.name } func (v *subtreeVerifier) KeyHash() uint32 { return v.keyHash } func (v *subtreeVerifier) Verify(msg, sig []byte) bool { return v.verifyNote(msg, sig) } -func (v *subtreeVerifier) VerifySubtree(timestamp uint64, logOrigin string, start, end uint64, hash []byte, sig []byte) bool { - return v.verifySubtree(timestamp, logOrigin, start, end, hash, sig) +func (v *subtreeVerifier) VerifySubtree(logOrigin string, start, end uint64, hash []byte, sig []byte) bool { + sigLine, ok := strings.CutPrefix(string(sig), "— ") + if !ok { + return false + } + sigLine, ok = strings.CutSuffix(sigLine, "\n") + if !ok { + return false + } + name, sigB64, ok := strings.Cut(sigLine, " ") + if !ok { + return false + } + sigRaw, err := base64.StdEncoding.DecodeString(sigB64) + if err != nil || len(sigRaw) != keyHashSize+timestampSize+mldsa.MLDSA44SignatureSize { + return false + } + if name != v.name || binary.BigEndian.Uint32(sigRaw[:keyHashSize]) != v.keyHash { + return false + } + sigRaw = sigRaw[keyHashSize:] + t := binary.BigEndian.Uint64(sigRaw[:timestampSize]) + // We would expect t == 0 here, IFF the signature was made by our SignSubtree implementation, however: + // SPEC: Semantically, a v1 subtree cosignature is a statement that the subtree with the specified root hash is consistent + // with all other historical views observed by the cosigner of the log identified by the origin line. + // If the timestamp is not zero, it is also a statement that, as of the specified time, this is the largest consistent + // tree the cosigner has observed for the log. + // So we'll handle that here so as to not fail verification of technically correct signatures. + if start != 0 && t != 0 { + return false + } + return v.verifySubtree(logOrigin, start, end, hash, sigRaw) } // isValidName reports whether name is valid. diff --git a/note/note_cosigv1_test.go b/note/note_cosigv1_test.go index bbad4ea..f9c7c63 100644 --- a/note/note_cosigv1_test.go +++ b/note/note_cosigv1_test.go @@ -9,6 +9,8 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/sha256" + "encoding/base64" "strings" "testing" "time" @@ -79,7 +81,8 @@ func TestFormatMLDSASignatureV1(t *testing.T) { }, } { t.Run(test.name, func(t *testing.T) { - _, err := formatMLDSACosignatureV1(test.cosignerName, 0, test.logOrigin, 0, 0, []byte{}) + root := sha256.Sum256([]byte("blah")) + _, err := formatMLDSACosignatureV1(test.cosignerName, 0, test.logOrigin, 0, 0, root[:]) if gotErr := err != nil; gotErr != test.wantErr { t.Fatalf("formatMLDSACosignatureV1: got %v", err) } @@ -232,7 +235,7 @@ func TestCoSigV1Timestamp(t *testing.T) { { name: "works", sig: note.Signature{Base64: "ZGhGuQAAAABm/qTPeyKXD+R2rzyQsxPiP8mXum7qq/iF0u4vanlqJyocWODBt97w9uL+8qT7S5gxEHWWOworDcFiEBYJXORmnFBOBA=="}, - wantTime: time.Unix(1727964367, 0), + wantTime: time.Unix(1727964367, 0).UTC(), }, { name: "wrong type of signature", sig: note.Signature{Base64: "eQjRQm6eSKzFoiYalgwCPXu2y3ijtg68is9M46JKxuZB+dRfTmeQeDBoXnvxZx2ugnkyV+MUMLXpWs1hPb/W/4xkNQY="}, @@ -251,7 +254,42 @@ func TestCoSigV1Timestamp(t *testing.T) { return } if gotTime != test.wantTime { - t.Fatalf("got time %v, want %v", gotTime.UnixMilli(), test.wantTime.UnixMilli()) + t.Fatalf("got time %v, want %v", gotTime, test.wantTime) + } + }) + } +} + +func TestCosignatureTimestamp(t *testing.T) { + for _, test := range []struct { + name string + sig note.Signature + wantErr bool + wantTime uint64 + }{ + { + name: "works", + sig: note.Signature{Base64: "ZGhGuQAAAABm/qTPeyKXD+R2rzyQsxPiP8mXum7qq/iF0u4vanlqJyocWODBt97w9uL+8qT7S5gxEHWWOworDcFiEBYJXORmnFBOBA=="}, + wantTime: 1727964367, + }, { + name: "wrong type of signature", + sig: note.Signature{Base64: "eQjRQm6eSKzFoiYalgwCPXu2y3ijtg68is9M46JKxuZB+dRfTmeQeDBoXnvxZx2ugnkyV+MUMLXpWs1hPb/W/4xkNQY="}, + wantErr: true, + }, { + name: "gibberish", + sig: note.Signature{Base64: "5%/$!\n 2"}, + wantErr: true, + }, + } { + t.Run(test.name, func(t *testing.T) { + gotTime, err := CosignatureTimestamp(test.sig) + if gotErr := err != nil; gotErr != test.wantErr { + t.Fatalf("got error %q, want err: %v", err, test.wantErr) + } else if gotErr { + return + } + if gotTime != test.wantTime { + t.Fatalf("got time %v, want %v", gotTime, test.wantTime) } }) } @@ -325,48 +363,28 @@ func TestSubtreeRoundtrip(t *testing.T) { if _, err := rand.Read(root); err != nil { t.Fatal(err) } - timestamp := uint64(time.Now().Unix()) - sig, err := signer.SignSubtree(timestamp, origin, start, end, root) + sig, err := signer.SignSubtree(origin, start, end, root) if err != nil { t.Fatal(err) } - if !verifier.VerifySubtree(timestamp, origin, start, end, root, sig) { - t.Error("Failed to verify valid subtree signature") + if !verifier.VerifySubtree(origin, start, end, root, sig) { + t.Fatalf("Failed to verify valid subtree signature %q", sig) } // Test failure cases wrongRoot := make([]byte, 32) wrongRoot[0] = 1 - if verifier.VerifySubtree(timestamp, origin, start, end, wrongRoot, sig) { + if verifier.VerifySubtree(origin, start, end, wrongRoot, sig) { t.Error("VerifySubtree succeeded with wrong root") } - if verifier.VerifySubtree(timestamp, "wrong origin", start, end, root, sig) { + if verifier.VerifySubtree("wrong origin", start, end, root, sig) { t.Error("VerifySubtree succeeded with wrong origin") } } -func TestMLDSAInvalidTimestamp(t *testing.T) { - skey, _ := mustGenerateMLDSAKey(t, "mldsa") - signer, err := NewMLDSASigner(skey) - if err != nil { - t.Fatal(err) - } - - origin := "test-log" - var start uint64 = 10 // > 0 - var end uint64 = 20 - root := make([]byte, 32) - timestamp := uint64(time.Now().Unix()) // > 0 - - _, err = signer.SignSubtree(timestamp, origin, start, end, root) - if err == nil { - t.Error("Expected error for invalid timestamp (start > 0 && timestamp > 0), got nil") - } -} - func TestGenerateMLDSAKey(t *testing.T) { for _, test := range []struct { name string @@ -470,21 +488,40 @@ func TestMLDSASignerFromCrypto(t *testing.T) { if _, err := rand.Read(root); err != nil { t.Fatal(err) } - timestamp := uint64(time.Now().Unix()) - - sig, err := signer.SignSubtree(timestamp, origin, start, end, root) + sig, err := signer.SignSubtree(origin, start, end, root) if err != nil { t.Fatalf("SignSubtree: %v", err) } verifier := signer.Verifier() - if !verifier.VerifySubtree(timestamp, origin, start, end, root, sig) { - t.Error("VerifySubtree failed") + if !verifier.VerifySubtree(origin, start, end, root, sig) { + t.Fatal("Failed to verify valid subtree signature") } }) } } +func TestMLDSAVerifyTorchwood(t *testing.T) { + // Key & Signature generated by filippo.io/torchwood + vkey := "witness.example/w1+7118e8f9+BhQzSuGTb/4Lcu4MREKut7NjCc8jQ0fjuNpEmJwKF9HV5lt3yF7H5jy+LcLOxjh/n4Gud7NOdRP3KpCH055x6Ntr4n6pZlJ+kmvZTJMgs5ygaJVf6q9zdhykYIPQz4fp6qm0fDXlVBqLVti/3zS0OiY5Kqbi0m55WZ9pnUnsiZT6cckK9FoWfXb1UHTsTgz+Uk+kQr4JHdJ/c5t9V6C0WE8JZMiVT0ljBbfP6VACzuEJmc+zbF8Cbd8Mc31PkTR3YWFok3m4YagXfDlzegsE9jg0qWyTWDDkg+rcrcyCMpPam8LsWxQZXZ2O3w9aDbwKB70URNreL/B6jNl95TXJjtEwmspLPxZwwPI8dE7lJbe9N2X0jQQbpcRvm46iLbZO+LdjblTMHfKgbCqgWej/n3QexrovbetYvrNR2qa7BMJk06PDDy9G8EvtNO6xhVp0DG8z4VkIx+Og9eqk3USAg4bKmN1ifuEeMcnlxvvPwnXjMp6kmmhDEsu1Qu4SgEFOG2PcUKdoaXlIDNADmJQHqeiA+JYhjvyTHDM609qpEkHsRYLbxGidKZtaZptOLEL7sLxIzVQMv07xLw2q9qRFxx3yM8JbhJGg5rP28Xhl+3KcGNXy6Ydxuv3MXYCohjwSqAjOdoxs/FzV10HokFOY60vDQLtMUhby7Ob5VviG6+K5tu6hZogYfme3bzZDLO7VMyHMLKKTjfwJS9p1cagSsbqg5MJucfCfXJAw2vWb8n6eUA6qroBLgY9wjkBg06JAoBv85B7caOBjTitplbXk3pfQAzEE9yKEfSDjEfeVOSrBHK3zJQzw0q8RHSqYs+J7AnFgHxZ5IP5TUWkI/jgg3mdxpbaJ/YJ56uNCgFopQZihOCRVc/RFT53Zb1DXIRgqoP9VTIV3l03VPB2NrH49bEAzsCbNiL4NwLchYxoBZkG4Qp/GpTrjCDoDGtR36JipKbmMforXsz5Oh9Yul8s9p1apJXeSCuGsu5OgRBMXjvUoY9p/GFXHEPd6SPEAtDJfBwC5aWDgl4YC5cxKVzs/+xOvzyXmUg7pxtcJ4vIlWmBrmLb4PGVmPurJwx6BcOp7JRcyHCpycAfGMQ7xsBwafuiVCBQHGJhuDKETJa3MAa4H1uTB7GCiKuT8OohZ2LE/395+sftoy/yrX0H6HdCp+QEQ9ZOx0hbVLBOoAY6qVL1bgEBy/YrHgkCxh2lEWdw+9zb6yLWZI6ONX4mBcj3a5dDYfkvm5gAU4MXGAEWnSNeN9+MkylMQjUM5G2Cn7CTrA7IZ23EZ0bGrRMC7PERKbSj2pITgWqxJWH5l2IyK16cviUaXwGaZg8T8t2p77HtccGUodm6rWS0zBhzEyFajBzSi36b9aa3uDeFjsoEonNUevKg/67jcq5Ua0NWqJ80c5q7glj4wWOMJiDHAFh56UzTPGh1zvndQI4ZxuGHRRgOrTiJ4w3llb9EbsEcTlvFOZzjXrZYfMKTdapHV+5Ykok0+avKNnaOPq9pvtvNDx524IGtQCH9i4RjfefsPsxnloXtqYvoV6O8dCiQHn4jBOECYdqA2ZjYVQ/oPNBt5n5TfNJgLBFokcAKVyrTkWrDXi1OHmw6vEtQJLSv3oqchEYIGXWaEi5/OCtruWfALpEl0rifAMgVhhJp2gL+GZU5c3EGOEwOoQQMKDRdYayEWovpu1KIeOMXaJcDle6hCZEseFxXFDg40MeCdXFyv+Ib55h5uJJDxXdiRzQxdKX+deoY=" + + sig := []byte("— witness.example/w1 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\n") + + root, _ := base64.StdEncoding.DecodeString("E7ERCtFloiK1fdb+B6/dLG0pM1nmIR0ETKBSauCjtVw=") + + start, end := uint64(8), uint64(13) + origin := "example.com/log" + + v, err := NewMLDSAVerifier(vkey) + if err != nil { + t.Fatalf("Invalid vkey: %v", err) + } + + if !v.VerifySubtree(origin, start, end, root, sig) { + t.Fatal("Failed to verify valid subtree signature") + } +} + func mustMLDSASigner(t *testing.T) crypto.Signer { t.Helper() mldsaK, err := mldsa.GenerateKey(mldsa.MLDSA44()) diff --git a/note/note_test.go b/note/note_test.go index dc49802..fd380f3 100644 --- a/note/note_test.go +++ b/note/note_test.go @@ -5,8 +5,11 @@ package note import ( + "crypto/sha256" + "encoding/base64" "fmt" "testing" + "golang.org/x/mod/sumdb/note" ) @@ -22,9 +25,9 @@ func TestSignerVerifier(t *testing.T) { for _, test := range []struct { name string - s string - v string - } { + s string + v string + }{ {name: "note", s: ns, v: nv}, {name: "mldsa", s: ms, v: mv}, } { @@ -37,7 +40,8 @@ func TestSignerVerifier(t *testing.T) { if err != nil { t.Fatalf("NewVerifier: %v", err) } - checkRoundTrip(t, fmt.Sprintf("%s\n0\nblah\n", test.name), ns, nv) + root := sha256.Sum256([]byte("blah")) + checkRoundTrip(t, fmt.Sprintf("%s\n0\n%s\n", test.name, base64.StdEncoding.EncodeToString(root[:])), ns, nv) }) } } @@ -50,7 +54,7 @@ func checkRoundTrip(t *testing.T, msg string, s note.Signer, v note.Verifier) { } n, err := note.Open(signed, note.VerifierList(v)) if err != nil { - t.Errorf("%s failed to open: %v", msg, err) + t.Fatalf("%s failed to open: %v", msg, err) } if n.Text != msg { t.Errorf("got %q want %q", n.Text, msg)