[ssl authentication, certificate] ssl syntax API description (SSLContext) in java, and connection with keytool tool


Related articles:
//-----------Java SSL begin----------------------
【ssl authentication, certificate】SSL two-way authentication Difference with SSL one-way authentication (schematic diagram)
[ssl authentication, certificate] ssl syntax API description (SSLContext) in java, connection with keytool tool
[ssl authentication, certificate] SSL two-way authentication java actual combat, keytool create certificate
[ssl authentication , certificate] Wireshark packet capture analysis
[ssl certification, certificate] View the content of the keystore file
//------------Java SSL end---------------- ----------

//-----------The following is the knowledge related to CA certificate and openssl-------------
[ssl certification, certificate] TLS/SSL two-way certification concept, openssl genrsa example
[ssl certification, certificate] openssl genrsa command detailed explanation
[ssl certification, certificate] SSL certificate basic concept, certificate format, difference between openssl and keytool

1 Introduction

In java, we often need to use SSL/TLS connection, which is an encrypted connection. The encrypted transmission of application layer protocols based on TCP/IP protocol such as HTTPS is also often based on this encrypted connection. The bottom layer of this connection jdk has A default implementation is provided, and it is based on the authentication implemented by the keystroe certificate library in jdk.

In commonly used http connection components (such as okhttp, httpcomponent), the underlying connection is generally established through SocketFactroy. Usually, we don’t need to set SocketFactory specially, and the default implementation built in jdk is used:

SocketFactory sf = SocketFactory.getDefault();

When the default SocketFactory implements ssl connection, it is based on {JAVA_HOME}/jre/lib/security/cacertsthe implementation of the default trusted certificate library, so when a certificate that does not exist in the certificate library is encountered, the problem of ssl connection exception will occur.

How to use the default trusted certificate store, please refer to the <2.3.1.1 Using the default certificate store> section below

At this point, we usually create a SocketFactory that ignores authentication checks and set it as the default implementation:

SocketFactory.setDefault(sf);

Or install an untrusted certificate into {JAVA_HOME}/jre/lib/security/cacerts.

But in fact, setting the default value affects the security of the entire system, because at this time it means that all certificates are trusted and vulnerable to attacks, and installing the certificate into the certificate store changes the standard jdk, which is quite Because the application is bound to jdk, it is not easy to migrate or update jdk.

Therefore, in order to solve the above two problems, we should use a specific SocketFactory for a specific service, so that the impact of ignoring certificate verification can be minimized.

The connection between the ssl syntax in java and the keytool tool

The implementation of the standard SSL protocol in Java is specifically implemented through the SSLContext mentioned later, and certificates must be provided in JKS (Java KeyStone) format, and keytool is a tool for generating JKS format files, even if you use openssl The tool creates the certificate, and it needs to use the corresponding tool to convert it into JKS format before it can be used in Java.

2. The system of SSLContext

Combining [ssl authentication, certificate] SSL two-way authentication java implementation, keytool to create a certificate in the example

All SSL/TLS network programming based on the TCP/IP protocol in java is essentially based on the SSLSocket object, so the core of this problem is how to let the component code use our customized method to create the SSLSocket object.

Common network programming components create SSLSocket through the SSLSocketFactory interface, so to customize the SSLSocket created by the network programming component, we only need to provide the component with a customized SSLSocketFactory.

Taking the http component as an example, common http components support us to set a customized SSLSocketFactory object instead of the default implementation.

The question now is, how do we create a custom SSLSocketFactory object?

Let's first take a look at the SSL architecture in jdk:
insert image description here

  • ① Communication core class - SSLSocket and SSLServerSocket. Friends who have used sockets for communication development are better understood. They correspond to Socket and ServerSocket, which just mean Socket and ServerSocket that implement the SSL protocol, and they are also subclasses of Socket and ServerSocket. SSLSocket is responsible for setting cipher suites, managing SSL sessions, handling handshake end times, and setting client mode or server mode.

  • ② Client and server Socket factories - SSLSocketFactory and SSLServerSocketFactory. In the design mode, the factory mode is specially used to produce the required instances. Here, the work of creating SSLSocket and SSLServerSocket objects is handed over to these two factory classes.

  • ③ SSL session - SSL Session. The secure communication handshake process requires a session. In order to improve communication efficiency, the SSL protocol allows multiple SSLSockets to share the same SSL session. In the same session, only the first opened SSLSocket needs to perform an SSL handshake, which is responsible for key generation and exchange. key, and other SSLSockets share key information.

  • ④ SSL context - SSLContext. It is an encapsulation of the entire SSL/TLS protocol and represents the implementation of the secure socket protocol. It is mainly responsible for setting various information in the secure communication process, such as certificate-related information. And it is responsible for constructing factory classes such as SSLSocketFactory, SSLServerSocketFactory and SSLEngine.

  • ⑤ SSL non-blocking engine - SSLEngine. If you're going to do NIO communication, you'll use this class, which lets the process support non-blocking, safe communication.

  • ⑥ Key Manager—KeyManager. This interface is responsible for selecting the security certificate used to prove its identity and sending it to the other party in the communication. The KeyManager object is generated by the KeyManagerFactory factory class.

  • ⑦ Trust Manager - TrustManager. This interface is responsible for judging whether to trust the security certificate of the other party, and the TrustManager object is generated by the TrustManagerFactory factory class.

