| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- package plugin_packager
- import (
- "crypto/rsa"
- "embed"
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "testing"
- "github.com/langgenius/dify-plugin-daemon/internal/utils/encryption"
- "github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/decoder"
- "github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/packager"
- "github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/signer"
- "github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/signer/withkey"
- )
- //go:embed testdata/manifest.yaml
- var manifest []byte
- //go:embed testdata/neko.yaml
- var neko []byte
- //go:embed testdata/.difyignore
- var dify_ignore []byte
- //go:embed testdata/ignored
- var ignored []byte
- //go:embed testdata/_assets/test.svg
- var test_svg []byte
- //go:embed testdata/keys
- var keys embed.FS
- // createMinimalPlugin creates a minimal test plugin and returns the zip file
- func createMinimalPlugin(t *testing.T) []byte {
- // create a temp directory
- tempDir := t.TempDir()
- // create basic files
- if err := os.WriteFile(filepath.Join(tempDir, "manifest.yaml"), manifest, 0644); err != nil {
- t.Errorf("failed to write manifest: %s", err.Error())
- return nil
- }
- if err := os.WriteFile(filepath.Join(tempDir, "neko.yaml"), neko, 0644); err != nil {
- t.Errorf("failed to write neko: %s", err.Error())
- return nil
- }
- // create _assets directory and files
- if err := os.MkdirAll(filepath.Join(tempDir, "_assets"), 0755); err != nil {
- t.Errorf("failed to create _assets directory: %s", err.Error())
- return nil
- }
- if err := os.WriteFile(filepath.Join(tempDir, "_assets/test.svg"), test_svg, 0644); err != nil {
- t.Errorf("failed to write test.svg: %s", err.Error())
- return nil
- }
- // create decoder
- originDecoder, err := decoder.NewFSPluginDecoder(tempDir)
- if err != nil {
- t.Errorf("failed to create decoder: %s", err.Error())
- return nil
- }
- // create packager
- packager := packager.NewPackager(originDecoder)
- // pack
- zip, err := packager.Pack(52428800)
- if err != nil {
- t.Errorf("failed to pack: %s", err.Error())
- return nil
- }
- return zip
- }
- func TestPackagerAndVerifier(t *testing.T) {
- // create a temp directory
- tempDir := t.TempDir()
- // create manifest
- if err := os.WriteFile(filepath.Join(tempDir, "manifest.yaml"), manifest, 0644); err != nil {
- t.Errorf("failed to write manifest: %s", err.Error())
- return
- }
- if err := os.WriteFile(filepath.Join(tempDir, "neko.yaml"), neko, 0644); err != nil {
- t.Errorf("failed to write neko: %s", err.Error())
- return
- }
- // create .difyignore
- if err := os.WriteFile(filepath.Join(tempDir, ".difyignore"), dify_ignore, 0644); err != nil {
- t.Errorf("failed to write .difyignore: %s", err.Error())
- return
- }
- // create ignored
- if err := os.WriteFile(filepath.Join(tempDir, "ignored"), ignored, 0644); err != nil {
- t.Errorf("failed to write ignored: %s", err.Error())
- return
- }
- // create ignored_paths
- if err := os.MkdirAll(filepath.Join(tempDir, "ignored_paths"), 0755); err != nil {
- t.Errorf("failed to create ignored_paths directory: %s", err.Error())
- return
- }
- // create ignored_paths/ignored
- if err := os.WriteFile(filepath.Join(tempDir, "ignored_paths/ignored"), ignored, 0644); err != nil {
- t.Errorf("failed to write ignored_paths/ignored: %s", err.Error())
- return
- }
- if err := os.MkdirAll(filepath.Join(tempDir, "_assets"), 0755); err != nil {
- t.Errorf("failed to create _assets directory: %s", err.Error())
- return
- }
- if err := os.WriteFile(filepath.Join(tempDir, "_assets/test.svg"), test_svg, 0644); err != nil {
- t.Errorf("failed to write test.svg: %s", err.Error())
- return
- }
- originDecoder, err := decoder.NewFSPluginDecoder(tempDir)
- if err != nil {
- t.Errorf("failed to create decoder: %s", err.Error())
- return
- }
- // walk
- err = originDecoder.Walk(func(filename string, dir string) error {
- if filename == "ignored" {
- return fmt.Errorf("should not walk into ignored")
- }
- if strings.HasPrefix(filename, "ignored_paths") {
- return fmt.Errorf("should not walk into ignored_paths")
- }
- return nil
- })
- if err != nil {
- t.Errorf("failed to walk: %s", err.Error())
- return
- }
- // check assets
- assets, err := originDecoder.Assets()
- if err != nil {
- t.Errorf("failed to get assets: %s", err.Error())
- return
- }
- if assets["test.svg"] == nil {
- t.Errorf("should have test.svg asset, got %v", assets)
- return
- }
- packager := packager.NewPackager(originDecoder)
- // pack
- zip, err := packager.Pack(52428800)
- if err != nil {
- t.Errorf("failed to pack: %s", err.Error())
- return
- }
- // sign
- signed, err := signer.SignPlugin(zip)
- if err != nil {
- t.Errorf("failed to sign: %s", err.Error())
- return
- }
- signedDecoder, err := decoder.NewZipPluginDecoder(signed)
- if err != nil {
- t.Errorf("failed to create zip decoder: %s", err.Error())
- return
- }
- // check assets
- assets, err = signedDecoder.Assets()
- if err != nil {
- t.Errorf("failed to get assets: %s", err.Error())
- return
- }
- if assets["test.svg"] == nil {
- t.Errorf("should have test.svg asset, got %v", assets)
- return
- }
- // verify
- err = decoder.VerifyPlugin(signedDecoder)
- if err != nil {
- t.Errorf("failed to verify: %s", err.Error())
- return
- }
- }
- func TestWrongSign(t *testing.T) {
- // create a minimal test plugin
- zip := createMinimalPlugin(t)
- if zip == nil {
- return
- }
- // sign
- signed, err := signer.SignPlugin(zip)
- if err != nil {
- t.Errorf("failed to sign: %s", err.Error())
- return
- }
- // modify the signed file, signature is at the end of the file
- signed[len(signed)-1] = 0
- signed[len(signed)-2] = 0
- // create a new decoder
- signedDecoder, err := decoder.NewZipPluginDecoder(signed)
- if err != nil {
- t.Errorf("failed to create zip decoder: %s", err.Error())
- return
- }
- // verify (expected to fail)
- err = decoder.VerifyPlugin(signedDecoder)
- if err == nil {
- t.Errorf("should fail to verify")
- return
- }
- }
- // loadPublicKeyFile loads a key file from the embed.FS and returns the public key
- func loadPublicKeyFile(t *testing.T, keyFile string) *rsa.PublicKey {
- keyBytes, err := keys.ReadFile(filepath.Join("testdata/keys", keyFile))
- if err != nil {
- t.Fatalf("failed to read key file: %s", err.Error())
- }
- key, err := encryption.LoadPublicKey(keyBytes)
- if err != nil {
- t.Fatalf("failed to load public key: %s", err.Error())
- }
- return key
- }
- // loadPrivateKeyFile loads a key file from the embed.FS and returns the private key
- func loadPrivateKeyFile(t *testing.T, keyFile string) *rsa.PrivateKey {
- keyBytes, err := keys.ReadFile(filepath.Join("testdata/keys", keyFile))
- if err != nil {
- t.Fatalf("failed to read key file: %s", err.Error())
- }
- key, err := encryption.LoadPrivateKey(keyBytes)
- if err != nil {
- t.Fatalf("failed to load private key: %s", err.Error())
- }
- return key
- }
- // extractPublicKey extracts the key file from the embed.FS and returns the file path
- func extractKeyFile(t *testing.T, keyFile string, tmpDir string) string {
- keyBytes, err := keys.ReadFile(filepath.Join("testdata/keys", keyFile))
- if err != nil {
- t.Fatalf("failed to read key file: %s", err.Error())
- }
- keyPath := filepath.Join(tmpDir, keyFile)
- if err := os.WriteFile(keyPath, keyBytes, 0644); err != nil {
- t.Fatalf("failed to write key file: %s", err.Error())
- }
- return keyPath
- }
- func TestSignPluginWithPrivateKey(t *testing.T) {
- // load public keys from embed.FS
- publicKey1 := loadPublicKeyFile(t, "test_key_pair_1.public.pem")
- publicKey2 := loadPublicKeyFile(t, "test_key_pair_2.public.pem")
- // load private keys from embed.FS
- privateKey1 := loadPrivateKeyFile(t, "test_key_pair_1.private.pem")
- privateKey2 := loadPrivateKeyFile(t, "test_key_pair_2.private.pem")
- // create a minimal test plugin
- zip := createMinimalPlugin(t)
- if zip == nil {
- return
- }
- // sign with private key 1 and create decoder
- signed1, err := withkey.SignPluginWithPrivateKey(zip, privateKey1)
- if err != nil {
- t.Errorf("failed to sign with private key 1: %s", err.Error())
- return
- }
- signedDecoder1, err := decoder.NewZipPluginDecoder(signed1)
- if err != nil {
- t.Errorf("failed to create zip decoder: %s", err.Error())
- return
- }
- // sign with private key 2 and create decoder
- signed2, err := withkey.SignPluginWithPrivateKey(zip, privateKey2)
- if err != nil {
- t.Errorf("failed to sign with private key 2: %s", err.Error())
- return
- }
- signedDecoder2, err := decoder.NewZipPluginDecoder(signed2)
- if err != nil {
- t.Errorf("failed to create zip decoder: %s", err.Error())
- return
- }
- // tamper the signed1 file and create decoder
- modifiedSigned1 := make([]byte, len(signed1))
- copy(modifiedSigned1, signed1)
- modifiedSigned1[len(modifiedSigned1)-10] = 0
- modifiedDecoder1, err := decoder.NewZipPluginDecoder(modifiedSigned1)
- if err != nil {
- t.Errorf("failed to create zip decoder: %s", err.Error())
- return
- }
- // define test cases
- tests := []struct {
- name string
- signedDecoder decoder.PluginDecoder
- publicKeys []*rsa.PublicKey
- expectSuccess bool
- }{
- {
- name: "verify plugin signed with private key 1 using embedded public key (should fail)",
- signedDecoder: signedDecoder1,
- publicKeys: nil, // use embedded public key
- expectSuccess: false,
- },
- {
- name: "verify plugin signed with private key 1 using public key 1 (should succeed)",
- signedDecoder: signedDecoder1,
- publicKeys: []*rsa.PublicKey{publicKey1},
- expectSuccess: true,
- },
- {
- name: "verify plugin signed with private key 1 using public key 2 (should fail)",
- signedDecoder: signedDecoder1,
- publicKeys: []*rsa.PublicKey{publicKey2},
- expectSuccess: false,
- },
- {
- name: "verify plugin signed with private key 2 using public key 1 (should fail)",
- signedDecoder: signedDecoder2,
- publicKeys: []*rsa.PublicKey{publicKey1},
- expectSuccess: false,
- },
- {
- name: "verify plugin signed with private key 2 using public key 2 (should succeed)",
- signedDecoder: signedDecoder2,
- publicKeys: []*rsa.PublicKey{publicKey2},
- expectSuccess: true,
- },
- {
- name: "verify modified plugin signed with private key 1 using public key 1 (should fail)",
- signedDecoder: modifiedDecoder1,
- publicKeys: []*rsa.PublicKey{publicKey1},
- expectSuccess: false,
- },
- {
- name: "verify modified plugin signed with private key 1 using public key 2 (should fail)",
- signedDecoder: modifiedDecoder1,
- publicKeys: []*rsa.PublicKey{publicKey2},
- expectSuccess: false,
- },
- }
- // run test cases
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var err error
- if tt.publicKeys == nil {
- err = decoder.VerifyPlugin(tt.signedDecoder)
- } else {
- err = decoder.VerifyPluginWithPublicKeys(tt.signedDecoder, tt.publicKeys)
- }
- if tt.expectSuccess && err != nil {
- t.Errorf("expected success but got error: %s", err.Error())
- }
- if !tt.expectSuccess && err == nil {
- t.Errorf("expected failure but got success")
- }
- })
- }
- }
- func TestVerifyPluginWithThirdPartyKeys(t *testing.T) {
- // create a temporary directory for the public key files (needed for storing the paths in environment variable)
- tempDir := t.TempDir()
- // extract public keys to files from embed.FS (needed for storing the paths in environment variable)
- publicKey1Path := extractKeyFile(t, "test_key_pair_1.public.pem", tempDir)
- publicKey2Path := extractKeyFile(t, "test_key_pair_2.public.pem", tempDir)
- // load private keys from embed.FS
- privateKey1 := loadPrivateKeyFile(t, "test_key_pair_1.private.pem")
- privateKey2 := loadPrivateKeyFile(t, "test_key_pair_2.private.pem")
- // create a minimal test plugin
- zip := createMinimalPlugin(t)
- if zip == nil {
- return
- }
- // sign with private key 1 and create decoder
- signed1, err := withkey.SignPluginWithPrivateKey(zip, privateKey1)
- if err != nil {
- t.Errorf("failed to sign with private key 1: %s", err.Error())
- return
- }
- signedDecoder1, err := decoder.NewZipPluginDecoder(signed1)
- if err != nil {
- t.Errorf("failed to create zip decoder: %s", err.Error())
- return
- }
- // sign with private key 2 and create decoder
- signed2, err := withkey.SignPluginWithPrivateKey(zip, privateKey2)
- if err != nil {
- t.Errorf("failed to sign with private key 2: %s", err.Error())
- return
- }
- signedDecoder2, err := decoder.NewZipPluginDecoder(signed2)
- if err != nil {
- t.Errorf("failed to create zip decoder: %s", err.Error())
- return
- }
- // tamper the signed1 file and create decoder
- modifiedSigned1 := make([]byte, len(signed1))
- copy(modifiedSigned1, signed1)
- modifiedSigned1[len(modifiedSigned1)-10] = 0
- modifiedDecoder1, err := decoder.NewZipPluginDecoder(modifiedSigned1)
- if err != nil {
- t.Errorf("failed to create zip decoder: %s", err.Error())
- return
- }
- // define test cases
- tests := []struct {
- name string
- keyPaths string
- signedDecoder decoder.PluginDecoder
- expectSuccess bool
- }{
- {
- name: "third-party verification with public key 1 (should succeed)",
- keyPaths: publicKey1Path,
- signedDecoder: signedDecoder1,
- expectSuccess: true,
- },
- {
- name: "third-party verification with public key 2 (should fail)",
- keyPaths: publicKey2Path,
- signedDecoder: signedDecoder1,
- expectSuccess: false,
- },
- {
- name: "third-party verification with both keys (should succeed)",
- keyPaths: fmt.Sprintf("%s,%s", publicKey1Path, publicKey2Path),
- signedDecoder: signedDecoder1,
- expectSuccess: true,
- },
- {
- name: "third-party verification with empty key path (should fail)",
- keyPaths: "",
- signedDecoder: signedDecoder1,
- expectSuccess: false,
- },
- {
- name: "third-party verification with non-existent key path (should fail)",
- keyPaths: "/non/existent/path.pem",
- signedDecoder: signedDecoder1,
- expectSuccess: false,
- },
- {
- name: "third-party verification with multiple keys including non-existent path (should fail)",
- keyPaths: fmt.Sprintf("%s,%s,/non/existent/path.pem", publicKey1Path, publicKey2Path),
- signedDecoder: signedDecoder1,
- expectSuccess: false,
- },
- {
- name: "third-party verification with multiple keys including extra spaces (should succeed)",
- keyPaths: fmt.Sprintf(" %s , %s ", publicKey1Path, publicKey2Path),
- signedDecoder: signedDecoder1,
- expectSuccess: true,
- },
- {
- name: "third-party verification with both keys, for file signed with key 2 (should succeed)",
- keyPaths: fmt.Sprintf("%s,%s", publicKey1Path, publicKey2Path),
- signedDecoder: signedDecoder2,
- expectSuccess: true,
- },
- {
- name: "third-party verification with both keys, for modified file (should fail)",
- keyPaths: fmt.Sprintf("%s,%s", publicKey1Path, publicKey2Path),
- signedDecoder: modifiedDecoder1,
- expectSuccess: false,
- },
- }
- // run test cases
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := decoder.VerifyPluginWithPublicKeyPaths(tt.signedDecoder, strings.Split(tt.keyPaths, ","))
- if tt.expectSuccess && err != nil {
- t.Errorf("expected success but got error: %s", err.Error())
- }
- if !tt.expectSuccess && err == nil {
- t.Errorf("expected failure but got success")
- }
- })
- }
- }
|