Refactor how signing/encryption keys are typed and (un)marshaled

This commit is contained in:
Brian Picciano 2024-06-15 23:02:24 +02:00
parent 65fa208a34
commit c645a8c767
13 changed files with 298 additions and 175 deletions

View File

@ -69,7 +69,7 @@ in rec {
''; '';
}; };
vendorHash = "sha256-P1TXG0fG8/6n37LmM5ApYctqoZzJFlvFAO2Zl85SVvk="; vendorHash = "sha256-33gwBj+6x9I/yz0Qf4G8YXRgC/HfwHCedqzrCE4FHHk=";
subPackages = [ subPackages = [
"./cmd/entrypoint" "./cmd/entrypoint"

View File

@ -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
} }

View File

@ -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)
} }

View File

@ -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(),
} }

View File

@ -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{

View File

@ -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

View File

@ -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
View 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)
}

View File

@ -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)
}

View File

@ -3,27 +3,24 @@
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
} }
@ -31,7 +28,7 @@ type HostPrivateCredentials struct {
// 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,38 +97,26 @@ 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,
} }
@ -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
View 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
View 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):])
}