forked from TrueCloudLab/lego
Initial support for SAN certificates
This commit is contained in:
parent
f6576e8815
commit
27a8cff3c6
3 changed files with 104 additions and 37 deletions
133
acme/client.go
133
acme/client.go
|
@ -208,9 +208,17 @@ func (c *Client) ObtainCertificates(domains []string, bundle bool) ([]Certificat
|
||||||
return nil, failures
|
return nil, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove failed challenges from slice
|
||||||
|
var succeededChallenges []authorizationResource
|
||||||
|
for _, chln := range challenges {
|
||||||
|
if failures[chln.Domain] == nil {
|
||||||
|
succeededChallenges = append(succeededChallenges, chln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logf("[INFO] acme: Validations succeeded; requesting certificates")
|
logf("[INFO] acme: Validations succeeded; requesting certificates")
|
||||||
|
|
||||||
certs, err := c.requestCertificates(challenges, bundle)
|
certs, err := c.requestCertificates(succeededChallenges, bundle)
|
||||||
for k, v := range err {
|
for k, v := range err {
|
||||||
failures[k] = v
|
failures[k] = v
|
||||||
}
|
}
|
||||||
|
@ -218,6 +226,47 @@ func (c *Client) ObtainCertificates(domains []string, bundle bool) ([]Certificat
|
||||||
return certs, failures
|
return certs, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ObtainSANCertificate(domains []string, bundle bool) (CertificateResource, map[string]error) {
|
||||||
|
if bundle {
|
||||||
|
logf("[INFO] acme: Obtaining bundled SAN certificate for %v", strings.Join(domains, ", "))
|
||||||
|
} else {
|
||||||
|
logf("[INFO] acme: Obtaining SAN certificate for %v", strings.Join(domains, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
challenges, failures := c.getChallenges(domains)
|
||||||
|
if len(challenges) == 0 {
|
||||||
|
return CertificateResource{}, failures
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := c.solveChallenges(challenges)
|
||||||
|
for k, v := range errs {
|
||||||
|
failures[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(failures) == len(domains) {
|
||||||
|
return CertificateResource{}, failures
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove failed challenges from slice
|
||||||
|
var succeededChallenges []authorizationResource
|
||||||
|
for _, chln := range challenges {
|
||||||
|
if failures[chln.Domain] == nil {
|
||||||
|
succeededChallenges = append(succeededChallenges, chln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logf("[INFO] acme: Validations succeeded; requesting certificates")
|
||||||
|
|
||||||
|
cert, err := c.requestCertificate(succeededChallenges, bundle)
|
||||||
|
if err != nil {
|
||||||
|
for _, chln := range succeededChallenges {
|
||||||
|
failures[chln.Domain] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, failures
|
||||||
|
}
|
||||||
|
|
||||||
// RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
// RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||||
func (c *Client) RevokeCertificate(certificate []byte) error {
|
func (c *Client) RevokeCertificate(certificate []byte) error {
|
||||||
certificates, err := parsePEMBundle(certificate)
|
certificates, err := parsePEMBundle(certificate)
|
||||||
|
@ -335,7 +384,7 @@ func (c *Client) RenewCertificate(cert CertificateResource, revokeOld bool, bund
|
||||||
|
|
||||||
// Looks through the challenge combinations to find a solvable match.
|
// Looks through the challenge combinations to find a solvable match.
|
||||||
// Then solves the challenges in series and returns.
|
// Then solves the challenges in series and returns.
|
||||||
func (c *Client) solveChallenges(challenges []*authorizationResource) map[string]error {
|
func (c *Client) solveChallenges(challenges []authorizationResource) map[string]error {
|
||||||
// loop through the resources, basically through the domains.
|
// loop through the resources, basically through the domains.
|
||||||
failures := make(map[string]error)
|
failures := make(map[string]error)
|
||||||
for _, authz := range challenges {
|
for _, authz := range challenges {
|
||||||
|
@ -378,8 +427,8 @@ func (c *Client) chooseSolvers(auth authorization, domain string) map[int]solver
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the challenges needed to proof our identifier to the ACME server.
|
// Get the challenges needed to proof our identifier to the ACME server.
|
||||||
func (c *Client) getChallenges(domains []string) ([]*authorizationResource, map[string]error) {
|
func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[string]error) {
|
||||||
resc, errc := make(chan *authorizationResource), make(chan domainError)
|
resc, errc := make(chan authorizationResource), make(chan domainError)
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
go func(domain string) {
|
go func(domain string) {
|
||||||
|
@ -413,11 +462,11 @@ func (c *Client) getChallenges(domains []string) ([]*authorizationResource, map[
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
resc <- &authorizationResource{Body: authz, NewCertURL: links["next"], AuthURL: resp.Header.Get("Location"), Domain: domain}
|
resc <- authorizationResource{Body: authz, NewCertURL: links["next"], AuthURL: resp.Header.Get("Location"), Domain: domain}
|
||||||
}(domain)
|
}(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
var responses []*authorizationResource
|
var responses []authorizationResource
|
||||||
failures := make(map[string]error)
|
failures := make(map[string]error)
|
||||||
for i := 0; i < len(domains); i++ {
|
for i := 0; i < len(domains); i++ {
|
||||||
select {
|
select {
|
||||||
|
@ -437,10 +486,17 @@ func (c *Client) getChallenges(domains []string) ([]*authorizationResource, map[
|
||||||
// requestCertificates iterates all granted authorizations, creates RSA private keys and CSRs.
|
// requestCertificates iterates all granted authorizations, creates RSA private keys and CSRs.
|
||||||
// It then uses these to request a certificate from the CA and returns the list of successfully
|
// It then uses these to request a certificate from the CA and returns the list of successfully
|
||||||
// granted certificates.
|
// granted certificates.
|
||||||
func (c *Client) requestCertificates(challenges []*authorizationResource, bundle bool) ([]CertificateResource, map[string]error) {
|
func (c *Client) requestCertificates(challenges []authorizationResource, bundle bool) ([]CertificateResource, map[string]error) {
|
||||||
resc, errc := make(chan CertificateResource), make(chan domainError)
|
resc, errc := make(chan CertificateResource), make(chan domainError)
|
||||||
for _, authz := range challenges {
|
for _, authz := range challenges {
|
||||||
go c.requestCertificate(authz, resc, errc, bundle)
|
go func(authz authorizationResource, resc chan CertificateResource, errc chan domainError) {
|
||||||
|
certRes, err := c.requestCertificate([]authorizationResource{authz}, bundle)
|
||||||
|
if err != nil {
|
||||||
|
errc <- domainError{Domain: authz.Domain, Error: err}
|
||||||
|
} else {
|
||||||
|
resc <- certRes
|
||||||
|
}
|
||||||
|
}(authz, resc, errc)
|
||||||
}
|
}
|
||||||
|
|
||||||
var certs []CertificateResource
|
var certs []CertificateResource
|
||||||
|
@ -457,39 +513,51 @@ func (c *Client) requestCertificates(challenges []*authorizationResource, bundle
|
||||||
close(resc)
|
close(resc)
|
||||||
close(errc)
|
close(errc)
|
||||||
|
|
||||||
return certs, nil
|
return certs, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) requestCertificate(authz *authorizationResource, result chan CertificateResource, errc chan domainError, bundle bool) {
|
func (c *Client) requestSANCertificate() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) requestCertificate(authz []authorizationResource, bundle bool) (CertificateResource, error) {
|
||||||
|
if len(authz) == 0 {
|
||||||
|
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
|
||||||
|
}
|
||||||
|
|
||||||
|
commonName := authz[0]
|
||||||
privKey, err := generatePrivateKey(rsakey, c.keyBits)
|
privKey, err := generatePrivateKey(rsakey, c.keyBits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- domainError{Domain: authz.Domain, Error: err}
|
return CertificateResource{}, err
|
||||||
return
|
}
|
||||||
|
|
||||||
|
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?
|
// TODO: should the CSR be customizable?
|
||||||
csr, err := generateCsr(privKey.(*rsa.PrivateKey), authz.Domain)
|
csr, err := generateCsr(privKey.(*rsa.PrivateKey), commonName.Domain, san)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- domainError{Domain: authz.Domain, Error: err}
|
return CertificateResource{}, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
csrString := base64.URLEncoding.EncodeToString(csr)
|
csrString := base64.URLEncoding.EncodeToString(csr)
|
||||||
jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: []string{authz.AuthURL}})
|
jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: authURLs})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- domainError{Domain: authz.Domain, Error: err}
|
return CertificateResource{}, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.jws.post(authz.NewCertURL, jsonBytes)
|
resp, err := c.jws.post(commonName.NewCertURL, jsonBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- domainError{Domain: authz.Domain, Error: err}
|
return CertificateResource{}, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKeyPem := pemEncode(privKey)
|
privateKeyPem := pemEncode(privKey)
|
||||||
cerRes := CertificateResource{
|
cerRes := CertificateResource{
|
||||||
Domain: authz.Domain,
|
Domain: commonName.Domain,
|
||||||
CertURL: resp.Header.Get("Location"),
|
CertURL: resp.Header.Get("Location"),
|
||||||
PrivateKey: privateKeyPem}
|
PrivateKey: privateKeyPem}
|
||||||
|
|
||||||
|
@ -502,8 +570,7 @@ func (c *Client) requestCertificate(authz *authorizationResource, result chan Ce
|
||||||
cert, err := ioutil.ReadAll(resp.Body)
|
cert, err := ioutil.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- domainError{Domain: authz.Domain, Error: err}
|
return CertificateResource{}, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The server returns a body with a length of zero if the
|
// The server returns a body with a length of zero if the
|
||||||
|
@ -523,7 +590,7 @@ func (c *Client) requestCertificate(authz *authorizationResource, result chan Ce
|
||||||
issuerCert, err := c.getIssuerCertificate(links["up"])
|
issuerCert, err := c.getIssuerCertificate(links["up"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we fail to aquire the issuer cert, return the issued certificate - do not fail.
|
// If we fail to aquire the issuer cert, return the issued certificate - do not fail.
|
||||||
logf("[WARNING] acme: [%s] Could not bundle issuer certificate: %v", authz.Domain, err)
|
logf("[WARNING] acme: [%s] Could not bundle issuer certificate: %v", commonName.Domain, err)
|
||||||
} else {
|
} else {
|
||||||
// Success - append the issuer cert to the issued cert.
|
// Success - append the issuer cert to the issued cert.
|
||||||
issuerCert = pemEncode(derCertificateBytes(issuerCert))
|
issuerCert = pemEncode(derCertificateBytes(issuerCert))
|
||||||
|
@ -532,9 +599,8 @@ func (c *Client) requestCertificate(authz *authorizationResource, result chan Ce
|
||||||
}
|
}
|
||||||
|
|
||||||
cerRes.Certificate = issuedCert
|
cerRes.Certificate = issuedCert
|
||||||
logf("[%s] Server responded with a certificate.", authz.Domain)
|
logf("[%s] Server responded with a certificate.", commonName.Domain)
|
||||||
result <- cerRes
|
return cerRes, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The certificate was granted but is not yet issued.
|
// The certificate was granted but is not yet issued.
|
||||||
|
@ -542,23 +608,20 @@ func (c *Client) requestCertificate(authz *authorizationResource, result chan Ce
|
||||||
ra := resp.Header.Get("Retry-After")
|
ra := resp.Header.Get("Retry-After")
|
||||||
retryAfter, err := strconv.Atoi(ra)
|
retryAfter, err := strconv.Atoi(ra)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- domainError{Domain: authz.Domain, Error: err}
|
return CertificateResource{}, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logf("[INFO] acme: [%s] Server responded with status 202; retrying after %ds", authz.Domain, retryAfter)
|
logf("[INFO] acme: [%s] Server responded with status 202; retrying after %ds", commonName.Domain, retryAfter)
|
||||||
time.Sleep(time.Duration(retryAfter) * time.Second)
|
time.Sleep(time.Duration(retryAfter) * time.Second)
|
||||||
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
errc <- domainError{Domain: authz.Domain, Error: handleHTTPError(resp)}
|
return CertificateResource{}, handleHTTPError(resp)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err = http.Get(cerRes.CertURL)
|
resp, err = http.Get(cerRes.CertURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- domainError{Domain: authz.Domain, Error: err}
|
return CertificateResource{}, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,13 +188,17 @@ func generatePrivateKey(t keyType, keyLength int) (crypto.PrivateKey, error) {
|
||||||
return nil, fmt.Errorf("Invalid keytype: %d", t)
|
return nil, fmt.Errorf("Invalid keytype: %d", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCsr(privateKey *rsa.PrivateKey, domain string) ([]byte, error) {
|
func generateCsr(privateKey *rsa.PrivateKey, domain string, san []string) ([]byte, error) {
|
||||||
template := x509.CertificateRequest{
|
template := x509.CertificateRequest{
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: domain,
|
CommonName: domain,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(san) > 0 {
|
||||||
|
template.DNSNames = san
|
||||||
|
}
|
||||||
|
|
||||||
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ func TestGenerateCSR(t *testing.T) {
|
||||||
t.Fatal("Error generating private key:", err)
|
t.Fatal("Error generating private key:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
csr, err := generateCsr(key.(*rsa.PrivateKey), "fizz.buzz")
|
csr, err := generateCsr(key.(*rsa.PrivateKey), "fizz.buzz", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error generating CSR:", err)
|
t.Error("Error generating CSR:", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue