/*
************************************************************************
**** C A N A D I A N A S T R O N O M Y D A T A C E N T R E *****
*
* (c) 2010. (c) 2010.
* National Research Council Conseil national de recherches
* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
* All rights reserved Tous droits reserves
*
* NRC disclaims any warranties Le CNRC denie toute garantie
* expressed, implied, or statu- enoncee, implicite ou legale,
* tory, of any kind with respect de quelque nature que se soit,
* to the software, including concernant le logiciel, y com-
* without limitation any war- pris sans restriction toute
* ranty of merchantability or garantie de valeur marchande
* fitness for a particular pur- ou de pertinence pour un usage
* pose. NRC shall not be liable particulier. Le CNRC ne
* in any event for any damages, pourra en aucun cas etre tenu
* whether direct or indirect, responsable de tout dommage,
* special or general, consequen- direct ou indirect, particul-
* tial or incidental, arising ier ou general, accessoire ou
* from the use of the software. fortuit, resultant de l'utili-
* sation du logiciel.
*
*
* @author adriand
*
* @version $Revision: $
*
*
**** C A N A D I A N A S T R O N O M Y D A T A C E N T R E *****
************************************************************************
*/
package org.astrogrid.security.delegation;
import java.io.IOException;
import java.io.Writer;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMWriter;
/**
* A collection of delegated credentials. For each key there is a private key,
* a certificate-signing request (CSR) and, optionally, a certificate.
*
* This class is thread safe. Initializing an identity is idempotent. The
* name, keys and CSR for each identity are immutable, and access to the
* certificate is synchronized. Further, the class will reject an attempt
* to set a certificate whose public key does not match that set for the
* identity at initialization; therefore, if two threads delegate to the
* same identity concurrently, the credentials held are not disrupted.
*
* @author Guy Rixon
*/
public class InMemoryDelegations extends Delegations
{
/**
* All the delegations, partial or complete, known to this
* object. The key-pairs for delegated credentials live here.
* The keys are hashes of the delegated principals: see
* {@link #hash} for details.
*/
private Map identities;
private KeyPairGenerator keyPairGenerator;
/**
* Constructs a Delegations object.
*/
protected InMemoryDelegations() {
// Add the Bouncy Castle JCE provider. This allows the CSR
// classes to work. The BC implementation of PKCS#10 depends on
// the ciphers in the BC provider.
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
erase();
try {
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
throw new RuntimeException("The JCE doesn't do RSA! Game over.");
}
keyPairGenerator.initialize(1024);
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#erase()
*/
public void erase() {
identities = new ConcurrentHashMap();
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#initializeIdentity(java.lang.String)
*/
public String initializeIdentity(String identity) throws GeneralSecurityException {
X500Principal p = new X500Principal(identity);
return initializeIdentity(p);
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#initializeIdentity(javax.security.auth.x500.X500Principal)
*/
public String initializeIdentity(X500Principal principal) throws GeneralSecurityException {
String hashKey = hash(principal);
if (!identities.containsKey(hashKey)) {
DelegatedIdentity id =
new DelegatedIdentity(principal.getName(X500Principal.CANONICAL),
this.keyPairGenerator.generateKeyPair());
identities.put(hashKey, id);
}
return hashKey;
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#getCsr(java.lang.String)
*/
public CertificateSigningRequest getCsr(String hashKey) {
DelegatedIdentity id = identities.get(hashKey);
return (id == null)? null : id.csr;
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#getPrivateKey(java.lang.String)
*/
public PrivateKey getPrivateKey(String hashKey) {
DelegatedIdentity id = identities.get(hashKey);
return (id == null)? null: (PrivateKey) id.keys.getPrivate();
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#getCertificate(java.lang.String)
*/
public X509Certificate[] getCertificates(String hashKey)
{
DelegatedIdentity id = identities.get(hashKey);
if (id == null)
{
return null;
}
else
{
synchronized (id)
{
return new X509Certificate[] { id.certificate };
}
}
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#remove(java.lang.String)
*/
public void remove(String hashKey) {
identities.remove(hashKey);
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#isKnown(java.lang.String)
*/
public boolean isKnown(String hashKey) {
return identities.containsKey(hashKey);
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#setCertificate(java.lang.String, java.security.cert.X509Certificate)
*/
public void setCertificates(String hashKey,
X509Certificate[] certificates) throws InvalidKeyException {
DelegatedIdentity id = identities.get(hashKey);
if (id == null) {
throw new InvalidKeyException("No identity matches the hash key " + hashKey);
} else {
id.setCertificate(certificates[0]);
}
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#getPrincipals()
*/
public Object[] getPrincipals() {
return identities.keySet().toArray();
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#getName(java.lang.String)
*/
public String getName(String hashKey) {
DelegatedIdentity id = identities.get(hashKey);
return (id == null)? null : id.dn;
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#getKeys(java.lang.String)
*/
public KeyPair getKeys(String hashKey) {
DelegatedIdentity id = identities.get(hashKey);
return (id == null)? null : id.getKeys();
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#writeCertificate(java.lang.String, java.io.Writer)
*/
public void writeCertificate(String hashKey, Writer out) throws IOException {
PEMWriter pem = new PEMWriter(out);
for (X509Certificate cert : getCertificates(hashKey))
{
pem.writeObject(cert);
}
pem.flush();
pem.close();
}
/* (non-Javadoc)
* @see org.astrogrid.security.delegation.Delegations#hasCertificate(java.lang.String)
*/
public boolean hasCertificate(String hashKey) {
X509Certificate[] certs = this.getCertificates(hashKey);
return ( certs != null && certs.length > 0);
}
protected class DelegatedIdentity {
protected final String dn;
protected final KeyPair keys;
protected final CertificateSigningRequest csr;
protected X509Certificate certificate;
protected DelegatedIdentity(String dn,
KeyPair keys) throws GeneralSecurityException {
this.dn = dn;
this.keys = keys;
this.csr = new CertificateSigningRequest(dn, keys);
this.certificate = null;
}
protected synchronized X509Certificate getCertificate() {
return certificate;
}
protected synchronized void setCertificate(X509Certificate c) throws InvalidKeyException {
if (c.getPublicKey().equals(keys.getPublic())) {
certificate = c;
}
else {
throw new InvalidKeyException("This certificate does not match the cached private-key.");
}
}
protected KeyPair getKeys() {
return keys;
}
}
}