forked from TrueCloudLab/lego
Add the ability to reuse a private key
This commit is contained in:
parent
6e33cd1b84
commit
db1a519684
4 changed files with 49 additions and 16 deletions
|
@ -1,6 +1,7 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -10,7 +11,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -194,12 +194,14 @@ func (c *Client) AgreeToTOS() error {
|
||||||
|
|
||||||
// ObtainCertificate tries to obtain a single certificate using all domains passed into it.
|
// ObtainCertificate tries to obtain a single certificate using all domains passed into it.
|
||||||
// The first domain in domains is used for the CommonName field of the certificate, all other
|
// The first domain in domains is used for the CommonName field of the certificate, all other
|
||||||
// domains are added using the Subject Alternate Names extension.
|
// domains are added using the Subject Alternate Names extension. A new private key is generated
|
||||||
|
// for every invocation of this function. If you do not want that you can supply your own private key
|
||||||
|
// in the privKey parameter. If this parameter is non-nil it will be used instead of generating a new one.
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and
|
// If bundle is true, the []byte contains both the issuer certificate and
|
||||||
// your issued certificate as a bundle.
|
// your issued certificate as a bundle.
|
||||||
// This function will never return a partial certificate. If one domain in the list fails,
|
// This function will never return a partial certificate. If one domain in the list fails,
|
||||||
// the whole certificate will fail.
|
// the whole certificate will fail.
|
||||||
func (c *Client) ObtainCertificate(domains []string, bundle bool) (CertificateResource, map[string]error) {
|
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey) (CertificateResource, map[string]error) {
|
||||||
if bundle {
|
if bundle {
|
||||||
logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
||||||
} else {
|
} else {
|
||||||
|
@ -220,7 +222,7 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool) (CertificateRe
|
||||||
|
|
||||||
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
cert, err := c.requestCertificate(challenges, bundle)
|
cert, err := c.requestCertificate(challenges, bundle, privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, chln := range challenges {
|
for _, chln := range challenges {
|
||||||
failures[chln.Domain] = err
|
failures[chln.Domain] = err
|
||||||
|
@ -255,6 +257,7 @@ func (c *Client) RevokeCertificate(certificate []byte) error {
|
||||||
// this function will start a new-cert flow where a new certificate gets generated.
|
// this function will start a new-cert flow where a new certificate gets generated.
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and
|
// If bundle is true, the []byte contains both the issuer certificate and
|
||||||
// your issued certificate as a bundle.
|
// your issued certificate as a bundle.
|
||||||
|
// For private key reuse the PrivateKey property of the passed in CertificateResource should be non-nil.
|
||||||
func (c *Client) RenewCertificate(cert CertificateResource, bundle bool) (CertificateResource, error) {
|
func (c *Client) RenewCertificate(cert CertificateResource, bundle bool) (CertificateResource, error) {
|
||||||
// Input certificate is PEM encoded. Decode it here as we may need the decoded
|
// Input certificate is PEM encoded. Decode it here as we may need the decoded
|
||||||
// cert later on in the renewal process. The input may be a bundle or a single certificate.
|
// cert later on in the renewal process. The input may be a bundle or a single certificate.
|
||||||
|
@ -316,7 +319,15 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle bool) (Certif
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newCert, failures := c.ObtainCertificate([]string{cert.Domain}, bundle)
|
var privKey crypto.PrivateKey
|
||||||
|
if cert.PrivateKey != nil {
|
||||||
|
privKey, err = parsePEMPrivateKey(cert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return CertificateResource{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newCert, failures := c.ObtainCertificate([]string{cert.Domain}, bundle, privKey)
|
||||||
return newCert, failures[cert.Domain]
|
return newCert, failures[cert.Domain]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,16 +423,19 @@ func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[s
|
||||||
return challenges, failures
|
return challenges, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) requestCertificate(authz []authorizationResource, bundle bool) (CertificateResource, error) {
|
func (c *Client) requestCertificate(authz []authorizationResource, bundle bool, privKey crypto.PrivateKey) (CertificateResource, error) {
|
||||||
if len(authz) == 0 {
|
if len(authz) == 0 {
|
||||||
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
|
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
|
||||||
}
|
}
|
||||||
|
|
||||||
commonName := authz[0]
|
commonName := authz[0]
|
||||||
privKey, err := generatePrivateKey(rsakey, c.keyBits)
|
var err error
|
||||||
|
if privKey == nil {
|
||||||
|
privKey, err = generatePrivateKey(rsakey, c.keyBits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CertificateResource{}, err
|
return CertificateResource{}, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var san []string
|
var san []string
|
||||||
var authURLs []string
|
var authURLs []string
|
||||||
|
|
|
@ -202,6 +202,19 @@ func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||||
return certificates, nil
|
return certificates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||||
|
keyBlock, _ := pem.Decode(key)
|
||||||
|
|
||||||
|
switch keyBlock.Type {
|
||||||
|
case "RSA PRIVATE KEY":
|
||||||
|
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||||
|
case "EC PRIVATE KEY":
|
||||||
|
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Unknown PEM header value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func generatePrivateKey(t keyType, keyLength int) (crypto.PrivateKey, error) {
|
func generatePrivateKey(t keyType, keyLength int) (crypto.PrivateKey, error) {
|
||||||
switch t {
|
switch t {
|
||||||
case eckey:
|
case eckey:
|
||||||
|
|
4
cli.go
4
cli.go
|
@ -55,6 +55,10 @@ func main() {
|
||||||
Value: 0,
|
Value: 0,
|
||||||
Usage: "The number of days left on a certificate to renew it.",
|
Usage: "The number of days left on a certificate to renew it.",
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "reuse-key",
|
||||||
|
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ func run(c *cli.Context) {
|
||||||
logger().Fatal("Please specify --domains or -d")
|
logger().Fatal("Please specify --domains or -d")
|
||||||
}
|
}
|
||||||
|
|
||||||
cert, failures := client.ObtainCertificate(c.GlobalStringSlice("domains"), true)
|
cert, failures := client.ObtainCertificate(c.GlobalStringSlice("domains"), true, nil)
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
for k, v := range failures {
|
for k, v := range failures {
|
||||||
logger().Printf("[%s] Could not obtain certificates\n\t%s", k, v.Error())
|
logger().Printf("[%s] Could not obtain certificates\n\t%s", k, v.Error())
|
||||||
|
@ -214,11 +214,6 @@ func renew(c *cli.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyBytes, err := ioutil.ReadFile(privPath)
|
|
||||||
if err != nil {
|
|
||||||
logger().Fatalf("Error while loading the private key for domain %s\n\t%s", domain, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
metaBytes, err := ioutil.ReadFile(metaPath)
|
metaBytes, err := ioutil.ReadFile(metaPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger().Fatalf("Error while loading the meta data for domain %s\n\t%s", domain, err.Error())
|
logger().Fatalf("Error while loading the meta data for domain %s\n\t%s", domain, err.Error())
|
||||||
|
@ -230,7 +225,14 @@ func renew(c *cli.Context) {
|
||||||
logger().Fatalf("Error while marshalling the meta data for domain %s\n\t%s", domain, err.Error())
|
logger().Fatalf("Error while marshalling the meta data for domain %s\n\t%s", domain, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Bool("reuse-key") {
|
||||||
|
keyBytes, err := ioutil.ReadFile(privPath)
|
||||||
|
if err != nil {
|
||||||
|
logger().Fatalf("Error while loading the private key for domain %s\n\t%s", domain, err.Error())
|
||||||
|
}
|
||||||
certRes.PrivateKey = keyBytes
|
certRes.PrivateKey = keyBytes
|
||||||
|
}
|
||||||
|
|
||||||
certRes.Certificate = certBytes
|
certRes.Certificate = certBytes
|
||||||
|
|
||||||
newCert, err := client.RenewCertificate(certRes, true)
|
newCert, err := client.RenewCertificate(certRes, true)
|
||||||
|
|
Loading…
Reference in a new issue