diff --git a/go-workspace/src/nebula/nebula.go b/go-workspace/src/nebula/nebula.go index b7d5f9d..00f60ae 100644 --- a/go-workspace/src/nebula/nebula.go +++ b/go-workspace/src/nebula/nebula.go @@ -4,6 +4,7 @@ package nebula import ( crypticnet "cryptic-net" + "crypto/ed25519" "crypto/rand" "fmt" "io" @@ -32,6 +33,13 @@ type HostCert struct { HostCert []byte } +// CACert contains the certificate and private files which can be used to create +// HostCerts. Each file is PEM encoded. +type CACert struct { + CACert []byte + CAKey []byte +} + // NewHostCert generates a new key/cert for a nebula host using the CA key // which will be found in the adminFS. func NewHostCert( @@ -123,3 +131,42 @@ func NewHostCert( HostCert: hostCrtPEM, }, nil } + +// NewCACert generates a CACert. The domain should be the network's root domain, +// and is included in the signing certificate's Name field. +func NewCACert(domain string) (CACert, error) { + + pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + panic(fmt.Errorf("generating ed25519 key: %w", err)) + } + + now := time.Now() + expireAt := now.Add(2 * 365 * 24 * time.Hour) + + caCrt := cert.NebulaCertificate{ + Details: cert.NebulaCertificateDetails{ + Name: fmt.Sprintf("%s cryptic-net root cert", domain), + NotBefore: now, + NotAfter: expireAt, + PublicKey: pubKey, + IsCA: true, + }, + } + + if err := caCrt.Sign(privKey); err != nil { + return CACert{}, fmt.Errorf("signing caCrt: %w", err) + } + + caKeyPEM := cert.MarshalEd25519PrivateKey(privKey) + + caCrtPem, err := caCrt.MarshalToPEM() + if err != nil { + return CACert{}, fmt.Errorf("marshaling caCrt: %w", err) + } + + return CACert{ + CACert: caCrtPem, + CAKey: caKeyPEM, + }, nil +}