Networking - yanbin's Blog

为 Android 程序创建 CA keystore 以及 self-signed keystore 的方法

为什么 Android 程序需要 CA KeyStore?
1. 在 Android 程序中建立一条 SSL/TLS 连接时,受信任 CA (Trusted CAs) 用来验证 server。
    Public-Key Infrastructure (PKI) 中有 trust certs 概念,许多网络工具实现了 trusted CA 的使用。
    比如: curl, Android URLConnection.
    Android 系统中有一个 trusted CAs list, 包含 100 多个 trusted CAs.
  
2. 建立 SSL/TLS 连接时现有的 trusted CAs 不能验证服务器证书,会引发一个 security exception:
       javax.net.ssl.SSLHandshakeException: ... : Trust anchor for certification path not found.
   具体而言,以下这些情况会引发这个问题:
      (a) The CA that issued the server certificate was unknown;
      (b) The server certificate wasn't signed by a CA, but was self signed;
      (c) The server configuration is missing an intermediate CA;
 
3. 针对(a) 和 (b) 这两种情况,解决方法是创建 SSL/TLS 连接时使用 Android TurstManager 工具。
    在 Android 程序中 TrustManager 用 KeyStore instance 初始化,而 KeyStore instance 读取/解析
    BKS/JKS 格式的 KeyStore 文件,获得证书信息。
  
4. 使用 keytool 工具创建 BKS 格式的 KeyStore 文件。
    keytool 在多数 linux 发行版中都可以通过 package 管理工具获得。
    keytool 创建 BKS 格式的 KeyStore 文件需要用到 BouncyCastle Provider, 这个文件 JRE 不提供,
    keytool 本身也没有提供,需要下载后通过参数指定。创建 JKS 格式的 KeyStore 不用这个文件。
 
    $ keytool -importcert -v -trustcacerts -file "root-ca.crt" -alias root-ca -keystore "root-ca.bks"
       -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar"
       -storetype BKS -storepass zhelishimia
 
   验证 keystore 文件是否正确:
   $ keytool -list -keystore "root-ca.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider
     -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass zhelishimima
 
5. 也可以直接用 server certificate 生成 keystore 并且设置为 trusted, 不过这样做不够安全,而且 server
    端的证书有改变时还需要生成新的证书。
  
 
有时也客户端程序也用证书标识自己的身份。或者有些 JAVA server 程序中建立 SSL 连接时用 KeyStore
提供证书信息。
1. 使用 openssl 工具转换 X509 格式的 public certificate 和 private key 为 pkcs12 格式的文件。
    $ openssl pkcs12 -export -in broker.crt -inkey broker.key -out broker.p12 -name client001
       -CAfile root-ca.crt -chain
 
2. 使用 openssl 工具和 pkcs12 文件生成 keystore
    $ keytool -importcert -v -trustcacerts -file "root-ca.crt" -alias root-ca -keystore "root-ca.bks"
       -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar"
       -storetype BKS -storepass 12345678
 
3. 在 Android 程序中, 建立一条 SSL 连接时可以用 KeyManager 工具指定本端的 public certificate,
    private key, KeyManager 需要用 KeyStore instace 初始化,KeyStore instance 加载/解析 keystore 文件,
    获取 public certificate 以及 private key 信息。
 
 
参考:

   

自签发 SSL/TLS 证书的方法以及遇到的一些问题

PKI (Public Key Infrastructure, 公开密钥基础架构)简介
0. 目的
PKI 旨在创造、管理、分配、使用、存储以及撤销数字证书
Unix/Linux 平台上常用的 PKI 软件是 openssl.
 
1. 数字证书是什么?
在密码学上视数字证书为一种身份凭证,是一种电子文件,用于证明公开密钥的所有者的身份。
数字证书文件中包含所有者的身份信息、公开密钥信息以及数字证书本身的数字签章;
数字签章用于验证证书文件内容正确无误——没有损坏或者没有被修改过——。
如果签章是正确的,而用户可以相信签署者,之后用户就知道他们可以使用这个密钥,来与密钥的拥有者进行通信。在X.509中规范了这个认证的过程与标准。①
认证过程中会包含「用 CA 的根(roo)证书验证这份数字证书是由该 CA 签发的」。
用户可以没有自己的数字证书,但是一定需要 CA 的根证书。比如:https 通信。
 
