99 lines
2.9 KiB
Go
99 lines
2.9 KiB
Go
package signer
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"errors"
|
|
"testing"
|
|
|
|
"basicstack.de/proofedmesh/services/signing-service/internal/canonical"
|
|
)
|
|
|
|
func newKey(t *testing.T) ed25519.PrivateKey {
|
|
t.Helper()
|
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return priv
|
|
}
|
|
|
|
func TestInMemory_SignRoundtrip(t *testing.T) {
|
|
priv := newKey(t)
|
|
m := NewInMemory()
|
|
m.AddKey("test-key", priv)
|
|
|
|
stmt := Statement{
|
|
"_type": "https://in-toto.io/Statement/v1",
|
|
"predicateType": "https://basicstack.de/attestations/rate_change/v1",
|
|
"subject": []any{map[string]any{"name": "per_diem_rate/PDR-1", "digest": map[string]any{"sha256": "deadbeef"}}},
|
|
"predicate": map[string]any{"change_kind": "create"},
|
|
}
|
|
s, err := SignStatement(context.Background(), m, "test-key", stmt)
|
|
if err != nil {
|
|
t.Fatalf("sign: %v", err)
|
|
}
|
|
if s.Envelope.PayloadType != "application/vnd.in-toto+json" {
|
|
t.Errorf("payload type: %q", s.Envelope.PayloadType)
|
|
}
|
|
if len(s.Envelope.Signatures) != 1 {
|
|
t.Fatalf("signatures: %d", len(s.Envelope.Signatures))
|
|
}
|
|
if s.Envelope.Signatures[0].KeyID != "test-key" {
|
|
t.Errorf("keyid: %q", s.Envelope.Signatures[0].KeyID)
|
|
}
|
|
|
|
// Verify the signature verifies against the public key.
|
|
pub := priv.Public().(ed25519.PublicKey)
|
|
payloadBytes, err := base64.RawURLEncoding.DecodeString(s.Envelope.Payload)
|
|
if err != nil {
|
|
t.Fatalf("decode payload: %v", err)
|
|
}
|
|
pae := encodePAE("application/vnd.in-toto+json", payloadBytes)
|
|
sig, err := base64.RawURLEncoding.DecodeString(s.Envelope.Signatures[0].Sig)
|
|
if err != nil {
|
|
t.Fatalf("decode sig: %v", err)
|
|
}
|
|
if !ed25519.Verify(pub, pae, sig) {
|
|
t.Fatal("signature does not verify against public key")
|
|
}
|
|
|
|
// content_hash should be reproducible from the envelope.
|
|
envBytes, _ := canonical.Marshal(s.Envelope)
|
|
expected := "sha256:" + canonical.SHA256Hex(envBytes)
|
|
if s.ContentHash != expected {
|
|
t.Errorf("content_hash mismatch:\n got: %s\nwant: %s", s.ContentHash, expected)
|
|
}
|
|
}
|
|
|
|
func TestInMemory_UnknownKeyRef(t *testing.T) {
|
|
m := NewInMemory()
|
|
_, _, err := m.Sign(context.Background(), "nope", []byte("x"))
|
|
if !errors.Is(err, ErrUnknownKey) {
|
|
t.Fatalf("expected ErrUnknownKey, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOpenBao_StubReturnsNotWired(t *testing.T) {
|
|
_, _, err := OpenBao{}.Sign(context.Background(), "k", []byte("x"))
|
|
if !errors.Is(err, ErrNotWired) {
|
|
t.Fatalf("expected ErrNotWired, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEncodePAE_FormatIsExact(t *testing.T) {
|
|
got := encodePAE("application/vnd.in-toto+json", []byte("hello"))
|
|
// "application/vnd.in-toto+json" is 28 bytes, "hello" is 5 bytes.
|
|
want := []byte("DSSEv1 28 application/vnd.in-toto+json 5 hello")
|
|
if string(got) != string(want) {
|
|
t.Errorf("PAE mismatch:\n got: %q\nwant: %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDecodeHexEd25519Key_RejectsWrongLength(t *testing.T) {
|
|
if _, err := DecodeHexEd25519Key("deadbeef"); err == nil {
|
|
t.Fatal("expected error for short key")
|
|
}
|
|
}
|