Skip to content

Implemented package_index.json signature verification #791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 7, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added arduino/security/keys/arduino_public.gpg.key
Binary file not shown.
43 changes: 43 additions & 0 deletions arduino/security/rice-box.go

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions arduino/security/signature_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package security

import (
"testing"

"github.com/arduino/go-paths-helper"
"github.com/stretchr/testify/require"
)

func TestSignatureVerification(t *testing.T) {
res, signer, err := VerifyArduinoDetachedSignature(paths.New("testdata/package_index.json"), paths.New("testdata/package_index.json.sig"))
require.NoError(t, err)
require.NotNil(t, signer)
require.True(t, res)
require.Equal(t, uint64(0x7baf404c2dfab4ae), signer.PrimaryKey.KeyId)

res, signer, err = VerifyArduinoDetachedSignature(paths.New("testdata/invalid_file.json"), paths.New("testdata/package_index.json.sig"))
require.False(t, res)
require.Nil(t, signer)
require.Error(t, err)
}
54 changes: 54 additions & 0 deletions arduino/security/signatures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package security

import (
"fmt"

rice "github.com/GeertJohan/go.rice"
"github.com/arduino/go-paths-helper"
"golang.org/x/crypto/openpgp"
)

// VerifyArduinoDetachedSignature that give signaturePath GPG signature match the given targetPath file
// ant the is an authentic signature from Arduino.
func VerifyArduinoDetachedSignature(targetPath *paths.Path, signaturePath *paths.Path) (bool, *openpgp.Entity, error) {
keysBox, err := rice.FindBox("keys")
if err != nil {
panic("could not find bundled signature keys")
}
arduinoKeyringFile, err := keysBox.Open("arduino_public.gpg.key")
if err != nil {
panic("could not find bundled signature keys")
}
keyRing, err := openpgp.ReadKeyRing(arduinoKeyringFile)
if err != nil {
return false, nil, fmt.Errorf("retrieving Arduino public keys: %s", err)
}

target, err := targetPath.Open()
if err != nil {
return false, nil, fmt.Errorf("opening target file: %s", err)
}
defer target.Close()
signature, err := signaturePath.Open()
if err != nil {
return false, nil, fmt.Errorf("opening signature file: %s", err)
}
defer signature.Close()
signer, err := openpgp.CheckDetachedSignature(keyRing, target, signature)
return (signer != nil && err == nil), signer, err
}
7,651 changes: 7,651 additions & 0 deletions arduino/security/testdata/package_index.json

Large diffs are not rendered by default.

