Skip to content

Commit

Permalink
added timestamp proposal check so that it does not go beyond timewind…
Browse files Browse the repository at this point in the history
…ow (#4942)

Signed-off-by: Fedor Partanskiy <fredprtnsk@gmail.com>
  • Loading branch information
pfi79 authored Aug 10, 2024
1 parent cf7cae0 commit 155457a
Show file tree
Hide file tree
Showing 12 changed files with 470 additions and 58 deletions.
10 changes: 4 additions & 6 deletions core/deliverservice/testdata/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,11 @@ peer:
# library: /etc/hyperledger/fabric/plugin/escc.so
handlers:
authFilters:
-
name: DefaultAuth
-
name: ExpirationCheck # This filter checks identity x509 certificate expiration
- name: DefaultAuth
- name: ExpirationCheck # This filter checks identity x509 certificate expiration
- name: TimeWindowCheck # This filter checks Timestamp in TimeWindow
decorators:
-
name: DefaultDecorator
- name: DefaultDecorator
endorsers:
escc:
name: DefaultEndorsement
Expand Down
69 changes: 69 additions & 0 deletions core/handlers/auth/filter/timewindow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright IBM Corp, SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package filter

import (
"context"
"time"

"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/core/handlers/auth"
"github.com/hyperledger/fabric/protoutil"
"github.com/pkg/errors"
)

// NewTimeWindowCheckFilter creates a new Filter that checks timewindow expiration
func NewTimeWindowCheckFilter(timeWindow time.Duration) auth.Filter {
return &timewindowCheckFilter{
timeWindow: timeWindow,
}
}

type timewindowCheckFilter struct {
next peer.EndorserServer
timeWindow time.Duration
}

// Init initializes the Filter with the next EndorserServer
func (f *timewindowCheckFilter) Init(next peer.EndorserServer) {
f.next = next
}

func validateTimewindowProposal(signedProp *peer.SignedProposal, timeWindow time.Duration) error {
prop, err := protoutil.UnmarshalProposal(signedProp.ProposalBytes)
if err != nil {
return errors.Wrap(err, "failed parsing proposal")
}

hdr, err := protoutil.UnmarshalHeader(prop.Header)
if err != nil {
return errors.Wrap(err, "failed parsing header")
}

chdr, err := protoutil.UnmarshalChannelHeader(hdr.ChannelHeader)
if err != nil {
return errors.Wrap(err, "failed parsing channel header")
}

timeProposal := chdr.Timestamp.AsTime().UTC()
now := time.Now().UTC()

if timeProposal.Add(timeWindow).Before(now) || timeProposal.Add(-timeWindow).After(now) {
return errors.Errorf("request unauthorized due to incorrect timestamp %s, peer time %s, peer.authentication.timewindow %s",
timeProposal.Format(time.RFC3339), now.Format(time.RFC3339), timeWindow.String())
}

return nil
}

// ProcessProposal processes a signed proposal
func (f *timewindowCheckFilter) ProcessProposal(ctx context.Context, signedProp *peer.SignedProposal) (*peer.ProposalResponse, error) {
if err := validateTimewindowProposal(signedProp, f.timeWindow); err != nil {
return nil, err
}
return f.next.ProcessProposal(ctx, signedProp)
}
64 changes: 64 additions & 0 deletions core/handlers/auth/filter/timewindow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package filter

import (
"context"
"testing"
"time"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/protoutil"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
)

func createSignedProposalForCheckTimeWindow(t *testing.T, tt time.Time) *peer.SignedProposal {
sHdr := protoutil.MakeSignatureHeader(createX509Identity(t, "notExpiredCert.pem"), nil)
hdr := protoutil.MakePayloadHeader(&common.ChannelHeader{
Timestamp: timestamppb.New(tt),
}, sHdr)
hdrBytes, err := proto.Marshal(hdr)
require.NoError(t, err)
prop := &peer.Proposal{
Header: hdrBytes,
}
propBytes, err := proto.Marshal(prop)
require.NoError(t, err)
return &peer.SignedProposal{
ProposalBytes: propBytes,
}
}

func TestTimeWindowCheckFilter(t *testing.T) {
nextEndorser := &mockEndorserServer{}
auth := NewTimeWindowCheckFilter(time.Minute * 15)
auth.Init(nextEndorser)

now := time.Now()

// Scenario I: Not expired timestamp
sp := createSignedProposalForCheckTimeWindow(t, now)
_, err := auth.ProcessProposal(context.Background(), sp)
require.NoError(t, err)
require.True(t, nextEndorser.invoked)
nextEndorser.invoked = false

// Scenario II: Expired timestamp before
sp = createSignedProposalForCheckTimeWindow(t, now.Add(-time.Minute*30))
_, err = auth.ProcessProposal(context.Background(), sp)
require.Contains(t, err.Error(), "request unauthorized due to incorrect timestamp")
require.False(t, nextEndorser.invoked)

// Scenario III: Expired timestamp after
sp = createSignedProposalForCheckTimeWindow(t, now.Add(time.Minute*30))
_, err = auth.ProcessProposal(context.Background(), sp)
require.Contains(t, err.Error(), "request unauthorized due to incorrect timestamp")
require.False(t, nextEndorser.invoked)
}
27 changes: 19 additions & 8 deletions core/handlers/library/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@
package library

import (
"time"

"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
)

// Config configures the factory methods
// and plugins for the registry
type Config struct {
AuthFilters []*HandlerConfig `yaml:"authFilters"`
Decorators []*HandlerConfig `yaml:"decorators"`
Endorsers PluginMapping `yaml:"endorsers"`
Validators PluginMapping `yaml:"validators"`
AuthFilters []*HandlerConfig `yaml:"authFilters"`
Decorators []*HandlerConfig `yaml:"decorators"`
Endorsers PluginMapping `yaml:"endorsers"`
Validators PluginMapping `yaml:"validators"`
AuthenticationTimeWindow time.Duration `yaml:"authenticationTimeWindow"`
}

// PluginMapping stores a map between chaincode id to plugin config
Expand Down Expand Up @@ -54,10 +57,18 @@ func LoadConfig() (Config, error) {
validators[k] = &HandlerConfig{Name: name, Library: library}
}

authenticationTimeWindow := viper.GetDuration("peer.authentication.timewindow")
if authenticationTimeWindow == 0 {
defaultTimeWindow := 15 * time.Minute
logger.Warningf("`peer.authentication.timewindow` not set; defaulting to %s", defaultTimeWindow)
authenticationTimeWindow = defaultTimeWindow
}

return Config{
AuthFilters: authFilters,
Decorators: decorators,
Endorsers: endorsers,
Validators: validators,
AuthFilters: authFilters,
Decorators: decorators,
Endorsers: endorsers,
Validators: validators,
AuthenticationTimeWindow: authenticationTimeWindow,
}, nil
}
14 changes: 10 additions & 4 deletions core/handlers/library/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"bytes"
"strings"
"testing"
"time"

