Skip to content

Commit

Permalink
trust: create TLS configuration based on CPPKI (#3847)
Browse files Browse the repository at this point in the history
Add a crypto manager that provides the necessary callbacks to create a SCION CPPKI backed `tls.Conifg`.
  • Loading branch information
JordiSubira authored Aug 25, 2020
1 parent 9ddebd2 commit 283cbce
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 3 deletions.
2 changes: 2 additions & 0 deletions go/pkg/trust/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ go_library(
"signer.go",
"signer_gen.go",
"store.go",
"tls_handshake.go",
"verifier.go",
],
importpath = "github.com/scionproto/scion/go/pkg/trust",
Expand Down Expand Up @@ -55,6 +56,7 @@ go_test(
"signer_gen_test.go",
"signer_test.go",
"store_test.go",
"tls_handshake_test.go",
"verifier_test.go",
],
data = glob(["testdata/**"]),
Expand Down
46 changes: 43 additions & 3 deletions go/pkg/trust/mock_trust/trust.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 109 additions & 0 deletions go/pkg/trust/tls_handshake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2020 ETH Zurich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package trust

import (
"context"
"crypto/tls"
"crypto/x509"
"time"

"github.com/scionproto/scion/go/lib/scrypto/cppki"
"github.com/scionproto/scion/go/lib/serrors"
)

const defaultTimeout = 5 * time.Second

// X509KeyPairLoader provides a certificate to be presented during TLS handshake.
type X509KeyPairLoader interface {
LoadX509KeyPair() (*tls.Certificate, error)
}

// TLSCryptoManager implements callbacks which will be called during TLS handshake.
type TLSCryptoManager struct {
Loader X509KeyPairLoader
DB DB
Timeout time.Duration
}

// NewTLSCryptoManager returns a new instance with the defaultTimeout.
func NewTLSCryptoManager(loader X509KeyPairLoader, db DB) *TLSCryptoManager {
return &TLSCryptoManager{
DB: db,
Loader: loader,
Timeout: defaultTimeout,
}
}

// GetCertificate retrieves a certificate to be presented during TLS handshake.
func (m *TLSCryptoManager) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
c, err := m.Loader.LoadX509KeyPair()
if err != nil {
return nil, serrors.WrapStr("loading server key pair", err)
}
return c, nil
}

// GetClientCertificate retrieves a client certificate to be presented during TLS handshake.
func (m *TLSCryptoManager) GetClientCertificate(_ *tls.CertificateRequestInfo) (*tls.Certificate,
error) {
c, err := m.Loader.LoadX509KeyPair()
if err != nil {
return nil, serrors.WrapStr("loading client key pair", err)
}
return c, nil
}

// VerifyPeerCertificate verifies the certificate presented by the peer during TLS handshake,
// based on the TRC.
func (m *TLSCryptoManager) VerifyPeerCertificate(rawCerts [][]byte,
_ [][]*x509.Certificate) error {
chain := make([]*x509.Certificate, len(rawCerts))
for i, asn1Data := range rawCerts {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
return serrors.WrapStr("parsing peer certificate", err)
}
chain[i] = cert
}
ia, err := cppki.ExtractIA(chain[0].Subject)
if err != nil {
return serrors.WrapStr("extracting ISD-AS from peer certificate", err)
} else if ia == nil {
return serrors.New("ISD-AS no present in peer certificate")
}
ctx, cancel := context.WithTimeout(context.Background(), m.Timeout)
defer cancel()
trcs, _, err := activeTRCs(ctx, m.DB, ia.I)
if err != nil {
return serrors.WrapStr("loading TRCs", err)
}
if err := verifyChain(chain, trcs); err != nil {
return serrors.WrapStr("verifying chains", err)
}
return nil
}

