Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion cmd/commandline/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package main

import (
"os"
"strings"

"github.com/langgenius/dify-plugin-daemon/cmd/commandline/signature"
"github.com/langgenius/dify-plugin-daemon/internal/utils/log"
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/decoder"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -33,7 +36,19 @@ var (
Run: func(c *cobra.Command, args []string) {
difypkgPath := args[0]
privateKeyPath := c.Flag("private_key").Value.String()
err := signature.Sign(difypkgPath, privateKeyPath)
authorizedCategory := c.Flag("authorized_category").Value.String()
if authorizedCategory != "" {
if !strings.EqualFold(authorizedCategory, string(decoder.AUTHORIZED_CATEGORY_LANGGENIUS)) &&
!strings.EqualFold(authorizedCategory, string(decoder.AUTHORIZED_CATEGORY_PARTNER)) &&
!strings.EqualFold(authorizedCategory, string(decoder.AUTHORIZED_CATEGORY_COMMUNITY)) {
log.Error("invalid authorized category: %s", authorizedCategory)
os.Exit(1)
}
}

err := signature.Sign(difypkgPath, privateKeyPath, &decoder.Verification{
AuthorizedCategory: decoder.AuthorizedCategory(authorizedCategory),
})
if err != nil {
os.Exit(1)
}
Expand Down Expand Up @@ -66,5 +81,12 @@ func init() {
signatureSignCommand.Flags().StringP("private_key", "p", "", "private key file")
signatureSignCommand.MarkFlagRequired("private_key")

signatureSignCommand.Flags().StringP(
"authorized_category",
"c",
string(decoder.AUTHORIZED_CATEGORY_LANGGENIUS),
"authorized category",
)

signatureVerifyCommand.Flags().StringP("public_key", "p", "", "public key file")
}
5 changes: 3 additions & 2 deletions cmd/commandline/signature/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (

"github.com/langgenius/dify-plugin-daemon/internal/utils/encryption"
"github.com/langgenius/dify-plugin-daemon/internal/utils/log"
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/decoder"
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/signer/withkey"
)

func Sign(difypkgPath string, privateKeyPath string) error {
func Sign(difypkgPath string, privateKeyPath string, verification *decoder.Verification) error {
// read the plugin and private key
plugin, err := os.ReadFile(difypkgPath)
if err != nil {
Expand All @@ -32,7 +33,7 @@ func Sign(difypkgPath string, privateKeyPath string) error {
}

// sign the plugin
pluginFile, err := withkey.SignPluginWithPrivateKey(plugin, privateKey)
pluginFile, err := withkey.SignPluginWithPrivateKey(plugin, verification, privateKey)
if err != nil {
log.Error("Failed to sign plugin: %v", err)
return err
Expand Down
59 changes: 57 additions & 2 deletions cmd/commandline/signature/signature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"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/signer"
"github.com/stretchr/testify/assert"
)

//go:embed testdata/dummy_plugin.difypkg
Expand Down Expand Up @@ -77,6 +80,7 @@ func TestSignAndVerify(t *testing.T) {
signKeyPath string
verifyKeyPath string
expectSuccess bool
verification *decoder.Verification
}

// test cases
Expand All @@ -86,36 +90,54 @@ func TestSignAndVerify(t *testing.T) {
signKeyPath: privateKey1Path,
verifyKeyPath: publicKey1Path,
expectSuccess: true,
verification: &decoder.Verification{
AuthorizedCategory: decoder.AUTHORIZED_CATEGORY_LANGGENIUS,
},
},
{
name: "sign with keypair1, verify with keypair2",
signKeyPath: privateKey1Path,
verifyKeyPath: publicKey2Path,
expectSuccess: false,
verification: &decoder.Verification{
AuthorizedCategory: decoder.AUTHORIZED_CATEGORY_LANGGENIUS,
},
},
{
name: "sign with keypair2, verify with keypair2",
signKeyPath: privateKey2Path,
verifyKeyPath: publicKey2Path,
expectSuccess: true,
verification: &decoder.Verification{
AuthorizedCategory: decoder.AUTHORIZED_CATEGORY_LANGGENIUS,
},
},
{
name: "sign with keypair2, verify with keypair1",
signKeyPath: privateKey2Path,
verifyKeyPath: publicKey1Path,
expectSuccess: false,
verification: &decoder.Verification{
AuthorizedCategory: decoder.AUTHORIZED_CATEGORY_LANGGENIUS,
},
},
{
name: "sign with keypair1, verify without key",
signKeyPath: privateKey1Path,
verifyKeyPath: "",
expectSuccess: false,
verification: &decoder.Verification{
AuthorizedCategory: decoder.AUTHORIZED_CATEGORY_LANGGENIUS,
},
},
{
name: "sign with keypair2, verify without key",
signKeyPath: privateKey2Path,
verifyKeyPath: "",
expectSuccess: false,
verification: &decoder.Verification{
AuthorizedCategory: decoder.AUTHORIZED_CATEGORY_LANGGENIUS,
},
},
}

Expand All @@ -129,7 +151,7 @@ func TestSignAndVerify(t *testing.T) {
}

// sign the plugin
Sign(testPluginPath, tt.signKeyPath)
Sign(testPluginPath, tt.signKeyPath, tt.verification)

// get the path of the signed plugin
dir := filepath.Dir(testPluginPath)
Expand Down Expand Up @@ -195,7 +217,9 @@ func TestVerifyTampered(t *testing.T) {
publicKeyPath := keyPairName + ".public.pem"

// Sign the plugin
Sign(dummyPluginPath, privateKeyPath)
Sign(dummyPluginPath, privateKeyPath, &decoder.Verification{
AuthorizedCategory: decoder.AUTHORIZED_CATEGORY_LANGGENIUS,
})

// Get the path of the signed plugin
dir := filepath.Dir(dummyPluginPath)
Expand Down Expand Up @@ -225,3 +249,34 @@ func TestVerifyTampered(t *testing.T) {
t.Errorf("Expected verification of tampered file to fail, but it succeeded")
}
}

/*
Formerly, the plugin is all signed by langgenius but has no authorized category
*/
func TestVerifyPluginWithoutVerificationField(t *testing.T) {
tempDir := t.TempDir()

// extract the minimal plugin content from the embedded data to a file
dummyPluginPath := filepath.Join(tempDir, "dummy_plugin.difypkg")
if err := os.WriteFile(dummyPluginPath, dummyPlugin, 0644); err != nil {
t.Fatalf("Failed to create dummy plugin file: %v", err)
}

pluginPackageWithoutVerificationField, err := signer.TraditionalSignPlugin(dummyPlugin)
if err != nil {
t.Fatalf("Failed to sign plugin: %v", err)
}

// sign a plugin
decoder, err := decoder.NewZipPluginDecoder(
pluginPackageWithoutVerificationField,
)
assert.NoError(t, err)

verification, err := decoder.Verification(false)
assert.NoError(t, err)
assert.Nil(t, verification)

verified := decoder.Verified()
assert.True(t, verified)
}
13 changes: 9 additions & 4 deletions cmd/license/sign/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import (
"os"

"github.com/langgenius/dify-plugin-daemon/internal/utils/log"
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/decoder"
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/signer"
)

func main() {
var (
in_path string
out_path string
help bool
in_path string
out_path string
help bool
authorized_category string
)

flag.StringVar(&in_path, "in", "", "input plugin file path")
flag.StringVar(&out_path, "out", "", "output plugin file path")
flag.StringVar(&authorized_category, "authorized_category", "", "authorized category")
flag.BoolVar(&help, "help", false, "show help")
flag.Parse()

Expand All @@ -32,7 +35,9 @@ func main() {
}

// sign plugin
pluginFile, err := signer.SignPlugin(plugin)
pluginFile, err := signer.SignPlugin(plugin, &decoder.Verification{
AuthorizedCategory: decoder.AuthorizedCategory(authorized_category),
})
if err != nil {
log.Panic("failed to sign plugin %v", err)
}
Expand Down
8 changes: 8 additions & 0 deletions internal/service/plugin_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,17 @@ func UploadPluginPkg(
}
}

verification, _ := decoderInstance.Verification(false)
if verification == nil && decoderInstance.Verified() {
verification = &decoder.Verification{
AuthorizedCategory: decoder.AUTHORIZED_CATEGORY_LANGGENIUS,
}
}

return entities.NewSuccessResponse(map[string]any{
"unique_identifier": pluginUniqueIdentifier,
"manifest": declaration,
"verification": verification,
})
}

Expand Down
7 changes: 7 additions & 0 deletions pkg/plugin_packager/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ type PluginDecoder interface {
// Signature returns the signature of the plugin, if available
Signature() (string, error)

// Verified returns true if the plugin is verified
Verified() bool

// Verification returns the verification of the plugin, if available
// Error will only returns if the plugin is not verified
Verification(ignoreVerifySignature bool) (*Verification, error)

// CreateTime returns the creation time of the plugin as a Unix timestamp
CreateTime() (int64, error)

Expand Down
13 changes: 13 additions & 0 deletions pkg/plugin_packager/decoder/entities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package decoder

type AuthorizedCategory string

const (
AUTHORIZED_CATEGORY_LANGGENIUS AuthorizedCategory = "langgenius"
AUTHORIZED_CATEGORY_PARTNER AuthorizedCategory = "partner"
AUTHORIZED_CATEGORY_COMMUNITY AuthorizedCategory = "community"
)

type Verification struct {
AuthorizedCategory AuthorizedCategory `json:"authorized_category"`
}
8 changes: 8 additions & 0 deletions pkg/plugin_packager/decoder/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ func (d *FSPluginDecoder) CreateTime() (int64, error) {
return 0, nil
}

func (d *FSPluginDecoder) Verification(ignoreVerifySignature bool) (*Verification, error) {
return nil, nil
}

func (d *FSPluginDecoder) Manifest() (plugin_entities.PluginDeclaration, error) {
return d.PluginDecoderHelper.Manifest(d)
}
Expand All @@ -189,3 +193,7 @@ func (d *FSPluginDecoder) UniqueIdentity() (plugin_entities.PluginUniqueIdentifi
func (d *FSPluginDecoder) CheckAssetsValid() error {
return d.PluginDecoderHelper.CheckAssetsValid(d)
}

func (d *FSPluginDecoder) Verified() bool {
return d.PluginDecoderHelper.verified(d)
}
44 changes: 28 additions & 16 deletions pkg/plugin_packager/decoder/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
type PluginDecoderHelper struct {
pluginDeclaration *plugin_entities.PluginDeclaration
checksum string

verifiedFlag *bool // used to store the verified flag, avoid calling verified function multiple times
}

func (p *PluginDecoderHelper) Manifest(decoder PluginDecoder) (plugin_entities.PluginDeclaration, error) {
Expand Down Expand Up @@ -271,22 +273,7 @@ func (p *PluginDecoderHelper) Manifest(decoder PluginDecoder) (plugin_entities.P

dec.FillInDefaultValues()

// verify signature
// 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
}
dec.Verified = p.verified(decoder)

p.pluginDeclaration = &dec
return dec, nil
Expand Down Expand Up @@ -423,3 +410,28 @@ func (p *PluginDecoderHelper) CheckAssetsValid(decoder PluginDecoder) error {

return nil
}

func (p *PluginDecoderHelper) verified(decoder PluginDecoder) bool {
if p.verifiedFlag != nil {
return *p.verifiedFlag
}

// verify signature
// 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 {
verified := VerifyPluginWithPublicKeyPaths(decoder, config.PublicKeyPaths) == nil
p.verifiedFlag = &verified
return verified
} else {
verified := VerifyPlugin(decoder) == nil
p.verifiedFlag = &verified
return verified
}
} else {
verified := VerifyPlugin(decoder) == nil
p.verifiedFlag = &verified
return verified
}
}
14 changes: 14 additions & 0 deletions pkg/plugin_packager/decoder/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/langgenius/dify-plugin-daemon/internal/core/license/public_key"
"github.com/langgenius/dify-plugin-daemon/internal/utils/encryption"
"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
)

// VerifyPlugin is a function that verifies the signature of a plugin
Expand Down Expand Up @@ -95,9 +96,22 @@ func VerifyPluginWithPublicKeys(decoder PluginDecoder, publicKeys []*rsa.PublicK
return err
}

// get the verification, at this point, verification is used to verify checksum
// just ignore verifying signature
verification, err := decoder.Verification(true)
if err != nil {
return err
}

// write the time into data
data.Write([]byte(strconv.FormatInt(createdAt, 10)))

if verification != nil {
// marshal
verificationBytes := parser.MarshalJsonBytes(verification)
data.Write(verificationBytes)
}

sigBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return err
Expand Down
Loading