2. 谁负责创造数字证书?
CA(Certificate Authority) 负责签发数字证书。
CA 由受信任第三方担任,签发数字证书给通信双方使用;有时只签发给服务方,比如: https 通信。
这样理解或许比较简单,互联网上的数字证书签发实际上要复杂。
我们自己签发证书给自己的程序使用,不公开业务也不公开 CA root 证书,可以不用关心太多。
 
大公司的内部业务也可能足够复杂的到需要 CA,RA 之类的。参见维基百科公开密钥基础建设
 
也有 12306 或者银行这样的公司要求用户自己下载它的 CA 的 root 证书。
 
3. 证书的层级结构。
 
设置 CA 信息和签发 CA root 证书
0.修改 openssl.conf 文件里的某些域的值。
req_distingushed_name section 可以考虑修改为:
[ req_distinguished_name ]
   countryName = Country Name (2 letter code)
   countryName_default = CN
   stateOrProvinceName = State or Province Name (full name)
   stateOrProvinceName_default = ShangHai

   localityName = Locality Name (eg, city)
   localityName_default = ShangHai
   0.organizationName = Organization Name (eg, company)
   0.organizationName_default = My Company Name

   organizationalUnitName = Organizational Unit Name (eg, section)
   organizationalUnitName_default = May Project

1. 生成 CA root key (根密钥); CA root key 非常重要。
a) 不仅签发或更新 CA root 证书需要。
b) 签发通信双方数字证书时也需要。

Create the keypair:
$ openssl genrsa -des3 -out root-ca.key 1024
 

0) 指定了 -des3 选项,openssl 要求输入 pass phrase;
pass phraseroot key 的密码;
CA 签发数字证书时需要输入这个密码,算是一种防范措施吧。

1) FIXME: 这种密码也可以用到普通证书的 key,
程序使用证书和 key 时, 一般会要求输入密码,可以作为用户密码用吗?

2) FIXME: 1024 是 key  的长度目前也支持 2048, 这两个值有很大的区别吗?

key 的长度为 2048 会耗费更多的 CPU 资源吧。

2. 自签发 CA 根证书(root certificat)
Use the key to sign itself:
$ openssl req -new -x509 -days 3650 -key root-ca.key -out root-ca.crt  -config openssl.cnf
0) 关键参数是 -x509:
生成的 root-ca.crt 是一个 self-signed X.509 certificate;

1) 这条命令将两个步骤合二为一。
a) 使用 private key 生成 certificate request; 需要填写证书持有者的信息;
b) 使用 certificate request 签发证书(singed)或自签发证书(self-signed);

2) 这个自签发证书为什么能做为 CA root certificate 使用?
因为指定了参数 -x509, openssl 配置文件 req section 指定了 x509_extensions = v3_ca;
而 v3_ca section 里则有:basicConstraints = CA:true
 
3) openssl.cnf 以及 CA directory 都不是系统中唯一存在的;
任意创建一个目录,创建配置文件(比如: openssl.cnf),也可以签发根证书。
$ mkdir demoCA && cd demoCA
$ touch index.txt
$ echo 01 > serial
$ mkdir private #指定 private_key=/path/to/demoCA/myCA/private/root-ca.key
$ mkdir myCA # 指定 CA_default:dir=/path/to/demoCA/myCA
 
4) 可以使用如下命令查看 root-ca.crt 的信息
$ openssl x509 -noout -text -in root-ca.crt

 

签发服务器证书
0.生成一个 private key, 并且直接用新生成 private key 创建一个 certificate request.
NOTE: -nodes 参数,使用这个参数生成的 key 不需要密码

$ openssl req -newkey rsa:1024 -keyout server01.key -nodes -config openssl.cnf -out server01.req

Country Name (2 letter code) [CN]:
State or Province Name (full name) [ShangHai]:
Locality Name (eg, city) [ShangHai]:
Organization Name (eg, company) [My Company Name]:
Second Organization Name (eg, company) [My Company Name]:
Organizational Unit Name (eg, section) [My Project]: 
Common Name (eg, YOUR name) []:*.example.com #CN
Email Address []:server01@example.com

NOTE: CN(common name) 可以是 hostname,IP 或者 domain, 与主机信息对应;
      也可以用这种形式: *.example.com
      CN 指的是  common name, 别与中国的代码弄混了。

 

1.使用新生成的 certificate request 签发数字证书。
$ openssl ca -config openssl.cnf -out server01.crt -infiles server01.req
# Using configuration from openssl.cnf

