org.xipki.security.p11.remote.RemoteP11CryptService.java Source code

Java tutorial

Introduction

Here is the source code for org.xipki.security.p11.remote.RemoteP11CryptService.java

Source

/*
 *
 * This file is part of the XiPKI project.
 * Copyright (c) 2014 - 2015 Lijun Liao
 * Author: Lijun Liao
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the XiPKI software without
 * disclosing the source code of your own applications.
 *
 * For more information, please contact Lijun Liao at this
 * address: lijun.liao@gmail.com
 */

package org.xipki.security.p11.remote;

import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.cmp.ErrorMsgContent;
import org.bouncycastle.asn1.cmp.GenMsgContent;
import org.bouncycastle.asn1.cmp.GenRepContent;
import org.bouncycastle.asn1.cmp.InfoTypeAndValue;
import org.bouncycastle.asn1.cmp.PKIBody;
import org.bouncycastle.asn1.cmp.PKIHeader;
import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
import org.bouncycastle.asn1.cmp.PKIMessage;
import org.bouncycastle.asn1.cmp.PKIStatusInfo;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.cert.cmp.GeneralPKIMessage;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.common.ObjectIdentifiers;
import org.xipki.common.ParamChecker;
import org.xipki.common.XipkiCmpConstants;
import org.xipki.common.util.SecurityUtil;
import org.xipki.common.util.X509Util;
import org.xipki.security.api.SignerException;
import org.xipki.security.api.p11.P11CryptService;
import org.xipki.security.api.p11.P11KeyIdentifier;
import org.xipki.security.api.p11.P11ModuleConf;
import org.xipki.security.api.p11.P11SlotIdentifier;
import org.xipki.security.api.p11.remote.KeyIdentifier;
import org.xipki.security.api.p11.remote.PSOTemplate;
import org.xipki.security.api.p11.remote.SlotAndKeyIdentifer;
import org.xipki.security.api.p11.remote.SlotIdentifier;

/**
 * @author Lijun Liao
 */

public abstract class RemoteP11CryptService implements P11CryptService {
    private static final Logger LOG = LoggerFactory.getLogger(RemoteP11CryptService.class);
    private final Random random = new Random();

    private final GeneralName sender = XipkiCmpConstants.remotep11_cmp_client;
    private final GeneralName recipient = XipkiCmpConstants.remoteP11_cmp_server;

    private final P11ModuleConf moduleConf;

    public RemoteP11CryptService(final P11ModuleConf moduleConf) {
        ParamChecker.assertNotNull("moduleConf", moduleConf);
        this.moduleConf = moduleConf;
    }

    protected abstract byte[] send(byte[] request) throws IOException;

    public int getServerVersion() throws SignerException {
        ASN1Encodable result = send(XipkiCmpConstants.ACTION_RP11_VERSION, DERNull.INSTANCE);

        ASN1Integer derInt;
        try {
            derInt = ASN1Integer.getInstance(result);
        } catch (IllegalArgumentException e) {
            throw new SignerException("the returned result is not INTEGER");
        }

        return (derInt == null) ? 0 : derInt.getPositiveValue().intValue();
    }

    @Override
    public byte[] CKM_RSA_PKCS(final byte[] encodedDigestInfo, final P11SlotIdentifier slotId,
            final P11KeyIdentifier keyId) throws SignerException {
        checkSlotId(slotId);
        return pso(XipkiCmpConstants.ACTION_RP11_PSO_RSA_PKCS, encodedDigestInfo, slotId, keyId);
    }

    @Override
    public byte[] CKM_RSA_X509(final byte[] hash, final P11SlotIdentifier slotId, final P11KeyIdentifier keyId)
            throws SignerException {
        checkSlotId(slotId);
        return pso(XipkiCmpConstants.ACTION_RP11_PSO_RSA_X509, hash, slotId, keyId);
    }

    @Override
    public byte[] CKM_ECDSA_Plain(final byte[] hash, final P11SlotIdentifier slotId, final P11KeyIdentifier keyId)
            throws SignerException {
        checkSlotId(slotId);
        return pso(XipkiCmpConstants.ACTION_RP11_PSO_ECDSA_PLAIN, hash, slotId, keyId);
    }

