<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 * 
 */

package org.apache.jmeter.util;

import java.net.HttpURLConnection;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.jmeter.util.keystore.JmeterKeyStore;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 * The SSLManager handles the KeyStore information for JMeter. Basically, it
 * handles all the logic for loading and initializing all the JSSE parameters
 * and selecting the alias to authenticate against if it is available.
 * SSLManager will try to automatically select the client certificate for you,
 * but if it can't make a decision, it will pop open a dialog asking you for
 * more information.
 * 
 * TODO: does not actually prompt
 * 
 */
public class JsseSSLManager extends SSLManager {
	private static final Logger log = LoggingManager.getLoggerForClass();

	private static final String HTTPS = "https"; // $NON-NLS-1$

	// Temporary fix to allow default protocol to be changed
	private static final String DEFAULT_SSL_PROTOCOL = 
		JMeterUtils.getPropDefault("https.default.protocol","TLS"); // $NON-NLS-1$ // $NON-NLS-2$

	// Allow reversion to original shared session context
	private static final boolean SHARED_SESSION_CONTEXT = 
		JMeterUtils.getPropDefault("https.sessioncontext.shared",false); // $NON-NLS-1$

	private static final int cps;
	
	static {
		log.info("Using default SSL protocol: "+DEFAULT_SSL_PROTOCOL);
		log.info("SSL session context: "+(SHARED_SESSION_CONTEXT ? "shared" : "per-thread"));
        cps = JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); // $NON-NLS-1$        