Enter pass phrase for /etc/pki/CA/private/root-ca.key: # 输入root key 文件的密码
DEBUG[load_index]: unique_subject = "yes"
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 2 (0x2)
Validity # FIXME: 默认是一年的有效期吗?
# 默认是 365 days 的有效期, 润年会是 366 days.
Not Before: Apr 29 15:18:20 2015 GMT
Not After : Apr 29 15:18:20 2016 GMT
Subject:
countryName = CN
stateOrProvinceName = ShangHai
localityName = ShangHai
organizationName = My Company Name
organizationName = My Company Name
organizationalUnitName = My Project
commonName = *.example.com
emailAddress = server01@example.com
.......
Certificate is to be certified until Apr 29 15:18:20 2015 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

 
签发客户端数字证书
0.为什么需要签发 client 证书?
a)client 可以用证书作为身份标识;
b)签发 client 数字证书与签发 server 数字证书相比,并没有特别的不同。
c)-nodes 不需要 key 文件有密码
 
1. 生成 client private key, 并生成 certificate request.
$ openssl req -newkey rsa:1024 -keyout client001.key -nodes3  -config openssl.cnf -out client001.req
Country Name (2 letter code) [CN]:
State or Province Name (full name) [ShangHai]:
Locality Name (eg, city) [ShangHai]:
Organization Name (eg, company) [My Company Name]:
Second Organization Name (eg, company) [My Company Name]:
Organizational Unit Name (eg, section) [My Project]: 
Common Name (eg, YOUR name) []:client001
EmailAddress []: client001@example.com
   
2. 使用新生成的 certificate request 签发证书
$ openssl ca -config openssl.cnf -out client001.crt -infiles client001.req

Enter pass phrase for /etc/pki/CA/private/root-ca.key:
DEBUG[load_index]: unique_subject = "yes"
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 2 (0x2)
Validity:
Not Before: Apr 29 15:18:20 2015 GMT
Not After : Apr 29 15:18:20 2016 GMT
Subject:
countryName = CN
stateOrProvinceName = ShangHai
localityName = ShangHai
organizationName = My Company Name
organizationName = My Company Name
organizationalUnitName = My Project
commonName = client001
emailAddress = client001@example.com
.......
Certificate is to be certified until Apr 29 15:18:20 2015 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
 
需要注意的几个问题
拖延症来袭,明天再更新

参考:
 

 

使用 libmosquitto 遇到的几个问题

1. 使用 libmosquitto 时必须要调用 mosquitto_loop_forever() 或者 mosquitto_loop();
 
2. mosquitto_loop_forever(); mosquitto_loop() 的第三个参数 max_packets 目前并没有使用,
    但是为了保持兼容性,必须设置为 1.
 
3. mosquitto_loop_start() 并不是在 thread 中调用,而是会启动一个 thread 处理 network  traffic, 
    称为 network thread; 同样的 mosquitto_loop_stop() 不是一定要在 thread 中调用,
    而是结束之前通过 mosquitto_loop_start() 创建的 network thread,
    mosquitto_loop_stop() 会等待 network thread 结束,而为了让 network thread 结束,
    又必须提前调用 mosquitto_disconnect(),
    不过可以通过设置 mosquitto_loop_stop() 的第二参数来强制 network thread 结束;

 
4.  注册 will message 同时指定 will topic。mosquitto_will_set() 的第二个参数就是指定 will topic 的。
     这就是 mqtt 里所说的 will message 有相应的 topic,  mosquitto_sub, mosquitto_pub 都有参数,
     可以指定 will topic: --will-topic;
 
5. 如果不使用 mosquitto_loop() 或者 mosquitto_loop_forever() 可以使用:
    (a) mosquitto_socket() 获取 connect socket handle;
    (b) 使用 select() 查询 socket handle 可读/可写;
    (c) 如果 select() 返回可读,mosquitto_read() 用来读取 message;
    (d) 如果返回可写,mosquitto_write() 用来发送 message;
    (e) 如果超时 mosquitto_loop_misc() 用来执行一些杂项;
 
6. libmosquitto 中 mosquitto_user_data_set() 可以设置 userdata,
    也可以在 mosquitto_new() 初始化时指定 userdata; 
    但是并没有接口获得 userdata; 毕竟 uesrdata 是外部数据,user 自己维护 userdata,
    当然应该知道指定给 mosquitto object 的 userdata 是哪一个。
 
