Background
Previously we discussed the basics of our HTTP Proxy Setup, and next we will deal with HTTPS connections securely. All future code samples will then use SSL for all connections. I use ‘SSL’ as the most widely used term, and the actual connections will usually always use TLS 1.3.
Goal: Developer Productivity
When working with OAuth secured UIs and APIs, SSL messages tend to be required in development environments:
- Messages to the Authorization Server often must use HTTPS
- Some vendors also require HTTPS URLs for Web Redirect URIs
In some setups, being able to view HTTPS messages in flight can be highly useful, both for learning OAuth standards, and troubleshooting failed messages.
Goal: Real World Development URLs
For the first code sample we created custom domain names for local PC hosting, and in the next sample these will be updated to HTTPS URLs:
- https://web.mycompany.com/spa
- https://api.mycompany.com/api
Code samples will download and use development certificates precreated by the openssl tool, and stored in a GitHub repository. We will take a closer look at how the certificate creation script works at the end of this post.
SSL Trust Model
I avoid standalone SSL certificates for local development. Instead I follow a public key infrastructure approach, in the same way as for real internet certificates. Each development certificate has a root authority, whose public key can be distributed and which components can be configured to trust.
Technical Challenges
Getting SSL Trust working can be problematic in some cases, since it requires different settings across tools and technologies. We will describe configuration for all of the following clients:
- Chrome / Safari Browsers
- Microsoft Browser
- Firefox Browser
- Electron Desktop Apps
- Node.js APIs
- .NET APIs
- Java APIs
- Kotlin Android Apps
- Swift iOS Apps
- Command Line Tools
In this blog we will run multiple UI and API code samples, then view OAuth messages for all of them, across these operating systems:
- Windows
- macOS
- Linux
- Android
- iOS
HTTP Proxy Root Certificates
HTTP proxy tools use a man in the middle technique to replace the root of the target URL’s SSL certificate. This enables the traffic to be viewed as HTTP, then re-encrypted using the proxy’s own certificate:
Some corporate networks use their own SSL Proxy / Firewall Filter that works in a similar manner way, injecting root certificates that replace the root authority of any non-whitelisted external URLs.
Development SSL Certificates
The next code sample will use a Wildcard SSL Certificate, whose root is a Development Root Certificate Authority. The wildcard certificate will use this blog’s custom domain names as Subject Alternative Names:
Identity the Root Authority to Trust
When running this blog’s code samples, the root authority to trust when tracing HTTPS requests will depend whether an HTTP proxy is being used.
Scenario | Trust Configuration |
---|---|
HTTP Proxy Running | Trust the proxy root certificate |
HTTP Proxy Not Running | Trust the development root certificate |
The following sections describe how to trust the proxy root certificate across various components. The steps to trust the development root certificate are equivalent.
Configure Operating System Trust
On Windows we must run the Microsoft Management Console (MMC.EXE), then select the Certificate Snap In and Import the Root Certificate under Local Computer / Trusted Root Certification Authorities:
On macOS we instead add the Root Certificate to the Keychain Access application under System / Certificates, then set the Always Trust option:
On Linux I configure a root certificate to be trusted by copying it to one of the following folders, depending on which distribution I am using. I also ensure that the file uses a .crt extension rather than .pem:
- /usr/local/share/ca-certificates
- /etc/pki/ca-trust/source/anchors
Then I run one of these commands, and the one to use again depends on the Linux distribution:
- sudo update-ca-certificates
- sudo update-ca-trust
Trusting a Root Certificate in Browsers
Browsers generally inherit SSL trust from one of the Operating System Default Trust Stores above. There may be some exceptions though, such as Google Chrome on Linux having its own certificate store, similar to that described for Firefox below.
Trusting a Root Certificate in the Firefox Browser
The Firefox browser has its own trust store, under Options / Privacy & Security / Certificates / View Certificates / Authorities / Import, and we can import root certificates here:
Trusting a Root Certificate in Node.js APIs
In the previous post we used a hack to get Node.js to trust a root certificate by setting this environment variable:
- NODE_TLS_REJECT_UNAUTHORIZED=0
A more correct option is to only trust whitelisted root certificates, via the following environment variable, which I set permanently on my PC:
- NODE_EXTRA_CA_CERTS=<path to bundle file>
A CA Bundle File contains the public keys of multiple root certificates, and I use a local file containing a number of entries:
# mycompany.com
-----BEGIN CERTIFICATE-----
MIIDZzCCAk+gAwIBAgIJAN7ZkzJkHq8DMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNV
BAMTIFNlbGYgU2lnbmVkIENBIGZvciBteWNvbXBhbnkuY29tMB4XDTIxMTIwNjE4
MTU0MVoXDTMxMTIwNDE4MTU0MVowKzEpMCcGA1UEAxMgU2VsZiBTaWduZWQgQ0Eg
Zm9yIG15Y29tcGFueS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCcstoGEwgoCNp4uJjyquvbFtMjlFEMrRBXKiiC/cF5u/qWUaOnCc2Yg6kyD9rf
eyQTVhjvxt4CrlZJqDeW/+EUY1NdMaztji9lGMv3YqtKRlYL8JzpiWQNHn7qeQhx
OmJJKEPF8XhriqjOBXVA2RefgKngbq6bv3xOvpplEjXBaiEC8ukHU7T0itplWEsu
RV9aY+o2odslkoTITEqkW9WFrNXwLwVH05tNb+5f7Jw/WE8uF+vPY5/9D98/LgZM
8wYQ0tvv+37FdJl25vk29zFaLEQf5iNO+IH1WtLDHdesCNYHq8CIluFC0o3Jf9ti
kz6Z5p2wiXrN+hkCnDsvOI29AgMBAAGjgY0wgYowHQYDVR0OBBYEFJ1scXlGbEsP
/+P6XNOq3x39eLqvMFsGA1UdIwRUMFKAFJ1scXlGbEsP/+P6XNOq3x39eLqvoS+k
LTArMSkwJwYDVQQDEyBTZWxmIFNpZ25lZCBDQSBmb3IgbXljb21wYW55LmNvbYIJ
AN7ZkzJkHq8DMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFnAxVG9
28nMEh3oYkvWCodGrAR8r0Vb2ck0FVOEeper78YhtvzzhW1mMhwnONo83PVpKmIf
6YlQC3XAb3y/0bVc/FtoN40Qh+zHL+WkOx8zqdE4fyRhq1AcGhFQ5Ccx7g+2GbcL
wVO3T+8FjR8Zc+9CWxdE4sDx9RB+34fq6f/6fJTIYp97F/A1FjLGS19rmm09VadL
c708yePaEAOjDCo47t3Y2myO6gGZCPgPsvdP3osxf6QfXLkTwGlrO07+DL0LNDZz
hfnsSjWkqnYf/gqyt00xplaP8vGsoidaSrwAXjGsqwt7rPFQRSoi8elE+3m5/Zaw
+Wmf3vgbiKxlTwY=
-----END CERTIFICATE-----
# Charles Proxy
...
Some Windows tools provide root certificates in a binary DER format. These will need to be translated to PEM format, via the below command, then added to the bundle file:
openssl x509 -inform der -in myRoot.cer -out myRoot.pem
Trusting a Root Certificate in .NET APIs
Later we will provide an OAuth Secured .Net Core API, and trust will be inherited from the Operating System SSL Trust Store. On Linux you will need to use a SSL_CERT_FILE environment variable that works in an equivalent way to the Node.js environment variable.
Trusting a Root Certificate in Java APIs
Later we will provide a OAuth Secured Sample Java API, and each Java installation has its own certificate trust store in a cacerts file. This is located under the Java install location in a lib/security folder. On macOS this is at the following type of location, for a Java 17 installation:
/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/security
Operations to manage certificates use the keytool utility, and I use the following commands to add, find and remove a trusted root:
sudo "$JAVA_HOME/bin/keytool" -import -alias charlesroot -cacerts -file ~/Desktop/charlesroot.pem -storepass changeit -noprompt
sudo "$JAVA_HOME/bin/keytool" -list -alias charlesroot -cacerts -storepass changeit -noprompt
sudo "$JAVA_HOME/bin/keytool" -delete -alias charlesroot -cacerts -storepass changeit -noprompt
Trusting a Root Certificate in Electron Apps
Later we will provide some OAuth Secured Desktop Apps, where there are two processes, which determine certificate trust differently:
Area | Trust Behaviour |
---|---|
Main Process | For requests from the main side of the app, extra root CAs cannot be trusted using NODE_EXTRA_CA_CERTS. For development purposes you have to use the environment variable NODE_TLS_REJECT_UNAUTHORIZED. |
Renderer Process | The Chromium Browser uses the system trust store on macOS or Windows, or you must import certificates into the browser on Linux desktops |
Trusting a Root Certificate in Command Line Tools
Command line tools usually use the default operating system trust store, so that these commands work once root certificates are trusted:
curl https://api.mycompany.com/api/companies
openssl s_client -connect api.mycompany.com:443
It is also possible to use a different certificate bundle file via an override, which may be required in some cases, and is useful for troubleshooting:
curl --cacert ~/certs/trusted.ca.pem https://api.mycompany.com/api/companies
openssl s_client -CAfile ~/certs/trusted.ca.pem -connect api.mycompany.com:443
Trusting a Root Certificate in iOS and Android
Mobile SSL Trust as its own complexities, which are covered in the later posts on mobile setup:
Creating Development Certificates with OpenSSL
Our first code sample uses port 80 but all subsequent samples listen on SSL port 443. The server will use a wildcard certificate whose root is the below self issued authority:
If required, the domain names listed in the extended/server.ext file can be replaced with your own values and you can then run the makeCerts.sh script to create your own certificates:
The end result is a PKCS#12 File, which combines the public certificate and private key, along with a password to protect the file when deployed. You can view the PEM file properties in an online certificate viewer, to understand issuer and DNS details:
Running UIs and APIs over SSL
When our API samples start up, the PKCS#12 File is read and used to listen on SSL port 443:
public async startListening(): Promise<void> {
const port = this._configuration.api.port;
if (this._configuration.api.sslCertificateFileName && this._configuration.api.sslCertificatePassword) {
const pfxFile = await fs.readFile(this._configuration.api.sslCertificateFileName);
const sslOptions = {
pfx: pfxFile,
passphrase: this._configuration.api.sslCertificatePassword,
};
const httpsServer = https.createServer(sslOptions, this._expressApp);
httpsServer.listen(port, () => {
console.log(`API is listening on HTTPS port ${port}`);
});
} else {
this._expressApp.listen(port, () => {
console.log(`API is listening on HTTP port ${port}`);
});
}
}
The host process then serves content securely for both of these base URLs:
- API JSON data from https://api.mycompany.com
- Web Static Content from https://web.mycompany.com
My preference is also to include full DNS API and Web domain names in the Subject Alternative Names of the certificate, since in some cases this is required by clients:
The Google Chrome browser has some useful tools under Main Menu / More Tools / Developer Tools / Security. We can use them to view SSL strength details, and it shows that our scripts produce TLS 1.3 certificates:
Where Are We?
We have improved our development setup, so that our future UIs and APIs will run over Real World SSL URLs. We are also able to view all OAuth message details on a Developer PC.
Next Steps
- Next we will focus on User Data Management in an OAuth Architecture
- For a list of all blog posts see the Index Page