storagenode/contact: send noise key and settings as contact info
Change-Id: I1e7a83de36d5cf16eed8874091b15af1e0b73df7
This commit is contained in:
parent
b6bcb32ecf
commit
382af95499
@ -4,9 +4,16 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"github.com/flynn/noise"
|
||||
|
||||
"storj.io/common/identity"
|
||||
"storj.io/common/pb"
|
||||
"storj.io/common/signing"
|
||||
)
|
||||
|
||||
// NoiseHeader is the drpcmigrate.Header prefix for DRPC over Noise.
|
||||
@ -26,3 +33,99 @@ func defaultNoiseConfig() noise.Config {
|
||||
Pattern: noise.HandshakeIK,
|
||||
}
|
||||
}
|
||||
|
||||
func signableNoisePublicKey(ts time.Time, key []byte) []byte {
|
||||
var buf [8]byte
|
||||
tsnano := ts.UnixNano()
|
||||
if tsnano < 0 {
|
||||
tsnano = 0
|
||||
}
|
||||
binary.BigEndian.PutUint64(buf[:], uint64(tsnano))
|
||||
return append(buf[:], key...)
|
||||
}
|
||||
|
||||
// GenerateNoiseKeyAttestation will sign a given Noise public key using the
|
||||
// Node's leaf key and certificate chain, generating a pb.NoiseKeyAttestation.
|
||||
func GenerateNoiseKeyAttestation(ctx context.Context, ident *identity.FullIdentity, info *pb.NoiseInfo) (_ *pb.NoiseKeyAttestation, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
ts := time.Now()
|
||||
signature, err := signing.SignerFromFullIdentity(ident).HashAndSign(ctx,
|
||||
append([]byte("noise-key-attestation-v1:"), signableNoisePublicKey(ts, info.PublicKey)...))
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
return &pb.NoiseKeyAttestation{
|
||||
NodeId: ident.ID,
|
||||
NodeCertchain: identity.EncodePeerIdentity(ident.PeerIdentity()),
|
||||
NoiseProto: info.Proto,
|
||||
NoisePublicKey: info.PublicKey,
|
||||
Timestamp: ts,
|
||||
Signature: signature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ValidateNoiseKeyAttestation will confirm that a provided
|
||||
// *pb.NoiseKeyAttestation was signed correctly.
|
||||
func ValidateNoiseKeyAttestation(ctx context.Context, attestation *pb.NoiseKeyAttestation) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
peer, err := identity.DecodePeerIdentity(ctx, attestation.NodeCertchain)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
err = peer.Leaf.CheckSignatureFrom(peer.CA)
|
||||
if err != nil {
|
||||
return Error.New("certificate chain invalid: %w", err)
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare(peer.ID.Bytes(), attestation.NodeId.Bytes()) != 1 {
|
||||
return Error.New("node id mismatch")
|
||||
}
|
||||
signee := signing.SigneeFromPeerIdentity(peer)
|
||||
unsigned := signableNoisePublicKey(attestation.Timestamp, attestation.NoisePublicKey)
|
||||
err = signee.HashAndVerifySignature(ctx,
|
||||
append([]byte("noise-key-attestation-v1:"), unsigned...),
|
||||
attestation.Signature)
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
// GenerateNoiseSessionAttestation will sign a given Noise session handshake
|
||||
// hash using the Node's leaf key and certificate chain, generating a
|
||||
// pb.NoiseSessionAttestation.
|
||||
func GenerateNoiseSessionAttestation(ctx context.Context, ident *identity.FullIdentity, handshakeHash []byte) (_ *pb.NoiseSessionAttestation, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
signature, err := signing.SignerFromFullIdentity(ident).HashAndSign(ctx,
|
||||
append([]byte("noise-session-attestation-v1:"), handshakeHash...))
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
return &pb.NoiseSessionAttestation{
|
||||
NodeId: ident.ID,
|
||||
NodeCertchain: identity.EncodePeerIdentity(ident.PeerIdentity()),
|
||||
NoiseHandshakeHash: handshakeHash,
|
||||
Signature: signature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ValidateNoiseSessionAttestation will confirm that a provided
|
||||
// *pb.NoiseSessionAttestation was signed correctly.
|
||||
func ValidateNoiseSessionAttestation(ctx context.Context, attestation *pb.NoiseSessionAttestation) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
peer, err := identity.DecodePeerIdentity(ctx, attestation.NodeCertchain)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
err = peer.Leaf.CheckSignatureFrom(peer.CA)
|
||||
if err != nil {
|
||||
return Error.New("certificate chain invalid: %w", err)
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare(peer.ID.Bytes(), attestation.NodeId.Bytes()) != 1 {
|
||||
return Error.New("node id mismatch")
|
||||
}
|
||||
signee := signing.SigneeFromPeerIdentity(peer)
|
||||
err = signee.HashAndVerifySignature(ctx,
|
||||
append([]byte("noise-session-attestation-v1:"), attestation.NoiseHandshakeHash...),
|
||||
attestation.Signature)
|
||||
return Error.Wrap(err)
|
||||
|
||||
}
|
||||
|
117
private/server/noise_test.go
Normal file
117
private/server/noise_test.go
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/common/identity"
|
||||
"storj.io/common/pb"
|
||||
"storj.io/common/storj"
|
||||
"storj.io/common/testcontext"
|
||||
)
|
||||
|
||||
func TestNoiseKeyAttestation(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
ident, err := identity.NewFullIdentity(ctx, identity.NewCAOptions{})
|
||||
require.NoError(t, err)
|
||||
ident2, err := identity.NewFullIdentity(ctx, identity.NewCAOptions{})
|
||||
require.NoError(t, err)
|
||||
ident3, err := identity.NewFullIdentity(ctx, identity.NewCAOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
noiseCfg, err := generateNoiseConf(ident)
|
||||
require.NoError(t, err)
|
||||
|
||||
attestation, err := GenerateNoiseKeyAttestation(ctx, ident, &pb.NoiseInfo{
|
||||
Proto: defaultNoiseProto,
|
||||
PublicKey: noiseCfg.StaticKeypair.Public,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, ValidateNoiseKeyAttestation(ctx, attestation))
|
||||
|
||||
badAttestation1 := *attestation
|
||||
badAttestation1.NodeId, err = storj.NodeIDFromString("121RTSDpyNZVcEU84Ticf2L1ntiuUimbWgfATz21tuvgk3vzoA6")
|
||||
require.NoError(t, err)
|
||||
err = ValidateNoiseKeyAttestation(ctx, &badAttestation1)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "node id mismatch")
|
||||
|
||||
badAttestation2 := *attestation
|
||||
badAttestation2.NoisePublicKey = badAttestation2.NoisePublicKey[:len(badAttestation2.NoisePublicKey)-1]
|
||||
err = ValidateNoiseKeyAttestation(ctx, &badAttestation2)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "signature is not valid")
|
||||
|
||||
badAttestation3 := *attestation
|
||||
badAttestation3.Timestamp = time.Now()
|
||||
err = ValidateNoiseKeyAttestation(ctx, &badAttestation3)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "signature is not valid")
|
||||
|
||||
ident2.CA = ident.CA
|
||||
badAttestation4 := *attestation
|
||||
badAttestation4.NodeCertchain = identity.EncodePeerIdentity(ident2.PeerIdentity())
|
||||
err = ValidateNoiseKeyAttestation(ctx, &badAttestation4)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "certificate chain invalid")
|
||||
|
||||
ident3.Leaf = ident.Leaf
|
||||
badAttestation5 := *attestation
|
||||
badAttestation5.NodeCertchain = identity.EncodePeerIdentity(ident3.PeerIdentity())
|
||||
err = ValidateNoiseKeyAttestation(ctx, &badAttestation5)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "certificate chain invalid")
|
||||
}
|
||||
|
||||
func TestNoiseSessionAttestation(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
ident, err := identity.NewFullIdentity(ctx, identity.NewCAOptions{})
|
||||
require.NoError(t, err)
|
||||
ident2, err := identity.NewFullIdentity(ctx, identity.NewCAOptions{})
|
||||
require.NoError(t, err)
|
||||
ident3, err := identity.NewFullIdentity(ctx, identity.NewCAOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
var hash [32]byte
|
||||
_, err = rand.Read(hash[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
attestation, err := GenerateNoiseSessionAttestation(ctx, ident, hash[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, ValidateNoiseSessionAttestation(ctx, attestation))
|
||||
|
||||
badAttestation1 := *attestation
|
||||
badAttestation1.NodeId, err = storj.NodeIDFromString("121RTSDpyNZVcEU84Ticf2L1ntiuUimbWgfATz21tuvgk3vzoA6")
|
||||
require.NoError(t, err)
|
||||
err = ValidateNoiseSessionAttestation(ctx, &badAttestation1)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "node id mismatch")
|
||||
|
||||
badAttestation2 := *attestation
|
||||
badAttestation2.NoiseHandshakeHash = badAttestation2.NoiseHandshakeHash[:len(badAttestation2.NoiseHandshakeHash)-1]
|
||||
err = ValidateNoiseSessionAttestation(ctx, &badAttestation2)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "signature is not valid")
|
||||
|
||||
ident2.CA = ident.CA
|
||||
badAttestation3 := *attestation
|
||||
badAttestation3.NodeCertchain = identity.EncodePeerIdentity(ident2.PeerIdentity())
|
||||
err = ValidateNoiseSessionAttestation(ctx, &badAttestation3)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "certificate chain invalid")
|
||||
|
||||
ident3.Leaf = ident.Leaf
|
||||
badAttestation4 := *attestation
|
||||
badAttestation4.NodeCertchain = identity.EncodePeerIdentity(ident3.PeerIdentity())
|
||||
err = ValidateNoiseSessionAttestation(ctx, &badAttestation4)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "certificate chain invalid")
|
||||
}
|
@ -231,6 +231,12 @@ func (p *Server) NoiseInfo() *pb.NoiseInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// NoiseKeyAttestation returns the noise key attestation for this server.
|
||||
func (p *Server) NoiseKeyAttestation(ctx context.Context) (_ *pb.NoiseKeyAttestation, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
return GenerateNoiseKeyAttestation(ctx, p.tlsOptions.Ident, p.NoiseInfo())
|
||||
}
|
||||
|
||||
// Close shuts down the server.
|
||||
func (p *Server) Close() error {
|
||||
p.mu.Lock()
|
||||
|
@ -47,6 +47,7 @@ type NodeInfo struct {
|
||||
Version pb.NodeVersion
|
||||
Capacity pb.NodeCapacity
|
||||
Operator pb.NodeOperator
|
||||
NoiseKeyAttestation *pb.NoiseKeyAttestation
|
||||
}
|
||||
|
||||
// Service is the contact service between storage nodes and satellites.
|
||||
@ -133,6 +134,7 @@ func (service *Service) pingSatelliteOnce(ctx context.Context, id storj.NodeID)
|
||||
Version: &self.Version,
|
||||
Capacity: &self.Capacity,
|
||||
Operator: &self.Operator,
|
||||
NoiseKeyAttestation: self.NoiseKeyAttestation,
|
||||
})
|
||||
service.quicStats.SetStatus(false)
|
||||
if err != nil {
|
||||
|
@ -420,6 +420,10 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
noiseKeyAttestation, err := peer.Server.NoiseKeyAttestation(context.Background())
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
self := contact.NodeInfo{
|
||||
ID: peer.ID(),
|
||||
Address: c.ExternalAddress,
|
||||
@ -429,6 +433,7 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
|
||||
WalletFeatures: config.Operator.WalletFeatures,
|
||||
},
|
||||
Version: *pbVersion,
|
||||
NoiseKeyAttestation: noiseKeyAttestation,
|
||||
}
|
||||
peer.Contact.PingStats = new(contact.PingStats)
|
||||
peer.Contact.QUICStats = contact.NewQUICStats(peer.Server.IsQUICEnabled())
|
||||
|
Loading…
Reference in New Issue
Block a user