diff options
Diffstat (limited to 'src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/core/KeyStoreUtils.java')
-rw-r--r-- | src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/core/KeyStoreUtils.java | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/core/KeyStoreUtils.java b/src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/core/KeyStoreUtils.java new file mode 100644 index 0000000..46f6fc5 --- /dev/null +++ b/src/plugins/certmanager/src/com/motorolamobility/studio/android/certmanager/core/KeyStoreUtils.java @@ -0,0 +1,686 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 com.motorolamobility.studio.android.certmanager.core; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStore.Builder; +import java.security.KeyStore.Entry; +import java.security.KeyStore.PasswordProtection; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.ProtectionParameter; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Map; + +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStrictStyle; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; +import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.bc.BcContentSignerBuilder; +import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder; +import org.eclipse.osgi.util.NLS; + +import com.motorola.studio.android.common.log.StudioLogger; +import com.motorola.studio.android.common.utilities.FileUtil; +import com.motorolamobility.studio.android.certmanager.exception.InvalidPasswordException; +import com.motorolamobility.studio.android.certmanager.exception.KeyStoreManagerException; +import com.motorolamobility.studio.android.certmanager.i18n.CertificateManagerNLS; +import com.motorolamobility.studio.android.certmanager.ui.model.CertificateDetailsInfo; + +public class KeyStoreUtils +{ + private static final String ERROR_DELETING_ALIAS = + CertificateManagerNLS.KeyStoreUtils_ErrorDeletingAlias; + + /** + * Creates a new empty KeyStore, from the default type, located at keyStoreFile with the password, password + * @param keyStoreFile The file pointing o where the new KeyStore will be located + * @param password the password for the new KeyStore + * @return the {@link KeyStore} representing the new KeyStore + * @throws InvalidPasswordException + * @throws KeyStoreException if KeyStore can't be created + */ + public static KeyStore createKeystore(File keyStoreFile, char[] password) + throws KeyStoreManagerException, InvalidPasswordException + { + return createKeystore(keyStoreFile, KeyStore.getDefaultType(), password); + } + + /** + * Creates a new empty KeyStore, located at keyStoreFile with the password, password + * @param keyStoreFile The file pointing o where the new KeyStore will be located + * @param keyStoreType The type of the new KeyStore + * @param password the password for the new KeyStore + * @return the {@link KeyStore} representing the new KeyStore + * @throws InvalidPasswordException + * @throws KeyStoreException if KeyStore can't be created + */ + public static KeyStore createKeystore(File keyStoreFile, String keyStoreType, char[] password) + throws KeyStoreManagerException, InvalidPasswordException + { + KeyStore keyStore = null; + if ((keyStoreFile != null) && !keyStoreFile.exists()) + { + keyStore = loadKeystore(keyStoreFile, password, keyStoreType); + try + { + writeKeyStore(keyStore, password, keyStoreFile); + } + catch (Exception e) + { + throw new KeyStoreManagerException(NLS.bind( + CertificateManagerNLS.KeyStoreUtils_Error_WriteKeyStore, keyStoreFile), e); + } + } + else + { + throw new KeyStoreManagerException(NLS.bind( + CertificateManagerNLS.KeyStoreUtils_Error_FileAlreadyExists, keyStoreFile)); + } + + return keyStore; + } + + public static void writeKeyStore(KeyStore keyStore, char[] password, File keyStoreFile) + throws FileNotFoundException, KeyStoreException, IOException, NoSuchAlgorithmException, + CertificateException, KeyStoreManagerException, InvalidPasswordException + { + + writeKeyStore(keyStore, null, password, keyStoreFile); + } + + private static void writeKeyStore(KeyStore keyStore, char[] oldPassword, char[] newPassword, + File keyStoreFile) throws FileNotFoundException, KeyStoreException, IOException, + NoSuchAlgorithmException, CertificateException, KeyStoreManagerException, + InvalidPasswordException + { + FileOutputStream fos = null; + try + { + if (oldPassword != null) + { + if (loadKeystore(keyStoreFile, oldPassword, keyStore.getType()) != null) + { + fos = new FileOutputStream(keyStoreFile); + keyStore.store(fos, newPassword); + } + } + else + { + fos = new FileOutputStream(keyStoreFile); + keyStore.store(fos, newPassword); + } + } + finally + { + if (fos != null) + { + try + { + fos.close(); + } + catch (IOException e) + { + StudioLogger.error("Could not close steam while writing keystore file. " + + e.getMessage()); + } + } + } + } + + /** + * Loads a KeyStore from a given file from the default type, usually JKS. + * If keyStoreFile path don't exist then a new empty KeyStore will be created on the given location. + * <b>Note:</b> Calling this method is the same as calling loadKeystore(keyStoreFile, password, KeyStore.getDefaultType()) + * @param keyStoreFile The keyStore location. + * @param password The KeyStore password + * @return the {@link KeyStore} representing the file. + * @throws KeyStoreManagerException + * @throws InvalidPasswordException + */ + public static KeyStore loadKeystore(File keyStoreFile, char[] password) + throws KeyStoreManagerException, InvalidPasswordException + { + return loadKeystore(keyStoreFile, password, KeyStore.getDefaultType()); + } + + /** + * Loads a KeyStore from a given file. + * If keyStoreFile path don't exist then a new empty KeyStore will be created on memory. + * If you want o create a new KeyStore file, calling createStore is recommended. + * @param keyStoreFile The keyStore location. + * @param password The KeyStore password + * @param storeType The Type of the keystore o be loaded. + * @return the {@link KeyStore} representing the file. + * @throws KeyStoreManagerException + * @throws InvalidPasswordException + */ + public static KeyStore loadKeystore(File keyStoreFile, char[] password, String storeType) + throws KeyStoreManagerException, InvalidPasswordException + { + KeyStore keyStore = null; + FileInputStream fis = null; + try + { + keyStore = KeyStore.getInstance(storeType); + + if ((keyStoreFile != null) && keyStoreFile.exists() && (keyStoreFile.length() > 0)) + { + fis = new FileInputStream(keyStoreFile); + } + + //fis = null means a new keyStore will be created + keyStore.load(fis, password); + } + catch (IOException e) + { + if (e.getMessage().contains("password was incorrect") + || (e.getCause() instanceof UnrecoverableKeyException)) + { + throw new InvalidPasswordException(e.getMessage()); + } + else + { + throw new KeyStoreManagerException(NLS.bind( + CertificateManagerNLS.KeyStoreUtils_Error_LoadKeyStore, keyStoreFile), e); + } + } + catch (Exception e) + { + throw new KeyStoreManagerException(NLS.bind( + CertificateManagerNLS.KeyStoreUtils_Error_LoadKeyStore, keyStoreFile), e); + } + finally + { + if (fis != null) + { + try + { + fis.close(); + } + catch (IOException e) + { + StudioLogger.error("Could not close steam while loading keystore. " + + e.getMessage()); + } + } + } + + return keyStore; + } + + /** + * Simply deletes the KeyStore File + * @param keyStoreFile teh KeyStore file to be deleted. + * @throws KeyStoreException If any error occur. + */ + public static void deleteKeystore(File keyStoreFile) throws KeyStoreManagerException + { + try + { + FileUtil.deleteFile(keyStoreFile); + } + catch (IOException e) + { + throw new KeyStoreManagerException(NLS.bind( + CertificateManagerNLS.KeyStoreUtils_Error_DeleteKeyStore, keyStoreFile), e); + } + } + + /** + * Write the keyStore in to the given file, protecting it with password. + * Warn: Since there's actually no way to change the password this method will overwrite the existing file with the keyStore contents, + * without further warning. + * @param keyStore the {@link KeyStore} to be written. + * @param keyStoreFile The KeyStore location + * @param oldPassword + * @param sourcePassword the new Password + * @throws KeyStoreException If file could no be write. + */ + public static void changeKeystorePasswd(KeyStore keyStore, File keyStoreFile, + char[] oldPassword, char[] newPassword) throws KeyStoreManagerException + { + try + { + keyStore = loadKeystore(keyStoreFile, oldPassword, keyStore.getType()); + writeKeyStore(keyStore, oldPassword, newPassword, keyStoreFile); + } + catch (Exception e) + { + throw new KeyStoreManagerException(NLS.bind( + CertificateManagerNLS.KeyStoreUtils_Error_WriteKeyStore, keyStoreFile), e); + } + } + + /** + * Adds a new enty to a given keyStore. + * @param keyStore The Keystore that will receive the entry + * @param keyStorePassword The KeyStore password + * @param keyStoreFile The KeyStore file path + * @param alias The new entry alias + * @param entry The Entry to be added + * @param entryPassword The password to protect the entry + * @throws KeyStoreManagerException if any error occurs. + */ + public static void addEntry(KeyStore keyStore, char[] keyStorePassword, File keyStoreFile, + String alias, Entry entry, char[] entryPassword) throws KeyStoreManagerException + { + try + { + PasswordProtection passwordProtection = new KeyStore.PasswordProtection(entryPassword); + keyStore = loadKeystore(keyStoreFile, keyStorePassword, keyStore.getType()); + + if (!keyStore.containsAlias(alias)) + { + keyStore.setEntry(alias, entry, passwordProtection); + writeKeyStore(keyStore, keyStorePassword, keyStoreFile); + } + else + { + throw new KeyStoreManagerException(NLS.bind("Alias \"{0}\" already exists.", alias)); + } + + } + catch (KeyStoreManagerException e) + { + throw e; + } + catch (Exception e) + { + throw new KeyStoreManagerException(NLS.bind( + CertificateManagerNLS.KeyStoreUtils_Error_AddEntryToKeyStore, alias), e); + } + } + + /** + * Adds a new enty to a given keyStore. + * @param keyStore The Keystore that will receive the entry + * @param keyStorePassword The KeyStore password + * @param keyStoreFile The KeyStore file path + * @param alias The new entry alias + * @param entry The Entry to be added + * @param entryPassword The password to protect the entry + * @throws KeyStoreManagerException if any error occurs. + */ + public static void changeEntryPassword(KeyStore keyStore, char[] keyStorePassword, + File keyStoreFile, String alias, Entry entry, char[] entryPassword) + throws KeyStoreManagerException + { + try + { + PasswordProtection passwordProtection = new KeyStore.PasswordProtection(entryPassword); + keyStore.setEntry(alias, entry, passwordProtection); + writeKeyStore(keyStore, keyStorePassword, keyStoreFile); + } + catch (Exception e) + { + throw new KeyStoreManagerException(NLS.bind( + "Error attempting to change password for {0}", alias), e); + } + } + + /** + * Create a new X509 certificate for a given KeyPair + * @param keyPair the {@link KeyPair} used to create the certificate, + * RSAPublicKey and RSAPrivateKey are mandatory on keyPair, IllegalArgumentExeption will be thrown otherwise. + * @param issuerName The issuer name to be used on the certificate + * @param ownerName The owner name to be used on the certificate + * @param expireDate The expire date + * @return The {@link X509Certificate} + * @throws IOException + * @throws OperatorCreationException + * @throws CertificateException + */ + public static X509Certificate createX509Certificate(KeyPair keyPair, + CertificateDetailsInfo certDetails) throws IOException, OperatorCreationException, + CertificateException + { + + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + if (!(publicKey instanceof RSAPublicKey) || !(privateKey instanceof RSAPrivateKey)) + { + throw new IllegalArgumentException( + CertificateManagerNLS.KeyStoreUtils_RSA_Keys_Expected); + } + + RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey; + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey; + + //Transform the PublicKey into the BouncyCastle expected format + ASN1InputStream asn1InputStream = null; + X509Certificate x509Certificate = null; + + try + { + asn1InputStream = + new ASN1InputStream(new ByteArrayInputStream(rsaPublicKey.getEncoded())); + SubjectPublicKeyInfo pubKey = + new SubjectPublicKeyInfo((ASN1Sequence) asn1InputStream.readObject()); + + X500NameBuilder nameBuilder = new X500NameBuilder(new BCStrictStyle()); + addField(BCStyle.C, certDetails.getCountry(), nameBuilder); + addField(BCStyle.ST, certDetails.getState(), nameBuilder); + addField(BCStyle.L, certDetails.getLocality(), nameBuilder); + addField(BCStyle.O, certDetails.getOrganization(), nameBuilder); + addField(BCStyle.OU, certDetails.getOrganizationUnit(), nameBuilder); + addField(BCStyle.CN, certDetails.getCommonName(), nameBuilder); + + X500Name subjectName = nameBuilder.build(); + X500Name issuerName = subjectName; + X509v3CertificateBuilder certBuilder = + new X509v3CertificateBuilder(issuerName, BigInteger.valueOf(new SecureRandom() + .nextInt()), GregorianCalendar.getInstance().getTime(), + certDetails.getExpirationDate(), subjectName, pubKey); + + AlgorithmIdentifier sigAlgId = + new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA"); //$NON-NLS-1$ + AlgorithmIdentifier digAlgId = + new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId); + BcContentSignerBuilder sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId); + + //Create RSAKeyParameters, the private key format expected by Bouncy Castle + RSAKeyParameters keyParams = + new RSAKeyParameters(true, rsaPrivateKey.getPrivateExponent(), + rsaPrivateKey.getModulus()); + + ContentSigner contentSigner = sigGen.build(keyParams); + X509CertificateHolder certificateHolder = certBuilder.build(contentSigner); + + //Convert the X509Certificate from BouncyCastle format to the java.security format + JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter(); + x509Certificate = certConverter.getCertificate(certificateHolder); + } + finally + { + if (asn1InputStream != null) + { + try + { + asn1InputStream.close(); + } + catch (IOException e) + { + StudioLogger.error("Could not close stream while creating X509 certificate. " + + e.getMessage()); + } + } + } + + return x509Certificate; + } + + private static void addField(ASN1ObjectIdentifier objectId, String value, + X500NameBuilder nameBuilder) + { + if (value.length() > 0) + { + nameBuilder.addRDN(objectId, value); + } + } + + /** + * Creates a new RSA KeyPair + * @return the new {@link KeyPair} + */ + public static KeyPair genKeyPair() throws NoSuchAlgorithmException + { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); //$NON-NLS-1$ + keyPairGen.initialize(2048); //As recommended by Android guys, key is created with 2048 bits. + KeyPair keyPair = keyPairGen.genKeyPair(); + return keyPair; + } + + /** + * Create a new private key entry inside the key pair + * @param keyPair + * @param x509Certificate + * @return + */ + public static PrivateKeyEntry createPrivateKeyEntry(KeyPair keyPair, + X509Certificate x509Certificate) + { + Certificate[] certChain = new Certificate[] + { + x509Certificate + }; + PrivateKeyEntry privateKeyEntry = + new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), certChain); + return privateKeyEntry; + } + + public static void deleteEntry(KeyStore keyStore, char[] password, File keyStoreFile, + String alias) throws KeyStoreManagerException + { + try + { + keyStore = loadKeystore(keyStoreFile, password, keyStore.getType()); + + keyStore.deleteEntry(alias); + writeKeyStore(keyStore, password, keyStoreFile); + } + catch (Exception e) + { + StudioLogger.error(KeyStoreUtils.class, ERROR_DELETING_ALIAS + alias, e); + throw new KeyStoreManagerException(ERROR_DELETING_ALIAS + alias, e); + } + } + + /** + * Change a keyStore type. + * @param keyStoreFile The KeyStoreFile + * @param password The KeyStore Password + * @param originalType the original Type + * @param destinationType the new KeyStore Type + * @throws KeyStoreManagerException If any error occurs, the operation will be canceled and reverted automatically. + * @throws InvalidPasswordException + */ + public static void changeKeyStoreType(File keyStoreFile, char[] password, String originalType, + String destinationType, Map<String, String> aliases) throws KeyStoreManagerException, + InvalidPasswordException + { + boolean rollBack = false; + String timeStamp = Long.toString(Calendar.getInstance().getTimeInMillis()); + File oldKsFile = new File(keyStoreFile.getAbsolutePath() + "_" + timeStamp); + oldKsFile.delete(); + boolean renamed = false; + renamed = keyStoreFile.renameTo(oldKsFile); + if (renamed) + { + try + { + Builder oldKsBuilder = + KeyStore.Builder.newInstance(originalType, null, oldKsFile, + new PasswordProtection(password)); + KeyStore oldKeyStore = oldKsBuilder.getKeyStore(); + + KeyStore newKeyStore = createKeystore(keyStoreFile, destinationType, password); + for (String alias : aliases.keySet()) + { + ProtectionParameter protectionParameter = + new PasswordProtection(aliases.get(alias).toCharArray()); + Entry entry = oldKeyStore.getEntry(alias, protectionParameter); + newKeyStore.setEntry(alias, entry, protectionParameter); + } + writeKeyStore(newKeyStore, password, keyStoreFile); + } + catch (InvalidPasswordException e) + { + rollBack = true; + StudioLogger + .error(KeyStoreUtils.class, + "Invalid password while trying to create a new keystore, changing a keyStore type.", + e); + + } + catch (Exception e) + { + if (e.getMessage().contains("password was incorrect") + || e.getCause().getMessage().contains("password was incorrect")) + { + keyStoreFile.delete(); + oldKsFile.renameTo(keyStoreFile); + throw new InvalidPasswordException(e.getMessage()); + } + else + { + StudioLogger.error(KeyStoreUtils.class, + "Exception occurred while attempting to change a keyStore type.", e); + rollBack = true; + } + } + + if (rollBack) + { + keyStoreFile.delete(); + oldKsFile.renameTo(keyStoreFile); + + throw new KeyStoreManagerException(NLS.bind( + "Could not convert the KeyStore {0} to type {1}", keyStoreFile, + destinationType)); + } + } + else + { + throw new KeyStoreManagerException( + NLS.bind( + "Could not convert the KeyStore {0} to type {1}, could not backup the current keyStore file, maybe it's in use by another program.", + keyStoreFile, destinationType)); + } + oldKsFile.delete(); + } + + /** + * Import a set of entries from sourcekeystore into the targetkeystore. + * If alias already exists on the target keystore then the alias is concatenated with the + * source keystore file name. + * @param targetKeyStore + * @param targetFile + * @param targetType + * @param targetPasswd + * @param sourceKeyStore + * @param sourceKeyStoreFile + * @param sourcePasswd + * @param aliases a map<String, String> containing alias as key and its password as value. this method assume that the password is correct + * @throws InvalidPasswordException + * @throws KeyStoreManagerException + */ + public static void importKeys(KeyStore targetKeyStore, File targetFile, String targetType, + char[] targetPasswd, KeyStore sourceKeyStore, File sourceKeyStoreFile, + char[] sourcePasswd, Map<String, String> aliases) throws InvalidPasswordException, + KeyStoreManagerException + { + if (!isValidKeyStorePasswd(targetFile, targetType, targetPasswd)) + { + throw new InvalidPasswordException( + CertificateManagerNLS.PasswordChanged_InvalidKeystorePassword); + } + + try + { + for (String alias : aliases.keySet()) + { + if (sourceKeyStore.containsAlias(alias)) + { + ProtectionParameter protectionParameter = + new PasswordProtection(aliases.get(alias).toCharArray()); + Entry entry = sourceKeyStore.getEntry(alias, protectionParameter); + if (targetKeyStore.containsAlias(alias)) + { + alias += "_" + sourceKeyStoreFile.getName(); + } + int i = 1; + while (targetKeyStore.containsAlias(alias)) + { + alias += "_" + i; + i++; + } + targetKeyStore.setEntry(alias, entry, protectionParameter); + } + else + { + StudioLogger + .error(KeyStoreUtils.class, + NLS.bind( + "Alias {0} could not be imported because it doesn't exists on originKeyStore", + alias)); + } + } + writeKeyStore(targetKeyStore, targetPasswd, targetFile); + } + catch (Exception e) + { + throw new KeyStoreManagerException("Could not import the selected aliases into " + + targetFile.getName(), e); + } + } + + /** + * Verifies if the password if valid + * @param keyStoreFile + * @param keyStoreType + * @param passwd + * @return true if password is valid, false otherwise. + * @throws KeyStoreManagerException + */ + public static boolean isValidKeyStorePasswd(File keyStoreFile, String keyStoreType, + char[] passwd) throws KeyStoreManagerException + { + KeyStore keystore = null; + try + { + keystore = loadKeystore(keyStoreFile, passwd, keyStoreType); + } + catch (InvalidPasswordException e) + { + //Do nothing, password is invalid + } + return keystore != null; + } +} |