"github.com/spf13/viper"
"github.com/stretchr/testify/require"
Expand All @@ -24,6 +25,8 @@ peer:
library: /path/to/default.so
- name: ExpirationCheck
library: /path/to/expiration.so
- name: TimeWindowCheck
library: /path/to/timewindow.so
decorators:
- name: DefaultDecorator
library: /path/to/decorators.so
Expand All @@ -49,6 +52,7 @@ peer:
AuthFilters: []*HandlerConfig{
{Name: "DefaultAuth", Library: "/path/to/default.so"},
{Name: "ExpirationCheck", Library: "/path/to/expiration.so"},
{Name: "TimeWindowCheck", Library: "/path/to/timewindow.so"},
},
Decorators: []*HandlerConfig{
{Name: "DefaultDecorator", Library: "/path/to/decorators.so"},
Expand All @@ -59,6 +63,7 @@ peer:
Validators: PluginMapping{
"vscc": &HandlerConfig{Name: "DefaultValidation", Library: "/path/to/vscc.so"},
},
AuthenticationTimeWindow: 900000000000,
}
require.EqualValues(t, expect, actual)
}
Expand Down Expand Up @@ -96,10 +101,11 @@ peer:
require.NotNil(t, actual)

expect := Config{
AuthFilters: nil,
Decorators: nil,
Endorsers: PluginMapping{"escc": &HandlerConfig{Name: "DefaultEndorsement", Library: "/path/to/foo"}},
Validators: PluginMapping{"vscc": &HandlerConfig{Name: "DefaultValidation"}},
AuthFilters: nil,
Decorators: nil,
Endorsers: PluginMapping{"escc": &HandlerConfig{Name: "DefaultEndorsement", Library: "/path/to/foo"}},
Validators: PluginMapping{"vscc": &HandlerConfig{Name: "DefaultValidation"}},
AuthenticationTimeWindow: time.Minute * 15,
}

