org.hyperledger.fabric_ca.sdk.HFCAClient.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperledger.fabric_ca.sdk.HFCAClient.java

Source

/*
 *  Copyright 2016, 2017 DTCC, Fujitsu Australia Software Technology, IBM - 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 org.hyperledger.fabric_ca.sdk;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
import javax.json.JsonValue;
import javax.json.JsonWriter;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.xml.bind.DatatypeConverter;

import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.apache.milagro.amcl.FP256BN.BIG;
import org.apache.milagro.amcl.RAND;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.util.io.pem.PemReader;
import org.hyperledger.fabric.protos.idemix.Idemix;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.NetworkConfig;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.helper.Utils;
import org.hyperledger.fabric.sdk.idemix.IdemixCredRequest;
import org.hyperledger.fabric.sdk.idemix.IdemixCredential;
import org.hyperledger.fabric.sdk.idemix.IdemixIssuerPublicKey;
import org.hyperledger.fabric.sdk.idemix.IdemixUtils;
import org.hyperledger.fabric.sdk.identity.IdemixEnrollment;
import org.hyperledger.fabric.sdk.identity.X509Enrollment;
import org.hyperledger.fabric.sdk.security.CryptoPrimitives;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric_ca.sdk.exception.AffiliationException;
import org.hyperledger.fabric_ca.sdk.exception.EnrollmentException;
import org.hyperledger.fabric_ca.sdk.exception.GenerateCRLException;
import org.hyperledger.fabric_ca.sdk.exception.HFCACertificateException;
import org.hyperledger.fabric_ca.sdk.exception.HTTPException;
import org.hyperledger.fabric_ca.sdk.exception.IdentityException;
import org.hyperledger.fabric_ca.sdk.exception.InfoException;
import org.hyperledger.fabric_ca.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric_ca.sdk.exception.RegistrationException;
import org.hyperledger.fabric_ca.sdk.exception.RevocationException;
import org.hyperledger.fabric_ca.sdk.helper.Config;
import org.hyperledger.fabric_ca.sdk.helper.Util;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * HFCAClient Hyperledger Fabric Certificate Authority Client.
 */

public class HFCAClient {
    private static final Config config = Config.getConfig(); // DO NOT REMOVE THIS IS NEEDED TO MAKE SURE WE FIRST LOAD CONFIG!!!
    /**
     * Default profile name.
     */
    public static final String DEFAULT_PROFILE_NAME = "";
    /**
     * HFCA_TYPE_PEER indicates that an identity is acting as a peer
     */
    public static final String HFCA_TYPE_PEER = "peer";
    /**
     * HFCA_TYPE_ORDERER indicates that an identity is acting as an orderer
     */
    public static final String HFCA_TYPE_ORDERER = "orderer";
    /**
     * HFCA_TYPE_CLIENT indicates that an identity is acting as a client
     */
    public static final String HFCA_TYPE_CLIENT = "client";
    /**
     * HFCA_TYPE_USER indicates that an identity is acting as a user
     */
    public static final String HFCA_TYPE_USER = "user";

    /**
     * HFCA_ATTRIBUTE_HFREGISTRARROLES is an attribute that allows a registrar to manage identities of the specified roles
     */
    public static final String HFCA_ATTRIBUTE_HFREGISTRARROLES = "hf.Registrar.Roles";
    /**
     * HFCA_ATTRIBUTE_HFREGISTRARDELEGATEROLES is an attribute that allows a registrar to give the roles specified
     * to a registree for its 'hf.Registrar.Roles' attribute
     */
    public static final String HFCA_ATTRIBUTE_HFREGISTRARDELEGATEROLES = "hf.Registrar.DelegateRoles";
    /**
     * HFCA_ATTRIBUTE_HFREGISTRARATTRIBUTES is an attribute that has a list of attributes that the registrar is allowed to register
     * for an identity
     */
    public static final String HFCA_ATTRIBUTE_HFREGISTRARATTRIBUTES = "hf.Registrar.Attributes";
    /**
     * HFCA_ATTRIBUTE_HFINTERMEDIATECA is a boolean attribute that allows an identity to enroll as an intermediate CA
     */
    public static final String HFCA_ATTRIBUTE_HFINTERMEDIATECA = "hf.IntermediateCA";
    /**
     * HFCA_ATTRIBUTE_HFREVOKER is a boolean attribute that allows an identity to revoker a user and/or certificates
     */
    public static final String HFCA_ATTRIBUTE_HFREVOKER = "hf.Revoker";
    /**
     * HFCA_ATTRIBUTE_HFAFFILIATIONMGR is a boolean attribute that allows an identity to manage affiliations
     */
    public static final String HFCA_ATTRIBUTE_HFAFFILIATIONMGR = "hf.AffiliationMgr";
    /**
     * HFCA_ATTRIBUTE_HFGENCRL is an attribute that allows an identity to generate a CRL
     */
    public static final String HFCA_ATTRIBUTE_HFGENCRL = "hf.GenCRL";

    private static final int CONNECTION_REQUEST_TIMEOUT = config.getConnectionRequestTimeout();
    private static final int CONNECT_TIMEOUT = config.getConnectTimeout();
    private static final int SOCKET_TIMEOUT = config.getSocketTimeout();

    private static final Log logger = LogFactory.getLog(HFCAClient.class);

    static final String FABRIC_CA_REQPROP = "caname";
    static final String HFCA_CONTEXT_ROOT = "/api/v1/";

    private static final String HFCA_ENROLL = HFCA_CONTEXT_ROOT + "enroll";
    private static final String HFCA_REGISTER = HFCA_CONTEXT_ROOT + "register";
    private static final String HFCA_REENROLL = HFCA_CONTEXT_ROOT + "reenroll";
    private static final String HFCA_REVOKE = HFCA_CONTEXT_ROOT + "revoke";
    private static final String HFCA_INFO = HFCA_CONTEXT_ROOT + "cainfo";
    private static final String HFCA_GENCRL = HFCA_CONTEXT_ROOT + "gencrl";
    private static final String HFCA_CERTIFICATE = HFCAClient.HFCA_CONTEXT_ROOT + "certificates";
    private static final String HFCA_IDEMIXCRED = HFCA_CONTEXT_ROOT + "idemix/credential";

    private final String url;
    private final boolean isSSL;
    private final Properties properties;

    // Cache the payload type, so don't need to make get cainfo call everytime
    private Boolean newPayloadType;

    /**
     * The Certificate Authority name.
     *
     * @return May return null or empty string for default certificate authority.
     */
    public String getCAName() {
        return caName;
    }

    private final String caName;

    private CryptoSuite cryptoSuite;

    private int statusCode = 400;

    /**
     * The Status Code level of client, HTTP status codes above this value will return in a
     * exception, otherwise, the status code will be return the status code and appropriate error
     * will be logged.
     *
     * @return statusCode
     */
    public int getStatusCode() {
        return statusCode;
    }

