acme-mock

An ACME server implementation performing no validations

git clone https://git.8pit.net/acme-mock.git

  1package main
  2
  3import (
  4	"bytes"
  5	"crypto/rand"
  6	"crypto/rsa"
  7	"crypto/x509"
  8	"encoding/base64"
  9	"encoding/json"
 10	"encoding/pem"
 11	"flag"
 12	"fmt"
 13	"github.com/nmeum/acme-mock/acme"
 14	"io/ioutil"
 15	"log"
 16	"math/big"
 17	"net/http"
 18	"os"
 19	"path"
 20	"strconv"
 21	"sync"
 22)
 23
 24////
 25// Types
 26////
 27
 28type acmeFn func(http.ResponseWriter, *http.Request) interface{}
 29
 30type orderCtx struct {
 31	obj *acme.Order
 32	crt []byte
 33}
 34
 35type jwsobj struct {
 36	Protected string `json:"protected"`
 37	Payload   string `json:"payload"`
 38	Signature string `json:"signature"`
 39}
 40
 41////
 42// Variables & Constants
 43////
 44
 45const (
 46	directoryPath  = "/directory"
 47	newNoncePath   = "/new-nonce"
 48	newAccountPath = "/new-account"
 49	newOrderPath   = "/new-order"
 50	revokeCertPath = "/revoke-cert"
 51	keyChangePath  = "/key-change"
 52
 53	finalizePath    = "/finalize/"
 54	certificatePath = "/certificate/"
 55	orderPath       = "/order/"
 56)
 57
 58var (
 59	httpsAddr = flag.String("a", ":443", "address used for HTTPS socket")
 60	tlsKey    = flag.String("k", "", "TLS private key")
 61	tlsCert   = flag.String("c", "", "TLS certificate")
 62	rsaBits   = flag.Int("b", 2048, "RSA key size")
 63)
 64
 65var key *rsa.PrivateKey
 66var orders []*orderCtx
 67var ordersMtx sync.Mutex
 68
 69////
 70// Utility functions
 71////
 72
 73func createCrt(csrMsg *acme.CSRMessage) ([]byte, error) {
 74	data, err := base64.RawURLEncoding.DecodeString(csrMsg.Csr)
 75	if err != nil {
 76		return nil, err
 77	}
 78
 79	csr, err := x509.ParseCertificateRequest(data)
 80	if err != nil {
 81		return nil, err
 82	}
 83
 84	temp := x509.Certificate{
 85		SerialNumber:   big.NewInt(5),
 86		Subject:        csr.Subject,
 87		DNSNames:       csr.DNSNames,
 88		EmailAddresses: csr.EmailAddresses,
 89		IPAddresses:    csr.IPAddresses,
 90	}
 91
 92	return x509.CreateCertificate(rand.Reader, &temp, &temp, &key.PublicKey, key)
 93}
 94
 95func getOrder(r *http.Request) (*orderCtx, error) {
 96	id, err := strconv.Atoi(path.Base(r.URL.Path))
 97	if err != nil {
 98		return nil, err
 99	}
100
101	ordersMtx.Lock()
102	defer ordersMtx.Unlock()
103
104	if id < len(orders) {
105		return orders[id], nil
106	} else {
107		return nil, fmt.Errorf("order not found")
108	}
109}
110
111func createURL(r *http.Request, path string) string {
112	r.URL.Host = r.Host
113	r.URL.Scheme = "https"
114	r.URL.Path = path
115
116	return r.URL.String()
117}
118
119////
120// Handlers
121////
122
123func directoryHandler(w http.ResponseWriter, r *http.Request) interface{} {
124	return acme.Directory{
125		NewNonceURL:   createURL(r, newNoncePath),
126		NewAccountURL: createURL(r, newAccountPath),
127		NewOrderURL:   createURL(r, newOrderPath),
128		RevokeCertURL: createURL(r, revokeCertPath),
129		KeyChangeURL:  createURL(r, keyChangePath),
130	}
131}
132
133func nonceHandler(w http.ResponseWriter, r *http.Request) {
134	// Hardcoded value copied from RFC 8555
135	w.Header().Add("Replay-Nonce", "oFvnlFP1wIhRlYS2jTaXbA")
136
137	w.Header().Add("Cache-Control", "no-store")
138	w.WriteHeader(http.StatusOK)
139}
140
141func accountHandler(w http.ResponseWriter, r *http.Request) interface{} {
142	return acme.Account{
143		Status: acme.StatusValid,
144		Orders: createURL(r, "orders"),
145	}
146}
147
148func newOrderHandler(w http.ResponseWriter, r *http.Request) interface{} {
149	var order acme.Order
150	err := json.NewDecoder(r.Body).Decode(&order)
151	if err != nil {
152		http.Error(w, "Bad Request", http.StatusBadRequest)
153		return nil
154	}
155
156	ordersMtx.Lock()
157	orderId := strconv.Itoa(len(orders))
158	orders = append(orders, &orderCtx{&order, nil})
159	ordersMtx.Unlock()
160
161	order.Finalize = createURL(r, path.Join(finalizePath, orderId))
162	order.Authorizations = []string{}
163
164	orderURL := createURL(r, path.Join(orderPath, orderId))
165	w.Header().Add("Location", orderURL)
166
167	w.WriteHeader(http.StatusCreated)
168	return order
169}
170
171func finalizeHandler(w http.ResponseWriter, r *http.Request) interface{} {
172	id := path.Base(r.URL.Path)
173	order, err := getOrder(r)
174	if err != nil {
175		http.Error(w, "Not Found", http.StatusNotFound)
176		return nil
177	}
178
179	var csrMsg acme.CSRMessage
180	err = json.NewDecoder(r.Body).Decode(&csrMsg)
181	if err != nil {
182		http.Error(w, "Invalid JSON", http.StatusBadRequest)
183		return nil
184	}
185
186	order.crt, err = createCrt(&csrMsg)
187	if err != nil {
188		http.Error(w, "createCrt failed", http.StatusInternalServerError)
189		return nil
190	}
191
192	order.obj.Status = acme.StatusValid
193	order.obj.Certificate = createURL(r, path.Join(certificatePath, id))
194
195	orderURL := createURL(r, path.Join(orderPath, id))
196	w.Header().Add("Location", orderURL)
197
198	return order.obj
199}
200
201func orderHandler(w http.ResponseWriter, r *http.Request) interface{} {
202	order, err := getOrder(r)
203	if err != nil {
204		http.Error(w, "Not Found", http.StatusNotFound)
205		return nil
206	}
207
208	return order.obj
209}
210
211func certHandler(w http.ResponseWriter, r *http.Request) {
212	order, err := getOrder(r)
213	if err != nil {
214		http.Error(w, "Not Found", http.StatusNotFound)
215		return
216	}
217
218	err = pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: order.crt})
219	if err != nil {
220		http.Error(w, "PEM encoding failed", http.StatusInternalServerError)
221		return
222	}
223}
224
225////
226// Middleware
227////
228
229func jsonMiddleware(fn acmeFn) http.Handler {
230	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
231		w.Header().Add("Content-Type", "application/json")
232
233		val := fn(w, r)
234		if val == nil {
235			return
236		}
237
238		err := json.NewEncoder(w).Encode(val)
239		if err != nil {
240			http.Error(w, "JSON encoding failed", http.StatusInternalServerError)
241			return
242		}
243	})
244}
245
246func jwtMiddleware(h http.Handler) http.Handler {
247	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
248		var jws jwsobj
249		err := json.NewDecoder(r.Body).Decode(&jws)
250		if err != nil {
251			http.Error(w, "Invalid JSON", http.StatusBadRequest)
252			return
253		}
254
255		payload, err := base64.RawURLEncoding.DecodeString(jws.Payload)
256		if err != nil {
257			http.Error(w, "Invalid Base64", http.StatusBadRequest)
258			return
259		}
260
261		r.Body = ioutil.NopCloser(bytes.NewReader(payload))
262		h.ServeHTTP(w, r)
263	})
264}
265
266////
267// main
268////
269
270func main() {
271	flag.Parse()
272	if *tlsKey == "" || *tlsCert == "" {
273		fmt.Fprintf(flag.CommandLine.Output(), "missing TLS key or certificate\n")
274		flag.Usage()
275		os.Exit(2)
276	}
277
278	var err error
279	key, err = rsa.GenerateKey(rand.Reader, *rsaBits)
280	if err != nil {
281		log.Fatal(err)
282	}
283
284	http.Handle(directoryPath, jsonMiddleware(directoryHandler))
285	http.HandleFunc(newNoncePath, nonceHandler)
286	http.Handle(newAccountPath, jsonMiddleware(accountHandler))
287	http.Handle(newOrderPath, jwtMiddleware(jsonMiddleware(newOrderHandler)))
288	http.Handle(finalizePath, jwtMiddleware(jsonMiddleware(finalizeHandler)))
289	http.HandleFunc(certificatePath, certHandler)
290	http.Handle(orderPath, jsonMiddleware(orderHandler))
291	log.Fatal(http.ListenAndServeTLS(*httpsAddr, *tlsCert, *tlsKey, nil))
292}