Skip to content

Commit 87f36ea

Browse files
runcommmartinvBen Krieger
authored
fix: import/export PEM voucher (#28)
* fix: import/export PEM voucher This patch changes the way importing and exporting a voucher was handled. For some reason, it was dealing with jsons of voucher/owner keys. This makes it so that it deals only with PEM voucher and owner keys verification is left out to be implemented elsewhere. Signed-off-by: Antonio Murdaca <[email protected]> Co-authored-by: Miguel Martin <[email protected]> Signed-off-by: Antonio Murdaca <[email protected]> * fix: add body size limiter to /api endpoints Signed-off-by: Ben Krieger <[email protected]> * fix: limit /api/v1/to0 to GET method Signed-off-by: Ben Krieger <[email protected]> * fix: add owner key check on voucher import by API Signed-off-by: Ben Krieger <[email protected]> * fix: add check for invalid PEM in voucher import API Signed-off-by: Ben Krieger <[email protected]> * fix: do not fail if voucher already exists and is the same Signed-off-by: Antonio Murdaca <[email protected]> --------- Signed-off-by: Antonio Murdaca <[email protected]> Signed-off-by: Ben Krieger <[email protected]> Co-authored-by: Miguel Martin <[email protected]> Co-authored-by: Ben Krieger <[email protected]>
1 parent 7b26bd5 commit 87f36ea

File tree

5 files changed

+153
-83
lines changed

5 files changed

+153
-83
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,16 @@ Use GET and PUT requests to view and update existing owner redirect data.
149149

150150
## Fetch and Post Voucher
151151
Fetch a Voucher
152+
152153
Fetch a voucher using curl and save it to a file named ownervoucher:
153154
```
154155
curl --location --request GET 'http://localhost:8038/api/v1/vouchers?guid=<guid>' -o ownervoucher
155156
```
156-
Post the Voucher to RV and Owner Server
157-
Post the fetched voucher to the RV and Owner server using curl:
157+
Post the Voucher to the Owner Server
158+
159+
Post the fetched voucher to the Owner server using curl:
158160
```
159-
curl -X POST 'http://localhost:8041/api/v1/owner/vouchers' -d @ownervoucher
160-
curl -X POST 'http://localhost:8043/api/v1/owner/vouchers' -d @ownervoucher
161+
curl -X POST 'http://localhost:8043/api/v1/owner/vouchers' --data-binary @ownervoucher
161162
```
162163
## Execute DI from the FDO GO Client.
163164
For Running the FDO GO Client setup, please refer to the FDO Go Client README.

api/handlers/to0.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,17 @@ package handlers
55

66
import (
77
"net/http"
8-
"path"
98

10-
"github.com/fido-device-onboard/go-fdo-server/internal/utils"
11-
12-
"github.com/fido-device-onboard/go-fdo-server/internal/to0"
139
"github.com/fido-device-onboard/go-fdo/protocol"
1410
"github.com/fido-device-onboard/go-fdo/sqlite"
11+
12+
"github.com/fido-device-onboard/go-fdo-server/internal/to0"
13+
"github.com/fido-device-onboard/go-fdo-server/internal/utils"
1514
)
1615

1716
func To0Handler(rvInfo *[][]protocol.RvInstruction, state *sqlite.DB) http.HandlerFunc {
1817
return func(w http.ResponseWriter, r *http.Request) {
19-
to0Guid := path.Base(r.URL.Path)
20-
if to0Guid == "" {
21-
http.Error(w, "GUID is required", http.StatusBadRequest)
22-
return
23-
}
18+
to0Guid := r.PathValue("guid")
2419

2520
if !utils.IsValidGUID(to0Guid) {
2621
http.Error(w, "GUID is not a valid GUID", http.StatusBadRequest)
@@ -37,6 +32,6 @@ func To0Handler(rvInfo *[][]protocol.RvInstruction, state *sqlite.DB) http.Handl
3732

3833
w.Header().Set("Content-Type", "text/plain")
3934
w.WriteHeader(http.StatusOK)
40-
w.Write([]byte(to0Guid))
35+
_, _ = w.Write([]byte(to0Guid))
4136
}
4237
}

api/handlers/vouchers.go

Lines changed: 107 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,26 @@
44
package handlers
55

66
import (
7+
"bytes"
8+
"crypto"
9+
"crypto/x509"
710
"database/sql"
811
"encoding/hex"
9-
"encoding/json"
12+
"encoding/pem"
1013
"fmt"
14+
"io"
1115
"net/http"
1216

17+
"github.com/fido-device-onboard/go-fdo"
18+
1319
"github.com/fido-device-onboard/go-fdo-server/internal/utils"
1420

1521
"log/slog"
1622

17-
"github.com/fido-device-onboard/go-fdo-server/internal/db"
18-
"github.com/fido-device-onboard/go-fdo-server/internal/rvinfo"
23+
"github.com/fido-device-onboard/go-fdo/cbor"
1924
"github.com/fido-device-onboard/go-fdo/protocol"
25+
26+
"github.com/fido-device-onboard/go-fdo-server/internal/db"
2027
)
2128

2229
func GetVoucherHandler(w http.ResponseWriter, r *http.Request) {
@@ -49,66 +56,119 @@ func GetVoucherHandler(w http.ResponseWriter, r *http.Request) {
4956
return
5057
}
5158

52-
ownerKeys, err := db.FetchOwnerKeys()
53-
if err != nil {
54-
slog.Debug("Error querying owner_keys", "error", err)
55-
http.Error(w, err.Error(), http.StatusInternalServerError)
56-
return
57-
}
58-
59-
response := struct {
60-
Voucher db.Voucher `json:"voucher"`
61-
OwnerKeys []db.OwnerKey `json:"owner_keys"`
62-
}{
63-
Voucher: voucher,
64-
OwnerKeys: ownerKeys,
65-
}
66-
67-
data, err := json.Marshal(response)
68-
if err != nil {
69-
slog.Debug("Error marshalling JSON", "error", err)
59+
w.Header().Set("Content-Type", "application/x-pem-file")
60+
if err := pem.Encode(w, &pem.Block{
61+
Type: "OWNERSHIP VOUCHER",
62+
Bytes: voucher.CBOR,
63+
}); err != nil {
64+
slog.Debug("Error encoding voucher", "error", err)
7065
http.Error(w, err.Error(), http.StatusInternalServerError)
7166
return
7267
}
73-
w.Header().Set("Content-Type", "application/json")
74-
w.Write(data)
7568
}
7669

7770
func InsertVoucherHandler(rvInfo *[][]protocol.RvInstruction) http.HandlerFunc {
7871
return func(w http.ResponseWriter, r *http.Request) {
79-
var request struct {
80-
Voucher db.Voucher `json:"voucher"`
81-
OwnerKeys []db.OwnerKey `json:"owner_keys"`
82-
}
83-
84-
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
85-
http.Error(w, "Invalid request payload", http.StatusBadRequest)
72+
body, err := io.ReadAll(r.Body)
73+
if err != nil {
74+
http.Error(w, "Failure to read the request body", http.StatusInternalServerError)
8675
return
8776
}
8877

89-
guidHex := hex.EncodeToString(request.Voucher.GUID)
90-
slog.Debug("Inserting voucher", "GUID", guidHex)
91-
92-
if err := db.InsertVoucher(request.Voucher); err != nil {
93-
slog.Debug("Error inserting into database", "error", err)
94-
http.Error(w, "Internal server error", http.StatusInternalServerError)
95-
return
78+
block, rest := pem.Decode(body)
79+
for ; block != nil; block, rest = pem.Decode(rest) {
80+
if block.Type != "OWNERSHIP VOUCHER" {
81+
slog.Debug("Got unknown label type", "type", block.Type)
82+
continue
83+
}
84+
var ov fdo.Voucher
85+
if err := cbor.Unmarshal(block.Bytes, &ov); err != nil {
86+
slog.Debug("Unable to decode cbor", "block", block.Bytes)
87+
http.Error(w, "Unable to decode cbor", http.StatusBadRequest)
88+
return
89+
}
90+
91+
if dbOv, err := db.FetchVoucher(ov.Header.Val.GUID[:]); err == nil {
92+
if bytes.Equal(block.Bytes, dbOv.CBOR) {
93+
slog.Debug("Voucher already exists", "guid", ov.Header.Val.GUID[:])
94+
continue
95+
}
96+
slog.Debug("Voucher guid already exists. not overwriting it", "guid", ov.Header.Val.GUID[:])
97+
}
98+
99+
// Check that voucher owner key matches
100+
expectedPubKey, err := ov.OwnerPublicKey()
101+
if err != nil {
102+
slog.Debug("Unable to parse owner public key of voucher", "err", err)
103+
http.Error(w, "Invalid voucher", http.StatusBadRequest)
104+
return
105+
}
106+
expectedKeyType := ov.Header.Val.ManufacturerKey.Type
107+
108+
ownerKeys, err := db.FetchOwnerKeys()
109+
if err != nil {
110+
slog.Debug("Error getting owner key", "err", err)
111+
http.Error(w, "Unable to get appropriate owner key type", http.StatusInternalServerError)
112+
return
113+
}
114+
var possibleOwnerKeys []db.OwnerKey
115+
for _, ownerKey := range ownerKeys {
116+
if ownerKey.Type == int(expectedKeyType) {
117+
possibleOwnerKeys = append(possibleOwnerKeys, ownerKey)
118+
}
119+
}
120+
CheckOwnerKey:
121+
switch possibilities := len(possibleOwnerKeys); possibilities {
122+
case 0:
123+
http.Error(w, "owner key in database does not match the owner of the voucher", http.StatusBadRequest)
124+
return
125+
126+
case 1, 2: // Can be two in the case of RSA 2048+3072 support
127+
if possibilities == 2 && expectedKeyType != protocol.RsaPkcsKeyType && expectedKeyType != protocol.RsaPssKeyType {
128+
slog.Error("database contains too many owner keys", "type", ov.Header.Val.ManufacturerKey.Type)
129+
http.Error(w, "database contains too many owner keys of a type", http.StatusInternalServerError)
130+
return
131+
}
132+
133+
for _, possibleOwnerKey := range possibleOwnerKeys {
134+
ownerKey, err := x509.ParsePKCS8PrivateKey(possibleOwnerKey.PKCS8)
135+
if err != nil {
136+
slog.Error("invalid owner key in DB", "type", expectedKeyType, "err", err)
137+
http.Error(w, "owner key in database is malformed", http.StatusInternalServerError)
138+
return
139+
}
140+
if ownerKey.(interface{ Public() crypto.PublicKey }).Public().(interface{ Equal(crypto.PublicKey) bool }).Equal(expectedPubKey) {
141+
break CheckOwnerKey
142+
}
143+
}
144+
145+
http.Error(w, "owner key in database does not match the owner of the voucher", http.StatusBadRequest)
146+
return
147+
148+
default:
149+
slog.Error("database contains too many owner keys", "type", ov.Header.Val.ManufacturerKey.Type)
150+
http.Error(w, "database contains too many owner keys of a type", http.StatusInternalServerError)
151+
return
152+
}
153+
154+
// TODO: https://github.com/fido-device-onboard/go-fdo-server/issues/18
155+
slog.Debug("Inserting voucher", "GUID", ov.Header.Val.GUID)
156+
157+
if err := db.InsertVoucher(db.Voucher{GUID: ov.Header.Val.GUID[:], CBOR: block.Bytes}); err != nil {
158+
slog.Debug("Error inserting into database", "error", err.Error())
159+
http.Error(w, "Internal server error", http.StatusInternalServerError)
160+
return
161+
}
162+
163+
*rvInfo = ov.Header.Val.RvInfo
96164
}
97165

98-
if err := db.UpdateOwnerKeys(request.OwnerKeys); err != nil {
99-
slog.Debug("Error updating owner key in database", "error", err)
100-
http.Error(w, "Internal server error", http.StatusInternalServerError)
166+
if len(bytes.TrimSpace(rest)) > 0 {
167+
http.Error(w, "Unable to decode PEM content", http.StatusBadRequest)
101168
return
102169
}
103170

104-
newRvInfo, err := rvinfo.GetRvInfoFromVoucher(request.Voucher.CBOR)
105-
if err != nil {
106-
fmt.Println("Error:", err)
107-
return
108-
}
109-
*rvInfo = newRvInfo
110171
w.Header().Set("Content-Type", "text/plain")
111172
w.WriteHeader(http.StatusOK)
112-
w.Write([]byte(guidHex))
113173
}
114174
}

api/routes.go

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
package api
55

66
import (
7-
"golang.org/x/time/rate"
7+
"io"
88
"net/http"
99

10-
"github.com/fido-device-onboard/go-fdo-server/api/handlers"
10+
"golang.org/x/time/rate"
11+
1112
transport "github.com/fido-device-onboard/go-fdo/http"
1213
"github.com/fido-device-onboard/go-fdo/protocol"
1314
"github.com/fido-device-onboard/go-fdo/sqlite"
15+
16+
"github.com/fido-device-onboard/go-fdo-server/api/handlers"
1417
)
1518

1619
// HTTPHandler handles HTTP requests
@@ -20,14 +23,27 @@ type HTTPHandler struct {
2023
state *sqlite.DB
2124
}
2225

23-
func rateLimitMiddleware(limiter *rate.Limiter, next http.Handler) http.Handler {
24-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
26+
func rateLimitMiddleware(limiter *rate.Limiter, next http.Handler) http.HandlerFunc {
27+
return func(w http.ResponseWriter, r *http.Request) {
2528
if !limiter.Allow() {
2629
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
2730
return
2831
}
2932
next.ServeHTTP(w, r)
30-
})
33+
}
34+
}
35+
36+
func bodySizeMiddleware(limitBytes int64, next http.Handler) http.HandlerFunc {
37+
return func(w http.ResponseWriter, r *http.Request) {
38+
r.Body = struct {
39+
io.Reader
40+
io.Closer
41+
}{
42+
Reader: io.LimitReader(r.Body, limitBytes),
43+
Closer: r.Body,
44+
}
45+
next.ServeHTTP(w, r)
46+
}
3147
}
3248

3349
// NewHTTPHandler creates a new HTTPHandler
@@ -37,25 +53,22 @@ func NewHTTPHandler(handler *transport.Handler, rvInfo *[][]protocol.RvInstructi
3753

3854
// RegisterRoutes registers the routes for the HTTP server
3955
func (h *HTTPHandler) RegisterRoutes() *http.ServeMux {
40-
handler := http.NewServeMux()
41-
limiter := rate.NewLimiter(2, 10)
56+
apiRouter := http.NewServeMux()
57+
apiRouter.Handle("/rvinfo", handlers.RvInfoHandler(h.rvInfo))
58+
apiRouter.HandleFunc("/owner/redirect", handlers.OwnerInfoHandler)
59+
apiRouter.Handle("GET /to0/{guid}", handlers.To0Handler(h.rvInfo, h.state))
60+
apiRouter.HandleFunc("GET /vouchers", handlers.GetVoucherHandler)
61+
apiRouter.Handle("POST /owner/vouchers", handlers.InsertVoucherHandler(h.rvInfo))
62+
63+
apiHandler := rateLimitMiddleware(rate.NewLimiter(2, 10),
64+
bodySizeMiddleware(1<<20, /* 1MB */
65+
apiRouter,
66+
),
67+
)
4268

69+
handler := http.NewServeMux()
4370
handler.Handle("POST /fdo/101/msg/{msg}", h.handler)
44-
handler.HandleFunc("/api/v1/rvinfo", func(w http.ResponseWriter, r *http.Request) {
45-
rateLimitMiddleware(limiter, http.HandlerFunc(handlers.RvInfoHandler(h.rvInfo))).ServeHTTP(w, r)
46-
})
47-
handler.HandleFunc("/api/v1/owner/redirect", func(w http.ResponseWriter, r *http.Request) {
48-
rateLimitMiddleware(limiter, http.HandlerFunc(handlers.OwnerInfoHandler)).ServeHTTP(w, r)
49-
})
50-
handler.HandleFunc("/api/v1/to0/", func(w http.ResponseWriter, r *http.Request) {
51-
rateLimitMiddleware(limiter, http.HandlerFunc(handlers.To0Handler(h.rvInfo, h.state))).ServeHTTP(w, r)
52-
})
53-
handler.HandleFunc("/api/v1/vouchers", func(w http.ResponseWriter, r *http.Request) {
54-
rateLimitMiddleware(limiter, http.HandlerFunc(handlers.GetVoucherHandler)).ServeHTTP(w, r)
55-
})
56-
handler.HandleFunc("/api/v1/owner/vouchers", func(w http.ResponseWriter, r *http.Request) {
57-
rateLimitMiddleware(limiter, http.HandlerFunc(handlers.InsertVoucherHandler(h.rvInfo))).ServeHTTP(w, r)
58-
})
71+
handler.Handle("/api/v1/", http.StripPrefix("/api/v1", apiHandler))
5972
handler.HandleFunc("/health", handlers.HealthHandler)
6073
return handler
6174
}

cmd/import_voucher.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ var importVoucherCmd = &cobra.Command{
5656
if err != nil {
5757
return fmt.Errorf("error parsing owner public key from voucher: %w", err)
5858
}
59+
// TODO: Allow importing vouchers with RSA 2048 manufacturer keys
5960
ownerKey, _, err := state.OwnerKey(context.Background(), ov.Header.Val.ManufacturerKey.Type, 3072)
6061
if err != nil {
6162
return fmt.Errorf("error getting owner key: %w", err)

0 commit comments

Comments
 (0)