diff --git a/signature/fixtures/crypto_signer/P256/private.pem b/signature/fixtures/crypto_signer/P256/private.pem new file mode 100644 index 0000000..c8b0ab2 --- /dev/null +++ b/signature/fixtures/crypto_signer/P256/private.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MHcCAQEEIKG2wc1cI8aGVw4f1GhMfGbhMiDZAE1NeNAyil63wbXPoAoGCCqGSM49 +AwEHoUQDQgAETbursv8d9C1Fa/89/d2FqSo2sw/zeke9F0IWiOKGJuuXENVcp8U5 +FfxStK9sKVH4tPMCpVNPNr1pMa/3NFVlLA== +-----END PRIVATE KEY----- diff --git a/signature/fixtures/crypto_signer/P256/public.pem b/signature/fixtures/crypto_signer/P256/public.pem new file mode 100644 index 0000000..5a5010e --- /dev/null +++ b/signature/fixtures/crypto_signer/P256/public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETbursv8d9C1Fa/89/d2FqSo2sw/z +eke9F0IWiOKGJuuXENVcp8U5FfxStK9sKVH4tPMCpVNPNr1pMa/3NFVlLA== +-----END PUBLIC KEY----- diff --git a/signature/sign_test.go b/signature/sign_test.go index 3b56ceb..13c6b58 100644 --- a/signature/sign_test.go +++ b/signature/sign_test.go @@ -2,8 +2,13 @@ package signature import ( "context" + "crypto" + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" "errors" "fmt" + "io" "math/rand" "os" "path" @@ -134,6 +139,126 @@ func TestSignVerify(t *testing.T) { } } +var _ crypto.Signer = MockCryptoSigner{} + +type MockCryptoSigner struct { + privateKey crypto.PrivateKey + publickKey crypto.PublicKey +} + +func (m MockCryptoSigner) Public() crypto.PublicKey { return m.publickKey } + +func (m MockCryptoSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + return ecdsa.SignASN1(rand, m.privateKey.(*ecdsa.PrivateKey), digest) +} + +func (m MockCryptoSigner) Algorithm() jwa.KeyAlgorithm { + return jwa.ES256 +} + +func TestSignVerifyCryptoSigner(t *testing.T) { + + t.Parallel() + ctx := context.Background() + + step := &pipeline.CommandStep{ + Command: "llamas", + Plugins: pipeline.Plugins{ + { + Source: "some-plugin#v1.0.0", + Config: nil, + }, + { + Source: "another-plugin#v3.4.5", + Config: map[string]any{"llama": "Kuzco"}, + }, + }, + Env: map[string]string{ + "CONTEXT": "cats", + "DEPLOY": "0", + }, + } + // The pipeline-level env that the agent uploads: + signEnv := map[string]string{ + "DEPLOY": "1", + } + + // The backend combines the pipeline and step envs, providing a new env: + verifyEnv := map[string]string{ + "CONTEXT": "cats", + "DEPLOY": "0", + "MISC": "llama drama", + } + + stepWithInvariants := &CommandStepWithInvariants{ + CommandStep: *step, + RepositoryURL: "fake-repo", + } + + cases := []struct { + name string + alg jwa.SignatureAlgorithm + expectedSignature string + }{ + { + name: "should sign using crypto.Signer", + alg: jwa.ES256, + expectedSignature: "eyJhbGciOiJFUzI1NiJ9..Op5KSww95n5s1b9jz0Me5UGqUQPcHzEIFvkWTB_yEv6qEDnnFUO1XsC5592fQoAcB0VnPnHaK31iSiCypREIdA", + }, + } + + wd, err := os.Getwd() + if err != nil { + t.Fatalf("os.Getwd() error = %v", err) + } + + privateKeyPath := path.Join(wd, "fixtures", "crypto_signer", "P256", "private.pem") + pemPrivateKey, err := os.ReadFile(privateKeyPath) + if err != nil { + t.Fatalf("os.ReadFile(%q) error = %v", privateKeyPath, err) + } + + block, _ := pem.Decode([]byte(pemPrivateKey)) + x509Encoded := block.Bytes + privateKey, _ := x509.ParseECPrivateKey(x509Encoded) + + publicKeyPath := path.Join(wd, "fixtures", "crypto_signer", "P256", "public.pem") + pemPublicKey, err := os.ReadFile(publicKeyPath) + if err != nil { + t.Fatalf("os.ReadFile(%q) error = %v", publicKeyPath, err) + } + + blockPub, _ := pem.Decode([]byte(pemPublicKey)) + x509EncodedPub := blockPub.Bytes + genericPublicKey, _ := x509.ParsePKIXPublicKey(x509EncodedPub) + publicKey := genericPublicKey.(*ecdsa.PublicKey) + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + sKey := MockCryptoSigner{ + privateKey: privateKey, + publickKey: publicKey, + } + + sig, err := Sign(ctx, sKey, stepWithInvariants, WithEnv(signEnv)) + if err != nil { + t.Fatalf("Sign(ctx, sKey, %v, WithEnv(%v)) error = %v", stepWithInvariants, signEnv, err) + } + + if sig.Algorithm != tc.alg.String() { + t.Errorf("Signature.Algorithm = %v, want %v", sig.Algorithm, tc.alg) + } + + if err := Verify(ctx, sig, sKey, stepWithInvariants, WithEnv(verifyEnv)); err != nil { + t.Errorf("Verify(ctx, %v, verifier, %v, WithEnv(%v)) = %v", sig, stepWithInvariants, verifyEnv, err) + } + + }) + } +} + type testFields map[string]any func (m testFields) SignedFields() (map[string]any, error) { return m, nil }