151 lines
3.3 KiB
Go
151 lines
3.3 KiB
Go
// This is a test server that implements the subset of RFC6962 APIs needed to
|
|
// run Boulder's CT log submission code. Currently it only implements add-chain.
|
|
// This is used by startservers.py.
|
|
package main
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/letsencrypt/boulder/cmd"
|
|
"github.com/letsencrypt/boulder/publisher"
|
|
)
|
|
|
|
type ctSubmissionRequest struct {
|
|
Chain []string `json:"chain"`
|
|
}
|
|
|
|
type integrationSrv struct {
|
|
sync.Mutex
|
|
submissions int64
|
|
key *ecdsa.PrivateKey
|
|
latencySchedule []float64
|
|
latencyItem int
|
|
}
|
|
|
|
func (is *integrationSrv) handler(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/ct/v1/add-pre-chain":
|
|
fallthrough
|
|
case "/ct/v1/add-chain":
|
|
if r.Method != "POST" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
bodyBytes, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
}
|
|
atomic.AddInt64(&is.submissions, 1)
|
|
|
|
if is.latencySchedule != nil {
|
|
is.Lock()
|
|
sleepTime := time.Duration(is.latencySchedule[is.latencyItem%len(is.latencySchedule)]) * time.Second
|
|
is.latencyItem++
|
|
is.Unlock()
|
|
time.Sleep(sleepTime)
|
|
}
|
|
|
|
var addChainReq ctSubmissionRequest
|
|
err = json.Unmarshal(bodyBytes, &addChainReq)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
}
|
|
if len(addChainReq.Chain) == 0 {
|
|
w.WriteHeader(400)
|
|
return
|
|
}
|
|
|
|
precert := false
|
|
if r.URL.Path == "/ct/v1/add-pre-chain" {
|
|
precert = true
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(publisher.CreateTestingSignedSCT(addChainReq.Chain, is.key, precert, time.Now()))
|
|
case "/submissions":
|
|
if r.Method != "GET" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
submissions := atomic.LoadInt64(&is.submissions)
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintf(w, "%d", submissions)
|
|
default:
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
}
|
|
|
|
type config struct {
|
|
Personalities []Personality
|
|
}
|
|
|
|
type Personality struct {
|
|
// Port (and optionally IP) to listen on
|
|
Addr string
|
|
// Private key for signing SCTs
|
|
// Generate your own with:
|
|
// openssl ecparam -name prime256v1 -genkey -outform der -noout | base64 -w 0
|
|
PrivKey string
|
|
// If present, sleep for the given number of seconds before replying. Each
|
|
// request uses the next number in the list, eventually cycling through.
|
|
LatencySchedule []float64
|
|
}
|
|
|
|
func runPersonality(p Personality) {
|
|
keyDER, err := base64.StdEncoding.DecodeString(p.PrivKey)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
key, err := x509.ParseECPrivateKey(keyDER)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
is := integrationSrv{
|
|
key: key,
|
|
latencySchedule: p.LatencySchedule,
|
|
}
|
|
srv := &http.Server{
|
|
Addr: p.Addr,
|
|
Handler: http.HandlerFunc(is.handler),
|
|
}
|
|
log.Printf("ct-test-srv on %s with pubkey %s", p.Addr,
|
|
base64.StdEncoding.EncodeToString(pubKeyBytes))
|
|
log.Fatal(srv.ListenAndServe())
|
|
}
|
|
|
|
func main() {
|
|
configFile := flag.String("config", "", "Path to config file.")
|
|
flag.Parse()
|
|
data, err := ioutil.ReadFile(*configFile)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
var c config
|
|
err = json.Unmarshal(data, &c)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
for _, p := range c.Personalities {
|
|
go runPersonality(p)
|
|
}
|
|
cmd.CatchSignals(nil, nil)
|
|
}
|