Page MenuHomeVyOS Platform

Service HTTPS using ACME certificate does not present full chain
Closed, InvalidPublicBUG

Description

After implementing configuration to have a certificate from Let's Encrypt via ACME be requested, then using that certificate for the HTTPS server/API, we noticed the certificate chain is not presented, therefore causing clients to not trust it.

pki {
    certificate LETS-ENCRYPT {
        acme {
            domain-name router.dal1.routedbits.com
            email <email>
        }
    }
}
service {
    https {
        certificates {
            certificate LETS-ENCRYPT
        }
        port 8443
    }
}
yzguy@prometheus:~# openssl s_client -connect router.dal1.routedbits.com:8443
CONNECTED(00000003)
depth=0 CN = router.dal1.routedbits.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = router.dal1.routedbits.com
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 CN = router.dal1.routedbits.com
verify return:1
---
Certificate chain
 0 s:CN = router.dal1.routedbits.com
   i:C = US, O = Let's Encrypt, CN = R3
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jan 31 19:03:42 2024 GMT; NotAfter: Apr 30 19:03:41 2024 GMT
---

The ACME support was originally added in https://github.com/vyos/vyos-1x/pull/2758, and looking at the code that was removed it referenced the fullchain.pem

ssl_certificate {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/fullchain.pem;

The new changes later reference only the cert.pem

tmp = read_file(f'{vyos_certbot_dir}/live/{name}/cert.pem')

Poking around, just copying /config/auth/letsencrypt/live/LETS-ENCRYPT/fullchain.pem over cert.pem, then deleting the service https section, adding it back (forcing it to copy the file again just cases it to load the first certificate

Seems perhaps we just need code that loads the chain certificates into PKI CAs, or when it loads the fullchain.pem, it loads all of them instead of just the one

The load_certificate method uses x509.load_pem_x509_certificate which seems to load just one, but there is also load_pem_x509_certificates (would require an upgrade of cryptography to 39.0.0+) that can load multiple, giving you a list of certificates.

This should give you a way to loop over all the certs in the fullchain.pem and load them in just like cert.pem is being loaded now

Something like

from vyos.utils.file import read_file
from vyos.pki import load_certificates # new method
from vyos.pki import encode_certificate
from vyos.pki import is_ca_certificate

tmp = read_file(f'{vyos_certbot_dir}/live/{name}/fullchain.pem')
tmp = load_certificates(tmp, wrap_tags=False)

for cert in tmp:
  ca_cert_base64s = []
  if not is_ca_certificate(cert)
    cert_base64 = "".join(encode_certificate(tmp).strip().split("\n")[1:-1])
  else:
    ca_cert_base64s.append("".join(encode_certificate(cert).strip().split("\n")[1:-1]))

Details

Difficulty level
Normal (likely a few hours)
Version
1.5-rolling-202401290023
Why the issue appeared?
Will be filled on close
Is it a breaking change?
Perfectly compatible
Issue type
Improvement (missing useful functionality)

Event Timeline

yzguy changed Difficulty level from Easy (less than an hour) to Normal (likely a few hours).
Viacheslav triaged this task as Normal priority.Mar 15 2024, 2:24 PM

This is how the PKI subsystem works. You should add the LE root certificates manually so we can build a full chain using the internal PKI tools for HTTPS server.

like for the staging LE PEM

set pki ca STAGING-PEM certificate 'MIIFWzCCA0OgAwIBAgIQTfQrldHumzpMLrM7jRBd1jANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJVUzEzMDEGA1UEChMqKFNUQUdJTkcpIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMSIwIAYDVQQDExkoU1RBR0lORykgUHJldGVuZCBQZWFyIFgxMB4XDTIwMDkwNDAwMDAwMFoXDTI1MDkxNTE2MDAwMFowWTELMAkGA1UEBhMCVVMxIDAeBgNVBAoTFyhTVEFHSU5HKSBMZXQncyBFbmNyeXB0MSgwJgYDVQQDEx8oU1RBR0lORykgQXJ0aWZpY2lhbCBBcHJpY290IFIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6TR8+74b46mOE1FUwBrvxzEYLck3iasmKrcQkb+gy/z9Jy7QNIAl0B9pVKp4YU76JwxF5DOZZhi7vK7SbCkK6FbHlyU5BiDYIxbbfvOL/jVGqdsSjNaJQTg3C3XrJja/HA4WCFEMVoT2wDZm8ABC1N+IQe7Q6FEqc8NwmTSnmmRQm4TQvr06DP+zgFK/MNubxWWDSbSKKTH5im5j2fZfg+j/tM1bGaczFWw8/lSnukyn5J2L+NJYnclzkXoh9nMFnyPmVbfyDPOc4Y25aTzVoeBKXa/cZ5MM+WddjdLbiWvm19f1sYn1aRaAIrkppv7kkn83vcth8XCG39qC2ZvaQIDAQABo4IBEDCCAQwwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTecnpI3zHDplDfn4Uj31c3S10uZTAfBgNVHSMEGDAWgBS182Xy/rAKkh/7PH3zRKCsYyXDFDA2BggrBgEFBQcBAQQqMCgwJgYIKwYBBQUHMAKGGmh0dHA6Ly9zdGcteDEuaS5sZW5jci5vcmcvMCsGA1UdHwQkMCIwIKAeoByGGmh0dHA6Ly9zdGcteDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQBgt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCNDLam9yN0EFxxn/3p+ruWO6n/9goCAM5PT6cC6fkjMs4uas6UGXJjr5j7PoTQf3C1vuxiIGRJC6qxV7yc6U0X+w0Mj85sHI5DnQVWN5+D1er7mp13JJA0xbAbHa3Rlczny2Q82XKui8WHuWra0gb2KLpfboYj1Ghgkhr3gau83pC/WQ8HfkwcvSwhIYqTqxoZUq8HIf3M82qS9aKOZE0CEmSyR1zZqQxJUT7emOUapkUN9poJ9zGc+FgRZvdro0XByphWXDaqMYph0DxW/10ig5j4xmmNDjCRmqIKsKoWA52wBTKKXK1na2ty/lW5dhtAxkz5rVZFd4sgS4J0O+zm6d5GRkWsNJ4knotGXl8vtS3X40KXeb3A5+/3p0qaD215Xq8oSNORfB2oI1kQuyEAJ5xvPTdfwRlyRG3lFYodrRg6poUBD/8fNTXMtzydpRgyzUQZh/18F6B/iW6cbiRN9r2Hkh05Om+q0/6w0DdZe+8YrNpfhSObr/1eVZbKGMIYqKmyZbBNu5ysENIK5MPc14mUeKmFjpN840VR5zunoU52lqpLDua/qIM8idk86xGWxx2ml43DO/Ya/tVZVok0mO0TUjzJIfPqyvr455IsIut4RlCR9Iq0EDTve2/ZwCuGhSjpTUFGSiQrR2JK2Evp+o6AETUkBCO1aw0PpQBPDQ=='

The HTTPS server will form a full chain in the background automatically
https://github.com/vyos/vyos-1x/blob/afaf715194a922c92f6ff6058abc0e4b9ff570d4/src/conf_mode/service_https.py#L184-L187