    /**
     * HFCAClient constructor
     *
     * @param url        Http URL for the Fabric's certificate authority services endpoint
     * @param properties PEM used for SSL .. not implemented.
     *                   <p>
     *                   Supported properties
     *                   <ul>
     *                   <li>pemFile - File location for x509 pem certificate for SSL.</li>
     *                   <li>allowAllHostNames - boolen(true/false) override certificates CN Host matching -- for development only.</li>
     *                   </ul>
     * @throws MalformedURLException
     */
    HFCAClient(String caName, String url, Properties properties) throws MalformedURLException {
        logger.debug(format("new HFCAClient %s", url));
        this.url = url;

        this.caName = caName; //name may be null

        URL purl = new URL(url);
        final String proto = purl.getProtocol();
        if (!"http".equals(proto) && !"https".equals(proto)) {
            throw new IllegalArgumentException("HFCAClient only supports http or https not " + proto);
        }
        final String host = purl.getHost();

        if (Utils.isNullOrEmpty(host)) {
            throw new IllegalArgumentException("HFCAClient url needs host");
        }

        final String path = purl.getPath();

        if (!Utils.isNullOrEmpty(path)) {

            throw new IllegalArgumentException(
                    "HFCAClient url does not support path portion in url remove path: '" + path + "'.");
        }

        final String query = purl.getQuery();

        if (!Utils.isNullOrEmpty(query)) {

            throw new IllegalArgumentException(
                    "HFCAClient url does not support query portion in url remove query: '" + query + "'.");
        }

        isSSL = "https".equals(proto);

        if (properties != null) {
            this.properties = (Properties) properties.clone(); //keep our own copy.
        } else {
            this.properties = null;
        }

    }

    public static HFCAClient createNewInstance(String url, Properties properties) throws MalformedURLException {

        return new HFCAClient(null, url, properties);

    }

    public static HFCAClient createNewInstance(String name, String url, Properties properties)
            throws MalformedURLException, InvalidArgumentException {

        if (name == null || name.isEmpty()) {

            throw new InvalidArgumentException("name must not be null or an empty string.");
        }

        return new HFCAClient(name, url, properties);

    }

    /**
     * Create HFCAClient from a NetworkConfig.CAInfo using default crypto suite.
     *
     * @param caInfo created from NetworkConfig.getOrganizationInfo("org_name").getCertificateAuthorities()
     * @return HFCAClient
     * @throws MalformedURLException
     * @throws InvalidArgumentException
     */

    public static HFCAClient createNewInstance(NetworkConfig.CAInfo caInfo)
            throws MalformedURLException, InvalidArgumentException {

        try {
            return createNewInstance(caInfo, CryptoSuite.Factory.getCryptoSuite());
        } catch (MalformedURLException e) {
            throw e;
        } catch (Exception e) {
            throw new InvalidArgumentException(e);
        }
    }

    /**
     * Create HFCAClient from a NetworkConfig.CAInfo
     *
     * @param caInfo      created from NetworkConfig.getOrganizationInfo("org_name").getCertificateAuthorities()
     * @param cryptoSuite the specific cryptosuite to use.
     * @return HFCAClient
     * @throws MalformedURLException
     * @throws InvalidArgumentException
     */

    public static HFCAClient createNewInstance(NetworkConfig.CAInfo caInfo, CryptoSuite cryptoSuite)
            throws MalformedURLException, InvalidArgumentException {

        if (null == caInfo) {
            throw new InvalidArgumentException("The caInfo parameter can not be null.");
        }

        if (null == cryptoSuite) {
            throw new InvalidArgumentException("The cryptoSuite parameter can not be null.");
        }

        HFCAClient ret = new HFCAClient(caInfo.getCAName(), caInfo.getUrl(), caInfo.getProperties());
        ret.setCryptoSuite(cryptoSuite);
        return ret;
    }

    public void setCryptoSuite(CryptoSuite cryptoSuite) {
        this.cryptoSuite = cryptoSuite;
    }

    public CryptoSuite getCryptoSuite() {
        return cryptoSuite;
    }

    /**
     * Register a user.
     *
     * @param request   Registration request with the following fields: name, role.
     * @param registrar The identity of the registrar (i.e. who is performing the registration).
     * @return the enrollment secret.
     * @throws RegistrationException    if registration fails.
     * @throws InvalidArgumentException
     */

    public String register(RegistrationRequest request, User registrar)
            throws RegistrationException, InvalidArgumentException {

        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        if (Utils.isNullOrEmpty(request.getEnrollmentID())) {
            throw new InvalidArgumentException("EntrollmentID cannot be null or empty");
        }

        if (registrar == null) {
            throw new InvalidArgumentException("Registrar should be a valid member");
        }
        logger.debug(format("register  url: %s, registrar: %s", url, registrar.getName()));

        setUpSSL();

        try {
            String body = request.toJson();
            JsonObject resp = httpPost(url + HFCA_REGISTER, body, registrar);
            String secret = resp.getString("secret");
            if (secret == null) {
                throw new Exception("secret was not found in response");
            }
            logger.debug(format("register  url: %s, registrar: %s done.", url, registrar));
            return secret;
        } catch (Exception e) {

            RegistrationException registrationException = new RegistrationException(
                    format("Error while registering the user %s url: %s  %s ", registrar, url, e.getMessage()), e);
            logger.error(registrar);
            throw registrationException;

        }

    }

    /**
     * Enroll the user with member service
     *
     * @param user   Identity name to enroll
     * @param secret Secret returned via registration
     * @return enrollment
     * @throws EnrollmentException
     * @throws InvalidArgumentException
     */
    public Enrollment enroll(String user, String secret) throws EnrollmentException, InvalidArgumentException {
        return enroll(user, secret, new EnrollmentRequest());
    }

    /**
     * Enroll the user with member service
     *
     * @param user   Identity name to enroll
     * @param secret Secret returned via registration
     * @param req    Enrollment request with the following fields: hosts, profile, csr, label, keypair
     * @return enrollment
     * @throws EnrollmentException
     * @throws InvalidArgumentException
     */

    public Enrollment enroll(String user, String secret, EnrollmentRequest req)
            throws EnrollmentException, InvalidArgumentException {

        logger.debug(format("url:%s enroll user: %s", url, user));

        if (Utils.isNullOrEmpty(user)) {
            throw new InvalidArgumentException("enrollment user is not set");
        }
        if (Utils.isNullOrEmpty(secret)) {
            throw new InvalidArgumentException("enrollment secret is not set");
        }

        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        setUpSSL();

        try {
            String pem = req.getCsr();
            KeyPair keypair = req.getKeyPair();
            if (null != pem && keypair == null) {
                throw new InvalidArgumentException(
                        "If certificate signing request is supplied the key pair needs to be supplied too.");
            }
            if (keypair == null) {
                logger.debug("[HFCAClient.enroll] Generating keys...");

                // generate ECDSA keys: signing and encryption keys
                keypair = cryptoSuite.keyGen();

                logger.debug("[HFCAClient.enroll] Generating keys...done!");
            }

            if (pem == null) {
                String csr = cryptoSuite.generateCertificationRequest(user, keypair);
                req.setCSR(csr);
            }

            if (caName != null && !caName.isEmpty()) {
                req.setCAName(caName);
            }
            String body = req.toJson();

            String responseBody = httpPost(url + HFCA_ENROLL, body, new UsernamePasswordCredentials(user, secret));

            logger.debug("response:" + responseBody);

            JsonReader reader = Json.createReader(new StringReader(responseBody));
            JsonObject jsonst = (JsonObject) reader.read();

            boolean success = jsonst.getBoolean("success");
            logger.debug(format("[HFCAClient] enroll success:[%s]", success));

            if (!success) {
                throw new EnrollmentException(
                        format("FabricCA failed enrollment for user %s response success is false.", user));
            }

            JsonObject result = jsonst.getJsonObject("result");
            if (result == null) {
                throw new EnrollmentException(
                        format("FabricCA failed enrollment for user %s - response did not contain a result", user));
            }

            Base64.Decoder b64dec = Base64.getDecoder();

            String signedPem = new String(b64dec.decode(result.getString("Cert").getBytes(UTF_8)));
            logger.debug(format("[HFCAClient] enroll returned pem:[%s]", signedPem));

            JsonArray messages = jsonst.getJsonArray("messages");
            if (messages != null && !messages.isEmpty()) {
                JsonObject jo = messages.getJsonObject(0);
                String message = format("Enroll request response message [code %d]: %s", jo.getInt("code"),
                        jo.getString("message"));
                logger.info(message);
            }
            logger.debug("Enrollment done.");

            return new X509Enrollment(keypair, signedPem);

        } catch (EnrollmentException ee) {
            logger.error(format("url:%s, user:%s  error:%s", url, user, ee.getMessage()), ee);
            throw ee;

        } catch (Exception e) {
            EnrollmentException ee = new EnrollmentException(format("Url:%s, Failed to enroll user %s ", url, user),
                    e);
            logger.error(e.getMessage(), e);
            throw ee;
        }

    }

