我已经在运行AWS的Ubuntu上创建了自签名CA证书。我还创建了由当前CA签名的客户端和服务器证书。
Mosquitto MQTT服务使用 SSL连接。不幸的是,当客户端尝试连接服务器时,出现Received fatal alert: unknown_ca
错误。
我在Raspberry Pi上测试了相同的证书生成过程,并且SSL + MQTT运行良好。
创建证书:
# CA
openssl req -new -newkey rsa:2048 -keyout private/cakey.pem -out careq.pem -config ./openssl.cnf
openssl ca -create_serial -out cacert.pem -days 365 -keyfile private/cakey.pem -selfsign -config ./openssl.cnf -infiles careq.pem
# Server
openssl req -new -out server.csr -config openssl.cnf -newkey rsa:2048 -nodes -keyout server.key
openssl ca -config openssl.cnf -in server.csr -out server.crt
# Client
openssl req -new -out client.csr -config openssl.cnf -newkey rsa:2048 -nodes -keyout client.key
openssl ca -config openssl.cnf -in client.csr -out client.crt
openssl.cnf
内容:
[ ca ]
default_ca = CA_default # The default ca section
[ CA_default ]
dir = . # Where everything is kept
certs = $dir/certsdb # Where the issued certs are kept
new_certs_dir = $certs # default place for new certs.
database = $dir/index.txt # database index file.
certificate = $dir/cacert.pem # The CA certificate
private_key = $dir/private/cakey.pem# The private key
serial = $dir/serial # The current serial number
RANDFILE = $dir/private/.rand # private random number file
crldir = $dir/crl
crlnumber = $dir/crlnumber # the current crl number
crl = $crldir/crl.pem # The current CRL
x509_extensions = usr_cert # The extentions to add to the cert
copy_extensions = copy
name_opt = ca_default # Subject Name options
cert_opt = ca_default # Certificate field options
default_days = 365 # how long to certify for
default_crl_days= 30 # how long before next CRL
default_md = sha1 # which md to use.
preserve = no # keep passed DN ordering
policy = policy_match
[ policy_match ]
countryName = match # Must be the same as the CA
stateOrProvinceName = match # Must be the same as the CA
organizationName = match # Must be the same as the CA
organizationalUnitName = optional # not required
commonName = supplied # must be there, whatever it is
emailAddress = optional # not required
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
prompt = yes
default_bits = 2048
#default_keyfile = privkey.pem
distinguished_name = req_distinguished_name # where to get DN for reqs
attributes = req_attributes # req attributes
x509_extensions = v3_ca # The extentions to add to self signed certs
req_extensions = v3_req # The extensions to add to req's
string_mask = nombstr
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = US
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = California
localityName = Locality Name (eg, city)
localityName_default = Hawthorne
0.organizationName = Organization Name (eg, company)
0.organizationName_default = PhilNet
organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_default = UN
commonName = Common Name (eg, YOUR name)
commonName_default = CN
commonName_max = 64
emailAddress_default = aaa@bbb.cc
emailAddress = Email Address
emailAddress_max = 64
[ req_attributes ]
[ usr_cert ]
basicConstraints=CA:false
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
#subjectAltName=email:move
subjectAltName = @alt_names
[alt_names]
IP.1 = 34.245.0.160
DNS.1 = ec2-34-245-0-160.eu-west-1.compute.amazonaws.com
[ usr_cert_has_san ]
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
[ v3_req ]
subjectAltName=email:move
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer:always
basicConstraints = CA:true
[ v3_ca_has_san ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints = CA:true
我的Java Web客户端使用paho
库进行MQTT通信:
public class TestMQTT4 {
public static void main(String[] args) {
System.out.println("Starting");
String serverUrl ="ssl://ec2-34-245-0-160.eu-west-1.compute.amazonaws.com:8887";
String path= "C:\\projects\\certs\\U\\CA6\\";
String caFilePath =path+"cacert.pem";
String clientCrtFilePath = path+ "client.crt";
String clientKeyFilePath = path+ "client.key";
String mqttUserName = "b";
String mqttPassword = "b";
MqttClient client;
try {
client = new MqttClient(serverUrl, "2");
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(mqttUserName);
options.setPassword(mqttPassword.toCharArray());
options.setConnectionTimeout(60);
options.setKeepAliveInterval(60);
options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1);
SSLSocketFactory socketFactory = getSocketFactory(caFilePath,clientCrtFilePath, clientKeyFilePath, "aaabbb");
//SSLSocketFactory socketFactory = getSocketFactory3(caFilePath);
options.setSocketFactory(socketFactory);
System.out.println("starting connect the server...");
client.connect(options);
System.out.println("connected!");
Thread.sleep(1000);
client.subscribe("aaa",0);
client.disconnect();
System.out.println("disconnected!");
} catch (MqttException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private static SSLSocketFactory getSocketFactory(final String caCrtFile,
final String crtFile, final String keyFile, final String password)
throws Exception {
Security.addProvider(new BouncyCastleProvider());
// load CA certificate
X509Certificate caCert = null;
FileInputStream fis = new FileInputStream(caCrtFile);
BufferedInputStream bis = new BufferedInputStream(fis);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
while (bis.available() > 0) {
caCert = (X509Certificate) cf.generateCertificate(bis);
}
//System.out.println("load CA certificate certificate done :"+caCert);
// load client certificate
bis = new BufferedInputStream(new FileInputStream(crtFile));
X509Certificate cert = null;
while (bis.available() > 0) {
cert = (X509Certificate) cf.generateCertificate(bis);
}
//System.out.println("load client certificate done :"+cert);
// load client private key
PEMParser pemParser = new PEMParser(new FileReader(keyFile));
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PrivateKey pk=null;
if (object instanceof PEMEncryptedKeyPair) {
KeyPair key;
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
System.out.println("Encrypted key - we will use provided password");
key = converter.getKeyPair(((PEMEncryptedKeyPair) object)
.decryptKeyPair(decProv));
pk = key.getPrivate();
} else
if (object instanceof PrivateKeyInfo)
{
System.out.println("got PrivateKeyInfo ");
pk = converter.getPrivateKey((PrivateKeyInfo) object);
System.out.println("algorithm "+pk.getAlgorithm() +" format " + pk.getFormat());
}else
if (object instanceof PEMKeyPair)
{
KeyPair key;
System.out.println("Unencrypted key - no password needed "+object.getClass().getName());
key = converter.getKeyPair((PEMKeyPair) object);
pk = key.getPrivate();
}
pemParser.close();
// CA certificate is used to authenticate server
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) { }
public void checkServerTrusted(X509Certificate[] certs, String authType) { }
} };
// client key and certificates are sent to server so it can authenticate
// us
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", pk, password.toCharArray(),
new java.security.cert.Certificate[] { cert });
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());
// finally, create SSL socket factory
SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(kmf.getKeyManagers(), trustAllCerts, null);
return context.getSocketFactory();
}
}
日志:
...
....
*** CertificateVerify
Signature Algorithm SHA256withRSA
update handshake state: certificate_verify[15]
upcoming handshake states: client change_cipher_spec[-1]
upcoming handshake states: client finished[20]
upcoming handshake states: server change_cipher_spec[-1]
upcoming handshake states: server finished[20]
MQTT Con: 2, WRITE: TLSv1.2 Handshake, length = 264
update handshake state: change_cipher_spec
upcoming handshake states: client finished[20]
upcoming handshake states: server change_cipher_spec[-1]
upcoming handshake states: server finished[20]
MQTT Con: 2, WRITE: TLSv1.2 Change Cipher Spec, length = 1
*** Finished
verify_data: { 74, 211, 43, 244, 29, 37, 34, 169, 164, 170, 51, 82 }
***
update handshake state: finished[20]
upcoming handshake states: server change_cipher_spec[-1]
upcoming handshake states: server finished[20]
MQTT Con: 2, WRITE: TLSv1.2 Handshake, length = 40
MQTT Con: 2, READ: TLSv1.2 Alert, length = 2
MQTT Con: 2, RECV TLSv1.2 ALERT: fatal, unknown_ca
%% Invalidated: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384]
MQTT Con: 2, called closeSocket()
MQTT Con: 2, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca
MQTT Con: 2, called close()
MQTT Con: 2, called closeInternal(true)
MqttException (0) - javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca
UPD
我发现连接良好(Raspberry Pi)和连接不良(AWS上的Ubuntu)的日志文件的签名算法有所不同
失败(在AWS上为Ubuntu)
*** CertificateVerify
Signature Algorithm SHA256withRSA
很好(Raspberry Pi)
*** CertificateVerify
Signature Algorithm SHA512withRSA
为什么我有不同的签名算法?在飞蛾情况下,使用相同的命令来创建和签署证书。
与Java通信时可能会出现问题吗?
UPD
我对注释进行了一些更改,现在我的证书可以正常工作了。
证书生成过程:
openssl req -new -newkey rsa:2048 -keyout private/cakey.pem -out careq.pem -config ./openssl.cnf -extensions v3_ca_509 -reqexts v3_ca_req -subj "/C=US/ST=California/L=Hawthorne/O=PhilNet/CN=CA/"
openssl ca -create_serial -out cacert.pem -days 365 -keyfile private/cakey.pem -selfsign -config ./openssl.cnf -extensions v3_ca_509 -infiles careq.pem
openssl req -new -newkey rsa:2048 -keyout server.key -out server.csr -config openssl.cnf -reqexts v3_server_req -nodes -subj "/C=US/ST=California/L=Hawthorne/O=PhilNet/CN=Server/"
openssl ca -config openssl.cnf -in server.csr -out server.crt
openssl req -new -newkey rsa:2048 -keyout client.key -out client.csr -config openssl.cnf -reqexts v3_server_req -nodes -subj "/C=US/ST=California/L=Hawthorne/O=PhilNet/CN=Client/"
openssl ca -config openssl.cnf -in client.csr -out client.crt
openssl.cnf文件:
[ ca ]
default_ca = CA_default # The default ca section
[ CA_default ]
dir = . # Where everything is kept
certs = $dir/certsdb # Where the issued certs are kept
new_certs_dir = $certs # default place for new certs.
database = $dir/index.txt # database index file.
certificate = $dir/cacert.pem # The CA certificate
private_key = $dir/private/cakey.pem# The private key
serial = $dir/serial # The current serial number
RANDFILE = $dir/private/.rand # private random number file
crldir = $dir/crl
crlnumber = $dir/crlnumber # the current crl number
crl = $crldir/crl.pem # The current CRL
#x509_extensions = usr_cert # The extentions to add to the cert
copy_extensions = copy
name_opt = ca_default # Subject Name options
cert_opt = ca_default # Certificate field options
default_days = 365 # how long to certify for
default_crl_days= 30 # how long before next CRL
default_md = sha1 # which md to use.
preserve = no # keep passed DN ordering
policy = policy_anything
[ policy_anything ]
countryName = match
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
prompt = yes
default_bits = 2048
attributes = req_attributes
#x509_extensions = v3_ca # The extentions to add to self signed certs
#req_extensions = v3_req # The extensions to add to req's
string_mask = nombstr
[ req_attributes ]
[ v3_ca_509 ]
basicConstraints = CA:true
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer:always
[ v3_ca_req ]
basicConstraints = CA:true
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, keyCertSign
[ v3_server_req ]
basicConstraints=CA:false
subjectKeyIdentifier=hash
subjectAltName = @alt_names_server
[alt_names_server]
IP.1 = 34.245.0.159
DNS.1 = ec2-34-245-0-159.eu-west-1.compute.amazonaws.com
[ v3_client_req ]
basicConstraints=CA:false
subjectKeyIdentifier=hash
subjectAltName = @alt_names_client
[alt_names_client]
IP.1 = 192.168.1.103
DNS.1 = GM