    @Override
    public byte[] CKM_ECDSA_X962(final byte[] hash, final P11SlotIdentifier slotId, final P11KeyIdentifier keyId)
            throws SignerException {
        checkSlotId(slotId);
        return pso(XipkiCmpConstants.ACTION_RP11_PSO_ECDSA_X962, hash, slotId, keyId);
    }

    @Override
    public byte[] CKM_DSA_Plain(final byte[] hash, final P11SlotIdentifier slotId, P11KeyIdentifier keyId)
            throws SignerException {
        checkSlotId(slotId);
        return pso(XipkiCmpConstants.ACTION_RP11_PSO_DSA_PLAIN, hash, slotId, keyId);
    }

    @Override
    public byte[] CKM_DSA_X962(final byte[] hash, final P11SlotIdentifier slotId, P11KeyIdentifier keyId)
            throws SignerException {
        checkSlotId(slotId);
        return pso(XipkiCmpConstants.ACTION_RP11_PSO_DSA_X962, hash, slotId, keyId);
    }

    @Override
    public PublicKey getPublicKey(final P11SlotIdentifier slotId, final P11KeyIdentifier keyId)
            throws SignerException {
        checkSlotId(slotId);
        byte[] keyBytes = getCertOrKey(XipkiCmpConstants.ACTION_RP11_GET_PUBLICKEY, slotId, keyId);
        if (keyBytes == null) {
            throw new SignerException("received no public key from server for " + keyId);
        }

        return generatePublicKey(keyBytes);
    }

    @Override
    public X509Certificate getCertificate(final P11SlotIdentifier slotId, final P11KeyIdentifier keyId)
            throws SignerException {
        checkSlotId(slotId);
        byte[] certBytes = getCertOrKey(XipkiCmpConstants.ACTION_RP11_GET_CERTIFICATE, slotId, keyId);
        if (certBytes == null) {
            throw new SignerException("received no certificate from server for " + keyId);
        }

        try {
            return X509Util.parseCert(certBytes);
        } catch (CertificateException | IOException e) {
            throw new SignerException(e.getClass().getName() + ": " + e.getMessage(), e);
        }
    }

    @Override
    public X509Certificate[] getCertificates(final P11SlotIdentifier slotId, final P11KeyIdentifier keyId)
            throws SignerException {
        checkSlotId(slotId);
        X509Certificate cert = getCertificate(slotId, keyId);
        if (cert == null) {
            return null;
        }

        return new X509Certificate[] { cert };
    }

    private byte[] pso(final int action, final byte[] message, final P11SlotIdentifier slotId,
            final P11KeyIdentifier keyId) throws SignerException {
        SlotAndKeyIdentifer slotAndKeyIdentifier = buildSlotAndKeyIdentifier(slotId, keyId);
        PSOTemplate psoTemplate = new PSOTemplate(slotAndKeyIdentifier, message);
        ASN1Encodable result = send(action, psoTemplate);

        ASN1OctetString octetString;
        try {
            octetString = DEROctetString.getInstance(result);
        } catch (IllegalArgumentException e) {
            throw new SignerException("the returned result is not OCTETSTRING");
        }

        return (octetString == null) ? null : octetString.getOctets();
    }

    private byte[] getCertOrKey(final int action, final P11SlotIdentifier slotId, final P11KeyIdentifier keyId)
            throws SignerException {
        SlotAndKeyIdentifer slotAndKeyIdentifier = buildSlotAndKeyIdentifier(slotId, keyId);
        ASN1Encodable result = send(action, slotAndKeyIdentifier);

        ASN1OctetString octetString;
        try {
            octetString = DEROctetString.getInstance(result);
        } catch (IllegalArgumentException e) {
            throw new SignerException("the returned result is not OCTETSTRING");
        }

        return (octetString == null) ? null : octetString.getOctets();
    }

    private SlotAndKeyIdentifer buildSlotAndKeyIdentifier(final P11SlotIdentifier slotId,
            final P11KeyIdentifier keyId) {
        SlotIdentifier slotIdentifier = new SlotIdentifier(slotId);
        KeyIdentifier keyIdentifier = new KeyIdentifier(keyId);
        return new SlotAndKeyIdentifer(slotIdentifier, keyIdentifier);
    }