    /**
     * Return information on the Fabric Certificate Authority.
     * No credentials are needed for this API.
     *
     * @return {@link HFCAInfo}
     * @throws InfoException
     * @throws InvalidArgumentException
     */

    public HFCAInfo info() throws InfoException, InvalidArgumentException {

        logger.debug(format("info url:%s", url));
        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        setUpSSL();

        try {

            JsonObjectBuilder factory = Json.createObjectBuilder();

            if (caName != null) {
                factory.add(HFCAClient.FABRIC_CA_REQPROP, caName);
            }
            JsonObject body = factory.build();

            String responseBody = httpPost(url + HFCA_INFO, body.toString(), (UsernamePasswordCredentials) null);

            logger.debug("response:" + responseBody);

            JsonReader reader = Json.createReader(new StringReader(responseBody));
            JsonObject jsonst = (JsonObject) reader.read();

            boolean success = jsonst.getBoolean("success");
            logger.debug(format("[HFCAClient] enroll success:[%s]", success));

            if (!success) {
                throw new EnrollmentException(format("FabricCA failed info %s", url));
            }

            JsonObject result = jsonst.getJsonObject("result");
            if (result == null) {
                throw new InfoException(
                        format("FabricCA info error  - response did not contain a result url %s", url));
            }

            String caName = result.getString("CAName");
            String caChain = result.getString("CAChain");
            String version = null;
            if (result.containsKey("Version")) {
                version = result.getString("Version");
            }
            String issuerPublicKey = null;
            if (result.containsKey("IssuerPublicKey")) {
                issuerPublicKey = result.getString("IssuerPublicKey");
            }
            String issuerRevocationPublicKey = null;
            if (result.containsKey("IssuerRevocationPublicKey")) {
                issuerRevocationPublicKey = result.getString("IssuerRevocationPublicKey");
            }

            logger.info(format("CA Name: %s, Version: %s, issuerPublicKey: %s, issuerRevocationPublicKey: %s",
                    caName, caChain, issuerPublicKey, issuerRevocationPublicKey));
            return new HFCAInfo(caName, caChain, version, issuerPublicKey, issuerRevocationPublicKey);

        } catch (Exception e) {
            InfoException ee = new InfoException(format("Url:%s, Failed to get info", url), e);
            logger.error(e.getMessage(), e);
            throw ee;
        }

    }

    /**
     * Re-Enroll the user with member service
     *
     * @param user User to be re-enrolled
     * @return enrollment
     * @throws EnrollmentException
     * @throws InvalidArgumentException
     */
    public Enrollment reenroll(User user) throws EnrollmentException, InvalidArgumentException {
        return reenroll(user, new EnrollmentRequest());
    }

    /**
     * Re-Enroll the user with member service
     *
     * @param user User to be re-enrolled
     * @param req  Enrollment request with the following fields: hosts, profile, csr, label
     * @return enrollment
     * @throws EnrollmentException
     * @throws InvalidArgumentException
     */

    public Enrollment reenroll(User user, EnrollmentRequest req)
            throws EnrollmentException, InvalidArgumentException {

        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        if (user == null) {
            throw new InvalidArgumentException("reenrollment user is missing");
        }
        if (user.getEnrollment() == null) {
            throw new InvalidArgumentException("reenrollment user is not a valid user object");
        }

        logger.debug(format("re-enroll user: %s, url: %s", user.getName(), url));

        try {
            setUpSSL();

            PublicKey publicKey = cryptoSuite
                    .bytesToCertificate(user.getEnrollment().getCert().getBytes(StandardCharsets.UTF_8))
                    .getPublicKey();

            KeyPair keypair = new KeyPair(publicKey, user.getEnrollment().getKey());

            // generate CSR

            String pem = cryptoSuite.generateCertificationRequest(user.getName(), keypair);

            // build request body
            req.setCSR(pem);
            if (caName != null && !caName.isEmpty()) {
                req.setCAName(caName);
            }
            String body = req.toJson();

            // build authentication header
            JsonObject result = httpPost(url + HFCA_REENROLL, body, user);

            // get new cert from response
            Base64.Decoder b64dec = Base64.getDecoder();
            String signedPem = new String(b64dec.decode(result.getString("Cert").getBytes(UTF_8)));
            logger.debug(format("[HFCAClient] re-enroll returned pem:[%s]", signedPem));

            logger.debug(format("reenroll user %s done.", user.getName()));
            return new X509Enrollment(keypair, signedPem);

        } catch (EnrollmentException ee) {
            logger.error(ee.getMessage(), ee);
            throw ee;
        } catch (Exception e) {
            EnrollmentException ee = new EnrollmentException(format("Failed to re-enroll user %s", user), e);
            logger.error(e.getMessage(), e);
            throw ee;
        }
    }

    /**
     * revoke one enrollment of user
     *
     * @param revoker    admin user who has revoker attribute configured in CA-server
     * @param enrollment the user enrollment to be revoked
     * @param reason     revoke reason, see RFC 5280
     * @throws RevocationException
     * @throws InvalidArgumentException
     */

    public void revoke(User revoker, Enrollment enrollment, String reason)
            throws RevocationException, InvalidArgumentException {
        revokeInternal(revoker, enrollment, reason, false);
    }

    /**
     * revoke one enrollment of user
     *
     * @param revoker    admin user who has revoker attribute configured in CA-server
     * @param enrollment the user enrollment to be revoked
     * @param reason     revoke reason, see RFC 5280
     * @param genCRL     generate CRL list
     * @throws RevocationException
     * @throws InvalidArgumentException
     */

    public String revoke(User revoker, Enrollment enrollment, String reason, boolean genCRL)
            throws RevocationException, InvalidArgumentException {
        return revokeInternal(revoker, enrollment, reason, genCRL);
    }

