forked from riobard/go-acme
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
174 lines (151 loc) · 4.28 KB
/
main.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"flag"
"log"
"net/http"
"os"
// "io"
"strings"
)
const (
ACMEChallengePathPrefix = "/.well-known/acme-challenge/"
LetsEncryptStaging = "https://acme-staging.api.letsencrypt.org/directory"
LetsEncryptProduction = "https://acme-v01.api.letsencrypt.org/directory"
)
var cfg struct {
KeyPath string
Addr string
Domains string
API string
Bits int
outKeyFile string
outCertFile string
}
func init() {
log.SetFlags(0) // do not log date
flag.StringVar(&cfg.KeyPath, "key", "", "path to account key")
flag.StringVar(&cfg.Addr, "addr", "127.0.0.1:81", "challenge server address")
flag.StringVar(&cfg.Domains, "domains", "", "comma-separated list of up to 100 domain names")
flag.StringVar(&cfg.API, "api", LetsEncryptProduction, "ACME API URL")
flag.IntVar(&cfg.Bits, "bit", 2048, "domain key length")
flag.StringVar(&cfg.outKeyFile, "keyFile", "privateKey.pem", "path to save private key")
flag.StringVar(&cfg.outCertFile, "certFile", "chain.pem", "path to save public cert")
flag.Parse()
}
func main() {
var err error
domains := strings.Split(cfg.Domains, ",")
if len(domains) > 100 {
log.Fatalf("Too many domains (%d > 100)", len(domains))
}
// read the account key from stdin if not given in flags
keyReader := os.Stdin
if cfg.KeyPath != "" {
keyReader, err = os.Open(cfg.KeyPath)
if err != nil {
log.Fatalf("Failed to read account key: %s", err)
}
}
key, err := loadRSAKey(keyReader)
if err != nil {
log.Fatalf("Failed to parse key: %s", err)
}
log.Printf("Connecting to ACME server at %s", cfg.API)
acme, err := OpenACME(cfg.API, key)
if err != nil {
log.Fatalf("Failed to connect to ACME server: %s", err)
}
// start the challenge server in background
log.Printf("Responding to ACME challenges at http://%s", cfg.Addr)
go http.ListenAndServe(cfg.Addr, acme)
log.Printf("Registering account key")
if err := acme.NewReg(); err != nil {
log.Fatalf("Failed to register account key: %s", err)
}
// authorize domains in parallel
type Done struct {
Domain string
Error error
}
ch := make(chan Done)
for _, domain := range domains {
go func(domain string) {
log.Printf("Authorizing domain %s", domain)
done := Done{Domain: domain}
if err := acme.NewAuthz(domain); err != nil {
done.Error = err
}
ch <- done
}(domain)
}
// collect authorization result
failed := false
for range domains {
if done := <-ch; done.Error != nil {
failed = true
log.Printf("Failed to authorize domain %s: %s", done.Domain, done.Error)
} else {
log.Printf("Authorized domain %s", done.Domain)
}
}
if failed {
log.Fatalln("Some domains failed authorization")
}
log.Printf("Generating domain key")
domainKey, err := rsa.GenerateKey(rand.Reader, cfg.Bits)
if err != nil {
log.Fatalf("Failed to generate domain key: %s", err)
}
// create certificate signing request
tpl := &x509.CertificateRequest{DNSNames: domains}
csr, err := x509.CreateCertificateRequest(rand.Reader, tpl, domainKey)
if err != nil {
log.Fatalf("Failed to create certificate request: %s", err)
}
log.Printf("Fetching certificates")
domainCrt, issuerCrt, err := acme.NewCert(csr)
if err != nil {
log.Fatalf("Failed to fetch certificates: %s", err)
}
var keyOutput = os.Stdout
if cfg.outKeyFile != "" {
keyFileWriter, err := os.OpenFile(cfg.outKeyFile, os.O_WRONLY | os.O_CREATE, 0644)
if err != nil {
log.Fatalf("Failed to read output key: %s", err)
}
keyOutput = keyFileWriter
}
defer keyOutput.Close()
// output domain key and certificates in PEM format
if err := pem.Encode(keyOutput, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(domainKey),
}); err != nil {
log.Fatalln(err)
}
var certOutput = os.Stdout
if cfg.outCertFile != "" {
certFileWriter, err := os.OpenFile(cfg.outCertFile, os.O_WRONLY | os.O_CREATE, 0644)
if err != nil {
log.Fatalf("Failed to read output cert: %s", err)
}
certOutput = certFileWriter
}
defer certOutput.Close()
if err := pem.Encode(certOutput, &pem.Block{
Type: "CERTIFICATE",
Bytes: domainCrt.Raw,
}); err != nil {
log.Fatalln(err)
}
if err := pem.Encode(certOutput, &pem.Block{
Type: "CERTIFICATE",
Bytes: issuerCrt.Raw,
}); err != nil {
log.Fatalln(err)
}
}