setup_test.go raw
1 package api
2
3 import (
4 "crypto/ecdsa"
5 "crypto/elliptic"
6 crand "crypto/rand"
7 "crypto/x509"
8 "crypto/x509/pkix"
9 "encoding/hex"
10 "encoding/pem"
11 "math/big"
12 "os"
13 "path/filepath"
14 "testing"
15 "time"
16
17 "github.com/stretchr/testify/require"
18 "gopkg.in/macaroon.v2"
19 )
20
21 // generateTestCert returns a self-signed certificate PEM block and its
22 // matching EC private key PEM block.
23 func generateTestCert(t *testing.T) (certPEM []byte, keyPEM []byte) {
24 t.Helper()
25
26 key, err := ecdsa.GenerateKey(elliptic.P256(), crand.Reader)
27 require.NoError(t, err)
28
29 template := x509.Certificate{
30 SerialNumber: big.NewInt(1),
31 Subject: pkix.Name{CommonName: "test"},
32 NotBefore: time.Unix(0, 0),
33 NotAfter: time.Unix(1<<31, 0),
34 }
35 der, err := x509.CreateCertificate(crand.Reader, &template, &template, &key.PublicKey, key)
36 require.NoError(t, err)
37
38 certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
39
40 keyDER, err := x509.MarshalPKCS8PrivateKey(key)
41 require.NoError(t, err)
42 keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
43
44 return certPEM, keyPEM
45 }
46
47 func TestReadAndCanonicalizeLNDCert(t *testing.T) {
48 certPEM, keyPEM := generateTestCert(t)
49 dir := t.TempDir()
50
51 t.Run("valid certificate", func(t *testing.T) {
52 path := filepath.Join(dir, "tls.cert")
53 require.NoError(t, os.WriteFile(path, certPEM, 0600))
54
55 got, err := readAndCanonicalizeLNDCert(path)
56 require.NoError(t, err)
57
58 raw, err := hex.DecodeString(got)
59 require.NoError(t, err)
60 require.True(t, x509.NewCertPool().AppendCertsFromPEM(raw))
61 })
62
63 t.Run("bundled private key is stripped", func(t *testing.T) {
64 path := filepath.Join(dir, "bundle.pem")
65 require.NoError(t, os.WriteFile(path, append(append([]byte{}, certPEM...), keyPEM...), 0600))
66
67 got, err := readAndCanonicalizeLNDCert(path)
68 require.NoError(t, err)
69
70 raw, err := hex.DecodeString(got)
71 require.NoError(t, err)
72 // Only the CERTIFICATE block must survive - the private key must not
73 // be persisted.
74 require.NotContains(t, string(raw), "PRIVATE KEY")
75 require.Contains(t, string(raw), "CERTIFICATE")
76 })
77
78 t.Run("arbitrary non-cert file is rejected", func(t *testing.T) {
79 path := filepath.Join(dir, "secret.txt")
80 require.NoError(t, os.WriteFile(path, []byte("root:x:0:0:root:/root:/bin/bash\n"), 0600))
81
82 _, err := readAndCanonicalizeLNDCert(path)
83 require.Error(t, err)
84 })
85
86 t.Run("missing file is rejected", func(t *testing.T) {
87 _, err := readAndCanonicalizeLNDCert(filepath.Join(dir, "does-not-exist"))
88 require.Error(t, err)
89 })
90 }
91
92 func TestReadAndCanonicalizeLNDMacaroon(t *testing.T) {
93 dir := t.TempDir()
94
95 t.Run("valid macaroon", func(t *testing.T) {
96 mac, err := macaroon.New([]byte("root-key"), []byte("id"), "location", macaroon.LatestVersion)
97 require.NoError(t, err)
98 raw, err := mac.MarshalBinary()
99 require.NoError(t, err)
100
101 path := filepath.Join(dir, "admin.macaroon")
102 require.NoError(t, os.WriteFile(path, raw, 0600))
103
104 got, err := readAndCanonicalizeLNDMacaroon(path)
105 require.NoError(t, err)
106
107 gotRaw, err := hex.DecodeString(got)
108 require.NoError(t, err)
109 roundTrip := &macaroon.Macaroon{}
110 require.NoError(t, roundTrip.UnmarshalBinary(gotRaw))
111 })
112
113 t.Run("arbitrary non-macaroon file is rejected", func(t *testing.T) {
114 path := filepath.Join(dir, "id_rsa")
115 require.NoError(t, os.WriteFile(path, []byte("-----BEGIN OPENSSH PRIVATE KEY-----\nsecret\n"), 0600))
116
117 _, err := readAndCanonicalizeLNDMacaroon(path)
118 require.Error(t, err)
119 })
120
121 t.Run("missing file is rejected", func(t *testing.T) {
122 _, err := readAndCanonicalizeLNDMacaroon(filepath.Join(dir, "does-not-exist"))
123 require.Error(t, err)
124 })
125 }
126
127 func TestValidateCLNLightningDir(t *testing.T) {
128 certPEM, keyPEM := generateTestCert(t)
129
130 writeCLNDir := func(t *testing.T, dir string) {
131 t.Helper()
132 require.NoError(t, os.WriteFile(filepath.Join(dir, "ca.pem"), certPEM, 0600))
133 require.NoError(t, os.WriteFile(filepath.Join(dir, "client.pem"), certPEM, 0600))
134 require.NoError(t, os.WriteFile(filepath.Join(dir, "client-key.pem"), keyPEM, 0600))
135 }
136
137 t.Run("valid directory", func(t *testing.T) {
138 dir := t.TempDir()
139 writeCLNDir(t, dir)
140 require.NoError(t, validateCLNLightningDir(dir, false))
141 })
142
143 t.Run("valid directory with hold subdirectory", func(t *testing.T) {
144 dir := t.TempDir()
145 writeCLNDir(t, dir)
146 holdDir := filepath.Join(dir, "hold")
147 require.NoError(t, os.Mkdir(holdDir, 0700))
148 writeCLNDir(t, holdDir)
149 require.NoError(t, validateCLNLightningDir(dir, true))
150 })
151
152 t.Run("hold requested but subdirectory missing", func(t *testing.T) {
153 dir := t.TempDir()
154 writeCLNDir(t, dir)
155 require.Error(t, validateCLNLightningDir(dir, true))
156 })
157
158 t.Run("arbitrary directory is rejected", func(t *testing.T) {
159 require.Error(t, validateCLNLightningDir(t.TempDir(), false))
160 })
161 }
162