Refactor how signing/encryption keys are typed and (un)marshaled
This commit is contained in:
parent
65fa208a34
commit
c645a8c767
@ -69,7 +69,7 @@ in rec {
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
vendorHash = "sha256-P1TXG0fG8/6n37LmM5ApYctqoZzJFlvFAO2Zl85SVvk=";
|
vendorHash = "sha256-33gwBj+6x9I/yz0Qf4G8YXRgC/HfwHCedqzrCE4FHHk=";
|
||||||
|
|
||||||
subPackages = [
|
subPackages = [
|
||||||
"./cmd/entrypoint"
|
"./cmd/entrypoint"
|
||||||
|
@ -78,10 +78,5 @@ type Host struct {
|
|||||||
// This assumes that the Host and its data has already been verified against the
|
// This assumes that the Host and its data has already been verified against the
|
||||||
// CA signing key.
|
// CA signing key.
|
||||||
func (h Host) IP() net.IP {
|
func (h Host) IP() net.IP {
|
||||||
ip, err := nebula.IPFromHostCertPEM(h.PublicCredentials.CertPEM)
|
return h.PublicCredentials.Cert.Details.Ips[0].IP
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("could not parse IP out of cert for host %q: %w", h.Name, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip
|
|
||||||
}
|
}
|
||||||
|
@ -381,13 +381,23 @@ var subCmdAdminCreateNebulaCert = subCmd{
|
|||||||
return fmt.Errorf("reading public key from %q: %w", *pubKeyPath, err)
|
return fmt.Errorf("reading public key from %q: %w", *pubKeyPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nebulaHostCertPEM, err := nebula.NewHostCertPEM(
|
var hostPub nebula.EncryptingPublicKey
|
||||||
adm.Nebula.CACredentials, string(hostPubPEM), *hostName, ip,
|
if err := hostPub.UnmarshalNebulaPEM(hostPubPEM); err != nil {
|
||||||
|
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nebulaHostCert, err := nebula.NewHostCert(
|
||||||
|
adm.Nebula.CACredentials, hostPub, *hostName, ip,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating cert: %w", err)
|
return fmt.Errorf("creating cert: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nebulaHostCertPEM, err := nebulaHostCert.MarshalToPEM()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshaling cert to PEM: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := os.Stdout.Write([]byte(nebulaHostCertPEM)); err != nil {
|
if _, err := os.Stdout.Write([]byte(nebulaHostCertPEM)); err != nil {
|
||||||
return fmt.Errorf("writing to stdout: %w", err)
|
return fmt.Errorf("writing to stdout: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"isle/jsonutil"
|
"isle/jsonutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var subCmdNebulaShow = subCmd{
|
var subCmdNebulaShow = subCmd{
|
||||||
@ -23,10 +21,10 @@ var subCmdNebulaShow = subCmd{
|
|||||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
caPublicCreds := hostBootstrap.CAPublicCredentials
|
caCert := hostBootstrap.CAPublicCredentials.Cert
|
||||||
caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(caPublicCreds.CertPEM))
|
caCertPEM, err := caCert.MarshalToPEM()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unmarshaling ca.crt: %w", err)
|
return fmt.Errorf("marshaling CA cert to PEM: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(caCert.Details.Subnets) != 1 {
|
if len(caCert.Details.Subnets) != 1 {
|
||||||
@ -48,7 +46,7 @@ var subCmdNebulaShow = subCmd{
|
|||||||
SubnetCIDR string
|
SubnetCIDR string
|
||||||
Lighthouses []outLighthouse
|
Lighthouses []outLighthouse
|
||||||
}{
|
}{
|
||||||
CACert: caPublicCreds.CertPEM,
|
CACert: string(caCertPEM),
|
||||||
SubnetCIDR: subnet.String(),
|
SubnetCIDR: subnet.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"code.betamike.com/micropelago/pmux/pmuxlib"
|
"code.betamike.com/micropelago/pmux/pmuxlib"
|
||||||
|
"github.com/slackhq/nebula/cert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// waitForNebula waits for the nebula interface to have been started up. It does
|
// waitForNebula waits for the nebula interface to have been started up. It does
|
||||||
@ -56,11 +57,29 @@ func nebulaPmuxProcConfig(
|
|||||||
staticHostMap[ip] = []string{host.Nebula.PublicAddr}
|
staticHostMap[ip] = []string{host.Nebula.PublicAddr}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caCertPEM, err := hostBootstrap.CAPublicCredentials.Cert.MarshalToPEM()
|
||||||
|
if err != nil {
|
||||||
|
return pmuxlib.ProcessConfig{}, fmt.Errorf(
|
||||||
|
"marshaling CA cert to PEM: :%w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostCertPEM, err := hostBootstrap.PublicCredentials.Cert.MarshalToPEM()
|
||||||
|
if err != nil {
|
||||||
|
return pmuxlib.ProcessConfig{}, fmt.Errorf(
|
||||||
|
"marshaling host cert to PEM: :%w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostKeyPEM := cert.MarshalX25519PrivateKey(
|
||||||
|
hostBootstrap.PrivateCredentials.EncryptingPrivateKey.Bytes(),
|
||||||
|
)
|
||||||
|
|
||||||
config := map[string]interface{}{
|
config := map[string]interface{}{
|
||||||
"pki": map[string]string{
|
"pki": map[string]string{
|
||||||
"ca": hostBootstrap.CAPublicCredentials.CertPEM,
|
"ca": string(caCertPEM),
|
||||||
"cert": hostBootstrap.PublicCredentials.CertPEM,
|
"cert": string(hostCertPEM),
|
||||||
"key": hostBootstrap.PrivateCredentials.PrivateKeyPEM,
|
"key": string(hostKeyPEM),
|
||||||
},
|
},
|
||||||
"static_host_map": staticHostMap,
|
"static_host_map": staticHostMap,
|
||||||
"punchy": map[string]bool{
|
"punchy": map[string]bool{
|
||||||
|
@ -23,6 +23,7 @@ require (
|
|||||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||||
|
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.13.5 // indirect
|
github.com/klauspost/compress v1.13.5 // indirect
|
||||||
github.com/klauspost/cpuid v1.3.1 // indirect
|
github.com/klauspost/cpuid v1.3.1 // indirect
|
||||||
github.com/minio/md5-simd v1.1.0 // indirect
|
github.com/minio/md5-simd v1.1.0 // indirect
|
||||||
|
@ -24,6 +24,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||||
|
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||||
github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4=
|
github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4=
|
||||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
117
go/nebula/encrypting_key.go
Normal file
117
go/nebula/encrypting_key.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package nebula
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/slackhq/nebula/cert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
encPrivKeyPrefix = []byte("x0")
|
||||||
|
encPubKeyPrefix = []byte("X0")
|
||||||
|
|
||||||
|
x25519 = ecdh.X25519()
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncryptingPublicKey wraps an X25519-based ECDH public key to provide
|
||||||
|
// convenient text (un)marshaling methods.
|
||||||
|
type EncryptingPublicKey struct{ inner *ecdh.PublicKey }
|
||||||
|
|
||||||
|
// MarshalText implements the encoding.TextMarshaler interface.
|
||||||
|
func (pk EncryptingPublicKey) MarshalText() ([]byte, error) {
|
||||||
|
return encodeWithPrefix(encPubKeyPrefix, pk.inner.Bytes()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the raw bytes of the EncryptingPublicKey.
|
||||||
|
func (k EncryptingPublicKey) Bytes() []byte {
|
||||||
|
return k.inner.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
|
func (pk *EncryptingPublicKey) UnmarshalText(b []byte) error {
|
||||||
|
b, err := decodeWithPrefix(encPubKeyPrefix, b)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshaling: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pk.inner, err = x25519.NewPublicKey(b); err != nil {
|
||||||
|
return fmt.Errorf("converting bytes to public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalNebulaPEM unmarshals the EncryptingPublicKey as a nebula host public
|
||||||
|
// key PEM.
|
||||||
|
func (pk *EncryptingPublicKey) UnmarshalNebulaPEM(b []byte) error {
|
||||||
|
b, _, err := cert.UnmarshalEd25519PublicKey(b)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshaling: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pk.inner, err = x25519.NewPublicKey(b); err != nil {
|
||||||
|
return fmt.Errorf("converting bytes to public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pk EncryptingPublicKey) String() string {
|
||||||
|
b, err := pk.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptingPrivateKey wraps an X25519-based ECDH private key to provide
|
||||||
|
// convenient text (un)marshaling methods.
|
||||||
|
type EncryptingPrivateKey struct{ inner *ecdh.PrivateKey }
|
||||||
|
|
||||||
|
// NewEncryptingPrivateKey generates and returns a fresh EncryptingPrivateKey.
|
||||||
|
func NewEncryptingPrivateKey() EncryptingPrivateKey {
|
||||||
|
k, err := x25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return EncryptingPrivateKey{k}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey returns the public key which corresponds with this private key.
|
||||||
|
func (k EncryptingPrivateKey) PublicKey() EncryptingPublicKey {
|
||||||
|
return EncryptingPublicKey{k.inner.PublicKey()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements the encoding.TextMarshaler interface.
|
||||||
|
func (k EncryptingPrivateKey) MarshalText() ([]byte, error) {
|
||||||
|
return encodeWithPrefix(encPrivKeyPrefix, k.inner.Bytes()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the raw bytes of the EncryptingPrivateKey.
|
||||||
|
func (k EncryptingPrivateKey) Bytes() []byte {
|
||||||
|
return k.inner.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
|
func (k *EncryptingPrivateKey) UnmarshalText(b []byte) error {
|
||||||
|
b, err := decodeWithPrefix(encPrivKeyPrefix, b)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshaling: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.inner, err = x25519.NewPrivateKey(b); err != nil {
|
||||||
|
return fmt.Errorf("converting bytes to private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k EncryptingPrivateKey) String() string {
|
||||||
|
b, err := k.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
@ -1,72 +0,0 @@
|
|||||||
package nebula
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SigningPrivateKey wraps an ed25519.PrivateKey to provide convenient JSON
|
|
||||||
// (un)marshaling methods.
|
|
||||||
type SigningPrivateKey ed25519.PrivateKey
|
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
|
||||||
func (k SigningPrivateKey) MarshalJSON() ([]byte, error) {
|
|
||||||
pemStr := cert.MarshalEd25519PrivateKey(ed25519.PrivateKey(k))
|
|
||||||
return json.Marshal(string(pemStr))
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
|
||||||
func (k *SigningPrivateKey) UnmarshalJSON(b []byte) error {
|
|
||||||
var pemStr string
|
|
||||||
if err := json.Unmarshal(b, &pemStr); err != nil {
|
|
||||||
return fmt.Errorf("unmarshaling into string: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key, _, err := cert.UnmarshalEd25519PrivateKey([]byte(pemStr))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshaling from PEM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*k = SigningPrivateKey(key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigningPublicKey wraps an ed25519.PublicKey to provide convenient JSON
|
|
||||||
// (un)marshaling methods.
|
|
||||||
type SigningPublicKey ed25519.PublicKey
|
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
|
||||||
func (k SigningPublicKey) MarshalJSON() ([]byte, error) {
|
|
||||||
pemStr := cert.MarshalEd25519PublicKey(ed25519.PublicKey(k))
|
|
||||||
return json.Marshal(string(pemStr))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements the json.Unmarshaler interface.
|
|
||||||
func (k *SigningPublicKey) UnmarshalJSON(b []byte) error {
|
|
||||||
var pemStr string
|
|
||||||
if err := json.Unmarshal(b, &pemStr); err != nil {
|
|
||||||
return fmt.Errorf("unmarshaling into string: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key, _, err := cert.UnmarshalEd25519PublicKey([]byte(pemStr))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshaling from PEM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*k = SigningPublicKey(key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSigningPair generates and returns a new key pair which can be used
|
|
||||||
// for signing arbitrary blobs of bytes.
|
|
||||||
func GenerateSigningPair() (SigningPublicKey, SigningPrivateKey) {
|
|
||||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("generating ed25519 key: %w", err))
|
|
||||||
}
|
|
||||||
return SigningPublicKey(pub), SigningPrivateKey(priv)
|
|
||||||
}
|
|
@ -3,35 +3,32 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"golang.org/x/crypto/curve25519"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HostPublicCredentials contains certificate and signing public keys which are
|
// HostPublicCredentials contains certificate and signing public keys which are
|
||||||
// able to be broadcast publicly.
|
// able to be broadcast publicly.
|
||||||
type HostPublicCredentials struct {
|
type HostPublicCredentials struct {
|
||||||
CertPEM string
|
Cert cert.NebulaCertificate
|
||||||
SigningKey SigningPublicKey
|
SigningKey SigningPublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// HostPrivateCredentials contains the private key files which will
|
// HostPrivateCredentials contains the private key files which will
|
||||||
// need to be present on a particular host.
|
// need to be present on a particular host.
|
||||||
type HostPrivateCredentials struct {
|
type HostPrivateCredentials struct {
|
||||||
PrivateKeyPEM string
|
EncryptingPrivateKey EncryptingPrivateKey
|
||||||
SigningPrivateKey SigningPrivateKey
|
SigningPrivateKey SigningPrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// CAPublicCredentials contains certificate and signing public keys which are
|
// CAPublicCredentials contains certificate and signing public keys which are
|
||||||
// able to be broadcast publicly. The signing public key is the same one which
|
// able to be broadcast publicly. The signing public key is the same one which
|
||||||
// is embedded into the certificate.
|
// is embedded into the certificate.
|
||||||
type CAPublicCredentials struct {
|
type CAPublicCredentials struct {
|
||||||
CertPEM string
|
Cert cert.NebulaCertificate
|
||||||
SigningKey SigningPublicKey
|
SigningKey SigningPublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,33 +39,28 @@ type CACredentials struct {
|
|||||||
SigningPrivateKey SigningPrivateKey
|
SigningPrivateKey SigningPrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHostCertPEM generates and signs a new host certificate containing the
|
// NewHostCert generates and signs a new host certificate containing the given
|
||||||
// given public key.
|
// public key.
|
||||||
func NewHostCertPEM(
|
func NewHostCert(
|
||||||
caCreds CACredentials, hostPubPEM string, hostName string, ip net.IP,
|
caCreds CACredentials,
|
||||||
|
hostPub EncryptingPublicKey,
|
||||||
|
hostName string,
|
||||||
|
ip net.IP,
|
||||||
) (
|
) (
|
||||||
string, error,
|
cert.NebulaCertificate, error,
|
||||||
) {
|
) {
|
||||||
hostPub, _, err := cert.UnmarshalX25519PublicKey([]byte(hostPubPEM))
|
caCert := caCreds.Public.Cert
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("unmarshaling public key PEM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(caCreds.Public.CertPEM))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("unmarshaling ca.crt: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
issuer, err := caCert.Sha256Sum()
|
issuer, err := caCert.Sha256Sum()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("getting ca.crt issuer: %w", err)
|
return cert.NebulaCertificate{}, fmt.Errorf("getting ca.crt issuer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expireAt := caCert.Details.NotAfter.Add(-1 * time.Second)
|
expireAt := caCert.Details.NotAfter.Add(-1 * time.Second)
|
||||||
|
|
||||||
subnet := caCert.Details.Subnets[0]
|
subnet := caCert.Details.Subnets[0]
|
||||||
if !subnet.Contains(ip) {
|
if !subnet.Contains(ip) {
|
||||||
return "", fmt.Errorf("invalid ip %q, not contained by network subnet %q", ip, subnet)
|
return cert.NebulaCertificate{}, fmt.Errorf("invalid ip %q, not contained by network subnet %q", ip, subnet)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostCert := cert.NebulaCertificate{
|
hostCert := cert.NebulaCertificate{
|
||||||
@ -80,26 +72,21 @@ func NewHostCertPEM(
|
|||||||
}},
|
}},
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
NotAfter: expireAt,
|
NotAfter: expireAt,
|
||||||
PublicKey: hostPub,
|
PublicKey: hostPub.Bytes(),
|
||||||
IsCA: false,
|
IsCA: false,
|
||||||
Issuer: issuer,
|
Issuer: issuer,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := hostCert.CheckRootConstrains(caCert); err != nil {
|
if err := hostCert.CheckRootConstrains(&caCert); err != nil {
|
||||||
return "", fmt.Errorf("validating certificate constraints: %w", err)
|
return cert.NebulaCertificate{}, fmt.Errorf("validating certificate constraints: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := signCert(&hostCert, caCreds.SigningPrivateKey); err != nil {
|
if err := signCert(&hostCert, caCreds.SigningPrivateKey); err != nil {
|
||||||
return "", fmt.Errorf("signing host cert with ca.key: %w", err)
|
return cert.NebulaCertificate{}, fmt.Errorf("signing host cert with ca.key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostCertPEM, err := hostCert.MarshalToPEM()
|
return hostCert, nil
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("marshalling host.crt: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(hostCertPEM), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHostCredentials generates a new key/cert for a nebula host using the CA
|
// NewHostCredentials generates a new key/cert for a nebula host using the CA
|
||||||
@ -110,39 +97,27 @@ func NewHostCredentials(
|
|||||||
pub HostPublicCredentials, priv HostPrivateCredentials, err error,
|
pub HostPublicCredentials, priv HostPrivateCredentials, err error,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// The logic here is largely based on
|
var (
|
||||||
// https://github.com/slackhq/nebula/blob/v1.4.0/cmd/nebula-cert/sign.go
|
encPrivKey = NewEncryptingPrivateKey()
|
||||||
|
encPubKey = encPrivKey.PublicKey()
|
||||||
|
|
||||||
var hostPub, hostKey []byte
|
signingPubKey, signingPrivKey = GenerateSigningPair()
|
||||||
{
|
)
|
||||||
var pubkey, privkey [32]byte
|
|
||||||
if _, err = io.ReadFull(rand.Reader, privkey[:]); err != nil {
|
|
||||||
err = fmt.Errorf("reading random bytes to form private key: %w", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
curve25519.ScalarBaseMult(&pubkey, &privkey)
|
|
||||||
hostPub, hostKey = pubkey[:], privkey[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
signingPubKey, signingPrivKey := GenerateSigningPair()
|
hostCert, err := NewHostCert(caCreds, encPubKey, hostName, ip)
|
||||||
|
|
||||||
hostPubPEM := cert.MarshalX25519PublicKey(hostPub)
|
|
||||||
hostKeyPEM := cert.MarshalX25519PrivateKey(hostKey)
|
|
||||||
|
|
||||||
hostCertPEM, err := NewHostCertPEM(caCreds, string(hostPubPEM), hostName, ip)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("creating host certificate: %w", err)
|
err = fmt.Errorf("creating host certificate: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pub = HostPublicCredentials{
|
pub = HostPublicCredentials{
|
||||||
CertPEM: hostCertPEM,
|
Cert: hostCert,
|
||||||
SigningKey: signingPubKey,
|
SigningKey: signingPubKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
priv = HostPrivateCredentials{
|
priv = HostPrivateCredentials{
|
||||||
PrivateKeyPEM: string(hostKeyPEM),
|
EncryptingPrivateKey: encPrivKey,
|
||||||
SigningPrivateKey: signingPrivKey,
|
SigningPrivateKey: signingPrivKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -151,14 +126,11 @@ func NewHostCredentials(
|
|||||||
// NewCACredentials generates a CACredentials. The domain should be the network's root domain,
|
// NewCACredentials generates a CACredentials. The domain should be the network's root domain,
|
||||||
// and is included in the signing certificate's Name field.
|
// and is included in the signing certificate's Name field.
|
||||||
func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
|
func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
|
||||||
|
var (
|
||||||
// The logic here is largely based on
|
signingPubKey, signingPrivKey = GenerateSigningPair()
|
||||||
// https://github.com/slackhq/nebula/blob/v1.4.0/cmd/nebula-cert/ca.go
|
now = time.Now()
|
||||||
|
expireAt = now.Add(2 * 365 * 24 * time.Hour)
|
||||||
signingPubKey, signingPrivKey := GenerateSigningPair()
|
)
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
expireAt := now.Add(2 * 365 * 24 * time.Hour)
|
|
||||||
|
|
||||||
caCert := cert.NebulaCertificate{
|
caCert := cert.NebulaCertificate{
|
||||||
Details: cert.NebulaCertificateDetails{
|
Details: cert.NebulaCertificateDetails{
|
||||||
@ -175,33 +147,11 @@ func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
|
|||||||
return CACredentials{}, fmt.Errorf("signing caCert: %w", err)
|
return CACredentials{}, fmt.Errorf("signing caCert: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
certPEM, err := caCert.MarshalToPEM()
|
|
||||||
if err != nil {
|
|
||||||
return CACredentials{}, fmt.Errorf("marshaling caCert: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return CACredentials{
|
return CACredentials{
|
||||||
Public: CAPublicCredentials{
|
Public: CAPublicCredentials{
|
||||||
CertPEM: string(certPEM),
|
Cert: caCert,
|
||||||
SigningKey: signingPubKey,
|
SigningKey: signingPubKey,
|
||||||
},
|
},
|
||||||
SigningPrivateKey: signingPrivKey,
|
SigningPrivateKey: signingPrivKey,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPFromHostCertPEM is a convenience function for parsing the IP of a host out
|
|
||||||
// of its nebula cert.
|
|
||||||
func IPFromHostCertPEM(hostCertPEM string) (net.IP, error) {
|
|
||||||
|
|
||||||
hostCert, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(hostCertPEM))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unmarshaling host certificate as PEM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ips := hostCert.Details.Ips
|
|
||||||
if len(ips) == 0 {
|
|
||||||
return nil, fmt.Errorf("malformed nebula host cert: no IPs")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips[0].IP, nil
|
|
||||||
}
|
|
||||||
|
78
go/nebula/signing_key.go
Normal file
78
go/nebula/signing_key.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package nebula
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
sigPrivKeyPrefix = []byte("s0")
|
||||||
|
sigPubKeyPrefix = []byte("S0")
|
||||||
|
)
|
||||||
|
|
||||||
|
// SigningPrivateKey wraps an ed25519.PrivateKey to provide convenient text
|
||||||
|
// (un)marshaling methods.
|
||||||
|
type SigningPrivateKey ed25519.PrivateKey
|
||||||
|
|
||||||
|
// MarshalText implements the encoding.TextMarshaler interface.
|
||||||
|
func (k SigningPrivateKey) MarshalText() ([]byte, error) {
|
||||||
|
return encodeWithPrefix(sigPrivKeyPrefix, k), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
|
func (k *SigningPrivateKey) UnmarshalText(b []byte) error {
|
||||||
|
b, err := decodeWithPrefix(sigPrivKeyPrefix, b)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshaling: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*k = SigningPrivateKey(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k SigningPrivateKey) String() string {
|
||||||
|
b, err := k.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigningPublicKey wraps an ed25519.PublicKey to provide convenient text
|
||||||
|
// (un)marshaling methods.
|
||||||
|
type SigningPublicKey ed25519.PublicKey
|
||||||
|
|
||||||
|
// MarshalText implements the encoding.TextMarshaler interface.
|
||||||
|
func (pk SigningPublicKey) MarshalText() ([]byte, error) {
|
||||||
|
return encodeWithPrefix(sigPubKeyPrefix, pk), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
|
func (pk *SigningPublicKey) UnmarshalText(b []byte) error {
|
||||||
|
b, err := decodeWithPrefix(sigPubKeyPrefix, b)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshaling: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*pk = SigningPublicKey(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pk SigningPublicKey) String() string {
|
||||||
|
b, err := pk.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSigningPair generates and returns a new key pair which can be used
|
||||||
|
// for signing arbitrary blobs of bytes.
|
||||||
|
func GenerateSigningPair() (SigningPublicKey, SigningPrivateKey) {
|
||||||
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("generating ed25519 key: %w", err))
|
||||||
|
}
|
||||||
|
return SigningPublicKey(pub), SigningPrivateKey(priv)
|
||||||
|
}
|
25
go/nebula/util.go
Normal file
25
go/nebula/util.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package nebula
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jxskiss/base62"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encodeWithPrefix(prefix []byte, b []byte) []byte {
|
||||||
|
res := make([]byte, 0, len(prefix)+len(b)*4)
|
||||||
|
res = append(res, prefix...)
|
||||||
|
res = base62.EncodeToBuf(res, b)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeWithPrefix(prefix []byte, b []byte) ([]byte, error) {
|
||||||
|
if len(b) < len(prefix) {
|
||||||
|
return nil, errors.New("input is too short")
|
||||||
|
} else if !bytes.HasPrefix(b, prefix) {
|
||||||
|
return nil, fmt.Errorf("missing expected prefix %q", prefix)
|
||||||
|
}
|
||||||
|
return base62.Decode(b[len(prefix):])
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user