/*
* 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.ui.model;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Set;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.osgi.util.NLS;
import com.motorola.studio.android.common.log.StudioLogger;
import com.motorolamobility.studio.android.certmanager.CertificateManagerActivator;
import com.motorolamobility.studio.android.certmanager.core.KeyStoreUtils;
import com.motorolamobility.studio.android.certmanager.core.PasswordProvider;
import com.motorolamobility.studio.android.certmanager.exception.KeyStoreManagerException;
import com.motorolamobility.studio.android.certmanager.i18n.CertificateManagerNLS;
import com.motorolamobility.studio.android.certmanager.views.KeystoreManagerView;
/**
* Represents one keystore element in {@link KeystoreManagerView}. It can be
* {@link Certificate} or {@link Key}.
*/
public class EntryNode extends AbstractTreeNode implements IKeyStoreEntry
{
/**
* The constant contains the key pair DER object identifier.
*/
public static final String KEY_PAIR_DER_OBJ_ID = "2.16.840.1.113793.23"; //$NON-NLS-1$
public static final int KEY_PASSWORD_MIN_SIZE = 6;
protected String alias;
private final String KEY_NONSAVED_PASSWORD_ICON_PATH = "icons/key.png"; //$NON-NLS-1$
private final String KEY_SAVED_PASSWORD_ICON_PATH = "icons/key_saved_password.png"; //$NON-NLS-1$
protected EntryNode()
{
}
/**
*
* @param keyStoreModel
* @param alias
* @throws KeyStoreManagerException
* if the alias is already listed in the tree
*/
public EntryNode(ITreeNode keyStoreModel, String alias) throws KeyStoreManagerException
{
this.alias = alias.toLowerCase();
setParent(keyStoreModel);
if (!isKeyPairEntry())
{
keyStoreModel.addChild(this);
}
// notify key entry addition
// KeyStoreModelEventManager.getInstance().fireEvent(this, KeyStoreModelEvent.EventType.ADD);
// Obtaining certificate to get tooltip information
X509Certificate cert = getX509Certificate();
if (cert != null)
{
X500Name x500name;
try
{
x500name = new JcaX509CertificateHolder(cert).getSubject();
RDN commonName =
x500name.getRDNs(BCStyle.CN).length >= 1 ? x500name.getRDNs(BCStyle.CN)[0]
: null;
RDN organization =
x500name.getRDNs(BCStyle.O).length >= 1 ? x500name.getRDNs(BCStyle.O)[0]
: null;
// Adding tooltip information
String org =
organization != null ? organization.getFirst().getValue().toString()
: CertificateManagerNLS.CertificateInfoDialog_NotAvailableProperty;
String name =
commonName != null ? commonName.getFirst().getValue().toString()
: CertificateManagerNLS.CertificateInfoDialog_NotAvailableProperty;
this.setTooltip(NLS.bind(CertificateManagerNLS.CertificateBlock_KeyTooltip, org,
name));
}
catch (CertificateEncodingException e)
{
String errorMsg = "Error getting data from certificate";
StudioLogger.error(EntryNode.class, errorMsg, e);
throw new KeyStoreManagerException(errorMsg, e);
}
}
}
/*(non-Javadoc)
*
* @see
* com.motorolamobility.studio.android.certmanager.ui.model.IKeyStoreEntry
* #getKeyStoreNode()
*/
@Override
public IKeyStore getKeyStoreNode()
{
return (KeyStoreNode) getParent();
}
/*
* (non-Javadoc)
*
* @see
* com.motorolamobility.studio.android.certmanager.ui.model.IKeyStoreEntry
* #getAlias()
*/
@Override
public String getAlias()
{
return alias;
}
/*
* (non-Javadoc)
*
* @see
* com.motorolamobility.studio.android.certmanager.ui.model.IKeyStoreEntry
* #isCertificateEntry()
*/
@Override
public boolean isCertificateEntry() throws KeyStoreException, KeyStoreManagerException
{
return getKeyStoreNode().getKeyStore().isCertificateEntry(alias);
}
/*
* (non-Javadoc)
*
* @see
* com.motorolamobility.studio.android.certmanager.ui.model.IKeyStoreEntry
* #isKeyEntry()
*/
@Override
public boolean isKeyEntry() throws KeyStoreException, KeyStoreManagerException
{
return getKeyStoreNode().getKeyStore().isKeyEntry(alias);
}
@Override
public boolean isKeyPairEntry()
{
X509Certificate certificate = getX509Certificate();
Set<String> criticalOIDs = certificate.getCriticalExtensionOIDs();
return (criticalOIDs != null) && criticalOIDs.contains(KEY_PAIR_DER_OBJ_ID);
}
/**
* @return {@link Certificate} if alias represents a certificate or null if
* the alias was not found (or if the type is not Certificate for
* the alias)
* @throws KeyStoreException
* if keystore not loaded yet
* @throws KeyStoreManagerException
*/
private Certificate getCertificate() throws KeyStoreException, KeyStoreManagerException
{
Certificate certificate = null;
KeyStore keyStore = getKeyStoreNode().getKeyStore();
if (keyStore.isCertificateEntry(alias))
{
certificate = keyStore.getCertificate(alias);
}
else
{
// unknown type
StudioLogger.error(CertificateManagerNLS.bind(
CertificateManagerNLS.EntryNode_NotFoundOrTypeWrong, alias));
}
return certificate;
}
/*
* (non-Javadoc)
*
* @see
* com.motorolamobility.studio.android.certmanager.ui.model.IKeyStoreEntry
* #getKey(java.lang.String)
*/
@Override
public Key getKey(String password) throws UnrecoverableKeyException, KeyStoreException,
NoSuchAlgorithmException, KeyStoreManagerException
{
Key key = null;
KeyStore keyStore = getKeyStoreNode().getKeyStore();
if (keyStore.isKeyEntry(alias))
{
key = keyStore.getKey(alias, password.toCharArray());
}
return key;
}
/*
* (non-Javadoc)
*
* @see
* com.motorolamobility.studio.android.certmanager.ui.model.IKeyStoreEntry
* #getPrivateKey(java.lang.String)
*/
@Override
public PrivateKey getPrivateKey(String password) throws UnrecoverableKeyException,
KeyStoreException, NoSuchAlgorithmException, KeyStoreManagerException,
InvalidKeyException
{
Key key = this.getKey(password);
if (!(key instanceof PrivateKey))
{
throw new InvalidKeyException("This is not a private key");
}
return (PrivateKey) key;
}
public Entry getKeyEntry(String password) throws KeyStoreException, NoSuchAlgorithmException,
KeyStoreManagerException, UnrecoverableEntryException
{
Entry key = null;
KeyStore keyStore = getKeyStoreNode().getKeyStore();
if (keyStore.isKeyEntry(alias))
{
key = keyStore.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray()));
}
return key;
}
/**
* Get all the certificates associated to this entry
*
* @return an Array of {@link Certificate}
* @throws KeyStoreException
* @throws KeyStoreManagerException
*/
private Certificate[] getCertificateChain() throws KeyStoreException, KeyStoreManagerException
{
KeyStore keyStore = getKeyStoreNode().getKeyStore();
return keyStore.getCertificateChain(alias);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((alias == null) ? 0 : alias.hashCode());
result =
(prime * result) + ((getKeyStoreNode() == null) ? 0 : getKeyStoreNode().hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof EntryNode))
{
return false;
}
EntryNode other = (EntryNode) obj;
if (alias == null)
{
if (other.alias != null)
{
return false;
}
}
else if (!alias.equals(other.alias))
{
return false;
}
if (getKeyStoreNode() == null)
{
if (other.getKeyStoreNode() != null)
{
return false;
}
}
else if (!getKeyStoreNode().equals(other.getKeyStoreNode()))
{
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return "KeyStoreEntry [alias=" + alias + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public void refresh()
{
// keys does not need to be refreshed
}
@Override
public String getId()
{
return alias;
}
@Override
public String getName()
{
return alias;
}
@Override
public ImageDescriptor getIcon()
{
//decision: we will not support key-pair, so we will have just key items below keystore node.
ImageDescriptor descr = null;
if (isPasswordSaved())
{
//saved password
descr =
CertificateManagerActivator.imageDescriptorFromPlugin(
CertificateManagerActivator.PLUGIN_ID, KEY_SAVED_PASSWORD_ICON_PATH);
}
else
{
//non saved password
descr =
CertificateManagerActivator.imageDescriptorFromPlugin(
CertificateManagerActivator.PLUGIN_ID, KEY_NONSAVED_PASSWORD_ICON_PATH);
}
return descr;
}
@Override
public boolean isLeaf()
{
return true;
}
@Override
public List<ITreeNode> getChildren()
{
return new ArrayList<ITreeNode>(0); // it is the leaf of the tree
}
public static IKeyStoreEntry createSelfSignedNode(IKeyStore keystore, String keyStorePass,
String alias, CertificateDetailsInfo certificateDetailsInfo)
throws KeyStoreManagerException
{
KeyPair keyPair = null;
try
{
keyPair = KeyStoreUtils.genKeyPair();
X509Certificate x509Certificate =
KeyStoreUtils.createX509Certificate(keyPair, certificateDetailsInfo);
if (keyStorePass == null)
{
PasswordProvider provider = new PasswordProvider(keystore.getFile());
keyStorePass = provider.getKeyStorePassword(true);
}
PrivateKeyEntry privateKeyEntry =
KeyStoreUtils.createPrivateKeyEntry(keyPair, x509Certificate);
KeyStoreUtils.addEntry(keystore.getKeyStore(), keyStorePass.toCharArray(), keystore
.getFile(), alias, privateKeyEntry, certificateDetailsInfo.getEntryPassword()
.toCharArray());
//force reload - because keystore cache can be old due to key entries additions/removals
keystore.forceReload(keyStorePass.toCharArray(), false);
}
catch (Exception e)
{
throw new KeyStoreManagerException(e.getMessage(), e);
}
return new EntryNode((ITreeNode) keystore, alias);
}
public static IKeyStoreEntry createSelfSignedNode(IKeyStore keystore, String alias,
CertificateDetailsInfo certificateDetailsInfo) throws KeyStoreManagerException
{
return createSelfSignedNode(keystore, null, alias, certificateDetailsInfo);
}
@Override
public boolean testAttribute(Object target, String name, String value)
{
boolean result = super.testAttribute(target, name, value);
if (name.equals(PROP_NAME_NODE_STATUS))
{
if (value.equals(PROP_VALUE_NODE_STATUS_WARNING))
{
X509Certificate x509Certificate = getX509Certificate();
try
{
// check validity concerning the current date
x509Certificate.checkValidity();
// now check validity related to magic date provided by
// Google
Calendar date = GregorianCalendar.getInstance();
date.clear();
date.set(2033, Calendar.OCTOBER, 22);
x509Certificate.checkValidity(date.getTime());
}
catch (CertificateExpiredException e)
{
// certificate has expired in the current date; or
// certificate has expired before 22 Oct 2033
setTooltip(CertificateManagerNLS.bind(
CertificateManagerNLS.CertificatePeriodExpired_Issue,
x509Certificate.getNotAfter()));
result = true; // decorate node
}
catch (CertificateNotYetValidException e)
{
// certificate is not yet valid in the current date; or
// certificate is not yet valid in 2033 => it must not
// happen but we need to deal with this case
setTooltip(CertificateManagerNLS.bind(
CertificateManagerNLS.CertificatePeriodNotYeatValid_Issue,
x509Certificate.getNotBefore()));
result = true; // decorate node
}
}
}
return result;
}
/**
* Get the first X509Certificate available in the entry
*
* @return
*/
@Override
public X509Certificate getX509Certificate()
{
X509Certificate x509Certificate = null;
try
{
if (isCertificateEntry())
{
Certificate cert = getCertificate();
if (cert instanceof X509Certificate)
{
// Android certificate
x509Certificate = (X509Certificate) cert;
}
}
else if (isKeyEntry())
{
Certificate[] chain = getCertificateChain();
for (int i = 0; i < chain.length; i++)
{
Certificate cert = chain[i];
if (cert instanceof X509Certificate)
{
// Android certificate
x509Certificate = (X509Certificate) cert;
}
}
}
}
catch (Exception e)
{
StudioLogger.error(EntryNode.class, CertificateManagerNLS.bind(
CertificateManagerNLS.EntryNode_ErrorGettingCertificateFromEntry, getAlias()),
e);
}
return x509Certificate;
}
@Override
protected boolean isPasswordSaved()
{
PasswordProvider pp = new PasswordProvider(getKeyStoreNode().getFile());
return pp.isPasswordSaved(alias);
}
}