Quick Start: Securing Connection Through Certificate-Based Authentication (Java)


This section helps you to quickly get started with using certificate-based authentication to secure the connection between the device and the cloud with JDK.

Before You Start

This step assumes that you have completed the following tutorials.


Create an Edge Gateway Product

This step is almost the same as what you have done in the aforementioned tutorials. The only difference is that you need to enable Certificate Authentication when you create the edge gateway product as shown in the following screenshot.

../_images/edge_ssl.png


The inverter product does not need to have Certificate Authentication enabled because the inverter connects to the EnOS Cloud through EnOS Edge. You only need to enable the authentication for the connection between the edge and the cloud.


Create the Edge Device

Create an edge device instance named Edge01_Certificate based on the product that you just created.

../_images/edge01_certificate.png


Take note of the device triple of the Edge01_Certificate device, which will be used for creating the certificate signing request (CSR). The following device triple is an example for your reference; you will need to use your own.

  • Product Key: Et***YP6
  • Device Key: UB***rOhJD
  • Device Secret: jgWGPE***B7bShf2P5cz


Create the Sub-device

Follow Connecting a Smart Device to EnOS Cloud to create an inverter device as shown in the following figure:

../_images/INV002.png

Step 1: Create JKS file using JDK

The code snippet in this step serves the following purposes:

  1. Generating a certificate signing request (CSR) file.
  2. Calling EnOS API to apply for a certificate.
  3. Generating the JKS file needed to connect EnOS Edge to EnOS Cloud.
import com.envision.apim.poseidon.config.PConfig;
import com.envision.apim.poseidon.core.Poseidon;
import com.envisioniot.enos.connect_service.v2_1.cert.ApplyCertificateRequest;
import com.envisioniot.enos.connect_service.v2_1.cert.ApplyCertificateResponse;
import com.envisioniot.enos.connect_service.vo.DeviceIdentifier;
import sun.security.pkcs10.PKCS10;
import sun.security.x509.X500Name;

import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

public class sampleCode {
    /**
     * Configuration for generating a certificate
     */
    // ALGORITHM indicates the algorithm used by the certificate. Possible values are: RSA or EC, which stands for RSA and ECC algorithm respectively.
    public static final String ALGORITHM = "EC";
    // ISSUE_AUTHORITY indicates the certificate type. Possible values are RSA and ECC.
    public static final String ISSUE_AUTHORITY = "ECC";
    // VALID_DAY indicates the validity period of the certificate.
    public static final Integer VALID_DAY = 300;
    //IS_ECC_CONNECT is boolean, indicating whether to use ECC certificate for connection. If you are using RSA, its value should be false.
    public static boolean IS_ECC_CONNECT = true;
    /**
     * Information needed for device authentication: the device key, product key, device secret, and asset ID of the device.
     * Use either DEVICE_KEY+PRODUCT_KEY or ASSET_ID to identify the device.
     * Delete the unused parameters.
     */
    public static final String DEVICE_KEY = "yourDeviceKey";
    public static final String PRODUCT_KEY = "yourProductKey";
    public static final String DEVICE_SECRET = "yourDeviceSecret";
    public static final String ASSET_ID = "yourAssetID";


    /**
     * For RSA, SIZE should be 2048, indicating the length of the key is 2048 bit.
     * For ECC, SIZE should be 256, indicating that the prime256v1 algorithm is used.
     */
    public static final Integer SIZE = 256;
    /**
     * SIGNER_ALGORITHM indicates the signature algorithm.
     * If RSA certificate is used, its value should be SHA256withRSA.
     * If ECC certificate is used, its value should be SHA256WITHECDSA.
     */
    public static final String SIGNER_ALGORITHM = "SHA256WITHECDSA";
    /**
     * CSR applicant information:
     * COMMON_NAME: no more than 64 characters.
     * ORGANIZATION_UNIT: no more than 64 characters
     * ORGANIZATION: no more than 64 characters
     * LOCALE: no more than 64 characters
     * STATE_OR_PROVINCE_NAME: no more than 64 characters
     * COUNTRY: 2-character country code. See countrycode.org
     */
    public static final String COMMON_NAME = "doc ecc cert test";
    public static final String ORGANIZATION_UNIT = "Envision cloud";
    public static final String ORGANIZATION = "Envision";
    public static final String LOCALE = "Shanghai";
    public static final String STATE_OR_PROVINCE_NAME = "Shanghai";
    public static final String COUNTRY = "CN";
    /**
     * The access key and secret key of an application registered on EnOS, used for calling EnOS API. You can find the keys in Application Registration on EnOS Management Console.
     */
    public static final String ACCESS_KEY = "yourAccessKey";
    public static final String SECRET_KEY = "yourSecretKey";
    // The URL of the API gateway. You can find it in Help > Environment Information in EnOS Management Console.
    public static final String API_GATEWAY_URL = "http://apim-apigw-proxy";
    /**
     * Organization ID. Mouse over the OU name to obtain it in EnOS Management Console.
     */
    public static final String ORG_ID = "yourOrgID";

