Skip to content

Commit adf1cbb

Browse files
authored
Merge pull request #24 from kemsta/test/raise-coverage-80
test: raise coverage for public APIs
2 parents 84640d3 + 35efbc2 commit adf1cbb

15 files changed

Lines changed: 1410 additions & 22 deletions

cert/csr_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cert_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
9+
certpkg "github.com/kemsta/go-easyrsa/v2/cert"
10+
)
11+
12+
func TestCSR_RequestAndSubject(t *testing.T) {
13+
pk := newTestPKI(t)
14+
15+
csrPEM, err := pk.GenReq("client1")
16+
require.NoError(t, err)
17+
18+
csr := &certpkg.CSR{Name: "client1", CSRPEM: csrPEM}
19+
req, err := csr.Request()
20+
require.NoError(t, err)
21+
require.NotNil(t, req)
22+
assert.Equal(t, "client1", req.Subject.CommonName)
23+
24+
subject, err := csr.Subject()
25+
require.NoError(t, err)
26+
assert.Equal(t, req.Subject, subject)
27+
}
28+
29+
func TestCSR_RequestAndSubjectErrors(t *testing.T) {
30+
csr := &certpkg.CSR{Name: "broken", CSRPEM: []byte("not a csr")}
31+
32+
_, err := csr.Request()
33+
assert.Error(t, err)
34+
35+
subject, err := csr.Subject()
36+
assert.Error(t, err)
37+
assert.Empty(t, subject.CommonName)
38+
}

cert/pair_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package cert_test
2+
3+
import (
4+
"crypto"
5+
"crypto/x509"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
11+
certpkg "github.com/kemsta/go-easyrsa/v2/cert"
12+
"github.com/kemsta/go-easyrsa/v2/pki"
13+
"github.com/kemsta/go-easyrsa/v2/storage/memory"
14+
)
15+
16+
func newTestPKI(t *testing.T) *pki.PKI {
17+
t.Helper()
18+
ks, cs, idx, sp, crl := memory.New()
19+
pk, err := pki.New(pki.Config{NoPass: true}, ks, cs, idx, sp, crl)
20+
require.NoError(t, err)
21+
return pk
22+
}
23+
24+
func requirePrivateKey[T any](t *testing.T, key crypto.PrivateKey) T {
25+
t.Helper()
26+
typed, ok := key.(T)
27+
require.Truef(t, ok, "expected private key type %T, got %T", *new(T), key)
28+
return typed
29+
}
30+
31+
func TestPair_PublicHelpersAcrossCertTypes(t *testing.T) {
32+
pk := newTestPKI(t)
33+
34+
caPair, err := pk.BuildCA()
35+
require.NoError(t, err)
36+
clientPair, err := pk.BuildClientFull("client1")
37+
require.NoError(t, err)
38+
serverPair, err := pk.BuildServerFull("server1")
39+
require.NoError(t, err)
40+
serverClientPair, err := pk.BuildServerClientFull("sc1")
41+
require.NoError(t, err)
42+
43+
tests := []struct {
44+
name string
45+
pair *certpkg.Pair
46+
wantType certpkg.CertType
47+
wantCA bool
48+
}{
49+
{name: "ca", pair: caPair, wantType: certpkg.CertTypeCA, wantCA: true},
50+
{name: "client", pair: clientPair, wantType: certpkg.CertTypeClient, wantCA: false},
51+
{name: "server", pair: serverPair, wantType: certpkg.CertTypeServer, wantCA: false},
52+
{name: "server-client", pair: serverClientPair, wantType: certpkg.CertTypeServerClient, wantCA: false},
53+
}
54+
55+
for _, tc := range tests {
56+
tc := tc
57+
t.Run(tc.name, func(t *testing.T) {
58+
crt, err := tc.pair.Certificate()
59+
require.NoError(t, err)
60+
require.NotNil(t, crt)
61+
62+
serial, err := tc.pair.Serial()
63+
require.NoError(t, err)
64+
assert.Zero(t, serial.Cmp(crt.SerialNumber))
65+
66+
certType, err := tc.pair.CertType()
67+
require.NoError(t, err)
68+
assert.Equal(t, tc.wantType, certType)
69+
70+
isCA, err := tc.pair.IsCA()
71+
require.NoError(t, err)
72+
assert.Equal(t, tc.wantCA, isCA)
73+
74+
assert.True(t, tc.pair.HasKey())
75+
key, err := tc.pair.PrivateKey()
76+
require.NoError(t, err)
77+
require.NotNil(t, key)
78+
requirePrivateKey[crypto.Signer](t, key)
79+
})
80+
}
81+
}
82+
83+
func TestPair_CertificateAndSerialErrors(t *testing.T) {
84+
pair := &certpkg.Pair{Name: "broken", CertPEM: []byte("not a cert")}
85+
86+
_, err := pair.Certificate()
87+
assert.Error(t, err)
88+
89+
_, err = pair.Serial()
90+
assert.Error(t, err)
91+
92+
_, err = pair.CertType()
93+
assert.Error(t, err)
94+
95+
_, err = pair.IsCA()
96+
assert.Error(t, err)
97+
}
98+
99+
func TestPair_PrivateKeyErrorsAndHasKey(t *testing.T) {
100+
t.Run("missing key", func(t *testing.T) {
101+
pair := &certpkg.Pair{Name: "nokey"}
102+
assert.False(t, pair.HasKey())
103+
_, err := pair.PrivateKey()
104+
assert.Error(t, err)
105+
})
106+
107+
t.Run("bad key pem", func(t *testing.T) {
108+
pair := &certpkg.Pair{Name: "badkey", KeyPEM: []byte("not a key")}
109+
assert.True(t, pair.HasKey())
110+
_, err := pair.PrivateKey()
111+
assert.Error(t, err)
112+
})
113+
}
114+
115+
func TestPair_CertTypeReturnsErrorForUnrecognizedEKU(t *testing.T) {
116+
pk := newTestPKI(t)
117+
_, err := pk.BuildCA()
118+
require.NoError(t, err)
119+
120+
pair, err := pk.BuildClientFull("codesign", pki.WithCertModifier(func(c *x509.Certificate) {
121+
c.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}
122+
}))
123+
require.NoError(t, err)
124+
125+
certType, err := pair.CertType()
126+
assert.Error(t, err)
127+
assert.NotEqual(t, certpkg.CertTypeClient, certType)
128+
}

