Before working with HTTPS, I recommend reading Android Training: Security with SSL. Many companies have adopted full-site HTTPS, but not all implementations are correct. This post records some issues I encountered.

Prerequisite knowledge:

  • Symmetric encryption
  • Asymmetric encryption

Certificate Formats

ExtensionDescription
.DERBinary certificate, usually uses .cer or .crt extension
.PEMBase64-encoded X.509v3 certificate, starts with ---BEGIN...
.CRT / .CEREssentially the same; .CRT is more aligned with Microsoft standards
.keyPublic/private key file processed with PKCS #8

Generating Self-Signed Certificates with OpenSSL

Generate Private Key

1
2
3
4
5
$ openssl genrsa -out key.pem 1024
Generating RSA private key, 1024 bit long modulus
....................++++++
.....................++++++
e is 65537 (0x10001)

Create Certificate Signing Request (CSR)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ openssl req -new -key key.pem -out request.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Beijing
Locality Name (eg, city) []:Beijing
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Hxq
Common Name (e.g. server FQDN or YOUR name) []:hao
Email Address []:haoxiqiang@live.com

The Common Name must match the server hostname, as required by the SSL-RFC specification.

Generate Self-Signed Certificate

1
$ openssl x509 -req -days 30 -in request.pem -signkey key.pem -out certificate.pem

Fetch Certificate from an Existing Server

1
echo | openssl s_client -connect hostname:443 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > hostname.pem

Using Certificates in Android

Android typically only recognizes BKS (Bouncy Castle KeyStore) certificates, so conversion is required.

Convert PEM to BKS

1
2
3
4
5
6
7
8
keytool -importcert -v \
 -trustcacerts \
 -alias 0 \
 -file <(openssl x509 -in hostname.pem) \
 -keystore $CERTSTORE -storetype BKS \
 -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
 -providerpath bcprov-jdk16-1.46.jar \
 -storepass password

Related tools:

Loading a BKS Certificate in Code

1
2
3
4
5
6
7
InputStream inputStream = context.getResources().openRawResource(res);
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(inputStream, password.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());

If you prefer not to use resource files, you can also load text-form certificates via String -> InputStream.

Certificate Format Conversion

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# PEM to DER
$ openssl x509 -outform der -in certificate.pem -out certificate.der
# PEM to P7B
$ openssl crl2pkcs7 -nocrl -certfile certificate.cer -out certificate.p7b -certfile CAcert.cer
# PEM to PFX
$ openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CAcert.crt
# DER to PEM
$ openssl x509 -inform der -in certificate.cer -out certificate.pem
# P7B to PEM
$ openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer
# PFX to PEM
$ openssl pkcs12 -in certificate.pfx -out certificate.cer -nodes

Viewing Certificate Information

1
2
3
openssl x509 -in cert.pem -text -noout
openssl x509 -in cert.cer -text -noout
openssl x509 -in cert.crt -text -noout

Common Issues

SSLPeerUnverifiedException: Hostname not verified

This exception occurs when the Common Name in a self-signed certificate doesn’t match the server domain. You can work around it with a custom HostnameVerifier, but never set verify() to always return true:

1
2
3
4
5
6
7
8
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        HostnameVerifier hv =
            HttpsURLConnection.getDefaultHostnameVerifier();
        return hv.verify("example.com", session);
    }
};

Reference: Hostname Not Verified Issue

References