Skip to content

Commit 13a5775

Browse files
committed
chore: more work towards supporting p7s
1 parent f35db41 commit 13a5775

18 files changed

+572
-29
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ bin
33
coverage
44
extensions
55
.idea
6+
.run

Diff for: cli/add.go

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
func add() *cobra.Command {
1818
addFlags, opts := serverFlags()
19+
1920
cmd := &cobra.Command{
2021
Use: "add <source>",
2122
Short: "Add an extension to the marketplace",

Diff for: cli/server.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"net"
77
"net/http"
8+
"os"
89
"os/signal"
910
"strings"
1011
"time"
@@ -24,13 +25,16 @@ import (
2425

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

3539
if cmd.Use == "server" {
3640
// Server only flags
@@ -54,8 +58,21 @@ func serverFlags() (addFlags func(cmd *cobra.Command), opts *storage.Options) {
5458
if before != nil {
5559
return before(cmd, args)
5660
}
57-
if sign { // TODO: Remove this for an actual key import
58-
opts.Signer, _ = extensionsign.GenerateKey()
61+
if signingKeyFile != "" { // TODO: Remove this for an actual key import
62+
signingKey, err := os.ReadFile(signingKeyFile)
63+
if err != nil {
64+
return xerrors.Errorf("read signing key: %w", err)
65+
}
66+
67+
signer, err := extensionsign.LoadKey(signingKey)
68+
if err != nil {
69+
return xerrors.Errorf("load signing key: %w", err)
70+
}
71+
opts.Signer = signer
72+
opts.Certificates, err = extensionsign.LoadCertificatesFromDisk(cmd.Context(), opts.Logger, certificates)
73+
if err != nil {
74+
return xerrors.Errorf("load certificates: %w", err)
75+
}
5976
}
6077
return nil
6178
}

Diff for: cli/signature.go

+187-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package cli
22

33
import (
4+
"context"
5+
"crypto/x509"
46
"fmt"
7+
"io"
58
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"strings"
612

13+
cms "github.com/github/smimesign/ietf-cms"
714
"github.com/spf13/cobra"
815
"golang.org/x/xerrors"
916

17+
"cdr.dev/slog"
1018
"github.com/coder/code-marketplace/extensionsign"
19+
"github.com/coder/code-marketplace/storage/easyzip"
1120
)
1221

1322
func signature() *cobra.Command {
@@ -17,10 +26,187 @@ func signature() *cobra.Command {
1726
Hidden: true, // Debugging tools
1827
Aliases: []string{"sig", "sigs", "signatures"},
1928
}
20-
cmd.AddCommand(compareSignatureSigZips())
29+
30+
cmd.AddCommand(compareSignatureSigZips(), verifySig())
31+
return cmd
32+
}
33+
34+
func verifySig() *cobra.Command {
35+
cmd := &cobra.Command{
36+
Use: "verify",
37+
Short: "Decode & verify a signature archive.",
38+
Args: cobra.ExactArgs(2),
39+
RunE: func(cmd *cobra.Command, args []string) error {
40+
logger := cmdLogger(cmd)
41+
ctx := cmd.Context()
42+
extensionVsix := args[0]
43+
p7sFile := args[1]
44+
45+
logger.Info(ctx, fmt.Sprintf("Decoding %q", p7sFile))
46+
47+
data, err := os.ReadFile(p7sFile)
48+
if err != nil {
49+
return xerrors.Errorf("read %q: %w", p7sFile, err)
50+
}
51+
52+
msg, err := easyzip.GetZipFileReader(data, ".signature.manifest")
53+
if err != nil {
54+
return xerrors.Errorf("get manifest: %w", err)
55+
}
56+
msgData, err := io.ReadAll(msg)
57+
if err != nil {
58+
return xerrors.Errorf("read manifest: %w", err)
59+
}
60+
61+
signed, err := extensionsign.ExtractP7SSig(data)
62+
if err != nil {
63+
return xerrors.Errorf("extract p7s: %w", err)
64+
}
65+
66+
fmt.Println("----------------Golang Verify----------------")
67+
valid, err := goVerify(ctx, logger, msgData, signed)
68+
if err != nil {
69+
logger.Error(ctx, "go verify", slog.Error(err))
70+
}
71+
logger.Info(ctx, fmt.Sprintf("Valid: %t", valid))
72+
73+
fmt.Println("----------------OpenSSL Verify----------------")
74+
valid, err = openSSLVerify(ctx, logger, msgData, signed)
75+
if err != nil {
76+
logger.Error(ctx, "openssl verify", slog.Error(err))
77+
}
78+
logger.Info(ctx, fmt.Sprintf("Valid: %t", valid))
79+
80+
fmt.Println("----------------vsce-sign Verify----------------")
81+
valid, err = vsceSignVerify(ctx, logger, extensionVsix, p7sFile)
82+
if err != nil {
83+
logger.Error(ctx, "openssl verify", slog.Error(err))
84+
}
85+
logger.Info(ctx, fmt.Sprintf("Valid: %t", valid))
86+
87+
return nil
88+
},
89+
}
2190
return cmd
2291
}
2392

93+
func goVerify(ctx context.Context, logger slog.Logger, message []byte, signature []byte) (bool, error) {
94+
sd, err := cms.ParseSignedData(signature)
95+
if err != nil {
96+
return false, xerrors.Errorf("new signed data: %w", err)
97+
}
98+
99+
fmt.Println("Detached:", sd.IsDetached())
100+
certs, err := sd.GetCertificates()
101+
if err != nil {
102+
return false, xerrors.Errorf("get certs: %w", err)
103+
}
104+
fmt.Println("Certificates:", len(certs))
105+
106+
sdData, err := sd.GetData()
107+
if err != nil {
108+
return false, xerrors.Errorf("get data: %w", err)
109+
}
110+
fmt.Println("Data:", len(sdData))
111+
112+
var verifyErr error
113+
var vcerts [][][]*x509.Certificate
114+
115+
sys, err := x509.SystemCertPool()
116+
if err != nil {
117+
return false, xerrors.Errorf("system cert pool: %w", err)
118+
}
119+
opts := x509.VerifyOptions{
120+
Intermediates: sys,
121+
Roots: sys,
122+
}
123+
124+
if sd.IsDetached() {
125+
vcerts, verifyErr = sd.VerifyDetached(message, opts)
126+
} else {
127+
vcerts, verifyErr = sd.Verify(opts)
128+
}
129+
if verifyErr != nil {
130+
logger.Error(ctx, "verify", slog.Error(verifyErr))
131+
}
132+
133+
certChain := dimensions(vcerts)
134+
fmt.Println(certChain)
135+
return verifyErr == nil, nil
136+
}
137+
138+
func openSSLVerify(ctx context.Context, logger slog.Logger, message []byte, signature []byte) (bool, error) {
139+
// 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
140+
tmpdir := os.TempDir()
141+
tmpdir = filepath.Join(tmpdir, "verify-sigs")
142+
defer os.RemoveAll(tmpdir)
143+
os.MkdirAll(tmpdir, 0755)
144+
msgPath := filepath.Join(tmpdir, ".signature.manifest")
145+
err := os.WriteFile(msgPath, message, 0644)
146+
if err != nil {
147+
return false, xerrors.Errorf("write message: %w", err)
148+
}
149+
150+
sigPath := filepath.Join(tmpdir, ".signature.p7s")
151+
err = os.WriteFile(sigPath, signature, 0644)
152+
if err != nil {
153+
return false, xerrors.Errorf("write signature: %w", err)
154+
}
155+
156+
cmd := exec.CommandContext(ctx, "openssl", "smime", "-verify",
157+
"-in", sigPath, "-content", msgPath, "-inform", "DER",
158+
"-CAfile", "/home/steven/go/src/github.com/coder/code-marketplace/extensionsign/testdata/cert2.pem",
159+
)
160+
output := &strings.Builder{}
161+
cmd.Stdout = output
162+
cmd.Stderr = output
163+
err = cmd.Run()
164+
fmt.Println(output.String())
165+
if err != nil {
166+
return false, xerrors.Errorf("run verify %q: %w", cmd.String(), err)
167+
}
168+
169+
return cmd.ProcessState.ExitCode() == 0, nil
170+
}
171+
172+
func vsceSignVerify(ctx context.Context, logger slog.Logger, vsixPath, sigPath string) (bool, error) {
173+
bin := os.Getenv("VSCE_SIGN_PATH")
174+
if bin == "" {
175+
return false, xerrors.Errorf("VSCE_SIGN_PATH not set")
176+
}
177+
178+
cmd := exec.CommandContext(ctx, bin, "verify",
179+
"--package", vsixPath,
180+
"--signaturearchive", sigPath,
181+
"-v",
182+
)
183+
fmt.Println(cmd.String())
184+
output := &strings.Builder{}
185+
cmd.Stdout = output
186+
cmd.Stderr = output
187+
err := cmd.Run()
188+
fmt.Println(output.String())
189+
if err != nil {
190+
return false, xerrors.Errorf("run verify %q: %w", cmd.String(), err)
191+
}
192+
193+
return cmd.ProcessState.ExitCode() == 0, nil
194+
}
195+
196+
func dimensions(chain [][][]*x509.Certificate) string {
197+
var str strings.Builder
198+
for _, top := range chain {
199+
str.WriteString(fmt.Sprintf("Chain, len=%d\n", len(top)))
200+
for _, second := range top {
201+
str.WriteString(fmt.Sprintf(" Certs len=%d\n", len(second)))
202+
for _, cert := range second {
203+
str.WriteString(fmt.Sprintf(" Cert: %s\n", cert.Subject))
204+
}
205+
}
206+
}
207+
return str.String()
208+
}
209+
24210
func compareSignatureSigZips() *cobra.Command {
25211
cmd := &cobra.Command{
26212
Use: "compare",

Diff for: extensionsign/algo.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package extensionsign
2+
3+
import (
4+
"context"
5+
"crypto"
6+
"crypto/x509"
7+
"encoding/pem"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
12+
cms "github.com/github/smimesign/ietf-cms"
13+
"golang.org/x/xerrors"
14+
)
15+
16+
var SigningAlgorithm = OpenSSLSign
17+
18+
func CMSAlgo(data []byte, certs []*x509.Certificate, signer crypto.Signer) (result []byte, err error) {
19+
return cms.SignDetached(data, certs, signer)
20+
}
21+
22+
// openssl smime -sign -signer <cert> -inkey <key> -binary -in .signature.manifest -outform der -out openssl.p7s
23+
func OpenSSLSign(data []byte, certs []*x509.Certificate, signer crypto.Signer) (result []byte, err error) {
24+
tmpdir := os.TempDir()
25+
tmpdir = filepath.Join(tmpdir, "sign-sigs")
26+
defer os.RemoveAll(tmpdir)
27+
28+
err = os.MkdirAll(tmpdir, 0755)
29+
if err != nil {
30+
return nil, xerrors.Errorf("create temp dir: %w", err)
31+
}
32+
33+
certPath := filepath.Join(tmpdir, "certs.pem")
34+
certFile, err := os.OpenFile(certPath, os.O_CREATE|os.O_WRONLY, 0644)
35+
if err != nil {
36+
return nil, xerrors.Errorf("open cert file: %w", err)
37+
}
38+
39+
for _, cert := range certs {
40+
if len(cert.Raw) == 0 {
41+
return nil, xerrors.Errorf("empty certificate")
42+
}
43+
err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
44+
if err != nil {
45+
return nil, err
46+
}
47+
}
48+
49+
keyPath := "/home/steven/go/src/github.com/coder/code-marketplace/extensionsign/testdata/key2.pem"
50+
//keyFile, err := os.Open(keyPath)
51+
//if err != nil {
52+
// return nil, err
53+
//}
54+
//pem.Encode(keyFile, &pem.Block{Type: "PRIVATE KEY", )
55+
56+
msgPath := filepath.Join(tmpdir, ".signature.manifest")
57+
messageFile, err := os.OpenFile(msgPath, os.O_CREATE|os.O_WRONLY, 0644)
58+
if err != nil {
59+
return nil, err
60+
}
61+
_, err = messageFile.Write(data)
62+
if err != nil {
63+
return nil, xerrors.Errorf("write message: %w", err)
64+
}
65+
66+
signed := filepath.Join(tmpdir, "openssl.p7s")
67+
cmd := exec.CommandContext(context.Background(), "openssl", "cms", "-sign",
68+
"-signer", certPath,
69+
"-inkey", keyPath,
70+
"-binary",
71+
"-in", msgPath,
72+
"-outform", "der",
73+
"-out", signed,
74+
)
75+
76+
err = cmd.Run()
77+
if err != nil {
78+
return nil, xerrors.Errorf("run openssl: %w", err)
79+
}
80+
81+
return os.ReadFile(signed)
82+
}

Diff for: extensionsign/doc.go

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
// Package extensionsign is a Go implementation of https://github.com/filiptronicek/node-ovsx-sign
2+
// See https://github.com/eclipse/openvsx/issues/543
23
package extensionsign

0 commit comments

Comments
 (0)