github.com/kubri/kubri@v0.5.1-0.20240317001612-bda2aaef967e/pkg/crypto/pgp/pgp_test.go (about)

     1  package pgp_test
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  
    12  	"github.com/kubri/kubri/internal/test"
    13  	"github.com/kubri/kubri/pkg/crypto"
    14  	"github.com/kubri/kubri/pkg/crypto/internal/cryptotest"
    15  	"github.com/kubri/kubri/pkg/crypto/pgp"
    16  )
    17  
    18  func TestPGP(t *testing.T) {
    19  	cryptotest.Test(t,
    20  		cryptotest.Implementation[*pgp.PrivateKey, *pgp.PublicKey]{
    21  			NewPrivateKey: func() (*pgp.PrivateKey, error) {
    22  				return pgp.NewPrivateKey("test", "test@example.com")
    23  			},
    24  			MarshalPrivateKey:   pgp.MarshalPrivateKey,
    25  			UnmarshalPrivateKey: pgp.UnmarshalPrivateKey,
    26  			Public:              pgp.Public,
    27  			MarshalPublicKey:    pgp.MarshalPublicKey,
    28  			UnmarshalPublicKey:  pgp.UnmarshalPublicKey,
    29  			Sign:                pgp.Sign,
    30  			Verify:              pgp.Verify,
    31  		},
    32  		cryptotest.WithCmpOptions(test.ComparePGPKeys()),
    33  	)
    34  
    35  	priv, _ := pgp.NewPrivateKey("test", "test@example.com")
    36  	pub := pgp.Public(priv)
    37  	pubBytes, _ := pgp.MarshalPublicKey(pub)
    38  	data := []byte("foo\nbar\nbaz")
    39  	sig, _ := pgp.Sign(priv, data)
    40  	signed, _ := pgp.SignText(priv, data)
    41  
    42  	t.Run("NewPrivateKey", func(t *testing.T) {
    43  		tests := []struct {
    44  			desc  string
    45  			name  string
    46  			email string
    47  			err   bool
    48  		}{
    49  			{
    50  				desc: "name only",
    51  				name: "test",
    52  			},
    53  			{
    54  				desc:  "email only",
    55  				email: "test",
    56  			},
    57  			{
    58  				desc: "missing name & email",
    59  				err:  true,
    60  			},
    61  		}
    62  
    63  		for _, test := range tests {
    64  			_, err := pgp.NewPrivateKey(test.name, test.email)
    65  			if (err == nil) == test.err {
    66  				t.Errorf("%s should return error %t got %t", test.desc, test.err, err == nil)
    67  			}
    68  		}
    69  	})
    70  
    71  	t.Run("SignText", func(t *testing.T) {
    72  		tests := []struct {
    73  			name string
    74  			key  *pgp.PrivateKey
    75  			data []byte
    76  			err  error
    77  		}{
    78  			{
    79  				name: "valid key",
    80  				key:  priv,
    81  				data: data,
    82  			},
    83  			{
    84  				name: "nil key",
    85  				data: data,
    86  				err:  crypto.ErrInvalidKey,
    87  			},
    88  		}
    89  
    90  		for _, test := range tests {
    91  			got, err := pgp.SignText(test.key, data)
    92  			if !errors.Is(err, test.err) {
    93  				t.Errorf("%s should return error %q got %q", test.name, test.err, err)
    94  			} else if test.err == nil {
    95  				gotData, gotSig, err := pgp.Split(got)
    96  				if err != nil {
    97  					t.Errorf("%s failed to split message: %s", test.name, err)
    98  				} else if diff := cmp.Diff(string(test.data), string(gotData)); diff != "" {
    99  					t.Error(test.name, diff)
   100  				} else if test.err == nil && !pgp.Verify(pub, gotData, gotSig) {
   101  					t.Error(test.name, "should pass verification")
   102  				}
   103  			}
   104  		}
   105  	})
   106  
   107  	t.Run("Split", func(t *testing.T) {
   108  		tests := []struct {
   109  			name string
   110  			in   []byte
   111  			want []byte
   112  			err  error
   113  		}{
   114  			{
   115  				name: "valid message",
   116  				in:   signed,
   117  				want: data,
   118  			},
   119  			{
   120  				name: "nil bytes",
   121  				err:  pgp.ErrInvalidMessage,
   122  			},
   123  			{
   124  				name: "unarmored data",
   125  				in:   data,
   126  				err:  pgp.ErrInvalidMessage,
   127  			},
   128  			{
   129  				name: "missing signature",
   130  				in:   []byte("-----BEGIN PGP SIGNED MESSAGE-----\r\nHash: SHA512\r\n\r\ndata\r\n"),
   131  				err:  pgp.ErrInvalidMessage,
   132  			},
   133  			{
   134  				name: "missing data",
   135  				in:   append([]byte{'\n'}, sig...),
   136  				err:  pgp.ErrInvalidMessage,
   137  			},
   138  		}
   139  
   140  		for _, test := range tests {
   141  			gotData, gotSig, err := pgp.Split(test.in)
   142  			if !errors.Is(err, test.err) {
   143  				t.Errorf("%s should return error %q got %q", test.name, test.err, err)
   144  			} else if diff := cmp.Diff(string(test.want), string(gotData)); diff != "" {
   145  				t.Error(test.name, diff)
   146  			} else if test.err == nil && !pgp.Verify(pub, gotData, gotSig) {
   147  				t.Error(test.name, "should pass verification")
   148  			}
   149  		}
   150  	})
   151  
   152  	t.Run("WrongKeyType", func(t *testing.T) {
   153  		t.Run("MarshalPrivateKey", func(t *testing.T) {
   154  			if _, err := pgp.MarshalPrivateKey(pub); err == nil {
   155  				t.Errorf("should return error")
   156  			}
   157  		})
   158  
   159  		t.Run("MarshalPublicKey", func(t *testing.T) {
   160  			if _, err := pgp.MarshalPublicKey(priv); err == nil {
   161  				t.Errorf("should return error")
   162  			}
   163  		})
   164  
   165  		t.Run("Sign", func(t *testing.T) {
   166  			if _, err := pgp.Sign(pub, data); err == nil {
   167  				t.Errorf("should return error")
   168  			}
   169  		})
   170  	})
   171  
   172  	t.Run("LockedKey", func(t *testing.T) {
   173  		privLocked, _ := priv.Lock([]byte("passphrase"))
   174  
   175  		t.Run("Sign", func(t *testing.T) {
   176  			if _, err := pgp.Sign(privLocked, data); err == nil {
   177  				t.Errorf("should return error")
   178  			}
   179  		})
   180  
   181  		t.Run("Verify", func(t *testing.T) {
   182  			if pgp.Verify(privLocked, data, sig) {
   183  				t.Errorf("should fail")
   184  			}
   185  		})
   186  	})
   187  
   188  	t.Run("GnuPG", func(t *testing.T) {
   189  		if _, err := exec.LookPath("gpg"); err != nil {
   190  			t.Skip("gpg not in path")
   191  		}
   192  
   193  		dir := t.TempDir()
   194  		os.WriteFile(filepath.Join(dir, "key.asc"), pubBytes, 0o600)
   195  		os.WriteFile(filepath.Join(dir, "data"), data, 0o600)
   196  		os.WriteFile(filepath.Join(dir, "data.asc"), sig, 0o600)
   197  		os.WriteFile(filepath.Join(dir, "signed"), signed, 0o600)
   198  
   199  		baseArgs := []string{"--no-default-keyring", "--keyring", "keyring.gpg"}
   200  		arguments := [][]string{
   201  			{"--import", "key.asc"},  // Create keybox & import key.
   202  			{"--verify", "data.asc"}, // Verify detached signature.
   203  			{"--verify", "signed"},   // Verify signed message.
   204  		}
   205  
   206  		for _, a := range arguments {
   207  			cmd := exec.Command("gpg", append(baseArgs, a...)...)
   208  			cmd.Dir = dir
   209  			out, err := cmd.CombinedOutput()
   210  			if err != nil {
   211  				t.Fatal(a, err, string(out))
   212  			}
   213  			t.Log(a, "\n"+string(out))
   214  		}
   215  	})
   216  }