Repository: scionproto/scion
Pull request #3629 (2020-01-21 14:15:46)

TrustStore: Ensure referenced TRC is available
When verifying a signature, the verifier now makes sure to have the
referenced TRC available locally.

This is one mechanism how TRC updates are disseminated.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/3629)
<!-- Reviewable:end -->



Patch:
diff --git a/go/lib/infra/modules/trust/mock_trust/trust.go b/go/lib/infra/modules/trust/mock_trust/trust.go
index ce545e59e..a7cb25220 100644
--- a/go/lib/infra/modules/trust/mock_trust/trust.go
+++ b/go/lib/infra/modules/trust/mock_trust/trust.go
@@ -42,6 +42,20 @@ func (m *MockCryptoProvider) EXPECT() *MockCryptoProviderMockRecorder {
 	return m.recorder
 }
 
+// AnnounceTRC mocks base method
+func (m *MockCryptoProvider) AnnounceTRC(arg0 context.Context, arg1 trust.TRCID, arg2 infra.TRCOpts) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AnnounceTRC", arg0, arg1, arg2)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AnnounceTRC indicates an expected call of AnnounceTRC
+func (mr *MockCryptoProviderMockRecorder) AnnounceTRC(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnounceTRC", reflect.TypeOf((*MockCryptoProvider)(nil).AnnounceTRC), arg0, arg1, arg2)
+}
+
 // GetASKey mocks base method
 func (m *MockCryptoProvider) GetASKey(arg0 context.Context, arg1 trust.ChainID, arg2 infra.ChainOpts) (scrypto.KeyMeta, error) {
 	m.ctrl.T.Helper()
diff --git a/go/lib/infra/modules/trust/provider.go b/go/lib/infra/modules/trust/provider.go
index 78915b7bf..2149ab05e 100644
--- a/go/lib/infra/modules/trust/provider.go
+++ b/go/lib/infra/modules/trust/provider.go
@@ -39,6 +39,10 @@ var ErrInactive = serrors.New("inactive")
 // CryptoProvider provides crypto material. A crypto provider can spawn network
 // requests if necessary and permitted.
 type CryptoProvider interface {
+	// AnnounceTRC announces the existence of a TRC, it must be called before
+	// verifying a signature based on a certificate chain to ensure the TRC in
+	// the signature source is available to the CryptoProvider.
+	AnnounceTRC(context.Context, TRCID, infra.TRCOpts) error
 	// GetTRC asks the trust store to return a valid and active TRC for isd,
 	// unless inactive TRCs are specifically allowed. The optionally configured
 	// server is queried over the network if the TRC is not available locally.
@@ -79,6 +83,17 @@ type Provider struct {
 	Router   Router
 }
 
+// AnnounceTRC announces the existence of a TRC, it must be called before
+// verifying a signature based on a certificate chain to ensure the TRC in
+// the signature source is available to the CryptoProvider.
+func (p Provider) AnnounceTRC(ctx context.Context, id TRCID, opts infra.TRCOpts) error {
+	// This could be implemented more efficiently, but comes with additional
+	// complexity in the code.
+	opts.AllowInactive = true
+	_, _, err := p.getCheckedTRC(ctx, id, opts)
+	return err
+}
+
 // GetTRC asks the trust store to return a valid and active TRC for isd,
 // unless inactive TRCs are specifically allowed. The optionally configured
 // server is queried over the network if the TRC is not available locally.
diff --git a/go/lib/infra/modules/trust/verifier.go b/go/lib/infra/modules/trust/verifier.go
index 019389d6b..8c9cb9ecd 100644
--- a/go/lib/infra/modules/trust/verifier.go
+++ b/go/lib/infra/modules/trust/verifier.go
@@ -112,11 +112,16 @@ func (v *verifier) Verify(ctx context.Context, msg []byte, sign *proto.SignS) er
 			"expected", v.BoundSrc, "actual", src)
 	}
 
