Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Npatilsen/dps cert march #1687

Draft
wants to merge 16 commits into
base: preview
Choose a base branch
from
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@
import com.microsoft.azure.sdk.iot.provisioning.device.AdditionalData;
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.service;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProviderSymmetricKey;
import com.microsoft.azure.sdk.iot.provisioning.security.hsm.SecurityProviderX509Cert;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jcajce.provider.asymmetric.X509;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.*;
import java.security.Key;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
Expand All @@ -37,18 +39,25 @@
@SuppressWarnings("CommentedOutCode") // Ignored in samples as we use these comments to show other options.
public class ProvisioningCertificateIssuanceSample
{
private static final String idScope = "[Your ID scope here]";
private static final String globalEndpoint = "[Your Provisioning Service Global Endpoint here]";
private static final ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.HTTPS;
private static final String idScope = "";
private static final String globalEndpoint = "";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static final String globalEndpoint = "";
private static final String globalEndpoint = "global.azure-devices-provisioning.net";

This default value works for everyone who isn't on a govt. or private cloud instance, so we may as well fill it in for them


// For the sake of security, you shouldn't save keys into String variables as that places them in heap memory. For the sake
// of simplicity within this sample, though, we will save it as a string. Typically this key would be loaded as byte[] so that
// it can be removed from stack memory.
private static final String SYMMETRIC_KEY = "";

// The registration Id to provision the device to. When creating an individual enrollment prior to running this sample, you choose this value.
private static final String REGISTRATION_ID = "";

private static final String DPS_CLIENT_CERTIFICATE_FOLDER = ".\\DpsClientCertificates";

//private static final ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.HTTPS;
//private static final ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.AMQPS;
//private static final ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.AMQPS_WS;
//private static final ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.MQTT;
private static final ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.MQTT;
//private static final ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.MQTT_WS;
private static final int MAX_TIME_TO_WAIT_FOR_REGISTRATION = 10000; // in milli seconds
private static final String leafPublicPem = "<Your Public Leaf Certificate Here>";
private static final String leafPrivateKeyPem = "<Your Leaf Key Here>";

private static final Collection<String> signerCertificatePemList = new LinkedList<>();

static class ProvisioningStatus
{
Expand Down Expand Up @@ -87,31 +96,37 @@ public static void main(String[] args) throws Exception
{
System.out.println("Starting...");
System.out.println("Beginning setup.");
SecurityProviderSymmetricKey securityProviderSymmetricKey = new SecurityProviderSymmetricKey(SYMMETRIC_KEY.getBytes(StandardCharsets.UTF_8), REGISTRATION_ID);
ProvisioningDeviceClient provisioningDeviceClient = null;
DeviceClient deviceClient = null;
try
{
ProvisioningStatus provisioningStatus = new ProvisioningStatus();

// For group enrollment uncomment this line
//signerCertificatePemList.add("<Your Signer/intermediate Certificate Here>");

X509Certificate leafPublicCert = parsePublicKeyCertificate(leafPublicPem);
Key leafPrivateKey = parsePrivateKey(leafPrivateKeyPem);
Collection<X509Certificate> signerCertificates = new LinkedList<>();
for (String signerCertificatePem : signerCertificatePemList)
File csrDirectory = new File(DPS_CLIENT_CERTIFICATE_FOLDER);
if (csrDirectory.mkdir())
{
signerCertificates.add(parsePublicKeyCertificate(signerCertificatePem));
System.out.println("Certificate directory created.");
}
else
{
System.out.println("Failed to create directory for certificates.");
throw new IOException();
Comment on lines +113 to +114
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
System.out.println("Failed to create directory for certificates.");
throw new IOException();
throw new IOException("Failed to create directory for certificates.");

}

SecurityProvider securityProviderX509 = new SecurityProviderX509Cert(leafPublicCert, leafPrivateKey, signerCertificates);
provisioningDeviceClient = ProvisioningDeviceClient.create(globalEndpoint, idScope, PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL,
securityProviderX509);
securityProviderSymmetricKey);

AdditionalData additionalData = new AdditionalData
{
operationalCertificateRequest = "someString",
};
GenerateCertificateSigningRequestFiles(securityProviderSymmetricKey.getRegistrationId(), csrDirectory);
String csrFile = String.format("%s\\%s.csr", csrDirectory.getAbsolutePath().toString(), securityProviderSymmetricKey.getRegistrationId());

// Read certificate signing request
Scanner sc = new Scanner(new File(csrFile));
String certificateSigningRequest = sc.next();

// Inform provisioning device client about the CSR
AdditionalData additionalData = new AdditionalData();
additionalData.setOperationalCertificateRequest(certificateSigningRequest);

provisioningDeviceClient.registerDevice(new ProvisioningDeviceClientRegistrationCallbackImpl(), provisioningStatus, additionalData);

Expand All @@ -130,6 +145,33 @@ public static void main(String[] args) throws Exception
Thread.sleep(MAX_TIME_TO_WAIT_FOR_REGISTRATION);
}

String issuedClientCertificate = provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getIssuedClientCertificate();

if (issuedClientCertificate == null)
{
System.out.println("Expected client certificate was not returned by DPS, exiting sample.");
return;
}

// Write issued certificate to disk
String cerFile = String.format("%s\\%s.cer", csrDirectory.getAbsolutePath(), provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getRegistrationId());
try
{
BufferedWriter writer = new BufferedWriter(new FileWriter(cerFile));
writer.write(issuedClientCertificate);
}
catch (IOException ex)
{
System.out.println("Encountered an exception writing issued client certificate to disk: " + ex.getMessage());
return;
}

System.out.println("Creatign an X509 certifiate from the issued client certificate...");
GeneratePfxFromPublicCertificateAndPrivateKey(securityProviderSymmetricKey.getRegistrationId(), csrDirectory);
X509Certificate clientCertificate = CreateX509CertificateFromPfxFile(securityProviderSymmetricKey.getRegistrationId(), csrDirectory);
System.out.println("COMPLETED CSR GENERATION!");
//SecurityProviderX509Cert auth = new SecurityProviderX509Cert(clientCertificate); // TODO

if (provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED)
{
System.out.println("IotHUb Uri : " + provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getIothubUri());
Expand All @@ -140,7 +182,8 @@ public static void main(String[] args) throws Exception
String deviceId = provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getDeviceId();
try
{
deviceClient = new DeviceClient(iotHubUri, deviceId, securityProviderX509, IotHubClientProtocol.MQTT);
// TODO -- replace with generated x509 security provider
deviceClient = new DeviceClient(iotHubUri, deviceId, securityProviderSymmetricKey, IotHubClientProtocol.MQTT);
deviceClient.open(false);
Message messageToSendFromDeviceToHub = new Message("Whatever message you would like to send");

Expand Down Expand Up @@ -182,6 +225,76 @@ public static void main(String[] args) throws Exception
}
}

private static void GenerateCertificateSigningRequestFiles(String subject, File certificateDirectory)
{
// TODO -- linux file path formatting
String path = certificateDirectory.getAbsolutePath();
String keyfile = String.format("%s\\%s.key", path, subject);
String csrFile = String.format("%s\\%s.csr", path, subject);

System.out.println(String.format("Generating private key for the certificate with subject %s", subject));
String keyGen = String.format("openssl genpkey -out \"%s\" -algorithm RSA -pkeyopt rsa_keygen_bits:2048", keyfile);

try
{
Process keyGenProcess = Runtime.getRuntime().exec(keyGen);
keyGenProcess.waitFor();
}
catch (IOException ex)
{
System.out.println("An exception was thrown while running an openssl command: " + ex.getMessage());
}
catch (InterruptedException ex)
{
System.out.println("The openssl command was interuppted.");
}

System.out.println(String.format("Generating CSR for certificate with subject %s", subject));
String csrGen = String.format("openssl req -new -subj /CN=%s -key \"%s\" -out \"%s\"", subject, keyfile, csrFile);
try
{
Process csrGenProcess = Runtime.getRuntime().exec(csrGen);
csrGenProcess.waitFor();
}
catch (IOException ex)
{
System.out.println("An exception was thrown while running an openssl command: " + ex.getMessage());
}
catch (InterruptedException ex)
{
System.out.println("The openssl command was interuppted.");
}
}

private static void GeneratePfxFromPublicCertificateAndPrivateKey(String subject, File certificateDirectory)
{
// TODO -- linux file formatting
String keyFile = String.format("%s\\%s.key", certificateDirectory.getAbsolutePath(), subject);
String cerFile = String.format("%s\\%s.cer", certificateDirectory.getAbsolutePath(), subject);
String pfxFile = String.format("%s\\%s.pfx", certificateDirectory.getAbsolutePath(), subject);

System.out.println("Generating .pfx file...");
String pfxGen = String.format("openssl pkcs12 -export -in \"%s\" -inkey \"%s\" -out \"%s\" -passout pass:", cerFile, keyFile, pfxFile);

try
{
Process pfxGenProcess = Runtime.getRuntime().exec(pfxGen);
}
catch (IOException ex)
{
System.out.println("An exception was thrown while running an openssl command: " + ex.getMessage());
}
}

private static X509Certificate CreateX509CertificateFromPfxFile(String subject, File certificateDirectory) throws FileNotFoundException, CertificateException
{
// TODO -- linux file formatting
String pfxFile = String.format("%s\\%s.pfx", certificateDirectory.getAbsolutePath(), subject);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
return (X509Certificate) certFactory.generateCertificate(new FileInputStream(pfxFile));
}


private static Key parsePrivateKey(String privateKeyString) throws IOException
{
Security.addProvider(new BouncyCastleProvider());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ public class AdditionalData
@Setter
private String provisioningPayload;

/**
* The PEM encoded operational client certificate request that DPS will send to a linked certificate authority which
* signs and returns an X.509 certificate that the IoT device can use to authenticate itself with an IoT Hub.
*/
@Getter
@Setter
public String operationalCertificateRequest;
patilsnr marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The choice of variable name on the .NET SDK (we'd like to keep the variable name same across all languages so as to keep the user experience similar): https://github.com/Azure/azure-iot-sdk-csharp/blob/c3008205c68de5b8fede085533048987e15af1c9/provisioning/device/src/ProvisioningRegistrationAdditionalData.cs#L23

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class ProvisioningDeviceClientRegistrationResult
@Getter
protected String provisioningPayload;

/*
/**
* The client certificate used by IoT Hub to authenticate a device.
*/
@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public final class ProvisioningDeviceClientConfig
@Setter
private String payload;

/*
/**
* Certificate request that DPS sends to its CA to sign and return an X509 certificate that is then linked to a device.
*/
@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "2023-05-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();

Expand Down
Loading