All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hyperledger.fabric.contract.ClientIdentity Maven / Gradle / Ivy

/*
 * Copyright 2019 IBM All Rights Reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */
package org.hyperledger.fabric.contract;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DEROctetString;
import org.hyperledger.fabric.Logger;
import org.hyperledger.fabric.protos.msp.Identities.SerializedIdentity;
import org.hyperledger.fabric.shim.ChaincodeStub;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * ClientIdentity represents information about the identity that submitted a
 * transaction. Chaincodes can use this class to obtain information about the
 * submitting identity including a unique ID, the MSP (Membership Service
 * Provider) ID, and attributes. Such information is useful in enforcing access
 * control by the chaincode.
 *
 */
public final class ClientIdentity {
    private static Logger logger = Logger.getLogger(ContractRouter.class.getName());

    private final String mspId;
    private final X509Certificate cert;
    private Map attrs;
    private final String id;
    // special OID used by Fabric to save attributes in x.509 certificates
    private static final String FABRIC_CERT_ATTR_OID = "1.2.3.4.5.6.7.8.1";

    /**
     * Creates new ClientIdentity helper.
     *
     * @param stub
     * @throws CertificateException
     * @throws JSONException
     * @throws IOException
     */
    public ClientIdentity(final ChaincodeStub stub) throws CertificateException, JSONException, IOException {
        final byte[] signingId = stub.getCreator();

        // Create a Serialized Identity protobuf
        final SerializedIdentity si = SerializedIdentity.parseFrom(signingId);
        this.mspId = si.getMspid();

        final byte[] idBytes = si.getIdBytes().toByteArray();

        final X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new ByteArrayInputStream(idBytes));
        this.cert = cert;

        this.attrs = new HashMap();
        // Get the extension where the identity attributes are stored
        final byte[] extensionValue = cert.getExtensionValue(FABRIC_CERT_ATTR_OID);
        if (extensionValue != null) {
            this.attrs = parseAttributes(extensionValue);
        }

        // Populate identity
        this.id = "x509::" + cert.getSubjectDN().getName() + "::" + cert.getIssuerDN().getName();
    }

    /**
     * getId returns the ID associated with the invoking identity. This ID is
     * guaranteed to be unique within the MSP.
     *
     * @return {String} A string in the format: "x509::{subject DN}::{issuer DN}"
     */
    public String getId() {
        return this.id;
    }

    /**
     * getMSPID returns the MSP ID of the invoking identity.
     *
     * @return {String}
     */
    public String getMSPID() {
        return this.mspId;
    }

    /**
     * parseAttributes returns a map of the attributes associated with an identity.
     *
     * @param extensionValue DER-encoded Octet string stored in the attributes
     *                       extension of the certificate, as a byte array
     * @return attrMap {Map} a map of identity attributes as key
     *         value pair strings
     * @throws IOException
     */
    private Map parseAttributes(final byte[] extensionValue) throws IOException {

        final Map attrMap = new HashMap();

        // Create ASN1InputStream from extensionValue
        try (ByteArrayInputStream inStream = new ByteArrayInputStream(extensionValue); ASN1InputStream asn1InputStream = new ASN1InputStream(inStream)) {

            // Read the DER object
            final ASN1Primitive derObject = asn1InputStream.readObject();
            if (derObject instanceof DEROctetString) {
                final DEROctetString derOctetString = (DEROctetString) derObject;

                // Create attributeString from octets and create JSON object
                final String attributeString = new String(derOctetString.getOctets(), UTF_8);
                final JSONObject extJSON = new JSONObject(attributeString);
                final JSONObject attrs = extJSON.getJSONObject("attrs");

                final Iterator keys = attrs.keys();
                while (keys.hasNext()) {
                    final String key = keys.next();
                    // Populate map with attributes and values
                    attrMap.put(key, attrs.getString(key));
                }
            }
        } catch (final JSONException error) {
            // creating a JSON object failed
            // decoded extensionValue is not a string containing JSON
            logger.error(() -> logger.formatError(error));
            // return empty map
        }
        return attrMap;
    }

    /**
     * getAttributeValue returns the value of the client's attribute named
     * `attrName`. If the invoking identity possesses the attribute, returns the
     * value of the attribute. If the invoking identity does not possess the
     * attribute, returns null.
     *
     * @param attrName Name of the attribute to retrieve the value from the
     *                 identity's credentials (such as x.509 certificate for
     *                 PKI-based MSPs).
     * @return {String | null} Value of the attribute or null if the invoking
     *         identity does not possess the attribute.
     */
    public String getAttributeValue(final String attrName) {
        if (this.attrs.containsKey(attrName)) {
            return this.attrs.get(attrName);
        } else {
            return null;
        }
    }

    /**
     * assertAttributeValue verifies that the invoking identity has the attribute
     * named `attrName` with a value of `attrValue`.
     *
     * @param attrName  Name of the attribute to retrieve the value from the
     *                  identity's credentials (such as x.509 certificate for
     *                  PKI-based MSPs)
     * @param attrValue Expected value of the attribute
     * @return {boolean} True if the invoking identity possesses the attribute and
     *         the attribute value matches the expected value. Otherwise, returns
     *         false.
     */
    public boolean assertAttributeValue(final String attrName, final String attrValue) {
        if (!this.attrs.containsKey(attrName)) {
            return false;
        } else {
            return attrValue.equals(this.attrs.get(attrName));
        }
    }

    /**
     * getX509Certificate returns the X509 certificate associated with the invoking
     * identity, or null if it was not identified by an X509 certificate, for
     * instance if the MSP is implemented with an alternative to PKI such as
     * [Identity Mixer](https://jira.hyperledger.org/browse/FAB-5673).
     *
     * @return {X509Certificate | null}
     */
    public X509Certificate getX509Certificate() {
        return this.cert;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy