Artifact 3dcd0ccda51a4543616cbdadbb54b9adc104d20b8d787d1a6156f8ab6eddbf81:
- File
internal/clearsign/noverifier_test.go
— part of check-in
[a87790f468]
at
2026-10-26 12:45:46
on branch main
— feat(sshverify): make in-process SSH signature verification opt-in
internal/clearsign keeps the SSH envelope, SSHSIG parsing, namespace, hash and SHA-1 checks, none of which need cryptography, and hands the key parse and signature check to an SSHVerifier that the new public package sshverify registers from init. The library therefore no longer links golang.org/x/crypto/ssh and the ECDSA, P-256, Ed25519 and RSA code it pulls in; html-artifact-publisher's TinyGo engine drops from 1,347,569 to 1,093,647 bytes gzipped (3,777,936 to 3,208,411 raw).
Without the verifier nothing is accepted unchecked: a well-formed SSH signature is ErrSSHVerifierUnavailable, which wraps ErrCannotVerify, so a seed under an SSH horizon-seed-key is refused, the held one is kept, and the sync or rebuild fails naming the package to import. Malformed signatures are still bad. The cli package (and so cmd/libfossil) and examples/tinygo import sshverify; a go list guard keeps x/crypto/ssh out of the root and db closures. (user: [email protected], size: 2817) [annotate] [blame] [check-ins using] [more...]
//go:build !js && !windows package clearsign import ( "context" "errors" "strings" "testing" ) // TestSSHVerifyWithoutVerifierFailsClosed: this test binary links no SSH // verifier, so a well-formed SSH signature by the right key cannot be // judged. Verify must say so, as ErrCannotVerify, never pass it, and never // call it bad; an unsigned artifact is still ErrUnsigned. func TestSSHVerifyWithoutVerifierFailsClosed(t *testing.T) { if sshVerifier != nil { t.Fatal("an SSH verifier is registered in clearsign's own tests") } for _, namespace := range []string{SSHNamespace, SeedNamespace} { t.Run(namespace, func(t *testing.T) { checkin, pub := sshKey(t, "ed25519") signer := &Signer{Command: strings.Replace(checkin.Command, "-n "+SSHNamespace, "-n "+namespace, 1), Namespace: namespace} signed := sign(t, signer, manifest) key := mustKey(t, pub) err := key.Verify(context.Background(), namespace, signed) if !errors.Is(err, ErrSSHVerifierUnavailable) || !errors.Is(err, ErrCannotVerify) || errors.Is(err, ErrBadSignature) { t.Fatalf("Verify with no verifier = %v, want ErrSSHVerifierUnavailable", err) } if !strings.Contains(err.Error(), "go-libfossil/sshverify") { t.Errorf("error %q does not name the package to import", err) } if err := key.Verify(context.Background(), namespace, manifest); err != ErrUnsigned { t.Fatalf("unsigned: Verify = %v, want ErrUnsigned", err) } }) } } // TestSSHVerifyWithoutVerifierStillRefusesTheMalformed: what needs no // cryptography to refuse (another namespace, a broken envelope) is refused // as bad whether or not a verifier is linked. func TestSSHVerifyWithoutVerifierStillRefusesTheMalformed(t *testing.T) { signer, pub := sshKey(t, "ed25519") key := mustKey(t, pub) signed := sign(t, signer, manifest) truncated := signed[:len(signed)-len(sshSigEnd)] for name, c := range map[string]struct { namespace string signed []byte }{ "namespace": {SeedNamespace, signed}, "armor": {SSHNamespace, truncated}, } { err := key.Verify(context.Background(), c.namespace, c.signed) if !errors.Is(err, ErrBadSignature) || errors.Is(err, ErrCannotVerify) { t.Errorf("%s: Verify = %v, want ErrBadSignature", name, err) } } } // TestParseKeyWithoutVerifier: an SSH key line is still read, and refused // when it is not one key, with no verifier linked. func TestParseKeyWithoutVerifier(t *testing.T) { _, pub := sshKey(t, "ecdsa") if k, err := ParseKey(pub); err != nil || k.String() != pub { t.Fatalf("ParseKey(%q) = %v, %v", pub, k, err) } fields := strings.Fields(pub) for _, s := range []string{ pub + "\n" + pub, "ssh-rsa " + fields[1], fields[0], "ssh-ed25519 AAAA", } { if _, err := ParseKey(s); err == nil { t.Errorf("ParseKey(%q) succeeded", s) } } }