GPG signature validation. Added utiliy and tests

This commit is contained in:
Bhaskar Brahma 2018-10-22 14:25:19 -07:00
Родитель 74c940d5bb
Коммит 3de23e7d21
4 изменённых файлов: 225 добавлений и 0 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -10,3 +10,4 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.idea

35
internal/gpg/errors.go Normal file
Просмотреть файл

@ -0,0 +1,35 @@
package gpg
type KeyringNotConfiguredError struct {
message string
}
type GpgExecuteError struct {
message string
}
type ValidationFailedForAllKeyringsError struct {
message string
}
func NewKeyringNotConfiguredError(message string) (*KeyringNotConfiguredError) {
return &KeyringNotConfiguredError{message: message}
}
func NewGpgExecuteError(message string) (*GpgExecuteError) {
return &GpgExecuteError{message: message}
}
func NewValidationFailedForAllKeyringsError(message string) (*ValidationFailedForAllKeyringsError) {
return &ValidationFailedForAllKeyringsError{message: message}
}
func (err *KeyringNotConfiguredError) Error() (string) {
return err.message
}
func (err *GpgExecuteError) Error() (string) {
return err.message
}
func (err *ValidationFailedForAllKeyringsError) Error() (string) {
return err.message
}

62
internal/gpg/gpg.go Normal file
Просмотреть файл

@ -0,0 +1,62 @@
package gpg
import (
"fmt"
"github.com/Azure/guestaction-extension-linux/pkg/executil"
)
const (
GPG = "gpg"
GPG_BATCH_OPTION = "--batch"
GPG_DECRYPT_OPTION = "--decrypt"
GPG_KEYRING_OPTION = "--keyring"
GPG_NO_DEFAULT_KEYRING_OPTION = "--no-default-keyring"
GPG_OUTPUT_OPTION = "--output"
GPG_YES_OPTION = "--yes"
)
var cmdHandler executil.Handler = executil.GetCommandHandler()
// Overwrite this value to mock the test
func VerifySignature(signedFilePath string, outputFilePath string, keyrings []string) (success bool, err error) {
/* Verifies a files's signature
Returns:
err == nil if there is an error
err != nil an error was encountered
*/
if (len(keyrings) == 0) || (len(keyrings) == 1 && keyrings[0] == "TODO: get default keyring path") {
return false, NewKeyringNotConfiguredError("GPG kerying path was empty")
}
for _, keyringPath := range keyrings {
if keyringPath == "" || keyringPath == "TODO: get default keyring path" {
continue
}
args := make([]string, 0, 10)
args = append(args, GPG_BATCH_OPTION, GPG_YES_OPTION, GPG_DECRYPT_OPTION)
if keyringPath != "" {
args = append(args, GPG_NO_DEFAULT_KEYRING_OPTION, GPG_KEYRING_OPTION, keyringPath)
}
args = append(args, GPG_OUTPUT_OPTION, outputFilePath, signedFilePath)
cmd := executil.NewCommand(GPG, args...)
// execute the command
cmdHandler.Execute(&cmd)
ret := cmd.ExitCode
err = cmd.CommandError
if err != nil {
// TODO: trace signature validation success
return false, NewGpgExecuteError(err.Error())
}
if ret != 0 {
return false, NewGpgExecuteError(fmt.Sprintf("Gpg execution returned code: %v", ret))
}
return true, nil
// TODO: trace signature validation failure
}
return false, NewValidationFailedForAllKeyringsError("No GPG keyring was able to verify the signed file")
}

127
internal/gpg/gpg_test.go Normal file
Просмотреть файл

