Ver código fonte

feat: verify plugin with public keys specified in environment variable in addition to official one

kurokobo 6 meses atrás
pai
commit
6f75bcca43

+ 6 - 0
.env.example

@@ -78,6 +78,12 @@ PPROF_ENABLED=false
 # if want to install plugin without verifying signature, set this to false
 FORCE_VERIFYING_SIGNATURE=true
 
+# Enable or disable third-party signature verification for plugins
+# Set to "true" to allow verification using additional public keys specified in THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS
+THIRD_PARTY_SIGNATURE_VERIFICATION_ENABLED=false
+# A comma-separated list of file paths to public keys in addition to the official public key for signature verification
+THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS=
+
 # proxy settings, example: HTTP_PROXY=http://host.docker.internal:7890
 HTTP_PROXY=
 HTTPS_PROXY=

+ 2 - 1
cmd/commandline/signature/verify.go

@@ -1,6 +1,7 @@
 package signature
 
 import (
+	"crypto/rsa"
 	"os"
 
 	"github.com/langgenius/dify-plugin-daemon/internal/utils/encryption"
@@ -44,7 +45,7 @@ func Verify(difypkgPath string, publicKeyPath string) error {
 		}
 
 		// verify the plugin
-		err = decoder.VerifyPluginWithPublicKey(decoderInstance, publicKey)
+		err = decoder.VerifyPluginWithPublicKeys(decoderInstance, []*rsa.PublicKey{publicKey})
 		if err != nil {
 			log.Error("Failed to verify plugin with provided public key: %v", err)
 			return err

+ 2 - 2
internal/core/plugin_manager/manager.go

@@ -194,11 +194,11 @@ func (p *PluginManager) BackwardsInvocation() dify_invocation.BackwardsInvocatio
 	return p.backwardsInvocation
 }
 
-func (p *PluginManager) SavePackage(plugin_unique_identifier plugin_entities.PluginUniqueIdentifier, pkg []byte) (
+func (p *PluginManager) SavePackage(plugin_unique_identifier plugin_entities.PluginUniqueIdentifier, pkg []byte, thirdPartySignatureVerificationConfig *decoder.ThirdPartySignatureVerificationConfig) (
 	*plugin_entities.PluginDeclaration, error,
 ) {
 	// try to decode the package
-	packageDecoder, err := decoder.NewZipPluginDecoder(pkg)
+	packageDecoder, err := decoder.NewZipPluginDecoderWithThirdPartySignatureVerificationConfig(pkg, thirdPartySignatureVerificationConfig)
 	if err != nil {
 		return nil, err
 	}

+ 12 - 6
internal/service/plugin_decoder.go

@@ -29,12 +29,12 @@ func UploadPluginPkg(
 		return exception.InternalServerError(err).ToResponse()
 	}
 
-	decoder, err := decoder.NewZipPluginDecoderWithSizeLimit(pluginFile, config.MaxPluginPackageSize)
+	decoderInstance, err := decoder.NewZipPluginDecoderWithSizeLimit(pluginFile, config.MaxPluginPackageSize)
 	if err != nil {
 		return exception.BadRequestError(err).ToResponse()
 	}
 
-	pluginUniqueIdentifier, err := decoder.UniqueIdentity()
+	pluginUniqueIdentifier, err := decoderInstance.UniqueIdentity()
 	if err != nil {
 		return exception.BadRequestError(err).ToResponse()
 	}
@@ -45,7 +45,10 @@ func UploadPluginPkg(
 	}
 
 	manager := plugin_manager.Manager()
-	declaration, err := manager.SavePackage(pluginUniqueIdentifier, pluginFile)
+	declaration, err := manager.SavePackage(pluginUniqueIdentifier, pluginFile, &decoder.ThirdPartySignatureVerificationConfig{
+		Enabled:        config.ThirdPartySignatureVerificationEnabled,
+		PublicKeyPaths: config.ThirdPartySignatureVerificationPublicKeys,
+	})
 	if err != nil {
 		return exception.BadRequestError(errors.Join(err, errors.New("failed to save package"))).ToResponse()
 	}
@@ -123,17 +126,20 @@ func UploadPluginBundle(
 					return exception.InternalServerError(errors.Join(errors.New("failed to fetch package from bundle"), err)).ToResponse()
 				} else {
 					// decode and save
-					decoder, err := decoder.NewZipPluginDecoder(asset)
+					decoderInstance, err := decoder.NewZipPluginDecoder(asset)
 					if err != nil {
 						return exception.BadRequestError(errors.Join(errors.New("failed to create package decoder"), err)).ToResponse()
 					}
 
-					pluginUniqueIdentifier, err := decoder.UniqueIdentity()
+					pluginUniqueIdentifier, err := decoderInstance.UniqueIdentity()
 					if err != nil {
 						return exception.BadRequestError(errors.Join(errors.New("failed to get package unique identifier"), err)).ToResponse()
 					}
 
-					declaration, err := manager.SavePackage(pluginUniqueIdentifier, asset)
+					declaration, err := manager.SavePackage(pluginUniqueIdentifier, asset, &decoder.ThirdPartySignatureVerificationConfig{
+						Enabled:        config.ThirdPartySignatureVerificationEnabled,
+						PublicKeyPaths: config.ThirdPartySignatureVerificationPublicKeys,
+					})
 					if err != nil {
 						return exception.InternalServerError(errors.Join(errors.New("failed to save package"), err)).ToResponse()
 					}

+ 5 - 0
internal/types/app/config.go

@@ -84,6 +84,11 @@ type Config struct {
 	// force verifying signature for all plugins, not allowing install plugin not signed
 	ForceVerifyingSignature *bool `envconfig:"FORCE_VERIFYING_SIGNATURE"`
 
+	// enable or disable third-party signature verification for plugins
+	ThirdPartySignatureVerificationEnabled bool `envconfig:"THIRD_PARTY_SIGNATURE_VERIFICATION_ENABLED"  default:"false"`
+	// a comma-separated list of file paths to public keys in addition to the official public key for signature verification
+	ThirdPartySignatureVerificationPublicKeys []string `envconfig:"THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS"  default:""`
+
 	// lifetime state management
 	LifetimeCollectionHeartbeatInterval int `envconfig:"LIFETIME_COLLECTION_HEARTBEAT_INTERVAL"  validate:"required"`
 	LifetimeCollectionGCInterval        int `envconfig:"LIFETIME_COLLECTION_GC_INTERVAL" validate:"required"`

+ 11 - 1
pkg/plugin_packager/decoder/helper.go

@@ -272,7 +272,17 @@ func (p *PluginDecoderHelper) Manifest(decoder PluginDecoder) (plugin_entities.P
 	dec.FillInDefaultValues()
 
 	// verify signature
-	dec.Verified = VerifyPlugin(decoder) == nil
+	// for ZipPluginDecoder, use the third party signature verification if it is enabled
+	if zipDecoder, ok := decoder.(*ZipPluginDecoder); ok {
+		config := zipDecoder.thirdPartySignatureVerificationConfig
+		if config != nil && config.Enabled && len(config.PublicKeyPaths) > 0 {
+			dec.Verified = VerifyPluginWithPublicKeyPaths(decoder, config.PublicKeyPaths) == nil
+		} else {
+			dec.Verified = VerifyPlugin(decoder) == nil
+		}
+	} else {
+		dec.Verified = VerifyPlugin(decoder) == nil
+	}
 
 	if err := dec.ManifestValidate(); err != nil {
 		return plugin_entities.PluginDeclaration{}, err

+ 48 - 8
pkg/plugin_packager/decoder/verifier.go

@@ -5,8 +5,10 @@ import (
 	"crypto/rsa"
 	"crypto/sha256"
 	"encoding/base64"
+	"os"
 	"path"
 	"strconv"
+	"strings"
 
 	"github.com/langgenius/dify-plugin-daemon/internal/core/license/public_key"
 	"github.com/langgenius/dify-plugin-daemon/internal/utils/encryption"
@@ -15,19 +17,51 @@ import (
 // VerifyPlugin is a function that verifies the signature of a plugin
 // It takes a plugin decoder and verifies the signature with a bundled public key
 func VerifyPlugin(decoder PluginDecoder) error {
-	// load public key
-	publicKey, err := encryption.LoadPublicKey(public_key.PUBLIC_KEY)
+	var publicKeys []*rsa.PublicKey
+
+	// load official public key
+	officialPublicKey, err := encryption.LoadPublicKey(public_key.PUBLIC_KEY)
 	if err != nil {
 		return err
 	}
+	publicKeys = append(publicKeys, officialPublicKey)
 
 	// verify the plugin
-	return VerifyPluginWithPublicKey(decoder, publicKey)
+	return VerifyPluginWithPublicKeys(decoder, publicKeys)
+}
+
+// VerifyPluginWithPublicKeyPaths is a function that verifies the signature of a plugin
+// It takes a plugin decoder and a list of public key paths to verify the signature
+func VerifyPluginWithPublicKeyPaths(decoder PluginDecoder, publicKeyPaths []string) error {
+	var publicKeys []*rsa.PublicKey
+
+	// load official public key
+	officialPublicKey, err := encryption.LoadPublicKey(public_key.PUBLIC_KEY)
+	if err != nil {
+		return err
+	}
+	publicKeys = append(publicKeys, officialPublicKey)
+
+	// load keys provided in the arguments
+	for _, publicKeyPath := range publicKeyPaths {
+		// open file by trimming the spaces in path
+		keyBytes, err := os.ReadFile(strings.TrimSpace(publicKeyPath))
+		if err != nil {
+			return err
+		}
+		publicKey, err := encryption.LoadPublicKey(keyBytes)
+		if err != nil {
+			return err
+		}
+		publicKeys = append(publicKeys, publicKey)
+	}
+
+	return VerifyPluginWithPublicKeys(decoder, publicKeys)
 }
 
-// VerifyPluginWithPublicKey is a function that verifies the signature of a plugin
-// It takes a plugin decoder and a public key to verify the signature
-func VerifyPluginWithPublicKey(decoder PluginDecoder, publicKey *rsa.PublicKey) error {
+// VerifyPluginWithPublicKeys is a function that verifies the signature of a plugin
+// It takes a plugin decoder and a list of public keys to verify the signature
+func VerifyPluginWithPublicKeys(decoder PluginDecoder, publicKeys []*rsa.PublicKey) error {
 	data := new(bytes.Buffer)
 	// read one by one
 	err := decoder.Walk(func(filename, dir string) error {
@@ -70,6 +104,12 @@ func VerifyPluginWithPublicKey(decoder PluginDecoder, publicKey *rsa.PublicKey)
 	}
 
 	// verify signature
-	err = encryption.VerifySign(publicKey, data.Bytes(), sigBytes)
-	return err
+	var lastErr error
+	for _, publicKey := range publicKeys {
+		lastErr = encryption.VerifySign(publicKey, data.Bytes(), sigBytes)
+		if lastErr == nil {
+			return nil
+		}
+	}
+	return lastErr
 }

+ 21 - 2
pkg/plugin_packager/decoder/zip.go

@@ -26,9 +26,16 @@ type ZipPluginDecoder struct {
 
 	sig        string
 	createTime int64
+
+	thirdPartySignatureVerificationConfig *ThirdPartySignatureVerificationConfig
 }
 
-func NewZipPluginDecoder(binary []byte) (*ZipPluginDecoder, error) {
+type ThirdPartySignatureVerificationConfig struct {
+    Enabled bool
+    PublicKeyPaths []string
+}
+
+func newZipPluginDecoder(binary []byte, thirdPartySignatureVerificationConfig *ThirdPartySignatureVerificationConfig) (*ZipPluginDecoder, error) {
 	reader, err := zip.NewReader(bytes.NewReader(binary), int64(len(binary)))
 	if err != nil {
 		return nil, errors.New(strings.ReplaceAll(err.Error(), "zip", "difypkg"))
@@ -37,6 +44,7 @@ func NewZipPluginDecoder(binary []byte) (*ZipPluginDecoder, error) {
 	decoder := &ZipPluginDecoder{
 		reader: reader,
 		err:    err,
+		thirdPartySignatureVerificationConfig: thirdPartySignatureVerificationConfig,
 	}
 
 	err = decoder.Open()
@@ -51,6 +59,17 @@ func NewZipPluginDecoder(binary []byte) (*ZipPluginDecoder, error) {
 	return decoder, nil
 }
 
+// NewZipPluginDecoder is a helper function to create ZipPluginDecoder
+func NewZipPluginDecoder(binary []byte) (*ZipPluginDecoder, error) {
+	return newZipPluginDecoder(binary, nil)
+}
+
+// NewZipPluginDecoderWithThirdPartySignatureVerificationConfig is a helper function
+// to create a ZipPluginDecoder with a third party signature verification
+func NewZipPluginDecoderWithThirdPartySignatureVerificationConfig(binary []byte, thirdPartySignatureVerificationConfig *ThirdPartySignatureVerificationConfig) (*ZipPluginDecoder, error) {
+	return newZipPluginDecoder(binary, thirdPartySignatureVerificationConfig)
+}
+
 // NewZipPluginDecoderWithSizeLimit is a helper function to create a ZipPluginDecoder with a size limit
 // It checks the total uncompressed size of the plugin package and returns an error if it exceeds the max size
 func NewZipPluginDecoderWithSizeLimit(binary []byte, maxSize int64) (*ZipPluginDecoder, error) {
@@ -70,7 +89,7 @@ func NewZipPluginDecoderWithSizeLimit(binary []byte, maxSize int64) (*ZipPluginD
 		}
 	}
 
-	return NewZipPluginDecoder(binary)
+	return newZipPluginDecoder(binary, nil)
 }
 
 func (z *ZipPluginDecoder) Stat(filename string) (fs.FileInfo, error) {