    private ASN1Encodable send(final int action, final ASN1Encodable content) throws SignerException {
        PKIHeader header = buildPKIHeader(null);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(action));
        if (content != null) {
            v.add(content);
        }
        InfoTypeAndValue itvReq = new InfoTypeAndValue(ObjectIdentifiers.id_xipki_cmp, new DERSequence(v));

        GenMsgContent genMsgContent = new GenMsgContent(itvReq);
        PKIBody body = new PKIBody(PKIBody.TYPE_GEN_MSG, genMsgContent);
        PKIMessage request = new PKIMessage(header, body);

        byte[] encodedRequest;
        try {
            encodedRequest = request.getEncoded();
        } catch (IOException e) {
            LOG.error("error while encode the PKI request {}", request);
            throw new SignerException(e.getMessage(), e);
        }

        byte[] encodedResponse;
        try {
            encodedResponse = send(encodedRequest);
        } catch (IOException e) {
            LOG.error("error while send the PKI request {} to server", request);
            throw new SignerException(e.getMessage(), e);
        }

        GeneralPKIMessage response;
        try {
            response = new GeneralPKIMessage(encodedResponse);
        } catch (IOException e) {
            LOG.error("error while decode the received PKI message: {}", Hex.toHexString(encodedResponse));
            throw new SignerException(e.getMessage(), e);
        }

        PKIHeader respHeader = response.getHeader();
        ASN1OctetString tid = respHeader.getTransactionID();
        GeneralName recipient = respHeader.getRecipient();
        if (sender.equals(recipient) == false) {
            LOG.warn("tid={}: unknown CMP requestor '{}'", tid, recipient);
        }

