package server import ( "context" "fmt" "io" "net" "net/http" "time" "rgdd.se/silent-ct/internal/x509util" ) const ( EndpointAddChain = "add-chain" EndpointGetStatus = "get-status" DefaultNetwork = "tcp" DefaultAddress = "localhost:2009" DefaultConfigFile = "/home/rgdd/.config/silent-ct/config.json" // FIXME ) type Config struct { Network string // tcp or unix Address string // hostname[:port] or path to a unix socket Nodes Nodes // Which nodes are trusted to issue what certificates } type Server struct { Config eventCh chan MessageNodeSubmission errorCh chan error } func New(cfg Config) (Server, error) { if cfg.Network == "" { cfg.Network = DefaultNetwork } if cfg.Address == "" { cfg.Network = DefaultAddress } return Server{Config: cfg}, nil } func (srv *Server) Run(ctx context.Context, submitCh chan MessageNodeSubmission, errorCh chan error) error { srv.eventCh = submitCh srv.errorCh = errorCh mux := http.NewServeMux() for _, handler := range srv.handlers() { handler.register(mux) } listener, err := net.Listen(srv.Network, srv.Address) if err != nil { return fmt.Errorf("listen: %v", err) } defer listener.Close() s := http.Server{Handler: mux} exitCh := make(chan error, 1) defer close(exitCh) go func() { exitCh <- s.Serve(listener) }() select { case err := <-exitCh: if err != nil && err != http.ErrServerClosed { return fmt.Errorf("serve: %v", err) } case <-ctx.Done(): tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := s.Shutdown(tctx); err != nil { return fmt.Errorf("shutdown: %v", err) } } return nil } func (srv *Server) handlers() []handler { return []handler{ handler{ method: http.MethodGet, endpoint: EndpointGetStatus, callback: func(w http.ResponseWriter, r *http.Request) (int, string) { return srv.getStatus(w, r) }, }, handler{ method: http.MethodPost, endpoint: EndpointAddChain, callback: func(w http.ResponseWriter, r *http.Request) (int, string) { return srv.addChain(w, r) }, }, } } func (srv *Server) getStatus(w http.ResponseWriter, r *http.Request) (int, string) { return http.StatusOK, "OK" } func (srv *Server) addChain(w http.ResponseWriter, r *http.Request) (int, string) { node, err := srv.Nodes.authenticate(r) if err != nil { return http.StatusForbidden, "Invalid HTTP Basic Auth credentials" } b, err := io.ReadAll(r.Body) if err != nil { return http.StatusBadRequest, "Read HTTP POST body failed" } defer r.Body.Close() chain, err := x509util.ParseChain(b) if err != nil { return http.StatusBadRequest, "Malformed HTTP POST body" } if err := node.check(chain[0]); err != nil { srv.errorCh <- ErrorUnauthorizedDomainName{ PEMChain: b, Node: node, Err: err, } } else { srv.eventCh <- MessageNodeSubmission{ SerialNumber: chain[0].SerialNumber.String(), NotBefore: chain[0].NotBefore, DomainNames: chain[0].DNSNames, PEMChain: b, } } return http.StatusOK, "OK" }