From e7fd871a9ce973f903214a0a9d10d31b72d8154a Mon Sep 17 00:00:00 2001
From: Ludovic Fernandez <ldez@users.noreply.github.com>
Date: Wed, 30 May 2018 19:53:04 +0200
Subject: [PATCH] ACME V2 support (#555)

---
 .gitignore                                    |   4 +-
 README.md                                     |   8 +-
 account.go                                    |  45 +-
 acme/challenges.go                            |   3 -
 acme/client.go                                | 687 +++++++++---------
 acme/client_test.go                           | 115 ++-
 acme/crypto.go                                |  27 +-
 acme/crypto_test.go                           |   2 +-
 acme/dns_challenge.go                         |  12 +-
 acme/dns_challenge_manual.go                  |  12 +-
 acme/dns_challenge_test.go                    |   4 +-
 acme/error.go                                 |  31 +-
 acme/http.go                                  |   2 +-
 acme/http_challenge.go                        |   7 +-
 acme/http_challenge_server.go                 |   6 +-
 acme/http_challenge_test.go                   |   4 +-
 acme/jws.go                                   |  94 ++-
 acme/messages.go                              | 120 ++-
 acme/pop_challenge.go                         |   1 -
 acme/tls_sni_challenge.go                     |  67 --
 acme/tls_sni_challenge_server.go              |  62 --
 acme/tls_sni_challenge_test.go                |  65 --
 cli.go                                        |  33 +-
 cli_handlers.go                               | 213 +++---
 crypto.go                                     |   2 +-
 log/logger.go                                 |  59 ++
 providers/dns/auroradns/auroradns.go          |  16 +-
 providers/dns/auroradns/auroradns_test.go     |   6 +-
 providers/dns/azure/azure.go                  |  70 +-
 providers/dns/bluecat/bluecat.go              | 119 ++-
 providers/dns/bluecat/bluecat_test.go         |  14 +-
 providers/dns/cloudflare/cloudflare.go        |  15 +-
 providers/dns/cloudxns/cloudxns.go            | 424 ++++++-----
 providers/dns/cloudxns/cloudxns_test.go       | 160 ++--
 providers/dns/digitalocean/digitalocean.go    |  47 +-
 .../dns/digitalocean/digitalocean_test.go     |   2 +-
 providers/dns/dns_providers.go                |   6 +-
 providers/dns/dnsimple/dnsimple.go            |  15 +-
 providers/dns/dnsimple/dnsimple_test.go       |  16 +-
 providers/dns/dnsmadeeasy/dnsmadeeasy.go      |   8 +-
 providers/dns/dnsmadeeasy/dnsmadeeasy_test.go |   1 +
 providers/dns/duckdns/duckdns.go              | 163 +++--
 providers/dns/duckdns/duckdns_test.go         | 130 ++--
 providers/dns/dyn/dyn.go                      |  27 +-
 providers/dns/exoscale/exoscale.go            |  14 +-
 providers/dns/gandi/gandi.go                  |  41 +-
 providers/dns/gandi/gandi_test.go             |  81 ---
 providers/dns/gandiv5/gandiv5.go              |  19 +-
 providers/dns/gandiv5/gandiv5_test.go         |  81 ---
 providers/dns/glesys/glesys.go                |  60 +-
 providers/dns/godaddy/godaddy.go              |  10 +-
 providers/dns/googlecloud/googlecloud.go      |   3 +-
 providers/dns/googlecloud/googlecloud_test.go |   1 +
 providers/dns/linode/linode.go                |  10 +-
 providers/dns/namecheap/namecheap.go          |   4 +-
 providers/dns/otc/mock.go                     |  12 +-
 providers/dns/otc/otc.go                      |  30 +-
 providers/dns/ovh/ovh.go                      |   2 +-
 providers/dns/pdns/pdns.go                    |  15 +-
 providers/dns/rackspace/rackspace.go          |  49 +-
 providers/dns/rackspace/rackspace_test.go     |   9 +-
 providers/dns/rfc2136/rfc2136.go              |  30 +-
 providers/dns/route53/route53.go              |  14 +-
 .../dns/route53/route53_integration_test.go   |  14 +-
 providers/dns/route53/route53_test.go         |   2 +-
 providers/http/memcached/memcached.go         |  20 +-
 providers/http/memcached/memcached_test.go    |   2 +-
 providers/http/webroot/webroot.go             |   9 +-
 68 files changed, 1637 insertions(+), 1819 deletions(-)
 delete mode 100644 acme/pop_challenge.go
 delete mode 100644 acme/tls_sni_challenge.go
 delete mode 100644 acme/tls_sni_challenge_server.go
 delete mode 100644 acme/tls_sni_challenge_test.go
 create mode 100644 log/logger.go

diff --git a/.gitignore b/.gitignore
index 61a095ef..1d610d90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
 lego.exe
 lego
 .lego
+.gitcookies
 .idea
+.vscode/
 dist/
 builds/
-.gitcookies
+vendor/
diff --git a/README.md b/README.md
index fde4b531..6cfce7a3 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,6 @@ yaourt -S lego-git
 - Revoke certificates
 - Robust implementation of all ACME challenges
   - HTTP (http-01)
-  - TLS with Server Name Indication (tls-sni-01)
   - DNS (dns-01)
 - SAN certificate support
 - Comes with multiple optional [DNS providers](https://github.com/xenolf/lego/tree/master/providers/dns)
@@ -107,15 +106,14 @@ GLOBAL OPTIONS:
    --accept-tos, -a            By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.
    --key-type value, -k value  Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384 (default: "rsa2048")
    --path value                Directory to use for storing the data (default: "/.lego")
-   --exclude value, -x value   Explicitly disallow solvers by name from being used. Solvers: "http-01", "tls-sni-01".
+   --exclude value, -x value   Explicitly disallow solvers by name from being used. Solvers: "http-01", "dns-01",.
    --webroot value             Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge
    --memcached-host value      Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.
    --http value                Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port
-   --tls value                 Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port
    --dns value                 Solve a DNS challenge using the specified provider. Disables all other challenges. Run 'lego dnshelp' for help on usage.
    --http-timeout value        Set the HTTP timeout value to a specific value in seconds. The default is 10 seconds. (default: 0)
    --dns-timeout value         Set the DNS timeout value to a specific value in seconds. The default is 10 seconds. (default: 0)
-   --dns-resolvers value       Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use Google's DNS resolvers.
+   --dns-resolvers value       Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
    --pem                       Generate a .pem file by concatanating the .key and .crt files together.
    --help, -h                  show help
    --version, -v               print the version
@@ -152,7 +150,7 @@ Obtain a certificate using the DNS challenge and AWS Route 53:
 $ AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=my_id AWS_SECRET_ACCESS_KEY=my_key lego --email="foo@bar.com" --domains="example.com" --dns="route53" run
 ```
 
-Note that `--dns=foo` implies `--exclude=http-01` and `--exclude=tls-sni-01`. lego will not attempt other challenges if you've told it to use DNS instead.
+Note that `--dns=foo` implies `--exclude=http-01`. lego will not attempt other challenges if you've told it to use DNS instead.
 
 Obtain a certificate given a certificate signing request (CSR) generated by something else:
 
diff --git a/account.go b/account.go
index 34856e16..938f886f 100644
--- a/account.go
+++ b/account.go
@@ -8,6 +8,7 @@ import (
 	"path"
 
 	"github.com/xenolf/lego/acme"
+	"github.com/xenolf/lego/log"
 )
 
 // Account represents a users local saved credentials
@@ -25,23 +26,23 @@ func NewAccount(email string, conf *Configuration) *Account {
 	// TODO: move to function in configuration?
 	accKeyPath := accKeysPath + string(os.PathSeparator) + email + ".key"
 	if err := checkFolder(accKeysPath); err != nil {
-		logger().Fatalf("Could not check/create directory for account %s: %v", email, err)
+		log.Fatalf("Could not check/create directory for account %s: %v", email, err)
 	}
 
 	var privKey crypto.PrivateKey
 	if _, err := os.Stat(accKeyPath); os.IsNotExist(err) {
 
-		logger().Printf("No key found for account %s. Generating a curve P384 EC key.", email)
+		log.Printf("No key found for account %s. Generating a curve P384 EC key.", email)
 		privKey, err = generatePrivateKey(accKeyPath)
 		if err != nil {
-			logger().Fatalf("Could not generate RSA private account key for account %s: %v", email, err)
+			log.Fatalf("Could not generate RSA private account key for account %s: %v", email, err)
 		}
 
-		logger().Printf("Saved key to %s", accKeyPath)
+		log.Printf("Saved key to %s", accKeyPath)
 	} else {
 		privKey, err = loadPrivateKey(accKeyPath)
 		if err != nil {
-			logger().Fatalf("Could not load RSA private key from file %s: %v", accKeyPath, err)
+			log.Fatalf("Could not load RSA private key from file %s: %v", accKeyPath, err)
 		}
 	}
 
@@ -52,29 +53,53 @@ func NewAccount(email string, conf *Configuration) *Account {
 
 	fileBytes, err := ioutil.ReadFile(accountFile)
 	if err != nil {
-		logger().Fatalf("Could not load file for account %s -> %v", email, err)
+		log.Fatalf("Could not load file for account %s -> %v", email, err)
 	}
 
 	var acc Account
 	err = json.Unmarshal(fileBytes, &acc)
 	if err != nil {
-		logger().Fatalf("Could not parse file for account %s -> %v", email, err)
+		log.Fatalf("Could not parse file for account %s -> %v", email, err)
 	}
 
 	acc.key = privKey
 	acc.conf = conf
 
-	if acc.Registration == nil {
-		logger().Fatalf("Could not load account for %s. Registration is nil.", email)
+	if acc.Registration == nil || acc.Registration.Body.Status == "" {
+		reg, err := tryRecoverAccount(privKey, conf)
+		if err != nil {
+			log.Fatalf("Could not load account for %s. Registration is nil -> %#v", email, err)
+		}
+
+		acc.Registration = reg
+		err = acc.Save()
+		if err != nil {
+			log.Fatalf("Could not save account for %s. Registration is nil -> %#v", email, err)
+		}
 	}
 
 	if acc.conf == nil {
-		logger().Fatalf("Could not load account for %s. Configuration is nil.", email)
+		log.Fatalf("Could not load account for %s. Configuration is nil.", email)
 	}
 
 	return &acc
 }
 
+func tryRecoverAccount(privKey crypto.PrivateKey, conf *Configuration) (*acme.RegistrationResource, error) {
+	// couldn't load account but got a key. Try to look the account up.
+	serverURL := conf.context.GlobalString("server")
+	client, err := acme.NewClient(serverURL, &Account{key: privKey, conf: conf}, acme.RSA2048)
+	if err != nil {
+		return nil, err
+	}
+
+	reg, err := client.ResolveAccountByKey()
+	if err != nil {
+		return nil, err
+	}
+	return reg, nil
+}
+
 /** Implementation of the acme.User interface **/
 
 // GetEmail returns the email address for the account
diff --git a/acme/challenges.go b/acme/challenges.go
index 85790050..cf7bd7f7 100644
--- a/acme/challenges.go
+++ b/acme/challenges.go
@@ -7,9 +7,6 @@ const (
 	// HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http
 	// Note: HTTP01ChallengePath returns the URL path to fulfill this challenge
 	HTTP01 = Challenge("http-01")
-	// TLSSNI01 is the "tls-sni-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#tls-with-server-name-indication-tls-sni
-	// Note: TLSSNI01ChallengeCert returns a certificate to fulfill this challenge
-	TLSSNI01 = Challenge("tls-sni-01")
 	// DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns
 	// Note: DNS01Record returns a DNS record which will fulfill this challenge
 	DNS01 = Challenge("dns-01")
diff --git a/acme/client.go b/acme/client.go
index bcb84437..786e5b2d 100644
--- a/acme/client.go
+++ b/acme/client.go
@@ -5,22 +5,16 @@ import (
 	"crypto"
 	"crypto/x509"
 	"encoding/base64"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io/ioutil"
-	"log"
 	"net"
-	"net/http"
 	"regexp"
 	"strconv"
 	"strings"
 	"time"
-)
 
-var (
-	// Logger is an optional custom logger.
-	Logger *log.Logger
+	"github.com/xenolf/lego/log"
 )
 
 const (
@@ -33,16 +27,6 @@ const (
 	overallRequestLimit = 18
 )
 
-// logf writes a log entry. It uses Logger if not
-// nil, otherwise it uses the default log.Logger.
-func logf(format string, args ...interface{}) {
-	if Logger != nil {
-		Logger.Printf(format, args...)
-	} else {
-		log.Printf(format, args...)
-	}
-}
-
 // User interface is to be implemented by users of this library.
 // It is used by the client type to get user specific information.
 type User interface {
@@ -82,27 +66,23 @@ func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) {
 		return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
 	}
 
-	if dir.NewRegURL == "" {
+	if dir.NewAccountURL == "" {
 		return nil, errors.New("directory missing new registration URL")
 	}
-	if dir.NewAuthzURL == "" {
-		return nil, errors.New("directory missing new authz URL")
-	}
-	if dir.NewCertURL == "" {
-		return nil, errors.New("directory missing new certificate URL")
-	}
-	if dir.RevokeCertURL == "" {
-		return nil, errors.New("directory missing revoke certificate URL")
+	if dir.NewOrderURL == "" {
+		return nil, errors.New("directory missing new order URL")
 	}
 
-	jws := &jws{privKey: privKey, directoryURL: caDirURL}
+	jws := &jws{privKey: privKey, getNonceURL: dir.NewNonceURL}
+	if reg := user.GetRegistration(); reg != nil {
+		jws.kid = reg.URI
+	}
 
 	// REVIEW: best possibility?
 	// Add all available solvers with the right index as per ACME
 	// spec to this map. Otherwise they won`t be found.
 	solvers := make(map[Challenge]solver)
 	solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}}
-	solvers[TLSSNI01] = &tlsSNIChallenge{jws: jws, validate: validate, provider: &TLSProviderServer{}}
 
 	return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil
 }
@@ -112,8 +92,6 @@ func (c *Client) SetChallengeProvider(challenge Challenge, p ChallengeProvider)
 	switch challenge {
 	case HTTP01:
 		c.solvers[challenge] = &httpChallenge{jws: c.jws, validate: validate, provider: p}
-	case TLSSNI01:
-		c.solvers[challenge] = &tlsSNIChallenge{jws: c.jws, validate: validate, provider: p}
 	case DNS01:
 		c.solvers[challenge] = &dnsChallenge{jws: c.jws, validate: validate, provider: p}
 	default:
@@ -141,24 +119,6 @@ func (c *Client) SetHTTPAddress(iface string) error {
 	return nil
 }
 
-// SetTLSAddress specifies a custom interface:port to be used for TLS based challenges.
-// If this option is not used, the default port 443 and all interfaces will be used.
-// To only specify a port and no interface use the ":port" notation.
-//
-// NOTE: This REPLACES any custom TLS-SNI provider previously set by calling
-// c.SetChallengeProvider with the default TLS-SNI challenge provider.
-func (c *Client) SetTLSAddress(iface string) error {
-	host, port, err := net.SplitHostPort(iface)
-	if err != nil {
-		return err
-	}
-
-	if chlng, ok := c.solvers[TLSSNI01]; ok {
-		chlng.(*tlsSNIChallenge).provider = NewTLSProviderServer(host, port)
-	}
-	return nil
-}
-
 // ExcludeChallenges explicitly removes challenges from the pool for solving.
 func (c *Client) ExcludeChallenges(challenges []Challenge) {
 	// Loop through all challenges and delete the requested one if found.
@@ -167,80 +127,138 @@ func (c *Client) ExcludeChallenges(challenges []Challenge) {
 	}
 }
 
+// GetToSURL returns the current ToS URL from the Directory
+func (c *Client) GetToSURL() string {
+	return c.directory.Meta.TermsOfService
+}
+
+// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
+func (c *Client) GetExternalAccountRequired() bool {
+	return c.directory.Meta.ExternalAccountRequired
+}
+
 // Register the current account to the ACME server.
-func (c *Client) Register() (*RegistrationResource, error) {
+func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) {
 	if c == nil || c.user == nil {
 		return nil, errors.New("acme: cannot register a nil client or user")
 	}
-	logf("[INFO] acme: Registering account for %s", c.user.GetEmail())
+	log.Printf("[INFO] acme: Registering account for %s", c.user.GetEmail())
 
-	regMsg := registrationMessage{
-		Resource: "new-reg",
-	}
+	accMsg := accountMessage{}
 	if c.user.GetEmail() != "" {
-		regMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
+		accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
 	} else {
-		regMsg.Contact = []string{}
+		accMsg.Contact = []string{}
 	}
+	accMsg.TermsOfServiceAgreed = tosAgreed
 
-	var serverReg Registration
-	var regURI string
-	hdr, err := postJSON(c.jws, c.directory.NewRegURL, regMsg, &serverReg)
+	var serverReg accountMessage
+	hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
 	if err != nil {
 		remoteErr, ok := err.(RemoteError)
 		if ok && remoteErr.StatusCode == 409 {
-			regURI = hdr.Get("Location")
-			regMsg = registrationMessage{
-				Resource: "reg",
-			}
-			if hdr, err = postJSON(c.jws, regURI, regMsg, &serverReg); err != nil {
-				return nil, err
-			}
 		} else {
 			return nil, err
 		}
 	}
 
-	reg := &RegistrationResource{Body: serverReg}
-
-	links := parseLinks(hdr["Link"])
-
-	if regURI == "" {
-		regURI = hdr.Get("Location")
-	}
-	reg.URI = regURI
-	if links["terms-of-service"] != "" {
-		reg.TosURL = links["terms-of-service"]
-	}
-
-	if links["next"] != "" {
-		reg.NewAuthzURL = links["next"]
-	} else {
-		return nil, errors.New("acme: The server did not return 'next' link to proceed")
+	reg := &RegistrationResource{
+		URI:  hdr.Get("Location"),
+		Body: serverReg,
 	}
+	c.jws.kid = reg.URI
 
 	return reg, nil
 }
 
+// Register the current account to the ACME server.
+func (c *Client) RegisterWithExternalAccountBinding(tosAgreed bool, kid string, hmacEncoded string) (*RegistrationResource, error) {
+	if c == nil || c.user == nil {
+		return nil, errors.New("acme: cannot register a nil client or user")
+	}
+	log.Printf("[INFO] acme: Registering account (EAB) for %s", c.user.GetEmail())
+
+	accMsg := accountMessage{}
+	if c.user.GetEmail() != "" {
+		accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
+	} else {
+		accMsg.Contact = []string{}
+	}
+	accMsg.TermsOfServiceAgreed = tosAgreed
+
+	hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
+	if err != nil {
+		return nil, fmt.Errorf("acme: could not decode hmac key: %s", err.Error())
+	}
+
+	eabJWS, err := c.jws.signEABContent(c.directory.NewAccountURL, kid, hmac)
+	if err != nil {
+		return nil, fmt.Errorf("acme: error signing eab content: %s", err.Error())
+	}
+
+	eabPayload := eabJWS.FullSerialize()
+
+	accMsg.ExternalAccountBinding = []byte(eabPayload)
+
+	var serverReg accountMessage
+	hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
+	if err != nil {
+		remoteErr, ok := err.(RemoteError)
+		if ok && remoteErr.StatusCode == 409 {
+		} else {
+			return nil, err
+		}
+	}
+
+	reg := &RegistrationResource{
+		URI:  hdr.Get("Location"),
+		Body: serverReg,
+	}
+	c.jws.kid = reg.URI
+
+	return reg, nil
+}
+
+// ResolveAccountByKey will attempt to look up an account using the given account key
+// and return its registration resource.
+func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) {
+	log.Printf("[INFO] acme: Trying to resolve account by key")
+
+	acc := accountMessage{OnlyReturnExisting: true}
+	hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	accountLink := hdr.Get("Location")
+	if accountLink == "" {
+		return nil, errors.New("Server did not return the account link")
+	}
+
+	var retAccount accountMessage
+	c.jws.kid = accountLink
+	_, err = postJSON(c.jws, accountLink, accountMessage{}, &retAccount)
+	if err != nil {
+		return nil, err
+	}
+
+	return &RegistrationResource{URI: accountLink, Body: retAccount}, nil
+}
+
 // DeleteRegistration deletes the client's user registration from the ACME
 // server.
 func (c *Client) DeleteRegistration() error {
 	if c == nil || c.user == nil {
 		return errors.New("acme: cannot unregister a nil client or user")
 	}
-	logf("[INFO] acme: Deleting account for %s", c.user.GetEmail())
+	log.Printf("[INFO] acme: Deleting account for %s", c.user.GetEmail())
 
-	regMsg := registrationMessage{
-		Resource: "reg",
-		Delete:   true,
+	accMsg := accountMessage{
+		Status: "deactivated",
 	}
 
-	_, err := postJSON(c.jws, c.user.GetRegistration().URI, regMsg, nil)
-	if err != nil {
-		return err
-	}
-
-	return nil
+	_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, nil)
+	return err
 }
 
 // QueryRegistration runs a POST request on the client's registration and
@@ -253,48 +271,25 @@ func (c *Client) QueryRegistration() (*RegistrationResource, error) {
 		return nil, errors.New("acme: cannot query the registration of a nil client or user")
 	}
 	// Log the URL here instead of the email as the email may not be set
-	logf("[INFO] acme: Querying account for %s", c.user.GetRegistration().URI)
+	log.Printf("[INFO] acme: Querying account for %s", c.user.GetRegistration().URI)
 
-	regMsg := registrationMessage{
-		Resource: "reg",
-	}
+	accMsg := accountMessage{}
 
-	var serverReg Registration
-	hdr, err := postJSON(c.jws, c.user.GetRegistration().URI, regMsg, &serverReg)
+	var serverReg accountMessage
+	_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, &serverReg)
 	if err != nil {
 		return nil, err
 	}
 
 	reg := &RegistrationResource{Body: serverReg}
 
-	links := parseLinks(hdr["Link"])
 	// Location: header is not returned so this needs to be populated off of
 	// existing URI
 	reg.URI = c.user.GetRegistration().URI
-	if links["terms-of-service"] != "" {
-		reg.TosURL = links["terms-of-service"]
-	}
-
-	if links["next"] != "" {
-		reg.NewAuthzURL = links["next"]
-	} else {
-		return nil, errors.New("acme: No new-authz link in response to registration query")
-	}
 
 	return reg, nil
 }
 
