storj/pkg/peertls/tlsopts/tls.go
JT Olio 6ede140df1
pkg/rpc: defeat MITM attacks in most cases (#3215)
This change adds a trusted registry (via the source code) of node address to node id mappings (currently only for well known Satellites) to defeat MITM attacks to Satellites. It also extends the uplink UI such that when entering a satellite address by hand, a node id prefix can also be added to defeat MITM attacks with unknown satellites.

When running uplink setup, satellite addresses can now be of the form 12EayRS2V1k@us-central-1.tardigrade.io (not even using a full node id) to ensure that the peer contacted is the peer that was expected. When using a known satellite address, the known node ids are used if no override is provided.
2019-10-12 14:34:41 -06:00

132 lines
3.6 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package tlsopts
import (
"crypto/tls"
"crypto/x509"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"storj.io/storj/pkg/identity"
"storj.io/storj/pkg/peertls"
"storj.io/storj/pkg/storj"
)
// ServerOption returns a grpc `ServerOption` for incoming connections
// to the node with this full identity.
func (opts *Options) ServerOption() grpc.ServerOption {
tlsConfig := opts.ServerTLSConfig()
return grpc.Creds(credentials.NewTLS(tlsConfig))
}
// DialOption returns a grpc `DialOption` for making outgoing connections
// to the node with this peer identity.
func (opts *Options) DialOption(id storj.NodeID) (grpc.DialOption, error) {
if id.IsZero() {
return nil, Error.New("no ID specified for DialOption")
}
tlsConfig := opts.ClientTLSConfig(id)
return grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), nil
}
// DialUnverifiedIDOption returns a grpc `DialUnverifiedIDOption`
func (opts *Options) DialUnverifiedIDOption() grpc.DialOption {
tlsConfig := opts.tlsConfig(false)
return grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))
}
// ServerTLSConfig returns a TSLConfig for use as a server in handshaking with a peer.
func (opts *Options) ServerTLSConfig() *tls.Config {
return opts.tlsConfig(true)
}
// ClientTLSConfig returns a TSLConfig for use as a client in handshaking with a peer.
func (opts *Options) ClientTLSConfig(id storj.NodeID) *tls.Config {
return opts.tlsConfig(false, verifyIdentity(id))
}
// ClientTLSConfigPrefix returns a TSLConfig for use as a client in handshaking with a peer.
// The peer node id is validated to match the given prefix
func (opts *Options) ClientTLSConfigPrefix(idPrefix string) *tls.Config {
return opts.tlsConfig(false, verifyIdentityPrefix(idPrefix))
}
// UnverifiedClientTLSConfig returns a TLSConfig for use as a client in handshaking with
// an unknown peer.
func (opts *Options) UnverifiedClientTLSConfig() *tls.Config {
return opts.tlsConfig(false)
}
func (opts *Options) tlsConfig(isServer bool, verificationFuncs ...peertls.PeerCertVerificationFunc) *tls.Config {
verificationFuncs = append(
[]peertls.PeerCertVerificationFunc{
peertls.VerifyPeerCertChains,
},
verificationFuncs...,
)
switch isServer {
case true:
verificationFuncs = append(
verificationFuncs,
opts.VerificationFuncs.server...,
)
case false:
verificationFuncs = append(
verificationFuncs,
opts.VerificationFuncs.client...,
)
}
config := &tls.Config{
Certificates: []tls.Certificate{*opts.Cert},
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
VerifyPeerCertificate: peertls.VerifyPeerFunc(
verificationFuncs...,
),
}
if isServer {
config.ClientAuth = tls.RequireAnyClientCert
}
return config
}
func verifyIdentity(id storj.NodeID) peertls.PeerCertVerificationFunc {
return func(_ [][]byte, parsedChains [][]*x509.Certificate) (err error) {
defer mon.TaskNamed("verifyIdentity")(nil)(&err)
peer, err := identity.PeerIdentityFromChain(parsedChains[0])
if err != nil {
return err
}
if peer.ID.String() != id.String() {
return Error.New("peer ID did not match requested ID")
}
return nil
}
}
func verifyIdentityPrefix(idPrefix string) peertls.PeerCertVerificationFunc {
return func(_ [][]byte, parsedChains [][]*x509.Certificate) (err error) {
defer mon.TaskNamed("verifyIdentityPrefix")(nil)(&err)
peer, err := identity.PeerIdentityFromChain(parsedChains[0])
if err != nil {
return err
}
if !strings.HasPrefix(peer.ID.String(), idPrefix) {
return Error.New("peer ID did not match requested ID prefix")
}
return nil
}
}