Firefox 54 Stopped Trusting Self-Signed Certs

14,904

Solution 1

To mimic the CA-chain requirements mandated by Firefox 54, the following is required:

  1. Keypair marked as a Root-CA, capable of generating an SSL certificate.
  2. Second keypair marked for SSL which obtains a chained certificate from Root-CA

To illustrate how this is done with Java keytool including the steps to create private keystores:

# Create a Root-CA private keystore capable of issuing SSL certificates
keytool -genkeypair -noprompt -alias my-ca -keyalg RSA -keysize 2048 -dname CN=localhost -validity 3650 -keystore .\my-ca.jks -storepass pass77 -keypass pass77 -ext ku:critical=cRLSign,keyCertSign -ext bc:critical=ca:true,pathlen:1

# Export the Root-CA certificate, to be used in the final SSL chain
keytool -exportcert -alias my-ca -keystore .\my-ca.jks -storepass pass77 -keypass pass77 -file .\my-ca.crt -rfc -ext ku:critical=cRLSign,keyCertSign -ext bc:critical=ca:true,pathlen:1

# Create a container SSL private keystore (external localhost.foo.bar dns entry optional:IE11 domain intranet policy)
keytool -genkeypair -noprompt -alias my-ssl -keyalg RSA -keysize 2048 -dname CN=localhost -validity 3650 -keystore .\my-ssl.jks -storepass pass77 -keypass pass77 -ext ku:critical=digitalSignature,keyEncipherment -ext eku=serverAuth,clientAuth -ext san=dns:localhost,dns:localhost.foo.bar -ext bc:critical=ca:false

# Create a certificate signing request (CSR) from our SSL private keystore
keytool -certreq -keyalg RSA -alias my-ssl -file .\my-ssl.csr -keystore .\my-ssl.jks -keypass pass77 -storepass pass77

# Issue an SSL certificate from the Root-CA private keystore in response to the request (external localhost.foo.bar dns entry optional)
keytool -keypass pass77 -storepass pass77 -validity 3650 -keystore .\my-ca.jks -gencert -alias my-ca -infile .\my-ssl.csr -ext ku:critical=digitalSignature,keyEncipherment -ext eku=serverAuth,clientAuth -ext san=dns:localhost,dns:localhost.foo.bar -ext bc:critical=ca:false -rfc -outfile .\my-ssl.crt

# Import Root-CA certificate into SSL private keystore
keytool  -noprompt -import -trustcacerts -alias my-ca -file my-ca.crt -keystore my-ssl.jks -keypass pass77 -storepass pass77

# Import an SSL (chained) certificate into keystore
keytool -import -trustcacerts -alias my-ssl -file my-ssl.crt -keystore my-ssl.jks -keypass pass77 -storepass pass77 -noprompt

Once this is done, only the Root-CA certificate needs to be trusted by Firefox, and can be imported using the GUI or via AutoConfig script.

The SSL server must be restarted using the new SSL private keystore, which will contain the chain of trust to work via SSL.

Since my-ssl.jks contains the entire chain of trust my-ca.jks, my-ca.crt, my-ssl.crt and my-ssl.csr can all safely be deleted (assuming my-ca.crt has been imported properly)

Solution 2

Inspired by the answer of @tresf and based largely on the blogpost How to Create Your Own SSL Certificate Authority for Local HTTPS Development by Brad Touesnard, I created a set of commands using openssl.

# Generate the root key
openssl genrsa -des3 -out myCA.key 2048

# Generate a root-certificate based on the root-key
openssl req -x509 -new -nodes -key myCA.key -sha256 -days 1825 -out myCA.pem

# Generate a new private key
openssl genrsa -out example.com.key 2048

# Generate a Certificate Signing Request (CSR) based on that private key
openssl req -new -key example.com.key -out example.com.csr

# Create a configuration-file
echo \
"authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = example.com
"> example.com.conf

# Create the certificate for the webserver to serve
openssl x509 -req -in example.com.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial \
-out example.com.crt -days 1825 -sha256 -extfile example.com.conf

How to use these files

1. Let your CA be trusted by your browser/keychain

Add myCa.pem to your browser/keychain to trust certificates signed by your new root certificate

2. Sign requests with your certificate

Add example.com.crt and example.com.key to the configuration of your webserver to sign requests to your domain

Solution 3