func verifyChain(chain []*x509.Certificate, trcs []cppki.SignedTRC) error {
var errs serrors.List
for _, trc := range trcs {
if err := cppki.VerifyChain(chain, cppki.VerifyOptions{TRC: &trc.TRC}); err != nil {
errs = append(errs, err)
continue
}
return nil
}
return errs.ToError()
}
130 changes: 130 additions & 0 deletions go/pkg/trust/tls_handshake_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2020 ETH Zurich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package trust_test

import (
"crypto/tls"
"net"
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/scionproto/scion/go/lib/scrypto/cppki"
"github.com/scionproto/scion/go/lib/xtest"
"github.com/scionproto/scion/go/pkg/trust"
"github.com/scionproto/scion/go/pkg/trust/mock_trust"
)

func TestTLSCryptoManagerVerifyPeerCertificate(t *testing.T) {
trc := xtest.LoadTRC(t, "testdata/common/trcs/ISD1-B1-S1.trc")
crt111File := "testdata/common/ISD1/ASff00_0_111/crypto/as/ISD1-ASff00_0_111.pem"

testCases := map[string]struct {
db func(ctrl *gomock.Controller) trust.DB
assertErr assert.ErrorAssertionFunc
}{
"valid": {
db: func(ctrl *gomock.Controller) trust.DB {
db := mock_trust.NewMockDB(ctrl)
db.EXPECT().SignedTRC(gomock.Any(), gomock.Any()).Return(trc, nil)
return db
},
assertErr: assert.NoError,
},
}
for name, tc := range testCases {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

db := tc.db(ctrl)
mgr := trust.TLSCryptoManager{
DB: db,
Timeout: 5 * time.Second,
}
rawChain := loadRawChain(t, crt111File)
err := mgr.VerifyPeerCertificate(rawChain, nil)
tc.assertErr(t, err)
})
}
}

func TestHandshake(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

trc := xtest.LoadTRC(t, "testdata/common/trcs/ISD1-B1-S1.trc")
crt111File := "testdata/common/ISD1/ASff00_0_111/crypto/as/ISD1-ASff00_0_111.pem"
key111File := "testdata/common/ISD1/ASff00_0_111/crypto/as/cp-as.key"
tlsCert, err := tls.LoadX509KeyPair(crt111File, key111File)
require.NoError(t, err)
chain, err := cppki.ReadPEMCerts(crt111File)
require.NoError(t, err)

db := mock_trust.NewMockDB(ctrl)
db.EXPECT().SignedTRC(gomock.Any(), gomock.Any()).MaxTimes(2).Return(trc, nil)
loader := mock_trust.NewMockX509KeyPairLoader(ctrl)
loader.EXPECT().LoadX509KeyPair().MaxTimes(2).Return(&tlsCert, nil)

mgr := trust.NewTLSCryptoManager(loader, db)
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()

client := tls.Client(clientConn, &tls.Config{
InsecureSkipVerify: true,
GetClientCertificate: mgr.GetClientCertificate,
VerifyPeerCertificate: mgr.VerifyPeerCertificate,
})
server := tls.Server(serverConn, &tls.Config{
InsecureSkipVerify: true,
GetCertificate: mgr.GetCertificate,
VerifyPeerCertificate: mgr.VerifyPeerCertificate,
ClientAuth: tls.RequireAnyClientCert,
})

connCheck := func(w, r net.Conn) {
msg := []byte("hello")

go func() {
_, err := w.Write(msg)
require.NoError(t, err)
}()

buf := make([]byte, 100)
n, err := r.Read(buf)
require.NoError(t, err)
require.Equal(t, msg, buf[:n])
}

connCheck(server, client)

assert.Equal(t, chain, client.ConnectionState().PeerCertificates)
assert.Equal(t, chain, server.ConnectionState().PeerCertificates)
assert.True(t, client.ConnectionState().HandshakeComplete)
assert.True(t, server.ConnectionState().HandshakeComplete)
}

func loadRawChain(t *testing.T, file string) [][]byte {
var chain [][]byte
for _, cert := range xtest.LoadChain(t, file) {
chain = append(chain, cert.Raw)
}
return chain
}

0 comments on commit 283cbce

Please sign in to comment.