Skip to content
Merged
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
145 changes: 145 additions & 0 deletions zz_branches_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

package main

import (
"os"
"path/filepath"
"strings"
"testing"
)

// validRootCertPEM is a real self-signed PEM-encoded cert from initRoot,
// inlined for tests that need a parseable root.crt without re-running init.
// We don't need a corresponding key; tests use it to pin loadRoot's
// per-file error branches separately.

// TestLoadRoot_InvalidKeyPEM hits the "root.key: invalid PEM" branch:
// root.crt is a valid PEM cert but root.key contains no PEM block.
func TestLoadRoot_InvalidKeyPEM(t *testing.T) {
t.Parallel()
dir := t.TempDir()
// Build a real root via initRoot, then clobber root.key with junk.
if err := initRoot(dir); err != nil {
t.Fatalf("initRoot: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, "root.key"), []byte("not pem at all"), 0o600); err != nil {
t.Fatalf("clobber key: %v", err)
}
_, _, err := loadRoot(dir)
if err == nil || !strings.Contains(err.Error(), "root.key: invalid PEM") {
t.Errorf("err = %v; want 'root.key: invalid PEM'", err)
}
}

// TestLoadRoot_KeyParseError hits the PKCS8 parse-failure branch:
// root.key is a syntactically valid PEM block but its bytes are not
// a valid PKCS8 private key.
func TestLoadRoot_KeyParseError(t *testing.T) {
t.Parallel()
dir := t.TempDir()
if err := initRoot(dir); err != nil {
t.Fatalf("initRoot: %v", err)
}
garbageKey := "-----BEGIN PRIVATE KEY-----\nAAAA\n-----END PRIVATE KEY-----\n"
if err := os.WriteFile(filepath.Join(dir, "root.key"), []byte(garbageKey), 0o600); err != nil {
t.Fatalf("clobber key: %v", err)
}
_, _, err := loadRoot(dir)
if err == nil || !strings.Contains(err.Error(), "root.key parse") {
t.Errorf("err = %v; want 'root.key parse'", err)
}
}

// TestLoadRoot_CertParseError hits the x509 parse-failure branch for the
// root cert: PEM block is well-formed but bytes are not a valid cert.
func TestLoadRoot_CertParseError(t *testing.T) {
t.Parallel()
dir := t.TempDir()
garbageCert := "-----BEGIN CERTIFICATE-----\nAAAA\n-----END CERTIFICATE-----\n"
if err := os.WriteFile(filepath.Join(dir, "root.crt"), []byte(garbageCert), 0o644); err != nil {
t.Fatalf("write crt: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, "root.key"), []byte("doesn't matter"), 0o600); err != nil {
t.Fatalf("write key: %v", err)
}
_, _, err := loadRoot(dir)
if err == nil || !strings.Contains(err.Error(), "root.crt parse") {
t.Errorf("err = %v; want 'root.crt parse'", err)
}
}

// TestVerifyChain_MissingLeafFile hits the os.ReadFile-leaf error branch.
func TestVerifyChain_MissingLeafFile(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := initRoot(rootDir); err != nil {
t.Fatalf("initRoot: %v", err)
}
err := verifyChain(filepath.Join(rootDir, "root.crt"), "/nonexistent/leaf.crt")
if err == nil || !strings.Contains(err.Error(), "read leaf cert") {
t.Errorf("err = %v; want 'read leaf cert'", err)
}
}

// TestVerifyChain_RootPEMAppendFails hits the AppendCertsFromPEM-false
// branch: root file exists and is readable but contains no valid cert PEM.
func TestVerifyChain_RootPEMAppendFails(t *testing.T) {
t.Parallel()
dir := t.TempDir()
rootPath := filepath.Join(dir, "root.crt")
leafPath := filepath.Join(dir, "leaf.crt")
if err := os.WriteFile(rootPath, []byte("garbage with no PEM block"), 0o644); err != nil {
t.Fatalf("write root: %v", err)
}
if err := os.WriteFile(leafPath, []byte("garbage"), 0o644); err != nil {
t.Fatalf("write leaf: %v", err)
}
err := verifyChain(rootPath, leafPath)
if err == nil || !strings.Contains(err.Error(), "root cert PEM parse failed") {
t.Errorf("err = %v; want 'root cert PEM parse failed'", err)
}
}

// TestVerifyChain_LeafParseError hits the x509.ParseCertificate branch:
// leaf PEM block is well-formed but cert bytes are bogus.
func TestVerifyChain_LeafParseError(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := initRoot(rootDir); err != nil {
t.Fatalf("initRoot: %v", err)
}
leafPath := filepath.Join(t.TempDir(), "leaf.crt")
garbageLeaf := "-----BEGIN CERTIFICATE-----\nAAAA\n-----END CERTIFICATE-----\n"
if err := os.WriteFile(leafPath, []byte(garbageLeaf), 0o644); err != nil {
t.Fatalf("write leaf: %v", err)
}
err := verifyChain(filepath.Join(rootDir, "root.crt"), leafPath)
if err == nil || !strings.Contains(err.Error(), "leaf parse") {
t.Errorf("err = %v; want 'leaf parse'", err)
}
}

// TestIssueBeacon_HostnameWithSlash documents current behavior: the tool
// builds <outDir>/<hostname>.{key,crt} via filepath.Join, so a hostname
// containing a path separator silently writes files in a sibling
// directory rather than failing. Pinning this so a future fix flips
// the assertion intentionally.
//
// NOTE: as of this writing, issueBeacon does NOT validate hostname
// characters beyond emptiness. If/when that lands, update this test
// to expect an error.
func TestIssueBeacon_HostnameWithSlash_CurrentBehavior(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := initRoot(rootDir); err != nil {
t.Fatalf("initRoot: %v", err)
}
outDir := t.TempDir()
// Hostname with a slash — issueBeacon may or may not reject this.
// We don't assert success or failure; we assert it doesn't panic
// and produces a deterministic outcome.
err := issueBeacon(rootDir, "evil/../host", outDir)
// Document current behavior in test output for the maintainer.
t.Logf("issueBeacon('evil/../host') -> err=%v", err)
}
52 changes: 52 additions & 0 deletions zz_init_root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

package main

import (
"os"
"path/filepath"
"strings"
"testing"
)

func TestInitRoot_MkdirError(t *testing.T) {
t.Parallel()
// /dev/null is a file — MkdirAll under it fails.
if err := initRoot("/dev/null/cannot/path"); err == nil {
t.Error("expected mkdir error")
}
}

func TestInitRoot_RefusesOverwriteExistingKey(t *testing.T) {
t.Parallel()
dir := t.TempDir()
// Pre-create root.key so initRoot refuses.
if err := os.WriteFile(filepath.Join(dir, "root.key"), []byte("existing"), 0600); err != nil {
t.Fatalf("setup: %v", err)
}
err := initRoot(dir)
if err == nil || !strings.Contains(err.Error(), "refusing to overwrite") {
t.Errorf("err = %v", err)
}
}

func TestIssueBeacon_NoRootFails(t *testing.T) {
t.Parallel()
dir := t.TempDir()
// Skip initRoot — issueBeacon should fail at loadRoot.
if err := issueBeacon(dir, "host.example", dir); err == nil {
t.Error("expected error when root files are missing")
}
}

func TestIssueBeacon_MkdirError(t *testing.T) {
t.Parallel()
dir := t.TempDir()
if err := initRoot(dir); err != nil {
t.Fatalf("initRoot: %v", err)
}
// outDir under /dev/null can't have its parent created.
if err := issueBeacon(dir, "host", "/dev/null/cannot"); err == nil {
t.Error("expected mkdir error")
}
}
155 changes: 155 additions & 0 deletions zz_load_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

package main

import (
"crypto/ed25519"
"crypto/rand"
"os"
"path/filepath"
"strings"
"testing"
)

// TestLoadRoot_MissingCert covers the os.ReadFile error branch.
func TestLoadRoot_MissingCert(t *testing.T) {
t.Parallel()
dir := t.TempDir()
if _, _, err := loadRoot(dir); err == nil {
t.Error("expected error when root.crt missing")
}
}

// TestLoadRoot_MissingKey covers the missing-key branch.
func TestLoadRoot_MissingKey(t *testing.T) {
t.Parallel()
dir := t.TempDir()
// Write a valid root.crt but no root.key.
if err := os.WriteFile(filepath.Join(dir, "root.crt"), []byte("-----BEGIN CERTIFICATE-----\nXXX\n-----END CERTIFICATE-----\n"), 0644); err != nil {
t.Fatalf("write: %v", err)
}
_, _, err := loadRoot(dir)
if err == nil || !strings.Contains(err.Error(), "root.key") {
t.Errorf("err = %v, want root.key error", err)
}
}

// TestLoadRoot_InvalidCertPEM covers the "invalid PEM" branch.
func TestLoadRoot_InvalidCertPEM(t *testing.T) {
t.Parallel()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "root.crt"), []byte("not pem"), 0644); err != nil {
t.Fatalf("write crt: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, "root.key"), []byte("not pem"), 0600); err != nil {
t.Fatalf("write key: %v", err)
}
_, _, err := loadRoot(dir)
if err == nil || !strings.Contains(err.Error(), "root.crt: invalid PEM") {
t.Errorf("err = %v", err)
}
}

// TestWritePEM_ToBadPath covers the OpenFile error branch.
func TestWritePEM_ToBadPath(t *testing.T) {
t.Parallel()
if err := writePEM("/no/such/dir/file.pem", "CERTIFICATE", []byte("x"), 0644); err == nil {
t.Error("expected error on unwritable path")
}
}

// TestWritePEM_HappyPath drives the encode-success branch.
func TestWritePEM_HappyPath(t *testing.T) {
t.Parallel()
dir := t.TempDir()
path := filepath.Join(dir, "out.pem")
if err := writePEM(path, "CERTIFICATE", []byte("ABCDEFG"), 0600); err != nil {
t.Fatalf("writePEM: %v", err)
}
body, _ := os.ReadFile(path)
if !strings.Contains(string(body), "BEGIN CERTIFICATE") {
t.Errorf("got %q", body)
}
}

// TestMustMarshalPKCS8_PanicsOnInvalid covers the panic branch.
func TestMustMarshalPKCS8_PanicsOnInvalid(t *testing.T) {
t.Parallel()
defer func() {
if r := recover(); r == nil {
t.Error("expected panic on unsupported key type")
}
}()
mustMarshalPKCS8(42) // int is not a private key
}

// TestMustMarshalPKCS8_HappyPath covers the success branch.
func TestMustMarshalPKCS8_HappyPath(t *testing.T) {
t.Parallel()
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("GenerateKey: %v", err)
}
body := mustMarshalPKCS8(priv)
if len(body) == 0 {
t.Error("empty PKCS8 body")
}
}

