Azure / azure-iot-sdk-java

A Java SDK for connecting devices to Microsoft Azure IoT services
https://azure.github.io/azure-iot-sdk-java/
Other
198 stars 236 forks source link

[Technical Question] Why there is a need of intermediate certificate to provision a device through DPS group enrollments #1795

Open amit12cool opened 1 month ago

amit12cool commented 1 month ago

I am provisioning a device through X509 certs and its strange that we need a intermediate cert to be used a signercertificate in the example given here (https://github.com/Azure/azure-iot-sdk-java/blob/main/provisioning/provisioning-device-client-samples/provisioning-X509-sample/readme.md) which mentions Obtain the certificates following instructions from [X509 Certificate Generator](https://github.com/Azure/azure-iot-sdk-java/tree/main/provisioning/provisioning-tools/provisioning-x509-cert-generator). If you are trying Group Enrollment then you will need to add signerCertificates to the Collection. You can add the signerCertificates in main() just before instantiating SecurityProviderX509Cert: signerCertificates.add("<Your Signer/intermediate Certificate Here>");

Now on the contrary I see this example also which doesn't uses the signerCertificate(Intermediate cert) and provisions a device using DPS group enrollment refer point 7 SecurityProvider securityProviderX509 = new SecurityProviderX509Cert(deviceX509Cert, deviceX509Key, null);

Now my questions are:- 1) The java sample I'm running works with intermediate certificate used as signerCertificates argument in here SecurityProviderX509Cert(leafPublicCert, leafPrivateKey, signerCertificates) so my certs are correct and DPS is also correctly configured. But when I pass null in signerCertificates the registerDevice call timeout and registration callback is never called. Which above example mentioned is correct? And why the second example doesn't work for me.

2) I have used node and c azure it sdk they don't need any intermediate certificate on a device for it to be provisioned to DPS using group enrollment. Why Java SDK needs that?

2) Also it would not a be a good solution for an Andorid app as that require the intermediatecertificate to be shipped with apk which is a security concern.

amit12cool commented 1 month ago

@Tim Taylor please provide your inputs, as this is one of the deciding factors for our software architecture

timtay-microsoft commented 1 month ago

You shouldn't need the intermediate certs here. This is probably just a documentation issue.

amit12cool commented 1 month ago

@timtay-microsoft It doesn't work without using the intermediate certificate.The callback ProvisioningDeviceClientRegistrationCallbackImpl is never invoked in the example mentioned here https://github.com/Azure/azure-iot-sdk-java/blob/main/provisioning/provisioning-device-client-samples/provisioning-X509-sample/readme.md. Can you share any reference if it works at your end?

Shahanshah-TA commented 1 month ago

just use empty LinkedList and it will work

amit12cool commented 1 month ago

just use empty LinkedList and it will work

it gives error image Although if i use intermediate it works

Shahanshah-TA commented 1 month ago

this one work for me I created certificate with this command

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/CN=deviceId"
package com.packagename;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;

import com.microsoft.azure.sdk.iot.device.*;
import com.microsoft.azure.sdk.iot.provisioning.device.*;
import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProvider;
import com.microsoft.azure.sdk.iot.provisioning.security.hsm.SecurityProviderX509Cert;
// import com.microsoft.azure.sdk.iot.device.DeviceClient;
// import com.microsoft.azure.sdk.iot.device.FileUploadCompletionNotification;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
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.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Scanner;
import android.util.Log;

@ReactModule(name = AzureProvisionWithCertificateModule.NAME)
public class AzureProvisionWithCertificateModule extends ReactContextBaseJavaModule {
  public static final String NAME = "AzureProvisionWithCertificate";

  public AzureProvisionWithCertificateModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  @NonNull
  public String getName() {
    return NAME;
  }

   public Key parsePrivateKey(String privateKeyString) throws IOException
    {
        Security.addProvider(new BouncyCastleProvider());
        PEMParser privateKeyParser = new PEMParser(new StringReader(privateKeyString));
        Object possiblePrivateKey = privateKeyParser.readObject();
        return getPrivateKey(possiblePrivateKey);
    }

