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

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

Open
amit12cool opened this issue Jul 27, 2024 · 7 comments
Labels

Comments

@amit12cool
Copy link

amit12cool commented Jul 27, 2024

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?

  3. 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
Copy link
Author

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

@timtay-microsoft
Copy link
Member

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

@amit12cool
Copy link
Author

amit12cool commented Jul 30, 2024

@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
Copy link

just use empty LinkedList and it will work

@amit12cool
Copy link
Author

just use empty LinkedList and it will work

it gives error
image
Although if i use intermediate it works

@Shahanshah-TA
Copy link

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
Copy link
Author

amit12cool commented Jul 30, 2024

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants