Java tutorial
/* * Copyright 2006, Queensland University of Technology * 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. * * Author: Bradley Beddoes * Creation Date: 1/5/07 * * Purpose: Default crypto processor impl */ package com.qut.middleware.crypto.impl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Random; import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500PrivateCredential; import org.apache.commons.codec.binary.Hex; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3._2000._09.xmldsig_.KeyInfo; import org.w3._2000._09.xmldsig_.KeyValue; import org.w3._2000._09.xmldsig_.ObjectFactory; import org.w3._2000._09.xmldsig_.RSAKeyValue; import org.w3._2000._09.xmldsig_.X509Data; import org.w3._2000._09.xmldsig_.X509IssuerSerialType; import com.qut.middleware.crypto.CryptoProcessor; import com.qut.middleware.crypto.KeystoreResolver; import com.qut.middleware.crypto.exception.CryptoException; import com.qut.middleware.saml2.schemas.metadata.KeyDescriptor; import com.qut.middleware.saml2.schemas.metadata.KeyTypes; import com.qut.middleware.saml2.sec.KeyName; public class CryptoProcessorImpl implements CryptoProcessor { private int certExpiryIntervalInYears; private String certIssuerDN; private String certIssuerEmail; private int keySize; /* Provides keydata that metadata documents will be signed with */ private KeystoreResolver localResolver; /* Local logging instance */ private Logger logger = LoggerFactory.getLogger(CryptoProcessorImpl.class.getName()); /** * Creates crypto processor object that must be configured using setters before use */ public CryptoProcessorImpl() { // not implemented } /** * Creates fully populated crypto processor ready for use * * @param localResolver * @param certIssuerDN * @param certIssuerEmail * @param certExpiryInterval * @param keySize */ public CryptoProcessorImpl(KeystoreResolver localResolver, String certIssuerDN, String certIssuerEmail, int certExpiryInterval, int keySize) { if (localResolver == null) { this.logger.error("localResolver for CryptoProcessorImpl was NULL"); throw new IllegalArgumentException("localResolver for CryptoProcessorImpl was NULL"); } if (certIssuerDN == null) { this.logger.error("certIssuerDN for CryptoProcessorImpl was NULL"); throw new IllegalArgumentException("certIssuerDN for CryptoProcessorImpl was NULL"); } if (certIssuerEmail == null) { this.logger.error("certIssuerEmail for CryptoProcessorImpl was NULL"); throw new IllegalArgumentException("certIssuerEmail for CryptoProcessorImpl was NULL"); } this.localResolver = localResolver; this.certIssuerDN = certIssuerDN; this.certIssuerEmail = certIssuerEmail; this.certExpiryIntervalInYears = certExpiryInterval; this.keySize = keySize; } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.CryptoProcessor#generatePassphrase() */ public String generatePassphrase() { SecureRandom random; String passphrase; byte[] buf; try { /* Attempt to get the specified RNG instance */ random = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException nsae) { random = new SecureRandom(); } buf = new byte[10]; random.nextBytes(buf); passphrase = new String(Hex.encodeHex(buf)); return passphrase; } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.impl.CryptoProcessor#createSigningKeyDescriptor(java.security.interfaces.RSAPublicKey, * java.lang.String) */ public KeyDescriptor createSigningKeyDescriptor(RSAPublicKey pubKey, String keyPairName, String issuerDN, String serialNumber) { KeyDescriptor keyDescriptor = new KeyDescriptor(); keyDescriptor.setUse(KeyTypes.SIGNING); KeyInfo keyInfo = new KeyInfo(); KeyName keyName = new KeyName(keyPairName); keyInfo.getContent().add(keyName); KeyValue keyValue = new KeyValue(); RSAKeyValue rsaKeyValue = new RSAKeyValue(); rsaKeyValue.setExponent(pubKey.getPublicExponent().toByteArray()); rsaKeyValue.setModulus(pubKey.getModulus().toByteArray()); keyValue.getContent().add(rsaKeyValue); keyInfo.getContent().add(keyValue); keyDescriptor.setKeyInfo(keyInfo); if (issuerDN != null && serialNumber != null) { BigInteger serialNumberValue = new BigInteger(serialNumber); X509Data x509Data = new X509Data(); X509IssuerSerialType x509IssuerSerialType = new X509IssuerSerialType(); x509IssuerSerialType.setX509IssuerName(issuerDN); x509IssuerSerialType.setX509SerialNumber(serialNumberValue); x509Data.getX509DataContent() .add(new ObjectFactory().createX509DataX509IssuerSerial(x509IssuerSerialType)); keyInfo.getContent().add(x509Data); } logger.debug("Generated KeyDescriptor for document signing with keyname " + keyPairName); return keyDescriptor; } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.impl.CryptoProcessor#generateKeyPair(int) */ public KeyPair generateKeyPair() throws CryptoException { try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(this.keySize); KeyPair keyPair = keyGen.generateKeyPair(); return keyPair; } catch (NoSuchAlgorithmException e) { this.logger.error("NoSuchAlgorithmException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.impl.CryptoProcessor#generateKeyStore(java.lang.String) */ public KeyStore generateKeyStore() throws CryptoException { try { logger.debug("Generating a new key store."); /* Add BC to the jdk security manager to be able to use it as a provider */ Security.addProvider(new BouncyCastleProvider()); /* Create and init an empty key store */ KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(null, null); /* * Populate all new key stores with the key data of the local resolver, generally this is for metadata * purposes to ensure that all systems in the authentication network can correctly validate the signed * metadata document */ X509Certificate localCertificate = (X509Certificate) localResolver.getLocalCertificate(); Calendar before = new GregorianCalendar(); Calendar expiry = new GregorianCalendar(); before.setTime(localCertificate.getNotBefore()); expiry.setTime(localCertificate.getNotAfter()); addPublicKey(keyStore, new KeyPair(this.localResolver.getLocalPublicKey(), this.localResolver.getLocalPrivateKey()), this.localResolver.getLocalKeyAlias(), this.certIssuerDN, before, expiry); return keyStore; } catch (KeyStoreException e) { this.logger.error("KeyStoreException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (NoSuchAlgorithmException e) { this.logger.error("NoSuchAlgorithmException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (CertificateException e) { this.logger.error("CertificateException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (IOException e) { this.logger.error("IOException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.impl.CryptoProcessor#addPublicKey(java.security.KeyStore, java.security.KeyPair, * java.lang.String, java.lang.String) */ public void addPublicKey(KeyStore ks, KeyPair keyPair, String keyPairName, String keyPairSubjectDN) throws CryptoException { try { X509Certificate cert = generateV3Certificate(keyPair, keyPairSubjectDN); ks.setCertificateEntry(keyPairName, cert); } catch (KeyStoreException e) { this.logger.error("KeyStoreException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } } /** * * @param ks * @param keyPair * @param keyPairName * @param keyPairSubjectDN * @param before * @param expiry * @throws CryptoException */ public void addPublicKey(KeyStore ks, KeyPair keyPair, String keyPairName, String keyPairSubjectDN, Calendar before, Calendar expiry) throws CryptoException { try { X509Certificate cert = generateV3Certificate(keyPair, keyPairSubjectDN, before, expiry); ks.setCertificateEntry(keyPairName, cert); } catch (KeyStoreException e) { this.logger.error("KeyStoreException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.impl.CryptoProcessor#addKeyPair(java.security.KeyStore, java.lang.String, * java.security.KeyPair, java.lang.String, java.lang.String, java.lang.String) */ public KeyStore addKeyPair(KeyStore keyStore, String keyStorePassphrase, KeyPair keyPair, String keyPairName, String keyPairPassphrase, String keyPairSubjectDN) throws CryptoException { logger.debug("Adding key pair to existing key store"); try { // Create the public key certificate for storage in the key store. X509Certificate cert = generateV3Certificate(keyPair, keyPairSubjectDN); X500PrivateCredential privateCredentials = new X500PrivateCredential(cert, keyPair.getPrivate(), keyPairName); Certificate[] certChain = new X509Certificate[1]; certChain[0] = privateCredentials.getCertificate(); // Load our generated key store up. They all have the same password, which we set. keyStore.load(null, keyStorePassphrase.toCharArray()); /* Add certificate which contains the public key and set the private key as a key entry in the key store */ keyStore.setCertificateEntry(privateCredentials.getAlias(), privateCredentials.getCertificate()); keyStore.setKeyEntry(privateCredentials.getAlias(), keyPair.getPrivate(), keyPairPassphrase.toCharArray(), certChain); return keyStore; } catch (NoSuchAlgorithmException e) { this.logger.error("NoSuchAlgorithmException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (CertificateException e) { this.logger.error("CertificateException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (KeyStoreException e) { this.logger.error("KeyStoreException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (IOException e) { this.logger.error("IOException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.impl.CryptoProcessor#generateV3Certificate(java.security.KeyPair, * java.lang.String) */ public X509Certificate generateV3Certificate(KeyPair pair, String certSubjectDN) throws CryptoException { /* Set the start of valid period to now - a few seconds for time skew. */ Calendar before = new GregorianCalendar(); before.add(Calendar.DAY_OF_MONTH, -1); /* Set the certificate expiry date to current time plus configured interval for expiry. */ Calendar expiry = new GregorianCalendar(); expiry.add(Calendar.YEAR, this.certExpiryIntervalInYears); return generateV3Certificate(pair, certSubjectDN, before, expiry); } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.impl.CryptoProcessor#generateV3Certificate(java.security.KeyPair, * java.lang.String) */ private X509Certificate generateV3Certificate(KeyPair pair, String certSubjectDN, Calendar before, Calendar expiry) throws CryptoException { X509V3CertificateGenerator cert = new X509V3CertificateGenerator(); /* Set the certificate serial number to a random number */ Random rand = new Random(); rand.setSeed(System.currentTimeMillis()); /* Generates a number between 0 and 2^32 as the serial */ BigInteger serial = BigInteger.valueOf(rand.nextInt(Integer.MAX_VALUE)); logger.info("Setting X509 Cert Serial to: " + serial); cert.setSerialNumber(serial); /* Set the certificate issuer */ cert.setIssuerDN(new X500Principal(this.certIssuerDN)); /* Set the start of valid period. */ cert.setNotBefore(before.getTime()); /* Set the certificate expiry date. */ cert.setNotAfter(expiry.getTime()); /* Set the subject */ cert.setSubjectDN(new X500Principal(certSubjectDN)); cert.setPublicKey(pair.getPublic()); /* Signature algorithm, this may need to be changed if not all hosts have SHA256 and RSA implementations */ cert.setSignatureAlgorithm("SHA512withRSA"); cert.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false)); /* Only for signing */ cert.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign)); cert.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth)); /* Set a contact email address for the issuer */ cert.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.rfc822Name, this.certIssuerEmail))); logger.debug("Generating X509Certificate for key pair: " + pair); try { /* Use the BouncyCastle provider to actually generate the X509Certificate now */ return cert.generateX509Certificate(pair.getPrivate(), "BC"); } catch (InvalidKeyException e) { this.logger.error("InvalidKeyException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (NoSuchProviderException e) { this.logger.error("NoSuchProviderException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (SecurityException e) { this.logger.error("SecurityException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (SignatureException e) { this.logger.error("SignatureException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } } /* (non-Javadoc) * @see com.qut.middleware.crypto.CryptoProcessor#serializeKeyStore(java.security.KeyStore, java.lang.String, java.lang.String) */ public void serializeKeyStore(KeyStore keyStore, String keyStorePassphrase, String filename) throws CryptoException { FileOutputStream fos = null; try { fos = new FileOutputStream(filename); keyStore.store(fos, keyStorePassphrase.toCharArray()); } catch (FileNotFoundException e) { this.logger.error(e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (KeyStoreException e) { this.logger.error(e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (NoSuchAlgorithmException e) { this.logger.error(e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (CertificateException e) { this.logger.error(e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (IOException e) { this.logger.error(e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } finally { if (fos != null) { try { fos.flush(); fos.close(); } catch (IOException e) { this.logger.error(e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } } } } public PublicKey convertByteArrayPublicKey(byte[] rawKey) throws CryptoException { ByteArrayInputStream inputStream = null; ObjectInputStream objectInputStream = null; PublicKey key; try { inputStream = new ByteArrayInputStream(rawKey); objectInputStream = new ObjectInputStream(inputStream); key = (PublicKey) objectInputStream.readObject(); return key; } catch (IOException e) { throw new CryptoException("Exception when attempting to unserialize PublicKey", e); } catch (ClassNotFoundException e) { throw new CryptoException("Exception when attempting to unserialize PublicKey", e); } finally { try { if (objectInputStream != null) objectInputStream.close(); if (inputStream != null) inputStream.close(); } catch (IOException e) { throw new CryptoException( "Exception when attempting to unserialize PublicKey (stream close failed)", e); } } } public byte[] convertPublicKeyByteArray(PublicKey key) throws CryptoException { byte[] serializedPK; ByteArrayOutputStream outputStream = null; ObjectOutputStream objectOutputStream = null; try { outputStream = new ByteArrayOutputStream(); objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(key); serializedPK = outputStream.toByteArray(); return serializedPK; } catch (IOException e) { throw new CryptoException("Exception when attempting to serialize PublicKey", e); } finally { try { if (objectOutputStream != null) objectOutputStream.close(); if (outputStream != null) outputStream.close(); } catch (IOException e) { throw new CryptoException("Exception when attempting to serialize PublicKey (stream close failed)", e); } } } /* * (non-Javadoc) * * @see com.qut.middleware.crypto.CryptoProcessor#convertKeystoreByteArray(java.security.KeyStore, java.lang.String) */ public byte[] convertKeystoreByteArray(KeyStore keyStore, String keyStorePassphrase) throws CryptoException { byte[] keyStoreBytes; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { keyStore.store(outputStream, keyStorePassphrase.toCharArray()); keyStoreBytes = outputStream.toByteArray(); return keyStoreBytes; } catch (KeyStoreException e) { this.logger.error("KeyStoreException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (NoSuchAlgorithmException e) { this.logger.error("NoSuchAlgorithmException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (CertificateException e) { this.logger.error("CertificateException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } catch (IOException e) { this.logger.error("IOException thrown, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); throw new CryptoException(e.getLocalizedMessage(), e); } finally { try { outputStream.close(); } catch (IOException e) { this.logger.error("IOException thrown in finally, " + e.getLocalizedMessage()); this.logger.debug(e.toString()); } } } public int getCertExpiryIntervalInYears() { return this.certExpiryIntervalInYears; } public void setCertExpiryIntervalInYears(int certExpiryIntervalInYears) { this.certExpiryIntervalInYears = certExpiryIntervalInYears; } public String getCertIssuerDN() { return this.certIssuerDN; } public void setCertIssuerDN(String certIssuerDN) { this.certIssuerDN = certIssuerDN; } public String getCertIssuerEmail() { return this.certIssuerEmail; } public void setCertIssuerEmail(String certIssuerEmail) { this.certIssuerEmail = certIssuerEmail; } public int getKeySize() { return this.keySize; } public void setKeySize(int keySize) { this.keySize = keySize; } public KeystoreResolver getLocalResolver() { return this.localResolver; } public void setLocalResolver(KeystoreResolver localResolver) { this.localResolver = localResolver; } }