internal/testutil/easyrsa_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package testutil
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
"path/filepath"
7+
"runtime"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func buildHelperBinary(t *testing.T) string {
15+
t.Helper()
16+
dir := t.TempDir()
17+
src := filepath.Join(dir, "main.go")
18+
out := filepath.Join(dir, "helper")
19+
if runtime.GOOS == "windows" {
20+
out += ".exe"
21+
}
22+
23+
const program = `package main
24+
import (
25+
"fmt"
26+
"os"
27+
"strings"
28+
)
29+
func main() {
30+
fmt.Printf("args=%s\n", strings.Join(os.Args[1:], ","))
31+
fmt.Printf("pki=%s\n", os.Getenv("EASYRSA_PKI"))
32+
fmt.Printf("batch=%s\n", os.Getenv("EASYRSA_BATCH"))
33+
if len(os.Args) > 0 && os.Args[len(os.Args)-1] == "fail" {
34+
os.Exit(3)
35+
}
36+
}
37+
`
38+
require.NoError(t, os.WriteFile(src, []byte(program), 0644))
39+
cmd := exec.Command("go", "build", "-o", out, src)
40+
output, err := cmd.CombinedOutput()
41+
require.NoErrorf(t, err, "go build failed: %s", output)
42+
return out
43+
}
44+
45+
func TestModuleRoot_FindsRepositoryRoot(t *testing.T) {
46+
root, err := moduleRoot()
47+
require.NoError(t, err)
48+
_, statErr := os.Stat(filepath.Join(root, "go.mod"))
49+
require.NoError(t, statErr)
50+
}
51+
52+
func TestNewRunnerRunE_UsesOverrideBinaryAndSetsEnv(t *testing.T) {
53+
binary := buildHelperBinary(t)
54+
t.Setenv("EASYRSA_BIN", binary)
55+
56+
runner := NewRunner(t, "/tmp/pki-dir")
57+
out, err := runner.RunE("status")
58+
require.NoError(t, err)
59+
assert.Contains(t, out, "args=--batch,status")
60+
assert.Contains(t, out, "pki=/tmp/pki-dir")
61+
assert.Contains(t, out, "batch=1")
62+
}
63+
64+
func TestRunnerRunE_ReturnsOutputOnFailure(t *testing.T) {
65+
binary := buildHelperBinary(t)
66+
t.Setenv("EASYRSA_BIN", binary)
67+
68+
runner := NewRunner(t, "/tmp/pki-dir")
69+
out, err := runner.RunE("fail")
70+
require.Error(t, err)
71+
assert.Contains(t, out, "args=--batch,fail")
72+
}
73+
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package testutil
2+
3+
import (
4+
"encoding/pem"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/kemsta/go-easyrsa/v2/cert"
13+
)
14+
15+
func TestWriteLegacyFixture_WritesExpectedLegacyLayout(t *testing.T) {
16+
dir := t.TempDir()
17+
fixture := WriteLegacyFixture(t, dir)
18+
19+
assert.Equal(t, dir, fixture.Dir)
20+
for _, tc := range []struct {
21+
name string
22+
pair *cert.Pair
23+
wantKey bool
24+
}{
25+
{name: "ca", pair: fixture.CAPair, wantKey: true},
26+
{name: "client-old", pair: fixture.ClientOld, wantKey: true},
27+
{name: "client-current", pair: fixture.ClientCurrent, wantKey: true},
28+
{name: "expired", pair: fixture.ExpiredPair, wantKey: true},
29+
{name: "revoked", pair: fixture.RevokedPair, wantKey: true},
30+
} {
31+
t.Run(tc.name, func(t *testing.T) {
32+
serialHex := MustSerial(t, tc.pair).Text(16)
33+
certPath := filepath.Join(dir, tc.pair.Name, serialHex+".crt")
34+
certPEM, err := os.ReadFile(certPath)
35+
require.NoError(t, err)
36+
assert.Equal(t, tc.pair.CertPEM, certPEM)
37+
38+
keyPath := filepath.Join(dir, tc.pair.Name, serialHex+".key")
39+
keyPEM, err := os.ReadFile(keyPath)
40+
if tc.wantKey {
41+
require.NoError(t, err)
42+
assert.Equal(t, tc.pair.KeyPEM, keyPEM)
43+
} else {
44+
assert.ErrorIs(t, err, os.ErrNotExist)
45+
}
46+
})
47+
}
48+
49+
crlPEM, err := os.ReadFile(filepath.Join(dir, "crl.pem"))
50+
require.NoError(t, err)
51+
assert.Equal(t, fixture.CRLPEM, crlPEM)
52+
53+
for _, keyPEM := range [][]byte{fixture.ClientOld.KeyPEM, fixture.ClientCurrent.KeyPEM} {
54+
block, _ := pem.Decode(keyPEM)
55+
require.NotNil(t, block)
56+
assert.Equal(t, "RSA PRIVATE KEY", block.Type)
57+
}
58+
}
59+
60+
func TestMustSerial_ReturnsIndependentCopy(t *testing.T) {
61+
dir := t.TempDir()
62+
fixture := WriteLegacyFixture(t, dir)
63+
64+
serial := MustSerial(t, fixture.ClientCurrent)
65+
serial.SetInt64(999)
66+
fresh := MustSerial(t, fixture.ClientCurrent)
67+
assert.NotZero(t, fresh.Cmp(serial))
68+
}
69+

