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
128 changes: 50 additions & 78 deletions note/note_cosigv1.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,30 +109,32 @@ func NewMLDSASignerFromCrypto(name string, signer crypto.Signer) (SubtreeSigner,
if _, err := c.Unmarshal(msg); err != nil {
return nil, err
}
return s.signSubtree(t, c.Origin, 0, c.Size, c.Hash)
sigRaw, err := s.signSubtree(t, c.Origin, 0, c.Size, c.Hash)
if err != nil {
return nil, err
}
// Note cosignatures must include the timestamp as prefix.
r := make([]byte, 0, timestampSize+len(sigRaw))
r = binary.BigEndian.AppendUint64(r, t)
r = append(r, sigRaw...)
return r, nil
}
s.signSubtree = func(timestamp uint64, logOrigin string, start, end uint64, root []byte) ([]byte, error) {
m, err := formatMLDSACosignatureV1(name, timestamp, logOrigin, start, end, root)
if err != nil {
return nil, err
}
sB, err := signer.Sign(nil, m, nil)
sigRaw, err := signer.Sign(nil, m, nil)
if err != nil {
return nil, err
}
// The signature itself is encoded as timestamp || signature.
sig := make([]byte, 0, timestampSize+mldsa.MLDSA44SignatureSize)
sig = binary.BigEndian.AppendUint64(sig, timestamp)
sig = append(sig, sB...)
return sig, nil
return sigRaw, nil
}
s.verifier = &subtreeVerifier{
name: name,
keyHash: s.hash,
verifyNote: func(msg, sig []byte) bool { return verifyMLDSACosigV1(pubKey, name)(msg, sig) },
verifySubtree: func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool {
return verifyMLDSACosigV1Subtree(pubKey, name)(logOrigin, start, end, hash, sig)
},
name: name,
keyHash: s.hash,
verifyNote: verifyMLDSACosigV1(pubKey, name),
verifySubtree: verifyMLDSACosigV1Subtree(pubKey, name),
}

return s, nil
Expand Down Expand Up @@ -160,10 +162,8 @@ func NewMLDSAVerifier(vkey string) (SubtreeVerifier, error) {
if err != nil {
return nil, err
}
v.verifyNote = func(msg, sig []byte) bool { return verifyMLDSACosigV1(pubKey, name)(msg, sig) }
v.verifySubtree = func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool {
return verifyMLDSACosigV1Subtree(pubKey, name)(logOrigin, start, end, hash, sig)
}
v.verifyNote = verifyMLDSACosigV1(pubKey, name)
v.verifySubtree = verifyMLDSACosigV1Subtree(pubKey, name)
return v, nil
}

