org.cryptoworkshop.ximix.node.crypto.key.ECKeyManager.java Source code

Java tutorial

Introduction

Here is the source code for org.cryptoworkshop.ximix.node.crypto.key.ECKeyManager.java

Source

/**
 * Copyright 2013 Crypto Workshop Pty Ltd
 *
 * 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.cryptoworkshop.ximix.node.crypto.key;

import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.ContentInfo;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.sec.ECPrivateKey;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS12PfxPdu;
import org.bouncycastle.pkcs.PKCS12PfxPduBuilder;
import org.bouncycastle.pkcs.PKCS12SafeBag;
import org.bouncycastle.pkcs.PKCS12SafeBagBuilder;
import org.bouncycastle.pkcs.PKCS12SafeBagFactory;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.pkcs.jcajce.JcaPKCS12SafeBagBuilder;
import org.bouncycastle.pkcs.jcajce.JcePKCS12MacCalculatorBuilder;
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder;
import org.cryptoworkshop.ximix.common.asn1.PartialPublicKeyInfo;
import org.cryptoworkshop.ximix.common.asn1.XimixObjectIdentifiers;
import org.cryptoworkshop.ximix.common.crypto.Algorithm;
import org.cryptoworkshop.ximix.common.crypto.threshold.ECCommittedSecretShare;
import org.cryptoworkshop.ximix.common.util.DecoupledListenerHandlerFactory;
import org.cryptoworkshop.ximix.common.util.ListenerHandler;
import org.cryptoworkshop.ximix.node.crypto.key.message.ECCommittedSecretShareMessage;
import org.cryptoworkshop.ximix.node.crypto.operator.bc.BcECPrivateKeyOperator;
import org.cryptoworkshop.ximix.node.crypto.util.BigIntegerShare;
import org.cryptoworkshop.ximix.node.crypto.util.ECPointShare;
import org.cryptoworkshop.ximix.node.crypto.util.Share;
import org.cryptoworkshop.ximix.node.crypto.util.ShareMap;
import org.cryptoworkshop.ximix.node.crypto.util.ShareMapListener;
import org.cryptoworkshop.ximix.node.service.Decoupler;
import org.cryptoworkshop.ximix.node.service.NodeContext;
import org.cryptoworkshop.ximix.node.service.PrivateKeyOperator;

/**
 * A manager for Elliptic Curve keys stored in a node.
 */
public class ECKeyManager implements KeyManager {
    private static final int TIME_OUT = 20;

    private final Map<String, ECDomainParameters> paramsMap = new HashMap<>();
    private final Map<String, ECPoint> hMap = new HashMap<>();
    private final Set<String> signingKeys = new HashSet<>();
    private final ShareMap<String, BigInteger> sharedPrivateKeyMap;
    private final ShareMap<String, ECPoint> sharedPublicKeyMap;
    private final NodeContext nodeContext;
    private final ListenerHandler<KeyManagerListener> listenerHandler;
    private final KeyManagerListener notifier;

    /**
     * Base constructor.
     *
     * @param nodeContext the node context this manager is associated with.
     */
    public ECKeyManager(NodeContext nodeContext) {
        this.nodeContext = nodeContext;
        this.listenerHandler = new DecoupledListenerHandlerFactory(nodeContext.getDecoupler(Decoupler.LISTENER),
                nodeContext.getEventNotifier()).createHandler(KeyManagerListener.class);
        this.notifier = listenerHandler.getNotifier();

        sharedPublicKeyMap = new ShareMap<>(nodeContext.getScheduledExecutorService(),
                nodeContext.getDecoupler(Decoupler.SHARING), nodeContext.getEventNotifier());
        sharedPrivateKeyMap = new ShareMap<>(nodeContext.getScheduledExecutorService(),
                nodeContext.getDecoupler(Decoupler.SHARING), nodeContext.getEventNotifier());

        sharedPrivateKeyMap.addListener(new ShareMapListener<String, BigInteger>() {
            @Override
            public void shareCompleted(ShareMap<String, BigInteger> shareMap, String id) {
                notifier.keyAdded(ECKeyManager.this, id);
            }
        });
    }

    @Override
    public String getID() {
        return "StdEC";
    }

    @Override
    public synchronized boolean hasPrivateKey(String keyID) {
        return sharedPrivateKeyMap.containsKey(keyID);
    }

    @Override
    public synchronized boolean isSigningKey(String keyID) {
        return signingKeys.contains(keyID);
    }