 public X509Certificate parsePublicKeyCertificate(String publicKeyCertificateString) throws IOException, CertificateException
    {
        Security.addProvider(new BouncyCastleProvider());
        PemReader publicKeyCertificateReader = new PemReader(new StringReader(publicKeyCertificateString));
        PemObject possiblePublicKeyCertificate = publicKeyCertificateReader.readPemObject();
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(possiblePublicKeyCertificate.getContent()));
    }

 public Key getPrivateKey(Object possiblePrivateKey) throws IOException
    {
        if (possiblePrivateKey instanceof PEMKeyPair)
        {
            return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) possiblePrivateKey)
                .getPrivate();
        }
        else if (possiblePrivateKey instanceof PrivateKeyInfo)
        {
            return new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo) possiblePrivateKey);
        }
        else
        {
            throw new IOException("Unable to parse private key, type unknown");
        }
    }

  @ReactMethod
  public void provisionAndUploadFile(String scopeId,String registrationId,String key,String certificate, String provisionHost, String fileNameWithFolder, String modelId Promise promise) {
   try{
    String ID_SCOPE = scopeId;
    String GLOBAL_ENDPOINT = provisionHost;
    ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.HTTPS;
    String leafPublicPem = certificate;
    String leafPrivateKeyPem = key;
    String fileName = fileNameWithFolder;
    String MODEL_ID =modelId;

    Collection<String> signerCertificatePemList = new LinkedList<>();

        X509Certificate leafPublicCert = parsePublicKeyCertificate(leafPublicPem);
       Key leafPrivateKey = parsePrivateKey(leafPrivateKeyPem);
       Collection<X509Certificate> signerCertificates = new LinkedList<>();

         for (String signerCertificatePem : signerCertificatePemList)
        {
            signerCertificates.add(parsePublicKeyCertificate(signerCertificatePem));
        }
      SecurityProvider securityProviderX509 = new SecurityProviderX509Cert(leafPublicCert, leafPrivateKey, signerCertificates);
        ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.create(
            GLOBAL_ENDPOINT,
            ID_SCOPE,
            PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL,
            securityProviderX509);
               AdditionalData additionalData = new AdditionalData();
        additionalData.setProvisioningPayload(String.format("{\"modelId\": \"%s\"}", MODEL_ID)); 

        ProvisioningDeviceClientRegistrationResult provisioningDeviceClientRegistrationResult = provisioningDeviceClient.registerDeviceSync(additionalData);
        provisioningDeviceClient.close();

         if (provisioningDeviceClientRegistrationResult.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED)
        {
            // connect to iothub
            String iotHubUri = provisioningDeviceClientRegistrationResult.getIothubUri();
            String deviceId = provisioningDeviceClientRegistrationResult.getDeviceId();
            DeviceClient deviceClient = new DeviceClient(iotHubUri, deviceId, securityProviderX509, IotHubClientProtocol.MQTT);
            deviceClient.open(false);
            Log.d("AzureProvisionWithCertificateModule", new FileUploadSasUriRequest(fileName).toString());
           FileUploadSasUriResponse sasUriResponse = deviceClient.getFileUploadSasUri(new FileUploadSasUriRequest(fileName));
           String blobUri = sasUriResponse.getBlobUri().toString();
           String correlationId = sasUriResponse.getCorrelationId().toString();
           String result =   blobUri + "::correlationId::" + correlationId + "::correlationId::" + iotHubUri;
           deviceClient.close();
      promise.resolve(result);
        }
      }catch(Exception e){
        promise.reject("Error",e);
      }

  }

}
amit12cool commented 1 month ago

this one work for me I created certificate with this command

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/CN=deviceId"
package com.packagename;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;

import com.microsoft.azure.sdk.iot.device.*;
import com.microsoft.azure.sdk.iot.provisioning.device.*;
import com.microsoft.azure.sdk.iot.provisioning.security.SecurityProvider;
import com.microsoft.azure.sdk.iot.provisioning.security.hsm.SecurityProviderX509Cert;
// import com.microsoft.azure.sdk.iot.device.DeviceClient;
// import com.microsoft.azure.sdk.iot.device.FileUploadCompletionNotification;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
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.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Scanner;
import android.util.Log;

@ReactModule(name = AzureProvisionWithCertificateModule.NAME)
public class AzureProvisionWithCertificateModule extends ReactContextBaseJavaModule {
  public static final String NAME = "AzureProvisionWithCertificate";

  public AzureProvisionWithCertificateModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  @NonNull
  public String getName() {
    return NAME;
  }

   public Key parsePrivateKey(String privateKeyString) throws IOException
    {
        Security.addProvider(new BouncyCastleProvider());
        PEMParser privateKeyParser = new PEMParser(new StringReader(privateKeyString));
        Object possiblePrivateKey = privateKeyParser.readObject();
        return getPrivateKey(possiblePrivateKey);
    }