    private String revokeInternal(User revoker, Enrollment enrollment, String reason, boolean genCRL)
            throws RevocationException, InvalidArgumentException {

        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        if (enrollment == null) {
            throw new InvalidArgumentException("revokee enrollment is not set");
        }
        if (revoker == null) {
            throw new InvalidArgumentException("revoker is not set");
        }

        logger.debug(format("revoke revoker: %s, reason: %s, url: %s", revoker.getName(), reason, url));

        try {
            setUpSSL();

            // get cert from to-be-revoked enrollment
            BufferedInputStream pem = new BufferedInputStream(
                    new ByteArrayInputStream(enrollment.getCert().getBytes()));
            CertificateFactory certFactory = CertificateFactory
                    .getInstance(Config.getConfig().getCertificateFormat());
            X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(pem);

            // get its serial number
            String serial = DatatypeConverter.printHexBinary(certificate.getSerialNumber().toByteArray());

            // get its aki
            // 2.5.29.35 : AuthorityKeyIdentifier
            byte[] extensionValue = certificate.getExtensionValue(Extension.authorityKeyIdentifier.getId());
            ASN1OctetString akiOc = ASN1OctetString.getInstance(extensionValue);
            String aki = DatatypeConverter
                    .printHexBinary(AuthorityKeyIdentifier.getInstance(akiOc.getOctets()).getKeyIdentifier());

            // build request body
            RevocationRequest req = new RevocationRequest(caName, null, serial, aki, reason, genCRL);
            String body = req.toJson();

            // send revoke request
            JsonObject resp = httpPost(url + HFCA_REVOKE, body, revoker);
            logger.debug("revoke done");

            if (genCRL) {
                if (resp.isEmpty()) {
                    throw new RevocationException("Failed to return CRL, revoke response is empty");
                }
                if (resp.isNull("CRL")) {
                    throw new RevocationException("Failed to return CRL");
                }
                return resp.getString("CRL");
            }
            return null;
        } catch (CertificateException e) {
            logger.error("Cannot validate certificate. Error is: " + e.getMessage());
            throw new RevocationException("Error while revoking cert. " + e.getMessage(), e);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RevocationException("Error while revoking the user. " + e.getMessage(), e);

        }
    }

    /**
     * revoke one user (including his all enrollments)
     *
     * @param revoker admin user who has revoker attribute configured in CA-server
     * @param revokee user who is to be revoked
     * @param reason  revoke reason, see RFC 5280
     * @throws RevocationException
     * @throws InvalidArgumentException
     */

    public void revoke(User revoker, String revokee, String reason)
            throws RevocationException, InvalidArgumentException {
        revokeInternal(revoker, revokee, reason, false);
    }

    /**
     * revoke one user (including his all enrollments)
     *
     * @param revoker admin user who has revoker attribute configured in CA-server
     * @param revokee user who is to be revoked
     * @param reason  revoke reason, see RFC 5280
     * @param genCRL  generate CRL
     * @throws RevocationException
     * @throws InvalidArgumentException
     */

    public String revoke(User revoker, String revokee, String reason, boolean genCRL)
            throws RevocationException, InvalidArgumentException {
        return revokeInternal(revoker, revokee, reason, genCRL);
    }

    private String revokeInternal(User revoker, String revokee, String reason, boolean genCRL)
            throws RevocationException, InvalidArgumentException {

        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        logger.debug(format("revoke revoker: %s, revokee: %s, reason: %s", revoker, revokee, reason));

        if (Utils.isNullOrEmpty(revokee)) {
            throw new InvalidArgumentException("revokee user is not set");
        }
        if (revoker == null) {
            throw new InvalidArgumentException("revoker is not set");
        }

        try {
            setUpSSL();

            // build request body
            RevocationRequest req = new RevocationRequest(caName, revokee, null, null, reason, genCRL);
            String body = req.toJson();

            // send revoke request
            JsonObject resp = httpPost(url + HFCA_REVOKE, body, revoker);

            logger.debug(format("revoke revokee: %s done.", revokee));

            if (genCRL) {
                if (resp.isEmpty()) {
                    throw new RevocationException("Failed to return CRL, revoke response is empty");
                }
                if (resp.isNull("CRL")) {
                    throw new RevocationException("Failed to return CRL");
                }
                return resp.getString("CRL");
            }
            return null;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RevocationException("Error while revoking the user. " + e.getMessage(), e);
        }
    }

    /**
     * revoke one certificate
     *
     * @param revoker admin user who has revoker attribute configured in CA-server
     * @param serial  serial number of the certificate to be revoked
     * @param aki     aki of the certificate to be revoke
     * @param reason  revoke reason, see RFC 5280
     * @throws RevocationException
     * @throws InvalidArgumentException
     */

    public void revoke(User revoker, String serial, String aki, String reason)
            throws RevocationException, InvalidArgumentException {
        revokeInternal(revoker, serial, aki, reason, false);
    }

    /**
     * revoke one enrollment of user
     *
     * @param revoker admin user who has revoker attribute configured in CA-server
     * @param serial  serial number of the certificate to be revoked
     * @param aki     aki of the certificate to be revoke
     * @param reason  revoke reason, see RFC 5280
     * @param genCRL  generate CRL list
     * @throws RevocationException
     * @throws InvalidArgumentException
     */

    public String revoke(User revoker, String serial, String aki, String reason, boolean genCRL)
            throws RevocationException, InvalidArgumentException {
        return revokeInternal(revoker, serial, aki, reason, genCRL);
    }

    private String revokeInternal(User revoker, String serial, String aki, String reason, boolean genCRL)
            throws RevocationException, InvalidArgumentException {

        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        if (Utils.isNullOrEmpty(serial)) {
            throw new IllegalArgumentException("Serial number id required to revoke ceritificate");
        }
        if (Utils.isNullOrEmpty(aki)) {
            throw new IllegalArgumentException("AKI is required to revoke certificate");
        }
        if (revoker == null) {
            throw new InvalidArgumentException("revoker is not set");
        }

        logger.debug(format("revoke revoker: %s, reason: %s, url: %s", revoker.getName(), reason, url));

        try {
            setUpSSL();

            // build request body
            RevocationRequest req = new RevocationRequest(caName, null, serial, aki, reason, genCRL);
            String body = req.toJson();

            // send revoke request
            JsonObject resp = httpPost(url + HFCA_REVOKE, body, revoker);
            logger.debug("revoke done");

            if (genCRL) {
                if (resp.isEmpty()) {
                    throw new RevocationException("Failed to return CRL, revoke response is empty");
                }
                if (resp.isNull("CRL")) {
                    throw new RevocationException("Failed to return CRL");
                }
                return resp.getString("CRL");
            }
            return null;
        } catch (CertificateException e) {
            logger.error("Cannot validate certificate. Error is: " + e.getMessage());
            throw new RevocationException("Error while revoking cert. " + e.getMessage(), e);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RevocationException("Error while revoking the user. " + e.getMessage(), e);
        }
    }

    /**
     * Generate certificate revocation list.
     *
     * @param registrar     admin user configured in CA-server
     * @param revokedBefore Restrict certificates returned to revoked before this date if not null.
     * @param revokedAfter  Restrict certificates returned to revoked after this date if not null.
     * @param expireBefore  Restrict certificates returned to expired before this date if not null.
     * @param expireAfter   Restrict certificates returned to expired after this date if not null.
     * @throws InvalidArgumentException
     */

