Created
April 20, 2017 02:22
-
-
Save nathan-osman/cb501a32a8727f922852388cc622c2a6 to your computer and use it in GitHub Desktop.
Obtain a Let's Encrypt certificate from the ACME staging server using golang.org/x/crypto/acme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"crypto" | |
"crypto/rand" | |
"crypto/rsa" | |
"crypto/x509" | |
"crypto/x509/pkix" | |
"encoding/pem" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net" | |
"net/http" | |
"os" | |
"path" | |
"strconv" | |
"strings" | |
"time" | |
"golang.org/x/crypto/acme" | |
) | |
const ( | |
addr = ":80" | |
domain = "example.com" | |
keyLength = 2048 | |
keyType = "RSA PRIVATE KEY" | |
certType = "CERTIFICATE" | |
stagingURL = "https://acme-staging.api.letsencrypt.org/directory" | |
) | |
func newKey(filename string) (crypto.Signer, error) { | |
log.Printf("generating %d-bit RSA key...", keyLength) | |
k, err := rsa.GenerateKey(rand.Reader, keyLength) | |
if err != nil { | |
return nil, err | |
} | |
b := pem.EncodeToMemory(&pem.Block{ | |
Type: keyType, | |
Bytes: x509.MarshalPKCS1PrivateKey(k), | |
}) | |
if err = ioutil.WriteFile(filename, b, 0600); err != nil { | |
log.Fatal(err) | |
} | |
log.Print("generated RSA key") | |
return k, nil | |
} | |
func main() { | |
// Create a temporary directory | |
d, err := ioutil.TempDir("/tmp", "") | |
if err != nil { | |
log.Fatal(err) | |
} | |
log.Printf("created \"%s\"", d) | |
// Generate account key | |
aKey, err := newKey(path.Join(d, "account.pem")) | |
if err != nil { | |
log.Fatal(err) | |
} | |
// Create the ACME client | |
c := &acme.Client{ | |
Key: aKey, | |
DirectoryURL: stagingURL, | |
} | |
// Begin with registration | |
log.Print("attempting registration...") | |
_, err = c.Register(context.TODO(), nil, func(string) bool { return true }) | |
if err != nil { | |
log.Fatal(err) | |
} | |
log.Print("registration succeeded") | |
// Attempt authorization | |
auth, err := c.Authorize(context.TODO(), domain) | |
if err != nil { | |
log.Fatal(err) | |
} | |
var challenge *acme.Challenge | |
for _, c := range auth.Challenges { | |
if c.Type == "http-01" { | |
challenge = c | |
} | |
} | |
if challenge == nil { | |
log.Fatal("no HTTP challenge found") | |
} | |
// Determine the correct path to listen on | |
cPath := c.HTTP01ChallengePath(challenge.Token) | |
cResponse, err := c.HTTP01ChallengeResponse(challenge.Token) | |
if err != nil { | |
log.Fatal(err) | |
} | |
// Create a server that responds to the request | |
mux := http.NewServeMux() | |
mux.HandleFunc(cPath, func(w http.ResponseWriter, r *http.Request) { | |
b := []byte(cResponse) | |
w.Header().Set("Content-Length", strconv.Itoa(len(b))) | |
w.WriteHeader(http.StatusOK) | |
w.Write(b) | |
}) | |
l, err := net.Listen("tcp", addr) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer l.Close() | |
go func() { | |
http.Serve(l, mux) | |
}() | |
// Perform the challenge | |
log.Print("performing challenge...") | |
_, err = c.Accept(context.TODO(), challenge) | |
if err != nil { | |
log.Fatal(err) | |
} | |
// Wait for authorization to complete | |
_, err = c.WaitAuthorization(context.TODO(), auth.URI) | |
if err != nil { | |
log.Fatal(err) | |
} | |
log.Print("challenge completed") | |
uDomain := strings.Replace(domain, ".", "_", -1) | |
// Generate a key for the domain | |
dKey, err := newKey(fmt.Sprintf("%s.key", path.Join(d, uDomain))) | |
if err != nil { | |
log.Fatal(err) | |
} | |
// Create the CSR (certificate signing request) | |
csr, err := x509.CreateCertificateRequest( | |
rand.Reader, | |
&x509.CertificateRequest{ | |
Subject: pkix.Name{CommonName: domain}, | |
}, | |
dKey, | |
) | |
if err != nil { | |
log.Fatal(err) | |
} | |
// Send the CSR and obtain the certificate | |
log.Print("signing the certificate") | |
ders, _, err := c.CreateCert(context.TODO(), csr, 90*24*time.Hour, true) | |
if err != nil { | |
log.Fatal(err) | |
} | |
log.Print("certificate signed!") | |
// Write the certificate bundle to disk | |
w, err := os.Create(path.Join( | |
d, fmt.Sprintf("%s.crt", uDomain), | |
)) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer w.Close() | |
for _, der := range ders { | |
err := pem.Encode(w, &pem.Block{ | |
Type: certType, | |
Bytes: der, | |
}) | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
log.Print("complete!") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for sharing the sample. seems like need upgrade to newer version