From b845337136c9acb74294dab05d16fc64d164b62b Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 23 Feb 2026 10:39:31 -0800 Subject: [PATCH 01/33] dps --- .../provisioning/device/AdditionalData.java | 14 ++++++ .../device/ProvisioningDeviceClient.java | 1 + ...sioningDeviceClientRegistrationResult.java | 16 ++++++ .../ProvisioningDeviceClientConfig.java | 4 ++ .../device/internal/SDKUtils.java | 2 +- .../contract/amqp/ContractAPIAmqp.java | 5 ++ .../contract/http/ContractAPIHttp.java | 5 ++ .../contract/mqtt/ContractAPIMqtt.java | 12 +---- .../parser/DeviceRegistrationParser.java | 49 +++++++------------ .../DeviceRegistrationResultParser.java | 45 +++++++---------- .../internal/task/ProvisioningTask.java | 3 +- .../device/internal/task/RegisterTask.java | 4 +- .../internal/task/RegistrationResult.java | 7 +++ .../device/internal/task/RequestData.java | 34 ++++--------- 14 files changed, 104 insertions(+), 97 deletions(-) diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/AdditionalData.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/AdditionalData.java index 4f0c06847c..352886db8a 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/AdditionalData.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/AdditionalData.java @@ -18,4 +18,18 @@ public class AdditionalData @Getter @Setter private String provisioningPayload; + + /** + *

+ * the base64-encoded Certificate Signing Request (CSR) to be sent during registration. + * When set, the DPS service will return an issued certificate chain in the registration result. + *

+ *

+ * The CSR should be a base64-encoded DER format CSR. + * The Common Name (CN) in the CSR should match the registration ID. + *

+ */ + @Getter + @Setter + private String clientCertificateSigningRequest; } diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/ProvisioningDeviceClient.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/ProvisioningDeviceClient.java index e18b12fc8f..ff0ac8317d 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/ProvisioningDeviceClient.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/ProvisioningDeviceClient.java @@ -117,6 +117,7 @@ public void registerDevice(ProvisioningDeviceClientRegistrationCallback provisio if (additionalData != null) { this.provisioningDeviceClientConfig.setPayload(additionalData.getProvisioningPayload()); + this.provisioningDeviceClientConfig.setCertificateSigningRequest(additionalData.getClientCertificateSigningRequest()); } this.provisioningDeviceClientConfig.setRegistrationCallback(provisioningDeviceClientRegistrationCallback, context); diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/ProvisioningDeviceClientRegistrationResult.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/ProvisioningDeviceClientRegistrationResult.java index 2010a672bd..f77ec60fdb 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/ProvisioningDeviceClientRegistrationResult.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/ProvisioningDeviceClientRegistrationResult.java @@ -10,6 +10,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.Collection; +import java.util.List; + @NoArgsConstructor public class ProvisioningDeviceClientRegistrationResult { @@ -42,4 +45,17 @@ public class ProvisioningDeviceClientRegistrationResult @Getter protected String provisioningPayload; + + /** + *

+ * the issued client certificate chain in response to a certificate signing request. + * This list will be null if no certificate signing request was provided during registration. + *

+ *

+ * The certificate chain is returned as an array of base64-encoded certificates. + * The first element is the device/leaf certificate, followed by intermediate CA certificates. + *

+ */ + @Getter + protected List issuedClientCertificateChain; } diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/ProvisioningDeviceClientConfig.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/ProvisioningDeviceClientConfig.java index 999ee2bf56..a0013a74d5 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/ProvisioningDeviceClientConfig.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/ProvisioningDeviceClientConfig.java @@ -50,6 +50,10 @@ public final class ProvisioningDeviceClientConfig @Setter private String payload; + @Getter + @Setter + private String certificateSigningRequest; + /** * Setter for the Registration Callback. * @param registrationCallback Registration Callback to be triggered. diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/SDKUtils.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/SDKUtils.java index 864618cc3a..2d21ad3cc9 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/SDKUtils.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/SDKUtils.java @@ -19,7 +19,7 @@ @Slf4j public class SDKUtils { - private static final String SERVICE_API_VERSION = "2019-03-31"; + private static final String SERVICE_API_VERSION = "2025-07-01-preview"; public static final String PROVISIONING_DEVICE_CLIENT_IDENTIFIER = "com.microsoft.azure.sdk.iot.dps.dps-device-client/"; public static final String PROVISIONING_DEVICE_CLIENT_VERSION = getPackageVersion(); diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java index b73fd48c23..67d642c0e9 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java @@ -65,6 +65,11 @@ public ContractAPIAmqp(ProvisioningDeviceClientConfig provisioningDeviceClientCo throw new ProvisioningDeviceClientException("The hostName cannot be null or empty."); } + if (provisioningDeviceClientConfig.getCertificateSigningRequest() != null && !provisioningDeviceClientConfig.getCertificateSigningRequest().isEmpty()) + { + throw new UnsupportedOperationException("Including a certificate signing request with a provisioning request is not supported over AMQP or AMQP_WS"); + } + this.hostName = hostName; this.useWebSockets = provisioningDeviceClientConfig.isUsingWebSocket(); diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java index 83d8bf3c54..82e7474120 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java @@ -97,6 +97,11 @@ public ContractAPIHttp(ProvisioningDeviceClientConfig provisioningDeviceClientCo //SRS_ContractAPIHttp_25_001: [The constructor shall save the scope id and hostname.] this.idScope = idScope; this.hostName = hostName; + + if (provisioningDeviceClientConfig.getCertificateSigningRequest() != null && !provisioningDeviceClientConfig.getCertificateSigningRequest().isEmpty()) + { + throw new UnsupportedOperationException("Including a certificate signing request with a provisioning request is not supported over HTTP"); + } } private HttpRequest prepareRequest( diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqtt.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqtt.java index 2e899f8900..802b1dad36 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqtt.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqtt.java @@ -66,7 +66,6 @@ public String getHostName() { */ public ContractAPIMqtt(ProvisioningDeviceClientConfig provisioningDeviceClientConfig) throws ProvisioningDeviceClientException { - // SRS_ContractAPIMqtt_07_024: [ If provisioningDeviceClientConfig is null, this method shall throw ProvisioningDeviceClientException. ] if (provisioningDeviceClientConfig == null) { throw new ProvisioningDeviceClientException("ProvisioningDeviceClientConfig cannot be NULL."); @@ -95,7 +94,6 @@ private void executeProvisioningMessage(String topic, byte[] body, ResponseCallb try { - // SRS_ProvisioningAmqpOperations_07_011: [This method shall wait for the response of this message for MAX_WAIT_TO_SEND_MSG and call the responseCallback with the reply.] synchronized (this.receiveLock) { this.receiveLock.waitLock(MAX_WAIT_TO_SEND_MSG); @@ -113,7 +111,6 @@ private void executeProvisioningMessage(String topic, byte[] body, ResponseCallb } catch (InterruptedException e) { - // SRS_ProvisioningAmqpOperations_07_012: [This method shall throw ProvisioningDeviceClientException if any failure is encountered.] throw new ProvisioningDeviceClientException("Provisioning service failed to reply is allotted time."); } @@ -225,7 +222,6 @@ public synchronized void close() throws ProvisioningDeviceConnectionException */ public synchronized void authenticateWithProvisioningService(RequestData requestData, ResponseCallback responseCallback, Object callbackContext) throws ProvisioningDeviceClientException { - //SRS_ContractAPIAmqp_07_003: [If responseCallback is null, this method shall throw ProvisioningDeviceClientException.] if (responseCallback == null) { throw new ProvisioningDeviceClientException("responseCallback cannot be null"); @@ -236,13 +232,9 @@ public synchronized void authenticateWithProvisioningService(RequestData request String sasToken = requestData.getSasToken(); if (sasToken == null || sasToken.isEmpty()) { - //SRS_ContractAPIAmqp_34_021: [If the requestData is not x509, but the provided requestData does not contain a sas token, this function shall - // throw a ProvisioningDeviceConnectionException.] throw new ProvisioningDeviceConnectionException(new IllegalArgumentException("RequestData's sas token cannot be null or empty")); } - //SRS_ContractAPIAmqp_34_020: [If the requestData is not x509, this function shall assume SymmetricKey authentication, and shall open the connection with - // the provided request data containing a sas token.] open(requestData); } @@ -255,10 +247,8 @@ public synchronized void authenticateWithProvisioningService(RequestData request { String topic = String.format(MQTT_REGISTER_MESSAGE_FMT, this.packetId++); - //SRS_ContractAPIMqtt_07_026: [ This method shall build the required Json input using parser. ] - byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload()).toJson().getBytes(StandardCharsets.UTF_8); + byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), requestData.getCertificateSigningRequest()).toJson().getBytes(StandardCharsets.UTF_8); - // SRS_ContractAPIMqtt_07_005: [This method shall send an MQTT message with the property of iotdps-register.] this.executeProvisioningMessage(topic, payload, responseCallback, callbackContext); } catch (IOException ex) diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java index af6855614c..4fcb70bb80 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java @@ -15,19 +15,19 @@ @SuppressWarnings("unused") // A number of private fields are unused but may be filled in by serialization public class DeviceRegistrationParser { - private static final String REGISTRATION_ID = "registrationId"; - @SerializedName(REGISTRATION_ID) + @SerializedName("registrationId") private String registrationId; - private static final String TPM = "tpm"; @SuppressWarnings("FieldCanBeLocal") - @SerializedName(TPM) + @SerializedName("tpm") private TpmAttestation tpmAttestation; - private static final String CUSTOM_PAYLOAD = "payload"; - @SerializedName(CUSTOM_PAYLOAD) + @SerializedName("payload") private String customPayload = null; + @SerializedName("csr") + private String certificateSigningRequest = null; + /** * Inner class describing TPM Attestation i.e it holds EndorsementKey and StorageRootKey */ @@ -56,51 +56,39 @@ static class TpmAttestation * @param customPayload Custom Payload being sent to the DPS service. Can be a {@code null} or empty value. * @throws IllegalArgumentException If the provided registration id was {@code null} or empty. */ - public DeviceRegistrationParser(String registrationId, String customPayload) throws IllegalArgumentException + public DeviceRegistrationParser(String registrationId, String customPayload, String certificateSigningRequest) throws IllegalArgumentException { - //SRS_DeviceRegistration_25_001: [ The constructor shall throw IllegalArgumentException if Registration Id is null or empty. ] - if (registrationId == null || registrationId.isEmpty()) - { - throw new IllegalArgumentException("Registration Id cannot be null or empty"); - } - - //SRS_DeviceRegistration_25_002: [ The constructor shall save the provided Registration Id. ] - this.registrationId = registrationId; - if (customPayload != null && !customPayload.isEmpty()) - { - this.customPayload = customPayload; - } + this(registrationId, customPayload, null, null, null); } /** * Constructor for Device Registration for TPM flow * @param registrationId Registration Id to be sent to the service. Cannot be a {@code null} or empty value. + * @param customPayload Custom Payload being sent to the DPS service. Can be a {@code null} or empty value. + * @param certificateSigningRequest The certificate signing request to be sent. Can be a {@code null} or empty value. * @param endorsementKey endorsement key to be sent to the service. Cannot be a {@code null} or empty value. * @param storageRootKey Storage Root Key to be sent to the service. Can be a {@code null} value. - * @param customPayload Custom Payload being sent to the DPS service. Can be a {@code null} or empty value. * @throws IllegalArgumentException is thrown if any of the input parameters are invalid. */ - public DeviceRegistrationParser(String registrationId, String customPayload, String endorsementKey, String storageRootKey) throws IllegalArgumentException + public DeviceRegistrationParser(String registrationId, String customPayload, String certificateSigningRequest, String endorsementKey, String storageRootKey) throws IllegalArgumentException { - //SRS_DeviceRegistration_25_003: [ The constructor shall throw IllegalArgumentException if Registration Id is null or empty. ] if (registrationId == null || registrationId.isEmpty()) { throw new IllegalArgumentException("Registration Id cannot be null or empty"); } - //SRS_DeviceRegistration_25_004: [ The constructor shall throw IllegalArgumentException if EndorsementKey is null or empty. ] - if (endorsementKey == null || endorsementKey.isEmpty()) - { - throw new IllegalArgumentException("endorsementKey cannot be null or empty"); - } - - //SRS_DeviceRegistration_25_006: [ The constructor shall save the provided Registration Id, EndorsementKey and StorageRootKey. ] this.registrationId = registrationId; if (customPayload != null && !customPayload.isEmpty()) { this.customPayload = customPayload; } - this.tpmAttestation = new TpmAttestation(endorsementKey, storageRootKey); + + this.certificateSigningRequest = certificateSigningRequest; + + if (endorsementKey != null && !endorsementKey.isEmpty()) + { + this.tpmAttestation = new TpmAttestation(endorsementKey, storageRootKey); + } } /** @@ -129,7 +117,6 @@ public DeviceRegistrationParser(String registrationId, String customPayload, Str */ public String toJson() { - //SRS_DeviceRegistration_25_007: [ This method shall create the expected Json with the provided Registration Id, EndorsementKey and StorageRootKey. ] Gson gson = new GsonBuilder().disableHtmlEscaping().create(); return gson.toJson(this); } diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationResultParser.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationResultParser.java index c907dd9b76..ba90e10cc7 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationResultParser.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationResultParser.java @@ -11,6 +11,8 @@ import com.google.gson.annotations.SerializedName; import lombok.Getter; +import java.util.List; + /** * Class that represents the REST API format for DeviceRegistrationResult * Format : https://docs.microsoft.com/en-us/rest/api/iot-dps/RuntimeRegistration/RegisterDevice#definitions_deviceregistrationresult @@ -18,71 +20,62 @@ @SuppressWarnings("unused") // A number of private fields are unused but may be filled in by serialization public class DeviceRegistrationResultParser { - private static final String REGISTRATION_ID = "registrationId"; - @SerializedName(REGISTRATION_ID) + @SerializedName("registrationId") @Getter private String registrationId; - private static final String CREATED_DATE_TIME_UTC = "createdDateTimeUtc"; - @SerializedName(CREATED_DATE_TIME_UTC) + @SerializedName("createdDateTimeUtc") @Getter private String createdDateTimeUtc; - private static final String ASSIGNED_HUB = "assignedHub"; - @SerializedName(ASSIGNED_HUB) + @SerializedName("assignedHub") @Getter private String assignedHub; - private static final String DEVICE_ID = "deviceId"; - @SerializedName(DEVICE_ID) + @SerializedName("deviceId") @Getter private String deviceId; - private static final String STATUS = "status"; - @SerializedName(STATUS) + @SerializedName("status") @Getter private String status; - private static final String SUBSTATUS = "substatus"; - @SerializedName(SUBSTATUS) + @SerializedName("substatus") @Getter private String substatus; - private static final String ETAG = "etag"; - @SerializedName(ETAG) + @SerializedName("etag") @Getter private String eTag; - private static final String LAST_UPDATES_DATE_TIME_UTC = "lastUpdatedDateTimeUtc"; - @SerializedName(LAST_UPDATES_DATE_TIME_UTC) + @SerializedName("lastUpdatedDateTimeUtc") @Getter private String lastUpdatesDateTimeUtc; - private static final String ERROR_CODE = "errorCode"; - @SerializedName(ERROR_CODE) + @SerializedName("errorCode") @Getter private Integer errorCode; - private static final String ERROR_MESSAGE = "errorMessage"; - @SerializedName(ERROR_MESSAGE) + @SerializedName("errorMessage") @Getter private String errorMessage; - private static final String TPM = "tpm"; - @SerializedName(TPM) + @SerializedName("tpm") @Getter private TpmRegistrationResultParser tpm; - private static final String X509 = "x509"; - @SerializedName(X509) + @SerializedName("x509") @Getter private X509RegistrationResultParser x509; - private static final String PAYLOAD = "payload"; - @SerializedName(PAYLOAD) + @SerializedName("payload") @Getter private JsonObject jsonPayload; + @SerializedName("issuedCertificateChain") + @Getter + private List issuedCertificateChain; + //empty constructor for Gson DeviceRegistrationResultParser() { diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/ProvisioningTask.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/ProvisioningTask.java index e301bcaf8c..73859b1f65 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/ProvisioningTask.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/ProvisioningTask.java @@ -224,6 +224,7 @@ private void executeStateMachineForStatus(RegistrationOperationStatusParser regi registrationInfo.setCreatedDateTimeUtc(registrationStatus.getCreatedDateTimeUtc()); registrationInfo.setLastUpdatesDateTimeUtc(registrationStatus.getLastUpdatesDateTimeUtc()); registrationInfo.setETag(registrationStatus.getETag()); + registrationInfo.setIssuedCertificateChain(registrationStatus.getIssuedCertificateChain()); if (this.securityProvider instanceof SecurityProviderTpm) { @@ -300,7 +301,7 @@ public Object call() throws Exception { //SRS_ProvisioningTask_25_015: [ This method shall invoke open call on the contract.] log.info("Opening the connection to device provisioning service..."); - provisioningDeviceClientContract.open(new RequestData(securityProvider.getRegistrationId(), securityProvider.getSSLContext(), securityProvider instanceof SecurityProviderX509, provisioningDeviceClientConfig.getPayload())); + provisioningDeviceClientContract.open(new RequestData(securityProvider.getRegistrationId(), securityProvider.getSSLContext(), securityProvider instanceof SecurityProviderX509, provisioningDeviceClientConfig.getPayload(), provisioningDeviceClientConfig.getCertificateSigningRequest())); //SRS_ProvisioningTask_25_007: [ This method shall invoke Register task and status task to execute the state machine of the service as per below rules.] /* Service State Machine Rules diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RegisterTask.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RegisterTask.java index ea638659c6..bd96fb42fe 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RegisterTask.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RegisterTask.java @@ -294,7 +294,7 @@ private RegistrationOperationStatusParser authenticateWithDPS() throws Provision if (this.securityProvider instanceof SecurityProviderX509) { - RequestData requestData = new RequestData(securityProvider.getRegistrationId(), sslContext, true, this.provisioningDeviceClientConfig.getPayload()); + RequestData requestData = new RequestData(securityProvider.getRegistrationId(), sslContext, true, this.provisioningDeviceClientConfig.getPayload(), this.provisioningDeviceClientConfig.getCertificateSigningRequest()); log.info("Authenticating with device provisioning service using x509 certificates"); return this.authenticateWithX509(requestData); } @@ -314,7 +314,7 @@ else if (this.securityProvider instanceof SecurityProviderTpm) } else if (this.securityProvider instanceof SecurityProviderSymmetricKey) { - RequestData requestData = new RequestData(securityProvider.getRegistrationId(), sslContext, null, this.provisioningDeviceClientConfig.getPayload()); + RequestData requestData = new RequestData(securityProvider.getRegistrationId(), sslContext, null, this.provisioningDeviceClientConfig.getPayload(), this.provisioningDeviceClientConfig.getCertificateSigningRequest()); log.info("Authenticating with device provisioning service using symmetric key"); return this.authenticateWithSasToken(requestData); diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RegistrationResult.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RegistrationResult.java index 17b85a1d43..4045f66055 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RegistrationResult.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RegistrationResult.java @@ -11,6 +11,8 @@ import com.microsoft.azure.sdk.iot.provisioning.device.ProvisioningDeviceClientStatus; import com.microsoft.azure.sdk.iot.provisioning.device.ProvisioningDeviceClientSubstatus; +import java.util.List; + class RegistrationResult extends ProvisioningDeviceClientRegistrationResult { /** @@ -58,4 +60,9 @@ void setLastUpdatesDateTimeUtc(String lastUpdatesDateTimeUtc) { this.lastUpdatesDateTimeUtc = lastUpdatesDateTimeUtc; } + + void setIssuedCertificateChain(List issuedCertificateChain) + { + this.issuedClientCertificateChain = issuedCertificateChain; + } } diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java index d250994162..bd89c4e3a1 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java @@ -45,6 +45,10 @@ public class RequestData @Setter(AccessLevel.PACKAGE) private String payload; + @Getter + @Setter(AccessLevel.PACKAGE) + private String certificateSigningRequest; + /** * Constructor for Request data * @param endorsementKey Endorsement key value. Can be {@code null} @@ -56,7 +60,6 @@ public class RequestData */ RequestData(byte[] endorsementKey, byte[] storageRootKey, String registrationId, SSLContext sslContext, String sasToken, String payload) { - //SRS_RequestData_25_001: [ Constructor shall save all the parameters and ignore the null parameters. ] this.endorsementKey = endorsementKey; this.storageRootKey = storageRootKey; this.registrationId = registrationId; @@ -72,10 +75,10 @@ public class RequestData * @param sslContext SSL context value. Can be {@code null} * @param sasToken SasToken value. Can be {@code null} * @param payload Payload value. Can be {@code null} + * @param certificateSigningRequest The optional certificate signing request. May be null or empty. */ - RequestData(String registrationId, SSLContext sslContext, String sasToken, String payload) + RequestData(String registrationId, SSLContext sslContext, String sasToken, String payload, String certificateSigningRequest) { - //SRS_RequestData_25_001: [ Constructor shall save all the parameters and ignore the null parameters. ] this.registrationId = registrationId; this.sslContext = sslContext; this.sasToken = sasToken; @@ -89,33 +92,15 @@ public class RequestData * @param sslContext SSL context value. Can be {@code null} * @param isX509 True if X509 flow, false otherwise * @param payload Payload value. Can be {@code null} + * @param certificateSigningRequest The optional certificate signing request. May be null or empty. */ - RequestData(String registrationId, SSLContext sslContext, boolean isX509, String payload) + RequestData(String registrationId, SSLContext sslContext, boolean isX509, String payload, String certificateSigningRequest) { - //SRS_RequestData_25_001: [ Constructor shall save all the parameters and ignore the null parameters. ] this.registrationId = registrationId; this.sslContext = sslContext; this.isX509 = isX509; this.payload = payload; - } - - /** - * Constructor for Request data - * @param registrationId Registration ID value. Can be {@code null}; - * @param operationId Operation ID value. Can be {@code null}; - * @param sslContext SSL context value. Can be {@code null}; - * @param sasToken SasToken value. Can be {@code null}; - * @param payload Payload value. Can be {@code null} - */ - RequestData(String registrationId, String operationId, SSLContext sslContext, String sasToken, String payload) - { - //SRS_RequestData_25_001: [ Constructor shall save all the parameters and ignore the null parameters. ] - this.registrationId = registrationId; - this.operationId = operationId; - this.sslContext = sslContext; - this.sasToken = sasToken; - this.payload = payload; - this.isX509 = false; + this.certificateSigningRequest = certificateSigningRequest; } /** @@ -124,7 +109,6 @@ public class RequestData */ public boolean isX509() { - //SRS_RequestData_25_015: [ This method shall return true is it is X509, false otherwise. ] return isX509; } } From bf00e6230d79eef39d3851495986aed5c9fb9712 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 23 Feb 2026 12:08:58 -0800 Subject: [PATCH 02/33] iot hub --- .../iot/device/CertificateSigningError.java | 79 ++++++++ .../device/CertificateSigningErrorCode.java | 54 ++++++ .../device/CertificateSigningErrorInfo.java | 75 ++++++++ .../iot/device/CertificateSigningRequest.java | 85 +++++++++ .../CertificateSigningRequestAccepted.java | 55 ++++++ .../device/CertificateSigningResponse.java | 68 +++++++ .../CertificateSigningResponseCallback.java | 23 +++ .../azure/sdk/iot/device/DeviceClient.java | 18 ++ .../azure/sdk/iot/device/MessageType.java | 3 +- .../transport/IotHubTransportMessage.java | 4 + .../iot/device/transport/TransportUtils.java | 2 +- .../mqtt/MqttCertificateSigning.java | 170 ++++++++++++++++++ .../transport/mqtt/MqttIotHubConnection.java | 28 ++- .../sdk/iot/device/twin/DeviceOperations.java | 1 + 14 files changed, 659 insertions(+), 6 deletions(-) create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningError.java create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorCode.java create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorInfo.java create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequest.java create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequestAccepted.java create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponse.java create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponseCallback.java create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningError.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningError.java new file mode 100644 index 0000000000..7c08c6416b --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningError.java @@ -0,0 +1,79 @@ +package com.microsoft.azure.sdk.iot.device; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.annotations.SerializedName; +import com.microsoft.azure.sdk.iot.device.twin.ParserUtility; +import lombok.Getter; + +import java.util.Date; + +public class CertificateSigningError +{ +/* Example: +{ + "errorCode": 400040, + "message": "Credential management operation failed", + "trackingId": "59b2922c-f1c9-451b-b02d-5b64bc31685a", + "timestampUtc": "2025-06-09T17:31:31.426574675Z", + "info": { + "correlationId": "8819e8d8-1324-4a9c-acde-ce0318e93f31", + "credentialError": "FailedToDecodeCsr", + "credentialMessage": "Failed to decode CSR: invalid base64 encoding" + } +} +*/ + @SerializedName("errorCode") + private String errorCodeString; + + @Getter + private transient CertificateSigningErrorCode errorCode; + + @SerializedName("message") + @Getter + private String message; + + @SerializedName("trackingId") + @Getter + private String trackingId; + + @SerializedName("timestampUtc") + private String timestampUtcString; + + @Getter + private transient Date timestampUtc; + + @SerializedName("info") + @Getter + private CertificateSigningErrorInfo info; + + public CertificateSigningError(String json) throws IllegalArgumentException + { + Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); + CertificateSigningError deserialized; + + ParserUtility.validateStringUTF8(json); + try + { + deserialized = gson.fromJson(json, CertificateSigningError.class); + } + catch (JsonSyntaxException malformed) + { + throw new IllegalArgumentException("Malformed json", malformed); + } + + this.errorCodeString = deserialized.errorCodeString; + this.errorCode = CertificateSigningErrorCode.GetValue(Integer.parseInt(this.errorCodeString)); + this.trackingId = deserialized.trackingId; + this.message = deserialized.message; + this.timestampUtcString = deserialized.timestampUtcString; + this.timestampUtc = ParserUtility.getDateTimeUtc(this.timestampUtcString); + this.info = deserialized.getInfo(); + } + + @SuppressWarnings("unused") // used by gson + CertificateSigningError() + { + } +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorCode.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorCode.java new file mode 100644 index 0000000000..b2c6f4b243 --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorCode.java @@ -0,0 +1,54 @@ +package com.microsoft.azure.sdk.iot.device; + +public enum CertificateSigningErrorCode +{ + InvalidProtocolVersion, + OperationNotAvailableInCurrentTier, + PreconditionFailed, + CredentialManagementPreconditionFailed, + ThrottleBacklogLimitExceeded, + ThrottlingBacklogTimeout, + CredentialOperationPending, + CredentialOperationActive, + CredentialOperationFailed, + DeviceNotFound, + DeviceUnavailable, + ServerError, + ServiceUnavailable, + Unknown; + + public static CertificateSigningErrorCode GetValue(int code) + { + switch (code) + { + case 400001: + return InvalidProtocolVersion; + case 403010: + return OperationNotAvailableInCurrentTier; + case 412001: + return PreconditionFailed; + case 412005: + return CredentialManagementPreconditionFailed; + case 429002: + return ThrottleBacklogLimitExceeded; + case 429003: + return ThrottlingBacklogTimeout; + case 409004: + return CredentialOperationPending; + case 409005: + return CredentialOperationActive; + case 400040: + return CredentialOperationFailed; + case 404001: + return DeviceNotFound; + case 503102: + return DeviceUnavailable; + case 500001: + return ServerError; + case 503001: + return ServiceUnavailable; + default: + return Unknown; + } + } +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorInfo.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorInfo.java new file mode 100644 index 0000000000..350d41c549 --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorInfo.java @@ -0,0 +1,75 @@ +package com.microsoft.azure.sdk.iot.device; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.annotations.SerializedName; +import com.microsoft.azure.sdk.iot.device.twin.ParserUtility; +import lombok.Getter; + +import java.util.Date; + +public class CertificateSigningErrorInfo { +/* Example: +{ + "correlationId": "8819e8d8-1324-4a9c-acde-ce0318e93f31", + "credentialError": "FailedToDecodeCsr", + "credentialMessage": "Failed to decode CSR: invalid base64 encoding" +} + +alternatively, in the case of an "operation already in progress" error: +{ + "requestId": "aabbcc", + "correlationId": "8819e8d8-1324-4a9c-acde-ce0318e93f31", + "operationExpires": "2025-06-09T17:31:31.426Z" +} +*/ + @SerializedName("correlationId") + @Getter + private String correlationId; + + @SerializedName("credentialError") + @Getter + private String credentialError; //TODO is this an enum? + + @SerializedName("credentialMessage") + @Getter + private String credentialMessage; + + @SerializedName("requestId") + private String requestId; + + @SerializedName("operationExpires") + private String operationExpiresString; + + @Getter + private transient Date operationExpires; + + public CertificateSigningErrorInfo(String json) throws IllegalArgumentException + { + Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); + CertificateSigningErrorInfo deserialized; + + ParserUtility.validateStringUTF8(json); + try + { + deserialized = gson.fromJson(json, CertificateSigningErrorInfo.class); + } + catch (JsonSyntaxException malformed) + { + throw new IllegalArgumentException("Malformed json", malformed); + } + + this.correlationId = deserialized.correlationId; + this.credentialError = deserialized.credentialError; + this.credentialMessage = deserialized.credentialMessage; + this.operationExpiresString = deserialized.operationExpiresString; + this.operationExpires = ParserUtility.getDateTimeUtc(this.operationExpiresString); + this.requestId = deserialized.requestId; + } + + @SuppressWarnings("unused") // used by gson + CertificateSigningErrorInfo() + { + } +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequest.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequest.java new file mode 100644 index 0000000000..a0709e9ae0 --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequest.java @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package com.microsoft.azure.sdk.iot.device; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import lombok.Setter; + +/** + * The request payload to send to IoT Hub to notify it when a file upload is completed, whether successful or not. + * Must set whether the file upload was a success or not, and must set the correlation Id, but all other fields are optional. + */ +public class CertificateSigningRequest +{ + /** + * Required. The device ID the certificate will be issued for. + * Must match the currently authenticated device ID. + */ + @SerializedName("id") + private String id = null; + + /** + * The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. + */ + @SerializedName("csr") + private String certificateSigningRequestData = null; + + /** + * Optional. Request ID to replace, or "*" to replace any active request. + * Use when: + * - The CSR is known to be different from a previous incomplete request + * - Client received 409005 and doesn't know if CSR has changed (e.g., storage failure) + * Default: null (will fail with 409005 if an active operation exists) + */ + @SerializedName("replace") + private String replace = null; + + /** + * + * @param id The device ID the certificate will be issued for. Must match the device Id of the device that will send this request. + * @param certificateSigningRequestData The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. + */ + public CertificateSigningRequest(String id, String certificateSigningRequestData) + { + this(id, certificateSigningRequestData, null); + } + + /** + * + * @param id The device ID the certificate will be issued for. Must match the device Id of the device that will send this request. + * @param certificateSigningRequestData The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. + * @param replace the request ID to replace, or "*" to replace any active request. For use if a + * previous certificate signing request has failed and you want to start over. + */ + public CertificateSigningRequest(String id, String certificateSigningRequestData, String replace) + { + if (id == null || id.isEmpty()) + { + throw new IllegalArgumentException("Id must be non-null and not empty"); + } + + if (certificateSigningRequestData == null || certificateSigningRequestData.isEmpty()) + { + throw new IllegalArgumentException("certificateSigningRequestData must be non-null and not empty"); + } + + this.id = id; + this.certificateSigningRequestData = certificateSigningRequestData; + } + + String toJson() + { + Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); + + return gson.toJson(this); + } + + @SuppressWarnings("unused") // used by gson + CertificateSigningRequest() + { + } +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequestAccepted.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequestAccepted.java new file mode 100644 index 0000000000..1218a5ade2 --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequestAccepted.java @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package com.microsoft.azure.sdk.iot.device; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import com.microsoft.azure.sdk.iot.device.twin.ParserUtility; +import lombok.Getter; + +import java.util.Date; +import java.util.List; + +/** + * The information provided from IoT Hub that can be used with the Azure Storage SDK to upload a file from your device, including authentication. + */ +public class CertificateSigningRequestAccepted +{ + @SerializedName("correlationId") + @Getter + private String correlationId; + + @SerializedName("operationExpires") + private String operationExpiresString; + + @Getter + private transient Date operationExpires; + + public CertificateSigningRequestAccepted(String json) throws IllegalArgumentException + { + Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); + CertificateSigningRequestAccepted deserialized; + + ParserUtility.validateStringUTF8(json); + try + { + deserialized = gson.fromJson(json, CertificateSigningRequestAccepted.class); + } + catch (JsonSyntaxException malformed) + { + throw new IllegalArgumentException("Malformed json", malformed); + } + + this.operationExpires = ParserUtility.getDateTimeUtc(deserialized.operationExpiresString); + this.correlationId = deserialized.correlationId; + } + + @SuppressWarnings("unused") // used by gson + CertificateSigningRequestAccepted() + { + } +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponse.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponse.java new file mode 100644 index 0000000000..c4c11e9495 --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponse.java @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package com.microsoft.azure.sdk.iot.device; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import com.microsoft.azure.sdk.iot.device.twin.ParserUtility; +import lombok.Getter; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * The information provided from IoT Hub that can be used with the Azure Storage SDK to upload a file from your device, including authentication. + */ +public class CertificateSigningResponse +{ + /** + *

+ * List of Base64-encoded certificates in the certificate chain. + *

+ *

+ * The first certificate is the issued device certificate, followed by intermediates. + *

+ */ + @SerializedName("certificates") + @Getter + private List certificates; + + /** + * Correlation ID for diagnostic and support purposes. + */ + @SerializedName("correlationId") + @Getter + private List correlationId; + + public CertificateSigningResponse(String json) throws IllegalArgumentException + { + Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); + CertificateSigningResponse deserialized; + + ParserUtility.validateStringUTF8(json); + try + { + deserialized = gson.fromJson(json, CertificateSigningResponse.class); + } + catch (JsonSyntaxException malformed) + { + throw new IllegalArgumentException("Malformed json", malformed); + } + + this.correlationId = deserialized.correlationId; + this.certificates = deserialized.certificates; + } + + @SuppressWarnings("unused") // used by gson + CertificateSigningResponse() + { + } +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponseCallback.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponseCallback.java new file mode 100644 index 0000000000..f4bec6c0ef --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponseCallback.java @@ -0,0 +1,23 @@ +package com.microsoft.azure.sdk.iot.device; + +public interface CertificateSigningResponseCallback +{ + /** + *

+ * Executes if/when IoT hub sends a 202 in response to the initial certificate signing request call. + *

+ *

+ * When this executes, it signals that IoT hub has begun processing the certificate signing request and + * will notify this client again once that request has been completed via {@link #onCertificateSigningComplete(CertificateSigningResponse)} ()} + *

+ *

+ * If the certificate signing request is not accetepted or fails for any reason, {@link #onCertificateSigningError(CertificateSigningError)} ()} + * will execute instead of this callback. + *

+ */ + public void onCertificateSigningRequestAccepted(CertificateSigningRequestAccepted accepted); + + public void onCertificateSigningComplete(CertificateSigningResponse response); + + public void onCertificateSigningError(CertificateSigningError error); +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index 102f7e5244..f6a5c019de 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -4,13 +4,16 @@ package com.microsoft.azure.sdk.iot.device; import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException; +import com.microsoft.azure.sdk.iot.device.transport.IotHubTransportMessage; import com.microsoft.azure.sdk.iot.device.transport.RetryPolicy; import com.microsoft.azure.sdk.iot.device.transport.TransportUtils; import com.microsoft.azure.sdk.iot.device.transport.https.HttpsTransportManager; +import com.microsoft.azure.sdk.iot.device.twin.DeviceOperations; import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProvider; import lombok.extern.slf4j.Slf4j; import java.io.IOException; +import java.util.UUID; /** *

@@ -271,6 +274,21 @@ public boolean isMultiplexed() return this.isMultiplexed; } + // TODO docs, timeouts? + public void sendCertificateSigningRequest(CertificateSigningRequest request, CertificateSigningResponseCallback callback) + { + // This one message signals to lower layers to both subscribe to MQTT response topic (if not already subscribed) + // and to send the CSR. This is a bit different from how methods/twins work but vastly simplifies the user + // experience here (compared to having a separate method for subscribing to CSR response topic). + IotHubTransportMessage certificateSigningRequest = new IotHubTransportMessage(request.toJson()); + certificateSigningRequest.setDeviceOperationType(DeviceOperations.DEVICE_OPERATION_CERTIFICATE_SIGNING_REQUEST); + certificateSigningRequest.setCertificateSigningResponseCallback(callback); + certificateSigningRequest.setMessageType(MessageType.CERTIFICATE_SIGNING_REQUEST); + certificateSigningRequest.setRequestId(UUID.randomUUID().toString()); + + this.getDeviceIO().sendEventAsync(certificateSigningRequest, null, null, this.config.getDeviceId()); + } + // Used by multiplexing clients to signal to this client what kind of multiplexing client is using this device client void markAsMultiplexed() { diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/MessageType.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/MessageType.java index 2cc1bbfba7..098910d476 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/MessageType.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/MessageType.java @@ -11,5 +11,6 @@ public enum MessageType UNKNOWN, DEVICE_TELEMETRY, DEVICE_METHODS, - DEVICE_TWIN + DEVICE_TWIN, + CERTIFICATE_SIGNING_REQUEST } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java index 4249c33ec0..8606e7af2a 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java @@ -36,6 +36,10 @@ public class IotHubTransportMessage extends Message @Setter private int qualityOfService; + @Getter + @Setter + private CertificateSigningResponseCallback certificateSigningResponseCallback; + /** * Constructor with binary data and message type * @param data The byte array of the message. diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/TransportUtils.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/TransportUtils.java index 56660c8bf5..dd00e2eac0 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/TransportUtils.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/TransportUtils.java @@ -15,7 +15,7 @@ @Slf4j public class TransportUtils { - public static final String IOTHUB_API_VERSION = "2020-09-30"; + public static final String IOTHUB_API_VERSION = "2025-08-01-preview"; private static final String JAVA_DEVICE_CLIENT_IDENTIFIER = "com.microsoft.azure.sdk.iot.iot-device-client"; public static final String CLIENT_VERSION = getPackageVersion(); diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java new file mode 100644 index 0000000000..fac3ab0d75 --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package com.microsoft.azure.sdk.iot.device.transport.mqtt; + +import com.microsoft.azure.sdk.iot.device.*; +import com.microsoft.azure.sdk.iot.device.transport.IotHubTransportMessage; +import com.microsoft.azure.sdk.iot.device.transport.TransportException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; +import java.util.regex.Pattern; + +@Slf4j +class MqttCertificateSigning extends Mqtt +{ + private final String certificateSigningResponseTopic = "$iothub/credentials/res/#"; + private boolean isStarted = false; + private Map inProgressRequestIdMap = new HashMap<>(); + private static final String REQ_ID = "?$rid="; + + public MqttCertificateSigning( + String deviceId, + MqttConnectOptions connectOptions, + Map unacknowledgedSentMessages, + Queue> receivedMessages) + { + super(null, deviceId, connectOptions, unacknowledgedSentMessages, receivedMessages); + + if (deviceId == null || deviceId.isEmpty()) + { + throw new IllegalArgumentException("Device id cannot be null or empty"); + } + } + + public void start() throws TransportException + { + // TODO how well does this handle resumed sessions/lost sessions? + if (!this.isStarted) + { + this.subscribe(this.certificateSigningResponseTopic); + this.isStarted = true; + } + } + + public void stop() + { + this.isStarted = false; + } + + public void send(IotHubTransportMessage message) throws TransportException + { + CertificateSigningResponseCallback signingCallback = message.getCertificateSigningResponseCallback(); + + // Service will ack this immediately, then later publish a message to the response topic + this.publish("$iothub/credentials/POST/issueCertificate/?$rid=" + message.getRequestId(), message); + inProgressRequestIdMap.put(message.getRequestId(), signingCallback); + + //TODO listening for response messages should work like how getTwin works. Listen on callback thread, not here. + } + + @Override + public IotHubTransportMessage receive() + { + synchronized (this.receivedMessagesLock) + { + IotHubTransportMessage message = null; + + Pair messagePair = this.receivedMessages.peek(); + + if (messagePair != null) + { + String topic = messagePair.getKey(); + + if (topic != null && topic.length() > 0) + { + //$iothub/credentials/res/{status}/?$rid={request_id} + if (topic.startsWith("$iothub/credentials/res/")) + { + MqttMessage mqttMessage = messagePair.getValue(); + byte[] payload = mqttMessage.getPayload(); + + //remove this message from the queue as this is the correct handler + this.receivedMessages.poll(); + + String[] topicTokens = topic.split(Pattern.quote("/")); + if (topicTokens.length == 5) + { + String status = topicTokens[4]; + String requestId = getRequestId(topicTokens[5]); + if (this.inProgressRequestIdMap.containsKey(requestId)) + { + CertificateSigningResponseCallback certificateSigningResponseCallback = this.inProgressRequestIdMap.get(requestId); + + if (status.equals("202")) + { + try + { + CertificateSigningRequestAccepted accepted = new CertificateSigningRequestAccepted(new String(payload, StandardCharsets.UTF_8)); + certificateSigningResponseCallback.onCertificateSigningRequestAccepted(accepted); + } + catch (IllegalArgumentException e) + { + log.error("Received certificate signing request accepted message with malformed payload. Ignoring it."); + } + } + else if (status.equals("200")) + { + try + { + CertificateSigningResponse response = new CertificateSigningResponse(new String(payload, StandardCharsets.UTF_8)); + certificateSigningResponseCallback.onCertificateSigningComplete(response); + } + catch (IllegalArgumentException e) + { + log.error("Received certificate signing response message with malformed payload. Ignoring it."); + } + } + else + { + try + { + CertificateSigningError error = new CertificateSigningError(new String(payload, StandardCharsets.UTF_8)); + certificateSigningResponseCallback.onCertificateSigningError(error); + } + catch (IllegalArgumentException e) + { + log.error("Received certificate signing error message with malformed payload. Ignoring it."); + } + } + } + else + { + log.warn("Received certificate signing response message for an unknown request Id. Ignoring it."); + } + } + else + { + log.warn("Received MQTT message on certificate signing response topic with an unexpected topic pattern. Ignoring it."); + //TODO ack it? Are they always QoS0 + } + } + } + } + + return message; + } + } + + private String getRequestId(String token) + { + String reqId = null; + + if (token.contains(REQ_ID)) // restriction for request id + { + int startIndex = token.indexOf(REQ_ID) + REQ_ID.length(); + int endIndex = token.length(); + + reqId = token.substring(startIndex, endIndex); + } + + return reqId; + } +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java index 51d1a4e968..f9e154238f 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java @@ -27,8 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; -import static com.microsoft.azure.sdk.iot.device.MessageType.DEVICE_METHODS; -import static com.microsoft.azure.sdk.iot.device.MessageType.DEVICE_TWIN; +import static com.microsoft.azure.sdk.iot.device.MessageType.*; import static com.microsoft.azure.sdk.iot.device.transport.mqtt.Mqtt.MAX_IN_FLIGHT_COUNT; import static org.eclipse.paho.client.mqttv3.MqttConnectOptions.MQTT_VERSION_3_1_1; @@ -62,6 +61,7 @@ public class MqttIotHubConnection implements IotHubTransportConnection, MqttMess private final MqttMessaging deviceMessaging; private final MqttTwin deviceTwin; private final MqttDirectMethod directMethod; + private final MqttCertificateSigning certificateSigning; private final Map receivedMessagesToAcknowledge = new ConcurrentHashMap<>(); @@ -237,6 +237,12 @@ else if (proxySettings.getProxy().type() == Proxy.Type.HTTP) connectOptions, unacknowledgedSentMessages, receivedMessages); + + this.certificateSigning = new MqttCertificateSigning( + deviceId, + connectOptions, + unacknowledgedSentMessages, + receivedMessages); } /** @@ -367,6 +373,12 @@ else if (message.getMessageType() == DEVICE_TWIN) log.trace("Sending MQTT device twin message ({})", message); this.deviceTwin.send((IotHubTransportMessage) message); } + else if (message.getMessageType() == CERTIFICATE_SIGNING_REQUEST && message instanceof IotHubTransportMessage) + { + this.certificateSigning.start(); + log.trace("Sending MQTT certificate signing request message ({})", message); + this.certificateSigning.send((IotHubTransportMessage) message); + } else { log.trace("Sending MQTT device telemetry message ({})", message); @@ -455,10 +467,18 @@ public void onMessageArrived(int messageId) } else { - transportMessage = deviceMessaging.receive(); + transportMessage = certificateSigning.receive(); if (transportMessage != null) { - log.trace("Received MQTT device messaging message ({})", transportMessage); + log.trace("Received MQTT certificate signing message ({})", transportMessage); + } + else + { + transportMessage = deviceMessaging.receive(); + if (transportMessage != null) + { + log.trace("Received MQTT device messaging message ({})", transportMessage); + } } } } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/twin/DeviceOperations.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/twin/DeviceOperations.java index d74b548a40..2f2d8bad1c 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/twin/DeviceOperations.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/twin/DeviceOperations.java @@ -17,5 +17,6 @@ public enum DeviceOperations DEVICE_OPERATION_METHOD_SUBSCRIBE_RESPONSE, DEVICE_OPERATION_METHOD_RECEIVE_REQUEST, DEVICE_OPERATION_METHOD_SEND_RESPONSE, + DEVICE_OPERATION_CERTIFICATE_SIGNING_REQUEST, DEVICE_OPERATION_UNKNOWN } From 948360e784a12cd56ee7931b84854f67511fe044 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 23 Feb 2026 12:09:04 -0800 Subject: [PATCH 03/33] sample so far --- .../certificate-signing-sample/pom.xml | 45 +++++++++++++++++ .../azure/sdk/iot/CertificateSigning.java | 37 ++++++++++++++ .../sdk/iot/CertificateSigningRequest.java | 48 +++++++++++++++++++ .../src/main/resources/log4j2.properties | 13 +++++ iothub/device/iot-device-samples/pom.xml | 1 + 5 files changed, 144 insertions(+) create mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/pom.xml create mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigning.java create mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java create mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/src/main/resources/log4j2.properties diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml b/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml new file mode 100644 index 0000000000..2986e7a291 --- /dev/null +++ b/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml @@ -0,0 +1,45 @@ + + + + com.microsoft.azure.sdk.iot.samples + iot-device-samples + 1.0.0 + + 4.0.0 + com.microsoft.azure.sdk.iot.samples.device + certificate-signing-sample + Certificate signing sample + + + microsoft + Microsoft + + + + UTF-8 + + + + com.microsoft.azure.sdk.iot.provisioning + provisioning-device-client + ${provisioning-device-client-version} + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + true + samples.com.microsoft.azure.sdk.iot.CertificateSigning + + + + + + + \ No newline at end of file diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigning.java b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigning.java new file mode 100644 index 0000000000..6cbe970dac --- /dev/null +++ b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigning.java @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package samples.com.microsoft.azure.sdk.iot; + +import com.microsoft.azure.sdk.iot.device.*; +import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException; +import com.microsoft.azure.sdk.iot.provisioning.device.ProvisioningDeviceClient; +import com.microsoft.azure.sdk.iot.provisioning.device.ProvisioningDeviceClientTransportProtocol; + +import java.io.IOException; +import java.net.URISyntaxException; + + +/** Sends a number of event messages to an IoT Hub. */ +public class CertificateSigning +{ + private static String idScope = ""; + + public static void main(String[] args) + throws IOException, URISyntaxException, InterruptedException, IotHubClientException + { + // Certificate signing feature is currently only supported over MQTT/MQTT_WS + IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; + //IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT_WS; + + ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT; + //ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT_WS; + + + ProvisioningDeviceClient provisioningDeviceClient = new ProvisioningDeviceClient( + "global.azure-devices-provisioning.net", + idScope, + dpsProtocol, + securityProvider); + } +} \ No newline at end of file diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java new file mode 100644 index 0000000000..53b575dd0f --- /dev/null +++ b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java @@ -0,0 +1,48 @@ +package samples.com.microsoft.azure.sdk.iot; + +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; + +import javax.security.auth.x500.X500Principal; + +import sun.security.pkcs10.*; +import sun.security.x509.*; + +public class CertificateSigningRequest +{ + public final PublicKey publicKey; + public final PrivateKey privateKey; + public final byte[] encodedPKCS10; + /** + * @param algorithm "RSA" or "ECC" + * @param commonName The common name of the certificate signing request. For this sample's purposes, + * this value should equal the registration Id being used in DPS. + */ + private CertificateSigningRequest(String algorithm, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException + { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); + keyGen.initialize(2048, new SecureRandom()); + KeyPair keypair = keyGen.generateKeyPair(); + this.publicKey = keypair.getPublic(); + this.privateKey = keypair.getPrivate(); + this.encodedPKCS10 = generatePKCS10(commonName); + } + + public byte[] generatePKCS10(String CN) throws NoSuchAlgorithmException, InvalidKeyException, IOException, CertificateException, SignatureException + { + // generate PKCS10 certificate request + String sigAlg = "MD5WithRSA"; + PKCS10 pkcs10 = new PKCS10(publicKey); + Signature signature = Signature.getInstance(sigAlg); + signature.initSign(privateKey); + X500Principal principal = new X500Principal( "CN=" + CN); + + // pkcs10CertificationRequest kpGen = new PKCS10CertificationRequest(sigAlg, principal, publicKey, null, privateKey); + // byte[] c = kpGen.getEncoded(); + X500Name x500name; + x500name= new X500Name(principal.getEncoded()); + pkcs10.encodeAndSign(x500name, signature); + return pkcs10.getEncoded(); + } +} diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/resources/log4j2.properties b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/resources/log4j2.properties new file mode 100644 index 0000000000..4451929a10 --- /dev/null +++ b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/resources/log4j2.properties @@ -0,0 +1,13 @@ +status = error +name = Log4j2PropertiesConfig + +appenders = console + +appender.console.type = Console +appender.console.name = LogToConsole +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d %p (%t) [%c] - %m%n + +rootLogger.level = debug +rootLogger.appenderRefs = stdout +rootLogger.appenderRef.stdout.ref = LogToConsole \ No newline at end of file diff --git a/iothub/device/iot-device-samples/pom.xml b/iothub/device/iot-device-samples/pom.xml index 3565570ea2..1c474afa3b 100644 --- a/iothub/device/iot-device-samples/pom.xml +++ b/iothub/device/iot-device-samples/pom.xml @@ -36,6 +36,7 @@ device-reconnection-sample custom-sas-token-provider-sample unix-domain-socket-sample + certificate-signing-sample From 3f266d9047a00b7a06256b2458bd2763ee64f209 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 23 Feb 2026 17:17:27 -0800 Subject: [PATCH 04/33] sample so far --- .../certificate-signing-sample/pom.xml | 8 +- .../azure/sdk/iot/CertificateSigning.java | 37 ---- .../sdk/iot/CertificateSigningRequest.java | 47 +++-- .../com/microsoft/azure/sdk/iot/Main.java | 17 ++ .../certificate-signing-sample/pom.xml | 43 +++++ .../samples/CertificateSigningRequest.java | 62 +++++++ .../sdk/iot/provisioning/samples/Main.java | 135 +++++++++++++++ .../samples/SSLContextBuilder.java | 163 ++++++++++++++++++ .../pom.xml | 1 + 9 files changed, 459 insertions(+), 54 deletions(-) delete mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigning.java create mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/Main.java create mode 100644 provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml create mode 100644 provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java create mode 100644 provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java create mode 100644 provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml b/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml index 2986e7a291..d16d5c7e37 100644 --- a/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml +++ b/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml @@ -24,6 +24,12 @@ provisioning-device-client ${provisioning-device-client-version} + + com.microsoft.azure.sdk.iot.provisioning.security + x509-provider + 2.0.2 + compile + @@ -35,7 +41,7 @@ true - samples.com.microsoft.azure.sdk.iot.CertificateSigning + samples.com.microsoft.azure.sdk.iot.Main diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigning.java b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigning.java deleted file mode 100644 index 6cbe970dac..0000000000 --- a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigning.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -package samples.com.microsoft.azure.sdk.iot; - -import com.microsoft.azure.sdk.iot.device.*; -import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException; -import com.microsoft.azure.sdk.iot.provisioning.device.ProvisioningDeviceClient; -import com.microsoft.azure.sdk.iot.provisioning.device.ProvisioningDeviceClientTransportProtocol; - -import java.io.IOException; -import java.net.URISyntaxException; - - -/** Sends a number of event messages to an IoT Hub. */ -public class CertificateSigning -{ - private static String idScope = ""; - - public static void main(String[] args) - throws IOException, URISyntaxException, InterruptedException, IotHubClientException - { - // Certificate signing feature is currently only supported over MQTT/MQTT_WS - IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; - //IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT_WS; - - ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT; - //ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT_WS; - - - ProvisioningDeviceClient provisioningDeviceClient = new ProvisioningDeviceClient( - "global.azure-devices-provisioning.net", - idScope, - dpsProtocol, - securityProvider); - } -} \ No newline at end of file diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java index 53b575dd0f..415223bf3e 100644 --- a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java +++ b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java @@ -3,6 +3,9 @@ import java.io.IOException; import java.security.*; import java.security.cert.CertificateException; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.Base64; import javax.security.auth.x500.X500Principal; @@ -14,35 +17,47 @@ public class CertificateSigningRequest public final PublicKey publicKey; public final PrivateKey privateKey; public final byte[] encodedPKCS10; + public final String base64EncodedPKCS10; + /** - * @param algorithm "RSA" or "ECC" + * @param algorithm "RSA" or "ECDSA" * @param commonName The common name of the certificate signing request. For this sample's purposes, * this value should equal the registration Id being used in DPS. */ - private CertificateSigningRequest(String algorithm, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException + public CertificateSigningRequest(String algorithm, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, InvalidAlgorithmParameterException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); - keyGen.initialize(2048, new SecureRandom()); + Signature signature; + if (algorithm.equalsIgnoreCase("RSA")) + { + signature = Signature.getInstance("SHA256withRSA"); + keyGen.initialize(new RSAKeyGenParameterSpec(4096, RSAKeyGenParameterSpec.F4)); + } + else if (algorithm.equalsIgnoreCase("ECC")) + { + signature = Signature.getInstance("SHA256withECDSA"); + keyGen.initialize(new ECGenParameterSpec("prime256v1")); + } + else + { + throw new IllegalArgumentException("Unrecognized encryption algorithm: " + algorithm); + } + KeyPair keypair = keyGen.generateKeyPair(); this.publicKey = keypair.getPublic(); this.privateKey = keypair.getPrivate(); - this.encodedPKCS10 = generatePKCS10(commonName); - } - public byte[] generatePKCS10(String CN) throws NoSuchAlgorithmException, InvalidKeyException, IOException, CertificateException, SignatureException - { // generate PKCS10 certificate request - String sigAlg = "MD5WithRSA"; - PKCS10 pkcs10 = new PKCS10(publicKey); - Signature signature = Signature.getInstance(sigAlg); - signature.initSign(privateKey); - X500Principal principal = new X500Principal( "CN=" + CN); - - // pkcs10CertificationRequest kpGen = new PKCS10CertificationRequest(sigAlg, principal, publicKey, null, privateKey); - // byte[] c = kpGen.getEncoded(); + PKCS10 pkcs10 = new PKCS10(this.publicKey); + + signature.initSign(this.privateKey); + X500Principal principal = new X500Principal( "CN=" + commonName); X500Name x500name; x500name= new X500Name(principal.getEncoded()); pkcs10.encodeAndSign(x500name, signature); - return pkcs10.getEncoded(); + signature.initSign(this.privateKey); + + this.encodedPKCS10 = pkcs10.getEncoded(); + this.base64EncodedPKCS10 = Base64.getEncoder().encodeToString(this.encodedPKCS10); } } diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/Main.java b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/Main.java new file mode 100644 index 0000000000..e31d63e9f7 --- /dev/null +++ b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/Main.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package samples.com.microsoft.azure.sdk.iot; + +public class Main +{ + +} + +private class DeviceCredentials +{ + public String AssignedHub; + public String DeviceId; + public String CertificatePath; + public String KeyPath; +} \ No newline at end of file diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml new file mode 100644 index 0000000000..4bea1c4626 --- /dev/null +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + com.microsoft.azure.sdk.iot.provisioning.samples + provisioning-device-client-samples + 1.0.0 + + + certificate-signing-sample + + + 11 + 11 + UTF-8 + + + + com.microsoft.azure.sdk.iot.provisioning.security + ${security-provider-artifact-id} + ${security-provider-version} + + + com.microsoft.azure.sdk.iot.provisioning + ${provisioning-device-client-artifact-id} + ${provisioning-device-client-version} + + + com.microsoft.azure.sdk.iot + ${iot-device-client-artifact-id} + ${iot-device-client-version} + + + com.microsoft.azure.sdk.iot.provisioning.security + x509-provider + 2.0.2 + compile + + + + \ No newline at end of file diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java new file mode 100644 index 0000000000..7426529f1b --- /dev/null +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java @@ -0,0 +1,62 @@ +package com.microsoft.azure.sdk.iot.provisioning.samples; + +import sun.security.pkcs10.PKCS10; +import sun.security.x509.X500Name; + +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.Base64; + +public class CertificateSigningRequest +{ + public final PublicKey publicKey; + public final PrivateKey privateKey; + public final byte[] encodedPKCS10; + public final String base64EncodedPKCS10; + + /** + * @param algorithm "RSA" or "ECDSA" + * @param commonName The common name of the certificate signing request. For this sample's purposes, + * this value should equal the registration Id being used in DPS. + */ + public CertificateSigningRequest(String algorithm, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, InvalidAlgorithmParameterException + { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); + Signature signature; + if (algorithm.equalsIgnoreCase("RSA")) + { + signature = Signature.getInstance("SHA256withRSA"); + keyGen.initialize(new RSAKeyGenParameterSpec(4096, RSAKeyGenParameterSpec.F4)); + } + else if (algorithm.equalsIgnoreCase("ECC")) + { + signature = Signature.getInstance("SHA256withECDSA"); + keyGen.initialize(new ECGenParameterSpec("prime256v1")); + } + else + { + throw new IllegalArgumentException("Unrecognized encryption algorithm: " + algorithm); + } + + KeyPair keypair = keyGen.generateKeyPair(); + this.publicKey = keypair.getPublic(); + this.privateKey = keypair.getPrivate(); + + // generate PKCS10 certificate request + PKCS10 pkcs10 = new PKCS10(this.publicKey); + + signature.initSign(this.privateKey); + X500Principal principal = new X500Principal( "CN=" + commonName); + X500Name x500name; + x500name= new X500Name(principal.getEncoded()); + pkcs10.encodeAndSign(x500name, signature); + signature.initSign(this.privateKey); + + this.encodedPKCS10 = pkcs10.getEncoded(); + this.base64EncodedPKCS10 = Base64.getEncoder().encodeToString(this.encodedPKCS10); + } +} diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java new file mode 100644 index 0000000000..61a248cb16 --- /dev/null +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -0,0 +1,135 @@ +package com.microsoft.azure.sdk.iot.provisioning.samples; + +import com.microsoft.azure.sdk.iot.device.ClientOptions; +import com.microsoft.azure.sdk.iot.device.DeviceClient; +import com.microsoft.azure.sdk.iot.device.IotHubClientProtocol; +import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException; +import com.microsoft.azure.sdk.iot.provisioning.device.*; +import com.microsoft.azure.sdk.iot.provisioning.device.internal.exceptions.ProvisioningDeviceClientException; +import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProvider; +import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProviderSymmetricKey; +import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProviderX509; +import com.microsoft.azure.sdk.iot.provisioning.security.hsm.SecurityProviderX509Cert; + +import javax.net.ssl.SSLContext; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.util.Base64; +import java.util.List; + +public class Main +{ + private static String idScope = ""; + private static String registrationId = "<>"; + private static String savedCertificatesPath = "<>"; + + public static void main(String[] args) + throws IOException, URISyntaxException, InterruptedException, IotHubClientException, GeneralSecurityException, ProvisioningDeviceClientException, ProvisioningDeviceClientException + { + // Certificate signing feature is currently only supported over MQTT/MQTT_WS + IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; + //IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT_WS; + + ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT; + //ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT_WS; + + CertificateSigningRequest certificateSigningRequest = + //new CertificateSigningRequest("RSA", registrationId); + new CertificateSigningRequest("ECDSA", registrationId); + + String privateKeyPem = getPrivateKeyString(certificateSigningRequest.privateKey); + WriteToFile(savedCertificatesPath, "privateKey.pem", privateKeyPem); + + SecurityProvider securityProvider = CreateSecurityProviderX509(); + //SecurityProvider securityProvider = CreateSecurityProviderSymmetricKey(); + + ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.create( + "global.azure-devices-provisioning.net", + idScope, + dpsProtocol, + securityProvider); + + AdditionalData provisioningAdditionalData = new AdditionalData(); + provisioningAdditionalData.setClientCertificateSigningRequest(certificateSigningRequest.base64EncodedPKCS10); + ProvisioningDeviceClientRegistrationResult provisioningResult = provisioningDeviceClient.registerDeviceSync(provisioningAdditionalData); + + if (provisioningResult.getProvisioningDeviceClientStatus() != ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED) + { + System.out.println("Provisioning failed with status: " + provisioningResult.getProvisioningDeviceClientStatus()); + return; + } + + if (provisioningResult.getIssuedClientCertificateChain() == null + || provisioningResult.getIssuedClientCertificateChain().isEmpty()) + { + System.out.println("Provisioning did not yield any issued client certificates. Did you include the certificate signing request in the provisioning request?"); + return; + } + + String issuedClientCertificatesPem = ConvertToPem(provisioningResult.getIssuedClientCertificateChain()); + WriteToFile(savedCertificatesPath, "clientCertificates.pem", issuedClientCertificatesPem); + + String leafCertificatePem = ConvertToPem(provisioningResult.getIssuedClientCertificateChain().get(0)); + + SSLContext deviceClientSslContext = SSLContextBuilder.buildSSLContext(leafCertificatePem, privateKeyPem); + + ClientOptions clientOptions = ClientOptions.builder().sslContext(deviceClientSslContext).build(); + String derivedConnectionString = String.format("HostName=%s;DeviceId=%;x509=true", provisioningResult.getIothubUri(), provisioningResult.getDeviceId()); + DeviceClient client = new DeviceClient(derivedConnectionString, iotHubProtocol, clientOptions); + + } + + private static SecurityProviderX509 CreateSecurityProviderX509() + { + return new SecurityProviderX509Cert(todo); + } + + private static SecurityProviderSymmetricKey CreateSecurityProviderSymmetricKey() + { + return new SecurityProviderSymmetricKey(todo); + } + + private static String getPrivateKeyString(PrivateKey privateKey) throws IOException + { + StringBuilder privateKeyStringBuilder = new StringBuilder(); + privateKeyStringBuilder.append("-----BEGIN PRIVATE KEY-----"); + String privateKeyBase64Encoded = Base64.getEncoder().encodeToString(privateKey.getEncoded()); + privateKeyStringBuilder.append(privateKeyBase64Encoded); + privateKeyStringBuilder.append("-----END PRIVATE KEY-----"); + return privateKeyStringBuilder.toString(); + } + + private static String ConvertToPem(List issuedClientCertificates) + { + StringBuilder pemBuilder = new StringBuilder(); + for (String issuedClientCertificate : issuedClientCertificates) + { + pemBuilder.append("-----BEGIN CERTIFICATE-----"); + pemBuilder.append(issuedClientCertificate); + pemBuilder.append("-----END CERTIFICATE-----"); + } + + return pemBuilder.toString(); + } + + private static String ConvertToPem(String issuedLeafCertificate) + { + StringBuilder pemBuilder = new StringBuilder(); + pemBuilder.append("-----BEGIN CERTIFICATE-----"); + pemBuilder.append(issuedLeafCertificate); + pemBuilder.append("-----END CERTIFICATE-----"); + + return pemBuilder.toString(); + } + + private static void WriteToFile(String path, String filename, String contents) throws IOException + { + BufferedWriter writer = new BufferedWriter(new FileWriter(path + "/" + filename)); + writer.write(contents); + writer.close(); + } +} \ No newline at end of file diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java new file mode 100644 index 0000000000..9e879d736e --- /dev/null +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE file in the project root for full license information. + */ + +package com.microsoft.azure.sdk.iot.provisioning.samples; + +import org.apache.commons.codec.binary.Base64; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Helper class that demonstrates how to build an SSLContext for x509 authentication from your public and private certificates, + * or how to build an SSLContext for SAS authentication from the default IoT Hub public certificates + */ +public class SSLContextBuilder +{ + private static final String SSL_CONTEXT_INSTANCE = "TLSv1.2"; + private static final String CERTIFICATE_TYPE = "X.509"; + private static final String PRIVATE_KEY_ALGORITHM = "RSA"; //TODO ECC support + + private static final String CERTIFICATE_ALIAS = "cert-alias"; + private static final String PRIVATE_KEY_ALIAS = "key-alias"; + + /** + * Create an SSLContext instance with the provided public certificate and private key that also trusts the public + * certificates loaded in your device's trusted root certification authorities certificate store. + * @param publicKeyCertificate the public key to use for x509 authentication. Does not need to include the + * Iot Hub trusted certificate as it will be added automatically as long as it is + * in your device's trusted root certification authorities certificate store. + * @param privateKey The private key to use for x509 authentication + * @return The created SSLContext that uses the provided public key and private key + * @throws GeneralSecurityException If the certificate creation fails, or if the SSLContext creation using those certificates fails. + * @throws IOException If the certificates cannot be read. + */ + public static SSLContext buildSSLContext(X509Certificate publicKeyCertificate, PrivateKey privateKey) throws GeneralSecurityException, IOException + { + char[] temporaryPassword = generateTemporaryPassword(); + + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(null); + keystore.setCertificateEntry(CERTIFICATE_ALIAS, publicKeyCertificate); + keystore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, temporaryPassword, new Certificate[] {publicKeyCertificate}); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keystore, temporaryPassword); + + SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE); + + // By leaving the TrustManager array null, the SSLContext will trust the certificates stored on your device's + // trusted root certification authorities certificate store. + // + // This must include the Baltimore CyberTrust Root public certificate: https://baltimore-cybertrust-root.chain-demos.digicert.com/info/index.html + // and eventually it will need to include the DigiCert Global Root G2 public certificate: https://global-root-g2.chain-demos.digicert.com/info/index.html + sslContext.init(kmf.getKeyManagers(), null, new SecureRandom()); + + return sslContext; + } + + /** + * Create an SSLContext instance with the provided public certificate and private key that also trusts the public + * certificates loaded in your device's trusted root certification authorities certificate store. + * @param publicKeyCertificateString the public key to use for x509 authentication. Does not need to include the + * Iot Hub trusted certificate as it will be added automatically as long as it is + * in your device's trusted root certification authorities certificate store. + * @param privateKeyString The private key to use for x509 authentication + * @return The created SSLContext that uses the provided public key and private key + * @throws GeneralSecurityException If the certificate creation fails, or if the SSLContext creation using those certificates fails. + * @throws IOException If the certificates cannot be read. + */ + public static SSLContext buildSSLContext(String publicKeyCertificateString, String privateKeyString) throws GeneralSecurityException, IOException + { + Key privateKey = parsePrivateKeyString(privateKeyString); + Certificate[] publicKeyCertificates = parsePublicCertificateString(publicKeyCertificateString); + + char[] temporaryPassword = generateTemporaryPassword(); + + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(null); + keystore.setCertificateEntry(CERTIFICATE_ALIAS, publicKeyCertificates[0]); + keystore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, temporaryPassword, publicKeyCertificates); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keystore, temporaryPassword); + + SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE); + + // By leaving the TrustManager array null, the SSLContext will trust the certificates stored on your device's + // trusted root certification authorities certificate store. + // + // This must include the Baltimore CyberTrust Root public certificate: https://baltimore-cybertrust-root.chain-demos.digicert.com/info/index.html + // and eventually it will need to include the DigiCert Global Root G2 public certificate: https://global-root-g2.chain-demos.digicert.com/info/index.html + sslContext.init(kmf.getKeyManagers(), null, new SecureRandom()); + + return sslContext; + } + + private static RSAPrivateKey parsePrivateKeyString(String privateKeyPEM) throws GeneralSecurityException + { + if (privateKeyPEM == null || privateKeyPEM.isEmpty()) + { + throw new IllegalArgumentException("Public key certificate cannot be null or empty"); + } + + privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", ""); + privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", ""); + byte[] encoded = Base64.decodeBase64(privateKeyPEM.getBytes(StandardCharsets.UTF_8)); + KeyFactory kf = KeyFactory.getInstance(PRIVATE_KEY_ALGORITHM); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return (RSAPrivateKey) kf.generatePrivate(keySpec); + } + + private static X509Certificate[] parsePublicCertificateString(String pemString) throws GeneralSecurityException, IOException + { + if (pemString == null || pemString.isEmpty()) + { + throw new IllegalArgumentException("Public key certificate cannot be null or empty"); + } + + try (InputStream pemInputStream = new ByteArrayInputStream(pemString.getBytes(StandardCharsets.UTF_8))) + { + CertificateFactory cf = CertificateFactory.getInstance(CERTIFICATE_TYPE); + Collection collection = new ArrayList<>(); + X509Certificate x509Cert; + + while (pemInputStream.available() > 0) + { + x509Cert = (X509Certificate) cf.generateCertificate(pemInputStream); + collection.add(x509Cert); + } + + return collection.toArray(new X509Certificate[0]); + } + } + + private static char[] generateTemporaryPassword() + { + char[] randomChars = new char[256]; + SecureRandom secureRandom = new SecureRandom(); + + for (int i = 0; i < 256; i++) + { + // character will be between 97 and 122 on the ASCII table. This forces it to be a lower case character. + // that ensures that the password, as a whole, is alphanumeric + randomChars[i] = (char) (97 + secureRandom.nextInt(26)); + } + + return randomChars; + } +} diff --git a/provisioning/provisioning-device-client-samples/pom.xml b/provisioning/provisioning-device-client-samples/pom.xml index fe3fb95517..38a4eaf51c 100644 --- a/provisioning/provisioning-device-client-samples/pom.xml +++ b/provisioning/provisioning-device-client-samples/pom.xml @@ -56,6 +56,7 @@ provisioning-symmetrickey-individual-sample provisioning-tpm-sample provisioning-X509-sample + certificate-signing-sample From 3a716013ec7567f22341db658582236235f4fcb3 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 24 Feb 2026 09:57:37 -0800 Subject: [PATCH 05/33] swap from callback to future? --- .../azure/sdk/iot/device/DeviceClient.java | 4 +- ...ava => IotHubCertificateSigningError.java} | 16 ++--- ...=> IotHubCertificateSigningErrorCode.java} | 4 +- ...=> IotHubCertificateSigningErrorInfo.java} | 10 +-- ...a => IotHubCertificateSigningRequest.java} | 16 ++--- ...HubCertificateSigningRequestAccepted.java} | 12 ++-- ... => IotHubCertificateSigningResponse.java} | 16 ++--- ...ubCertificateSigningResponseCallback.java} | 12 ++-- .../transport/IotHubTransportMessage.java | 2 +- .../mqtt/MqttCertificateSigning.java | 18 ++--- .../certificate-signing-sample/pom.xml | 5 ++ .../samples/CertificateSigningRequest.java | 53 +++------------ .../CertificateSigningRequestGenerator.java | 66 +++++++++++++++++++ .../sdk/iot/provisioning/samples/Main.java | 47 ++++++++++--- 14 files changed, 166 insertions(+), 115 deletions(-) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{CertificateSigningError.java => IotHubCertificateSigningError.java} (77%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{CertificateSigningErrorCode.java => IotHubCertificateSigningErrorCode.java} (92%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{CertificateSigningErrorInfo.java => IotHubCertificateSigningErrorInfo.java} (86%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{CertificateSigningRequest.java => IotHubCertificateSigningRequest.java} (81%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{CertificateSigningRequestAccepted.java => IotHubCertificateSigningRequestAccepted.java} (78%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{CertificateSigningResponse.java => IotHubCertificateSigningResponse.java} (76%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{CertificateSigningResponseCallback.java => IotHubCertificateSigningResponseCallback.java} (50%) create mode 100644 provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index f6a5c019de..8be3ec2837 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -275,14 +275,14 @@ public boolean isMultiplexed() } // TODO docs, timeouts? - public void sendCertificateSigningRequest(CertificateSigningRequest request, CertificateSigningResponseCallback callback) + public void sendCertificateSigningRequest(IotHubCertificateSigningRequest request, IotHubCertificateSigningResponseCallback callback) { // This one message signals to lower layers to both subscribe to MQTT response topic (if not already subscribed) // and to send the CSR. This is a bit different from how methods/twins work but vastly simplifies the user // experience here (compared to having a separate method for subscribing to CSR response topic). IotHubTransportMessage certificateSigningRequest = new IotHubTransportMessage(request.toJson()); certificateSigningRequest.setDeviceOperationType(DeviceOperations.DEVICE_OPERATION_CERTIFICATE_SIGNING_REQUEST); - certificateSigningRequest.setCertificateSigningResponseCallback(callback); + certificateSigningRequest.setIotHubCertificateSigningResponseCallback(callback); certificateSigningRequest.setMessageType(MessageType.CERTIFICATE_SIGNING_REQUEST); certificateSigningRequest.setRequestId(UUID.randomUUID().toString()); diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningError.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningError.java similarity index 77% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningError.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningError.java index 7c08c6416b..194f855d7e 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningError.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningError.java @@ -9,7 +9,7 @@ import java.util.Date; -public class CertificateSigningError +public class IotHubCertificateSigningError { /* Example: { @@ -28,7 +28,7 @@ public class CertificateSigningError private String errorCodeString; @Getter - private transient CertificateSigningErrorCode errorCode; + private transient IotHubCertificateSigningErrorCode errorCode; @SerializedName("message") @Getter @@ -46,17 +46,17 @@ public class CertificateSigningError @SerializedName("info") @Getter - private CertificateSigningErrorInfo info; + private IotHubCertificateSigningErrorInfo info; - public CertificateSigningError(String json) throws IllegalArgumentException + public IotHubCertificateSigningError(String json) throws IllegalArgumentException { Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); - CertificateSigningError deserialized; + IotHubCertificateSigningError deserialized; ParserUtility.validateStringUTF8(json); try { - deserialized = gson.fromJson(json, CertificateSigningError.class); + deserialized = gson.fromJson(json, IotHubCertificateSigningError.class); } catch (JsonSyntaxException malformed) { @@ -64,7 +64,7 @@ public CertificateSigningError(String json) throws IllegalArgumentException } this.errorCodeString = deserialized.errorCodeString; - this.errorCode = CertificateSigningErrorCode.GetValue(Integer.parseInt(this.errorCodeString)); + this.errorCode = IotHubCertificateSigningErrorCode.GetValue(Integer.parseInt(this.errorCodeString)); this.trackingId = deserialized.trackingId; this.message = deserialized.message; this.timestampUtcString = deserialized.timestampUtcString; @@ -73,7 +73,7 @@ public CertificateSigningError(String json) throws IllegalArgumentException } @SuppressWarnings("unused") // used by gson - CertificateSigningError() + IotHubCertificateSigningError() { } } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorCode.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorCode.java similarity index 92% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorCode.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorCode.java index b2c6f4b243..92c54a8479 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorCode.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorCode.java @@ -1,6 +1,6 @@ package com.microsoft.azure.sdk.iot.device; -public enum CertificateSigningErrorCode +public enum IotHubCertificateSigningErrorCode { InvalidProtocolVersion, OperationNotAvailableInCurrentTier, @@ -17,7 +17,7 @@ public enum CertificateSigningErrorCode ServiceUnavailable, Unknown; - public static CertificateSigningErrorCode GetValue(int code) + public static IotHubCertificateSigningErrorCode GetValue(int code) { switch (code) { diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorInfo.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorInfo.java similarity index 86% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorInfo.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorInfo.java index 350d41c549..086e6d7c2c 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningErrorInfo.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorInfo.java @@ -9,7 +9,7 @@ import java.util.Date; -public class CertificateSigningErrorInfo { +public class IotHubCertificateSigningErrorInfo { /* Example: { "correlationId": "8819e8d8-1324-4a9c-acde-ce0318e93f31", @@ -45,15 +45,15 @@ public class CertificateSigningErrorInfo { @Getter private transient Date operationExpires; - public CertificateSigningErrorInfo(String json) throws IllegalArgumentException + public IotHubCertificateSigningErrorInfo(String json) throws IllegalArgumentException { Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); - CertificateSigningErrorInfo deserialized; + IotHubCertificateSigningErrorInfo deserialized; ParserUtility.validateStringUTF8(json); try { - deserialized = gson.fromJson(json, CertificateSigningErrorInfo.class); + deserialized = gson.fromJson(json, IotHubCertificateSigningErrorInfo.class); } catch (JsonSyntaxException malformed) { @@ -69,7 +69,7 @@ public CertificateSigningErrorInfo(String json) throws IllegalArgumentException } @SuppressWarnings("unused") // used by gson - CertificateSigningErrorInfo() + IotHubCertificateSigningErrorInfo() { } } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequest.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequest.java similarity index 81% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequest.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequest.java index a0709e9ae0..607fc63044 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequest.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequest.java @@ -5,15 +5,9 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; -import lombok.Setter; -/** - * The request payload to send to IoT Hub to notify it when a file upload is completed, whether successful or not. - * Must set whether the file upload was a success or not, and must set the correlation Id, but all other fields are optional. - */ -public class CertificateSigningRequest +public class IotHubCertificateSigningRequest { /** * Required. The device ID the certificate will be issued for. @@ -39,23 +33,21 @@ public class CertificateSigningRequest private String replace = null; /** - * * @param id The device ID the certificate will be issued for. Must match the device Id of the device that will send this request. * @param certificateSigningRequestData The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. */ - public CertificateSigningRequest(String id, String certificateSigningRequestData) + public IotHubCertificateSigningRequest(String id, String certificateSigningRequestData) { this(id, certificateSigningRequestData, null); } /** - * * @param id The device ID the certificate will be issued for. Must match the device Id of the device that will send this request. * @param certificateSigningRequestData The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. * @param replace the request ID to replace, or "*" to replace any active request. For use if a * previous certificate signing request has failed and you want to start over. */ - public CertificateSigningRequest(String id, String certificateSigningRequestData, String replace) + public IotHubCertificateSigningRequest(String id, String certificateSigningRequestData, String replace) { if (id == null || id.isEmpty()) { @@ -79,7 +71,7 @@ String toJson() } @SuppressWarnings("unused") // used by gson - CertificateSigningRequest() + IotHubCertificateSigningRequest() { } } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequestAccepted.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequestAccepted.java similarity index 78% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequestAccepted.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequestAccepted.java index 1218a5ade2..23f7b23c0a 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningRequestAccepted.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequestAccepted.java @@ -6,18 +6,16 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; -import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import com.microsoft.azure.sdk.iot.device.twin.ParserUtility; import lombok.Getter; import java.util.Date; -import java.util.List; /** * The information provided from IoT Hub that can be used with the Azure Storage SDK to upload a file from your device, including authentication. */ -public class CertificateSigningRequestAccepted +public class IotHubCertificateSigningRequestAccepted { @SerializedName("correlationId") @Getter @@ -29,15 +27,15 @@ public class CertificateSigningRequestAccepted @Getter private transient Date operationExpires; - public CertificateSigningRequestAccepted(String json) throws IllegalArgumentException + public IotHubCertificateSigningRequestAccepted(String json) throws IllegalArgumentException { Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); - CertificateSigningRequestAccepted deserialized; + IotHubCertificateSigningRequestAccepted deserialized; ParserUtility.validateStringUTF8(json); try { - deserialized = gson.fromJson(json, CertificateSigningRequestAccepted.class); + deserialized = gson.fromJson(json, IotHubCertificateSigningRequestAccepted.class); } catch (JsonSyntaxException malformed) { @@ -49,7 +47,7 @@ public CertificateSigningRequestAccepted(String json) throws IllegalArgumentExce } @SuppressWarnings("unused") // used by gson - CertificateSigningRequestAccepted() + IotHubCertificateSigningRequestAccepted() { } } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponse.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponse.java similarity index 76% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponse.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponse.java index c4c11e9495..f5f376fb0f 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponse.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponse.java @@ -6,22 +6,16 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; -import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import com.microsoft.azure.sdk.iot.device.twin.ParserUtility; import lombok.Getter; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.List; /** * The information provided from IoT Hub that can be used with the Azure Storage SDK to upload a file from your device, including authentication. */ -public class CertificateSigningResponse +public class IotHubCertificateSigningResponse { /** *

@@ -42,15 +36,15 @@ public class CertificateSigningResponse @Getter private List correlationId; - public CertificateSigningResponse(String json) throws IllegalArgumentException + public IotHubCertificateSigningResponse(String json) throws IllegalArgumentException { Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); - CertificateSigningResponse deserialized; + IotHubCertificateSigningResponse deserialized; ParserUtility.validateStringUTF8(json); try { - deserialized = gson.fromJson(json, CertificateSigningResponse.class); + deserialized = gson.fromJson(json, IotHubCertificateSigningResponse.class); } catch (JsonSyntaxException malformed) { @@ -62,7 +56,7 @@ public CertificateSigningResponse(String json) throws IllegalArgumentException } @SuppressWarnings("unused") // used by gson - CertificateSigningResponse() + IotHubCertificateSigningResponse() { } } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponseCallback.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponseCallback.java similarity index 50% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponseCallback.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponseCallback.java index f4bec6c0ef..a5f0f9daa3 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/CertificateSigningResponseCallback.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponseCallback.java @@ -1,6 +1,6 @@ package com.microsoft.azure.sdk.iot.device; -public interface CertificateSigningResponseCallback +public interface IotHubCertificateSigningResponseCallback { /** *

@@ -8,16 +8,16 @@ public interface CertificateSigningResponseCallback *

*

* When this executes, it signals that IoT hub has begun processing the certificate signing request and - * will notify this client again once that request has been completed via {@link #onCertificateSigningComplete(CertificateSigningResponse)} ()} + * will notify this client again once that request has been completed via {@link #onCertificateSigningComplete(IotHubCertificateSigningResponse)} ()} *

*

- * If the certificate signing request is not accetepted or fails for any reason, {@link #onCertificateSigningError(CertificateSigningError)} ()} + * If the certificate signing request is not accetepted or fails for any reason, {@link #onCertificateSigningError(IotHubCertificateSigningError)} ()} * will execute instead of this callback. *

*/ - public void onCertificateSigningRequestAccepted(CertificateSigningRequestAccepted accepted); + public void onCertificateSigningRequestAccepted(IotHubCertificateSigningRequestAccepted accepted); - public void onCertificateSigningComplete(CertificateSigningResponse response); + public void onCertificateSigningComplete(IotHubCertificateSigningResponse response); - public void onCertificateSigningError(CertificateSigningError error); + public void onCertificateSigningError(IotHubCertificateSigningError error); } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java index 8606e7af2a..c226e79962 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java @@ -38,7 +38,7 @@ public class IotHubTransportMessage extends Message @Getter @Setter - private CertificateSigningResponseCallback certificateSigningResponseCallback; + private IotHubCertificateSigningResponseCallback iotHubCertificateSigningResponseCallback; /** * Constructor with binary data and message type diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java index fac3ab0d75..0f15bda262 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java @@ -22,7 +22,7 @@ class MqttCertificateSigning extends Mqtt { private final String certificateSigningResponseTopic = "$iothub/credentials/res/#"; private boolean isStarted = false; - private Map inProgressRequestIdMap = new HashMap<>(); + private Map inProgressRequestIdMap = new HashMap<>(); private static final String REQ_ID = "?$rid="; public MqttCertificateSigning( @@ -56,7 +56,7 @@ public void stop() public void send(IotHubTransportMessage message) throws TransportException { - CertificateSigningResponseCallback signingCallback = message.getCertificateSigningResponseCallback(); + IotHubCertificateSigningResponseCallback signingCallback = message.getIotHubCertificateSigningResponseCallback(); // Service will ack this immediately, then later publish a message to the response topic this.publish("$iothub/credentials/POST/issueCertificate/?$rid=" + message.getRequestId(), message); @@ -96,14 +96,14 @@ public IotHubTransportMessage receive() String requestId = getRequestId(topicTokens[5]); if (this.inProgressRequestIdMap.containsKey(requestId)) { - CertificateSigningResponseCallback certificateSigningResponseCallback = this.inProgressRequestIdMap.get(requestId); + IotHubCertificateSigningResponseCallback iotHubCertificateSigningResponseCallback = this.inProgressRequestIdMap.get(requestId); if (status.equals("202")) { try { - CertificateSigningRequestAccepted accepted = new CertificateSigningRequestAccepted(new String(payload, StandardCharsets.UTF_8)); - certificateSigningResponseCallback.onCertificateSigningRequestAccepted(accepted); + IotHubCertificateSigningRequestAccepted accepted = new IotHubCertificateSigningRequestAccepted(new String(payload, StandardCharsets.UTF_8)); + iotHubCertificateSigningResponseCallback.onCertificateSigningRequestAccepted(accepted); } catch (IllegalArgumentException e) { @@ -114,8 +114,8 @@ else if (status.equals("200")) { try { - CertificateSigningResponse response = new CertificateSigningResponse(new String(payload, StandardCharsets.UTF_8)); - certificateSigningResponseCallback.onCertificateSigningComplete(response); + IotHubCertificateSigningResponse response = new IotHubCertificateSigningResponse(new String(payload, StandardCharsets.UTF_8)); + iotHubCertificateSigningResponseCallback.onCertificateSigningComplete(response); } catch (IllegalArgumentException e) { @@ -126,8 +126,8 @@ else if (status.equals("200")) { try { - CertificateSigningError error = new CertificateSigningError(new String(payload, StandardCharsets.UTF_8)); - certificateSigningResponseCallback.onCertificateSigningError(error); + IotHubCertificateSigningError error = new IotHubCertificateSigningError(new String(payload, StandardCharsets.UTF_8)); + iotHubCertificateSigningResponseCallback.onCertificateSigningError(error); } catch (IllegalArgumentException e) { diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml index 4bea1c4626..2587735bc2 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml @@ -38,6 +38,11 @@ 2.0.2 compile + + org.projectlombok + lombok + provided + \ No newline at end of file diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java index 7426529f1b..c5ecc6643b 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java @@ -1,5 +1,7 @@ package com.microsoft.azure.sdk.iot.provisioning.samples; +import lombok.AllArgsConstructor; +import lombok.Getter; import sun.security.pkcs10.PKCS10; import sun.security.x509.X500Name; @@ -11,52 +13,15 @@ import java.security.spec.RSAKeyGenParameterSpec; import java.util.Base64; +@AllArgsConstructor public class CertificateSigningRequest { - public final PublicKey publicKey; - public final PrivateKey privateKey; - public final byte[] encodedPKCS10; - public final String base64EncodedPKCS10; + @Getter + private final PublicKey publicKey; - /** - * @param algorithm "RSA" or "ECDSA" - * @param commonName The common name of the certificate signing request. For this sample's purposes, - * this value should equal the registration Id being used in DPS. - */ - public CertificateSigningRequest(String algorithm, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, InvalidAlgorithmParameterException - { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); - Signature signature; - if (algorithm.equalsIgnoreCase("RSA")) - { - signature = Signature.getInstance("SHA256withRSA"); - keyGen.initialize(new RSAKeyGenParameterSpec(4096, RSAKeyGenParameterSpec.F4)); - } - else if (algorithm.equalsIgnoreCase("ECC")) - { - signature = Signature.getInstance("SHA256withECDSA"); - keyGen.initialize(new ECGenParameterSpec("prime256v1")); - } - else - { - throw new IllegalArgumentException("Unrecognized encryption algorithm: " + algorithm); - } + @Getter + private final PrivateKey privateKey; - KeyPair keypair = keyGen.generateKeyPair(); - this.publicKey = keypair.getPublic(); - this.privateKey = keypair.getPrivate(); - - // generate PKCS10 certificate request - PKCS10 pkcs10 = new PKCS10(this.publicKey); - - signature.initSign(this.privateKey); - X500Principal principal = new X500Principal( "CN=" + commonName); - X500Name x500name; - x500name= new X500Name(principal.getEncoded()); - pkcs10.encodeAndSign(x500name, signature); - signature.initSign(this.privateKey); - - this.encodedPKCS10 = pkcs10.getEncoded(); - this.base64EncodedPKCS10 = Base64.getEncoder().encodeToString(this.encodedPKCS10); - } + @Getter + private final String base64EncodedPKCS10; } diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java new file mode 100644 index 0000000000..e8c74e612e --- /dev/null +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java @@ -0,0 +1,66 @@ +package com.microsoft.azure.sdk.iot.provisioning.samples; + +import sun.security.pkcs10.PKCS10; +import sun.security.x509.X500Name; + +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.Base64; + +public class CertificateSigningRequestGenerator +{ + private final Signature signature; + private final KeyPairGenerator keyGen; + private final String commonName; + + /** + * @param algorithm "RSA" or "ECDSA" + * @param commonName The common name of the certificate signing request. For this sample's purposes, + * this value should equal the registration Id being used in DPS. + */ + public CertificateSigningRequestGenerator(String algorithm, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, InvalidAlgorithmParameterException + { + this.keyGen = KeyPairGenerator.getInstance(algorithm); + if (algorithm.equalsIgnoreCase("RSA")) + { + this.signature = Signature.getInstance("SHA256withRSA"); + this.keyGen.initialize(new RSAKeyGenParameterSpec(4096, RSAKeyGenParameterSpec.F4)); + } + else if (algorithm.equalsIgnoreCase("ECC")) + { + this.signature = Signature.getInstance("SHA256withECDSA"); + this.keyGen.initialize(new ECGenParameterSpec("prime256v1")); + } + else + { + throw new IllegalArgumentException("Unrecognized encryption algorithm: " + algorithm); + } + + this.commonName = commonName; + } + + public CertificateSigningRequest GenerateNewCertificateSigningRequest() throws InvalidKeyException, IOException, CertificateException, SignatureException + { + KeyPair keypair = this.keyGen.generateKeyPair(); + PublicKey publicKey = keypair.getPublic(); + PrivateKey privateKey = keypair.getPrivate(); + + // generate PKCS10 certificate request + PKCS10 pkcs10 = new PKCS10(publicKey); + + this.signature.initSign(privateKey); + X500Principal principal = new X500Principal( "CN=" + this.commonName); + X500Name x500name; + x500name= new X500Name(principal.getEncoded()); + pkcs10.encodeAndSign(x500name, this.signature); + this.signature.initSign(privateKey); + + byte[] encodedPKCS10 = pkcs10.getEncoded(); + String base64EncodedPKCS10 = Base64.getEncoder().encodeToString(encodedPKCS10); + return new CertificateSigningRequest(publicKey, privateKey, base64EncodedPKCS10); + } +} diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 61a248cb16..68a56ec70e 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -1,8 +1,6 @@ package com.microsoft.azure.sdk.iot.provisioning.samples; -import com.microsoft.azure.sdk.iot.device.ClientOptions; -import com.microsoft.azure.sdk.iot.device.DeviceClient; -import com.microsoft.azure.sdk.iot.device.IotHubClientProtocol; +import com.microsoft.azure.sdk.iot.device.*; import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException; import com.microsoft.azure.sdk.iot.provisioning.device.*; import com.microsoft.azure.sdk.iot.provisioning.device.internal.exceptions.ProvisioningDeviceClientException; @@ -20,6 +18,8 @@ import java.security.PrivateKey; import java.util.Base64; import java.util.List; +import java.util.Observable; +import java.util.concurrent.Future; public class Main { @@ -37,11 +37,13 @@ public static void main(String[] args) ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT; //ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT_WS; - CertificateSigningRequest certificateSigningRequest = + CertificateSigningRequestGenerator csrGenerator = //new CertificateSigningRequest("RSA", registrationId); - new CertificateSigningRequest("ECDSA", registrationId); + new CertificateSigningRequestGenerator("ECDSA", registrationId); - String privateKeyPem = getPrivateKeyString(certificateSigningRequest.privateKey); + CertificateSigningRequest dpsCsr = csrGenerator.GenerateNewCertificateSigningRequest(); + + String privateKeyPem = getPrivateKeyString(dpsCsr.getPrivateKey()); WriteToFile(savedCertificatesPath, "privateKey.pem", privateKeyPem); SecurityProvider securityProvider = CreateSecurityProviderX509(); @@ -54,7 +56,7 @@ public static void main(String[] args) securityProvider); AdditionalData provisioningAdditionalData = new AdditionalData(); - provisioningAdditionalData.setClientCertificateSigningRequest(certificateSigningRequest.base64EncodedPKCS10); + provisioningAdditionalData.setClientCertificateSigningRequest(dpsCsr.getBase64EncodedPKCS10()); ProvisioningDeviceClientRegistrationResult provisioningResult = provisioningDeviceClient.registerDeviceSync(provisioningAdditionalData); if (provisioningResult.getProvisioningDeviceClientStatus() != ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED) @@ -77,10 +79,39 @@ public static void main(String[] args) SSLContext deviceClientSslContext = SSLContextBuilder.buildSSLContext(leafCertificatePem, privateKeyPem); + String deviceId = provisioningResult.getDeviceId(); + String iotHubUri = provisioningResult.getIothubUri(); + ClientOptions clientOptions = ClientOptions.builder().sslContext(deviceClientSslContext).build(); - String derivedConnectionString = String.format("HostName=%s;DeviceId=%;x509=true", provisioningResult.getIothubUri(), provisioningResult.getDeviceId()); + String derivedConnectionString = String.format("HostName=%s;DeviceId=%s;x509=true", iotHubUri, deviceId); DeviceClient client = new DeviceClient(derivedConnectionString, iotHubProtocol, clientOptions); + CertificateSigningRequest renewalCsr = csrGenerator.GenerateNewCertificateSigningRequest(); + + IotHubCertificateSigningRequest iothubCsr = + new IotHubCertificateSigningRequest(deviceId, renewalCsr.getBase64EncodedPKCS10(), "*"); + + Future<> + client.sendCertificateSigningRequest(iothubCsr, new IotHubCertificateSigningResponseCallback() + { + @Override + public void onCertificateSigningRequestAccepted(IotHubCertificateSigningRequestAccepted accepted) + { + + } + + @Override + public void onCertificateSigningComplete(IotHubCertificateSigningResponse response) + { + + } + + @Override + public void onCertificateSigningError(IotHubCertificateSigningError error) + { + + } + }); } private static SecurityProviderX509 CreateSecurityProviderX509() From cbf94d7958a0d4bc755ebaa9bf4c752a82bd4100 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 24 Feb 2026 11:11:49 -0800 Subject: [PATCH 06/33] more --- .../azure/sdk/iot/device/DeviceClient.java | 37 ++++++++ .../IotHubCertificateSigningError.java | 2 +- .../IotHubCertificateSigningErrorCode.java | 2 +- .../IotHubCertificateSigningErrorInfo.java | 2 +- .../IotHubCertificateSigningException.java | 14 +++ .../IotHubCertificateSigningRequest.java | 20 ++--- ...tHubCertificateSigningRequestAccepted.java | 2 +- .../IotHubCertificateSigningResponse.java | 4 +- ...HubCertificateSigningResponseCallback.java | 2 +- ...tHubCertificateSigningResponseFutures.java | 17 ++++ .../transport/IotHubTransportMessage.java | 1 + .../mqtt/MqttCertificateSigning.java | 4 + .../certificate-signing-sample/pom.xml | 12 +++ .../sdk/iot/provisioning/samples/Main.java | 86 +++++++++++-------- .../README.md | 30 +++++++ 15 files changed, 184 insertions(+), 51 deletions(-) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{ => certificatesigning}/IotHubCertificateSigningError.java (97%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{ => certificatesigning}/IotHubCertificateSigningErrorCode.java (96%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{ => certificatesigning}/IotHubCertificateSigningErrorInfo.java (97%) create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{ => certificatesigning}/IotHubCertificateSigningRequest.java (77%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{ => certificatesigning}/IotHubCertificateSigningRequestAccepted.java (96%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{ => certificatesigning}/IotHubCertificateSigningResponse.java (94%) rename iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/{ => certificatesigning}/IotHubCertificateSigningResponseCallback.java (94%) create mode 100644 iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseFutures.java create mode 100644 provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index 8be3ec2837..19f84c463e 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -3,6 +3,7 @@ package com.microsoft.azure.sdk.iot.device; +import com.microsoft.azure.sdk.iot.device.certificatesigning.*; import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException; import com.microsoft.azure.sdk.iot.device.transport.IotHubTransportMessage; import com.microsoft.azure.sdk.iot.device.transport.RetryPolicy; @@ -14,6 +15,7 @@ import java.io.IOException; import java.util.UUID; +import java.util.concurrent.CompletableFuture; /** *

@@ -289,6 +291,41 @@ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest reques this.getDeviceIO().sendEventAsync(certificateSigningRequest, null, null, this.config.getDeviceId()); } + public IotHubCertificateSigningResponseFutures sendCertificateSigningRequest(IotHubCertificateSigningRequest request) + { + //TODO do we care that Futures support cancellation? Hub doesn't support the scenario + IotHubCertificateSigningResponseFutures responses = new IotHubCertificateSigningResponseFutures(); + CompletableFuture acceptedFuture = new CompletableFuture<>(); + CompletableFuture responseFuture = new CompletableFuture<>(); + + responses.setOnCertificateSigningRequestAccepted(acceptedFuture); + responses.setOnCertificateSigningCompleted(responseFuture); + + this.sendCertificateSigningRequest(request, new IotHubCertificateSigningResponseCallback() + { + @Override + public void onCertificateSigningRequestAccepted(IotHubCertificateSigningRequestAccepted accepted) + { + acceptedFuture.complete(accepted); + } + + @Override + public void onCertificateSigningComplete(IotHubCertificateSigningResponse response) + { + responseFuture.complete(response); + } + + @Override + public void onCertificateSigningError(IotHubCertificateSigningError error) + { + acceptedFuture.completeExceptionally(new IotHubCertificateSigningException(error.getMessage(), error)); + responseFuture.completeExceptionally(new IotHubCertificateSigningException(error.getMessage(), error)); + } + }); + + return responses; + } + // Used by multiplexing clients to signal to this client what kind of multiplexing client is using this device client void markAsMultiplexed() { diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningError.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningError.java similarity index 97% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningError.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningError.java index 194f855d7e..f471c8b561 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningError.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningError.java @@ -1,4 +1,4 @@ -package com.microsoft.azure.sdk.iot.device; +package com.microsoft.azure.sdk.iot.device.certificatesigning; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorCode.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorCode.java similarity index 96% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorCode.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorCode.java index 92c54a8479..d7991ab376 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorCode.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorCode.java @@ -1,4 +1,4 @@ -package com.microsoft.azure.sdk.iot.device; +package com.microsoft.azure.sdk.iot.device.certificatesigning; public enum IotHubCertificateSigningErrorCode { diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorInfo.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorInfo.java similarity index 97% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorInfo.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorInfo.java index 086e6d7c2c..36ef53bcab 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningErrorInfo.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorInfo.java @@ -1,4 +1,4 @@ -package com.microsoft.azure.sdk.iot.device; +package com.microsoft.azure.sdk.iot.device.certificatesigning; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java new file mode 100644 index 0000000000..6bb2c0fc86 --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java @@ -0,0 +1,14 @@ +package com.microsoft.azure.sdk.iot.device.certificatesigning; + +import lombok.Getter; + +public class IotHubCertificateSigningException extends Exception +{ + @Getter + private IotHubCertificateSigningError error; + + public IotHubCertificateSigningException(String message, IotHubCertificateSigningError error) + { + super(message); + } +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequest.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java similarity index 77% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequest.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java index 607fc63044..1341604efe 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequest.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -package com.microsoft.azure.sdk.iot.device; +package com.microsoft.azure.sdk.iot.device.certificatesigning; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -20,7 +20,7 @@ public class IotHubCertificateSigningRequest * The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. */ @SerializedName("csr") - private String certificateSigningRequestData = null; + private String certificateSigningRequest = null; /** * Optional. Request ID to replace, or "*" to replace any active request. @@ -34,36 +34,36 @@ public class IotHubCertificateSigningRequest /** * @param id The device ID the certificate will be issued for. Must match the device Id of the device that will send this request. - * @param certificateSigningRequestData The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. + * @param certificateSigningRequest The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. */ - public IotHubCertificateSigningRequest(String id, String certificateSigningRequestData) + public IotHubCertificateSigningRequest(String id, String certificateSigningRequest) { - this(id, certificateSigningRequestData, null); + this(id, certificateSigningRequest, null); } /** * @param id The device ID the certificate will be issued for. Must match the device Id of the device that will send this request. - * @param certificateSigningRequestData The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. + * @param certificateSigningRequest The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. * @param replace the request ID to replace, or "*" to replace any active request. For use if a * previous certificate signing request has failed and you want to start over. */ - public IotHubCertificateSigningRequest(String id, String certificateSigningRequestData, String replace) + public IotHubCertificateSigningRequest(String id, String certificateSigningRequest, String replace) { if (id == null || id.isEmpty()) { throw new IllegalArgumentException("Id must be non-null and not empty"); } - if (certificateSigningRequestData == null || certificateSigningRequestData.isEmpty()) + if (certificateSigningRequest == null || certificateSigningRequest.isEmpty()) { throw new IllegalArgumentException("certificateSigningRequestData must be non-null and not empty"); } this.id = id; - this.certificateSigningRequestData = certificateSigningRequestData; + this.certificateSigningRequest = certificateSigningRequest; } - String toJson() + public String toJson() { Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequestAccepted.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequestAccepted.java similarity index 96% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequestAccepted.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequestAccepted.java index 23f7b23c0a..411b2e4e22 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningRequestAccepted.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequestAccepted.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -package com.microsoft.azure.sdk.iot.device; +package com.microsoft.azure.sdk.iot.device.certificatesigning; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponse.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponse.java similarity index 94% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponse.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponse.java index f5f376fb0f..78598e3323 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponse.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponse.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -package com.microsoft.azure.sdk.iot.device; +package com.microsoft.azure.sdk.iot.device.certificatesigning; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -34,7 +34,7 @@ public class IotHubCertificateSigningResponse */ @SerializedName("correlationId") @Getter - private List correlationId; + private String correlationId; public IotHubCertificateSigningResponse(String json) throws IllegalArgumentException { diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponseCallback.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseCallback.java similarity index 94% rename from iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponseCallback.java rename to iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseCallback.java index a5f0f9daa3..26a78ca369 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/IotHubCertificateSigningResponseCallback.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseCallback.java @@ -1,4 +1,4 @@ -package com.microsoft.azure.sdk.iot.device; +package com.microsoft.azure.sdk.iot.device.certificatesigning; public interface IotHubCertificateSigningResponseCallback { diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseFutures.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseFutures.java new file mode 100644 index 0000000000..1244ff972c --- /dev/null +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseFutures.java @@ -0,0 +1,17 @@ +package com.microsoft.azure.sdk.iot.device.certificatesigning; + +import lombok.Getter; +import lombok.Setter; + +import java.util.concurrent.Future; + +public class IotHubCertificateSigningResponseFutures +{ + @Getter + @Setter + Future OnCertificateSigningRequestAccepted; + + @Getter + @Setter + Future OnCertificateSigningCompleted; +} diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java index c226e79962..159ddf89a1 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/IotHubTransportMessage.java @@ -3,6 +3,7 @@ package com.microsoft.azure.sdk.iot.device.transport; +import com.microsoft.azure.sdk.iot.device.certificatesigning.IotHubCertificateSigningResponseCallback; import com.microsoft.azure.sdk.iot.device.twin.DeviceOperations; import com.microsoft.azure.sdk.iot.device.*; import com.microsoft.azure.sdk.iot.device.transport.https.HttpsMethod; diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java index 0f15bda262..4482ff68f6 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java @@ -4,6 +4,10 @@ package com.microsoft.azure.sdk.iot.device.transport.mqtt; import com.microsoft.azure.sdk.iot.device.*; +import com.microsoft.azure.sdk.iot.device.certificatesigning.IotHubCertificateSigningError; +import com.microsoft.azure.sdk.iot.device.certificatesigning.IotHubCertificateSigningRequestAccepted; +import com.microsoft.azure.sdk.iot.device.certificatesigning.IotHubCertificateSigningResponse; +import com.microsoft.azure.sdk.iot.device.certificatesigning.IotHubCertificateSigningResponseCallback; import com.microsoft.azure.sdk.iot.device.transport.IotHubTransportMessage; import com.microsoft.azure.sdk.iot.device.transport.TransportException; import lombok.extern.slf4j.Slf4j; diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml index 2587735bc2..9cffb61346 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml @@ -44,5 +44,17 @@ provided + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + \ No newline at end of file diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 68a56ec70e..3de9c45c72 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -1,31 +1,33 @@ package com.microsoft.azure.sdk.iot.provisioning.samples; import com.microsoft.azure.sdk.iot.device.*; +import com.microsoft.azure.sdk.iot.device.certificatesigning.*; import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException; import com.microsoft.azure.sdk.iot.provisioning.device.*; import com.microsoft.azure.sdk.iot.provisioning.device.internal.exceptions.ProvisioningDeviceClientException; import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProvider; import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProviderSymmetricKey; -import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProviderX509; -import com.microsoft.azure.sdk.iot.provisioning.security.hsm.SecurityProviderX509Cert; import javax.net.ssl.SSLContext; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.util.Base64; import java.util.List; -import java.util.Observable; -import java.util.concurrent.Future; +import java.util.concurrent.ExecutionException; public class Main { - private static String idScope = ""; - private static String registrationId = "<>"; - private static String savedCertificatesPath = "<>"; + private static final String DPS_ID_SCOPE = "<>"; + private static final String DPS_REGISTRATION_ID = "<>"; + private static final String SAMPLE_CERTIFICATES_OUTPUT_PATH = "<>"; + private static final String DPS_SYMMETRIC_KEY = "<>"; public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException, IotHubClientException, GeneralSecurityException, ProvisioningDeviceClientException, ProvisioningDeviceClientException @@ -39,19 +41,18 @@ public static void main(String[] args) CertificateSigningRequestGenerator csrGenerator = //new CertificateSigningRequest("RSA", registrationId); - new CertificateSigningRequestGenerator("ECDSA", registrationId); + new CertificateSigningRequestGenerator("ECDSA", DPS_REGISTRATION_ID); CertificateSigningRequest dpsCsr = csrGenerator.GenerateNewCertificateSigningRequest(); String privateKeyPem = getPrivateKeyString(dpsCsr.getPrivateKey()); - WriteToFile(savedCertificatesPath, "privateKey.pem", privateKeyPem); + WriteToFile(SAMPLE_CERTIFICATES_OUTPUT_PATH, "privateKey.pem", privateKeyPem); - SecurityProvider securityProvider = CreateSecurityProviderX509(); - //SecurityProvider securityProvider = CreateSecurityProviderSymmetricKey(); + SecurityProvider securityProvider = CreateSecurityProvider(); ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.create( "global.azure-devices-provisioning.net", - idScope, + DPS_ID_SCOPE, dpsProtocol, securityProvider); @@ -73,7 +74,7 @@ public static void main(String[] args) } String issuedClientCertificatesPem = ConvertToPem(provisioningResult.getIssuedClientCertificateChain()); - WriteToFile(savedCertificatesPath, "clientCertificates.pem", issuedClientCertificatesPem); + WriteToFile(SAMPLE_CERTIFICATES_OUTPUT_PATH, "clientCertificates.pem", issuedClientCertificatesPem); String leafCertificatePem = ConvertToPem(provisioningResult.getIssuedClientCertificateChain().get(0)); @@ -86,42 +87,58 @@ public static void main(String[] args) String derivedConnectionString = String.format("HostName=%s;DeviceId=%s;x509=true", iotHubUri, deviceId); DeviceClient client = new DeviceClient(derivedConnectionString, iotHubProtocol, clientOptions); + client.open(false); + + client.sendEvent(new Message("Hello from the CSR sample!")); + CertificateSigningRequest renewalCsr = csrGenerator.GenerateNewCertificateSigningRequest(); IotHubCertificateSigningRequest iothubCsr = new IotHubCertificateSigningRequest(deviceId, renewalCsr.getBase64EncodedPKCS10(), "*"); - Future<> - client.sendCertificateSigningRequest(iothubCsr, new IotHubCertificateSigningResponseCallback() + IotHubCertificateSigningResponseFutures csrResponseFutures = client.sendCertificateSigningRequest(iothubCsr); + + IotHubCertificateSigningResponse response; + IotHubCertificateSigningRequestAccepted accepted; + + try + { + accepted = csrResponseFutures.getOnCertificateSigningRequestAccepted().get(); + System.out.println("The certificate signing request was accepted by Iot Hub. Operation will expire at: " + accepted.getOperationExpires()); + response = csrResponseFutures.getOnCertificateSigningCompleted().get(); + System.out.println("Iot Hub completed the certificate signing request."); + } + catch (ExecutionException e) { - @Override - public void onCertificateSigningRequestAccepted(IotHubCertificateSigningRequestAccepted accepted) - { + //TODO I believe this should always be the case, but double check this + IotHubCertificateSigningException ex = (IotHubCertificateSigningException) e.getCause(); + System.out.println("Encountered an issue while renewing the certificates: " + ex.getMessage()); + return; + } - } + String renewedClientCertificatesPem = ConvertToPem(response.getCertificates()); + WriteToFile(SAMPLE_CERTIFICATES_OUTPUT_PATH, "clientCertificates.pem", renewedClientCertificatesPem); - @Override - public void onCertificateSigningComplete(IotHubCertificateSigningResponse response) - { + String renewedLeafCertificatePem = ConvertToPem(provisioningResult.getIssuedClientCertificateChain().get(0)); - } + client.close(); - @Override - public void onCertificateSigningError(IotHubCertificateSigningError error) - { + SSLContext renewedDeviceClientSslContext = SSLContextBuilder.buildSSLContext(renewedLeafCertificatePem, privateKeyPem); + ClientOptions renewedClientOptions = ClientOptions.builder().sslContext(renewedDeviceClientSslContext).build(); + client = new DeviceClient(derivedConnectionString, iotHubProtocol, renewedClientOptions); - } - }); - } + client.open(true); - private static SecurityProviderX509 CreateSecurityProviderX509() - { - return new SecurityProviderX509Cert(todo); + client.sendEvent(new Message("Hello from the CSR sample!")); + + client.close(); } - private static SecurityProviderSymmetricKey CreateSecurityProviderSymmetricKey() + // This sample can use any combination of individual enrollment vs enrollment group and TPM vs Symmetric Key vs x509 auth. + // For simpicity in demonstrating the CSR feature, though, this sample will use Symmetric Key + individual enrollment. + private static SecurityProviderSymmetricKey CreateSecurityProvider() { - return new SecurityProviderSymmetricKey(todo); + return new SecurityProviderSymmetricKey(DPS_SYMMETRIC_KEY.getBytes(StandardCharsets.UTF_8), DPS_REGISTRATION_ID); } private static String getPrivateKeyString(PrivateKey privateKey) throws IOException @@ -159,6 +176,7 @@ private static String ConvertToPem(String issuedLeafCertificate) private static void WriteToFile(String path, String filename, String contents) throws IOException { + Files.deleteIfExists(Path.of(path, filename)); BufferedWriter writer = new BufferedWriter(new FileWriter(path + "/" + filename)); writer.write(contents); writer.close(); diff --git a/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md b/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md new file mode 100644 index 0000000000..634a2c42fe --- /dev/null +++ b/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md @@ -0,0 +1,30 @@ +# DPS and IoT hub Certificate Signing Sample + +This sample demonstrates the certificate signing features available when provisioning a device with the Device Provisioning Service and when connected to IoT Hub. + +*Note that this feature is currently only available over MQTT/MQTT_WS for both DPS and IoT Hub* + +## DPS feature demonstrated + +When provisioning a device, you may optionally include a certificate signing request such that the provisioned device can use those certificates when connecting to IoT hub after provisioning completes. + +```java +AdditionalData provisioningAdditionalData = new AdditionalData(); +provisioningAdditionalData.setClientCertificateSigningRequest(...); +ProvisioningDeviceClientRegistrationResult provisioningResult = provisioningDeviceClient.registerDeviceSync(provisioningAdditionalData); +List issuedClientCertificates = provisioningResult.getIssuedClientCertificateChain(); +``` + +## IoT hub feature demonstrated + +If your device needs to renew its certificates for any reason, it can send a certificate signing request to IoT hub + +```java +DeviceClient client = new DeviceClient(...); +client.open(false); +IotHubCertificateSigningRequest iothubCsr = new IotHubCertificateSigningRequest(...); +client.sendCertificateSigningRequest(iothubCsr); +``` + +Once IoT hub has accepted and completed this certificate signing response, you can close the connection to IoT hub, create a new client for the device that uses these renewed certificates, and then re-open the connection. + From 6e237e005853def61c4062653507fa3ed400ffab Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 24 Feb 2026 14:10:52 -0800 Subject: [PATCH 07/33] asdf --- .../azure/sdk/iot/device/DeviceClient.java | 43 ++++++++++++++++++- ...tHubCertificateSigningResponseFutures.java | 19 ++++++++ .../mqtt/MqttCertificateSigning.java | 2 +- .../sdk/iot/provisioning/samples/Main.java | 3 +- .../samples/SSLContextBuilder.java | 40 +++++++++++------ .../README.md | 27 +++++++++++- 6 files changed, 117 insertions(+), 17 deletions(-) diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index 19f84c463e..2c282c76ba 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -276,9 +276,31 @@ public boolean isMultiplexed() return this.isMultiplexed; } - // TODO docs, timeouts? + /** + *

+ * Send a certificate signing request to IoT hub and receive the signed certificates back. + *

+ *

+ * This is a multi-step process: + * - This client sends the certificate signing request to IoT hub + * - IoT hub will quickly send a response message that describes if the request was accepted or not + * - If accepted, IoT hub will go through the certificate signing process. Once completed, IoT hub will send another message back to this client with the signed certificates. + * + * The user-provided callback will be notified when each of these steps has finished. + *

+ *

+ * To instead be notified via futures, see {@link #sendCertificateSigningRequest(IotHubCertificateSigningRequest)} + *

+ * @param request The certificate signing request to make of IoT hub. + * @param callback The callback that will notify you for each important step in this process. + */ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest request, IotHubCertificateSigningResponseCallback callback) { + if (this.config.getProtocol() != IotHubClientProtocol.MQTT && this.config.getProtocol() != IotHubClientProtocol.MQTT_WS) + { + throw new UnsupportedOperationException("Certificate signing is only supported over MQTT or MQTT_WS"); + } + // This one message signals to lower layers to both subscribe to MQTT response topic (if not already subscribed) // and to send the CSR. This is a bit different from how methods/twins work but vastly simplifies the user // experience here (compared to having a separate method for subscribing to CSR response topic). @@ -291,9 +313,26 @@ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest reques this.getDeviceIO().sendEventAsync(certificateSigningRequest, null, null, this.config.getDeviceId()); } + /** + *

+ * Send a certificate signing request to IoT hub and receive the signed certificates back. + *

+ *

+ * This is a multi-step process: + * - This client sends the certificate signing request to IoT hub + * - IoT hub will quickly send a response message that describes if the request was accepted or not + * - If accepted, IoT hub will go through the certificate signing process. Once completed, IoT hub will send another message back to this client with the signed certificates. + * + * Each future in the returned collection will be completed when each corresponding step has finished. + *

+ *

+ * To instead be notified via callback, see {@link #sendCertificateSigningRequest(IotHubCertificateSigningRequest,IotHubCertificateSigningResponseCallback)} + *

+ * @param request The certificate signing request to make of IoT hub. + * @return A collection of the futures that will complete once each corresponding step in this process has completed. + */ public IotHubCertificateSigningResponseFutures sendCertificateSigningRequest(IotHubCertificateSigningRequest request) { - //TODO do we care that Futures support cancellation? Hub doesn't support the scenario IotHubCertificateSigningResponseFutures responses = new IotHubCertificateSigningResponseFutures(); CompletableFuture acceptedFuture = new CompletableFuture<>(); CompletableFuture responseFuture = new CompletableFuture<>(); diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseFutures.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseFutures.java index 1244ff972c..13964815f7 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseFutures.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseFutures.java @@ -5,12 +5,31 @@ import java.util.concurrent.Future; +/** + * A collection of futures that complete at each stage of the certificate signing process + */ public class IotHubCertificateSigningResponseFutures { + /** + *

+ * This future will complete once IoT hub has accepted the certificate signing request. + *

+ *

+ * If IoT hub instead responds with an error, this future will complete exceptionally with a {@link IotHubCertificateSigningException}. + *

+ */ @Getter @Setter Future OnCertificateSigningRequestAccepted; + /** + *

+ * This future will complete once IoT hub has finished signing the certificates and has sent them back to this client. + *

+ *

+ * If IoT hub instead responds with an error, this future will complete exceptionally with a {@link IotHubCertificateSigningException}. + *

+ */ @Getter @Setter Future OnCertificateSigningCompleted; diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java index 4482ff68f6..3edd00351e 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java @@ -66,7 +66,7 @@ public void send(IotHubTransportMessage message) throws TransportException this.publish("$iothub/credentials/POST/issueCertificate/?$rid=" + message.getRequestId(), message); inProgressRequestIdMap.put(message.getRequestId(), signingCallback); - //TODO listening for response messages should work like how getTwin works. Listen on callback thread, not here. + // IoT hub will respond to this request by sending a few response messages over the subscribed topic. } @Override diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 3de9c45c72..7112f28db5 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -24,9 +24,10 @@ public class Main { + // The path to write all created certificates to + private static final String SAMPLE_CERTIFICATES_OUTPUT_PATH = "~/SampleCertificates"; private static final String DPS_ID_SCOPE = "<>"; private static final String DPS_REGISTRATION_ID = "<>"; - private static final String SAMPLE_CERTIFICATES_OUTPUT_PATH = "<>"; private static final String DPS_SYMMETRIC_KEY = "<>"; public static void main(String[] args) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java index 9e879d736e..bbb728e5e7 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java @@ -30,7 +30,8 @@ public class SSLContextBuilder { private static final String SSL_CONTEXT_INSTANCE = "TLSv1.2"; private static final String CERTIFICATE_TYPE = "X.509"; - private static final String PRIVATE_KEY_ALGORITHM = "RSA"; //TODO ECC support + private static final String RSA_PRIVATE_KEY_ALGORITHM = "RSA"; + private static final String ECC_PRIVATE_KEY_ALGORITHM = "ECDSA"; private static final String CERTIFICATE_ALIAS = "cert-alias"; private static final String PRIVATE_KEY_ALIAS = "key-alias"; @@ -100,27 +101,42 @@ public static SSLContext buildSSLContext(String publicKeyCertificateString, Stri // By leaving the TrustManager array null, the SSLContext will trust the certificates stored on your device's // trusted root certification authorities certificate store. - // - // This must include the Baltimore CyberTrust Root public certificate: https://baltimore-cybertrust-root.chain-demos.digicert.com/info/index.html - // and eventually it will need to include the DigiCert Global Root G2 public certificate: https://global-root-g2.chain-demos.digicert.com/info/index.html sslContext.init(kmf.getKeyManagers(), null, new SecureRandom()); return sslContext; } - private static RSAPrivateKey parsePrivateKeyString(String privateKeyPEM) throws GeneralSecurityException + private static PrivateKey parsePrivateKeyString(String privateKeyPEM) throws GeneralSecurityException { if (privateKeyPEM == null || privateKeyPEM.isEmpty()) { - throw new IllegalArgumentException("Public key certificate cannot be null or empty"); + throw new IllegalArgumentException("Private key cannot be null or empty"); } - privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", ""); - privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", ""); - byte[] encoded = Base64.decodeBase64(privateKeyPEM.getBytes(StandardCharsets.UTF_8)); - KeyFactory kf = KeyFactory.getInstance(PRIVATE_KEY_ALGORITHM); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); - return (RSAPrivateKey) kf.generatePrivate(keySpec); + if (privateKeyPEM.contains("BEGIN PRIVATE KEY")) + { + // If it is an RSA private key + privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", ""); + privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", ""); + byte[] encoded = Base64.decodeBase64(privateKeyPEM.getBytes(StandardCharsets.UTF_8)); + KeyFactory kf = KeyFactory.getInstance(RSA_PRIVATE_KEY_ALGORITHM); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return kf.generatePrivate(keySpec); + } + else if (privateKeyPEM.contains("BEGIN EC PRIVATE KEY")) + { + // If it is an ECC private key + privateKeyPEM = privateKeyPEM.replace("-----BEGIN EC PRIVATE KEY-----\n", ""); + privateKeyPEM = privateKeyPEM.replace("-----END EC PRIVATE KEY-----", ""); + byte[] encoded = Base64.decodeBase64(privateKeyPEM.getBytes(StandardCharsets.UTF_8)); + KeyFactory kf = KeyFactory.getInstance(ECC_PRIVATE_KEY_ALGORITHM); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return kf.generatePrivate(keySpec); + } + else + { + throw new IllegalArgumentException("Malformed private key"); + } } private static X509Certificate[] parsePublicCertificateString(String pemString) throws GeneralSecurityException, IOException diff --git a/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md b/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md index 634a2c42fe..fed702bf03 100644 --- a/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md +++ b/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md @@ -4,6 +4,23 @@ This sample demonstrates the certificate signing features available when provisi *Note that this feature is currently only available over MQTT/MQTT_WS for both DPS and IoT Hub* +## Prerequisites + +1. **Azure IoT Hub** - An Azure IoT Hub instance +1. **Azure Device Provisioning Service (DPS)** - Linked to your IoT Hub (for initial provisioning) +1. **DPS Enrollment** - Configured for certificate issuance + +## How to run + +Simply fill in your specific values for the fields defined at the beginning + +```java +private static final String SAMPLE_CERTIFICATES_OUTPUT_PATH = "~/SampleCertificates"; +private static final String DPS_ID_SCOPE = "<>"; +private static final String DPS_REGISTRATION_ID = "<>"; +private static final String DPS_SYMMETRIC_KEY = "<>"; +``` + ## DPS feature demonstrated When provisioning a device, you may optionally include a certificate signing request such that the provisioned device can use those certificates when connecting to IoT hub after provisioning completes. @@ -15,6 +32,8 @@ ProvisioningDeviceClientRegistrationResult provisioningResult = provisioningDevi List issuedClientCertificates = provisioningResult.getIssuedClientCertificateChain(); ``` +*Note that the provisioning device client itself does not use these soon-to-be-signed certificates when authenticating with DPS. It must use one of the TPM/Symmetric Key/x509 authentication mechanisms detailed in other samples in this directory.* + ## IoT hub feature demonstrated If your device needs to renew its certificates for any reason, it can send a certificate signing request to IoT hub @@ -23,8 +42,14 @@ If your device needs to renew its certificates for any reason, it can send a cer DeviceClient client = new DeviceClient(...); client.open(false); IotHubCertificateSigningRequest iothubCsr = new IotHubCertificateSigningRequest(...); -client.sendCertificateSigningRequest(iothubCsr); +... = client.sendCertificateSigningRequest(iothubCsr); ``` Once IoT hub has accepted and completed this certificate signing response, you can close the connection to IoT hub, create a new client for the device that uses these renewed certificates, and then re-open the connection. +## Additional feature notes + +- This certificate signing feature works for both RSA and ECC certificates +- The DPS flow is applicable for any combination of + - Individual enrollment vs enrollment group + - Symmetric key vs TPM vs x509 authentication \ No newline at end of file From 435441c1e1d31d0304a2a01d9f46887523a73fa0 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Thu, 26 Feb 2026 10:44:33 -0800 Subject: [PATCH 08/33] keep --- .../mqtt/MqttCertificateSigning.java | 2 +- .../certificate-signing-sample/pom.xml | 4 ++ .../samples/CertificateSigningRequest.java | 9 ---- .../CertificateSigningRequestGenerator.java | 20 ++++--- .../provisioning/samples/CertificateType.java | 7 +++ .../sdk/iot/provisioning/samples/Main.java | 54 ++++++++++++++----- .../samples/SSLContextBuilder.java | 8 +-- .../contract/amqp/ContractAPIAmqp.java | 2 +- .../contract/http/ContractAPIHttp.java | 6 +-- .../parser/DeviceRegistrationParser.java | 2 +- .../device/internal/task/RequestData.java | 19 +++++++ .../device/internal/task/StatusTask.java | 5 -- .../contract/http/ContractAPIHttpTest.java | 14 ++--- .../parser/DeviceRegistrationParserTest.java | 16 +++--- 14 files changed, 111 insertions(+), 57 deletions(-) create mode 100644 provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateType.java diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java index 3edd00351e..4ebb292815 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java @@ -63,8 +63,8 @@ public void send(IotHubTransportMessage message) throws TransportException IotHubCertificateSigningResponseCallback signingCallback = message.getIotHubCertificateSigningResponseCallback(); // Service will ack this immediately, then later publish a message to the response topic - this.publish("$iothub/credentials/POST/issueCertificate/?$rid=" + message.getRequestId(), message); inProgressRequestIdMap.put(message.getRequestId(), signingCallback); + this.publish("$iothub/credentials/POST/issueCertificate/?$rid=" + message.getRequestId(), message); // IoT hub will respond to this request by sending a few response messages over the subscribed topic. } diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml index 9cffb61346..c7ebaa7b4a 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml @@ -43,6 +43,10 @@ lombok provided + + org.bouncycastle + bcprov-jdk15on + diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java index c5ecc6643b..ac92137ef9 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequest.java @@ -2,16 +2,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import sun.security.pkcs10.PKCS10; -import sun.security.x509.X500Name; - -import javax.security.auth.x500.X500Principal; -import java.io.IOException; import java.security.*; -import java.security.cert.CertificateException; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.RSAKeyGenParameterSpec; -import java.util.Base64; @AllArgsConstructor public class CertificateSigningRequest diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java index e8c74e612e..e107efdb9c 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java @@ -1,5 +1,6 @@ package com.microsoft.azure.sdk.iot.provisioning.samples; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import sun.security.pkcs10.PKCS10; import sun.security.x509.X500Name; @@ -11,6 +12,9 @@ import java.security.spec.RSAKeyGenParameterSpec; import java.util.Base64; +import static com.microsoft.azure.sdk.iot.provisioning.samples.CertificateType.ECC; +import static com.microsoft.azure.sdk.iot.provisioning.samples.CertificateType.RSA; + public class CertificateSigningRequestGenerator { private final Signature signature; @@ -18,26 +22,30 @@ public class CertificateSigningRequestGenerator private final String commonName; /** - * @param algorithm "RSA" or "ECDSA" + * @param certificateType RSA or ECC * @param commonName The common name of the certificate signing request. For this sample's purposes, * this value should equal the registration Id being used in DPS. */ - public CertificateSigningRequestGenerator(String algorithm, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, InvalidAlgorithmParameterException + public CertificateSigningRequestGenerator(CertificateType certificateType, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, InvalidAlgorithmParameterException { - this.keyGen = KeyPairGenerator.getInstance(algorithm); - if (algorithm.equalsIgnoreCase("RSA")) + BouncyCastleProvider prov = new BouncyCastleProvider(); + Security.addProvider(prov); + + if (certificateType == RSA) { + this.keyGen = KeyPairGenerator.getInstance("RSA", prov); this.signature = Signature.getInstance("SHA256withRSA"); this.keyGen.initialize(new RSAKeyGenParameterSpec(4096, RSAKeyGenParameterSpec.F4)); } - else if (algorithm.equalsIgnoreCase("ECC")) + else if (certificateType == ECC) { + this.keyGen = KeyPairGenerator.getInstance("EC", prov); this.signature = Signature.getInstance("SHA256withECDSA"); this.keyGen.initialize(new ECGenParameterSpec("prime256v1")); } else { - throw new IllegalArgumentException("Unrecognized encryption algorithm: " + algorithm); + throw new IllegalArgumentException("Unrecognized certificate type"); } this.commonName = commonName; diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateType.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateType.java new file mode 100644 index 0000000000..f30bdc93db --- /dev/null +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateType.java @@ -0,0 +1,7 @@ +package com.microsoft.azure.sdk.iot.provisioning.samples; + +public enum CertificateType +{ + ECC, + RSA +} diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 7112f28db5..03605bf908 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -40,13 +40,15 @@ public static void main(String[] args) ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT; //ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT_WS; + CertificateType certificateType = CertificateType.ECC; + //CertificateType certificateType = CertificateType.RSA; CertificateSigningRequestGenerator csrGenerator = - //new CertificateSigningRequest("RSA", registrationId); - new CertificateSigningRequestGenerator("ECDSA", DPS_REGISTRATION_ID); + //new CertificateSigningRequest(CertificateType.RSA, registrationId); + new CertificateSigningRequestGenerator(certificateType, PROVISIONED_DEVICE_ID); CertificateSigningRequest dpsCsr = csrGenerator.GenerateNewCertificateSigningRequest(); - String privateKeyPem = getPrivateKeyString(dpsCsr.getPrivateKey()); + String privateKeyPem = getPrivateKeyString(dpsCsr.getPrivateKey(), certificateType); WriteToFile(SAMPLE_CERTIFICATES_OUTPUT_PATH, "privateKey.pem", privateKeyPem); SecurityProvider securityProvider = CreateSecurityProvider(); @@ -81,6 +83,8 @@ public static void main(String[] args) SSLContext deviceClientSslContext = SSLContextBuilder.buildSSLContext(leafCertificatePem, privateKeyPem); + System.out.println("Provisioning finished successfully. Opening device client connection with the newly signed certificates."); + String deviceId = provisioningResult.getDeviceId(); String iotHubUri = provisioningResult.getIothubUri(); @@ -92,11 +96,14 @@ public static void main(String[] args) client.sendEvent(new Message("Hello from the CSR sample!")); + + System.out.println("Creating new CSR to send to IoT hub."); CertificateSigningRequest renewalCsr = csrGenerator.GenerateNewCertificateSigningRequest(); IotHubCertificateSigningRequest iothubCsr = new IotHubCertificateSigningRequest(deviceId, renewalCsr.getBase64EncodedPKCS10(), "*"); + System.out.println("Sending new CSR to IoT hub."); IotHubCertificateSigningResponseFutures csrResponseFutures = client.sendCertificateSigningRequest(iothubCsr); IotHubCertificateSigningResponse response; @@ -137,18 +144,39 @@ public static void main(String[] args) // This sample can use any combination of individual enrollment vs enrollment group and TPM vs Symmetric Key vs x509 auth. // For simpicity in demonstrating the CSR feature, though, this sample will use Symmetric Key + individual enrollment. - private static SecurityProviderSymmetricKey CreateSecurityProvider() + private static SecurityProviderSymmetricKey CreateSecurityProvider() throws NoSuchAlgorithmException, InvalidKeyException { - return new SecurityProviderSymmetricKey(DPS_SYMMETRIC_KEY.getBytes(StandardCharsets.UTF_8), DPS_REGISTRATION_ID); + byte[] derivedSymmetricKey = + SecurityProviderSymmetricKey + .ComputeDerivedSymmetricKey( + ENROLLMENT_GROUP_SYMMETRIC_KEY.getBytes(StandardCharsets.UTF_8), + PROVISIONED_DEVICE_ID); + + return new SecurityProviderSymmetricKey(derivedSymmetricKey, PROVISIONED_DEVICE_ID); } - private static String getPrivateKeyString(PrivateKey privateKey) throws IOException + private static String getPrivateKeyString(PrivateKey privateKey, CertificateType certificateType) throws IOException { StringBuilder privateKeyStringBuilder = new StringBuilder(); - privateKeyStringBuilder.append("-----BEGIN PRIVATE KEY-----"); + if (certificateType == CertificateType.RSA) + { + privateKeyStringBuilder.append("-----BEGIN PRIVATE KEY-----\r\n"); + } + else if (certificateType == CertificateType.ECC) + { + privateKeyStringBuilder.append("-----BEGIN EC PRIVATE KEY-----\r\n"); + } String privateKeyBase64Encoded = Base64.getEncoder().encodeToString(privateKey.getEncoded()); privateKeyStringBuilder.append(privateKeyBase64Encoded); - privateKeyStringBuilder.append("-----END PRIVATE KEY-----"); + privateKeyStringBuilder.append("\r\n"); + if (certificateType == CertificateType.RSA) + { + privateKeyStringBuilder.append("-----END PRIVATE KEY-----\r\n"); + } + else if (certificateType == CertificateType.ECC) + { + privateKeyStringBuilder.append("-----END EC PRIVATE KEY-----\r\n"); + } return privateKeyStringBuilder.toString(); } @@ -157,9 +185,10 @@ private static String ConvertToPem(List issuedClientCertificates) StringBuilder pemBuilder = new StringBuilder(); for (String issuedClientCertificate : issuedClientCertificates) { - pemBuilder.append("-----BEGIN CERTIFICATE-----"); + pemBuilder.append("-----BEGIN CERTIFICATE-----\r\n"); pemBuilder.append(issuedClientCertificate); - pemBuilder.append("-----END CERTIFICATE-----"); + pemBuilder.append("\r\n"); + pemBuilder.append("-----END CERTIFICATE-----\r\n"); } return pemBuilder.toString(); @@ -168,9 +197,10 @@ private static String ConvertToPem(List issuedClientCertificates) private static String ConvertToPem(String issuedLeafCertificate) { StringBuilder pemBuilder = new StringBuilder(); - pemBuilder.append("-----BEGIN CERTIFICATE-----"); + pemBuilder.append("-----BEGIN CERTIFICATE-----\r\n"); pemBuilder.append(issuedLeafCertificate); - pemBuilder.append("-----END CERTIFICATE-----"); + pemBuilder.append("\r\n"); + pemBuilder.append("-----END CERTIFICATE-----\r\n"); return pemBuilder.toString(); } diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java index bbb728e5e7..092788b0a1 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/SSLContextBuilder.java @@ -116,8 +116,8 @@ private static PrivateKey parsePrivateKeyString(String privateKeyPEM) throws Gen if (privateKeyPEM.contains("BEGIN PRIVATE KEY")) { // If it is an RSA private key - privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", ""); - privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", ""); + privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\r\n", ""); + privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----\r\n", ""); byte[] encoded = Base64.decodeBase64(privateKeyPEM.getBytes(StandardCharsets.UTF_8)); KeyFactory kf = KeyFactory.getInstance(RSA_PRIVATE_KEY_ALGORITHM); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); @@ -126,8 +126,8 @@ private static PrivateKey parsePrivateKeyString(String privateKeyPEM) throws Gen else if (privateKeyPEM.contains("BEGIN EC PRIVATE KEY")) { // If it is an ECC private key - privateKeyPEM = privateKeyPEM.replace("-----BEGIN EC PRIVATE KEY-----\n", ""); - privateKeyPEM = privateKeyPEM.replace("-----END EC PRIVATE KEY-----", ""); + privateKeyPEM = privateKeyPEM.replace("-----BEGIN EC PRIVATE KEY-----\r\n", ""); + privateKeyPEM = privateKeyPEM.replace("-----END EC PRIVATE KEY-----\r\n", ""); byte[] encoded = Base64.decodeBase64(privateKeyPEM.getBytes(StandardCharsets.UTF_8)); KeyFactory kf = KeyFactory.getInstance(ECC_PRIVATE_KEY_ALGORITHM); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java index 67d642c0e9..1c44fdac51 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java @@ -189,7 +189,7 @@ public synchronized void authenticateWithProvisioningService(RequestData request this.amqpSaslHandler.setSasToken(requestData.getSasToken()); } - byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload()).toJson().getBytes(StandardCharsets.UTF_8); + byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), null).toJson().getBytes(StandardCharsets.UTF_8); // SRS_ContractAPIAmqp_07_005: [This method shall send an AMQP message with the property of iotdps-register.] this.provisioningAmqpOperations.sendRegisterMessage(responseCallback, callbackContext, payload); diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java index 82e7474120..19161d6612 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java @@ -216,7 +216,7 @@ public synchronized void requestNonceForTPM(RequestData requestData, ResponseCal String base64EncodedEk = new String(encodeBase64(requestData.getEndorsementKey()), StandardCharsets.UTF_8); String base64EncodedSrk = new String(encodeBase64(requestData.getStorageRootKey()), StandardCharsets.UTF_8); //SRS_ContractAPIHttp_25_025: [ This method shall build the required Json input using parser. ] - byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), base64EncodedEk, base64EncodedSrk).toJson().getBytes(StandardCharsets.UTF_8); + byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), null, base64EncodedEk, base64EncodedSrk).toJson().getBytes(StandardCharsets.UTF_8); //SRS_ContractAPIHttp_25_005: [This method shall prepare the PUT request by setting following headers on a HttpRequest 1. User-Agent : User Agent String for the SDK 2. Accept : "application/json" 3. Content-Type: "application/json; charset=utf-8".] HttpRequest httpRequest = this.prepareRequest(new URL(url), HttpMethod.PUT, payload, null); //SRS_ContractAPIHttp_25_006: [This method shall set the SSLContext for the Http Request.] @@ -298,11 +298,11 @@ public synchronized void authenticateWithProvisioningService(RequestData request //SRS_ContractAPIHttp_25_027: [ This method shall base 64 encoded endorsement key, storage root key. ] String base64EncodedEk = new String(encodeBase64(requestData.getEndorsementKey()), StandardCharsets.UTF_8); String base64EncodedSrk = new String(encodeBase64(requestData.getStorageRootKey()), StandardCharsets.UTF_8); - payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), base64EncodedEk, base64EncodedSrk).toJson().getBytes(StandardCharsets.UTF_8); + payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), null, base64EncodedEk, base64EncodedSrk).toJson().getBytes(StandardCharsets.UTF_8); } else { - payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload()).toJson().getBytes(StandardCharsets.UTF_8); + payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), null).toJson().getBytes(StandardCharsets.UTF_8); } //SRS_ContractAPIHttp_25_013: [This method shall prepare the PUT request by setting following headers on a HttpRequest 1. User-Agent : User Agent String for the SDK 2. Accept : "application/json" 3. Content-Type: "application/json; charset=utf-8" 4. Authorization: specified sas token as authorization if a non null value is given.] diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java index 4fcb70bb80..4fdf74f5e5 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java @@ -58,7 +58,7 @@ static class TpmAttestation */ public DeviceRegistrationParser(String registrationId, String customPayload, String certificateSigningRequest) throws IllegalArgumentException { - this(registrationId, customPayload, null, null, null); + this(registrationId, customPayload, certificateSigningRequest, null, null); } /** diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java index bd89c4e3a1..2c9217e4fe 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java @@ -84,6 +84,7 @@ public class RequestData this.sasToken = sasToken; this.payload = payload; this.isX509 = false; + this.certificateSigningRequest = certificateSigningRequest; } /** @@ -103,6 +104,24 @@ public class RequestData this.certificateSigningRequest = certificateSigningRequest; } + /** + * Constructor for Request data + * @param registrationId Registration ID value. Can be {@code null}; + * @param operationId Operation ID value. Can be {@code null}; + * @param sslContext SSL context value. Can be {@code null}; + * @param sasToken SasToken value. Can be {@code null}; + * @param payload Payload value. Can be {@code null} + */ + RequestData(String registrationId, String operationId, SSLContext sslContext, String sasToken, String payload) + { + this.registrationId = registrationId; + this.operationId = operationId; + this.sslContext = sslContext; + this.sasToken = sasToken; + this.payload = payload; + this.isX509 = false; + } + /** * If the flow with the service is X509 or not. * @return true if the flow is X509. diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/StatusTask.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/StatusTask.java index 38f6e4ef06..ba045a29eb 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/StatusTask.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/StatusTask.java @@ -66,7 +66,6 @@ public void run(ResponseData responseData, Object context) throws ProvisioningDe String operationId, Authorization authorization) throws ProvisioningDeviceClientException { - //SRS_StatusTask_25_002: [ Constructor shall throw ProvisioningDeviceClientException if operationId , securityProvider, authorization or provisioningDeviceClientContract is null. ] if (provisioningDeviceClientContract == null) { throw new ProvisioningDeviceClientException(new IllegalArgumentException("provisioningDeviceClientContract cannot be null")); @@ -92,7 +91,6 @@ public void run(ResponseData responseData, Object context) throws ProvisioningDe throw new ProvisioningDeviceClientException(new IllegalArgumentException("authorization cannot be null")); } - //SRS_StatusTask_25_001: [ Constructor shall save operationId , securityProvider, provisioningDeviceClientContract and authorization. ] this.securityProvider = securityProvider; this.provisioningDeviceClientContract = provisioningDeviceClientContract; this.provisioningDeviceClientConfig = provisioningDeviceClientConfig; @@ -104,14 +102,12 @@ private RegistrationOperationStatusParser getRegistrationStatus(String operation { try { - //SRS_StatusTask_25_003: [ This method shall throw ProvisioningDeviceClientException if registration id is null or empty. ] String registrationId = this.securityProvider.getRegistrationId(); if (registrationId == null || registrationId.isEmpty()) { throw new ProvisioningDeviceSecurityException("registrationId cannot be null or empty"); } - //SRS_StatusTask_25_004: [ This method shall retrieve the SSL context from Authorization and throw ProvisioningDeviceClientException if it is null. ] SSLContext sslContext = authorization.getSslContext(); if (sslContext == null) { @@ -119,7 +115,6 @@ private RegistrationOperationStatusParser getRegistrationStatus(String operation } RequestData requestData = new RequestData( registrationId, operationId, authorization.getSslContext(), authorization.getSasToken(), null); - //SRS_StatusTask_25_005: [ This method shall trigger getRegistrationState on the contract API and wait for response and return it. ] ResponseData responseData = new ResponseData(); provisioningDeviceClientContract.getRegistrationStatus(requestData, new ResponseCallbackImpl(), responseData); if (responseData.getResponseData() == null || responseData.getContractState() != ContractState.DPS_REGISTRATION_RECEIVED) diff --git a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java index 8ea7477e2d..fbe493eb0e 100644 --- a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java +++ b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java @@ -252,7 +252,7 @@ public void requestNonceWithDPSTPMSucceeds() throws IOException, ProvisioningDev result = mockedTpmRegistrationResultParser; mockedTpmRegistrationResultParser.getAuthenticationKey(); result = encodeBase64String("some auth key".getBytes(StandardCharsets.UTF_8)); - new DeviceRegistrationParser(anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -444,7 +444,7 @@ public void requestNonceWithDPSTPMThrowsHubExceptionWithStatusOtherThan404Throws ProvisioningDeviceClientExceptionManager.verifyHttpResponse(mockedHttpResponse); result = new ProvisioningDeviceHubException("test Exception"); mockedHttpResponse.getStatus(); - new DeviceRegistrationParser(anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -492,7 +492,7 @@ public void requestNonceWithDPSTPMThrowsHubExceptionWithStatusLessThan300Throws( ProvisioningDeviceClientExceptionManager.verifyHttpResponse(mockedHttpResponse); mockedHttpResponse.getStatus(); result = 200; - new DeviceRegistrationParser(anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -582,7 +582,7 @@ public void authenticateWithDPSWithOutAuthSucceeds(@Mocked DeviceRegistrationPar result = null; mockedDeviceRegistrationParser.toJson(); result = "TEST JSON"; - new DeviceRegistrationParser(anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -687,7 +687,7 @@ public void authenticateWithDPSThrowsOnSendFailure() throws IOException, Provisi result = null; ProvisioningDeviceClientExceptionManager.verifyHttpResponse(mockedHttpResponse); result = new ProvisioningDeviceHubException("test Exception"); - new DeviceRegistrationParser(anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -740,7 +740,7 @@ public void authenticateWithDPSWithAuthSucceeds() throws IOException, Provisioni result = mockedHttpResponse; mockedHttpResponse.getStatus(); result = 400; - new DeviceRegistrationParser(anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -799,7 +799,7 @@ public void authenticateWithDPSWithAuthAndRetryAfterSucceeds() throws IOExceptio mockedHttpResponse.getStatus(); result = 400; - new DeviceRegistrationParser(anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; diff --git a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParserTest.java b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParserTest.java index e50f3c1ed9..f27b1f9689 100644 --- a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParserTest.java +++ b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParserTest.java @@ -27,8 +27,8 @@ public void constructorWithoutTPMSucceed() throws Exception { final String expectedJson = "{\"registrationId\":\"" + TEST_REGISTRATION_ID + "\"}"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, ""); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", ""); assertNotNull(deviceRegistrationParser.toJson()); assertEquals(expectedJson, deviceRegistrationParser.toJson()); } @@ -37,14 +37,14 @@ public void constructorWithoutTPMSucceed() throws Exception @Test (expected = IllegalArgumentException.class) public void constructorWithoutTPMOnNullRegistrationIdThrows() throws Exception { - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(null, ""); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(null, "", ""); } @Test (expected = IllegalArgumentException.class) public void constructorWithoutTPMOnEmptyRegistrationIdThrows() throws Exception { - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser("", ""); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser("", "", ""); } //SRS_DeviceRegistration_25_006: [ The constructor shall save the provided Registration Id, EndorsementKey and StorageRootKey. ] @@ -61,7 +61,7 @@ public void constructorWithTPMSucceed() throws Exception "\"storageRootKey\":\"testStorageRootKey\"" + "}}"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(regID, "", eKey, sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(regID, "", "", eKey, sRKey); assertNotNull(deviceRegistrationParser.toJson()); assertEquals(expectedJson, deviceRegistrationParser.toJson()); } @@ -72,7 +72,7 @@ public void constructorWithTPMOnNullRegistrationIdThrows() throws Exception { final String eKey = "testEndorsementKey"; final String sRKey = "testStorageRootKey"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(null, "", eKey, sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(null, "", "", eKey, sRKey); } @Test (expected = IllegalArgumentException.class) @@ -80,7 +80,7 @@ public void constructorWithTPMOnEmptyRegistrationIdThrows() throws Exception { final String eKey = "testEndorsementKey"; final String sRKey = "testStorageRootKey"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser("", "", eKey, sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser("", "", "", eKey, sRKey); } //SRS_DeviceRegistration_25_004: [ The constructor shall throw IllegalArgumentException if EndorsementKey is null or empty. ] @@ -89,7 +89,7 @@ public void constructorWithTPMOnNullEkThrows() throws Exception { final String eKey = "testEndorsementKey"; final String sRKey = "testStorageRootKey"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", null, sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", "", null, sRKey); } @Test (expected = IllegalArgumentException.class) @@ -97,6 +97,6 @@ public void constructorWithTPMOnEmptyEkThrows() throws Exception { final String eKey = "testEndorsementKey"; final String sRKey = "testStorageRootKey"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", "", sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", "", "", sRKey); } } From 7abd57f35f7ddb91423a6ff1af6b967e53403921 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Thu, 26 Feb 2026 11:43:23 -0800 Subject: [PATCH 09/33] more --- .../azure/sdk/iot/device/DeviceClient.java | 1 + .../IotHubCertificateSigningRequest.java | 6 ++++++ .../device/transport/mqtt/MqttIotHubConnection.java | 1 + .../azure/sdk/iot/provisioning/samples/Main.java | 8 ++++++-- .../src/main/resources/log4j2.properties | 13 +++++++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/resources/log4j2.properties diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index 2c282c76ba..e296d5909d 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -296,6 +296,7 @@ public boolean isMultiplexed() */ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest request, IotHubCertificateSigningResponseCallback callback) { + //TODO return requestId in both APIs! if (this.config.getProtocol() != IotHubClientProtocol.MQTT && this.config.getProtocol() != IotHubClientProtocol.MQTT_WS) { throw new UnsupportedOperationException("Certificate signing is only supported over MQTT or MQTT_WS"); diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java index 1341604efe..ba777e2ce9 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java @@ -33,6 +33,8 @@ public class IotHubCertificateSigningRequest private String replace = null; /** + * Create a certificate signing request that will fail if any certificate signing requests for this device are already in progress. + * * @param id The device ID the certificate will be issued for. Must match the device Id of the device that will send this request. * @param certificateSigningRequest The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. */ @@ -42,6 +44,9 @@ public IotHubCertificateSigningRequest(String id, String certificateSigningReque } /** + * Create a certificate signing request that will be accepted by IoT hub depending on the provided "replace" value and depending on + * if any certificate signing requests for this device are already in progress. + * * @param id The device ID the certificate will be issued for. Must match the device Id of the device that will send this request. * @param certificateSigningRequest The Base64-encoded PKCS#10 CSR without PEM headers/footers or newlines. * @param replace the request ID to replace, or "*" to replace any active request. For use if a @@ -61,6 +66,7 @@ public IotHubCertificateSigningRequest(String id, String certificateSigningReque this.id = id; this.certificateSigningRequest = certificateSigningRequest; + this.replace = replace; } public String toJson() diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java index f9e154238f..1d0a9b2d1e 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java @@ -288,6 +288,7 @@ public void open() throws TransportException this.deviceMessaging.setMqttAsyncClient(mqttAsyncClient); this.deviceTwin.setMqttAsyncClient(mqttAsyncClient); this.directMethod.setMqttAsyncClient(mqttAsyncClient); + this.certificateSigning.setMqttAsyncClient(mqttAsyncClient); this.deviceMessaging.start(); this.state = IotHubConnectionStatus.CONNECTED; diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 03605bf908..8a3861b16a 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -17,6 +17,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.util.Base64; import java.util.List; @@ -31,7 +33,7 @@ public class Main private static final String DPS_SYMMETRIC_KEY = "<>"; public static void main(String[] args) - throws IOException, URISyntaxException, InterruptedException, IotHubClientException, GeneralSecurityException, ProvisioningDeviceClientException, ProvisioningDeviceClientException + throws IOException, URISyntaxException, InterruptedException, IotHubClientException, GeneralSecurityException, ProvisioningDeviceClientException { // Certificate signing feature is currently only supported over MQTT/MQTT_WS IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; @@ -92,7 +94,9 @@ public static void main(String[] args) String derivedConnectionString = String.format("HostName=%s;DeviceId=%s;x509=true", iotHubUri, deviceId); DeviceClient client = new DeviceClient(derivedConnectionString, iotHubProtocol, clientOptions); - client.open(false); + //TODO there is some delay between provisioning result and IoT hub accepting those credentials? This sometimes + // hits an unauthorized error + client.open(true); client.sendEvent(new Message("Hello from the CSR sample!")); diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/resources/log4j2.properties b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/resources/log4j2.properties new file mode 100644 index 0000000000..4451929a10 --- /dev/null +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/resources/log4j2.properties @@ -0,0 +1,13 @@ +status = error +name = Log4j2PropertiesConfig + +appenders = console + +appender.console.type = Console +appender.console.name = LogToConsole +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d %p (%t) [%c] - %m%n + +rootLogger.level = debug +rootLogger.appenderRefs = stdout +rootLogger.appenderRef.stdout.ref = LogToConsole \ No newline at end of file From 86a7d177d3237e182b96ea5f0837aa91aacac5a7 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Fri, 27 Feb 2026 14:11:03 -0800 Subject: [PATCH 10/33] keep --- .../com/microsoft/azure/sdk/iot/provisioning/samples/Main.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 8a3861b16a..5e952547e0 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -44,8 +44,9 @@ public static void main(String[] args) CertificateType certificateType = CertificateType.ECC; //CertificateType certificateType = CertificateType.RSA; + CertificateSigningRequestGenerator csrGenerator = - //new CertificateSigningRequest(CertificateType.RSA, registrationId); + //new CertificateSigningRequest(CertificateType.RSA, PROVISIONED_DEVICE_ID); new CertificateSigningRequestGenerator(certificateType, PROVISIONED_DEVICE_ID); CertificateSigningRequest dpsCsr = csrGenerator.GenerateNewCertificateSigningRequest(); From 186a24a0fff01d1b425bcedd35ec92030b56de4d Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Fri, 27 Feb 2026 15:44:18 -0800 Subject: [PATCH 11/33] It works --- .../azure/sdk/iot/device/DeviceClient.java | 2 +- .../azure/sdk/iot/device/MessageType.java | 2 +- .../mqtt/MqttCertificateSigning.java | 23 ++++++- .../transport/mqtt/MqttIotHubConnection.java | 2 +- .../certificate-signing-sample/pom.xml | 51 --------------- .../sdk/iot/CertificateSigningRequest.java | 63 ------------------- .../com/microsoft/azure/sdk/iot/Main.java | 17 ----- .../src/main/resources/log4j2.properties | 13 ---- iothub/device/iot-device-samples/pom.xml | 1 - .../sdk/iot/provisioning/samples/Main.java | 3 + 10 files changed, 26 insertions(+), 151 deletions(-) delete mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/pom.xml delete mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java delete mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/Main.java delete mode 100644 iothub/device/iot-device-samples/certificate-signing-sample/src/main/resources/log4j2.properties diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index e296d5909d..673d7ba17d 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -308,7 +308,7 @@ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest reques IotHubTransportMessage certificateSigningRequest = new IotHubTransportMessage(request.toJson()); certificateSigningRequest.setDeviceOperationType(DeviceOperations.DEVICE_OPERATION_CERTIFICATE_SIGNING_REQUEST); certificateSigningRequest.setIotHubCertificateSigningResponseCallback(callback); - certificateSigningRequest.setMessageType(MessageType.CERTIFICATE_SIGNING_REQUEST); + certificateSigningRequest.setMessageType(MessageType.CERTIFICATE_SIGNING); certificateSigningRequest.setRequestId(UUID.randomUUID().toString()); this.getDeviceIO().sendEventAsync(certificateSigningRequest, null, null, this.config.getDeviceId()); diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/MessageType.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/MessageType.java index 098910d476..4a9a0c8773 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/MessageType.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/MessageType.java @@ -12,5 +12,5 @@ public enum MessageType DEVICE_TELEMETRY, DEVICE_METHODS, DEVICE_TWIN, - CERTIFICATE_SIGNING_REQUEST + CERTIFICATE_SIGNING } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java index 4ebb292815..3e01eada69 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttCertificateSigning.java @@ -96,8 +96,8 @@ public IotHubTransportMessage receive() String[] topicTokens = topic.split(Pattern.quote("/")); if (topicTokens.length == 5) { - String status = topicTokens[4]; - String requestId = getRequestId(topicTokens[5]); + String status = topicTokens[3]; + String requestId = getRequestId(topicTokens[4]); if (this.inProgressRequestIdMap.containsKey(requestId)) { IotHubCertificateSigningResponseCallback iotHubCertificateSigningResponseCallback = this.inProgressRequestIdMap.get(requestId); @@ -108,6 +108,10 @@ public IotHubTransportMessage receive() { IotHubCertificateSigningRequestAccepted accepted = new IotHubCertificateSigningRequestAccepted(new String(payload, StandardCharsets.UTF_8)); iotHubCertificateSigningResponseCallback.onCertificateSigningRequestAccepted(accepted); + IotHubTransportMessage transportMessage = new IotHubTransportMessage(new byte[0], MessageType.CERTIFICATE_SIGNING); + transportMessage.setMessageType(MessageType.CERTIFICATE_SIGNING); + transportMessage.setQualityOfService(mqttMessage.getQos()); + return transportMessage; } catch (IllegalArgumentException e) { @@ -118,8 +122,12 @@ else if (status.equals("200")) { try { + this.inProgressRequestIdMap.remove(requestId); IotHubCertificateSigningResponse response = new IotHubCertificateSigningResponse(new String(payload, StandardCharsets.UTF_8)); iotHubCertificateSigningResponseCallback.onCertificateSigningComplete(response); + IotHubTransportMessage transportMessage = new IotHubTransportMessage(new byte[0], MessageType.CERTIFICATE_SIGNING); + transportMessage.setQualityOfService(mqttMessage.getQos()); + return transportMessage; } catch (IllegalArgumentException e) { @@ -130,24 +138,33 @@ else if (status.equals("200")) { try { + this.inProgressRequestIdMap.remove(requestId); IotHubCertificateSigningError error = new IotHubCertificateSigningError(new String(payload, StandardCharsets.UTF_8)); iotHubCertificateSigningResponseCallback.onCertificateSigningError(error); + IotHubTransportMessage transportMessage = new IotHubTransportMessage(new byte[0], MessageType.CERTIFICATE_SIGNING); + transportMessage.setQualityOfService(mqttMessage.getQos()); + return transportMessage; } catch (IllegalArgumentException e) { log.error("Received certificate signing error message with malformed payload. Ignoring it."); + IotHubTransportMessage transportMessage = new IotHubTransportMessage(new byte[0], MessageType.CERTIFICATE_SIGNING); + transportMessage.setQualityOfService(mqttMessage.getQos()); + return transportMessage; } } } else { log.warn("Received certificate signing response message for an unknown request Id. Ignoring it."); + IotHubTransportMessage transportMessage = new IotHubTransportMessage(new byte[0], MessageType.CERTIFICATE_SIGNING); + transportMessage.setQualityOfService(mqttMessage.getQos()); + return transportMessage; } } else { log.warn("Received MQTT message on certificate signing response topic with an unexpected topic pattern. Ignoring it."); - //TODO ack it? Are they always QoS0 } } } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java index 1d0a9b2d1e..4e7ef8660d 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/transport/mqtt/MqttIotHubConnection.java @@ -374,7 +374,7 @@ else if (message.getMessageType() == DEVICE_TWIN) log.trace("Sending MQTT device twin message ({})", message); this.deviceTwin.send((IotHubTransportMessage) message); } - else if (message.getMessageType() == CERTIFICATE_SIGNING_REQUEST && message instanceof IotHubTransportMessage) + else if (message.getMessageType() == CERTIFICATE_SIGNING && message instanceof IotHubTransportMessage) { this.certificateSigning.start(); log.trace("Sending MQTT certificate signing request message ({})", message); diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml b/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml deleted file mode 100644 index d16d5c7e37..0000000000 --- a/iothub/device/iot-device-samples/certificate-signing-sample/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - com.microsoft.azure.sdk.iot.samples - iot-device-samples - 1.0.0 - - 4.0.0 - com.microsoft.azure.sdk.iot.samples.device - certificate-signing-sample - Certificate signing sample - - - microsoft - Microsoft - - - - UTF-8 - - - - com.microsoft.azure.sdk.iot.provisioning - provisioning-device-client - ${provisioning-device-client-version} - - - com.microsoft.azure.sdk.iot.provisioning.security - x509-provider - 2.0.2 - compile - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.0 - - - - true - samples.com.microsoft.azure.sdk.iot.Main - - - - - - - \ No newline at end of file diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java deleted file mode 100644 index 415223bf3e..0000000000 --- a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/CertificateSigningRequest.java +++ /dev/null @@ -1,63 +0,0 @@ -package samples.com.microsoft.azure.sdk.iot; - -import java.io.IOException; -import java.security.*; -import java.security.cert.CertificateException; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.RSAKeyGenParameterSpec; -import java.util.Base64; - -import javax.security.auth.x500.X500Principal; - -import sun.security.pkcs10.*; -import sun.security.x509.*; - -public class CertificateSigningRequest -{ - public final PublicKey publicKey; - public final PrivateKey privateKey; - public final byte[] encodedPKCS10; - public final String base64EncodedPKCS10; - - /** - * @param algorithm "RSA" or "ECDSA" - * @param commonName The common name of the certificate signing request. For this sample's purposes, - * this value should equal the registration Id being used in DPS. - */ - public CertificateSigningRequest(String algorithm, String commonName) throws CertificateException, NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, InvalidAlgorithmParameterException - { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); - Signature signature; - if (algorithm.equalsIgnoreCase("RSA")) - { - signature = Signature.getInstance("SHA256withRSA"); - keyGen.initialize(new RSAKeyGenParameterSpec(4096, RSAKeyGenParameterSpec.F4)); - } - else if (algorithm.equalsIgnoreCase("ECC")) - { - signature = Signature.getInstance("SHA256withECDSA"); - keyGen.initialize(new ECGenParameterSpec("prime256v1")); - } - else - { - throw new IllegalArgumentException("Unrecognized encryption algorithm: " + algorithm); - } - - KeyPair keypair = keyGen.generateKeyPair(); - this.publicKey = keypair.getPublic(); - this.privateKey = keypair.getPrivate(); - - // generate PKCS10 certificate request - PKCS10 pkcs10 = new PKCS10(this.publicKey); - - signature.initSign(this.privateKey); - X500Principal principal = new X500Principal( "CN=" + commonName); - X500Name x500name; - x500name= new X500Name(principal.getEncoded()); - pkcs10.encodeAndSign(x500name, signature); - signature.initSign(this.privateKey); - - this.encodedPKCS10 = pkcs10.getEncoded(); - this.base64EncodedPKCS10 = Base64.getEncoder().encodeToString(this.encodedPKCS10); - } -} diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/Main.java b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/Main.java deleted file mode 100644 index e31d63e9f7..0000000000 --- a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/Main.java +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -package samples.com.microsoft.azure.sdk.iot; - -public class Main -{ - -} - -private class DeviceCredentials -{ - public String AssignedHub; - public String DeviceId; - public String CertificatePath; - public String KeyPath; -} \ No newline at end of file diff --git a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/resources/log4j2.properties b/iothub/device/iot-device-samples/certificate-signing-sample/src/main/resources/log4j2.properties deleted file mode 100644 index 4451929a10..0000000000 --- a/iothub/device/iot-device-samples/certificate-signing-sample/src/main/resources/log4j2.properties +++ /dev/null @@ -1,13 +0,0 @@ -status = error -name = Log4j2PropertiesConfig - -appenders = console - -appender.console.type = Console -appender.console.name = LogToConsole -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %d %p (%t) [%c] - %m%n - -rootLogger.level = debug -rootLogger.appenderRefs = stdout -rootLogger.appenderRef.stdout.ref = LogToConsole \ No newline at end of file diff --git a/iothub/device/iot-device-samples/pom.xml b/iothub/device/iot-device-samples/pom.xml index 1c474afa3b..3565570ea2 100644 --- a/iothub/device/iot-device-samples/pom.xml +++ b/iothub/device/iot-device-samples/pom.xml @@ -36,7 +36,6 @@ device-reconnection-sample custom-sas-token-provider-sample unix-domain-socket-sample - certificate-signing-sample diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 5e952547e0..7a1fdbd87d 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -86,6 +86,7 @@ public static void main(String[] args) SSLContext deviceClientSslContext = SSLContextBuilder.buildSSLContext(leafCertificatePem, privateKeyPem); + provisioningDeviceClient.close(); System.out.println("Provisioning finished successfully. Opening device client connection with the newly signed certificates."); String deviceId = provisioningResult.getDeviceId(); @@ -145,6 +146,8 @@ public static void main(String[] args) client.sendEvent(new Message("Hello from the CSR sample!")); client.close(); + + System.out.println("Done."); } // This sample can use any combination of individual enrollment vs enrollment group and TPM vs Symmetric Key vs x509 auth. From 09b524887a8123a8e4681bcca62e3a2a1533d1aa Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 10:39:41 -0800 Subject: [PATCH 12/33] Its fine --- .../com/microsoft/azure/sdk/iot/provisioning/samples/Main.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 7a1fdbd87d..a1c5e2e5c4 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -29,8 +29,8 @@ public class Main // The path to write all created certificates to private static final String SAMPLE_CERTIFICATES_OUTPUT_PATH = "~/SampleCertificates"; private static final String DPS_ID_SCOPE = "<>"; - private static final String DPS_REGISTRATION_ID = "<>"; private static final String DPS_SYMMETRIC_KEY = "<>"; + private static final String PROVISIONED_DEVICE_ID = "myCsrProvisionedDevice"; public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException, IotHubClientException, GeneralSecurityException, ProvisioningDeviceClientException From 649e2be165800d057527378768a3891dee50f1e8 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 10:40:48 -0800 Subject: [PATCH 13/33] todo --- .../com/microsoft/azure/sdk/iot/provisioning/samples/Main.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index a1c5e2e5c4..1540d0ccde 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -96,8 +96,6 @@ public static void main(String[] args) String derivedConnectionString = String.format("HostName=%s;DeviceId=%s;x509=true", iotHubUri, deviceId); DeviceClient client = new DeviceClient(derivedConnectionString, iotHubProtocol, clientOptions); - //TODO there is some delay between provisioning result and IoT hub accepting those credentials? This sometimes - // hits an unauthorized error client.open(true); client.sendEvent(new Message("Hello from the CSR sample!")); From 871bf2d2584269e0c1433d284c503cf3bb1d635c Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 11:02:26 -0800 Subject: [PATCH 14/33] documentation --- .../azure/sdk/iot/device/DeviceClient.java | 2 +- .../IotHubCertificateSigningError.java | 18 ++++++ .../IotHubCertificateSigningErrorCode.java | 4 ++ .../IotHubCertificateSigningErrorInfo.java | 57 ++++++++++++++----- .../IotHubCertificateSigningException.java | 3 + .../IotHubCertificateSigningRequest.java | 9 +++ ...tHubCertificateSigningRequestAccepted.java | 8 ++- 7 files changed, 85 insertions(+), 16 deletions(-) diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index 673d7ba17d..e698f68937 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -309,7 +309,7 @@ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest reques certificateSigningRequest.setDeviceOperationType(DeviceOperations.DEVICE_OPERATION_CERTIFICATE_SIGNING_REQUEST); certificateSigningRequest.setIotHubCertificateSigningResponseCallback(callback); certificateSigningRequest.setMessageType(MessageType.CERTIFICATE_SIGNING); - certificateSigningRequest.setRequestId(UUID.randomUUID().toString()); + certificateSigningRequest.setRequestId(certificateSigningRequest.getRequestId()); this.getDeviceIO().sendEventAsync(certificateSigningRequest, null, null, this.config.getDeviceId()); } diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningError.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningError.java index f471c8b561..40119df5d3 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningError.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningError.java @@ -9,6 +9,9 @@ import java.util.Date; +/** + * The error reported by IoT hub if certificate signing fails. + */ public class IotHubCertificateSigningError { /* Example: @@ -27,13 +30,22 @@ public class IotHubCertificateSigningError @SerializedName("errorCode") private String errorCodeString; + /** + * The error code that explains why the operation failed. + */ @Getter private transient IotHubCertificateSigningErrorCode errorCode; + /** + * The human readable error message + */ @SerializedName("message") @Getter private String message; + /** + * The tracking Id associated with this failure. If you request support for this failure, please include this tracking Id. + */ @SerializedName("trackingId") @Getter private String trackingId; @@ -41,9 +53,15 @@ public class IotHubCertificateSigningError @SerializedName("timestampUtc") private String timestampUtcString; + /** + * The UTC time at which this error happened. + */ @Getter private transient Date timestampUtc; + /** + * Further information about this error. + */ @SerializedName("info") @Getter private IotHubCertificateSigningErrorInfo info; diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorCode.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorCode.java index d7991ab376..0d65826374 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorCode.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorCode.java @@ -2,7 +2,11 @@ public enum IotHubCertificateSigningErrorCode { + /** + * This error code should never happen since this SDK hardcodes the protocol version to a valid version + */ InvalidProtocolVersion, + OperationNotAvailableInCurrentTier, PreconditionFailed, CredentialManagementPreconditionFailed, diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorInfo.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorInfo.java index 36ef53bcab..de60bcd5a6 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorInfo.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningErrorInfo.java @@ -9,36 +9,65 @@ import java.util.Date; -public class IotHubCertificateSigningErrorInfo { -/* Example: +/** + *

+ * Additional context for why a certificate signing operation failed. + *

+ *

+ * Depending on the type of error, some fields will be present and others will not. For example, if the error was that + * certificate signing failed because a certificate signing operation was already in progress, {@link #operationExpires} + * and {@link #requestId} will be present. + *

+ */ +public class IotHubCertificateSigningErrorInfo { - "correlationId": "8819e8d8-1324-4a9c-acde-ce0318e93f31", - "credentialError": "FailedToDecodeCsr", - "credentialMessage": "Failed to decode CSR: invalid base64 encoding" -} -alternatively, in the case of an "operation already in progress" error: -{ - "requestId": "aabbcc", - "correlationId": "8819e8d8-1324-4a9c-acde-ce0318e93f31", - "operationExpires": "2025-06-09T17:31:31.426Z" -} -*/ + /* Example: + { + "correlationId": "8819e8d8-1324-4a9c-acde-ce0318e93f31", + "credentialError": "FailedToDecodeCsr", + "credentialMessage": "Failed to decode CSR: invalid base64 encoding" + } + + alternatively, in the case of an "operation already in progress" error: + { + "requestId": "aabbcc", + "correlationId": "8819e8d8-1324-4a9c-acde-ce0318e93f31", + "operationExpires": "2025-06-09T17:31:31.426Z" + } + */ + + /** + * The correlation Id associated with this certificate signing request. For diagnostic purposes only. + */ @SerializedName("correlationId") @Getter private String correlationId; + /** + * The credential error code + */ @SerializedName("credentialError") @Getter - private String credentialError; //TODO is this an enum? + private String credentialError; + /** + * The human readable credential error message + */ @SerializedName("credentialMessage") @Getter private String credentialMessage; + /** + * The request Id associated with this certificate signing request failure + */ @SerializedName("requestId") private String requestId; + /** + * Only present if this error details a "certificate signing operation already in progress" error. This value is + * when the already-in-progress certificate signing operation for this device will expire. + */ @SerializedName("operationExpires") private String operationExpiresString; diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java index 6bb2c0fc86..a849839645 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java @@ -2,6 +2,9 @@ import lombok.Getter; +/** + * IoT hub reported an error during a certificate signing request. Further details are nested in {@link #getError()}. + */ public class IotHubCertificateSigningException extends Exception { @Getter diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java index ba777e2ce9..90e2773d2e 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java @@ -6,6 +6,9 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; +import lombok.Getter; + +import java.util.UUID; public class IotHubCertificateSigningRequest { @@ -32,6 +35,12 @@ public class IotHubCertificateSigningRequest @SerializedName("replace") private String replace = null; + /** + * The randomly generated request Id associated with this certificate signing request. + */ + @Getter + private final transient String requestId = UUID.randomUUID().toString(); + /** * Create a certificate signing request that will fail if any certificate signing requests for this device are already in progress. * diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequestAccepted.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequestAccepted.java index 411b2e4e22..2a36618261 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequestAccepted.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequestAccepted.java @@ -13,10 +13,13 @@ import java.util.Date; /** - * The information provided from IoT Hub that can be used with the Azure Storage SDK to upload a file from your device, including authentication. + * The information provided from IoT Hub when it accepts a certificate signing request. */ public class IotHubCertificateSigningRequestAccepted { + /** + * The correlation Id for this certificate signing request flow. For diagnostic purposes only. + */ @SerializedName("correlationId") @Getter private String correlationId; @@ -24,6 +27,9 @@ public class IotHubCertificateSigningRequestAccepted @SerializedName("operationExpires") private String operationExpiresString; + /** + * The UTC time at which this accepted certificate signing request will have expired if IoT Hub does not send any further updates. + */ @Getter private transient Date operationExpires; From ef6bbde5a90997437ddcdd74d8918049bf1b0c06 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 11:46:50 -0800 Subject: [PATCH 15/33] bug fix --- .../java/com/microsoft/azure/sdk/iot/device/DeviceClient.java | 1 - .../certificatesigning/IotHubCertificateSigningRequest.java | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index e698f68937..0d49fe05d1 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -296,7 +296,6 @@ public boolean isMultiplexed() */ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest request, IotHubCertificateSigningResponseCallback callback) { - //TODO return requestId in both APIs! if (this.config.getProtocol() != IotHubClientProtocol.MQTT && this.config.getProtocol() != IotHubClientProtocol.MQTT_WS) { throw new UnsupportedOperationException("Certificate signing is only supported over MQTT or MQTT_WS"); diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java index 90e2773d2e..e648993b24 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningRequest.java @@ -39,7 +39,7 @@ public class IotHubCertificateSigningRequest * The randomly generated request Id associated with this certificate signing request. */ @Getter - private final transient String requestId = UUID.randomUUID().toString(); + private final transient String requestId; /** * Create a certificate signing request that will fail if any certificate signing requests for this device are already in progress. @@ -76,6 +76,7 @@ public IotHubCertificateSigningRequest(String id, String certificateSigningReque this.id = id; this.certificateSigningRequest = certificateSigningRequest; this.replace = replace; + this.requestId = UUID.randomUUID().toString(); } public String toJson() @@ -88,5 +89,6 @@ public String toJson() @SuppressWarnings("unused") // used by gson IotHubCertificateSigningRequest() { + this.requestId = UUID.randomUUID().toString(); } } From 6b9f43cd732d7b9402d6d2133bff96c687ebc3d5 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 11:50:30 -0800 Subject: [PATCH 16/33] another bug fix --- .../azure/sdk/iot/device/DeviceClient.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index 0d49fe05d1..8701d0de17 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -278,12 +278,12 @@ public boolean isMultiplexed() /** *

- * Send a certificate signing request to IoT hub and receive the signed certificates back. + * Send a certificate signing certificateSigningRequest to IoT hub and receive the signed certificates back. *

*

* This is a multi-step process: - * - This client sends the certificate signing request to IoT hub - * - IoT hub will quickly send a response message that describes if the request was accepted or not + * - This client sends the certificate signing certificateSigningRequest to IoT hub + * - IoT hub will quickly send a response message that describes if the certificateSigningRequest was accepted or not * - If accepted, IoT hub will go through the certificate signing process. Once completed, IoT hub will send another message back to this client with the signed certificates. * * The user-provided callback will be notified when each of these steps has finished. @@ -291,10 +291,10 @@ public boolean isMultiplexed() *

* To instead be notified via futures, see {@link #sendCertificateSigningRequest(IotHubCertificateSigningRequest)} *

- * @param request The certificate signing request to make of IoT hub. + * @param certificateSigningRequest The certificate signing certificateSigningRequest to make of IoT hub. * @param callback The callback that will notify you for each important step in this process. */ - public void sendCertificateSigningRequest(IotHubCertificateSigningRequest request, IotHubCertificateSigningResponseCallback callback) + public void sendCertificateSigningRequest(IotHubCertificateSigningRequest certificateSigningRequest, IotHubCertificateSigningResponseCallback callback) { if (this.config.getProtocol() != IotHubClientProtocol.MQTT && this.config.getProtocol() != IotHubClientProtocol.MQTT_WS) { @@ -304,23 +304,23 @@ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest reques // This one message signals to lower layers to both subscribe to MQTT response topic (if not already subscribed) // and to send the CSR. This is a bit different from how methods/twins work but vastly simplifies the user // experience here (compared to having a separate method for subscribing to CSR response topic). - IotHubTransportMessage certificateSigningRequest = new IotHubTransportMessage(request.toJson()); - certificateSigningRequest.setDeviceOperationType(DeviceOperations.DEVICE_OPERATION_CERTIFICATE_SIGNING_REQUEST); - certificateSigningRequest.setIotHubCertificateSigningResponseCallback(callback); - certificateSigningRequest.setMessageType(MessageType.CERTIFICATE_SIGNING); - certificateSigningRequest.setRequestId(certificateSigningRequest.getRequestId()); + IotHubTransportMessage message = new IotHubTransportMessage(certificateSigningRequest.toJson()); + message.setDeviceOperationType(DeviceOperations.DEVICE_OPERATION_CERTIFICATE_SIGNING_REQUEST); + message.setIotHubCertificateSigningResponseCallback(callback); + message.setMessageType(MessageType.CERTIFICATE_SIGNING); + message.setRequestId(certificateSigningRequest.getRequestId()); - this.getDeviceIO().sendEventAsync(certificateSigningRequest, null, null, this.config.getDeviceId()); + this.getDeviceIO().sendEventAsync(message, null, null, this.config.getDeviceId()); } /** *

- * Send a certificate signing request to IoT hub and receive the signed certificates back. + * Send a certificate signing certificateSigningRequest to IoT hub and receive the signed certificates back. *

*

* This is a multi-step process: - * - This client sends the certificate signing request to IoT hub - * - IoT hub will quickly send a response message that describes if the request was accepted or not + * - This client sends the certificate signing certificateSigningRequest to IoT hub + * - IoT hub will quickly send a response message that describes if the certificateSigningRequest was accepted or not * - If accepted, IoT hub will go through the certificate signing process. Once completed, IoT hub will send another message back to this client with the signed certificates. * * Each future in the returned collection will be completed when each corresponding step has finished. @@ -328,10 +328,10 @@ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest reques *

* To instead be notified via callback, see {@link #sendCertificateSigningRequest(IotHubCertificateSigningRequest,IotHubCertificateSigningResponseCallback)} *

- * @param request The certificate signing request to make of IoT hub. + * @param certificateSigningRequest The certificate signing certificateSigningRequest to make of IoT hub. * @return A collection of the futures that will complete once each corresponding step in this process has completed. */ - public IotHubCertificateSigningResponseFutures sendCertificateSigningRequest(IotHubCertificateSigningRequest request) + public IotHubCertificateSigningResponseFutures sendCertificateSigningRequest(IotHubCertificateSigningRequest certificateSigningRequest) { IotHubCertificateSigningResponseFutures responses = new IotHubCertificateSigningResponseFutures(); CompletableFuture acceptedFuture = new CompletableFuture<>(); @@ -340,7 +340,7 @@ public IotHubCertificateSigningResponseFutures sendCertificateSigningRequest(Iot responses.setOnCertificateSigningRequestAccepted(acceptedFuture); responses.setOnCertificateSigningCompleted(responseFuture); - this.sendCertificateSigningRequest(request, new IotHubCertificateSigningResponseCallback() + this.sendCertificateSigningRequest(certificateSigningRequest, new IotHubCertificateSigningResponseCallback() { @Override public void onCertificateSigningRequestAccepted(IotHubCertificateSigningRequestAccepted accepted) From 0b7af30927a13009f2e06809772234ad022189e1 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 11:55:13 -0800 Subject: [PATCH 17/33] async --- .../microsoft/azure/sdk/iot/device/DeviceClient.java | 11 +++++------ .../azure/sdk/iot/provisioning/samples/Main.java | 6 +++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java index 8701d0de17..b4ae507fa4 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/DeviceClient.java @@ -14,7 +14,6 @@ import lombok.extern.slf4j.Slf4j; import java.io.IOException; -import java.util.UUID; import java.util.concurrent.CompletableFuture; /** @@ -289,12 +288,12 @@ public boolean isMultiplexed() * The user-provided callback will be notified when each of these steps has finished. *

*

- * To instead be notified via futures, see {@link #sendCertificateSigningRequest(IotHubCertificateSigningRequest)} + * To instead be notified via futures, see {@link #sendCertificateSigningRequestAsync(IotHubCertificateSigningRequest)} *

* @param certificateSigningRequest The certificate signing certificateSigningRequest to make of IoT hub. * @param callback The callback that will notify you for each important step in this process. */ - public void sendCertificateSigningRequest(IotHubCertificateSigningRequest certificateSigningRequest, IotHubCertificateSigningResponseCallback callback) + public void sendCertificateSigningRequestAsync(IotHubCertificateSigningRequest certificateSigningRequest, IotHubCertificateSigningResponseCallback callback) { if (this.config.getProtocol() != IotHubClientProtocol.MQTT && this.config.getProtocol() != IotHubClientProtocol.MQTT_WS) { @@ -326,12 +325,12 @@ public void sendCertificateSigningRequest(IotHubCertificateSigningRequest certif * Each future in the returned collection will be completed when each corresponding step has finished. *

*

- * To instead be notified via callback, see {@link #sendCertificateSigningRequest(IotHubCertificateSigningRequest,IotHubCertificateSigningResponseCallback)} + * To instead be notified via callback, see {@link #sendCertificateSigningRequestAsync(IotHubCertificateSigningRequest,IotHubCertificateSigningResponseCallback)} *

* @param certificateSigningRequest The certificate signing certificateSigningRequest to make of IoT hub. * @return A collection of the futures that will complete once each corresponding step in this process has completed. */ - public IotHubCertificateSigningResponseFutures sendCertificateSigningRequest(IotHubCertificateSigningRequest certificateSigningRequest) + public IotHubCertificateSigningResponseFutures sendCertificateSigningRequestAsync(IotHubCertificateSigningRequest certificateSigningRequest) { IotHubCertificateSigningResponseFutures responses = new IotHubCertificateSigningResponseFutures(); CompletableFuture acceptedFuture = new CompletableFuture<>(); @@ -340,7 +339,7 @@ public IotHubCertificateSigningResponseFutures sendCertificateSigningRequest(Iot responses.setOnCertificateSigningRequestAccepted(acceptedFuture); responses.setOnCertificateSigningCompleted(responseFuture); - this.sendCertificateSigningRequest(certificateSigningRequest, new IotHubCertificateSigningResponseCallback() + this.sendCertificateSigningRequestAsync(certificateSigningRequest, new IotHubCertificateSigningResponseCallback() { @Override public void onCertificateSigningRequestAccepted(IotHubCertificateSigningRequestAccepted accepted) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 1540d0ccde..a8fafa0bdd 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -108,7 +108,7 @@ public static void main(String[] args) new IotHubCertificateSigningRequest(deviceId, renewalCsr.getBase64EncodedPKCS10(), "*"); System.out.println("Sending new CSR to IoT hub."); - IotHubCertificateSigningResponseFutures csrResponseFutures = client.sendCertificateSigningRequest(iothubCsr); + IotHubCertificateSigningResponseFutures csrResponseFutures = client.sendCertificateSigningRequestAsync(iothubCsr); IotHubCertificateSigningResponse response; IotHubCertificateSigningRequestAccepted accepted; @@ -133,6 +133,8 @@ public static void main(String[] args) String renewedLeafCertificatePem = ConvertToPem(provisioningResult.getIssuedClientCertificateChain().get(0)); + System.out.println("Closing the connection to IoT hub and reconnecting with the newly signed certificates instead..."); + client.close(); SSLContext renewedDeviceClientSslContext = SSLContextBuilder.buildSSLContext(renewedLeafCertificatePem, privateKeyPem); @@ -141,6 +143,8 @@ public static void main(String[] args) client.open(true); + System.out.println("Successfully opened the connection with the newly signed certificates!"); + client.sendEvent(new Message("Hello from the CSR sample!")); client.close(); From c243d6b7648c700fdc54751897145f975f7fb304 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 16:20:01 -0800 Subject: [PATCH 18/33] different CSR provider --- iot-e2e-tests/common/pom.xml | 8 +--- pom.xml | 8 ++-- .../certificate-signing-sample/pom.xml | 10 ++++- .../CertificateSigningRequestGenerator.java | 41 +++++++++++-------- .../sdk/iot/provisioning/samples/Main.java | 18 +++++++- .../provisioning-X509-sample/pom.xml | 6 +-- 6 files changed, 55 insertions(+), 36 deletions(-) diff --git a/iot-e2e-tests/common/pom.xml b/iot-e2e-tests/common/pom.xml index 57bee43436..c49f3d07e6 100644 --- a/iot-e2e-tests/common/pom.xml +++ b/iot-e2e-tests/common/pom.xml @@ -71,13 +71,7 @@
org.bouncycastle - bcmail-jdk15on - 1.70 - - - org.bouncycastle - bcprov-jdk15on - 1.70 + bcprov-jdk18on org.apache.logging.log4j diff --git a/pom.xml b/pom.xml index b63f3d0863..e2797da0d4 100644 --- a/pom.xml +++ b/pom.xml @@ -147,13 +147,13 @@ org.bouncycastle - bcmail-jdk15on - 1.70 + bcprov-jdk18on + 1.83 org.bouncycastle - bcprov-jdk15on - 1.70 + bcpkix-jdk18on + 1.83 org.apache.logging.log4j diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml index c7ebaa7b4a..6a4db0ac19 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml @@ -45,7 +45,15 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on + + + org.bouncycastle + bcpkix-jdk18on + + + org.bouncycastle + bcpkix-jdk18on
diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java index e107efdb9c..16cf68c8cd 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/CertificateSigningRequestGenerator.java @@ -1,8 +1,21 @@ package com.microsoft.azure.sdk.iot.provisioning.samples; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import sun.security.pkcs10.PKCS10; -import sun.security.x509.X500Name; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Security; +import org.bouncycastle.asn1.pkcs.CertificationRequest; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import java.security.spec.*; import javax.security.auth.x500.X500Principal; import java.io.IOException; @@ -17,7 +30,7 @@ public class CertificateSigningRequestGenerator { - private final Signature signature; + private final String signature; private final KeyPairGenerator keyGen; private final String commonName; @@ -34,13 +47,13 @@ public CertificateSigningRequestGenerator(CertificateType certificateType, Strin if (certificateType == RSA) { this.keyGen = KeyPairGenerator.getInstance("RSA", prov); - this.signature = Signature.getInstance("SHA256withRSA"); + this.signature = "SHA256withRSA"; this.keyGen.initialize(new RSAKeyGenParameterSpec(4096, RSAKeyGenParameterSpec.F4)); } else if (certificateType == ECC) { this.keyGen = KeyPairGenerator.getInstance("EC", prov); - this.signature = Signature.getInstance("SHA256withECDSA"); + this.signature = "SHA256withECDSA"; this.keyGen.initialize(new ECGenParameterSpec("prime256v1")); } else @@ -51,24 +64,18 @@ else if (certificateType == ECC) this.commonName = commonName; } - public CertificateSigningRequest GenerateNewCertificateSigningRequest() throws InvalidKeyException, IOException, CertificateException, SignatureException + public CertificateSigningRequest GenerateNewCertificateSigningRequest() throws InvalidKeyException, IOException, CertificateException, SignatureException, OperatorCreationException { KeyPair keypair = this.keyGen.generateKeyPair(); PublicKey publicKey = keypair.getPublic(); PrivateKey privateKey = keypair.getPrivate(); - // generate PKCS10 certificate request - PKCS10 pkcs10 = new PKCS10(publicKey); - - this.signature.initSign(privateKey); - X500Principal principal = new X500Principal( "CN=" + this.commonName); - X500Name x500name; - x500name= new X500Name(principal.getEncoded()); - pkcs10.encodeAndSign(x500name, this.signature); - this.signature.initSign(privateKey); + org.bouncycastle.asn1.x500.X500Name name = new X500Name("CN=" + this.commonName); + PKCS10CertificationRequestBuilder reqBuilder = new JcaPKCS10CertificationRequestBuilder(name, publicKey); + JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(this.signature); + PKCS10CertificationRequest csr = reqBuilder.build(signerBuilder.build(privateKey)); - byte[] encodedPKCS10 = pkcs10.getEncoded(); - String base64EncodedPKCS10 = Base64.getEncoder().encodeToString(encodedPKCS10); + String base64EncodedPKCS10 = Base64.getEncoder().encodeToString(csr.getEncoded()); return new CertificateSigningRequest(publicKey, privateKey, base64EncodedPKCS10); } } diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index a8fafa0bdd..9b725f1b79 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -5,8 +5,10 @@ import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException; import com.microsoft.azure.sdk.iot.provisioning.device.*; import com.microsoft.azure.sdk.iot.provisioning.device.internal.exceptions.ProvisioningDeviceClientException; +import com.microsoft.azure.sdk.iot.provisioning.device.internal.exceptions.ProvisioningDeviceHubException; import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProvider; import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProviderSymmetricKey; +import org.bouncycastle.operator.OperatorCreationException; import javax.net.ssl.SSLContext; import java.io.BufferedWriter; @@ -33,7 +35,7 @@ public class Main private static final String PROVISIONED_DEVICE_ID = "myCsrProvisionedDevice"; public static void main(String[] args) - throws IOException, URISyntaxException, InterruptedException, IotHubClientException, GeneralSecurityException, ProvisioningDeviceClientException + throws IOException, URISyntaxException, InterruptedException, IotHubClientException, GeneralSecurityException, ProvisioningDeviceClientException, OperatorCreationException { // Certificate signing feature is currently only supported over MQTT/MQTT_WS IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; @@ -64,7 +66,19 @@ public static void main(String[] args) AdditionalData provisioningAdditionalData = new AdditionalData(); provisioningAdditionalData.setClientCertificateSigningRequest(dpsCsr.getBase64EncodedPKCS10()); - ProvisioningDeviceClientRegistrationResult provisioningResult = provisioningDeviceClient.registerDeviceSync(provisioningAdditionalData); + + //TODO what kinds of exceptions do we expect here if the csr was gibberish, for example? + ProvisioningDeviceClientRegistrationResult provisioningResult; + try + { + provisioningResult = provisioningDeviceClient.registerDeviceSync(provisioningAdditionalData); + } + catch (ProvisioningDeviceHubException e) + { + System.out.println("Provisioning failed with error: " + e.getMessage()); + provisioningDeviceClient.close(); + return; + } if (provisioningResult.getProvisioningDeviceClientStatus() != ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED) { diff --git a/provisioning/provisioning-device-client-samples/provisioning-X509-sample/pom.xml b/provisioning/provisioning-device-client-samples/provisioning-X509-sample/pom.xml index 6db59ebea5..de3fcb505d 100644 --- a/provisioning/provisioning-device-client-samples/provisioning-X509-sample/pom.xml +++ b/provisioning/provisioning-device-client-samples/provisioning-X509-sample/pom.xml @@ -36,11 +36,7 @@ org.bouncycastle - bcmail-jdk15on - - - org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on From 693f3d0736fab004e894f4cb60d5fb8249651497 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 16:32:48 -0800 Subject: [PATCH 19/33] factor out --- .../azure/sdk/iot/provisioning/samples/Main.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 9b725f1b79..afd664e817 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -33,22 +33,16 @@ public class Main private static final String DPS_ID_SCOPE = "<>"; private static final String DPS_SYMMETRIC_KEY = "<>"; private static final String PROVISIONED_DEVICE_ID = "myCsrProvisionedDevice"; + private static final CertificateType certificateType = CertificateType.ECC; // ECC vs RSA + + // Certificate signing feature is currently only supported over MQTT/MQTT_WS + private static final IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; + private static final ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT; public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException, IotHubClientException, GeneralSecurityException, ProvisioningDeviceClientException, OperatorCreationException { - // Certificate signing feature is currently only supported over MQTT/MQTT_WS - IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; - //IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT_WS; - - ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT; - //ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT_WS; - - CertificateType certificateType = CertificateType.ECC; - //CertificateType certificateType = CertificateType.RSA; - CertificateSigningRequestGenerator csrGenerator = - //new CertificateSigningRequest(CertificateType.RSA, PROVISIONED_DEVICE_ID); new CertificateSigningRequestGenerator(certificateType, PROVISIONED_DEVICE_ID); CertificateSigningRequest dpsCsr = csrGenerator.GenerateNewCertificateSigningRequest(); From 09e237066eaa3d5a59ae383605a0a8c1c81b44f6 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 16:33:01 -0800 Subject: [PATCH 20/33] one more --- .../com/microsoft/azure/sdk/iot/provisioning/samples/Main.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index afd664e817..9e1d25fafc 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -31,7 +31,7 @@ public class Main // The path to write all created certificates to private static final String SAMPLE_CERTIFICATES_OUTPUT_PATH = "~/SampleCertificates"; private static final String DPS_ID_SCOPE = "<>"; - private static final String DPS_SYMMETRIC_KEY = "<>"; + private static final String ENROLLMENT_GROUP_SYMMETRIC_KEY = ""; private static final String PROVISIONED_DEVICE_ID = "myCsrProvisionedDevice"; private static final CertificateType certificateType = CertificateType.ECC; // ECC vs RSA From eebd7b7b9ebc9f07f42f8dc761708073c4f252a4 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 16:44:32 -0800 Subject: [PATCH 21/33] ECC and RSA work! --- .../com/microsoft/azure/sdk/iot/provisioning/samples/Main.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 9e1d25fafc..6594740d87 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -33,7 +33,7 @@ public class Main private static final String DPS_ID_SCOPE = "<>"; private static final String ENROLLMENT_GROUP_SYMMETRIC_KEY = ""; private static final String PROVISIONED_DEVICE_ID = "myCsrProvisionedDevice"; - private static final CertificateType certificateType = CertificateType.ECC; // ECC vs RSA + private static final CertificateType certificateType = CertificateType.RSA; // ECC vs RSA // Certificate signing feature is currently only supported over MQTT/MQTT_WS private static final IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; From 93c43ba84b24053591aa2c881a6cf7a2f8af686d Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 16:48:51 -0800 Subject: [PATCH 22/33] readme path --- .../README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) rename provisioning/provisioning-device-client-samples/{provisioning-symmetrickey-individual-sample => certificate-signing-sample}/README.md (82%) diff --git a/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md b/provisioning/provisioning-device-client-samples/certificate-signing-sample/README.md similarity index 82% rename from provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md rename to provisioning/provisioning-device-client-samples/certificate-signing-sample/README.md index fed702bf03..363e6fed30 100644 --- a/provisioning/provisioning-device-client-samples/provisioning-symmetrickey-individual-sample/README.md +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/README.md @@ -12,15 +12,25 @@ This sample demonstrates the certificate signing features available when provisi ## How to run -Simply fill in your specific values for the fields defined at the beginning +Simply fill in your specific values for the fields defined at the beginning: ```java private static final String SAMPLE_CERTIFICATES_OUTPUT_PATH = "~/SampleCertificates"; private static final String DPS_ID_SCOPE = "<>"; -private static final String DPS_REGISTRATION_ID = "<>"; private static final String DPS_SYMMETRIC_KEY = "<>"; ``` +And optionally specify other values (which have defaults): + +```java +private static final String PROVISIONED_DEVICE_ID = "myCsrProvisionedDevice"; +private static final CertificateType certificateType = CertificateType.RSA; // ECC vs RSA + +// Certificate signing feature is currently only supported over MQTT/MQTT_WS +private static final IotHubClientProtocol iotHubProtocol = IotHubClientProtocol.MQTT; +private static final ProvisioningDeviceClientTransportProtocol dpsProtocol = ProvisioningDeviceClientTransportProtocol.MQTT; +``` + ## DPS feature demonstrated When provisioning a device, you may optionally include a certificate signing request such that the provisioned device can use those certificates when connecting to IoT hub after provisioning completes. From a63909ba5e761370a5abece13046911374998baf Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Mon, 2 Mar 2026 17:00:55 -0800 Subject: [PATCH 23/33] more deps --- .../certificate-signing-sample/pom.xml | 4 ---- .../provisioning-X509-sample/pom.xml | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml index 6a4db0ac19..2325ef9dfc 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml @@ -51,10 +51,6 @@ org.bouncycastle bcpkix-jdk18on - - org.bouncycastle - bcpkix-jdk18on - diff --git a/provisioning/provisioning-device-client-samples/provisioning-X509-sample/pom.xml b/provisioning/provisioning-device-client-samples/provisioning-X509-sample/pom.xml index de3fcb505d..f7fb740ca7 100644 --- a/provisioning/provisioning-device-client-samples/provisioning-X509-sample/pom.xml +++ b/provisioning/provisioning-device-client-samples/provisioning-X509-sample/pom.xml @@ -38,6 +38,10 @@ org.bouncycastle bcprov-jdk18on + + org.bouncycastle + bcpkix-jdk18on + From c772877f092c573f029dd6e8d233a2647f7c33f1 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 09:51:29 -0800 Subject: [PATCH 24/33] Javadoc fixes --- .../IotHubCertificateSigningException.java | 5 +++- .../IotHubCertificateSigningResponse.java | 2 +- ...HubCertificateSigningResponseCallback.java | 28 ++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java index a849839645..da06c28c7d 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningException.java @@ -3,10 +3,13 @@ import lombok.Getter; /** - * IoT hub reported an error during a certificate signing request. Further details are nested in {@link #getError()}. + * IoT hub reported an error during a certificate signing request.. */ public class IotHubCertificateSigningException extends Exception { + /** + * The error reported by IoT hub that caused the certificate signing to fail. + */ @Getter private IotHubCertificateSigningError error; diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponse.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponse.java index 78598e3323..1cf5b4048d 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponse.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponse.java @@ -13,7 +13,7 @@ import java.util.List; /** - * The information provided from IoT Hub that can be used with the Azure Storage SDK to upload a file from your device, including authentication. + * The response message from IoT hub containing the signed certificates. */ public class IotHubCertificateSigningResponse { diff --git a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseCallback.java b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseCallback.java index 26a78ca369..bf3e3f7871 100644 --- a/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseCallback.java +++ b/iothub/device/iot-device-client/src/main/java/com/microsoft/azure/sdk/iot/device/certificatesigning/IotHubCertificateSigningResponseCallback.java @@ -1,10 +1,13 @@ package com.microsoft.azure.sdk.iot.device.certificatesigning; +/** + * The callback for each stage of making a certificate signing request to IoT Hub. + */ public interface IotHubCertificateSigningResponseCallback { /** *

- * Executes if/when IoT hub sends a 202 in response to the initial certificate signing request call. + * Executes if/when IoT hub sends a 202 in response to the certificate signing request. *

*

* When this executes, it signals that IoT hub has begun processing the certificate signing request and @@ -14,10 +17,33 @@ public interface IotHubCertificateSigningResponseCallback * If the certificate signing request is not accetepted or fails for any reason, {@link #onCertificateSigningError(IotHubCertificateSigningError)} ()} * will execute instead of this callback. *

+ * @param accepted The response message from IoT hub saying that the certificate signing request was accepted. */ public void onCertificateSigningRequestAccepted(IotHubCertificateSigningRequestAccepted accepted); + /** + *

+ * Executes if/when IoT hub sends a 200 in response to the certificate signing request. + *

+ *

+ * When this executes, it signals that IoT hub has completed signing the certificates. + *

+ *

+ * If the certificate signing request cannot be completed or fails for any reason, {@link #onCertificateSigningError(IotHubCertificateSigningError)} ()} + * will execute instead of this callback. + *

+ * @param response The signed certificates + */ public void onCertificateSigningComplete(IotHubCertificateSigningResponse response); + /** + *

+ * Executes if/when IoT hub sends a response to the certificate signing request, but it is neither a 202 nor a 200. + *

+ *

+ * This callback may execute even after a certificate signing request has been accepted. + *

+ * @param error details on why this certificate signing request failed. + */ public void onCertificateSigningError(IotHubCertificateSigningError error); } From 114dd059272496162524a2279c7432f152bfd145 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 09:57:01 -0800 Subject: [PATCH 25/33] component governance --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e2797da0d4..04536f8952 100644 --- a/pom.xml +++ b/pom.xml @@ -83,7 +83,7 @@ com.fasterxml.jackson.core jackson-core - 2.21.0 + 2.21.1 com.fasterxml.jackson.core From 3dd7ab11a9343ad1dd19b2efbefe55193c7a19e5 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 10:37:38 -0800 Subject: [PATCH 26/33] unit test --- .../device/internal/contract/UrlPathBuilderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/UrlPathBuilderTest.java b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/UrlPathBuilderTest.java index 0a7abcf887..e05b55f135 100644 --- a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/UrlPathBuilderTest.java +++ b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/UrlPathBuilderTest.java @@ -27,7 +27,7 @@ public class UrlPathBuilderTest private static final String TEST_HOST_NAME = "testHostName"; private static final String TEST_REGISTRATION_ID = "testRegistrationId"; private static final String TEST_OPERATION_ID = "testOperationId"; - private static final String SERVICE_API_VERSION = "2019-03-31"; + private static final String SERVICE_API_VERSION = "2025-07-01-preview"; //SRS_UrlPathBuilder_25_001: [ Constructor shall save scope id.] @Test From 7d15d0e52b820c2e561cd0d6ffedd204e8e79205 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 10:38:19 -0800 Subject: [PATCH 27/33] java 8 sample --- .../certificate-signing-sample/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml index 2325ef9dfc..68456e1922 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/pom.xml @@ -58,8 +58,8 @@ org.apache.maven.plugins maven-compiler-plugin - 11 - 11 + 1.8 + 1.8
From ff4af3be9805609c11c7cc7208db474ba7781907 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 10:56:36 -0800 Subject: [PATCH 28/33] java 8 sample --- .../azure/sdk/iot/provisioning/samples/Main.java | 3 +-- .../device/internal/task/RequestData.java | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 6594740d87..20ed94e840 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -17,7 +17,7 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; +import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -225,7 +225,6 @@ private static String ConvertToPem(String issuedLeafCertificate) private static void WriteToFile(String path, String filename, String contents) throws IOException { - Files.deleteIfExists(Path.of(path, filename)); BufferedWriter writer = new BufferedWriter(new FileWriter(path + "/" + filename)); writer.write(contents); writer.close(); diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java index 2c9217e4fe..88e9303160 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java @@ -87,6 +87,18 @@ public class RequestData this.certificateSigningRequest = certificateSigningRequest; } + /** + * Constructor for Request data + * @param registrationId Registration ID value. Can be {@code null} + * @param sslContext SSL context value. Can be {@code null} + * @param isX509 True if X509 flow, false otherwise + * @param payload Payload value. Can be {@code null} + */ + RequestData(String registrationId, SSLContext sslContext, boolean isX509, String payload) + { + this(registrationId, sslContext, isX509, payload, null); + } + /** * Constructor for Request data * @param registrationId Registration ID value. Can be {@code null} From 1c0895b6db35ac0313a41a1384a2cf98e0837573 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 11:21:37 -0800 Subject: [PATCH 29/33] revert some changes --- .../contract/amqp/ContractAPIAmqp.java | 2 +- .../contract/http/ContractAPIHttp.java | 6 +- .../parser/DeviceRegistrationParser.java | 68 +++++++++++++------ .../device/internal/task/RequestData.java | 12 ++++ .../parser/DeviceRegistrationParserTest.java | 22 +++--- 5 files changed, 73 insertions(+), 37 deletions(-) diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java index 1c44fdac51..67d642c0e9 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java @@ -189,7 +189,7 @@ public synchronized void authenticateWithProvisioningService(RequestData request this.amqpSaslHandler.setSasToken(requestData.getSasToken()); } - byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), null).toJson().getBytes(StandardCharsets.UTF_8); + byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload()).toJson().getBytes(StandardCharsets.UTF_8); // SRS_ContractAPIAmqp_07_005: [This method shall send an AMQP message with the property of iotdps-register.] this.provisioningAmqpOperations.sendRegisterMessage(responseCallback, callbackContext, payload); diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java index 19161d6612..82e7474120 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java @@ -216,7 +216,7 @@ public synchronized void requestNonceForTPM(RequestData requestData, ResponseCal String base64EncodedEk = new String(encodeBase64(requestData.getEndorsementKey()), StandardCharsets.UTF_8); String base64EncodedSrk = new String(encodeBase64(requestData.getStorageRootKey()), StandardCharsets.UTF_8); //SRS_ContractAPIHttp_25_025: [ This method shall build the required Json input using parser. ] - byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), null, base64EncodedEk, base64EncodedSrk).toJson().getBytes(StandardCharsets.UTF_8); + byte[] payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), base64EncodedEk, base64EncodedSrk).toJson().getBytes(StandardCharsets.UTF_8); //SRS_ContractAPIHttp_25_005: [This method shall prepare the PUT request by setting following headers on a HttpRequest 1. User-Agent : User Agent String for the SDK 2. Accept : "application/json" 3. Content-Type: "application/json; charset=utf-8".] HttpRequest httpRequest = this.prepareRequest(new URL(url), HttpMethod.PUT, payload, null); //SRS_ContractAPIHttp_25_006: [This method shall set the SSLContext for the Http Request.] @@ -298,11 +298,11 @@ public synchronized void authenticateWithProvisioningService(RequestData request //SRS_ContractAPIHttp_25_027: [ This method shall base 64 encoded endorsement key, storage root key. ] String base64EncodedEk = new String(encodeBase64(requestData.getEndorsementKey()), StandardCharsets.UTF_8); String base64EncodedSrk = new String(encodeBase64(requestData.getStorageRootKey()), StandardCharsets.UTF_8); - payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), null, base64EncodedEk, base64EncodedSrk).toJson().getBytes(StandardCharsets.UTF_8); + payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), base64EncodedEk, base64EncodedSrk).toJson().getBytes(StandardCharsets.UTF_8); } else { - payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload(), null).toJson().getBytes(StandardCharsets.UTF_8); + payload = new DeviceRegistrationParser(requestData.getRegistrationId(), requestData.getPayload()).toJson().getBytes(StandardCharsets.UTF_8); } //SRS_ContractAPIHttp_25_013: [This method shall prepare the PUT request by setting following headers on a HttpRequest 1. User-Agent : User Agent String for the SDK 2. Accept : "application/json" 3. Content-Type: "application/json; charset=utf-8" 4. Authorization: specified sas token as authorization if a non null value is given.] diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java index 4fdf74f5e5..1cac6860ba 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParser.java @@ -15,14 +15,17 @@ @SuppressWarnings("unused") // A number of private fields are unused but may be filled in by serialization public class DeviceRegistrationParser { - @SerializedName("registrationId") + private static final String REGISTRATION_ID = "registrationId"; + @SerializedName(REGISTRATION_ID) private String registrationId; + private static final String TPM = "tpm"; @SuppressWarnings("FieldCanBeLocal") - @SerializedName("tpm") + @SerializedName(TPM) private TpmAttestation tpmAttestation; - @SerializedName("payload") + private static final String CUSTOM_PAYLOAD = "payload"; + @SerializedName(CUSTOM_PAYLOAD) private String customPayload = null; @SerializedName("csr") @@ -56,20 +59,40 @@ static class TpmAttestation * @param customPayload Custom Payload being sent to the DPS service. Can be a {@code null} or empty value. * @throws IllegalArgumentException If the provided registration id was {@code null} or empty. */ + public DeviceRegistrationParser(String registrationId, String customPayload) throws IllegalArgumentException + { + this(registrationId, customPayload, null); + } + public DeviceRegistrationParser(String registrationId, String customPayload, String certificateSigningRequest) throws IllegalArgumentException { - this(registrationId, customPayload, certificateSigningRequest, null, null); + if (registrationId == null || registrationId.isEmpty()) + { + throw new IllegalArgumentException("Registration Id cannot be null or empty"); + } + + this.registrationId = registrationId; + if (customPayload != null && !customPayload.isEmpty()) + { + this.customPayload = customPayload; + } + + this.certificateSigningRequest = certificateSigningRequest; } /** * Constructor for Device Registration for TPM flow * @param registrationId Registration Id to be sent to the service. Cannot be a {@code null} or empty value. - * @param customPayload Custom Payload being sent to the DPS service. Can be a {@code null} or empty value. - * @param certificateSigningRequest The certificate signing request to be sent. Can be a {@code null} or empty value. * @param endorsementKey endorsement key to be sent to the service. Cannot be a {@code null} or empty value. * @param storageRootKey Storage Root Key to be sent to the service. Can be a {@code null} value. + * @param customPayload Custom Payload being sent to the DPS service. Can be a {@code null} or empty value. * @throws IllegalArgumentException is thrown if any of the input parameters are invalid. */ + public DeviceRegistrationParser(String registrationId, String customPayload, String endorsementKey, String storageRootKey) throws IllegalArgumentException + { + this(registrationId, customPayload, null, endorsementKey, storageRootKey); + } + public DeviceRegistrationParser(String registrationId, String customPayload, String certificateSigningRequest, String endorsementKey, String storageRootKey) throws IllegalArgumentException { if (registrationId == null || registrationId.isEmpty()) @@ -77,47 +100,48 @@ public DeviceRegistrationParser(String registrationId, String customPayload, Str throw new IllegalArgumentException("Registration Id cannot be null or empty"); } - this.registrationId = registrationId; - if (customPayload != null && !customPayload.isEmpty()) + if (endorsementKey == null || endorsementKey.isEmpty()) { - this.customPayload = customPayload; + throw new IllegalArgumentException("endorsementKey cannot be null or empty"); } this.certificateSigningRequest = certificateSigningRequest; - - if (endorsementKey != null && !endorsementKey.isEmpty()) + this.registrationId = registrationId; + if (customPayload != null && !customPayload.isEmpty()) { - this.tpmAttestation = new TpmAttestation(endorsementKey, storageRootKey); + this.customPayload = customPayload; } + this.tpmAttestation = new TpmAttestation(endorsementKey, storageRootKey); } /** * Generates JSON output for this class. * Expected format : * For TPM : - *
+     * 
      *     {@code
      *     "{\"registrationId\":\"[RegistrationID value]\"," +
-            "\"tpm\":{" +
-            "\"endorsementKey\":\"[Endorsement Key value]\"," +
-            "\"storageRootKey\":\"[Storage root key value]\"" +
-            "}
-            "\"payload\":\"[Custom Data]\""
-            }"
+    "\"tpm\":{" +
+    "\"endorsementKey\":\"[Endorsement Key value]\"," +
+    "\"storageRootKey\":\"[Storage root key value]\"" +
+    "}
+    "\"payload\":\"[Custom Data]\""
+    }"
      *     }
-         * 
+ *
* For X509: *
      *     {@code
      *     "{\"registrationId\":\"[RegistrationID value]\"," +
-            }"
+    }"
      *     }
      * 
* @return A string that is JSON formatted. */ public String toJson() { + //SRS_DeviceRegistration_25_007: [ This method shall create the expected Json with the provided Registration Id, EndorsementKey and StorageRootKey. ] Gson gson = new GsonBuilder().disableHtmlEscaping().create(); return gson.toJson(this); } -} +} \ No newline at end of file diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java index 88e9303160..350db1ea13 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/RequestData.java @@ -69,6 +69,18 @@ public class RequestData this.isX509 = false; } + /** + * Constructor for Request data + * @param registrationId Registration ID value. Can be {@code null} + * @param sslContext SSL context value. Can be {@code null} + * @param sasToken SasToken value. Can be {@code null} + * @param payload Payload value. Can be {@code null} + */ + RequestData(String registrationId, SSLContext sslContext, String sasToken, String payload) + { + this(registrationId, sslContext, sasToken, payload, null); + } + /** * Constructor for Request data * @param registrationId Registration ID value. Can be {@code null} diff --git a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParserTest.java b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParserTest.java index f27b1f9689..4c60b63797 100644 --- a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParserTest.java +++ b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/parser/DeviceRegistrationParserTest.java @@ -27,8 +27,8 @@ public void constructorWithoutTPMSucceed() throws Exception { final String expectedJson = "{\"registrationId\":\"" + TEST_REGISTRATION_ID + "\"}"; + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, ""); - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", ""); assertNotNull(deviceRegistrationParser.toJson()); assertEquals(expectedJson, deviceRegistrationParser.toJson()); } @@ -37,14 +37,14 @@ public void constructorWithoutTPMSucceed() throws Exception @Test (expected = IllegalArgumentException.class) public void constructorWithoutTPMOnNullRegistrationIdThrows() throws Exception { - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(null, "", ""); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(null, ""); } @Test (expected = IllegalArgumentException.class) public void constructorWithoutTPMOnEmptyRegistrationIdThrows() throws Exception { - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser("", "", ""); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser("", ""); } //SRS_DeviceRegistration_25_006: [ The constructor shall save the provided Registration Id, EndorsementKey and StorageRootKey. ] @@ -57,11 +57,11 @@ public void constructorWithTPMSucceed() throws Exception final String sRKey = "testStorageRootKey"; final String expectedJson = "{\"registrationId\":\"testID\"," + "\"tpm\":{" + - "\"endorsementKey\":\"testEndorsementKey\"," + - "\"storageRootKey\":\"testStorageRootKey\"" + + "\"endorsementKey\":\"testEndorsementKey\"," + + "\"storageRootKey\":\"testStorageRootKey\"" + "}}"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(regID, "", "", eKey, sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(regID, "", eKey, sRKey); assertNotNull(deviceRegistrationParser.toJson()); assertEquals(expectedJson, deviceRegistrationParser.toJson()); } @@ -72,7 +72,7 @@ public void constructorWithTPMOnNullRegistrationIdThrows() throws Exception { final String eKey = "testEndorsementKey"; final String sRKey = "testStorageRootKey"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(null, "", "", eKey, sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(null, "", eKey, sRKey); } @Test (expected = IllegalArgumentException.class) @@ -80,7 +80,7 @@ public void constructorWithTPMOnEmptyRegistrationIdThrows() throws Exception { final String eKey = "testEndorsementKey"; final String sRKey = "testStorageRootKey"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser("", "", "", eKey, sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser("", "", eKey, sRKey); } //SRS_DeviceRegistration_25_004: [ The constructor shall throw IllegalArgumentException if EndorsementKey is null or empty. ] @@ -89,7 +89,7 @@ public void constructorWithTPMOnNullEkThrows() throws Exception { final String eKey = "testEndorsementKey"; final String sRKey = "testStorageRootKey"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", "", null, sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", null, sRKey); } @Test (expected = IllegalArgumentException.class) @@ -97,6 +97,6 @@ public void constructorWithTPMOnEmptyEkThrows() throws Exception { final String eKey = "testEndorsementKey"; final String sRKey = "testStorageRootKey"; - DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", "", "", sRKey); + DeviceRegistrationParser deviceRegistrationParser = new DeviceRegistrationParser(TEST_REGISTRATION_ID, "", "", sRKey); } -} +} \ No newline at end of file From a3a9629152c29172ce25d69fc3869a1862035857 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 11:21:49 -0800 Subject: [PATCH 30/33] java 8 sample --- .../com/microsoft/azure/sdk/iot/provisioning/samples/Main.java | 1 + 1 file changed, 1 insertion(+) diff --git a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java index 20ed94e840..5c42c5d984 100644 --- a/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java +++ b/provisioning/provisioning-device-client-samples/certificate-signing-sample/src/main/java/com/microsoft/azure/sdk/iot/provisioning/samples/Main.java @@ -225,6 +225,7 @@ private static String ConvertToPem(String issuedLeafCertificate) private static void WriteToFile(String path, String filename, String contents) throws IOException { + Files.deleteIfExists(Paths.get(path, filename)); BufferedWriter writer = new BufferedWriter(new FileWriter(path + "/" + filename)); writer.write(contents); writer.close(); From 177e57c1da3806f9ff5b6514d4b2951ba985daaf Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 11:29:15 -0800 Subject: [PATCH 31/33] more fixes --- .../contract/http/ContractAPIHttpTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java index fbe493eb0e..8ea7477e2d 100644 --- a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java +++ b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java @@ -252,7 +252,7 @@ public void requestNonceWithDPSTPMSucceeds() throws IOException, ProvisioningDev result = mockedTpmRegistrationResultParser; mockedTpmRegistrationResultParser.getAuthenticationKey(); result = encodeBase64String("some auth key".getBytes(StandardCharsets.UTF_8)); - new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -444,7 +444,7 @@ public void requestNonceWithDPSTPMThrowsHubExceptionWithStatusOtherThan404Throws ProvisioningDeviceClientExceptionManager.verifyHttpResponse(mockedHttpResponse); result = new ProvisioningDeviceHubException("test Exception"); mockedHttpResponse.getStatus(); - new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -492,7 +492,7 @@ public void requestNonceWithDPSTPMThrowsHubExceptionWithStatusLessThan300Throws( ProvisioningDeviceClientExceptionManager.verifyHttpResponse(mockedHttpResponse); mockedHttpResponse.getStatus(); result = 200; - new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -582,7 +582,7 @@ public void authenticateWithDPSWithOutAuthSucceeds(@Mocked DeviceRegistrationPar result = null; mockedDeviceRegistrationParser.toJson(); result = "TEST JSON"; - new DeviceRegistrationParser(anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -687,7 +687,7 @@ public void authenticateWithDPSThrowsOnSendFailure() throws IOException, Provisi result = null; ProvisioningDeviceClientExceptionManager.verifyHttpResponse(mockedHttpResponse); result = new ProvisioningDeviceHubException("test Exception"); - new DeviceRegistrationParser(anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -740,7 +740,7 @@ public void authenticateWithDPSWithAuthSucceeds() throws IOException, Provisioni result = mockedHttpResponse; mockedHttpResponse.getStatus(); result = 400; - new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; @@ -799,7 +799,7 @@ public void authenticateWithDPSWithAuthAndRetryAfterSucceeds() throws IOExceptio mockedHttpResponse.getStatus(); result = 400; - new DeviceRegistrationParser(anyString, anyString, anyString, anyString, anyString); + new DeviceRegistrationParser(anyString, anyString, anyString, anyString); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); result = "some json"; From cc051ea85bcc8510a0a3c4d6fdb937508537e542 Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 12:10:00 -0800 Subject: [PATCH 32/33] unit test fixes --- .../device/internal/contract/mqtt/ContractAPIMqttTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqttTest.java b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqttTest.java index e8498ea2d9..e89614ce32 100644 --- a/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqttTest.java +++ b/provisioning/provisioning-device-client/src/test/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqttTest.java @@ -433,7 +433,7 @@ public void authenticateWithProvisioningServiceWithX509Succeeds() throws Provisi mockedMqttConnection.isMqttConnected(); result = true; - Deencapsulation.newInstance(DeviceRegistrationParser.class, new Class[] {String.class, String.class}, TEST_REGISTRATION_ID, null); + Deencapsulation.newInstance(DeviceRegistrationParser.class, new Class[] {String.class, String.class, String.class}, TEST_REGISTRATION_ID, null, null); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); @@ -503,7 +503,7 @@ public void authenticateWithProvisioningServiceWithPayloadSucceeds() throws Prov mockedRequestData.getPayload(); result = TEST_PAYLOAD; - Deencapsulation.newInstance(DeviceRegistrationParser.class, new Class[] {String.class, String.class}, TEST_REGISTRATION_ID, TEST_PAYLOAD); + Deencapsulation.newInstance(DeviceRegistrationParser.class, new Class[] {String.class, String.class, String.class}, TEST_REGISTRATION_ID, TEST_PAYLOAD, null); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); @@ -600,7 +600,7 @@ public void authenticateWithProvisioningServiceSuccessWithSymmetricKey() throws mockedRequestData.getSslContext(); result = mockedSslContext; - Deencapsulation.newInstance(DeviceRegistrationParser.class, new Class[] {String.class, String.class}, TEST_REGISTRATION_ID, null); + Deencapsulation.newInstance(DeviceRegistrationParser.class, new Class[] {String.class, String.class, String.class}, TEST_REGISTRATION_ID, null, null); result = mockedDeviceRegistrationParser; mockedDeviceRegistrationParser.toJson(); From 42ec3cea6c669597bed690275b2b1f770e986f1a Mon Sep 17 00:00:00 2001 From: Tim Taylor Date: Tue, 3 Mar 2026 13:57:39 -0800 Subject: [PATCH 33/33] jdk15on version --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 04536f8952..12d25a44e0 100644 --- a/pom.xml +++ b/pom.xml @@ -155,6 +155,11 @@ bcpkix-jdk18on 1.83 + + org.bouncycastle + bcprov-jdk15on + 1.70 + org.apache.logging.log4j log4j-api