7.保证 libmosquitto 和/或 mosquitto 版本一致。
   mosquitto-1.3.x 与 mosquitto-1.4 有兼容性问题。
   客户端使用中级 CA 签发的证书与 broker 建立 ssl 连接时,会有 tlsv1 alert unknown ca error.

 

mqtt 基础

0. mqtt 是一种轻量级的 publish/subscribe 消息传输协议。
   这是一个应用层协议, 一种互联网通信协议,并不依赖底层网络协议。
   正因为其轻量级,所以在低电量设备的中广泛应用。mqtt 可以应用在更多的情景。
   常见的应用:facebook, 微博。
 
1. mqtt message 规格:
   MAX LENGTH: The Payload length valid values are between 0 and 268,435,455 (0xf,fff,fff).
   没错是 7 个f, 256 * 1024 * 1024 - 1.
 
2. mqtt 之 "pub/sub":
   mqtt 的基础原理是推送消息(publishing messages)和订阅主题(subscribing topics), 称为 "pub/sub".
   clients 连接到 broker ---> subscrib topics, 其他 clients 连接到 broker ---> publish message 到 topic,
   所有订阅了这个 topic 并且在线的 clients 都会收到这条 message.
 
   假如 clients publish message 到 topic, 但是订阅这个 topic 的 clients
   都没有在线, 或者还没有 client 订阅这个 topic. 这条 message 将会被消失(被丢弃了?是的被丢弃了)。
   可以设置 message 的 retained flag,再有其他 client 订阅到 topic 时会收到这条 message.
 
   1) 如果 publish message 时有 client 订阅到 topic, 这个条 message 还会保留吗?
   2) FIXME: 保留的 message 发送过之后还会再次发送到其他后来订阅到 topic 的 client 吗?
      **** retained message 保证每当 client 订阅 topic 时,会把 message 发送到 client.
            所有新在线的 client 都可以收到这条 message 直到 borker 进程重启。
      **** 即使 broker 进程重启,也有办法从硬盘存储中读取之前的消息,并且按照消息类型安排发送。
 
3. mqtt, broker:
   提供通用的简单的接口,接受其他 client 的连接。client 可以将 subscribedmessage 存入 database、
   发送到 twitter. 当然也可以从 database, twitter 读取 message 并推送。
   协议接口是简单通用的,任何实现的 client 都可以连接到任何实现的 broker.
 
4. mqtt, topic:
   1) Messages in MQTT are published on topics. There is no need to configure a topic,
      publishing on it is enough.
      topic 不需要配置,也不需要预先创建,有 client publish message 或这 subscribed topic 时由 broker 创建。
 
   2) topic 是层级结构,使用 '/' 作为分隔符, 就像是 Linux/Unix 的文件系统路径结构一样。
 
   3) 支持两个通配符(wildcard): '+' 和 '#'.
      '+': 通配层级结构中的一层。 a/b/+/d, a/b/c1/d   a/b/c2/d   a/b/c3/d 都会匹配。
      '#': 通配层级结构中所有剩余的层。 a/#, a/b/c1/d   a/b/c2/d   a/b/c3/d
 
   5) Zero length topic levels:
      Zero length topic levels are valid.
      0 长度 topic levels 表现出非现实的行为。可能会表现出 '+' 的行为,也可能表现出 '#' 的行为。
      具体行为要看 topic 的层级结构。
      例如有如下层级结构:
      a/b/c/d
      a/b/c1/d
      a/b/x/d
 
      a/b//d 相当于 a/b/+/d/
      a/b/c/ 相当于 a/b/c/+
      a/b/   相当于 a/b/#
 
      1) 如果给出的 0 length topic level 不是最后一层,那么就相当于 '+';
      2) 如果给出的 0 length topic level 是最后一层,要看实际 topic
         层级结构中, 这一层是不是最后一层,(a)如果是最后一层,那么就相当于 '+';
         (b) 如果不是最后一层,就相当于 '#'; 
 
