GPG signature validation. Added utiliy and tests
This commit is contained in:
Родитель
74c940d5bb
Коммит
3de23e7d21
|
@ -10,3 +10,4 @@
|
|||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
.idea
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче