Skip to content
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

cppki: limit segment expiration by certificate lifetime #4369

Merged
merged 2 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions control/beaconing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ go_library(
"//private/segment/verifier:go_default_library",
"//private/topology:go_default_library",
"//private/tracing:go_default_library",
"//private/trust:go_default_library",
"@com_github_opentracing_opentracing_go//:go_default_library",
],
)
Expand Down Expand Up @@ -70,6 +71,7 @@ go_test(
"//pkg/scrypto/signed:go_default_library",
"//pkg/segment:go_default_library",
"//pkg/segment/extensions/staticinfo:go_default_library",
"//pkg/slayers/path:go_default_library",
"//pkg/slayers/path/scion:go_default_library",
"//pkg/snet:go_default_library",
"//pkg/snet/addrutil:go_default_library",
Expand Down
64 changes: 50 additions & 14 deletions control/beaconing/extender.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,22 @@ import (
"github.com/scionproto/scion/control/ifstate"
"github.com/scionproto/scion/pkg/addr"
"github.com/scionproto/scion/pkg/log"
"github.com/scionproto/scion/pkg/metrics"
"github.com/scionproto/scion/pkg/private/serrors"
"github.com/scionproto/scion/pkg/private/util"
seg "github.com/scionproto/scion/pkg/segment"
"github.com/scionproto/scion/pkg/segment/extensions/digest"
"github.com/scionproto/scion/pkg/segment/extensions/epic"
"github.com/scionproto/scion/pkg/slayers/path"
"github.com/scionproto/scion/private/trust"
)

// SignerGen generates signers and returns their expiration time.
type SignerGen interface {
// Generate generates a signer it.
Generate(ctx context.Context) (trust.Signer, error)
}

// Extender extends path segments.
type Extender interface {
// Extend extends the path segment. The zero value for ingress indicates
Expand All @@ -44,8 +52,8 @@ type Extender interface {
type DefaultExtender struct {
// IA is the local IA
IA addr.IA
// Signer is used to sign path segments.
Signer seg.Signer
// SignerGen is used to sign path segments.
SignerGen SignerGen
// MAC is used to calculate the hop field MAC.
MAC func() hash.Hash
// Intfs holds all interfaces in the AS.
Expand All @@ -60,6 +68,11 @@ type DefaultExtender struct {
StaticInfo func() *StaticInfoCfg
// EPIC defines whether the EPIC authenticators should be added when the segment is extended.
EPIC bool

// SegmentExpirationDeficient is a gauge that is set to 1 if the expiration time of the segment
// is below the maximum expiration time. This happens when the signer expiration time is lower
// than the maximum segment expiration time.
SegmentExpirationDeficient metrics.Gauge
}

// Extend extends the beacon with hop fields.
Expand All @@ -85,8 +98,27 @@ func (s *DefaultExtender) Extend(
}
ts := pseg.Info.Timestamp

signer, err := s.SignerGen.Generate(ctx)
if err != nil {
return serrors.WrapStr("getting signer", err)
}
// Make sure the hop expiration time is not longer than the signer expiration time.
expTime := s.MaxExpTime()
if ts.Add(path.ExpTimeToDuration(expTime)).After(signer.Expiration) {
metrics.GaugeSet(s.SegmentExpirationDeficient, 1)
var err error
expTime, err = path.ExpTimeFromDuration(signer.Expiration.Sub(ts))
if err != nil {
return serrors.WrapStr(
"calculating expiry time from signer expiration time", err,
"signer_expiration", signer.Expiration,
)
}
} else {
metrics.GaugeSet(s.SegmentExpirationDeficient, 0)
}
hopBeta := extractBeta(pseg)
hopEntry, epicHopMac, err := s.createHopEntry(ingress, egress, ts, hopBeta)
hopEntry, epicHopMac, err := s.createHopEntry(ingress, egress, expTime, ts, hopBeta)
if err != nil {
return serrors.WrapStr("creating hop entry", err)
}
Expand All @@ -104,7 +136,7 @@ func (s *DefaultExtender) Extend(
// is traversed.

peerBeta := hopBeta ^ binary.BigEndian.Uint16(hopEntry.HopField.MAC[:2])
peerEntries, epicPeerMacs, err := s.createPeerEntries(egress, peers, ts, peerBeta)
peerEntries, epicPeerMacs, err := s.createPeerEntries(egress, peers, expTime, ts, peerBeta)
if err != nil {
return err
}
Expand Down Expand Up @@ -143,7 +175,7 @@ func (s *DefaultExtender) Extend(
}
}

if err := pseg.AddASEntry(ctx, asEntry, s.Signer); err != nil {
if err := pseg.AddASEntry(ctx, asEntry, signer); err != nil {
return err
}
if egress == 0 {
Expand All @@ -153,12 +185,12 @@ func (s *DefaultExtender) Extend(
}

func (s *DefaultExtender) createPeerEntries(egress uint16, peers []uint16,
ts time.Time, beta uint16) ([]seg.PeerEntry, [][]byte, error) {
expTime uint8, ts time.Time, beta uint16) ([]seg.PeerEntry, [][]byte, error) {

peerEntries := make([]seg.PeerEntry, 0, len(peers))
peerEpicMacs := make([][]byte, 0, len(peers))
for _, peer := range peers {
peerEntry, epicMac, err := s.createPeerEntry(peer, egress, ts, beta)
peerEntry, epicMac, err := s.createPeerEntry(peer, egress, expTime, ts, beta)
if err != nil {
log.Debug("Ignoring peer link upon error",
"task", s.Task, "peer_interface", peer, "err", err)
Expand All @@ -170,15 +202,20 @@ func (s *DefaultExtender) createPeerEntries(egress uint16, peers []uint16,
return peerEntries, peerEpicMacs, nil
}

func (s *DefaultExtender) createHopEntry(ingress, egress uint16, ts time.Time,
beta uint16) (seg.HopEntry, []byte, error) {
func (s *DefaultExtender) createHopEntry(
ingress,
egress uint16,
expTime uint8,
ts time.Time,
beta uint16,
) (seg.HopEntry, []byte, error) {

remoteInMTU, err := s.remoteMTU(ingress)
if err != nil {
return seg.HopEntry{}, nil, serrors.WrapStr("checking remote ingress interface (mtu)", err,
"interfaces", ingress)
}
hopF, epicMac := s.createHopF(ingress, egress, ts, beta)
hopF, epicMac := s.createHopF(ingress, egress, expTime, ts, beta)
return seg.HopEntry{
IngressMTU: int(remoteInMTU),
HopField: seg.HopField{
Expand All @@ -190,15 +227,15 @@ func (s *DefaultExtender) createHopEntry(ingress, egress uint16, ts time.Time,
}, epicMac, nil
}

func (s *DefaultExtender) createPeerEntry(ingress, egress uint16, ts time.Time,
func (s *DefaultExtender) createPeerEntry(ingress, egress uint16, expTime uint8, ts time.Time,
beta uint16) (seg.PeerEntry, []byte, error) {

remoteInIA, remoteInIfID, remoteInMTU, err := s.remoteInfo(ingress)
if err != nil {
return seg.PeerEntry{}, nil, serrors.WrapStr("checking remote ingress interface", err,
"ingress_interface", ingress)
}
hopF, epicMac := s.createHopF(ingress, egress, ts, beta)
hopF, epicMac := s.createHopF(ingress, egress, expTime, ts, beta)
return seg.PeerEntry{
PeerMTU: int(remoteInMTU),
Peer: remoteInIA,
Expand Down Expand Up @@ -259,10 +296,9 @@ func (s *DefaultExtender) remoteInfo(ifid uint16) (
return topoInfo.IA, topoInfo.RemoteID, topoInfo.MTU, nil
}

func (s *DefaultExtender) createHopF(ingress, egress uint16, ts time.Time,
func (s *DefaultExtender) createHopF(ingress, egress uint16, expTime uint8, ts time.Time,
beta uint16) (path.HopField, []byte) {

expTime := s.MaxExpTime()
input := make([]byte, path.MACBufferSize)
path.MACInput(beta, util.TimeToSecs(ts), expTime, ingress, egress, input)

Expand Down
114 changes: 107 additions & 7 deletions control/beaconing/extender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import (
cryptopb "github.com/scionproto/scion/pkg/proto/crypto"
"github.com/scionproto/scion/pkg/scrypto"
seg "github.com/scionproto/scion/pkg/segment"
"github.com/scionproto/scion/pkg/slayers/path"
"github.com/scionproto/scion/private/topology"
"github.com/scionproto/scion/private/trust"
)

func TestDefaultExtenderExtend(t *testing.T) {
Expand Down Expand Up @@ -98,8 +100,8 @@ func TestDefaultExtenderExtend(t *testing.T) {
intfs.Get(peer).Activate(peerRemoteIfs[peer])
}
ext := &beaconing.DefaultExtender{
IA: topo.IA(),
Signer: testSigner(t, priv, topo.IA()),
IA: topo.IA(),
SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())},
MAC: func() hash.Hash {
mac, err := scrypto.InitMac(make([]byte, 16))
require.NoError(t, err)
Expand Down Expand Up @@ -171,8 +173,8 @@ func TestDefaultExtenderExtend(t *testing.T) {
intfs := ifstate.NewInterfaces(interfaceInfos(topo), ifstate.Config{})
require.NoError(t, err)
ext := &beaconing.DefaultExtender{
IA: topo.IA(),
Signer: testSigner(t, priv, topo.IA()),
IA: topo.IA(),
SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())},
MAC: func() hash.Hash {
mac, err := scrypto.InitMac(make([]byte, 16))
require.NoError(t, err)
Expand All @@ -189,8 +191,106 @@ func TestDefaultExtenderExtend(t *testing.T) {
err = ext.Extend(context.Background(), pseg, 0, graph.If_111_A_112_X, []uint16{})
require.NoError(t, err)
assert.Equal(t, uint8(1), pseg.ASEntries[0].HopEntry.HopField.ExpTime)

})
t.Run("segment and signer expiration interaction", func(t *testing.T) {
ts := time.Now()
testCases := map[string]struct {
SignerGen beaconing.SignerGen
MaxExpTime func() uint8
ExpTime uint8
ErrAssertion assert.ErrorAssertionFunc
}{
"signer expires before max expiration time": {
SignerGen: testSignerGen{
Signer: func() trust.Signer {
s := testSigner(t, priv, topo.IA())
s.Expiration = ts.Add(path.MaxTTL / 2)
return s
}(),
},
ExpTime: 127,
MaxExpTime: func() uint8 { return 255 },
ErrAssertion: assert.NoError,
},
"signer expires after max expiration time": {
SignerGen: testSignerGen{
Signer: func() trust.Signer {
s := testSigner(t, priv, topo.IA())
s.Expiration = ts.Add(path.MaxTTL)
return s
}(),
},
ExpTime: 254,
MaxExpTime: func() uint8 { return 254 },
ErrAssertion: assert.NoError,
},
"minimum signer expiration time": {
SignerGen: testSignerGen{
Signer: func() trust.Signer {
s := testSigner(t, priv, topo.IA())
s.Expiration = ts.Add(path.MaxTTL / 256)
return s
}(),
},
ExpTime: 0,
MaxExpTime: func() uint8 { return 10 },
ErrAssertion: assert.NoError,
},
"signer expiration time too small": {
SignerGen: testSignerGen{
Signer: func() trust.Signer {
s := testSigner(t, priv, topo.IA())
s.Expiration = ts.Add(path.MaxTTL / 257)
return s
}(),
},
MaxExpTime: func() uint8 { return 10 },
ErrAssertion: assert.Error,
},
"signer expiration time too large uses MaxExpTime": {
SignerGen: testSignerGen{
Signer: func() trust.Signer {
s := testSigner(t, priv, topo.IA())
s.Expiration = ts.Add(2 * path.MaxTTL)
return s
}(),
},
ExpTime: 157,
MaxExpTime: func() uint8 { return 157 },
ErrAssertion: assert.NoError,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
mctrl := gomock.NewController(t)
defer mctrl.Finish()
intfs := ifstate.NewInterfaces(interfaceInfos(topo), ifstate.Config{})
require.NoError(t, err)
ext := &beaconing.DefaultExtender{
IA: topo.IA(),
SignerGen: tc.SignerGen,
MAC: func() hash.Hash {
mac, err := scrypto.InitMac(make([]byte, 16))
require.NoError(t, err)
return mac
},
Intfs: intfs,
MTU: 1337,
MaxExpTime: tc.MaxExpTime,
StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
}
pseg, err := seg.CreateSegment(ts, uint16(mrand.Int()))
require.NoError(t, err)
err = ext.Extend(context.Background(), pseg, 0, graph.If_111_A_112_X, []uint16{})
tc.ErrAssertion(t, err)
if err != nil {
return
}
assert.Equal(t, tc.ExpTime, pseg.ASEntries[0].HopEntry.HopField.ExpTime)
})
}
})

t.Run("segment is not extended on error", func(t *testing.T) {
defaultSigner := func(t *testing.T) seg.Signer {
return testSigner(t, priv, topo.IA())
Expand Down Expand Up @@ -238,8 +338,8 @@ func TestDefaultExtenderExtend(t *testing.T) {
defer mctrl.Finish()
intfs := ifstate.NewInterfaces(interfaceInfos(topo), ifstate.Config{})
ext := &beaconing.DefaultExtender{
IA: topo.IA(),
Signer: testSigner(t, priv, topo.IA()),
IA: topo.IA(),
SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())},
MAC: func() hash.Hash {
mac, err := scrypto.InitMac(make([]byte, 16))
require.NoError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions control/beaconing/originator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestOriginatorRun(t *testing.T) {
Extender: &beaconing.DefaultExtender{
IA: topo.IA(),
MTU: topo.MTU(),
Signer: signer,
SignerGen: testSignerGen{Signer: signer},
Intfs: intfs,
MAC: macFactory,
MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
Expand Down Expand Up @@ -130,7 +130,7 @@ func TestOriginatorRun(t *testing.T) {
Extender: &beaconing.DefaultExtender{
IA: topo.IA(),
MTU: topo.MTU(),
Signer: signer,
SignerGen: testSignerGen{Signer: signer},
Intfs: intfs,
MAC: macFactory,
MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
Expand Down
6 changes: 3 additions & 3 deletions control/beaconing/propagator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestPropagatorRunNonCore(t *testing.T) {
Extender: &beaconing.DefaultExtender{
IA: topo.IA(),
MTU: topo.MTU(),
Signer: testSigner(t, priv, topo.IA()),
SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())},
Intfs: intfs,
MAC: macFactory,
MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
Expand Down Expand Up @@ -138,7 +138,7 @@ func TestPropagatorRunCore(t *testing.T) {
Extender: &beaconing.DefaultExtender{
IA: topo.IA(),
MTU: topo.MTU(),
Signer: testSigner(t, priv, topo.IA()),
SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())},
Intfs: intfs,
MAC: macFactory,
MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
Expand Down Expand Up @@ -226,7 +226,7 @@ func TestPropagatorFastRecovery(t *testing.T) {
Extender: &beaconing.DefaultExtender{
IA: topo.IA(),
MTU: topo.MTU(),
Signer: testSigner(t, priv, topo.IA()),
SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())},
Intfs: intfs,
MAC: macFactory,
MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
Expand Down
Loading