main
go 105 lines 2.56 KB
Raw
1 package keyless
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "net/http"
9 "strings"
10 "time"
11
12 ksigner "github.com/gosuda/keyless_tls/relay/signer"
13 "github.com/gosuda/keyless_tls/relay/signrpc"
14 )
15
16 const (
17 RelayKeyID = "relay-cert"
18 defaultAllowedSkew = 5 * time.Minute
19 )
20
21 type Signer struct {
22 service *ksigner.Service
23 keyID string
24 }
25
26 func NewSigner(keyPEM []byte) (*Signer, error) {
27 signingKey, err := ksigner.ParsePrivateKeyPEM(keyPEM)
28 if err != nil {
29 return nil, fmt.Errorf("parse keyless signing key: %w", err)
30 }
31
32 store := ksigner.NewStaticKeyStore()
33 if err := store.Put(RelayKeyID, signingKey); err != nil {
34 return nil, fmt.Errorf("register keyless signing key: %w", err)
35 }
36
37 return &Signer{
38 service: &ksigner.Service{
39 Store: store,
40 AllowedSkew: defaultAllowedSkew,
41 },
42 keyID: RelayKeyID,
43 }, nil
44 }
45
46 func (s *Signer) KeyID() string {
47 if s == nil {
48 return ""
49 }
50 return s.keyID
51 }
52
53 func (s *Signer) Sign(ctx context.Context, req *signrpc.SignRequest) (*signrpc.SignResponse, error) {
54 if s == nil || s.service == nil {
55 return nil, errors.New("keyless signer is disabled")
56 }
57 return s.service.Sign(ctx, req)
58 }
59
60 func (s *Signer) Handler() http.Handler {
61 mux := http.NewServeMux()
62 mux.HandleFunc(signrpc.SignPath, func(w http.ResponseWriter, r *http.Request) {
63 if r.Method != http.MethodPost {
64 w.Header().Set("Allow", http.MethodPost)
65 writeJSONError(w, http.StatusMethodNotAllowed, "method not allowed")
66 return
67 }
68 if ct := r.Header.Get("Content-Type"); ct != "" && !strings.HasPrefix(ct, "application/json") {
69 writeJSONError(w, http.StatusUnsupportedMediaType, "content type must be application/json")
70 return
71 }
72
73 r.Body = http.MaxBytesReader(w, r.Body, 4<<10)
74 defer r.Body.Close()
75
76 var req signrpc.SignRequest
77 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
78 writeJSONError(w, http.StatusBadRequest, "invalid json body")
79 return
80 }
81
82 resp, err := s.Sign(r.Context(), &req)
83 if err != nil {
84 status := http.StatusInternalServerError
85 switch {
86 case errors.Is(err, ksigner.ErrInvalidArgument):
87 status = http.StatusBadRequest
88 case errors.Is(err, ksigner.ErrPermissionDenied):
89 status = http.StatusForbidden
90 }
91 writeJSONError(w, status, err.Error())
92 return
93 }
94
95 w.Header().Set("Content-Type", "application/json")
96 _ = json.NewEncoder(w).Encode(resp)
97 })
98 return mux
99 }
100
101 func writeJSONError(w http.ResponseWriter, status int, message string) {
102 w.Header().Set("Content-Type", "application/json")
103 w.WriteHeader(status)
104 _ = json.NewEncoder(w).Encode(signrpc.ErrorResponse{Error: message})
105 }