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.client;
013:
014: import gnu.inet.encoding.StringprepException;
015:
016: import java.io.IOException;
017: import java.net.MalformedURLException;
018: import java.net.URL;
019: import java.security.Key;
020: import java.security.PrivateKey;
021: import java.security.cert.CertPath;
022: import java.security.cert.CertPathValidator;
023: import java.security.cert.CertPathValidatorException;
024: import java.security.cert.CertStore;
025: import java.security.cert.CertificateFactory;
026: import java.security.cert.CollectionCertStoreParameters;
027: import java.security.cert.PKIXParameters;
028: import java.security.cert.TrustAnchor;
029: import java.security.cert.X509Certificate;
030: import java.util.ArrayList;
031: import java.util.Collection;
032: import java.util.Date;
033: import java.util.HashSet;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Set;
037:
038: import javax.xml.bind.JAXBContext;
039: import javax.xml.bind.JAXBElement;
040: import javax.xml.bind.JAXBException;
041: import javax.xml.bind.Marshaller;
042: import javax.xml.bind.PropertyException;
043: import javax.xml.bind.Unmarshaller;
044: import javax.xml.namespace.QName;
045: import javax.xml.parsers.DocumentBuilder;
046: import javax.xml.parsers.DocumentBuilderFactory;
047: import javax.xml.parsers.ParserConfigurationException;
048: import javax.xml.transform.Source;
049: import javax.xml.transform.TransformerFactoryConfigurationError;
050: import javax.xml.transform.dom.DOMSource;
051: import javax.xml.transform.stream.StreamSource;
052: import javax.xml.ws.Dispatch;
053: import javax.xml.ws.Service;
054:
055: import org.apache.log4j.Logger;
056: import org.apache.xml.security.exceptions.XMLSecurityException;
057: import org.apache.xml.security.signature.XMLSignatureException;
058: import org.apache.xml.security.transforms.TransformationException;
059: import org.ejbca.core.protocol.xkms.XKMSService;
060: import org.ejbca.core.protocol.xkms.common.XKMSConstants;
061: import org.ejbca.core.protocol.xkms.common.XKMSNamespacePrefixMapper;
062: import org.ejbca.core.protocol.xkms.common.XKMSUtil;
063: import org.ejbca.util.CertTools;
064: import org.w3._2002._03.xkms_.LocateRequestType;
065: import org.w3._2002._03.xkms_.LocateResultType;
066: import org.w3._2002._03.xkms_.ObjectFactory;
067: import org.w3._2002._03.xkms_.RecoverRequestType;
068: import org.w3._2002._03.xkms_.RecoverResultType;
069: import org.w3._2002._03.xkms_.RegisterRequestType;
070: import org.w3._2002._03.xkms_.RegisterResultType;
071: import org.w3._2002._03.xkms_.ReissueRequestType;
072: import org.w3._2002._03.xkms_.ReissueResultType;
073: import org.w3._2002._03.xkms_.RequestAbstractType;
074: import org.w3._2002._03.xkms_.RevokeRequestType;
075: import org.w3._2002._03.xkms_.RevokeResultType;
076: import org.w3._2002._03.xkms_.ValidateRequestType;
077: import org.w3._2002._03.xkms_.ValidateResultType;
078: import org.w3c.dom.Document;
079: import org.xml.sax.SAXException;
080:
081: /**
082: * Helper class that performs the prefix replacements
083: * and does the dispatch invokation.
084: *
085: *
086: * @author Philip Vendil 2006 dec 19
087: *
088: * @version $Id: XKMSInvoker.java,v 1.2 2007/01/05 05:32:54 herrvendil Exp $
089: */
090:
091: public class XKMSInvoker {
092:
093: private static Logger log = Logger.getLogger(XKMSInvoker.class);
094:
095: private static JAXBContext jAXBContext = null;
096: private static Marshaller marshaller = null;
097: private static Unmarshaller unmarshaller = null;
098: private static DocumentBuilderFactory dbf = null;
099:
100: private Collection cacerts = null;
101:
102: private static Dispatch<Source> sourceDispatch = null;
103: private ObjectFactory xKMSObjectFactory = new ObjectFactory();
104:
105: static {
106: try {
107: org.apache.xml.security.Init.init();
108: CertTools.installBCProvider();
109:
110: jAXBContext = JAXBContext
111: .newInstance("org.w3._2002._03.xkms_:org.w3._2001._04.xmlenc_:org.w3._2000._09.xmldsig_");
112: marshaller = jAXBContext.createMarshaller();
113: try {
114: marshaller.setProperty(
115: "com.sun.xml.bind.namespacePrefixMapper",
116: new XKMSNamespacePrefixMapper());
117: } catch (PropertyException e) {
118: log.error(
119: "Error registering namespace mapper property",
120: e);
121: }
122: dbf = DocumentBuilderFactory.newInstance();
123: dbf.setNamespaceAware(true);
124: unmarshaller = jAXBContext.createUnmarshaller();
125:
126: } catch (JAXBException e) {
127: log
128: .error(
129: "Error initializing RequestAbstractTypeResponseGenerator",
130: e);
131: }
132:
133: }
134:
135: /**
136: * Creates an invoker to the web service at the specified URL
137: *
138: * @param serviceURL the url to the web service.
139: * @param cacerts a collection of trusted CA signing responses. Use null if signed responeses isn't required.
140: */
141: public XKMSInvoker(String serviceURL, Collection cacerts) {
142: XKMSService xkmsService;
143: try {
144: xkmsService = new XKMSService(
145: new URL(serviceURL + ".wsdl"), new QName(
146: "http://www.w3.org/2002/03/xkms#wsdl",
147: "XKMSService"));
148: sourceDispatch = xkmsService.createDispatch(new QName(
149: "http://www.w3.org/2002/03/xkms#wsdl", "XKMSPort"),
150: Source.class, Service.Mode.PAYLOAD);
151: } catch (MalformedURLException e) {
152: log.error("Error creating XKMS Service instance", e);
153: }
154:
155: this .cacerts = cacerts;
156: if (cacerts == null) {
157: cacerts = new ArrayList();
158: }
159: }
160:
161: /**
162: * Creates a locate call to the web service
163: *
164: * @param locateRequestType the request
165: * @param signCert the certificate that should sign the request, or null of no signing should be performed
166: * @param privateKey the key doing the signing, or null of no signing should be performed
167: * @return a LocateResultType
168: * @throws XKMSResponseSignatureException if the response signature didn't verify
169: */
170: public LocateResultType locate(LocateRequestType locateRequestType,
171: X509Certificate signCert, Key privateKey)
172: throws XKMSResponseSignatureException {
173: JAXBElement<LocateRequestType> locateRequest = xKMSObjectFactory
174: .createLocateRequest(locateRequestType);
175: DOMSource domSource = performSigning(locateRequest,
176: locateRequestType.getId(), signCert, privateKey);
177: JAXBElement<LocateResultType> response = invoke(domSource);
178:
179: return response.getValue();
180: }
181:
182: /**
183: * Creates a validate call to the web service
184: *
185: * @param validateRequestType the request
186: * @param signCert the certificate that should sign the request, or null of no signing should be performed
187: * @param privateKey the key doing the signing, or null of no signing should be performed
188: * @return a ValidateResultType
189: * @throws XKMSResponseSignatureException if the response signature didn't verify
190: */
191: public ValidateResultType validate(
192: ValidateRequestType validateRequestType,
193: X509Certificate signCert, Key privateKey)
194: throws XKMSResponseSignatureException {
195: JAXBElement<ValidateRequestType> validateRequest = xKMSObjectFactory
196: .createValidateRequest(validateRequestType);
197: DOMSource domSource = performSigning(validateRequest,
198: validateRequestType.getId(), signCert, privateKey);
199: JAXBElement<ValidateResultType> response = invoke(domSource);
200:
201: return response.getValue();
202: }
203:
204: /**
205: * Creates a register call to the web service
206: *
207: * @param registerRequestType the request
208: * @param signCert the certificate that should sign the request, or null of no signing should be performed
209: * @param privateKey the key doing the signing, or null of no signing should be performed
210: * @param authenticationPassphrase the authenticationkeybinding passphrase, use null if it shouldn't be used.
211: * @param pOPPrivateKey private key to sign POP Element, use null to not append POPElement
212: * @param prototypeKeyBindingId is of the PrototypeKeyBinding tag.
213: * @return a RegisterResultType
214: * @throws XKMSResponseSignatureException if the response signature didn't verify
215: * @throws StringprepException if the passphrase doesn't fullfull the SASLPrep profile
216: */
217: public RegisterResultType register(
218: RegisterRequestType registerRequestType,
219: X509Certificate signCert, Key privateKey,
220: String authenticationPassphrase, PrivateKey pOPPrivateKey,
221: String prototypeKeyBindingId)
222: throws XKMSResponseSignatureException, StringprepException {
223: JAXBElement<RegisterRequestType> registerRequest = xKMSObjectFactory
224: .createRegisterRequest(registerRequestType);
225: DOMSource domSource = performSigning(registerRequest,
226: registerRequestType.getId(), signCert, privateKey,
227: authenticationPassphrase, pOPPrivateKey,
228: prototypeKeyBindingId);
229: JAXBElement<RegisterResultType> response = invoke(domSource);
230:
231: return response.getValue();
232: }
233:
234: /**
235: * Creates a reissue call to the web service
236: *
237: * @param reissueRequestType the request
238: * @param signCert the certificate that should sign the request, or null of no signing should be performed
239: * @param privateKey the key doing the signing, or null of no signing should be performed
240: * @param authenticationPassphrase the authenticationkeybinding passphrase, use null if it shouldn't be used.
241: * @param pOPPrivateKey private key to sign POP Element, use null to not append POPElement
242: * @param reissueKeyBindingId is of the PrototypeKeyBinding tag.
243: * @return a ReissueResultType
244: * @throws XKMSResponseSignatureException if the response signature didn't verify
245: * @throws StringprepException if the passphrase doesn't fullfull the SASLPrep profile
246: */
247: public ReissueResultType reissue(
248: ReissueRequestType reissueRequestType,
249: X509Certificate signCert, Key privateKey,
250: String authenticationPassphrase, PrivateKey pOPPrivateKey,
251: String reissueKeyBindingId)
252: throws XKMSResponseSignatureException, StringprepException {
253: JAXBElement<ReissueRequestType> reissueRequest = xKMSObjectFactory
254: .createReissueRequest(reissueRequestType);
255: DOMSource domSource = performSigning(reissueRequest,
256: reissueRequestType.getId(), signCert, privateKey,
257: authenticationPassphrase, pOPPrivateKey,
258: reissueKeyBindingId);
259: JAXBElement<ReissueResultType> response = invoke(domSource);
260:
261: return response.getValue();
262: }
263:
264: /**
265: * Creates a recover call to the web service
266: *
267: * @param recoverRequestType the request
268: * @param signCert the certificate that should sign the request, or null of no signing should be performed
269: * @param privateKey the key doing the signing, or null of no signing should be performed
270: * @param authenticationPassphrase the authenticationkeybinding passphrase, use null if it shouldn't be used.
271: * @param reissueKeyBindingId is of the PrototypeKeyBinding tag.
272: * @return a ReissueResultType
273: * @throws XKMSResponseSignatureException if the response signature didn't verify
274: * @throws StringprepException if the passphrase doesn't fullfull the SASLPrep profile
275: */
276: public RecoverResultType recover(
277: RecoverRequestType recoverRequestType,
278: X509Certificate signCert, Key privateKey,
279: String authenticationPassphrase, String recoverKeyBindingId)
280: throws XKMSResponseSignatureException, StringprepException {
281: JAXBElement<RecoverRequestType> recoverRequest = xKMSObjectFactory
282: .createRecoverRequest(recoverRequestType);
283: DOMSource domSource = performSigning(recoverRequest,
284: recoverRequestType.getId(), signCert, privateKey,
285: authenticationPassphrase, null, recoverKeyBindingId);
286: JAXBElement<RecoverResultType> response = invoke(domSource);
287:
288: return response.getValue();
289: }
290:
291: /**
292: * Creates a revoke call to the web service
293: *
294: * @param recvokeRequestType the request
295: * @param signCert the certificate that should sign the request, or null of no signing should be performed
296: * @param privateKey the key doing the signing, or null of no signing should be performed
297: * @param authenticationPassphrase the authenticationkeybinding passphrase, use null if it shouldn't be used.
298: * @param revokeKeyBindingId is of the PrototypeKeyBinding tag.
299: * @return a RevokeResultType
300: * @throws XKMSResponseSignatureException if the response signature didn't verify
301: * @throws StringprepException if the passphrase doesn't fullfull the SASLPrep profile
302: */
303: public RevokeResultType revoke(RevokeRequestType revokeRequestType,
304: X509Certificate signCert, Key privateKey,
305: String authenticationPassphrase, String revokeKeyBindingId)
306: throws XKMSResponseSignatureException, StringprepException {
307: JAXBElement<RevokeRequestType> revokeRequest = xKMSObjectFactory
308: .createRevokeRequest(revokeRequestType);
309: DOMSource domSource = performSigning(revokeRequest,
310: revokeRequestType.getId(), signCert, privateKey,
311: authenticationPassphrase, null, revokeKeyBindingId);
312: JAXBElement<RevokeResultType> response = invoke(domSource);
313:
314: return response.getValue();
315: }
316:
317: /**
318: * Method that performs the actual invokation.
319: * @param abstractMessageType
320: * @return
321: * @throws XKMSResponseSignatureException
322: */
323: private JAXBElement invoke(DOMSource domSource)
324: throws XKMSResponseSignatureException {
325: JAXBElement result = null;
326:
327: try {
328: Source response = sourceDispatch.invoke(domSource);
329:
330: DocumentBuilder db = dbf.newDocumentBuilder();
331: Document doc = db.parse(((StreamSource) response)
332: .getInputStream());
333:
334: verifyResponseSignature(doc);
335: result = (JAXBElement) unmarshaller.unmarshal(doc);
336: } catch (JAXBException e) {
337: log.error("Error marshalling XKMS request", e);
338: } catch (ParserConfigurationException e) {
339: log.error("Error parsing XKMS response", e);
340: } catch (SAXException e) {
341: log.error("Error parsing XKMS response", e);
342: } catch (IOException e) {
343: log.error("Error parsing XKMS response", e);
344: }
345:
346: return result;
347: }
348:
349: /**
350: * Creates a signature on a request and returns a DOM source.
351: *
352: * @param messageAbstractType the request to sign
353: * @param signCert the certificate that should sign the request, or null of no signing should be performed
354: * @param privateKey the key doing the signing, or null of no signing should be performed
355: * @return a DOMSource or null if request was invalid
356: */
357: private DOMSource performSigning(JAXBElement messageAbstractType,
358: String messageId, X509Certificate signCert, Key privateKey) {
359: DOMSource retval = null;
360:
361: try {
362: retval = performSigning(messageAbstractType, messageId,
363: signCert, privateKey, null, null, null);
364: } catch (StringprepException e) {
365: // Should never happen
366: }
367:
368: return retval;
369: }
370:
371: /**
372: * Creates a signature on a request and returns a DOM source.
373: *
374: * @param messageAbstractType the request to sign
375: * @param signCert the certificate that should sign the request, or null of no signing should be performed
376: * @param privateKey the key doing the signing, or null of no signing should be performed
377: * @param authenticationPassphrase the authenticationkeybinding passphrase, use null if it shouldn't be used.
378: * @param pOPPrivateKey private key to sign POP Element, use null to not append POPElement
379: * @param prototypeKeyBindingId is of the PrototypeKeyBinding tag.
380: * @return a DOMSource or null if request was invalid
381: * @throws StringprepException if the passphrase doesn't fullfull the SASLPrep profile
382: */
383: private DOMSource performSigning(JAXBElement messageAbstractType,
384: String messageId, X509Certificate signCert, Key privateKey,
385: String authenticationPassphrase, PrivateKey pOPPrivateKey,
386: String prototypeKeyBindingId) throws StringprepException {
387: DOMSource retval = null;
388:
389: try {
390: if (signCert != null && privateKey != null) {
391: RequestAbstractType requestAbstractType = (RequestAbstractType) messageAbstractType
392: .getValue();
393: requestAbstractType.getResponseMechanism().add(
394: XKMSConstants.RESPONSMEC_REQUESTSIGNATUREVALUE);
395: }
396:
397: Document doc = dbf.newDocumentBuilder().newDocument();
398: marshaller.marshal(messageAbstractType, doc);
399:
400: if (authenticationPassphrase != null) {
401: doc = XKMSUtil
402: .appendKeyBindingAuthentication(doc,
403: authenticationPassphrase,
404: prototypeKeyBindingId);
405: }
406:
407: if (pOPPrivateKey != null) {
408: doc = XKMSUtil.appendProofOfPossession(doc,
409: pOPPrivateKey, prototypeKeyBindingId);
410: }
411:
412: if (signCert != null && privateKey != null) {
413: org.apache.xml.security.signature.XMLSignature xmlSig = new org.apache.xml.security.signature.XMLSignature(
414: doc,
415: "",
416: org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1,
417: org.apache.xml.security.c14n.Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
418: org.apache.xml.security.transforms.Transforms transforms = new org.apache.xml.security.transforms.Transforms(
419: doc);
420: transforms
421: .addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
422: transforms
423: .addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
424: xmlSig
425: .addDocument(
426: "#" + messageId,
427: transforms,
428: org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);
429: xmlSig.addKeyInfo(signCert);
430: doc.getDocumentElement().insertBefore(
431: xmlSig.getElement(),
432: doc.getDocumentElement().getFirstChild());
433: xmlSig.sign(privateKey);
434: }
435:
436: retval = new DOMSource(doc);
437:
438: } catch (XMLSignatureException e) {
439: log.error("Error performing XML Signature ", e);
440: } catch (TransformationException e) {
441: log.error("Error parsing XML request ", e);
442: } catch (JAXBException e) {
443: log.error("Error parsing XML request ", e);
444: } catch (ParserConfigurationException e) {
445: log.error("Error parsing XML request ", e);
446: } catch (XMLSecurityException e) {
447: log.error("Error performing XML Signature ", e);
448: }
449:
450: return retval;
451: }
452:
453: /**
454: * Method that verifies the response signature,
455: *
456: * doesn't check the revokation status of the server certificate.
457: *
458: * @param response, the response from the service
459: * @throws {@link XKMSResponseSignatureException} if the signature doesn't verify
460: */
461: private void verifyResponseSignature(Document doc)
462: throws XKMSResponseSignatureException {
463: try {
464:
465: boolean signatureExists = false;
466:
467: org.w3c.dom.NodeList xmlSigs = doc.getElementsByTagNameNS(
468: "http://www.w3.org/2000/09/xmldsig#", "Signature");
469: signatureExists = xmlSigs.getLength() > 0;
470:
471: if (signatureExists && cacerts != null) {
472:
473: try {
474: org.w3c.dom.Element xmlSigElement = (org.w3c.dom.Element) xmlSigs
475: .item(0);
476: org.apache.xml.security.signature.XMLSignature xmlVerifySig = new org.apache.xml.security.signature.XMLSignature(
477: xmlSigElement, null);
478:
479: org.apache.xml.security.keys.KeyInfo keyInfo = xmlVerifySig
480: .getKeyInfo();
481: java.security.cert.X509Certificate verCert = keyInfo
482: .getX509Certificate();
483:
484: // Check signature
485: if (xmlVerifySig.checkSignatureValue(verCert)) {
486:
487: Collection cACertChain = cacerts;
488: // Check issuer and validity
489: X509Certificate rootCert = null;
490: Iterator iter = cACertChain.iterator();
491: while (iter.hasNext()) {
492: X509Certificate cert = (X509Certificate) iter
493: .next();
494: if (cert.getIssuerDN().equals(
495: cert.getSubjectDN())) {
496: rootCert = cert;
497: break;
498: }
499: }
500:
501: if (rootCert == null) {
502: throw new CertPathValidatorException(
503: "Error Root CA cert not found in cACertChain");
504: }
505:
506: List list = new ArrayList();
507: list.add(verCert);
508: list.add(cACertChain);
509:
510: CollectionCertStoreParameters ccsp = new CollectionCertStoreParameters(
511: list);
512: CertStore store = CertStore.getInstance(
513: "Collection", ccsp);
514:
515: //validating path
516: List certchain = new ArrayList();
517: certchain.addAll(cACertChain);
518: certchain.add(verCert);
519: CertPath cp = CertificateFactory.getInstance(
520: "X.509", "BC").generateCertPath(
521: certchain);
522:
523: Set trust = new HashSet();
524: trust.add(new TrustAnchor(rootCert, null));
525:
526: CertPathValidator cpv = CertPathValidator
527: .getInstance("PKIX", "BC");
528: PKIXParameters param = new PKIXParameters(trust);
529: param.addCertStore(store);
530: param.setDate(new Date());
531: param.setRevocationEnabled(false);
532:
533: cpv.validate(cp, param);
534: } else {
535: throw new XKMSResponseSignatureException(
536: "Error XKMS request signature doesn't verify.");
537: }
538: } catch (Exception e) {
539: throw new XKMSResponseSignatureException(
540: "Error when verifying signature request.",
541: e);
542: }
543: } else {
544: if (cacerts != null) {
545: throw new XKMSResponseSignatureException(
546: "Error XKMS response didn't return and signed response");
547: }
548: }
549: } catch (TransformerFactoryConfigurationError e) {
550: log.error("Error when DOM parsing request.", e);
551: }
552: }
553: }
|