Skip to content

Commit aa7042e

Browse files
committed
[FAB-7632] Block expired x509 identities in endorsement
This change set adds an authentication filter which is enabled by default (but can be removed at will) that blocks proposals with identities that are x509 identities and they are expired. Change-Id: Id1d30d6be297a06dbc4579a4e8652ed07139241b Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 665ace6 commit aa7042e

File tree

6 files changed

+234
-0
lines changed

6 files changed

+234
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package filter
8+
9+
import (
10+
"time"
11+
12+
"github.com/hyperledger/fabric/common/crypto"
13+
"github.com/hyperledger/fabric/core/handlers/auth"
14+
"github.com/hyperledger/fabric/protos/peer"
15+
"github.com/hyperledger/fabric/protos/utils"
16+
"github.com/pkg/errors"
17+
"golang.org/x/net/context"
18+
)
19+
20+
// NewExpirationCheckFilter creates a new Filter that checks identity expiration
21+
func NewExpirationCheckFilter() auth.Filter {
22+
return &expirationCheckFilter{}
23+
}
24+
25+
type expirationCheckFilter struct {
26+
next peer.EndorserServer
27+
}
28+
29+
// Init initializes the Filter with the next EndorserServer
30+
func (f *expirationCheckFilter) Init(next peer.EndorserServer) {
31+
f.next = next
32+
}
33+
34+
func validateProposal(signedProp *peer.SignedProposal) error {
35+
prop, err := utils.GetProposal(signedProp.ProposalBytes)
36+
if err != nil {
37+
return errors.Wrap(err, "failed parsing proposal")
38+
}
39+
40+
hdr, err := utils.GetHeader(prop.Header)
41+
if err != nil {
42+
return errors.Wrap(err, "failed parsing header")
43+
}
44+
45+
sh, err := utils.GetSignatureHeader(hdr.SignatureHeader)
46+
if err != nil {
47+
return errors.Wrap(err, "failed parsing signature header")
48+
}
49+
expirationTime := crypto.ExpiresAt(sh.Creator)
50+
if !expirationTime.IsZero() && time.Now().After(expirationTime) {
51+
return errors.New("identity expired")
52+
}
53+
return nil
54+
}
55+
56+
// ProcessProposal processes a signed proposal
57+
func (f *expirationCheckFilter) ProcessProposal(ctx context.Context, signedProp *peer.SignedProposal) (*peer.ProposalResponse, error) {
58+
if err := validateProposal(signedProp); err != nil {
59+
return nil, err
60+
}
61+
return f.next.ProcessProposal(ctx, signedProp)
62+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package filter
8+
9+
import (
10+
"io/ioutil"
11+
"path/filepath"
12+
"testing"
13+
14+
"github.com/golang/protobuf/proto"
15+
"github.com/hyperledger/fabric/protos/common"
16+
"github.com/hyperledger/fabric/protos/msp"
17+
"github.com/hyperledger/fabric/protos/peer"
18+
"github.com/hyperledger/fabric/protos/utils"
19+
"github.com/stretchr/testify/assert"
20+
"golang.org/x/net/context"
21+
)
22+
23+
type mutator func([]byte) []byte
24+
25+
func noopMutator(b []byte) []byte {
26+
return b
27+
}
28+
29+
func corruptMutator(b []byte) []byte {
30+
b = append(b, 0)
31+
return b
32+
}
33+
34+
func createX509Identity(t *testing.T, certFileName string) []byte {
35+
certBytes, err := ioutil.ReadFile(filepath.Join("testdata", certFileName))
36+
assert.NoError(t, err)
37+
sId := &msp.SerializedIdentity{
38+
IdBytes: certBytes,
39+
}
40+
idBytes, err := proto.Marshal(sId)
41+
assert.NoError(t, err)
42+
return idBytes
43+
}
44+
45+
func createIdemixIdentity(t *testing.T) []byte {
46+
idemixId := &msp.SerializedIdemixIdentity{
47+
NymX: []byte{1, 2, 3},
48+
NymY: []byte{1, 2, 3},
49+
OU: []byte("OU1"),
50+
}
51+
idemixBytes, err := proto.Marshal(idemixId)
52+
assert.NoError(t, err)
53+
sId := &msp.SerializedIdentity{
54+
IdBytes: idemixBytes,
55+
}
56+
idBytes, err := proto.Marshal(sId)
57+
assert.NoError(t, err)
58+
return idBytes
59+
}
60+
61+
func createSignedProposal(t *testing.T, serializedIdentity []byte, corruptSigHdr mutator, corruptHdr mutator) *peer.SignedProposal {
62+
sHdr := utils.MakeSignatureHeader(serializedIdentity, nil)
63+
hdr := utils.MakePayloadHeader(&common.ChannelHeader{}, sHdr)
64+
hdr.SignatureHeader = corruptSigHdr(hdr.SignatureHeader)
65+
hdrBytes, err := proto.Marshal(hdr)
66+
assert.NoError(t, err)
67+
prop := &peer.Proposal{
68+
Header: hdrBytes,
69+
}
70+
prop.Header = corruptHdr(prop.Header)
71+
propBytes, err := proto.Marshal(prop)
72+
assert.NoError(t, err)
73+
return &peer.SignedProposal{
74+
ProposalBytes: propBytes,
75+
}
76+
}
77+
78+
func createValidSignedProposal(t *testing.T, serializedIdentity []byte) *peer.SignedProposal {
79+
return createSignedProposal(t, serializedIdentity, noopMutator, noopMutator)
80+
}
81+
82+
func createSignedProposalWithInvalidSigHeader(t *testing.T, serializedIdentity []byte) *peer.SignedProposal {
83+
return createSignedProposal(t, serializedIdentity, corruptMutator, noopMutator)
84+
}
85+
86+
func createSignedProposalWithInvalidHeader(t *testing.T, serializedIdentity []byte) *peer.SignedProposal {
87+
return createSignedProposal(t, serializedIdentity, noopMutator, corruptMutator)
88+
}
89+
90+
func TestExpirationCheckFilter(t *testing.T) {
91+
nextEndorser := &mockEndorserServer{}
92+
auth := NewExpirationCheckFilter()
93+
auth.Init(nextEndorser)
94+
95+
// Scenario I: Expired x509 identity
96+
sp := createValidSignedProposal(t, createX509Identity(t, "expiredCert.pem"))
97+
_, err := auth.ProcessProposal(context.Background(), sp)
98+
assert.Equal(t, err.Error(), "identity expired")
99+
assert.False(t, nextEndorser.invoked)
100+
101+
// Scenario II: Not expired x509 identity
102+
sp = createValidSignedProposal(t, createX509Identity(t, "notExpiredCert.pem"))
103+
_, err = auth.ProcessProposal(context.Background(), sp)
104+
assert.NoError(t, err)
105+
assert.True(t, nextEndorser.invoked)
106+
nextEndorser.invoked = false
107+
108+
// Scenario III: Idemix identity
109+
sp = createValidSignedProposal(t, createIdemixIdentity(t))
110+
_, err = auth.ProcessProposal(context.Background(), sp)
111+
assert.NoError(t, err)
112+
assert.True(t, nextEndorser.invoked)
113+
nextEndorser.invoked = false
114+
115+
// Scenario IV: Malformed proposal
116+
sp = createValidSignedProposal(t, createX509Identity(t, "notExpiredCert.pem"))
117+
sp.ProposalBytes = append(sp.ProposalBytes, 0)
118+
_, err = auth.ProcessProposal(context.Background(), sp)
119+
assert.Error(t, err)
120+
assert.Contains(t, err.Error(), "failed parsing proposal")
121+
assert.False(t, nextEndorser.invoked)
122+
123+
// Scenario V: Malformed signature header
124+
sp = createSignedProposalWithInvalidSigHeader(t, createX509Identity(t, "notExpiredCert.pem"))
125+
_, err = auth.ProcessProposal(context.Background(), sp)
126+
assert.Error(t, err)
127+
assert.Contains(t, err.Error(), "failed parsing signature header")
128+
assert.False(t, nextEndorser.invoked)
129+
130+
// Scenario VI: Malformed header
131+
sp = createSignedProposalWithInvalidHeader(t, createX509Identity(t, "notExpiredCert.pem"))
132+
_, err = auth.ProcessProposal(context.Background(), sp)
133+
assert.Error(t, err)
134+
assert.Contains(t, err.Error(), "failed parsing header")
135+
assert.False(t, nextEndorser.invoked)
136+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICGTCCAcCgAwIBAgIRAKLReasLg2oNMbOafRp0a/EwCgYIKoZIzj0EAwIwczEL
3+
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
4+
cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh
5+
Lm9yZzEuZXhhbXBsZS5jb20wHhcNODkxMjE1MDc1NTAwWhcNODkxMjE1MDgwMDAw
6+
WjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN
7+
U2FuIEZyYW5jaXNjbzEfMB0GA1UEAxMWcGVlcjAub3JnMS5leGFtcGxlLmNvbTBZ
8+
MBMGByqGSM49AgEGCCqGSM49AwEHA0IABGRKxsl6MGrNEgyj78c1uVDgR0lqHvuf
9+
jBS/hlMbOqkF9f+oj1Hfr2oAQYMgj6hwiePxzXTRyk+NboqgVgccstujTTBLMA4G
10+
A1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAIEIBuSbFuduz
11+
ktspAE6FAP7r1N5ClHZM1B/fSiRh9BXGMAoGCCqGSM49BAMCA0cAMEQCIFWScCx8
12+
KIAmvO0qN2qPdG8UeeSr10gvdHl7vohRlDMXAiBt1Pks8/McNoUNI1Q5kInsWroH
13+
1pE6XdTNIOsKDKnd5g==
14+
-----END CERTIFICATE-----
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICNjCCAd2gAwIBAgIRAMnf9/dmV9RvCCVw9pZQUfUwCgYIKoZIzj0EAwIwgYEx
3+
CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4g
4+
RnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMQwwCgYDVQQLEwND
5+
T1AxHDAaBgNVBAMTE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTcxMTEyMTM0MTEx
6+
WhcNMjcxMTEwMTM0MTExWjBpMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZv
7+
cm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEMMAoGA1UECxMDQ09QMR8wHQYD
8+
VQQDExZwZWVyMC5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D
9+
AQcDQgAEZ8S4V71OBJpyMIVZdwYdFXAckItrpvSrCf0HQg40WW9XSoOOO76I+Umf
10+
EkmTlIJXP7/AyRRSRU38oI8Ivtu4M6NNMEswDgYDVR0PAQH/BAQDAgeAMAwGA1Ud
11+
EwEB/wQCMAAwKwYDVR0jBCQwIoAginORIhnPEFZUhXm6eWBkm7K7Zc8R4/z7LW4H
12+
ossDlCswCgYIKoZIzj0EAwIDRwAwRAIgVikIUZzgfuFsGLQHWJUVJCU7pDaETkaz
13+
PzFgsCiLxUACICgzJYlW7nvZxP7b6tbeu3t8mrhMXQs956mD4+BoKuNI
14+
-----END CERTIFICATE-----

core/handlers/library/library.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ func (r *HandlerLibrary) DefaultAuth() auth.Filter {
2727
return filter.NewFilter()
2828
}
2929

30+
// ExpirationCheck is an auth filter which blocks requests
31+
// from identities with expired x509 certificates
32+
func (r *HandlerLibrary) ExpirationCheck() auth.Filter {
33+
return filter.NewExpirationCheckFilter()
34+
}
35+
3036
// DefaultDecorator creates a default decorator
3137
// that doesn't do anything with the input, simply
3238
// returns the input as output.

sampleconfig/core.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ peer:
368368
authFilters:
369369
-
370370
name: DefaultAuth
371+
-
372+
name: ExpirationCheck # This filter checks identity x509 certificate expiration
371373
decorators:
372374
-
373375
name: DefaultDecorator

0 commit comments

Comments
 (0)