/** Copyright (C) 2012 Delcyon, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.delcyon.capo.crypto; import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.X509EncodedKeySpec; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Random; import java.util.logging.Level; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.DHParameterSpec; import javax.xml.bind.DatatypeConverter; import org.bouncycastle.asn1.x500.X500NameBuilder; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.delcyon.capo.CapoApplication; import com.delcyon.capo.Configuration; import com.delcyon.capo.Configuration.PREFERENCE; import com.delcyon.capo.controller.server.ControllerProcessingException; import com.delcyon.capo.protocol.server.AbstractClientRequestProcessor; import com.delcyon.capo.protocol.server.ClientRequest; import com.delcyon.capo.protocol.server.ClientRequestProcessor; import com.delcyon.capo.protocol.server.ClientRequestProcessorProvider; import com.delcyon.capo.protocol.server.ClientRequestXMLProcessor; import com.delcyon.capo.server.CapoServer; import com.delcyon.capo.server.CapoServer.Preferences; import com.delcyon.capo.xml.XPath; import com.delcyon.capo.xml.cdom.CNode; /** * @author jeremiah * */ @ClientRequestProcessorProvider(name="CertificateRequest") public class CertificateRequestProcessor extends AbstractClientRequestProcessor implements ClientRequestProcessor { private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; private String sessionID = null; @Override public String getSessionId() { return sessionID; } @Override public void init(ClientRequestXMLProcessor clientRequestXMLProcessor, String sessionID, HashMap<String, String> sessionHashMap,String requestName) throws Exception { this.sessionID = sessionID; } @Override public void process(ClientRequest clientRequest) throws Exception { String type = XPath.selectSingleNodeValue(clientRequest.getRequestDocument().getDocumentElement(), "//CertificateRequest/@"+CertificateRequest.Attributes.TYPE); if (type == null || type.trim().isEmpty()) { throw new ControllerProcessingException("CertificateRequest missing type attribute", clientRequest.getRequestDocument()); } else { Element certificateRequestElement = (Element) XPath.selectSingleNode(clientRequest.getRequestDocument().getDocumentElement(), "//CertificateRequest"); String publicKeyString = XPath.selectSingleNodeValue(clientRequest.getRequestDocument().getDocumentElement(), "//CertificateRequest/@"+CertificateRequest.Attributes.CLIENT_PUBLIC_KEY); String dhGeneratorString = XPath.selectSingleNodeValue(clientRequest.getRequestDocument().getDocumentElement(), "//CertificateRequest/@"+CertificateRequest.Attributes.DH_GENERATOR); String dhLengthString = XPath.selectSingleNodeValue(clientRequest.getRequestDocument().getDocumentElement(), "//CertificateRequest/@"+CertificateRequest.Attributes.DH_LENGTH); String dhPrimeString = XPath.selectSingleNodeValue(clientRequest.getRequestDocument().getDocumentElement(), "//CertificateRequest/@"+CertificateRequest.Attributes.DH_PRIME); String clientID = XPath.selectSingleNodeValue(clientRequest.getRequestDocument().getDocumentElement(), "//CertificateRequest/@"+CertificateRequest.Attributes.CLIENT_ID); //process clientID //check to see if client ID is a valid client id, if not, we need to create a new one if (clientID == null || clientID.matches("capo\\.client\\.0") || clientID.matches("capo\\.client\\.\\d+") == false) { clientID = "capo.client."+CapoApplication.getDataManager().nextValue("client_id_sequence")+""; } //create parameter specs BigInteger dhParameterSpecGenerator = new BigInteger(dhGeneratorString, 16); BigInteger dhParameterSpecPrime = new BigInteger(dhPrimeString, 16); int dhParameterSpecLength = Integer.parseInt(dhLengthString); //on remote generate a key pair using original specs KeyPairGenerator keyPairGenerator2 = KeyPairGenerator.getInstance("DH"); DHParameterSpec dhParameterSpec2 = new DHParameterSpec(dhParameterSpecPrime, dhParameterSpecGenerator, dhParameterSpecLength); keyPairGenerator2.initialize(dhParameterSpec2); //generate remote key pair KeyPair keyPair2 = keyPairGenerator2.generateKeyPair(); byte[] encodedPublicKey2 = keyPair2.getPublic().getEncoded(); //start remote key agreement KeyAgreement keyAgreement2 = KeyAgreement.getInstance("DH"); keyAgreement2.init(keyPair2.getPrivate()); //read in keyspec KeyFactory keyFactory2 = KeyFactory.getInstance("DH"); X509EncodedKeySpec x509Spec2 = new X509EncodedKeySpec(DatatypeConverter.parseBase64Binary(publicKeyString)); //load keyspec, and finish key agreement PublicKey publicKey1 = keyFactory2.generatePublic(x509Spec2); keyAgreement2.doPhase(publicKey1, true); //get remote secret key byte secret2[] = keyAgreement2.generateSecret(); //use our secret to generate our remote secret key SecretKeyFactory secretKeyFactory2 = SecretKeyFactory.getInstance("DES"); DESKeySpec desKeySpec2 = new DESKeySpec(secret2); SecretKey secretKey2 = secretKeyFactory2.generateSecret(desKeySpec2); //encrypt message Cipher cipher2 = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher2.init(Cipher.ENCRYPT_MODE, secretKey2); byte encryptedMessage[] = cipher2.doFinal(CapoApplication.getCeritifcate()); //populate attributes certificateRequestElement.setAttribute(CertificateRequest.Attributes.SERVER_PUBLIC_KEY.toString(), DatatypeConverter.printBase64Binary(encodedPublicKey2)); certificateRequestElement.setAttribute(CertificateRequest.Attributes.PAYLOAD.toString(), DatatypeConverter.printBase64Binary(encryptedMessage)); certificateRequestElement.setAttribute(CertificateRequest.Attributes.SERVER_ID.toString(), CapoApplication.getConfiguration().getValue(CapoServer.Preferences.SERVER_ID)); certificateRequestElement.setAttribute(CertificateRequest.Attributes.CLIENT_ID.toString(), clientID); String oneTimePassword = CapoApplication.getConfiguration().getValue(PREFERENCE.CLIENT_VERIFICATION_PASSWORD); if (oneTimePassword.isEmpty()) { oneTimePassword = (new Random().nextInt(Integer.MAX_VALUE))+""; CapoApplication.logger.log(Level.INFO, "One time client verification password = '"+oneTimePassword+"'"); } Document originalRequestDocument = clientRequest.getRequestDocument(); //chnage this to a response, and send it back ((CNode) originalRequestDocument.getDocumentElement()).setNodeName("ServerResponse"); ((CNode) originalRequestDocument.getDocumentElement().getElementsByTagName("CertificateRequest").item(0)).setNodeName("CertificateRequestResponse"); clientRequest.getXmlStreamProcessor().writeDocument(originalRequestDocument); Document requestDocument = clientRequest.getXmlStreamProcessor().readNextDocument(); byte[] encryptedPayload = DatatypeConverter.parseBase64Binary(XPath.selectSingleNodeValue(requestDocument.getDocumentElement(), "//CertificateRequest/@"+CertificateRequest.Attributes.PAYLOAD)); //decrypt message cipher2.init(Cipher.DECRYPT_MODE, secretKey2); String returnedPassword = new String(cipher2.doFinal(encryptedPayload)); if (oneTimePassword.equals(returnedPassword)) { //create certificate from public key byte[] clientPublicKeyBytes = DatatypeConverter.parseBase64Binary(XPath.selectSingleNodeValue(requestDocument.getDocumentElement(), "//CertificateRequest/@"+CertificateRequest.Attributes.CLIENT_PUBLIC_KEY)); KeyFactory keyFactory3 = KeyFactory.getInstance("RSA"); X509EncodedKeySpec x509Spec3 = new X509EncodedKeySpec(clientPublicKeyBytes); //load keyspec, and finish key agreement PublicKey clientPublicKey = keyFactory3.generatePublic(x509Spec3); X500NameBuilder x500NameBuilder = new X500NameBuilder(BCStyle.INSTANCE); String clientAlias = clientID+".cert"; x500NameBuilder.addRDN(BCStyle.CN,clientAlias); String serverAlias = CapoApplication.getConfiguration().getValue(Preferences.SERVER_ID)+".private"; RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) CapoApplication.getKeyStore().getKey(serverAlias, CapoApplication.getConfiguration().getValue(Configuration.PREFERENCE.KEYSTORE_PASSWORD).toCharArray()); ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(rsaPrivateKey); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MONTH, CapoApplication.getConfiguration().getIntValue(Preferences.KEY_MONTHS_VALID)); X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(x500NameBuilder.build(), BigInteger.valueOf(System.currentTimeMillis()), new Date(System.currentTimeMillis() - 50000),calendar.getTime(),x500NameBuilder.build(), clientPublicKey); X509Certificate certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateBuilder.build(contentSigner)); CapoApplication.getKeyStore().setCertificateEntry(clientAlias, certificate); ((CapoServer)CapoApplication.getApplication()).writeKeyStore(CapoApplication.getKeyStore()); Document responseDocument = CapoApplication.getDefaultDocument("default_response.xml"); responseDocument.getDocumentElement().setAttribute("result", "SUCCESS"); clientRequest.getXmlStreamProcessor().writeDocument(responseDocument); } else { Document responseDocument = CapoApplication.getDefaultDocument("default_response.xml"); responseDocument.getDocumentElement().setAttribute("result", "WRONG_PASSWORD"); clientRequest.getXmlStreamProcessor().writeDocument(responseDocument); } } } @Override public Document readNextDocument() throws Exception { // TODO Auto-generated method stub return null; } }