    public String generateCRL(User registrar, Date revokedBefore, Date revokedAfter, Date expireBefore,
            Date expireAfter) throws InvalidArgumentException, GenerateCRLException {

        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        if (registrar == null) {
            throw new InvalidArgumentException("registrar is not set");
        }

        try {
            setUpSSL();

            //---------------------------------------
            JsonObjectBuilder factory = Json.createObjectBuilder();
            if (revokedBefore != null) {
                factory.add("revokedBefore", Util.dateToString(revokedBefore));
            }
            if (revokedAfter != null) {
                factory.add("revokedAfter", Util.dateToString(revokedAfter));
            }
            if (expireBefore != null) {
                factory.add("expireBefore", Util.dateToString(expireBefore));
            }
            if (expireAfter != null) {
                factory.add("expireAfter", Util.dateToString(expireAfter));
            }
            if (caName != null) {
                factory.add(HFCAClient.FABRIC_CA_REQPROP, caName);
            }

            JsonObject jsonObject = factory.build();

            StringWriter stringWriter = new StringWriter();
            JsonWriter jsonWriter = Json.createWriter(new PrintWriter(stringWriter));
            jsonWriter.writeObject(jsonObject);
            jsonWriter.close();
            String body = stringWriter.toString();

            //---------------------------------------

            // send revoke request
            JsonObject ret = httpPost(url + HFCA_GENCRL, body, registrar);

            return ret.getString("CRL");

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new GenerateCRLException(e.getMessage(), e);
        }
    }

    /**
     * Creates a new HFCA Identity object
     *
     * @param enrollmentID The enrollment ID associated for this identity
     * @return HFCAIdentity object
     * @throws InvalidArgumentException Invalid (null) argument specified
     */

    public HFCAIdentity newHFCAIdentity(String enrollmentID) throws InvalidArgumentException {
        return new HFCAIdentity(enrollmentID, this);
    }

    /**
     * gets all identities that the registrar is allowed to see
     *
     * @param registrar The identity of the registrar (i.e. who is performing the registration).
     * @return the identity that was requested
     * @throws IdentityException        if adding an identity fails.
     * @throws InvalidArgumentException Invalid (null) argument specified
     */

    public Collection<HFCAIdentity> getHFCAIdentities(User registrar)
            throws IdentityException, InvalidArgumentException {
        if (registrar == null) {
            throw new InvalidArgumentException("Registrar should be a valid member");
        }

        logger.debug(format("identity  url: %s, registrar: %s", url, registrar.getName()));

        try {
            JsonObject result = httpGet(HFCAIdentity.HFCA_IDENTITY, registrar);

            Collection<HFCAIdentity> allIdentities = new ArrayList<>();

            JsonArray identities = result.getJsonArray("identities");
            if (identities != null && !identities.isEmpty()) {
                for (int i = 0; i < identities.size(); i++) {
                    JsonObject identity = identities.getJsonObject(i);
                    HFCAIdentity idObj = new HFCAIdentity(identity);
                    allIdentities.add(idObj);
                }
            }

            logger.debug(format("identity  url: %s, registrar: %s done.", url, registrar));
            return allIdentities;
        } catch (HTTPException e) {
            String msg = format("[HTTP Status Code: %d] - Error while getting all users from url '%s': %s",
                    e.getStatusCode(), url, e.getMessage());
            IdentityException identityException = new IdentityException(msg, e);
            logger.error(msg);
            throw identityException;
        } catch (Exception e) {
            String msg = format("Error while getting all users from url '%s': %s", url, e.getMessage());
            IdentityException identityException = new IdentityException(msg, e);
            logger.error(msg);
            throw identityException;
        }

    }

    /**
     * @param name Name of the affiliation
     * @return HFCAAffiliation object
     * @throws InvalidArgumentException Invalid (null) argument specified
     */
    public HFCAAffiliation newHFCAAffiliation(String name) throws InvalidArgumentException {
        return new HFCAAffiliation(name, this);
    }

    /**
     * gets all affiliations that the registrar is allowed to see
     *
     * @param registrar The identity of the registrar (i.e. who is performing the registration).
     * @return The affiliations that were requested
     * @throws AffiliationException     if getting all affiliations fails
     * @throws InvalidArgumentException
     */

    public HFCAAffiliation getHFCAAffiliations(User registrar)
            throws AffiliationException, InvalidArgumentException {
        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set.");
        }

        if (registrar == null) {
            throw new InvalidArgumentException("Registrar should be a valid member");
        }

        logger.debug(format("affiliations  url: %s, registrar: %s", url, registrar.getName()));

