Skip to content
File

Blob: tun/server/keyless_rpc_test.go

go403 lines
1package server
2 
3import (
4 "bytes"
5 "crypto"
6 "crypto/ed25519"
7 "crypto/rand"
8 "crypto/rsa"
9 "crypto/sha256"
10 "crypto/tls"
11 "crypto/x509"
12 "crypto/x509/pkix"
13 "math/big"
14 "testing"
15 "time"
16 
17 "github.com/stretchr/testify/mock"
18 "github.com/stretchr/testify/require"
19 "go.miragespace.co/specter/spec/acme"
20 mocks "go.miragespace.co/specter/spec/mocks"
21 "go.miragespace.co/specter/spec/pow"
22 "go.miragespace.co/specter/spec/protocol"
23 "go.miragespace.co/specter/spec/rpc"
24 "go.miragespace.co/specter/spec/transport"
25 "go.miragespace.co/specter/spec/tun"
26)
27 
28func TestKeylessGetCertificate(t *testing.T) {
29 as := require.New(t)
30 
31 resolver := new(mocks.Resolver)
32 certProvider := new(mocks.CertProvider)
33 
34 logger, node, _, _, serv := getFixture(t, as, withResolver(resolver))
35 cli, _, _ := getIdentities()
36 
37 serv.Config.CertProvider = certProvider
38 
39 ctx := t.Context()
40 
41 hostname := "external.example.com"
42 token := &protocol.ClientToken{
43 Token: mustGenerateToken(),
44 }
45 cli.Address = string(token.GetToken())
46 
47 clientBuf, err := cli.MarshalVT()
48 as.NoError(err)
49 
50 bundle := &protocol.CustomHostname{
51 ClientIdentity: cli,
52 ClientToken: token,
53 }
54 
55 bundleBuf, err := bundle.MarshalVT()
56 as.NoError(err)
57 
58 certProvider.On("GetCertificateWithContext",
59 mock.Anything,
60 mock.MatchedBy(func(chi *tls.ClientHelloInfo) bool {
61 return chi.ServerName == hostname
62 }),
63 ).Return(&tls.Certificate{
64 Certificate: [][]byte{
65 []byte("123"),
66 },
67 }, nil)
68 
69 node.On("Get",
70 mock.Anything,
71 mock.MatchedBy(func(key []byte) bool {
72 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
73 }),
74 ).Return(bundleBuf, nil)
75 node.On("Get",
76 mock.Anything,
77 mock.MatchedBy(func(key []byte) bool {
78 return bytes.Equal(key, []byte(tun.ClientTokenKey(token)))
79 }),
80 ).Return(clientBuf, nil)
81 node.On("Put",
82 mock.Anything,
83 mock.MatchedBy(func(key []byte) bool {
84 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
85 }),
86 mock.Anything,
87 ).Return(nil).Once()
88 node.On("PrefixAppend",
89 mock.Anything,
90 mock.MatchedBy(func(key []byte) bool {
91 return bytes.Equal(key, []byte(tun.ClientHostnamesPrefix(token)))
92 }),
93 mock.MatchedBy(func(key []byte) bool {
94 return bytes.Equal(key, []byte(hostname))
95 }),
96 ).Return(nil).Once()
97 
98 tp := mocks.SelfTransport()
99 streamRouter := transport.NewStreamRouter(logger, nil, tp)
100 go streamRouter.Accept(ctx)
101 
102 serv.AttachRouter(ctx, streamRouter)
103 
104 cRPC := rpc.DynamicTunnelClient(rpc.DisablePooling(ctx), tp)
105 
106 privKey := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
107 tp.WithCertificate(toCertificate(as, logger, cli, token, withExtractPrivKey(privKey)))
108 
109 proof, err := pow.GenerateSolution(privKey, pow.Parameters{
110 Difficulty: acme.HashcashDifficulty,
111 Expires: acme.HashcashExpires,
112 GetSubject: func(pubKey ed25519.PublicKey) string {
113 return hostname
114 },
115 })
116 as.NoError(err)
117 
118 resp, err := cRPC.AcmeValidate(rpc.WithNode(ctx, cli), &protocol.ValidateRequest{
119 Proof: proof,
120 Hostname: hostname,
121 })
122 
123 as.NoError(err)
124 as.NotNil(resp)
125 as.Equal(testRootDomain, resp.GetApex())
126 
127 cert, err := cRPC.GetCertificate(rpc.WithNode(ctx, cli), &protocol.KeylessGetCertificateRequest{
128 Proof: proof,
129 Hostname: hostname,
130 })
131 as.NoError(err)
132 as.NotNil(cert)
133 as.Equal([]byte("123"), cert.GetCertificates()[0])
134 
135 node.AssertExpectations(t)
136 resolver.AssertExpectations(t)
137}
138 
139func TestKeylessGetCertificateUsesCache(t *testing.T) {
140 as := require.New(t)
141 
142 resolver := new(mocks.Resolver)
143 certProvider := new(mocks.CertProvider)
144 
145 logger, node, _, _, serv := getFixture(t, as, withResolver(resolver))
146 cli, _, _ := getIdentities()
147 
148 serv.Config.CertProvider = certProvider
149 
150 ctx := t.Context()
151 
152 hostname := "external.example.com"
153 token := &protocol.ClientToken{
154 Token: mustGenerateToken(),
155 }
156 cli.Address = string(token.GetToken())
157 
158 clientBuf, err := cli.MarshalVT()
159 as.NoError(err)
160 
161 bundle := &protocol.CustomHostname{
162 ClientIdentity: cli,
163 ClientToken: token,
164 }
165 
166 bundleBuf, err := bundle.MarshalVT()
167 as.NoError(err)
168 
169 certProvider.On("GetCertificateWithContext",
170 mock.Anything,
171 mock.MatchedBy(func(chi *tls.ClientHelloInfo) bool {
172 return chi.ServerName == hostname
173 }),
174 ).Return(&tls.Certificate{
175 Certificate: [][]byte{
176 []byte("123"),
177 },
178 }, nil).Once()
179 
180 node.On("Get",
181 mock.Anything,
182 mock.MatchedBy(func(key []byte) bool {
183 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
184 }),
185 ).Return(bundleBuf, nil)
186 node.On("Get",
187 mock.Anything,
188 mock.MatchedBy(func(key []byte) bool {
189 return bytes.Equal(key, []byte(tun.ClientTokenKey(token)))
190 }),
191 ).Return(clientBuf, nil)
192 node.On("Put",
193 mock.Anything,
194 mock.MatchedBy(func(key []byte) bool {
195 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
196 }),
197 mock.Anything,
198 ).Return(nil).Once()
199 node.On("PrefixAppend",
200 mock.Anything,
201 mock.MatchedBy(func(key []byte) bool {
202 return bytes.Equal(key, []byte(tun.ClientHostnamesPrefix(token)))
203 }),
204 mock.MatchedBy(func(key []byte) bool {
205 return bytes.Equal(key, []byte(hostname))
206 }),
207 ).Return(nil).Once()
208 
209 tp := mocks.SelfTransport()
210 streamRouter := transport.NewStreamRouter(logger, nil, tp)
211 go streamRouter.Accept(ctx)
212 
213 serv.AttachRouter(ctx, streamRouter)
214 
215 cRPC := rpc.DynamicTunnelClient(rpc.DisablePooling(ctx), tp)
216 
217 privKey := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
218 tp.WithCertificate(toCertificate(as, logger, cli, token, withExtractPrivKey(privKey)))
219 
220 proof, err := pow.GenerateSolution(privKey, pow.Parameters{
221 Difficulty: acme.HashcashDifficulty,
222 Expires: acme.HashcashExpires,
223 GetSubject: func(pubKey ed25519.PublicKey) string {
224 return hostname
225 },
226 })
227 as.NoError(err)
228 
229 resp, err := cRPC.AcmeValidate(rpc.WithNode(ctx, cli), &protocol.ValidateRequest{
230 Proof: proof,
231 Hostname: hostname,
232 })
233 
234 as.NoError(err)
235 as.NotNil(resp)
236 as.Equal(testRootDomain, resp.GetApex())
237 
238 first, err := cRPC.GetCertificate(rpc.WithNode(ctx, cli), &protocol.KeylessGetCertificateRequest{
239 Proof: proof,
240 Hostname: hostname,
241 })
242 as.NoError(err)
243 as.NotNil(first)
244 as.Equal([]byte("123"), first.GetCertificates()[0])
245 
246 second, err := cRPC.GetCertificate(rpc.WithNode(ctx, cli), &protocol.KeylessGetCertificateRequest{
247 Proof: proof,
248 Hostname: hostname,
249 })
250 as.NoError(err)
251 as.NotNil(second)
252 as.Equal([]byte("123"), second.GetCertificates()[0])
253 
254 certProvider.AssertNumberOfCalls(t, "GetCertificateWithContext", 1)
255 
256 node.AssertExpectations(t)
257 resolver.AssertExpectations(t)
258}
259 
260func TestKeylessSign(t *testing.T) {
261 as := require.New(t)
262 
263 resolver := new(mocks.Resolver)
264 certProvider := new(mocks.CertProvider)
265 
266 logger, node, _, _, serv := getFixture(t, as, withResolver(resolver))
267 cli, _, _ := getIdentities()
268 
269 serv.Config.CertProvider = certProvider
270 
271 ctx := t.Context()
272 
273 hostname := "external.example.com"
274 token := &protocol.ClientToken{
275 Token: mustGenerateToken(),
276 }
277 cli.Address = string(token.GetToken())
278 
279 clientBuf, err := cli.MarshalVT()
280 as.NoError(err)
281 
282 bundle := &protocol.CustomHostname{
283 ClientIdentity: cli,
284 ClientToken: token,
285 }
286 
287 bundleBuf, err := bundle.MarshalVT()
288 as.NoError(err)
289 
290 _, providerX509Cert, providerPrivateKey := testMakeRSACert(as)
291 certProvider.On("GetCertificateWithContext",
292 mock.Anything,
293 mock.MatchedBy(func(chi *tls.ClientHelloInfo) bool {
294 return chi.ServerName == hostname
295 }),
296 ).Return(&tls.Certificate{
297 Certificate: [][]byte{providerX509Cert.Raw},
298 Leaf: providerX509Cert,
299 PrivateKey: providerPrivateKey,
300 }, nil)
301 
302 node.On("Get",
303 mock.Anything,
304 mock.MatchedBy(func(key []byte) bool {
305 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
306 }),
307 ).Return(bundleBuf, nil)
308 node.On("Get",
309 mock.Anything,
310 mock.MatchedBy(func(key []byte) bool {
311 return bytes.Equal(key, []byte(tun.ClientTokenKey(token)))
312 }),
313 ).Return(clientBuf, nil)
314 node.On("Put",
315 mock.Anything,
316 mock.MatchedBy(func(key []byte) bool {
317 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
318 }),
319 mock.Anything,
320 ).Return(nil).Once()
321 node.On("PrefixAppend",
322 mock.Anything,
323 mock.MatchedBy(func(key []byte) bool {
324 return bytes.Equal(key, []byte(tun.ClientHostnamesPrefix(token)))
325 }),
326 mock.MatchedBy(func(key []byte) bool {
327 return bytes.Equal(key, []byte(hostname))
328 }),
329 ).Return(nil).Once()
330 
331 tp := mocks.SelfTransport()
332 streamRouter := transport.NewStreamRouter(logger, nil, tp)
333 go streamRouter.Accept(ctx)
334 
335 serv.AttachRouter(ctx, streamRouter)
336 
337 cRPC := rpc.DynamicTunnelClient(rpc.DisablePooling(ctx), tp)
338 
339 privKey := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
340 tp.WithCertificate(toCertificate(as, logger, cli, token, withExtractPrivKey(privKey)))
341 
342 proof, err := pow.GenerateSolution(privKey, pow.Parameters{
343 Difficulty: acme.HashcashDifficulty,
344 Expires: acme.HashcashExpires,
345 GetSubject: func(pubKey ed25519.PublicKey) string {
346 return hostname
347 },
348 })
349 as.NoError(err)
350 
351 resp, err := cRPC.AcmeValidate(rpc.WithNode(ctx, cli), &protocol.ValidateRequest{
352 Proof: proof,
353 Hostname: hostname,
354 })
355 
356 as.NoError(err)
357 as.NotNil(resp)
358 as.Equal(testRootDomain, resp.GetApex())
359 
360 rawMsg := []byte("hello world")
361 digest := sha256.Sum256(rawMsg)
362 sig, err := cRPC.Sign(rpc.WithNode(ctx, cli), &protocol.KeylessSignRequest{
363 Proof: proof,
364 Hostname: hostname,
365 Digest: digest[:],
366 Algo: protocol.KeylessSignRequest_SHA256,
367 })
368 as.NoError(err)
369 as.NotNil(sig)
370 as.NoError(rsa.VerifyPKCS1v15(providerX509Cert.PublicKey.(*rsa.PublicKey), crypto.SHA256, digest[:], sig.GetSignature()))
371 
372 node.AssertExpectations(t)
373 resolver.AssertExpectations(t)
374}
375 
376func testMakeRSACert(as *require.Assertions) (derBytes []byte, x509Cert *x509.Certificate, privateKey *rsa.PrivateKey) {
377 var err error
378 
379 privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
380 as.NoError(err)
381 
382 template := x509.Certificate{
383 SerialNumber: big.NewInt(1),
384 Subject: pkix.Name{
385 Organization: []string{"dev"},
386 },
387 NotBefore: time.Now(),
388 NotAfter: time.Now().Add(time.Hour * 24 * 365), // Valid for 1 year
389 
390 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
391 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
392 BasicConstraintsValid: true,
393 }
394 
395 derBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
396 as.NoError(err)
397 
398 x509Cert, err = x509.ParseCertificate(derBytes)
399 as.NoError(err)
400 
401 return
402}