Git Product home page Git Product logo

letsencrypt's Introduction

A Let's Encrypt client for Go

GoDoc

About

This is a client package for Let's Encrypt.

Rather than being a "one click TLS" service like Let's Encrypt's command line tool, this package exposes the functionality defined by the ACME spec. It is up to the user to determine which challenges they support and how they wish to complete them.

Since the ACME spec is still a draft and Let's Encrypt has yet to enter public beta, this package should be regarded as experimental (though it should still work!).

Read more about the package in this blog post.

Example usage

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "crypto/x509/pkix"
    "log"

    "github.com/ericchiang/letsencrypt"
)

var supportedChallengs = []string{
    letsencrypt.ChallengeHTTP,
    letsencrypt.ChallengeTLSSNI,
}

func main() {
    cli, err := letsencrypt.NewClient("http://localhost:4000/directory")
    if err != nil {
        log.Fatal("failed to create client:", err)
    }

    // Create a private key for your account and register
    accountKey, err := rsa.GenerateKey(rand.Reader, 4096)
    if err != nil {
        log.Fatal(err)
    }
    if _, err := cli.NewRegistration(accountKey); err != nil {
        log.Fatal("new registration failed:", err)
    }

    // ask for a set of challenges for a given domain
    auth, _, err := cli.NewAuthorization(accountKey, "dns", "example.org")
    if err != nil {
        log.Fatal(err)
    }
    chals := auth.Combinations(supportedChallenges...)
    if len(chals) == 0 {
        log.Fatal("no supported challenge combinations")
    }

    /*
        review challenge combinations and complete them
    */

    // create a certificate request for your domain
    csr, certKey, err := newCSR()
    if err != nil {
        log.Fatal(err)
    }

    // Request a certificate for your domain
    cert, err := cli.NewCertificate(accountKey, csr)
    if err != nil {
        log.Fatal(err)
    }
    // We've got a certificate. Let's Encrypt!
}

func newCSR() (*x509.CertificateRequest, *rsa.PrivateKey, error) {
    certKey, err := rsa.GenerateKey(rand.Random, 4096)
    if err != nil {
        return nil, nil, err
    }
    template := &x509.CertificateRequest{
        SignatureAlgorithm: x509.SHA256WithRSA,
        PublicKeyAlgorithm: x509.RSA,
        PublicKey:          &certKey.PublicKey,
        Subject:            pkix.Name{CommonName: "example.org"},
        DNSNames:           []string{"example.org"},
    }
    csrDER, err := x509.CreateCertificateRequest(rand.Reader, template, certKey)
    if err != nil {
        return nil, nil, err
    }
    csr, err := x509.ParseCertificateRequest(csrDER)
    if err != nil {
        return nil, nil, err
    }
    return csr, certKey, nil
}

Challenges

HTTP

HTTP challenges (http-01) require provising an HTTP resource at a given path on your domain.

chal := chals[0]
if chal.Type != ChallengeHTTP {
    log.Fatal("this isn't an HTTP challenge!")
}

path, resource, err := chal.HTTP(accountKey)
if err != nil {
    log.Fatal(err)
}

go func() {
    // Listen on HTTP for a request at the given path.
    hf := func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path != path {
            http.NotFound(w, r)
            return
        }
        io.WriteString(w, resource)
    }
    log.Fatal(http.ListenAndServe(":80", http.HandlerFunc(hf)))
}()

// Tell the server the challenge is ready and poll the server for updates.
if err := cli.ChallengeReady(accountKey, chal); err != nil {
    // oh no, you failed the challenge
    log.Fatal(err)
}
// The challenge has been verified!

TLS SNI

TLS SNI challenges (tls-sni-01) require responding to a given TLS Server Name Indication request with a specific certificate. These server names will not be for the actual domain begin validated, so the challenge can be completed without certificate errors for users.

chal := chals[0]
if chal.Type != ChallengeTLSSNI {
    log.Fatal("this isn't an TLS SNI challenge!")
}

certs, err := chal.TLSSNI(accountKey)
if err != nil {
    log.Fatal(err)
}

go func() {
    // Configure a custom response function for SNI requests.
    getCertificate := func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
        if cert, ok := certs[clientHello.ServerName]; ok {
            return cert, nil
        }
        return nil, nil
    }
    s := &http.Server{
        Addr:      ":443",
        TLSConfig: &tls.Config{GetCertificate: getCertificate},
        Handler:   http.HandlerFunc(http.NotFound),
    }
    log.Fatal(s.ListenAndServeTLS("self-signed.cert", "self-signed.key"))
}()

// Tell the server the challenge is ready and poll the server for updates.
if err := cli.ChallengeReady(accountKey, chal); err != nil {
    // oh no, you failed the challenge
    log.Fatal(err)
}
// The challenge has been verified!

Running the tests

The test suite runs against an installation of Let's Encrypt's boulder. Follow instructions in that repo for running in development mode on 127.0.0.1:4000.

Boulder will not issue cerficiates for non-public domains (e.g. .localdomain). In addition it keeps a blacklist of domains to not issue certificates for.

Before running boulder, you must edit the base blacklist to allow example.org and localhost.localdomain.

$GOPATH$src/github.com/letsencrypt/boulder/cmd/policy-loader/base-rules.json

In order to masqurade as a public domain, the tests require adding an entry to /etc/hosts to manually change Boulder's DNS resolution. Specifically, have example.org resolve to 127.0.0.1.

$ sudo cat /etc/hosts
127.0.0.1       localhost.localdomain localhost
127.0.0.1       example.org example
::1     localhost6.localdomain6 localhost6

If you hit rate limits, shut down the Boulder instance and reload the database with ./test/create_db.sh.

letsencrypt's People

Contributors

ericchiang avatar

Watchers

Kenn White avatar James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.