Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: more work towards supporting p7s #81

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ bin
coverage
extensions
.idea
.run
1 change: 1 addition & 0 deletions cli/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

func add() *cobra.Command {
addFlags, opts := serverFlags()

cmd := &cobra.Command{
Use: "add <source>",
Short: "Add an extension to the marketplace",
Expand Down
25 changes: 20 additions & 5 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"net"
"net/http"
"os"
"os/signal"
"strings"
"time"
Expand All @@ -24,13 +25,14 @@ import (

func serverFlags() (addFlags func(cmd *cobra.Command), opts *storage.Options) {
opts = &storage.Options{}
var sign bool
var certificates []string
var signingKeyFile string
return func(cmd *cobra.Command) {
cmd.Flags().StringVar(&opts.ExtDir, "extensions-dir", "", "The path to extensions.")
cmd.Flags().StringVar(&opts.Artifactory, "artifactory", "", "Artifactory server URL.")
cmd.Flags().StringVar(&opts.Repo, "repo", "", "Artifactory repository.")
cmd.Flags().BoolVar(&sign, "sign", false, "Sign extensions.")
_ = cmd.Flags().MarkHidden("sign") // This flag needs to import a key, not just be a bool
cmd.Flags().StringArrayVar(&certificates, "certs", []string{}, "The path to certificates that match the signing key.")
cmd.Flags().StringVar(&signingKeyFile, "key", "", "The path to signing key file in PEM format.")
cmd.Flags().BoolVar(&opts.SaveSigZips, "save-sigs", false, "Save signed extensions to disk for debugging.")
_ = cmd.Flags().MarkHidden("save-sigs")

Expand All @@ -56,8 +58,21 @@ func serverFlags() (addFlags func(cmd *cobra.Command), opts *storage.Options) {
if before != nil {
return before(cmd, args)
}
if sign { // TODO: Remove this for an actual key import
opts.Signer, _ = extensionsign.GenerateKey()
if signingKeyFile != "" { // TODO: Remove this for an actual key import
signingKey, err := os.ReadFile(signingKeyFile)
if err != nil {
return xerrors.Errorf("read signing key: %w", err)
}

signer, err := extensionsign.LoadKey(signingKey)
if err != nil {
return xerrors.Errorf("load signing key: %w", err)
}
opts.Signer = signer
opts.Certificates, err = extensionsign.LoadCertificatesFromDisk(cmd.Context(), opts.Logger, certificates)
if err != nil {
return xerrors.Errorf("load certificates: %w", err)
}
}
return nil
}
Expand Down
241 changes: 240 additions & 1 deletion cli/signature.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package cli

import (
"context"
"crypto/x509"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

cms "github.com/github/smimesign/ietf-cms"
"github.com/spf13/cobra"
"golang.org/x/xerrors"

"cdr.dev/slog"
"github.com/coder/code-marketplace/extensionsign"
)

Expand All @@ -17,10 +24,242 @@
Hidden: true, // Debugging tools
Aliases: []string{"sig", "sigs", "signatures"},
}
cmd.AddCommand(compareSignatureSigZips())

cmd.AddCommand(compareSignatureSigZips(), verifySig())
return cmd
}

var (
localCA = false
)

func verifySig() *cobra.Command {
cmd := &cobra.Command{
Use: "verify <extension.vsix> <signature.p7s>",
Short: "Decode & verify a signature archive.",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
logger := cmdLogger(cmd)
ctx := cmd.Context()
extensionVsix := args[0]
msgData, err := os.ReadFile(extensionVsix)
if err != nil {
return xerrors.Errorf("read %q: %w", extensionVsix, err)
}

p7sFile := args[1]

logger.Info(ctx, fmt.Sprintf("Decoding %q", p7sFile))

data, err := os.ReadFile(p7sFile)
if err != nil {
return xerrors.Errorf("read %q: %w", p7sFile, err)
}

//msg, err := easyzip.GetZipFileReader(data, extensionVsix)
//if err != nil {
// return xerrors.Errorf("get manifest: %w", err)
//}
//msgData, err := io.ReadAll(msg)
//if err != nil {
// return xerrors.Errorf("read manifest: %w", err)
//}

signed, err := extensionsign.ExtractP7SSig(data)
if err != nil {
return xerrors.Errorf("extract p7s: %w", err)
}

fmt.Println("----------------Golang Verify----------------")
valid, err := goVerify(ctx, logger, msgData, signed)
if err != nil {
logger.Error(ctx, "go verify", slog.Error(err))
}
logger.Info(ctx, fmt.Sprintf("Valid: %t", valid))

fmt.Println("----------------OpenSSL Verify CMS----------------")
valid, err = openSSLVerify(ctx, "cms", logger, msgData, signed)
if err != nil {
logger.Error(ctx, "openssl verify", slog.Error(err))
}
logger.Info(ctx, fmt.Sprintf("Valid: %t", valid))

fmt.Println("----------------OpenSSL Verify SMIME----------------")
valid, err = openSSLVerify(ctx, "smime", logger, msgData, signed)
if err != nil {
logger.Error(ctx, "openssl verify", slog.Error(err))
}
logger.Info(ctx, fmt.Sprintf("Valid: %t", valid))

fmt.Println("----------------node-ovsx-sign Verify----------------")
valid, err = openVSXSignVerify(ctx, logger, extensionVsix, p7sFile)
if err != nil {
logger.Error(ctx, "open vsx verify", slog.Error(err))
}
logger.Info(ctx, fmt.Sprintf("Valid: %t", valid))

fmt.Println("----------------vsce-sign Verify----------------")
valid, err = vsceSignVerify(ctx, logger, extensionVsix, p7sFile)
if err != nil {
logger.Error(ctx, "openssl verify", slog.Error(err))
}
logger.Info(ctx, fmt.Sprintf("Valid: %t", valid))

return nil
},
}
cmd.Flags().BoolVar(&localCA, "local-ca", true, "Use the local CA for verification.")
return cmd
}

func goVerify(ctx context.Context, logger slog.Logger, message []byte, signature []byte) (bool, error) {
sd, err := cms.ParseSignedData(signature)
if err != nil {
return false, xerrors.Errorf("new signed data: %w", err)
}

fmt.Println("Detached:", sd.IsDetached())
certs, err := sd.GetCertificates()
if err != nil {
return false, xerrors.Errorf("get certs: %w", err)
}
fmt.Println("Certificates:", len(certs))

sdData, err := sd.GetData()
if err != nil {
return false, xerrors.Errorf("get data: %w", err)
}
fmt.Println("Data:", len(sdData))

var verifyErr error
var vcerts [][][]*x509.Certificate

sys, err := x509.SystemCertPool()
if err != nil {
return false, xerrors.Errorf("system cert pool: %w", err)
}
opts := x509.VerifyOptions{
Intermediates: sys,
Roots: sys,
}

if sd.IsDetached() {
vcerts, verifyErr = sd.VerifyDetached(message, opts)
} else {
vcerts, verifyErr = sd.Verify(opts)
}
if verifyErr != nil {
logger.Error(ctx, "verify", slog.Error(verifyErr))
}

certChain := dimensions(vcerts)
fmt.Println(certChain)
return verifyErr == nil, nil
}

func openSSLVerify(ctx context.Context, algo string, logger slog.Logger, message []byte, signature []byte) (bool, error) {
// openssl cms -verify -in message_from_alice_for_bob.msg -inform DER -CAfile ehealth_root_ca.cer | openssl cms -decrypt -inform DER -recip bob_etk_pair.pem | openssl cms -inform DER -cmsout -print
tmpdir := os.TempDir()
tmpdir = filepath.Join(tmpdir, "verify-sigs")
defer os.RemoveAll(tmpdir)
os.MkdirAll(tmpdir, 0755)

Check failure on line 165 in cli/signature.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `os.MkdirAll` is not checked (errcheck)
msgPath := filepath.Join(tmpdir, ".signature.manifest")
err := os.WriteFile(msgPath, message, 0644)
if err != nil {
return false, xerrors.Errorf("write message: %w", err)
}

sigPath := filepath.Join(tmpdir, ".signature.p7s")
err = os.WriteFile(sigPath, signature, 0644)
if err != nil {
return false, xerrors.Errorf("write signature: %w", err)
}

if localCA {

Check failure on line 178 in cli/signature.go

View workflow job for this annotation

GitHub Actions / lint

SA9003: empty branch (staticcheck)

}

// smime or cms
cmd := exec.CommandContext(ctx, "openssl", algo, "-verify",
"-in", sigPath, "-content", msgPath, "-inform", "DER",
)
if localCA {
cmd.Args = append(cmd.Args, "-CAfile", "/home/steven/go/src/github.com/coder/code-marketplace/extensionsign/testdata/cert2.pem")
}
output := &strings.Builder{}
//cmd.Stdout = output
cmd.Stderr = output
err = cmd.Run()
fmt.Println(output.String())
if err != nil {
return false, xerrors.Errorf("run verify %q: %w", cmd.String(), err)
}

return cmd.ProcessState.ExitCode() == 0, nil
}

func openVSXSignVerify(ctx context.Context, logger slog.Logger, vsixPath, sigPath string) (bool, error) {
bin := os.Getenv("OPEN_VSX_SIGN_PATH")
if bin == "" {
return false, xerrors.Errorf("OPEN_VSX_SIGN_PATH not set")
}

cmd := exec.CommandContext(ctx, bin, "verify",
vsixPath,
sigPath,
"--public-key", "/home/steven/go/src/github.com/coder/code-marketplace/extensionsign/testdata/public.pem",
)
fmt.Println(cmd.String())
output := &strings.Builder{}
cmd.Stdout = output
cmd.Stderr = output
err := cmd.Run()
fmt.Println(output.String())
if err != nil {
return false, xerrors.Errorf("run verify %q: %w", cmd.String(), err)
}

return cmd.ProcessState.ExitCode() == 0, nil
}

func vsceSignVerify(ctx context.Context, logger slog.Logger, vsixPath, sigPath string) (bool, error) {
bin := os.Getenv("VSCE_SIGN_PATH")
if bin == "" {
return false, xerrors.Errorf("VSCE_SIGN_PATH not set")
}

cmd := exec.CommandContext(ctx, bin, "verify",
"--package", vsixPath,
"--signaturearchive", sigPath,
"-v",
)
fmt.Println(cmd.String())
output := &strings.Builder{}
cmd.Stdout = output
cmd.Stderr = output
err := cmd.Run()
fmt.Println(output.String())
if err != nil {
return false, xerrors.Errorf("run verify %q: %w", cmd.String(), err)
}

return cmd.ProcessState.ExitCode() == 0, nil
}

func dimensions(chain [][][]*x509.Certificate) string {
var str strings.Builder
for _, top := range chain {
str.WriteString(fmt.Sprintf("Chain, len=%d\n", len(top)))
for _, second := range top {
str.WriteString(fmt.Sprintf(" Certs len=%d\n", len(second)))
for _, cert := range second {
str.WriteString(fmt.Sprintf(" Cert: %s\n", cert.Subject))
}
}
}
return str.String()
}

func compareSignatureSigZips() *cobra.Command {
cmd := &cobra.Command{
Use: "compare",
Expand Down
Loading
Loading