Binary file added arduino/security/testdata/package_index.json.sig
Binary file not shown.
59 changes: 52 additions & 7 deletions commands/instances.go
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ import (
"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/arduino/libraries"
"github.com/arduino/arduino-cli/arduino/libraries/librariesmanager"
"github.com/arduino/arduino-cli/arduino/security"
"github.com/arduino/arduino-cli/cli/globals"
"github.com/arduino/arduino-cli/configuration"
rpc "github.com/arduino/arduino-cli/rpc/commands"
@@ -208,14 +209,14 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexReq, downloadCB Downlo

logrus.WithField("url", URL).Print("Updating index")

tmpFile, err := ioutil.TempFile("", "")
if err != nil {
return nil, fmt.Errorf("creating temp file for download: %s", err)
}
if err := tmpFile.Close(); err != nil {
return nil, fmt.Errorf("creating temp file for download: %s", err)
var tmp *paths.Path
if tmpFile, err := ioutil.TempFile("", ""); err != nil {
return nil, fmt.Errorf("creating temp file for index download: %s", err)
} else if err := tmpFile.Close(); err != nil {
return nil, fmt.Errorf("creating temp file for index download: %s", err)
} else {
tmp = paths.New(tmpFile.Name())
}
tmp := paths.New(tmpFile.Name())
defer tmp.Remove()

config, err := GetDownloaderConfig()
@@ -232,6 +233,45 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexReq, downloadCB Downlo
return nil, fmt.Errorf("downloading index %s: %s", URL, d.Error())
}

// Check for signature
var tmpSig *paths.Path
var coreIndexSigPath *paths.Path
if URL.Hostname() == "downloads.arduino.cc" {
URLSig, err := url.Parse(URL.String())
if err != nil {
return nil, fmt.Errorf("parsing url for index signature check: %s", err)
}
URLSig.Path += ".sig"

if t, err := ioutil.TempFile("", ""); err != nil {
return nil, fmt.Errorf("creating temp file for index signature download: %s", err)
} else if err := t.Close(); err != nil {
return nil, fmt.Errorf("creating temp file for index signature download: %s", err)
} else {
tmpSig = paths.New(t.Name())
}
defer tmpSig.Remove()

d, err := downloader.DownloadWithConfig(tmpSig.String(), URLSig.String(), *config)
if err != nil {
return nil, fmt.Errorf("downloading index signature %s: %s", URLSig, err)
}

coreIndexSigPath = indexpath.Join(path.Base(URLSig.Path))
Download(d, "Updating index: "+coreIndexSigPath.Base(), downloadCB)
if d.Error() != nil {
return nil, fmt.Errorf("downloading index signature %s: %s", URL, d.Error())
}

valid, _, err := security.VerifyArduinoDetachedSignature(tmp, tmpSig)
if err != nil {
return nil, fmt.Errorf("signature verification error: %s", err)
}
if !valid {
return nil, fmt.Errorf("index has an invalid signature")
}
}

if _, err := packageindex.LoadIndex(tmp); err != nil {
return nil, fmt.Errorf("invalid package index in %s: %s", URL, err)
}
@@ -243,6 +283,11 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexReq, downloadCB Downlo
if err := tmp.CopyTo(coreIndexPath); err != nil {
return nil, fmt.Errorf("saving downloaded index %s: %s", URL, err)
}
if tmpSig != nil {
if err := tmpSig.CopyTo(coreIndexSigPath); err != nil {
return nil, fmt.Errorf("saving downloaded index signature: %s", err)
}
}
}
if _, err := Rescan(id); err != nil {
return nil, fmt.Errorf("rescanning filesystem: %s", err)
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@ module github.com/arduino/arduino-cli

go 1.14

// This one must be kept until https://github.com/GeertJohan/go.rice/pull/159 is merged
replace github.com/GeertJohan/go.rice => github.com/cmaglie/go.rice v1.0.1

require (
bou.ke/monkey v1.0.1
github.com/GeertJohan/go.rice v1.0.0
@@ -38,12 +41,13 @@ require (
github.com/spf13/cobra v1.0.0
github.com/spf13/jwalterweatherman v1.0.0
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.6.1
go.bug.st/cleanup v1.0.0
go.bug.st/downloader/v2 v2.0.1
go.bug.st/relaxed-semver v0.0.0-20190922224835-391e10178d18
go.bug.st/serial v1.0.0
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 // indirect
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect
golang.org/x/text v0.3.2
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -30,6 +30,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cmaglie/go.rice v1.0.1 h1:3jnwuiZ7w6VZ9348Ux6BNdknTsUsUfQxk/uqZZw7OdQ=
github.com/cmaglie/go.rice v1.0.1/go.mod h1:6n5Svb/wfzAWT9V3ZtDe8xk6rjbOX/cHu3obOH0Loew=
github.com/cmaglie/pb v1.0.27 h1:ynGj8vBXR+dtj4B7Q/W/qGt31771Ux5iFfRQBnwdQiA=
github.com/cmaglie/pb v1.0.27/go.mod h1:GilkKZMXYjBA4NxItWFfO+lwkp59PLHQ+IOW/b/kmZI=
github.com/codeclysm/cc v1.2.2 h1:1ChS4EvWTjw6bH2sd6QiMcmih0itVVrWdh9MmOliX/I=
@@ -144,6 +146,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228 h1:Cvfd2dOlXIPTeEkOT/h8PyK4phBngOM4at9/jlgy7d4=
github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228/go.mod h1:MGuVJ1+5TX1SCoO2Sx0eAnjpdRytYla2uC1YIZfkC9c=
@@ -209,6 +212,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -305,5 +310,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=