        return extractItvInfoValue(action, response);
    }

    private static ASN1Encodable extractItvInfoValue(final int action, final GeneralPKIMessage response)
            throws SignerException {
        PKIBody respBody = response.getBody();
        int bodyType = respBody.getType();

        if (PKIBody.TYPE_ERROR == bodyType) {
            ErrorMsgContent content = (ErrorMsgContent) respBody.getContent();
            PKIStatusInfo statusInfo = content.getPKIStatusInfo();
            throw new SignerException(
                    "server answered with ERROR: " + SecurityUtil.formatPKIStatusInfo(statusInfo));
        }

        else if (PKIBody.TYPE_GEN_REP != bodyType) {
            throw new SignerException("unknown PKI body type " + bodyType + " instead the exceptected ["
                    + PKIBody.TYPE_GEN_REP + ", " + PKIBody.TYPE_ERROR + "]");
        }

        GenRepContent genRep = (GenRepContent) respBody.getContent();

        InfoTypeAndValue[] itvs = genRep.toInfoTypeAndValueArray();
        InfoTypeAndValue itv = null;
        if (itvs != null && itvs.length > 0) {
            for (InfoTypeAndValue m : itvs) {
                if (ObjectIdentifiers.id_xipki_cmp.equals(m.getInfoType())) {
                    itv = m;
                    break;
                }
            }
        }
        if (itv == null) {
            throw new SignerException("the response does not contain InfoTypeAndValue '"
                    + ObjectIdentifiers.id_xipki_cmp.getId() + "'");
        }

        ASN1Encodable itvValue = itv.getInfoValue();
        if (itvValue == null) {
            throw new SignerException(
                    "value of InfoTypeAndValue '" + ObjectIdentifiers.id_xipki_cmp.getId() + "'  is incorrect");
        }
        try {
            ASN1Sequence seq = ASN1Sequence.getInstance(itvValue);
            int receivedAction = ASN1Integer.getInstance(seq.getObjectAt(0)).getPositiveValue().intValue();
            if (receivedAction != action) {
                throw new SignerException(
                        "xipki action '" + receivedAction + "'  is not the expected '" + action + "'");
            }
            return seq.size() > 1 ? seq.getObjectAt(1) : null;
        } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
            throw new SignerException("value of response (type nfoTypeAndValue) '"
                    + ObjectIdentifiers.id_xipki_cmp.getId() + "'  is incorrect");
        }
    }

    private PKIHeader buildPKIHeader(ASN1OctetString tid) {
        PKIHeaderBuilder hBuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, sender, recipient);
        hBuilder.setMessageTime(new ASN1GeneralizedTime(new Date()));

        if (tid == null) {
            tid = new DEROctetString(randomTransactionId());
        }
        hBuilder.setTransactionID(tid);

        return hBuilder.build();
    }

    private byte[] randomTransactionId() {
        byte[] tid = new byte[20];
        synchronized (random) {
            random.nextBytes(tid);
        }
        return tid;
    }

    private static PublicKey generatePublicKey(final byte[] encodedSubjectPublicKeyInfo) throws SignerException {
        SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(encodedSubjectPublicKeyInfo);

        X509EncodedKeySpec keyspec = new X509EncodedKeySpec(encodedSubjectPublicKeyInfo);
        ASN1ObjectIdentifier aid = pkInfo.getAlgorithm().getAlgorithm();

        KeyFactory kf;

        try {
            if (PKCSObjectIdentifiers.rsaEncryption.equals(aid)) {
                kf = KeyFactory.getInstance("RSA");
            } else if (X9ObjectIdentifiers.id_ecPublicKey.equals(aid)) {
                kf = KeyFactory.getInstance("ECDSA");
            } else if (X9ObjectIdentifiers.id_dsa.equals(aid)) {
                kf = KeyFactory.getInstance("DSA");
            } else {
                throw new SignerException("unsupported key algorithm: " + aid);
            }
        } catch (NoSuchAlgorithmException e) {
            throw new SignerException("NoSuchAlgorithmException: " + e.getMessage(), e);
        }

        try {
            return kf.generatePublic(keyspec);
        } catch (InvalidKeySpecException e) {
            throw new SignerException("InvalidKeySpecException: " + e.getMessage(), e);
        }
    }

    @Override
    public P11SlotIdentifier[] getSlotIdentifiers() throws SignerException {
        ASN1Encodable resp = send(XipkiCmpConstants.ACTION_RP11_LIST_SLOTS, null);
        if (resp instanceof ASN1Sequence == false) {
            throw new SignerException("response is not ASN1Sequence, but " + resp.getClass().getName());
        }

        ASN1Sequence seq = (ASN1Sequence) resp;
        int n = seq.size();

        List<P11SlotIdentifier> slotIds = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            SlotIdentifier asn1SlotId;
            try {
                ASN1Encodable obj = seq.getObjectAt(i);
                asn1SlotId = SlotIdentifier.getInstance(obj);
            } catch (Exception e) {
                throw new SignerException(e.getMessage(), e);
            }

            P11SlotIdentifier slotId = asn1SlotId.getSlotId();
            if (moduleConf.isSlotIncluded(slotId)) {
                slotIds.add(slotId);
            }
        }
        return slotIds.toArray(new P11SlotIdentifier[0]);
    }

    @Override
    public String[] getKeyLabels(final P11SlotIdentifier slotId) throws SignerException {
        checkSlotId(slotId);
        ASN1Encodable resp = send(XipkiCmpConstants.ACTION_RP11_LIST_KEYLABELS, new SlotIdentifier(slotId));
        if (resp instanceof ASN1Sequence == false) {
            throw new SignerException("response is not ASN1Sequence, but " + resp.getClass().getName());
        }

        ASN1Sequence seq = (ASN1Sequence) resp;
        int n = seq.size();

        String[] keyLabels = new String[n];
        for (int i = 0; i < n; i++) {
            ASN1Encodable obj = seq.getObjectAt(i);
            if (obj instanceof ASN1String == false) {
                throw new SignerException(
                        "object at index " + i + " is not ASN1String, but " + resp.getClass().getName());
            }
            keyLabels[i] = ((ASN1String) obj).getString();
        }

        return keyLabels;
    }

    private void checkSlotId(final P11SlotIdentifier slotId) throws SignerException {
        if (moduleConf.isSlotIncluded(slotId) == false) {
            throw new SignerException("cound not find slot (" + slotId.toString() + ")");
        }
    }

    public P11ModuleConf getModuleConf() {
        return moduleConf;
    }

}