diff --git a/core/network/fabric-interop-cc/contracts/interop/decoders.go b/core/network/fabric-interop-cc/contracts/interop/decoders.go index e8532ca1f4..e8b5fee0ca 100644 --- a/core/network/fabric-interop-cc/contracts/interop/decoders.go +++ b/core/network/fabric-interop-cc/contracts/interop/decoders.go @@ -12,11 +12,14 @@ package main import ( + "encoding/base64" "encoding/json" "strings" + "fmt" "github.com/hyperledger-labs/weaver-dlt-interoperability/common/protos-go/common" "github.com/hyperledger-labs/weaver-dlt-interoperability/common/protos-go/identity" + protoV2 "google.golang.org/protobuf/proto" ) func decodeMembership(jsonBytes []byte) (*common.Membership, error) { @@ -30,13 +33,15 @@ func decodeMembership(jsonBytes []byte) (*common.Membership, error) { return &decodeObj, nil } -func decodeCounterAttestedMembership(jsonBytes []byte) (*identity.CounterAttestedMembership, error) { +func decodeCounterAttestedMembership(protoBytesBase64 string) (*identity.CounterAttestedMembership, error) { var decodeObj identity.CounterAttestedMembership - dec := json.NewDecoder(strings.NewReader(string(jsonBytes))) - dec.DisallowUnknownFields() - err := dec.Decode(&decodeObj) + protoBytes, err := base64.StdEncoding.DecodeString(protoBytesBase64) if err != nil { - return nil, err + return nil, fmt.Errorf("Counter attested membership could not be decoded from base64: %s", err.Error()) + } + err = protoV2.Unmarshal(protoBytes, &decodeObj) + if err != nil { + return nil, fmt.Errorf("Unable to unmarshal counter attested membership: %s", err.Error()) } return &decodeObj, nil } diff --git a/core/network/fabric-interop-cc/contracts/interop/membership.go b/core/network/fabric-interop-cc/contracts/interop/membership.go index 94e7422504..5770406ef2 100644 --- a/core/network/fabric-interop-cc/contracts/interop/membership.go +++ b/core/network/fabric-interop-cc/contracts/interop/membership.go @@ -157,7 +157,7 @@ func (s *SmartContract) UpdateLocalMembership(ctx contractapi.TransactionContext } membershipLocalKey, err := ctx.GetStub().CreateCompositeKey(membershipObjectType, []string{membershipLocalSecurityDomain}) - _, getErr := s.GetMembershipBySecurityDomain(ctx, membership.SecurityDomain) + _, getErr := s.GetMembershipBySecurityDomain(ctx, membershipLocalSecurityDomain) if getErr != nil { return getErr } @@ -176,14 +176,20 @@ func (s *SmartContract) UpdateLocalMembership(ctx contractapi.TransactionContext } -// CreateMembershipIIN cc is used to store a Membership in the ledger -// TODO: Replace 'CreateMembership' with this function after updating the Fabric CLI. -func (s *SmartContract) CreateMembershipIIN(ctx contractapi.TransactionContextInterface, counterAttestedMembershipJSON string) error { +// CreateMembership cc is used to store a Membership in the ledger +// TODO: Remove call to 'createMembership' after creating Corda IIN Agents. +func (s *SmartContract) CreateMembership(ctx contractapi.TransactionContextInterface, counterAttestedMembershipSerialized string) error { // Check if the caller has IIN agent privileges if isIINAgent, err := isClientIINAgent(ctx); err != nil { return fmt.Errorf("IIN Agent client check error: %s", err) } else if !isIINAgent { - return fmt.Errorf("Caller not an IIN Agent; access denied") + // Check if the caller has network admin privileges + if isAdmin, err := isClientNetworkAdmin(ctx); err != nil { + return fmt.Errorf("Admin client check error: %s", err) + } else if !isAdmin { + return fmt.Errorf("Caller neither a network admin nor an IIN Agent; access denied") + } + return s.createMembership(ctx, counterAttestedMembershipSerialized) // HACK to handle unattested memberships (for Corda) for backward compatibility } // Check if caller is one of the authorized IIN agents @@ -205,7 +211,7 @@ func (s *SmartContract) CreateMembershipIIN(ctx contractapi.TransactionContextIn return fmt.Errorf("Caller with identity %+v is not a designated IIN Agent: %+v", callerId, err) } - counterAttestedMembership, err := decodeCounterAttestedMembership([]byte(counterAttestedMembershipJSON)) + counterAttestedMembership, err := decodeCounterAttestedMembership(counterAttestedMembershipSerialized) if err != nil { return fmt.Errorf("Counter Attested Membership Unmarshal error: %s", err) } @@ -304,16 +310,16 @@ func (s *SmartContract) CreateMembershipIIN(ctx contractapi.TransactionContextIn return fmt.Errorf("Membership already exists for membership id: %s. Use 'UpdateMembership' to update.", foreignMembership.SecurityDomain) } - membershipBytes, err := json.Marshal(attestedMembershipSet.Membership) + membershipBytes, err := json.Marshal(foreignMembership) if err != nil { return fmt.Errorf("Marshal error: %s", err) } return ctx.GetStub().PutState(membershipKey, membershipBytes) } -// CreateMembership cc is used to store a Membership in the ledger -// TODO: Replace this function with 'CreateMembership' after updating the Fabric CLI. Retaining this temporarily for backward compatibility. -func (s *SmartContract) CreateMembership(ctx contractapi.TransactionContextInterface, membershipJSON string) error { +// createMembership is used by a network admin to store a Membership in the ledger with an unattested membership +// TODO: Remove this function after creating Corda IIN Agents. Retaining this temporarily for backward compatibility. +func (s *SmartContract) createMembership(ctx contractapi.TransactionContextInterface, membershipJSON string) error { membership, err := decodeMembership([]byte(membershipJSON)) if err != nil { return fmt.Errorf("Unmarshal error: %s", err) @@ -340,14 +346,20 @@ func (s *SmartContract) CreateMembership(ctx contractapi.TransactionContextInter return ctx.GetStub().PutState(membershipKey, membershipBytes) } -// UpdateMembershipIIN cc is used to update an existing Membership in the ledger -// TODO: Replace 'UpdateMembership' with this function after updating the Fabric CLI. -func (s *SmartContract) UpdateMembershipIIN(ctx contractapi.TransactionContextInterface, counterAttestedMembershipJSON string) error { +// UpdateMembership cc is used to update an existing Membership in the ledger +// TODO: Remove call to 'updateMembership' after creating Corda IIN Agents. +func (s *SmartContract) UpdateMembership(ctx contractapi.TransactionContextInterface, counterAttestedMembershipSerialized string) error { // Check if the caller has IIN agent privileges if isIINAgent, err := isClientIINAgent(ctx); err != nil { return fmt.Errorf("IIN Agent client check error: %s", err) } else if !isIINAgent { - return fmt.Errorf("Caller not an IIN Agent; access denied") + // Check if the caller has network admin privileges + if isAdmin, err := isClientNetworkAdmin(ctx); err != nil { + return fmt.Errorf("Admin client check error: %s", err) + } else if !isAdmin { + return fmt.Errorf("Caller neither a network admin nor an IIN Agent; access denied") + } + return s.updateMembership(ctx, counterAttestedMembershipSerialized) // HACK to handle unattested memberships (for Corda) for backward compatibility } // Check if caller is one of the authorized IIN agents @@ -369,7 +381,7 @@ func (s *SmartContract) UpdateMembershipIIN(ctx contractapi.TransactionContextIn return fmt.Errorf("Caller with identity %+v is not a designated IIN Agent: %+v", callerId, err) } - counterAttestedMembership, err := decodeCounterAttestedMembership([]byte(counterAttestedMembershipJSON)) + counterAttestedMembership, err := decodeCounterAttestedMembership(counterAttestedMembershipSerialized) if err != nil { return fmt.Errorf("Counter Attested Membership Unmarshal error: %s", err) } @@ -465,7 +477,7 @@ func (s *SmartContract) UpdateMembershipIIN(ctx contractapi.TransactionContextIn return getErr } - membershipBytes, err := json.Marshal(attestedMembershipSet.Membership) + membershipBytes, err := json.Marshal(foreignMembership) if err != nil { return fmt.Errorf("Marshal error: %s", err) } @@ -473,9 +485,9 @@ func (s *SmartContract) UpdateMembershipIIN(ctx contractapi.TransactionContextIn } -// UpdateMembership cc is used to update an existing Membership in the ledger -// TODO: Replace this function with 'UpdateMembership' after updating the Fabric CLI. Retaining this temporarily for backward compatibility. -func (s *SmartContract) UpdateMembership(ctx contractapi.TransactionContextInterface, membershipJSON string) error { +// updateMembership is used by a network admin to update an existing Membership in the ledger with an unattested membership +// TODO: Remove this function after creating Corda IIN Agents. Retaining this temporarily for backward compatibility. +func (s *SmartContract) updateMembership(ctx contractapi.TransactionContextInterface, membershipJSON string) error { membership, err := decodeMembership([]byte(membershipJSON)) if err != nil { return fmt.Errorf("Unmarshal error: %s", err) @@ -525,9 +537,8 @@ func (s *SmartContract) DeleteLocalMembership(ctx contractapi.TransactionContext return nil } -// DeleteMembershipIIN cc is used to delete an existing Membership in the ledger -// TODO: Replace 'DeleteMembership' with this function after updating the Fabric CLI. -func (s *SmartContract) DeleteMembershipIIN(ctx contractapi.TransactionContextInterface, membershipID string) error { +// DeleteMembership cc is used to delete an existing Membership in the ledger +func (s *SmartContract) DeleteMembership(ctx contractapi.TransactionContextInterface, membershipID string) error { // Check if the caller has IIN agent privileges if isIINAgent, err := isClientIINAgent(ctx); err != nil { return fmt.Errorf("IIN Agent client check error: %s", err) @@ -551,25 +562,6 @@ func (s *SmartContract) DeleteMembershipIIN(ctx contractapi.TransactionContextIn return nil } -// DeleteMembership cc is used to delete an existing Membership in the ledger -// TODO: Replace this function with 'DeleteMembership' after updating the Fabric CLI. Retaining this temporarily for backward compatibility. -func (s *SmartContract) DeleteMembership(ctx contractapi.TransactionContextInterface, membershipID string) error { - membershipKey, err := ctx.GetStub().CreateCompositeKey(membershipObjectType, []string{membershipID}) - bytes, err := ctx.GetStub().GetState(membershipKey) - if err != nil { - return err - } - if bytes == nil { - return fmt.Errorf("Membership with id: %s does not exist", membershipID) - } - err = ctx.GetStub().DelState(membershipKey) - if err != nil { - return fmt.Errorf("failed to delete asset %s: %v", membershipKey, err) - } - - return nil -} - // GetMembershipBySecurityDomain cc gets the Membership for the provided id func (s *SmartContract) GetMembershipBySecurityDomain(ctx contractapi.TransactionContextInterface, securityDomain string) (string, error) { membershipKey, err := ctx.GetStub().CreateCompositeKey(membershipObjectType, []string{securityDomain}) diff --git a/core/network/fabric-interop-cc/contracts/interop/membership_test.go b/core/network/fabric-interop-cc/contracts/interop/membership_test.go index fd74aa9644..112d92cb7d 100644 --- a/core/network/fabric-interop-cc/contracts/interop/membership_test.go +++ b/core/network/fabric-interop-cc/contracts/interop/membership_test.go @@ -259,7 +259,7 @@ func TestUpdateLocalMembership(t *testing.T) { clientIdentity.GetAttributeValueCalls(setClientAdmin) ctx.GetClientIdentityReturns(clientIdentity) err = interopcc.UpdateLocalMembership(ctx, string(membershipBytes)) - require.EqualError(t, err, fmt.Sprintf("Membership with id: %s does not exist", membershipAsset.SecurityDomain)) + require.EqualError(t, err, fmt.Sprintf("Membership with id: %s does not exist", membershipLocalSecurityDomain)) // Valid cert chain member := &member1 @@ -267,7 +267,7 @@ func TestUpdateLocalMembership(t *testing.T) { membershipBytes, err = json.Marshal(&membershipAsset) require.NoError(t, err) err = interopcc.UpdateLocalMembership(ctx, string(membershipBytes)) - require.EqualError(t, err, fmt.Sprintf("Membership with id: %s does not exist", membershipAsset.SecurityDomain)) + require.EqualError(t, err, fmt.Sprintf("Membership with id: %s does not exist", membershipLocalSecurityDomain)) // Membership already exists update the Membership chaincodeStub.GetStateReturns(membershipBytes, nil) @@ -294,7 +294,7 @@ func TestUpdateLocalMembership(t *testing.T) { } // TODO: Remove later. Keeping for backward compatibility. -func TestCreateMembership(t *testing.T) { +func TestCreateMembershipUnattested(t *testing.T) { ctx, chaincodeStub := wtest.PrepMockStub() interopcc := SmartContract{} @@ -306,6 +306,14 @@ func TestCreateMembership(t *testing.T) { membershipBytes, err := json.Marshal(&membershipAsset) require.NoError(t, err) + + // Case when caller is not an admin + err = interopcc.CreateMembership(ctx, string(membershipBytes)) + require.EqualError(t, err, "Caller neither a network admin nor an IIN Agent; access denied") + // Case when no Membership is found + clientIdentity := &mocks.ClientIdentity{} + clientIdentity.GetAttributeValueCalls(setClientAdmin) + ctx.GetClientIdentityReturns(clientIdentity) err = interopcc.CreateMembership(ctx, string(membershipBytes)) require.NoError(t, err) // Invalid Input check @@ -317,8 +325,7 @@ func TestCreateMembership(t *testing.T) { require.EqualError(t, err, fmt.Sprintf("Membership already exists for membership id: %s. Use 'UpdateMembership' to update.", membershipAsset.SecurityDomain)) } -// TODO: Rename later. -func TestCreateMembershipIIN(t *testing.T) { +func TestCreateMembership(t *testing.T) { ctx, chaincodeStub := wtest.PrepMockStub() interopcc := SmartContract{} @@ -458,16 +465,17 @@ func TestCreateMembershipIIN(t *testing.T) { } // Record membership info: should fail because the caller is not an IIN Agent - counterAttestedMembershipBytes, err := json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err := protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) - require.EqualError(t, err, "Caller not an IIN Agent; access denied") + counterAttestedMembershipBytes := base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) + require.EqualError(t, err, "Caller neither a network admin nor an IIN Agent; access denied") // Record membership info: should fail because the local IIN Agent is not registered clientIdentity := &mocks.ClientIdentity{} clientIdentity.GetAttributeValueCalls(setClientIINAgent) ctx.GetClientIdentityReturns(clientIdentity) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) require.Error(t, err) // Record local membership info @@ -483,13 +491,13 @@ func TestCreateMembershipIIN(t *testing.T) { chaincodeStub.GetStateReturnsOnCall(3, nil, nil) clientIdentity.GetAttributeValueCalls(setClientIINAgent) ctx.GetClientIdentityReturns(clientIdentity) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) require.NoError(t, err) // Record membership info again: should fail because membership has already been recorded against this security domain chaincodeStub.GetStateReturnsOnCall(4, localMembershipBytes, nil) chaincodeStub.GetStateReturnsOnCall(5, []byte{}, nil) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, fmt.Sprintf("Membership already exists for membership id: %s. Use 'UpdateMembership' to update.", membershipAsset.SecurityDomain)) // One of the local signatures is invalid: should fail @@ -498,11 +506,12 @@ func TestCreateMembershipIIN(t *testing.T) { signatureLocal2, err = ecdsa.SignASN1(randomLocal2, keyLocal2, hashedLocal2) require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(6, localMembershipBytes, nil) chaincodeStub.GetStateReturnsOnCall(7, nil, nil) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, "Unable to Validate Signature: Signature Verification failed. ECDSA VERIFY") // One of the foreign signatures is invalid: should fail @@ -527,10 +536,11 @@ func TestCreateMembershipIIN(t *testing.T) { require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) counterAttestedMembership.AttestedMembershipSet = attestedMembershipSetBytes - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(7, localMembershipBytes, nil) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, "Unable to Validate Signature: Signature Verification failed. ECDSA VERIFY") // One of the foreign attestations has an invalid nonce: should fail @@ -556,10 +566,11 @@ func TestCreateMembershipIIN(t *testing.T) { require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) counterAttestedMembership.AttestedMembershipSet = attestedMembershipSetBytes - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(8, localMembershipBytes, nil) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, fmt.Sprintf("Mismatched nonces across two attestations: %s, %s", nonce, attestation1.Nonce)) // Foreign membership has an invalid cert chain: should fail @@ -599,10 +610,11 @@ func TestCreateMembershipIIN(t *testing.T) { require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) counterAttestedMembership.AttestedMembershipSet = attestedMembershipSetBytes - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(9, localMembershipBytes, nil) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) require.Error(t, err) // Foreign attestation has invalid security domain: should fail @@ -642,15 +654,16 @@ func TestCreateMembershipIIN(t *testing.T) { require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) counterAttestedMembership.AttestedMembershipSet = attestedMembershipSetBytes - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(10, localMembershipBytes, nil) - err = interopcc.CreateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.CreateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, fmt.Sprintf("Foreign agent security domain %s does not match attested membership security domain invalid", securityDomainId)) } // TODO: Remove later. Keeping for backward compatibility. -func TestUpdateMembership(t *testing.T) { +func TestUpdateMembershipUnattested(t *testing.T) { ctx, chaincodeStub := wtest.PrepMockStub() interopcc := SmartContract{} @@ -662,6 +675,14 @@ func TestUpdateMembership(t *testing.T) { membershipBytes, err := json.Marshal(&membershipAsset) require.NoError(t, err) + + // Case when caller is not an admin + err = interopcc.UpdateMembership(ctx, string(membershipBytes)) + require.EqualError(t, err, "Caller neither a network admin nor an IIN Agent; access denied") + // Case when no Membership is found + clientIdentity := &mocks.ClientIdentity{} + clientIdentity.GetAttributeValueCalls(setClientAdmin) + ctx.GetClientIdentityReturns(clientIdentity) err = interopcc.UpdateMembership(ctx, string(membershipBytes)) require.EqualError(t, err, fmt.Sprintf("Membership with id: %s does not exist", membershipAsset.SecurityDomain)) // Invalid Input check @@ -674,8 +695,7 @@ func TestUpdateMembership(t *testing.T) { require.NoError(t, err) } -// TODO: Rename later. -func TestUpdateMembershipIIN(t *testing.T) { +func TestUpdateMembership(t *testing.T) { ctx, chaincodeStub := wtest.PrepMockStub() interopcc := SmartContract{} @@ -815,16 +835,18 @@ func TestUpdateMembershipIIN(t *testing.T) { } // Record membership info: should fail because the caller is not an IIN Agent - counterAttestedMembershipBytes, err := json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err := protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) - require.EqualError(t, err, "Caller not an IIN Agent; access denied") + counterAttestedMembershipBytes := base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) + require.NoError(t, err) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) + require.EqualError(t, err, "Caller neither a network admin nor an IIN Agent; access denied") // Record membership info: should fail because the local IIN Agent is not registered clientIdentity := &mocks.ClientIdentity{} clientIdentity.GetAttributeValueCalls(setClientIINAgent) ctx.GetClientIdentityReturns(clientIdentity) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) require.Error(t, err) // Record local membership info @@ -840,13 +862,13 @@ func TestUpdateMembershipIIN(t *testing.T) { chaincodeStub.GetStateReturnsOnCall(3, nil, nil) clientIdentity.GetAttributeValueCalls(setClientIINAgent) ctx.GetClientIdentityReturns(clientIdentity) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, fmt.Sprintf("Membership with id: %s does not exist", securityDomainId)) // Record membership info again: should succeed now chaincodeStub.GetStateReturnsOnCall(4, localMembershipBytes, nil) chaincodeStub.GetStateReturnsOnCall(5, []byte{}, nil) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) require.NoError(t, err) // One of the local signatures is invalid: should fail @@ -855,11 +877,12 @@ func TestUpdateMembershipIIN(t *testing.T) { signatureLocal2, err = ecdsa.SignASN1(randomLocal2, keyLocal2, hashedLocal2) require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(6, localMembershipBytes, nil) chaincodeStub.GetStateReturnsOnCall(7, []byte{}, nil) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, "Unable to Validate Signature: Signature Verification failed. ECDSA VERIFY") // One of the foreign signatures is invalid: should fail @@ -884,10 +907,11 @@ func TestUpdateMembershipIIN(t *testing.T) { require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) counterAttestedMembership.AttestedMembershipSet = attestedMembershipSetBytes - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(7, localMembershipBytes, nil) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, "Unable to Validate Signature: Signature Verification failed. ECDSA VERIFY") // One of the foreign attestations has an invalid nonce: should fail @@ -913,10 +937,11 @@ func TestUpdateMembershipIIN(t *testing.T) { require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) counterAttestedMembership.AttestedMembershipSet = attestedMembershipSetBytes - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(8, localMembershipBytes, nil) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, fmt.Sprintf("Mismatched nonces across two attestations: %s, %s", nonce, attestation1.Nonce)) // Foreign membership has an invalid cert chain: should fail @@ -956,10 +981,11 @@ func TestUpdateMembershipIIN(t *testing.T) { require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) counterAttestedMembership.AttestedMembershipSet = attestedMembershipSetBytes - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(9, localMembershipBytes, nil) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) require.Error(t, err) // Foreign attestation has invalid security domain: should fail @@ -999,10 +1025,11 @@ func TestUpdateMembershipIIN(t *testing.T) { require.NoError(t, err) attestationLocal2.Signature = base64.StdEncoding.EncodeToString(signatureLocal2) counterAttestedMembership.AttestedMembershipSet = attestedMembershipSetBytes - counterAttestedMembershipBytes, err = json.Marshal(&counterAttestedMembership) + counterAttestedMembershipBytesPlain, err = protoV2.Marshal(&counterAttestedMembership) require.NoError(t, err) + counterAttestedMembershipBytes = base64.StdEncoding.EncodeToString(counterAttestedMembershipBytesPlain) chaincodeStub.GetStateReturnsOnCall(10, localMembershipBytes, nil) - err = interopcc.UpdateMembershipIIN(ctx, string(counterAttestedMembershipBytes)) + err = interopcc.UpdateMembership(ctx, string(counterAttestedMembershipBytes)) require.EqualError(t, err, fmt.Sprintf("Foreign agent security domain %s does not match attested membership security domain invalid", securityDomainId)) } @@ -1037,28 +1064,8 @@ func TestDeleteMembership(t *testing.T) { ctx, chaincodeStub := wtest.PrepMockStub() interopcc := SmartContract{} - // Case when a Membership exists - chaincodeStub.GetStateReturns([]byte{}, nil) - err := interopcc.DeleteMembership(ctx, securityDomainId) - require.NoError(t, err) - - // Case when no Membership is found - chaincodeStub.GetStateReturns(nil, nil) - err = interopcc.DeleteMembership(ctx, securityDomainId) - require.EqualError(t, err, fmt.Sprintf("Membership with id: %s does not exist", securityDomainId)) - - // Handle GetState Error - chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset")) - err = interopcc.DeleteMembership(ctx, securityDomainId) - require.EqualError(t, err, fmt.Sprintf("unable to retrieve asset")) -} - -func TestDeleteMembershipIIN(t *testing.T) { - ctx, chaincodeStub := wtest.PrepMockStub() - interopcc := SmartContract{} - // Case when caller is not an IIN Agent - err := interopcc.DeleteMembershipIIN(ctx, securityDomainId) + err := interopcc.DeleteMembership(ctx, securityDomainId) require.EqualError(t, err, "Caller not an IIN Agent; access denied") // Case when a Membership exists @@ -1066,17 +1073,17 @@ func TestDeleteMembershipIIN(t *testing.T) { clientIdentity.GetAttributeValueCalls(setClientIINAgent) ctx.GetClientIdentityReturns(clientIdentity) chaincodeStub.GetStateReturns([]byte{}, nil) - err = interopcc.DeleteMembershipIIN(ctx, securityDomainId) + err = interopcc.DeleteMembership(ctx, securityDomainId) require.NoError(t, err) // Case when no Membership is found chaincodeStub.GetStateReturns(nil, nil) - err = interopcc.DeleteMembershipIIN(ctx, securityDomainId) + err = interopcc.DeleteMembership(ctx, securityDomainId) require.EqualError(t, err, fmt.Sprintf("Membership with id: %s does not exist", securityDomainId)) // Handle GetState Error chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset")) - err = interopcc.DeleteMembershipIIN(ctx, securityDomainId) + err = interopcc.DeleteMembership(ctx, securityDomainId) require.EqualError(t, err, fmt.Sprintf("unable to retrieve asset")) } diff --git a/samples/fabric/fabric-cli/.gitignore b/samples/fabric/fabric-cli/.gitignore index de9b193dc4..797eaadb30 100644 --- a/samples/fabric/fabric-cli/.gitignore +++ b/samples/fabric/fabric-cli/.gitignore @@ -18,3 +18,7 @@ src/wallet-network1 src/wallet-network2 chaincode.json remote-network-config.json +admin.id +networkadmin.id +iinagent.id +*.proto.serialized diff --git a/samples/fabric/fabric-cli/src/commands/configure/all.ts b/samples/fabric/fabric-cli/src/commands/configure/all.ts index 65fe891d1c..b5754eee56 100644 --- a/samples/fabric/fabric-cli/src/commands/configure/all.ts +++ b/samples/fabric/fabric-cli/src/commands/configure/all.ts @@ -7,6 +7,7 @@ import { GluegunCommand } from 'gluegun' import * as path from 'path' import { commandHelp, addData, getNetworkConfig } from '../../helpers/helpers' +import { enrollAndRecordWalletIdentity } from '../../helpers/fabric-functions' import { generateMembership, generateAccessControl, @@ -52,8 +53,19 @@ const command: GluegunCommand = { logger.level = 'debug' logger.debug('Debugging is enabled') } + // for each network, generate network admin identity and IIN Agent identity (there's only one org per network) + const networkAdminUser = 'networkadmin' + const iinAgentUser = 'iinagent' + for (const network of array) { + // Create a network admin + print.info(`Creating network admin wallet identity for network: ${network}`) + await enrollAndRecordWalletIdentity(networkAdminUser, null, network, true, false) + // Create an IIN Agent + print.info(`Creating IIN Agent wallet identity for network: ${network}`) + await enrollAndRecordWalletIdentity(iinAgentUser, null, network, false, true) + } // for each network it - // 1. Generate network configs (membership, access control and verification policy) + // 1. Generate network configs (membership, access control, and verification policy) // 2. Add default data // 3. Loads configs from other networks in the credentials folder for (const network of array) { @@ -67,9 +79,10 @@ const command: GluegunCommand = { ) return } + const username = currusername || `user1` print.info(`Generating membership for network: ${network}`) - // 1. Generate network configs (membership, access control and verification policy) + // 1. Generate network configs (membership, access control, and verification policy) await generateMembership( process.env.DEFAULT_CHANNEL ? process.env.DEFAULT_CHANNEL : 'mychannel', process.env.DEFAULT_CHAINCODE diff --git a/samples/fabric/fabric-cli/src/commands/configure/create/all.ts b/samples/fabric/fabric-cli/src/commands/configure/create/all.ts index 53fb2b41e0..e02b3562ea 100644 --- a/samples/fabric/fabric-cli/src/commands/configure/create/all.ts +++ b/samples/fabric/fabric-cli/src/commands/configure/create/all.ts @@ -6,9 +6,11 @@ import { GluegunCommand } from 'gluegun' import { commandHelp, getNetworkConfig } from '../../../helpers/helpers' +import { getCredentialPath, enrollAndRecordWalletIdentity } from '../../../helpers/fabric-functions' import logger from '../../../helpers/logger' import * as path from 'path' import * as dotenv from 'dotenv' +import * as fs from 'fs' dotenv.config({ path: path.resolve(__dirname, '../../.env') }) import { @@ -65,6 +67,20 @@ const command: GluegunCommand = { return } + // Create wallet credentials + const credentialFolderPath = getCredentialPath() + const networkNames = fs + .readdirSync(credentialFolderPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .filter(item => item.name.startsWith('network')) // HACK until we add IIN Agents for Corda networks + .map(item => item.name) + for (const networkName of networkNames) { + print.info(`Creating network admin wallet identity for network: ${networkName}`) + await enrollAndRecordWalletIdentity('networkadmin', null, networkName, true, false) // Create a network admin + print.info(`Creating IIN Agent wallet identity for network ${networkName}`) + await enrollAndRecordWalletIdentity('iinagent', null, networkName, false, true) // Create an IIN Agent + } + // Membership logger.info(`Generating membership for ${options['local-network']}`) await generateMembership( diff --git a/samples/fabric/fabric-cli/src/commands/configure/network.ts b/samples/fabric/fabric-cli/src/commands/configure/network.ts index d31a79aca4..c560d56401 100644 --- a/samples/fabric/fabric-cli/src/commands/configure/network.ts +++ b/samples/fabric/fabric-cli/src/commands/configure/network.ts @@ -6,10 +6,12 @@ import { GluegunCommand } from 'gluegun' import { commandHelp } from '../../helpers/helpers' +import { getCredentialPath, enrollAndRecordWalletIdentity } from '../../helpers/fabric-functions' import { configureNetwork } from '../../helpers/interop-setup/configure-network' import logger from '../../helpers/logger' import * as path from 'path' import * as dotenv from 'dotenv' +import * as fs from 'fs' dotenv.config({ path: path.resolve(__dirname, '../../.env') }) const command: GluegunCommand = { @@ -47,6 +49,21 @@ const command: GluegunCommand = { logger.level = 'debug' logger.debug('Debugging is enabled') } + + // Create wallet credentials + const credentialFolderPath = getCredentialPath() + const networkNames = fs + .readdirSync(credentialFolderPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .filter(item => item.name.startsWith('network')) // HACK until we add IIN Agents for Corda networks + .map(item => item.name) + for (const networkName of networkNames) { + print.info(`Creating network admin wallet identity for network: ${networkName}`) + await enrollAndRecordWalletIdentity('networkadmin', null, networkName, true, false) // Create a network admin + print.info(`Creating IIN Agent wallet identity for network ${networkName}`) + await enrollAndRecordWalletIdentity('iinagent', null, networkName, false, true) // Create an IIN Agent + } + await configureNetwork(options['local-network'], logger) process.exit() } diff --git a/samples/fabric/fabric-cli/src/commands/user/add.ts b/samples/fabric/fabric-cli/src/commands/user/add.ts index 727800c1fd..2c4329c083 100644 --- a/samples/fabric/fabric-cli/src/commands/user/add.ts +++ b/samples/fabric/fabric-cli/src/commands/user/add.ts @@ -5,10 +5,10 @@ */ import { GluegunCommand } from 'gluegun' -import { commandHelp, getNetworkConfig, saveUserCertToFile } from '../../helpers/helpers' +import { commandHelp } from '../../helpers/helpers' import * as fs from 'fs' import * as path from 'path' -import { walletSetup } from '../../helpers/fabric-functions' +import { enrollAndRecordWalletIdentity } from '../../helpers/fabric-functions' const command: GluegunCommand = { name: 'add', @@ -23,7 +23,7 @@ const command: GluegunCommand = { print, toolbox, `fabric-cli user add --target-network=network1 --id=user --secret=userpw`, - `fabric-cli user add --target-network= --id= --secret=`, + `fabric-cli user add --target-network= --id= --secret= [--network-admin] [--iin-agent]`, [ { name: '--target-network', @@ -39,6 +39,16 @@ const command: GluegunCommand = { name: '--secret', description: 'password for the username being added (Optional: random password is used)' + }, + { + name: '--network-admin', + description: + 'Flag to indicate whether this user should have a network admin attribute.' + }, + { + name: '--iin-agent', + description: + 'Flag to indicate whether this user should have an IIN agent attribute.' } ], command, @@ -53,23 +63,7 @@ const command: GluegunCommand = { print.error('--id is required arguement, please specify the username here') } - const userName = options['id'] - const userPwd = options['secret'] - const net = getNetworkConfig(options['target-network']) - - const ccpPath = path.resolve(__dirname, net.connProfilePath) - const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')) - console.log(net) - - let wallet = await walletSetup(options['target-network'], - ccp, - net.mspId, - userName, - userPwd, - true - ) - - saveUserCertToFile(userName, options['target-network']) + await enrollAndRecordWalletIdentity(options['id'], options['secret'], options['target-network'], options['network-admin'], options['iin-agent']) process.exit() } } diff --git a/samples/fabric/fabric-cli/src/helpers/fabric-functions.ts b/samples/fabric/fabric-cli/src/helpers/fabric-functions.ts index 72f756829e..0f25a2854a 100644 --- a/samples/fabric/fabric-cli/src/helpers/fabric-functions.ts +++ b/samples/fabric/fabric-cli/src/helpers/fabric-functions.ts @@ -5,10 +5,13 @@ */ import { Gateway, Wallets, Contract, X509Identity } from 'fabric-network' -import { handlePromise } from './helpers' +import { commandHelp, getNetworkConfig, saveUserCertToFile, handlePromise } from './helpers' import * as FabricCAServices from 'fabric-ca-client' import { Certificate } from '@fidm/x509' import { Utils, ICryptoKey } from 'fabric-common' +import * as membership_pb from "@hyperledger-labs/weaver-protos-js/common/membership_pb" +import * as iin_agent_pb from "@hyperledger-labs/weaver-protos-js/identity/agent_pb" +import { InteroperableHelper } from '@hyperledger-labs/weaver-fabric-interop-sdk' import * as path from 'path' import * as dotenv from 'dotenv' dotenv.config({ path: path.resolve(__dirname, '../../.env') }) @@ -25,10 +28,7 @@ const getUserCertBase64 = async ( networkName: string, username: string ) => { - const walletPath = process.env.WALLET_PATH - ? process.env.WALLET_PATH - : path.join(__dirname, '../', `wallet-${networkName}`) - const wallet = await Wallets.newFileSystemWallet(walletPath) + const wallet = await getWalletForNetwork(networkName) const userId = await wallet.get(username) if (!userId) { throw new Error(`User ${username} not present in wallet of ${networkName}.`) @@ -42,10 +42,11 @@ const walletSetup = async ( mspId: string, userName: string, userPwd: string = '', + isNetworkAdmin: boolean = false, + isIINAgent: boolean = false, register: boolean = false, logger: any = console ) => { - // Create a new CA client for interacting with the CA. // Create a new CA client for interacting with the CA. const org = ccp.client['organization'] const caName = ccp.organizations[org]['certificateAuthorities'][0] @@ -53,14 +54,10 @@ const walletSetup = async ( const ca = new FabricCAServices(caURL) const ident = ca.newIdentityService() - const walletPath = process.env.WALLET_PATH - ? process.env.WALLET_PATH - : path.join(__dirname, '../', `wallet-${networkName}`) - const wallet = await Wallets.newFileSystemWallet(walletPath) - - logger.debug(`Wallet Setup: wallet path: ${walletPath}`) + const wallet = await getWalletForNetwork(networkName) - // build a user object for authenticating with the CA // Check to see if we've already enrolled the admin user. + // build a user object for authenticating with the CA + // Check to see if we've already enrolled the admin user. let adminIdentity = await wallet.get('admin') if (adminIdentity) { @@ -94,8 +91,15 @@ const walletSetup = async ( logger.error(`Identity ${userName} does not exist. Please add user in the network.\n`) return } - var secret, enrollment; + var secret, enrollment var enrollmentDone = false + var attributes = [] + if (isNetworkAdmin) { + attributes.push({ "name": "network-admin", "value": "true", "ecert": true }) + } + if (isIINAgent) { + attributes.push({ "name": "iin-agent", "value": "true", "ecert": true }) + } try { if (!userPwd) { secret = await ca.register( @@ -103,7 +107,8 @@ const walletSetup = async ( affiliation: 'org1.department1', enrollmentID: userName, maxEnrollments: -1, - role: 'client' + role: 'client', + attrs: attributes }, adminUser ) @@ -115,7 +120,8 @@ const walletSetup = async ( enrollmentID: userName, enrollmentSecret: userPwd, maxEnrollments: -1, - role: 'client' + role: 'client', + attrs: attributes }, adminUser ) @@ -161,18 +167,40 @@ const walletSetup = async ( return wallet } + +const enrollAndRecordWalletIdentity = async ( + userName: string, + userPwd: string, + networkName: string, + isNetworkAdmin: boolean = false, + isIINAgent: boolean = false, + logger: any = console +) => { + const net = getNetworkConfig(networkName) + const ccpPath = path.resolve(__dirname, net.connProfilePath) + const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')) + logger.info(net) + + const wallet = await walletSetup(networkName, ccp, net.mspId, userName, userPwd, isNetworkAdmin, isIINAgent, true) + saveUserCertToFile(userName, networkName) + + return wallet +} + const getCurrentNetworkCredentialPath = (networkName: string): string => { const credentialsPath = process.env.MEMBER_CREDENTIAL_FOLDER ? path.resolve(__dirname, process.env.MEMBER_CREDENTIAL_FOLDER, networkName) : path.join(__dirname, '../data', 'credentials', networkName) return credentialsPath } + const getCredentialPath = (): string => { const credentialsPath = process.env.MEMBER_CREDENTIAL_FOLDER ? path.resolve(__dirname, process.env.MEMBER_CREDENTIAL_FOLDER) : path.join(__dirname, '../data', 'credentials') return credentialsPath } + const generateAccessControl = async ( channel: string, contractName: string, @@ -269,6 +297,7 @@ const generateVerificationPolicy = async ( JSON.stringify(verificationPolicy) ) } + const generateMembership = async ( channel: string, contractName: string, @@ -289,7 +318,8 @@ const generateMembership = async ( const network = await gateway.getNetwork(channel) const mspConfig = await getMspConfig(network, logger) const membershipJSON = formatMSP(mspConfig, networkName) - logger.debug(`membershipJSON: ${JSON.stringify(membershipJSON)}`) + const membershipJSONStr = JSON.stringify(membershipJSON) + logger.debug(`membershipJSON: ${membershipJSONStr}`) const credentialsPath = getCurrentNetworkCredentialPath(networkName) logger.debug(`Credentials Path: ${credentialsPath}`) @@ -300,9 +330,84 @@ const generateMembership = async ( fs.writeFileSync( path.join(credentialsPath, `membership.json`), - JSON.stringify(membershipJSON) + membershipJSONStr ) - return mspConfig + + // Generate protobufs and attestations for all other networks that have IIN Agents + const credentialFolderPath = getCredentialPath() + const otherNetworkNames = fs + .readdirSync(credentialFolderPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .filter(item => item.name.startsWith('network')) // HACK until we add IIN Agents for Corda networks + .map(item => item.name) + // Reorder the array so that the local network is the first element + // We need to record local membership before recording other networks' memberships + otherNetworkNames.splice(otherNetworkNames.indexOf(networkName), 1) + + if (otherNetworkNames.length > 0) { + // Convert membership object to protobuf + let membershipProto = new membership_pb.Membership() + membershipProto.setSecuritydomain(membershipJSON.securityDomain) + Object.keys(membershipJSON.members).forEach( (memberName, index) => { + const certInfo = membershipJSON.members[memberName] + let memberProto = new membership_pb.Member() + memberProto.setType(certInfo.type) + memberProto.setValue(certInfo.value) + membershipProto.getMembersMap().set(memberName, memberProto) + }) + + // For every other network, generate a counter attested membership set + const serializedMembership = membershipProto.serializeBinary() + const serializedMembershipBase64 = Buffer.from(serializedMembership).toString('base64') + const nonce = 'j849j94j40f440fkfjkld0e043' // Some random string + const membershipBase64WithNonce = serializedMembershipBase64 + nonce + // Get wallet key and cert for this network's IIN Agent + const localWallet = await getWalletForNetwork(networkName) + const localKeyCert = await getKeyAndCertForRemoteRequestbyUserName(localWallet, 'iinagent') + // Sign using wallet identity + let securityDomainMember = new iin_agent_pb.SecurityDomainMemberIdentity() + securityDomainMember.setSecurityDomain(membershipJSON.securityDomain) + securityDomainMember.setMemberId(Object.keys(membershipJSON.members)[0]) + let localAttestation = new iin_agent_pb.Attestation() + localAttestation.setUnitIdentity(securityDomainMember) + localAttestation.setCertificate(localKeyCert.cert) + const localSig = InteroperableHelper.signMessage(membershipBase64WithNonce, localKeyCert.key.toBytes()).toString("base64") + localAttestation.setSignature(localSig) + localAttestation.setNonce(nonce) + let attestedMembershipSet = new iin_agent_pb.CounterAttestedMembership.AttestedMembershipSet() + attestedMembershipSet.setMembership(Buffer.from(serializedMembershipBase64)) + attestedMembershipSet.setAttestationsList( [ localAttestation ] ) + const serializedAttestedMembershipSet = attestedMembershipSet.serializeBinary() + const serializedttestedMembershipSetBase64 = Buffer.from(serializedAttestedMembershipSet).toString('base64') + const serializedttestedMembershipSetBase64WithNonce = serializedttestedMembershipSetBase64 + nonce + for (const otherNetworkName of otherNetworkNames) { + // Get wallet key and cert for other network's IIN Agent + const otherWallet = await getWalletForNetwork(otherNetworkName) + const otherKeyCert = await getKeyAndCertForRemoteRequestbyUserName(otherWallet, 'iinagent') + // Sign using wallet identity + let otherSecurityDomainMember = new iin_agent_pb.SecurityDomainMemberIdentity() + otherSecurityDomainMember.setSecurityDomain(otherNetworkName) + otherSecurityDomainMember.setMemberId(getNetworkConfig(otherNetworkName).mspId) + let otherAttestation = new iin_agent_pb.Attestation() + otherAttestation.setUnitIdentity(otherSecurityDomainMember) + otherAttestation.setCertificate(otherKeyCert.cert) + const otherSig = InteroperableHelper.signMessage(serializedttestedMembershipSetBase64WithNonce, otherKeyCert.key.toBytes()).toString("base64") + otherAttestation.setSignature(otherSig) + otherAttestation.setNonce(nonce) + + // Generate chaincode argument and save it in a file + let counterAttestedMembership = new iin_agent_pb.CounterAttestedMembership() + counterAttestedMembership.setAttestedMembershipSet(Buffer.from(serializedttestedMembershipSetBase64)) + counterAttestedMembership.setAttestationsList( [ otherAttestation ] ) + + fs.writeFileSync( + path.join(credentialsPath, `attested-membership-${otherNetworkName}.proto.serialized`), + Buffer.from(counterAttestedMembership.serializeBinary()).toString('base64') + ) + } + } + + return membershipJSON } const formatMSP = (mspConfig: MspConfig, networkId: string) => { @@ -448,7 +553,7 @@ async function fabricHelper({ userPwd = `user1pw` } - const wallet = await walletSetup(networkName, ccp, mspId, userString, userPwd, registerUser, logger) + const wallet = await walletSetup(networkName, ccp, mspId, userString, userPwd, false, false, registerUser, logger) // Check to see if we've already enrolled the user. const identity = await wallet.get(userString) if (!identity) { @@ -514,6 +619,7 @@ async function query( throw new Error(error) } } + async function invoke( query: Query, connProfilePath: string, @@ -554,6 +660,7 @@ async function invoke( throw new Error(error) } } + const getKeyAndCertForRemoteRequestbyUserName = async ( wallet: any, username: string @@ -577,12 +684,24 @@ const getKeyAndCertForRemoteRequestbyUserName = async ( return { key: privKey, cert: identity.credentials.certificate } } +const getWalletForNetwork = async ( + networkName: string, +) => { + const walletPath = process.env.WALLET_PATH + ? process.env.WALLET_PATH + : path.join(__dirname, '../', `wallet-${networkName}`) + + const wallet = await Wallets.newFileSystemWallet(walletPath) + return wallet +} + export { getUserCertBase64, walletSetup, invoke, query, + enrollAndRecordWalletIdentity, fabricHelper, generateMembership, generateAccessControl, diff --git a/samples/fabric/fabric-cli/src/helpers/interop-setup/configure-network.ts b/samples/fabric/fabric-cli/src/helpers/interop-setup/configure-network.ts index c56b701cdd..2a627a7ae4 100644 --- a/samples/fabric/fabric-cli/src/helpers/interop-setup/configure-network.ts +++ b/samples/fabric/fabric-cli/src/helpers/interop-setup/configure-network.ts @@ -13,7 +13,7 @@ import { } from '../fabric-functions' import { handlePromise, getNetworkConfig } from '../helpers' -const helperInvoke = async (ccFunc, ccArg, ...args) => { +const helperInvoke = async (userId, ccFunc, ccArg, ...args) => { const [contractName, channelName, connProfilePath, networkName, logger] = args const [invokeResponse, invokeError] = await handlePromise( invoke( @@ -26,17 +26,20 @@ const helperInvoke = async (ccFunc, ccArg, ...args) => { connProfilePath, networkName, global.__DEFAULT_MSPID__, - logger + logger, + userId, + (userId === '') ) ) logger.debug(`${ccFunc} Invoke ${JSON.stringify(invokeResponse)}`) if (invokeError) { - logger.error(`${ccFunc} Invoke Error: ${ccArg}`) + logger.error(`${ccFunc} Invoke Error: ${ccFunc}: ${ccArg}`) throw new Error(`${ccFunc} Invoke Error ${invokeError}`) } else { logger.info(`Successfully invoked ${ccFunc}`) } } + const configureNetwork = async (mainNetwork: string, logger: any = console) => { const networkEnv = getNetworkConfig(mainNetwork) logger.debug(`NetworkEnv: ${JSON.stringify(networkEnv)}`) @@ -52,15 +55,44 @@ const configureNetwork = async (mainNetwork: string, logger: any = console) => { .readdirSync(credentialFolderPath, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(item => item.name) + // Reorder the array so that the local network is the first element + // We need to record local membership before recording other networks' memberships + networkFolders.splice(networkFolders.indexOf(mainNetwork), 1) + networkFolders.splice(0, 0, mainNetwork) + for (const index in networkFolders) { const network = networkFolders[index] + if (network === mainNetwork) { + // A network needs to load/record only other networks' credentials + const localMembershipPath = path.join( + getCurrentNetworkCredentialPath(network), + 'membership.json' + ) + if ( + !fs.existsSync(localMembershipPath) + ) { + logger.error(`Missing credential file for network: ${network}`) + } else { + await loadLocalHelper( + networkEnv.connProfilePath, + mainNetwork, + process.env.DEFAULT_CHANNEL ? process.env.DEFAULT_CHANNEL : 'mychannel', + process.env.DEFAULT_CHAINCODE + ? process.env.DEFAULT_CHAINCODE + : 'interop', + localMembershipPath, + logger + ) + } + continue; + } const accessControlPath = path.join( getCurrentNetworkCredentialPath(network), 'access-control.json' ) const membershipPath = path.join( getCurrentNetworkCredentialPath(network), - 'membership.json' + (network.startsWith('network') ? 'attested-membership-' + mainNetwork + '.proto.serialized' : 'membership.json') ) const verificationPolicyPath = path.join( getCurrentNetworkCredentialPath(network), @@ -80,6 +112,7 @@ const configureNetwork = async (mainNetwork: string, logger: any = console) => { process.env.DEFAULT_CHAINCODE ? process.env.DEFAULT_CHAINCODE : 'interop', + network, accessControlPath, membershipPath, verificationPolicyPath, @@ -88,11 +121,37 @@ const configureNetwork = async (mainNetwork: string, logger: any = console) => { } } } + +const loadLocalHelper = async ( + connProfilePath: string, + networkName: string, + channelName: string, + contractName: string, + localMembershipPath: string, + logger: any = console +): Promise => { + const localMembership = Buffer.from(fs.readFileSync(localMembershipPath)).toString() + const helperInvokeArgs = [ + contractName, + channelName, + connProfilePath, + networkName, + logger + ] + try { + await helperInvoke('networkadmin', 'CreateLocalMembership', localMembership, ...helperInvokeArgs) + } catch (e) { + logger.info('CreateLocalMembership attempting Update') + await helperInvoke('networkadmin', 'UpdateLocalMembership', localMembership, ...helperInvokeArgs) + } +} + const configureNetworkHelper = async ( connProfilePath: string, networkName: string, channelName: string, contractName: string, + targetNetwork: string, accessControlPath: string, membershipPath: string, verificationPolicyPath: string, @@ -106,7 +165,7 @@ const configureNetworkHelper = async ( fs.readFileSync(verificationPolicyPath) ).toString() - const membership = Buffer.from(fs.readFileSync(membershipPath)).toString() + const attestedMembership = Buffer.from(fs.readFileSync(membershipPath)).toString() const helperInvokeArgs = [ contractName, channelName, @@ -116,6 +175,7 @@ const configureNetworkHelper = async ( ] try { await helperInvoke( + '', 'CreateAccessControlPolicy', accessControl, ...helperInvokeArgs @@ -123,6 +183,7 @@ const configureNetworkHelper = async ( } catch (e) { logger.info('CreateAccessControlPolicy attempting Update') await helperInvoke( + '', 'UpdateAccessControlPolicy', accessControl, ...helperInvokeArgs @@ -130,6 +191,7 @@ const configureNetworkHelper = async ( } try { await helperInvoke( + '', 'CreateVerificationPolicy', verificationPolicy, ...helperInvokeArgs @@ -137,16 +199,18 @@ const configureNetworkHelper = async ( } catch (e) { logger.info('CreateVerificationPolicy attempting Update') await helperInvoke( + '', 'UpdateVerificationPolicy', verificationPolicy, ...helperInvokeArgs ) } + const memberRecordingUser = (targetNetwork.startsWith('network') ? 'iinagent': 'networkadmin') // HACK until we add IIN Agents for Corda networks try { - await helperInvoke('CreateMembership', membership, ...helperInvokeArgs) + await helperInvoke(memberRecordingUser, 'CreateMembership', attestedMembership, ...helperInvokeArgs) } catch (e) { logger.info('CreateMembership attempting Update') - await helperInvoke('UpdateMembership', membership, ...helperInvokeArgs) + await helperInvoke(memberRecordingUser, 'UpdateMembership', attestedMembership, ...helperInvokeArgs) } } diff --git a/samples/fabric/fabric-cli/src/wallet-Dummy_Network/admin.id b/samples/fabric/fabric-cli/src/wallet-Dummy_Network/admin.id deleted file mode 100644 index 5728d610d1..0000000000 --- a/samples/fabric/fabric-cli/src/wallet-Dummy_Network/admin.id +++ /dev/null @@ -1 +0,0 @@ -{"credentials":{"certificate":"-----BEGIN CERTIFICATE-----\nMIIB6zCCAZGgAwIBAgIUGdKilZLHRbBMNKIJkfJW/txCmycwCgYIKoZIzj0EAwIw\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\nY2Etc2VydmVyMB4XDTIxMTIyMjEyNDcwMFoXDTIyMTIyMjEyNTIwMFowITEPMA0G\nA1UECxMGY2xpZW50MQ4wDAYDVQQDEwVhZG1pbjBZMBMGByqGSM49AgEGCCqGSM49\nAwEHA0IABAgC75qKkepz3qmN2mXtOvZcBTY+jpPj7JfZulgbtHRNGM+IuLNY1Ba6\nXtCq+aE2L0hCrixdeU57Pv2kfe304fKjYDBeMA4GA1UdDwEB/wQEAwIHgDAMBgNV\nHRMBAf8EAjAAMB0GA1UdDgQWBBS82LliVtb0bSkNlAUSopxdVOTiHDAfBgNVHSME\nGDAWgBRyhvWC+jgvOsuRSF6arXu2zl0g/DAKBggqhkjOPQQDAgNIADBFAiEAvVwm\nR5bED0bGKh0a5I0SgFhbWWJqsB3TdTkDH6VG2uYCIDTQxhdKFEYjt/zD8hfo7Viv\nJcSUDK4i6VBl0kNc7fiw\n-----END CERTIFICATE-----\n","privateKey":"-----BEGIN PRIVATE KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5/h+UW63+O946lp+\r\n69A03VKnE5IB/FyAJt6/vDc+sGKhRANCAAQIAu+aipHqc96pjdpl7Tr2XAU2Po6T\r\n4+yX2bpYG7R0TRjPiLizWNQWul7QqvmhNi9IQq4sXXlOez79pH3t9OHy\r\n-----END PRIVATE KEY-----\r\n"},"mspId":"Org1MSP","type":"X.509","version":1} \ No newline at end of file diff --git a/samples/fabric/fabric-cli/src/wallet-Dummy_Network/alice.id b/samples/fabric/fabric-cli/src/wallet-Dummy_Network/alice.id deleted file mode 100644 index 29b71059c8..0000000000 --- a/samples/fabric/fabric-cli/src/wallet-Dummy_Network/alice.id +++ /dev/null @@ -1 +0,0 @@ -{"credentials":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICdzCCAh6gAwIBAgIURDTyar4rqQHsdS+e3SNfXWPA4MgwCgYIKoZIzj0EAwIw\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\nY2Etc2VydmVyMB4XDTIxMTIyMjEyNDcwMFoXDTIyMTIyMjEyNTIwMFowQjEwMA0G\nA1UECxMGY2xpZW50MAsGA1UECxMEb3JnMTASBgNVBAsTC2RlcGFydG1lbnQxMQ4w\nDAYDVQQDEwVhbGljZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABN81GZdxlkqc\nCmjrFFFoRTu52bH8W8aZ1jlNm63WEHdTkhBokpYhz3CKOLtfcvXEo5gRMFEwiebw\nnBMCXY4X4cmjgcswgcgwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYD\nVR0OBBYEFG0ENsxu+zkEC3WEtr5LjvjXroH3MB8GA1UdIwQYMBaAFHKG9YL6OC86\ny5FIXpqte7bOXSD8MGgGCCoDBAUGBwgBBFx7ImF0dHJzIjp7ImhmLkFmZmlsaWF0\naW9uIjoib3JnMS5kZXBhcnRtZW50MSIsImhmLkVucm9sbG1lbnRJRCI6ImFsaWNl\nIiwiaGYuVHlwZSI6ImNsaWVudCJ9fTAKBggqhkjOPQQDAgNHADBEAiBLH04OFthd\nlKJstFwzia4E7/7j4dm/0QSS2TDdzatJbAIgIKcEmZEHg5fyygrUBXdB204mVVuD\nBQrjKcyqDb3j1OM=\n-----END CERTIFICATE-----\n","privateKey":"-----BEGIN PRIVATE KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQggiIMoz/H/In441zW\r\nhkWBymQqawT3iOBIkLM/87xIx+OhRANCAATfNRmXcZZKnApo6xRRaEU7udmx/FvG\r\nmdY5TZut1hB3U5IQaJKWIc9wiji7X3L1xKOYETBRMInm8JwTAl2OF+HJ\r\n-----END PRIVATE KEY-----\r\n"},"mspId":"Org1MSP","type":"X.509","version":1} \ No newline at end of file diff --git a/samples/fabric/fabric-cli/src/wallet-Dummy_Network/bob.id b/samples/fabric/fabric-cli/src/wallet-Dummy_Network/bob.id deleted file mode 100644 index 607805e971..0000000000 --- a/samples/fabric/fabric-cli/src/wallet-Dummy_Network/bob.id +++ /dev/null @@ -1 +0,0 @@ -{"credentials":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICdDCCAhqgAwIBAgIUQt/AkTbCRgPAQf8WzYTr75bGaj0wCgYIKoZIzj0EAwIw\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\nY2Etc2VydmVyMB4XDTIxMTIyMjEyNDgwMFoXDTIyMTIyMjEyNTMwMFowQDEwMA0G\nA1UECxMGY2xpZW50MAsGA1UECxMEb3JnMTASBgNVBAsTC2RlcGFydG1lbnQxMQww\nCgYDVQQDEwNib2IwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT05/C4elB82Jgf\nJmK5GecjY/xNELBdsR4g441WB8ocZ/5JEYy5mM0aMZJfOi0vVn+OVzQMJ5sOz0k5\nHdxvkPc/o4HJMIHGMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMB0GA1Ud\nDgQWBBR1+ssr8ZO4lLO+IkgYhChfHz6yETAfBgNVHSMEGDAWgBRyhvWC+jgvOsuR\nSF6arXu2zl0g/DBmBggqAwQFBgcIAQRaeyJhdHRycyI6eyJoZi5BZmZpbGlhdGlv\nbiI6Im9yZzEuZGVwYXJ0bWVudDEiLCJoZi5FbnJvbGxtZW50SUQiOiJib2IiLCJo\nZi5UeXBlIjoiY2xpZW50In19MAoGCCqGSM49BAMCA0gAMEUCIQCNIwE4oXMQqgXK\n1cpsPU7PWhAr0oy7P4TpsR1SN64GmwIgVmPNb7O77M63U756XPdLW0HCTQSdZSHK\n1EoNob1WC5o=\n-----END CERTIFICATE-----\n","privateKey":"-----BEGIN PRIVATE KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgsj1o70bbnyEugrQa\r\nTehiIXbHnzCKK0xaQuuI7lIUeVahRANCAAT05/C4elB82JgfJmK5GecjY/xNELBd\r\nsR4g441WB8ocZ/5JEYy5mM0aMZJfOi0vVn+OVzQMJ5sOz0k5HdxvkPc/\r\n-----END PRIVATE KEY-----\r\n"},"mspId":"Org1MSP","type":"X.509","version":1} \ No newline at end of file diff --git a/samples/fabric/fabric-cli/src/wallet-Dummy_Network/user1.id b/samples/fabric/fabric-cli/src/wallet-Dummy_Network/user1.id deleted file mode 100644 index 6107739f71..0000000000 --- a/samples/fabric/fabric-cli/src/wallet-Dummy_Network/user1.id +++ /dev/null @@ -1 +0,0 @@ -{"credentials":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICRjCCAe2gAwIBAgIUBMI+vcDI6Z/4bMZN2Cjp6vAXDtgwCgYIKoZIzj0EAwIw\naDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRkwFwYDVQQDExBmYWJyaWMt\nY2Etc2VydmVyMB4XDTIxMDkwNzA5MTQwMFoXDTIyMDkwNzA5MTkwMFowITEPMA0G\nA1UECxMGY2xpZW50MQ4wDAYDVQQDEwV1c2VyMTBZMBMGByqGSM49AgEGCCqGSM49\nAwEHA0IABFoZaE1MsLtrLRYFzJRyj5uelfL/Fa5pLN2kiO+M5tnxf0+wAotEeC8z\nLCS1KHVIQ7UJD/AOL1OFkbJCHKSdFJGjgbswgbgwDgYDVR0PAQH/BAQDAgeAMAwG\nA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPzrPjhbfwF/wxP8iFKy4NspQw0uMB8GA1Ud\nIwQYMBaAFHKG9YL6OC86y5FIXpqte7bOXSD8MFgGCCoDBAUGBwgBBEx7ImF0dHJz\nIjp7ImhmLkFmZmlsaWF0aW9uIjoiIiwiaGYuRW5yb2xsbWVudElEIjoidXNlcjEi\nLCJoZi5UeXBlIjoiY2xpZW50In19MAoGCCqGSM49BAMCA0cAMEQCIDP8WLlE4btz\niw6tI/EFdLY/CSOW1P8bOFquaz5YwjYhAiBxEKH6+udDehvHU+wmRaC/Kp1VJNJZ\ndbjNTs65n8zfbQ==\n-----END CERTIFICATE-----\n","privateKey":"-----BEGIN PRIVATE KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgdSo5uIgF2yX79LXZ\r\nmP1USII6QeIYwfNCUQvo0rtKkZyhRANCAARaGWhNTLC7ay0WBcyUco+bnpXy/xWu\r\naSzdpIjvjObZ8X9PsAKLRHgvMywktSh1SEO1CQ/wDi9ThZGyQhyknRSR\r\n-----END PRIVATE KEY-----\r\n"},"mspId":"Org1MSP","type":"X.509","version":1} \ No newline at end of file