Skip to content

Drop invalid TLS certs during initial handshake #1923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions chains/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/ava-labs/avalanchego/snow/networking/sender"
"github.com/ava-labs/avalanchego/snow/networking/timeout"
"github.com/ava-labs/avalanchego/snow/validators"
"github.com/ava-labs/avalanchego/staking"
"github.com/ava-labs/avalanchego/subnets"
"github.com/ava-labs/avalanchego/trace"
"github.com/ava-labs/avalanchego/utils/buffer"
Expand Down Expand Up @@ -168,7 +169,7 @@ type ChainConfig struct {

type ManagerConfig struct {
SybilProtectionEnabled bool
StakingCert tls.Certificate // needed to sign snowman++ blocks
StakingTLSCert tls.Certificate // needed to sign snowman++ blocks
StakingBLSKey *bls.SecretKey
TracingEnabled bool
// Must not be used unless [TracingEnabled] is true as this may be nil.
Expand Down Expand Up @@ -234,6 +235,9 @@ type manager struct {
ids.Aliaser
ManagerConfig

stakingSigner crypto.Signer
stakingCert *staking.Certificate

// Those notified when a chain is created
registrants []Registrant

Expand Down Expand Up @@ -262,6 +266,8 @@ func New(config *ManagerConfig) Manager {
return &manager{
Aliaser: ids.NewAliaser(),
ManagerConfig: *config,
stakingSigner: config.StakingTLSCert.PrivateKey.(crypto.Signer),
stakingCert: staking.CertificateFromX509(config.StakingTLSCert.Leaf),
subnets: make(map[ids.ID]subnets.Subnet),
chains: make(map[ids.ID]handler.Handler),
chainsQueue: buffer.NewUnboundedBlockingDeque[ChainParameters](initialQueueSize),
Expand Down Expand Up @@ -772,8 +778,8 @@ func (m *manager) createAvalancheChain(
m.ApricotPhase4Time,
m.ApricotPhase4MinPChainHeight,
minBlockDelay,
m.StakingCert.PrivateKey.(crypto.Signer),
m.StakingCert.Leaf,
m.stakingSigner,
m.stakingCert,
)

if m.MeterVMEnabled {
Expand Down Expand Up @@ -1114,8 +1120,8 @@ func (m *manager) createSnowmanChain(
m.ApricotPhase4Time,
m.ApricotPhase4MinPChainHeight,
minBlockDelay,
m.StakingCert.PrivateKey.(crypto.Signer),
m.StakingCert.Leaf,
m.stakingSigner,
m.stakingCert,
)

if m.MeterVMEnabled {
Expand Down
4 changes: 2 additions & 2 deletions ids/node_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ package ids

import (
"bytes"
"crypto/x509"
"errors"
"fmt"

"github.com/ava-labs/avalanchego/staking"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/hashing"
)
Expand Down Expand Up @@ -76,7 +76,7 @@ func ToNodeID(bytes []byte) (NodeID, error) {
return NodeID(nodeID), err
}

func NodeIDFromCert(cert *x509.Certificate) NodeID {
func NodeIDFromCert(cert *staking.Certificate) NodeID {
return hashing.ComputeHash160Array(
hashing.ComputeHash256(cert.Raw),
)
Expand Down
6 changes: 4 additions & 2 deletions network/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func getTLS(t *testing.T, index int) (ids.NodeID, *tls.Certificate, *tls.Config)
tlsConfigs = append(tlsConfigs, tlsConfig)
}

cert := tlsCerts[index]
return ids.NodeIDFromCert(cert.Leaf), cert, tlsConfigs[index]
tlsCert := tlsCerts[index]
cert := staking.CertificateFromX509(tlsCert.Leaf)
nodeID := ids.NodeIDFromCert(cert)
return nodeID, tlsCert, tlsConfigs[index]
}
5 changes: 3 additions & 2 deletions network/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/ava-labs/avalanchego/snow/networking/tracker"
"github.com/ava-labs/avalanchego/snow/uptime"
"github.com/ava-labs/avalanchego/snow/validators"
"github.com/ava-labs/avalanchego/staking"
"github.com/ava-labs/avalanchego/subnets"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/ips"
Expand Down Expand Up @@ -407,7 +408,7 @@ func TestTrackVerifiesSignatures(t *testing.T) {
require.NoError(validators.Add(network.config.Validators, constants.PrimaryNetworkID, nodeID, nil, ids.Empty, 1))

_, err := network.Track(ids.EmptyNodeID, []*ips.ClaimedIPPort{{
Cert: tlsCert.Leaf,
Cert: staking.CertificateFromX509(tlsCert.Leaf),
IPPort: ips.IPPort{
IP: net.IPv4(123, 132, 123, 123),
Port: 10000,
Expand Down Expand Up @@ -589,7 +590,7 @@ func TestDialDeletesNonValidators(t *testing.T) {
for i, net := range networks {
if i != 0 {
peerAcks, err := net.Track(config.MyNodeID, []*ips.ClaimedIPPort{{
Cert: config.TLSConfig.Certificates[0].Leaf,
Cert: staking.CertificateFromX509(config.TLSConfig.Certificates[0].Leaf),
IPPort: ip.IPPort,
Timestamp: ip.Timestamp,
Signature: ip.Signature,
Expand Down
3 changes: 1 addition & 2 deletions network/peer/ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package peer
import (
"crypto"
"crypto/rand"
"crypto/x509"

"github.com/ava-labs/avalanchego/staking"
"github.com/ava-labs/avalanchego/utils/hashing"
Expand Down Expand Up @@ -50,7 +49,7 @@ type SignedIP struct {
Signature []byte
}

func (ip *SignedIP) Verify(cert *x509.Certificate) error {
func (ip *SignedIP) Verify(cert *staking.Certificate) error {
return staking.CheckSignature(
cert,
ip.UnsignedIP.bytes(),
Expand Down
12 changes: 6 additions & 6 deletions network/peer/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package peer
import (
"bufio"
"context"
"crypto/x509"
"errors"
"io"
"math"
Expand All @@ -20,6 +19,7 @@ import (
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/message"
"github.com/ava-labs/avalanchego/proto/pb/p2p"
"github.com/ava-labs/avalanchego/staking"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/ips"
Expand All @@ -43,7 +43,7 @@ type Peer interface {

// Cert returns the certificate that the remote peer is using to
// authenticate their messages.
Cert() *x509.Certificate
Cert() *staking.Certificate

// LastSent returns the last time a message was sent to the peer.
LastSent() time.Time
Expand Down Expand Up @@ -112,7 +112,7 @@ type peer struct {

// [cert] is this peer's certificate, specifically the leaf of the
// certificate chain they provided.
cert *x509.Certificate
cert *staking.Certificate

// node ID of this peer.
id ids.NodeID
Expand Down Expand Up @@ -176,7 +176,7 @@ type peer struct {
func Start(
config *Config,
conn net.Conn,
cert *x509.Certificate,
cert *staking.Certificate,
id ids.NodeID,
messageQueue MessageQueue,
) Peer {
Expand Down Expand Up @@ -207,7 +207,7 @@ func (p *peer) ID() ids.NodeID {
return p.id
}

func (p *peer) Cert() *x509.Certificate {
func (p *peer) Cert() *staking.Certificate {
return p.cert
}

Expand Down Expand Up @@ -1009,7 +1009,7 @@ func (p *peer) handlePeerList(msg *p2p.PeerList) {
// the peers this peer told us about
discoveredIPs := make([]*ips.ClaimedIPPort, len(msg.ClaimedIpPorts))
for i, claimedIPPort := range msg.ClaimedIpPorts {
tlsCert, err := x509.ParseCertificate(claimedIPPort.X509Certificate)
tlsCert, err := staking.ParseCertificate(claimedIPPort.X509Certificate)
if err != nil {
p.Log.Debug("message with invalid field",
zap.Stringer("nodeID", p.id),
Expand Down
13 changes: 7 additions & 6 deletions network/peer/peer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package peer
import (
"context"
"crypto"
"crypto/x509"
"net"
"testing"
"time"
Expand Down Expand Up @@ -41,7 +40,7 @@ type testPeer struct {
type rawTestPeer struct {
config *Config
conn net.Conn
cert *x509.Certificate
cert *staking.Certificate
nodeID ids.NodeID
inboundMsgChan <-chan message.InboundMessage
}
Expand Down Expand Up @@ -69,12 +68,14 @@ func makeRawTestPeers(t *testing.T, trackedSubnets set.Set[ids.ID]) (*rawTestPee

tlsCert0, err := staking.NewTLSCert()
require.NoError(err)
cert0 := staking.CertificateFromX509(tlsCert0.Leaf)

tlsCert1, err := staking.NewTLSCert()
require.NoError(err)
cert1 := staking.CertificateFromX509(tlsCert1.Leaf)

nodeID0 := ids.NodeIDFromCert(tlsCert0.Leaf)
nodeID1 := ids.NodeIDFromCert(tlsCert1.Leaf)
nodeID0 := ids.NodeIDFromCert(cert0)
nodeID1 := ids.NodeIDFromCert(cert1)

mc := newMessageCreator(t)

Expand Down Expand Up @@ -134,14 +135,14 @@ func makeRawTestPeers(t *testing.T, trackedSubnets set.Set[ids.ID]) (*rawTestPee
peer0 := &rawTestPeer{
config: &peerConfig0,
conn: conn0,
cert: tlsCert0.Leaf,
cert: cert0,
nodeID: nodeID0,
inboundMsgChan: inboundMsgChan0,
}
peer1 := &rawTestPeer{
config: &peerConfig1,
conn: conn1,
cert: tlsCert1.Leaf,
cert: cert1,
nodeID: nodeID1,
inboundMsgChan: inboundMsgChan1,
}
Expand Down
32 changes: 25 additions & 7 deletions network/peer/upgrader.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ package peer

import (
"crypto/tls"
"crypto/x509"
"errors"
"net"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/staking"
)

var (
Expand All @@ -21,7 +21,7 @@ var (

type Upgrader interface {
// Must be thread safe
Upgrade(net.Conn) (ids.NodeID, net.Conn, *x509.Certificate, error)
Upgrade(net.Conn) (ids.NodeID, net.Conn, *staking.Certificate, error)
}

type tlsServerUpgrader struct {
Expand All @@ -34,7 +34,7 @@ func NewTLSServerUpgrader(config *tls.Config) Upgrader {
}
}

func (t tlsServerUpgrader) Upgrade(conn net.Conn) (ids.NodeID, net.Conn, *x509.Certificate, error) {
func (t tlsServerUpgrader) Upgrade(conn net.Conn) (ids.NodeID, net.Conn, *staking.Certificate, error) {
return connToIDAndCert(tls.Server(conn, t.config))
}

Expand All @@ -48,11 +48,11 @@ func NewTLSClientUpgrader(config *tls.Config) Upgrader {
}
}

func (t tlsClientUpgrader) Upgrade(conn net.Conn) (ids.NodeID, net.Conn, *x509.Certificate, error) {
func (t tlsClientUpgrader) Upgrade(conn net.Conn) (ids.NodeID, net.Conn, *staking.Certificate, error) {
return connToIDAndCert(tls.Client(conn, t.config))
}

func connToIDAndCert(conn *tls.Conn) (ids.NodeID, net.Conn, *x509.Certificate, error) {
func connToIDAndCert(conn *tls.Conn) (ids.NodeID, net.Conn, *staking.Certificate, error) {
if err := conn.Handshake(); err != nil {
return ids.NodeID{}, nil, nil, err
}
Expand All @@ -61,6 +61,24 @@ func connToIDAndCert(conn *tls.Conn) (ids.NodeID, net.Conn, *x509.Certificate, e
if len(state.PeerCertificates) == 0 {
return ids.NodeID{}, nil, nil, errNoCert
}
peerCert := state.PeerCertificates[0]
return ids.NodeIDFromCert(peerCert), conn, peerCert, nil

tlsCert := state.PeerCertificates[0]
// Invariant: ParseCertificate is used rather than CertificateFromX509 to
// ensure that signature verification can assume the certificate was
// parseable according the staking package's parser.
peerCert, err := staking.ParseCertificate(tlsCert.Raw)
if err != nil {
return ids.NodeID{}, nil, nil, err
}

// We validate the certificate here to attempt to make the validity of the
// peer certificate as clear as possible. Specifically, a node running a
// prior version using an invalid certificate should not be able to report
// healthy.
if err := staking.ValidateCertificate(peerCert); err != nil {
return ids.NodeID{}, nil, nil, err
}

nodeID := ids.NodeIDFromCert(peerCert)
return nodeID, conn, peerCert, nil
}
12 changes: 10 additions & 2 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import (
"github.com/ava-labs/avalanchego/snow/networking/tracker"
"github.com/ava-labs/avalanchego/snow/uptime"
"github.com/ava-labs/avalanchego/snow/validators"
"github.com/ava-labs/avalanchego/staking"
"github.com/ava-labs/avalanchego/trace"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/constants"
Expand Down Expand Up @@ -805,7 +806,7 @@ func (n *Node) initChainManager(avaxAssetID ids.ID) error {

n.chainManager = chains.New(&chains.ManagerConfig{
SybilProtectionEnabled: n.Config.SybilProtectionEnabled,
StakingCert: n.Config.StakingTLSCert,
StakingTLSCert: n.Config.StakingTLSCert,
StakingBLSKey: n.Config.StakingSigningKey,
Log: n.Log,
LogFactory: n.LogFactory,
Expand Down Expand Up @@ -1344,16 +1345,23 @@ func (n *Node) Initialize(
logger logging.Logger,
logFactory logging.Factory,
) error {
tlsCert := config.StakingTLSCert.Leaf
stakingCert := staking.CertificateFromX509(tlsCert)
if err := staking.ValidateCertificate(stakingCert); err != nil {
return fmt.Errorf("invalid staking certificate: %w", err)
}

n.Log = logger
n.Config = config
n.ID = ids.NodeIDFromCert(n.Config.StakingTLSCert.Leaf)
n.ID = ids.NodeIDFromCert(stakingCert)
n.LogFactory = logFactory
n.DoneShuttingDown.Add(1)

pop := signer.NewProofOfPossession(n.Config.StakingSigningKey)
n.Log.Info("initializing node",
zap.Stringer("version", version.CurrentApp),
zap.Stringer("nodeID", n.ID),
zap.Stringer("stakingKeyType", tlsCert.PublicKeyAlgorithm),
zap.Reflect("nodePOP", pop),
zap.Reflect("providedFlags", n.Config.ProvidedFlags),
zap.Reflect("config", n.Config),
Expand Down
27 changes: 27 additions & 0 deletions staking/asn1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package staking

import (
"crypto"
"crypto/x509"
"fmt"

// Explicitly import for the crypto.RegisterHash init side-effects.
//
// Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/x509.go#L30-L34
_ "crypto/sha256"
)

// Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/x509.go#L326-L350
var signatureAlgorithmVerificationDetails = map[x509.SignatureAlgorithm]x509.PublicKeyAlgorithm{
x509.SHA256WithRSA: x509.RSA,
x509.ECDSAWithSHA256: x509.ECDSA,
}

func init() {
if !crypto.SHA256.Available() {
panic(fmt.Sprintf("required hash %q is not available", crypto.SHA256))
}
}
Loading