/************************************************************************* * * * EJBCA: The OpenSource Certificate Authority * * * * This software is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or any later version. * * * * See terms of license at gnu.org. * * * *************************************************************************/ package org.ejbca.util.keystore; import java.io.*; import java.security.*; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.interfaces.DSAParams; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.*; import java.util.ArrayList; import java.util.Properties; import javax.crypto.Cipher; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.provider.JCEECPublicKey; import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECCurve; import org.ejbca.core.model.ca.catoken.CATokenConstants; import org.ejbca.core.model.util.AlgorithmTools; import org.ejbca.cvc.PublicKeyEC; import org.ejbca.util.CertTools; import org.ejbca.util.FileTools; /** * Tools to handle common key and keystore operations. * * @version $Id: KeyTools.java 8245 2009-11-04 12:42:07Z anatom $ */ public class KeyTools { private static Logger log = Logger.getLogger(KeyTools.class); /** The name of Suns pkcs11 implementation */ public static final String SUNPKCS11CLASS = "sun.security.pkcs11.SunPKCS11"; public static final String IAIKPKCS11CLASS = "iaik.pkcs.pkcs11.provider.IAIKPkcs11"; /** * Prevent from creating new KeyTools object */ private KeyTools() { } /** * Generates a keypair * * @param keyspec specification of keys to generate, typical value is 1024 for RSA or DSA keys, or prime192v1 for ECDSA keys * @param keyalg algorithm of keys to generate, typical value is RSA, DSA or ECDSA, see org.ejbca.core.model.ca.catoken.CATokenConstants.KEYALGORITHM_XX * * @see org.ejbca.core.model.ca.catoken.CATokenConstants * @see org.bouncycastle.asn1.x9.X962NamedCurves * @see org.bouncycastle.asn1.nist.NISTNamedCurves * @see org.bouncycastle.asn1.sec.SECNamedCurves * * @return KeyPair the generated keypair * @throws InvalidAlgorithmParameterException * @see org.ejbca.core.model.ca.catoken.CATokenConstants#KEYALGORITHM_RSA */ public static KeyPair genKeys(String keySpec, String keyAlg) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { if (log.isTraceEnabled()) { log.trace(">genKeys("+keySpec+", "+keyAlg+")"); } KeyPairGenerator keygen = KeyPairGenerator.getInstance(keyAlg, "BC"); if (StringUtils.equals(keyAlg, CATokenConstants.KEYALGORITHM_ECDSA)) { org.bouncycastle.jce.spec.ECParameterSpec ecSpec = null; if ( (keySpec == null) || StringUtils.equals(keySpec,"implicitlyCA") ) { log.debug("Generating implicitlyCA encoded ECDSA key pair"); // If the keySpec is null, we have "implicitlyCA" defined EC parameters // The parameters were already installed when we installed the provider // We just make sure that ecSpec == null here } else { log.debug("Generating named curve ECDSA key pair: "+keySpec); // We have EC keys ecSpec = ECNamedCurveTable.getParameterSpec(keySpec); if (ecSpec == null) { throw new InvalidAlgorithmParameterException("keySpec "+keySpec+" is invalid for ECDSA."); } } keygen.initialize(ecSpec, new SecureRandom()); } else { // RSA or DSA keys int keysize = Integer.parseInt(keySpec); keygen.initialize(keysize); } KeyPair keys = keygen.generateKeyPair(); if (log.isDebugEnabled()) { PublicKey pk = keys.getPublic(); int len = getKeyLength(pk); log.debug("Generated " + keys.getPublic().getAlgorithm() + " keys with length " + len); } log.trace("= 0. 0 usually means the length can not be calculated, * for example if the key is an EC key and the "implicitlyCA" encoding is used. */ public static int getKeyLength(PublicKey pk) { int len = -1; if (pk instanceof RSAPublicKey) { RSAPublicKey rsapub = (RSAPublicKey) pk; len = rsapub.getModulus().bitLength(); } else if (pk instanceof JCEECPublicKey) { JCEECPublicKey ecpriv = (JCEECPublicKey) pk; org.bouncycastle.jce.spec.ECParameterSpec spec = ecpriv.getParameters(); if (spec != null) { len = spec.getN().bitLength(); } else { // We support the key, but we don't know the key length len = 0; } } else if (pk instanceof ECPublicKey) { ECPublicKey ecpriv = (ECPublicKey) pk; java.security.spec.ECParameterSpec spec = ecpriv.getParams(); if (spec != null) { len = spec.getOrder().bitLength(); // does this really return something we expect? } else { // We support the key, but we don't know the key length len = 0; } } else if (pk instanceof DSAPublicKey) { DSAPublicKey dsapub = (DSAPublicKey) pk; if ( dsapub.getParams() != null ) { len = dsapub.getParams().getP().bitLength(); } else { len = dsapub.getY().bitLength(); } } return len; } /** * Gets the key AlgorithmParameterSpec of supported keys. Can be used to initialize a KeyPairGenerator to generate a key of equal type and size. * @param pk PublicKey used to derive the AlgorithmParameterSpec * @return null if key is unsupported, otherwise a AlgorithmParameterSpec. */ public static AlgorithmParameterSpec getKeyGenSpec(PublicKey pk) { AlgorithmParameterSpec ret = null; if (pk instanceof RSAPublicKey) { log.debug("getKeyGenSpec: RSA"); RSAPublicKey rpk = (RSAPublicKey)pk; ret = new RSAKeyGenParameterSpec(getKeyLength(pk), rpk.getPublicExponent()); } else if (pk instanceof DSAPublicKey) { log.debug("getKeyGenSpec: DSA"); DSAPublicKey dpk = (DSAPublicKey)pk; DSAParams params = dpk.getParams(); ret = new DSAParameterSpec(params.getP(), params.getQ(), params.getG()); } else if (pk instanceof ECPublicKey) { log.debug("getKeyGenSpec: ECPublicKey"); ECPublicKey ecpub = (ECPublicKey) pk; java.security.spec.ECParameterSpec sunsp = ecpub.getParams(); EllipticCurve ecurve = new EllipticCurve(sunsp.getCurve().getField(), sunsp.getCurve().getA(), sunsp.getCurve().getB()); //ECParameterSpec par = new ECNamedCurveSpec(null, sunsp.getCurve(), sunsp.getGenerator(), sunsp.getOrder(), BigInteger.valueOf(sunsp.getCofactor())); ECParameterSpec params = new ECParameterSpec(ecurve, sunsp.getGenerator(), sunsp.getOrder(), sunsp.getCofactor()); if (log.isDebugEnabled()) { log.debug("Fieldsize: "+params.getCurve().getField().getFieldSize()); EllipticCurve curve = params.getCurve(); log.debug("CurveA: "+curve.getA().toString(16)); log.debug("CurveB: "+curve.getB().toString(16)); log.debug("CurveSeed: "+curve.getSeed()); ECFieldFp field = (ECFieldFp)curve.getField(); log.debug("CurveSfield: "+field.getP().toString(16)); ECPoint p = params.getGenerator(); log.debug("Generator: "+p.getAffineX().toString(16)+", "+p.getAffineY().toString(16)); log.debug("Order: "+params.getOrder().toString(16)); log.debug("CoFactor: "+params.getCofactor()); } ret = params; } else if (pk instanceof JCEECPublicKey) { log.debug("getKeyGenSpec: JCEECPublicKey"); JCEECPublicKey ecpub = (JCEECPublicKey) pk; org.bouncycastle.jce.spec.ECParameterSpec bcsp = ecpub.getParameters(); ECCurve curve = bcsp.getCurve(); //TODO: this probably does not work for key generation with the Sun PKCS#11 provider. Maybe seed needs to be set to null as above? Or something else, the BC curve is it the same? ECParameterSpec params = new ECNamedCurveSpec(null, curve, bcsp.getG(), bcsp.getN(), bcsp.getH()); ret = params; //EllipticCurve ecc = new EllipticCurve(curve.) //ECParameterSpec sp = new ECParameterSpec(, bcsp.getG(), bcsp.getN(), bcsp.getH().intValue()); } return ret; } /** * Retrieves the certificate chain from a keystore. * * @param keyStore the keystore, which has been loaded and opened. * @param privateKeyAlias the alias of the privatekey for which the certchain belongs. * * @return array of Certificate, or null if no certificates are found. */ public static Certificate[] getCertChain(KeyStore keyStore, String privateKeyAlias) throws KeyStoreException { if (log.isTraceEnabled()) { log.trace(">getCertChain: alias='" + privateKeyAlias + "'"); } Certificate[] certchain = keyStore.getCertificateChain(privateKeyAlias); if (certchain == null) { return null; } log.debug("Certchain retrieved from alias '" + privateKeyAlias + "' has length " + certchain.length); if (certchain.length < 1) { log.error("Cannot load certificate chain with alias '" + privateKeyAlias + "' from keystore."); if (log.isTraceEnabled()) { log.trace("=0 ) { pw.println("slot"+(isIndex ? "ListIndex":"")+" = "+slot); } if (attributesFile != null) { byte[] attrs = FileTools.readFiletoBuffer(attributesFile); pw.println(new String(attrs)); } pw.flush(); pw.close(); if (log.isDebugEnabled()) { log.debug(baos.toString()); } // Properties for the IAIK PKCS#11 provider Properties prop = new Properties(); prop.setProperty("PKCS11_NATIVE_MODULE", libFile.getCanonicalPath()); // If using Slot Index it is denoted by brackets in iaik prop.setProperty("SLOT_ID", isIndex ? ("["+slot+"]") : slot); if (log.isDebugEnabled()) { log.debug(prop.toString()); } return getP11Provider(new ByteArrayInputStream(baos.toByteArray()), prop); } /** * * @param is for the SUN PKCS#11 provider * @param prop for the IAIK PKCS#11 provider * @return Java security Provider for a PCKS#11 token * @throws IOException if neither the IAIK or the SUN provider can be created */ private static Provider getP11Provider(final InputStream is, Properties prop) throws IOException { // We will construct the PKCS11 provider (sun.security..., or iaik...) using reflection, because // the sun class does not exist on all platforms in jdk5, and we want to be able to compile everything. // The below code replaces the single line (for the SUN provider): // return new SunPKCS11(new ByteArrayInputStream(baos.toByteArray())); // We will first try to construct the more competent IAIK provider, if it exists in the classpath // if that does not exist, we will revert back to use the SUN provider if (prop!=null) try { final Class implClass = Class.forName(IAIKPKCS11CLASS); log.info("Using IAIK PKCS11 provider: "+IAIKPKCS11CLASS); // iaik PKCS11 has Properties as constructor argument return (Provider)implClass.getConstructor(Properties.class).newInstance(new Object[] {prop}); } catch (Exception e) { // do nothing here. Sun provider is tested below. } try { // Sun PKCS11 has InputStream as constructor argument final Class implClass = Class.forName(SUNPKCS11CLASS); log.info("Using SUN PKCS11 provider: "+SUNPKCS11CLASS); return (Provider)implClass.getConstructor(InputStream.class).newInstance(new Object[] {is}); } catch (Exception e2) { log.error("Error constructing pkcs11 provider: "+e2.getMessage()); IOException ioe = new IOException("Error constructing pkcs11 provider: "+e2.getMessage()); ioe.initCause(e2); throw ioe; } } /** * @param Input stream for sun configuration file. * @return The Sun provider * @throws IOException */ public static Provider getSunP11Provider(final InputStream is) throws IOException { return getP11Provider(is, null); } /** * Detect if "Unlimited Strength" Policy files hase bean properly installed. * * @return true if key strength is limited */ public static boolean isUsingExportableCryptography() { boolean returnValue = true; try { int keylen = Cipher.getMaxAllowedKeyLength("DES"); log.debug("MaxAllowedKeyLength for DES is: "+keylen); if ( keylen == Integer.MAX_VALUE ) { returnValue = false; } } catch (NoSuchAlgorithmException e) { } return returnValue; } /** * Sign provided data with specified private key and algortihm * * @param privateKey the private key * @param signatureAlgorithm e.g. as returned by caToken.getCATokenInfo().getSignatureAlgorithm() * @param data the data to sign * @return the signature */ public static byte[] signData(PrivateKey privateKey , String signatureAlgorithm, byte[] data) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { Signature signer = Signature.getInstance(signatureAlgorithm); signer.initSign(privateKey); signer.update(data); return (signer.sign()); } /** * Verify signed data with specified public key, algorith and signature * * @param publicKey the public key * @param signatureAlgorithm e.g. as returned by caToken.getCATokenInfo().getSignatureAlgorithm() * @param data the data to verify * @param signature the signature * @return true if the signature is ok */ public static boolean verifyData(PublicKey publicKey , String signatureAlgorithm, byte[] data, byte[] signature) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { Signature signer = Signature.getInstance(signatureAlgorithm); signer.initVerify(publicKey); signer.update(data); return (signer.verify(signature)); } /** Testing a key pair to verify that it is possible to first sign and then verify with it. * * @param priv * @param pub * @param provider A provider used for signing with the private key, or null if "BC" should be used. * @throws Exception */ public static void testKey(PrivateKey priv, PublicKey pub, String provider) throws Exception { final byte input[] = "Lillan gick pa vagen ut, motte dar en katt...".getBytes(); final byte signBV[]; String testSigAlg = (String)AlgorithmTools.getSignatureAlgorithms(pub).iterator().next(); if ( testSigAlg == null ) { testSigAlg = "SHA1WithRSA"; } { String prov = "BC"; if (provider != null) { prov = provider; } Signature signature = Signature.getInstance(testSigAlg, prov); signature.initSign( priv ); signature.update( input ); signBV = signature.sign(); }{ Signature signature = Signature.getInstance(testSigAlg, "BC"); signature.initVerify(pub); signature.update(input); if ( !signature.verify(signBV) ) { throw new InvalidKeyException("Not possible to sign and then verify with key pair."); } } } } // KeyTools