As we can see from the figure, to create an SSLSocketFactory, we need to create it through the SSLContext object, and the SSLContext object depends on the two objects TrustManager and KeyManager, both of which depend on the KeyStore object.

So we need to start from the KeyStore object.

2.1 KeyStore

The KeyStore object is actually a certificate store. After the jdk built-in certificate store mentioned above {JAVA_HOME}/jre/lib/security/cacertsis loaded into the memory, the KeyStore object is finally formed.

KeyStore is a warehouse, but KeyManager (the input parameter is a KeyStore containing the local private key + local certificate) and TrustManager (the input parameter is a KeyStore containing the peer certificate) will be mentioned later, that is to say, according to different Protocol, KeyStore will actually store different content

There are several ways to create a certificate store:

2.1.1 Create through the certificate library file:

String key="{JAVA_HOME}/jre/lib/security/cacerts";
String password = "123456";
KeyStore keystore=KeyStore.getInstance("JKS");
keystore.load(new FileInputStream(key),password.toCharArray());

In the above code, we create a {JAVA_HOME}/jre/lib/security/cacertscertificate store object using a file as the certificate store source.

The certificate library file can be generated by the keytool command provided by jdk, which will not be discussed here.

2.1.2 Randomly generate a self-signed certificate library

The randomly generated certificate is not stored in the actual physical directory, but is directly imported into the keyStore:

String algorithm = "RSA";
String algorithmSign = "MD5WithRSA";
try {
    
    
    CertAndKeyGen cak = new CertAndKeyGen(algorithm,algorithmSign);
    cak.generate(1024);
    X500Name subject = new X500Name("CN=localhost,o=netease");
    X509Certificate certificate = cak.getSelfCertificate(subject,10);
    
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null);
    
    keyStore.setKeyEntry("local", cak.getPrivateKey(),password.toCharArray(), new Certificate[]{
    
    certificate});
    return keyStore;
}catch (Exception e){
    
    
    throw new RuntimeException(e);
}

Here we use the RSA algorithm to create a certificate generator, and specify a randomly generated key pair with a length of 1024, and then use the key pair to generate a certificate object X509Certificate.

Then we initialized an empty certificate store via keyStore.load(null); and finally set our randomly generated certificate into the certificate store:

keyStore.setKeyEntry("local", cak.getPrivateKey(),password.toCharArray(), new Certificate[]{
    
    certificate});

2.2 KeyManager

KeyManager is a key manager, which is mainly used to verify the validity of certificates. There are usually two ways to create key manager objects.

It needs to be emphasized here that the input parameter of KeyManager is a KeyStore containing the local private key + local certificate. Of course, since a password is required to access keyStone, it is relatively safe; while the input parameter of TrustManager is a keystore that only contains the peer certificate
. KeyStore

2.2.1 KeyManagerFactory factory creation:

String password =123456“;
KeyStore keystore = null; // 省略keystore创建过程,可以参考前面的小结
KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");  
kmf.init(keystore,password.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();

In this code, we get a SunX509 key manager factory and initialize it with the keystore and password we specified. After initialization, we use the factory to create a key manager array.

This key manager array is generated based on our key store and supports the verification of certificates in the key store.

2.2.2 Create a key manager array yourself:

This method is relatively seldom used, because the main function of the key manager is to pass its own key to the other party for verification. Usually we only need to use the KeyManagerFactory and KeyStore to create the default implementation of jdk directly. Rarely There are scenarios where you need to customize the key manager yourself.

The following is X509KeyManagerthe anonymous abstract class implementation of the interface, where you can know which abstract methods exist:

KeyManager[] kms = new KeyManager[]{
    
    
        new X509KeyManager() {
    
    
            @Override
            public String[] getClientAliases(String s, Principal[] principals) {
    
    
                return new String[0];
            }

            @Override
            public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
    
    
                return null;
            }

            @Override
            public String[] getServerAliases(String s, Principal[] principals) {
    
    
                return new String[0];
            }

            @Override
            public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
    
    
                return null;
            }

            @Override
            public X509Certificate[] getCertificateChain(String s) {
    
    
                return new X509Certificate[0];
            }

            @Override
            public PrivateKey getPrivateKey(String s) {
    
    
                return null;
            }
        }
};

