双向验证中, 如果服务端证书过期更新了,客户端的信任证书都得一一的更新。所以利用证书链来解决这个问题。
而一般服务端证书和客户端证书都是有独立的CA中心签发的, 本例子有两个CA中心(S03RootCA服务端CA和C03RootCA客户端CA)来模拟证书链的信任关系。本例的关系如下, S: Server, C: Client
S03RootCA
|
|----S03CA1
|---Server1
C03RootCA
|
|----C03CA1
|---Client1
1. 生成服务器端CA证书链Cert Path
1.1 生成服务器端的根证书S03RootCA
1.1.1 创建密钥
openssl genrsa -des3 -out S03RootCA.key 2048
1.1.1 利用CA密钥自签署CA证书
openssl req -config S03RootCA.cnf -new -x509 -days 3650 -key S03RootCA.key -out S03RootCA.crt
1.2 生成服务器端的二级CA证书S03CA1
1.2.1 创建密钥
openssl genrsa -des3 -out S03CA1.key 2048
1.2.2 生成Certificate Signing Request(CSR)
openssl req -config S03CA1.cnf -new -key S03CA1.key -out S03CA1.csr
1.3 生成的csr文件交给CA(S03RootCA)签名后形成自己服务端二级证书
openssl ca -config S03RootCA.cnf -keyfile S03RootCA.key -cert S03RootCA.crt -in S03CA1.csr -out S03CA1.crt -days 3650
2. 生成客户端CA证书链Cert Path
2.1 生成客户端的根证书C03RootCA
2.1.1 创建密钥
openssl genrsa -des3 -out C03RootCA.key 1024
2.1.1 利用CA密钥自签署CA证书
openssl req -config C03RootCA.cnf -new -x509 -days 3650 -key C03RootCA.key -out C03RootCA.crt
2.2 生成客户端的二级CA证书C03CA1
2.2.1 创建密钥
openssl genrsa -des3 -out C03CA1.key 1024
2.2.2 生成Certificate Signing Request(CSR)
openssl req -config C03CA1.cnf -new -key C03CA1.key -out C03CA1.csr
2.3 生成的csr文件交给CA(C03RootCA)签名后形成客户端二级证书
openssl ca -config C03RootCA.cnf -keyfile C03RootCA.key -cert C03RootCA.crt -in C03CA1.csr -out C03CA1.crt -days 3650
(以上步骤已经将服务端CA和客户端CA创建)
3. 利用Keytool生成服务器端的keystore文件并在CA中心签名
3.1 以jks格式生成服务器端包含Public key和Private Key的keystore文件,keypass与storepass务必要一样,因为在tomcat server.xml中只配置一个password.
keytool -genkey -alias Server1 -keystore Server1Keystore.jks -keypass 123456 -storepass 123456 -keyalg RSA -keysize 512 -validity 365 -v -dname "CN = server1.firefly.com,OU =Server1,O = Firefly,L = ShenZhen,C = CN"
3.2 生成Certificate Signing Request(CSR)
keytool -certreq -alias Server1 -keystore Server1Keystore.jks -file Server1.csr
3.3 将Server1.csr到服务器端CA中心(S03CA1)去签名
openssl ca -config S03CA1.cnf -keyfile S03CA1.key -cert S03CA1.crt -in Server1.csr -out Server1FromCA.crt -days 3650
3.4 格式化Server1FromCA.crt,否则用keytool import的时候会出现error:invalid DER-encoded certificate data
openssl x509 -in Server1FromCA.crt -out Server1FromCA.der -outform DER
3.5 将经过CA签名的Server1FromCA.der导入keystore中
3.5.1 格式化根证书S03RootCA, 并导入keystore.
openssl x509 -in S03RootCA.crt -out S03RootCA.der -outform DER
keytool -import -alias S03RootCA -keystore Server1Keystore.jks -file S03RootCA.der
3.5.2 格式化CA二级证书S03CA1, 并导入keystore.
openssl x509 -in S03CA1.crt -out S03CA1.der -outform DER
keytool -import -alias S03CA1 -keystore Server1Keystore.jks -file S03CA1.der
3.5.3 将经过CA签名后的Server1FromCA.der导入keystore.(别名必须与KeyEntry的一样,在导入之前必须先导入CA的根证书和二级证书)
keytool -import -alias Server1 -keystore Server1Keystore.jks -file Server1FromCA.der
4. 利用Keytool生成客户端的keystore文件并在CA中心签名
4.1 以jks格式生成服务器端包含Public key和Private Key的keystore文件.
keytool -genkey -alias Client1 -keystore Client1Keystore.jks -keypass 123456 -storepass 123456 -keyalg RSA -keysize 512 -validity 365 -v -dname "CN = client1.firefly.com,OU =Client1,O = Firefly,L = ShenZhen,C = CN"
4.2 生成Certificate Signing Request(CSR)
keytool -certreq -alias Client1 -keystore Client1Keystore.jks -file Client1.csr
4.3 将Client1.csr到服务器端CA中心(C03CA1)去签名
openssl ca -config C03CA1.cnf -keyfile C03CA1.key -cert C03CA1.crt -in Client1.csr -out Client1FromCA.crt -days 3650
4.4 格式化Client1FromCA.crt,否则用keytool import的时候会出现error:invalid DER-encoded certificate data
openssl x509 -in Client1FromCA.crt -out Client1FromCA.der -outform DER
4.5 将经过CA签名的Server1FromCA.der导入keystore中
4.5.1 格式化根证书C03RootCA, 并导入keystore.
openssl x509 -in C03RootCA.crt -out C03RootCA.der -outform DER
keytool -import -alias C03RootCA -keystore Client1Keystore.jks -file C03RootCA.der
4.5.2 格式化CA二级证书C03CA1, 并导入keystore.
openssl x509 -in C03CA1.crt -out C03CA1.der -outform DER
keytool -import -alias C03CA1 -keystore Client1Keystore.jks -file C03CA1.der
4.5.3 将经过CA签名后的Server1FromCA.der导入keystore.(别名必须与KeyEntry的一样,在导入之前必须先导入CA的根证书和二级证书)
keytool -import -alias Client1 -keystore Client1Keystore.jks -file Client1FromCA.der
5. 客户端和服务器端建立信任关系
5.1 将客户端的(S03RootCA.der,C03CA1.der,Client1FromCA.der)发送给服务端Server1,并导入服务端的truststore。
keytool -import -alias C03CA1 -keystore Server1Truststore.jks -storepass 123456 -file C03CA1.der
5.2 将服务端的(S03RootCA.der,S03CA1.der,Server1FromCA.der)发送给客户端Client1,并导入客户端的truststore,一般客户端会信任服务端的根证书或二级证书。这样服务器端证书更新了, 无需更新客户的truststore。
keytool -import -alias S03RootCA -keystore Client1Truststore.jks -storepass 123456 -file S03RootCA.der
服务器端: Server1Keystore.jks Server1Truststore.jks
客户端: Client1Keystore.jks Client1Truststore.jks
6 在tomcat 服务器配置server.xml
<Connector port="8443" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" disableUploadTimeout="true" acceptCount="100" debug="0" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="keys/Server1Keystore.jks" keystorePass="123456" truststoreFile="keys/Server1Truststore.jks" truststorePass="123456"/>
7 客户端代码
package com.ssl; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.URLEncoder; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; /** * java ClientCertPathTest Client1Truststore.jks 123456 Client1Keystore.jks 123456 192.168.1.123 8443 dummy.txt */ /** * @author Kevin Li * */ public class ClientCertPathTest { public static void main(String args[]) { if (args.length != 7) { System.out .println("Usage: java ClientCertPathTest <trustStore_file> <password> <keyStore_file> <password> <IP> <port> <DummyFile>"); System.exit(1); } try { System.setProperty("javax.net.ssl.trustStore", args[0]); System.setProperty("javax.net.ssl.trustStorePassword", args[1]); System.setProperty("javax.net.ssl.keyStore", args[2]); System.setProperty("javax.net.ssl.keyStorePassword", args[3]); String ip = args[4]; int port = Integer.parseInt(args[5]); String dummyPath = args[6]; System.out.println("TrustStore:" + args[0]); System.out.println("KeyStore:" + args[2]); System.out.println("IP:" + args[4] + ":" + args[5]); System.out.println("--------------------"); SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory .getDefault(); SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(ip, port);// sslsocket.startHandshake(); PrintWriter printwriter = new PrintWriter(new BufferedWriter( new OutputStreamWriter(sslsocket.getOutputStream()))); String requestStr = readFile(dummyPath); System.out.println("Wating from response---"); requestStr = URLEncoder.encode(requestStr); printwriter.write("POST " + "/" + " HTTP/1.0\r\n"); printwriter .write("Content-Length: " + requestStr.length() + "\r\n"); printwriter .write("Content-Type: application/x-www-form-urlencoded\r\n"); printwriter.write("\r\n"); printwriter.write(requestStr); printwriter.println(); printwriter.flush(); BufferedReader bufferedreader = new BufferedReader( new InputStreamReader(sslsocket.getInputStream())); String s = null; while ((s = bufferedreader.readLine()) != null) System.out.println(s); bufferedreader.close(); printwriter.close(); sslsocket.close(); } catch (Exception exception) { exception.printStackTrace(); } } private static String readFile(String path) throws Exception { File inFile = new File(path); FileReader fr = new FileReader(inFile); BufferedReader br = new BufferedReader(fr); StringBuffer sb = new StringBuffer(); String eachLine = br.readLine(); while (eachLine != null) { sb.append(eachLine); eachLine = br.readLine(); } br.close(); fr.close(); return sb.toString(); } }
备注:
当证书签发超过两级时,在IE中查看证书是会出现如下
“因为证书路径中的证书颁发机构似乎没有颁发证书的权限或不能被用作终端实体证书,证书无效”
在Firefox中
Error code: sec_error_path_len_constraint_invalid
Certificate path length constraint is invalid.
SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID -8155 Certificate path length constraint is invalid.
证书->详细信息->基本限制
Subject Type=CA
Path Length Constraint=0 如果为0,将会出现如上问题。
解决方法:
在.cnf中修改 path length
[ v3_ca ]
basicConstraints = critical, CA:true, pathlen:4
附录:
# ================================================= # OpenSSL configuration file # ================================================= #RANDFILE = $ENV::SSLDIR/.rnd [ ca ] default_ca = CA_default [ CA_default ] #dir = $ENV::SSLDIR dir =G:/study/ssl/sm #dir =c:/likun/study/ssl/sm certs = $dir/certs new_certs_dir = $dir/newcerts crl_dir = $dir/crl database = $dir/index.txt private_key = $dir/private/ca.key certificate = $dir/ca.crt serial = $dir/serial crl = $dir/crl.pem RANDFILE = $dir/private/.rand default_days = 365 default_crl_days = 30 default_md = md5 preserve = no policy = policy_anything name_opt = ca_default cert_opt = ca_default x509_extensions = v3_ca [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 2048 default_md = sha1 default_keyfile = privkey.pem distinguished_name = req_distinguished_name x509_extensions = v3_ca string_mask = nombstr [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN countryName_min = 2 countryName_max = 2 localityName = Locality Name (eg, city) localityName_default = ShenZhen organizationName = Organization Name (eg, company) organizationName_default =Firefly commonName = Common Name (eg, YOUR name) commonName_default = S03RootCA commonName_max = 64 emailAddress = Email Address emailAddress_default = [email protected] emailAddress_max = 64 [ usr_cert ] basicConstraints = CA:FALSE # nsCaRevocationUrl = https://url-to-exposed-clr-list/crl.pem [ ssl_server ] basicConstraints = CA:FALSE nsCertType = server keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, nsSGC, msSGC nsComment = "OpenSSL Certificate for SSL Web Server" [ ssl_client ] basicConstraints = CA:FALSE nsCertType = client keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = clientAuth nsComment = "OpenSSL Certificate for SSL Client" [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] basicConstraints = critical, CA:true, pathlen:4 nsCertType = sslCA keyUsage = cRLSign, keyCertSign extendedKeyUsage = serverAuth, clientAuth nsComment = "OpenSSL CA Certificate" [ crl_ext ] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment nsComment = "OpenSSL generated CRL"
# ================================================= # OpenSSL configuration file # ================================================= #RANDFILE = $ENV::SSLDIR/.rnd [ ca ] default_ca = CA_default [ CA_default ] #dir = $ENV::SSLDIR dir =G:/study/ssl/sm #dir =c:/likun/study/ssl/sm certs = $dir/certs new_certs_dir = $dir/newcerts crl_dir = $dir/crl database = $dir/index.txt private_key = $dir/private/ca.key certificate = $dir/ca.crt serial = $dir/serial crl = $dir/crl.pem RANDFILE = $dir/private/.rand default_days = 365 default_crl_days = 30 default_md = sha1 preserve = no policy = policy_anything name_opt = ca_default cert_opt = ca_default [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 2048 default_md = sha1 default_keyfile = privkey.pem distinguished_name = req_distinguished_name x509_extensions = v3_ca string_mask = nombstr [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN countryName_min = 2 countryName_max = 2 localityName = Locality Name (eg, city) localityName_default = ShenZhen organizationName = Organization Name (eg, company) organizationName_default =Firefly commonName = Common Name (eg, YOUR name) commonName_default = S03CA1 commonName_max = 64 [ usr_cert ] basicConstraints = CA:FALSE # nsCaRevocationUrl = https://url-to-exposed-clr-list/crl.pem [ ssl_server ] basicConstraints = CA:FALSE nsCertType = server keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, nsSGC, msSGC nsComment = "OpenSSL Certificate for SSL Web Server" [ ssl_client ] basicConstraints = CA:FALSE nsCertType = client keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = clientAuth nsComment = "OpenSSL Certificate for SSL Client" [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] basicConstraints = critical, CA:true, pathlen:4 nsCertType = sslCA keyUsage = cRLSign, keyCertSign extendedKeyUsage = serverAuth, clientAuth nsComment = "OpenSSL CA Certificate" [ crl_ext ] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment nsComment = "OpenSSL generated CRL"
# ================================================= # OpenSSL configuration file # ================================================= #RANDFILE = $ENV::SSLDIR/.rnd [ ca ] default_ca = CA_default [ CA_default ] #dir = $ENV::SSLDIR dir =G:/study/ssl/sm #dir =c:/likun/study/ssl/sm certs = $dir/certs new_certs_dir = $dir/newcerts crl_dir = $dir/crl database = $dir/index.txt private_key = $dir/private/ca.key certificate = $dir/ca.crt serial = $dir/serial crl = $dir/crl.pem RANDFILE = $dir/private/.rand default_days = 365 default_crl_days = 30 default_md = md5 preserve = no policy = policy_anything name_opt = ca_default cert_opt = ca_default x509_extensions = v3_ca [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 2048 default_md = sha1 default_keyfile = privkey.pem distinguished_name = req_distinguished_name x509_extensions = v3_ca string_mask = nombstr [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN countryName_min = 2 countryName_max = 2 localityName = Locality Name (eg, city) localityName_default = ShenZhen organizationName = Organization Name (eg, company) organizationName_default =Firefly commonName = Common Name (eg, YOUR name) commonName_default = C03RootCA commonName_max = 64 emailAddress = Email Address emailAddress_default = [email protected] emailAddress_max = 64 [ usr_cert ] basicConstraints = CA:FALSE # nsCaRevocationUrl = https://url-to-exposed-clr-list/crl.pem [ ssl_server ] basicConstraints = CA:FALSE nsCertType = server keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, nsSGC, msSGC nsComment = "OpenSSL Certificate for SSL Web Server" [ ssl_client ] basicConstraints = CA:FALSE nsCertType = client keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = clientAuth nsComment = "OpenSSL Certificate for SSL Client" [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] basicConstraints = critical, CA:true, pathlen:4 nsCertType = sslCA keyUsage = cRLSign, keyCertSign extendedKeyUsage = serverAuth, clientAuth nsComment = "OpenSSL CA Certificate" [ crl_ext ] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment nsComment = "OpenSSL generated CRL"
# ================================================= # OpenSSL configuration file # ================================================= #RANDFILE = $ENV::SSLDIR/.rnd [ ca ] default_ca = CA_default [ CA_default ] #dir = $ENV::SSLDIR dir =G:/study/ssl/sm #dir =c:/likun/study/ssl/sm certs = $dir/certs new_certs_dir = $dir/newcerts crl_dir = $dir/crl database = $dir/index.txt private_key = $dir/private/ca.key certificate = $dir/ca.crt serial = $dir/serial crl = $dir/crl.pem RANDFILE = $dir/private/.rand default_days = 365 default_crl_days = 30 default_md = sha1 preserve = no policy = policy_anything name_opt = ca_default cert_opt = ca_default [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 2048 default_md = sha1 default_keyfile = privkey.pem distinguished_name = req_distinguished_name x509_extensions = v3_ca string_mask = nombstr [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN countryName_min = 2 countryName_max = 2 localityName = Locality Name (eg, city) localityName_default = ShenZhen organizationName = Organization Name (eg, company) organizationName_default =Firefly commonName = Common Name (eg, YOUR name) commonName_default = C03CA1 commonName_max = 64 [ usr_cert ] basicConstraints = CA:FALSE # nsCaRevocationUrl = https://url-to-exposed-clr-list/crl.pem [ ssl_server ] basicConstraints = CA:FALSE nsCertType = server keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, nsSGC, msSGC nsComment = "OpenSSL Certificate for SSL Web Server" [ ssl_client ] basicConstraints = CA:FALSE nsCertType = client keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = clientAuth nsComment = "OpenSSL Certificate for SSL Client" [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] basicConstraints = critical, CA:true, pathlen:4 nsCertType = sslCA keyUsage = cRLSign, keyCertSign extendedKeyUsage = serverAuth, clientAuth nsComment = "OpenSSL CA Certificate" [ crl_ext ] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment nsComment = "OpenSSL generated CRL"