Expand Down Expand Up @@ -365,30 +365,35 @@ func verifyEd25519CosigV1(key []byte) func(msg, sig []byte) bool {
// verifyMLDSACosigV1 returns a checkpoint-cosignature verifying function
// based on the provided key and cosigner name.
//
// Checkpoint signatures are simply subtree signatures over the range [0, size),
// so we parse the checkpoint and then use the subtree verifier to verify
// the signature as a subtree signature.
// Checkpoint signatures are simply subtree signatures over the range [0, size).
func verifyMLDSACosigV1(pubKey *mldsa.PublicKey, name string) func(msg, sig []byte) bool {
verifySubtree := verifyMLDSACosigV1Subtree(pubKey, name)
return func(msg, sig []byte) bool {
c := &log.Checkpoint{}
if _, err := c.Unmarshal(msg); err != nil {
return false
}
return verifySubtree(c.Origin, 0, c.Size, c.Hash, sig)
if len(sig) != timestampSize+mldsa.MLDSA44SignatureSize {
return false
}
timestamp := binary.BigEndian.Uint64(sig)
sig = sig[timestampSize:]
m, err := formatMLDSACosignatureV1(name, timestamp, c.Origin, 0, c.Size, c.Hash)
if err != nil {
return false
}
return mldsa.Verify(pubKey, m, sig, nil) == nil
}
}

// verifyMLDSACosigV1Subtree returns a subtree-cosignature verifying function
// based on the provided key and cosigner name.
// By definition, the timestamp used to verify is implicitly zero.
func verifyMLDSACosigV1Subtree(pubKey *mldsa.PublicKey, name string) func(logOrigin string, start, end uint64, root []byte, sig []byte) bool {
return func(logOrigin string, start, end uint64, root []byte, sig []byte) bool {
if len(sig) != timestampSize+mldsa.MLDSA44SignatureSize {
if len(sig) != mldsa.MLDSA44SignatureSize {
return false
}
t := binary.BigEndian.Uint64(sig)
sig = sig[timestampSize:]
m, err := formatMLDSACosignatureV1(name, t, logOrigin, start, end, root)
m, err := formatMLDSACosignatureV1(name, 0, logOrigin, start, end, root)
if err != nil {
return false
}
Expand Down Expand Up @@ -426,7 +431,7 @@ var (
errInvalidPayload = errors.New("invalid message payload")
)

func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin string, start, end uint64, hash []byte) ([]byte, error) {
func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin string, start, end uint64, root []byte) ([]byte, error) {
// SPEC: If start is not zero, timestamp MUST be zero.
if start > 0 && timestamp > 0 {
return nil, errInvalidTimestamp
Expand All @@ -441,7 +446,7 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s
if lo, lc := len(logOrigin), len(cosignerName); lo == 0 || lo > 255 || lc == 0 || lc > 255 {
return nil, errSignerID
}
if len(hash) != sha256.Size {
if len(root) != sha256.Size {
return nil, errInvalidPayload
}

Expand All @@ -454,8 +459,8 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s
// opaque log_origin<1..2^8-1>;
// uint64 start;
// uint64 end;
// uint8 hash[32];
// } cosigned_message;
// uint8 root_hash[32];
// } CosignedSubtree;
//
// See https://c2sp.org/tlog-cosignature for more details.

Expand All @@ -469,7 +474,7 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s
r.AddBytes([]byte(logOrigin))
r.AddUint64(start)
r.AddUint64(end)
r.AddBytes(hash)
r.AddBytes(root)
return r.Bytes()
}

Expand Down Expand Up @@ -513,7 +518,10 @@ var (
// provide access to a similarly capable verifier.
type SubtreeSigner interface {
note.Signer
// SignSubtree returns a note-style signature line over the subtree described by the provided arguments.
// SignSubtree returns a signature over the subtree described by the provided arguments.
// The returned signature is a []byte containing the signature value as outputted by the cosignature algorithm.
// Unlike a checkpoint cosignature, there is no timestamp, and the signature is not formatted
// as a note signature line.
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
Expand All @@ -532,28 +540,22 @@ 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(logOrigin string, start, end uint64, root []byte) ([]byte, error) {
stSig, err := s.signSubtree(0, logOrigin, start, end, root)
sig, 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
return sig, 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 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
// VerifySubtree verifies signatures over the provided subtree arguments.
// Expects that sig is a raw signature, as produced by SignSubtree, with no leading
// timestamp.
VerifySubtree(logOrigin string, start, end uint64, root []byte, sig []byte) bool
}

// subtreeVerifier implements the note.Verifier interface to facilitate cosigning operations
Expand All @@ -563,44 +565,14 @@ type subtreeVerifier struct {
name string
keyHash uint32
verifyNote func([]byte, []byte) bool
verifySubtree func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool
verifySubtree func(logOrigin string, start, end uint64, root []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(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)
func (v *subtreeVerifier) VerifySubtree(logOrigin string, start, end uint64, root []byte, sig []byte) bool {
return v.verifySubtree(logOrigin, start, end, root, sig)
}

// isValidName reports whether name is valid.
Expand Down
Loading
Loading