Skip to content

Commit

Permalink
w1p
Browse files Browse the repository at this point in the history
  • Loading branch information
abukosek committed Feb 20, 2020
1 parent 0ca1065 commit f9a5dbe
Show file tree
Hide file tree
Showing 38 changed files with 388 additions and 116 deletions.
6 changes: 3 additions & 3 deletions go/common/accessctl/accessctl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ func TestSubjectFromCertificate(t *testing.T) {
require.NoError(err, "Failed to create a temporary directory")
defer os.RemoveAll(dataDir)

ident, err := identity.LoadOrGenerate(dataDir, memorySigner.NewFactory())
ident, err := identity.LoadOrGenerate(dataDir, memorySigner.NewFactory(), false)
require.NoError(err, "Failed to generate a new identity")
require.Len(ident.TLSCertificate.Certificate, 1, "The generated identity contains more than 1 certificate in the chain")
require.Len(ident.GetTLSCertificate().Certificate, 1, "The generated identity contains more than 1 certificate in the chain")

x509Cert, err := x509.ParseCertificate(ident.TLSCertificate.Certificate[0])
x509Cert, err := x509.ParseCertificate(ident.GetTLSCertificate().Certificate[0])
require.NoError(err, "Failed to parse X.509 certificate from TLS certificate")

sub := SubjectFromX509Certificate(x509Cert)
Expand Down
14 changes: 9 additions & 5 deletions go/common/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"google.golang.org/grpc/keepalive"

"github.com/oasislabs/oasis-core/go/common/grpc/auth"
"github.com/oasislabs/oasis-core/go/common/identity"
"github.com/oasislabs/oasis-core/go/common/logging"
"github.com/oasislabs/oasis-core/go/common/service"
)
Expand Down Expand Up @@ -298,8 +299,8 @@ type ServerConfig struct { // nolint: maligned
Port uint16
// Path is the path for the local server. Leave nil to create a TCP server.
Path string
// Certificate is the certificate used by the server. Should be nil for local servers.
Certificate *tls.Certificate
// Identity is the identity of the worker that's running the server.
Identity *identity.Identity
// InstallWrapper specifies whether intercepting facilities should be enabled on this server,
// to enable intercepting RPC calls with a wrapper.
InstallWrapper bool
Expand Down Expand Up @@ -473,11 +474,14 @@ func NewServer(config *ServerConfig) (*Server, error) {
sOpts = append(sOpts, grpc.CustomCodec(&CBORCodec{}))
sOpts = append(sOpts, config.CustomOptions...)

if config.Certificate != nil {
if config.Identity != nil && config.Identity.GetTLSCertificate() != nil {
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*config.Certificate},
ClientAuth: clientAuthType,
ClientAuth: clientAuthType,
GetCertificate: func(ch *tls.ClientHelloInfo) (*tls.Certificate, error) {
return config.Identity.GetTLSCertificate(), nil
},
}

sOpts = append(sOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
}

Expand Down
4 changes: 3 additions & 1 deletion go/common/grpc/policy/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/oasislabs/oasis-core/go/common/grpc/policy"
"github.com/oasislabs/oasis-core/go/common/grpc/policy/api"
cmnTesting "github.com/oasislabs/oasis-core/go/common/grpc/testing"
"github.com/oasislabs/oasis-core/go/common/identity"
)

var testNs = common.NewTestNamespaceFromSeed([]byte("oasis common grpc policy test ns"), 0)
Expand Down Expand Up @@ -60,9 +61,10 @@ func TestAccessPolicy(t *testing.T) {
serverConfig := &cmnGrpc.ServerConfig{
Name: host,
Port: port,
Certificate: serverTLSCert,
Identity: &identity.Identity{},
CustomOptions: []grpc.ServerOption{grpc.CustomCodec(&cmnGrpc.CBORCodec{})},
}
serverConfig.Identity.SetTLSCertificate(serverTLSCert)
grpcServer, err := cmnGrpc.NewServer(serverConfig)
require.NoErrorf(err, "Failed to create a new gRPC server: %v", err)

Expand Down
11 changes: 7 additions & 4 deletions go/common/grpc/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
commonGrpc "github.com/oasislabs/oasis-core/go/common/grpc"
"github.com/oasislabs/oasis-core/go/common/grpc/auth"
cmnTesting "github.com/oasislabs/oasis-core/go/common/grpc/testing"
"github.com/oasislabs/oasis-core/go/common/identity"
)

const (
Expand Down Expand Up @@ -58,9 +59,10 @@ func TestGRPCProxy(t *testing.T) {
serverConfig := &commonGrpc.ServerConfig{
Name: host,
Port: port,
Certificate: serverTLSCert,
Identity: &identity.Identity{},
CustomOptions: []grpc.ServerOption{grpc.CustomCodec(&commonGrpc.CBORCodec{})},
}
serverConfig.Identity.SetTLSCertificate(serverTLSCert)
grpcServer, err := commonGrpc.NewServer(serverConfig)
require.NoErrorf(err, "Failed to create a new gRPC server: %v", err)

Expand All @@ -84,14 +86,15 @@ func TestGRPCProxy(t *testing.T) {

// Create a proxy gRPC server.
proxyServerConfig := &commonGrpc.ServerConfig{
Name: host,
Port: port + 1,
Certificate: serverTLSCert,
Name: host,
Port: port + 1,
Identity: &identity.Identity{},
CustomOptions: []grpc.ServerOption{
// All unknown requests will be proxied to the grpc server above.
grpc.UnknownServiceHandler(Handler(conn)),
},
}
proxyServerConfig.Identity.SetTLSCertificate(serverTLSCert)
proxyGrpcServer, err := commonGrpc.NewServer(proxyServerConfig)
require.NoErrorf(err, "Failed to create a proxy gRPC server: %v", err)

Expand Down
8 changes: 4 additions & 4 deletions go/common/grpc/testing/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ func CreateCertificate(t *testing.T) (*tls.Certificate, *x509.Certificate) {
require.NoError(err, "Failed to create a temporary directory")
defer os.RemoveAll(dataDir)

ident, err := identity.LoadOrGenerate(dataDir, memorySigner.NewFactory())
ident, err := identity.LoadOrGenerate(dataDir, memorySigner.NewFactory(), false)
require.NoError(err, "Failed to generate a new identity")
require.Len(ident.TLSCertificate.Certificate, 1, "The generated identity contains more than 1 TLS certificate in the chain")
require.Len(ident.GetTLSCertificate().Certificate, 1, "The generated identity contains more than 1 TLS certificate in the chain")

x509Cert, err := x509.ParseCertificate(ident.TLSCertificate.Certificate[0])
x509Cert, err := x509.ParseCertificate(ident.GetTLSCertificate().Certificate[0])
require.NoError(err, "Failed to parse X.509 certificate from TLS certificate")

return ident.TLSCertificate, x509Cert
return ident.GetTLSCertificate(), x509Cert
}

// PingQuery is the PingServer query.
Expand Down
139 changes: 117 additions & 22 deletions go/common/identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"crypto/rand"
"crypto/tls"
"path/filepath"
"sync"

"github.com/oasislabs/oasis-core/go/common/crypto/signature"
"github.com/oasislabs/oasis-core/go/common/crypto/signature/signers/memory"
tlsCert "github.com/oasislabs/oasis-core/go/common/crypto/tls"
"github.com/oasislabs/oasis-core/go/common/errors"
)

const (
Expand All @@ -30,8 +32,16 @@ const (
tlsCertFilename = "tls_identity_cert.pem"
)

// ErrCertificateRotationForbidden is returned by RotateCertificates if
// TLS certificate rotation is forbidden. This happens when rotation is
// enabled and an existing TLS certificate was successfully loaded
// (or a new one was generated and persisted to disk).
var ErrCertificateRotationForbidden = errors.New("identity", 1, "identity: TLS certificate rotation forbidden")

// Identity is a node identity.
type Identity struct {
sync.RWMutex

// NodeSigner is a node identity key signer.
NodeSigner signature.Signer
// P2PSigner is a node P2P link key signer.
Expand All @@ -40,21 +50,85 @@ type Identity struct {
ConsensusSigner signature.Signer
// TLSSigner is a node TLS certificate signer.
TLSSigner signature.Signer
// DoNotRotate flag is true if we mustn't rotate the TLS certificates.
DoNotRotate bool
// TLSCertificate is a certificate that can be used for TLS.
TLSCertificate *tls.Certificate
tlsCertificate *tls.Certificate
// NextTLSCertificate is a certificate that can be used for TLS in the next epoch.
nextTLSCertificate *tls.Certificate
}

// RotateCertificates rotates the TLS certificates.
// This is called on each epoch change.
func (i *Identity) RotateCertificates() error {
if i.DoNotRotate {
return ErrCertificateRotationForbidden
}

i.Lock()
defer i.Unlock()

if i.tlsCertificate != nil {
// Use the prepared certificate.
if i.nextTLSCertificate != nil {
i.tlsCertificate = i.nextTLSCertificate
}

// Generate a new TLS certificate to be used in the next epoch.
var err error
i.nextTLSCertificate, err = tlsCert.Generate(CommonName)
if err != nil {
return err
}
}

return nil
}

// GetTLSCertificate returns the current TLS certificate.
func (i *Identity) GetTLSCertificate() *tls.Certificate {
i.RLock()
defer i.RUnlock()

return i.tlsCertificate
}

// SetTLSCertificate sets the current TLS certificate.
func (i *Identity) SetTLSCertificate(cert *tls.Certificate) {
i.Lock()
defer i.Unlock()

i.tlsCertificate = cert
}

// GetNextTLSCertificate returns the next TLS certificate.
func (i *Identity) GetNextTLSCertificate() *tls.Certificate {
i.RLock()
defer i.RUnlock()

return i.nextTLSCertificate
}

// SetNextTLSCertificate sets the next TLS certificate.
func (i *Identity) SetNextTLSCertificate(nextCert *tls.Certificate) {
i.Lock()
defer i.Unlock()

i.nextTLSCertificate = nextCert
}

// Load loads an identity.
func Load(dataDir string, signerFactory signature.SignerFactory) (*Identity, error) {
return doLoadOrGenerate(dataDir, signerFactory, false)
return doLoadOrGenerate(dataDir, signerFactory, false, false)
}

// LoadOrGenerate loads or generates an identity.
func LoadOrGenerate(dataDir string, signerFactory signature.SignerFactory) (*Identity, error) {
return doLoadOrGenerate(dataDir, signerFactory, true)
// If persistTLS is true, it saves the generated TLS certificates to disk.
func LoadOrGenerate(dataDir string, signerFactory signature.SignerFactory, persistTLS bool) (*Identity, error) {
return doLoadOrGenerate(dataDir, signerFactory, true, persistTLS)
}

func doLoadOrGenerate(dataDir string, signerFactory signature.SignerFactory, shouldGenerate bool) (*Identity, error) {
func doLoadOrGenerate(dataDir string, signerFactory signature.SignerFactory, shouldGenerate bool, persistTLS bool) (*Identity, error) {
var signers []signature.Signer
for _, v := range []struct {
role signature.SignerRole
Expand Down Expand Up @@ -86,30 +160,51 @@ func doLoadOrGenerate(dataDir string, signerFactory signature.SignerFactory, sho
signers = append(signers, signer)
}

// TLS certificate.
//
// TODO: The key and cert could probably be made totally ephemeral, as long
// as the registry update takes effect immediately.
var (
cert *tls.Certificate
err error
nextCert *tls.Certificate
dnr bool
)

// First, check if we can load the TLS certificate from disk.
tlsCertPath, tlsKeyPath := TLSCertPaths(dataDir)
if shouldGenerate {
cert, err = tlsCert.LoadOrGenerate(tlsCertPath, tlsKeyPath, CommonName)
cert, err := tlsCert.Load(tlsCertPath, tlsKeyPath)
if err == nil {
// Load successful, ensure that we won't ever rotate the certificates.
dnr = true
} else {
cert, err = tlsCert.Load(tlsCertPath, tlsKeyPath)
}
if err != nil {
return nil, err
// Freshly generate TLS certificates.
cert, err = tlsCert.Generate(CommonName)
if err != nil {
return nil, err
}

if persistTLS {
// Save generated TLS certificate to disk.
err = tlsCert.Save(tlsCertPath, tlsKeyPath, cert)
if err != nil {
return nil, err
}

// Disable TLS rotation if we're persisting TLS certificates.
dnr = true
} else {
// Not persisting TLS certificate to disk, generate a new
// certificate to be used in the next rotation.
nextCert, err = tlsCert.Generate(CommonName)
if err != nil {
return nil, err
}
}
}

return &Identity{
NodeSigner: signers[0],
P2PSigner: signers[1],
ConsensusSigner: signers[2],
TLSSigner: memory.NewFromRuntime(cert.PrivateKey.(ed25519.PrivateKey)),
TLSCertificate: cert,
NodeSigner: signers[0],
P2PSigner: signers[1],
ConsensusSigner: signers[2],
TLSSigner: memory.NewFromRuntime(cert.PrivateKey.(ed25519.PrivateKey)),
DoNotRotate: dnr,
tlsCertificate: cert,
nextTLSCertificate: nextCert,
}, nil
}

Expand Down
28 changes: 24 additions & 4 deletions go/common/identity/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,36 @@ func TestLoadOrGenerate(t *testing.T) {
factory := fileSigner.NewFactory(dataDir, signature.SignerNode, signature.SignerP2P, signature.SignerConsensus)

// Generate a new identity.
identity, err := LoadOrGenerate(dataDir, factory)
identity, err := LoadOrGenerate(dataDir, factory, true)
require.NoError(t, err, "LoadOrGenerate")

// Load an existing identity.
identity2, err := LoadOrGenerate(dataDir, factory)
identity2, err := LoadOrGenerate(dataDir, factory, false)
require.NoError(t, err, "LoadOrGenerate (2)")
require.EqualValues(t, identity.NodeSigner, identity2.NodeSigner)
require.EqualValues(t, identity.P2PSigner, identity2.P2PSigner)
require.EqualValues(t, identity.ConsensusSigner, identity2.ConsensusSigner)
require.EqualValues(t, identity.TLSSigner, identity2.TLSSigner)
// TODO: Check that it always generates a fresh certificate once oasis-core#1541 is done.
require.EqualValues(t, identity.TLSCertificate, identity2.TLSCertificate)
require.EqualValues(t, identity.GetTLSCertificate(), identity2.GetTLSCertificate())

dataDir2, err := ioutil.TempDir("", "oasis-identity-test2_")
require.NoError(t, err, "create data dir (2)")
defer os.RemoveAll(dataDir2)

// Generate a new identity again, this time without persisting TLS certs.
identity3, err := LoadOrGenerate(dataDir2, factory, false)
require.NoError(t, err, "LoadOrGenerate (3)")

// Load it back.
identity4, err := LoadOrGenerate(dataDir, factory, false)
require.NoError(t, err, "LoadOrGenerate (4)")
require.EqualValues(t, identity3.NodeSigner, identity4.NodeSigner)
require.EqualValues(t, identity3.P2PSigner, identity4.P2PSigner)
require.EqualValues(t, identity3.ConsensusSigner, identity4.ConsensusSigner)
require.NotEqual(t, identity.TLSSigner, identity3.TLSSigner)
require.NotEqual(t, identity2.TLSSigner, identity3.TLSSigner)
require.NotEqual(t, identity3.TLSSigner, identity4.TLSSigner)
require.NotEqual(t, identity.GetTLSCertificate(), identity3.GetTLSCertificate())
require.NotEqual(t, identity2.GetTLSCertificate(), identity3.GetTLSCertificate())
require.NotEqual(t, identity3.GetTLSCertificate(), identity4.GetTLSCertificate())
}
13 changes: 12 additions & 1 deletion go/common/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,23 @@ type CommitteeInfo struct {
// Certificate is the certificate for establishing TLS connections.
Certificate []byte `json:"certificate"`

// NextCertificate is the certificate that will be used for establishing TLS connections in the next epoch.
NextCertificate []byte `json:"next_certificate,omitempty"`

// Addresses is the list of committee addresses at which the node can be reached.
Addresses []CommitteeAddress `json:"addresses"`
}

// Equal compares vs another CommitteeInfo for equality.
func (c *CommitteeInfo) Equal(other *CommitteeInfo) bool {
// XXX: Why is this top-level certificate even needed?
if !bytes.Equal(c.Certificate, other.Certificate) {
return false
}

if !bytes.Equal(c.NextCertificate, other.NextCertificate) {
return false
}

if len(c.Addresses) != len(other.Addresses) {
return false
}
Expand All @@ -202,6 +208,11 @@ func (c *CommitteeInfo) ParseCertificate() (*x509.Certificate, error) {
return x509.ParseCertificate(c.Certificate)
}

// ParseCertificate returns the parsed x509 next certificate.
func (c *CommitteeInfo) ParseNextCertificate() (*x509.Certificate, error) {
return x509.ParseCertificate(c.NextCertificate)
}

// P2PInfo contains information for connecting to this node via P2P transport.
type P2PInfo struct {
// ID is the unique identifier of the node on the P2P transport.
Expand Down
Loading

0 comments on commit f9a5dbe

Please sign in to comment.