    //Used to save the generated public-private key pair.
    private static PrivateKey PRIVATE_KEY;

    /**
     * The following parameters are used to save the generated files:
     * SAVE_CSR_FILE_PATH: The CSR file
     * SAVE_DEVICE_CERT_FILE_PATH: The certificate requested
     * SAVE_ROOT_CERT_FILE_PATH: The root certificate
     * JKS_PASSWORD: The JKS password.
     */
    public static final String SAVE_CSR_FILE_PATH = "edge.csr";
    public static final String SAVE_DEVICE_CERT_FILE_PATH = "edge.pem";
    public static final String SAVE_ROOT_CERT_FILE_PATH = "cacert.pem";
    public static final String JKS_PASSWORD = "123456";

    public static final String PRIVATE_ENTRY_NAME = "edge";
    public static final String CA_CERT_ENTRY_NAME = "cacert";
    public static final String JKS_FILE_NAME = "edge.jks";


    //Generates the public-private key pair of the CSR file
    public static KeyPair generateKeyPair(String algorithm, int size) throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(algorithm);
        keyPairGen.initialize(size, new SecureRandom());
        return keyPairGen.generateKeyPair();
    }

    //Generates the CSR file
    public static void createCsrFile() throws CertificateException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException {
        try (ByteArrayOutputStream bs = new ByteArrayOutputStream();
             PrintStream ps = new PrintStream(bs)) {
            KeyPair keyPair = generateKeyPair(ALGORITHM, SIZE);
            PKCS10 pkcs10 = new PKCS10(keyPair.getPublic());
            Signature signature = Signature.getInstance(SIGNER_ALGORITHM);
            signature.initSign(keyPair.getPrivate());
            // Save the private key for generating JKS file
            PRIVATE_KEY = keyPair.getPrivate();
            X500Name x500Name = new X500Name(COMMON_NAME, ORGANIZATION_UNIT, ORGANIZATION, LOCALE, STATE_OR_PROVINCE_NAME, COUNTRY);
            pkcs10.encodeAndSign(x500Name, signature);
            pkcs10.print(ps);
            byte[] c = bs.toByteArray();
            String csrFileString = new String(c);
            // Save the CSR file to the specified path
            saveFile(csrFileString, SAVE_CSR_FILE_PATH);
        }
    }

    /**
     * Save a file
     */
    public static void saveFile(String fileContent, String filePath) throws IOException {
        File fp = new File(filePath);
        try (OutputStream os = new FileOutputStream(fp)) {
            os.write(fileContent.getBytes());
        }
    }


    public static void main(String[] args) throws NoSuchAlgorithmException, CertificateException, SignatureException, InvalidKeyException, IOException, KeyStoreException {
        // step1: Make sure the value of ISSUE_AUTHORITY is consistent with your certificate type.
        createCsrFile();
        // step2: Call EnOS API to obtain a certificate
        applyCertToDevice();
        // step3: Use the certificate and the root certificate to generate JKS
        createDeviceJks();
    }


    /**
    *Generate the JKS file
    *
    */
    public static void createDeviceJks() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
        // step1: Read the device certificate
        String deviceCertificate = readFile(SAVE_DEVICE_CERT_FILE_PATH);
        // ==> Convert it into X.509 certificate
        X509Certificate x509Certificate = parseCertificate(deviceCertificate);
        // step2: Read the root certificate
        String rootCaCertificate = readFile(SAVE_ROOT_CERT_FILE_PATH);
        X509Certificate rootCertificate = parseCertificate(rootCaCertificate);
        // step3: Create a key library
        KeyStore keyStore = KeyStore.getInstance("jks");
        // 初始化
        keyStore.load(null, null);
        // step4: Save the certificate and private key to the library
        addPrivateEntry(keyStore, JKS_PASSWORD, PRIVATE_KEY, PRIVATE_ENTRY_NAME, x509Certificate, rootCertificate);
        // step5: Add the root certificate to the JKS trusted list
        addTrustEntry(keyStore, CA_CERT_ENTRY_NAME, rootCertificate);
        // step6: Save the JKS file保存JKS文件
        saveKeystore(keyStore, JKS_FILE_NAME, JKS_PASSWORD);
    }

    //Save the certificate and private key to the library
    public static void addPrivateEntry(KeyStore caKs, String password, PrivateKey privateKey, String privateEntryName, X509Certificate... chainCerts) {
        try {
            if (caKs != null && chainCerts != null && privateKey != null &&
                    privateEntryName != null && !privateEntryName.isEmpty()) {
                assert chainCerts.length > 0 : "chainCert don't input!";
                KeyStore.PrivateKeyEntry skEntry = new KeyStore.PrivateKeyEntry(privateKey, chainCerts);
                caKs.setEntry(privateEntryName, skEntry, new KeyStore.PasswordProtection(password.toCharArray()));
            }
        } catch (KeyStoreException e) {
            System.err.println("when add private entry is occur error!");
        }
    }

    //Add the root certificate to the JKS trusted list
    public static void addTrustEntry(KeyStore caKs, String entryName, X509Certificate rootCert) {
        try {
            if (rootCert != null && caKs != null && entryName != null && !entryName.isEmpty()) {
                caKs.setEntry(entryName, new KeyStore.TrustedCertificateEntry(rootCert), null);
            }
        } catch (KeyStoreException e) {
            System.err.println("when add trust entry is occur error!");
        }
    }

    //Save the JKS file
    public static void saveKeystore(KeyStore keyStore, String jksFileName, String password)
            throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
        // Save as .jks file
        try (FileOutputStream fos = new FileOutputStream(jksFileName)) {
            keyStore.store(fos, password.toCharArray());
        }
    }

    //Convert into X.509 certificate
    public static X509Certificate parseCertificate(String certificateContentString) throws IOException {
        if (certificateContentString != null && !certificateContentString.trim().isEmpty()) {
            try (InputStream inputStream = new ByteArrayInputStream(certificateContentString.getBytes())) {
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                return (X509Certificate) cf.generateCertificate(inputStream);
            } catch (CertificateException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    //Bind the certificate to the device
    public static void applyCertToDevice() throws IOException {
        // Read the generated CSR file
        String certificateRequest = readFile(SAVE_CSR_FILE_PATH);
        // Configure the parameters of the device to which the certificate will be bound
        ApplyCertificateRequest applyCertificateRequest = createApplyCertParam(certificateRequest);
        // Make a request using EnOS API
        ApplyCertificateResponse certRsp =
                Poseidon.config(PConfig.init().appKey(ACCESS_KEY).appSecret(SECRET_KEY).debug())
                        .url(API_GATEWAY_URL)
                        .getResponse(applyCertificateRequest, ApplyCertificateResponse.class);
        //Obtain the certificate and the root certificate of the device
        if (certRsp.success()) {
            // Save the device certificate
            saveFile(certRsp.getData().getCert(), SAVE_DEVICE_CERT_FILE_PATH);
            // Save the root certificate
            saveFile(certRsp.getData().getCaCert(), SAVE_ROOT_CERT_FILE_PATH);
        }
    }

    //Configure the parameter of the request to bind the certificate to the device
    public static ApplyCertificateRequest createApplyCertParam(String certificateRequest) {
        ApplyCertificateRequest applyCertificateRequest = new ApplyCertificateRequest();
        applyCertificateRequest.setCsr(certificateRequest);
        /*
         * Make sure the value of ISSUE_AUTHORITY is consistent with the CSR certificate type.
         */
        applyCertificateRequest.setIssueAuthority(ISSUE_AUTHORITY);
        /*
         * Make sure the validity period of the certificate is less than the maximum validity period of the device.
         */
        applyCertificateRequest.setValidDay(VALID_DAY);
        /*
         * Device identity
         */
        DeviceIdentifier deviceIdentifier = new DeviceIdentifier();
        /*
         * Use one of the following methods to identify a device:
         * ASSET_ID
         * PRODUCT_KEY + DEVICE_KEY
         */
        applyCertificateRequest.setAssetId(ASSET_ID);
        applyCertificateRequest.setProductKey(PRODUCT_KEY);
        applyCertificateRequest.setDeviceKey(DEVICE_KEY);
        /*
         * Organization ID
         */
        applyCertificateRequest.setOrgId(ORG_ID);
        return applyCertificateRequest;
    }

    /**
     * Read CSR file
     */
    public static String readFile(String path) {
        try {
            return Files.lines(Paths.get(path), StandardCharsets.UTF_8)
                    .collect(Collectors.joining("\n"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return EMPTY_STRING;
    }
}

Step 2: Use SDK to Connect EnOS Edge to EnOS


You can use either MQTT SDK or HTTP SDK to connect.

MQTT SDK

Add the following Maven dependency to your Java project. The version of the MQTT SDK must be 2.2.5 or later.

<dependency>
    <groupId>com.envisioniot</groupId>
    <artifactId>enos-mqtt</artifactId>
    <version>2.2.16</version>
</dependency>


The code sample to connect EnOS Edge to EnOS is as follows. Replace the code snippet in Step 5 of Connecting a Non-smart Device to EnOS Cloud via Edge.

public static boolean IS_ECC_CONNECT = true; //The value of this parameter is false for RSA certificate and true for ECC certificate.
public static final String DEVICE_KEY = "yourDeviceKey";
public static final String PRODUCT_KEY = "yourProductKey";
public static final String DEVICE_SECRET = "yourDeviceSecret";
public static final String JKS_PASSWORD = "yourJksPassword";
public static final String JKS_FILE_NAME = "edge.jks";
public static final String SSL_CONNECT_URL = "ssl://MqttBrokerUrl:18883";


private static void connectEnos() {
    DefaultProfile defaultProfile = new DefaultProfile(SSL_CONNECT_URL, PRODUCT_KEY, DEVICE_KEY,DEVICE_SECRET);
    // Configure the connection
    defaultProfile.setConnectionTimeout(60).setKeepAlive(180).setAutoReconnect(false)
            .setSSLSecured(true)
            // Configure bi-directional authentication, the JKS file and password
            .setSSLJksPath(JKS_FILE_NAME, JKS_PASSWORD)
            // Configure whether to use ECC certificate to connect to EnOS
            .setEccConnect(IS_ECC_CONNECT);
    final MqttClient mqttClient = new MqttClient(defaultProfile);
    mqttClient.connect(new ConnCallback() {
        @Override
        public void connectComplete(boolean reconnect) {
            System.out.println("connect success");
        }

        @Override
        public void connectLost(Throwable cause) {
            System.out.println("connect lost");
        }

        @Override
        public void connectFailed(Throwable cause) {
            System.out.println("onConnectFailed : " + cause);
        }
    });
}

HTTP SDK

Add the following Maven dependency to your Java project. The version of the HTTP SDK must be 0.1.9 or later.

<dependency>
    <groupId>com.envisioniot</groupId>
    <artifactId>enos-http</artifactId>
    <version>0.2.1</version>
</dependency>


The code sample to connect EnOS Edge to EnOS is as follows. Replace the code snippet in Step 5 of Connecting a Non-smart Device to EnOS Cloud via Edge.

public class HttpBiDirectionalAuthenticate {
    // EnOS HTTP Broker URL, which can be obtained from Environment Information page in EnOS Console
    // ssl port 8443
    static final String BROKER_URL = "https://broker_url:8443/";

    // Device credentials, which can be obtained from Device Details page in EnOS Console
    static final String PRODUCT_KEY = "productKey";
    static final String DEVICE_KEY = "deviceKey";
    static final String DEVICE_SECRET = "deviceSecret";

    private static String jksPath = "jskPath";
    private static String jksPassword = "jskPassword";

    /** Ecc cert flag
     * if use ECC certificate, chose true
     * if use RSA certificate, chose false */
    static final boolean IS_ECC_CONNECT = false;

    public static void main(String[] args) throws EnvisionException {
        // construct a static device credential via ProductKey, DeviceKey and DeviceSecret
        StaticDeviceCredential credential = new StaticDeviceCredential(
                PRODUCT_KEY, DEVICE_KEY, DEVICE_SECRET);

        // construct a http connection
        SessionConfiguration configuration = SessionConfiguration
                .builder()
                .lifetime(30_000)
                .sslSecured(true)
                .isEccConnect(IS_ECC_CONNECT)
                .jksPath(jksPath)
                .jksPassword(jksPassword)
                .build();

        HttpConnection connection = new HttpConnection.Builder(BROKER_URL, credential)
                .sessionConfiguration(configuration)
                .build();

        MeasurepointPostRequest request = buildMeasurepointPostRequest();

        try
        {
            MeasurepointPostResponse response = connection.publish(request, null);
            System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(response));
        } catch (EnvisionException | IOException e)
        {
            e.printStackTrace();
        }
    }

    private static MeasurepointPostRequest buildMeasurepointPostRequest()
    {
        // Measurepoints are defined in ThingModel
        return MeasurepointPostRequest.builder()
                .addMeasurePoint("Int_value", 100)
                .addMeasurePoint("DI_value_01", 5)
                .build();
    }
}

Step 3: Start the Sample Program

Run the code snippet in the previous step after you have replaced the original one in Connecting a Non-smart Device to EnOS Cloud via Edge.

Step 4: Check the Device Connection Status

After you run the sample program, the edge device logs in and adds sub-devices into its topology, and proxies the sub-devices to connect to the cloud. The device connection status is shown in the following figure:

../_images/device_list.png

Step 5: Check Device Data

Go to the console, select Device Management > Device Assets, click the view icon go to Device Details, open the Measurement Points tab, select a measurement point, and click the View Data icon to check the historical data records.