    public synchronized AsymmetricCipherKeyPair generateKeyPair(String keyID, Algorithm algorithm,
            int numberOfPeers, ECDomainParameters domainParameters, ECPoint h) {
        if (!paramsMap.containsKey(keyID)) {
            ECKeyPairGenerator kpGen = new ECKeyPairGenerator();

            kpGen.init(new ECKeyGenerationParameters(domainParameters, new SecureRandom()));

            AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();

            sharedPrivateKeyMap.init(keyID, numberOfPeers);
            sharedPublicKeyMap.init(keyID, numberOfPeers);

            if (algorithm.equals(Algorithm.ECDSA)) {
                signingKeys.add(keyID);
            }

            hMap.put(keyID, h);
            paramsMap.put(keyID, ((ECPublicKeyParameters) kp.getPublic()).getParameters());

            return kp;
        } else {
            throw new IllegalStateException("Key " + keyID + " already exists.");
        }
    }

    @Override
    public SubjectPublicKeyInfo fetchPublicKey(String keyID) throws IOException {
        if (sharedPublicKeyMap.containsKey(keyID)) {
            Share<ECPoint> share = sharedPublicKeyMap.getShare(keyID, TIME_OUT, TimeUnit.SECONDS);

            if (share != null) {
                ECPoint q = share.getValue();
                ECDomainParameters params = paramsMap.get(keyID);

                return SubjectPublicKeyInfo.getInstance(SubjectPublicKeyInfoFactory
                        .createSubjectPublicKeyInfo(new ECPublicKeyParameters(q, params)).getEncoded());
            }
        }

        return null;
    }

    @Override
    public PartialPublicKeyInfo fetchPartialPublicKey(String keyID) throws IOException {
        if (sharedPrivateKeyMap.containsKey(keyID)) {
            ECDomainParameters params = paramsMap.get(keyID);
            Share<BigInteger> share = sharedPrivateKeyMap.getShare(keyID, TIME_OUT, TimeUnit.SECONDS);
            BigInteger d = share.getValue();
            ECPoint Q = params.getG().multiply(d);

            return new PartialPublicKeyInfo(share.getSequenceNo(),
                    SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(new ECPublicKeyParameters(Q, params)));
        }

        return null;
    }

    public synchronized void buildSharedKey(String keyID, ECCommittedSecretShareMessage message) {
        ECDomainParameters domainParams = paramsMap.get(keyID);
        ECCommittedSecretShare share = new ECCommittedSecretShare(message.getValue(), message.getWitness(),
                message.getCommitmentFactors());

        if (share.isRevealed(message.getIndex(), domainParams, hMap.get(keyID))) {
            sharedPrivateKeyMap.addValue(keyID, new BigIntegerShare(message.getIndex(), message.getValue()));
            sharedPublicKeyMap.addValue(keyID, new ECPointShare(message.getIndex(), message.getQ()));
        } else {
            throw new IllegalStateException("Commitment for " + keyID + " failed!");
        }
    }

    public BigInteger getPartialPrivateKey(String keyID) {
        return sharedPrivateKeyMap.getShare(keyID).getValue();
    }

    public synchronized byte[] getEncoded(char[] password) throws IOException, GeneralSecurityException {
        KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");

        try {
            OutputEncryptor encOut = new JcePKCSPBEOutputEncryptorBuilder(NISTObjectIdentifiers.id_aes256_CBC)
                    .setProvider("BC").build(password);

            JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
            PKCS12PfxPduBuilder builder = new PKCS12PfxPduBuilder();

            for (String keyID : sharedPrivateKeyMap.getIDs()) {
                ECDomainParameters domainParams = paramsMap.get(keyID);
                PrivateKey privKey = fact
                        .generatePrivate(new PKCS8EncodedKeySpec(PrivateKeyInfoFactory
                                .createPrivateKeyInfo(new ECPrivateKeyParameters(
                                        sharedPrivateKeyMap.getShare(keyID).getValue(), domainParams))
                                .getEncoded()));
                SubjectPublicKeyInfo pubKey = this.fetchPublicKey(keyID);

                // TODO: perhaps add CA cert and trust anchor to key store if available
                PKCS12SafeBagBuilder eeCertBagBuilder = new PKCS12SafeBagBuilder(
                        createCertificate(keyID, sharedPrivateKeyMap.getShare(keyID).getSequenceNo(),
                                (PrivateKey) nodeContext.getNodeCAStore().getKey("nodeCA", new char[0])));

                eeCertBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString(keyID));

                SubjectKeyIdentifier pubKeyId = extUtils.createSubjectKeyIdentifier(pubKey);

                eeCertBagBuilder.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, pubKeyId);

                PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, encOut);

                keyBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString(keyID));
                keyBagBuilder.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, pubKeyId);

                builder.addEncryptedData(
                        new JcePKCSPBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC)
                                .setProvider("BC").build(password),
                        new PKCS12SafeBag[] { eeCertBagBuilder.build() });

                builder.addData(keyBagBuilder.build());
            }

            PKCS12PfxPdu pfx = builder.build(new JcePKCS12MacCalculatorBuilder(NISTObjectIdentifiers.id_sha256),
                    password);

            return pfx.getEncoded(ASN1Encoding.DL);
        } catch (PKCSException e) {
            throw new GeneralSecurityException("Unable to create key store: " + e.getMessage(), e);
        } catch (OperatorCreationException e) {
            throw new GeneralSecurityException("Unable to create operator: " + e.getMessage(), e);
        }
    }

    public synchronized void load(char[] password, byte[] encoding) throws IOException, GeneralSecurityException {
        try {
            PKCS12PfxPdu pfx = new PKCS12PfxPdu(encoding);
            InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
                    .setProvider("BC").build(password);
            ContentInfo[] infos = pfx.getContentInfos();

            for (int i = 0; i != infos.length; i++) {
                if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData)) {
                    PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);

                    PKCS12SafeBag[] bags = dataFact.getSafeBags();

                    Attribute[] attributes = bags[0].getAttributes();

                    X509CertificateHolder cert = (X509CertificateHolder) bags[0].getBagValue();

                    String keyID = getKeyID(attributes);
                    ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters) PublicKeyFactory
                            .createKey(cert.getSubjectPublicKeyInfo());

                    paramsMap.put(keyID, publicKeyParameters.getParameters());
                    sharedPublicKeyMap.init(keyID, 1);
                    sharedPublicKeyMap.addValue(keyID, new ECPointShare(ASN1Integer.getInstance(
                            cert.getExtension(XimixObjectIdentifiers.ximixShareIdExtension).getParsedValue())
                            .getValue().intValue(), publicKeyParameters.getQ()));

                    if (KeyUsage.fromExtensions(cert.getExtensions()).hasUsages(KeyUsage.digitalSignature)) {
                        signingKeys.add(keyID);
                    }
                } else {
                    PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]);

                    PKCS12SafeBag[] bags = dataFact.getSafeBags();
                    String keyID = getKeyID(bags[0].getAttributes());

                    PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo) bags[0].getBagValue();
                    PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider);

                    sharedPrivateKeyMap.init(keyID, 1);
                    sharedPrivateKeyMap.addValue(keyID,
                            new BigIntegerShare(sharedPublicKeyMap.getShare(keyID).getSequenceNo(),
                                    ECPrivateKey.getInstance(info.parsePrivateKey()).getKey()));
                }
            }
        } catch (PKCSException e) {
            throw new GeneralSecurityException("Unable to load key store: " + e.getMessage(), e);
        }
    }

    public synchronized ECDomainParameters getParams(String keyID) {
        return paramsMap.get(keyID);
    }

    @Override
    public void addListener(KeyManagerListener listener) {
        listenerHandler.addListener(listener);
    }

    @Override
    public PrivateKeyOperator getPrivateKeyOperator(String keyID) {
        Share<BigInteger> privateKeyShare = sharedPrivateKeyMap.getShare(keyID);
        if (privateKeyShare == null) {
            return null;
        }

        return new BcECPrivateKeyOperator(privateKeyShare.getSequenceNo(), paramsMap.get(keyID),
                privateKeyShare.getValue());
    }

    private X509CertificateHolder createCertificate(String keyID, int sequenceNo, PrivateKey privKey)
            throws GeneralSecurityException, OperatorCreationException, IOException {
        String name = "C=AU, O=Ximix Network Node, OU=" + nodeContext.getName();

        //
        // create the certificate - version 3
        //
        X509v3CertificateBuilder v3CertBuilder = new X509v3CertificateBuilder(new X500Name(name),
                BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30),
                new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365)), new X500Name(name),
                this.fetchPublicKey(keyID));

        // we use keyUsage extension to distinguish between signing and encryption keys

        if (signingKeys.contains(keyID)) {
            v3CertBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature));
        } else {
            v3CertBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.dataEncipherment));
        }

        v3CertBuilder.addExtension(XimixObjectIdentifiers.ximixShareIdExtension, true, new ASN1Integer(sequenceNo));

        return v3CertBuilder.build(new JcaContentSignerBuilder("SHA256withECDSA").setProvider("BC").build(privKey));
    }

    private String getKeyID(Attribute[] attributes) {
        for (Attribute attr : attributes) {
            if (PKCS12SafeBag.friendlyNameAttribute.equals(attr.getAttrType())) {
                return DERBMPString.getInstance(attr.getAttrValues().getObjectAt(0)).getString();
            }
        }

        throw new IllegalStateException("No friendlyNameAttribute found.");
    }
}