Skip to content

Commit 78d59e9

Browse files
committed
add MqttSslHandlerProvider.java
1 parent 2e72f71 commit 78d59e9

File tree

7 files changed

+371
-0
lines changed

7 files changed

+371
-0
lines changed

pom.xml

+11
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<logback.version>1.2.6</logback.version>
4545
<apache.commons-lang3.version>3.12.0</apache.commons-lang3.version>
4646
<guava.version>30.0-jre</guava.version>
47+
<bouncycastle.version>1.67</bouncycastle.version>
4748
<redisson.version>3.13.6</redisson.version>
4849
</properties>
4950

@@ -107,6 +108,16 @@
107108
<artifactId>guava</artifactId>
108109
<version>${guava.version}</version>
109110
</dependency>
111+
<dependency>
112+
<groupId>org.bouncycastle</groupId>
113+
<artifactId>bcprov-jdk15on</artifactId>
114+
<version>${bouncycastle.version}</version>
115+
</dependency>
116+
<dependency>
117+
<groupId>org.bouncycastle</groupId>
118+
<artifactId>bcpkix-jdk15on</artifactId>
119+
<version>${bouncycastle.version}</version>
120+
</dependency>
110121
<dependency>
111122
<groupId>org.springframework</groupId>
112123
<artifactId>spring-context</artifactId>

server/pom.xml

+8
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@
5252
<groupId>com.google.guava</groupId>
5353
<artifactId>guava</artifactId>
5454
</dependency>
55+
<dependency>
56+
<groupId>org.bouncycastle</groupId>
57+
<artifactId>bcprov-jdk15on</artifactId>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.bouncycastle</groupId>
61+
<artifactId>bcpkix-jdk15on</artifactId>
62+
</dependency>
5563
</dependencies>
5664

5765
<build>

server/src/main/java/iot/technology/mqtt/server/ssl/MqttSslHandlerProvider.java

+115
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
package iot.technology.mqtt.server.ssl;
22

3+
import io.netty.handler.ssl.SslHandler;
4+
import iot.technology.mqtt.server.utils.EncryptionUtil;
5+
import iot.technology.mqtt.server.utils.SslUtil;
36
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.beans.factory.annotation.Qualifier;
49
import org.springframework.beans.factory.annotation.Value;
510
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
11+
import org.springframework.boot.context.properties.ConfigurationProperties;
12+
import org.springframework.context.annotation.Bean;
613
import org.springframework.stereotype.Component;
14+
import org.springframework.util.StringUtils;
15+
16+
import javax.net.ssl.*;
17+
import java.security.cert.CertificateException;
18+
import java.security.cert.X509Certificate;
19+
import java.util.concurrent.CountDownLatch;
20+
import java.util.concurrent.TimeUnit;
721