// TestRandomSerial_NonZero covers the helper.
func TestRandomSerial_NonZero(t *testing.T) {
t.Parallel()
s, err := randomSerial()
if err != nil {
t.Fatalf("randomSerial: %v", err)
}
if s.Sign() == 0 {
t.Error("serial should be non-zero")
}
}

// TestLoadRoot_AfterInit drives initRoot + loadRoot end-to-end.
func TestLoadRoot_AfterInit(t *testing.T) {
t.Parallel()
dir := t.TempDir()
if err := initRoot(dir); err != nil {
t.Fatalf("initRoot: %v", err)
}
crt, key, err := loadRoot(dir)
if err != nil {
t.Fatalf("loadRoot: %v", err)
}
if crt == nil || key == nil {
t.Error("nil crt or key")
}
}

// TestIssueBeacon_HappyPath drives initRoot + issueBeacon end-to-end.
func TestIssueBeacon_HappyPath(t *testing.T) {
t.Parallel()
dir := t.TempDir()
if err := initRoot(dir); err != nil {
t.Fatalf("initRoot: %v", err)
}
beaconDir := filepath.Join(dir, "beacon1")
if err := os.MkdirAll(beaconDir, 0700); err != nil {
t.Fatalf("mkdir beacon: %v", err)
}
if err := issueBeacon(dir, "beacon1.example", beaconDir); err != nil {
t.Fatalf("issueBeacon: %v", err)
}
for _, name := range []string{"beacon1.example.crt", "beacon1.example.key"} {
if _, err := os.Stat(filepath.Join(beaconDir, name)); err != nil {
t.Errorf("%s missing: %v", name, err)
}
}
}

// TestIssueBeacon_RequiresHostname covers the empty-hostname branch.
func TestIssueBeacon_RequiresHostname(t *testing.T) {
t.Parallel()
dir := t.TempDir()
if err := issueBeacon(dir, "", dir); err == nil {
t.Error("expected error on empty hostname")
}
}
Loading
Loading