require.EqualValues(t, expect, actual)
Expand Down
12 changes: 11 additions & 1 deletion core/handlers/library/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0
package library

import (
"time"

"github.com/hyperledger/fabric/core/handlers/auth"
"github.com/hyperledger/fabric/core/handlers/auth/filter"
"github.com/hyperledger/fabric/core/handlers/decoration"
Expand All @@ -19,7 +21,9 @@ import (

// HandlerLibrary is used to assert
// how to create the various handlers
type HandlerLibrary struct{}
type HandlerLibrary struct {
authenticationTimeWindow time.Duration
}

// DefaultAuth creates a default auth.Filter
// that doesn't do any access control checks - simply
Expand All @@ -36,6 +40,12 @@ func (r *HandlerLibrary) ExpirationCheck() auth.Filter {
return filter.NewExpirationCheckFilter()
}

// TimeWindowCheck is an auth filter which blocks requests
// from identities with Timestamp not in window
func (r *HandlerLibrary) TimeWindowCheck() auth.Filter {
return filter.NewTimeWindowCheckFilter(r.authenticationTimeWindow)
}

// DefaultDecorator creates a default decorator
// that doesn't do anything with the input, simply
// returns the input as output.
Expand Down
19 changes: 12 additions & 7 deletions core/handlers/library/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"reflect"
"sync"
"time"

"github.com/hyperledger/fabric-lib-go/common/flogging"
"github.com/hyperledger/fabric/core/handlers/auth"
Expand Down Expand Up @@ -47,10 +48,11 @@ const (
)

type registry struct {
filters []auth.Filter
decorators []decoration.Decorator
endorsers map[string]endorsement2.PluginFactory
validators map[string]validation.PluginFactory
filters []auth.Filter
decorators []decoration.Decorator
endorsers map[string]endorsement2.PluginFactory
validators map[string]validation.PluginFactory
authenticationTimeWindow time.Duration
}

var (
Expand All @@ -63,8 +65,9 @@ var (
func InitRegistry(c Config) Registry {
once.Do(func() {
reg = registry{
endorsers: make(map[string]endorsement2.PluginFactory),
validators: make(map[string]validation.PluginFactory),
endorsers: make(map[string]endorsement2.PluginFactory),
validators: make(map[string]validation.PluginFactory),
authenticationTimeWindow: c.AuthenticationTimeWindow,
}
reg.loadHandlers(c)
})
Expand Down Expand Up @@ -100,7 +103,9 @@ func (r *registry) evaluateModeAndLoad(c *HandlerConfig, handlerType HandlerType

// loadCompiled loads a statically compiled handler
func (r *registry) loadCompiled(handlerFactory string, handlerType HandlerType, extraArgs ...string) {
registryMD := reflect.ValueOf(&HandlerLibrary{})
registryMD := reflect.ValueOf(&HandlerLibrary{
authenticationTimeWindow: r.authenticationTimeWindow,
})

o := registryMD.MethodByName(handlerFactory)
if !o.IsValid() {
Expand Down
Loading

0 comments on commit 155457a

Please sign in to comment.