From c645a8c7675fa4bf162c247fe386d933c6f540ce Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sat, 15 Jun 2024 23:02:24 +0200 Subject: [PATCH] Refactor how signing/encryption keys are typed and (un)marshaled --- default.nix | 2 +- go/bootstrap/hosts.go | 7 +- go/cmd/entrypoint/admin.go | 14 +- go/cmd/entrypoint/nebula.go | 10 +- go/cmd/entrypoint/nebula_util.go | 25 +++- go/go.mod | 1 + go/go.sum | 2 + go/nebula/encrypting_key.go | 117 +++++++++++++++++ go/nebula/keys.go | 72 ----------- go/nebula/nebula.go | 120 +++++------------- go/nebula/signing_key.go | 78 ++++++++++++ .../{keys_test.go => signing_key_test.go} | 0 go/nebula/util.go | 25 ++++ 13 files changed, 298 insertions(+), 175 deletions(-) create mode 100644 go/nebula/encrypting_key.go delete mode 100644 go/nebula/keys.go create mode 100644 go/nebula/signing_key.go rename go/nebula/{keys_test.go => signing_key_test.go} (100%) create mode 100644 go/nebula/util.go diff --git a/default.nix b/default.nix index f752824..c595ae9 100644 --- a/default.nix +++ b/default.nix @@ -69,7 +69,7 @@ in rec { ''; }; - vendorHash = "sha256-P1TXG0fG8/6n37LmM5ApYctqoZzJFlvFAO2Zl85SVvk="; + vendorHash = "sha256-33gwBj+6x9I/yz0Qf4G8YXRgC/HfwHCedqzrCE4FHHk="; subPackages = [ "./cmd/entrypoint" diff --git a/go/bootstrap/hosts.go b/go/bootstrap/hosts.go index 862216a..b03a61d 100644 --- a/go/bootstrap/hosts.go +++ b/go/bootstrap/hosts.go @@ -78,10 +78,5 @@ type Host struct { // This assumes that the Host and its data has already been verified against the // CA signing key. func (h Host) IP() net.IP { - ip, err := nebula.IPFromHostCertPEM(h.PublicCredentials.CertPEM) - if err != nil { - panic(fmt.Errorf("could not parse IP out of cert for host %q: %w", h.Name, err)) - } - - return ip + return h.PublicCredentials.Cert.Details.Ips[0].IP } diff --git a/go/cmd/entrypoint/admin.go b/go/cmd/entrypoint/admin.go index ffeb7e7..5577c36 100644 --- a/go/cmd/entrypoint/admin.go +++ b/go/cmd/entrypoint/admin.go @@ -381,13 +381,23 @@ var subCmdAdminCreateNebulaCert = subCmd{ return fmt.Errorf("reading public key from %q: %w", *pubKeyPath, err) } - nebulaHostCertPEM, err := nebula.NewHostCertPEM( - adm.Nebula.CACredentials, string(hostPubPEM), *hostName, ip, + var hostPub nebula.EncryptingPublicKey + 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 { 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 { return fmt.Errorf("writing to stdout: %w", err) } diff --git a/go/cmd/entrypoint/nebula.go b/go/cmd/entrypoint/nebula.go index 710495f..3b3094c 100644 --- a/go/cmd/entrypoint/nebula.go +++ b/go/cmd/entrypoint/nebula.go @@ -4,8 +4,6 @@ import ( "fmt" "isle/jsonutil" "os" - - "github.com/slackhq/nebula/cert" ) var subCmdNebulaShow = subCmd{ @@ -23,10 +21,10 @@ var subCmdNebulaShow = subCmd{ return fmt.Errorf("loading host bootstrap: %w", err) } - caPublicCreds := hostBootstrap.CAPublicCredentials - caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(caPublicCreds.CertPEM)) + caCert := hostBootstrap.CAPublicCredentials.Cert + caCertPEM, err := caCert.MarshalToPEM() 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 { @@ -48,7 +46,7 @@ var subCmdNebulaShow = subCmd{ SubnetCIDR string Lighthouses []outLighthouse }{ - CACert: caPublicCreds.CertPEM, + CACert: string(caCertPEM), SubnetCIDR: subnet.String(), } diff --git a/go/cmd/entrypoint/nebula_util.go b/go/cmd/entrypoint/nebula_util.go index 9128837..ffbcd1b 100644 --- a/go/cmd/entrypoint/nebula_util.go +++ b/go/cmd/entrypoint/nebula_util.go @@ -10,6 +10,7 @@ import ( "path/filepath" "code.betamike.com/micropelago/pmux/pmuxlib" + "github.com/slackhq/nebula/cert" ) // 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} } + 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{}{ "pki": map[string]string{ - "ca": hostBootstrap.CAPublicCredentials.CertPEM, - "cert": hostBootstrap.PublicCredentials.CertPEM, - "key": hostBootstrap.PrivateCredentials.PrivateKeyPEM, + "ca": string(caCertPEM), + "cert": string(hostCertPEM), + "key": string(hostKeyPEM), }, "static_host_map": staticHostMap, "punchy": map[string]bool{ diff --git a/go/go.mod b/go/go.mod index c64d10e..66af9b3 100644 --- a/go/go.mod +++ b/go/go.mod @@ -23,6 +23,7 @@ require ( github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/json-iterator/go v1.1.12 // 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/cpuid v1.3.1 // indirect github.com/minio/md5-simd v1.1.0 // indirect diff --git a/go/go.sum b/go/go.sum index 3d045de..e08bef0 100644 --- a/go/go.sum +++ b/go/go.sum @@ -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/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/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/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= diff --git a/go/nebula/encrypting_key.go b/go/nebula/encrypting_key.go new file mode 100644 index 0000000..0a72e5a --- /dev/null +++ b/go/nebula/encrypting_key.go @@ -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) +} diff --git a/go/nebula/keys.go b/go/nebula/keys.go deleted file mode 100644 index d64c0a1..0000000 --- a/go/nebula/keys.go +++ /dev/null @@ -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) -} diff --git a/go/nebula/nebula.go b/go/nebula/nebula.go index 1346780..78bc460 100644 --- a/go/nebula/nebula.go +++ b/go/nebula/nebula.go @@ -3,35 +3,32 @@ package nebula import ( - "crypto/rand" "fmt" - "io" "net" "time" "github.com/slackhq/nebula/cert" - "golang.org/x/crypto/curve25519" ) // HostPublicCredentials contains certificate and signing public keys which are // able to be broadcast publicly. type HostPublicCredentials struct { - CertPEM string + Cert cert.NebulaCertificate SigningKey SigningPublicKey } // HostPrivateCredentials contains the private key files which will // need to be present on a particular host. type HostPrivateCredentials struct { - PrivateKeyPEM string - SigningPrivateKey SigningPrivateKey + EncryptingPrivateKey EncryptingPrivateKey + SigningPrivateKey SigningPrivateKey } // CAPublicCredentials contains certificate and signing public keys which are // able to be broadcast publicly. The signing public key is the same one which // is embedded into the certificate. type CAPublicCredentials struct { - CertPEM string + Cert cert.NebulaCertificate SigningKey SigningPublicKey } @@ -42,33 +39,28 @@ type CACredentials struct { SigningPrivateKey SigningPrivateKey } -// NewHostCertPEM generates and signs a new host certificate containing the -// given public key. -func NewHostCertPEM( - caCreds CACredentials, hostPubPEM string, hostName string, ip net.IP, +// NewHostCert generates and signs a new host certificate containing the given +// public key. +func NewHostCert( + caCreds CACredentials, + hostPub EncryptingPublicKey, + hostName string, + ip net.IP, ) ( - string, error, + cert.NebulaCertificate, error, ) { - hostPub, _, err := cert.UnmarshalX25519PublicKey([]byte(hostPubPEM)) - 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) - } + caCert := caCreds.Public.Cert issuer, err := caCert.Sha256Sum() 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) subnet := caCert.Details.Subnets[0] 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{ @@ -80,26 +72,21 @@ func NewHostCertPEM( }}, NotBefore: time.Now(), NotAfter: expireAt, - PublicKey: hostPub, + PublicKey: hostPub.Bytes(), IsCA: false, Issuer: issuer, }, } - if err := hostCert.CheckRootConstrains(caCert); err != nil { - return "", fmt.Errorf("validating certificate constraints: %w", err) + if err := hostCert.CheckRootConstrains(&caCert); err != nil { + return cert.NebulaCertificate{}, fmt.Errorf("validating certificate constraints: %w", err) } 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() - if err != nil { - return "", fmt.Errorf("marshalling host.crt: %w", err) - } - - return string(hostCertPEM), nil + return hostCert, nil } // 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, ) { - // The logic here is largely based on - // https://github.com/slackhq/nebula/blob/v1.4.0/cmd/nebula-cert/sign.go + var ( + encPrivKey = NewEncryptingPrivateKey() + encPubKey = encPrivKey.PublicKey() - var hostPub, hostKey []byte - { - 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() + ) - signingPubKey, signingPrivKey := GenerateSigningPair() - - hostPubPEM := cert.MarshalX25519PublicKey(hostPub) - hostKeyPEM := cert.MarshalX25519PrivateKey(hostKey) - - hostCertPEM, err := NewHostCertPEM(caCreds, string(hostPubPEM), hostName, ip) + hostCert, err := NewHostCert(caCreds, encPubKey, hostName, ip) if err != nil { err = fmt.Errorf("creating host certificate: %w", err) return } pub = HostPublicCredentials{ - CertPEM: hostCertPEM, + Cert: hostCert, SigningKey: signingPubKey, } priv = HostPrivateCredentials{ - PrivateKeyPEM: string(hostKeyPEM), - SigningPrivateKey: signingPrivKey, + EncryptingPrivateKey: encPrivKey, + SigningPrivateKey: signingPrivKey, } return @@ -151,14 +126,11 @@ func NewHostCredentials( // NewCACredentials generates a CACredentials. The domain should be the network's root domain, // and is included in the signing certificate's Name field. func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) { - - // The logic here is largely based on - // https://github.com/slackhq/nebula/blob/v1.4.0/cmd/nebula-cert/ca.go - - signingPubKey, signingPrivKey := GenerateSigningPair() - - now := time.Now() - expireAt := now.Add(2 * 365 * 24 * time.Hour) + var ( + signingPubKey, signingPrivKey = GenerateSigningPair() + now = time.Now() + expireAt = now.Add(2 * 365 * 24 * time.Hour) + ) caCert := cert.NebulaCertificate{ Details: cert.NebulaCertificateDetails{ @@ -175,33 +147,11 @@ func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) { 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{ Public: CAPublicCredentials{ - CertPEM: string(certPEM), + Cert: caCert, SigningKey: signingPubKey, }, SigningPrivateKey: signingPrivKey, }, 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 -} diff --git a/go/nebula/signing_key.go b/go/nebula/signing_key.go new file mode 100644 index 0000000..b1eed8d --- /dev/null +++ b/go/nebula/signing_key.go @@ -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) +} diff --git a/go/nebula/keys_test.go b/go/nebula/signing_key_test.go similarity index 100% rename from go/nebula/keys_test.go rename to go/nebula/signing_key_test.go diff --git a/go/nebula/util.go b/go/nebula/util.go new file mode 100644 index 0000000..68ddda9 --- /dev/null +++ b/go/nebula/util.go @@ -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):]) +}