001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: Client.java,v 1.6 2008/10/10 00:15:09 hengming Exp $
026 *
027 */
028
029package com.sun.identity.liberty.ws.soapbinding;
030
031import com.sun.identity.common.HttpURLConnectionManager;
032import com.sun.identity.common.SystemConfigurationUtil;
033import com.sun.identity.shared.configuration.SystemPropertiesManager;
034
035import com.sun.identity.liberty.ws.common.wsse.WSSEConstants;
036import com.sun.identity.liberty.ws.security.SecurityUtils;
037import com.sun.identity.saml.xmlsig.JKSKeyProvider;
038
039import java.io.FileInputStream;
040import java.io.InputStream;
041import java.io.IOException;
042import java.io.OutputStream;
043
044import java.net.URL;
045import java.net.URLConnection;
046
047import java.security.cert.X509Certificate;
048import java.security.KeyStore;
049import java.security.NoSuchAlgorithmException;
050import java.security.KeyStoreException;
051import java.security.Provider;
052import java.security.Security;
053
054import javax.net.ssl.HttpsURLConnection;
055import javax.net.ssl.KeyManager;
056import javax.net.ssl.KeyManagerFactory;
057import javax.net.ssl.SSLContext;
058import javax.net.ssl.TrustManager;
059import javax.net.ssl.TrustManagerFactory;
060import javax.net.ssl.X509KeyManager;
061
062import javax.xml.transform.dom.DOMSource;
063import javax.xml.transform.stream.StreamResult;
064import javax.xml.transform.Transformer;
065import javax.xml.transform.TransformerFactory;
066
067import org.w3c.dom.Document;
068import org.w3c.dom.Element;
069import org.w3c.dom.Node;
070
071/**
072 * The <code>Client</code> class provides web service clients with a method to
073 * send requests using SOAP connection to web service servers.
074 *
075 * @supported.all.api
076 */
077public class Client {
078    
079    private static TransformerFactory tfactory =
080            TransformerFactory.newInstance();
081    private static KeyManager[] kms = null;
082    private static TrustManager[] tms = null;
083    private static X509KeyManager defaultX509km = null;
084    private static String defaultCertAlias = null;
085    private final static String SOAP_KEYSTORE_FILE_PROP =
086            "com.sun.identity.liberty.ws.soap.truststore";
087    private final static String SOAP_KEYSTORE_PASS_FILE_PROP =
088            "com.sun.identity.liberty.ws.soap.storepass";
089    private final static String SOAP_KEYSTORE_TYPE_PROP =
090            "com.sun.identity.liberty.ws.soap.storetype";
091    private final static String SOAP_PRIVATE_KEY_PASS_FILE_PROP  =
092            "com.sun.identity.liberty.ws.soap.keypass";
093    private final static String SOAP_TRUST_MNGR_PROP =
094            "com.sun.identity.liberty.ws.soap.trustmanager";
095    private final static String SOAP_TRUST_SECMNGR_ALGO_PROP =
096            "com.sun.identity.liberty.ws.soap.securitymanager.algorithm";
097    
098    static {
099        defaultCertAlias = SystemPropertiesManager.get(
100                "com.sun.identity.liberty.ws.soap.certalias");
101    }
102    
103    private Client() {}
104    
105    /**
106     * Sends a request to a SOAP endpoint and returns the response. The server
107     * only contains one servlet for different web services. So the SOAP
108     * endpoint URL has format 'servlet_URL/key'
109     *
110     * @param req the request
111     * @param connectTo the SOAP endpoint URL
112     * @return a response from the SOAP endpoint
113     * @throws SOAPBindingException if an error occurs while sending the
114     *                                 message
115     * @throws SOAPFaultException if the response is a SOAP Fault
116     */
117    public static Message sendRequest(Message req,String connectTo)
118    throws SOAPBindingException, SOAPFaultException {
119        return sendRequest(req, connectTo, null, null);
120    }
121    
122    /**
123     * Sends a request to a SOAP endpoint and returns the response. The server
124     * only contains one servlet for different web services. So the SOAP
125     * endpoint URL has format 'servlet_URL/key'.
126     *
127     * @param req the request message.
128     * @param connectTo the SOAP endpoint URL
129     * @param certAlias the cert alias of a client certificate being used in
130     *                  SSL
131     * @return a response from the SOAP endpoint
132     * @throws SOAPBindingException if an error occurs while sending the
133     *                                 message
134     * @throws SOAPFaultException if the response is a SOAP Fault
135     */
136    public static Message sendRequest(Message req,String connectTo,
137            String certAlias) throws SOAPBindingException, SOAPFaultException {
138        return sendRequest(req, connectTo, certAlias, null);
139    }
140    
141    /**
142     * Sends a request to a SOAP endpoint and returns the response. The server
143     * only contains one servlet for different web services. So the SOAP
144     * endpoint URL has format 'servlet_URL/key'.
145     *
146     * @param req the request message.
147     * @param connectTo the SOAP endpoint URL
148     * @param certAlias the cert alias of a client certificate
149     * @param soapAction the SOAPAction header
150     * @return a response from the SOAP endpoint
151     * @throws SOAPFaultException if a SOAP Fault occurs
152     * @throws SOAPBindingException if a error occurs while processing,
153     *                                 sending or receiving Message
154     */
155    public static Message sendRequest(Message req,String connectTo,
156            String certAlias,String soapAction)
157            throws SOAPBindingException, SOAPFaultException {
158        URLConnection con = null;
159        
160        try {
161            con = getConnection(connectTo, certAlias);
162        } catch (Exception e) {
163            Utils.debug.error("Client:sendRequest", e);
164            throw new SOAPBindingException(e.getMessage());
165        }
166        
167        if(soapAction == null || soapAction.length() == 0) {
168            soapAction = "";
169        }
170        
171        con.setRequestProperty(SOAPBindingConstants.SOAP_ACTION_HEADER,
172                soapAction);
173        
174        
175        Document doc = null;
176        int securityProfileType = req.getSecurityProfileType();
177        if (securityProfileType == Message.ANONYMOUS ||
178                securityProfileType == Message.BEARER_TOKEN) {
179            doc  = req.toDocument(true);
180        } else {
181            
182            Element sigElem = SecurityUtils.signMessage(req);
183            if (sigElem == null) {
184                String msg = Utils.bundle.getString("cannotSignRequest");
185                Utils.debug.error("Client.sendRequest: " + msg);
186                throw new SOAPBindingException(msg);
187            }
188            doc = sigElem.getOwnerDocument();
189        }
190        if (Utils.debug.messageEnabled()) {
191            Utils.debug.message("Client.sendRequest: signed request\n" + req);
192        }
193        
194        OutputStream os = null;
195        try {
196            os = con.getOutputStream();
197            Transformer transformer = tfactory.newTransformer();
198            transformer.setOutputProperty("omit-xml-declaration", "yes");
199            transformer.transform(new DOMSource(doc.getDocumentElement()),
200                    new StreamResult(os));
201        } catch (Exception e) {
202            Utils.debug.error("Client:sendRequest", e);
203            throw new SOAPBindingException(e.getMessage());
204        } finally {
205            if (os != null) {
206                try {
207                    os.close();
208                } catch (Exception e) {
209                    Utils.debug.error("Client:sendRequest", e);
210                }
211            }
212        }
213        
214        Message resp = null;
215        InputStream is = null;
216        try {
217            is = con.getInputStream();
218            resp = new Message(is);
219            if (resp.getSOAPFault() != null) {
220                throw new SOAPFaultException(resp);
221            }
222            Utils.enforceProcessingRules(resp,
223                    req.getCorrelationHeader().getMessageID(), false);
224        } catch (IOException e) {
225            Utils.debug.error("Client:sendRequest", e);
226            throw new SOAPBindingException(e.getMessage());
227        } finally {
228            if (is != null) {
229                try {
230                    is.close();
231                } catch (Exception e) {
232                    Utils.debug.error("Client:sendRequest", e);
233                }
234            }
235        }
236        
237        resp.setProtocol(con.getURL().getProtocol());
238        if (resp.getSecurityProfileType() != Message.ANONYMOUS &&
239                !SecurityUtils.verifyMessage(resp)) {
240            
241            String msg = Utils.bundle.getString("cannotVerifySignature");
242            Utils.debug.error("Client.sendRequest: " + msg);
243            throw new SOAPBindingException(msg);
244        }
245        return resp;
246    }
247    
248    /**
249     * Gets URLConnection associated with the endpoint. If it is a SSL
250     * connection, the certAlias will be used to get the client certificate.
251     *
252     * @param endpoint the url of the SOAP receiver
253     * @param certAlias the cert alias of a client certificate
254     * @return a URLConnection object
255     * @throws Exception if an error occurs while connecting to server
256     */
257    private static URLConnection getConnection(String endpoint,String certAlias)
258    throws Exception {
259        URL url = new URL(endpoint);
260        URLConnection con = HttpURLConnectionManager.getConnection(url); 
261        
262        if (Utils.debug.messageEnabled()) {
263            Utils.debug.message("Client.getConnection: con class = " +
264                    con.getClass());
265        }
266        
267        if (con instanceof HttpsURLConnection) {
268            if (kms == null) {
269                initializeJSSE();
270            }
271            if (certAlias != null) {
272                kms[0] = new WSX509KeyManager(defaultX509km, certAlias);
273            } else {
274                kms[0] = new WSX509KeyManager(defaultX509km, defaultCertAlias);
275            }
276            
277            SSLContext ctx = SSLContext.getInstance("TLS");
278            ctx.init(kms, tms, null);
279            HttpsURLConnection scon = (HttpsURLConnection) con;
280            scon.setSSLSocketFactory(ctx.getSocketFactory());
281        } else {
282            if (Utils.debug.warningEnabled()) {
283                Utils.debug.warning("Client.getConnection: not instance of " +
284                        "HttpsURLConnection, client cert not selected.");
285            }
286        }
287        
288        con.setDoInput(true);
289        con.setDoOutput(true);
290        con.setRequestProperty("content-type", "text/xml");
291        
292        return con;
293    }
294    
295    
296    
297    
298    /**
299     * Initializes JSSE enviroment.
300     *
301     * @throws Exception if an error occurs while initializing JSSE
302     */
303    private static void initializeJSSE() throws Exception {
304        // put SunJSSE at fisrt place, so that JSSE will work
305        Provider provider = Security.getProvider("SunJSSE");
306        if (provider != null) {
307            Security.removeProvider("SunJSSE");
308            Security.insertProviderAt(provider, 1);
309        }
310        
311        String algorithm =  SystemPropertiesManager.get(
312                SOAP_TRUST_SECMNGR_ALGO_PROP);
313        if(algorithm == null || algorithm.length() <= 0) {
314            algorithm = "SunX509";
315        }
316        JKSKeyProvider jkskp = createKeyProvider() ;
317        KeyStore trustStore = jkskp.getKeyStore();
318        KeyManagerFactory kf = KeyManagerFactory.getInstance(algorithm);
319        kf.init(trustStore,jkskp.getPrivateKeyPass().toCharArray() );
320        
321        kms = kf.getKeyManagers();
322        defaultX509km = (X509KeyManager)kms[0];
323        
324        defineTrustManager(trustStore, algorithm);
325        
326    }
327    
328    /**
329     * Define a Trust manager
330     *
331     * @param trustStore the keystore used to store certificates
332     * @param algorithm the algorithm to user
333     * @throws Exception if an error occurs while instantiating the custom
334     * class or using the keystore
335     */
336    private static void defineTrustManager(KeyStore trustStore,
337            String algorithm)
338            throws SOAPBindingException{
339        boolean error = false ;
340        try{
341            TrustManagerFactory tf =
342                    TrustManagerFactory.getInstance(algorithm);
343            tf.init(trustStore);
344            TrustManager[] defaultTrustManagers = tf.getTrustManagers();
345            String trustManagerDefinition = SystemPropertiesManager.get(
346                    SOAP_TRUST_MNGR_PROP);
347            if(trustManagerDefinition != null
348                    && trustManagerDefinition.length() > 0) {
349                tms = new TrustManager[defaultTrustManagers.length + 1];
350                tms[0] = (TrustManager)
351                Class.forName(trustManagerDefinition).newInstance();
352                for(int i = 0; i < defaultTrustManagers.length; i++) {
353                    tms[i + 1 ] = defaultTrustManagers[i];
354                }
355            } else {                
356                tms = defaultTrustManagers;
357            }
358        }catch(ClassNotFoundException cnfe){
359            Utils.debug.error(
360                    "Client.defineTrustManager class not found: " ,cnfe);
361            error = true ;
362        }catch(InstantiationException ie){
363            Utils.debug.error(
364                    "Client.defineTrustManager cannot instantiate: " , ie);
365            error = true ;
366        }catch(NoSuchAlgorithmException nsae){
367            Utils.debug.error(
368                    "Client.defineTrustManager no algorithm: " , nsae);
369            error = true ;
370        }catch(IllegalAccessException iae){
371            Utils.debug.error(
372                    "Client.defineTrustManager illegal access: " , iae);
373            error = true ;
374        }catch(KeyStoreException kse){
375            Utils.debug.error(
376                    "Client.defineTrustManager keystore: " , kse);
377            error = true ;
378        }
379        if(error  ){
380            String msg = Utils.bundle.getString("cannotDefineTrustManager");
381            throw new SOAPBindingException(msg);
382        }
383        
384    }
385    
386    
387    
388    
389    /**
390     * Checks if  Trust Keystore properties are defined
391     * @return true if a specific trust store is to be used
392     **/
393    
394    private static boolean useSpecificTrustStore(){
395        return(
396                (SystemConfigurationUtil.getProperty(SOAP_KEYSTORE_FILE_PROP)!= null)&&
397                (SystemConfigurationUtil.getProperty(SOAP_KEYSTORE_PASS_FILE_PROP)!= null)&&
398                (SystemConfigurationUtil.getProperty(SOAP_KEYSTORE_TYPE_PROP)!= null)&&
399                (SystemConfigurationUtil.getProperty(SOAP_PRIVATE_KEY_PASS_FILE_PROP)!= null));
400    }
401    
402     /**
403     * Create a JKSKeyProvider using either default properties or specific properties 
404     * @return the JKSKeyProvider
405     **/
406       
407    private static JKSKeyProvider createKeyProvider() {
408        JKSKeyProvider jksKp  ;
409        if (useSpecificTrustStore()) {
410            jksKp = new JKSKeyProvider(
411                    SOAP_KEYSTORE_FILE_PROP,SOAP_KEYSTORE_PASS_FILE_PROP,
412                    SOAP_KEYSTORE_TYPE_PROP, SOAP_PRIVATE_KEY_PASS_FILE_PROP);
413        } else {
414            jksKp = new JKSKeyProvider();
415        }
416        return jksKp ;
417    }
418    
419    
420    
421    
422}