Retrieving user identities and attributes in chaincode
In this step, we will retrieve a user's identity during the execution of the chaincode. The ABAC functionality available to chaincode is provided by the Client Identity Chaincode (CID) library.
Every transaction proposal submitted to the chaincode carries along with it the ecert of the invoker –the user submitting the transaction. The chaincode has access to the ecert through importing the CID library and invoking the library functions with the argument ChaincodeStubInterface, that is, the argument stub received in both the Init and Invoke methods.
The chaincode can use the certificate to extract information about the invoker, including:
- The ID of the invoker
- The unique ID of the Membership Service Provider (MSP) which issued the invoker certificate
- The standard attributes of the certificate, such as its domain name, email, and so on
- The ecert attributes associated with the client identity, stored within the certificate
The functions provided by the CID library are listed in the following snippet:
// Returns the ID associated with the invoking identity. // This ID is unique within the MSP (Fabric CA) which issued the identity, however, it is not guaranteed to be unique across all MSPs of the network. func GetID() (string, error) // Returns the unique ID of the MSP associated with the identity that submitted the transaction. // The combination of the MSPID and of the identity ID are guaranteed to be unique across the network. func GetMSPID() (string, error) // Returns the value of the ecert attribute named `attrName`. // If the ecert has the attribute, the `found` returns true and the `value` returns the value of the attribute. // If the ecert does not have the attribute, `found` returns false and `value` returns empty string. func GetAttributeValue(attrName string) (value string, found bool, err error) // The function verifies that the ecert has the attribute named `attrName` and that the attribute value equals to `attrValue`. // The function returns nil if there is a match, else, it returns error. func AssertAttributeValue(attrName, attrValue string) error // Returns the X509 identity certificate. // The certificate is an instance of a type Certificate from the library "crypto/x509". func GetX509Certificate() (*x509.Certificate, error)
In the following codeblock, we define a function, getTxCreatorInfo, which obtains basic identity information about the invoker. First, we must import the CID and x509 libraries, as seen in lines 3 and 4. The unique MSPID is retrieved in line 13 and the X509 certificate is obtained in line 19. In line 24, we then retrieve the CommonName of the certificate, which contains the unique string of the Fabric CA within the network. These two attributes are returned by the function and used in subsequent access control validation, as shown in the following snippet:
import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/core/chaincode/lib/cid" "crypto/x509" ) func getTxCreatorInfo(stub shim.ChaincodeStubInterface) (string, string, error) { var mspid string var err error var cert *x509.Certificate mspid, err = cid.GetMSPID(stub) if err != nil { fmt.Printf("Error getting MSP identity: %sn", err.Error()) return "", "", err } cert, err = cid.GetX509Certificate(stub) if err != nil { fmt.Printf("Error getting client certificate: %sn", err.Error()) return "", "", err } return mspid, cert.Issuer.CommonName, nil }
We now need to define and implement the simple access control policy in our chaincode. Each function of the chaincode can only be invoked by members of a specific organization; each chaincode function will therefore validate whether the invoker is a member of the required organization. For example, the function requestTrade can be invoked only by members of the Importer organization. In the following snippet, the function authenticateImporterOrg validates whether the invoker is a member of ImporterOrgMSP. This function then will be invoked from the requestTrade function to enforce access control.
func authenticateExportingEntityOrg(mspID string, certCN string) bool {
return (mspID == "ExportingEntityOrgMSP") && (certCN == "ca.exportingentityorg.trade.com")
}
func authenticateExporterOrg(mspID string, certCN string) bool {
return (mspID == "ExporterOrgMSP") && (certCN == "ca.exporterorg.trade.com")
}
func authenticateImporterOrg(mspID string, certCN string) bool {
return (mspID == "ImporterOrgMSP") && (certCN == "ca.importerorg.trade.com")
}
func authenticateCarrierOrg(mspID string, certCN string) bool {
return (mspID == "CarrierOrgMSP") && (certCN == "ca.carrierorg.trade.com")
}
func authenticateRegulatorOrg(mspID string, certCN string) bool {
return (mspID == "RegulatorOrgMSP") && (certCN == "ca.regulatororg.trade.com")
}
In the following snippet is shown the invocation of access control validation, which has granted access only to members of ImporterOrgMSP. The function is invoked with the arguments obtained from the getTxCreatorInfo function.
creatorOrg, creatorCertIssuer, err = getTxCreatorInfo(stub)
if !authenticateImporterOrg(creatorOrg, creatorCertIssuer) {
return shim.Error("Caller not a member of Importer Org. Access denied.")
}
Now, we need to place our authentication functions into a separate file, accessControlUtils.go, which is located in the same directory as the main tradeWorkflow.go file. This file will be automatically imported into the main chaincode file during compilation so we can refer to the functions defined in it.