Add ObtainCertificateForCSR()

This commit also breaks requestCertificate() into two parts, the first of
which generates a CSR, the second of which became requestCertificateForCsr()
which does what the name implies.
This commit is contained in:
Will Glynn 2016-02-11 19:00:41 -06:00 committed by Chris Marchesi
parent c570b320ae
commit 8d7afd02b9

View file

@ -278,6 +278,69 @@ func (c *Client) AgreeToTOS() error {
return err
}
// ObtainCertificateForCSR tries to obtain a certificate matching the CSR passed into it.
// The domains are inferred from the CommonName and SubjectAltNames, if any. The private key
// for this CSR is not required.
// If bundle is true, the []byte contains both the issuer certificate and
// your issued certificate as a bundle.
// This function will never return a partial certificate. If one domain in the list fails,
// the whole certificate will fail.
func (c *Client) ObtainCertificateForCSR(csr []byte, bundle bool) (CertificateResource, map[string]error) {
// parse the CSR
parsedCsr, err := x509.ParseCertificateRequest(csr)
if err != nil {
return CertificateResource{}, map[string]error{"csr": err}
}
// figure out what domains it concerns
// start with the common name
domains := []string{parsedCsr.Subject.CommonName}
// loop over the SubjectAltName DNS names
DNSNames:
for _, sanName := range parsedCsr.DNSNames {
// /
for _, existingName := range domains {
if existingName == sanName {
// duplicate; skip this name
continue DNSNames
}
}
// name is unique
domains = append(domains, sanName)
}
if bundle {
logf("[INFO][%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
} else {
logf("[INFO][%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
}
challenges, failures := c.getChallenges(domains)
// If any challenge fails - return. Do not generate partial SAN certificates.
if len(failures) > 0 {
return CertificateResource{}, failures
}
errs := c.solveChallenges(challenges)
// If any challenge fails - return. Do not generate partial SAN certificates.
if len(errs) > 0 {
return CertificateResource{}, errs
}
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
cert, err := c.requestCertificateForCsr(challenges, bundle, csr, nil)
if err != nil {
for _, chln := range challenges {
failures[chln.Domain] = err
}
}
return cert, failures
}
// 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
// domains are added using the Subject Alternate Names extension. A new private key is generated
@ -528,7 +591,6 @@ func (c *Client) requestCertificate(authz []authorizationResource, bundle bool,
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
}
commonName := authz[0]
var err error
if privKey == nil {
privKey, err = generatePrivateKey(c.keyType)
@ -537,11 +599,11 @@ func (c *Client) requestCertificate(authz []authorizationResource, bundle bool,
}
}
// determine certificate name(s) based on the authorization resources
commonName := authz[0]
var san []string
var authURLs []string
for _, auth := range authz[1:] {
san = append(san, auth.Domain)
authURLs = append(authURLs, auth.AuthURL)
}
// TODO: should the CSR be customizable?
@ -550,6 +612,17 @@ func (c *Client) requestCertificate(authz []authorizationResource, bundle bool,
return CertificateResource{}, err
}
return c.requestCertificateForCsr(authz, bundle, csr, pemEncode(privKey))
}
func (c *Client) requestCertificateForCsr(authz []authorizationResource, bundle bool, csr []byte, privateKeyPem []byte) (CertificateResource, error) {
commonName := authz[0]
var authURLs []string
for _, auth := range authz[1:] {
authURLs = append(authURLs, auth.AuthURL)
}
csrString := base64.URLEncoding.EncodeToString(csr)
jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: authURLs})
if err != nil {
@ -561,7 +634,6 @@ func (c *Client) requestCertificate(authz []authorizationResource, bundle bool,
return CertificateResource{}, err
}
privateKeyPem := pemEncode(privKey)
cerRes := CertificateResource{
Domain: commonName.Domain,
CertURL: resp.Header.Get("Location"),