-// AgreeToTOS updates the Client registration and sends the agreement to
-// the server.
-func (c *Client) AgreeToTOS() error {
-	reg := c.user.GetRegistration()
-
-	reg.Body.Agreement = c.user.GetRegistration().TosURL
-	reg.Body.Resource = "reg"
-	_, err := postJSON(c.jws, c.user.GetRegistration().URI, c.user.GetRegistration().Body, nil)
-	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.
@@ -302,7 +297,7 @@ func (c *Client) AgreeToTOS() error {
 // 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 x509.CertificateRequest, bundle bool) (CertificateResource, map[string]error) {
+func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (*CertificateResource, error) {
 	// figure out what domains it concerns
 	// start with the common name
 	domains := []string{csr.Subject.CommonName}
@@ -322,40 +317,49 @@ DNSNames:
 	}
 
 	if bundle {
-		logf("[INFO][%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
+		log.Printf("[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, ", "))
+		log.Printf("[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 {
-		for _, auth := range challenges {
-			c.disableAuthz(auth)
-		}
-
-		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.Raw, nil)
+	order, err := c.createOrderForIdentifiers(domains)
 	if err != nil {
-		for _, chln := range challenges {
-			failures[chln.Domain] = err
+		return nil, err
+	}
+	authz, err := c.getAuthzForOrder(order)
+	if err != nil {
+		// If any challenge fails, return. Do not generate partial SAN certificates.
+		/*for _, auth := range authz {
+			c.disableAuthz(auth)
+		}*/
+		return nil, err
+	}
+
+	err = c.solveChallengeForAuthz(authz)
+	if err != nil {
+		// If any challenge fails, return. Do not generate partial SAN certificates.
+		return nil, err
+	}
+
+	log.Printf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
+
+	failures := make(ObtainError)
+	cert, err := c.requestCertificateForCsr(order, bundle, csr.Raw, nil)
+	if err != nil {
+		for _, chln := range authz {
+			failures[chln.Identifier.Value] = err
 		}
 	}
 
 	// Add the CSR to the certificate so that it can be used for renewals.
 	cert.CSR = pemEncode(&csr)
 
-	return cert, failures
+	// do not return an empty failures map, because
+	// it would still be a non-nil error value
+	if len(failures) > 0 {
+		return cert, failures
+	}
+	return cert, nil
 }
 
 // ObtainCertificate tries to obtain a single certificate using all domains passed into it.
@@ -367,39 +371,52 @@ DNSNames:
 // 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) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, map[string]error) {
+func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) {
+	if len(domains) == 0 {
+		return nil, errors.New("No domains to obtain a certificate for")
+	}
+
 	if bundle {
-		logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
+		log.Printf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
 	} else {
-		logf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
+		log.Printf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
 	}
 
-	challenges, failures := c.getChallenges(domains)
-	// If any challenge fails - return. Do not generate partial SAN certificates.
-	if len(failures) > 0 {
-		for _, auth := range challenges {
-			c.disableAuthz(auth)
-		}
-
-		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.requestCertificate(challenges, bundle, privKey, mustStaple)
+	order, err := c.createOrderForIdentifiers(domains)
 	if err != nil {
-		for _, chln := range challenges {
-			failures[chln.Domain] = err
+		return nil, err
+	}
+	authz, err := c.getAuthzForOrder(order)
+	if err != nil {
+		// If any challenge fails, return. Do not generate partial SAN certificates.
+		/*for _, auth := range authz {
+			c.disableAuthz(auth)
+		}*/
+		return nil, err
+	}
+
+	err = c.solveChallengeForAuthz(authz)
+	if err != nil {
+		// If any challenge fails, return. Do not generate partial SAN certificates.
+		return nil, err
+	}
+
+	log.Printf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
+
+	failures := make(ObtainError)
+	cert, err := c.requestCertificateForOrder(order, bundle, privKey, mustStaple)
+	if err != nil {
+		for _, auth := range authz {
+			failures[auth.Identifier.Value] = err
 		}
 	}
 
-	return cert, failures
+	// do not return an empty failures map, because
+	// it would still be a non-nil error value
+	if len(failures) > 0 {
+		return cert, failures
+	}
+	return cert, nil
 }
 
 // RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
@@ -416,7 +433,7 @@ func (c *Client) RevokeCertificate(certificate []byte) error {
 
 	encodedCert := base64.URLEncoding.EncodeToString(x509Cert.Raw)
 
-	_, err = postJSON(c.jws, c.directory.RevokeCertURL, revokeCertMessage{Resource: "revoke-cert", Certificate: encodedCert}, nil)
+	_, err = postJSON(c.jws, c.directory.RevokeCertURL, revokeCertMessage{Certificate: encodedCert}, nil)
 	return err
 }
 
@@ -428,22 +445,22 @@ func (c *Client) RevokeCertificate(certificate []byte) error {
 // If bundle is true, the []byte contains both the issuer certificate and
 // 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, mustStaple bool) (CertificateResource, error) {
+func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple bool) (*CertificateResource, error) {
 	// 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.
 	certificates, err := parsePEMBundle(cert.Certificate)
 	if err != nil {
-		return CertificateResource{}, err
+		return nil, err
 	}
 
 	x509Cert := certificates[0]
 	if x509Cert.IsCA {
-		return CertificateResource{}, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain)
+		return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain)
 	}
 
 	// This is just meant to be informal for the user.
 	timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
-	logf("[INFO][%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours()))
+	log.Printf("[INFO][%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours()))
 
 	// We always need to request a new certificate to renew.
 	// Start by checking to see if the certificate was based off a CSR, and
@@ -451,22 +468,21 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b
 	if len(cert.CSR) > 0 {
 		csr, err := pemDecodeTox509CSR(cert.CSR)
 		if err != nil {
-			return CertificateResource{}, err
+			return nil, err
 		}
 		newCert, failures := c.ObtainCertificateForCSR(*csr, bundle)
-		return newCert, failures[cert.Domain]
+		return newCert, failures
 	}
 
 	var privKey crypto.PrivateKey
 	if cert.PrivateKey != nil {
 		privKey, err = parsePEMPrivateKey(cert.PrivateKey)
 		if err != nil {
-			return CertificateResource{}, err
+			return nil, err
 		}
 	}
 
 	var domains []string
-	var failures map[string]error
 	// check for SAN certificate
 	if len(x509Cert.DNSNames) > 1 {
 		domains = append(domains, x509Cert.Subject.CommonName)
@@ -480,272 +496,276 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b
 		domains = append(domains, x509Cert.Subject.CommonName)
 	}
 
-	newCert, failures := c.ObtainCertificate(domains, bundle, privKey, mustStaple)
-	return newCert, failures[cert.Domain]
+	newCert, err := c.ObtainCertificate(domains, bundle, privKey, mustStaple)
+	return newCert, err
+}
+
+func (c *Client) createOrderForIdentifiers(domains []string) (orderResource, error) {
+
+	var identifiers []identifier
+	for _, domain := range domains {
+		identifiers = append(identifiers, identifier{Type: "dns", Value: domain})
+	}
+
+	order := orderMessage{
+		Identifiers: identifiers,
+	}
+
+	var response orderMessage
+	hdr, err := postJSON(c.jws, c.directory.NewOrderURL, order, &response)
+	if err != nil {
+		return orderResource{}, err
+	}
+
+	orderRes := orderResource{
+		URL:          hdr.Get("Location"),
+		Domains:      domains,
+		orderMessage: response,
+	}
+	return orderRes, nil
 }
 
 // Looks through the challenge combinations to find a solvable match.
 // Then solves the challenges in series and returns.
-func (c *Client) solveChallenges(challenges []authorizationResource) map[string]error {
+func (c *Client) solveChallengeForAuthz(authorizations []authorization) error {
+	failures := make(ObtainError)
+
 	// loop through the resources, basically through the domains.
-	failures := make(map[string]error)
-	for _, authz := range challenges {
-		if authz.Body.Status == "valid" {
+	for _, authz := range authorizations {
+		if authz.Status == "valid" {
 			// Boulder might recycle recent validated authz (see issue #267)
-			logf("[INFO][%s] acme: Authorization already valid; skipping challenge", authz.Domain)
+			log.Printf("[INFO][%s] acme: Authorization already valid; skipping challenge", authz.Identifier.Value)
 			continue
 		}
+
 		// no solvers - no solving
-		if solvers := c.chooseSolvers(authz.Body, authz.Domain); solvers != nil {
-			for i, solver := range solvers {
-				// TODO: do not immediately fail if one domain fails to validate.
-				err := solver.Solve(authz.Body.Challenges[i], authz.Domain)
-				if err != nil {
-					c.disableAuthz(authz)
-					failures[authz.Domain] = err
-				}
+		if i, solver := c.chooseSolver(authz, authz.Identifier.Value); solver != nil {
+			err := solver.Solve(authz.Challenges[i], authz.Identifier.Value)
+			if err != nil {
+				//c.disableAuthz(authz.Identifier)
+				failures[authz.Identifier.Value] = err
 			}
 		} else {
-			c.disableAuthz(authz)
-			failures[authz.Domain] = fmt.Errorf("[%s] acme: Could not determine solvers", authz.Domain)
+			//c.disableAuthz(authz)
+			failures[authz.Identifier.Value] = fmt.Errorf("[%s] acme: Could not determine solvers", authz.Identifier.Value)
 		}
 	}
 
-	return failures
-}
-
-// Checks all combinations from the server and returns an array of
-// solvers which should get executed in series.
-func (c *Client) chooseSolvers(auth authorization, domain string) map[int]solver {
-	for _, combination := range auth.Combinations {
-		solvers := make(map[int]solver)
-		for _, idx := range combination {
-			if solver, ok := c.solvers[auth.Challenges[idx].Type]; ok {
-				solvers[idx] = solver
-			} else {
-				logf("[INFO][%s] acme: Could not find solver for: %s", domain, auth.Challenges[idx].Type)
-			}
-		}
-
-		// If we can solve the whole combination, return the solvers
-		if len(solvers) == len(combination) {
-			return solvers
-		}
+	// be careful not to return an empty failures map, for
+	// even an empty ObtainError is a non-nil error value
+	if len(failures) > 0 {
+		return failures
 	}
 	return nil
 }
 
+// Checks all challenges from the server in order and returns the first matching solver.
+func (c *Client) chooseSolver(auth authorization, domain string) (int, solver) {
+	for i, challenge := range auth.Challenges {
+		if solver, ok := c.solvers[Challenge(challenge.Type)]; ok {
+			return i, solver
+		}
+		log.Printf("[INFO][%s] acme: Could not find solver for: %s", domain, challenge.Type)
+	}
+	return 0, nil
+}
+
 // Get the challenges needed to proof our identifier to the ACME server.
-func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[string]error) {
-	resc, errc := make(chan authorizationResource), make(chan domainError)
+func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error) {
+	resc, errc := make(chan authorization), make(chan domainError)
 
 	delay := time.Second / overallRequestLimit
 
-	for _, domain := range domains {
+	for _, authzURL := range order.Authorizations {
 		time.Sleep(delay)
 
-		go func(domain string) {
-			authMsg := authorization{Resource: "new-authz", Identifier: identifier{Type: "dns", Value: domain}}
+		go func(authzURL string) {
 			var authz authorization
-			hdr, err := postJSON(c.jws, c.user.GetRegistration().NewAuthzURL, authMsg, &authz)
+			_, err := getJSON(authzURL, &authz)
 			if err != nil {
-				errc <- domainError{Domain: domain, Error: err}
+				errc <- domainError{Domain: authz.Identifier.Value, Error: err}
 				return
 			}
 
-			links := parseLinks(hdr["Link"])
-			if links["next"] == "" {
-				logf("[ERROR][%s] acme: Server did not provide next link to proceed", domain)
-				errc <- domainError{Domain: domain, Error: errors.New("Server did not provide next link to proceed")}
-				return
-			}
-
-			resc <- authorizationResource{Body: authz, NewCertURL: links["next"], AuthURL: hdr.Get("Location"), Domain: domain}
-		}(domain)
+			resc <- authz
+		}(authzURL)
 	}
 
-	responses := make(map[string]authorizationResource)
-	failures := make(map[string]error)
-	for i := 0; i < len(domains); i++ {
+	var responses []authorization
+	failures := make(ObtainError)
+	for i := 0; i < len(order.Authorizations); i++ {
 		select {
 		case res := <-resc:
-			responses[res.Domain] = res
+			responses = append(responses, res)
 		case err := <-errc:
 			failures[err.Domain] = err.Error
 		}
 	}
 
-	challenges := make([]authorizationResource, 0, len(responses))
-	for _, domain := range domains {
-		if challenge, ok := responses[domain]; ok {
-			challenges = append(challenges, challenge)
-		}
-	}
-
-	logAuthz(challenges)
+	logAuthz(order)
 
 	close(resc)
 	close(errc)
 
-	return challenges, failures
+	// be careful to not return an empty failures map;
+	// even if empty, they become non-nil error values
+	if len(failures) > 0 {
+		return responses, failures
+	}
+	return responses, nil
 }
 
-func logAuthz(authz []authorizationResource) {
-	for _, auth := range authz {
-		logf("[INFO][%s] AuthURL: %s", auth.Domain, auth.AuthURL)
+func logAuthz(order orderResource) {
+	for i, auth := range order.Authorizations {
+		log.Printf("[INFO][%s] AuthURL: %s", order.Identifiers[i].Value, auth)
 	}
 }
 
 // cleanAuthz loops through the passed in slice and disables any auths which are not "valid"
-func (c *Client) disableAuthz(auth authorizationResource) error {
+func (c *Client) disableAuthz(authURL string) error {
 	var disabledAuth authorization
-	_, err := postJSON(c.jws, auth.AuthURL, deactivateAuthMessage{Resource: "authz", Status: "deactivated"}, &disabledAuth)
+	_, err := postJSON(c.jws, authURL, deactivateAuthMessage{Status: "deactivated"}, &disabledAuth)
 	return err
 }
 
-func (c *Client) requestCertificate(authz []authorizationResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) {
-	if len(authz) == 0 {
-		return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
-	}
+func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) {
 
 	var err error
 	if privKey == nil {
 		privKey, err = generatePrivateKey(c.keyType)
 		if err != nil {
-			return CertificateResource{}, err
+			return nil, err
 		}
 	}
 
 	// determine certificate name(s) based on the authorization resources
-	commonName := authz[0]
+	commonName := order.Domains[0]
 	var san []string
-	for _, auth := range authz[1:] {
-		san = append(san, auth.Domain)
+	for _, auth := range order.Identifiers {
+		san = append(san, auth.Value)
 	}
 
 	// TODO: should the CSR be customizable?
-	csr, err := generateCsr(privKey, commonName.Domain, san, mustStaple)
+	csr, err := generateCsr(privKey, commonName, san, mustStaple)
 	if err != nil {
-		return CertificateResource{}, err
+		return nil, err
 	}
 
-	return c.requestCertificateForCsr(authz, bundle, csr, pemEncode(privKey))
+	return c.requestCertificateForCsr(order, bundle, csr, pemEncode(privKey))
 }
 
-func (c *Client) requestCertificateForCsr(authz []authorizationResource, bundle bool, csr []byte, privateKeyPem []byte) (CertificateResource, error) {
-	commonName := authz[0]
+func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr []byte, privateKeyPem []byte) (*CertificateResource, error) {
+	commonName := order.Domains[0]
 
-	var authURLs []string
-	for _, auth := range authz[1:] {
-		authURLs = append(authURLs, auth.AuthURL)
+	csrString := base64.RawURLEncoding.EncodeToString(csr)
+	var retOrder orderMessage
+	_, error := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder)
+	if error != nil {
+		return nil, error
 	}
 
-	csrString := base64.URLEncoding.EncodeToString(csr)
-	jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: authURLs})
-	if err != nil {
-		return CertificateResource{}, err
-	}
-
-	resp, err := c.jws.post(commonName.NewCertURL, jsonBytes)
-	if err != nil {
-		return CertificateResource{}, err
+	if retOrder.Status == "invalid" {
+		return nil, error
 	}
 
 	certRes := CertificateResource{
-		Domain:     commonName.Domain,
-		CertURL:    resp.Header.Get("Location"),
+		Domain:     commonName,
+		CertURL:    retOrder.Certificate,
 		PrivateKey: privateKeyPem,
 	}
 
+	if retOrder.Status == "valid" {
+		// if the certificate is available right away, short cut!
+		ok, err := c.checkCertResponse(retOrder, &certRes, bundle)
+		if err != nil {
+			return nil, err
+		}
+
+		if ok {
+			return &certRes, nil
+		}
+	}
+
 	maxChecks := 1000
 	for i := 0; i < maxChecks; i++ {
-		done, err := c.checkCertResponse(resp, &certRes, bundle)
-		resp.Body.Close()
+		_, err := getJSON(order.URL, &retOrder)
 		if err != nil {
-			return CertificateResource{}, err
+			return nil, err
+		}
+		done, err := c.checkCertResponse(retOrder, &certRes, bundle)
+		if err != nil {
+			return nil, err
 		}
 		if done {
 			break
 		}
 		if i == maxChecks-1 {
-			return CertificateResource{}, fmt.Errorf("polled for certificate %d times; giving up", i)
-		}
-		resp, err = httpGet(certRes.CertURL)
-		if err != nil {
-			return CertificateResource{}, err
+			return nil, fmt.Errorf("polled for certificate %d times; giving up", i)
 		}
 	}
 
-	return certRes, nil
+	return &certRes, nil
 }
 
-// checkCertResponse checks resp to see if a certificate is contained in the
-// response, and if so, loads it into certRes and returns true. If the cert
-// is not yet ready, it returns false. This function honors the waiting period
-// required by the Retry-After header of the response, if specified. This
-// function may read from resp.Body but does NOT close it. The certRes input
+// checkCertResponse checks to see if the certificate is ready and a link is contained in the
+// response. if so, loads it into certRes and returns true. If the cert
+// is not yet ready, it returns false. The certRes input
 // should already have the Domain (common name) field populated. If bundle is
 // true, the certificate will be bundled with the issuer's cert.
-func (c *Client) checkCertResponse(resp *http.Response, certRes *CertificateResource, bundle bool) (bool, error) {
-	switch resp.StatusCode {
-	case 201, 202:
+func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResource, bundle bool) (bool, error) {
+
+	switch order.Status {
+	case "valid":
+		resp, err := httpGet(order.Certificate)
+		if err != nil {
+			return false, err
+		}
+
 		cert, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
 		if err != nil {
 			return false, err
 		}
 
-		// The server returns a body with a length of zero if the
-		// certificate was not ready at the time this request completed.
-		// Otherwise the body is the certificate.
-		if len(cert) > 0 {
-			certRes.CertStableURL = resp.Header.Get("Content-Location")
-			certRes.AccountRef = c.user.GetRegistration().URI
+		// The issuer certificate link is always supplied via an "up" link
+		// in the response headers of a new certificate.
+		links := parseLinks(resp.Header["Link"])
+		if link, ok := links["up"]; ok {
+			issuerCert, err := c.getIssuerCertificate(link)
 
-			issuedCert := pemEncode(derCertificateBytes(cert))
-
-			// The issuer certificate link is always supplied via an "up" link
-			// in the response headers of a new certificate.
-			links := parseLinks(resp.Header["Link"])
-			issuerCert, err := c.getIssuerCertificate(links["up"])
 			if err != nil {
 				// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
-				logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err)
+				log.Printf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err)
 			} else {
 				issuerCert = pemEncode(derCertificateBytes(issuerCert))
 
 				// If bundle is true, we want to return a certificate bundle.
 				// To do this, we append the issuer cert to the issued cert.
 				if bundle {
-					issuedCert = append(issuedCert, issuerCert...)
+					cert = append(cert, issuerCert...)
 				}
+
+				certRes.IssuerCertificate = issuerCert
 			}
-
-			certRes.Certificate = issuedCert
-			certRes.IssuerCertificate = issuerCert
-			logf("[INFO][%s] Server responded with a certificate.", certRes.Domain)
-			return true, nil
 		}
 
-		// The certificate was granted but is not yet issued.
-		// Check retry-after and loop.
-		ra := resp.Header.Get("Retry-After")
-		retryAfter, err := strconv.Atoi(ra)
-		if err != nil {
-			return false, err
-		}
-
-		logf("[INFO][%s] acme: Server responded with status 202; retrying after %ds", certRes.Domain, retryAfter)
-		time.Sleep(time.Duration(retryAfter) * time.Second)
+		certRes.Certificate = cert
+		certRes.CertURL = order.Certificate
+		certRes.CertStableURL = order.Certificate
+		log.Printf("[INFO][%s] Server responded with a certificate.", certRes.Domain)
+		return true, nil
 
+	case "processing":
 		return false, nil
-	default:
-		return false, handleHTTPError(resp)
+	case "invalid":
+		return false, errors.New("Order has invalid state: invalid")
 	}
+
+	return false, nil
 }
 
 // getIssuerCertificate requests the issuer certificate
 func (c *Client) getIssuerCertificate(url string) ([]byte, error) {
-	logf("[INFO] acme: Requesting issuer cert from %s", url)
+	log.Printf("[INFO] acme: Requesting issuer cert from %s", url)
 	resp, err := httpGet(url)
 	if err != nil {
 		return nil, err
@@ -786,10 +806,10 @@ func parseLinks(links []string) map[string]string {
 
 // validate makes the ACME server start validating a
 // challenge response, only returning once it is done.
-func validate(j *jws, domain, uri string, chlng challenge) error {
-	var challengeResponse challenge
+func validate(j *jws, domain, uri string, c challenge) error {
+	var chlng challenge
 
-	hdr, err := postJSON(j, uri, chlng, &challengeResponse)
+	hdr, err := postJSON(j, uri, c, &chlng)
 	if err != nil {
 		return err
 	}
@@ -797,27 +817,26 @@ func validate(j *jws, domain, uri string, chlng challenge) error {
 	// After the path is sent, the ACME server will access our server.
 	// Repeatedly check the server for an updated status on our request.
 	for {
-		switch challengeResponse.Status {
+		switch chlng.Status {
 		case "valid":
-			logf("[INFO][%s] The server validated our request", domain)
+			log.Printf("[INFO][%s] The server validated our request", domain)
 			return nil
 		case "pending":
-			break
 		case "invalid":
-			return handleChallengeError(challengeResponse)
+			return handleChallengeError(chlng)
 		default:
-			return errors.New("The server returned an unexpected state.")
+			return errors.New("the server returned an unexpected state")
 		}
 
 		ra, err := strconv.Atoi(hdr.Get("Retry-After"))
 		if err != nil {
 			// The ACME server MUST return a Retry-After.
 			// If it doesn't, we'll just poll hard.
-			ra = 1
+			ra = 5
 		}
 		time.Sleep(time.Duration(ra) * time.Second)
 
-		hdr, err = getJSON(uri, &challengeResponse)
+		hdr, err = getJSON(uri, &chlng)
 		if err != nil {
 			return err
 		}
diff --git a/acme/client_test.go b/acme/client_test.go
index b18334c8..1e51b9a6 100644
--- a/acme/client_test.go
+++ b/acme/client_test.go
@@ -27,7 +27,13 @@ func TestNewClient(t *testing.T) {
 	}
 
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
+		data, _ := json.Marshal(directory{
+			NewNonceURL:   "http://test",
+			NewAccountURL: "http://test",
+			NewOrderURL:   "http://test",
+			RevokeCertURL: "http://test",
+			KeyChangeURL:  "http://test",
+		})
 		w.Write(data)
 	}))
 
@@ -47,7 +53,7 @@ func TestNewClient(t *testing.T) {
 		t.Errorf("Expected keyType to be %s but was %s", keyType, client.keyType)
 	}
 
-	if expected, actual := 2, len(client.solvers); actual != expected {
+	if expected, actual := 1, len(client.solvers); actual != expected {
 		t.Fatalf("Expected %d solver(s), got %d", expected, actual)
 	}
 }
@@ -65,7 +71,13 @@ func TestClientOptPort(t *testing.T) {
 	}
 
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
+		data, _ := json.Marshal(directory{
+			NewNonceURL:   "http://test",
+			NewAccountURL: "http://test",
+			NewOrderURL:   "http://test",
+			RevokeCertURL: "http://test",
+			KeyChangeURL:  "http://test",
+		})
 		w.Write(data)
 	}))
 
@@ -76,7 +88,6 @@ func TestClientOptPort(t *testing.T) {
 		t.Fatalf("Could not create client: %v", err)
 	}
 	client.SetHTTPAddress(net.JoinHostPort(optHost, optPort))
-	client.SetTLSAddress(net.JoinHostPort(optHost, optPort))
 
 	httpSolver, ok := client.solvers[HTTP01].(*httpChallenge)
 	if !ok {
@@ -92,31 +103,13 @@ func TestClientOptPort(t *testing.T) {
 		t.Errorf("Expected http-01 to have iface %s but was %s", optHost, got)
 	}
 
-	httpsSolver, ok := client.solvers[TLSSNI01].(*tlsSNIChallenge)
-	if !ok {
-		t.Fatal("Expected tls-sni-01 solver to be httpChallenge type")
-	}
-	if httpsSolver.jws != client.jws {
-		t.Error("Expected tls-sni-01 to have same jws as client")
-	}
-	if got := httpsSolver.provider.(*TLSProviderServer).port; got != optPort {
-		t.Errorf("Expected tls-sni-01 to have port %s but was %s", optPort, got)
-	}
-	if got := httpsSolver.provider.(*TLSProviderServer).iface; got != optHost {
-		t.Errorf("Expected tls-sni-01 to have port %s but was %s", optHost, got)
-	}
-
 	// test setting different host
 	optHost = "127.0.0.1"
 	client.SetHTTPAddress(net.JoinHostPort(optHost, optPort))
-	client.SetTLSAddress(net.JoinHostPort(optHost, optPort))
 
 	if got := httpSolver.provider.(*HTTPProviderServer).iface; got != optHost {
 		t.Errorf("Expected http-01 to have iface %s but was %s", optHost, got)
 	}
-	if got := httpsSolver.provider.(*TLSProviderServer).port; got != optPort {
-		t.Errorf("Expected tls-sni-01 to have port %s but was %s", optPort, got)
-	}
 }
 
 func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) {
@@ -124,12 +117,12 @@ func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) {
 		time.Sleep(250 * time.Millisecond)
 		w.Header().Add("Replay-Nonce", "12345")
 		w.Header().Add("Retry-After", "0")
-		writeJSONResponse(w, &challenge{Type: "http-01", Status: "Valid", URI: "http://example.com/", Token: "token"})
+		writeJSONResponse(w, &challenge{Type: "http-01", Status: "Valid", URL: "http://example.com/", Token: "token"})
 	}))
 	defer ts.Close()
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 512)
-	j := &jws{privKey: privKey, directoryURL: ts.URL}
+	j := &jws{privKey: privKey, getNonceURL: ts.URL}
 	ch := make(chan bool)
 	resultCh := make(chan bool)
 	go func() {
@@ -163,12 +156,12 @@ func TestValidate(t *testing.T) {
 		case "POST":
 			st := statuses[0]
 			statuses = statuses[1:]
-			writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URI: "http://example.com/", Token: "token"})
+			writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"})
 
 		case "GET":
 			st := statuses[0]
 			statuses = statuses[1:]
-			writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URI: "http://example.com/", Token: "token"})
+			writeJSONResponse(w, &challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"})
 
 		default:
 			http.Error(w, r.Method, http.StatusMethodNotAllowed)
@@ -177,7 +170,7 @@ func TestValidate(t *testing.T) {
 	defer ts.Close()
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 512)
-	j := &jws{privKey: privKey, directoryURL: ts.URL}
+	j := &jws{privKey: privKey, getNonceURL: ts.URL}
 
 	tsts := []struct {
 		name     string
@@ -186,10 +179,10 @@ func TestValidate(t *testing.T) {
 	}{
 		{"POST-unexpected", []string{"weird"}, "unexpected"},
 		{"POST-valid", []string{"valid"}, ""},
-		{"POST-invalid", []string{"invalid"}, "Error Detail"},
+		{"POST-invalid", []string{"invalid"}, "Error"},
 		{"GET-unexpected", []string{"pending", "weird"}, "unexpected"},
 		{"GET-valid", []string{"pending", "valid"}, ""},
-		{"GET-invalid", []string{"pending", "invalid"}, "Error Detail"},
+		{"GET-invalid", []string{"pending", "invalid"}, "Error"},
 	}
 
 	for _, tst := range tsts {
@@ -209,9 +202,15 @@ func TestGetChallenges(t *testing.T) {
 		case "GET", "HEAD":
 			w.Header().Add("Replay-Nonce", "12345")
 			w.Header().Add("Retry-After", "0")
-			writeJSONResponse(w, directory{NewAuthzURL: ts.URL, NewCertURL: ts.URL, NewRegURL: ts.URL, RevokeCertURL: ts.URL})
+			writeJSONResponse(w, directory{
+				NewNonceURL:   ts.URL,
+				NewAccountURL: ts.URL,
+				NewOrderURL:   ts.URL,
+				RevokeCertURL: ts.URL,
+				KeyChangeURL:  ts.URL,
+			})
 		case "POST":
-			writeJSONResponse(w, authorization{})
+			writeJSONResponse(w, orderMessage{})
 		}
 	}))
 	defer ts.Close()
@@ -224,7 +223,7 @@ func TestGetChallenges(t *testing.T) {
 	}
 	user := mockUser{
 		email:      "test@test.com",
-		regres:     &RegistrationResource{NewAuthzURL: ts.URL},
+		regres:     &RegistrationResource{URI: ts.URL},
 		privatekey: key,
 	}
 
@@ -233,12 +232,60 @@ func TestGetChallenges(t *testing.T) {
 		t.Fatalf("Could not create client: %v", err)
 	}
 
-	_, failures := client.getChallenges([]string{"example.com"})
-	if failures["example.com"] == nil {
+	_, err = client.createOrderForIdentifiers([]string{"example.com"})
+	if err != nil {
 		t.Fatal("Expecting \"Server did not provide next link to proceed\" error, got nil")
 	}
 }
 
+func TestResolveAccountByKey(t *testing.T) {
+	keyBits := 512
+	keyType := RSA2048
+	key, err := rsa.GenerateKey(rand.Reader, keyBits)
+	if err != nil {
+		t.Fatal("Could not generate test key:", err)
+	}
+	user := mockUser{
+		email:      "test@test.com",
+		regres:     new(RegistrationResource),
+		privatekey: key,
+	}
+
+	var ts *httptest.Server
+	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		switch r.RequestURI {
+		case "/directory":
+			writeJSONResponse(w, directory{
+				NewNonceURL:   ts.URL + "/nonce",
+				NewAccountURL: ts.URL + "/account",
+				NewOrderURL:   ts.URL + "/newOrder",
+				RevokeCertURL: ts.URL + "/revokeCert",
+				KeyChangeURL:  ts.URL + "/keyChange",
+			})
+		case "/nonce":
+			w.Header().Add("Replay-Nonce", "12345")
+			w.Header().Add("Retry-After", "0")
+		case "/account":
+			w.Header().Set("Location", ts.URL+"/account_recovery")
+		case "/account_recovery":
+			writeJSONResponse(w, accountMessage{
+				Status: "valid",
+			})
+		}
+	}))
+
+	client, err := NewClient(ts.URL+"/directory", user, keyType)
+	if err != nil {
+		t.Fatalf("Could not create client: %v", err)
+	}
+
+	if res, err := client.ResolveAccountByKey(); err != nil {
+		t.Fatalf("Unexpected error resolving account by key: %v", err)
+	} else if res.Body.Status != "valid" {
+		t.Errorf("Unexpected account status: %v", res.Body.Status)
+	}
+}
+
 // writeJSONResponse marshals the body as JSON and writes it to the response.
 func writeJSONResponse(w http.ResponseWriter, body interface{}) {
 	bs, err := json.Marshal(body)
diff --git a/acme/crypto.go b/acme/crypto.go
index fa868a90..7d4f4425 100644
--- a/acme/crypto.go
+++ b/acme/crypto.go
@@ -9,6 +9,7 @@ import (
 	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509/pkix"
+	"encoding/asn1"
 	"encoding/base64"
 	"encoding/pem"
 	"errors"
@@ -17,12 +18,10 @@ import (
 	"io/ioutil"
 	"math/big"
 	"net/http"
-	"strings"
 	"time"
 
-	"encoding/asn1"
-
 	"golang.org/x/crypto/ocsp"
+	jose "gopkg.in/square/go-jose.v2"
 )
 
 // KeyType represents the key algo as well as the key size or curve to use.
@@ -118,6 +117,10 @@ func GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
 	defer req.Body.Close()
 
 	ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024))
+	if err != nil {
+		return nil, nil, err
+	}
+
 	ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
 	if err != nil {
 		return nil, nil, err
@@ -136,9 +139,9 @@ func getKeyAuthorization(token string, key interface{}) (string, error) {
 	}
 
 	// Generate the Key Authorization for the challenge
-	jwk := keyAsJWK(publicKey)
+	jwk := &jose.JSONWebKey{Key: publicKey}
 	if jwk == nil {
-		return "", errors.New("Could not generate JWK from key.")
+		return "", errors.New("could not generate JWK from key")
 	}
 	thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
 	if err != nil {
@@ -146,11 +149,7 @@ func getKeyAuthorization(token string, key interface{}) (string, error) {
 	}
 
 	// unpad the base64URL
-	keyThumb := base64.URLEncoding.EncodeToString(thumbBytes)
-	index := strings.Index(keyThumb, "=")
-	if index != -1 {
-		keyThumb = keyThumb[:index]
-	}
+	keyThumb := base64.RawURLEncoding.EncodeToString(thumbBytes)
 
 	return token + "." + keyThumb, nil
 }
@@ -177,7 +176,7 @@ func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
 	}
 
 	if len(certificates) == 0 {
-		return nil, errors.New("No certificates were found while parsing the bundle.")
+		return nil, errors.New("no certificates were found while parsing the bundle")
 	}
 
 	return certificates, nil
@@ -192,7 +191,7 @@ func parsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
 	case "EC PRIVATE KEY":
 		return x509.ParseECPrivateKey(keyBlock.Bytes)
 	default:
-		return nil, errors.New("Unknown PEM header value")
+		return nil, errors.New("unknown PEM header value")
 	}
 }
 
@@ -211,7 +210,7 @@ func generatePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
 		return rsa.GenerateKey(rand.Reader, 8192)
 	}
 
-	return nil, fmt.Errorf("Invalid KeyType: %s", keyType)
+	return nil, fmt.Errorf("invalid KeyType: %s", keyType)
 }
 
 func generateCsr(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
@@ -243,10 +242,8 @@ func pemEncode(data interface{}) []byte {
 		pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
 	case *rsa.PrivateKey:
 		pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
-		break
 	case *x509.CertificateRequest:
 		pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
-		break
 	case derCertificateBytes:
 		pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(derCertificateBytes))}
 	}
diff --git a/acme/crypto_test.go b/acme/crypto_test.go
index 6f43835f..3ddf5d01 100644
--- a/acme/crypto_test.go
+++ b/acme/crypto_test.go
@@ -28,7 +28,7 @@ func TestGenerateCSR(t *testing.T) {
 	if err != nil {
 		t.Error("Error generating CSR:", err)
 	}
-	if csr == nil || len(csr) == 0 {
+	if len(csr) == 0 {
 		t.Error("Expected CSR with data, but it was nil or length 0")
 	}
 }
diff --git a/acme/dns_challenge.go b/acme/dns_challenge.go
index d6844dcd..d494501c 100644
--- a/acme/dns_challenge.go
+++ b/acme/dns_challenge.go
@@ -5,12 +5,12 @@ import (
 	"encoding/base64"
 	"errors"
 	"fmt"
-	"log"
 	"net"
 	"strings"
 	"time"
 
 	"github.com/miekg/dns"
+	"github.com/xenolf/lego/log"
 )
 
 type preCheckDNSFunc func(fqdn, value string) (bool, error)
@@ -29,6 +29,7 @@ var defaultNameservers = []string{
 	"google-public-dns-b.google.com:53",
 }
 
+// RecursiveNameservers are used to pre-check DNS propagations
 var RecursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
 
 // DNSTimeout is used to override the default DNS timeout of 10 seconds.
@@ -57,8 +58,7 @@ func getNameservers(path string, defaults []string) []string {
 func DNS01Record(domain, keyAuth string) (fqdn string, value string, ttl int) {
 	keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
 	// base64URL encoding without padding
-	keyAuthSha := base64.URLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
-	value = strings.TrimRight(keyAuthSha, "=")
+	value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
 	ttl = 120
 	fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
 	return
@@ -72,7 +72,7 @@ type dnsChallenge struct {
 }
 
 func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
-	logf("[INFO][%s] acme: Trying to solve DNS-01", domain)
+	log.Printf("[INFO][%s] acme: Trying to solve DNS-01", domain)
 
 	if s.provider == nil {
 		return errors.New("No DNS Provider configured")
@@ -97,7 +97,7 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
 
 	fqdn, value, _ := DNS01Record(domain, keyAuth)
 
-	logf("[INFO][%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers)
+	log.Printf("[INFO][%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers)
 
 	var timeout, interval time.Duration
 	switch provider := s.provider.(type) {
@@ -114,7 +114,7 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
 		return err
 	}
 
-	return s.validate(s.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
+	return s.validate(s.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
 }
 
 // checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
diff --git a/acme/dns_challenge_manual.go b/acme/dns_challenge_manual.go
index 240384e6..cd4c3c8a 100644
--- a/acme/dns_challenge_manual.go
+++ b/acme/dns_challenge_manual.go
@@ -4,6 +4,8 @@ import (
 	"bufio"
 	"fmt"
 	"os"
+
+	"github.com/xenolf/lego/log"
 )
 
 const (
@@ -28,9 +30,9 @@ func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
 		return err
 	}
 
-	logf("[INFO] acme: Please create the following TXT record in your %s zone:", authZone)
-	logf("[INFO] acme: %s", dnsRecord)
-	logf("[INFO] acme: Press 'Enter' when you are done")
+	log.Printf("[INFO] acme: Please create the following TXT record in your %s zone:", authZone)
+	log.Printf("[INFO] acme: %s", dnsRecord)
+	log.Printf("[INFO] acme: Press 'Enter' when you are done")
 
 	reader := bufio.NewReader(os.Stdin)
 	_, _ = reader.ReadString('\n')
@@ -47,7 +49,7 @@ func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
 		return err
 	}
 
-	logf("[INFO] acme: You can now remove this TXT record from your %s zone:", authZone)
-	logf("[INFO] acme: %s", dnsRecord)
+	log.Printf("[INFO] acme: You can now remove this TXT record from your %s zone:", authZone)
+	log.Printf("[INFO] acme: %s", dnsRecord)
 	return nil
 }
diff --git a/acme/dns_challenge_test.go b/acme/dns_challenge_test.go
index 117ac303..e561739b 100644
--- a/acme/dns_challenge_test.go
+++ b/acme/dns_challenge_test.go
@@ -100,9 +100,9 @@ func TestDNSValidServerResponse(t *testing.T) {
 	}))
 
 	manualProvider, _ := NewDNSProviderManual()
-	jws := &jws{privKey: privKey, directoryURL: ts.URL}
+	jws := &jws{privKey: privKey, getNonceURL: ts.URL}
 	solver := &dnsChallenge{jws: jws, validate: validate, provider: manualProvider}
-	clientChallenge := challenge{Type: "dns01", Status: "pending", URI: ts.URL, Token: "http8"}
+	clientChallenge := challenge{Type: "dns01", Status: "pending", URL: ts.URL, Token: "http8"}
 
 	go func() {
 		time.Sleep(time.Second * 2)
diff --git a/acme/error.go b/acme/error.go
index e4bc934c..78694deb 100644
--- a/acme/error.go
+++ b/acme/error.go
@@ -1,6 +1,7 @@
 package acme
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
@@ -9,8 +10,8 @@ import (
 )
 
 const (
-	tosAgreementError = "Must agree to subscriber agreement before any further actions"
-	invalidNonceError = "JWS has invalid anti-replay nonce"
+	tosAgreementError = "Terms of service have changed"
+	invalidNonceError = "urn:ietf:params:acme:error:badNonce"
 )
 
 // RemoteError is the base type for all errors specific to the ACME protocol.
@@ -42,27 +43,23 @@ type domainError struct {
 	Error  error
 }
 
-type challengeError struct {
-	RemoteError
-	records []validationRecord
-}
+// ObtainError is returned when there are specific errors available
+// per domain. For example in ObtainCertificate
+type ObtainError map[string]error
 
-func (c challengeError) Error() string {
-
-	var errStr string
-	for _, validation := range c.records {
-		errStr = errStr + fmt.Sprintf("\tValidation for %s:%s\n\tResolved to:\n\t\t%s\n\tUsed: %s\n\n",
-			validation.Hostname, validation.Port, strings.Join(validation.ResolvedAddresses, "\n\t\t"), validation.UsedAddress)
+func (e ObtainError) Error() string {
+	buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
+	for dom, err := range e {
+		buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err))
 	}
-
-	return fmt.Sprintf("%s\nError Detail:\n%s", c.RemoteError.Error(), errStr)
+	return buffer.String()
 }
 
 func handleHTTPError(resp *http.Response) error {
 	var errorDetail RemoteError
 
 	contentType := resp.Header.Get("Content-Type")
-	if contentType == "application/json" || contentType == "application/problem+json" {
+	if contentType == "application/json" || strings.HasPrefix(contentType, "application/problem+json") {
 		err := json.NewDecoder(resp.Body).Decode(&errorDetail)
 		if err != nil {
 			return err
@@ -82,7 +79,7 @@ func handleHTTPError(resp *http.Response) error {
 		return TOSError{errorDetail}
 	}
 
-	if errorDetail.StatusCode == http.StatusBadRequest && strings.HasPrefix(errorDetail.Detail, invalidNonceError) {
+	if errorDetail.StatusCode == http.StatusBadRequest && errorDetail.Type == invalidNonceError {
 		return NonceError{errorDetail}
 	}
 
@@ -90,5 +87,5 @@ func handleHTTPError(resp *http.Response) error {
 }
 
 func handleChallengeError(chlng challenge) error {
-	return challengeError{chlng.Error, chlng.ValidationRecords}
+	return chlng.Error
 }
diff --git a/acme/http.go b/acme/http.go
index e469e0de..b93e5344 100644
--- a/acme/http.go
+++ b/acme/http.go
@@ -102,7 +102,7 @@ func getJSON(uri string, respBody interface{}) (http.Header, error) {
 func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
 	jsonBytes, err := json.Marshal(reqBody)
 	if err != nil {
-		return nil, errors.New("Failed to marshal network message...")
+		return nil, errors.New("Failed to marshal network message")
 	}
 
 	resp, err := j.post(uri, jsonBytes)
diff --git a/acme/http_challenge.go b/acme/http_challenge.go
index 95cb1fd8..7659bfc5 100644
--- a/acme/http_challenge.go
+++ b/acme/http_challenge.go
@@ -2,7 +2,8 @@ package acme
 
 import (
 	"fmt"
-	"log"
+
+	"github.com/xenolf/lego/log"
 )
 
 type httpChallenge struct {
@@ -18,7 +19,7 @@ func HTTP01ChallengePath(token string) string {
 
 func (s *httpChallenge) Solve(chlng challenge, domain string) error {
 
-	logf("[INFO][%s] acme: Trying to solve HTTP-01", domain)
+	log.Printf("[INFO][%s] acme: Trying to solve HTTP-01", domain)
 
 	// Generate the Key Authorization for the challenge
 	keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
@@ -37,5 +38,5 @@ func (s *httpChallenge) Solve(chlng challenge, domain string) error {
 		}
 	}()
 
-	return s.validate(s.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
+	return s.validate(s.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
 }
diff --git a/acme/http_challenge_server.go b/acme/http_challenge_server.go
index 64c6a828..214a278f 100644
--- a/acme/http_challenge_server.go
+++ b/acme/http_challenge_server.go
@@ -5,6 +5,8 @@ import (
 	"net"
 	"net/http"
 	"strings"
+
+	"github.com/xenolf/lego/log"
 )
 
 // HTTPProviderServer implements ChallengeProvider for `http-01` challenge
@@ -61,9 +63,9 @@ func (s *HTTPProviderServer) serve(domain, token, keyAuth string) {
 		if strings.HasPrefix(r.Host, domain) && r.Method == "GET" {
 			w.Header().Add("Content-Type", "text/plain")
 			w.Write([]byte(keyAuth))
-			logf("[INFO][%s] Served key authentication", domain)
+			log.Printf("[INFO][%s] Served key authentication", domain)
 		} else {
-			logf("[WARN] Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method)
+			log.Printf("[WARN] Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method)
 			w.Write([]byte("TEST"))
 		}
 	})
diff --git a/acme/http_challenge_test.go b/acme/http_challenge_test.go
index 7400f56d..10f92028 100644
--- a/acme/http_challenge_test.go
+++ b/acme/http_challenge_test.go
@@ -11,7 +11,7 @@ import (
 func TestHTTPChallenge(t *testing.T) {
 	privKey, _ := rsa.GenerateKey(rand.Reader, 512)
 	j := &jws{privKey: privKey}
-	clientChallenge := challenge{Type: HTTP01, Token: "http1"}
+	clientChallenge := challenge{Type: string(HTTP01), Token: "http1"}
 	mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
 		uri := "http://localhost:23457/.well-known/acme-challenge/" + chlng.Token
 		resp, err := httpGet(uri)
@@ -46,7 +46,7 @@ func TestHTTPChallenge(t *testing.T) {
 func TestHTTPChallengeInvalidPort(t *testing.T) {
 	privKey, _ := rsa.GenerateKey(rand.Reader, 128)
 	j := &jws{privKey: privKey}
-	clientChallenge := challenge{Type: HTTP01, Token: "http2"}
+	clientChallenge := challenge{Type: string(HTTP01), Token: "http2"}
 	solver := &httpChallenge{jws: j, validate: stubValidate, provider: &HTTPProviderServer{port: "123456"}}
 
 	if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil {
diff --git a/acme/jws.go b/acme/jws.go
index a3943434..bea76210 100644
--- a/acme/jws.go
+++ b/acme/jws.go
@@ -10,39 +10,29 @@ import (
 	"net/http"
 	"sync"
 
-	"gopkg.in/square/go-jose.v1"
+	"gopkg.in/square/go-jose.v2"
 )
 
 type jws struct {
-	directoryURL string
-	privKey      crypto.PrivateKey
-	nonces       nonceManager
-}
-
-func keyAsJWK(key interface{}) *jose.JsonWebKey {
-	switch k := key.(type) {
-	case *ecdsa.PublicKey:
-		return &jose.JsonWebKey{Key: k, Algorithm: "EC"}
-	case *rsa.PublicKey:
-		return &jose.JsonWebKey{Key: k, Algorithm: "RSA"}
-
-	default:
-		return nil
-	}
+	getNonceURL string
+	privKey     crypto.PrivateKey
+	kid         string
+	nonces      nonceManager
 }
 
 // Posts a JWS signed message to the specified URL.
 // It does NOT close the response body, so the caller must
 // do that if no error was returned.
 func (j *jws) post(url string, content []byte) (*http.Response, error) {
-	signedContent, err := j.signContent(content)
+	signedContent, err := j.signContent(url, content)
 	if err != nil {
-		return nil, fmt.Errorf("Failed to sign content -> %s", err.Error())
+		return nil, fmt.Errorf("failed to sign content -> %s", err.Error())
 	}
 
-	resp, err := httpPost(url, "application/jose+json", bytes.NewBuffer([]byte(signedContent.FullSerialize())))
+	data := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
+	resp, err := httpPost(url, "application/jose+json", data)
 	if err != nil {
-		return nil, fmt.Errorf("Failed to HTTP POST to %s -> %s", url, err.Error())
+		return nil, fmt.Errorf("failed to HTTP POST to %s -> %s", url, err.Error())
 	}
 
 	nonce, nonceErr := getNonceFromResponse(resp)
@@ -53,7 +43,7 @@ func (j *jws) post(url string, content []byte) (*http.Response, error) {
 	return resp, nil
 }
 
-func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
+func (j *jws) signContent(url string, content []byte) (*jose.JSONWebSignature, error) {
 
 	var alg jose.SignatureAlgorithm
 	switch k := j.privKey.(type) {
@@ -67,25 +57,71 @@ func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
 		}
 	}
 
-	signer, err := jose.NewSigner(alg, j.privKey)
-	if err != nil {
-		return nil, fmt.Errorf("Failed to create jose signer -> %s", err.Error())
+	jsonKey := jose.JSONWebKey{
+		Key:   j.privKey,
+		KeyID: j.kid,
+	}
+
+	signKey := jose.SigningKey{
+		Algorithm: alg,
+		Key:       jsonKey,
+	}
+	options := jose.SignerOptions{
+		NonceSource:  j,
+		ExtraHeaders: make(map[jose.HeaderKey]interface{}),
+	}
+	options.ExtraHeaders["url"] = url
+	if j.kid == "" {
+		options.EmbedJWK = true
+	}
+
+	signer, err := jose.NewSigner(signKey, &options)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create jose signer -> %s", err.Error())
 	}
-	signer.SetNonceSource(j)
 
 	signed, err := signer.Sign(content)
 	if err != nil {
-		return nil, fmt.Errorf("Failed to sign content -> %s", err.Error())
+		return nil, fmt.Errorf("failed to sign content -> %s", err.Error())
 	}
 	return signed, nil
 }
 
+func (j *jws) signEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
+	jwk := jose.JSONWebKey{Key: j.privKey}
+	jwkJSON, err := jwk.Public().MarshalJSON()
+	if err != nil {
+		return nil, fmt.Errorf("acme: error encoding eab jwk key: %s", err.Error())
+	}
+
+	signer, err := jose.NewSigner(
+		jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
+		&jose.SignerOptions{
+			EmbedJWK: false,
+			ExtraHeaders: map[jose.HeaderKey]interface{}{
+				"kid": kid,
+				"url": url,
+			},
+		},
+	)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %s", err.Error())
+	}
+
+	signed, err := signer.Sign(jwkJSON)
+	if err != nil {
+		return nil, fmt.Errorf("failed to External Account Binding sign content -> %s", err.Error())
+	}
+
+	return signed, nil
+}
+
 func (j *jws) Nonce() (string, error) {
 	if nonce, ok := j.nonces.Pop(); ok {
 		return nonce, nil
 	}
 
-	return getNonce(j.directoryURL)
+	return getNonce(j.getNonceURL)
 }
 
 type nonceManager struct {
@@ -115,7 +151,7 @@ func (n *nonceManager) Push(nonce string) {
 func getNonce(url string) (string, error) {
 	resp, err := httpHead(url)
 	if err != nil {
-		return "", fmt.Errorf("Failed to get nonce from HTTP HEAD -> %s", err.Error())
+		return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %s", err.Error())
 	}
 
 	return getNonceFromResponse(resp)
@@ -124,7 +160,7 @@ func getNonce(url string) (string, error) {
 func getNonceFromResponse(resp *http.Response) (string, error) {
 	nonce := resp.Header.Get("Replay-Nonce")
 	if nonce == "" {
-		return "", fmt.Errorf("Server did not respond with a proper nonce header.")
+		return "", fmt.Errorf("server did not respond with a proper nonce header")
 	}
 
 	return nonce, nil
diff --git a/acme/messages.go b/acme/messages.go
index 79ccf154..6946cc15 100644
--- a/acme/messages.go
+++ b/acme/messages.go
@@ -1,59 +1,62 @@
 package acme
 
 import (
+	"encoding/json"
 	"time"
-
-	"gopkg.in/square/go-jose.v1"
 )
 
-type directory struct {
-	NewAuthzURL   string `json:"new-authz"`
-	NewCertURL    string `json:"new-cert"`
-	NewRegURL     string `json:"new-reg"`
-	RevokeCertURL string `json:"revoke-cert"`
-}
-
-type registrationMessage struct {
-	Resource string   `json:"resource"`
-	Contact  []string `json:"contact"`
-	Delete   bool     `json:"delete,omitempty"`
-}
-
-// Registration is returned by the ACME server after the registration
-// The client implementation should save this registration somewhere.
-type Registration struct {
-	Resource       string          `json:"resource,omitempty"`
-	ID             int             `json:"id"`
-	Key            jose.JsonWebKey `json:"key"`
-	Contact        []string        `json:"contact"`
-	Agreement      string          `json:"agreement,omitempty"`
-	Authorizations string          `json:"authorizations,omitempty"`
-	Certificates   string          `json:"certificates,omitempty"`
-}
-
 // RegistrationResource represents all important informations about a registration
 // of which the client needs to keep track itself.
 type RegistrationResource struct {
-	Body        Registration `json:"body,omitempty"`
-	URI         string       `json:"uri,omitempty"`
-	NewAuthzURL string       `json:"new_authzr_uri,omitempty"`
-	TosURL      string       `json:"terms_of_service,omitempty"`
+	Body accountMessage `json:"body,omitempty"`
+	URI  string         `json:"uri,omitempty"`
 }
 
-type authorizationResource struct {
-	Body       authorization
-	Domain     string
-	NewCertURL string
-	AuthURL    string
+type directory struct {
+	NewNonceURL   string `json:"newNonce"`
+	NewAccountURL string `json:"newAccount"`
+	NewOrderURL   string `json:"newOrder"`
+	RevokeCertURL string `json:"revokeCert"`
+	KeyChangeURL  string `json:"keyChange"`
+	Meta          struct {
+		TermsOfService          string   `json:"termsOfService"`
+		Website                 string   `json:"website"`
+		CaaIdentities           []string `json:"caaIdentities"`
+		ExternalAccountRequired bool     `json:"externalAccountRequired"`
+	} `json:"meta"`
+}
+
+type accountMessage struct {
+	Status                 string          `json:"status,omitempty"`
+	Contact                []string        `json:"contact,omitempty"`
+	TermsOfServiceAgreed   bool            `json:"termsOfServiceAgreed,omitempty"`
+	Orders                 string          `json:"orders,omitempty"`
+	OnlyReturnExisting     bool            `json:"onlyReturnExisting,omitempty"`
+	ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
+}
+
+type orderResource struct {
+	URL          string   `json:"url,omitempty"`
+	Domains      []string `json:"domains,omitempty"`
+	orderMessage `json:"body,omitempty"`
+}
+
+type orderMessage struct {
+	Status         string       `json:"status,omitempty"`
+	Expires        string       `json:"expires,omitempty"`
+	Identifiers    []identifier `json:"identifiers"`
+	NotBefore      string       `json:"notBefore,omitempty"`
+	NotAfter       string       `json:"notAfter,omitempty"`
+	Authorizations []string     `json:"authorizations,omitempty"`
+	Finalize       string       `json:"finalize,omitempty"`
+	Certificate    string       `json:"certificate,omitempty"`
 }
 
 type authorization struct {
-	Resource     string      `json:"resource,omitempty"`
-	Identifier   identifier  `json:"identifier"`
-	Status       string      `json:"status,omitempty"`
-	Expires      time.Time   `json:"expires,omitempty"`
-	Challenges   []challenge `json:"challenges,omitempty"`
-	Combinations [][]int     `json:"combinations,omitempty"`
+	Status     string      `json:"status"`
+	Expires    time.Time   `json:"expires"`
+	Identifier identifier  `json:"identifier"`
+	Challenges []challenge `json:"challenges"`
 }
 
 type identifier struct {
@@ -61,41 +64,26 @@ type identifier struct {
 	Value string `json:"value"`
 }
 
-type validationRecord struct {
-	URI               string   `json:"url,omitempty"`
-	Hostname          string   `json:"hostname,omitempty"`
-	Port              string   `json:"port,omitempty"`
-	ResolvedAddresses []string `json:"addressesResolved,omitempty"`
-	UsedAddress       string   `json:"addressUsed,omitempty"`
-}
-
 type challenge struct {
-	Resource          string             `json:"resource,omitempty"`
-	Type              Challenge          `json:"type,omitempty"`
-	Status            string             `json:"status,omitempty"`
-	URI               string             `json:"uri,omitempty"`
-	Token             string             `json:"token,omitempty"`
-	KeyAuthorization  string             `json:"keyAuthorization,omitempty"`
-	TLS               bool               `json:"tls,omitempty"`
-	Iterations        int                `json:"n,omitempty"`
-	Error             RemoteError        `json:"error,omitempty"`
-	ValidationRecords []validationRecord `json:"validationRecord,omitempty"`
+	URL              string      `json:"url"`
+	Type             string      `json:"type"`
+	Status           string      `json:"status"`
+	Token            string      `json:"token"`
+	Validated        time.Time   `json:"validated"`
+	KeyAuthorization string      `json:"keyAuthorization"`
+	Error            RemoteError `json:"error"`
 }
 
 type csrMessage struct {
-	Resource       string   `json:"resource,omitempty"`
-	Csr            string   `json:"csr"`
-	Authorizations []string `json:"authorizations"`
+	Csr string `json:"csr"`
 }
 
 type revokeCertMessage struct {
-	Resource    string `json:"resource"`
 	Certificate string `json:"certificate"`
 }
 
 type deactivateAuthMessage struct {
-	Resource string `json:"resource,omitempty"`
-	Status   string `jsom:"status"`
+	Status string `jsom:"status"`
 }
 
 // CertificateResource represents a CA issued certificate.
diff --git a/acme/pop_challenge.go b/acme/pop_challenge.go
deleted file mode 100644
index 8d2a213b..00000000
--- a/acme/pop_challenge.go
+++ /dev/null
@@ -1 +0,0 @@
-package acme
diff --git a/acme/tls_sni_challenge.go b/acme/tls_sni_challenge.go
deleted file mode 100644
index 34383cbf..00000000
--- a/acme/tls_sni_challenge.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package acme
-
-import (
-	"crypto/rsa"
-	"crypto/sha256"
-	"crypto/tls"
-	"encoding/hex"
-	"fmt"
-	"log"
-)
-
-type tlsSNIChallenge struct {
-	jws      *jws
-	validate validateFunc
-	provider ChallengeProvider
-}
-
-func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error {
-	// FIXME: https://github.com/ietf-wg-acme/acme/pull/22
-	// Currently we implement this challenge to track boulder, not the current spec!
-
-	logf("[INFO][%s] acme: Trying to solve TLS-SNI-01", domain)
-
-	// Generate the Key Authorization for the challenge
-	keyAuth, err := getKeyAuthorization(chlng.Token, t.jws.privKey)
-	if err != nil {
-		return err
-	}
-
-	err = t.provider.Present(domain, chlng.Token, keyAuth)
-	if err != nil {
-		return fmt.Errorf("[%s] error presenting token: %v", domain, err)
-	}
-	defer func() {
-		err := t.provider.CleanUp(domain, chlng.Token, keyAuth)
-		if err != nil {
-			log.Printf("[%s] error cleaning up: %v", domain, err)
-		}
-	}()
-	return t.validate(t.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
-}
-
-// TLSSNI01ChallengeCert returns a certificate and target domain for the `tls-sni-01` challenge
-func TLSSNI01ChallengeCert(keyAuth string) (tls.Certificate, string, error) {
-	// generate a new RSA key for the certificates
-	tempPrivKey, err := generatePrivateKey(RSA2048)
-	if err != nil {
-		return tls.Certificate{}, "", err
-	}
-	rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
-	rsaPrivPEM := pemEncode(rsaPrivKey)
-
-	zBytes := sha256.Sum256([]byte(keyAuth))
-	z := hex.EncodeToString(zBytes[:sha256.Size])
-	domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
-	tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
-	if err != nil {
-		return tls.Certificate{}, "", err
-	}
-
-	certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
-	if err != nil {
-		return tls.Certificate{}, "", err
-	}
-
-	return certificate, domain, nil
-}
diff --git a/acme/tls_sni_challenge_server.go b/acme/tls_sni_challenge_server.go
deleted file mode 100644
index df00fbb5..00000000
--- a/acme/tls_sni_challenge_server.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package acme
-
-import (
-	"crypto/tls"
-	"fmt"
-	"net"
-	"net/http"
-)
-
-// TLSProviderServer implements ChallengeProvider for `TLS-SNI-01` challenge
-// It may be instantiated without using the NewTLSProviderServer function if
-// you want only to use the default values.
-type TLSProviderServer struct {
-	iface    string
-	port     string
-	done     chan bool
-	listener net.Listener
-}
-
-// NewTLSProviderServer creates a new TLSProviderServer on the selected interface and port.
-// Setting iface and / or port to an empty string will make the server fall back to
-// the "any" interface and port 443 respectively.
-func NewTLSProviderServer(iface, port string) *TLSProviderServer {
-	return &TLSProviderServer{iface: iface, port: port}
-}
-
-// Present makes the keyAuth available as a cert
-func (s *TLSProviderServer) Present(domain, token, keyAuth string) error {
-	if s.port == "" {
-		s.port = "443"
-	}
-
-	cert, _, err := TLSSNI01ChallengeCert(keyAuth)
-	if err != nil {
-		return err
-	}
-
-	tlsConf := new(tls.Config)
-	tlsConf.Certificates = []tls.Certificate{cert}
-
-	s.listener, err = tls.Listen("tcp", net.JoinHostPort(s.iface, s.port), tlsConf)
-	if err != nil {
-		return fmt.Errorf("Could not start HTTPS server for challenge -> %v", err)
-	}
-
-	s.done = make(chan bool)
-	go func() {
-		http.Serve(s.listener, nil)
-		s.done <- true
-	}()
-	return nil
-}
-
-// CleanUp closes the HTTP server.
-func (s *TLSProviderServer) CleanUp(domain, token, keyAuth string) error {
-	if s.listener == nil {
-		return nil
-	}
-	s.listener.Close()
-	<-s.done
-	return nil
-}
diff --git a/acme/tls_sni_challenge_test.go b/acme/tls_sni_challenge_test.go
deleted file mode 100644
index 83b2833a..00000000
--- a/acme/tls_sni_challenge_test.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package acme
-
-import (
-	"crypto/rand"
-	"crypto/rsa"
-	"crypto/sha256"
-	"crypto/tls"
-	"encoding/hex"
-	"fmt"
-	"strings"
-	"testing"
-)
-
-func TestTLSSNIChallenge(t *testing.T) {
-	privKey, _ := rsa.GenerateKey(rand.Reader, 512)
-	j := &jws{privKey: privKey}
-	clientChallenge := challenge{Type: TLSSNI01, Token: "tlssni1"}
-	mockValidate := func(_ *jws, _, _ string, chlng challenge) error {
-		conn, err := tls.Dial("tcp", "localhost:23457", &tls.Config{
-			InsecureSkipVerify: true,
-		})
-		if err != nil {
-			t.Errorf("Expected to connect to challenge server without an error. %s", err.Error())
-		}
-
-		// Expect the server to only return one certificate
-		connState := conn.ConnectionState()
-		if count := len(connState.PeerCertificates); count != 1 {
-			t.Errorf("Expected the challenge server to return exactly one certificate but got %d", count)
-		}
-
-		remoteCert := connState.PeerCertificates[0]
-		if count := len(remoteCert.DNSNames); count != 1 {
-			t.Errorf("Expected the challenge certificate to have exactly one DNSNames entry but had %d", count)
-		}
-
-		zBytes := sha256.Sum256([]byte(chlng.KeyAuthorization))
-		z := hex.EncodeToString(zBytes[:sha256.Size])
-		domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
-
-		if remoteCert.DNSNames[0] != domain {
-			t.Errorf("Expected the challenge certificate DNSName to match %s but was %s", domain, remoteCert.DNSNames[0])
-		}
-
-		return nil
-	}
-	solver := &tlsSNIChallenge{jws: j, validate: mockValidate, provider: &TLSProviderServer{port: "23457"}}
-
-	if err := solver.Solve(clientChallenge, "localhost:23457"); err != nil {
-		t.Errorf("Solve error: got %v, want nil", err)
-	}
-}
-
-func TestTLSSNIChallengeInvalidPort(t *testing.T) {
-	privKey, _ := rsa.GenerateKey(rand.Reader, 128)
-	j := &jws{privKey: privKey}
-	clientChallenge := challenge{Type: TLSSNI01, Token: "tlssni2"}
-	solver := &tlsSNIChallenge{jws: j, validate: stubValidate, provider: &TLSProviderServer{port: "123456"}}
-
-	if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil {
-		t.Errorf("Solve error: got %v, want error", err)
-	} else if want, want18 := "invalid port 123456", "123456: invalid port"; !strings.HasSuffix(err.Error(), want) && !strings.HasSuffix(err.Error(), want18) {
-		t.Errorf("Solve error: got %q, want suffix %q", err.Error(), want)
-	}
-}
diff --git a/cli.go b/cli.go
index 1fce0c56..ba51c1bc 100644
--- a/cli.go
+++ b/cli.go
@@ -4,26 +4,15 @@ package main
 
 import (
 	"fmt"
-	"log"
 	"os"
 	"path"
 	"text/tabwriter"
 
 	"github.com/urfave/cli"
 	"github.com/xenolf/lego/acme"
+	"github.com/xenolf/lego/log"
 )
 
-// Logger is used to log errors; if nil, the default log.Logger is used.
-var Logger *log.Logger
-
-// logger is an helper function to retrieve the available logger
-func logger() *log.Logger {
-	if Logger == nil {
-		Logger = log.New(os.Stderr, "", log.LstdFlags)
-	}
-	return Logger
-}
-
 var (
 	version = "dev"
 )
@@ -45,7 +34,7 @@ func main() {
 
 	app.Before = func(c *cli.Context) error {
 		if c.GlobalString("path") == "" {
-			logger().Fatal("Could not determine current working directory. Please pass --path.")
+			log.Fatal("Could not determine current working directory. Please pass --path.")
 		}
 		return nil
 	}
@@ -124,6 +113,18 @@ func main() {
 			Name:  "accept-tos, a",
 			Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
 		},
+		cli.BoolFlag{
+			Name:  "eab",
+			Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
+		},
+		cli.StringFlag{
+			Name:  "kid",
+			Usage: "Key identifier from External CA. Used for External Account Binding.",
+		},
+		cli.StringFlag{
+			Name:  "hmac",
+			Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
+		},
 		cli.StringFlag{
 			Name:  "key-type, k",
 			Value: "rsa2048",
@@ -136,7 +137,7 @@ func main() {
 		},
 		cli.StringSliceFlag{
 			Name:  "exclude, x",
-			Usage: "Explicitly disallow solvers by name from being used. Solvers: \"http-01\", \"tls-sni-01\", \"dns-01\",.",
+			Usage: "Explicitly disallow solvers by name from being used. Solvers: \"http-01\", \"dns-01\".",
 		},
 		cli.StringFlag{
 			Name:  "webroot",
@@ -150,10 +151,6 @@ func main() {
 			Name:  "http",
 			Usage: "Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port",
 		},
-		cli.StringFlag{
-			Name:  "tls",
-			Usage: "Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port",
-		},
 		cli.StringFlag{
 			Name:  "dns",
 			Usage: "Solve a DNS challenge using the specified provider. Disables all other challenges. Run 'lego dnshelp' for help on usage.",
diff --git a/cli_handlers.go b/cli_handlers.go
index b8790c4b..c5aaafe8 100644
--- a/cli_handlers.go
+++ b/cli_handlers.go
@@ -6,6 +6,7 @@ import (
 	"crypto/x509"
 	"encoding/json"
 	"encoding/pem"
+	"fmt"
 	"io/ioutil"
 	"net/http"
 	"os"
@@ -15,6 +16,7 @@ import (
 
 	"github.com/urfave/cli"
 	"github.com/xenolf/lego/acme"
+	"github.com/xenolf/lego/log"
 	"github.com/xenolf/lego/providers/dns"
 	"github.com/xenolf/lego/providers/http/memcached"
 	"github.com/xenolf/lego/providers/http/webroot"
@@ -50,12 +52,12 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
 
 	err := checkFolder(c.GlobalString("path"))
 	if err != nil {
-		logger().Fatalf("Could not check/create path: %s", err.Error())
+		log.Fatalf("Could not check/create path: %v", err)
 	}
 
 	conf := NewConfiguration(c)
 	if len(c.GlobalString("email")) == 0 {
-		logger().Fatal("You have to pass an account (email address) to the program using --email or -m")
+		log.Fatal("You have to pass an account (email address) to the program using --email or -m")
 	}
 
 	//TODO: move to account struct? Currently MUST pass email.
@@ -63,12 +65,14 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
 
 	keyType, err := conf.KeyType()
 	if err != nil {
-		logger().Fatal(err.Error())
+		log.Fatal(err)
 	}
 
+	acme.UserAgent = fmt.Sprintf("le-go/cli %s", c.App.Version)
+
 	client, err := acme.NewClient(c.GlobalString("server"), acc, keyType)
 	if err != nil {
-		logger().Fatalf("Could not create client: %s", err.Error())
+		log.Fatalf("Could not create client: %v", err)
 	}
 
 	if len(c.GlobalStringSlice("exclude")) > 0 {
@@ -78,75 +82,88 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
 	if c.GlobalIsSet("webroot") {
 		provider, err := webroot.NewHTTPProvider(c.GlobalString("webroot"))
 		if err != nil {
-			logger().Fatal(err)
+			log.Fatal(err)
 		}
 
-		client.SetChallengeProvider(acme.HTTP01, provider)
+		err = client.SetChallengeProvider(acme.HTTP01, provider)
+		if err != nil {
+			log.Fatal(err)
+		}
 
 		// --webroot=foo indicates that the user specifically want to do a HTTP challenge
 		// infer that the user also wants to exclude all other challenges
-		client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01})
+		client.ExcludeChallenges([]acme.Challenge{acme.DNS01})
 	}
 	if c.GlobalIsSet("memcached-host") {
 		provider, err := memcached.NewMemcachedProvider(c.GlobalStringSlice("memcached-host"))
 		if err != nil {
-			logger().Fatal(err)
+			log.Fatal(err)
 		}
 
-		client.SetChallengeProvider(acme.HTTP01, provider)
+		err = client.SetChallengeProvider(acme.HTTP01, provider)
+		if err != nil {
+			log.Fatal(err)
+		}
 
 		// --memcached-host=foo:11211 indicates that the user specifically want to do a HTTP challenge
 		// infer that the user also wants to exclude all other challenges
-		client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01})
+		client.ExcludeChallenges([]acme.Challenge{acme.DNS01})
 	}
 	if c.GlobalIsSet("http") {
-		if strings.Index(c.GlobalString("http"), ":") == -1 {
-			logger().Fatalf("The --http switch only accepts interface:port or :port for its argument.")
+		if !strings.Contains(c.GlobalString("http"), ":") {
+			log.Fatalf("The --http switch only accepts interface:port or :port for its argument.")
 		}
-		client.SetHTTPAddress(c.GlobalString("http"))
-	}
 
-	if c.GlobalIsSet("tls") {
-		if strings.Index(c.GlobalString("tls"), ":") == -1 {
-			logger().Fatalf("The --tls switch only accepts interface:port or :port for its argument.")
+		err = client.SetHTTPAddress(c.GlobalString("http"))
+		if err != nil {
+			log.Fatal(err)
 		}
-		client.SetTLSAddress(c.GlobalString("tls"))
 	}
 
 	if c.GlobalIsSet("dns") {
 		provider, err := dns.NewDNSChallengeProviderByName(c.GlobalString("dns"))
 		if err != nil {
-			logger().Fatal(err)
+			log.Fatal(err)
 		}
 
-		client.SetChallengeProvider(acme.DNS01, provider)
+		err = client.SetChallengeProvider(acme.DNS01, provider)
+		if err != nil {
+			log.Fatal(err)
+		}
 
 		// --dns=foo indicates that the user specifically want to do a DNS challenge
 		// infer that the user also wants to exclude all other challenges
-		client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
+		client.ExcludeChallenges([]acme.Challenge{acme.HTTP01})
+	}
+
+	if client.GetExternalAccountRequired() && !c.GlobalIsSet("eab") {
+		log.Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.")
 	}
 
 	return conf, acc, client
 }
 
-func saveCertRes(certRes acme.CertificateResource, conf *Configuration) {
+func saveCertRes(certRes *acme.CertificateResource, conf *Configuration) {
+	// make sure no funny chars are in the cert names (like wildcards ;))
+	domainName := strings.Replace(certRes.Domain, "*", "_", -1)
+
 	// We store the certificate, private key and metadata in different files
 	// as web servers would not be able to work with a combined file.
-	certOut := path.Join(conf.CertPath(), certRes.Domain+".crt")
-	privOut := path.Join(conf.CertPath(), certRes.Domain+".key")
-	pemOut := path.Join(conf.CertPath(), certRes.Domain+".pem")
-	metaOut := path.Join(conf.CertPath(), certRes.Domain+".json")
-	issuerOut := path.Join(conf.CertPath(), certRes.Domain+".issuer.crt")
+	certOut := path.Join(conf.CertPath(), domainName+".crt")
+	privOut := path.Join(conf.CertPath(), domainName+".key")
+	pemOut := path.Join(conf.CertPath(), domainName+".pem")
+	metaOut := path.Join(conf.CertPath(), domainName+".json")
+	issuerOut := path.Join(conf.CertPath(), domainName+".issuer.crt")
 
 	err := ioutil.WriteFile(certOut, certRes.Certificate, 0600)
 	if err != nil {
-		logger().Fatalf("Unable to save Certificate for domain %s\n\t%s", certRes.Domain, err.Error())
+		log.Fatalf("Unable to save Certificate for domain %s\n\t%v", certRes.Domain, err)
 	}
 
 	if certRes.IssuerCertificate != nil {
 		err = ioutil.WriteFile(issuerOut, certRes.IssuerCertificate, 0600)
 		if err != nil {
-			logger().Fatalf("Unable to save IssuerCertificate for domain %s\n\t%s", certRes.Domain, err.Error())
+			log.Fatalf("Unable to save IssuerCertificate for domain %s\n\t%v", certRes.Domain, err)
 		}
 	}
 
@@ -154,70 +171,59 @@ func saveCertRes(certRes acme.CertificateResource, conf *Configuration) {
 		// if we were given a CSR, we don't know the private key
 		err = ioutil.WriteFile(privOut, certRes.PrivateKey, 0600)
 		if err != nil {
-			logger().Fatalf("Unable to save PrivateKey for domain %s\n\t%s", certRes.Domain, err.Error())
+			log.Fatalf("Unable to save PrivateKey for domain %s\n\t%v", certRes.Domain, err)
 		}
 
 		if conf.context.GlobalBool("pem") {
 			err = ioutil.WriteFile(pemOut, bytes.Join([][]byte{certRes.Certificate, certRes.PrivateKey}, nil), 0600)
 			if err != nil {
-				logger().Fatalf("Unable to save Certificate and PrivateKey in .pem for domain %s\n\t%s", certRes.Domain, err.Error())
+				log.Fatalf("Unable to save Certificate and PrivateKey in .pem for domain %s\n\t%v", certRes.Domain, err)
 			}
 		}
 
 	} else if conf.context.GlobalBool("pem") {
 		// we don't have the private key; can't write the .pem file
-		logger().Fatalf("Unable to save pem without private key for domain %s\n\t%s; are you using a CSR?", certRes.Domain, err.Error())
+		log.Fatalf("Unable to save pem without private key for domain %s\n\t%v; are you using a CSR?", certRes.Domain, err)
 	}
 
 	jsonBytes, err := json.MarshalIndent(certRes, "", "\t")
 	if err != nil {
-		logger().Fatalf("Unable to marshal CertResource for domain %s\n\t%s", certRes.Domain, err.Error())
+		log.Fatalf("Unable to marshal CertResource for domain %s\n\t%v", certRes.Domain, err)
 	}
 
 	err = ioutil.WriteFile(metaOut, jsonBytes, 0600)
 	if err != nil {
-		logger().Fatalf("Unable to save CertResource for domain %s\n\t%s", certRes.Domain, err.Error())
+		log.Fatalf("Unable to save CertResource for domain %s\n\t%v", certRes.Domain, err)
 	}
 }
 
-func handleTOS(c *cli.Context, client *acme.Client, acc *Account) {
+func handleTOS(c *cli.Context, client *acme.Client) bool {
 	// Check for a global accept override
 	if c.GlobalBool("accept-tos") {
-		err := client.AgreeToTOS()
-		if err != nil {
-			logger().Fatalf("Could not agree to TOS: %s", err.Error())
-		}
-
-		acc.Save()
-		return
+		return true
 	}
 
 	reader := bufio.NewReader(os.Stdin)
-	logger().Printf("Please review the TOS at %s", acc.Registration.TosURL)
+	log.Printf("Please review the TOS at %s", client.GetToSURL())
 
 	for {
-		logger().Println("Do you accept the TOS? Y/n")
+		log.Println("Do you accept the TOS? Y/n")
 		text, err := reader.ReadString('\n')
 		if err != nil {
-			logger().Fatalf("Could not read from console: %s", err.Error())
+			log.Fatalf("Could not read from console: %v", err)
 		}
 
 		text = strings.Trim(text, "\r\n")
 
 		if text == "n" {
-			logger().Fatal("You did not accept the TOS. Unable to proceed.")
+			log.Fatal("You did not accept the TOS. Unable to proceed.")
 		}
 
 		if text == "Y" || text == "y" || text == "" {
-			err = client.AgreeToTOS()
-			if err != nil {
-				logger().Fatalf("Could not agree to TOS: %s", err.Error())
-			}
-			acc.Save()
-			break
+			return true
 		}
 
-		logger().Println("Your input was invalid. Please answer with one of Y/y, n or by pressing enter.")
+		log.Println("Your input was invalid. Please answer with one of Y/y, n or by pressing enter.")
 	}
 }
 
@@ -253,18 +259,43 @@ func readCSRFile(filename string) (*x509.CertificateRequest, error) {
 }
 
 func run(c *cli.Context) error {
+	var err error
+
 	conf, acc, client := setup(c)
 	if acc.Registration == nil {
-		reg, err := client.Register()
+		accepted := handleTOS(c, client)
+		if !accepted {
+			log.Fatal("You did not accept the TOS. Unable to proceed.")
+		}
+
+		var reg *acme.RegistrationResource
+
+		if c.GlobalBool("eab") {
+			kid := c.GlobalString("kid")
+			hmacEncoded := c.GlobalString("hmac")
+
+			if kid == "" || hmacEncoded == "" {
+				log.Fatalf("Requires arguments --kid and --hmac.")
+			}
+
+			reg, err = client.RegisterWithExternalAccountBinding(
+				accepted,
+				kid,
+				hmacEncoded,
+			)
+		} else {
+			reg, err = client.Register(accepted)
+		}
+
 		if err != nil {
-			logger().Fatalf("Could not complete registration\n\t%s", err.Error())
+			log.Fatalf("Could not complete registration\n\t%v", err)
 		}
 
 		acc.Registration = reg
 		acc.Save()
 
-		logger().Print("!!!! HEADS UP !!!!")
-		logger().Printf(`
+		log.Print("!!!! HEADS UP !!!!")
+		log.Printf(`
 		Your account credentials have been saved in your Let's Encrypt
 		configuration directory at "%s".
 		You should make a secure backup	of this folder now. This
@@ -274,43 +305,32 @@ func run(c *cli.Context) error {
 
 	}
 
-	// If the agreement URL is empty, the account still needs to accept the LE TOS.
-	if acc.Registration.Body.Agreement == "" {
-		handleTOS(c, client, acc)
-	}
-
 	// we require either domains or csr, but not both
 	hasDomains := len(c.GlobalStringSlice("domains")) > 0
 	hasCsr := len(c.GlobalString("csr")) > 0
 	if hasDomains && hasCsr {
-		logger().Fatal("Please specify either --domains/-d or --csr/-c, but not both")
+		log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
 	}
 	if !hasDomains && !hasCsr {
-		logger().Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
+		log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
 	}
 
-	var cert acme.CertificateResource
-	var failures map[string]error
+	var cert *acme.CertificateResource
 
 	if hasDomains {
 		// obtain a certificate, generating a new private key
-		cert, failures = client.ObtainCertificate(c.GlobalStringSlice("domains"), !c.Bool("no-bundle"), nil, c.Bool("must-staple"))
+		cert, err = client.ObtainCertificate(c.GlobalStringSlice("domains"), !c.Bool("no-bundle"), nil, c.Bool("must-staple"))
 	} else {
 		// read the CSR
 		csr, err := readCSRFile(c.GlobalString("csr"))
-		if err != nil {
-			// we couldn't read the CSR
-			failures = map[string]error{"csr": err}
-		} else {
+		if err == nil {
 			// obtain a certificate for this CSR
-			cert, failures = client.ObtainCertificateForCSR(*csr, !c.Bool("no-bundle"))
+			cert, err = client.ObtainCertificateForCSR(*csr, !c.Bool("no-bundle"))
 		}
 	}
 
-	if len(failures) > 0 {
-		for k, v := range failures {
-			logger().Printf("[%s] Could not obtain certificates\n\t%s", k, v.Error())
-		}
+	if err != nil {
+		log.Printf("Could not obtain certificates\n\t%v", err)
 
 		// Make sure to return a non-zero exit code if ObtainSANCertificate
 		// returned at least one error. Due to us not returning partial
@@ -318,9 +338,8 @@ func run(c *cli.Context) error {
 		os.Exit(1)
 	}
 
-	err := checkFolder(conf.CertPath())
-	if err != nil {
-		logger().Fatalf("Could not check/create path: %s", err.Error())
+	if err = checkFolder(conf.CertPath()); err != nil {
+		log.Fatalf("Could not check/create path: %v", err)
 	}
 
 	saveCertRes(cert, conf)
@@ -331,25 +350,27 @@ func run(c *cli.Context) error {
 func revoke(c *cli.Context) error {
 	conf, acc, client := setup(c)
 	if acc.Registration == nil {
-		logger().Fatalf("Account %s is not registered. Use 'run' to register a new account.\n", acc.Email)
+		log.Fatalf("Account %s is not registered. Use 'run' to register a new account.\n", acc.Email)
 	}
 
-	err := checkFolder(conf.CertPath())
-	if err != nil {
-		logger().Fatalf("Could not check/create path: %s", err.Error())
+	if err := checkFolder(conf.CertPath()); err != nil {
+		log.Fatalf("Could not check/create path: %v", err)
 	}
 
 	for _, domain := range c.GlobalStringSlice("domains") {
-		logger().Printf("Trying to revoke certificate for domain %s", domain)
+		log.Printf("Trying to revoke certificate for domain %s", domain)
 
 		certPath := path.Join(conf.CertPath(), domain+".crt")
 		certBytes, err := ioutil.ReadFile(certPath)
+		if err != nil {
+			log.Println(err)
+		}
 
 		err = client.RevokeCertificate(certBytes)
 		if err != nil {
-			logger().Fatalf("Error while revoking the certificate for domain %s\n\t%s", domain, err.Error())
+			log.Fatalf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
 		} else {
-			logger().Print("Certificate was revoked.")
+			log.Println("Certificate was revoked.")
 		}
 	}
 
@@ -359,14 +380,15 @@ func revoke(c *cli.Context) error {
 func renew(c *cli.Context) error {
 	conf, acc, client := setup(c)
 	if acc.Registration == nil {
-		logger().Fatalf("Account %s is not registered. Use 'run' to register a new account.\n", acc.Email)
+		log.Fatalf("Account %s is not registered. Use 'run' to register a new account.\n", acc.Email)
 	}
 
 	if len(c.GlobalStringSlice("domains")) <= 0 {
-		logger().Fatal("Please specify at least one domain.")
+		log.Fatal("Please specify at least one domain.")
 	}
 
 	domain := c.GlobalStringSlice("domains")[0]
+	domain = strings.Replace(domain, "*", "_", -1)
 
 	// load the cert resource from files.
 	// We store the certificate, private key and metadata in different files
@@ -377,13 +399,13 @@ func renew(c *cli.Context) error {
 
 	certBytes, err := ioutil.ReadFile(certPath)
 	if err != nil {
-		logger().Fatalf("Error while loading the certificate for domain %s\n\t%s", domain, err.Error())
+		log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
 	}
 
 	if c.IsSet("days") {
 		expTime, err := acme.GetPEMCertExpiration(certBytes)
 		if err != nil {
-			logger().Printf("Could not get Certification expiration for domain %s", domain)
+			log.Printf("Could not get Certification expiration for domain %s", domain)
 		}
 
 		if int(expTime.Sub(time.Now()).Hours()/24.0) > c.Int("days") {
@@ -393,19 +415,18 @@ func renew(c *cli.Context) error {
 
 	metaBytes, err := ioutil.ReadFile(metaPath)
 	if err != nil {
-		logger().Fatalf("Error while loading the meta data for domain %s\n\t%s", domain, err.Error())
+		log.Fatalf("Error while loading the meta data for domain %s\n\t%v", domain, err)
 	}
 
 	var certRes acme.CertificateResource
-	err = json.Unmarshal(metaBytes, &certRes)
-	if err != nil {
-		logger().Fatalf("Error while marshalling the meta data for domain %s\n\t%s", domain, err.Error())
+	if err := json.Unmarshal(metaBytes, &certRes); err != nil {
+		log.Fatalf("Error while marshalling the meta data for domain %s\n\t%v", domain, err)
 	}
 
 	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())
+			log.Fatalf("Error while loading the private key for domain %s\n\t%v", domain, err)
 		}
 		certRes.PrivateKey = keyBytes
 	}
@@ -414,7 +435,7 @@ func renew(c *cli.Context) error {
 
 	newCert, err := client.RenewCertificate(certRes, !c.Bool("no-bundle"), c.Bool("must-staple"))
 	if err != nil {
-		logger().Fatalf("%s", err.Error())
+		log.Fatal(err)
 	}
 
 	saveCertRes(newCert, conf)
diff --git a/crypto.go b/crypto.go
index 8b23e2fc..0d42b173 100644
--- a/crypto.go
+++ b/crypto.go
@@ -52,5 +52,5 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
 		return x509.ParseECPrivateKey(keyBlock.Bytes)
 	}
 
-	return nil, errors.New("Unknown private key type.")
+	return nil, errors.New("unknown private key type")
 }
diff --git a/log/logger.go b/log/logger.go
new file mode 100644
index 00000000..291541c0
--- /dev/null
+++ b/log/logger.go
@@ -0,0 +1,59 @@
+package log
+
+import (
+	"log"
+	"os"
+)
+
+// Logger is an optional custom logger.
+var Logger *log.Logger
+
+// Fatal writes a log entry.
+// It uses Logger if not nil, otherwise it uses the default log.Logger.
+func Fatal(args ...interface{}) {
+	if Logger == nil {
+		Logger = log.New(os.Stderr, "", log.LstdFlags)
+	}
+
+	Logger.Fatal(args...)
+}
+
+// Fatalf writes a log entry.
+// It uses Logger if not nil, otherwise it uses the default log.Logger.
+func Fatalf(format string, args ...interface{}) {
+	if Logger == nil {
+		Logger = log.New(os.Stderr, "", log.LstdFlags)
+	}
+
+	Logger.Fatalf(format, args...)
+}
+
+// Print writes a log entry.
+// It uses Logger if not nil, otherwise it uses the default log.Logger.
+func Print(args ...interface{}) {
+	if Logger == nil {
+		Logger = log.New(os.Stdout, "", log.LstdFlags)
+	}
+
+	Logger.Print(args...)
+}
+
+// Println writes a log entry.
+// It uses Logger if not nil, otherwise it uses the default log.Logger.
+func Println(args ...interface{}) {
+	if Logger == nil {
+		Logger = log.New(os.Stdout, "", log.LstdFlags)
+	}
+
+	Logger.Println(args...)
+}
+
+// Printf writes a log entry.
+// It uses Logger if not nil, otherwise it uses the default log.Logger.
+func Printf(format string, args ...interface{}) {
+	if Logger == nil {
+		Logger = log.New(os.Stdout, "", log.LstdFlags)
+	}
+
+	Logger.Printf(format, args...)
+}
diff --git a/providers/dns/auroradns/auroradns.go b/providers/dns/auroradns/auroradns.go
index 55b48f9b..03d31754 100644
--- a/providers/dns/auroradns/auroradns.go
+++ b/providers/dns/auroradns/auroradns.go
@@ -2,12 +2,13 @@ package auroradns
 
 import (
 	"fmt"
+	"os"
+	"sync"
+
 	"github.com/edeckers/auroradnsclient"
 	"github.com/edeckers/auroradnsclient/records"
 	"github.com/edeckers/auroradnsclient/zones"
 	"github.com/xenolf/lego/acme"
-	"os"
-	"sync"
 )
 
 // DNSProvider describes a provider for AuroraDNS
@@ -59,7 +60,7 @@ func (provider *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRe
 		}
 	}
 
-	return zones.ZoneRecord{}, fmt.Errorf("Could not find Zone record")
+	return zones.ZoneRecord{}, fmt.Errorf("could not find Zone record")
 }
 
 // Present creates a record with a secret
@@ -83,6 +84,9 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
 	authZone = acme.UnFqdn(authZone)
 
 	zoneRecord, err := provider.getZoneInformationByName(authZone)
+	if err != nil {
+		return fmt.Errorf("could not create record: %v", err)
+	}
 
 	reqData :=
 		records.CreateRecordRequest{
@@ -94,7 +98,7 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
 
 	respData, err := provider.client.CreateRecord(zoneRecord.ID, reqData)
 	if err != nil {
-		return fmt.Errorf("Could not create record: '%s'.", err)
+		return fmt.Errorf("could not create record: %v", err)
 	}
 
 	provider.recordIDsMu.Lock()
@@ -113,12 +117,12 @@ func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	provider.recordIDsMu.Unlock()
 
 	if !ok {
-		return fmt.Errorf("Unknown recordID for '%s'", fqdn)
+		return fmt.Errorf("unknown recordID for %q", fqdn)
 	}
 
 	authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
 	if err != nil {
-		return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err)
+		return fmt.Errorf("could not determine zone for domain: %q. %v", domain, err)
 	}
 
 	authZone = acme.UnFqdn(authZone)
diff --git a/providers/dns/auroradns/auroradns_test.go b/providers/dns/auroradns/auroradns_test.go
index f4df7fa6..f93bc95d 100644
--- a/providers/dns/auroradns/auroradns_test.go
+++ b/providers/dns/auroradns/auroradns_test.go
@@ -8,7 +8,7 @@ import (
 	"testing"
 )
 
-var fakeAuroraDNSUserId = "asdf1234"
+var fakeAuroraDNSUserID = "asdf1234"
 var fakeAuroraDNSKey = "key"
 
 func TestAuroraDNSPresent(t *testing.T) {
@@ -60,7 +60,7 @@ func TestAuroraDNSPresent(t *testing.T) {
 
 	defer mock.Close()
 
-	auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserId, fakeAuroraDNSKey)
+	auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserID, fakeAuroraDNSKey)
 	if auroraProvider == nil {
 		t.Fatal("Expected non-nil AuroraDNS provider, but was nil")
 	}
@@ -123,7 +123,7 @@ func TestAuroraDNSCleanUp(t *testing.T) {
 	}))
 	defer mock.Close()
 
-	auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserId, fakeAuroraDNSKey)
+	auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserID, fakeAuroraDNSKey)
 	if auroraProvider == nil {
 		t.Fatal("Expected non-nil AuroraDNS provider, but was nil")
 	}
diff --git a/providers/dns/azure/azure.go b/providers/dns/azure/azure.go
index 9022af47..b26d8526 100644
--- a/providers/dns/azure/azure.go
+++ b/providers/dns/azure/azure.go
@@ -7,12 +7,10 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"strings"
 	"time"
 
 	"github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns"
-
-	"strings"
-
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/adal"
 	"github.com/Azure/go-autorest/autorest/azure"
@@ -22,32 +20,31 @@ import (
 
 // DNSProvider is an implementation of the acme.ChallengeProvider interface
 type DNSProvider struct {
-	clientId       string
+	clientID       string
 	clientSecret   string
-	subscriptionId string
-	tenantId       string
+	subscriptionID string
+	tenantID       string
 	resourceGroup  string
-
-	context context.Context
+	context        context.Context
 }
 
 // NewDNSProvider returns a DNSProvider instance configured for azure.
 // Credentials must be passed in the environment variables: AZURE_CLIENT_ID,
 // AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
 func NewDNSProvider() (*DNSProvider, error) {
-	clientId := os.Getenv("AZURE_CLIENT_ID")
+	clientID := os.Getenv("AZURE_CLIENT_ID")
 	clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
-	subscriptionId := os.Getenv("AZURE_SUBSCRIPTION_ID")
-	tenantId := os.Getenv("AZURE_TENANT_ID")
+	subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID")
+	tenantID := os.Getenv("AZURE_TENANT_ID")
 	resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP")
-	return NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup)
+	return NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup)
 }
 
 // NewDNSProviderCredentials uses the supplied credentials to return a
 // DNSProvider instance configured for azure.
-func NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup string) (*DNSProvider, error) {
-	if clientId == "" || clientSecret == "" || subscriptionId == "" || tenantId == "" || resourceGroup == "" {
-		missingEnvVars := []string{}
+func NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup string) (*DNSProvider, error) {
+	if clientID == "" || clientSecret == "" || subscriptionID == "" || tenantID == "" || resourceGroup == "" {
+		var missingEnvVars []string
 		for _, envVar := range []string{"AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_RESOURCE_GROUP"} {
 			if os.Getenv(envVar) == "" {
 				missingEnvVars = append(missingEnvVars, envVar)
@@ -57,10 +54,10 @@ func NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId,
 	}
 
 	return &DNSProvider{
-		clientId:       clientId,
+		clientID:       clientID,
 		clientSecret:   clientSecret,
-		subscriptionId: subscriptionId,
-		tenantId:       tenantId,
+		subscriptionID: subscriptionID,
+		tenantID:       tenantID,
 		resourceGroup:  resourceGroup,
 		// TODO: A timeout can be added here for cancellation purposes.
 		context: context.Background(),
@@ -81,8 +78,12 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 		return err
 	}
 
-	rsc := dns.NewRecordSetsClient(c.subscriptionId)
+	rsc := dns.NewRecordSetsClient(c.subscriptionID)
 	spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
+	if err != nil {
+		return err
+	}
+
 	rsc.Authorizer = autorest.NewBearerAuthorizer(spt)
 
 	relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
@@ -90,16 +91,12 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 		Name: &relative,
 		RecordSetProperties: &dns.RecordSetProperties{
 			TTL:        to.Int64Ptr(60),
-			TxtRecords: &[]dns.TxtRecord{dns.TxtRecord{Value: &[]string{value}}},
+			TxtRecords: &[]dns.TxtRecord{{Value: &[]string{value}}},
 		},
 	}
+
 	_, err = rsc.CreateOrUpdate(c.context, c.resourceGroup, zone, relative, dns.TXT, rec, "", "")
-
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 // Returns the relative record to the domain
@@ -117,15 +114,16 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	}
 
 	relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
-	rsc := dns.NewRecordSetsClient(c.subscriptionId)
+	rsc := dns.NewRecordSetsClient(c.subscriptionID)
 	spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
-	rsc.Authorizer = autorest.NewBearerAuthorizer(spt)
-	_, err = rsc.Delete(c.context, c.resourceGroup, zone, relative, dns.TXT, "")
 	if err != nil {
 		return err
 	}
 
-	return nil
+	rsc.Authorizer = autorest.NewBearerAuthorizer(spt)
+
+	_, err = rsc.Delete(c.context, c.resourceGroup, zone, relative, dns.TXT, "")
+	return err
 }
 
 // Checks that azure has a zone for this domain name.
@@ -137,12 +135,14 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
 
 	// Now we want to to Azure and get the zone.
 	spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
+	if err != nil {
+		return "", err
+	}
 
-	dc := dns.NewZonesClient(c.subscriptionId)
+	dc := dns.NewZonesClient(c.subscriptionID)
 	dc.Authorizer = autorest.NewBearerAuthorizer(spt)
 
 	zone, err := dc.Get(c.context, c.resourceGroup, acme.UnFqdn(authZone))
-
 	if err != nil {
 		return "", err
 	}
@@ -154,9 +154,9 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
 // NewServicePrincipalTokenFromCredentials creates a new ServicePrincipalToken using values of the
 // passed credentials map.
 func (c *DNSProvider) newServicePrincipalTokenFromCredentials(scope string) (*adal.ServicePrincipalToken, error) {
-	oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, c.tenantId)
+	oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, c.tenantID)
 	if err != nil {
-		panic(err)
+		return nil, err
 	}
-	return adal.NewServicePrincipalToken(*oauthConfig, c.clientId, c.clientSecret, scope)
+	return adal.NewServicePrincipalToken(*oauthConfig, c.clientID, c.clientSecret, scope)
 }
diff --git a/providers/dns/bluecat/bluecat.go b/providers/dns/bluecat/bluecat.go
index 92b8a21d..b0374b7e 100644
--- a/providers/dns/bluecat/bluecat.go
+++ b/providers/dns/bluecat/bluecat.go
@@ -13,18 +13,19 @@ import (
 	"strings"
 	"time"
 
-	"github.com/xenolf/lego/acme"
 	"io/ioutil"
+
+	"github.com/xenolf/lego/acme"
 )
 
-const bluecatUrlTemplate = "%s/Services/REST/v1"
+const bluecatURLTemplate = "%s/Services/REST/v1"
 const configType = "Configuration"
 const viewType = "View"
 const txtType = "TXTRecord"
 const zoneType = "Zone"
 
 type entityResponse struct {
-	Id         uint   `json:"id"`
+	ID         uint   `json:"id"`
 	Name       string `json:"name"`
 	Type       string `json:"type"`
 	Properties string `json:"properties"`
@@ -33,7 +34,7 @@ type entityResponse struct {
 // DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
 // Bluecat's Address Manager REST API to manage TXT records for a domain.
 type DNSProvider struct {
-	baseUrl    string
+	baseURL    string
 	userName   string
 	password   string
 	configName string
@@ -55,7 +56,7 @@ func NewDNSProvider() (*DNSProvider, error) {
 	password := os.Getenv("BLUECAT_PASSWORD")
 	configName := os.Getenv("BLUECAT_CONFIG_NAME")
 	dnsView := os.Getenv("BLUECAT_DNS_VIEW")
-	httpClient := http.Client{Timeout: time.Duration(30 * time.Second)}
+	httpClient := http.Client{Timeout: 30 * time.Second}
 	return NewDNSProviderCredentials(server, userName, password, configName, dnsView, httpClient)
 }
 
@@ -67,7 +68,7 @@ func NewDNSProviderCredentials(server, userName, password, configName, dnsView s
 	}
 
 	return &DNSProvider{
-		baseUrl:    fmt.Sprintf(bluecatUrlTemplate, server),
+		baseURL:    fmt.Sprintf(bluecatURLTemplate, server),
 		userName:   userName,
 		password:   password,
 		configName: configName,
@@ -79,7 +80,7 @@ func NewDNSProviderCredentials(server, userName, password, configName, dnsView s
 // Send a REST request, using query parameters specified. The Authorization
 // header will be set if we have an active auth token
 func (d *DNSProvider) sendRequest(method, resource string, payload interface{}, queryArgs map[string]string) (*http.Response, error) {
-	url := fmt.Sprintf("%s/%s", d.baseUrl, resource)
+	url := fmt.Sprintf("%s/%s", d.baseURL, resource)
 
 	body, err := json.Marshal(payload)
 	if err != nil {
@@ -159,14 +160,14 @@ func (d *DNSProvider) logout() error {
 
 	if resp.StatusCode != 200 {
 		return fmt.Errorf("Bluecat API request failed to delete session with HTTP status code %d", resp.StatusCode)
-	} else {
-		authBytes, _ := ioutil.ReadAll(resp.Body)
-		authResp := string(authBytes)
+	}
 
-		if !strings.Contains(authResp, "successfully") {
-			msg := strings.Trim(authResp, "\"")
-			return fmt.Errorf("Bluecat API request failed to delete session: %s", msg)
-		}
+	authBytes, _ := ioutil.ReadAll(resp.Body)
+	authResp := string(authBytes)
+
+	if !strings.Contains(authResp, "successfully") {
+		msg := strings.Trim(authResp, "\"")
+		return fmt.Errorf("Bluecat API request failed to delete session: %s", msg)
 	}
 
 	d.token = ""
@@ -175,7 +176,7 @@ func (d *DNSProvider) logout() error {
 }
 
 // Lookup the entity ID of the configuration named in our properties
-func (d *DNSProvider) lookupConfId() (uint, error) {
+func (d *DNSProvider) lookupConfID() (uint, error) {
 	queryArgs := map[string]string{
 		"parentId": strconv.Itoa(0),
 		"name":     d.configName,
@@ -193,18 +194,18 @@ func (d *DNSProvider) lookupConfId() (uint, error) {
 	if err != nil {
 		return 0, err
 	}
-	return conf.Id, nil
+	return conf.ID, nil
 }
 
 // Find the DNS view with the given name within
-func (d *DNSProvider) lookupViewId(viewName string) (uint, error) {
-	confId, err := d.lookupConfId()
+func (d *DNSProvider) lookupViewID(viewName string) (uint, error) {
+	confID, err := d.lookupConfID()
 	if err != nil {
 		return 0, err
 	}
 
 	queryArgs := map[string]string{
-		"parentId": strconv.FormatUint(uint64(confId), 10),
+		"parentId": strconv.FormatUint(uint64(confID), 10),
 		"name":     d.dnsView,
 		"type":     viewType,
 	}
@@ -221,13 +222,13 @@ func (d *DNSProvider) lookupViewId(viewName string) (uint, error) {
 		return 0, err
 	}
 
-	return view.Id, nil
+	return view.ID, nil
 }
 
 // Return the entityId of the parent zone by recursing from the root view
 // Also return the simple name of the host
-func (d *DNSProvider) lookupParentZoneId(viewId uint, fqdn string) (uint, string, error) {
-	parentViewId := viewId
+func (d *DNSProvider) lookupParentZoneID(viewID uint, fqdn string) (uint, string, error) {
+	parentViewID := viewID
 	name := ""
 
 	if fqdn != "" {
@@ -236,25 +237,24 @@ func (d *DNSProvider) lookupParentZoneId(viewId uint, fqdn string) (uint, string
 		name = zones[0]
 
 		for i := last; i > -1; i-- {
-			zoneId, err := d.getZone(parentViewId, zones[i])
-			if err != nil || zoneId == 0 {
-				return parentViewId, name, err
+			zoneID, err := d.getZone(parentViewID, zones[i])
+			if err != nil || zoneID == 0 {
+				return parentViewID, name, err
 			}
-			if (i > 0) {
-				name = strings.Join(zones[0:i],".")
+			if i > 0 {
+				name = strings.Join(zones[0:i], ".")
 			}
-			parentViewId = zoneId
+			parentViewID = zoneID
 		}
 	}
 
-	return parentViewId, name, nil
+	return parentViewID, name, nil
 }
 
 // Get the DNS zone with the specified name under the parentId
-func (d *DNSProvider) getZone(parentId uint, name string) (uint, error) {
-
+func (d *DNSProvider) getZone(parentID uint, name string) (uint, error) {
 	queryArgs := map[string]string{
-		"parentId": strconv.FormatUint(uint64(parentId), 10),
+		"parentId": strconv.FormatUint(uint64(parentID), 10),
 		"name":     name,
 		"type":     zoneType,
 	}
@@ -275,7 +275,7 @@ func (d *DNSProvider) getZone(parentId uint, name string) (uint, error) {
 		return 0, err
 	}
 
-	return zone.Id, nil
+	return zone.ID, nil
 }
 
 // Present creates a TXT record using the specified parameters
@@ -289,21 +289,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 		return err
 	}
 
-	viewId, err := d.lookupViewId(d.dnsView)
+	viewID, err := d.lookupViewID(d.dnsView)
 	if err != nil {
 		return err
 	}
 
-	parentZoneId, name, err := d.lookupParentZoneId(viewId, fqdn)
+	parentZoneID, name, err := d.lookupParentZoneID(viewID, fqdn)
+	if err != nil {
+		return err
+	}
 
 	queryArgs := map[string]string{
-		"parentId":     strconv.FormatUint(uint64(parentZoneId), 10),
+		"parentId": strconv.FormatUint(uint64(parentZoneID), 10),
 	}
 
 	body := bluecatEntity{
-		Name:		name,
-		Type:		"TXTRecord",
-		Properties:	fmt.Sprintf("ttl=%d|absoluteName=%s|txt=%s|", ttl, fqdn, value),
+		Name:       name,
+		Type:       "TXTRecord",
+		Properties: fmt.Sprintf("ttl=%d|absoluteName=%s|txt=%s|", ttl, fqdn, value),
 	}
 
 	resp, err := d.sendRequest("POST", "addEntity", body, queryArgs)
@@ -321,23 +324,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 		return fmt.Errorf("Bluecat API addEntity request failed: %s", addTxtResp)
 	}
 
-	err = d.deploy(uint(parentZoneId))
+	err = d.deploy(parentZoneID)
 	if err != nil {
 		return err
 	}
 
-	err = d.logout()
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return d.logout()
 }
 
 // Deploy the DNS config for the specified entity to the authoritative servers
-func (d *DNSProvider) deploy(entityId uint) error {
+func (d *DNSProvider) deploy(entityID uint) error {
 	queryArgs := map[string]string{
-		"entityId": strconv.FormatUint(uint64(entityId), 10),
+		"entityId": strconv.FormatUint(uint64(entityID), 10),
 	}
 
 	resp, err := d.sendRequest("POST", "quickDeploy", nil, queryArgs)
@@ -359,18 +357,18 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 		return err
 	}
 
-	viewId, err := d.lookupViewId(d.dnsView)
+	viewID, err := d.lookupViewID(d.dnsView)
 	if err != nil {
 		return err
 	}
 
-	parentId, name, err := d.lookupParentZoneId(viewId, fqdn)
+	parentID, name, err := d.lookupParentZoneID(viewID, fqdn)
 	if err != nil {
 		return err
 	}
 
 	queryArgs := map[string]string{
-		"parentId": strconv.FormatUint(uint64(parentId), 10),
+		"parentId": strconv.FormatUint(uint64(parentID), 10),
 		"name":     name,
 		"type":     txtType,
 	}
@@ -387,7 +385,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 		return err
 	}
 	queryArgs = map[string]string{
-		"objectId": strconv.FormatUint(uint64(txtRec.Id), 10),
+		"objectId": strconv.FormatUint(uint64(txtRec.ID), 10),
 	}
 
 	resp, err = d.sendRequest("DELETE", "delete", nil, queryArgs)
@@ -396,23 +394,18 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	}
 	defer resp.Body.Close()
 
-	err = d.deploy(parentId)
+	err = d.deploy(parentID)
 	if err != nil {
 		return err
 	}
 
-	err = d.logout()
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return d.logout()
 }
 
-//JSON body for Bluecat entity requests and responses
+// JSON body for Bluecat entity requests and responses
 type bluecatEntity struct {
-	ID      string `json:"id,omitempty"`
-	Name    string `json:"name"`
-	Type    string `json:"type"`
+	ID         string `json:"id,omitempty"`
+	Name       string `json:"name"`
+	Type       string `json:"type"`
 	Properties string `json:"properties"`
 }
diff --git a/providers/dns/bluecat/bluecat_test.go b/providers/dns/bluecat/bluecat_test.go
index c1138ffc..0362e58a 100644
--- a/providers/dns/bluecat/bluecat_test.go
+++ b/providers/dns/bluecat/bluecat_test.go
@@ -4,8 +4,9 @@ import (
 	"os"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
 	"time"
+
+	"github.com/stretchr/testify/assert"
 )
 
 var (
@@ -14,7 +15,7 @@ var (
 	bluecatUserName   string
 	bluecatPassword   string
 	bluecatConfigName string
-	bluecatDnsView    string
+	bluecatDNSView    string
 	bluecatDomain     string
 )
 
@@ -24,8 +25,13 @@ func init() {
 	bluecatPassword = os.Getenv("BLUECAT_PASSWORD")
 	bluecatDomain = os.Getenv("BLUECAT_DOMAIN")
 	bluecatConfigName = os.Getenv("BLUECAT_CONFIG_NAME")
-	bluecatDnsView = os.Getenv("BLUECAT_DNS_VIEW")
-	if len(bluecatServer) > 0 && len(bluecatDomain) > 0 && len(bluecatUserName) > 0 && len(bluecatPassword) > 0 && len(bluecatConfigName) > 0 && len(bluecatDnsView) > 0 {
+	bluecatDNSView = os.Getenv("BLUECAT_DNS_VIEW")
+	if len(bluecatServer) > 0 &&
+		len(bluecatDomain) > 0 &&
+		len(bluecatUserName) > 0 &&
+		len(bluecatPassword) > 0 &&
+		len(bluecatConfigName) > 0 &&
+		len(bluecatDNSView) > 0 {
 		bluecatLiveTest = true
 	}
 }
diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go
index d62b26f0..7cc32faf 100644
--- a/providers/dns/cloudflare/cloudflare.go
+++ b/providers/dns/cloudflare/cloudflare.go
@@ -81,11 +81,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 	}
 
 	_, err = c.makeRequest("POST", fmt.Sprintf("/zones/%s/dns_records", zoneID), bytes.NewReader(body))
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 // CleanUp removes the TXT record matching the specified parameters
@@ -98,11 +94,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	}
 
 	_, err = c.makeRequest("DELETE", fmt.Sprintf("/zones/%s/dns_records/%s", record.ZoneID, record.ID), nil)
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
@@ -162,7 +154,7 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) {
 		}
 	}
 
-	return nil, fmt.Errorf("No existing record found for %s", fqdn)
+	return nil, fmt.Errorf("no existing record found for %s", fqdn)
 }
 
 func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
@@ -187,7 +179,6 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
 
 	req.Header.Set("X-Auth-Email", c.authEmail)
 	req.Header.Set("X-Auth-Key", c.authKey)
-	//req.Header.Set("User-Agent", userAgent())
 
 	client := http.Client{Timeout: 30 * time.Second}
 	resp, err := client.Do(req)
diff --git a/providers/dns/cloudxns/cloudxns.go b/providers/dns/cloudxns/cloudxns.go
index 59697417..1204af14 100644
--- a/providers/dns/cloudxns/cloudxns.go
+++ b/providers/dns/cloudxns/cloudxns.go
@@ -1,214 +1,210 @@
-// Package cloudxns implements a DNS provider for solving the DNS-01 challenge
-// using cloudxns DNS.
-package cloudxns
-
-import (
-	"bytes"
-	"crypto/md5"
-	"encoding/hex"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"os"
-	"strconv"
-	"time"
-
-	"github.com/xenolf/lego/acme"
-)
-
-const cloudXNSBaseURL = "https://www.cloudxns.net/api2/"
-
-// DNSProvider is an implementation of the acme.ChallengeProvider interface
-type DNSProvider struct {
-	apiKey    string
-	secretKey string
-}
-
-// NewDNSProvider returns a DNSProvider instance configured for cloudxns.
-// Credentials must be passed in the environment variables: CLOUDXNS_API_KEY
-// and CLOUDXNS_SECRET_KEY.
-func NewDNSProvider() (*DNSProvider, error) {
-	apiKey := os.Getenv("CLOUDXNS_API_KEY")
-	secretKey := os.Getenv("CLOUDXNS_SECRET_KEY")
-	return NewDNSProviderCredentials(apiKey, secretKey)
-}
-
-// NewDNSProviderCredentials uses the supplied credentials to return a
-// DNSProvider instance configured for cloudxns.
-func NewDNSProviderCredentials(apiKey, secretKey string) (*DNSProvider, error) {
-	if apiKey == "" || secretKey == "" {
-		return nil, fmt.Errorf("CloudXNS credentials missing")
-	}
-
-	return &DNSProvider{
-		apiKey:    apiKey,
-		secretKey: secretKey,
-	}, nil
-}
-
-// Present creates a TXT record to fulfil the dns-01 challenge.
-func (c *DNSProvider) Present(domain, token, keyAuth string) error {
-	fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
-	zoneID, err := c.getHostedZoneID(fqdn)
-	if err != nil {
-		return err
-	}
-
-	return c.addTxtRecord(zoneID, fqdn, value, ttl)
-}
-
-// CleanUp removes the TXT record matching the specified parameters.
-func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
-	fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
-	zoneID, err := c.getHostedZoneID(fqdn)
-	if err != nil {
-		return err
-	}
-
-	recordID, err := c.findTxtRecord(zoneID, fqdn)
-	if err != nil {
-		return err
-	}
-
-	return c.delTxtRecord(recordID, zoneID)
-}
-
-func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
-	type Data struct {
-		ID     string `json:"id"`
-		Domain string `json:"domain"`
-	}
-
-	authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
-	if err != nil {
-		return "", err
-	}
-
-	result, err := c.makeRequest("GET", "domain", nil)
-	if err != nil {
-		return "", err
-	}
-
-	var domains []Data
-	err = json.Unmarshal(result, &domains)
-	if err != nil {
-		return "", err
-	}
-
-	for _, data := range domains {
-		if data.Domain == authZone {
-			return data.ID, nil
-		}
-	}
-
-	return "", fmt.Errorf("Zone %s not found in cloudxns for domain %s", authZone, fqdn)
-}
-
-func (c *DNSProvider) findTxtRecord(zoneID, fqdn string) (string, error) {
-	result, err := c.makeRequest("GET", fmt.Sprintf("record/%s?host_id=0&offset=0&row_num=2000", zoneID), nil)
-	if err != nil {
-		return "", err
-	}
-
-	var records []cloudXNSRecord
-	err = json.Unmarshal(result, &records)
-	if err != nil {
-		return "", err
-	}
-
-	for _, record := range records {
-		if record.Host == acme.UnFqdn(fqdn) && record.Type == "TXT" {
-			return record.RecordID, nil
-		}
-	}
-
-	return "", fmt.Errorf("No existing record found for %s", fqdn)
-}
-
-func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error {
-	id, err := strconv.Atoi(zoneID)
-	if err != nil {
-		return err
-	}
-
-	payload := cloudXNSRecord{
-		ID:     id,
-		Host:   acme.UnFqdn(fqdn),
-		Value:  value,
-		Type:   "TXT",
-		LineID: 1,
-		TTL:    ttl,
-	}
-
-	body, err := json.Marshal(payload)
-	if err != nil {
-		return err
-	}
-
-	_, err = c.makeRequest("POST", "record", body)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (c *DNSProvider) delTxtRecord(recordID, zoneID string) error {
-	_, err := c.makeRequest("DELETE", fmt.Sprintf("record/%s/%s", recordID, zoneID), nil)
-	return err
-}
-
-func (c *DNSProvider) hmac(url, date, body string) string {
-	sum := md5.Sum([]byte(c.apiKey + url + body + date + c.secretKey))
-	return hex.EncodeToString(sum[:])
-}
-
-func (c *DNSProvider) makeRequest(method, uri string, body []byte) (json.RawMessage, error) {
-	type APIResponse struct {
-		Code    int             `json:"code"`
-		Message string          `json:"message"`
-		Data    json.RawMessage `json:"data,omitempty"`
-	}
-
-	url := cloudXNSBaseURL + uri
-	req, err := http.NewRequest(method, url, bytes.NewReader(body))
-	if err != nil {
-		return nil, err
-	}
-
-	requestDate := time.Now().Format(time.RFC1123Z)
-
-	req.Header.Set("API-KEY", c.apiKey)
-	req.Header.Set("API-REQUEST-DATE", requestDate)
-	req.Header.Set("API-HMAC", c.hmac(url, requestDate, string(body)))
-	req.Header.Set("API-FORMAT", "json")
-
-	resp, err := acme.HTTPClient.Do(req)
-	if err != nil {
-		return nil, err
-	}
-
-	defer resp.Body.Close()
-
-	var r APIResponse
-	err = json.NewDecoder(resp.Body).Decode(&r)
-	if err != nil {
-		return nil, err
-	}
-
-	if r.Code != 1 {
-		return nil, fmt.Errorf("CloudXNS API Error: %s", r.Message)
-	}
-	return r.Data, nil
-}
-
-type cloudXNSRecord struct {
-	ID       int    `json:"domain_id,omitempty"`
-	RecordID string `json:"record_id,omitempty"`
-
-	Host   string `json:"host"`
-	Value  string `json:"value"`
-	Type   string `json:"type"`
-	LineID int    `json:"line_id,string"`
-	TTL    int    `json:"ttl,string"`
-}
+// Package cloudxns implements a DNS provider for solving the DNS-01 challenge
+// using cloudxns DNS.
+package cloudxns
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"os"
+	"strconv"
+	"time"
+
+	"github.com/xenolf/lego/acme"
+)
+
+const cloudXNSBaseURL = "https://www.cloudxns.net/api2/"
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface
+type DNSProvider struct {
+	apiKey    string
+	secretKey string
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for cloudxns.
+// Credentials must be passed in the environment variables: CLOUDXNS_API_KEY
+// and CLOUDXNS_SECRET_KEY.
+func NewDNSProvider() (*DNSProvider, error) {
+	apiKey := os.Getenv("CLOUDXNS_API_KEY")
+	secretKey := os.Getenv("CLOUDXNS_SECRET_KEY")
+	return NewDNSProviderCredentials(apiKey, secretKey)
+}
+
+// NewDNSProviderCredentials uses the supplied credentials to return a
+// DNSProvider instance configured for cloudxns.
+func NewDNSProviderCredentials(apiKey, secretKey string) (*DNSProvider, error) {
+	if apiKey == "" || secretKey == "" {
+		return nil, fmt.Errorf("CloudXNS credentials missing")
+	}
+
+	return &DNSProvider{
+		apiKey:    apiKey,
+		secretKey: secretKey,
+	}, nil
+}
+
+// Present creates a TXT record to fulfil the dns-01 challenge.
+func (c *DNSProvider) Present(domain, token, keyAuth string) error {
+	fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
+	zoneID, err := c.getHostedZoneID(fqdn)
+	if err != nil {
+		return err
+	}
+
+	return c.addTxtRecord(zoneID, fqdn, value, ttl)
+}
+
+// CleanUp removes the TXT record matching the specified parameters.
+func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+	fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+	zoneID, err := c.getHostedZoneID(fqdn)
+	if err != nil {
+		return err
+	}
+
+	recordID, err := c.findTxtRecord(zoneID, fqdn)
+	if err != nil {
+		return err
+	}
+
+	return c.delTxtRecord(recordID, zoneID)
+}
+
+func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
+	type Data struct {
+		ID     string `json:"id"`
+		Domain string `json:"domain"`
+	}
+
+	authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+	if err != nil {
+		return "", err
+	}
+
+	result, err := c.makeRequest("GET", "domain", nil)
+	if err != nil {
+		return "", err
+	}
+
+	var domains []Data
+	err = json.Unmarshal(result, &domains)
+	if err != nil {
+		return "", err
+	}
+
+	for _, data := range domains {
+		if data.Domain == authZone {
+			return data.ID, nil
+		}
+	}
+
+	return "", fmt.Errorf("zone %s not found in cloudxns for domain %s", authZone, fqdn)
+}
+
+func (c *DNSProvider) findTxtRecord(zoneID, fqdn string) (string, error) {
+	result, err := c.makeRequest("GET", fmt.Sprintf("record/%s?host_id=0&offset=0&row_num=2000", zoneID), nil)
+	if err != nil {
+		return "", err
+	}
+
+	var records []cloudXNSRecord
+	err = json.Unmarshal(result, &records)
+	if err != nil {
+		return "", err
+	}
+
+	for _, record := range records {
+		if record.Host == acme.UnFqdn(fqdn) && record.Type == "TXT" {
+			return record.RecordID, nil
+		}
+	}
+
+	return "", fmt.Errorf("no existing record found for %s", fqdn)
+}
+
+func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error {
+	id, err := strconv.Atoi(zoneID)
+	if err != nil {
+		return err
+	}
+
+	payload := cloudXNSRecord{
+		ID:     id,
+		Host:   acme.UnFqdn(fqdn),
+		Value:  value,
+		Type:   "TXT",
+		LineID: 1,
+		TTL:    ttl,
+	}
+
+	body, err := json.Marshal(payload)
+	if err != nil {
+		return err
+	}
+
+	_, err = c.makeRequest("POST", "record", body)
+	return err
+}
+
+func (c *DNSProvider) delTxtRecord(recordID, zoneID string) error {
+	_, err := c.makeRequest("DELETE", fmt.Sprintf("record/%s/%s", recordID, zoneID), nil)
+	return err
+}
+
+func (c *DNSProvider) hmac(url, date, body string) string {
+	sum := md5.Sum([]byte(c.apiKey + url + body + date + c.secretKey))
+	return hex.EncodeToString(sum[:])
+}
+
+func (c *DNSProvider) makeRequest(method, uri string, body []byte) (json.RawMessage, error) {
+	type APIResponse struct {
+		Code    int             `json:"code"`
+		Message string          `json:"message"`
+		Data    json.RawMessage `json:"data,omitempty"`
+	}
+
+	url := cloudXNSBaseURL + uri
+	req, err := http.NewRequest(method, url, bytes.NewReader(body))
+	if err != nil {
+		return nil, err
+	}
+
+	requestDate := time.Now().Format(time.RFC1123Z)
+
+	req.Header.Set("API-KEY", c.apiKey)
+	req.Header.Set("API-REQUEST-DATE", requestDate)
+	req.Header.Set("API-HMAC", c.hmac(url, requestDate, string(body)))
+	req.Header.Set("API-FORMAT", "json")
+
+	resp, err := acme.HTTPClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+
+	var r APIResponse
+	err = json.NewDecoder(resp.Body).Decode(&r)
+	if err != nil {
+		return nil, err
+	}
+
+	if r.Code != 1 {
+		return nil, fmt.Errorf("CloudXNS API Error: %s", r.Message)
+	}
+	return r.Data, nil
+}
+
+type cloudXNSRecord struct {
+	ID       int    `json:"domain_id,omitempty"`
+	RecordID string `json:"record_id,omitempty"`
+
+	Host   string `json:"host"`
+	Value  string `json:"value"`
+	Type   string `json:"type"`
+	LineID int    `json:"line_id,string"`
+	TTL    int    `json:"ttl,string"`
+}
diff --git a/providers/dns/cloudxns/cloudxns_test.go b/providers/dns/cloudxns/cloudxns_test.go
index 8f26ba82..ee08df2b 100644
--- a/providers/dns/cloudxns/cloudxns_test.go
+++ b/providers/dns/cloudxns/cloudxns_test.go
@@ -1,80 +1,80 @@
-package cloudxns
-
-import (
-	"os"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-)
-
-var (
-	cxLiveTest  bool
-	cxAPIKey    string
-	cxSecretKey string
-	cxDomain    string
-)
-
-func init() {
-	cxAPIKey = os.Getenv("CLOUDXNS_API_KEY")
-	cxSecretKey = os.Getenv("CLOUDXNS_SECRET_KEY")
-	cxDomain = os.Getenv("CLOUDXNS_DOMAIN")
-	if len(cxAPIKey) > 0 && len(cxSecretKey) > 0 && len(cxDomain) > 0 {
-		cxLiveTest = true
-	}
-}
-
-func restoreCloudXNSEnv() {
-	os.Setenv("CLOUDXNS_API_KEY", cxAPIKey)
-	os.Setenv("CLOUDXNS_SECRET_KEY", cxSecretKey)
-}
-
-func TestNewDNSProviderValid(t *testing.T) {
-	os.Setenv("CLOUDXNS_API_KEY", "")
-	os.Setenv("CLOUDXNS_SECRET_KEY", "")
-	_, err := NewDNSProviderCredentials("123", "123")
-	assert.NoError(t, err)
-	restoreCloudXNSEnv()
-}
-
-func TestNewDNSProviderValidEnv(t *testing.T) {
-	os.Setenv("CLOUDXNS_API_KEY", "123")
-	os.Setenv("CLOUDXNS_SECRET_KEY", "123")
-	_, err := NewDNSProvider()
-	assert.NoError(t, err)
-	restoreCloudXNSEnv()
-}
-
-func TestNewDNSProviderMissingCredErr(t *testing.T) {
-	os.Setenv("CLOUDXNS_API_KEY", "")
-	os.Setenv("CLOUDXNS_SECRET_KEY", "")
-	_, err := NewDNSProvider()
-	assert.EqualError(t, err, "CloudXNS credentials missing")
-	restoreCloudXNSEnv()
-}
-
-func TestCloudXNSPresent(t *testing.T) {
-	if !cxLiveTest {
-		t.Skip("skipping live test")
-	}
-
-	provider, err := NewDNSProviderCredentials(cxAPIKey, cxSecretKey)
-	assert.NoError(t, err)
-
-	err = provider.Present(cxDomain, "", "123d==")
-	assert.NoError(t, err)
-}
-
-func TestCloudXNSCleanUp(t *testing.T) {
-	if !cxLiveTest {
-		t.Skip("skipping live test")
-	}
-
-	time.Sleep(time.Second * 2)
-
-	provider, err := NewDNSProviderCredentials(cxAPIKey, cxSecretKey)
-	assert.NoError(t, err)
-
-	err = provider.CleanUp(cxDomain, "", "123d==")
-	assert.NoError(t, err)
-}
+package cloudxns
+
+import (
+	"os"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var (
+	cxLiveTest  bool
+	cxAPIKey    string
+	cxSecretKey string
+	cxDomain    string
+)
+
+func init() {
+	cxAPIKey = os.Getenv("CLOUDXNS_API_KEY")
+	cxSecretKey = os.Getenv("CLOUDXNS_SECRET_KEY")
+	cxDomain = os.Getenv("CLOUDXNS_DOMAIN")
+	if len(cxAPIKey) > 0 && len(cxSecretKey) > 0 && len(cxDomain) > 0 {
+		cxLiveTest = true
+	}
+}
+
+func restoreCloudXNSEnv() {
+	os.Setenv("CLOUDXNS_API_KEY", cxAPIKey)
+	os.Setenv("CLOUDXNS_SECRET_KEY", cxSecretKey)
+}
+
+func TestNewDNSProviderValid(t *testing.T) {
+	os.Setenv("CLOUDXNS_API_KEY", "")
+	os.Setenv("CLOUDXNS_SECRET_KEY", "")
+	_, err := NewDNSProviderCredentials("123", "123")
+	assert.NoError(t, err)
+	restoreCloudXNSEnv()
+}
+
+func TestNewDNSProviderValidEnv(t *testing.T) {
+	os.Setenv("CLOUDXNS_API_KEY", "123")
+	os.Setenv("CLOUDXNS_SECRET_KEY", "123")
+	_, err := NewDNSProvider()
+	assert.NoError(t, err)
+	restoreCloudXNSEnv()
+}
+
+func TestNewDNSProviderMissingCredErr(t *testing.T) {
+	os.Setenv("CLOUDXNS_API_KEY", "")
+	os.Setenv("CLOUDXNS_SECRET_KEY", "")
+	_, err := NewDNSProvider()
+	assert.EqualError(t, err, "CloudXNS credentials missing")
+	restoreCloudXNSEnv()
+}
+
+func TestCloudXNSPresent(t *testing.T) {
+	if !cxLiveTest {
+		t.Skip("skipping live test")
+	}
+
+	provider, err := NewDNSProviderCredentials(cxAPIKey, cxSecretKey)
+	assert.NoError(t, err)
+
+	err = provider.Present(cxDomain, "", "123d==")
+	assert.NoError(t, err)
+}
+
+func TestCloudXNSCleanUp(t *testing.T) {
+	if !cxLiveTest {
+		t.Skip("skipping live test")
+	}
+
+	time.Sleep(time.Second * 2)
+
+	provider, err := NewDNSProviderCredentials(cxAPIKey, cxSecretKey)
+	assert.NoError(t, err)
+
+	err = provider.CleanUp(cxDomain, "", "123d==")
+	assert.NoError(t, err)
+}
diff --git a/providers/dns/digitalocean/digitalocean.go b/providers/dns/digitalocean/digitalocean.go
index da261b39..ef749e6f 100644
--- a/providers/dns/digitalocean/digitalocean.go
+++ b/providers/dns/digitalocean/digitalocean.go
@@ -22,6 +22,12 @@ type DNSProvider struct {
 	recordIDsMu  sync.Mutex
 }
 
+// Timeout returns the timeout and interval to use when checking for DNS
+// propagation. Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+	return 60 * time.Second, 5 * time.Second
+}
+
 // NewDNSProvider returns a DNSProvider instance configured for Digital
 // Ocean. Credentials must be passed in the environment variable:
 // DO_AUTH_TOKEN.
@@ -44,34 +50,17 @@ func NewDNSProviderCredentials(apiAuthToken string) (*DNSProvider, error) {
 
 // Present creates a TXT record using the specified parameters
 func (d *DNSProvider) Present(domain, token, keyAuth string) error {
-	// txtRecordRequest represents the request body to DO's API to make a TXT record
-	type txtRecordRequest struct {
-		RecordType string `json:"type"`
-		Name       string `json:"name"`
-		Data       string `json:"data"`
-	}
-
-	// txtRecordResponse represents a response from DO's API after making a TXT record
-	type txtRecordResponse struct {
-		DomainRecord struct {
-			ID   int    `json:"id"`
-			Type string `json:"type"`
-			Name string `json:"name"`
-			Data string `json:"data"`
-		} `json:"domain_record"`
-	}
-
 	fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
 
 	authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
 	if err != nil {
-		return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err)
+		return fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
 	}
 
 	authZone = acme.UnFqdn(authZone)
 
 	reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, authZone)
-	reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value}
+	reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value, TTL: 30}
 	body, err := json.Marshal(reqData)
 	if err != nil {
 		return err
@@ -124,7 +113,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 
 	authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
 	if err != nil {
-		return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err)
+		return fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
 	}
 
 	authZone = acme.UnFqdn(authZone)
@@ -164,3 +153,21 @@ type digitalOceanAPIError struct {
 }
 
 var digitalOceanBaseURL = "https://api.digitalocean.com"
+
+// txtRecordRequest represents the request body to DO's API to make a TXT record
+type txtRecordRequest struct {
+	RecordType string `json:"type"`
+	Name       string `json:"name"`
+	Data       string `json:"data"`
+	TTL        int    `json:"ttl"`
+}
+
+// txtRecordResponse represents a response from DO's API after making a TXT record
+type txtRecordResponse struct {
+	DomainRecord struct {
+		ID   int    `json:"id"`
+		Type string `json:"type"`
+		Name string `json:"name"`
+		Data string `json:"data"`
+	} `json:"domain_record"`
+}
diff --git a/providers/dns/digitalocean/digitalocean_test.go b/providers/dns/digitalocean/digitalocean_test.go
index 7498508b..c2bef08b 100644
--- a/providers/dns/digitalocean/digitalocean_test.go
+++ b/providers/dns/digitalocean/digitalocean_test.go
@@ -33,7 +33,7 @@ func TestDigitalOceanPresent(t *testing.T) {
 		if err != nil {
 			t.Fatalf("Error reading request body: %v", err)
 		}
-		if got, want := string(reqBody), `{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI"}`; got != want {
+		if got, want := string(reqBody), `{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":30}`; got != want {
 			t.Errorf("Expected body data to be: `%s` but got `%s`", want, got)
 		}
 
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index 72ab0d8c..83693d20 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -1,4 +1,3 @@
-// Factory for DNS providers
 package dns
 
 import (
@@ -7,6 +6,7 @@ import (
 	"github.com/xenolf/lego/acme"
 	"github.com/xenolf/lego/providers/dns/auroradns"
 	"github.com/xenolf/lego/providers/dns/azure"
+	"github.com/xenolf/lego/providers/dns/bluecat"
 	"github.com/xenolf/lego/providers/dns/cloudflare"
 	"github.com/xenolf/lego/providers/dns/cloudxns"
 	"github.com/xenolf/lego/providers/dns/digitalocean"
@@ -35,9 +35,9 @@ import (
 	"github.com/xenolf/lego/providers/dns/rfc2136"
 	"github.com/xenolf/lego/providers/dns/route53"
 	"github.com/xenolf/lego/providers/dns/vultr"
-	"github.com/xenolf/lego/providers/dns/bluecat"
 )
 
+// NewDNSChallengeProviderByName Factory for DNS providers
 func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) {
 	var err error
 	var provider acme.ChallengeProvider
@@ -107,7 +107,7 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
 	case "exec":
 		provider, err = exec.NewDNSProvider()
 	default:
-		err = fmt.Errorf("Unrecognised DNS provider: %s", name)
+		err = fmt.Errorf("unrecognised DNS provider: %s", name)
 	}
 	return provider, err
 }
diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go
index df76a241..30c7fc2f 100644
--- a/providers/dns/dnsimple/dnsimple.go
+++ b/providers/dns/dnsimple/dnsimple.go
@@ -23,14 +23,14 @@ type DNSProvider struct {
 // See: https://developer.dnsimple.com/v2/#authentication
 func NewDNSProvider() (*DNSProvider, error) {
 	accessToken := os.Getenv("DNSIMPLE_OAUTH_TOKEN")
-	baseUrl := os.Getenv("DNSIMPLE_BASE_URL")
+	baseURL := os.Getenv("DNSIMPLE_BASE_URL")
 
-	return NewDNSProviderCredentials(accessToken, baseUrl)
+	return NewDNSProviderCredentials(accessToken, baseURL)
 }
 
 // NewDNSProviderCredentials uses the supplied credentials to return a
 // DNSProvider instance configured for dnsimple.
-func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error) {
+func NewDNSProviderCredentials(accessToken, baseURL string) (*DNSProvider, error) {
 	if accessToken == "" {
 		return nil, fmt.Errorf("DNSimple OAuth token is missing")
 	}
@@ -38,8 +38,8 @@ func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error
 	client := dnsimple.NewClient(dnsimple.NewOauthTokenCredentials(accessToken))
 	client.UserAgent = "lego"
 
-	if baseUrl != "" {
-		client.BaseURL = baseUrl
+	if baseURL != "" {
+		client.BaseURL = baseURL
 	}
 
 	return &DNSProvider{client: client}, nil
@@ -119,8 +119,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, error) {
 	}
 
 	if hostedZone.ID == 0 {
-		return "", fmt.Errorf("Zone %s not found in DNSimple for domain %s", authZone, domain)
-
+		return "", fmt.Errorf("zone %s not found in DNSimple for domain %s", authZone, domain)
 	}
 
 	return hostedZone.Name, nil
@@ -173,7 +172,7 @@ func (c *DNSProvider) getAccountID() (string, error) {
 	}
 
 	if whoamiResponse.Data.Account == nil {
-		return "", fmt.Errorf("DNSimple user tokens are not supported, please use an account token.")
+		return "", fmt.Errorf("DNSimple user tokens are not supported, please use an account token")
 	}
 
 	return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil
diff --git a/providers/dns/dnsimple/dnsimple_test.go b/providers/dns/dnsimple/dnsimple_test.go
index bd35790d..0d44410f 100644
--- a/providers/dns/dnsimple/dnsimple_test.go
+++ b/providers/dns/dnsimple/dnsimple_test.go
@@ -12,19 +12,19 @@ var (
 	dnsimpleLiveTest   bool
 	dnsimpleOauthToken string
 	dnsimpleDomain     string
-	dnsimpleBaseUrl    string
+	dnsimpleBaseURL    string
 )
 
 func init() {
 	dnsimpleOauthToken = os.Getenv("DNSIMPLE_OAUTH_TOKEN")
 	dnsimpleDomain = os.Getenv("DNSIMPLE_DOMAIN")
-	dnsimpleBaseUrl = "https://api.sandbox.dnsimple.com"
+	dnsimpleBaseURL = "https://api.sandbox.dnsimple.com"
 
 	if len(dnsimpleOauthToken) > 0 && len(dnsimpleDomain) > 0 {
-		baseUrl := os.Getenv("DNSIMPLE_BASE_URL")
+		baseURL := os.Getenv("DNSIMPLE_BASE_URL")
 
-		if baseUrl != "" {
-			dnsimpleBaseUrl = baseUrl
+		if baseURL != "" {
+			dnsimpleBaseURL = baseURL
 		}
 
 		dnsimpleLiveTest = true
@@ -33,7 +33,7 @@ func init() {
 
 func restoreDNSimpleEnv() {
 	os.Setenv("DNSIMPLE_OAUTH_TOKEN", dnsimpleOauthToken)
-	os.Setenv("DNSIMPLE_BASE_URL", dnsimpleBaseUrl)
+	os.Setenv("DNSIMPLE_BASE_URL", dnsimpleBaseURL)
 }
 
 //
@@ -114,7 +114,7 @@ func TestLiveDNSimplePresent(t *testing.T) {
 		t.Skip("skipping live test")
 	}
 
-	provider, err := NewDNSProviderCredentials(dnsimpleOauthToken, dnsimpleBaseUrl)
+	provider, err := NewDNSProviderCredentials(dnsimpleOauthToken, dnsimpleBaseURL)
 	assert.NoError(t, err)
 
 	err = provider.Present(dnsimpleDomain, "", "123d==")
@@ -132,7 +132,7 @@ func TestLiveDNSimpleCleanUp(t *testing.T) {
 
 	time.Sleep(time.Second * 1)
 
-	provider, err := NewDNSProviderCredentials(dnsimpleOauthToken, dnsimpleBaseUrl)
+	provider, err := NewDNSProviderCredentials(dnsimpleOauthToken, dnsimpleBaseURL)
 	assert.NoError(t, err)
 
 	err = provider.CleanUp(dnsimpleDomain, "", "123d==")
diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/providers/dns/dnsmadeeasy/dnsmadeeasy.go
index c4363a4e..11b1ac44 100644
--- a/providers/dns/dnsmadeeasy/dnsmadeeasy.go
+++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.go
@@ -95,11 +95,7 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
 	record := &Record{Type: "TXT", Name: name, Value: value, TTL: ttl}
 
 	err = d.createRecord(domain, record)
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 // CleanUp removes the TXT records matching the specified parameters
@@ -226,7 +222,7 @@ func (d *DNSProvider) sendRequest(method, resource string, payload interface{})
 	}
 	client := &http.Client{
 		Transport: transport,
-		Timeout:   time.Duration(10 * time.Second),
+		Timeout:   10 * time.Second,
 	}
 	resp, err := client.Do(req)
 	if err != nil {
diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go b/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go
index e860ecb6..8d6e27ac 100644
--- a/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go
+++ b/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go
@@ -28,6 +28,7 @@ func TestPresentAndCleanup(t *testing.T) {
 	}
 
 	provider, err := NewDNSProvider()
+	assert.NoError(t, err)
 
 	err = provider.Present(testDomain, "", "123d==")
 	assert.NoError(t, err)
diff --git a/providers/dns/duckdns/duckdns.go b/providers/dns/duckdns/duckdns.go
index 6e2102a7..855838ae 100644
--- a/providers/dns/duckdns/duckdns.go
+++ b/providers/dns/duckdns/duckdns.go
@@ -1,82 +1,81 @@
-// Adds lego support for http://duckdns.org .
-//
-// See http://www.duckdns.org/spec.jsp for more info on updating TXT records.
-package duckdns
-
-import (
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"os"
-
-	"github.com/xenolf/lego/acme"
-)
-
-// DNSProvider adds and removes the record for the DNS challenge
-type DNSProvider struct {
-	// The duckdns api token
-	token string
-}
-
-// NewDNSProvider returns a new DNS provider using
-// environment variable DUCKDNS_TOKEN for adding and removing the DNS record.
-func NewDNSProvider() (*DNSProvider, error) {
-	duckdnsToken := os.Getenv("DUCKDNS_TOKEN")
-
-	return NewDNSProviderCredentials(duckdnsToken)
-}
-
-// NewDNSProviderCredentials uses the supplied credentials to return a
-// DNSProvider instance configured for http://duckdns.org .
-func NewDNSProviderCredentials(duckdnsToken string) (*DNSProvider, error) {
-	if duckdnsToken == "" {
-		return nil, errors.New("environment variable DUCKDNS_TOKEN not set")
-	}
-
-	return &DNSProvider{token: duckdnsToken}, nil
-}
-
-// makeDuckdnsURL creates a url to clear the set or unset the TXT record.
-// txt == "" will clear the TXT record.
-func makeDuckdnsURL(domain, token, txt string) string {
-	requestBase := fmt.Sprintf("https://www.duckdns.org/update?domains=%s&token=%s", domain, token)
-	if txt == "" {
-		return requestBase + "&clear=true"
-	}
-	return requestBase + "&txt=" + txt
-}
-
-func issueDuckdnsRequest(url string) error {
-	response, err := acme.HTTPClient.Get(url)
-	if err != nil {
-		return err
-	}
-	defer response.Body.Close()
-
-	bodyBytes, err := ioutil.ReadAll(response.Body)
-	if err != nil {
-		return err
-	}
-	body := string(bodyBytes)
-	if body != "OK" {
-		return fmt.Errorf("Request to change TXT record for duckdns returned the following result (%s) this does not match expectation (OK) used url [%s]", body, url)
-	}
-	return nil
-}
-
-// Present creates a TXT record to fulfil the dns-01 challenge.
-// In duckdns you only have one TXT record shared with
-// the domain and all sub domains.
-//
-// To update the TXT record we just need to make one simple get request.
-func (d *DNSProvider) Present(domain, token, keyAuth string) error {
-	_, txtRecord, _ := acme.DNS01Record(domain, keyAuth)
-	url := makeDuckdnsURL(domain, d.token, txtRecord)
-	return issueDuckdnsRequest(url)
-}
-
-// CleanUp clears duckdns TXT record
-func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
-	url := makeDuckdnsURL(domain, d.token, "")
-	return issueDuckdnsRequest(url)
-}
+// Package duckdns Adds lego support for http://duckdns.org .
+// See http://www.duckdns.org/spec.jsp for more info on updating TXT records.
+package duckdns
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/xenolf/lego/acme"
+)
+
+// DNSProvider adds and removes the record for the DNS challenge
+type DNSProvider struct {
+	// The duckdns api token
+	token string
+}
+
+// NewDNSProvider returns a new DNS provider using
+// environment variable DUCKDNS_TOKEN for adding and removing the DNS record.
+func NewDNSProvider() (*DNSProvider, error) {
+	duckdnsToken := os.Getenv("DUCKDNS_TOKEN")
+
+	return NewDNSProviderCredentials(duckdnsToken)
+}
+
+// NewDNSProviderCredentials uses the supplied credentials to return a
+// DNSProvider instance configured for http://duckdns.org .
+func NewDNSProviderCredentials(duckdnsToken string) (*DNSProvider, error) {
+	if duckdnsToken == "" {
+		return nil, errors.New("environment variable DUCKDNS_TOKEN not set")
+	}
+
+	return &DNSProvider{token: duckdnsToken}, nil
+}
+
+// makeDuckdnsURL creates a url to clear the set or unset the TXT record.
+// txt == "" will clear the TXT record.
+func makeDuckdnsURL(domain, token, txt string) string {
+	requestBase := fmt.Sprintf("https://www.duckdns.org/update?domains=%s&token=%s", domain, token)
+	if txt == "" {
+		return requestBase + "&clear=true"
+	}
+	return requestBase + "&txt=" + txt
+}
+
+func issueDuckdnsRequest(url string) error {
+	response, err := acme.HTTPClient.Get(url)
+	if err != nil {
+		return err
+	}
+	defer response.Body.Close()
+
+	bodyBytes, err := ioutil.ReadAll(response.Body)
+	if err != nil {
+		return err
+	}
+	body := string(bodyBytes)
+	if body != "OK" {
+		return fmt.Errorf("Request to change TXT record for duckdns returned the following result (%s) this does not match expectation (OK) used url [%s]", body, url)
+	}
+	return nil
+}
+
+// Present creates a TXT record to fulfil the dns-01 challenge.
+// In duckdns you only have one TXT record shared with
+// the domain and all sub domains.
+//
+// To update the TXT record we just need to make one simple get request.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+	_, txtRecord, _ := acme.DNS01Record(domain, keyAuth)
+	url := makeDuckdnsURL(domain, d.token, txtRecord)
+	return issueDuckdnsRequest(url)
+}
+
+// CleanUp clears duckdns TXT record
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+	url := makeDuckdnsURL(domain, d.token, "")
+	return issueDuckdnsRequest(url)
+}
diff --git a/providers/dns/duckdns/duckdns_test.go b/providers/dns/duckdns/duckdns_test.go
index f1afed4f..aefc0943 100644
--- a/providers/dns/duckdns/duckdns_test.go
+++ b/providers/dns/duckdns/duckdns_test.go
@@ -1,65 +1,65 @@
-package duckdns
-
-import (
-	"os"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-)
-
-var (
-	duckdnsLiveTest bool
-	duckdnsToken    string
-	duckdnsDomain   string
-)
-
-func init() {
-	duckdnsToken = os.Getenv("DUCKDNS_TOKEN")
-	duckdnsDomain = os.Getenv("DUCKDNS_DOMAIN")
-	if len(duckdnsDomain) > 0 && len(duckdnsDomain) > 0 {
-		duckdnsLiveTest = true
-	}
-}
-
-func restoreDuckdnsEnv() {
-	os.Setenv("DUCKDNS_TOKEN", duckdnsToken)
-}
-
-func TestNewDNSProviderValidEnv(t *testing.T) {
-	os.Setenv("DUCKDNS_TOKEN", "123")
-	_, err := NewDNSProvider()
-	assert.NoError(t, err)
-	restoreDuckdnsEnv()
-}
-func TestNewDNSProviderMissingCredErr(t *testing.T) {
-	os.Setenv("DUCKDNS_TOKEN", "")
-	_, err := NewDNSProvider()
-	assert.EqualError(t, err, "environment variable DUCKDNS_TOKEN not set")
-	restoreDuckdnsEnv()
-}
-func TestLiveDuckdnsPresent(t *testing.T) {
-	if !duckdnsLiveTest {
-		t.Skip("skipping live test")
-	}
-
-	provider, err := NewDNSProvider()
-	assert.NoError(t, err)
-
-	err = provider.Present(duckdnsDomain, "", "123d==")
-	assert.NoError(t, err)
-}
-
-func TestLiveDuckdnsCleanUp(t *testing.T) {
-	if !duckdnsLiveTest {
-		t.Skip("skipping live test")
-	}
-
-	time.Sleep(time.Second * 10)
-
-	provider, err := NewDNSProvider()
-	assert.NoError(t, err)
-
-	err = provider.CleanUp(duckdnsDomain, "", "123d==")
-	assert.NoError(t, err)
-}
+package duckdns
+
+import (
+	"os"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var (
+	duckdnsLiveTest bool
+	duckdnsToken    string
+	duckdnsDomain   string
+)
+
+func init() {
+	duckdnsToken = os.Getenv("DUCKDNS_TOKEN")
+	duckdnsDomain = os.Getenv("DUCKDNS_DOMAIN")
+	if len(duckdnsToken) > 0 && len(duckdnsDomain) > 0 {
+		duckdnsLiveTest = true
+	}
+}
+
+func restoreDuckdnsEnv() {
+	os.Setenv("DUCKDNS_TOKEN", duckdnsToken)
+}
+
+func TestNewDNSProviderValidEnv(t *testing.T) {
+	os.Setenv("DUCKDNS_TOKEN", "123")
+	_, err := NewDNSProvider()
+	assert.NoError(t, err)
+	restoreDuckdnsEnv()
+}
+func TestNewDNSProviderMissingCredErr(t *testing.T) {
+	os.Setenv("DUCKDNS_TOKEN", "")
+	_, err := NewDNSProvider()
+	assert.EqualError(t, err, "environment variable DUCKDNS_TOKEN not set")
+	restoreDuckdnsEnv()
+}
+func TestLiveDuckdnsPresent(t *testing.T) {
+	if !duckdnsLiveTest {
+		t.Skip("skipping live test")
+	}
+
+	provider, err := NewDNSProvider()
+	assert.NoError(t, err)
+
+	err = provider.Present(duckdnsDomain, "", "123d==")
+	assert.NoError(t, err)
+}
+
+func TestLiveDuckdnsCleanUp(t *testing.T) {
+	if !duckdnsLiveTest {
+		t.Skip("skipping live test")
+	}
+
+	time.Sleep(time.Second * 10)
+
+	provider, err := NewDNSProvider()
+	assert.NoError(t, err)
+
+	err = provider.CleanUp(duckdnsDomain, "", "123d==")
+	assert.NoError(t, err)
+}
diff --git a/providers/dns/dyn/dyn.go b/providers/dns/dyn/dyn.go
index 277dffb9..ad65fab6 100644
--- a/providers/dns/dyn/dyn.go
+++ b/providers/dns/dyn/dyn.go
@@ -80,7 +80,7 @@ func (d *DNSProvider) sendRequest(method, resource string, payload interface{})
 		req.Header.Set("Auth-Token", d.token)
 	}
 
-	client := &http.Client{Timeout: time.Duration(10 * time.Second)}
+	client := &http.Client{Timeout: 10 * time.Second}
 	resp, err := client.Do(req)
 	if err != nil {
 		return nil, err
@@ -158,7 +158,7 @@ func (d *DNSProvider) logout() error {
 	req.Header.Set("Content-Type", "application/json")
 	req.Header.Set("Auth-Token", d.token)
 
-	client := &http.Client{Timeout: time.Duration(10 * time.Second)}
+	client := &http.Client{Timeout: 10 * time.Second}
 	resp, err := client.Do(req)
 	if err != nil {
 		return err
@@ -206,12 +206,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 		return err
 	}
 
-	err = d.logout()
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return d.logout()
 }
 
 func (d *DNSProvider) publish(zone, notes string) error {
@@ -222,12 +217,9 @@ func (d *DNSProvider) publish(zone, notes string) error {
 
 	pub := &publish{Publish: true, Notes: notes}
 	resource := fmt.Sprintf("Zone/%s/", zone)
-	_, err := d.sendRequest("PUT", resource, pub)
-	if err != nil {
-		return err
-	}
 
-	return nil
+	_, err := d.sendRequest("PUT", resource, pub)
+	return err
 }
 
 // CleanUp removes the TXT record matching the specified parameters
@@ -253,7 +245,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	req.Header.Set("Content-Type", "application/json")
 	req.Header.Set("Auth-Token", d.token)
 
-	client := &http.Client{Timeout: time.Duration(10 * time.Second)}
+	client := &http.Client{Timeout: 10 * time.Second}
 	resp, err := client.Do(req)
 	if err != nil {
 		return err
@@ -269,10 +261,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 		return err
 	}
 
-	err = d.logout()
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return d.logout()
 }
diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go
index 4b125e8d..1467e8d7 100644
--- a/providers/dns/exoscale/exoscale.go
+++ b/providers/dns/exoscale/exoscale.go
@@ -16,7 +16,7 @@ type DNSProvider struct {
 	client *egoscale.Client
 }
 
-// Credentials must be passed in the environment variables:
+// NewDNSProvider Credentials must be passed in the environment variables:
 // EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT.
 func NewDNSProvider() (*DNSProvider, error) {
 	key := os.Getenv("EXOSCALE_API_KEY")
@@ -25,7 +25,7 @@ func NewDNSProvider() (*DNSProvider, error) {
 	return NewDNSProviderClient(key, secret, endpoint)
 }
 
-// Uses the supplied parameters to return a DNSProvider instance
+// NewDNSProviderClient Uses the supplied parameters to return a DNSProvider instance
 // configured for Exoscale.
 func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) {
 	if key == "" || secret == "" {
@@ -48,7 +48,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 		return err
 	}
 
-	recordID, err := c.FindExistingRecordId(zone, recordName)
+	recordID, err := c.FindExistingRecordID(zone, recordName)
 	if err != nil {
 		return err
 	}
@@ -84,7 +84,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 		return err
 	}
 
-	recordID, err := c.FindExistingRecordId(zone, recordName)
+	recordID, err := c.FindExistingRecordID(zone, recordName)
 	if err != nil {
 		return err
 	}
@@ -99,9 +99,9 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	return nil
 }
 
-// Query Exoscale to find an existing record for this name.
+// FindExistingRecordID Query Exoscale to find an existing record for this name.
 // Returns nil if no record could be found
-func (c *DNSProvider) FindExistingRecordId(zone, recordName string) (int64, error) {
+func (c *DNSProvider) FindExistingRecordID(zone, recordName string) (int64, error) {
 	records, err := c.client.GetRecords(zone)
 	if err != nil {
 		return -1, errors.New("Error while retrievening DNS records: " + err.Error())
@@ -114,7 +114,7 @@ func (c *DNSProvider) FindExistingRecordId(zone, recordName string) (int64, erro
 	return 0, nil
 }
 
-// Extract DNS zone and DNS entry name
+// FindZoneAndRecordName Extract DNS zone and DNS entry name
 func (c *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) {
 	zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
 	if err != nil {
diff --git a/providers/dns/gandi/gandi.go b/providers/dns/gandi/gandi.go
index 422b02a2..750bc8b4 100644
--- a/providers/dns/gandi/gandi.go
+++ b/providers/dns/gandi/gandi.go
@@ -74,15 +74,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 	if ttl < 300 {
 		ttl = 300 // 300 is gandi minimum value for ttl
 	}
+
 	// find authZone and Gandi zone_id for fqdn
 	authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers)
 	if err != nil {
 		return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err)
 	}
+
 	zoneID, err := d.getZoneID(authZone)
 	if err != nil {
 		return err
 	}
+
 	// determine name of TXT record
 	if !strings.HasSuffix(
 		strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
@@ -90,40 +93,49 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 			"Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn)
 	}
 	name := fqdn[:len(fqdn)-len("."+authZone)]
+
 	// acquire lock and check there is not a challenge already in
 	// progress for this value of authZone
 	d.inProgressMu.Lock()
 	defer d.inProgressMu.Unlock()
+
 	if _, ok := d.inProgressAuthZones[authZone]; ok {
 		return fmt.Errorf(
 			"Gandi DNS: challenge already in progress for authZone %s",
 			authZone)
 	}
+
 	// perform API actions to create and activate new gandi zone
 	// containing the required TXT record
 	newZoneName := fmt.Sprintf(
 		"%s [ACME Challenge %s]",
 		acme.UnFqdn(authZone), time.Now().Format(time.RFC822Z))
+
 	newZoneID, err := d.cloneZone(zoneID, newZoneName)
 	if err != nil {
 		return err
 	}
+
 	newZoneVersion, err := d.newZoneVersion(newZoneID)
 	if err != nil {
 		return err
 	}
+
 	err = d.addTXTRecord(newZoneID, newZoneVersion, name, value, ttl)
 	if err != nil {
 		return err
 	}
+
 	err = d.setZoneVersion(newZoneID, newZoneVersion)
 	if err != nil {
 		return err
 	}
+
 	err = d.setZone(authZone, newZoneID)
 	if err != nil {
 		return err
 	}
+
 	// save data necessary for CleanUp
 	d.inProgressFQDNs[fqdn] = inProgressInfo{
 		zoneID:    zoneID,
@@ -142,25 +154,25 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	// acquire lock and retrieve zoneID, newZoneID and authZone
 	d.inProgressMu.Lock()
 	defer d.inProgressMu.Unlock()
+
 	if _, ok := d.inProgressFQDNs[fqdn]; !ok {
 		// if there is no cleanup information then just return
 		return nil
 	}
+
 	zoneID := d.inProgressFQDNs[fqdn].zoneID
 	newZoneID := d.inProgressFQDNs[fqdn].newZoneID
 	authZone := d.inProgressFQDNs[fqdn].authZone
 	delete(d.inProgressFQDNs, fqdn)
 	delete(d.inProgressAuthZones, authZone)
+
 	// perform API actions to restore old gandi zone for authZone
 	err := d.setZone(authZone, zoneID)
 	if err != nil {
 		return err
 	}
-	err = d.deleteZone(newZoneID)
-	if err != nil {
-		return err
-	}
-	return nil
+
+	return d.deleteZone(newZoneID)
 }
 
 // Timeout returns the values (40*time.Minute, 60*time.Second) which
@@ -259,15 +271,18 @@ func (e rpcError) Error() string {
 
 func httpPost(url string, bodyType string, body io.Reader) ([]byte, error) {
 	client := http.Client{Timeout: 60 * time.Second}
+
 	resp, err := client.Post(url, bodyType, body)
 	if err != nil {
 		return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err)
 	}
 	defer resp.Body.Close()
+
 	b, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 		return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err)
 	}
+
 	return b, nil
 }
 
@@ -281,12 +296,14 @@ func rpcCall(call *methodCall, resp response) error {
 	if err != nil {
 		return fmt.Errorf("Gandi DNS: Marshal Error: %v", err)
 	}
+
 	// post
 	b = append([]byte(`<?xml version="1.0"?>`+"\n"), b...)
 	respBody, err := httpPost(endpoint, "text/xml", bytes.NewReader(b))
 	if err != nil {
 		return err
 	}
+
 	// unmarshal
 	err = xml.Unmarshal(respBody, resp)
 	if err != nil {
@@ -313,12 +330,14 @@ func (d *DNSProvider) getZoneID(domain string) (int, error) {
 	if err != nil {
 		return 0, err
 	}
+
 	var zoneID int
 	for _, member := range resp.StructMembers {
 		if member.Name == "zone_id" {
 			zoneID = member.ValueInt
 		}
 	}
+
 	if zoneID == 0 {
 		return 0, fmt.Errorf(
 			"Gandi DNS: Could not determine zone_id for %s", domain)
@@ -346,12 +365,14 @@ func (d *DNSProvider) cloneZone(zoneID int, name string) (int, error) {
 	if err != nil {
 		return 0, err
 	}
+
 	var newZoneID int
 	for _, member := range resp.StructMembers {
 		if member.Name == "id" {
 			newZoneID = member.ValueInt
 		}
 	}
+
 	if newZoneID == 0 {
 		return 0, fmt.Errorf("Gandi DNS: Could not determine cloned zone_id")
 	}
@@ -370,6 +391,7 @@ func (d *DNSProvider) newZoneVersion(zoneID int) (int, error) {
 	if err != nil {
 		return 0, err
 	}
+
 	if resp.Value == 0 {
 		return 0, fmt.Errorf("Gandi DNS: Could not create new zone version")
 	}
@@ -402,10 +424,7 @@ func (d *DNSProvider) addTXTRecord(zoneID int, version int, name string, value s
 			},
 		},
 	}, resp)
-	if err != nil {
-		return err
-	}
-	return nil
+	return err
 }
 
 func (d *DNSProvider) setZoneVersion(zoneID int, version int) error {
@@ -421,6 +440,7 @@ func (d *DNSProvider) setZoneVersion(zoneID int, version int) error {
 	if err != nil {
 		return err
 	}
+
 	if !resp.Value {
 		return fmt.Errorf("Gandi DNS: could not set zone version")
 	}
@@ -440,12 +460,14 @@ func (d *DNSProvider) setZone(domain string, zoneID int) error {
 	if err != nil {
 		return err
 	}
+
 	var respZoneID int
 	for _, member := range resp.StructMembers {
 		if member.Name == "zone_id" {
 			respZoneID = member.ValueInt
 		}
 	}
+
 	if respZoneID != zoneID {
 		return fmt.Errorf(
 			"Gandi DNS: Could not set new zone_id for %s", domain)
@@ -465,6 +487,7 @@ func (d *DNSProvider) deleteZone(zoneID int) error {
 	if err != nil {
 		return err
 	}
+
 	if !resp.Value {
 		return fmt.Errorf("Gandi DNS: could not delete zone_id")
 	}
diff --git a/providers/dns/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go
index 451333ca..520c7e37 100644
--- a/providers/dns/gandi/gandi_test.go
+++ b/providers/dns/gandi/gandi_test.go
@@ -1,41 +1,15 @@
 package gandi
 
 import (
-	"crypto"
-	"crypto/rand"
-	"crypto/rsa"
 	"io"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
-	"os"
 	"regexp"
 	"strings"
 	"testing"
-
-	"github.com/xenolf/lego/acme"
 )
 
-// stagingServer is the Let's Encrypt staging server used by the live test
-const stagingServer = "https://acme-staging.api.letsencrypt.org/directory"
-
-// user implements acme.User and is used by the live test
-type user struct {
-	Email        string
-	Registration *acme.RegistrationResource
-	key          crypto.PrivateKey
-}
-
-func (u *user) GetEmail() string {
-	return u.Email
-}
-func (u *user) GetRegistration() *acme.RegistrationResource {
-	return u.Registration
-}
-func (u *user) GetPrivateKey() crypto.PrivateKey {
-	return u.key
-}
-
 // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC
 // Server, whose responses are predetermined for particular requests.
 func TestDNSProvider(t *testing.T) {
@@ -92,61 +66,6 @@ func TestDNSProvider(t *testing.T) {
 	}
 }
 
-// TestDNSProviderLive performs a live test to obtain a certificate
-// using the Let's Encrypt staging server. It runs provided that both
-// the environment variables GANDI_API_KEY and GANDI_TEST_DOMAIN are
-// set. Otherwise the test is skipped.
-//
-// To complete this test, go test must be run with the -timeout=40m
-// flag, since the default timeout of 10m is insufficient.
-func TestDNSProviderLive(t *testing.T) {
-	apiKey := os.Getenv("GANDI_API_KEY")
-	domain := os.Getenv("GANDI_TEST_DOMAIN")
-	if apiKey == "" || domain == "" {
-		t.Skip("skipping live test")
-	}
-	// create a user.
-	const rsaKeySize = 2048
-	privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
-	if err != nil {
-		t.Fatal(err)
-	}
-	myUser := user{
-		Email: "test@example.com",
-		key:   privateKey,
-	}
-	// create a client using staging server
-	client, err := acme.NewClient(stagingServer, &myUser, acme.RSA2048)
-	if err != nil {
-		t.Fatal(err)
-	}
-	provider, err := NewDNSProviderCredentials(apiKey)
-	if err != nil {
-		t.Fatal(err)
-	}
-	err = client.SetChallengeProvider(acme.DNS01, provider)
-	if err != nil {
-		t.Fatal(err)
-	}
-	client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
-	// register and agree tos
-	reg, err := client.Register()
-	if err != nil {
-		t.Fatal(err)
-	}
-	myUser.Registration = reg
-	err = client.AgreeToTOS()
-	if err != nil {
-		t.Fatal(err)
-	}
-	// complete the challenge
-	bundle := false
-	_, failures := client.ObtainCertificate([]string{domain}, bundle, nil, false)
-	if len(failures) > 0 {
-		t.Fatal(failures)
-	}
-}
-
 // serverResponses is the XML-RPC Request->Response map used by the
 // fake RPC server. It was generated by recording a real RPC session
 // which resulted in the successful issue of a cert, and then
diff --git a/providers/dns/gandiv5/gandiv5.go b/providers/dns/gandiv5/gandiv5.go
index 86cc7bf3..4614723a 100644
--- a/providers/dns/gandiv5/gandiv5.go
+++ b/providers/dns/gandiv5/gandiv5.go
@@ -21,6 +21,7 @@ var (
 	// endpoint is the Gandi API endpoint used by Present and
 	// CleanUp. It is overridden during tests.
 	endpoint = "https://dns.api.gandi.net/api/v5"
+
 	// findZoneByFqdn determines the DNS zone of an fqdn. It is overridden
 	// during tests.
 	findZoneByFqdn = acme.FindZoneByFqdn
@@ -66,11 +67,13 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 	if ttl < 300 {
 		ttl = 300 // 300 is gandi minimum value for ttl
 	}
+
 	// find authZone
 	authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers)
 	if err != nil {
 		return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err)
 	}
+
 	// determine name of TXT record
 	if !strings.HasSuffix(
 		strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
@@ -78,15 +81,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 			"Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn)
 	}
 	name := fqdn[:len(fqdn)-len("."+authZone)]
+
 	// acquire lock and check there is not a challenge already in
 	// progress for this value of authZone
 	d.inProgressMu.Lock()
 	defer d.inProgressMu.Unlock()
+
 	// add TXT record into authZone
 	err = d.addTXTRecord(acme.UnFqdn(authZone), name, value, ttl)
 	if err != nil {
 		return err
 	}
+
 	// save data necessary for CleanUp
 	d.inProgressFQDNs[fqdn] = inProgressInfo{
 		authZone:  authZone,
@@ -98,6 +104,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 // CleanUp removes the TXT record matching the specified parameters.
 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+
 	// acquire lock and retrieve authZone
 	d.inProgressMu.Lock()
 	defer d.inProgressMu.Unlock()
@@ -105,15 +112,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 		// if there is no cleanup information then just return
 		return nil
 	}
+
 	fieldName := d.inProgressFQDNs[fqdn].fieldName
 	authZone := d.inProgressFQDNs[fqdn].authZone
 	delete(d.inProgressFQDNs, fqdn)
+
 	// delete TXT record from authZone
-	err := d.deleteTXTRecord(acme.UnFqdn(authZone), fieldName)
-	if err != nil {
-		return err
-	}
-	return nil
+	return d.deleteTXTRecord(acme.UnFqdn(authZone), fieldName)
 }
 
 // Timeout returns the values (20*time.Minute, 20*time.Second) which
@@ -149,16 +154,18 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf
 	if err != nil {
 		return nil, err
 	}
+
 	req, err := http.NewRequest(method, url, bytes.NewReader(body))
 	if err != nil {
 		return nil, err
 	}
+
 	req.Header.Set("Content-Type", "application/json")
 	if len(d.apiKey) > 0 {
 		req.Header.Set("X-Api-Key", d.apiKey)
 	}
 
-	client := &http.Client{Timeout: time.Duration(10 * time.Second)}
+	client := &http.Client{Timeout: 10 * time.Second}
 	resp, err := client.Do(req)
 	if err != nil {
 		return nil, err
diff --git a/providers/dns/gandiv5/gandiv5_test.go b/providers/dns/gandiv5/gandiv5_test.go
index 56e63915..f6fa779f 100644
--- a/providers/dns/gandiv5/gandiv5_test.go
+++ b/providers/dns/gandiv5/gandiv5_test.go
@@ -1,41 +1,15 @@
 package gandiv5
 
 import (
-	"crypto"
-	"crypto/rand"
-	"crypto/rsa"
 	"io"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
-	"os"
 	"regexp"
 	"strings"
 	"testing"
-
-	"github.com/xenolf/lego/acme"
 )
 
-// stagingServer is the Let's Encrypt staging server used by the live test
-const stagingServer = "https://acme-staging.api.letsencrypt.org/directory"
-
-// user implements acme.User and is used by the live test
-type user struct {
-	Email        string
-	Registration *acme.RegistrationResource
-	key          crypto.PrivateKey
-}
-
-func (u *user) GetEmail() string {
-	return u.Email
-}
-func (u *user) GetRegistration() *acme.RegistrationResource {
-	return u.Registration
-}
-func (u *user) GetPrivateKey() crypto.PrivateKey {
-	return u.key
-}
-
 // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC
 // Server, whose responses are predetermined for particular requests.
 func TestDNSProvider(t *testing.T) {
@@ -92,61 +66,6 @@ func TestDNSProvider(t *testing.T) {
 	}
 }
 
-// TestDNSProviderLive performs a live test to obtain a certificate
-// using the Let's Encrypt staging server. It runs provided that both
-// the environment variables GANDIV5_API_KEY and GANDI_TEST_DOMAIN are
-// set. Otherwise the test is skipped.
-//
-// To complete this test, go test must be run with the -timeout=40m
-// flag, since the default timeout of 10m is insufficient.
-func TestDNSProviderLive(t *testing.T) {
-	apiKey := os.Getenv("GANDIV5_API_KEY")
-	domain := os.Getenv("GANDI_TEST_DOMAIN")
-	if apiKey == "" || domain == "" {
-		t.Skip("skipping live test")
-	}
-	// create a user.
-	const rsaKeySize = 2048
-	privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
-	if err != nil {
-		t.Fatal(err)
-	}
-	myUser := user{
-		Email: "test@example.com",
-		key:   privateKey,
-	}
-	// create a client using staging server
-	client, err := acme.NewClient(stagingServer, &myUser, acme.RSA2048)
-	if err != nil {
-		t.Fatal(err)
-	}
-	provider, err := NewDNSProviderCredentials(apiKey)
-	if err != nil {
-		t.Fatal(err)
-	}
-	err = client.SetChallengeProvider(acme.DNS01, provider)
-	if err != nil {
-		t.Fatal(err)
-	}
-	client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
-	// register and agree tos
-	reg, err := client.Register()
-	if err != nil {
-		t.Fatal(err)
-	}
-	myUser.Registration = reg
-	err = client.AgreeToTOS()
-	if err != nil {
-		t.Fatal(err)
-	}
-	// complete the challenge
-	bundle := false
-	_, failures := client.ObtainCertificate([]string{domain}, bundle, nil, false)
-	if len(failures) > 0 {
-		t.Fatal(failures)
-	}
-}
-
 // serverResponses is the JSON Request->Response map used by the
 // fake JSON server.
 var serverResponses = map[string]string{
diff --git a/providers/dns/glesys/glesys.go b/providers/dns/glesys/glesys.go
index 36c6c00d..7623b564 100644
--- a/providers/dns/glesys/glesys.go
+++ b/providers/dns/glesys/glesys.go
@@ -6,7 +6,6 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"log"
 	"net/http"
 	"os"
 	"strings"
@@ -14,29 +13,14 @@ import (
 	"time"
 
 	"github.com/xenolf/lego/acme"
+	"github.com/xenolf/lego/log"
 )
 
 // GleSYS API reference: https://github.com/GleSYS/API/wiki/API-Documentation
 
-// domainAPI is the GleSYS API endpoint used by Present and CleanUp. 
+// domainAPI is the GleSYS API endpoint used by Present and CleanUp.
 const domainAPI = "https://api.glesys.com/domain"
 
-var (
-	// Logger is used to log API communication results;
-	// if nil, the default log.Logger is used.
-	Logger *log.Logger
-)
-
-// logf writes a log entry. It uses Logger if not
-// nil, otherwise it uses the default log.Logger.
-func logf(format string, args ...interface{}) {
-	if Logger != nil {
-		Logger.Printf(format, args...)
-	} else {
-		log.Printf(format, args...)
-	}
-}
-
 // DNSProvider is an implementation of the
 // acme.ChallengeProviderTimeout interface that uses GleSYS
 // API to manage TXT records for a domain.
@@ -80,6 +64,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 	if err != nil {
 		return fmt.Errorf("GleSYS DNS: findZoneByFqdn failure: %v", err)
 	}
+
 	// determine name of TXT record
 	if !strings.HasSuffix(
 		strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
@@ -87,23 +72,27 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 			"GleSYS DNS: unexpected authZone %s for fqdn %s", authZone, fqdn)
 	}
 	name := fqdn[:len(fqdn)-len("."+authZone)]
+
 	// acquire lock and check there is not a challenge already in
 	// progress for this value of authZone
 	d.inProgressMu.Lock()
 	defer d.inProgressMu.Unlock()
+
 	// add TXT record into authZone
-	recordId, err := d.addTXTRecord(domain, acme.UnFqdn(authZone), name, value, ttl)
+	recordID, err := d.addTXTRecord(domain, acme.UnFqdn(authZone), name, value, ttl)
 	if err != nil {
 		return err
 	}
+
 	// save data necessary for CleanUp
-	d.activeRecords[fqdn] = recordId
+	d.activeRecords[fqdn] = recordID
 	return nil
 }
 
 // CleanUp removes the TXT record matching the specified parameters.
 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+
 	// acquire lock and retrieve authZone
 	d.inProgressMu.Lock()
 	defer d.inProgressMu.Unlock()
@@ -111,14 +100,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 		// if there is no cleanup information then just return
 		return nil
 	}
-	recordId := d.activeRecords[fqdn]
+
+	recordID := d.activeRecords[fqdn]
 	delete(d.activeRecords, fqdn)
+
 	// delete TXT record from authZone
-	err := d.deleteTXTRecord(domain, recordId)
-	if err != nil {
-		return err
-	}
-	return nil
+	return d.deleteTXTRecord(domain, recordID)
 }
 
 // Timeout returns the values (20*time.Minute, 20*time.Second) which
@@ -135,7 +122,7 @@ type addRecordRequest struct {
 	Host       string `json:"host"`
 	Type       string `json:"type"`
 	Data       string `json:"data"`
-	Ttl        int    `json:"ttl,omitempty"`
+	TTL        int    `json:"ttl,omitempty"`
 }
 
 type deleteRecordRequest struct {
@@ -160,14 +147,16 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf
 	if err != nil {
 		return nil, err
 	}
+
 	req, err := http.NewRequest(method, url, bytes.NewReader(body))
 	if err != nil {
 		return nil, err
 	}
+
 	req.Header.Set("Content-Type", "application/json")
 	req.SetBasicAuth(d.apiUser, d.apiKey)
 
-	client := &http.Client{Timeout: time.Duration(10 * time.Second)}
+	client := &http.Client{Timeout: 10 * time.Second}
 	resp, err := client.Do(req)
 	if err != nil {
 		return nil, err
@@ -177,6 +166,7 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf
 	if resp.StatusCode >= 400 {
 		return nil, fmt.Errorf("GleSYS DNS: request failed with HTTP status code %d", resp.StatusCode)
 	}
+
 	var response responseStruct
 	err = json.NewDecoder(resp.Body).Decode(&response)
 
@@ -187,14 +177,14 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf
 
 func (d *DNSProvider) addTXTRecord(fqdn string, domain string, name string, value string, ttl int) (int, error) {
 	response, err := d.sendRequest("POST", "addrecord", addRecordRequest{
-		Domainname:    domain,
-		Host:          name,
-		Type:          "TXT",
-		Data:          value,
-		Ttl:           ttl,
+		Domainname: domain,
+		Host:       name,
+		Type:       "TXT",
+		Data:       value,
+		TTL:        ttl,
 	})
 	if response != nil && response.Response.Status.Code == 200 {
-		logf("[INFO][%s] GleSYS DNS: Successfully created recordid %d", fqdn, response.Response.Record.Recordid)
+		log.Printf("[INFO][%s] GleSYS DNS: Successfully created recordid %d", fqdn, response.Response.Record.Recordid)
 		return response.Response.Record.Recordid, nil
 	}
 	return 0, err
@@ -205,7 +195,7 @@ func (d *DNSProvider) deleteTXTRecord(fqdn string, recordid int) error {
 		Recordid: recordid,
 	})
 	if response != nil && response.Response.Status.Code == 200 {
-		logf("[INFO][%s] GleSYS DNS: Successfully deleted recordid %d", fqdn, recordid)
+		log.Printf("[INFO][%s] GleSYS DNS: Successfully deleted recordid %d", fqdn, recordid)
 	}
 	return err
 }
diff --git a/providers/dns/godaddy/godaddy.go b/providers/dns/godaddy/godaddy.go
index 4112f662..54fbad03 100644
--- a/providers/dns/godaddy/godaddy.go
+++ b/providers/dns/godaddy/godaddy.go
@@ -10,9 +10,10 @@ import (
 
 	"bytes"
 	"encoding/json"
-	"github.com/xenolf/lego/acme"
 	"io/ioutil"
 	"strings"
+
+	"github.com/xenolf/lego/acme"
 )
 
 // GoDaddyAPIURL represents the API endpoint to call.
@@ -75,7 +76,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 			Type: "TXT",
 			Name: recordName,
 			Data: value,
-			Ttl:  ttl,
+			TTL:  ttl,
 		},
 	}
 
@@ -98,7 +99,7 @@ func (c *DNSProvider) updateRecords(records []DNSRecord, domainZone string, reco
 
 	if resp.StatusCode != http.StatusOK {
 		bodyBytes, _ := ioutil.ReadAll(resp.Body)
-		return fmt.Errorf("Could not create record %v; Status: %v; Body: %s\n", string(body), resp.StatusCode, string(bodyBytes))
+		return fmt.Errorf("could not create record %v; Status: %v; Body: %s", string(body), resp.StatusCode, string(bodyBytes))
 	}
 	return nil
 }
@@ -146,10 +147,11 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Res
 	return client.Do(req)
 }
 
+// DNSRecord a DNS record
 type DNSRecord struct {
 	Type     string `json:"type"`
 	Name     string `json:"name"`
 	Data     string `json:"data"`
 	Priority int    `json:"priority,omitempty"`
-	Ttl      int    `json:"ttl,omitempty"`
+	TTL      int    `json:"ttl,omitempty"`
 }
diff --git a/providers/dns/googlecloud/googlecloud.go b/providers/dns/googlecloud/googlecloud.go
index ba753f6d..dc46152f 100644
--- a/providers/dns/googlecloud/googlecloud.go
+++ b/providers/dns/googlecloud/googlecloud.go
@@ -11,7 +11,6 @@ import (
 	"github.com/xenolf/lego/acme"
 
 	"golang.org/x/net/context"
-	"golang.org/x/oauth2"
 	"golang.org/x/oauth2/google"
 
 	"google.golang.org/api/dns/v1"
@@ -74,7 +73,7 @@ func NewDNSProviderServiceAccount(project string, saFile string) (*DNSProvider,
 	if err != nil {
 		return nil, fmt.Errorf("Unable to acquire config: %v", err)
 	}
-	client := conf.Client(oauth2.NoContext)
+	client := conf.Client(context.Background())
 
 	svc, err := dns.New(client)
 	if err != nil {
diff --git a/providers/dns/googlecloud/googlecloud_test.go b/providers/dns/googlecloud/googlecloud_test.go
index 75a10d9a..70b0e730 100644
--- a/providers/dns/googlecloud/googlecloud_test.go
+++ b/providers/dns/googlecloud/googlecloud_test.go
@@ -80,6 +80,7 @@ func TestLiveGoogleCloudPresentMultiple(t *testing.T) {
 
 	// Check that we're able to create multiple entries
 	err = provider.Present(gcloudDomain, "1", "123d==")
+	assert.NoError(t, err)
 	err = provider.Present(gcloudDomain, "2", "123d==")
 	assert.NoError(t, err)
 }
diff --git a/providers/dns/linode/linode.go b/providers/dns/linode/linode.go
index a91d2b48..1e8cbc50 100644
--- a/providers/dns/linode/linode.go
+++ b/providers/dns/linode/linode.go
@@ -19,7 +19,7 @@ const (
 )
 
 type hostedZoneInfo struct {
-	domainId     int
+	domainID     int
 	resourceName string
 }
 
@@ -72,7 +72,7 @@ func (p *DNSProvider) Present(domain, token, keyAuth string) error {
 		return err
 	}
 
-	if _, err = p.linode.CreateDomainResourceTXT(zone.domainId, acme.UnFqdn(fqdn), value, 60); err != nil {
+	if _, err = p.linode.CreateDomainResourceTXT(zone.domainID, acme.UnFqdn(fqdn), value, 60); err != nil {
 		return err
 	}
 
@@ -88,7 +88,7 @@ func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	}
 
 	// Get all TXT records for the specified domain.
-	resources, err := p.linode.GetResourcesByType(zone.domainId, "TXT")
+	resources, err := p.linode.GetResourcesByType(zone.domainID, "TXT")
 	if err != nil {
 		return err
 	}
@@ -101,7 +101,7 @@ func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 				return err
 			}
 			if resp.ResourceID != resource.ResourceID {
-				return errors.New("Error deleting resource: resource IDs do not match!")
+				return errors.New("error deleting resource: resource IDs do not match")
 			}
 			break
 		}
@@ -125,7 +125,7 @@ func (p *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
 	}
 
 	return &hostedZoneInfo{
-		domainId:     domain.DomainID,
+		domainID:     domain.DomainID,
 		resourceName: resourceName,
 	}, nil
 }
diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go
index d7eb4093..2991d817 100644
--- a/providers/dns/namecheap/namecheap.go
+++ b/providers/dns/namecheap/namecheap.go
@@ -144,7 +144,7 @@ func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, e
 		}
 	}
 	if longest < 1 {
-		return nil, fmt.Errorf("Invalid domain name '%s'", domain)
+		return nil, fmt.Errorf("invalid domain name %q", domain)
 	}
 
 	tld := strings.Join(parts[longest:], ".")
@@ -318,7 +318,7 @@ func (d *DNSProvider) setHosts(ch *challenge, hosts []host) error {
 			shr.Errors[0].Description, shr.Errors[0].Number)
 	}
 	if shr.Result.IsSuccess != "true" {
-		return fmt.Errorf("Namecheap setHosts failed.")
+		return fmt.Errorf("Namecheap setHosts failed")
 	}
 
 	return nil
diff --git a/providers/dns/otc/mock.go b/providers/dns/otc/mock.go
index 0f2acb4b..babc2b3a 100644
--- a/providers/dns/otc/mock.go
+++ b/providers/dns/otc/mock.go
@@ -2,11 +2,12 @@ package otc
 
 import (
 	"fmt"
-	"github.com/stretchr/testify/assert"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 var fakeOTCUserName = "test"
@@ -15,12 +16,14 @@ var fakeOTCDomainName = "test"
 var fakeOTCProjectName = "test"
 var fakeOTCToken = "62244bc21da68d03ebac94e6636ff01f"
 
+// DNSMock mock
 type DNSMock struct {
 	t      *testing.T
 	Server *httptest.Server
 	Mux    *http.ServeMux
 }
 
+// NewDNSMock create a new DNSMock
 func NewDNSMock(t *testing.T) *DNSMock {
 	return &DNSMock{
 		t: t,
@@ -38,6 +41,7 @@ func (m *DNSMock) ShutdownServer() {
 	m.Server.Close()
 }
 
+// HandleAuthSuccessfully Handle auth successfully
 func (m *DNSMock) HandleAuthSuccessfully() {
 	m.Mux.HandleFunc("/v3/auth/token", func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("X-Subject-Token", fakeOTCToken)
@@ -64,6 +68,7 @@ func (m *DNSMock) HandleAuthSuccessfully() {
 	})
 }
 
+// HandleListZonesSuccessfully Handle list zones successfully
 func (m *DNSMock) HandleListZonesSuccessfully() {
 	m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, `{
@@ -79,6 +84,7 @@ func (m *DNSMock) HandleListZonesSuccessfully() {
 	})
 }
 
+// HandleListZonesEmpty Handle list zones empty
 func (m *DNSMock) HandleListZonesEmpty() {
 	m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, `{
@@ -93,6 +99,7 @@ func (m *DNSMock) HandleListZonesEmpty() {
 	})
 }
 
+// HandleDeleteRecordsetsSuccessfully Handle delete recordsets successfully
 func (m *DNSMock) HandleDeleteRecordsetsSuccessfully() {
 	m.Mux.HandleFunc("/v2/zones/123123/recordsets/321321", func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, `{
@@ -107,6 +114,7 @@ func (m *DNSMock) HandleDeleteRecordsetsSuccessfully() {
 	})
 }
 
+// HandleListRecordsetsEmpty Handle list recordsets empty
 func (m *DNSMock) HandleListRecordsetsEmpty() {
 	m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, `{
@@ -118,6 +126,8 @@ func (m *DNSMock) HandleListRecordsetsEmpty() {
 		assert.Equal(m.t, r.URL.RawQuery, "type=TXT&name=_acme-challenge.example.com.")
 	})
 }
+
+// HandleListRecordsetsSuccessfully Handle list recordsets successfully
 func (m *DNSMock) HandleListRecordsetsSuccessfully() {
 	m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) {
 		if r.Method == "GET" {
diff --git a/providers/dns/otc/otc.go b/providers/dns/otc/otc.go
index 86bcaa9b..86918730 100644
--- a/providers/dns/otc/otc.go
+++ b/providers/dns/otc/otc.go
@@ -59,6 +59,7 @@ func NewDNSProviderCredentials(domainName, userName, password, projectName, iden
 	}, nil
 }
 
+// SendRequest send request
 func (d *DNSProvider) SendRequest(method, resource string, payload interface{}) (io.Reader, error) {
 	url := fmt.Sprintf("%s/%s", d.otcBaseURL, resource)
 
@@ -81,7 +82,7 @@ func (d *DNSProvider) SendRequest(method, resource string, payload interface{})
 	tr.DisableKeepAlives = true
 
 	client := &http.Client{
-		Timeout:   time.Duration(10 * time.Second),
+		Timeout:   10 * time.Second,
 		Transport: tr,
 	}
 	resp, err := client.Do(req)
@@ -168,7 +169,7 @@ func (d *DNSProvider) loginRequest() error {
 	}
 	req.Header.Set("Content-Type", "application/json")
 
-	client := &http.Client{Timeout: time.Duration(10 * time.Second)}
+	client := &http.Client{Timeout: 10 * time.Second}
 	resp, err := client.Do(req)
 	if err != nil {
 		return err
@@ -221,12 +222,7 @@ func (d *DNSProvider) loginRequest() error {
 // Starts a new OTC API Session. Authenticates using userName, password
 // and receives a token to be used in for subsequent requests.
 func (d *DNSProvider) login() error {
-	err := d.loginRequest()
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return d.loginRequest()
 }
 
 func (d *DNSProvider) getZoneID(zone string) (string, error) {
@@ -305,10 +301,7 @@ func (d *DNSProvider) deleteRecordSet(zoneID, recordID string) error {
 	resource := fmt.Sprintf("zones/%s/recordsets/%s", zoneID, recordID)
 
 	_, err := d.SendRequest("DELETE", resource, nil)
-	if err != nil {
-		return err
-	}
-	return nil
+	return err
 }
 
 // Present creates a TXT record using the specified parameters
@@ -340,7 +333,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 		Name        string   `json:"name"`
 		Description string   `json:"description"`
 		Type        string   `json:"type"`
-		Ttl         int      `json:"ttl"`
+		TTL         int      `json:"ttl"`
 		Records     []string `json:"records"`
 	}
 
@@ -348,16 +341,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 		Name:        fqdn,
 		Description: "Added TXT record for ACME dns-01 challenge using lego client",
 		Type:        "TXT",
-		Ttl:         300,
+		TTL:         ttl,
 		Records:     []string{fmt.Sprintf("\"%s\"", value)},
 	}
 	_, err = d.SendRequest("POST", resource, r1)
-
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 // CleanUp removes the TXT record matching the specified parameters
@@ -375,7 +363,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	}
 
 	zoneID, err := d.getZoneID(authZone)
-
 	if err != nil {
 		return err
 	}
@@ -384,5 +371,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	if err != nil {
 		return fmt.Errorf("unable go get record %s for zone %s: %s", fqdn, domain, err)
 	}
+
 	return d.deleteRecordSet(zoneID, recordID)
 }
diff --git a/providers/dns/ovh/ovh.go b/providers/dns/ovh/ovh.go
index 290a8d7d..a49fc774 100644
--- a/providers/dns/ovh/ovh.go
+++ b/providers/dns/ovh/ovh.go
@@ -1,4 +1,4 @@
-// Package OVH implements a DNS provider for solving the DNS-01
+// Package ovh implements a DNS provider for solving the DNS-01
 // challenge using OVH DNS.
 package ovh
 
diff --git a/providers/dns/pdns/pdns.go b/providers/dns/pdns/pdns.go
index a7856e6d..10124ae2 100644
--- a/providers/dns/pdns/pdns.go
+++ b/providers/dns/pdns/pdns.go
@@ -29,12 +29,12 @@ type DNSProvider struct {
 // PDNS_API_URL and PDNS_API_KEY.
 func NewDNSProvider() (*DNSProvider, error) {
 	key := os.Getenv("PDNS_API_KEY")
-	hostUrl, err := url.Parse(os.Getenv("PDNS_API_URL"))
+	hostURL, err := url.Parse(os.Getenv("PDNS_API_URL"))
 	if err != nil {
 		return nil, err
 	}
 
-	return NewDNSProviderCredentials(hostUrl, key)
+	return NewDNSProviderCredentials(hostURL, key)
 }
 
 // NewDNSProviderCredentials uses the supplied credentials to return a
@@ -107,12 +107,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 	}
 
 	_, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body))
-	if err != nil {
-		fmt.Println("here")
-		return err
-	}
-
-	return nil
+	return err
 }
 
 // CleanUp removes the TXT record matching the specified parameters
@@ -131,7 +126,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 
 	rrsets := rrSets{
 		RRSets: []rrSet{
-			rrSet{
+			{
 				Name:       set.Name,
 				Type:       set.Type,
 				ChangeType: "DELETE",
@@ -220,7 +215,7 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*rrSet, error) {
 		}
 	}
 
-	return nil, fmt.Errorf("No existing record found for %s", fqdn)
+	return nil, fmt.Errorf("no existing record found for %s", fqdn)
 }
 
 func (c *DNSProvider) getAPIVersion() {
diff --git a/providers/dns/rackspace/rackspace.go b/providers/dns/rackspace/rackspace.go
index 13daa8c8..d30e582b 100644
--- a/providers/dns/rackspace/rackspace.go
+++ b/providers/dns/rackspace/rackspace.go
@@ -92,7 +92,7 @@ func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) {
 	client := http.Client{Timeout: 30 * time.Second}
 	resp, err := client.Do(req)
 	if err != nil {
-		return nil, fmt.Errorf("Error querying Rackspace Identity API: %v", err)
+		return nil, fmt.Errorf("error querying Rackspace Identity API: %v", err)
 	}
 	defer resp.Body.Close()
 
@@ -115,7 +115,7 @@ func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) {
 		}
 	}
 	if dnsEndpoint == "" {
-		return nil, fmt.Errorf("Failed to populate DNS endpoint, check Rackspace API for changes.")
+		return nil, fmt.Errorf("failed to populate DNS endpoint, check Rackspace API for changes")
 	}
 
 	return &DNSProvider{
@@ -132,8 +132,8 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 		return err
 	}
 
-	rec := RackspaceRecords{
-		RackspaceRecord: []RackspaceRecord{{
+	rec := Records{
+		Record: []Record{{
 			Name: acme.UnFqdn(fqdn),
 			Type: "TXT",
 			Data: value,
@@ -147,11 +147,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
 	}
 
 	_, err = c.makeRequest("POST", fmt.Sprintf("/domains/%d/records", zoneID), bytes.NewReader(body))
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 // CleanUp removes the TXT record matching the specified parameters
@@ -168,11 +164,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	}
 
 	_, err = c.makeRequest("DELETE", fmt.Sprintf("/domains/%d/records?id=%s", zoneID, record.ID), nil)
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 // getHostedZoneID performs a lookup to get the DNS zone which needs
@@ -205,36 +197,35 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (int, error) {
 
 	// If nothing was returned, or for whatever reason more than 1 was returned (the search uses exact match, so should not occur)
 	if zoneSearchResponse.TotalEntries != 1 {
-		return 0, fmt.Errorf("Found %d zones for %s in Rackspace for domain %s", zoneSearchResponse.TotalEntries, authZone, fqdn)
+		return 0, fmt.Errorf("found %d zones for %s in Rackspace for domain %s", zoneSearchResponse.TotalEntries, authZone, fqdn)
 	}
 
 	return zoneSearchResponse.HostedZones[0].ID, nil
 }
 
 // findTxtRecord searches a DNS zone for a TXT record with a specific name
-func (c *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*RackspaceRecord, error) {
+func (c *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*Record, error) {
 	result, err := c.makeRequest("GET", fmt.Sprintf("/domains/%d/records?type=TXT&name=%s", zoneID, acme.UnFqdn(fqdn)), nil)
 	if err != nil {
 		return nil, err
 	}
 
-	var records RackspaceRecords
+	var records Records
 	err = json.Unmarshal(result, &records)
 	if err != nil {
 		return nil, err
 	}
 
-	recordsLength := len(records.RackspaceRecord)
+	recordsLength := len(records.Record)
 	switch recordsLength {
 	case 1:
-		break
 	case 0:
-		return nil, fmt.Errorf("No TXT record found for %s", fqdn)
+		return nil, fmt.Errorf("no TXT record found for %s", fqdn)
 	default:
-		return nil, fmt.Errorf("More than 1 TXT record found for %s", fqdn)
+		return nil, fmt.Errorf("more than 1 TXT record found for %s", fqdn)
 	}
 
-	return &records.RackspaceRecord[0], nil
+	return &records.Record[0], nil
 }
 
 // makeRequest is a wrapper function used for making DNS API requests
@@ -251,13 +242,13 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
 	client := http.Client{Timeout: 30 * time.Second}
 	resp, err := client.Do(req)
 	if err != nil {
-		return nil, fmt.Errorf("Error querying DNS API: %v", err)
+		return nil, fmt.Errorf("error querying DNS API: %v", err)
 	}
 
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
-		return nil, fmt.Errorf("Request failed for %s %s. Response code: %d", method, url, resp.StatusCode)
+		return nil, fmt.Errorf("request failed for %s %s. Response code: %d", method, url, resp.StatusCode)
 	}
 
 	var r json.RawMessage
@@ -269,13 +260,13 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
 	return r, nil
 }
 
-// RackspaceRecords is the list of records sent/received from the DNS API
-type RackspaceRecords struct {
-	RackspaceRecord []RackspaceRecord `json:"records"`
+// Records is the list of records sent/received from the DNS API
+type Records struct {
+	Record []Record `json:"records"`
 }
 
-// RackspaceRecord represents a Rackspace DNS record
-type RackspaceRecord struct {
+// Record represents a Rackspace DNS record
+type Record struct {
 	Name string `json:"name"`
 	Type string `json:"type"`
 	Data string `json:"data"`
diff --git a/providers/dns/rackspace/rackspace_test.go b/providers/dns/rackspace/rackspace_test.go
index 22c979ca..ed7a12f9 100644
--- a/providers/dns/rackspace/rackspace_test.go
+++ b/providers/dns/rackspace/rackspace_test.go
@@ -42,13 +42,14 @@ func liveRackspaceEnv() {
 	os.Setenv("RACKSPACE_API_KEY", rackspaceAPIKey)
 }
 
-func startTestServers() (identityAPI, dnsAPI *httptest.Server) {
-	dnsAPI = httptest.NewServer(dnsMux())
+func startTestServers() (*httptest.Server, *httptest.Server) {
+	dnsAPI := httptest.NewServer(dnsMux())
 	dnsEndpoint := dnsAPI.URL + "/123456"
 
-	identityAPI = httptest.NewServer(identityHandler(dnsEndpoint))
+	identityAPI := httptest.NewServer(identityHandler(dnsEndpoint))
 	testAPIURL = identityAPI.URL + "/"
-	return
+
+	return identityAPI, dnsAPI
 }
 
 func closeTestServers(identityAPI, dnsAPI *httptest.Server) {
diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go
index dde42ddf..8f4231df 100644
--- a/providers/dns/rfc2136/rfc2136.go
+++ b/providers/dns/rfc2136/rfc2136.go
@@ -27,7 +27,7 @@ type DNSProvider struct {
 // dynamic update. Configured with environment variables:
 // RFC2136_NAMESERVER: Network address in the form "host" or "host:port".
 // RFC2136_TSIG_ALGORITHM: Defaults to hmac-md5.sig-alg.reg.int. (HMAC-MD5).
-// See https://github.com/miekg/dns/blob/master/tsig.go for supported values. 
+// See https://github.com/miekg/dns/blob/master/tsig.go for supported values.
 // RFC2136_TSIG_KEY: Name of the secret key as defined in DNS server configuration.
 // RFC2136_TSIG_SECRET: Secret key payload.
 // RFC2136_TIMEOUT: DNS propagation timeout in time.ParseDuration format. (60s)
@@ -77,7 +77,7 @@ func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret, t
 		if err != nil {
 			return nil, err
 		} else if t < 0 {
-			return nil, fmt.Errorf("Invalid/negative RFC2136_TIMEOUT: %v", timeout)
+			return nil, fmt.Errorf("invalid/negative RFC2136_TIMEOUT: %v", timeout)
 		} else {
 			d.timeout = t
 		}
@@ -86,26 +86,26 @@ func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret, t
 	return d, nil
 }
 
-// Returns the timeout configured with RFC2136_TIMEOUT, or 60s.
+// Timeout Returns the timeout configured with RFC2136_TIMEOUT, or 60s.
 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
-    return d.timeout, 2 * time.Second
+	return d.timeout, 2 * time.Second
 }
 
 // Present creates a TXT record using the specified parameters
-func (r *DNSProvider) Present(domain, token, keyAuth string) error {
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 	fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
-	return r.changeRecord("INSERT", fqdn, value, ttl)
+	return d.changeRecord("INSERT", fqdn, value, ttl)
 }
 
 // CleanUp removes the TXT record matching the specified parameters
-func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 	fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
-	return r.changeRecord("REMOVE", fqdn, value, ttl)
+	return d.changeRecord("REMOVE", fqdn, value, ttl)
 }
 
-func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
+func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
 	// Find the zone for the given fqdn
-	zone, err := acme.FindZoneByFqdn(fqdn, []string{r.nameserver})
+	zone, err := acme.FindZoneByFqdn(fqdn, []string{d.nameserver})
 	if err != nil {
 		return err
 	}
@@ -127,20 +127,20 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
 	case "REMOVE":
 		m.Remove(rrs)
 	default:
-		return fmt.Errorf("Unexpected action: %s", action)
+		return fmt.Errorf("unexpected action: %s", action)
 	}
 
 	// Setup client
 	c := new(dns.Client)
 	c.SingleInflight = true
 	// TSIG authentication / msg signing
-	if len(r.tsigKey) > 0 && len(r.tsigSecret) > 0 {
-		m.SetTsig(dns.Fqdn(r.tsigKey), r.tsigAlgorithm, 300, time.Now().Unix())
-		c.TsigSecret = map[string]string{dns.Fqdn(r.tsigKey): r.tsigSecret}
+	if len(d.tsigKey) > 0 && len(d.tsigSecret) > 0 {
+		m.SetTsig(dns.Fqdn(d.tsigKey), d.tsigAlgorithm, 300, time.Now().Unix())
+		c.TsigSecret = map[string]string{dns.Fqdn(d.tsigKey): d.tsigSecret}
 	}
 
 	// Send the query
-	reply, _, err := c.Exchange(m, r.nameserver)
+	reply, _, err := c.Exchange(m, d.nameserver)
 	if err != nil {
 		return fmt.Errorf("DNS update failed: %v", err)
 	}
diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go
index e16e12f0..adc15401 100644
--- a/providers/dns/route53/route53.go
+++ b/providers/dns/route53/route53.go
@@ -99,7 +99,7 @@ func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
 	hostedZoneID, err := r.getHostedZoneID(fqdn)
 	if err != nil {
-		return fmt.Errorf("Failed to determine Route 53 hosted zone ID: %v", err)
+		return fmt.Errorf("failed to determine Route 53 hosted zone ID: %v", err)
 	}
 
 	recordSet := newTXTRecordSet(fqdn, value, ttl)
@@ -118,7 +118,7 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
 
 	resp, err := r.client.ChangeResourceRecordSets(reqParams)
 	if err != nil {
-		return fmt.Errorf("Failed to change Route 53 record set: %v", err)
+		return fmt.Errorf("failed to change Route 53 record set: %v", err)
 	}
 
 	statusID := resp.ChangeInfo.Id
@@ -129,9 +129,9 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
 		}
 		resp, err := r.client.GetChange(reqParams)
 		if err != nil {
-			return false, fmt.Errorf("Failed to query Route 53 change status: %v", err)
+			return false, fmt.Errorf("failed to query Route 53 change status: %v", err)
 		}
-		if *resp.ChangeInfo.Status == route53.ChangeStatusInsync {
+		if aws.StringValue(resp.ChangeInfo.Status) == route53.ChangeStatusInsync {
 			return true, nil
 		}
 		return false, nil
@@ -160,14 +160,14 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
 	var hostedZoneID string
 	for _, hostedZone := range resp.HostedZones {
 		// .Name has a trailing dot
-		if !*hostedZone.Config.PrivateZone && *hostedZone.Name == authZone {
-			hostedZoneID = *hostedZone.Id
+		if !aws.BoolValue(hostedZone.Config.PrivateZone) && aws.StringValue(hostedZone.Name) == authZone {
+			hostedZoneID = aws.StringValue(hostedZone.Id)
 			break
 		}
 	}
 
 	if len(hostedZoneID) == 0 {
-		return "", fmt.Errorf("Zone %s not found in Route 53 for domain %s", authZone, fqdn)
+		return "", fmt.Errorf("zone %s not found in Route 53 for domain %s", authZone, fqdn)
 	}
 
 	if strings.HasPrefix(hostedZoneID, "/hostedzone/") {
diff --git a/providers/dns/route53/route53_integration_test.go b/providers/dns/route53/route53_integration_test.go
index 17ba4a08..6ece1888 100644
--- a/providers/dns/route53/route53_integration_test.go
+++ b/providers/dns/route53/route53_integration_test.go
@@ -11,7 +11,6 @@ import (
 )
 
 func TestRoute53TTL(t *testing.T) {
-
 	m, err := testGetAndPreCheck()
 	if err != nil {
 		t.Skip(err.Error())
@@ -19,13 +18,14 @@ func TestRoute53TTL(t *testing.T) {
 
 	provider, err := NewDNSProvider()
 	if err != nil {
-		t.Fatalf("Fatal: %s", err.Error())
+		t.Fatal(err)
 	}
 
 	err = provider.Present(m["route53Domain"], "foo", "bar")
 	if err != nil {
-		t.Fatalf("Fatal: %s", err.Error())
+		t.Fatal(err)
 	}
+
 	// we need a separate R53 client here as the one in the DNS provider is
 	// unexported.
 	fqdn := "_acme-challenge." + m["route53Domain"] + "."
@@ -33,23 +33,25 @@ func TestRoute53TTL(t *testing.T) {
 	zoneID, err := provider.getHostedZoneID(fqdn)
 	if err != nil {
 		provider.CleanUp(m["route53Domain"], "foo", "bar")
-		t.Fatalf("Fatal: %s", err.Error())
+		t.Fatal(err)
 	}
+
 	params := &route53.ListResourceRecordSetsInput{
 		HostedZoneId: aws.String(zoneID),
 	}
 	resp, err := svc.ListResourceRecordSets(params)
 	if err != nil {
 		provider.CleanUp(m["route53Domain"], "foo", "bar")
-		t.Fatalf("Fatal: %s", err.Error())
+		t.Fatal(err)
 	}
 
 	for _, v := range resp.ResourceRecordSets {
-		if *v.Name == fqdn && *v.Type == "TXT" && *v.TTL == 10 {
+		if aws.StringValue(v.Name) == fqdn && aws.StringValue(v.Type) == "TXT" && aws.Int64Value(v.TTL) == 10 {
 			provider.CleanUp(m["route53Domain"], "foo", "bar")
 			return
 		}
 	}
+
 	provider.CleanUp(m["route53Domain"], "foo", "bar")
 	t.Fatalf("Could not find a TXT record for _acme-challenge.%s with a TTL of 10", m["route53Domain"])
 }
diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go
index de4e28f3..e1505470 100644
--- a/providers/dns/route53/route53_test.go
+++ b/providers/dns/route53/route53_test.go
@@ -65,7 +65,7 @@ func TestRegionFromEnv(t *testing.T) {
 	os.Setenv("AWS_REGION", "us-east-1")
 
 	sess := session.New(aws.NewConfig())
-	assert.Equal(t, "us-east-1", *sess.Config.Region, "Expected Region to be set from environment")
+	assert.Equal(t, "us-east-1", aws.StringValue(sess.Config.Region), "Expected Region to be set from environment")
 
 	restoreRoute53Env()
 }
diff --git a/providers/http/memcached/memcached.go b/providers/http/memcached/memcached.go
index 9ac8b811..fc625bc5 100644
--- a/providers/http/memcached/memcached.go
+++ b/providers/http/memcached/memcached.go
@@ -10,18 +10,18 @@ import (
 	"github.com/xenolf/lego/acme"
 )
 
-// HTTPProvider implements ChallengeProvider for `http-01` challenge
-type MemcachedProvider struct {
+// HTTPProvider implements HTTPProvider for `http-01` challenge
+type HTTPProvider struct {
 	hosts []string
 }
 
-// NewHTTPProvider returns a HTTPProvider instance with a configured webroot path
-func NewMemcachedProvider(hosts []string) (*MemcachedProvider, error) {
+// NewMemcachedProvider returns a HTTPProvider instance with a configured webroot path
+func NewMemcachedProvider(hosts []string) (*HTTPProvider, error) {
 	if len(hosts) == 0 {
-		return nil, fmt.Errorf("No memcached hosts provided")
+		return nil, fmt.Errorf("no memcached hosts provided")
 	}
 
-	c := &MemcachedProvider{
+	c := &HTTPProvider{
 		hosts: hosts,
 	}
 
@@ -29,7 +29,7 @@ func NewMemcachedProvider(hosts []string) (*MemcachedProvider, error) {
 }
 
 // Present makes the token available at `HTTP01ChallengePath(token)` by creating a file in the given webroot path
-func (w *MemcachedProvider) Present(domain, token, keyAuth string) error {
+func (w *HTTPProvider) Present(domain, token, keyAuth string) error {
 	var errs []error
 
 	challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
@@ -39,7 +39,7 @@ func (w *MemcachedProvider) Present(domain, token, keyAuth string) error {
 			errs = append(errs, err)
 			continue
 		}
-		mc.Add(&memcache.Item{
+		_ = mc.Add(&memcache.Item{
 			Key:        challengePath,
 			Value:      []byte(keyAuth),
 			Expiration: 60,
@@ -47,14 +47,14 @@ func (w *MemcachedProvider) Present(domain, token, keyAuth string) error {
 	}
 
 	if len(errs) == len(w.hosts) {
-		return fmt.Errorf("Unable to store key in any of the memcache hosts -> %v", errs)
+		return fmt.Errorf("unable to store key in any of the memcache hosts -> %v", errs)
 	}
 
 	return nil
 }
 
 // CleanUp removes the file created for the challenge
-func (w *MemcachedProvider) CleanUp(domain, token, keyAuth string) error {
+func (w *HTTPProvider) CleanUp(domain, token, keyAuth string) error {
 	// Memcached will clean up itself, that's what expiration is for.
 	return nil
 }
diff --git a/providers/http/memcached/memcached_test.go b/providers/http/memcached/memcached_test.go
index 287a3330..f9488a1d 100644
--- a/providers/http/memcached/memcached_test.go
+++ b/providers/http/memcached/memcached_test.go
@@ -31,7 +31,7 @@ func init() {
 func TestNewMemcachedProviderEmpty(t *testing.T) {
 	emptyHosts := make([]string, 0)
 	_, err := NewMemcachedProvider(emptyHosts)
-	assert.EqualError(t, err, "No memcached hosts provided")
+	assert.EqualError(t, err, "no memcached hosts provided")
 }
 
 func TestNewMemcachedProviderValid(t *testing.T) {
diff --git a/providers/http/webroot/webroot.go b/providers/http/webroot/webroot.go
index 4bf211f3..f9dce06d 100644
--- a/providers/http/webroot/webroot.go
+++ b/providers/http/webroot/webroot.go
@@ -35,12 +35,12 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error {
 	challengeFilePath := path.Join(w.path, acme.HTTP01ChallengePath(token))
 	err = os.MkdirAll(path.Dir(challengeFilePath), 0755)
 	if err != nil {
-		return fmt.Errorf("Could not create required directories in webroot for HTTP challenge -> %v", err)
+		return fmt.Errorf("could not create required directories in webroot for HTTP challenge -> %v", err)
 	}
 
 	err = ioutil.WriteFile(challengeFilePath, []byte(keyAuth), 0644)
 	if err != nil {
-		return fmt.Errorf("Could not write file in webroot for HTTP challenge -> %v", err)
+		return fmt.Errorf("could not write file in webroot for HTTP challenge -> %v", err)
 	}
 
 	return nil
@@ -48,10 +48,9 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error {
 
 // CleanUp removes the file created for the challenge
 func (w *HTTPProvider) CleanUp(domain, token, keyAuth string) error {
-	var err error
-	err = os.Remove(path.Join(w.path, acme.HTTP01ChallengePath(token)))
+	err := os.Remove(path.Join(w.path, acme.HTTP01ChallengePath(token)))
 	if err != nil {
-		return fmt.Errorf("Could not remove file in webroot after HTTP challenge -> %v", err)
+		return fmt.Errorf("could not remove file in webroot after HTTP challenge -> %v", err)
 	}
 
 	return nil