Skip to content

Commit

Permalink
Fix/pubkey (#11)
Browse files Browse the repository at this point in the history
* fix(pubkey, id): fix generate pubkey and extension id, #5

* chore(commands): some improvements
  • Loading branch information
mmadfox authored Jun 26, 2024
1 parent 4a03128 commit 94c0800
Show file tree
Hide file tree
Showing 21 changed files with 396 additions and 134 deletions.
14 changes: 11 additions & 3 deletions crx3/commands/base64.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@ func newBase64Cmd() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
extension := crx3.Extension(args[0])
infile, err := toPath(args[0])
if err != nil {
return fmt.Errorf("invalid infile path: %w", err)
}
outfile, err := toPath(opts.Outfile)
if err != nil {
return fmt.Errorf("invalid outfile path: %w", err)
}
extension := crx3.Extension(infile)
b, err := extension.Base64()
if err != nil {
return err
}
if opts.HasOutfile() {
file, err := os.Create(opts.Outfile)
if len(outfile) > 0 {
file, err := os.Create(outfile)
if err != nil {
return err
}
Expand Down
28 changes: 28 additions & 0 deletions crx3/commands/command.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package commands

import (
"os/user"
"path/filepath"
"strings"

"github.com/spf13/cobra"
)

Expand All @@ -22,3 +26,27 @@ func New() *cobra.Command {

return cmd
}

func toPath(path string) (string, error) {
if strings.HasPrefix(path, "~") {
usr, err := user.Current()
if err != nil {
return "", err
}
home := usr.HomeDir
if path == "~" {
return home, nil
}
return filepath.Join(home, path[2:]), nil
}
return path, nil
}

func sanitizeKeySize(size int) int {
switch size {
case 2048, 3072, 4096:
return size
default:
return 2048
}
}
14 changes: 11 additions & 3 deletions crx3/commands/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,24 @@ import (
func newIDCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "id [infile]",
Short: "Generate extension id",
Short: "Generate id from header extension or manifest file",
Long: `
The identifier is generated from the hash of the public key, which is located in the extension header or declared in the key field of the manifest.
If the key is specified in the manifest, the public key is taken from there; otherwise, the search continues in the header.
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("infile is required")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
infile := args[0]
id, err := crx3.ID(infile)
infile, err := toPath(args[0])
if err != nil {
return err
}
ext := crx3.Extension(infile)
id, err := ext.ID()
if err != nil {
return err
}
Expand Down
28 changes: 15 additions & 13 deletions crx3/commands/keygen.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/spf13/cobra"
)

const pemExt = ".pem"

type keygenOpts struct {
PrivateKeySize int
}
Expand All @@ -17,6 +19,12 @@ func newKeygenCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "keygen [file]",
Short: "Create a new private key",
Long: `
If no file is specified, the private key is printed to stdout.
Otherwise, the private key is saved to the specified file.
If the file does not have a .pem extension, it is added automatically.
Size of the private key can be set with the --size or -s flag. Sizes of 2048, 3072, or 4096 bits are allowed.
`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
crx3.SetDefaultKeySize(sanitizeKeySize(opts.PrivateKeySize))
pk, err := crx3.NewPrivateKey()
Expand All @@ -28,24 +36,18 @@ func newKeygenCmd() *cobra.Command {
fmt.Print(string(key))
return nil
}
filename := args[0]
if !strings.HasSuffix(filename, ".pem") {
filename = filename + ".pem"
filename, err := toPath(args[0])
if err != nil {
return fmt.Errorf("invalid infile path: %w", err)
}
if !strings.HasSuffix(filename, pemExt) {
filename = filename + pemExt
}
return crx3.SavePrivateKey(filename, pk)
},
}

cmd.Flags().IntVarP(&opts.PrivateKeySize, "size", "s", 4096, "private key size")
cmd.Flags().IntVarP(&opts.PrivateKeySize, "size", "s", 2048, "private key size")

return cmd
}

func sanitizeKeySize(size int) int {
switch size {
case 2048, 3072, 4096:
return size
default:
return 4096
}
}
13 changes: 10 additions & 3 deletions crx3/commands/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,28 @@ func newPackCmd() *cobra.Command {
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
crx3.SetDefaultKeySize(sanitizeKeySize(opts.PrivateKeySize))
unpacked := args[0]
unpacked, err := toPath(args[0])
if err != nil {
return err
}
var pk *rsa.PrivateKey
if opts.hasPem() {
pk, err = crx3.LoadPrivateKey(opts.PrivateKey)
if err != nil {
return err
}
}
return crx3.Pack(unpacked, opts.Outfile, pk)
out, err := toPath(opts.Outfile)
if err != nil {
return err
}
return crx3.Pack(unpacked, out, pk)
},
}

cmd.Flags().StringVarP(&opts.PrivateKey, "pem", "p", "", "load private key")
cmd.Flags().StringVarP(&opts.Outfile, "outfile", "o", "", "save to file")
cmd.Flags().IntVarP(&opts.PrivateKeySize, "size", "s", 4096, "private key size")
cmd.Flags().IntVarP(&opts.PrivateKeySize, "size", "s", 2048, "private key size")

return cmd
}
19 changes: 15 additions & 4 deletions crx3/commands/pubkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,35 @@ func newPubkeyCmd() *cobra.Command {
var opts pubkeyOpts
cmd := &cobra.Command{
Use: "pubkey [extension]",
Short: "Retrieves the public key from an extension",
Short: "Retrieves the public key from the extension header or manifest file",
Long: `
The public key is searched for first in the manifest file, and if not found, the search continues in the extension header.
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("file is required")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
ext := crx3.Extension(args[0])
infile, err := toPath(args[0])
if err != nil {
return fmt.Errorf("failed to open infile: %w", err)
}
outfile, err := toPath(opts.Outfile)
if err != nil {
return fmt.Errorf("failed to open outfile: %w", err)
}
ext := crx3.Extension(infile)
pubkey, _, err := ext.PublicKey()
if err != nil {
return err
}
if len(opts.Outfile) == 0 {
if len(outfile) == 0 {
fmt.Print(string(pubkey))
return nil
}
file, err := os.OpenFile(opts.Outfile, os.O_CREATE|os.O_WRONLY, 0644)
file, err := os.OpenFile(outfile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
Expand Down
25 changes: 22 additions & 3 deletions crx3/commands/unpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,44 @@ package commands

import (
"errors"
"fmt"

crx3 "github.com/mediabuyerbot/go-crx3"
"github.com/spf13/cobra"
)

type uppackOpts struct {
Outfile string
}

func newUnpackCmd() *cobra.Command {
var opts uppackOpts
cmd := &cobra.Command{
Use: "unpack [extension.crx]",
Short: "Unpack chrome extension into current directory",
Use: "unpack [extension.crx] [flags]",
Short: "Unpack chrome extension into current directory or specified directory",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("extension is required")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
infile := args[0]
infile, err := toPath(args[0])
if err != nil {
return fmt.Errorf("invalid infile path: %w", err)
}
outfile, err := toPath(opts.Outfile)
if err != nil {
return fmt.Errorf("invalid outfile path: %w", err)
}
if len(outfile) > 0 {
return crx3.UnpackTo(infile, outfile)
}
return crx3.Unpack(infile)
},
}

cmd.Flags().StringVarP(&opts.Outfile, "outfile", "o", "", "save to file")

return cmd
}
6 changes: 5 additions & 1 deletion crx3/commands/unzip.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"errors"
"fmt"
"os"
"strings"

Expand Down Expand Up @@ -29,7 +30,10 @@ func newUnzipCmd() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
infile := args[0]
infile, err := toPath(args[0])
if err != nil {
return fmt.Errorf("invalid infile: %w", err)
}
zipFile, err := os.Open(infile)
if err != nil {
return err
Expand Down
6 changes: 5 additions & 1 deletion crx3/commands/zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"errors"
"fmt"
"os"

crx3 "github.com/mediabuyerbot/go-crx3"
Expand All @@ -28,7 +29,10 @@ func newZipCmd() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
infile := args[0]
infile, err := toPath(args[0])
if err != nil {
return fmt.Errorf("invalid infile: %w", err)
}
if !opts.HasOutfile() {
opts.Outfile = infile + ".zip"
}
Expand Down
44 changes: 42 additions & 2 deletions extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,46 @@ func (e Extension) String() string {
return string(e)
}

// ID returns an extension id.
// ID calculates the Chrome Extension ID for the Extension instance.
// It supports directories, ZIP archives, and CRX3 files. If the extension is unpacked,
// contained in a ZIP archive, or is a CRX3 file with a specified key in its manifest,
// the ID is generated from this key. The function returns an error if the extension is empty,
// the file cannot be read, the key is not found, or the file format is unsupported.
func (e Extension) ID() (string, error) {
return ID(e.String())
if e.IsEmpty() {
return "", fmt.Errorf("%w: %s", ErrPathNotFound, e)
}
switch {
case e.IsDir():
manifest := manifestFile(e.String())
file, err := os.ReadFile(manifest)
if err != nil {
return "", fmt.Errorf("crx3: failed to read file %s: %w", manifest, err)
}
pubkey := parseKeyFromManifest(file)
if len(pubkey) == 0 {
return "", fmt.Errorf("crx3: failed to parse key from manifest file %s", manifest)
}
return IDFromPubKey([]byte(pubkey))
case e.IsZip():
pubkey, err := parseManifestFromZip(e.String())
if err != nil {
return "", err
}
if len(pubkey) == 0 {
return "", fmt.Errorf("crx3: failed to parse key from manifest file %s", e)
}
return IDFromPubKey([]byte(pubkey))
case e.IsCRX3():
pubkey, err := parseManifestFromZip(e.String())
if err != nil || len(pubkey) == 0 {
return ID(e.String())
}
if len(pubkey) > 0 {
return IDFromPubKey([]byte(pubkey))
}
}
return "", fmt.Errorf("%w: %s", ErrUnknownFileExtension, e)
}

// IsEmpty checks if the extension is empty.
Expand Down Expand Up @@ -271,8 +308,11 @@ func parseKeyFromManifest(data []byte) string {

func formatPemKey(key []byte) []byte {
str := string(key)
str = strings.TrimSpace(str)
str = strings.Replace(str, "-----BEGIN RSA PUBLIC KEY-----", "", 1)
str = strings.Replace(str, "-----END RSA PUBLIC KEY-----", "", 1)
str = strings.Replace(str, "-----BEGIN PUBLIC KEY-----", "", 1)
str = strings.Replace(str, "-----END PUBLIC KEY-----", "", 1)
str = strings.TrimSpace(str)
str = strings.ReplaceAll(str, "\n", "")
return []byte(str)
Expand Down
Loading

0 comments on commit 94c0800

Please sign in to comment.