        try {
            JsonObject result = httpGet(HFCAAffiliation.HFCA_AFFILIATION, registrar);
            HFCAAffiliation affiliations = new HFCAAffiliation(result);

            logger.debug(format("affiliations  url: %s, registrar: %s done.", url, registrar));
            return affiliations;
        } catch (HTTPException e) {
            String msg = format("[HTTP Status Code: %d] - Error while getting all affiliations from url '%s': %s",
                    e.getStatusCode(), url, e.getMessage());
            AffiliationException affiliationException = new AffiliationException(msg, e);
            logger.error(msg);
            throw affiliationException;
        } catch (Exception e) {
            String msg = format("Error while getting all affiliations from url '%s': %s", url, e.getMessage());
            AffiliationException affiliationException = new AffiliationException(msg, e);
            logger.error(msg);
            throw affiliationException;
        }

    }

    /**
     * @return HFCACertificateRequest object
     */
    public HFCACertificateRequest newHFCACertificateRequest() {
        return new HFCACertificateRequest();
    }

    /** idemixEnroll returns an Identity Mixer Enrollment, which supports anonymity and unlinkability
     *
     * @param enrollment a x509 enrollment credential
     * @return IdemixEnrollment
     * @throws EnrollmentException
     * @throws InvalidArgumentException
     */
    public Enrollment idemixEnroll(Enrollment enrollment, String mspID)
            throws EnrollmentException, InvalidArgumentException {
        if (cryptoSuite == null) {
            throw new InvalidArgumentException("Crypto primitives not set");
        }

        if (enrollment == null) {
            throw new InvalidArgumentException("enrollment is missing");
        }

        if (Utils.isNullOrEmpty(mspID)) {
            throw new InvalidArgumentException("mspID cannot be null or empty");
        }

        if (enrollment instanceof IdemixEnrollment) {
            throw new InvalidArgumentException("enrollment type must be x509");
        }
        final RAND rng = IdemixUtils.getRand();
        try {
            setUpSSL();

            // Get nonce
            IdemixEnrollmentRequest idemixEnrollReq = new IdemixEnrollmentRequest();
            String body = idemixEnrollReq.toJson();
            JsonObject result = httpPost(url + HFCA_IDEMIXCRED, body, enrollment);
            if (result == null) {
                throw new EnrollmentException("No response received for idemix enrollment request");
            }
            String nonceString = result.getString("Nonce");
            if (Utils.isNullOrEmpty(nonceString)) {
                throw new InvalidArgumentException(
                        "fabric-ca-server did not return a nonce in the response from " + HFCA_IDEMIXCRED);
            }
            byte[] nonceBytes = Base64.getDecoder().decode(nonceString.getBytes());
            BIG nonce = BIG.fromBytes(nonceBytes);

            // Get issuer public key and revocation key from the cainfo section of response
            JsonObject info = result.getJsonObject("CAInfo");
            if (info == null) {
                throw new Exception(
                        "fabric-ca-server did not return 'cainfo' in the response from " + HFCA_IDEMIXCRED);
            }
            IdemixIssuerPublicKey ipk = getIssuerPublicKey(info.getString("IssuerPublicKey"));
            PublicKey rpk = getRevocationPublicKey(info.getString("IssuerRevocationPublicKey"));

            // Create and send idemix credential request
            BIG sk = new BIG(IdemixUtils.randModOrder(rng));
            IdemixCredRequest idemixCredRequest = new IdemixCredRequest(sk, nonce, ipk);
            idemixEnrollReq.setIdemixCredReq(idemixCredRequest);
            body = idemixEnrollReq.toJson();
            result = httpPost(url + HFCA_IDEMIXCRED, body, enrollment);
            if (result == null) {
                throw new EnrollmentException("No response received for idemix enrollment request");
            }

            // Deserialize idemix credential
            String credential = result.getString("Credential");
            if (Utils.isNullOrEmpty(credential)) {
                throw new InvalidArgumentException(
                        "fabric-ca-server did not return a 'credential' in the response from " + HFCA_IDEMIXCRED);
            }
            byte[] credBytes = Base64.getDecoder().decode(credential.getBytes(UTF_8));
            Idemix.Credential credProto = Idemix.Credential.parseFrom(credBytes);
            IdemixCredential cred = new IdemixCredential(credProto);

            // Deserialize idemix cri (Credential Revocation Information)
            String criStr = result.getString("CRI");
            if (Utils.isNullOrEmpty(criStr)) {
                throw new InvalidArgumentException(
                        "fabric-ca-server did not return a 'CRI' in the response from " + HFCA_IDEMIXCRED);
            }
            byte[] criBytes = Base64.getDecoder().decode(criStr.getBytes(UTF_8));
            Idemix.CredentialRevocationInformation cri = Idemix.CredentialRevocationInformation.parseFrom(criBytes);

            JsonObject attrs = result.getJsonObject("Attrs");
            if (attrs == null) {
                throw new EnrollmentException(
                        "fabric-ca-server did not return 'attrs' in the response from " + HFCA_IDEMIXCRED);
            }
            String ou = attrs.getString("OU");
            if (Utils.isNullOrEmpty(ou)) {
                throw new InvalidArgumentException(
                        "fabric-ca-server did not return a 'ou' attribute in the response from " + HFCA_IDEMIXCRED);
            }
            int role = attrs.getInt("Role"); // Encoded IdemixRole from Fabric-Ca

            // Return the idemix enrollment
            return new IdemixEnrollment(ipk, rpk, mspID, sk, cred, cri, ou, role);

        } catch (EnrollmentException ee) {
            logger.error(ee.getMessage(), ee);
            throw ee;
        } catch (Exception e) {
            EnrollmentException ee = new EnrollmentException("Failed to get Idemix credential", e);
            logger.error(e.getMessage(), e);
            throw ee;
        }
    }

    private IdemixIssuerPublicKey getIssuerPublicKey(String str)
            throws EnrollmentException, InvalidProtocolBufferException {
        if (Utils.isNullOrEmpty(str)) {
            throw new EnrollmentException(
                    "fabric-ca-server did not return 'issuerPublicKey' in the response from " + HFCA_IDEMIXCRED);
        }
        byte[] ipkBytes = Base64.getDecoder().decode(str.getBytes());
        Idemix.IssuerPublicKey ipkProto = Idemix.IssuerPublicKey.parseFrom(ipkBytes);
        return new IdemixIssuerPublicKey(ipkProto);
    }

    private PublicKey getRevocationPublicKey(String str)
            throws EnrollmentException, IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        if (Utils.isNullOrEmpty(str)) {
            throw new EnrollmentException(
                    "fabric-ca-server did not return 'issuerPublicKey' in the response from " + HFCA_IDEMIXCRED);
        }
        String pem = new String(Base64.getDecoder().decode(str));
        byte[] der = convertPemToDer(pem);
        return KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(der));
    }

    private byte[] convertPemToDer(String pem) throws IOException {
        PemReader pemReader = new PemReader(new StringReader(pem));
        return pemReader.readPemObject().getContent();
    }

    /**
     * Gets all certificates that the registrar is allowed to see and based on filter parameters that
     * are part of the certificate request.
     *
     * @param registrar The identity of the registrar (i.e. who is performing the registration).
     * @param req The certificate request that contains filter parameters
     * @return HFCACertificateResponse object
     * @throws HFCACertificateException Failed to process get certificate request
     */
    public HFCACertificateResponse getHFCACertificates(User registrar, HFCACertificateRequest req)
            throws HFCACertificateException {
        try {
            logger.debug(format("certificate url: %s, registrar: %s", HFCA_CERTIFICATE, registrar.getName()));

            JsonObject result = httpGet(HFCA_CERTIFICATE, registrar, req.getQueryParameters());

            int statusCode = result.getInt("statusCode");
            Collection<HFCACredential> certs = new ArrayList<>();
            if (statusCode < 400) {
                JsonArray certificates = result.getJsonArray("certs");
                if (certificates != null && !certificates.isEmpty()) {
                    for (int i = 0; i < certificates.size(); i++) {
                        String certPEM = certificates.getJsonObject(i).getString("PEM");
                        certs.add(new HFCAX509Certificate(certPEM));
                    }
                }
                logger.debug(format("certificate url: %s, registrar: %s done.", HFCA_CERTIFICATE, registrar));
            }
            return new HFCACertificateResponse(statusCode, certs);
        } catch (HTTPException e) {
            String msg = format("[Code: %d] - Error while getting certificates from url '%s': %s",
                    e.getStatusCode(), HFCA_CERTIFICATE, e.getMessage());
            HFCACertificateException certificateException = new HFCACertificateException(msg, e);
            logger.error(msg);
            throw certificateException;
        } catch (Exception e) {
            String msg = format("Error while getting certificates from url '%s': %s", HFCA_CERTIFICATE,
                    e.getMessage());
            HFCACertificateException certificateException = new HFCACertificateException(msg, e);
            logger.error(msg);
            throw certificateException;
        }
    }

    /**
     * Http Post Request.
     *
     * @param url         Target URL to POST to.
     * @param body        Body to be sent with the post.
     * @param credentials Credentials to use for basic auth.
     * @return Body of post returned.
     * @throws Exception
     */
    String httpPost(String url, String body, UsernamePasswordCredentials credentials) throws Exception {
        logger.debug(format("httpPost %s, body:%s", url, body));

        final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        CredentialsProvider provider = null;
        if (credentials != null) {
            provider = new BasicCredentialsProvider();

            provider.setCredentials(AuthScope.ANY, credentials);
            httpClientBuilder.setDefaultCredentialsProvider(provider);
        }

        if (registry != null) {

            httpClientBuilder.setConnectionManager(new PoolingHttpClientConnectionManager(registry));

        }

        HttpClient client = httpClientBuilder.build();

        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(getRequestConfig());

        AuthCache authCache = new BasicAuthCache();

        HttpHost targetHost = new HttpHost(httpPost.getURI().getHost(), httpPost.getURI().getPort());

        if (credentials != null) {
            authCache.put(targetHost, new

            BasicScheme());

        }

        final HttpClientContext context = HttpClientContext.create();

        if (null != provider) {
            context.setCredentialsProvider(provider);
        }

        if (credentials != null) {
            context.setAuthCache(authCache);
        }

        httpPost.setEntity(new StringEntity(body));
        if (credentials != null) {
            httpPost.addHeader(new

            BasicScheme().

                    authenticate(credentials, httpPost, context));
        }

        HttpResponse response = client.execute(httpPost, context);
        int status = response.getStatusLine().getStatusCode();

        HttpEntity entity = response.getEntity();
        logger.trace(format("httpPost %s  sending...", url));
        String responseBody = entity != null ? EntityUtils.toString(entity) : null;
        logger.trace(format("httpPost %s  responseBody %s", url, responseBody));

        if (status >= 400) {

            Exception e = new Exception(format(
                    "POST request to %s  with request body: %s, " + "failed with status code: %d. Response: %s",
                    url, body, status, responseBody));
            logger.error(e.getMessage());
            throw e;
        }
        logger.debug(format("httpPost Status: %d returning: %s ", status, responseBody));

        return responseBody;
    }

    JsonObject httpPost(String url, String body, User registrar) throws Exception {
        String authHTTPCert = getHTTPAuthCertificate(registrar.getEnrollment(), "POST", url, body);
        return post(url, body, authHTTPCert);
    }

    JsonObject httpPost(String url, String body, Enrollment enrollment) throws Exception {
        String authHTTPCert = getHTTPAuthCertificate(enrollment, "POST", url, body);
        return post(url, body, authHTTPCert);
    }

    JsonObject post(String url, String body, String authHTTPCert) throws Exception {
        url = addCAToURL(url);
        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(getRequestConfig());
        logger.debug(format("httpPost %s, body:%s, authHTTPCert: %s", url, body, authHTTPCert));

        final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        if (registry != null) {
            httpClientBuilder.setConnectionManager(new PoolingHttpClientConnectionManager(registry));
        }
        HttpClient client = httpClientBuilder.build();

        final HttpClientContext context = HttpClientContext.create();
        httpPost.setEntity(new StringEntity(body));
        httpPost.addHeader("Authorization", authHTTPCert);

        HttpResponse response = client.execute(httpPost, context);

        return getResult(response, body, "POST");
    }

    JsonObject httpGet(String url, User registrar) throws Exception {
        return httpGet(url, registrar, null);
    }

    JsonObject httpGet(String url, User registrar, Map<String, String> queryMap) throws Exception {
        String getURL = getURL(url, queryMap);
        String authHTTPCert = getHTTPAuthCertificate(registrar.getEnrollment(), "GET", getURL, "");
        HttpGet httpGet = new HttpGet(getURL);
        httpGet.setConfig(getRequestConfig());
        logger.debug(format("httpGet %s, authHTTPCert: %s", url, authHTTPCert));

        final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        if (registry != null) {
            httpClientBuilder.setConnectionManager(new PoolingHttpClientConnectionManager(registry));
        }
        HttpClient client = httpClientBuilder.build();

        final HttpClientContext context = HttpClientContext.create();
        httpGet.addHeader("Authorization", authHTTPCert);

        HttpResponse response = client.execute(httpGet, context);

        return getResult(response, "", "GET");
    }

    JsonObject httpPut(String url, String body, User registrar) throws Exception {
        String authHTTPCert = getHTTPAuthCertificate(registrar.getEnrollment(), "PUT", url, body);
        String putURL = addCAToURL(url);
        HttpPut httpPut = new HttpPut(putURL);
        httpPut.setConfig(getRequestConfig());
        logger.debug(format("httpPutt %s, body:%s, authHTTPCert: %s", url, body, authHTTPCert));

        final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        if (registry != null) {
            httpClientBuilder.setConnectionManager(new PoolingHttpClientConnectionManager(registry));
        }
        HttpClient client = httpClientBuilder.build();

        final HttpClientContext context = HttpClientContext.create();
        httpPut.setEntity(new StringEntity(body));
        httpPut.addHeader("Authorization", authHTTPCert);

        HttpResponse response = client.execute(httpPut, context);

        return getResult(response, body, "PUT");
    }

    JsonObject httpDelete(String url, User registrar) throws Exception {
        String authHTTPCert = getHTTPAuthCertificate(registrar.getEnrollment(), "DELETE", url, "");
        String deleteURL = addCAToURL(url);
        HttpDelete httpDelete = new HttpDelete(deleteURL);
        httpDelete.setConfig(getRequestConfig());
        logger.debug(format("httpPut %s, authHTTPCert: %s", url, authHTTPCert));

        final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        if (registry != null) {
            httpClientBuilder.setConnectionManager(new PoolingHttpClientConnectionManager(registry));
        }
        HttpClient client = httpClientBuilder.build();

        final HttpClientContext context = HttpClientContext.create();
        httpDelete.addHeader("Authorization", authHTTPCert);

        HttpResponse response = client.execute(httpDelete, context);

        return getResult(response, "", "DELETE");
    }

    JsonObject getResult(HttpResponse response, String body, String type)
            throws HTTPException, ParseException, IOException {

        int respStatusCode = response.getStatusLine().getStatusCode();
        HttpEntity entity = response.getEntity();
        logger.trace(format("response status %d, HttpEntity %s ", respStatusCode, "" + entity));
        String responseBody = entity != null ? EntityUtils.toString(entity) : null;
        logger.trace(format("responseBody: %s ", responseBody));

        // If the status code in the response is greater or equal to the status code set in the client object then an exception will
        // be thrown, otherwise, we continue to read the response and return any error code that is less than 'statusCode'
        if (respStatusCode >= statusCode) {
            HTTPException e = new HTTPException(
                    format("%s request to %s failed request body %s. Response: %s", type, url, body, responseBody),
                    respStatusCode);
            logger.error(e.getMessage());
            throw e;
        }
        if (responseBody == null) {

            HTTPException e = new HTTPException(
                    format("%s request to %s failed request body %s with null response body returned.", type, url,
                            body),
                    respStatusCode);
            logger.error(e.getMessage());
            throw e;

        }

        logger.debug("Status: " + respStatusCode);

        JsonReader reader = Json.createReader(new StringReader(responseBody));
        JsonObject jobj = (JsonObject) reader.read();

        JsonObjectBuilder job = Json.createObjectBuilder();
        job.add("statusCode", respStatusCode);

        JsonArray errors = jobj.getJsonArray("errors");
        // If the status code is greater than or equal to 400 but less than or equal to the client status code setting,
        // then encountered an error and we return back the status code, and log the error rather than throwing an exception.
        if (respStatusCode < statusCode && respStatusCode >= 400) {
            if (errors != null && !errors.isEmpty()) {
                JsonObject jo = errors.getJsonObject(0);
                String errorMsg = format(
                        "[HTTP Status Code: %d] - %s request to %s failed request body %s error message: [Error Code %d] - %s",
                        respStatusCode, type, url, body, jo.getInt("code"), jo.getString("message"));
                logger.error(errorMsg);
            }
            return job.build();
        }
        if (errors != null && !errors.isEmpty()) {
            JsonObject jo = errors.getJsonObject(0);
            HTTPException e = new HTTPException(
                    format("%s request to %s failed request body %s error message: [Error Code %d] - %s", type, url,
                            body, jo.getInt("code"), jo.getString("message")),
                    respStatusCode);
            throw e;
        }

        boolean success = jobj.getBoolean("success");
        if (!success) {
            HTTPException e = new HTTPException(
                    format("%s request to %s failed request body %s Body of response did not contain success", type,
                            url, body),
                    respStatusCode);
            logger.error(e.getMessage());
            throw e;
        }

        JsonObject result = jobj.getJsonObject("result");
        if (result == null) {
            HTTPException e = new HTTPException(
                    format("%s request to %s failed request body %s " + "Body of response did not contain result",
                            type, url, body),
                    respStatusCode);
            logger.error(e.getMessage());
            throw e;
        }

        JsonArray messages = jobj.getJsonArray("messages");
        if (messages != null && !messages.isEmpty()) {
            JsonObject jo = messages.getJsonObject(0);
            String message = format(
                    "%s request to %s failed request body %s response message: [Error Code %d] - %s", type, url,
                    body, jo.getInt("code"), jo.getString("message"));
            logger.info(message);
        }

        // Construct JSON object that contains the result and HTTP status code
        for (Entry<String, JsonValue> entry : result.entrySet()) {
            job.add(entry.getKey(), entry.getValue());
        }
        job.add("statusCode", respStatusCode);
        result = job.build();

        logger.debug(format("%s %s, body:%s result: %s", type, url, body, "" + result));
        return result;
    }

    String getHTTPAuthCertificate(Enrollment enrollment, String method, String url, String body) throws Exception {
        Base64.Encoder b64 = Base64.getEncoder();
        String cert = b64.encodeToString(enrollment.getCert().getBytes(UTF_8));
        body = b64.encodeToString(body.getBytes(UTF_8));
        String signString;
        // Cache the version, so don't need to make info call everytime the same client is used
        if (newPayloadType == null) {
            newPayloadType = true;

            // If CA version is less than 1.4.0, use old payload
            String caVersion = info().getVersion();
            logger.info(format("CA Version: %s", caVersion));

            if (Utils.isNullOrEmpty(caVersion)) {
                newPayloadType = false;
            }

            String version = caVersion + ".";
            if (version.startsWith("1.1.") || version.startsWith("1.2.") || version.startsWith("1.3.")) {
                newPayloadType = false;
            }
        }

        if (newPayloadType) {
            url = addCAToURL(url);
            String file = b64.encodeToString(new URL(url).getFile().getBytes(UTF_8));
            signString = method + "." + file + "." + body + "." + cert;
        } else {
            signString = body + "." + cert;
        }

        byte[] signature = cryptoSuite.sign(enrollment.getKey(), signString.getBytes(UTF_8));
        return cert + "." + b64.encodeToString(signature);
    }

    private Registry<ConnectionSocketFactory> registry = null;
    //Only use crypto primitives for reuse of its truststore on TLS
    CryptoPrimitives cryptoPrimitives = null;

    private void setUpSSL() throws InvalidArgumentException {

        if (cryptoPrimitives == null) {
            try {
                cryptoPrimitives = new CryptoPrimitives();
                cryptoPrimitives.init();
            } catch (Exception e) {
                throw new InvalidArgumentException(e);
            }
        }

        if (isSSL && null == registry) {
            if (!properties.containsKey("pemBytes") && !properties.containsKey("pemFile")) {

                logger.warn("SSL with no CA certficates in either pemBytes or pemFile");

            }
            try {

                if (properties.containsKey("pemBytes")) {
                    byte[] permbytes = (byte[]) properties.get("pemBytes");

                    try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(permbytes))) {
                        cryptoPrimitives.addCACertificatesToTrustStore(bis);
                    }

                }

                if (properties.containsKey("pemFile")) {
                    String pemFile = properties.getProperty("pemFile");
                    if (pemFile != null) {
                        String[] pems = pemFile.split("[ \t]*,[ \t]*");

                        for (String pem : pems) {
                            if (null != pem && !pem.isEmpty()) {
                                try {
                                    try (BufferedInputStream bis = new BufferedInputStream(
                                            new ByteArrayInputStream(Files.readAllBytes(Paths.get(pem))))) {
                                        cryptoPrimitives.addCACertificatesToTrustStore(bis);
                                    }
                                } catch (IOException e) {
                                    throw new InvalidArgumentException(
                                            format("Unable to add CA certificate, can't open certificate file %s",
                                                    new File(pem).getAbsolutePath()));
                                }
                            }
                        }
                    }
                }

                SSLContext sslContext = SSLContexts.custom()
                        .loadTrustMaterial(cryptoPrimitives.getTrustStore(), null).build();

                ConnectionSocketFactory sf;
                if (null != properties && "true".equals(properties.getProperty("allowAllHostNames"))) {
                    AllHostsSSLSocketFactory msf = new AllHostsSSLSocketFactory(cryptoPrimitives.getTrustStore());
                    msf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
                    sf = msf;
                } else {
                    sf = new SSLConnectionSocketFactory(sslContext);
                }

                registry = RegistryBuilder.<ConnectionSocketFactory>create().register("https", sf)
                        .register("http", new PlainConnectionSocketFactory()).build();

            } catch (Exception e) {
                logger.error(e);
                throw new InvalidArgumentException(e);
            }
        }

    }

    private class AllHostsSSLSocketFactory extends SSLSocketFactory {
        final SSLContext sslContext = SSLContext.getInstance("TLS");

        AllHostsSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException,
                KeyStoreException, UnrecoverableKeyException {
            super(truststore);

            TrustManager tm = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };

            sslContext.init(null, new TrustManager[] { tm }, null);
        }

        @Override
        public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
            return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
        }

        @Override
        public Socket createSocket() throws IOException {
            return sslContext.getSocketFactory().createSocket();
        }
    }

    String getURL(String endpoint) throws URISyntaxException, MalformedURLException, InvalidArgumentException {
        return getURL(endpoint, null);
    }

    String getURL(String endpoint, Map<String, String> queryMap)
            throws URISyntaxException, MalformedURLException, InvalidArgumentException {
        setUpSSL();
        String url = addCAToURL(this.url + endpoint);
        URIBuilder uri = new URIBuilder(url);
        if (queryMap != null) {
            for (Map.Entry<String, String> param : queryMap.entrySet()) {
                if (!Utils.isNullOrEmpty(param.getValue())) {
                    uri.addParameter(param.getKey(), param.getValue());
                }
            }
        }
        return uri.build().toURL().toString();
    }

    String addCAToURL(String url) throws URISyntaxException, MalformedURLException {
        URIBuilder uri = new URIBuilder(url);
        if (caName != null) {
            uri.addParameter("ca", caName);
        }
        return uri.build().toURL().toString();
    }

    // Convert the identity request to a JSON string
    String toJson(JsonObject toJsonFunc) {
        StringWriter stringWriter = new StringWriter();
        JsonWriter jsonWriter = Json.createWriter(new PrintWriter(stringWriter));
        jsonWriter.writeObject(toJsonFunc);
        jsonWriter.close();
        return stringWriter.toString();
    }

    private RequestConfig getRequestConfig() {

        RequestConfig.Builder ret = RequestConfig.custom();

        ret.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT);
        ret.setConnectTimeout(CONNECT_TIMEOUT);
        ret.setSocketTimeout(SOCKET_TIMEOUT);

        return ret.build();

    }

}