Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion internal/identity/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: Apache-2.0

load("@rules_go//go:def.bzl", "go_library")
load("@rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "identity",
Expand All @@ -12,3 +12,9 @@ go_library(
"@org_golang_google_protobuf//reflect/protoreflect",
],
)

go_test(
name = "identity_test",
srcs = ["identity_test.go"],
embed = [":identity"],
)
57 changes: 21 additions & 36 deletions internal/identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,33 @@ type Identity struct {

// Generate creates a new self-signed X.509 certificate and ECDSA private key.
func Generate(shortName string) (*Identity, error) {
priv, err := newPrivateKey()
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to generate private key: %w", err)
}

template, err := newCertificateTemplate(shortName)
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to generate serial number: %w", err)
}

derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: shortName,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, fmt.Errorf("failed to create certificate: %w", err)
}
Expand Down Expand Up @@ -151,37 +167,6 @@ func MarshalCertificate(cert *x509.Certificate) []byte {
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
}

func newPrivateKey() (*ecdsa.PrivateKey, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate private key: %w", err)
}
return priv, nil
}

func newCertificateTemplate(shortName string) (*x509.Certificate, error) {
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %w", err)
}

return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: shortName,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}, nil
}

// UnmarshalCertificate imports a certificate from PEM format.
func UnmarshalCertificate(data []byte) (*x509.Certificate, error) {
block, _ := pem.Decode(data)
Expand Down
98 changes: 78 additions & 20 deletions internal/identity/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,85 @@
package identity

import (
"bytes"
"encoding/pem"
"testing"
)

func TestGenerate(t *testing.T) {
shortName := "test-agent"
ident, err := Generate(shortName)
if err != nil {
t.Fatalf("Generate() failed: %v", err)
}

if ident == nil {
t.Fatal("Generate() returned nil identity")
}

if ident.Certificate == nil {
t.Error("Generate() returned identity with nil certificate")
} else if ident.Certificate.Subject.CommonName != shortName {
t.Errorf("Generate() certificate common name = %q, want %q", ident.Certificate.Subject.CommonName, shortName)
}

if ident.PrivateKey == nil {
t.Error("Generate() returned identity with nil private key")
}
func TestUnmarshalCertificate(t *testing.T) {
// 1. Success case: Generate a valid identity and round-trip its certificate.
t.Run("Success", func(t *testing.T) {
id, err := Generate("test-agent")
if err != nil {
t.Fatalf("Failed to generate identity: %v", err)
}

pemData := MarshalCertificate(id.Certificate)
unmarshaled, err := UnmarshalCertificate(pemData)
if err != nil {
t.Fatalf("UnmarshalCertificate failed: %v", err)
}

if !bytes.Equal(id.Certificate.Raw, unmarshaled.Raw) {
t.Errorf("Unmarshaled certificate RAW bytes do not match original")
}
if id.Certificate.Subject.CommonName != unmarshaled.Subject.CommonName {
t.Errorf("Expected Subject %q, got %q", id.Certificate.Subject.CommonName, unmarshaled.Subject.CommonName)
}
})

// 2. Failure: Invalid PEM block.
t.Run("InvalidPEM", func(t *testing.T) {
invalidPEM := []byte("this is not a PEM block")
cert, err := UnmarshalCertificate(invalidPEM)
if err == nil {
t.Errorf("Expected error for invalid PEM data, got nil")
}
if cert != nil {
t.Errorf("Expected nil certificate for invalid PEM data")
}
})

// 3. Failure: Empty input.
t.Run("EmptyInput", func(t *testing.T) {
cert, err := UnmarshalCertificate([]byte{})
if err == nil {
t.Errorf("Expected error for empty input, got nil")
}
if cert != nil {
t.Errorf("Expected nil certificate for empty input")
}
})

// 4. Failure: Valid PEM but wrong type.
t.Run("WrongPEMType", func(t *testing.T) {
wrongBlock := &pem.Block{
Type: "NOT A CERTIFICATE",
Bytes: []byte{0xDE, 0xAD, 0xBE, 0xEF},
}
wrongData := pem.EncodeToMemory(wrongBlock)
_, err := UnmarshalCertificate(wrongData)
// UnmarshalCertificate doesn't explicitly check the Type field,
// it just calls x509.ParseCertificate(block.Bytes).
// So it will likely fail during parsing.
if err == nil {
t.Errorf("Expected error for wrong PEM type / invalid DER, got nil")
}
})

// 5. Failure: Valid PEM "CERTIFICATE" but invalid DER bytes.
t.Run("InvalidDER", func(t *testing.T) {
invalidDERBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: []byte("definitely not a valid X.509 DER certificate"),
}
invalidData := pem.EncodeToMemory(invalidDERBlock)
cert, err := UnmarshalCertificate(invalidData)
if err == nil {
t.Errorf("Expected error for invalid DER bytes, got nil")
}
if cert != nil {
t.Errorf("Expected nil certificate for invalid DER bytes")
}
})
}
5 changes: 5 additions & 0 deletions internal/names/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ package names

import (
"math/rand"
"time"
)

var loadedNames []string

func init() {
rand.Seed(time.Now().UnixNano())
}

// GenerateForIndex returns a random name from the pre-loaded list.
// It prefers names starting with 'A' for index 0, 'B' for index 1, etc.
func GenerateForIndex(index int) string {
Expand Down
35 changes: 0 additions & 35 deletions internal/paxos/cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,38 +195,3 @@ func TestCell_Propose_LockCheckerError(t *testing.T) {
t.Errorf("expected lock checker to be called exactly once, got %d. This indicates backoff.Permanent is not preventing retries.", callCount)
}
}

func TestCell_SetSelfAddress(t *testing.T) {
tmpDir := t.TempDir()

store, err := state.NewStore(tmpDir)
if err != nil {
t.Fatalf("failed to create store: %v", err)
}
defer store.Close()

agentID := "agent-1"
acceptor := NewAcceptor(agentID, nil, store)

cell := NewCell(agentID, store, nil, acceptor, nil, "initial-grpc", "initial-http")

// Verify initial state
if cell.selfGRPCAddr != "initial-grpc" {
t.Errorf("expected initial grpc addr 'initial-grpc', got %q", cell.selfGRPCAddr)
}
if cell.selfHTTPURL != "initial-http" {
t.Errorf("expected initial http url 'initial-http', got %q", cell.selfHTTPURL)
}

newGRPC := "new-grpc:50051"
newHTTP := "http://new-http:8080"
cell.SetSelfAddress(newGRPC, newHTTP)

// Verify updated state
if cell.selfGRPCAddr != newGRPC {
t.Errorf("expected updated grpc addr %q, got %q", newGRPC, cell.selfGRPCAddr)
}
if cell.selfHTTPURL != newHTTP {
t.Errorf("expected updated http url %q, got %q", newHTTP, cell.selfHTTPURL)
}
}