 public X509Certificate parsePublicKeyCertificate(String publicKeyCertificateString) throws IOException, CertificateException
    {
        Security.addProvider(new BouncyCastleProvider());
        PemReader publicKeyCertificateReader = new PemReader(new StringReader(publicKeyCertificateString));
        PemObject possiblePublicKeyCertificate = publicKeyCertificateReader.readPemObject();
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(possiblePublicKeyCertificate.getContent()));
    }

 public Key getPrivateKey(Object possiblePrivateKey) throws IOException
    {
        if (possiblePrivateKey instanceof PEMKeyPair)
        {
            return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) possiblePrivateKey)
                .getPrivate();
        }
        else if (possiblePrivateKey instanceof PrivateKeyInfo)
        {
            return new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo) possiblePrivateKey);
        }
        else
        {
            throw new IOException("Unable to parse private key, type unknown");
        }
    }

  @ReactMethod
  public void provisionAndUploadFile(String scopeId,String registrationId,String key,String certificate, String provisionHost, String fileNameWithFolder, String modelId Promise promise) {
   try{
    String ID_SCOPE = scopeId;
    String GLOBAL_ENDPOINT = provisionHost;
    ProvisioningDeviceClientTransportProtocol PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL = ProvisioningDeviceClientTransportProtocol.HTTPS;
    String leafPublicPem = certificate;
    String leafPrivateKeyPem = key;
    String fileName = fileNameWithFolder;
    String MODEL_ID =modelId;

    Collection<String> signerCertificatePemList = new LinkedList<>();

        X509Certificate leafPublicCert = parsePublicKeyCertificate(leafPublicPem);
       Key leafPrivateKey = parsePrivateKey(leafPrivateKeyPem);
       Collection<X509Certificate> signerCertificates = new LinkedList<>();

         for (String signerCertificatePem : signerCertificatePemList)
        {
            signerCertificates.add(parsePublicKeyCertificate(signerCertificatePem));
        }
      SecurityProvider securityProviderX509 = new SecurityProviderX509Cert(leafPublicCert, leafPrivateKey, signerCertificates);
        ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.create(
            GLOBAL_ENDPOINT,
            ID_SCOPE,
            PROVISIONING_DEVICE_CLIENT_TRANSPORT_PROTOCOL,
            securityProviderX509);
               AdditionalData additionalData = new AdditionalData();
        additionalData.setProvisioningPayload(String.format("{\"modelId\": \"%s\"}", MODEL_ID)); 

        ProvisioningDeviceClientRegistrationResult provisioningDeviceClientRegistrationResult = provisioningDeviceClient.registerDeviceSync(additionalData);
        provisioningDeviceClient.close();

         if (provisioningDeviceClientRegistrationResult.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED)
        {
            // connect to iothub
            String iotHubUri = provisioningDeviceClientRegistrationResult.getIothubUri();
            String deviceId = provisioningDeviceClientRegistrationResult.getDeviceId();
            DeviceClient deviceClient = new DeviceClient(iotHubUri, deviceId, securityProviderX509, IotHubClientProtocol.MQTT);
            deviceClient.open(false);
            Log.d("AzureProvisionWithCertificateModule", new FileUploadSasUriRequest(fileName).toString());
           FileUploadSasUriResponse sasUriResponse = deviceClient.getFileUploadSasUri(new FileUploadSasUriRequest(fileName));
           String blobUri = sasUriResponse.getBlobUri().toString();
           String correlationId = sasUriResponse.getCorrelationId().toString();
           String result =   blobUri + "::correlationId::" + correlationId + "::correlationId::" + iotHubUri;
           deviceClient.close();
      promise.resolve(result);
        }
      }catch(Exception e){
        promise.reject("Error",e);
      }

  }

}

I followed these steps https://learn.microsoft.com/en-us/azure/iot-dps/tutorial-custom-hsm-enrollment-group-x509?tabs=windows&pivots=programming-language-ansi-c#create-an-x509-certificate-chain. I think the cert chain is correct.

With your cert I get image Are you using DPS group enrollment? If so, please use an intermediate cert on DPS and please let me know in that case your example works or not?

@timtay-microsoft Do you mean we don't need intermediate cert if we are using DPS group enrollment with intermediate certificate uploaded to DPS as root cert?