Underscoring the “private” in private key

Last weekend, Eric Lawrence found that the Amazon Music app, like Zoom, can automatically be launched from web pages without any user interaction. The way this works is through a local web server, accepting HTTP requests from web pages to, for example, instantly launch the Amazon Music app to play a particular song. This bypasses the built-in safety net in browsers that seek confirmation from users before launching an application. The right way to implement this feature is to register and use a custom protocol handler.

Safari confirmation prompt for launching an application.

However, that wasn’t the thing that stood out. The local web server is accessible over HTTPS using a public DNS name (amazonmusiclocal.com) that resolves to 127.0.0.1. This is unusual, because browsers consider localhost to be a secure context, which means that you can make HTTP requests to localhost (e.g. http://localhost:8080) from an HTTPS origin. If it is possible to establish an HTTPS connection using the aforementioned public DNS name, chances are that the private key for the publicly trusted certificate is likely hard-coded somewhere in the application binaries.

To verify whether that was the case, I installed the Amazon Music app in a clean virtual machine. Which allowed me to identify the Amazon Music local web server by listing the processes that are listening on any given port. As depicted below, the Amazon Music Helper process is listening on TCP port 18800. It is also interesting to note that it binds to any interface, not just the loopback interface.

Amazon Music local web server listening on the loopback interface

Then I used cURL to verify that the local web server presents a valid and publicly trusted certificate and that we can successfully establish an HTTPS connection.

Using cURL to establish an HTTPS connection with amazonmusiclocal.com

The local web server is handled by the Amazon Music Helper program. If the RSA private key is is hard-coded in that binary, then it should be fairly straightforward to locate it by looking for known indicators. For example, a PEM or DER encoded 2048-bit RSA private key, can be recognized by byte sequences 30 82 04 and 4d 49 49 45 (Base64 string "MIIE"), respectively. Unfortunately, this yielded no results.

The next step I took was to load the binary into a disassembler to dive a little deeper. What immediately stood out was the procedure at 0x10000ac0 labeled Morpho::HttpDispatcher::parseCertAndKey() and its calls to the following procedures:

  • Morpho::HelperServer::getAesKey()
  • Morpho::HelperServer::getAesIV()
  • Morpho::AesEncryptor::decrypt() (two times)

This indicated that both the RSA private key and the leaf certificate were encrypted using AES. The respective hexadecimal-encoded ciphertexts, as shown below, are copied into memory, and then read by the two decrypt() calls.

Private key and leaf certificate ciphertexts

In order to decrypt the private key and leaf certificate, I had to find the AES encryption key and initialization vector (IV). Unfortunately, I couldn’t easily find either of them hard-coded in the binary. Hence, I opted to use a debugger (LLDB) instead to launch the Amazon Music Helper program, set breakpoints right before the decryption calls, and read the encryption key and IV from memory.

  • 256-bit key: fb4098e00b3fb0562a625d96599b2be6a3876ccfc21908c1cb173850745d1cd5
  • 128-bit IV: 3ac0660ee766394f65a6eb26ae3dbf84

Given the RSA private key and leaf certificate ciphertexts are saved as files key.der.enc and certificate.pem.enc, respectively, both can be decrypted using the following OpenSSL commands. The ciphertexts and the decrypted private key and leaf certificate can be found here.

Decrypting the private key
Decrypting the leaf certificate

To verify that the private key corresponds to the public key in the leaf certificate, the SHA-256 hash of the public key derived from the private key can be compared to the SHA-256 hash of the public key in the leaf certificate.

Comparing the SHA-256 public key hashes

It should be obvious to anyone, but private keys are meant to remain private. This holds also true for the private key that corresponds to the public key in the publicly trusted certificates for www.amazonmusiclocal.com that Amazon shipped with the Amazon Music app. For reasons unclear to me, Amazon obscured this fact by encrypting private key and only hard-coding the ciphertext. As demonstrated, this doesn’t make a difference as long as the encryption key is trivially recoverable. As outlined in the CA/Browser Forum’s Baseline Requirements, this is considered a key compromise, which means that private key should be revoked.

A Private Key is said to be compromised if its value has been disclosed to an unauthorized person or an unauthorized person has had access to it.

Baseline Requirements (1.6.1. Definitions)

The CA SHALL revoke a Certificate within 24 hours if one or more of the following occurs:

  1. The Subscriber requests in writing that the CA revoke the Certificate;
  2. The Subscriber notifies the CA that the original certificate request was not authorized and does not retroactively grant authorization;
  3. The CA obtains evidence that the Subscriber’s Private Key corresponding to the Public Key in the Certificate suffered a Key Compromise; or
  4. The CA obtains evidence that the validation of domain authorization or control for any Fully-Qualified Domain Name or IP address in the Certificate should not be relied upon.
Baseline Requirements (4.9.1.2. Reasons for Revoking a Subordinate CA Certificate)

There was no need to report the certificate to the issuing certificate authority, which also happened to be Amazon, because the certificate was altegader revoked within hours after Eric Lawrence’s original tweet.

Requesting the OCSP revocation status of the leaf certificate