diff --git a/crypto/identity.go b/crypto/identity.go index 6e969f2..c9bc6c5 100644 --- a/crypto/identity.go +++ b/crypto/identity.go @@ -34,8 +34,14 @@ func (id *Identity) Sign(message []byte) []byte { return ed25519.Sign(id.PrivateKey, message) } -// Verify checks a signature against the public key. +// Verify checks a signature against the public key. A public key that is not +// exactly ed25519.PublicKeySize bytes is rejected rather than passed to +// ed25519.Verify, which panics on a wrong-length key — an attacker-supplied +// key reaches this from unauthenticated message paths. func Verify(publicKey ed25519.PublicKey, message, signature []byte) bool { + if len(publicKey) != ed25519.PublicKeySize { + return false + } return ed25519.Verify(publicKey, message, signature) } diff --git a/crypto/zz_verify_badkey_test.go b/crypto/zz_verify_badkey_test.go new file mode 100644 index 0000000..34ff58a --- /dev/null +++ b/crypto/zz_verify_badkey_test.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +package crypto + +import ( + "crypto/ed25519" + "testing" +) + +func TestVerifyRejectsWrongLengthKeyWithoutPanic(t *testing.T) { + id, err := GenerateIdentity() + if err != nil { + t.Fatal(err) + } + msg := []byte("challenge") + sig := id.Sign(msg) + + for _, n := range []int{0, 1, 5, 31, 33, 64} { + bad := make([]byte, n) + if Verify(bad, msg, sig) { + t.Fatalf("Verify accepted a %d-byte public key", n) + } + } + + if Verify(nil, msg, sig) { + t.Fatal("Verify accepted a nil public key") + } + + if !Verify(id.PublicKey, msg, sig) { + t.Fatal("Verify rejected a valid signature") + } + if len(id.PublicKey) != ed25519.PublicKeySize { + t.Fatalf("unexpected key size %d", len(id.PublicKey)) + } +}