package authn import ( "crypto/ed25519" "crypto/sha256" "encoding/base64" "testing" "github.com/stretchr/testify/require" ) func TestVerifyRequestSignature(t *testing.T) { t.Parallel() clientPrivateKey := newTestPrivateKey("primary") clientPublicKey := clientPrivateKey.Public().(ed25519.PublicKey) otherPrivateKey := newTestPrivateKey("other") fields := RequestSigningFields{ ProtocolVersion: "v1", DeviceSessionID: "device-session-123", MessageType: "fleet.move", TimestampMS: 123456789, RequestID: "request-123", PayloadHash: mustSHA256([]byte("payload")), } signature := ed25519.Sign(clientPrivateKey, BuildRequestSigningInput(fields)) tests := []struct { name string clientPublicKey string signature []byte fields RequestSigningFields wantErr error }{ { name: "valid signature", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature, fields: fields, }, { name: "message type change rejects signature", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature, fields: func() RequestSigningFields { mutated := fields mutated.MessageType = "fleet.attack" return mutated }(), wantErr: ErrInvalidRequestSignature, }, { name: "request id change rejects signature", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature, fields: func() RequestSigningFields { mutated := fields mutated.RequestID = "request-456" return mutated }(), wantErr: ErrInvalidRequestSignature, }, { name: "payload hash change rejects signature", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature, fields: func() RequestSigningFields { mutated := fields mutated.PayloadHash = mustSHA256([]byte("other")) return mutated }(), wantErr: ErrInvalidRequestSignature, }, { name: "wrong key rejects signature", clientPublicKey: base64.StdEncoding.EncodeToString(otherPrivateKey.Public().(ed25519.PublicKey)), signature: signature, fields: fields, wantErr: ErrInvalidRequestSignature, }, { name: "bit flipped signature rejects", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: func() []byte { corrupted := append([]byte(nil), signature...) corrupted[0] ^= 0xff return corrupted }(), fields: fields, wantErr: ErrInvalidRequestSignature, }, { name: "invalid signature length rejects", clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey), signature: signature[:len(signature)-1], fields: fields, wantErr: ErrInvalidRequestSignature, }, { name: "invalid base64 public key rejects", clientPublicKey: "%%%not-base64%%%", signature: signature, fields: fields, wantErr: ErrInvalidClientPublicKey, }, { name: "invalid public key length rejects", clientPublicKey: base64.StdEncoding.EncodeToString([]byte("short")), signature: signature, fields: fields, wantErr: ErrInvalidClientPublicKey, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() err := VerifyRequestSignature(tt.clientPublicKey, tt.signature, tt.fields) if tt.wantErr == nil { require.NoError(t, err) return } require.ErrorIs(t, err, tt.wantErr) }) } } func newTestPrivateKey(label string) ed25519.PrivateKey { seed := sha256.Sum256([]byte("gateway-authn-signature-test-" + label)) return ed25519.NewKeyFromSeed(seed[:]) }