-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcertnab.go
150 lines (131 loc) · 3.23 KB
/
certnab.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package certnab
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"github.com/ericchiang/letsencrypt"
)
var supportedChallenges = []string{
letsencrypt.ChallengeHTTP,
}
// NewClient constructs our Certnab client.
func NewClient(acmeURL, dest, domain string) (*Client, error) {
var c Client
srv := fmt.Sprintf("https://%s/directory", acmeURL)
cli, err := letsencrypt.NewClient(srv)
if err != nil {
return nil, err
}
c.LEClient = cli
c.dest = dest
c.ourDomain = domain
return &c, nil
}
// Client wraps information we need to talk to an ACME server
// and request certificates.
type Client struct {
LEClient *letsencrypt.Client
dest string
ourDomain string
}
// HTTPChallenge binds to port 80 and serves content from an exchange with the
// ACME server. This proves control over a server at our domain name.
func (c *Client) HTTPChallenge() error {
accountKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
if _, err := c.LEClient.NewRegistration(accountKey); err != nil {
return fmt.Errorf("new registration failed: %v", err)
}
auth, _, err := c.LEClient.NewAuthorization(accountKey, "dns", c.ourDomain)
if err != nil {
return err
}
chals := auth.Combinations(supportedChallenges...)
if len(chals) == 0 {
return errors.New("no supported challenge combinations")
}
if len(chals[0]) == 0 {
return errors.New("no supported challenge combinations")
}
chal := chals[0][0]
path, resource, err := chal.HTTP(accountKey)
if err != nil {
return err
}
go func() {
// Serve the requested resource
f := 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(f)))
}()
if err := c.LEClient.ChallengeReady(accountKey, chal); err != nil {
return fmt.Errorf("challenge failed: %v", err)
}
csr, key, err := c.newCSR()
if err != nil {
return err
}
cert, err := c.LEClient.NewCertificate(accountKey, csr)
if err != nil {
return err
}
pemCert, err := c.LEClient.Bundle(cert)
if err != nil {
return err
}
err = ioutil.WriteFile("cert.pem", pemCert, 0755)
if err != nil {
return err
}
pemKey := pemEncodePrivateKey(key)
err = ioutil.WriteFile("key.pem", pemKey, 0755)
if err != nil {
return err
}
return nil
}
func pemEncodePrivateKey(priv *rsa.PrivateKey) []byte {
return pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
},
)
}
func (c *Client) newCSR() (*x509.CertificateRequest, *rsa.PrivateKey, error) {
key, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, err
}
template := &x509.CertificateRequest{
SignatureAlgorithm: x509.SHA256WithRSA,
PublicKeyAlgorithm: x509.RSA,
PublicKey: &key.PublicKey,
Subject: pkix.Name{CommonName: c.ourDomain},
DNSNames: []string{c.ourDomain},
}
csrDER, err := x509.CreateCertificateRequest(rand.Reader, template, key)
if err != nil {
return nil, nil, err
}
csr, err := x509.ParseCertificateRequest(csrDER)
if err != nil {
return nil, nil, err
}
return csr, key, nil
}