使用Paho库连接AWS

Paho是Eclipse提供的,一个用Java编写的MQTT客户端的库。

本文连接云端使用的是X509证书方式连接。

一、配置Paho库

首先引入Paho:

api 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
api 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'

权限申请增加:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

添加service:

<service android:name="org.eclipse.paho.android.service.MqttService" />

做完这些后,就可以使用Paho了。

二、连接AWS IOT

然后我们来使用Paho连接AWS,首先贴代码:

MqttConnectOptions options = new MqttConnectOptions();
SocketFactory socketFactory = getSocketFactory(context);
if(socketFactory == null){
    return;
}
options.setSocketFactory(socketFactory);
options.setCleanSession(true);
options.setConnectionTimeout(15);
options.setKeepAliveInterval(60);
try {
    MqttAndroidClient client = new MqttAndroidClient(context,
            "ssl://XXX:8883",
            "MQTT_Test");

    client.connect(options, null, new IMqttActionListener() {
        @Override
        public void onSuccess(IMqttToken asyncActionToken) {
            Log.i(TAG, "连接成功 ");
        }

        @Override
        public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
            Log.i(TAG, "连接失败 :" + exception);
        }
    });
} catch (MqttException e) {
    e.printStackTrace();
}

这一段代码主要做了两件事,第一件构建MqttConnectOptions,然后使用MqttConnectOptions作为参数连接AWS IOT。

1.构建MqttConnectOptions:这里对MqttConnectOptions设置了SocketFactory(因为是X509的认证方式,所以会使用到SocketFactory)、CleanSession(设置客户端和服务器是否应在重新启动和重新连接时记住状态)、ConnectionTimeout(设置连接超时时间,单位为秒)、KeepAliveInterval(设置“保持活动”间隔,单位为秒,按照设置的时间为间隔会一直去检测连接是否可用)。

2.连接IOT:初始化MqttAndroidClient,需要Context,ServiceURL(由"ssl://" + EndPoint + ":8883",这里的8883端口号根据实际情况设置),ClientID(这里可以随便设置,但在相同的证书,相同的地址的情况下,ClientID在同一时间不可重复),最后使用MqttConnectOptions作为参数、设置连接状态监听便可开始连接。

第二点连接也可以初始化MqttClient,然后使用connect连接。

MqttClient client = new MqttClient("ssl://XXX:8883",
        "test", new MemoryPersistence());
client.connect(options);

上面的代码对所有的MQTT连接应该都是适用,但是每一个云端平台应该都有不一样的地方,这个不一样的地方就在SocketFactory。下面就介绍SocketFactory的生成。

三、SocketFactory的生成

SocketFactory可以由SSLContext取得,SSLContext的构造离不开KeyStore,所以怎么将X509证书放入KeyStore,成了关键,KeyStore创建有问题,前面的连接也会有问题。

X509证书有两种情况,一个是直接就知道证书私钥的内容,一个是写入到一个文件中无法知道证书私钥的内容。

1.有明文的证书、私钥

这里指已经知道证书、私钥的内容,但是没有封装好的文件的情况。这时呢需要将证书以及私钥封装成Certificate以及Key使用。下面的封装方法来自微软。

需要导入这些库用来封装:

api 'org.bouncycastle:bcprov-jdk15on:1.66'
api 'org.bouncycastle:bcpkix-jdk15on:1.66'

证书内容封装成Certificate:

private X509Certificate buildCertificate(String certificateString) throws CertificateException
{
    try
    {
        Security.addProvider(new BouncyCastleProvider());
        PemReader publicKeyCertificateReader = new PemReader(new StringReader(certificateString));
        PemObject possiblePublicKeyCertificate = publicKeyCertificateReader.readPemObject();
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(possiblePublicKeyCertificate.getContent()));
    }
    catch (Exception e)
    {
        throw new CertificateException(e);
    }
}

私钥内容封装成Key:

private Key buildKey(String keyString){
    try
    {
        Security.addProvider(new BouncyCastleProvider());
        PEMParser privateKeyParser = new PEMParser(new StringReader(keyString));
        Object possiblePrivateKey = privateKeyParser.readObject();
        return getPrivateKey(possiblePrivateKey);
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
    return null;
}

private 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");
    }
}

2.已经有证书文件

这里以pkcs12文件为例,从文件中取出Certificate以及Key:

public void getCertAndKey(Context context){
    try {
        String password = "password";
        InputStream assetsIn = context.getAssets().open("test.pkcs12");
        KeyStore inStore = KeyStore.getInstance("PKCS12");
        inStore.load(assetsIn, password.toCharArray());
        Enumeration enums = inStore.aliases();
        while (enums.hasMoreElements()) {
            String keyAlias = (String) enums.nextElement();
            if (inStore.isKeyEntry(keyAlias)) {
                Key key = inStore.getKey(keyAlias, password.toCharArray());
                Certificate[] certChain = inStore.getCertificateChain(keyAlias);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

从文件里面可以使用KeyStore取出Certificate以及Key,需要密码(生成pkcs12文件时设置的密码)。

以上两种方式可以得到Certificate以及Key,有两个类就可以开始构建KeyStore:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry("cert-alias", certChain[0]);
keyStore.setKeyEntry("key-alias", key, "password".toCharArray(), certChain);

这里的key以及certChain就是上面生成的(有明码的证书以及私钥构建的是X509Certificate,可以直接创建一个Certificate[],然后将X509Certificate放入位置为0的地方)。

有了KeyStore就可以创建SSLContext了,下面来创建SSLContext:

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
        .getDefaultAlgorithm());
kmf.init(keyStore, "password".toCharArray());
KeyManager[] km = kmf.getKeyManagers();

sc.init(km, null, new SecureRandom());

在KeyManagerFactory.init的时候使用的密码是创建KeyStore时设置的密码

这样SSLContext创建完成,就可以直接使用SSLContext的方法获取到SocketFactory:

sc.getSocketFactory()

到这里SocketFactory就已经有了,就可以做为参数设置到MqttConnectOptions中。

四、遇到的问题

因为我是先写的Azure云的连接,所以之后写AWS的连接时,就直接用了Azure的方式建立了SocketFactory,结果一直报

SSL routines:OPENSSL_internal:SSLV3_ALERT_BAD_CERTIFICATE

这个错误,它的意思是提示证书错误。

因为Azure构建KeyStore时,setCertificateEntry方法里面用的证书并不是我们之前所用到的明文的证书,是其他的证书,所以AWS我直接使用RootCA证书来代替Azure这里的证书,就一直提示证书错误,其实这里使用的证书与接下来setKeyEntry方法设置的证书是一样的。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值