        if (cps > 0) {
	        log.info("Setting up HTTPS SlowProtocol, cps="+cps);
        }

	}

	/**
	 * Cache the SecureRandom instance because it takes a long time to create
	 */
	private SecureRandom rand;

	private Provider pro = null;

    private SSLContext defaultContext; // If we are using a single session
    private ThreadLocal threadlocal; // Otherwise
       
	/**
	 * Create the SSLContext, and wrap all the X509KeyManagers with
	 * our X509KeyManager so that we can choose our alias.
	 * 
	 * @param provider
	 *            Description of Parameter
	 */
	public JsseSSLManager(Provider provider) {
		log.debug("ssl Provider =  " + provider);
		setProvider(provider);
		if (null == this.rand) { // Surely this is always null in the constructor?
			this.rand = new SecureRandom();
		}
		try {
			if (SHARED_SESSION_CONTEXT) {
				log.debug("Creating shared context");
                this.defaultContext = createContext();
			} else {
		        this.threadlocal = new ThreadLocal();
			}
    
            /*
             * Set up Java defaults.
             * N.B. does not allow SlowSocket - fails with:
             * java.lang.RuntimeException: Export restriction: this JSSE implementation is non-pluggable.
             */
            
            HttpsURLConnection.setDefaultSSLSocketFactory(new HttpSSLProtocolSocketFactory(this));
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            /*
             * Also set up HttpClient defaults
             */
            Protocol protocol = new Protocol(
                    JsseSSLManager.HTTPS, 
                    (ProtocolSocketFactory) new HttpSSLProtocolSocketFactory(this, cps),
                    443);
            Protocol.registerProtocol(JsseSSLManager.HTTPS, protocol);
            log.debug("SSL stuff all set");
        } catch (GeneralSecurityException ex) {
            log.error("Could not set up SSLContext", ex);
        }
		log.debug("JsseSSLManager installed");
	}

	/**
	 * Sets the Context attribute of the JsseSSLManager object
	 * 
	 * @param conn
	 *            The new Context value
	 */
	public void setContext(HttpURLConnection conn) {
		if (conn instanceof HttpsURLConnection) {
/*
 * No point doing this on a per-connection basis, as there is currently no way to configure it.
 * So we leave it to the defaults set up in the SSL Context
 * 
 */
//			HttpsURLConnection secureConn = (HttpsURLConnection) conn;
//			secureConn.setSSLSocketFactory(this.getContext().getSocketFactory());
		} else {
			log.warn("Unexpected HttpURLConnection class: "+conn.getClass().getName());
		}
	}

	/**
	 * Sets the Provider attribute of the JsseSSLManager object
	 * 
	 * @param p
	 *            The new Provider value
	 */
	protected final void setProvider(Provider p) {
		super.setProvider(p);
		if (null == this.pro) {
			this.pro = p;
		}
	}

	/**
	 * Returns the SSLContext we are using.
	 * This is either a context per thread,
	 * or, for backwards compatibility, a single shared context.
	 * 
	 * @return The Context value
	 */
    public SSLContext getContext() throws GeneralSecurityException {
    	if (SHARED_SESSION_CONTEXT) {
        	if (log.isDebugEnabled()){
			    log.debug("Using shared SSL context for: "+Thread.currentThread().getName());
        	}
            return this.defaultContext;
    	}
    	
        SSLContext sslContext = (SSLContext) this.threadlocal.get();
        if (sslContext == null) {
        	if (log.isDebugEnabled()){
			    log.debug("Creating threadLocal SSL context for: "+Thread.currentThread().getName());
        	}
            sslContext = createContext();
            this.threadlocal.set(sslContext);
        }
    	if (log.isDebugEnabled()){
		    log.debug("Using threadLocal SSL context for: "+Thread.currentThread().getName());
    	}
        return sslContext;
    }
    
    /**
     * Resets the SSLContext if using per-thread contexts.
     *
     */
    public void resetContext() {
    	if (!SHARED_SESSION_CONTEXT) {
    		log.debug("Clearing session context for current thread");
            this.threadlocal.set(null);
    	}
    }
    /*
     * 
     * Creates new SSL context
     * @return SSL context
     * @throws GeneralSecurityException
     */
	private SSLContext createContext() throws GeneralSecurityException {
		SSLContext context;
        if (pro != null) {
            context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL, pro); // $NON-NLS-1$
        } else {
            context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); // $NON-NLS-1$
        }
        KeyManagerFactory managerFactory = 
            KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());             
        JmeterKeyStore keys = this.getKeyStore();
        managerFactory.init(null, this.defaultpw.toCharArray());
        KeyManager[] managers = managerFactory.getKeyManagers();
        log.debug(keys.getClass().toString());
        
        // Now wrap the default managers with our key manager
        for (int i = 0; i < managers.length; i++) {
            if (managers[i] instanceof X509KeyManager) {
                X509KeyManager manager = (X509KeyManager) managers[i];
                managers[i] = new WrappedX509KeyManager(manager, keys);
            }
        }
        
        // Get the default trust managers
        TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(<commit_msg>Fix NPE when password is not provided<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 * 
 */

package org.apache.jmeter.util;

import java.net.HttpURLConnection;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.jmeter.util.keystore.JmeterKeyStore;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 * The SSLManager handles the KeyStore information for JMeter. Basically, it
 * handles all the logic for loading and initializing all the JSSE parameters
 * and selecting the alias to authenticate against if it is available.
 * SSLManager will try to automatically select the client certificate for you,
 * but if it can't make a decision, it will pop open a dialog asking you for
 * more information.
 * 
 * TODO: does not actually prompt
 * 
 */
public class JsseSSLManager extends SSLManager {
	private static final Logger log = LoggingManager.getLoggerForClass();

	private static final String HTTPS = "https"; // $NON-NLS-1$

	// Temporary fix to allow default protocol to be changed
	private static final String DEFAULT_SSL_PROTOCOL = 
		JMeterUtils.getPropDefault("https.default.protocol","TLS"); // $NON-NLS-1$ // $NON-NLS-2$

	// Allow reversion to original shared session context
	private static final boolean SHARED_SESSION_CONTEXT = 
		JMeterUtils.getPropDefault("https.sessioncontext.shared",false); // $NON-NLS-1$

	private static final int cps;
	
	static {
		log.info("Using default SSL protocol: "+DEFAULT_SSL_PROTOCOL);
		log.info("SSL session context: "+(SHARED_SESSION_CONTEXT ? "shared" : "per-thread"));
        cps = JMeterUtils.getPropDefault("httpclient.socket.https.cps", 0); // $NON-NLS-1$        

        if (cps > 0) {
	        log.info("Setting up HTTPS SlowProtocol, cps="+cps);
        }

	}

	/**
	 * Cache the SecureRandom instance because it takes a long time to create
	 */
	private SecureRandom rand;

	private Provider pro = null;

    private SSLContext defaultContext; // If we are using a single session
    private ThreadLocal threadlocal; // Otherwise
       
	/**
	 * Create the SSLContext, and wrap all the X509KeyManagers with
	 * our X509KeyManager so that we can choose our alias.
	 * 
	 * @param provider
	 *            Description of Parameter
	 */
	public JsseSSLManager(Provider provider) {
		log.debug("ssl Provider =  " + provider);
		setProvider(provider);
		if (null == this.rand) { // Surely this is always null in the constructor?
			this.rand = new SecureRandom();
		}
		try {
			if (SHARED_SESSION_CONTEXT) {
				log.debug("Creating shared context");
                this.defaultContext = createContext();
			} else {
		        this.threadlocal = new ThreadLocal();
			}
    
            /*
             * Set up Java defaults.
             * N.B. does not allow SlowSocket - fails with:
             * java.lang.RuntimeException: Export restriction: this JSSE implementation is non-pluggable.
             */
            
            HttpsURLConnection.setDefaultSSLSocketFactory(new HttpSSLProtocolSocketFactory(this));
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            /*
             * Also set up HttpClient defaults
             */
            Protocol protocol = new Protocol(
                    JsseSSLManager.HTTPS, 
                    (ProtocolSocketFactory) new HttpSSLProtocolSocketFactory(this, cps),
                    443);
            Protocol.registerProtocol(JsseSSLManager.HTTPS, protocol);
            log.debug("SSL stuff all set");
        } catch (GeneralSecurityException ex) {
            log.error("Could not set up SSLContext", ex);
        }
		log.debug("JsseSSLManager installed");
	}

	/**
	 * Sets the Context attribute of the JsseSSLManager object
	 * 
	 * @param conn
	 *            The new Context value
	 */
	public void setContext(HttpURLConnection conn) {
		if (conn instanceof HttpsURLConnection) {
/*
 * No point doing this on a per-connection basis, as there is currently no way to configure it.
 * So we leave it to the defaults set up in the SSL Context
 * 
 */
//			HttpsURLConnection secureConn = (HttpsURLConnection) conn;
//			secureConn.setSSLSocketFactory(this.getContext().getSocketFactory());
		} else {
			log.warn("Unexpected HttpURLConnection class: "+conn.getClass().getName());
		}
	}

	/**
	 * Sets the Provider attribute of the JsseSSLManager object
	 * 
	 * @param p
	 *            The new Provider value
	 */
	protected final void setProvider(Provider p) {
		super.setProvider(p);
		if (null == this.pro) {
			this.pro = p;
		}
	}

	/**
	 * Returns the SSLContext we are using.
	 * This is either a context per thread,
	 * or, for backwards compatibility, a single shared context.
	 * 
	 * @return The Context value
	 */
    public SSLContext getContext() throws GeneralSecurityException {
    	if (SHARED_SESSION_CONTEXT) {
        	if (log.isDebugEnabled()){
			    log.debug("Using shared SSL context for: "+Thread.currentThread().getName());
        	}
            return this.defaultContext;
    	}
    	
        SSLContext sslContext = (SSLContext) this.threadlocal.get();
        if (sslContext == null) {
        	if (log.isDebugEnabled()){
			    log.debug("Creating threadLocal SSL context for: "+Thread.currentThread().getName());
        	}
            sslContext = createContext();
            this.threadlocal.set(sslContext);
        }
    	if (log.isDebugEnabled()){
		    log.debug("Using threadLocal SSL context for: "+Thread.currentThread().getName());
    	}
        return sslContext;
    }
    
    /**
     * Resets the SSLContext if using per-thread contexts.
     *
     */
    public void resetContext() {
    	if (!SHARED_SESSION_CONTEXT) {
    		log.debug("Clearing session context for current thread");
            this.threadlocal.set(null);
    	}
    }
    /*
     * 
     * Creates new SSL context
     * @return SSL context
     * @throws GeneralSecurityException
     */
	private SSLContext createContext() throws GeneralSecurityException {
		SSLContext context;
        if (pro != null) {
            context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL, pro); // $NON-NLS-1$
        } else {
            context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); // $NON-NLS-1$
        }
        KeyManagerFactory managerFactory = 
            KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());             
        JmeterKeyStore keys = this.getKeyStore();
        managerFactory.init(null, defaultpw == null ? new char[]{} : defaultpw.toCharArray());
        KeyManager[] managers = managerFactory.getKeyManagers();
        log.debug(keys.getClass().toString());
        
        // Now wrap the default managers with our key manager
        for (int i = 0; i < managers.length; i++) {
            if (managers[i] instanceof X509KeyManager) {
                X509KeyManager manager = (X509KeyManager) managers[i];
                managers[i] = new WrappedX509KeyManager(manager, keys);
            }
        }
        
        // Get the default trust managers
        TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
{'fraction_non_alphanumeric': 0.09320667818077144, 'fraction_numerical': 0.005066206102475533, 'mean_word_length': 2.8355045263855154, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5558002', 'n_tokens_mistral': 4910, 'n_tokens_neox': 4628, 'n_words': 1427}