As @tresf and @Zombaya have stated, Firefox requires two certificates:

  • An authority certificate
  • A development certificate

The authority certificate is used to sign the development certificate. The development certificate is bound to an HTTP port. The web server listens to that port for requests.

Windows Development Environment

Other answers explain what to do in Java and Unix environments. Here's what I do in my Windows development environment. This creates certificates trusted by Firefox, Chrome, and Internet Explorer:

Override DNS with an entry in C:\Windows\System32\drivers\etc\hosts file.

127.0.0.1  dev.brainstorm.com

Create the authority and development certificates and store them in the local machine certificate store using PowerShell. Substitute "Brainstorm" with your company name and DNS entry. Run PowerShell as an administrator.

# Create authority certificate.
# TextExtension adds the Server Authentication enhanced key usage and the CA basic contraint.
$authorityCert = New-SelfSignedCertificate `
    -Subject "CN=Brainstorm CA,OU=IT,O=Brainstorm Certificate Authority,C=US" `
    -KeyAlgorithm RSA `
    -KeyLength 4096 `
    -KeyUsage CertSign, CRLSign, DigitalSignature, KeyEncipherment, DataEncipherment `
    -KeyExportPolicy Exportable `
    -NotBefore (Get-Date) `
    -NotAfter (Get-Date).AddYears(10) `
    -HashAlgorithm SHA256 `
    -CertStoreLocation "Cert:\LocalMachine\My" `
    -FriendlyName "Brainstorm CA" `
    -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1", "2.5.29.19={critical}{text}ca=1")


# Create development certificate.
# Sign it with authority certificate.
# TextExtension adds the Server Authentication enhanced key usage.
$devCert = New-SelfSignedCertificate `
    -Subject "CN=Brainstorm,OU=Application Development,O=Brainstorm,C=US" `
    -DnsName dev.brainstorm.com `
    -KeyAlgorithm RSA `
    -KeyLength 4096 `
    -KeyUsage DigitalSignature, KeyEncipherment, DataEncipherment `
    -KeyExportPolicy Exportable `
    -NotBefore (Get-Date) `
    -NotAfter (Get-Date).AddYears(10) `
    -HashAlgorithm SHA256 `
    -CertStoreLocation "Cert:\LocalMachine\My" `
    -FriendlyName "Brainstorm" `
    -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1") `
    -Signer $authorityCert

# Export authority certificate to file.
$directory = "C:\Users\Erik\Documents\Temp\Certificates\"
if(!(test-path $directory))
{
New-Item -ItemType Directory -Force -Path $directory
}
$authorityCertPath = 'Cert:\LocalMachine\My\' + ($authorityCert.ThumbPrint)
$authorityCertFilename = $directory + "Authority.cer"
Export-Certificate -Cert $authorityCertPath -FilePath $authorityCertFilename

# Import authority certificate from file to Trusted Root store.
Import-Certificate -FilePath $authorityCertFilename -CertStoreLocation "Cert:\LocalMachine\Root"

# Delete authority certificate file.
Remove-Item -Path $authorityCertFilename

Grant developer permission to host a website and service at specific URLs and ports (via IIS Express). Use standard SSL port for website, use another port for service. Why? IIS Express cannot simultaneously host two applications on same port differentiated by host name. They must use different ports.

netsh http add urlacl url=https://dev.brainstorm.com:443/ user="Erik"
netsh http add urlacl url=https://dev.brainstorm.com:44300/ user="Erik"

If you need to remove developer permission to host website at URL:

netsh http delete urlacl url=https://dev.brainstorm.com:443/
netsh http delete urlacl url=https://dev.brainstorm.com:44300/

List the certificates in Local Computer store.

Get-ChildItem -path "Cert:\LocalMachine\My"

Copy the thumbprint of the development certificate (not the authority certificate).

List the certificates bound to HTTP ports. (IIS Express configures ports 44300 - 44399 with its own SSL certificate.)

netsh http show sslcert

