WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 9f2241d

Browse files
authored
Support P256 network curve in API calls (#16)
This PR adds PrivateKey, PublicKey, and TrustedKey types for handling host keys and CA keys. dnapi will always send both P256 and Ed25519 keys with enrollment requests. From that point forward, dnapi will only generate keys of the type used in prior requests. dnapi does not attempt to "filter" trusted keys to only the curve supported by Nebula. In other words, a P256 network requires P256 CAs and host keys, but does not necessarily require P256 trusted keys. Some code was moved into a keys sub-package in order to allow dnapitest to import it, while still allowing the dnapi package to import dnapitest. The bulk of this code is tested through a call to DoUpdate.
1 parent 2e2264a commit 9f2241d

File tree

12 files changed

+1131
-196
lines changed

12 files changed

+1131
-196
lines changed

client.go

Lines changed: 100 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package dnapi
44
import (
55
"bytes"
66
"context"
7+
"crypto/ecdsa"
78
"crypto/ed25519"
9+
"crypto/rand"
810
"encoding/json"
911
"fmt"
1012
"io"
@@ -14,9 +16,9 @@ import (
1416
"sync/atomic"
1517
"time"
1618

19+
"github.com/DefinedNet/dnapi/keys"
1720
"github.com/DefinedNet/dnapi/message"
1821
"github.com/sirupsen/logrus"
19-
"github.com/slackhq/nebula/cert"
2022
)
2123

2224
// Client communicates with the API server.
@@ -93,29 +95,43 @@ type EnrollMeta struct {
9395
// On success it returns the Nebula config generated by the server, a Nebula private key PEM to be inserted into the
9496
// config (see api.InsertConfigPrivateKey), credentials to be used in DNClient API requests, and a meta object
9597
// containing organization info.
96-
func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code string) ([]byte, []byte, *Credentials, *EnrollMeta, error) {
98+
func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code string) ([]byte, []byte, *keys.Credentials, *EnrollMeta, error) {
9799
logger.WithFields(logrus.Fields{"server": c.dnServer}).Debug("Making enrollment request to API")
98100

99-
// Generate initial Ed25519 keypair for API communication
100-
dhPubkeyPEM, dhPrivkeyPEM, edPubkey, edPrivkey, err := newKeys()
101+
// Generate newKeys for the enrollment request
102+
newKeys, err := keys.New()
103+
if err != nil {
104+
return nil, nil, nil, nil, err
105+
}
106+
107+
hostEd25519PublicKeyPEM, err := newKeys.HostEd25519PublicKey.MarshalPEM()
108+
if err != nil {
109+
return nil, nil, nil, nil, err
110+
}
111+
112+
hostP256PublicKeyPEM, err := newKeys.HostP256PublicKey.MarshalPEM()
101113
if err != nil {
102114
return nil, nil, nil, nil, err
103115
}
104116

105117
// Make a request to the API with the enrollment code
106118
jv, err := json.Marshal(message.EnrollRequest{
107-
Code: code,
108-
DHPubkey: dhPubkeyPEM,
109-
EdPubkey: cert.MarshalEd25519PublicKey(edPubkey),
110-
Timestamp: time.Now(),
119+
Code: code,
120+
NebulaPubkeyX25519: newKeys.NebulaX25519PublicKeyPEM,
121+
HostPubkeyEd25519: hostEd25519PublicKeyPEM,
122+
NebulaPubkeyP256: newKeys.NebulaP256PublicKeyPEM,
123+
HostPubkeyP256: hostP256PublicKeyPEM,
124+
Timestamp: time.Now(),
111125
})
112126
if err != nil {
113127
return nil, nil, nil, nil, err
114128
}
129+
115130
enrollURL, err := url.JoinPath(c.dnServer, message.EnrollEndpoint)
116131
if err != nil {
117132
return nil, nil, nil, nil, err
118133
}
134+
119135
req, err := http.NewRequestWithContext(ctx, "POST", enrollURL, bytes.NewBuffer(jv))
120136
if err != nil {
121137
return nil, nil, nil, nil, err
@@ -157,22 +173,36 @@ func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code str
157173
OrganizationName: r.Data.Organization.Name,
158174
}
159175

160-
trustedKeys, err := Ed25519PublicKeysFromPEM(r.Data.TrustedKeys)
176+
// Determine the private keys to save based on the network curve type
177+
var privkeyPEM []byte
178+
var privkey keys.PrivateKey
179+
switch r.Data.Network.Curve {
180+
case message.NetworkCurve25519:
181+
privkeyPEM = newKeys.NebulaX25519PrivateKeyPEM
182+
privkey = newKeys.HostEd25519PrivateKey
183+
case message.NetworkCurveP256:
184+
privkeyPEM = newKeys.NebulaP256PrivateKeyPEM
185+
privkey = newKeys.HostP256PrivateKey
186+
default:
187+
return nil, nil, nil, nil, &APIError{e: fmt.Errorf("unsupported curve type: %s", r.Data.Network.Curve), ReqID: reqID}
188+
}
189+
190+
trustedKeys, err := keys.TrustedKeysFromPEM(r.Data.TrustedKeys)
161191
if err != nil {
162192
return nil, nil, nil, nil, &APIError{e: fmt.Errorf("failed to load trusted keys from bundle: %s", err), ReqID: reqID}
163193
}
164194

165-
creds := &Credentials{
195+
creds := &keys.Credentials{
166196
HostID: r.Data.HostID,
167-
PrivateKey: edPrivkey,
197+
PrivateKey: privkey,
168198
Counter: r.Data.Counter,
169199
TrustedKeys: trustedKeys,
170200
}
171-
return r.Data.Config, dhPrivkeyPEM, creds, meta, nil
201+
return r.Data.Config, privkeyPEM, creds, meta, nil
172202
}
173203

174204
// CheckForUpdate sends a signed message to the DNClient API to learn if there is a new configuration available.
175-
func (c *Client) CheckForUpdate(ctx context.Context, creds Credentials) (bool, error) {
205+
func (c *Client) CheckForUpdate(ctx context.Context, creds keys.Credentials) (bool, error) {
176206
respBody, err := c.postDNClient(ctx, message.CheckForUpdate, nil, creds.HostID, creds.Counter, creds.PrivateKey)
177207
if err != nil {
178208
return false, fmt.Errorf("failed to post message to dnclient api: %w", err)
@@ -187,7 +217,7 @@ func (c *Client) CheckForUpdate(ctx context.Context, creds Credentials) (bool, e
187217

188218
// LongPollWait sends a signed message to a DNClient API endpoint that will block, returning only
189219
// if there is an action the client should take before the timeout (config updates, debug commands)
190-
func (c *Client) LongPollWait(ctx context.Context, creds Credentials, supportedActions []string) (*message.LongPollWaitResponse, error) {
220+
func (c *Client) LongPollWait(ctx context.Context, creds keys.Credentials, supportedActions []string) (*message.LongPollWaitResponse, error) {
191221
value, err := json.Marshal(message.LongPollWaitRequest{
192222
SupportedActions: supportedActions,
193223
})
@@ -207,30 +237,54 @@ func (c *Client) LongPollWait(ctx context.Context, creds Credentials, supportedA
207237
return &result.Data, nil
208238
}
209239

210-
// DoUpdate sends a signed message to the DNClient API to fetch the new configuration update. During this call a new
211-
// DH X25519 keypair is generated for the new Nebula certificate as well as a new Ed25519 keypair for DNClient API
212-
// communication. On success it returns the new config, a Nebula private key PEM to be inserted into the config (see
213-
// api.InsertConfigPrivateKey) and new DNClient API credentials.
214-
func (c *Client) DoUpdate(ctx context.Context, creds Credentials) ([]byte, []byte, *Credentials, error) {
240+
// DoUpdate sends a signed message to the DNClient API to fetch the new configuration update. During this call new keys
241+
// are generated both for Nebula and DNClient API communication. If the API response is successful, the new configuration
242+
// is returned along with the new Nebula private key PEM and new DNClient API credentials.
243+
//
244+
// See dnapi.InsertConfigPrivateKey for how to insert the new Nebula private key into the configuration.
245+
func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte, []byte, *keys.Credentials, error) {
215246
// Rotate keys
216-
dhPubkeyPEM, dhPrivkeyPEM, edPubkey, edPrivkey, err := newKeys()
247+
var nebulaPrivkeyPEM []byte // ECDH
248+
var hostPrivkey keys.PrivateKey // ECDSA
249+
250+
newKeys, err := keys.New()
217251
if err != nil {
218-
return nil, nil, nil, err
252+
return nil, nil, nil, fmt.Errorf("failed to generate new keys: %s", err)
219253
}
220254

221-
updateKeys := message.DoUpdateRequest{
222-
EdPubkeyPEM: cert.MarshalEd25519PublicKey(edPubkey),
223-
DHPubkeyPEM: dhPubkeyPEM,
224-
Nonce: nonce(),
255+
msg := message.DoUpdateRequest{
256+
Nonce: nonce(),
257+
}
258+
259+
// Set the correct keypair based on the current private key type
260+
switch creds.PrivateKey.Unwrap().(type) {
261+
case ed25519.PrivateKey:
262+
hostPubkeyPEM, err := newKeys.HostEd25519PublicKey.MarshalPEM()
263+
if err != nil {
264+
return nil, nil, nil, fmt.Errorf("failed to marshal Ed25519 public key: %s", err)
265+
}
266+
hostPrivkey = newKeys.HostEd25519PrivateKey
267+
nebulaPrivkeyPEM = newKeys.NebulaX25519PrivateKeyPEM
268+
msg.HostPubkeyEd25519 = hostPubkeyPEM
269+
msg.NebulaPubkeyX25519 = newKeys.NebulaX25519PublicKeyPEM
270+
case *ecdsa.PrivateKey:
271+
hostPubkeyPEM, err := newKeys.HostP256PublicKey.MarshalPEM()
272+
if err != nil {
273+
return nil, nil, nil, fmt.Errorf("failed to marshal P256 public key: %s", err)
274+
}
275+
hostPrivkey = newKeys.HostP256PrivateKey
276+
nebulaPrivkeyPEM = newKeys.NebulaP256PrivateKeyPEM
277+
msg.HostPubkeyP256 = hostPubkeyPEM
278+
msg.NebulaPubkeyP256 = newKeys.NebulaP256PublicKeyPEM
225279
}
226280

227-
updateKeysBlob, err := json.Marshal(updateKeys)
281+
blob, err := json.Marshal(msg)
228282
if err != nil {
229283
return nil, nil, nil, fmt.Errorf("failed to marshal DNClient message: %s", err)
230284
}
231285

232286
// Make API call
233-
resp, err := c.postDNClient(ctx, message.DoUpdate, updateKeysBlob, creds.HostID, creds.Counter, creds.PrivateKey)
287+
resp, err := c.postDNClient(ctx, message.DoUpdate, blob, creds.HostID, creds.Counter, creds.PrivateKey)
234288
if err != nil {
235289
return nil, nil, nil, fmt.Errorf("failed to make API call to Defined Networking: %w", err)
236290
}
@@ -243,7 +297,7 @@ func (c *Client) DoUpdate(ctx context.Context, creds Credentials) ([]byte, []byt
243297
// Verify the signature
244298
valid := false
245299
for _, caPubkey := range creds.TrustedKeys {
246-
if ed25519.Verify(caPubkey, resultWrapper.Data.Message, resultWrapper.Data.Signature) {
300+
if caPubkey.Verify(resultWrapper.Data.Message, resultWrapper.Data.Signature) {
247301
valid = true
248302
break
249303
}
@@ -260,31 +314,31 @@ func (c *Client) DoUpdate(ctx context.Context, creds Credentials) ([]byte, []byt
260314
}
261315

262316
// Verify the nonce
263-
if !bytes.Equal(result.Nonce, updateKeys.Nonce) {
264-
return nil, nil, nil, fmt.Errorf("nonce mismatch between request (%s) and response (%s)", updateKeys.Nonce, result.Nonce)
317+
if !bytes.Equal(result.Nonce, msg.Nonce) {
318+
return nil, nil, nil, fmt.Errorf("nonce mismatch between request (%s) and response (%s)", msg.Nonce, result.Nonce)
265319
}
266320

267321
// Verify the counter
268322
if result.Counter <= creds.Counter {
269323
return nil, nil, nil, fmt.Errorf("counter in request (%d) should be less than counter in response (%d)", creds.Counter, result.Counter)
270324
}
271325

272-
trustedKeys, err := Ed25519PublicKeysFromPEM(result.TrustedKeys)
326+
trustedKeys, err := keys.TrustedKeysFromPEM(result.TrustedKeys)
273327
if err != nil {
274328
return nil, nil, nil, fmt.Errorf("failed to load trusted keys from bundle: %s", err)
275329
}
276330

277-
newCreds := &Credentials{
331+
newCreds := &keys.Credentials{
278332
HostID: creds.HostID,
279333
Counter: result.Counter,
280-
PrivateKey: edPrivkey,
334+
PrivateKey: hostPrivkey,
281335
TrustedKeys: trustedKeys,
282336
}
283337

284-
return result.Config, dhPrivkeyPEM, newCreds, nil
338+
return result.Config, nebulaPrivkeyPEM, newCreds, nil
285339
}
286340

287-
func (c *Client) CommandResponse(ctx context.Context, creds Credentials, responseToken string, response any) error {
341+
func (c *Client) CommandResponse(ctx context.Context, creds keys.Credentials, responseToken string, response any) error {
288342
value, err := json.Marshal(message.CommandResponseRequest{
289343
ResponseToken: responseToken,
290344
Response: response,
@@ -297,7 +351,7 @@ func (c *Client) CommandResponse(ctx context.Context, creds Credentials, respons
297351
return err
298352
}
299353

300-
func (c *Client) StreamCommandResponse(ctx context.Context, creds Credentials, responseToken string) (*StreamController, error) {
354+
func (c *Client) StreamCommandResponse(ctx context.Context, creds keys.Credentials, responseToken string) (*StreamController, error) {
301355
value, err := json.Marshal(message.CommandResponseRequest{
302356
ResponseToken: responseToken,
303357
})
@@ -310,7 +364,7 @@ func (c *Client) StreamCommandResponse(ctx context.Context, creds Credentials, r
310364

311365
// streamingPostDNClient wraps and signs the given dnclientRequestWrapper message, and makes a streaming API call.
312366
// On success, it returns a StreamController to interact with the request. On error, the error is returned.
313-
func (c *Client) streamingPostDNClient(ctx context.Context, reqType string, value []byte, hostID string, counter uint, privkey ed25519.PrivateKey) (*StreamController, error) {
367+
func (c *Client) streamingPostDNClient(ctx context.Context, reqType string, value []byte, hostID string, counter uint, privkey keys.PrivateKey) (*StreamController, error) {
314368
pr, pw := io.Pipe()
315369

316370
postBody, err := SignRequestV1(reqType, value, hostID, counter, privkey)
@@ -368,7 +422,7 @@ func (c *Client) streamingPostDNClient(ctx context.Context, reqType string, valu
368422

369423
// postDNClient wraps and signs the given dnclientRequestWrapper message, and makes the API call.
370424
// On success, it returns the response message body. On error, the error is returned.
371-
func (c *Client) postDNClient(ctx context.Context, reqType string, value []byte, hostID string, counter uint, privkey ed25519.PrivateKey) ([]byte, error) {
425+
func (c *Client) postDNClient(ctx context.Context, reqType string, value []byte, hostID string, counter uint, privkey keys.PrivateKey) ([]byte, error) {
372426
postBody, err := SignRequestV1(reqType, value, hostID, counter, privkey)
373427
if err != nil {
374428
return nil, err
@@ -472,3 +526,11 @@ func (t *uaTransport) RoundTrip(req *http.Request) (*http.Response, error) {
472526
req.Header.Set("User-Agent", t.useragent)
473527
return t.T.RoundTrip(req)
474528
}
529+
530+
func nonce() []byte {
531+
nonce := make([]byte, 16)
532+
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
533+
panic(err)
534+
}
535+
return nonce
536+
}

0 commit comments

Comments
 (0)