@@ -4,7 +4,9 @@ package dnapi
44import (
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