java.io.FileNotFoundException для основного кода Cloud Iot

В настоящее время я работаю над программой на Android Things для подключения к Google Cloud IoT Core. Раньше я использовал код maven, предоставленный Google, и модифицировал его для Gradle (со всем импортом и прочим). После всех проверок всякий раз, когда я пытаюсь запустить программу на Raspberry Pi3 под управлением Android Things, она продолжает выдавать эту ошибку.

W/System.err: java.io.FileNotFoundException: com/example/adityaprakash/test/rsa_private.pem (No such file or directory)

сообщая мне, что файл закрытого ключа, который я должен использовать для JWT, не существует, несмотря на то, что он существует, и я указал путь к файлу pem. Вот мои коды Java

package com.example.adityaprakash.test;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        Log.i("#########","######");
        MqttExample mqtt = new MqttExample();
        try {
            mqtt.Start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MqttExample.java

package com.example.adityaprakash.test;
// [END cloudiotcore_mqtt_imports]
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import org.joda.time.DateTime;

import java.io.BufferedReader;
import java.io.FileReader;

import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;

import android.util.Base64;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class MqttExample {

    // [START cloudiotcore_mqtt_createjwt]
    /** Create a Cloud IoT Core JWT for the given project id, signed with the given RSA key. */
    public static String createJwtRsa(String projectId, String privateKeyFile) throws Exception {
        DateTime now = new DateTime();

        String strKeyPEM = "";
        BufferedReader br = new BufferedReader(new FileReader(privateKeyFile));
        String line;
        while ((line = br.readLine()) != null) {
            strKeyPEM += line + "\n";
        }
        br.close();
        // Create a JWT to authenticate this device. The device will be disconnected after the token
        // expires, and will have to reconnect with a new token. The audience field should always be set
        // to the GCP project id.
        JwtBuilder jwtBuilder =
                Jwts.builder()
                        .setIssuedAt(now.toDate())
                        .setExpiration(now.plusMinutes(20).toDate())
                        .setAudience(projectId);
        String privateKeyPEM = strKeyPEM;
        privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", "");
        privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");
        byte[] encoded = Base64.decode(privateKeyPEM,Base64.DEFAULT);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
        KeyFactory kf = KeyFactory.getInstance("RSA");

        return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
    }


    /** Parse arguments, configure MQTT, and publish messages. */
    public void Start() throws Exception {
        // [START cloudiotcore_mqtt_configuremqtt]
        MqttExampleOptions options = MqttExampleOptions.values();
        if (options == null) {
            // Could not parse.
            System.exit(1);
        }
        // Build the connection string for Google's Cloud IoT Core MQTT server. Only SSL
        // connections are accepted. For server authentication, the JVM's root certificates
        // are used.
        final String mqttServerAddress =
                String.format("ssl://%s:%s", options.mqttBridgeHostname, options.mqttBridgePort);

        // Create our MQTT client. The mqttClientId is a unique string that identifies this device. For
        // Google Cloud IoT Core, it must be in the format below.
        final String mqttClientId =
                String.format(
                        "projects/%s/locations/%s/registries/%s/devices/%s",
                        options.projectId, options.cloudRegion, options.registryId, options.deviceId);

        MqttConnectOptions connectOptions = new MqttConnectOptions();
        // Note that the the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we
        // explictly set this. If you don't set MQTT version, the server will immediately close its
        // connection to your device.
        connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);

        // With Google Cloud IoT Core, the username field is ignored, however it must be set for the
        // Paho client library to send the password field. The password field is used to transmit a JWT
        // to authorize the device.
        connectOptions.setUserName("unused");

        System.out.println(options.algorithm);
        if (options.algorithm.equals("RS256")) {
            connectOptions.setPassword(
                    createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
        }else {
            throw new IllegalArgumentException(
                    "Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'.");
        }
        // [END cloudiotcore_mqtt_configuremqtt]

        // [START cloudiotcore_mqtt_publish]
        // Create a client, and connect to the Google MQTT bridge.
        MqttClient client = new MqttClient(mqttServerAddress, mqttClientId, new MemoryPersistence());
        try {
            client.connect(connectOptions);

            // Publish to the events or state topic based on the flag.
            String subTopic = options.messageType.equals("event") ? "events" : options.messageType;

            // The MQTT topic that this device will publish telemetry data to. The MQTT topic name is
            // required to be in the format below. Note that this is not the same as the device registry's
            // Cloud Pub/Sub topic.
            String mqttTopic = String.format("/devices/%s/%s", options.deviceId, subTopic);

            // Publish numMessages messages to the MQTT bridge, at a rate of 1 per second.
            for (int i = 1; i <= options.numMessages; ++i) {
                String payload = String.format("%s/%s-payload number-%d", options.registryId, options.deviceId, i);
                System.out.format(
                        "Publishing %s message %d/%d: '%s'\n",
                        options.messageType, i, options.numMessages, payload);

                // Publish "payload" to the MQTT topic. qos=1 means at least once delivery. Cloud IoT Core
                // also supports qos=0 for at most once delivery.
                MqttMessage message = new MqttMessage(payload.getBytes());
                message.setQos(1);
                client.publish(mqttTopic, message);

                if (options.messageType.equals("event")) {
                    // Send telemetry events every second
                    Thread.sleep(1000);
                }
                else {
                    // Note: Update Device state less frequently than with telemetry events
                    Thread.sleep(5000);
                }
            }
        } finally {
            // Disconnect the client and finish the run.
            client.disconnect();
        }
        System.out.println("Finished loop successfully. Goodbye!");
        // [END cloudiotcore_mqtt_publish]
    }

}

и код MqttExampleOptions.java:

package com.example.adityaprakash.test;

public class MqttExampleOptions {
    String projectId;
    String registryId;
    String deviceId;
    String privateKeyFile;
    String algorithm;
    String cloudRegion;
    int numMessages;
    String mqttBridgeHostname;
    short mqttBridgePort;
    String messageType;

    /** Construct an MqttExampleOptions class. */
    public static MqttExampleOptions values() {

        try {

            MqttExampleOptions res = new MqttExampleOptions();

            res.projectId = "_";
            res.registryId = "_";
            res.deviceId = "_";
            res.privateKeyFile = "com/example/adityaprakash/test/rsa_private.pem";
            res.algorithm = "RS256";
            res.cloudRegion = "asia-east1";
            res.numMessages = 100;
            res.mqttBridgeHostname = "mqtt.googleapis.com";
            res.mqttBridgePort = 8883;
            res.messageType = "event";
            return res;

        } catch (Exception e) {
            System.err.println(e.getMessage());
            return null;
        }
    }
}

Пожалуйста, кто-нибудь может дать решение этой проблемы.
P.S. Я знаю, что код выглядит совершенно дерьмовым. У меня нет опыта программирования под Android, поэтому, пожалуйста, оставьте его.


person Aditya    schedule 14.11.2017    source источник
comment
Наиболее возможная причина в том, что ваш файл существует, но у него нет разрешения на чтение, предоставьте разрешение на ваш файл .pem.   -  person Nitin Dhomse    schedule 14.11.2017


Ответы (3)


Пример, который вы используете, не предназначен для Android.

res.privateKeyFile = "com/example/adityaprakash/test/rsa_private.pem";

Не будет относиться к одному и тому же каталогу в файловой системе Android.


Я написал AndroidThings объяснение того, как общаться с Cloud IoT Core, здесь: http://blog.blundellapps.co.uk/tut-google-cloud-iot-core-mqtt-on-android/

Вы можете настроить связь следующим образом (с вашим файлом pem в каталоге /raw)

// Setup the communication with your Google IoT Core details
        communicator = new IotCoreCommunicator.Builder()
                .withContext(this)
                .withCloudRegion("your-region") // ex: europe-west1
                .withProjectId("your-project-id")   // ex: supercoolproject23236
                .withRegistryId("your-registry-id") // ex: my-devices
                .withDeviceId("a-device-id") // ex: my-test-raspberry-pi
                .withPrivateKeyRawFileId(R.raw.rsa_private)
                .build();

Исходный код находится здесь: https://github.com/blundell/CloudIoTCoreMQTTExample


Обратите внимание, что приведенного выше достаточно для безопасной среды или для проверки работоспособности от начала до конца. Однако, если вы хотите выпустить производственное устройство IoT, вам следует встроить PEM в ПЗУ и использовать доступ к частному хранилищу файлов. https://developer.android.com/training/articles/keystore.html

Пример этого можно найти здесь: https://github.com/androidthings/sensorhub-cloud-iot

Конкретно этот класс:

Отн. ="nofollow noreferrer">https://github.com/androidthings/sensorhub-cloud-iot/blob/e50bde0100fa81818ebbadb54561b3b68ccb64b8/app/src/main/java/com/example/androidthings/sensorhub/cloud/cloudiot/MqttAuthentication.java< /а>

Затем вы можете сгенерировать и использовать PEM на устройстве:

  public Certificate getCertificate() {
    KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
    ks.load(null);

    certificate = ks.getCertificate("Cloud IoT Authentication");
    if (certificate == null) {
      Log.w(TAG, "No IoT Auth Certificate found, generating new cert");
      generateAuthenticationKey();
      certificate = ks.getCertificate(keyAlias);
    }
    Log.i(TAG, "loaded certificate: " + keyAlias); 
 }

а также

    private void generateAuthenticationKey() throws GeneralSecurityException {
      KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
      kpg.initialize(new KeyGenParameterSpec.Builder("Cloud IoT Authentication",KeyProperties.PURPOSE_SIGN)
       .setKeySize(2048)
       .setCertificateSubject(new X500Principal("CN=unused"))
       .setDigests(KeyProperties.DIGEST_SHA256)
     .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
       .build());

      kpg.generateKeyPair();
   }
person Blundell    schedule 15.11.2017

Я почти уверен, что вы неправильно выполняете файловый ввод-вывод. Ваш файл "com/example/adityaprakash/test/rsa_private.pem" не соответствует фактическому пути к файлу на устройстве. Расположение файлов на устройстве может отличаться от вашего проекта. Вам нужно будет определить, где на устройстве на самом деле находится ваш файл.

person Nick Felker    schedule 14.11.2017
comment
Если я использую adb push, чтобы поместить файл rsa_private_pkcs8 в любой каталог на SD-карте Android Things и указать его полный путь в переменной, тогда он должен работать правильно? - person Aditya; 16.11.2017
comment
Да, путь к файлу должен соответствовать используемой вами строке. - person Nick Felker; 17.11.2017

В AndroidThings проще предоставить учетные данные для аутентификации в ресурсе Android. См. мой форк примера WeatherStation, чтобы узнать, как это работает.

Сначала скопируйте файл закрытого ключа (например, rsa_private_pkcs8) в app/src/main/res/raw/privatekey.txt.

Затем вы можете загрузить ключ, используемый для расчета вашего JWT, как:

Context mContext;
int resIdPk = getResources().getIdentifier("privatekey", "raw", getPackageName());    

...

InputStream privateKey = mContext.getResources().openRawResource(resIdPk);
byte[] keyBytes = inputStreamToBytes(privateKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("EC");

И последнее замечание: похоже, вы ссылаетесь на файл, который не в формате pkcs8, что вызовет проблемы с Java. Обязательно используйте ключ, упакованный в PKCS8, при открытии учетных данных на Android (Java).

person class    schedule 14.11.2017