Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
r/certificate: do not require exactly 2 certificates in the bundle
Browse files Browse the repository at this point in the history
Let's Encrypt, in part of the work in migrating to their own independent
root CA certificate, has introduced an additional intermediate in the
default chain to help compatibility with older systems, namely Android
devices.

The new chain is now being issued with new requests, which has caused
issues with our current assertions where we look for two certificates.
This assertion possibly could fail with other ACME CAs that are not
Let's Encrypt that may eventually need to do something similar.

For now, we are just removing the assertion that expects this. For PEM
encoding we are just concatenating the entire collection of issuer
certificates in issuer_pem. This will be migrated to a list at a later
time. Since PFX encoding takes certificates un-encoded, this is a more
natural, graceful change.

Additionally, we should look to add a certificate chain preference at a
later time, as Let's Encrypt will be eventually transitioning their
default chain to one that does not include the cross-signed
intermediate.

Related to vancluever#154.

(cherry picked from commit a8f59aa)
  • Loading branch information
vancluever authored and saitho committed Feb 28, 2021
1 parent cd08e2b commit bb176ae
Showing 1 changed file with 15 additions and 14 deletions.
29 changes: 15 additions & 14 deletions acme/acme_structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,28 +208,33 @@ func certDaysRemaining(cert *certificate.Resource) (int64, error) {
return remaining / 86400, nil
}

// splitPEMBundle gets a slice of x509 certificates from parsePEMBundle,
// and always returns 2 certificates - the issued cert first, and the issuer
// certificate second.
// splitPEMBundle gets a slice of x509 certificates from
// parsePEMBundle.
//
// if the certificate count in a bundle is != 2 then this function will fail.
// The first certificate split is returned as the issued certificate,
// with the rest returned as the issuer (intermediate) chain.
//
// Technically, it will be possible for issuer to be empty, if there
// are zero certificates in the intermediate chain. This is highly
// unlikely, however.
func splitPEMBundle(bundle []byte) (cert, issuer []byte, err error) {
cb, err := parsePEMBundle(bundle)
if err != nil {
return
}
if len(cb) != 2 {
err = fmt.Errorf("Certificate bundle does not contain exactly 2 certificates")
return
}

// lego always returns the issued cert first, if the CA is first there is a problem
if cb[0].IsCA {
err = fmt.Errorf("First certificate is a CA certificate")
return
}

cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cb[0].Raw})
issuer = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cb[1].Raw})
issuer = make([]byte, 0)
for _, ic := range cb[1:] {
issuer = append(issuer, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ic.Raw})...)
}

return
}

Expand All @@ -244,10 +249,6 @@ func bundleToPKCS12(bundle, key []byte, password string) ([]byte, error) {
return nil, err
}

if len(cb) != 2 {
return nil, fmt.Errorf("Certificate bundle does not contain exactly 2 certificates")
}

// lego always returns the issued cert first, if the CA is first there is a problem
if cb[0].IsCA {
return nil, fmt.Errorf("First certificate is a CA certificate")
Expand All @@ -258,7 +259,7 @@ func bundleToPKCS12(bundle, key []byte, password string) ([]byte, error) {
return nil, err
}

pfxData, err := pkcs12.Encode(rand.Reader, pk, cb[0], cb[1:2], password)
pfxData, err := pkcs12.Encode(rand.Reader, pk, cb[0], cb[1:], password)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit bb176ae

Please sign in to comment.