Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b845337
dps
timtay-microsoft Feb 23, 2026
bf00e62
iot hub
timtay-microsoft Feb 23, 2026
948360e
sample so far
timtay-microsoft Feb 23, 2026
3f266d9
sample so far
timtay-microsoft Feb 24, 2026
3a71601
swap from callback to future?
timtay-microsoft Feb 24, 2026
cbf94d7
more
timtay-microsoft Feb 24, 2026
6e237e0
asdf
timtay-microsoft Feb 24, 2026
435441c
keep
timtay-microsoft Feb 26, 2026
7abd57f
more
timtay-microsoft Feb 26, 2026
86a7d17
keep
timtay-microsoft Feb 27, 2026
186a24a
It works
timtay-microsoft Feb 27, 2026
09b5248
Its fine
timtay-microsoft Mar 2, 2026
649e2be
todo
timtay-microsoft Mar 2, 2026
871bf2d
documentation
timtay-microsoft Mar 2, 2026
ef6bbde
bug fix
timtay-microsoft Mar 2, 2026
6b9f43c
another bug fix
timtay-microsoft Mar 2, 2026
0b7af30
async
timtay-microsoft Mar 2, 2026
c243d6b
different CSR provider
timtay-microsoft Mar 3, 2026
693f3d0
factor out
timtay-microsoft Mar 3, 2026
09e2370
one more
timtay-microsoft Mar 3, 2026
eebd7b7
ECC and RSA work!
timtay-microsoft Mar 3, 2026
93c43ba
readme path
timtay-microsoft Mar 3, 2026
a63909b
more deps
timtay-microsoft Mar 3, 2026
c772877
Javadoc fixes
timtay-microsoft Mar 3, 2026
114dd05
component governance
timtay-microsoft Mar 3, 2026
3dd7ab1
unit test
timtay-microsoft Mar 3, 2026
7d15d0e
java 8 sample
timtay-microsoft Mar 3, 2026
ff4af3b
java 8 sample
timtay-microsoft Mar 3, 2026
1c0895b
revert some changes
timtay-microsoft Mar 3, 2026
a3a9629
java 8 sample
timtay-microsoft Mar 3, 2026
177e57c
more fixes
timtay-microsoft Mar 3, 2026
cc051ea
unit test fixes
timtay-microsoft Mar 3, 2026
42ec3ce
jdk15on version
timtay-microsoft Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions iot-e2e-tests/common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,7 @@
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@

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;
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.concurrent.CompletableFuture;

/**
* <p>
Expand Down Expand Up @@ -271,6 +275,95 @@ public boolean isMultiplexed()
return this.isMultiplexed;
}

/**
* <p>
* Send a certificate signing certificateSigningRequest to IoT hub and receive the signed certificates back.
* </p>
* <p>
* This is a multi-step process:
* - 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.
* </p>
* <p>
* To instead be notified via futures, see {@link #sendCertificateSigningRequestAsync(IotHubCertificateSigningRequest)}
* </p>
* @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 sendCertificateSigningRequestAsync(IotHubCertificateSigningRequest certificateSigningRequest, 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).
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(message, null, null, this.config.getDeviceId());
}

/**
* <p>
* Send a certificate signing certificateSigningRequest to IoT hub and receive the signed certificates back.
* </p>
* <p>
* This is a multi-step process:
* - 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.
* </p>
* <p>
* To instead be notified via callback, see {@link #sendCertificateSigningRequestAsync(IotHubCertificateSigningRequest,IotHubCertificateSigningResponseCallback)}
* </p>
* @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 sendCertificateSigningRequestAsync(IotHubCertificateSigningRequest certificateSigningRequest)
{
IotHubCertificateSigningResponseFutures responses = new IotHubCertificateSigningResponseFutures();
CompletableFuture<IotHubCertificateSigningRequestAccepted> acceptedFuture = new CompletableFuture<>();
CompletableFuture<IotHubCertificateSigningResponse> responseFuture = new CompletableFuture<>();

responses.setOnCertificateSigningRequestAccepted(acceptedFuture);
responses.setOnCertificateSigningCompleted(responseFuture);

this.sendCertificateSigningRequestAsync(certificateSigningRequest, 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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public enum MessageType
UNKNOWN,
DEVICE_TELEMETRY,
DEVICE_METHODS,
DEVICE_TWIN
DEVICE_TWIN,
CERTIFICATE_SIGNING
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.microsoft.azure.sdk.iot.device.certificatesigning;

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;

/**
* The error reported by IoT hub if certificate signing fails.
*/
public class IotHubCertificateSigningError
{
/* 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;

/**
* 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;

@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;

public IotHubCertificateSigningError(String json) throws IllegalArgumentException
{
Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create();
IotHubCertificateSigningError deserialized;

ParserUtility.validateStringUTF8(json);
try
{
deserialized = gson.fromJson(json, IotHubCertificateSigningError.class);
}
catch (JsonSyntaxException malformed)
{
throw new IllegalArgumentException("Malformed json", malformed);
}

this.errorCodeString = deserialized.errorCodeString;
this.errorCode = IotHubCertificateSigningErrorCode.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
IotHubCertificateSigningError()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.microsoft.azure.sdk.iot.device.certificatesigning;

public enum IotHubCertificateSigningErrorCode
{
/**
* This error code should never happen since this SDK hardcodes the protocol version to a valid version
*/
InvalidProtocolVersion,

OperationNotAvailableInCurrentTier,
PreconditionFailed,
CredentialManagementPreconditionFailed,
ThrottleBacklogLimitExceeded,
ThrottlingBacklogTimeout,
CredentialOperationPending,
CredentialOperationActive,
CredentialOperationFailed,
DeviceNotFound,
DeviceUnavailable,
ServerError,
ServiceUnavailable,
Unknown;

public static IotHubCertificateSigningErrorCode 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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.microsoft.azure.sdk.iot.device.certificatesigning;

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;

/**
* <p>
* Additional context for why a certificate signing operation failed.
* </p>
* <p>
* 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.
* </p>
*/
public class IotHubCertificateSigningErrorInfo
{

/* 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;

/**
* 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;

@Getter
private transient Date operationExpires;

public IotHubCertificateSigningErrorInfo(String json) throws IllegalArgumentException
{
Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create();
IotHubCertificateSigningErrorInfo deserialized;

ParserUtility.validateStringUTF8(json);
try
{
deserialized = gson.fromJson(json, IotHubCertificateSigningErrorInfo.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
IotHubCertificateSigningErrorInfo()
{
}
}
Loading
Loading