Skip to content
This repository has been archived by the owner on Oct 27, 2019. It is now read-only.

Commit

Permalink
http-01 validation add.
Browse files Browse the repository at this point in the history
  • Loading branch information
rekby committed Jan 12, 2018
1 parent 7c4016d commit 4a0338e
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 106 deletions.
146 changes: 98 additions & 48 deletions acme.go
Expand Up @@ -25,6 +25,8 @@ import (
const (
SNI01_EXPIRE_TOKEN time.Duration = time.Minute * 10
ACME_DOMAIN_SUFFIX = ".acme.invalid"
TLSSNI01 = "tls-sni-01"
HTTP01 = "http-01"
)

type acmeStruct struct {
Expand Down Expand Up @@ -118,15 +120,15 @@ func (this *acmeStruct) CleanupTimer() {
time.AfterFunc(SNI01_EXPIRE_TOKEN, this.CleanupTimer)
}

func (this *acmeStruct) authorizeDomain(ctx context.Context, domain string) error {
func (this *acmeStruct) authorizeDomain(ctx context.Context, domain string) (deleteAuthTokenFunc context.CancelFunc, err error) {
client, err := this.acmePool.Get(ctx)
if client != nil {
defer this.acmePool.Put(client)
}

if err != nil {
logrus.Errorf("Can't get acme client for authorize domain %v: %v", DomainPresent(domain), err)
return err
return deleteAuthTokenFunc, err
}

var auth *acmeapi.Authorization
Expand All @@ -145,7 +147,7 @@ func (this *acmeStruct) authorizeDomain(ctx context.Context, domain string) erro
logrus.Infof("Create authorization for domain %v", DomainPresent(domain))
} else {
logrus.Errorf("Can't create new authorization for domain %v: %v", DomainPresent(domain), err)
return errors.New("Can't create new authorization for domain")
return deleteAuthTokenFunc, errors.New("Can't create new authorization for domain")
}

if logrus.GetLevel() >= logrus.DebugLevel {
Expand All @@ -159,36 +161,67 @@ func (this *acmeStruct) authorizeDomain(ctx context.Context, domain string) erro
canAuthorize := false
var challenge *acmeapi.Challenge
for _, cmb := range auth.Combinations {
if len(cmb) == 1 && auth.Challenges[cmb[0]].Type == "tls-sni-01" {
canAuthorize = true
if len(cmb) == 1 {
challenge = auth.Challenges[cmb[0]]
break
if challenge.Type == TLSSNI01 ||
(challenge.Type == HTTP01 && CanHttpValidation()) {
canAuthorize = true
challenge = auth.Challenges[cmb[0]]
break
}
}
}
if !canAuthorize {
logrus.Errorf("Can't find good challange combination for domain: '%v'", domain)
return errors.New("Can't find good challange combination")
return deleteAuthTokenFunc, errors.New("Can't find good challange combination")
}

acmeHostName, err := acmeutils.TLSSNIHostname(this.privateKey, challenge.Token)
if err == nil {
logrus.Debugf("Create acme-auth hostname for domain %v: %v", DomainPresent(domain), acmeHostName)
} else {
logrus.Errorf("Can't create acme domain for domain %v token '%v': %v", DomainPresent(domain), challenge.Token, err)
return errors.New("Can't create acme domain")
}
this.authDomainPut(acmeHostName)
// defer this.authDomainDelete(acmeHostName) // no detete auth domain - it will be cleaned up be timeout.
// it need for check authDomain while create cert - out of the function
logrus.Debugf("Select challenge type for domain '%v': %v", domain, challenge.Type)

switch challenge.Type {
case TLSSNI01:
acmeHostName, err := acmeutils.TLSSNIHostname(this.privateKey, challenge.Token)
if err == nil {
logrus.Debugf("Create acme-auth hostname for domain %v: %v", DomainPresent(domain), acmeHostName)
} else {
logrus.Errorf("Can't create acme domain for domain %v token '%v': %v", DomainPresent(domain), challenge.Token, err)
return deleteAuthTokenFunc, errors.New("Can't create acme domain")
}
this.authDomainPut(acmeHostName)
oldDeleteAuthTokenFunc := deleteAuthTokenFunc
deleteAuthTokenFunc = func() {
if oldDeleteAuthTokenFunc != nil {
oldDeleteAuthTokenFunc()
}
this.authDomainDelete(acmeHostName)
}

case HTTP01:
authKey, err := acmeutils.KeyAuthorization(this.privateKey, challenge.Token)
if err != nil {
logrus.Errorf("Can't acmeutils.KeyAuthorization for '%v': %v", domain, err)
return deleteAuthTokenFunc, err
}
Http01TokenPut(challenge.Token, authKey)
oldDeleteAuthTokenFunc := deleteAuthTokenFunc
deleteAuthTokenFunc = func() {
if oldDeleteAuthTokenFunc != nil {
oldDeleteAuthTokenFunc()
}
Http01TokenDelete(challenge.Token)
}
default:
logrus.Errorf("Unexpected challenge type when create challenge response: '%v'", challenge.Type)
return deleteAuthTokenFunc, errors.New("Unexpected challenge type when create challenge response")
}
logrus.Debugf("Create challenge response for domain %v", DomainPresent(domain))
challengeResponse, err := acmeutils.ChallengeResponseJSON(this.privateKey, challenge.Token, challenge.Type)
if err == nil {
//pass
} else {
logrus.Errorf("Can't create challenge response for domain %v, token '%v', challenge type %v: %v",
DomainPresent(domain), challenge.Token, challenge.Type, err)
return errors.New("Can't create challenge response")
return deleteAuthTokenFunc, errors.New("Can't create challenge response")
}
for i := 0; i < TRY_COUNT; i++ {
logrus.Debugf("Respond to challenge for domain %v", DomainPresent(domain))
Expand All @@ -204,7 +237,7 @@ func (this *acmeStruct) authorizeDomain(ctx context.Context, domain string) erro
logrus.Debugf("Send challenge response for domain %v", DomainPresent(domain))
} else {
logrus.Errorf("Can't send response for challenge of domain %v: %v", DomainPresent(domain), err)
return errors.New("Can't send response for challenge")
return deleteAuthTokenFunc, errors.New("Can't send response for challenge")
}

for i := 0; i < TRY_COUNT; i++ {
Expand All @@ -221,10 +254,10 @@ func (this *acmeStruct) authorizeDomain(ctx context.Context, domain string) erro
// pass
} else {
logrus.Errorf("Can't load challenge for domain %v: %v", DomainPresent(domain), err)
return errors.New("Can't load challenge")
return deleteAuthTokenFunc, errors.New("Can't load challenge")
}

return nil
return deleteAuthTokenFunc, nil
}

/*
Expand Down Expand Up @@ -261,9 +294,14 @@ func (this *acmeStruct) CreateCertificate(ctx context.Context, domains []string,
}

func (this *acmeStruct) createCertificateAcme(ctx context.Context, domains []string, main_domain string) (cert *tls.Certificate, err error) {
authorizedDomains := make([]string, 0, len(domains))
type authorizedDomainInfo struct {
domain string
clearFunc func()
}

authorizedDomainsInfo := make([]authorizedDomainInfo, 0, len(domains))

authorizedDomainsChan := make(chan string, len(domains))
authorizedDomainsChan := make(chan authorizedDomainInfo, len(domains))
wg := &sync.WaitGroup{}
wg.Add(len(domains))

Expand All @@ -277,9 +315,9 @@ func (this *acmeStruct) createCertificateAcme(ctx context.Context, domains []str
wg.Done()
}()

auth_err := this.authorizeDomain(ctx, auth_domain)
deleteAuthTokenFunc, auth_err := this.authorizeDomain(ctx, auth_domain)
if auth_err == nil {
authorizedDomainsChan <- auth_domain
authorizedDomainsChan <- authorizedDomainInfo{auth_domain, deleteAuthTokenFunc}
} else {
logrus.Infof("Can't authorize domain %v: %v", DomainPresent(auth_domain), auth_err)
}
Expand All @@ -291,28 +329,40 @@ func (this *acmeStruct) createCertificateAcme(ctx context.Context, domains []str
close(authorizedDomainsChan)
}()

for domain := range authorizedDomainsChan {
authorizedDomains = append(authorizedDomains, domain)
authorizedDomains := make([]string, 0, len(authorizedDomainsChan))
for domainInfo := range authorizedDomainsChan {
authorizedDomainsInfo = append(authorizedDomainsInfo, domainInfo)
authorizedDomains = append(authorizedDomains, domainInfo.domain)
}

// Clear authorization after retrieve certificate (or error)
defer func() {
for _, domainInfo := range authorizedDomainsInfo {
if domainInfo.clearFunc != nil {
domainInfo.clearFunc()
}
}
}()

if main_domain != "" && !stringsContains(authorizedDomains, main_domain) {
logrus.Infof("Authorized domains '%v' doesn't contain main domain %v", authorizedDomains, DomainPresent(main_domain))
logrus.Infof("Authorized domains '%v' doesn't contain main domain %v", authorizedDomainsInfo, DomainPresent(main_domain))
return nil, errors.New("Authorized domains doesn't contain main domain")
}

// sort domains
for i := 0; i < len(authorizedDomains)-1; i++ {
for i := 0; i < len(authorizedDomainsInfo)-1; i++ {
l := strings.ToLower(authorizedDomains[i])
r := strings.ToLower(authorizedDomains[i+1])

// www. - to end, other - alphabet
if strings.HasPrefix(l, "www.") && !strings.HasPrefix(r, "www.") || l > r {
tmp := authorizedDomains[i]
authorizedDomains[i] = authorizedDomains[i+1]
authorizedDomains[i+1] = tmp
tmp := authorizedDomainsInfo[i]
authorizedDomainsInfo[i] = authorizedDomainsInfo[i+1]
authorizedDomainsInfo[i+1] = tmp
}
}

if len(authorizedDomains) == 0 {
if len(authorizedDomainsInfo) == 0 {
logrus.Infof("Can't authorize any domains from '%v'", domains)
return nil, errors.New("Can't authorize domains")
}
Expand All @@ -322,14 +372,14 @@ func (this *acmeStruct) createCertificateAcme(ctx context.Context, domains []str
defer this.acmePool.Put(client)
}
if err != nil {
logrus.Errorf("Can't get acme client from pool for domains '%v': %v", authorizedDomains, err)
logrus.Errorf("Can't get acme client from pool for domains '%v': %v", authorizedDomainsInfo, err)
return nil, err
}

// Generate CSR
certKey, err := rsa.GenerateKey(cryptorand.Reader, *privateKeyBits)
if err == nil {
logrus.Debugf("Create private key for domains '%v'", authorizedDomains)
logrus.Debugf("Create private key for domains '%v'", authorizedDomainsInfo)
} else {
logrus.Errorf("Can't create rsa key for domain %v: %v", DomainPresent(main_domain), err)
return nil, errors.New("Can't create rsa key")
Expand All @@ -341,30 +391,30 @@ func (this *acmeStruct) createCertificateAcme(ctx context.Context, domains []str
Subject: pkix.Name{CommonName: authorizedDomains[0]},
DNSNames: authorizedDomains,
}
logrus.Debugf("Create CSR for domains '%v'", authorizedDomains)
logrus.Debugf("Create CSR for domains '%v'", authorizedDomainsInfo)
csrDER, err := x509.CreateCertificateRequest(cryptorand.Reader, certRequest, certKey)
if err == nil {
logrus.Debugf("Created CSR for domains '%v'", authorizedDomains)
logrus.Debugf("Created CSR for domains '%v'", authorizedDomainsInfo)
} else {
logrus.Errorf("Can't create csr for domains '%v': %v", authorizedDomains, err)
logrus.Errorf("Can't create csr for domains '%v': %v", authorizedDomainsInfo, err)
return nil, errors.New("Can't create csr")
}

var certResponse *acmeapi.Certificate
for i := 0; i < TRY_COUNT; i++ {
logrus.Debugf("Certificate request for domains '%v'", authorizedDomains)
logrus.Debugf("Certificate request for domains '%v'", authorizedDomainsInfo)
certResponse, err = client.RequestCertificate(csrDER, ctx)
if err == nil {
break
} else {
logrus.Infof("Can't get certificate for domains '%v': %v (response: %#v)", authorizedDomains, err, certResponse)
logrus.Infof("Can't get certificate for domains '%v': %v (response: %#v)", authorizedDomainsInfo, err, certResponse)
time.Sleep(RETRY_SLEEP)
}
}
if err == nil {
logrus.Infof("Get certificate for domains '%v'", authorizedDomains)
logrus.Infof("Get certificate for domains '%v'", authorizedDomainsInfo)
} else {
logrus.Errorf("Can't get certificate for domains '%v': %v", authorizedDomains, err)
logrus.Errorf("Can't get certificate for domains '%v': %v", authorizedDomainsInfo, err)
return nil, errors.New("Can't request certificate")
}

Expand All @@ -381,25 +431,25 @@ func (this *acmeStruct) createCertificateAcme(ctx context.Context, domains []str
certKeyPEM := pemEncode(x509.MarshalPKCS1PrivateKey(certKey), "RSA PRIVATE KEY")

tmpCert, err := tls.X509KeyPair(certPEM, certKeyPEM)
logrus.Debugf("Parsed cert count for domains '%v': %v", authorizedDomains, len(tmpCert.Certificate))
logrus.Debugf("Parsed cert count for domains '%v': %v", authorizedDomainsInfo, len(tmpCert.Certificate))
if err == nil {
logrus.Infof("Cert parsed for domains '%v'", authorizedDomains)
logrus.Infof("Cert parsed for domains '%v'", authorizedDomainsInfo)
cert = &tmpCert
} else {
logrus.Errorf("Can't parse cert for domains '%v': %v", authorizedDomains, err)
logrus.Errorf("Can't parse cert for domains '%v': %v", authorizedDomainsInfo, err)
return nil, errors.New("Can't parse cert for domain")
}

if len(cert.Certificate) > 0 {
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err == nil {
logrus.Debugf("Leaf certificate parsed from domains '%v'", authorizedDomains)
logrus.Debugf("Leaf certificate parsed from domains '%v'", authorizedDomainsInfo)
} else {
logrus.Errorf("Can't parse leaf certificate for domains '%v': %v", authorizedDomains, err)
logrus.Errorf("Can't parse leaf certificate for domains '%v': %v", authorizedDomainsInfo, err)
cert.Leaf = nil
}
} else {
logrus.Errorf("Certificate for domains doesn't contain certificates '%v'", authorizedDomains)
logrus.Errorf("Certificate for domains doesn't contain certificates '%v'", authorizedDomainsInfo)
return nil, errors.New("Certificate for domain doesn't contain certificates")
}

Expand Down
5 changes: 4 additions & 1 deletion flags.go
Expand Up @@ -4,6 +4,7 @@ import (
"flag"
"net"
"regexp"
"strconv"
"time"
)

Expand All @@ -14,7 +15,8 @@ var (
additionalHeadersParam = flag.String("additional-headers", "X-Forwarded-Proto=https", "Additional headers for proxied requests. Separate multiple headers by comma.")
allowIPRefreshInterval = flag.Duration("allow-ips-refresh", time.Hour, "For local, domain and ifconfig.io - how often ip addresses will be refreshed. Format https://golang.org/pkg/time/#ParseDuration.")
allowIPsString = flag.String("allowed-ips", "auto", "Allowable ip addresses (ipv4,ipv6) separated by comma. It can contain special variables (without quotes): 'auto' - try to auto determine allowable address, the logic may change between versions. 'local' (all autodetected local IP) and 'nat' - detect IP by request to http://ifconfig.io/ip - it's needed for public ip auto-detection behind NAT.")
bindToS = flag.String("bind-to", ":443", "List of ports, ip addresses or port:ip separated by comma. For example: 1.1.1.1,2.2.2.2,3.3.3.3:443,4.4.4.4. Ports other then 443 may be used only if tcp-connections proxied from port 443 (iptables,nginx,socat and so on) because Let's Encrypt now checks connections using port 443 only.")
bindToS = flag.String("bind-to", ":"+strconv.Itoa(DEFAULT_BIND_PORT), "List of ports, ip addresses or port:ip separated by comma. For example: 1.1.1.1,2.2.2.2,3.3.3.3:443,4.4.4.4. Ports other then 443 may be used only if tcp-connections proxied from port 443 (iptables,nginx,socat and so on) because Let's Encrypt now checks connections using port 443 only.")
bindHttpValidationToS = flag.String("bind-http-validation-to", "127.0.0.1:"+strconv.Itoa(DEFAULT_BIND_HTTP_VALIDATION_PORT), "Bind address for http-validation port. List of ports, ip addresses or port:ip separated by comma. Requests to <domain>/.well-known/acme-challenge/ must be redirect to this port for http-01 validation.")
blockBadDomainDuration = flag.Duration("block-bad-domain-duration", time.Hour, "Disable trying to obtain certificate for a domain after error")
certDir = flag.String("cert-dir", "certificates", `Directory for saved cached certificates. Set cert-dir=- to disable saving of certificates.`)
certJsonSave = flag.Bool("cert-json", false, "Save JSON information about certificate near the certificate file with same name with .json extension")
Expand Down Expand Up @@ -78,6 +80,7 @@ var (
paramTargetTcpAddr *net.TCPAddr
subdomainPrefixedForUnion []string
bindTo []net.TCPAddr
bindHttpValidationTo []net.TCPAddr
globalConnectionNumber int64
targetMap map[string]*net.TCPAddr
)
4 changes: 0 additions & 4 deletions http-build-in-proxy.go
Expand Up @@ -99,10 +99,6 @@ func acceptConnectionsBuiltinProxy(listeners []*net.TCPListener) {

go server.Serve(tlsListener)
}

// block forever
var ch chan bool
<-ch
}

// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
Expand Down

0 comments on commit 4a0338e

Please sign in to comment.