001: /*************************************************************************
002: * *
003: * EJBCA: The OpenSource Certificate Authority *
004: * *
005: * This software is free software; you can redistribute it and/or *
006: * modify it under the terms of the GNU Lesser General Public *
007: * License as published by the Free Software Foundation; either *
008: * version 2.1 of the License, or any later version. *
009: * *
010: * See terms of license at gnu.org. *
011: * *
012: *************************************************************************/package org.ejbca.core.protocol.xkms.common;
013:
014: import gnu.inet.encoding.Stringprep;
015: import gnu.inet.encoding.StringprepException;
016:
017: import java.io.UnsupportedEncodingException;
018: import java.math.BigInteger;
019: import java.security.InvalidKeyException;
020: import java.security.KeyFactory;
021: import java.security.NoSuchAlgorithmException;
022: import java.security.PrivateKey;
023: import java.security.interfaces.RSAPrivateCrtKey;
024: import java.security.interfaces.RSAPrivateKey;
025: import java.security.spec.InvalidKeySpecException;
026: import java.security.spec.RSAPrivateCrtKeySpec;
027:
028: import javax.crypto.Mac;
029: import javax.crypto.SecretKey;
030: import javax.crypto.spec.SecretKeySpec;
031: import javax.xml.bind.JAXBContext;
032: import javax.xml.bind.JAXBElement;
033: import javax.xml.bind.JAXBException;
034: import javax.xml.bind.Marshaller;
035: import javax.xml.bind.PropertyException;
036: import javax.xml.bind.Unmarshaller;
037: import javax.xml.parsers.DocumentBuilder;
038: import javax.xml.parsers.DocumentBuilderFactory;
039: import javax.xml.parsers.ParserConfigurationException;
040:
041: import org.apache.log4j.Logger;
042: import org.apache.xml.security.algorithms.SignatureAlgorithm;
043: import org.apache.xml.security.encryption.EncryptedData;
044: import org.apache.xml.security.encryption.XMLCipher;
045: import org.apache.xml.security.encryption.XMLEncryptionException;
046: import org.apache.xml.security.exceptions.XMLSecurityException;
047: import org.apache.xml.security.transforms.Transforms;
048: import org.apache.xml.security.utils.EncryptionConstants;
049: import org.ejbca.util.CertTools;
050: import org.w3._2001._04.xmlenc_.EncryptedDataType;
051: import org.w3._2002._03.xkms_.ObjectFactory;
052: import org.w3._2002._03.xkms_.PrivateKeyType;
053: import org.w3._2002._03.xkms_.RSAKeyPairType;
054: import org.w3c.dom.Document;
055: import org.w3c.dom.Element;
056:
057: /**
058: * A util class containing static help methods to process various
059: * XKMS messages
060: *
061: *
062: * @author Philip Vendil 2006 dec 30
063: *
064: * @version $Id: XKMSUtil.java,v 1.2 2007/02/02 09:37:48 anatom Exp $
065: */
066:
067: public class XKMSUtil {
068:
069: /** HMAC-SHA1 initial key values */
070: public static final byte[] KEY_AUTHENTICATION = { 0x1 };
071: public static final byte[] KEY_REVOCATIONCODEIDENTIFIER_PASS1 = { 0x2 };
072: public static final byte[] KEY_REVOCATIONCODEIDENTIFIER_PASS2 = { 0x3 };
073: public static final byte[] KEY_PRIVATEKEYDATA = { 0x4 };
074:
075: private static final String ENCRYPTION_ALGORITHMURI = XMLCipher.TRIPLEDES;
076: private static final String SHAREDSECRET_HASH_ALGORITH = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
077:
078: private static Logger log = Logger.getLogger(XKMSUtil.class);
079:
080: private static ObjectFactory xKMSObjectFactory = new ObjectFactory();
081:
082: private static JAXBContext jAXBContext = null;
083: private static Marshaller marshaller = null;
084: private static Unmarshaller unmarshaller = null;
085: private static DocumentBuilderFactory dbf = null;
086:
087: static {
088: try {
089: CertTools.installBCProvider();
090: org.apache.xml.security.Init.init();
091:
092: jAXBContext = JAXBContext
093: .newInstance("org.w3._2002._03.xkms_:org.w3._2001._04.xmlenc_:org.w3._2000._09.xmldsig_");
094: marshaller = jAXBContext.createMarshaller();
095: try {
096: marshaller.setProperty(
097: "com.sun.xml.bind.namespacePrefixMapper",
098: new XKMSNamespacePrefixMapper());
099: } catch (PropertyException e) {
100: log.error(
101: "Error registering namespace mapper property",
102: e);
103: }
104: dbf = DocumentBuilderFactory.newInstance();
105: dbf.setNamespaceAware(true);
106: unmarshaller = jAXBContext.createUnmarshaller();
107:
108: } catch (JAXBException e) {
109: log
110: .error(
111: "Error initializing RequestAbstractTypeResponseGenerator",
112: e);
113: }
114: }
115:
116: /**
117: * Encrypting a java RSA Private key into a PrivateKeyType object used in register,reissue and recover respolses.
118: * using the shared secret.
119: *
120: * The method uses the HMAC-SHA1 for generating the shared secret
121: * and tripple des for encryption
122: *
123: * @param rSAPrivateKey the privatekey
124: * @param sharedSecret the shared secret, cannot be null.
125: * @return The Document with the encrypted key included.
126: * @throws StringprepException if the shared secret doesn't conform with the SASLprep profile as specified in the XKMS specification.
127: * @throws XMLEncryptionException if any other exception occurs during the processing.
128: */
129: public static PrivateKeyType getEncryptedXMLFromPrivateKey(
130: RSAPrivateCrtKey rSAPrivateKey, String sharedSecret)
131: throws StringprepException, XMLEncryptionException {
132: PrivateKeyType privateKeyType = null;
133: try {
134: DocumentBuilder db = dbf.newDocumentBuilder();
135: Document rSAKeyPairDoc = db.newDocument();
136:
137: SecretKey sk = getSecretKeyFromPassphrase(sharedSecret,
138: true, 24, KEY_PRIVATEKEYDATA);
139:
140: RSAKeyPairType rSAKeyPairType = xKMSObjectFactory
141: .createRSAKeyPairType();
142:
143: rSAKeyPairType.setModulus(rSAPrivateKey.getModulus()
144: .toByteArray());
145: rSAKeyPairType.setExponent(rSAPrivateKey
146: .getPublicExponent().toByteArray());
147: rSAKeyPairType
148: .setP(rSAPrivateKey.getPrimeP().toByteArray());
149: rSAKeyPairType
150: .setQ(rSAPrivateKey.getPrimeQ().toByteArray());
151: rSAKeyPairType.setDP(rSAPrivateKey.getPrimeExponentP()
152: .toByteArray());
153: rSAKeyPairType.setDQ(rSAPrivateKey.getPrimeExponentQ()
154: .toByteArray());
155: rSAKeyPairType.setInverseQ(rSAPrivateKey
156: .getCrtCoefficient().toByteArray());
157: rSAKeyPairType.setD(rSAPrivateKey.getPrivateExponent()
158: .toByteArray());
159:
160: JAXBElement<RSAKeyPairType> rSAKeyPair = xKMSObjectFactory
161: .createRSAKeyPair(rSAKeyPairType);
162:
163: marshaller.marshal(rSAKeyPair, rSAKeyPairDoc);
164:
165: Document envelopedDoc = db.newDocument();
166: Element unencryptedElement = envelopedDoc
167: .createElement("PrivateKey");
168: envelopedDoc.appendChild(unencryptedElement);
169: Element node = (Element) envelopedDoc
170: .adoptNode(rSAKeyPairDoc.getDocumentElement());
171: unencryptedElement.appendChild(node);
172:
173: Element rootElement = envelopedDoc.getDocumentElement();
174:
175: XMLCipher xmlCipher = XMLCipher.getProviderInstance(
176: ENCRYPTION_ALGORITHMURI, "BC");
177: xmlCipher.init(XMLCipher.ENCRYPT_MODE, sk);
178:
179: EncryptedData encryptedData = xmlCipher.getEncryptedData();
180: encryptedData.setMimeType("text/xml");
181:
182: xmlCipher.doFinal(envelopedDoc, rootElement, true);
183:
184: JAXBElement unmarshalledData = (JAXBElement) unmarshaller
185: .unmarshal(envelopedDoc.getDocumentElement()
186: .getFirstChild());
187:
188: EncryptedDataType encryptedDataType = (EncryptedDataType) unmarshalledData
189: .getValue();
190: privateKeyType = xKMSObjectFactory.createPrivateKeyType();
191: privateKeyType.setEncryptedData(encryptedDataType);
192:
193: } catch (ParserConfigurationException e) {
194: log.error("Error encryption private key", e);
195: throw new XMLEncryptionException(e.getMessage(), e);
196: } catch (XMLSecurityException e) {
197: log.error("Error encryption private key", e);
198: throw new XMLEncryptionException(e.getMessage(), e);
199: } catch (JAXBException e) {
200: log.error("Error encryption private key", e);
201: throw new XMLEncryptionException(e.getMessage(), e);
202: } catch (Exception e) {
203: log.error("Error encryption private key", e);
204: throw new XMLEncryptionException(e.getMessage(), e);
205: }
206:
207: return privateKeyType;
208: }
209:
210: /**
211: * Method to get the private key from an XKMS message with an encrypted
212: * PrivateKey tag. The method uses the HMAC-SHA1 for generating the shared secret
213: * and tripple des for encryption.
214: *
215: * @param privateKeyType the JAXB version of the PrivateKey tag
216: * @param sharedSecret the shared secret, cannot be null.
217: * @return a java RSAPrivateKey
218: * @throws StringprepException if the shared secret doesn't conform with the SASLprep profile as specified in the XKMS specification.
219: * @throws XMLEncryptionException if any other exception occurs during the processing.
220: */
221: public static RSAPrivateKey getPrivateKeyFromEncryptedXML(
222: PrivateKeyType privateKeyType, String sharedSecret)
223: throws StringprepException, XMLEncryptionException {
224: RSAPrivateKey privkey2 = null;
225: try {
226: DocumentBuilder db = dbf.newDocumentBuilder();
227: Document privateKeyDoc = db.newDocument();
228: marshaller.marshal(privateKeyType, privateKeyDoc);
229:
230: Element encryptedDataElement = (Element) privateKeyDoc
231: .getElementsByTagNameNS(
232: EncryptionConstants.EncryptionSpecNS,
233: EncryptionConstants._TAG_ENCRYPTEDDATA)
234: .item(0);
235:
236: SecretKey sk = getSecretKeyFromPassphrase(sharedSecret,
237: true, 24, KEY_PRIVATEKEYDATA);
238:
239: XMLCipher xmlDecipher = XMLCipher.getProviderInstance(
240: ENCRYPTION_ALGORITHMURI, "BC");
241:
242: xmlDecipher.init(XMLCipher.DECRYPT_MODE, sk);
243:
244: xmlDecipher.doFinal(privateKeyDoc, encryptedDataElement);
245:
246: JAXBElement<RSAKeyPairType> rSAKeyPair = (JAXBElement<RSAKeyPairType>) unmarshaller
247: .unmarshal(privateKeyDoc.getDocumentElement()
248: .getFirstChild());
249:
250: RSAKeyPairType rSAKeyPairType = rSAKeyPair.getValue();
251:
252: RSAPrivateCrtKeySpec rSAPrivateKeySpec = new RSAPrivateCrtKeySpec(
253: new BigInteger(rSAKeyPairType.getModulus()),
254: new BigInteger(rSAKeyPairType.getExponent()),
255: new BigInteger(rSAKeyPairType.getD()),
256: new BigInteger(rSAKeyPairType.getP()),
257: new BigInteger(rSAKeyPairType.getQ()),
258: new BigInteger(rSAKeyPairType.getDP()),
259: new BigInteger(rSAKeyPairType.getDQ()),
260: new BigInteger(rSAKeyPairType.getInverseQ()));
261:
262: privkey2 = (RSAPrivateKey) KeyFactory.getInstance("RSA")
263: .generatePrivate(rSAPrivateKeySpec);
264:
265: } catch (InvalidKeySpecException e) {
266: log.error("Error decrypting private key", e);
267: throw new XMLEncryptionException(e.getMessage(), e);
268: } catch (NoSuchAlgorithmException e) {
269: log.error("Error decrypting private key", e);
270: throw new XMLEncryptionException(e.getMessage(), e);
271: } catch (XMLSecurityException e) {
272: log.error("Error decrypting private key", e);
273: throw new XMLEncryptionException(e.getMessage(), e);
274: } catch (JAXBException e) {
275: log.error("Error decrypting private key", e);
276: throw new XMLEncryptionException(e.getMessage(), e);
277: } catch (ParserConfigurationException e) {
278: log.error("Error decrypting private key", e);
279: throw new XMLEncryptionException(e.getMessage(), e);
280: } catch (Exception e) {
281: log.error("Error decrypting private key", e);
282: throw new XMLEncryptionException(e.getMessage(), e);
283: }
284:
285: return privkey2;
286: }
287:
288: /**
289: * Genereates a secret key from a passphrase according to the
290: * XKMS specifikation. The HMAC-SHA1 algorithm is used.
291: *
292: * The passphrase is first checked against SALSPrep profile
293: * according to the XKMS specificatiom
294: *
295: * @param passphrase the passphrase to use, may no be null
296: * @param performSASLprep if sASLprep should be called on the input string.
297: * @param keylength the length of the key returned.
298: * @param keyType one of the initial KEY_ constants
299: * @return The SecretKey used in encryption/decryption
300: * @throws StringprepException if the passphrase doesn't fullfull the SASLPrep profile
301: * @throws XMLEncryptionException If any other exception occured during generation.
302: */
303: public static SecretKey getSecretKeyFromPassphrase(
304: String passphrase, boolean performSASLprep, int keylength,
305: byte[] keyType) throws StringprepException,
306: XMLEncryptionException {
307: SecretKey retval = null;
308: try {
309: byte[] finalKey = new byte[keylength];
310:
311: int keyIndex = 0;
312: byte[] currentKey = keyType;
313:
314: Document doc = dbf.newDocumentBuilder().newDocument();
315: SignatureAlgorithm sa = new SignatureAlgorithm(doc,
316: SHAREDSECRET_HASH_ALGORITH, 33);
317:
318: // Make the string saslpreped
319: String sASLPrepedPassword = passphrase;
320: if (performSASLprep) {
321: sASLPrepedPassword = Stringprep.saslprep(passphrase);
322: }
323:
324: while (keyIndex < keylength) {
325: SecretKey sk = new SecretKeySpec(currentKey, sa
326: .getJCEAlgorithmString());
327:
328: Mac m = Mac.getInstance("HmacSHA1");
329: m.init(sk);
330: m.update(sASLPrepedPassword.getBytes("ISO8859-1"));
331: byte[] mac = m.doFinal();
332: for (int i = 0; i < mac.length; i++) {
333: if (keyIndex < keylength) {
334: finalKey[keyIndex] = mac[i];
335: keyIndex++;
336: } else {
337: break;
338: }
339: }
340: mac[0] = (byte) (mac[0] ^ currentKey[0]);
341: currentKey = mac;
342:
343: retval = new SecretKeySpec(finalKey, sa
344: .getJCEAlgorithmString());
345: }
346: } catch (IllegalMonitorStateException e) {
347:
348: } catch (ParserConfigurationException e) {
349: log.error("Error generating secret key", e);
350: throw new XMLEncryptionException(e.getMessage(), e);
351: } catch (XMLSecurityException e) {
352: log.error("Error generating secret key", e);
353: throw new XMLEncryptionException(e.getMessage(), e);
354: } catch (NoSuchAlgorithmException e) {
355: log.error("Error generating secret key", e);
356: throw new XMLEncryptionException(e.getMessage(), e);
357: } catch (InvalidKeyException e) {
358: log.error("Error generating secret key", e);
359: throw new XMLEncryptionException(e.getMessage(), e);
360: } catch (IllegalStateException e) {
361: log.error("Error generating secret key", e);
362: throw new XMLEncryptionException(e.getMessage(), e);
363: } catch (UnsupportedEncodingException e) {
364: log.error("Error generating secret key", e);
365: throw new XMLEncryptionException(e.getMessage(), e);
366: }
367:
368: return retval;
369: }
370:
371: /**
372: * Method appending a authorization keybinding element to
373: * a requestDoc
374: *
375: * @param requestDoc
376: * @param passphrase
377: * @param prototypeKeyBindingId
378: * @return the requestDoc with authorization appended
379: * @throws StringprepException if the passphrase doesn't fullfull the SASLPrep profile
380: * @throws XMLSecurityException If any other exception occured during generation.
381: */
382: public static Document appendKeyBindingAuthentication(
383: Document requestDoc, String passphrase,
384: String prototypeKeyBindingId) throws StringprepException,
385: XMLSecurityException {
386: SecretKey sk = XKMSUtil.getSecretKeyFromPassphrase(passphrase,
387: true, 20, XKMSUtil.KEY_AUTHENTICATION);
388:
389: org.apache.xml.security.signature.XMLSignature authXMLSig = new org.apache.xml.security.signature.XMLSignature(
390: requestDoc,
391: "",
392: org.apache.xml.security.signature.XMLSignature.ALGO_ID_MAC_HMAC_SHA1,
393: org.apache.xml.security.c14n.Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
394: org.apache.xml.security.transforms.Transforms transforms = new org.apache.xml.security.transforms.Transforms(
395: requestDoc);
396: transforms
397: .addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
398: authXMLSig
399: .addDocument(
400: "#" + prototypeKeyBindingId,
401: transforms,
402: org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);
403:
404: authXMLSig.sign(sk);
405:
406: Element authenticationElement = requestDoc.createElementNS(
407: "http://www.w3.org/2002/03/xkms#", "Authentication");
408: Element keyBindingAuthenticationElement = requestDoc
409: .createElementNS("http://www.w3.org/2002/03/xkms#",
410: "KeyBindingAuthentication");
411: keyBindingAuthenticationElement.appendChild(authXMLSig
412: .getElement().cloneNode(true));
413: authenticationElement
414: .appendChild(keyBindingAuthenticationElement);
415: requestDoc.getDocumentElement().appendChild(
416: authenticationElement);
417:
418: return requestDoc;
419: }
420:
421: public static Document appendProofOfPossession(Document requestDoc,
422: PrivateKey privateKey, String prototypeKeyBindingId)
423: throws XMLSecurityException {
424: org.apache.xml.security.signature.XMLSignature xmlSig = new org.apache.xml.security.signature.XMLSignature(
425: requestDoc,
426: "",
427: org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1,
428: org.apache.xml.security.c14n.Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
429: Transforms transforms = new org.apache.xml.security.transforms.Transforms(
430: requestDoc);
431: transforms
432: .addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
433:
434: xmlSig
435: .addDocument(
436: "#" + prototypeKeyBindingId,
437: transforms,
438: org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);
439:
440: xmlSig.sign(privateKey);
441:
442: Element pOPElement = requestDoc.createElementNS(
443: "http://www.w3.org/2002/03/xkms#", "ProofOfPossession");
444: pOPElement.appendChild(xmlSig.getElement().cloneNode(true));
445: requestDoc.getDocumentElement().appendChild(pOPElement);
446:
447: return requestDoc;
448: }
449:
450: }
|