Refactor how host data is signed, now it's simpler and probably more secure
This commit is contained in:
parent
f13a08abfb
commit
2768be00d8
@ -168,6 +168,7 @@ in rec {
|
||||
export PATH=${pkgs.lib.makeBinPath [
|
||||
appImage
|
||||
pkgs.busybox
|
||||
pkgs.yq-go
|
||||
pkgs.jq
|
||||
pkgs.dig
|
||||
]}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"isle/admin"
|
||||
"isle/garage"
|
||||
"isle/nebula"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@ -27,31 +28,81 @@ func AppDirPath(appDirPath string) string {
|
||||
return filepath.Join(appDirPath, "share/bootstrap.json")
|
||||
}
|
||||
|
||||
// Garage contains parameters needed to connect to and use the garage cluster.
|
||||
type Garage struct {
|
||||
// TODO RPCSecret and GlobalBucketS3APICredentials are duplicated here and
|
||||
// in AdminCreationParams, might as well just use them from there
|
||||
RPCSecret string
|
||||
AdminToken string
|
||||
GlobalBucketS3APICredentials garage.S3APICredentials
|
||||
}
|
||||
|
||||
// Bootstrap is used for accessing all information contained within a
|
||||
// bootstrap.json file.
|
||||
type Bootstrap struct {
|
||||
AdminCreationParams admin.CreationParams
|
||||
CAPublicCredentials nebula.CAPublicCredentials
|
||||
Garage Garage
|
||||
|
||||
Hosts map[string]Host
|
||||
HostName string
|
||||
PrivateCredentials nebula.HostPrivateCredentials
|
||||
HostAssigned `json:"-"`
|
||||
SignedHostAssigned nebula.Signed[HostAssigned] // signed by CA
|
||||
|
||||
Nebula struct {
|
||||
CAPublicCredentials nebula.CAPublicCredentials
|
||||
HostCredentials nebula.HostCredentials
|
||||
SignedPublicCredentials string
|
||||
Hosts map[string]Host
|
||||
}
|
||||
|
||||
// New initializes and returns a new Bootstrap file for a new host. This
|
||||
// function assigns Hosts an empty map.
|
||||
func New(
|
||||
caCreds nebula.CACredentials,
|
||||
adminCreationParams admin.CreationParams,
|
||||
garage Garage,
|
||||
name string,
|
||||
ip net.IP,
|
||||
) (
|
||||
Bootstrap, error,
|
||||
) {
|
||||
hostPubCreds, hostPrivCreds, err := nebula.NewHostCredentials(
|
||||
caCreds, name, ip,
|
||||
)
|
||||
if err != nil {
|
||||
return Bootstrap{}, fmt.Errorf("generating host credentials: %w", err)
|
||||
}
|
||||
|
||||
Garage struct {
|
||||
RPCSecret string
|
||||
AdminToken string
|
||||
GlobalBucketS3APICredentials garage.S3APICredentials
|
||||
assigned := HostAssigned{
|
||||
Name: name,
|
||||
PublicCredentials: hostPubCreds,
|
||||
}
|
||||
|
||||
signedAssigned, err := nebula.Sign(assigned, caCreds.SigningPrivateKey)
|
||||
if err != nil {
|
||||
return Bootstrap{}, fmt.Errorf("signing assigned fields: %w", err)
|
||||
}
|
||||
|
||||
return Bootstrap{
|
||||
AdminCreationParams: adminCreationParams,
|
||||
CAPublicCredentials: caCreds.Public,
|
||||
Garage: garage,
|
||||
PrivateCredentials: hostPrivCreds,
|
||||
HostAssigned: assigned,
|
||||
SignedHostAssigned: signedAssigned,
|
||||
Hosts: map[string]Host{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FromReader reads a bootstrap file from the given io.Reader.
|
||||
func FromReader(r io.Reader) (Bootstrap, error) {
|
||||
var b Bootstrap
|
||||
|
||||
err := json.NewDecoder(r).Decode(&b)
|
||||
if err != nil {
|
||||
return Bootstrap{}, fmt.Errorf("decoding json: %w", err)
|
||||
}
|
||||
|
||||
if b.HostAssigned, err = b.SignedHostAssigned.UnwrapUnsafe(); err != nil {
|
||||
return Bootstrap{}, fmt.Errorf("unwrapping host assigned: %w", err)
|
||||
}
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
@ -76,9 +127,9 @@ func (b Bootstrap) WriteTo(into io.Writer) error {
|
||||
// HostName isn't found in the Hosts map.
|
||||
func (b Bootstrap) ThisHost() Host {
|
||||
|
||||
host, ok := b.Hosts[b.HostName]
|
||||
host, ok := b.Hosts[b.Name]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("hostname %q not defined in bootstrap's hosts", b.HostName))
|
||||
panic(fmt.Sprintf("hostname %q not defined in bootstrap's hosts", b.Name))
|
||||
}
|
||||
|
||||
return host
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"isle/garage"
|
||||
"isle/nebula"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
||||
@ -24,26 +23,24 @@ const (
|
||||
// into garage so that other hosts are able to see relevant configuration for
|
||||
// it.
|
||||
func (b Bootstrap) PutGarageBoostrapHost(ctx context.Context) error {
|
||||
var (
|
||||
host = b.ThisHost()
|
||||
client = b.GlobalBucketS3APIClient()
|
||||
)
|
||||
|
||||
host := b.ThisHost()
|
||||
client := b.GlobalBucketS3APIClient()
|
||||
|
||||
// the base Bootstrap has the public credentials signed by the CA, but we
|
||||
// need this to be presented in the data stored into garage, so other hosts
|
||||
// can verify that the stored host object is signed by the host public key,
|
||||
// and that the host public key is signed by the CA.
|
||||
host.Nebula.SignedPublicCredentials = b.Nebula.SignedPublicCredentials
|
||||
|
||||
hostB, err := json.Marshal(host)
|
||||
configured, err := nebula.Sign(
|
||||
host.HostConfigured, b.PrivateCredentials.SigningPrivateKey,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding host data: %w", err)
|
||||
return fmt.Errorf("signing host configured data: %w", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
err = nebula.SignAndWrap(buf, b.Nebula.HostCredentials.SigningPrivateKeyPEM, hostB)
|
||||
hostB, err := json.Marshal(AuthenticatedHost{
|
||||
Assigned: b.SignedHostAssigned,
|
||||
Configured: configured,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("signing encoded host data: %w", err)
|
||||
return fmt.Errorf("encoding host data: %w", err)
|
||||
}
|
||||
|
||||
filePath := filepath.Join(
|
||||
@ -52,7 +49,11 @@ func (b Bootstrap) PutGarageBoostrapHost(ctx context.Context) error {
|
||||
)
|
||||
|
||||
_, err = client.PutObject(
|
||||
ctx, garage.GlobalBucket, filePath, buf, int64(buf.Len()),
|
||||
ctx,
|
||||
garage.GlobalBucket,
|
||||
filePath,
|
||||
bytes.NewReader(hostB),
|
||||
int64(len(hostB)),
|
||||
minio.PutObjectOptions{},
|
||||
)
|
||||
|
||||
@ -119,49 +120,19 @@ func (b Bootstrap) GetGarageBootstrapHosts(
|
||||
return nil, fmt.Errorf("retrieving object %q: %w", objInfo.Key, err)
|
||||
}
|
||||
|
||||
hostB, hostSig, err := nebula.Unwrap(obj)
|
||||
var authedHost AuthenticatedHost
|
||||
|
||||
err = json.NewDecoder(obj).Decode(&authedHost)
|
||||
obj.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unwrapping signature from %q: %w", objInfo.Key, err)
|
||||
logger.Warn(ctx, "object contains invalid json", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var host Host
|
||||
if err = json.Unmarshal(hostB, &host); err != nil {
|
||||
return nil, fmt.Errorf("decoding object %q: %w", objInfo.Key, err)
|
||||
}
|
||||
|
||||
hostPublicCredsB, hostPublicCredsSig, err := nebula.Unwrap(
|
||||
strings.NewReader(host.Nebula.SignedPublicCredentials),
|
||||
)
|
||||
|
||||
host, err := authedHost.Unwrap(b.CAPublicCredentials)
|
||||
if err != nil {
|
||||
logger.Warn(ctx, "unwrapping signed public creds", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = nebula.ValidateSignature(
|
||||
b.Nebula.CAPublicCredentials.SigningKeyPEM,
|
||||
hostPublicCredsB,
|
||||
hostPublicCredsSig,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Warn(ctx, "invalid signed public creds", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var hostPublicCreds nebula.HostPublicCredentials
|
||||
if err := json.Unmarshal(hostPublicCredsB, &hostPublicCreds); err != nil {
|
||||
logger.Warn(ctx, "unmarshaling signed public creds", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = nebula.ValidateSignature(hostPublicCreds.SigningKeyPEM, hostB, hostSig)
|
||||
|
||||
if err != nil {
|
||||
logger.Warn(ctx, "invalid host data", err)
|
||||
continue
|
||||
logger.Warn(ctx, "host could not be authenticated", err)
|
||||
}
|
||||
|
||||
hosts[host.Name] = host
|
||||
|
@ -1,44 +1,15 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"isle/nebula"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NebulaHost describes the nebula configuration of a Host which is relevant for
|
||||
// other hosts to know.
|
||||
type NebulaHost struct {
|
||||
SignedPublicCredentials string
|
||||
PublicAddr string
|
||||
}
|
||||
|
||||
// NewNebulaHostSignedPublicCredentials constructs the SignedPublicCredentials
|
||||
// field of the NebulaHost struct, using the CACredentials to sign the
|
||||
// HostPublicCredentials.
|
||||
func NewNebulaHostSignedPublicCredentials(
|
||||
caCreds nebula.CACredentials,
|
||||
hostPublicCreds nebula.HostPublicCredentials,
|
||||
) (
|
||||
string, error,
|
||||
) {
|
||||
|
||||
hostPublicCredsB, err := json.Marshal(hostPublicCreds)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshaling host's public credentials: %w", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
err = nebula.SignAndWrap(buf, caCreds.SigningPrivateKeyPEM, hostPublicCredsB)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("signing host's public credentials: %w", err)
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
PublicAddr string
|
||||
}
|
||||
|
||||
// GarageHost describes a single garage instance in the GarageHost.
|
||||
@ -54,12 +25,51 @@ type GarageHost struct {
|
||||
Instances []GarageHostInstance
|
||||
}
|
||||
|
||||
// Host consolidates all information about a single host from the bootstrap
|
||||
// file.
|
||||
// HostAssigned are all fields related to a host which were assigned to it by an
|
||||
// admin.
|
||||
type HostAssigned struct {
|
||||
Name string
|
||||
PublicCredentials nebula.HostPublicCredentials
|
||||
}
|
||||
|
||||
// HostConfigured are all the fields a host can configure for itself.
|
||||
type HostConfigured struct {
|
||||
Nebula NebulaHost `json:",omitempty"`
|
||||
Garage GarageHost `json:",omitempty"`
|
||||
}
|
||||
|
||||
// AuthenticatedHost wraps all the data about a host which other hosts may know
|
||||
// about it, such that those hosts can authenticate that the data is valid and
|
||||
// approved by an admin.
|
||||
type AuthenticatedHost struct {
|
||||
Assigned nebula.Signed[HostAssigned] // signed by CA
|
||||
Configured nebula.Signed[HostConfigured] // signed by host
|
||||
}
|
||||
|
||||
// Unwrap attempts to authenticate and unwrap the Host embedded in this
|
||||
// instance. nebula.ErrInvalidSignature is returned if any signatures are
|
||||
// invalid.
|
||||
func (ah AuthenticatedHost) Unwrap(caCreds nebula.CAPublicCredentials) (Host, error) {
|
||||
assigned, err := ah.Assigned.Unwrap(caCreds.SigningKey)
|
||||
if err != nil {
|
||||
return Host{}, fmt.Errorf("unwrapping assigned fields using CA public key: %w", err)
|
||||
}
|
||||
|
||||
configured, err := ah.Configured.Unwrap(assigned.PublicCredentials.SigningKey)
|
||||
if err != nil {
|
||||
return Host{}, fmt.Errorf("unwrapping configured fields using host public key: %w", err)
|
||||
}
|
||||
|
||||
return Host{assigned, configured}, nil
|
||||
}
|
||||
|
||||
// Host contains all data bout a host which other hosts may know about it.
|
||||
//
|
||||
// A Host should only be obtained over the network as an AuthenticatedHost, and
|
||||
// subsequently Unwrapped.
|
||||
type Host struct {
|
||||
Name string
|
||||
Nebula NebulaHost
|
||||
Garage GarageHost
|
||||
HostAssigned
|
||||
HostConfigured
|
||||
}
|
||||
|
||||
// IP returns the IP address encoded in the Host's nebula certificate, or panics
|
||||
@ -68,21 +78,7 @@ 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 {
|
||||
|
||||
hostPublicCredsB, _, err := nebula.Unwrap(
|
||||
strings.NewReader(h.Nebula.SignedPublicCredentials),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unwrapping host's signed public credentials: %w", err))
|
||||
}
|
||||
|
||||
var hostPublicCreds nebula.HostPublicCredentials
|
||||
if err := json.Unmarshal(hostPublicCredsB, &hostPublicCreds); err != nil {
|
||||
panic(fmt.Errorf("unmarshaling host's public credentials: %w", err))
|
||||
}
|
||||
|
||||
ip, err := nebula.IPFromHostCertPEM(hostPublicCreds.CertPEM)
|
||||
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))
|
||||
}
|
||||
|
@ -142,46 +142,28 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
return fmt.Errorf("creating nebula CA cert: %w", err)
|
||||
}
|
||||
|
||||
nebulaHostCreds, err := nebula.NewHostCredentials(nebulaCACreds, *hostName, ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating nebula cert for host: %w", err)
|
||||
}
|
||||
|
||||
nebulaHostSignedPublicCreds, err := bootstrap.NewNebulaHostSignedPublicCredentials(
|
||||
nebulaCACreds,
|
||||
nebulaHostCreds.Public,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating signed public credentials for host: %w", err)
|
||||
}
|
||||
|
||||
adminCreationParams := admin.CreationParams{
|
||||
ID: randStr(32),
|
||||
Name: *name,
|
||||
Domain: *domain,
|
||||
}
|
||||
|
||||
hostBootstrap := bootstrap.Bootstrap{
|
||||
AdminCreationParams: adminCreationParams,
|
||||
Hosts: map[string]bootstrap.Host{
|
||||
*hostName: bootstrap.Host{
|
||||
Name: *hostName,
|
||||
Nebula: bootstrap.NebulaHost{
|
||||
SignedPublicCredentials: nebulaHostSignedPublicCreds,
|
||||
},
|
||||
},
|
||||
},
|
||||
HostName: *hostName,
|
||||
garageBootstrap := bootstrap.Garage{
|
||||
RPCSecret: randStr(32),
|
||||
AdminToken: randStr(32),
|
||||
GlobalBucketS3APICredentials: garage.NewS3APICredentials(),
|
||||
}
|
||||
|
||||
hostBootstrap.Nebula.CAPublicCredentials = nebulaCACreds.Public
|
||||
hostBootstrap.Nebula.HostCredentials = nebulaHostCreds
|
||||
hostBootstrap.Nebula.SignedPublicCredentials = nebulaHostSignedPublicCreds
|
||||
|
||||
hostBootstrap.Garage.RPCSecret = randStr(32)
|
||||
hostBootstrap.Garage.AdminToken = randStr(32)
|
||||
hostBootstrap.Garage.GlobalBucketS3APICredentials = garage.NewS3APICredentials()
|
||||
hostBootstrap, err := bootstrap.New(
|
||||
nebulaCACreds,
|
||||
adminCreationParams,
|
||||
garageBootstrap,
|
||||
*hostName,
|
||||
ip,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing bootstrap data: %w", err)
|
||||
}
|
||||
|
||||
if hostBootstrap, daemonConfig, err = coalesceDaemonConfigAndBootstrap(hostBootstrap, daemonConfig); err != nil {
|
||||
return fmt.Errorf("merging daemon config into bootstrap data: %w", err)
|
||||
@ -263,7 +245,7 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
var subCmdAdminCreateBootstrap = subCmd{
|
||||
name: "create-bootstrap",
|
||||
descr: "Creates a new bootstrap.json file for a particular host and writes it to stdout",
|
||||
checkLock: true,
|
||||
checkLock: false,
|
||||
do: func(subCmdCtx subCmdCtx) error {
|
||||
|
||||
flags := subCmdCtx.flagSet(false)
|
||||
@ -306,39 +288,29 @@ var subCmdAdminCreateBootstrap = subCmd{
|
||||
return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err)
|
||||
}
|
||||
|
||||
garageBootstrap := bootstrap.Garage{
|
||||
RPCSecret: adm.Garage.RPCSecret,
|
||||
AdminToken: randStr(32),
|
||||
GlobalBucketS3APICredentials: adm.Garage.GlobalBucketS3APICredentials,
|
||||
}
|
||||
|
||||
newHostBootstrap, err := bootstrap.New(
|
||||
adm.Nebula.CACredentials,
|
||||
adm.CreationParams,
|
||||
garageBootstrap,
|
||||
*hostName,
|
||||
ip,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing bootstrap data: %w", err)
|
||||
}
|
||||
|
||||
hostBootstrap, err := loadHostBootstrap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||
}
|
||||
|
||||
nebulaHostCreds, err := nebula.NewHostCredentials(adm.Nebula.CACredentials, *hostName, ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating new nebula host key/cert: %w", err)
|
||||
}
|
||||
|
||||
nebulaHostSignedPublicCreds, err := bootstrap.NewNebulaHostSignedPublicCredentials(
|
||||
adm.Nebula.CACredentials,
|
||||
nebulaHostCreds.Public,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating signed public credentials for host: %w", err)
|
||||
}
|
||||
|
||||
newHostBootstrap := bootstrap.Bootstrap{
|
||||
AdminCreationParams: adm.CreationParams,
|
||||
|
||||
Hosts: hostBootstrap.Hosts,
|
||||
HostName: *hostName,
|
||||
}
|
||||
|
||||
newHostBootstrap.Nebula.CAPublicCredentials = adm.Nebula.CACredentials.Public
|
||||
newHostBootstrap.Nebula.HostCredentials = nebulaHostCreds
|
||||
newHostBootstrap.Nebula.SignedPublicCredentials = nebulaHostSignedPublicCreds
|
||||
|
||||
newHostBootstrap.Garage.RPCSecret = adm.Garage.RPCSecret
|
||||
newHostBootstrap.Garage.AdminToken = randStr(32)
|
||||
newHostBootstrap.Garage.GlobalBucketS3APICredentials = adm.Garage.GlobalBucketS3APICredentials
|
||||
newHostBootstrap.Hosts = hostBootstrap.Hosts
|
||||
|
||||
return newHostBootstrap.WriteTo(os.Stdout)
|
||||
},
|
||||
|
@ -2,10 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"isle/bootstrap"
|
||||
"isle/daemon"
|
||||
"isle/garage"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -17,10 +17,11 @@ func coalesceDaemonConfigAndBootstrap(
|
||||
) {
|
||||
|
||||
host := bootstrap.Host{
|
||||
Name: hostBootstrap.HostName,
|
||||
Nebula: bootstrap.NebulaHost{
|
||||
SignedPublicCredentials: hostBootstrap.Nebula.SignedPublicCredentials,
|
||||
PublicAddr: daemonConfig.VPN.PublicAddr,
|
||||
HostAssigned: hostBootstrap.HostAssigned,
|
||||
HostConfigured: bootstrap.HostConfigured{
|
||||
Nebula: bootstrap.NebulaHost{
|
||||
PublicAddr: daemonConfig.VPN.PublicAddr,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ var subCmdNebulaShow = subCmd{
|
||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||
}
|
||||
|
||||
caPublicCreds := hostBootstrap.Nebula.CAPublicCredentials
|
||||
caPublicCreds := hostBootstrap.CAPublicCredentials
|
||||
caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(caPublicCreds.CertPEM))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling ca.crt: %w", err)
|
||||
|
@ -58,9 +58,9 @@ func nebulaPmuxProcConfig(
|
||||
|
||||
config := map[string]interface{}{
|
||||
"pki": map[string]string{
|
||||
"ca": hostBootstrap.Nebula.CAPublicCredentials.CertPEM,
|
||||
"cert": hostBootstrap.Nebula.HostCredentials.Public.CertPEM,
|
||||
"key": hostBootstrap.Nebula.HostCredentials.PrivateKeyPEM,
|
||||
"ca": hostBootstrap.CAPublicCredentials.CertPEM,
|
||||
"cert": hostBootstrap.PublicCredentials.CertPEM,
|
||||
"key": hostBootstrap.PrivateCredentials.PrivateKeyPEM,
|
||||
},
|
||||
"static_host_map": staticHostMap,
|
||||
"punchy": map[string]bool{
|
||||
|
72
go/nebula/keys.go
Normal file
72
go/nebula/keys.go
Normal file
@ -0,0 +1,72 @@
|
||||
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)
|
||||
}
|
52
go/nebula/keys_test.go
Normal file
52
go/nebula/keys_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSigningKeysJSON(t *testing.T) {
|
||||
pub, priv := GenerateSigningPair()
|
||||
|
||||
t.Run("SigningPublicKey", func(t *testing.T) {
|
||||
pubJSON, err := json.Marshal(pub)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(string(pubJSON), `"-----BEGIN `) {
|
||||
t.Fatalf("pub key didn't marshal to PEM: %q", pubJSON)
|
||||
}
|
||||
|
||||
var pub2 SigningPublicKey
|
||||
if err := json.Unmarshal(pubJSON, &pub2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal([]byte(pub), []byte(pub2)) {
|
||||
t.Fatalf("json unmarshaling got different result: %q", pub2)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SigningPrivateKey", func(t *testing.T) {
|
||||
privJSON, err := json.Marshal(priv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(string(privJSON), `"-----BEGIN `) {
|
||||
t.Fatalf("priv key didn't marshal to PEM: %q", privJSON)
|
||||
}
|
||||
|
||||
var priv2 SigningPrivateKey
|
||||
if err := json.Unmarshal(privJSON, &priv2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal([]byte(priv), []byte(priv2)) {
|
||||
t.Fatalf("json unmarshaling got different result: %q", priv2)
|
||||
}
|
||||
})
|
||||
}
|
@ -3,11 +3,7 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@ -17,38 +13,33 @@ import (
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
// ErrInvalidSignature is returned from functions when a signature validation
|
||||
// fails.
|
||||
var ErrInvalidSignature = errors.New("invalid signature")
|
||||
|
||||
// HostPublicCredentials contains certificate and signing public keys which are
|
||||
// able to be broadcast publicly.
|
||||
type HostPublicCredentials struct {
|
||||
CertPEM string
|
||||
SigningKeyPEM string
|
||||
CertPEM string
|
||||
SigningKey SigningPublicKey
|
||||
}
|
||||
|
||||
// HostCredentials contains the certificate and private key files which will
|
||||
// need to be present on a particular host. Each file is PEM encoded.
|
||||
type HostCredentials struct {
|
||||
Public HostPublicCredentials
|
||||
PrivateKeyPEM string
|
||||
SigningPrivateKeyPEM string
|
||||
// HostPrivateCredentials contains the private key files which will
|
||||
// need to be present on a particular host.
|
||||
type HostPrivateCredentials struct {
|
||||
PrivateKeyPEM string
|
||||
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
|
||||
SigningKeyPEM string // TODO remove redundant field
|
||||
CertPEM string
|
||||
SigningKey SigningPublicKey
|
||||
}
|
||||
|
||||
// CACredentials contains the certificate and private files which can be used to
|
||||
// create and validate HostCredentials. Each file is PEM encoded.
|
||||
type CACredentials struct {
|
||||
Public CAPublicCredentials
|
||||
SigningPrivateKeyPEM string
|
||||
Public CAPublicCredentials
|
||||
SigningPrivateKey SigningPrivateKey
|
||||
}
|
||||
|
||||
// NewHostCertPEM generates and signs a new host certificate containing the
|
||||
@ -63,11 +54,6 @@ func NewHostCertPEM(
|
||||
return "", fmt.Errorf("unmarshaling public key PEM: %w", err)
|
||||
}
|
||||
|
||||
caSigningKey, _, err := cert.UnmarshalEd25519PrivateKey([]byte(caCreds.SigningPrivateKeyPEM))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshaling ca.key: %w", err)
|
||||
}
|
||||
|
||||
caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(caCreds.Public.CertPEM))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshaling ca.crt: %w", err)
|
||||
@ -104,7 +90,7 @@ func NewHostCertPEM(
|
||||
return "", fmt.Errorf("validating certificate constraints: %w", err)
|
||||
}
|
||||
|
||||
if err := hostCert.Sign(caSigningKey); err != nil {
|
||||
if err := signCert(&hostCert, caCreds.SigningPrivateKey); err != nil {
|
||||
return "", fmt.Errorf("signing host cert with ca.key: %w", err)
|
||||
}
|
||||
|
||||
@ -121,46 +107,45 @@ func NewHostCertPEM(
|
||||
func NewHostCredentials(
|
||||
caCreds CACredentials, hostName string, ip net.IP,
|
||||
) (
|
||||
HostCredentials, error,
|
||||
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
|
||||
|
||||
signingPubKey, signingPrivKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("generating ed25519 key: %w", err))
|
||||
}
|
||||
|
||||
signingPrivKeyPEM := cert.MarshalEd25519PrivateKey(signingPrivKey)
|
||||
signingPubKeyPEM := cert.MarshalEd25519PublicKey(signingPubKey)
|
||||
|
||||
var hostPub, hostKey []byte
|
||||
{
|
||||
var pubkey, privkey [32]byte
|
||||
if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil {
|
||||
return HostCredentials{}, fmt.Errorf("reading random bytes to form private key: %w", err)
|
||||
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()
|
||||
|
||||
hostPubPEM := cert.MarshalX25519PublicKey(hostPub)
|
||||
hostKeyPEM := cert.MarshalX25519PrivateKey(hostKey)
|
||||
|
||||
hostCertPEM, err := NewHostCertPEM(caCreds, string(hostPubPEM), hostName, ip)
|
||||
if err != nil {
|
||||
return HostCredentials{}, fmt.Errorf("creating host certificate: %w", err)
|
||||
err = fmt.Errorf("creating host certificate: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
return HostCredentials{
|
||||
Public: HostPublicCredentials{
|
||||
CertPEM: hostCertPEM,
|
||||
SigningKeyPEM: string(signingPubKeyPEM),
|
||||
},
|
||||
PrivateKeyPEM: string(hostKeyPEM),
|
||||
SigningPrivateKeyPEM: string(signingPrivKeyPEM),
|
||||
}, nil
|
||||
pub = HostPublicCredentials{
|
||||
CertPEM: hostCertPEM,
|
||||
SigningKey: signingPubKey,
|
||||
}
|
||||
|
||||
priv = HostPrivateCredentials{
|
||||
PrivateKeyPEM: string(hostKeyPEM),
|
||||
SigningPrivateKey: signingPrivKey,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewCACredentials generates a CACredentials. The domain should be the network's root domain,
|
||||
@ -170,10 +155,7 @@ 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, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("generating ed25519 key: %w", err))
|
||||
}
|
||||
signingPubKey, signingPrivKey := GenerateSigningPair()
|
||||
|
||||
now := time.Now()
|
||||
expireAt := now.Add(2 * 365 * 24 * time.Hour)
|
||||
@ -189,13 +171,10 @@ func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
|
||||
},
|
||||
}
|
||||
|
||||
if err := caCert.Sign(signingPrivKey); err != nil {
|
||||
if err := signCert(&caCert, signingPrivKey); err != nil {
|
||||
return CACredentials{}, fmt.Errorf("signing caCert: %w", err)
|
||||
}
|
||||
|
||||
signingPrivKeyPEM := cert.MarshalEd25519PrivateKey(signingPrivKey)
|
||||
signingPubKeyPEM := cert.MarshalEd25519PublicKey(signingPubKey)
|
||||
|
||||
certPEM, err := caCert.MarshalToPEM()
|
||||
if err != nil {
|
||||
return CACredentials{}, fmt.Errorf("marshaling caCert: %w", err)
|
||||
@ -203,10 +182,10 @@ func NewCACredentials(domain string, subnet *net.IPNet) (CACredentials, error) {
|
||||
|
||||
return CACredentials{
|
||||
Public: CAPublicCredentials{
|
||||
CertPEM: string(certPEM),
|
||||
SigningKeyPEM: string(signingPubKeyPEM),
|
||||
CertPEM: string(certPEM),
|
||||
SigningKey: signingPubKey,
|
||||
},
|
||||
SigningPrivateKeyPEM: string(signingPrivKeyPEM),
|
||||
SigningPrivateKey: signingPrivKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -226,76 +205,3 @@ func IPFromHostCertPEM(hostCertPEM string) (net.IP, error) {
|
||||
|
||||
return ips[0].IP, nil
|
||||
}
|
||||
|
||||
// SignAndWrap signs the given bytes using the host key, and writes an
|
||||
// encoded, versioned structure containing the signature and the given bytes.
|
||||
func SignAndWrap(into io.Writer, signingKeyPEM string, b []byte) error {
|
||||
|
||||
key, _, err := cert.UnmarshalEd25519PrivateKey([]byte(signingKeyPEM))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling private key: %w", err)
|
||||
}
|
||||
|
||||
sig, err := key.Sign(rand.Reader, b, crypto.Hash(0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating signature: %w", err)
|
||||
}
|
||||
|
||||
if _, err := into.Write([]byte("0")); err != nil {
|
||||
return fmt.Errorf("writing version byte: %w", err)
|
||||
}
|
||||
|
||||
err = pem.Encode(into, &pem.Block{
|
||||
Type: "NEBULA ED25519 SIGNATURE",
|
||||
Bytes: sig,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing PEM encoding of signature: %w", err)
|
||||
}
|
||||
|
||||
if _, err := into.Write(b); err != nil {
|
||||
return fmt.Errorf("writing input bytes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unwrap reads a stream of bytes which was produced by SignAndWrap, and returns
|
||||
// the original input to SignAndWrap as well as the signature which was
|
||||
// created. ValidateSignature can be used to validate the signature.
|
||||
func Unwrap(from io.Reader) (b, sig []byte, err error) {
|
||||
|
||||
full, err := io.ReadAll(from)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("reading full input: %w", err)
|
||||
} else if len(full) < 3 {
|
||||
return nil, nil, fmt.Errorf("input too small")
|
||||
} else if full[0] != '0' {
|
||||
return nil, nil, fmt.Errorf("unexpected version byte: %d", full[0])
|
||||
}
|
||||
|
||||
full = full[1:]
|
||||
|
||||
pemBlock, rest := pem.Decode(full)
|
||||
if pemBlock == nil {
|
||||
return nil, nil, fmt.Errorf("PEM-encoded signature could not be decoded")
|
||||
}
|
||||
|
||||
return rest, pemBlock.Bytes, nil
|
||||
}
|
||||
|
||||
// ValidateSignature can be used to validate a signature produced by Unwrap.
|
||||
func ValidateSignature(signingPubKeyPEM string, b, sig []byte) error {
|
||||
|
||||
pubKey, _, err := cert.UnmarshalEd25519PublicKey([]byte(signingPubKeyPEM))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling certificate as PEM: %w", err)
|
||||
}
|
||||
|
||||
if !ed25519.Verify(pubKey, b, sig) {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
ip net.IP
|
||||
ipNet *net.IPNet
|
||||
caCredsA, caCredsB CACredentials
|
||||
hostCredsA, hostCredsB HostCredentials
|
||||
ip net.IP
|
||||
ipNet *net.IPNet
|
||||
caCredsA, caCredsB CACredentials
|
||||
hostPubCredsA, hostPubCredsB HostPublicCredentials
|
||||
hostPrivCredsA, hostPrivCredsB HostPrivateCredentials
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -32,40 +30,14 @@ func init() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hostCredsA, err = NewHostCredentials(caCredsA, "foo", ip)
|
||||
hostPubCredsA, hostPrivCredsA, err = NewHostCredentials(caCredsA, "foo", ip)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hostCredsB, err = NewHostCredentials(caCredsB, "bar", ip)
|
||||
hostPubCredsB, hostPrivCredsB, err = NewHostCredentials(caCredsB, "bar", ip)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSignAndWrap(t *testing.T) {
|
||||
|
||||
b := []byte("foo bar baz")
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if err := SignAndWrap(buf, hostCredsA.SigningPrivateKeyPEM, b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gotB, gotSig, err := Unwrap(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
} else if !bytes.Equal(b, gotB) {
|
||||
t.Fatalf("got %q but expected %q", gotB, b)
|
||||
}
|
||||
|
||||
if err := ValidateSignature(hostCredsA.Public.SigningKeyPEM, b, gotSig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ValidateSignature(hostCredsB.Public.SigningKeyPEM, b, gotSig); !errors.Is(err, ErrInvalidSignature) {
|
||||
t.Fatalf("expected ErrInvalidSignature but got %v", err)
|
||||
}
|
||||
}
|
||||
|
103
go/nebula/signed.go
Normal file
103
go/nebula/signed.go
Normal file
@ -0,0 +1,103 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/slackhq/nebula/cert"
|
||||
)
|
||||
|
||||
// ErrInvalidSignature is returned from functions when a signature validation
|
||||
// fails.
|
||||
var ErrInvalidSignature = errors.New("invalid signature")
|
||||
|
||||
func signCert(c *cert.NebulaCertificate, k SigningPrivateKey) error {
|
||||
return c.Sign(ed25519.PrivateKey(k))
|
||||
}
|
||||
|
||||
// Signed wraps an arbitrary value with a signature which was generated using a
|
||||
// SigningPrivateKey. It can be JSON (un)marshaled while preserving all of its
|
||||
// properties.
|
||||
type Signed[T any] json.RawMessage
|
||||
|
||||
type signed[T any] struct {
|
||||
Signature []byte
|
||||
Body json.RawMessage
|
||||
}
|
||||
|
||||
// Sign will generate a Signed of the given value by first JSON marshaling it,
|
||||
// and then signing the resulting bytes.
|
||||
func Sign[T any](v T, k SigningPrivateKey) (Signed[T], error) {
|
||||
var res Signed[T]
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("json marshaling: %w", err)
|
||||
}
|
||||
|
||||
sig, err := ed25519.PrivateKey(k).Sign(rand.Reader, b, crypto.Hash(0))
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("generating signature: %w", err)
|
||||
}
|
||||
|
||||
return json.Marshal(signed[T]{Signature: sig, Body: json.RawMessage(b)})
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (s Signed[T]) MarshalJSON() ([]byte, error) {
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (s *Signed[T]) UnmarshalJSON(b []byte) error {
|
||||
*s = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unwrap produces the original value which Sign was called on, or returns
|
||||
// ErrInvalidSignature if the signature is not valid for the value and public
|
||||
// key.
|
||||
func (s Signed[T]) Unwrap(pubK SigningPublicKey) (T, error) {
|
||||
var (
|
||||
res T
|
||||
into signed[T]
|
||||
)
|
||||
|
||||
if err := json.Unmarshal(s, &into); err != nil {
|
||||
return res, fmt.Errorf("json unmarshaling outer Signed: %w", err)
|
||||
}
|
||||
|
||||
if !ed25519.Verify(
|
||||
ed25519.PublicKey(pubK), []byte(into.Body), into.Signature,
|
||||
) {
|
||||
return res, ErrInvalidSignature
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(into.Body, &res); err != nil {
|
||||
return res, fmt.Errorf("json unmarshaling: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// UnwrapUnsafe is like Unwrap, but it will not check the signature.
|
||||
func (s Signed[T]) UnwrapUnsafe() (T, error) {
|
||||
var (
|
||||
res T
|
||||
into signed[T]
|
||||
)
|
||||
|
||||
if err := json.Unmarshal(s, &into); err != nil {
|
||||
return res, fmt.Errorf("json unmarshaling outer Signed: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(into.Body, &res); err != nil {
|
||||
return res, fmt.Errorf("json unmarshaling: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
48
go/nebula/signed_test.go
Normal file
48
go/nebula/signed_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
package nebula
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSigned(t *testing.T) {
|
||||
type msg struct {
|
||||
A int
|
||||
B string
|
||||
C bool
|
||||
}
|
||||
|
||||
a := msg{1, "FOO", true}
|
||||
|
||||
signedA, err := Sign(a, hostPrivCredsA.SigningPrivateKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
signedJSON, err := json.Marshal(signedA)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(string(signedJSON))
|
||||
|
||||
var signedB Signed[msg]
|
||||
if err := json.Unmarshal(signedJSON, &signedB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = signedB.Unwrap(hostPubCredsB.SigningKey)
|
||||
if !errors.Is(err, ErrInvalidSignature) {
|
||||
t.Fatalf("expected ErrInvalidSignature but got %v", err)
|
||||
}
|
||||
|
||||
b, err := signedB.Unwrap(hostPubCredsA.SigningKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if a != b {
|
||||
t.Fatalf("expected:%+v, got:%+v", a, b)
|
||||
}
|
||||
}
|
@ -12,5 +12,5 @@ source "$UTILS"/with-1-data-1-empty-node-cluster.sh
|
||||
bootstrap_file="$XDG_DATA_HOME/isle/bootstrap.json"
|
||||
|
||||
[ "$(jq -rc <"$bootstrap_file" '.AdminCreationParams')" = "$(jq -rc <admin.json '.CreationParams')" ]
|
||||
[ "$(jq -rc <"$bootstrap_file" '.Nebula.CAPublicCredentials')" = "$(jq -rc <admin.json '.Nebula.CACredentials.Public')" ]
|
||||
[ "$(jq -r <"$bootstrap_file" '.HostName')" = "primus" ]
|
||||
[ "$(jq -rc <"$bootstrap_file" '.CAPublicCredentials')" = "$(jq -rc <admin.json '.Nebula.CACredentials.Public')" ]
|
||||
[ "$(jq -r <"$bootstrap_file" '.SignedHostAssigned.Body.Name')" = "primus" ]
|
||||
|
@ -5,10 +5,10 @@ adminBS="$XDG_DATA_HOME"/isle/bootstrap.json
|
||||
bs="$secondus_bootstrap" # set in with-1-data-1-empty-node-cluster.sh
|
||||
|
||||
[ "$(jq -r <"$bs" '.AdminCreationParams')" = "$(jq -r <admin.json '.CreationParams')" ]
|
||||
[ "$(jq -r <"$bs" '.HostName')" = "secondus" ]
|
||||
[ "$(jq -r <"$bs" '.SignedHostAssigned.Body.Name')" = "secondus" ]
|
||||
|
||||
[ "$(jq -r <"$bs" '.Hosts.primus.Nebula.SignedPublicCredentials')" \
|
||||
= "$(jq -r <"$adminBS" '.Nebula.SignedPublicCredentials')" ]
|
||||
[ "$(jq -r <"$bs" '.Hosts.primus.PublicCredentials')" \
|
||||
= "$(jq -r <"$adminBS" '.SignedHostAssigned.Body.PublicCredentials')" ]
|
||||
|
||||
[ "$(jq <"$bs" '.Hosts.primus.Garage.Instances|length')" = "3" ]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user