Skip to content

Commit a0336fe

Browse files
authored
chore: migrate to subscribeToCertificateUpdates API (#71)
1 parent a8f7d8a commit a0336fe

6 files changed

Lines changed: 85 additions & 91 deletions

File tree

pom.xml

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,27 @@
4343
<dependency>
4444
<groupId>com.aws.greengrass</groupId>
4545
<artifactId>client-devices-auth</artifactId>
46-
<version>2.0.0-SNAPSHOT</version>
46+
<version>2.2.0-SNAPSHOT</version>
4747
<scope>provided</scope>
4848
</dependency>
4949
<dependency>
5050
<groupId>com.aws.greengrass</groupId>
5151
<artifactId>nucleus</artifactId>
52-
<version>2.2.0-SNAPSHOT</version>
52+
<version>2.6.0-SNAPSHOT</version>
5353
<scope>provided</scope>
5454
</dependency>
55-
<dependency>
56-
<groupId>org.eclipse.paho</groupId>
57-
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
58-
<version>[1.2.5,)</version>
59-
</dependency>
6055
<dependency>
6156
<groupId>com.aws.greengrass</groupId>
6257
<artifactId>nucleus</artifactId>
63-
<version>2.2.0-SNAPSHOT</version>
58+
<version>2.6.0-SNAPSHOT</version>
6459
<type>test-jar</type>
6560
<scope>test</scope>
6661
</dependency>
62+
<dependency>
63+
<groupId>org.eclipse.paho</groupId>
64+
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
65+
<version>[1.2.5,)</version>
66+
</dependency>
6767
<dependency>
6868
<groupId>org.projectlombok</groupId>
6969
<artifactId>lombok</artifactId>
@@ -76,21 +76,6 @@
7676
<version>0.12.1</version>
7777
<scope>test</scope>
7878
</dependency>
79-
<!-- This dependency is also shadowed in Nucleus because I import moquette for testing.
80-
Have to import it explicitly here-->
81-
<dependency>
82-
<groupId>org.slf4j</groupId>
83-
<artifactId>slf4j-api</artifactId>
84-
<version>1.7.30</version>
85-
<scope>test</scope>
86-
</dependency>
87-
<!-- This dependency is also shadowed in Nucleus because I import moquette for testing.
88-
Have to import it explicitly here-->
89-
<dependency>
90-
<groupId>com.fasterxml.jackson.core</groupId>
91-
<artifactId>jackson-annotations</artifactId>
92-
<version>2.11.2</version>
93-
</dependency>
9479
<dependency>
9580
<groupId>org.junit.jupiter</groupId>
9681
<artifactId>junit-jupiter-api</artifactId>

recipe.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"ComponentPublisher": "{COMPONENT_PUBLISHER}",
88
"ComponentDependencies": {
99
"aws.greengrass.clientdevices.Auth": {
10-
"VersionRequirement": ">=2.0.0 <2.2.0",
10+
"VersionRequirement": ">=2.2.0 <2.3.0",
1111
"DependencyType": "HARD"
1212
}
1313
},

src/main/java/com/aws/greengrass/mqttbridge/MQTTBridge.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@
66
package com.aws.greengrass.mqttbridge;
77

88
import com.aws.greengrass.builtin.services.pubsub.PubSubIPCEventStreamAgent;
9-
import com.aws.greengrass.certificatemanager.certificate.CsrProcessingException;
109
import com.aws.greengrass.componentmanager.KernelConfigResolver;
1110
import com.aws.greengrass.config.Node;
1211
import com.aws.greengrass.config.Topics;
1312
import com.aws.greengrass.config.WhatHappened;
1413
import com.aws.greengrass.dependency.ImplementsService;
1514
import com.aws.greengrass.dependency.State;
1615
import com.aws.greengrass.device.ClientDevicesAuthService;
16+
import com.aws.greengrass.device.exception.CertificateGenerationException;
1717
import com.aws.greengrass.lifecyclemanager.Kernel;
1818
import com.aws.greengrass.lifecyclemanager.PluginService;
1919
import com.aws.greengrass.lifecyclemanager.exceptions.ServiceLoadException;
20-
import com.aws.greengrass.mqttbridge.auth.CsrGeneratingException;
2120
import com.aws.greengrass.mqttbridge.auth.MQTTClientKeyStore;
2221
import com.aws.greengrass.mqttbridge.clients.IoTCoreClient;
2322
import com.aws.greengrass.mqttbridge.clients.MQTTClient;
@@ -116,7 +115,7 @@ public void install() {
116115
public void startup() {
117116
try {
118117
mqttClientKeyStore.init();
119-
} catch (CsrProcessingException | KeyStoreException | CsrGeneratingException e) {
118+
} catch (KeyStoreException | CertificateGenerationException e) {
120119
serviceErrored(e);
121120
return;
122121
}
@@ -163,6 +162,8 @@ public void startup() {
163162

164163
@Override
165164
public void shutdown() {
165+
mqttClientKeyStore.shutdown();
166+
166167
messageBridge.removeMessageClient(TopicMapping.TopicType.LocalMqtt);
167168
if (mqttClient != null) {
168169
mqttClient.stop();

src/main/java/com/aws/greengrass/mqttbridge/auth/MQTTClientKeyStore.java

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,34 @@
55

66
package com.aws.greengrass.mqttbridge.auth;
77

8-
import com.aws.greengrass.certificatemanager.CertificateManager;
9-
import com.aws.greengrass.certificatemanager.certificate.CertificateRequestGenerator;
10-
import com.aws.greengrass.certificatemanager.certificate.CsrProcessingException;
8+
import com.aws.greengrass.device.ClientDevicesAuthServiceApi;
9+
import com.aws.greengrass.device.api.CertificateUpdateEvent;
10+
import com.aws.greengrass.device.api.GetCertificateRequest;
11+
import com.aws.greengrass.device.api.GetCertificateRequestOptions;
12+
import com.aws.greengrass.device.exception.CertificateGenerationException;
1113
import com.aws.greengrass.logging.api.Logger;
1214
import com.aws.greengrass.logging.impl.LogManager;
15+
import com.aws.greengrass.mqttbridge.MQTTBridge;
1316
import lombok.AccessLevel;
1417
import lombok.Getter;
15-
import org.bouncycastle.operator.OperatorCreationException;
1618

1719
import java.io.ByteArrayInputStream;
1820
import java.io.IOException;
1921
import java.io.InputStream;
2022
import java.nio.charset.StandardCharsets;
2123
import java.security.KeyManagementException;
22-
import java.security.KeyPair;
23-
import java.security.KeyPairGenerator;
2424
import java.security.KeyStore;
2525
import java.security.KeyStoreException;
2626
import java.security.NoSuchAlgorithmException;
2727
import java.security.UnrecoverableKeyException;
2828
import java.security.cert.CertificateException;
2929
import java.security.cert.CertificateFactory;
3030
import java.security.cert.X509Certificate;
31+
import java.util.Arrays;
3132
import java.util.Enumeration;
3233
import java.util.List;
3334
import java.util.concurrent.CopyOnWriteArrayList;
35+
import java.util.stream.Stream;
3436
import javax.inject.Inject;
3537
import javax.net.ssl.KeyManagerFactory;
3638
import javax.net.ssl.SSLContext;
@@ -40,19 +42,13 @@
4042
public class MQTTClientKeyStore {
4143
private static final Logger LOGGER = LogManager.getLogger(MQTTClientKeyStore.class);
4244
static final char[] DEFAULT_KEYSTORE_PASSWORD = "".toCharArray();
43-
private static final String DEFAULT_CN = "aws-greengrass-mqttbridge";
4445
static final String KEY_ALIAS = "aws-greengrass-mqttbridge";
45-
private static final String RSA_KEY_INSTANCE = "RSA";
46-
private static final int RSA_KEY_LENGTH = 2048;
4746

4847
@Getter(AccessLevel.PACKAGE)
4948
private KeyStore keyStore;
50-
51-
private KeyPair keyPair;
52-
53-
private final CertificateManager certificateManager;
54-
49+
private final ClientDevicesAuthServiceApi clientDevicesAuthServiceApi;
5550
private final List<UpdateListener> updateListeners = new CopyOnWriteArrayList<>();
51+
private final GetCertificateRequest clientCertificateRequest;
5652

5753
@FunctionalInterface
5854
public interface UpdateListener {
@@ -62,56 +58,51 @@ public interface UpdateListener {
6258
/**
6359
* Constructor for MQTTClient KeyStore.
6460
*
65-
* @param certificateManager certificate manager for subscribing to cert updates
61+
* @param clientDevicesAuthServiceApi client devices auth api for subscribing to cert updates
6662
*/
6763
@Inject
68-
public MQTTClientKeyStore(CertificateManager certificateManager) {
69-
this.certificateManager = certificateManager;
64+
public MQTTClientKeyStore(ClientDevicesAuthServiceApi clientDevicesAuthServiceApi) {
65+
GetCertificateRequestOptions options = new GetCertificateRequestOptions();
66+
options.setCertificateType(GetCertificateRequestOptions.CertificateType.CLIENT);
67+
this.clientCertificateRequest = new GetCertificateRequest(MQTTBridge.SERVICE_NAME, options, this::updateCert);
68+
this.clientDevicesAuthServiceApi = clientDevicesAuthServiceApi;
7069
}
7170

7271
/**
7372
* Initialize keypair and keystore and subscribe to cert updates.
7473
*
75-
* @throws CsrProcessingException if unable to subscribe with csr
76-
* @throws KeyStoreException if unable to generate keypair or load keystore
77-
* @throws CsrGeneratingException if unable to generate csr
74+
* @throws KeyStoreException if unable to generate keypair or load keystore
75+
* @throws CertificateGenerationException if unable to request a client certificate
7876
*/
79-
public void init() throws CsrProcessingException, KeyStoreException, CsrGeneratingException {
80-
try {
81-
keyPair = newRSAKeyPair();
82-
} catch (NoSuchAlgorithmException e) {
83-
throw new KeyStoreException("unable to generate keypair for key store", e);
84-
}
85-
77+
public void init() throws KeyStoreException, CertificateGenerationException {
8678
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
8779
try {
8880
keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD);
8981
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
9082
throw new KeyStoreException("Unable to load keystore", e);
9183
}
9284

93-
String csr;
94-
try {
95-
//client cert doesn't require SANs
96-
csr = CertificateRequestGenerator.createCSR(keyPair, DEFAULT_CN, null, null);
97-
} catch (IOException | OperatorCreationException e) {
98-
throw new CsrGeneratingException("Unable to generate CSR from keypair", e);
99-
}
100-
certificateManager.subscribeToClientCertificateUpdates(csr, this::updateCert);
85+
clientDevicesAuthServiceApi.subscribeToCertificateUpdates(clientCertificateRequest);
10186
}
10287

103-
private KeyPair newRSAKeyPair() throws NoSuchAlgorithmException {
104-
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA_KEY_INSTANCE);
105-
kpg.initialize(RSA_KEY_LENGTH);
106-
return kpg.generateKeyPair();
88+
/**
89+
* Shutdown client key store.
90+
*/
91+
public void shutdown() {
92+
clientDevicesAuthServiceApi.unsubscribeFromCertificateUpdates(clientCertificateRequest);
10793
}
10894

109-
private void updateCert(X509Certificate... certChain) {
95+
private void updateCert(CertificateUpdateEvent certificateUpdate) {
11096
try {
11197
LOGGER.atDebug().log("Storing new client certificate to be used on next connect attempt");
112-
keyStore.setKeyEntry(KEY_ALIAS, keyPair.getPrivate(), DEFAULT_KEYSTORE_PASSWORD, certChain);
98+
X509Certificate[] certChain = Stream.concat(
99+
Stream.of(certificateUpdate.getCertificate()),
100+
Arrays.stream(certificateUpdate.getCaCertificates()))
101+
.toArray(X509Certificate[]::new);
102+
keyStore.setKeyEntry(
103+
KEY_ALIAS, certificateUpdate.getKeyPair().getPrivate(), DEFAULT_KEYSTORE_PASSWORD, certChain);
113104
} catch (KeyStoreException e) {
114-
LOGGER.atError("Unable to store generated cert", e);
105+
LOGGER.atError().log("Unable to store generated cert", e);
115106
}
116107
}
117108

src/test/java/com/aws/greengrass/mqttbridge/MQTTBridgeTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ public class MQTTBridgeTest extends GGServiceTestUtil {
8686

8787
@BeforeEach
8888
void setup() throws IOException {
89+
// Set this property for kernel to scan its own classpath to find plugins
90+
System.setProperty("aws.greengrass.scanSelfClasspath", "true");
91+
8992
kernel = new Kernel();
9093
kernel.getContext().put(CertificateManager.class, mockCertificateManager);
9194
IConfig defaultConfig = new MemoryConfig(new Properties());

src/test/java/com/aws/greengrass/mqttbridge/auth/MQTTClientKeyStoreTest.java

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,43 @@
55

66
package com.aws.greengrass.mqttbridge.auth;
77

8-
import com.aws.greengrass.certificatemanager.CertificateManager;
8+
import com.aws.greengrass.device.ClientDevicesAuthServiceApi;
9+
import com.aws.greengrass.device.api.CertificateUpdateEvent;
10+
import com.aws.greengrass.device.api.GetCertificateRequest;
911
import com.aws.greengrass.testcommons.testutilities.GGExtension;
10-
import java.security.cert.CertificateEncodingException;
12+
import org.junit.jupiter.api.BeforeEach;
1113
import org.junit.jupiter.api.Test;
1214
import org.junit.jupiter.api.extension.ExtendWith;
1315
import org.mockito.ArgumentCaptor;
1416
import org.mockito.Mock;
1517
import org.mockito.junit.jupiter.MockitoExtension;
1618

19+
import javax.net.SocketFactory;
20+
import javax.net.ssl.SSLSocketFactory;
1721
import java.io.ByteArrayInputStream;
1822
import java.io.ByteArrayOutputStream;
1923
import java.io.IOException;
2024
import java.io.InputStream;
2125
import java.nio.charset.StandardCharsets;
26+
import java.security.KeyPair;
27+
import java.security.KeyPairGenerator;
2228
import java.security.KeyStore;
29+
import java.security.NoSuchAlgorithmException;
2330
import java.security.PrivateKey;
31+
import java.security.cert.CertificateEncodingException;
2432
import java.security.cert.CertificateException;
2533
import java.security.cert.CertificateFactory;
2634
import java.security.cert.X509Certificate;
2735
import java.util.Base64;
2836
import java.util.Collections;
2937
import java.util.concurrent.CountDownLatch;
3038
import java.util.concurrent.TimeUnit;
31-
import java.util.function.Consumer;
32-
import javax.net.SocketFactory;
33-
import javax.net.ssl.SSLSocketFactory;
3439

3540
import static com.aws.greengrass.mqttbridge.auth.MQTTClientKeyStore.DEFAULT_KEYSTORE_PASSWORD;
3641
import static com.aws.greengrass.mqttbridge.auth.MQTTClientKeyStore.KEY_ALIAS;
3742
import static org.hamcrest.MatcherAssert.assertThat;
3843
import static org.hamcrest.Matchers.instanceOf;
3944
import static org.hamcrest.Matchers.is;
40-
import static org.mockito.ArgumentMatchers.any;
4145
import static org.mockito.Mockito.times;
4246
import static org.mockito.Mockito.verify;
4347

@@ -64,24 +68,33 @@ public class MQTTClientKeyStoreTest {
6468
private static final byte[] END_CERT = "\r\n-----END CERTIFICATE-----\r\n".getBytes(StandardCharsets.UTF_8);
6569

6670
@Mock
67-
private CertificateManager mockCertificateManager;
71+
private ClientDevicesAuthServiceApi mockServiceApi;
72+
private KeyPair keyPair;
73+
74+
@BeforeEach
75+
void beforeEach() throws NoSuchAlgorithmException {
76+
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
77+
kpg.initialize(2048);
78+
keyPair = kpg.generateKeyPair();
79+
}
6880

6981
@Test
7082
void GIVEN_MQTTClientKeyStore_WHEN_initialized_THEN_keyAndCertGenerated() throws Exception {
71-
MQTTClientKeyStore mqttClientKeyStore = new MQTTClientKeyStore(mockCertificateManager);
83+
MQTTClientKeyStore mqttClientKeyStore = new MQTTClientKeyStore(mockServiceApi);
7284
mqttClientKeyStore.init();
7385

74-
ArgumentCaptor<Consumer<X509Certificate[]>> cbArgumentCaptor = ArgumentCaptor.forClass(Consumer.class);
75-
verify(mockCertificateManager, times(1))
76-
.subscribeToClientCertificateUpdates(any(String.class), cbArgumentCaptor.capture());
77-
Consumer<X509Certificate[]> certCallback = cbArgumentCaptor.getValue();
86+
ArgumentCaptor<GetCertificateRequest> cbArgumentCaptor = ArgumentCaptor.forClass(GetCertificateRequest.class);
87+
verify(mockServiceApi, times(1))
88+
.subscribeToCertificateUpdates(cbArgumentCaptor.capture());
89+
GetCertificateRequest getCertificateRequest = cbArgumentCaptor.getValue();
7890

7991
KeyStore keyStore = mqttClientKeyStore.getKeyStore();
8092
assertThat(keyStore.size(), is(0));
8193

8294
X509Certificate certificate = pemToX509Certificate(CERTIFICATE);
83-
X509Certificate[] chain = {certificate, certificate};
84-
certCallback.accept(chain);
95+
CertificateUpdateEvent certificateUpdate =
96+
new CertificateUpdateEvent(keyPair, certificate, new X509Certificate[]{certificate});
97+
getCertificateRequest.getCertificateUpdateConsumer().accept(certificateUpdate);
8598
assertThat(keyStore.size(), is(1));
8699

87100
PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS, DEFAULT_KEYSTORE_PASSWORD);
@@ -100,7 +113,7 @@ private void verifyStoredCertificate(X509Certificate cert) throws CertificateEnc
100113

101114
@Test
102115
void GIVEN_MQTTClientKeyStore_WHEN_called_updateCA_THEN_CA_stored() throws Exception {
103-
MQTTClientKeyStore mqttClientKeyStore = new MQTTClientKeyStore(mockCertificateManager);
116+
MQTTClientKeyStore mqttClientKeyStore = new MQTTClientKeyStore(mockServiceApi);
104117
mqttClientKeyStore.init();
105118
CountDownLatch updateLatch = new CountDownLatch(1);
106119
mqttClientKeyStore.listenToCAUpdates(updateLatch::countDown);
@@ -120,22 +133,23 @@ void GIVEN_MQTTClientKeyStore_WHEN_called_updateCA_THEN_CA_stored() throws Excep
120133

121134
@Test
122135
void GIVEN_MQTTClientKeyStore_WHEN_getSSLSocketFactory_THEN_returns_SSLSocketFactory() throws Exception {
123-
MQTTClientKeyStore mqttClientKeyStore = new MQTTClientKeyStore(mockCertificateManager);
136+
MQTTClientKeyStore mqttClientKeyStore = new MQTTClientKeyStore(mockServiceApi);
124137
mqttClientKeyStore.init();
125138
CountDownLatch updateLatch = new CountDownLatch(1);
126139
mqttClientKeyStore.listenToCAUpdates(updateLatch::countDown);
127140

128-
ArgumentCaptor<Consumer<X509Certificate[]>> cbArgumentCaptor = ArgumentCaptor.forClass(Consumer.class);
129-
verify(mockCertificateManager, times(1))
130-
.subscribeToClientCertificateUpdates(any(String.class), cbArgumentCaptor.capture());
131-
Consumer<X509Certificate[]> certCallback = cbArgumentCaptor.getValue();
141+
ArgumentCaptor<GetCertificateRequest> cbArgumentCaptor = ArgumentCaptor.forClass(GetCertificateRequest.class);
142+
verify(mockServiceApi, times(1))
143+
.subscribeToCertificateUpdates(cbArgumentCaptor.capture());
144+
GetCertificateRequest certificateRequest = cbArgumentCaptor.getValue();
132145

133146
KeyStore keyStore = mqttClientKeyStore.getKeyStore();
134147
assertThat(keyStore.size(), is(0));
135148

136149
X509Certificate certificate = pemToX509Certificate(CERTIFICATE);
137-
X509Certificate[] chain = {certificate, certificate};
138-
certCallback.accept(chain);
150+
CertificateUpdateEvent certificateUpdate =
151+
new CertificateUpdateEvent(keyPair, certificate, new X509Certificate[]{certificate});
152+
certificateRequest.getCertificateUpdateConsumer().accept(certificateUpdate);
139153
mqttClientKeyStore.updateCA(Collections.singletonList(CERTIFICATE));
140154
assertThat(updateLatch.await(100, TimeUnit.MILLISECONDS), is(true));
141155
assertThat(keyStore.size(), is(2));

0 commit comments

Comments
 (0)