2.3 TrustManager

TrustManager is a verification manager, which is generally used to verify 对方发来的证书whether the connection is valid, and there are usually two ways to create it.

TrustManager saves a copy of the authorization certificate of the server, which is similar to a white list, and is used to judge whether the certificate sent by the peer is legal. Only the certificate of tks is valid, otherwise the connection is directly rejected.

2.3.1 Use TrustManagerFactory to create:

String password =123456“;
KeyStore keystore = null; // 省略keystore创建过程,可以参考前面的小结
TrustManagerFactory tmf=TrustManagerFactory.getInstance("SunX509");  
tmf.init(keystore);   //执行init后,就可以调用下文的  tmf.getTrustManagers()方法 
TrustManager[] tms = tmf.getTrustManagers();

Here we use the instance of TrustManagerFactory and KeyStore to create an array of authentication managers, which can be used to verify all certificates sent from the other side of the connection, and only trust the certificates trusted in KeyStore.

2.3.1.1 Use the default certificate store

tmf.init(null); will be automatically used {JAVA_HOME}/jre/lib/security/cacertsas the trust certificate store

public javax.net.ssl.TrustManagerFactory  {
    
    
 public final void init(KeyStore ks)  throws KeyStoreException{
    
    
 factorySpi.engineInit(ks);  //跟进去
}

Implement class TrustManagerFactoryImpl:

public   sun.security.ssl.TrustManagerFactoryImpl  {
    
    
    protected void engineInit(KeyStore ks) throws KeyStoreException{
    
    
     if (ks == null) {
    
    
            try {
    
    
                trustManager = getInstance(TrustStoreManager.getTrustedCerts());   //跟进去TrustStoreManager.getTrustedCerts()
                ......
}
public     TrustStoreManager  {
    
    
	public static Set<X509Certificate> getTrustedCerts() throws Exception {
    
    
        return tam.getTrustedCerts(TrustStoreDescriptor.createInstance());  //TrustStoreDescriptor.createInstance()
    }
public  TrustStoreDescriptor   {
    
    

    private static final String defaultStore =
                defaultStorePath + fileSep + "cacerts";     //{JAVA_HOME}/jre/lib/security/cacerts

   static TrustStoreDescriptor createInstance() {
    
    
             return AccessController.doPrivileged(
                    new PrivilegedAction<TrustStoreDescriptor>() {
    
    

                @Override
                public TrustStoreDescriptor run() {
    
    
                    // Get the system properties for trust store.
                    String storePropName = System.getProperty(
                            "javax.net.ssl.trustStore", jsseDefaultStore);
                    String storePropType = System.getProperty(
                            "javax.net.ssl.trustStoreType",
                            KeyStore.getDefaultType());
                    String storePropProvider = System.getProperty(
                            "javax.net.ssl.trustStoreProvider", "");
                    String storePropPassword = System.getProperty(
                            "javax.net.ssl.trustStorePassword", "");     
   。。。。
    if (!"NONE".equals(storePropName)) {
    
    
                        String[] fileNames =
                                new String[] {
    
    storePropName, defaultStore};               //defaultStore是前文定义的变量                

2.3.2 Create an authentication manager data that trusts all certificates by yourself:

Through the following anonymous abstract class of the X509ExtendedTrustManager interface, we can know that there are abstract methods for peer certificate verification:

TrustManager[] tms = new TrustManager[]{
    
    
    X509TrustManager trustManager = new X509ExtendedTrustManager(){
    
    
        public X509Certificate[] getAcceptedIssuers(){
    
     return null; }
        public void checkClientTrusted(X509Certificate[] certs, String authType){
    
    }
        public void checkServerTrusted(X509Certificate[] certs, String authType){
    
    }
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
    
    }
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
    
    }
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
    
    }
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
    
    }
    }
}

Here we directly create an X509TrustManager object as an element of the authentication manager array, and this object does not verify all certificates (if no exception is thrown, it means that the verification is passed), which means that all certificates are trusted.

This is our common implementation of trust for self-signed certificates. Of course, this is not safe.

2.4 SSLContext

We finally arrived at the SSLContext object. All the objects we created before are ultimately used to construct an encrypted transmission context, which is the SSLContext.

Now we can easily create an SSLContext object:

KeyManager[] kms = null; // 省略创建过程
TrustManager[] tms = null; // 省略创建过程
SecureRandom sr = new SecureRandom();
SSLContext ssl=SSLContext.getInstance("TLS");  
ssl.init(kms,tms,sr);  

The ssl object can be initialized through the key manager, authentication manager and random number SecureRandom, where SecureRandom can also be null.

It is worth noting that in this security context, the key manager and authentication manager are not necessary.

When your program initiates a connection to the server as a client (for example, the http client connects to the http server), if the server does not require authentication of the client certificate (usually the http server does not require authentication of the client certificate), you can not pass kms, at this time, you only need to think whether your program needs to authenticate the server certificate. At this time, you only need to pass tms to decide how to authenticate the server certificate:

ssl.init(null,tms,sr);  

When your program is connected as a server by the client, assuming that you do not require client certificate authentication (most HTTP servers do not require client certificate authentication), you only need to send your certificate to the client. , the client may verify:

ssl.init(kms,null,sr);

When your program connects with another program and both sides require certificate authentication, you need to pass kms and tms at the same time:

ssl.init(kms,tms,sr);

2.5 SSLSocketFactory

Now that all SSL contexts are built, SSLSocketFactory only needs to be obtained directly through SSLContext:

SSLContext ssl=null;//省略创建过程
SSLSocketFactory sf = ssl.getSocketFactory();

So far we have created a fully customized SSLSocketFactory, just pass it to the corresponding component as the socket factory.

2.6 HttpsURLConnection

The common third-party http components can indeed realize the ignore certificate of the specified request through the custom SSLSocketFactory, instead of ignoring the certificate globally, but the built-in jdk is rather pitiful. Instead, it HttpsURLConnectiondirectly 不支持针对连接单独制定socket工厂uses SSLSocketFactory.getDefault()the default socket factory, which leads us to only Ignore certificate verification can be realized by setting ignore certificate globally.

However, this is not completely unchangeable. Check the source code of SSLSocketFactory.getDefault() to find the following code:


  public static synchronized ServerSocketFactory getDefault() {
    
    
	String var0 = getSecurityProperty("ssl.SocketFactory.provider");
	if (var0 != null) {
    
    
	    log("setting up default SSLSocketFactory");
	
	    try {
    
    
	        Class var1 = null;
	
	        try {
    
    
	            var1 = Class.forName(var0);
	        } catch (ClassNotFoundException var5) {
    
    
	            ClassLoader var3 = ClassLoader.getSystemClassLoader();
	            if (var3 != null) {
    
    
	                var1 = var3.loadClass(var0);
	            }
	        }
	
	        log("class " + var0 + " is loaded");
	        SSLSocketFactory var2 = (SSLSocketFactory)var1.newInstance();
	        log("instantiated an instance of class " + var0);
	        theFactory = var2;
	        return var2;
	    } catch (Exception var6) {
    
    
	        log("SSLSocketFactory instantiation failed: " + var6.toString());
	        theFactory = new DefaultSSLSocketFactory(var6);
	        return theFactory;
	    }
	}

Here we see String var0 = getSecurityProperty("ssl.SocketFactory.provider");that there is a way to specify the socket factory implementation class through system properties, so we can use our custom socket factory for a single connection through ThreadLocal and custom implementation classes:

First create a custom factory:

public class CustomSocketFactory implement SSLSocketFactory{
    
    

}

Via threadLocal():

//...
ThreadLocal<Boolean> useCustom = new InheritableThreadLocal<>();

HttpsURLConnect conn = null;//省略创建过程
String old = Security.getProperty("ssl.SocketFactory.provider");
try{
    
    
    if(useCustom.get()){
    
    
        // 指定socket工厂
        Security.setProperty("ssl.SocketFactory.provider", CustomSocketFactory.class.getName());
    }
    conn.connect();
}finally{
    
    
    // 恢复socket工厂
    Security.setProperty("ssl.SocketFactory.provider", old);  //貌似也不对,万一是并发的请求,
														       //那么修改该环境变量的,没有恢复的话,会影响其他的线程读取该环境变量
}
//...

All the classes in this article are based on the implementation of jdk8, and no external dependencies are used. If external dependencies are used, the implementation may be simpler.

reference

ssl connection in java
Java SSL implementation uses detailed explanation
of SSL/TLS communication and its practice in Java

Guess you like

Origin blog.csdn.net/m0_45406092/article/details/129959801