Generating pair of .jks and .pem certificates.
Tags: certificates, tls, https, jks
Published: 2020-11-04 22:33:08

I recently run into needing a pair of `*.pem` and `*.jks` 
self-signed certificates for local TLS verification. After some reading I put together this guide for generating either and converting it to the other.

You can use two approaches:

* [Generating the TLS Certificates](#generating-certificates)
* [Ignoring the TLS verification](#ignoring-tls)

----

_Note: This guide is based on documentation for an open source library I built recently called [IBeam][ibeam]. It is a wrapper working with IBKR Client Portal Web API Gateway. For that reason all example URLs below point to `localhost:5000/v1/api/one/user`. Modify this URL to whatever routes your server supports._

# <a name="generating-certificates"></a>Generating Certificates

You can generate your own self-signed certificate in two ways:

* [Using Keytool](#using-keytool) to generate `cacert.jks`
* [Using OpenSSL](#using-openssl) to generate `cacert.pem`

Either way you chose, you will then need to convert one certificate into the other. Therefore, you will need both [Keytool][jre] and [OpenSSL][openssl] to generate your certificates.

Note that you can't generate `cacert.jks` and `cacert.pem` independently. You must generate only one certificate first using either method and then convert it into the other format.

### <a name="using-keytool"></a>Using Keytool

#### Generate JKS

Keytool is a Java tool shipped with [Java Runtime Environment][jre] (JRE). It can be found in `JRE_ROOT/bin/keytool`.

1. To generate the `cacert.jks` run:

    ```posh
    keytool -genkey -keyalg RSA -alias selfsigned \
     -keystore cacert.jks -storepass YOUR_CERTIFICATE_PASSWORD \
     -validity 730 -keysize 2048 
    ```
    
    Note the YOUR_CERTIFICATE_PASSWORD field. Replace it which certificate password you want to use. You will need to use this same password in later steps.
    
    Optionally, you may want to add additional option to provide Subject Alternative Names (SAN) in order for the certificate to accept requests from your client hosts. For instance, if the server is to be communicated with from two client machines, one with IP address of `10.148.0.0` and one with DNS of `my-client.machine.com`, your keytool command line should include:
        
    ```posh
    -ext SAN=ip:10.147.0.0,dns:my-client.machine.com
    ```

1. Upon running command from Step 1, you will be asked the following questions which you may chose to ignore:
    * What is your first and last name?
    * What is the name of your organizational unit?
    * What is the name of your organization?
    * What is the name of your City or Locality?
    * What is the name of your State or Province?
    * What is the two-letter country code for this unit?

1. Eventually, Keytool will ask for your confirmation, along the lines of:
    
    > Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?

    Type `yes` to continue if the information is correct.

1. Finally, Keytool will ask you for the key password. You may simply hit return to use the same password as specified in the `-storepass` flag in Step 1. DO NOT provide a different password than YOUR_CERTIFICATE_PASSWORD specified above.

1. You should now have the `cacert.jks` file generated in your current directory.


#### Convert JKS to PEM

To convert a `cacert.jks` to `cacert.pem` file you need to:

1. Convert `cacert.jks` to `cacert.p12` using Keytool:
    ```posh
    keytool -importkeystore -srckeystore cacert.jks \
     -destkeystore cacert.p12 -srcstoretype jks -deststoretype pkcs12
    ```
    You will be asked for a new password for `cacert.p12`, as well as for the original password of `cacert.jks`. Ensure you use the same password as when generating the `cacert.jks`.

1. Convert `cacert.p12` to `cacert.pem` using OpenSSL:
    ```posh
    openssl pkcs12 -in cacert.p12 -out cacert.pem
    ```
    Again, you will be asked for a new password for `cacert.pem`, as well as for the original password of `cacert.p12`. Ensure you use the same password as when generating the `cacert.jks` and `cacert.p12`.

1. You should now have the `cacert.pem` file generated in your current directory.

You should now have `cacert.jks`, `cacert.p12` and `cacert.pem`. You will only need the `.jks` and `.pem` files. You may delete the redundant `cacert.p12` file.

### <a name="using-openssl"></a>Using OpenSSL

#### Generate PEM

1. To generate a `cacert.pem` using OpenSSL run:

    ```posh
    openssl req -x509 -days 730 -newkey rsa:2048 \
     -keyout key.pem -out cert.pem
    ```
   
    Optionally, you may want to add additional option to provide Subject Alternative Names (SAN) in order for the certificate to accept requests from your client hosts. To do so, you must create a `san.cnf` used as a configuration file for openssl, and add the following to the openssl command line:
    
    ```posh
    -config san.cnf
    ```
   
    Your `san.cnf` can take multiple forms, yet to support SAN it requires the subjectAltName field. Have a look at this template [san.cnf][san-file] file that you can use as a basis to specify your SANs.
   
    For instance, if the server is to be communicated with from two client machines, one with IP address of `10.148.0.0` and one with DNS of `my-client.machine.com`, your `san.cnf` should contain:
   
    ```cfg
    [alt_names]
    IP.1 = 10.148.0.0
    DNS.1 = my-client.machine.com
    ```
   
1. You will be asked for a password. You will need to use this same password in later steps.

1. You should now have `key.pem` and `cert.pem` files in your current directory.

1. Combine `key.pem` and `cert.pem` to create `cacert.pem`:

    ```posh 
    cat key.pem cert.pem > cacert.pem 
    ```
   
    You can also merge these two files manually if you prefer.
   
1. You should now have `cacert.pem`, `key.pem` and `cert.pem`. You will only need the `cacert.pem` file. You may delete the redundant `key.pem` and `cert.pem` files.

#### PEM to JKS

To convert a `cacert.pem` to `cacert.jks` file you need to:


1. Convert `cacert.pem` to `cacert.p12` using OpenSSL:
    ```posh
    openssl pkcs12 -export -in cacert.pem -out cacert.p12
    ```
   
    You will be asked for a new password for `cacert.p12`, as well as for the original password of `cacert.pem`. Ensure you use the same password as when generating the `cacert.pem`.

1. Convert `cacert.p12` to `cacert.jks` using Keytool:
    ```posh
    keytool -importkeystore -srckeystore cacert.p12 \
     -srcstoretype pkcs12 -destkeystore cacert.jks
    ```
   
    Again, you will be asked for a new password for `cacert.jks`, as well as for the original password of `cacert.p12`. Ensure you use the same password as when generating the `cacert.pem` and `cacert.p12`.

1. You should now have the `cacert.jks` file generated in your current directory.

You should now have `cacert.pem`, `cacert.p12` and `cacert.jks`. You will only need the `.jks` and `.pem` files. You may delete the redundant `cacert.p12` file.


## <a name="use-your-certificate"></a>Use your certificate


#### cURL

cURL accepts `--cacert` flag that can be used to pass the certificate. See [cURL documentation][curl-ssl] for more.
```posh
curl -X GET "https://localhost:5000/v1/api/one/user" \
  --cacert cacert.pem
```

#### Python urllib3

Python [urllib3][urllib3] library allows you to specify a SSL context through which you can specify the location of your certificate.

```python
context = ssl.create_default_context()
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
context.load_verify_locations('cacert.pem')
urllib.request.urlopen("https://localhost:5000/v1/api/one/user", 
    context=context)
```

#### Python requests
Python [requests][requests-ssl] library allows you to set the `verify` parameter to specify the location of your certificate.  
```python
requests.get("https://localhost:5000/v1/api/one/user", 
    verify='cacert.pem')
```


# <a name="ignoring-tls"></a>Ignoring the TLS verification

For debugging and development, you may chose to ignore the TLS verification. This section describes ways of doing so in `cURL`, Python's `urllib3` and `requests` libraries.

### cURL

cURL accepts `--insecure` or `-k` flags that can be used to ignore verifying certificates. See [cURL documentation][curl-k] for more.
```posh
curl -X GET "https://localhost:5000/v1/api/one/user" -k
```

### Python urllib3

Python [urllib3][urllib3] library allows you to specify an empty SSL context, which effectively will ignore verifying the certificates.

```python
empty_context = ssl.SSLContext()
urllib.request.urlopen("https://localhost:5000/v1/api/one/user", 
    context=empty_context)
```

### Python requests
Python [requests][requests-ssl] library allows you to set the `verify` parameter to `False` when making requests, which effectively will ignore verifying the certificates.  
```python
requests.get("https://localhost:5000/v1/api/one/user", 
    verify=False)
```

----

Hope you guys find this guide useful 👋

----

_Final word: It's a whole another conversation whether it is a good idea to create self-signed certificates and where do they belong. This guide is merely showing you how to create a pair of certificates. It is recommended to verify whether self-signed certificates would be the right choice for your use case._

[ibeam]: https://github.com/Voyz/ibeam
[san-file]: https://github.com/Voyz/ibeam/blob/master/support/san.cnf

[jre]: https://www.java.com/en/download/
[pem]: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail
[pkcs]: https://en.wikipedia.org/wiki/PKCS
[jks]: https://en.wikipedia.org/wiki/Java_KeyStore
[openssl]: https://www.openssl.org/
[curl-ssl]: https://curl.haxx.se/docs/sslcerts.html
[urllib3]: https://urllib3.readthedocs.io/en/latest/
[requests-ssl]: https://2.python-requests.org/en/master/user/advanced/#ssl-cert-verification