Files
vault/internal/pqcrypt/suite_test.go
T
Andy Dixon d95f2d04a4 Initial vault: single-binary post-quantum file vault
Strip Fury back to its vault functionality in one `vault` command:
genkey, init, add (file or directory, optional folder inside the vault),
list, get, mv and rm.

The hybrid FrodoKEM-640 + ML-KEM-768 encryption core is kept under
internal/pqcrypt. Signing, policies, profiles, backups, SSH remotes,
FIDO2, agents, provenance and compression are removed.
2026-09-26 14:05:10 +01:00

136 lines
3.1 KiB
Go

// Copyright 2026 The fury Authors.
// SPDX-License-Identifier: GPL-3.0-or-later
package pqcrypt
import (
"bytes"
"io"
"testing"
)
// TestPQRoundTrip exercises the full Encrypt -> Decrypt path through the default
// post-quantum suite with a 256-bit file key.
func TestPQRoundTrip(t *testing.T) {
id, err := GeneratePQIdentity()
if err != nil {
t.Fatal(err)
}
r := id.Recipient()
plaintext := []byte("fury: post-quantum only, hard g like GIF")
buf := &bytes.Buffer{}
w, err := Encrypt(buf, r)
if err != nil {
t.Fatal(err)
}
if _, err := w.Write(plaintext); err != nil {
t.Fatal(err)
}
if err := w.Close(); err != nil {
t.Fatal(err)
}
out, err := Decrypt(bytes.NewReader(buf.Bytes()), id)
if err != nil {
t.Fatal(err)
}
got, err := io.ReadAll(out)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, plaintext) {
t.Fatalf("round-trip mismatch: got %q want %q", got, plaintext)
}
}
func TestPQFileKeyIs256Bit(t *testing.T) {
if fileKeySize != 32 {
t.Fatalf("fileKeySize = %d, want 32 (256-bit)", fileKeySize)
}
}
// TestPQWrongIdentity confirms a different identity cannot decrypt.
func TestPQWrongIdentity(t *testing.T) {
id, _ := GeneratePQIdentity()
other, _ := GeneratePQIdentity()
buf := &bytes.Buffer{}
w, err := Encrypt(buf, id.Recipient())
if err != nil {
t.Fatal(err)
}
w.Write([]byte("secret"))
w.Close()
if _, err := Decrypt(bytes.NewReader(buf.Bytes()), other); err == nil {
t.Fatal("decryption with wrong identity unexpectedly succeeded")
}
}
// TestSuiteRegistry checks the default suite is registered and self-describing.
func TestSuiteRegistry(t *testing.T) {
s := defaultSuite()
if s.tag() != "frodo640shake+mlkem768" {
t.Fatalf("unexpected default suite %q", s.tag())
}
if pqSuitesByID[s.id()] != s {
t.Fatal("suite not registered by id")
}
if pqSuitesByTag[s.tag()] != s {
t.Fatal("suite not registered by tag")
}
}
// TestSeedDeterministic confirms an identity round-trips through its seed, which
// is what makes compact passphrase-wrapped identities possible.
func TestSeedDeterministic(t *testing.T) {
id, err := GeneratePQIdentity()
if err != nil {
t.Fatal(err)
}
id2, err := newPQIdentityFromSeed(defaultSuite(), id.Seed())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(id.pub, id2.pub) || !bytes.Equal(id.priv, id2.priv) {
t.Fatal("identity not deterministic from seed")
}
}
func TestIdentityAndRecipientAccessorsReturnCopies(t *testing.T) {
id, err := GeneratePQIdentity()
if err != nil {
t.Fatal(err)
}
seed := id.Seed()
seed[0] ^= 0xff
if id.seed[0] == seed[0] {
t.Fatal("Seed returned mutable identity internals")
}
inputSeed := id.Seed()
id2, err := newPQIdentityFromSeed(defaultSuite(), inputSeed)
if err != nil {
t.Fatal(err)
}
inputSeed[0] ^= 0xff
if id2.seed[0] == inputSeed[0] {
t.Fatal("newPQIdentityFromSeed retained caller-owned seed slice")
}
recipient := id.Recipient()
pub := recipient.Bytes()
pub[0] ^= 0xff
if recipient.pub[0] == pub[0] {
t.Fatal("Bytes returned mutable recipient internals")
}
recipient.pub[0] ^= 0xff
if id.pub[0] == recipient.pub[0] {
t.Fatal("Recipient returned mutable identity public-key internals")
}
}