shared: Add file verification

Signed-off-by: Thomas Hipp <thomas.hipp@canonical.com>
This commit is contained in:
Thomas Hipp 2018-02-14 17:59:39 +01:00
parent b16fb338d8
commit 0460f8867c
No known key found for this signature in database
GPG Key ID: 993408D1137B7D51
8 changed files with 184 additions and 11 deletions

View File

@ -82,19 +82,35 @@ func Download(file, checksum string) error {
return nil
}
// downloadChecksum downloads or opens URL, and matches fname against the
// sha256sums inside of the downloaded or opened file.
func downloadChecksum(URL string, fname string) (string, error) {
var client http.Client
var (
client http.Client
tempFile *os.File
err error
)
tempFile, err := ioutil.TempFile(os.TempDir(), "sha256.")
if err != nil {
return "", err
}
defer os.Remove(tempFile.Name())
// do not re-download checksum file if it's already present
fi, err := os.Stat(filepath.Join(os.TempDir(), URL))
if err == nil && !fi.IsDir() {
tempFile, err = os.Open(filepath.Join(os.TempDir(), URL))
if err != nil {
return "", err
}
defer os.Remove(tempFile.Name())
} else {
tempFile, err = ioutil.TempFile(os.TempDir(), "sha256.")
if err != nil {
return "", err
}
defer os.Remove(tempFile.Name())
_, err = lxd.DownloadFileSha256(&client, "", nil, nil, "", URL, "", tempFile)
// ignore hash mismatch
if err != nil && !strings.HasPrefix(err.Error(), "Hash mismatch") {
return "", err
_, err = lxd.DownloadFileSha256(&client, "", nil, nil, "", URL, "", tempFile)
// ignore hash mismatch
if err != nil && !strings.HasPrefix(err.Error(), "Hash mismatch") {
return "", err
}
}
tempFile.Seek(0, 0)
@ -102,7 +118,6 @@ func downloadChecksum(URL string, fname string) (string, error) {
scanner := bufio.NewScanner(tempFile)
for scanner.Scan() {
s := strings.Split(scanner.Text(), " ")
//if len(s) == 2 && s[1] == fmt.Sprintf("*%s", filepath.Base(fname)) {
matched, _ := regexp.MatchString(fmt.Sprintf(".*%s", filepath.Base(fname)), s[len(s)-1])
if matched {
return s[0], nil

View File

@ -1,9 +1,13 @@
package shared
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
lxd "github.com/lxc/lxd/shared"
)
// Copy copies a file.
@ -42,3 +46,35 @@ func RunCommand(name string, arg ...string) error {
return cmd.Run()
}
// VerifyFile verifies a file using gpg.
func VerifyFile(signedFile, signatureFile string, keys []string) (bool, error) {
var out string
gpgDir := filepath.Join(os.TempDir(), "distrobuilder.gpg")
err := os.MkdirAll(gpgDir, 0700)
if err != nil {
return false, err
}
defer os.RemoveAll(gpgDir)
out, err = lxd.RunCommand("gpg", append([]string{"--homedir", gpgDir, "--recv-keys"}, keys...)...)
if err != nil {
return false, fmt.Errorf("Failed to receive keys: %s", out)
}
if signatureFile != "" {
out, err = lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signatureFile, signedFile)
if err != nil {
return false, fmt.Errorf("Failed to verify: %s", out)
}
} else {
out, err = lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signedFile)
if err != nil {
return false, fmt.Errorf("Failed to verify: %s", out)
}
}
return true, nil
}

81
shared/util_test.go Normal file
View File

@ -0,0 +1,81 @@
package shared
import (
"fmt"
"log"
"os"
"path/filepath"
"testing"
)
func TestVerifyFile(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
fmt.Errorf("Failed to retrieve working directory: %s", err)
}
testdataDir := filepath.Join(wd, "..", "testdata")
keys := []string{"0x5DE8949A899C8D99"}
tests := []struct {
name string
signedFile string
signatureFile string
keys []string
shouldFail bool
}{
{
"testfile with detached signature",
filepath.Join(testdataDir, "testfile"),
filepath.Join(testdataDir, "testfile.sig"),
keys,
false,
},
{
"testfile with cleartext signature",
filepath.Join(testdataDir, "testfile.asc"),
"",
keys,
false,
},
{
"testfile with invalid cleartext signature",
filepath.Join(testdataDir, "testfile-invalid.asc"),
"",
keys,
true,
},
{
"testfile with normal signature",
filepath.Join(testdataDir, "testfile.gpg"),
"",
keys,
false,
},
{
"no keys",
filepath.Join(testdataDir, "testfile"),
filepath.Join(testdataDir, "testfile.sig"),
[]string{},
true,
},
{
"invalid key",
filepath.Join(testdataDir, "testfile.asc"),
"",
[]string{"0x46181433FBB75451"},
true,
},
}
for i, tt := range tests {
log.Printf("Running test #%d: %s", i, tt.name)
valid, err := VerifyFile(tt.signedFile, tt.signatureFile, tt.keys)
if !tt.shouldFail && !valid {
t.Fatalf("Failed to verify: %s\n%s", tt.name, err)
}
if tt.shouldFail && valid {
t.Fatalf("Expected to fail: %s", tt.name)
}
}
}

1
testdata/testfile vendored Normal file
View File

@ -0,0 +1 @@
I need to be verified.

20
testdata/testfile-invalid.asc vendored Normal file
View File

@ -0,0 +1,20 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
I need to be verified.
-----BEGIN PGP SIGNATURE-----
kQIzBAEBCgAdFiEEYfHyDZhnHyXKGYh8mTQI0RN7fVEFAlqFpu8ACgkQmTQI0RN7
fVGj3w/7BCzAkG995rA/7ba371SW/5uifLKxEn/izWzuJsEO40BN0rzV53XsIqew
TMhudZo2r1lF7L0KkVChCl/E//aGB5srHRmQlogJqjdyw4qCuVmTe/QMadjo67fS
wSqH40p5KCQeLZ33xF60vbMwf7ZwtSesFnCsQyvhu85+FDpuexGKKKDxSmO4WjHV
lL5nDZ0vtSghw3yobGWiYBQ/6MqGLkL6yK0LAY50slywTgAb5WtSE2YCTTLeJOEi
PEWMWbWoRYmN9ijUowo9YP6cKj4Fz0LtbWMBuHDgvO7Zl/qrb57NxRgBM0cCzAnR
zEwjRjcfK7GGk+NyfAGbeabgJT/ATI/51sB3MBJgbd+FcSt4zMUL2qfwFDtrTqK3
7NaKOUh7fVnsGeKY/4DSz0+hJy4qR9JuawDCuiS8CJHzp9LKxKmQDhFfpmFYWOOr
Nqc4PifAc0OQ3n1iJGMZ0I5CSP79hRLu7FTyOEhARAz1VMR9lOmEAT+M7RcbENs6
U06mI5h5tyKyBt0cUKQSKtYGKydR2+ZGVkkjEpodcU9RpRzvBFQMU23XdtVPNnya
sf3ddNIbkaWkF17oxy7PW4ZFnWbA8wATEnnWi3dPIGhRRdS2qJioXFziW/idSkUB
AagCicMVQ1XDX/Hg5HrwUBrGBk1JZ3TTwzZ/kpePgry1XSLuGxI=
=vSiP
-----END PGP SIGNATURE-----

20
testdata/testfile.asc vendored Normal file
View File

@ -0,0 +1,20 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
I need to be verified.
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCgAdFiEEYfHyDZhnHyXKGYh8mTQI0RN7fVEFAlqFpu8ACgkQmTQI0RN7
fVGj3w/7BCzAkG995rA/7ba371SW/5uifLKxEn/izWzuJsEO40BN0rzV53XsIqew
TMhudZo2r1lF7L0KkVChCl/E//aGB5srHRmQlogJqjdyw4qCuVmTe/QMadjo67fS
wSqH40p5KCQeLZ33xF60vbMwf7ZwtSesFnCsQyvhu85+FDpuexGKKKDxSmO4WjHV
lL5nDZ0vtSghw3yobGWiYBQ/6MqGLkL6yK0LAY50slywTgAb5WtSE2YCTTLeJOEi
PEWMWbWoRYmN9ijUowo9YP6cKj4Fz0LtbWMBuHDgvO7Zl/qrb57NxRgBM0cCzAnR
zEwjRjcfK7GGk+NyfAGbeabgJT/ATI/51sB3MBJgbd+FcSt4zMUL2qfwFDtrTqK3
7NaKOUh7fVnsGeKY/4DSz0+hJy4qR9JuawDCuiS8CJHzp9LKxKmQDhFfpmFYWOOr
Nqc4PifAc0OQ3n1iJGMZ0I5CSP79hRLu7FTyOEhARAz1VMR9lOmEAT+M7RcbENs6
U06mI5h5tyKyBt0cUKQSKtYGKydR2+ZGVkkjEpodcU9RpRzvBFQMU23XdtVPNnya
sf3ddNIbkaWkF17oxy7PW4ZFnWbA8wATEnnWi3dPIGhRRdS2qJioXFziW/idSkUB
AagCicMVQ1XDX/Hg5HrwUBrGBk1JZ3TTwzZ/kpePgry1XSLuGxI=
=vSiP
-----END PGP SIGNATURE-----

BIN
testdata/testfile.gpg vendored Normal file

Binary file not shown.

BIN
testdata/testfile.sig vendored Normal file

Binary file not shown.