Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
chore: more work towards supporting p7s
  • Loading branch information
Emyrk committed Dec 13, 2024
commit 28f3728d45f53efc88d1b92d10ec1fb7a51b4eb5
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
188 changes: 187 additions & 1 deletion cli/signature.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package cli

import (
"context"
"crypto/x509"
"fmt"
"io"
"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"
"github.com/coder/code-marketplace/storage/easyzip"
)

func signature() *cobra.Command {
Expand All @@ -17,10 +26,187 @@ func signature() *cobra.Command {
Hidden: true, // Debugging tools
Aliases: []string{"sig", "sigs", "signatures"},
}
cmd.AddCommand(compareSignatureSigZips())

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

func verifySig() *cobra.Command {
cmd := &cobra.Command{
Use: "verify",
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]
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, ".signature.manifest")
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----------------")
valid, err = openSSLVerify(ctx, logger, msgData, signed)
if err != nil {
logger.Error(ctx, "openssl 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
},
}
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, 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)
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)
}

cmd := exec.CommandContext(ctx, "openssl", "smime", "-verify",
"-in", sigPath, "-content", msgPath, "-inform", "DER",
"-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 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
82 changes: 82 additions & 0 deletions extensionsign/algo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package extensionsign

import (
"context"
"crypto"
"crypto/x509"
"encoding/pem"
"os"
"os/exec"
"path/filepath"

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

var SigningAlgorithm = OpenSSLSign

func CMSAlgo(data []byte, certs []*x509.Certificate, signer crypto.Signer) (result []byte, err error) {
return cms.SignDetached(data, certs, signer)
}

// openssl smime -sign -signer <cert> -inkey <key> -binary -in .signature.manifest -outform der -out openssl.p7s
func OpenSSLSign(data []byte, certs []*x509.Certificate, signer crypto.Signer) (result []byte, err error) {
tmpdir := os.TempDir()
tmpdir = filepath.Join(tmpdir, "sign-sigs")
defer os.RemoveAll(tmpdir)

err = os.MkdirAll(tmpdir, 0755)
if err != nil {
return nil, xerrors.Errorf("create temp dir: %w", err)
}

certPath := filepath.Join(tmpdir, "certs.pem")
certFile, err := os.OpenFile(certPath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, xerrors.Errorf("open cert file: %w", err)
}

for _, cert := range certs {
if len(cert.Raw) == 0 {
return nil, xerrors.Errorf("empty certificate")
}
err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
if err != nil {
return nil, err
}
}

keyPath := "/home/steven/go/src/github.com/coder/code-marketplace/extensionsign/testdata/key2.pem"
//keyFile, err := os.Open(keyPath)
//if err != nil {
// return nil, err
//}
//pem.Encode(keyFile, &pem.Block{Type: "PRIVATE KEY", )

msgPath := filepath.Join(tmpdir, ".signature.manifest")
messageFile, err := os.OpenFile(msgPath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
_, err = messageFile.Write(data)
if err != nil {
return nil, xerrors.Errorf("write message: %w", err)
}

signed := filepath.Join(tmpdir, "openssl.p7s")
cmd := exec.CommandContext(context.Background(), "openssl", "cms", "-sign",
"-signer", certPath,
"-inkey", keyPath,
"-binary",
"-in", msgPath,
"-outform", "der",
"-out", signed,
)

err = cmd.Run()
if err != nil {
return nil, xerrors.Errorf("run openssl: %w", err)
}

return os.ReadFile(signed)
}
1 change: 1 addition & 0 deletions extensionsign/doc.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// Package extensionsign is a Go implementation of https://github.com/filiptronicek/node-ovsx-sign
// See https://github.com/eclipse/openvsx/issues/543
package extensionsign
Loading