Copy the Application ID (it's the same for all IIS Express ports 44300 - 44399). Replace the website and service ports already bound by IIS Express with our development certificate (the certhash is the thumbprint from above). You may need to run netsh first, then enter http command, then enter add sslcert... command.

netsh http add sslcert hostnameport=dev.brainstorm.com:443 certhash=FE035397A4C44AB591A1D9D4DC0B44074D0F95BA appid={214124cd-d05b-4309-9af9-9caa44b2b74a} certstore=my
netsh http add sslcert hostnameport=dev.brainstorm.com:44300 certhash=FE035397A4C44AB591A1D9D4DC0B44074D0F95BA appid={214124cd-d05b-4309-9af9-9caa44b2b74a} certstore=my

If you need to unbind certificates from HTTP ports:

netsh http delete sslcert hostnameport=dev.brainstorm.com:443
netsh http delete sslcert hostnameport=dev.brainstorm.com:44300

In Visual Studio, configure the service's launchSettings.json file (in Properties folder):

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "https://dev.brainstorm.com:44300/",
      "sslPort": 44300
    }
  },
  "profiles": {
    "Default": {
      "commandName": "IISExpress",
      "use64Bit": true
    }
  }
}

In Visual Studio, configure the website's launchSettings.json file (in Properties folder):

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "https://dev.brainstorm.com/",
      "sslPort": 443
    }
  },
  "profiles": {
    "Default": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "use64Bit": true
    }
  }
}

Configure IIS Express (in hidden .vs/config folder):

<sites>
  <site name="Website" id="1" serverAutoStart="true">
    <application path="/">
      <virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebSite" />
    </application>
    <bindings>
      <binding protocol="https" bindingInformation="*:443:dev.brainstorm.com" />
    </bindings>
  </site>
  <site name="Service" id="2">
    <application path="/">
      <virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\IIS Service" />
    </application>
    <bindings>
      <binding protocol="https" bindingInformation="*:44300:dev.brainstorm.com" />
    </bindings>
  </site>
  <siteDefaults>
    <logFile logFormat="W3C" directory="%IIS_USER_HOME%\Logs" />
    <traceFailedRequestsLogging directory="%IIS_USER_HOME%\TraceLogFiles" enabled="true" maxLogFileSizeKB="1024" />
  </siteDefaults>
  <applicationDefaults applicationPool="Clr4IntegratedAppPool" />
  <virtualDirectoryDefaults allowSubDirConfig="true" />
</sites>

In Firefox, navigate to about:config and set the security.enterprise_roots.enabled parameter to true.

Share:
14,904

Related videos on Youtube

tresf
Author by

tresf

Specializes in anything desktop related: Linux, MacOS, Windows. Entrepreneur specializing in niche web technologies. Hobbies include audio production and helping with FOSS audio projects.

Updated on July 06, 2022

