Рыба проекта. Минимальная функциональность
This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/keymanager"
|
||||
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/mlkem768"
|
||||
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/x25519"
|
||||
"git.tswf.io/infra/go-synapse-backupper/pkg/domain/crypto"
|
||||
)
|
||||
|
||||
func makeRegistry(t *testing.T) crypto.Registry {
|
||||
t.Helper()
|
||||
reg := crypto.NewRegistry()
|
||||
if err := reg.Register(0x0006, func() crypto.KEM { return mlkem768.New() }); err != nil {
|
||||
t.Fatalf("register mlkem768: %v", err)
|
||||
}
|
||||
if err := reg.Register(0x0007, func() crypto.KEM { return x25519.New() }); err != nil {
|
||||
t.Fatalf("register x25519: %v", err)
|
||||
}
|
||||
return reg
|
||||
}
|
||||
|
||||
func TestKeygenCmdFlags(t *testing.T) {
|
||||
cmd := newKeygenCmd()
|
||||
|
||||
// Verify flags exist and have correct defaults.
|
||||
if cmd.Flag("type") == nil {
|
||||
t.Fatal("missing --type flag")
|
||||
}
|
||||
if cmd.Flag("type").DefValue != "both" {
|
||||
t.Errorf("--type default = %q, want both", cmd.Flag("type").DefValue)
|
||||
}
|
||||
|
||||
if cmd.Flag("out-prefix") == nil {
|
||||
t.Fatal("missing --out-prefix flag")
|
||||
}
|
||||
if cmd.Flag("out-prefix").DefValue != "" {
|
||||
t.Errorf("--out-prefix default = %q, want empty", cmd.Flag("out-prefix").DefValue)
|
||||
}
|
||||
|
||||
if cmd.Flag("force") == nil {
|
||||
t.Fatal("missing --force flag")
|
||||
}
|
||||
if cmd.Flag("force").DefValue != "false" {
|
||||
t.Errorf("--force default = %q, want false", cmd.Flag("force").DefValue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdInvalidType(t *testing.T) {
|
||||
cmd := newKeygenCmd()
|
||||
cmd.SetArgs([]string{"--type", "invalid", "--out-prefix", filepath.Join(t.TempDir(), "keys")})
|
||||
|
||||
err := cmd.Execute()
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid --type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdBoth(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "both", "--out-prefix", prefix})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify all 4 files exist.
|
||||
files := []string{
|
||||
prefix + ".pq.pub.pem",
|
||||
prefix + ".pq.priv.pem",
|
||||
prefix + ".classical.pub.pem",
|
||||
prefix + ".classical.priv.pem",
|
||||
}
|
||||
for _, f := range files {
|
||||
if _, err := os.Stat(f); err != nil {
|
||||
t.Errorf("expected file %s to exist: %v", f, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdPEMTypes(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "both", "--out-prefix", prefix})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
wantType string
|
||||
wantKeyLen int
|
||||
}{
|
||||
{prefix + ".pq.pub.pem", "ML-KEM-768 PUBLIC KEY", 1184},
|
||||
{prefix + ".pq.priv.pem", "ML-KEM-768 PRIVATE KEY", 64},
|
||||
{prefix + ".classical.pub.pem", "X25519 PUBLIC KEY", 32},
|
||||
{prefix + ".classical.priv.pem", "X25519 PRIVATE KEY", 32},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
data, err := os.ReadFile(tt.path)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", tt.path, err)
|
||||
}
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
t.Fatalf("failed to decode PEM from %s", tt.path)
|
||||
}
|
||||
if block.Type != tt.wantType {
|
||||
t.Errorf("%s PEM type = %q, want %q", tt.path, block.Type, tt.wantType)
|
||||
}
|
||||
if len(block.Bytes) != tt.wantKeyLen {
|
||||
t.Errorf("%s key len = %d, want %d", tt.path, len(block.Bytes), tt.wantKeyLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdRoundTrip(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "both", "--out-prefix", prefix})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
km := keymanager.NewKeyManager(reg)
|
||||
|
||||
schemes := []struct {
|
||||
pubPath string
|
||||
privPath string
|
||||
schemeID uint16
|
||||
pubLen int
|
||||
privLen int
|
||||
}{
|
||||
{prefix + ".pq.pub.pem", prefix + ".pq.priv.pem", 0x0006, 1184, 64},
|
||||
{prefix + ".classical.pub.pem", prefix + ".classical.priv.pem", 0x0007, 32, 32},
|
||||
}
|
||||
|
||||
for _, s := range schemes {
|
||||
pub, err := km.LoadPub(s.pubPath, s.schemeID)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadPub %s: %v", s.pubPath, err)
|
||||
}
|
||||
priv, err := km.LoadPriv(s.privPath, s.schemeID)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadPriv %s: %v", s.privPath, err)
|
||||
}
|
||||
|
||||
if pub.SchemeID() != s.schemeID {
|
||||
t.Errorf("pub.SchemeID() = 0x%04x, want 0x%04x", pub.SchemeID(), s.schemeID)
|
||||
}
|
||||
if priv.SchemeID() != s.schemeID {
|
||||
t.Errorf("priv.SchemeID() = 0x%04x, want 0x%04x", priv.SchemeID(), s.schemeID)
|
||||
}
|
||||
if len(pub.Raw()) != s.pubLen {
|
||||
t.Errorf("pub.Raw() len = %d, want %d", len(pub.Raw()), s.pubLen)
|
||||
}
|
||||
if len(priv.Raw()) != s.privLen {
|
||||
t.Errorf("priv.Raw() len = %d, want %d", len(priv.Raw()), s.privLen)
|
||||
}
|
||||
if len(pub.KeyID()) != 8 {
|
||||
t.Errorf("pub.KeyID() len = %d, want 8", len(pub.KeyID()))
|
||||
}
|
||||
if len(priv.KeyID()) != 8 {
|
||||
t.Errorf("priv.KeyID() len = %d, want 8", len(priv.KeyID()))
|
||||
}
|
||||
|
||||
// Verify the raw bytes round-trip correctly by checking the PEM
|
||||
// contents match what LoadPub/LoadPriv return.
|
||||
pubPEM, err := os.ReadFile(s.pubPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read pub PEM: %v", err)
|
||||
}
|
||||
pubBlock, _ := pem.Decode(pubPEM)
|
||||
if pubBlock == nil {
|
||||
t.Fatal("failed to decode pub PEM")
|
||||
}
|
||||
if !bytes.Equal(pub.Raw(), pubBlock.Bytes) {
|
||||
t.Errorf("pub.Raw() does not match PEM bytes")
|
||||
}
|
||||
|
||||
privPEM, err := os.ReadFile(s.privPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read priv PEM: %v", err)
|
||||
}
|
||||
privBlock, _ := pem.Decode(privPEM)
|
||||
if privBlock == nil {
|
||||
t.Fatal("failed to decode priv PEM")
|
||||
}
|
||||
if !bytes.Equal(priv.Raw(), privBlock.Bytes) {
|
||||
t.Errorf("priv.Raw() does not match PEM bytes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdPQOnly(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "pq", "--out-prefix", prefix})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(prefix + ".pq.pub.pem"); err != nil {
|
||||
t.Errorf("expected pq.pub.pem to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(prefix + ".pq.priv.pem"); err != nil {
|
||||
t.Errorf("expected pq.priv.pem to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(prefix + ".classical.pub.pem"); !os.IsNotExist(err) {
|
||||
t.Errorf("expected classical.pub.pem to NOT exist")
|
||||
}
|
||||
if _, err := os.Stat(prefix + ".classical.priv.pem"); !os.IsNotExist(err) {
|
||||
t.Errorf("expected classical.priv.pem to NOT exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdClassicalOnly(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "classical", "--out-prefix", prefix})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(prefix + ".classical.pub.pem"); err != nil {
|
||||
t.Errorf("expected classical.pub.pem to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(prefix + ".classical.priv.pem"); err != nil {
|
||||
t.Errorf("expected classical.priv.pem to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(prefix + ".pq.pub.pem"); !os.IsNotExist(err) {
|
||||
t.Errorf("expected pq.pub.pem to NOT exist")
|
||||
}
|
||||
if _, err := os.Stat(prefix + ".pq.priv.pem"); !os.IsNotExist(err) {
|
||||
t.Errorf("expected pq.priv.pem to NOT exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdPermissions(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "both", "--out-prefix", prefix})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
pubFiles := []string{prefix + ".pq.pub.pem", prefix + ".classical.pub.pem"}
|
||||
for _, f := range pubFiles {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
t.Fatalf("stat %s: %v", f, err)
|
||||
}
|
||||
mode := info.Mode().Perm()
|
||||
if mode != 0o644 {
|
||||
t.Errorf("%s permissions = 0%o, want 0644", f, mode)
|
||||
}
|
||||
}
|
||||
|
||||
privFiles := []string{prefix + ".pq.priv.pem", prefix + ".classical.priv.pem"}
|
||||
for _, f := range privFiles {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
t.Fatalf("stat %s: %v", f, err)
|
||||
}
|
||||
mode := info.Mode().Perm()
|
||||
if mode != 0o600 {
|
||||
t.Errorf("%s permissions = 0%o, want 0600", f, mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdNoOverwrite(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
// Create an existing file.
|
||||
existing := prefix + ".pq.pub.pem"
|
||||
if err := os.WriteFile(existing, []byte("existing"), 0o644); err != nil {
|
||||
t.Fatalf("write existing file: %v", err)
|
||||
}
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "both", "--out-prefix", prefix})
|
||||
|
||||
err := cmd.Execute()
|
||||
if err == nil {
|
||||
t.Fatal("expected error when file exists without --force")
|
||||
}
|
||||
|
||||
// Verify existing file was not overwritten.
|
||||
data, err := os.ReadFile(existing)
|
||||
if err != nil {
|
||||
t.Fatalf("read existing file: %v", err)
|
||||
}
|
||||
if !bytes.Equal(data, []byte("existing")) {
|
||||
t.Error("existing file was overwritten without --force")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdForceOverwrite(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
// Create an existing file.
|
||||
existing := prefix + ".pq.pub.pem"
|
||||
if err := os.WriteFile(existing, []byte("existing"), 0o644); err != nil {
|
||||
t.Fatalf("write existing file: %v", err)
|
||||
}
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "both", "--out-prefix", prefix, "--force"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify file was overwritten with valid PEM.
|
||||
data, err := os.ReadFile(existing)
|
||||
if err != nil {
|
||||
t.Fatalf("read overwritten file: %v", err)
|
||||
}
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
t.Fatal("overwritten file is not valid PEM")
|
||||
}
|
||||
if block.Type != "ML-KEM-768 PUBLIC KEY" {
|
||||
t.Errorf("overwritten PEM type = %q, want ML-KEM-768 PUBLIC KEY", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdKeyIDConsistency(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "keys")
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "both", "--out-prefix", prefix})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
km := keymanager.NewKeyManager(reg)
|
||||
|
||||
schemes := []struct {
|
||||
pubPath string
|
||||
privPath string
|
||||
schemeID uint16
|
||||
pubLen int
|
||||
privLen int
|
||||
}{
|
||||
{prefix + ".pq.pub.pem", prefix + ".pq.priv.pem", 0x0006, 1184, 64},
|
||||
{prefix + ".classical.pub.pem", prefix + ".classical.priv.pem", 0x0007, 32, 32},
|
||||
}
|
||||
|
||||
for _, s := range schemes {
|
||||
pub, err := km.LoadPub(s.pubPath, s.schemeID)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadPub %s: %v", s.pubPath, err)
|
||||
}
|
||||
priv, err := km.LoadPriv(s.privPath, s.schemeID)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadPriv %s: %v", s.privPath, err)
|
||||
}
|
||||
|
||||
if len(pub.Raw()) != s.pubLen {
|
||||
t.Errorf("pub %s raw len = %d, want %d", s.pubPath, len(pub.Raw()), s.pubLen)
|
||||
}
|
||||
if len(priv.Raw()) != s.privLen {
|
||||
t.Errorf("priv %s raw len = %d, want %d", s.privPath, len(priv.Raw()), s.privLen)
|
||||
}
|
||||
|
||||
// KeyID must be present and 8 bytes for both pub and priv.
|
||||
if len(pub.KeyID()) != 8 {
|
||||
t.Errorf("pub %s KeyID len = %d, want 8", s.pubPath, len(pub.KeyID()))
|
||||
}
|
||||
if len(priv.KeyID()) != 8 {
|
||||
t.Errorf("priv %s KeyID len = %d, want 8", s.privPath, len(priv.KeyID()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeygenCmdEmptyPrefix(t *testing.T) {
|
||||
reg := makeRegistry(t)
|
||||
dir := t.TempDir()
|
||||
|
||||
cmd := newKeygenCmdWithDeps(reg)
|
||||
cmd.SetArgs([]string{"--type", "pq"})
|
||||
cmd.SetOut(nil)
|
||||
cmd.SetErr(nil)
|
||||
// Change working directory to temp dir so empty prefix creates files there.
|
||||
origWd, _ := os.Getwd()
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("Chdir(%q): %v", dir, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.Chdir(origWd); err != nil {
|
||||
t.Fatalf("Chdir(%q): %v", origWd, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dir, ".pq.pub.pem")); err != nil {
|
||||
t.Errorf("expected .pq.pub.pem to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, ".pq.priv.pem")); err != nil {
|
||||
t.Errorf("expected .pq.priv.pem to exist: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user