5. Quality of Service.
   QoS 有三个级别:0,  1,   2
   mqtt QoS 并不是 TCP/IP 网络中报文的 QoS. 也不是 Linux 网络管理中的 QoS.
   mqtt QoS 定义了消息的发送和接收方式:
 
   1) Higher levels of QoS are more reliable, but involve higher latency and have
      higher bandwidth requirements.
      越高级别的 QoS 越可靠。相应的高级别的 QoS 带宽消耗大,延迟高
      因为要做握手或者发送接收确认。
 
   2) 三种级别的 QoS 消息发送/接收方式:
      0: The broker/client will deliver the message once, with no confirmation.
         提交 message 一次,不执行‘确认’操作。
 
      1: The broker/client will deliver the message at least once, with
         confirmation required.
         提交 message 至少一次,含有‘确认’请求,会执行'确认'操作。
 
      2: The broker/client will deliver the message exactly once by using a
         four step handshake.
         使用'4步握手'保证精确的提交一次 message.
   
   3)  Messages may be sent at any QoS level, and clients may attempt to
       subscribe to topics at any QoS level
 
       当 publish message 使用的 QoS 不同于 subscribe message 使用的 QoS 时,
       message 发送到 subscribed client 时,使用较低级别的 QoS.
       e.g.:
       (a) published message 使用 QoS 2, 一个 client0 订阅时使用 QoS 0, 那么
           message 提交给这个 client0 时使用 QoS 0.
       (b) 另一个 client1 订阅到这个 Topic 使用 QoS 2, 那么 message 提交给这个
           client 时使用 QoS 2.
       (c) 如果一个 client2 订阅时使用 QoS 2, 另一个 client3 publish message 
           使用 QoS 0, message 提交给 client2 时使用 QoS 0.
 
6. Retained Messages.
   1) 所有的 message 都可以设置为保留的。broker 保留 message 直到所有的订阅者接收 message.
   2) 而且当一个新的 client 订阅这个 topic,这个条 message 也会提交到这个 client.
   3) 可以用 retained message 来实现"last known good"机制。
 
 
7. Clean session / Durable connections
   1) client 连接到 broker 时设置 clean session flag 为 false, client 与 broker
      间建立了一个长连接(Durable connnecton); 当 client 断开与 broker 的连接时,
      任何 subscriptions 还有剩余的 QoS 为 1 或 2 的 messages,
      那么会保存这些 subscriptions/messages 直到再次连接。
 
   2) 如果 clean session flag 为 true,就相当于 'session start', 当 disconnect 时,
      会移除这个 client 所有的 subscriptions.
 
8. wills.
   will 是一种 message, 无异于其他 mqtt message。
   在 client 与 broker 建立连接时,client 告知 broker 一个 will message,用来
   在 client 与 broker 不正常断开连接时发送 will message 到一个特殊的 topic。
   这个特殊的 topic 是一个 system topic; system topic 有特殊命名,有特殊的访问权限,用于管理。
   will message 同样可以设置 QoS.
 

libcurl 简单使用

1. 必须首先调用 curl_global_init(), 并且指定 CURL_GLOBAL_ALL 作为参数。

    curl_global_init() 会分配一些内存,用于内部的实现。

 

2. 调用 curl_global_cleanup() 清除 curl_global_init() 分配的资源。
    当然这个函数可以在程序任何执行之中调用,不一定是程序将要结束之前调用。
    在完成了所有与 liburl 有关的工作之后,调用 curl_global_cleanup().
 
3. 使用 easy-functions  或者 multi-functions
    1) easy-functions 一般来说可以满足基本工作。
    2) 调用 CURL *curl_easy_init() 获得一个 easy session handle, 其实可以说是获得一个 curl handle.
    3) 使用 curl_easy_setopt(); 指定一个要请求的 URL.
    4) 使用 curl_easy_perform(); 完成请求,这里可能会阻塞。
    5) 使用 curl_easy_cleanup(curl), 完成清理工作。
 
4) curl_global_init() 一般会从系统分配内存。
    也可以使用 curl_global_init_mem() 指定一个自定义的内存地址给 libcurl  用来分配内存 ,这个地址可以是 stack, 也可以是动态分配的。
 
5) 小例子。
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#define REMOTE_URL   "http://www.google.com"
int main(int argc, char **argv)
{
     CURL *curl; 
     CURLcode res;
     curl_global_init(CURL_GLOBAL_ALL);
     curl = curl_easy_init();
     if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, REMOTE_URL);
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        res = curl_easy_perform(curl);
        if (res != CURLE_OK) 
           fprintf(stderr, "curl_easy_perform error: %s\n", curl_easy_strerror(res));
        curl_easy_cleanup(curl);
    }
    curl_global_cleanup();
    return 0;
}

 




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee