Add the ability to reuse a private key

This commit is contained in:
xenolf 2016-01-08 10:14:41 +01:00
parent 6e33cd1b84
commit db1a519684
4 changed files with 49 additions and 16 deletions

View file

@ -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

View file

@ -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
View file

@ -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.",
},
}, },
}, },
} }

View file

@ -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)