Skip to content

Commit a5be7b6

Browse files
committed
make the signaturePubKey overwritable
1 parent 5fcb7c4 commit a5be7b6

File tree

10 files changed

+157
-117
lines changed

10 files changed

+157
-117
lines changed

conn.go

+80-77
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919

2020
import (
2121
"bytes"
22+
"crypto/rsa"
2223
"encoding/json"
2324
"errors"
2425
"fmt"
@@ -79,111 +80,113 @@ type Upload struct {
7980

8081
var uploadStatusStr = "ProgrammerStatus"
8182

82-
func uploadHandler(c *gin.Context) {
83-
data := new(Upload)
84-
if err := c.BindJSON(data); err != nil {
85-
c.String(http.StatusBadRequest, fmt.Sprintf("err with the payload. %v", err.Error()))
86-
return
87-
}
88-
89-
log.Printf("%+v %+v %+v %+v %+v %+v", data.Port, data.Board, data.Rewrite, data.Commandline, data.Extra, data.Filename)
90-
91-
if data.Port == "" {
92-
c.String(http.StatusBadRequest, "port is required")
93-
return
94-
}
95-
96-
if data.Board == "" {
97-
c.String(http.StatusBadRequest, "board is required")
98-
log.Error("board is required")
99-
return
100-
}
101-
102-
if !data.Extra.Network {
103-
if data.Signature == "" {
104-
c.String(http.StatusBadRequest, "signature is required")
83+
func uploadHandler(pubKey *rsa.PublicKey) func(*gin.Context) {
84+
return func(c *gin.Context) {
85+
data := new(Upload)
86+
if err := c.BindJSON(data); err != nil {
87+
c.String(http.StatusBadRequest, fmt.Sprintf("err with the payload. %v", err.Error()))
10588
return
10689
}
10790

108-
if data.Commandline == "" {
109-
c.String(http.StatusBadRequest, "commandline is required for local board")
91+
log.Printf("%+v %+v %+v %+v %+v %+v", data.Port, data.Board, data.Rewrite, data.Commandline, data.Extra, data.Filename)
92+
93+
if data.Port == "" {
94+
c.String(http.StatusBadRequest, "port is required")
11095
return
11196
}
11297

113-
err := utilities.VerifyInput(data.Commandline, data.Signature)
114-
115-
if err != nil {
116-
c.String(http.StatusBadRequest, "signature is invalid")
98+
if data.Board == "" {
99+
c.String(http.StatusBadRequest, "board is required")
100+
log.Error("board is required")
117101
return
118102
}
119-
}
120103

121-
buffer := bytes.NewBuffer(data.Hex)
104+
if !data.Extra.Network {
105+
if data.Signature == "" {
106+
c.String(http.StatusBadRequest, "signature is required")
107+
return
108+
}
122109

123-
filePath, err := utilities.SaveFileonTempDir(data.Filename, buffer)
124-
if err != nil {
125-
c.String(http.StatusBadRequest, err.Error())
126-
return
127-
}
110+
if data.Commandline == "" {
111+
c.String(http.StatusBadRequest, "commandline is required for local board")
112+
return
113+
}
128114

129-
tmpdir, err := os.MkdirTemp("", "extrafiles")
130-
if err != nil {
131-
c.String(http.StatusBadRequest, err.Error())
132-
return
133-
}
115+
err := utilities.VerifyInput(data.Commandline, data.Signature, pubKey)
134116

135-
for _, extraFile := range data.ExtraFiles {
136-
path, err := utilities.SafeJoin(tmpdir, extraFile.Filename)
137-
if err != nil {
138-
c.String(http.StatusBadRequest, err.Error())
139-
return
117+
if err != nil {
118+
c.String(http.StatusBadRequest, "signature is invalid")
119+
return
120+
}
140121
}
141-
log.Printf("Saving %s on %s", extraFile.Filename, path)
142122

143-
err = os.MkdirAll(filepath.Dir(path), 0744)
144-
if err != nil {
145-
c.String(http.StatusBadRequest, err.Error())
146-
return
147-
}
123+
buffer := bytes.NewBuffer(data.Hex)
148124

149-
err = os.WriteFile(path, extraFile.Hex, 0644)
125+
filePath, err := utilities.SaveFileonTempDir(data.Filename, buffer)
150126
if err != nil {
151127
c.String(http.StatusBadRequest, err.Error())
152128
return
153129
}
154-
}
155130

156-
if data.Rewrite != "" {
157-
data.Board = data.Rewrite
158-
}
159-
160-
go func() {
161-
// Resolve commandline
162-
commandline, err := upload.PartiallyResolve(data.Board, filePath, tmpdir, data.Commandline, data.Extra, Tools)
131+
tmpdir, err := os.MkdirTemp("", "extrafiles")
163132
if err != nil {
164-
send(map[string]string{uploadStatusStr: "Error", "Msg": err.Error()})
133+
c.String(http.StatusBadRequest, err.Error())
165134
return
166135
}
167136

168-
l := PLogger{Verbose: true}
169-
170-
// Upload
171-
if data.Extra.Network {
172-
err = errors.New("network upload is not supported anymore, pease use OTA instead")
173-
} else {
174-
send(map[string]string{uploadStatusStr: "Starting", "Cmd": "Serial"})
175-
err = upload.Serial(data.Port, commandline, data.Extra, l)
137+
for _, extraFile := range data.ExtraFiles {
138+
path, err := utilities.SafeJoin(tmpdir, extraFile.Filename)
139+
if err != nil {
140+
c.String(http.StatusBadRequest, err.Error())
141+
return
142+
}
143+
log.Printf("Saving %s on %s", extraFile.Filename, path)
144+
145+
err = os.MkdirAll(filepath.Dir(path), 0744)
146+
if err != nil {
147+
c.String(http.StatusBadRequest, err.Error())
148+
return
149+
}
150+
151+
err = os.WriteFile(path, extraFile.Hex, 0644)
152+
if err != nil {
153+
c.String(http.StatusBadRequest, err.Error())
154+
return
155+
}
176156
}
177157

178-
// Handle result
179-
if err != nil {
180-
send(map[string]string{uploadStatusStr: "Error", "Msg": err.Error()})
181-
return
158+
if data.Rewrite != "" {
159+
data.Board = data.Rewrite
182160
}
183-
send(map[string]string{uploadStatusStr: "Done", "Flash": "Ok"})
184-
}()
185161

186-
c.String(http.StatusAccepted, "")
162+
go func() {
163+
// Resolve commandline
164+
commandline, err := upload.PartiallyResolve(data.Board, filePath, tmpdir, data.Commandline, data.Extra, Tools)
165+
if err != nil {
166+
send(map[string]string{uploadStatusStr: "Error", "Msg": err.Error()})
167+
return
168+
}
169+
170+
l := PLogger{Verbose: true}
171+
172+
// Upload
173+
if data.Extra.Network {
174+
err = errors.New("network upload is not supported anymore, pease use OTA instead")
175+
} else {
176+
send(map[string]string{uploadStatusStr: "Starting", "Cmd": "Serial"})
177+
err = upload.Serial(data.Port, commandline, data.Extra, l)
178+
}
179+
180+
// Handle result
181+
if err != nil {
182+
send(map[string]string{uploadStatusStr: "Error", "Msg": err.Error()})
183+
return
184+
}
185+
send(map[string]string{uploadStatusStr: "Done", "Flash": "Ok"})
186+
}()
187+
188+
c.String(http.StatusAccepted, "")
189+
}
187190
}
188191

189192
// PLogger sends the info from the upload to the websocket

globals/globals.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ package globals
1717

1818
// DefaultIndexURL is the default index url
1919
var (
20-
// SignatureKey is the public key used to verify commands and url sent by the builder
21-
SignatureKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvc0yZr1yUSen7qmE3cxF\nIE12rCksDnqR+Hp7o0nGi9123eCSFcJ7CkIRC8F+8JMhgI3zNqn4cUEn47I3RKD1\nZChPUCMiJCvbLbloxfdJrUi7gcSgUXrlKQStOKF5Iz7xv1M4XOP3JtjXLGo3EnJ1\npFgdWTOyoSrA8/w1rck4c/ISXZSinVAggPxmLwVEAAln6Itj6giIZHKvA2fL2o8z\nCeK057Lu8X6u2CG8tRWSQzVoKIQw/PKK6CNXCAy8vo4EkXudRutnEYHEJlPkVgPn\n2qP06GI+I+9zKE37iqj0k1/wFaCVXHXIvn06YrmjQw6I0dDj/60Wvi500FuRVpn9\ntwIDAQAB\n-----END PUBLIC KEY-----"
20+
// ArduinoSignaturePubKey is the public key used to verify commands and url sent by the builder
21+
ArduinoSignaturePubKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvc0yZr1yUSen7qmE3cxF\nIE12rCksDnqR+Hp7o0nGi9123eCSFcJ7CkIRC8F+8JMhgI3zNqn4cUEn47I3RKD1\nZChPUCMiJCvbLbloxfdJrUi7gcSgUXrlKQStOKF5Iz7xv1M4XOP3JtjXLGo3EnJ1\npFgdWTOyoSrA8/w1rck4c/ISXZSinVAggPxmLwVEAAln6Itj6giIZHKvA2fL2o8z\nCeK057Lu8X6u2CG8tRWSQzVoKIQw/PKK6CNXCAy8vo4EkXudRutnEYHEJlPkVgPn\n2qP06GI+I+9zKE37iqj0k1/wFaCVXHXIvn06YrmjQw6I0dDj/60Wvi500FuRVpn9\ntwIDAQAB\n-----END PUBLIC KEY-----"
2222
)

main.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
_ "embed"
2323
"encoding/json"
2424
"flag"
25+
"fmt"
2526
"html/template"
2627
"io"
2728
"os"
@@ -81,7 +82,7 @@ var (
8182
logDump = iniConf.String("log", "off", "off = (default)")
8283
origins = iniConf.String("origins", "", "Allowed origin list for CORS")
8384
portsFilterRegexp = iniConf.String("regex", "usb|acm|com", "Regular expression to filter serial port list")
84-
signatureKey = iniConf.String("signatureKey", globals.SignatureKey, "Pem-encoded public key to verify signed commandlines")
85+
signatureKey = iniConf.String("signatureKey", globals.ArduinoSignaturePubKey, "Pem-encoded public key to verify signed commandlines")
8586
updateURL = iniConf.String("updateUrl", "", "")
8687
verbose = iniConf.Bool("v", true, "show debug logging")
8788
crashreport = iniConf.Bool("crashreport", false, "enable crashreport logging")
@@ -278,9 +279,20 @@ func loop() {
278279
}
279280
}
280281

282+
if signatureKey == nil {
283+
log.Panicf("signature public key cannot be nil")
284+
}
285+
if len(*signatureKey) == 0 {
286+
log.Panicf("signature public key cannot be empty")
287+
}
288+
signaturePubKey, err := utilities.ParseRsaPublicKey(*signatureKey)
289+
if err != nil {
290+
log.Panicf("cannot parse signature key: %s", err)
291+
}
292+
281293
// Instantiate Index and Tools
282294
Index = index.Init(*indexURL, config.GetDataDir())
283-
Tools = tools.New(config.GetDataDir(), Index, logger)
295+
Tools = tools.New(config.GetDataDir(), Index, logger, signaturePubKey)
284296

285297
// see if we are supposed to wait 5 seconds
286298
if *isLaunchSelf {
@@ -454,7 +466,7 @@ func loop() {
454466
r.LoadHTMLFiles("templates/nofirefox.html")
455467

456468
r.GET("/", homeHandler)
457-
r.POST("/upload", uploadHandler)
469+
r.POST("/upload", uploadHandler(signaturePubKey))
458470
r.GET("/socket.io/", socketHandler)
459471
r.POST("/socket.io/", socketHandler)
460472
r.Handle("WS", "/socket.io/", socketHandler)
@@ -464,7 +476,7 @@ func loop() {
464476
r.POST("/update", updateHandler)
465477

466478
// Mount goa handlers
467-
goa := v2.Server(config.GetDataDir().String(), Index)
479+
goa := v2.Server(config.GetDataDir().String(), Index, signaturePubKey)
468480
r.Any("/v2/*path", gin.WrapH(goa))
469481

470482
go func() {

main_test.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ import (
3030

3131
"github.com/arduino/arduino-create-agent/config"
3232
"github.com/arduino/arduino-create-agent/gen/tools"
33+
"github.com/arduino/arduino-create-agent/globals"
3334
"github.com/arduino/arduino-create-agent/index"
3435
"github.com/arduino/arduino-create-agent/upload"
36+
"github.com/arduino/arduino-create-agent/utilities"
3537
v2 "github.com/arduino/arduino-create-agent/v2"
3638
"github.com/gin-gonic/gin"
3739
"github.com/stretchr/testify/require"
@@ -54,7 +56,7 @@ func TestValidSignatureKey(t *testing.T) {
5456

5557
func TestUploadHandlerAgainstEvilFileNames(t *testing.T) {
5658
r := gin.New()
57-
r.POST("/", uploadHandler)
59+
r.POST("/", uploadHandler(utilities.MustParseRsaPublicKey(globals.ArduinoSignaturePubKey)))
5860
ts := httptest.NewServer(r)
5961

6062
uploadEvilFileName := Upload{
@@ -90,7 +92,7 @@ func TestUploadHandlerAgainstEvilFileNames(t *testing.T) {
9092

9193
func TestUploadHandlerAgainstBase64WithoutPaddingMustFail(t *testing.T) {
9294
r := gin.New()
93-
r.POST("/", uploadHandler)
95+
r.POST("/", uploadHandler(utilities.MustParseRsaPublicKey(globals.ArduinoSignaturePubKey)))
9496
ts := httptest.NewServer(r)
9597
defer ts.Close()
9698

@@ -119,7 +121,7 @@ func TestInstallToolV2(t *testing.T) {
119121
Index := index.Init(indexURL, config.GetDataDir())
120122

121123
r := gin.New()
122-
goa := v2.Server(config.GetDataDir().String(), Index)
124+
goa := v2.Server(config.GetDataDir().String(), Index, utilities.MustParseRsaPublicKey(globals.ArduinoSignaturePubKey))
123125
r.Any("/v2/*path", gin.WrapH(goa))
124126
ts := httptest.NewServer(r)
125127

@@ -213,7 +215,7 @@ func TestInstalledHead(t *testing.T) {
213215
Index := index.Init(indexURL, config.GetDataDir())
214216

215217
r := gin.New()
216-
goa := v2.Server(config.GetDataDir().String(), Index)
218+
goa := v2.Server(config.GetDataDir().String(), Index, utilities.MustParseRsaPublicKey(globals.ArduinoSignaturePubKey))
217219
r.Any("/v2/*path", gin.WrapH(goa))
218220
ts := httptest.NewServer(r)
219221

tools/download_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"testing"
2222
"time"
2323

24+
"github.com/arduino/arduino-create-agent/globals"
2425
"github.com/arduino/arduino-create-agent/index"
26+
"github.com/arduino/arduino-create-agent/utilities"
2527
"github.com/arduino/arduino-create-agent/v2/pkgs"
2628
"github.com/arduino/go-paths-helper"
2729
"github.com/stretchr/testify/require"
@@ -128,7 +130,7 @@ func TestDownload(t *testing.T) {
128130
IndexFile: *paths.New("testdata", "test_tool_index.json"),
129131
LastRefresh: time.Now(),
130132
}
131-
testTools := New(tempDirPath, &testIndex, func(msg string) { t.Log(msg) })
133+
testTools := New(tempDirPath, &testIndex, func(msg string) { t.Log(msg) }, utilities.MustParseRsaPublicKey(globals.ArduinoSignaturePubKey))
132134

133135
for _, tc := range testCases {
134136
t.Run(tc.name+"-"+tc.version, func(t *testing.T) {
@@ -175,7 +177,7 @@ func TestCorruptedInstalled(t *testing.T) {
175177
defer fileJSON.Close()
176178
_, err = fileJSON.Write([]byte("Hello"))
177179
require.NoError(t, err)
178-
testTools := New(tempDirPath, &testIndex, func(msg string) { t.Log(msg) })
180+
testTools := New(tempDirPath, &testIndex, func(msg string) { t.Log(msg) }, utilities.MustParseRsaPublicKey(globals.ArduinoSignaturePubKey))
179181
// Download the tool
180182
err = testTools.Download("arduino-test", "avrdude", "6.3.0-arduino17", "keep")
181183
require.NoError(t, err)

tools/tools.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package tools
1717

1818
import (
19+
"crypto/rsa"
1920
"encoding/json"
2021
"path/filepath"
2122
"strings"
@@ -55,14 +56,14 @@ type Tools struct {
5556
// The New functions accept the directory to use to host the tools,
5657
// an index (used to download the tools),
5758
// and a logger to log the operations
58-
func New(directory *paths.Path, index *index.Resource, logger func(msg string)) *Tools {
59+
func New(directory *paths.Path, index *index.Resource, logger func(msg string), signPubKey *rsa.PublicKey) *Tools {
5960
t := &Tools{
6061
directory: directory,
6162
index: index,
6263
logger: logger,
6364
installed: map[string]string{},
6465
mutex: sync.RWMutex{},
65-
tools: pkgs.New(index, directory.String(), "replace"),
66+
tools: pkgs.New(index, directory.String(), "replace", signPubKey),
6667
}
6768
_ = t.readMap()
6869
return t

0 commit comments

Comments
 (0)