@ -0,0 +1,127 @@
package gpg
import (
"errors"
"github.com/Azure/guestaction-extension-linux/pkg/executil"
"os"
"testing"
)
type CommandHandlerMock struct {
commandToExecute func(*executil.Command) ()
}
type TestWithAssert testing.T
func (cmdH CommandHandlerMock) Execute(command *executil.Command) {
cmdH.commandToExecute(command)
}
func SuccessfulExecution(cmd *executil.Command) () {
(*cmd).ExitCode = 0
(*cmd).IsSuccessful = true
(*cmd).CommandError = nil
}
func FailedExecution(cmd *executil.Command) () {
(*cmd).ExitCode = 1
(*cmd).IsSuccessful = false
(*cmd).CommandError = errors.New("expected test error")
}
func VerifyParametersInRightSpot(cmd *executil.Command, t *TestWithAssert, signedFilePath string, outputFilePath string, keyringpath string) () {
t.AssertStringsAreEqual(cmd.Name, GPG)
for index, value := range cmd.Arguments {
switch value {
case signedFilePath:
t.AssertIntAreEqual(index, len(cmd.Arguments)-1)
case outputFilePath:
t.AssertStringsAreEqual(cmd.Arguments[index-1], GPG_OUTPUT_OPTION)
case keyringpath:
t.AssertStringsAreEqual(cmd.Arguments[index-1], GPG_KEYRING_OPTION)
t.AssertStringsAreEqual(cmd.Arguments[index-2], GPG_NO_DEFAULT_KEYRING_OPTION)
}
}
(*cmd).ExitCode = 0
(*cmd).IsSuccessful = true
(*cmd).CommandError = nil
}
func TestGpgValidationSucceedsMock(t *testing.T) {
cmdHandler = CommandHandlerMock{commandToExecute: SuccessfulExecution}
success, err := VerifySignature("mockPath", "mockPath", []string{"keyring1", "keyring2"})
if err != nil || !success {
t.Fatal(err.Error())
}
}
func TestVerifyFailsWithExecutionFails(t *testing.T) {
cmdHandler = CommandHandlerMock{commandToExecute: FailedExecution}
success, err := VerifySignature("mockPath", "mockPath", []string{"keyring1", "keyring2"})
_, typeMatched := err.(*GpgExecuteError)
if typeMatched && !success {
return
}
t.Fatal("Error was of unexpected type")
}
func TestGpgValidationSucceeds(t *testing.T) {
// skip this test can't use real gpg keyring
t.SkipNow()
signedFilePath := "./testresources/helloworld.py.asc"
outputFilePath := "./testoutput/helloworld.py"
keyringPath := "./testresources/testkeyring.gpg"
FailIfFileNotExist(signedFilePath, t)
FailIfFileNotExist(keyringPath, t)
if t.Failed() {
t.Fatal("Cannot find required files. Test cannot proceed")
}
success, err := VerifySignature(signedFilePath, outputFilePath, []string{keyringPath})
if err != nil || !success {
t.Fatal(err.Error())
}
}
func TestGpgKeyringPathEmptyThrowsError(t *testing.T) {
cmdHandler = CommandHandlerMock{commandToExecute: SuccessfulExecution}
success, err := VerifySignature("mockPath", "mockPath", nil)
_, typeMatched := err.(*KeyringNotConfiguredError)
if typeMatched && !success {
return
}
t.Fatal("Error was of unexpected type")
}
func TestParametesAreProperlyPassed(t *testing.T) {
signedFilePath := "./testresources/helloworld.py.asc"
outputFilePath := "./testoutput/helloworld.py"
keyringPath := "./testresources/testkeyring.gpg"
tt := TestWithAssert(*t)
cmdHandler = CommandHandlerMock{commandToExecute: func(command *executil.Command) {
VerifyParametersInRightSpot(command, &tt, signedFilePath, outputFilePath, keyringPath)
}}
success, err := VerifySignature(signedFilePath, outputFilePath, []string{keyringPath})
if err != nil || !success {
t.Fatal(err.Error())
}
}
func FailIfFileNotExist(filepath string, t *testing.T) {
if _, err := os.Stat(filepath); os.IsNotExist(err) {
t.Fail()
}
}
func (t *TestWithAssert) AssertStringsAreEqual(actual string, expected string) {
if actual != expected {
t.Errorf("Values are not equal expected: %s, actual: %s", expected, actual)
}
}
func (t *TestWithAssert) AssertIntAreEqual(actual int, expected int) {
if actual != expected {
t.Errorf("Values are not equal expected: %v, actual: %v", expected, actual)
}
}