822
/**
923
* @author mushuwei
@@ -15,4 +29,105 @@ public class MqttSslHandlerProvider {
1529

1630
@Value("${mqtt.ssl.protocol}")
1731
private String sslProtocol;
32+
33+
@Bean
34+
@ConfigurationProperties(prefix = "mqtt.ssl.credentials")
35+
public SslCredentialsConfig mqttSslCredentials() {
36+
return new SslCredentialsConfig("MQTT SSL Credentials", false);
37+
}
38+
39+
@Autowired
40+
@Qualifier("mqttSslCredentials")
41+
private SslCredentialsConfig mqttSslCredentialsConfig;
42+
43+
private SSLContext sslContext;
44+
45+
public SslHandler getSslHandler() {
46+
if (sslContext == null) {
47+
sslContext = createSslContext();
48+
}
49+
SSLEngine sslEngine = sslContext.createSSLEngine();
50+
sslEngine.setUseClientMode(false);
51+
sslEngine.setNeedClientAuth(false);
52+
sslEngine.setWantClientAuth(true);
53+
sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
54+
sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
55+
sslEngine.setEnableSessionCreation(true);
56+
return new SslHandler(sslEngine);
57+
}
58+
59+
private SSLContext createSslContext() {
60+
try {
61+
SslCredentials sslCredentials = this.mqttSslCredentialsConfig.getCredentials();
62+
TrustManagerFactory tmFactory = sslCredentials.createTrustManagerFactory();
63+
KeyManagerFactory kmf = sslCredentials.createKeyManagerFactory();
64+
65+
KeyManager[] km = kmf.getKeyManagers();
66+
TrustManager x509wrapped = getX509TrustManager(tmFactory);
67+
TrustManager[] tm = {x509wrapped};
68+
if (StringUtils.isEmpty(sslProtocol)) {
69+
sslProtocol = "TLS";
70+
}
71+
SSLContext sslContext = SSLContext.getInstance(sslProtocol);
72+
sslContext.init(km, tm, null);
73+
return sslContext;
74+
} catch (Exception e) {
75+
log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e);
76+
throw new RuntimeException("Failed to get SSL context", e);
77+
}
78+
}
79+
80+
private TrustManager getX509TrustManager(TrustManagerFactory tmf) {
81+
X509TrustManager x509Tm = null;
82+
for (TrustManager tm : tmf.getTrustManagers()) {
83+
if (tm instanceof X509TrustManager) {
84+
x509Tm = (X509TrustManager) tm;
85+
break;
86+
}
87+
}
88+
return new MqttX509TrustManager(x509Tm);
89+
}
90+
91+
static class MqttX509TrustManager implements X509TrustManager {
92+
93+
private final X509TrustManager trustManager;
94+
95+
MqttX509TrustManager(X509TrustManager trustManager) {
96+
this.trustManager = trustManager;
97+
}
98+
99+
@Override
100+
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
101+
String credentialsBody = null;
102+
for (X509Certificate cert : chain) {
103+
try {
104+
String strCert = SslUtil.getCertificateString(cert);
105+
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
106+
final String[] credentialsBodyHolder = new String[1];
107+
CountDownLatch latch = new CountDownLatch(1);
108+
//TODO 处理业务逻辑
109+
latch.await(10, TimeUnit.SECONDS);
110+
if (strCert.equals(credentialsBodyHolder[0])) {
111+
credentialsBody = credentialsBodyHolder[0];
112+
break;
113+
}
114+
} catch (InterruptedException e) {
115+
throw new RuntimeException(e);
116+
}
117+
}
118+
if (credentialsBody == null) {
119+
throw new CertificateException("Invalid Device Certificate");
120+
}
121+
}
122+
123+
@Override
124+
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
125+
trustManager.checkServerTrusted(chain, authType);
126+
}
127+
128+
@Override
129+
public X509Certificate[] getAcceptedIssuers() {
130+
return trustManager.getAcceptedIssuers();
131+
}
132+
}
18133
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package iot.technology.mqtt.server.ssl;
2+
3+
import iot.technology.mqtt.server.utils.ResourceUtils;
4+
import lombok.Data;
5+
import lombok.EqualsAndHashCode;
6+
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
7+
import org.bouncycastle.cert.X509CertificateHolder;
8+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
9+
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
10+
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
11+
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
12+
import org.bouncycastle.openssl.PEMParser;
13+
import org.bouncycastle.openssl.PEMKeyPair;
14+
import org.bouncycastle.openssl.PEMDecryptorProvider;
15+
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
16+
import org.springframework.util.StringUtils;
17+
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.io.InputStreamReader;
21+
import java.security.GeneralSecurityException;
22+
import java.security.KeyStore;
23+
import java.security.PrivateKey;
24+
import java.security.Security;
25+
import java.security.cert.CertPath;
26+
import java.security.cert.Certificate;
27+
import java.security.cert.CertificateFactory;
28+
import java.security.cert.X509Certificate;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
import java.util.stream.Collectors;
32+
33+
@Data
34+
@EqualsAndHashCode(callSuper = false)
35+
public class PemSslCredentials extends AbstractSslCredentials {
36+
37+
public static final String DEFAULT_KEY_ALIAS = "server";
38+
39+
private String certFile;
40+
private String keyFile;
41+
private String keyPassword;
42+
43+
@Override
44+
protected boolean canUse() {
45+
return ResourceUtils.resourceExists(this, this.certFile);
46+
}
47+
48+
@Override
49+
protected KeyStore loadKeyStore(boolean trustsOnly, char[] keyPasswordArray) throws IOException, GeneralSecurityException {
50+
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
51+
Security.addProvider(new BouncyCastleProvider());
52+
}
53+
List<X509Certificate> certificates = new ArrayList<>();
54+
PrivateKey privateKey = null;
55+
JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter();
56+
JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter();
57+
try (InputStream inStream = ResourceUtils.getInputStream(this, this.certFile)) {
58+
try (PEMParser pemParser = new PEMParser(new InputStreamReader(inStream))) {
59+
Object object;
60+
while ((object = pemParser.readObject()) != null) {
61+
if (object instanceof X509CertificateHolder) {
62+
X509Certificate x509Cert = certConverter.getCertificate((X509CertificateHolder) object);
63+
certificates.add(x509Cert);
64+
} else if (object instanceof PEMEncryptedKeyPair) {
65+
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(keyPasswordArray);
66+
privateKey = keyConverter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)).getPrivate();
67+
} else if (object instanceof PEMKeyPair) {
68+
privateKey = keyConverter.getKeyPair((PEMKeyPair) object).getPrivate();
69+
} else if (object instanceof PrivateKeyInfo) {
70+
privateKey = keyConverter.getPrivateKey((PrivateKeyInfo) object);
71+
}
72+
}
73+
}
74+
}
75+
if (privateKey == null && !StringUtils.isEmpty(this.keyFile)) {
76+
if (ResourceUtils.resourceExists(this, this.keyFile)) {
77+
try (InputStream inStream = ResourceUtils.getInputStream(this, this.keyFile)) {
78+
try (PEMParser pemParser = new PEMParser(new InputStreamReader(inStream))) {
79+
Object object;
80+
while ((object = pemParser.readObject()) != null) {
81+
if (object instanceof PEMEncryptedKeyPair) {
82+
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(keyPasswordArray);
83+
privateKey = keyConverter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)).getPrivate();
84+
break;
85+
} else if (object instanceof PEMKeyPair) {
86+
privateKey = keyConverter.getKeyPair((PEMKeyPair) object).getPrivate();
87+
break;
88+
} else if (object instanceof PrivateKeyInfo) {
89+
privateKey = keyConverter.getPrivateKey((PrivateKeyInfo) object);
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}
96+
if (certificates.isEmpty()) {
97+
throw new IllegalArgumentException("No certificates found in certFile: " + this.certFile);
98+
}
99+
if (privateKey == null && !trustsOnly) {
100+
throw new IllegalArgumentException("Unable to load private key neither from certFile: " + this.certFile
101+
+ " nor form keyFile: " + this.keyFile);
102+
}
103+
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
104+
keyStore.load(null);
105+
if (trustsOnly) {
106+
List<Certificate> unique = certificates.stream().distinct().collect(Collectors.toList());
107+
for (int i = 0; i < unique.size(); i++) {
108+
keyStore.setCertificateEntry("root-" + i, unique.get(i));
109+
}
110+
}
111+
if (privateKey != null) {
112+
CertificateFactory factory = CertificateFactory.getInstance("X.509");
113+
CertPath certPath = factory.generateCertPath(certificates);
114+
List<? extends Certificate> path = certPath.getCertificates();
115+
Certificate[] x509Certificates = path.toArray(new Certificate[0]);
116+
keyStore.setKeyEntry(DEFAULT_KEY_ALIAS, privateKey, keyPasswordArray, x509Certificates);
117+
}
118+
return keyStore;
119+
}
120+
121+
@Override
122+
protected void updateKeyAlias(String keyAlias) {
123+
}
124+
125+
@Override
126+
public String getKeyAlias() {
127+
return DEFAULT_KEY_ALIAS;
128+
}
129+
}

server/src/main/java/iot/technology/mqtt/server/ssl/SslCredentialsConfig.java

+41
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import lombok.Data;
44
import lombok.extern.slf4j.Slf4j;
55

6+
import javax.annotation.PostConstruct;
7+
68
@Slf4j
79
@Data
810
public class SslCredentialsConfig {
@@ -11,5 +13,44 @@ public class SslCredentialsConfig {
1113

1214
private SslCredentialsType type;
1315

16+
private PemSslCredentials pem;
17+
18+
private KeystoreSslCredentials keystore;
19+
20+
private SslCredentials credentials;
21+
22+
private final String name;
23+
24+
private final boolean trustsOnly;
25+
26+
public SslCredentialsConfig(String name, boolean trustsOnly) {
27+
this.name = name;
28+
this.trustsOnly = trustsOnly;
29+
}
30+
31+
@PostConstruct
32+
public void init() {
33+
if (this.enabled) {
34+
log.info("{}: Initializing SSL credentials.", name);
35+
if (SslCredentialsType.PEM.equals(type) && pem.canUse()) {
36+
this.credentials = this.pem;
37+
} else if (keystore.canUse()) {
38+
if (SslCredentialsType.PEM.equals(type)) {
39+
log.warn("{}: Specified PEM configuration is not valid. Using SSL keystore configuration as fallback.", name);
40+
}
41+
this.credentials = this.keystore;
42+
} else {
43+
throw new RuntimeException(name + ": Invalid SSL credentials configuration. None of the PEM or KEYSTORE configurations can be used!");
44+
}
45+
try {
46+
this.credentials.init(this.trustsOnly);
47+
} catch (Exception e) {
48+
throw new RuntimeException(name + ": Failed to init SSL credentials configuration.", e);
49+
}
50+
} else {
51+
log.info("{}: Skipping initialization of disabled SSL credentials.", name);
52+
}
53+
}
54+
1455

1556
}

0 commit comments

Comments
 (0)