diff --git a/internal/arduino/resources/resources_test.go b/internal/arduino/resources/resources_test.go index 0ca1bdd884b..4546d88c314 100644 --- a/internal/arduino/resources/resources_test.go +++ b/internal/arduino/resources/resources_test.go @@ -131,29 +131,45 @@ func TestIndexDownloadAndSignatureWithinArchive(t *testing.T) { require.NoError(t, err) defer ln.Close() go server.Serve(ln) + defer server.Close() - validIdxURL, err := url.Parse("http://" + ln.Addr().String() + "/valid/package_index.tar.bz2") - require.NoError(t, err) - idxResource := &IndexResource{URL: validIdxURL} - destDir, err := paths.MkTempDir("", "") - require.NoError(t, err) - defer destDir.RemoveAll() - err = idxResource.Download(ctx, destDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig()) - require.NoError(t, err) - require.True(t, destDir.Join("package_index.json").Exist()) - require.True(t, destDir.Join("package_index.json.sig").Exist()) - - invalidIdxURL, err := url.Parse("http://" + ln.Addr().String() + "/invalid/package_index.tar.bz2") - require.NoError(t, err) - invIdxResource := &IndexResource{URL: invalidIdxURL} - invDestDir, err := paths.MkTempDir("", "") - require.NoError(t, err) - defer invDestDir.RemoveAll() - err = invIdxResource.Download(ctx, invDestDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig()) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid signature") - require.False(t, invDestDir.Join("package_index.json").Exist()) - require.False(t, invDestDir.Join("package_index.json.sig").Exist()) + { + validIdxURL, err := url.Parse("http://" + ln.Addr().String() + "/valid_signature_in_the_future/package_index.tar.bz2") + require.NoError(t, err) + idxResource := &IndexResource{URL: validIdxURL} + destDir, err := paths.MkTempDir("", "") + require.NoError(t, err) + defer destDir.RemoveAll() + err = idxResource.Download(ctx, destDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig()) + require.NoError(t, err) + require.True(t, destDir.Join("package_index.json").Exist()) + require.True(t, destDir.Join("package_index.json.sig").Exist()) + } + { + validIdxURL, err := url.Parse("http://" + ln.Addr().String() + "/valid/package_index.tar.bz2") + require.NoError(t, err) + idxResource := &IndexResource{URL: validIdxURL} + destDir, err := paths.MkTempDir("", "") + require.NoError(t, err) + defer destDir.RemoveAll() + err = idxResource.Download(ctx, destDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig()) + require.NoError(t, err) + require.True(t, destDir.Join("package_index.json").Exist()) + require.True(t, destDir.Join("package_index.json.sig").Exist()) + } + { + invalidIdxURL, err := url.Parse("http://" + ln.Addr().String() + "/invalid/package_index.tar.bz2") + require.NoError(t, err) + invIdxResource := &IndexResource{URL: invalidIdxURL} + invDestDir, err := paths.MkTempDir("", "") + require.NoError(t, err) + defer invDestDir.RemoveAll() + err = invIdxResource.Download(ctx, invDestDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig()) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid signature") + require.False(t, invDestDir.Join("package_index.json").Exist()) + require.False(t, invDestDir.Join("package_index.json.sig").Exist()) + } } func TestIndexFileName(t *testing.T) { diff --git a/internal/arduino/resources/testdata/valid_signature_in_the_future/package_index.tar.bz2 b/internal/arduino/resources/testdata/valid_signature_in_the_future/package_index.tar.bz2 new file mode 100644 index 00000000000..22284c67be4 Binary files /dev/null and b/internal/arduino/resources/testdata/valid_signature_in_the_future/package_index.tar.bz2 differ diff --git a/internal/arduino/security/signatures.go b/internal/arduino/security/signatures.go index fb6ed9b0697..38416bac4b6 100644 --- a/internal/arduino/security/signatures.go +++ b/internal/arduino/security/signatures.go @@ -16,14 +16,19 @@ package security import ( + "bytes" "embed" "errors" "io" "os" + "time" "github.com/ProtonMail/go-crypto/openpgp" + pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/arduino/arduino-cli/internal/i18n" "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" ) //go:embed keys/* @@ -71,16 +76,30 @@ func VerifySignature(targetPath *paths.Path, signaturePath *paths.Path, arduinoK if err != nil { return false, nil, errors.New(i18n.Tr("retrieving Arduino public keys: %s", err)) } - target, err := targetPath.Open() + target, err := targetPath.ReadFile() if err != nil { return false, nil, errors.New(i18n.Tr("opening target file: %s", err)) } - defer target.Close() - signature, err := signaturePath.Open() + signature, err := signaturePath.ReadFile() if err != nil { return false, nil, errors.New(i18n.Tr("opening signature file: %s", err)) } - defer signature.Close() - signer, err := openpgp.CheckDetachedSignature(keyRing, target, signature, nil) + signer, err := openpgp.CheckDetachedSignature(keyRing, bytes.NewBuffer(target), bytes.NewBuffer(signature), nil) + + // Some users reported spurious "expired signature" errors. After some investigation + // we found that all of them had a wrong system date set on their machine, with + // a date set in the past. + // Even if the error says that the signature is "expired", it's actually a + // signature that is not yet valid (it will be in the future). + // Since we could not trust the system clock, we recheck the signature with a date set + // in the future, so we may avoid to display a difficult to understand error to the user. + year2100 := time.Date(2100, 0, 0, 0, 0, 0, 0, time.UTC) + if errors.Is(err, pgperrors.ErrSignatureExpired) && time.Now().Before(year2100) { + logrus.Warn("Ignoring expired signature") + signer, err = openpgp.CheckDetachedSignature(keyRing, bytes.NewBuffer(target), bytes.NewBuffer(signature), &packet.Config{ + Time: func() time.Time { return year2100 }, + }) + } + return (signer != nil && err == nil), signer, err }