pki/clean_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package pki_test
2+
3+
import (
4+
"errors"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/kemsta/go-easyrsa/v2/cert"
12+
"github.com/kemsta/go-easyrsa/v2/pki"
13+
"github.com/kemsta/go-easyrsa/v2/storage"
14+
"github.com/kemsta/go-easyrsa/v2/storage/memory"
15+
)
16+
17+
type cleaningKeyStorage struct {
18+
storage.KeyStorage
19+
cleaned map[string]bool
20+
err error
21+
}
22+
23+
func mustSerial(t *testing.T, pair *cert.Pair) *big.Int {
24+
t.Helper()
25+
serial, err := pair.Serial()
26+
require.NoError(t, err)
27+
return new(big.Int).Set(serial)
28+
}
29+
30+
func (c *cleaningKeyStorage) CleanOrphans(knownSerials map[string]bool) error {
31+
c.cleaned = make(map[string]bool, len(knownSerials))
32+
for k, v := range knownSerials {
33+
c.cleaned[k] = v
34+
}
35+
return c.err
36+
}
37+
38+
func TestClean_PassesKnownSerialsToCleaner(t *testing.T) {
39+
innerKS, cs, idx, sp, crl := memory.New()
40+
ks := &cleaningKeyStorage{KeyStorage: innerKS}
41+
pk := mustNewPKI(t, pki.Config{NoPass: true, SequentialSerial: true}, ks, cs, idx, sp, crl)
42+
43+
caPair, err := pk.BuildCA()
44+
require.NoError(t, err)
45+
clientPair, err := pk.BuildClientFull("client1")
46+
require.NoError(t, err)
47+
48+
require.NoError(t, pk.Clean())
49+
assert.Equal(t, map[string]bool{
50+
storage.HexSerial(mustSerial(t, caPair)): true,
51+
storage.HexSerial(mustSerial(t, clientPair)): true,
52+
}, ks.cleaned)
53+
}
54+
55+
func TestClean_NoOpWhenStorageDoesNotImplementCleaner(t *testing.T) {
56+
pk := newTestPKI(pki.Config{NoPass: true})
57+
buildTestCA(t, pk)
58+
_, err := pk.BuildClientFull("client1")
59+
require.NoError(t, err)
60+
require.NoError(t, pk.Clean())
61+
}
62+
63+
func TestClean_PropagatesCleanerError(t *testing.T) {
64+
innerKS, cs, idx, sp, crl := memory.New()
65+
ks := &cleaningKeyStorage{KeyStorage: innerKS, err: errors.New("clean failed")}
66+
pk := mustNewPKI(t, pki.Config{NoPass: true}, ks, cs, idx, sp, crl)
67+
buildTestCA(t, pk)
68+
69+
err := pk.Clean()
70+
require.EqualError(t, err, "clean failed")
71+
}

0 commit comments

Comments
 (0)