Skip to content

Commit

Permalink
[FAB-1420] Convert signature policy to provider
Browse files Browse the repository at this point in the history
https://jira.hyperledger.org/browse/FAB-1420

The cuathdsl signature policy type used to be the only policy type
available.  In order to allow new types of policy, a provider framework
is needed.

This changeset introduces the provider framework and converts the
existing cauthdsl based signature policy work to be one of these
providers.

Finally, in response to some changeset feedback, it also introduces the
notion of a SignedData struct which can be common to all of the crypto
implementations (and signficiantly simplifies the interface
definitions).

Change-Id: Ib67ff9f6de0433f73c96e5a4b229f1587a0f0363
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed Jan 2, 2017
1 parent 46f7af0 commit bad7bdc
Show file tree
Hide file tree
Showing 12 changed files with 409 additions and 157 deletions.
45 changes: 9 additions & 36 deletions common/cauthdsl/cauthdsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,14 @@ import (

// CryptoHelper is used to provide a plugin point for different signature validation types
type CryptoHelper interface {
VerifySignature(msg []byte, id []byte, signature []byte) bool
}

// SignaturePolicyEvaluator is useful for a chain Reader to stream blocks as they are created
type SignaturePolicyEvaluator struct {
compiledAuthenticator func([]byte, [][]byte, [][]byte) bool
}

// NewSignaturePolicyEvaluator evaluates a protbuf SignaturePolicy to produce a 'compiled' version which can be invoked in code
func NewSignaturePolicyEvaluator(policy *cb.SignaturePolicyEnvelope, ch CryptoHelper) (*SignaturePolicyEvaluator, error) {
if policy.Version != 0 {
return nil, fmt.Errorf("This evaluator only understands messages of version 0, but version was %d", policy.Version)
}

compiled, err := compile(policy.Policy, policy.Identities, ch)
if err != nil {
return nil, err
}

return &SignaturePolicyEvaluator{
compiledAuthenticator: compiled,
}, nil
VerifySignature(signedData *cb.SignedData) error
}

// compile recursively builds a go evaluatable function corresponding to the policy specified
func compile(policy *cb.SignaturePolicy, identities [][]byte, ch CryptoHelper) (func([]byte, [][]byte, [][]byte) bool, error) {
func compile(policy *cb.SignaturePolicy, identities [][]byte, ch CryptoHelper) (func([]*cb.SignedData) bool, error) {
switch t := policy.Type.(type) {
case *cb.SignaturePolicy_From:
policies := make([]func([]byte, [][]byte, [][]byte) bool, len(t.From.Policies))
policies := make([]func([]*cb.SignedData) bool, len(t.From.Policies))
for i, policy := range t.From.Policies {
compiledPolicy, err := compile(policy, identities, ch)
if err != nil {
Expand All @@ -62,10 +41,10 @@ func compile(policy *cb.SignaturePolicy, identities [][]byte, ch CryptoHelper) (
policies[i] = compiledPolicy

}
return func(msg []byte, ids [][]byte, signatures [][]byte) bool {
return func(signedData []*cb.SignedData) bool {
verified := int32(0)
for _, policy := range policies {
if policy(msg, ids, signatures) {
if policy(signedData) {
verified++
}
}
Expand All @@ -76,21 +55,15 @@ func compile(policy *cb.SignaturePolicy, identities [][]byte, ch CryptoHelper) (
return nil, fmt.Errorf("Identity index out of range, requested %d, but identies length is %d", t.SignedBy, len(identities))
}
signedByID := identities[t.SignedBy]
return func(msg []byte, ids [][]byte, signatures [][]byte) bool {
for i, id := range ids {
if bytes.Equal(id, signedByID) {
return ch.VerifySignature(msg, id, signatures[i])
return func(signedData []*cb.SignedData) bool {
for _, sd := range signedData {
if bytes.Equal(sd.Identity, signedByID) {
return ch.VerifySignature(sd) == nil
}
}
return false
}, nil
default:
return nil, fmt.Errorf("Unknown type: %T:%v", t, t)
}

}

// Authenticate returns nil if the authentication policy is satisfied, or an error indicating why the authentication failed
func (ape *SignaturePolicyEvaluator) Authenticate(msg []byte, ids [][]byte, signatures [][]byte) bool {
return ape.compiledAuthenticator(msg, ids, signatures)
}
65 changes: 41 additions & 24 deletions common/cauthdsl/cauthdsl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cauthdsl

import (
"bytes"
"fmt"
"testing"

"github.com/golang/protobuf/proto"
Expand All @@ -27,71 +28,87 @@ import (
var invalidSignature = []byte("badsigned")
var validSignature = []byte("signed")
var signers = [][]byte{[]byte("signer0"), []byte("signer1")}
var msgs = [][]byte{nil, nil}

type mockCryptoHelper struct {
}

func (mch *mockCryptoHelper) VerifySignature(msg []byte, id []byte, signature []byte) bool {
return bytes.Equal(signature, validSignature)
func (mch *mockCryptoHelper) VerifySignature(sd *cb.SignedData) error {
if !bytes.Equal(sd.Signature, validSignature) {
return fmt.Errorf("Bad signature")
}
return nil
}

func toSignedData(data [][]byte, identities [][]byte, signatures [][]byte) []*cb.SignedData {
signedData := make([]*cb.SignedData, len(data))
for i := range signedData {
signedData[i] = &cb.SignedData{
Data: data[i],
Identity: identities[i],
Signature: signatures[i],
}
}
return signedData
}

func TestSimpleSignature(t *testing.T) {
mch := &mockCryptoHelper{}
policy := Envelope(SignedBy(0), signers)

spe, err := NewSignaturePolicyEvaluator(policy, mch)
spe, err := compile(policy.Policy, policy.Identities, mch)
if err != nil {
t.Fatalf("Could not create a new SignaturePolicyEvaluator using the given policy, crypto-helper: %s", err)
}

if !spe.Authenticate(nil, [][]byte{signers[0]}, [][]byte{validSignature}) {
t.Error("Expected authentication to succeed with valid signatures")
if !spe([]*cb.SignedData{&cb.SignedData{Identity: signers[0], Signature: validSignature}}) {
t.Errorf("Expected authentication to succeed with valid signatures")
}
if spe.Authenticate(nil, [][]byte{signers[0]}, [][]byte{invalidSignature}) {
t.Error("Expected authentication to fail given the invalid signature")
if spe([]*cb.SignedData{&cb.SignedData{Identity: signers[0], Signature: invalidSignature}}) {
t.Errorf("Expected authentication to fail given the invalid signature")
}
if spe.Authenticate(nil, [][]byte{signers[1]}, [][]byte{validSignature}) {
t.Error("Expected authentication to fail because signers[1] is not authorized in the policy, despite his valid signature")
if spe([]*cb.SignedData{&cb.SignedData{Identity: signers[1], Signature: validSignature}}) {
t.Errorf("Expected authentication to fail because signers[1] is not authorized in the policy, despite his valid signature")
}
}

func TestMultipleSignature(t *testing.T) {
mch := &mockCryptoHelper{}
policy := Envelope(And(SignedBy(0), SignedBy(1)), signers)

spe, err := NewSignaturePolicyEvaluator(policy, mch)
spe, err := compile(policy.Policy, policy.Identities, mch)
if err != nil {
t.Fatalf("Could not create a new SignaturePolicyEvaluator using the given policy, crypto-helper: %s", err)
}

if !spe.Authenticate(nil, signers, [][]byte{validSignature, validSignature}) {
t.Error("Expected authentication to succeed with valid signatures")
if !spe(toSignedData(msgs, signers, [][]byte{validSignature, validSignature})) {
t.Errorf("Expected authentication to succeed with valid signatures")
}
if spe.Authenticate(nil, signers, [][]byte{validSignature, invalidSignature}) {
t.Error("Expected authentication to fail given one of two invalid signatures")
if spe(toSignedData(msgs, signers, [][]byte{validSignature, invalidSignature})) {
t.Errorf("Expected authentication to fail given one of two invalid signatures")
}
if spe.Authenticate(nil, [][]byte{signers[0], signers[0]}, [][]byte{validSignature, validSignature}) {
t.Error("Expected authentication to fail because although there were two valid signatures, one was duplicated")
if spe(toSignedData(msgs, [][]byte{signers[0], signers[0]}, [][]byte{validSignature, validSignature})) {
t.Errorf("Expected authentication to fail because although there were two valid signatures, one was duplicated")
}
}

func TestComplexNestedSignature(t *testing.T) {
mch := &mockCryptoHelper{}
policy := Envelope(And(Or(And(SignedBy(0), SignedBy(1)), And(SignedBy(0), SignedBy(0))), SignedBy(0)), signers)

spe, err := NewSignaturePolicyEvaluator(policy, mch)
spe, err := compile(policy.Policy, policy.Identities, mch)
if err != nil {
t.Fatalf("Could not create a new SignaturePolicyEvaluator using the given policy, crypto-helper: %s", err)
}

if !spe.Authenticate(nil, signers, [][]byte{validSignature, validSignature}) {
t.Error("Expected authentication to succeed with valid signatures")
if !spe(toSignedData(msgs, signers, [][]byte{validSignature, validSignature})) {
t.Errorf("Expected authentication to succeed with valid signatures")
}
if spe.Authenticate(nil, signers, [][]byte{invalidSignature, validSignature}) {
t.Error("Expected authentication failure as only the signature of signer[1] was valid")
if spe(toSignedData(msgs, signers, [][]byte{invalidSignature, validSignature})) {
t.Errorf("Expected authentication failure as only the signature of signer[1] was valid")
}
if !spe.Authenticate(nil, [][]byte{signers[0], signers[0]}, [][]byte{validSignature, validSignature}) {
t.Error("Expected authentication to succeed because the rule allows duplicated signatures for signer[0]")
if !spe(toSignedData(msgs, [][]byte{signers[0], signers[0]}, [][]byte{validSignature, validSignature})) {
t.Errorf("Expected authentication to succeed because the rule allows duplicated signatures for signer[0]")
}
}

Expand All @@ -102,7 +119,7 @@ func TestNegatively(t *testing.T) {
b, _ := proto.Marshal(rpolicy)
policy := &cb.SignaturePolicyEnvelope{}
_ = proto.Unmarshal(b, policy)
_, err := NewSignaturePolicyEvaluator(policy, mch)
_, err := compile(policy.Policy, policy.Identities, mch)
if err == nil {
t.Fatal("Should have errored compiling because the Type field was nil")
}
Expand Down
77 changes: 77 additions & 0 deletions common/cauthdsl/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright IBM Corp. 2016 All Rights Reserved.
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 cauthdsl

import (
"errors"
"fmt"

"github.com/hyperledger/fabric/common/policies"
cb "github.com/hyperledger/fabric/protos/common"

"github.com/golang/protobuf/proto"
)

type provider struct {
helper CryptoHelper
}

// NewProviderImpl provides a policy generator for cauthdsl type policies
func NewPolicyProvider(helper CryptoHelper) policies.Provider {
return &provider{
helper: helper,
}
}

// NewPolicy creates a new policy based on the policy bytes
func (pr *provider) NewPolicy(data []byte) (policies.Policy, error) {
sigPolicy := &cb.SignaturePolicyEnvelope{}
if err := proto.Unmarshal(data, sigPolicy); err != nil {
return nil, fmt.Errorf("Error unmarshaling to SignaturePolicy: %s", err)
}

if sigPolicy.Version != 0 {
return nil, fmt.Errorf("This evaluator only understands messages of version 0, but version was %d", sigPolicy.Version)
}

compiled, err := compile(sigPolicy.Policy, sigPolicy.Identities, pr.helper)
if err != nil {
return nil, err
}

return &policy{
evaluator: compiled,
}, nil

}

type policy struct {
evaluator func([]*cb.SignedData) bool
}

// Evaluate takes a set of SignedData and evaluates whether this set of signatures satisfies the policy
func (p *policy) Evaluate(signatureSet []*cb.SignedData) error {
if p == nil {
return fmt.Errorf("No such policy")
}

ok := p.evaluator(signatureSet)
if !ok {
return errors.New("Failed to authenticate policy")
}
return nil
}
35 changes: 17 additions & 18 deletions common/policies/policy_test.go → common/cauthdsl/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package policies
package cauthdsl

import (
"fmt"
"testing"

"github.com/hyperledger/fabric/common/cauthdsl"
"github.com/hyperledger/fabric/common/policies"
cb "github.com/hyperledger/fabric/protos/common"

"github.com/golang/protobuf/proto"
)

type mockCryptoHelper struct{}

func (mch *mockCryptoHelper) VerifySignature(msg []byte, identity []byte, signature []byte) bool {
return true
}

var acceptAllPolicy []byte
var rejectAllPolicy []byte

Expand All @@ -52,9 +46,9 @@ func marshalOrPanic(msg proto.Message) []byte {
func makePolicySource(policyResult bool) []byte {
var policyData *cb.SignaturePolicyEnvelope
if policyResult {
policyData = cauthdsl.AcceptAllPolicy
policyData = AcceptAllPolicy
} else {
policyData = cauthdsl.RejectAllPolicy
policyData = RejectAllPolicy
}
marshaledPolicy := marshalOrPanic(&cb.Policy{
Type: int32(cb.Policy_SIGNATURE),
Expand All @@ -63,7 +57,7 @@ func makePolicySource(policyResult bool) []byte {
return marshaledPolicy
}

func addPolicy(manager *ManagerImpl, id string, policy []byte) {
func addPolicy(manager *policies.ManagerImpl, id string, policy []byte) {
manager.BeginConfig()
err := manager.ProposeConfig(&cb.ConfigurationItem{
Type: cb.ConfigurationItem_Policy,
Expand All @@ -76,42 +70,47 @@ func addPolicy(manager *ManagerImpl, id string, policy []byte) {
manager.CommitConfig()
}

func providerMap() map[int32]policies.Provider {
r := make(map[int32]policies.Provider)
r[int32(cb.Policy_SIGNATURE)] = NewPolicyProvider(&mockCryptoHelper{})
return r
}

func TestAccept(t *testing.T) {
policyID := "policyID"
m := NewManagerImpl(&mockCryptoHelper{})
t.Logf("%p %x %v", acceptAllPolicy, acceptAllPolicy, acceptAllPolicy)
m := policies.NewManagerImpl(providerMap())
addPolicy(m, policyID, acceptAllPolicy)
policy, ok := m.GetPolicy(policyID)
if !ok {
t.Error("Should have found policy which was just added, but did not")
}
err := policy.Evaluate(nil, nil, nil, nil)
err := policy.Evaluate([]*cb.SignedData{})
if err != nil {
t.Fatalf("Should not have errored evaluating an acceptAll policy: %s", err)
}
}

func TestReject(t *testing.T) {
policyID := "policyID"
m := NewManagerImpl(&mockCryptoHelper{})
m := policies.NewManagerImpl(providerMap())
addPolicy(m, policyID, rejectAllPolicy)
policy, ok := m.GetPolicy(policyID)
if !ok {
t.Error("Should have found policy which was just added, but did not")
}
err := policy.Evaluate(nil, nil, nil, nil)
err := policy.Evaluate([]*cb.SignedData{})
if err == nil {
t.Fatal("Should have errored evaluating the rejectAll policy")
}
}

func TestRejectOnUnknown(t *testing.T) {
m := NewManagerImpl(&mockCryptoHelper{})
m := policies.NewManagerImpl(providerMap())
policy, ok := m.GetPolicy("FakePolicyID")
if ok {
t.Error("Should not have found policy which was never added, but did")
}
err := policy.Evaluate(nil, nil, nil, nil)
err := policy.Evaluate([]*cb.SignedData{})
if err == nil {
t.Fatal("Should have errored evaluating the default policy")
}
Expand Down
Loading

0 comments on commit bad7bdc

Please sign in to comment.