Comments

  • tresf
    tresf almost 2 years

    With the recent upgrade of Firefox 54, my self-signed localhost SSL certificate stopped being trusted.

    I've been using a Firefox AutoConfigure script to install this certificate, and the technique has been working successfully for several years. Firefox uses its own certificate store, cert8.db which contains the certificate, verified using Firefox Preferences, Advanced, Certificates, View Certificates, Authorities.

    This is reproducible on both MacOS as well as Windows. I've attached a sample certificate for reference. This is identical to one we would install.

    What changed in Firefox 54? I've reviewed the changelog and can't find anything specific to how it trusts certificates.

    Edit: Link to Firefox bug that most likely introduced this change: firefox #1294580

    -----BEGIN CERTIFICATE-----
    MIID/DCCAuSgAwIBAgIEDZj+fTANBgkqhkiG9w0BAQsFADCBmjELMAkGA1UEBhMC
    VVMxCzAJBgNVBAgTAk5ZMRIwEAYDVQQHEwlDYW5hc3RvdGExGzAZBgNVBAoTElFa
    IEluZHVzdHJpZXMsIExMQzEbMBkGA1UECxMSUVogSW5kdXN0cmllcywgTExDMRww
    GgYJKoZIhvcNAQkBFg1zdXBwb3J0QHF6LmlvMRIwEAYDVQQDEwlsb2NhbGhvc3Qw
    HhcNMTcwMjEyMDMzMjEwWhcNMzcwMjEyMDMzMjEwWjCBmjELMAkGA1UEBhMCVVMx
    CzAJBgNVBAgTAk5ZMRIwEAYDVQQHEwlDYW5hc3RvdGExGzAZBgNVBAoTElFaIElu
    ZHVzdHJpZXMsIExMQzEbMBkGA1UECxMSUVogSW5kdXN0cmllcywgTExDMRwwGgYJ
    KoZIhvcNAQkBFg1zdXBwb3J0QHF6LmlvMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEi
    MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCemwdWhvytOwhsRyEo/9ck3nKP
    oBvMdkaiXKbMWlYZfYyb/EsJzw/LiEqGGhflWjneQLcgq0nuTtRaA9cm/vgPtVRX
    OHewJeYBI2C4avJyjdFfQYHJKxuLi3nwmZ5JwcDm04H6SADwdyQuYB4AFr32uY5D
    3id0gyDV+EX9sSOPThtdBpEbaBcFmAdAGdQUCzSJyi4Yu6UkIs7OPBHp9lOvm8VQ
    r6ZVnqdFEXmxgpgMS0sQwDwZnBB3hFcVmE/sYy+2gV/h+yvRUjgqwC/SoLh9f4D0
    eG19E3OEmsSyFM9K2Wl4ltOE/Aq1KFm7dPw34nDKxYcVDpm6JczWycbCi4zjAgMB
    AAGjSDBGMCUGA1UdEQQeMByCCWxvY2FsaG9zdIIPbG9jYWxob3N0LnF6LmlvMB0G
    A1UdDgQWBBT3Qs6/qQSmunLIGKQxz3GBO+RgIzANBgkqhkiG9w0BAQsFAAOCAQEA
    lVI3sWr6wTtVtc7gsV9Kk99xNOUm5W2kp/Ot5CHvUIw68Ar1WIiouWT9BbjkvFc+
    QpbtqKhluTdHI1/JP44r7A8qMApyYQLhw3AS/WTzRoOBOECJk3hYgGBIxAaoqvKY
    HKCOULTqkoX8pgNhYobebn/BpeoSvXW+oxT21y7ElE01eMtrLsqXKaN5FODxVzJq
    7jatxCaRZCy2Ki3R0cB5ZMIVvWSDeT1TLgh5UKWdldNsTdTNhbQSdm8ayU0uj4fH
    tKqwh9lKvrBJiawghmADjZjeNEQzIJfjznF/soqVZnRNZO/phDH327lDE2UcD1IN
    k4BqNRJmz5lrQeYz8GcfYA==
    -----END CERTIFICATE-----
    
  • tresf
    tresf almost 7 years
    Thanks although that didn't work for a one-link chain. Here's the new cert: gist.githubusercontent.com/tresf/… Side note... I'm using Java's keytool and nameConstraints doesn't seem to be supported (or not documented)
  • David Keeler
    David Keeler almost 7 years
    It looks like that new certificate and the one you pasted in the original question have different public keys. Unless you either re-use the same key or re-issue the server's certificate and sign it with the key from this new root certificate, it won't work. Also, you shouldn't need to include the subjectAlternativeName extension for the root certificate.
  • tresf
    tresf almost 7 years
    Correct, we re-issue the keys and restart the server on each cert generation as part of our security model. The fact that the public key differs is a valid observation but irrelevant to the problem. The private keys are uniquely generated so they can be safely shared here as well if needed.
  • tresf
    tresf almost 7 years
    The subjectAlternativeName extension is needed since it's a self-signed certificate (it's its own end-entity), which is the primary purpose of a self-signed certificate for small self-contained applications. If Firefox decides to never allow single, self-signed certificates (as opposed to the way Edge, IE11, Chrome, Opera and Safari do allow them), this flag would instead be moved to the intermediate certificate.
  • tresf
    tresf almost 7 years
    We've successful tested this technique on all major platforms to success. Many coming here may be looking for a pure openssl solution and one should also be provided as it is much more common than keytool for setting up a certificate chain.
  • Zombaya
    Zombaya about 6 years
    I did indeed wound up here looking for a pure openssl-solution and provided it as an answer.
  • Mugur 'Bud' Chirica
    Mugur 'Bud' Chirica about 6 years
    Your answer was incredibly helpful, thank you very much.
  • savram
    savram almost 6 years
  • ford04
    ford04 over 3 years
    Excellent answer. You may need to convert to .pem for the web server: cat my.crt my.key > my.pem
  • Stefan Schmidt
    Stefan Schmidt about 2 years
    As mentioned below an additional requirement is to set security.enterprise_roots.enabled to true in about:config of Firefox. In macOS the root certificate worked for me after I checked Always Trust for Secure Sockets Layer (SSL) and X.509 Basic Policy in the Trust section of the root certificate.