-	id := ChainID{IA: src.IA, Version: src.ChainVer}
-	opts := infra.ChainOpts{
-		TrustStoreOpts: infra.TrustStoreOpts{Server: v.Server},
+	// Announce TRC version to the provider, to ensure the TRC referenced in the
+	// signature source is available locally.
+	id := TRCID{ISD: src.IA.I, Version: src.TRCVer}
+	tOpts := infra.TRCOpts{TrustStoreOpts: infra.TrustStoreOpts{Server: v.Server}}
+	if err := v.Store.AnnounceTRC(ctx, id, tOpts); err != nil {
+		return err
 	}
-	key, err := v.Store.GetASKey(ctx, id, opts)
+
+	opts := infra.ChainOpts{TrustStoreOpts: infra.TrustStoreOpts{Server: v.Server}}
+	key, err := v.Store.GetASKey(ctx, ChainID{IA: src.IA, Version: src.ChainVer}, opts)
 	if err != nil {
 		metrics.Verifier.Verify(l.WithResult(errToLabel(err))).Inc()
 		return err


Test patch:
diff --git a/go/lib/infra/modules/trust/provider_test.go b/go/lib/infra/modules/trust/provider_test.go
index 0048df6b3..68c2d1ff1 100644
--- a/go/lib/infra/modules/trust/provider_test.go
+++ b/go/lib/infra/modules/trust/provider_test.go
@@ -38,6 +38,103 @@ import (
 	"github.com/scionproto/scion/go/lib/xtest"
 )
 
+func TestCryptoProviderAnnounceTRC(t *testing.T) {
+	internal := serrors.New("internal")
+	type mocks struct {
+		DB       *mock_trust.MockDB
+		Recurser *mock_trust.MockRecurser
+		Resolver *mock_trust.MockResolver
+		Router   *mock_trust.MockRouter
+	}
+	tests := map[string]struct {
+		Expect      func(m *mocks, dec *decoded.TRC)
+		Opts        infra.TRCOpts
+		ExpectedErr error
+	}{
+		"TRC in database": {
+			Expect: func(m *mocks, dec *decoded.TRC) {
+				m.DB.EXPECT().GetRawTRC(gomock.Any(),
+					trust.TRCID{ISD: dec.TRC.ISD, Version: dec.TRC.Version}).Return(
+					dec.Raw, nil,
+				)
+			},
+			Opts: infra.TRCOpts{},
+		},
+		"not found, resolve success": {
+			Expect: func(m *mocks, dec *decoded.TRC) {
+				ip := &net.IPAddr{IP: []byte{127, 0, 0, 1}}
+				m.DB.EXPECT().GetRawTRC(gomock.Any(),
+					trust.TRCID{ISD: dec.TRC.ISD, Version: dec.TRC.Version}).Return(
+					nil, trust.ErrNotFound,
+				)
+				m.Recurser.EXPECT().AllowRecursion(gomock.Any()).Return(nil)
+				req := trust.TRCReq{
+					ISD:     dec.TRC.ISD,
+					Version: dec.TRC.Version,
+				}
+				m.Resolver.EXPECT().TRC(gomock.Any(), req, ip).Return(*dec, nil)
+			},
+			Opts: infra.TRCOpts{
+				TrustStoreOpts: infra.TrustStoreOpts{
+					Server: &net.IPAddr{IP: []byte{127, 0, 0, 1}},
+				},
+			},
+		},
+		"DB error": {
+			Expect: func(m *mocks, dec *decoded.TRC) {
+				m.DB.EXPECT().GetRawTRC(gomock.Any(),
+					trust.TRCID{ISD: dec.TRC.ISD, Version: dec.TRC.Version}).Return(
+					nil, internal,
+				)
+			},
+			ExpectedErr: internal,
+		},
+		"not found, local only": {
+			Expect: func(m *mocks, dec *decoded.TRC) {
+				m.DB.EXPECT().GetRawTRC(gomock.Any(),
+					trust.TRCID{ISD: dec.TRC.ISD, Version: dec.TRC.Version}).Return(
+					nil, trust.ErrNotFound,
+				)
+			},
+			Opts:        infra.TRCOpts{TrustStoreOpts: infra.TrustStoreOpts{LocalOnly: true}},
+			ExpectedErr: trust.ErrNotFound,
+		},
+		"not found, recursion not allowed": {
+			Expect: func(m *mocks, dec *decoded.TRC) {
+				m.DB.EXPECT().GetRawTRC(gomock.Any(),
+					trust.TRCID{ISD: dec.TRC.ISD, Version: dec.TRC.Version}).Return(
+					nil, trust.ErrNotFound,
+				)
+				m.Recurser.EXPECT().AllowRecursion(gomock.Any()).Return(internal)
+			},
+			ExpectedErr: internal,
+		},
+	}
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			mctrl := gomock.NewController(t)
+			defer mctrl.Finish()
+			m := mocks{
+				DB:       mock_trust.NewMockDB(mctrl),
+				Recurser: mock_trust.NewMockRecurser(mctrl),
+				Resolver: mock_trust.NewMockResolver(mctrl),
+				Router:   mock_trust.NewMockRouter(mctrl),
+			}
+			decoded := loadTRC(t, trc1v1)
+			test.Expect(&m, &decoded)
+			provider := trust.Provider{
+				DB:       m.DB,
+				Recurser: m.Recurser,
+				Resolver: m.Resolver,
+				Router:   m.Router,
+			}
+			id := trust.TRCID{ISD: trc1v1.ISD, Version: trc1v1.Version}
+			err := provider.AnnounceTRC(context.Background(), id, test.Opts)
+			xtest.AssertErrorsIs(t, err, test.ExpectedErr)
+		})
+	}
+}
+
 func TestCryptoProviderGetTRC(t *testing.T) {
 	internal := serrors.New("internal")
 	type mocks struct {
diff --git a/go/lib/infra/modules/trust/verifier_test.go b/go/lib/infra/modules/trust/verifier_test.go
index e4922c41f..c29ff2789 100644
--- a/go/lib/infra/modules/trust/verifier_test.go
+++ b/go/lib/infra/modules/trust/verifier_test.go
@@ -102,8 +102,12 @@ func TestVerify(t *testing.T) {
 		ctrl := gomock.NewController(t)
 		defer ctrl.Finish()
 		p := mock_trust.NewMockCryptoProvider(ctrl)
-		p.EXPECT().GetASKey(gomock.Any(), gomock.Any(),
-			gomock.Any()).Return(scrypto.KeyMeta{Key: public, Algorithm: scrypto.Ed25519}, nil)
+		p.EXPECT().AnnounceTRC(gomock.Any(), trust.TRCID{ISD: 1, Version: 2}, gomock.Any()).Return(
+			nil,
+		)
+		p.EXPECT().GetASKey(gomock.Any(), gomock.Any(), gomock.Any()).Return(
+			scrypto.KeyMeta{Key: public, Algorithm: scrypto.Ed25519}, nil,
+		)
 
 		v := &trust.Verifier{
 			Store: p,
@@ -123,13 +127,12 @@ func TestVerifierWithIA(t *testing.T) {
 	assert.Equal(t, y.BoundIA, ia)
 }
 
-func validSignS(msg, ias string) *proto.SignS {
-	//_, priv, _ := scrypto.GenKeyPair(scrypto.Ed25519)
-	ia, _ := addr.IAFromString(ias)
+func validSignS(msg, rawIA string) *proto.SignS {
+	ia, _ := addr.IAFromString(rawIA)
 	src := ctrl.SignSrcDef{
 		IA:       ia,
 		ChainVer: 1,
-		TRCVer:   1,
+		TRCVer:   2,
 	}
 	sign := proto.NewSignS(proto.SignType_ed25519, src.Pack())
 	sign.SetTimestamp(time.Now())