Skip to content

Commit 3b91874

Browse files
committed
have some sort of signing going
1 parent fe51069 commit 3b91874

File tree

6 files changed

+230
-2
lines changed

6 files changed

+230
-2
lines changed

cli/signature.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
"golang.org/x/xerrors"
9+
10+
"github.com/coder/code-marketplace/internal/extensionsign"
11+
)
12+
13+
func signature() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "signature",
16+
Aliases: []string{"sig", "sigs", "signatures"},
17+
}
18+
cmd.AddCommand(compareSignatureSigZips())
19+
return cmd
20+
}
21+
22+
func compareSignatureSigZips() *cobra.Command {
23+
cmd := &cobra.Command{
24+
Use: "compare",
25+
Args: cobra.ExactArgs(2),
26+
RunE: func(cmd *cobra.Command, args []string) error {
27+
decode := func(path string) (extensionsign.SignatureManifest, error) {
28+
data, err := os.ReadFile(path)
29+
if err != nil {
30+
return extensionsign.SignatureManifest{}, xerrors.Errorf("read %q: %w", args[0], err)
31+
}
32+
33+
sig, err := extensionsign.ExtractSignatureManifest(data)
34+
if err != nil {
35+
return extensionsign.SignatureManifest{}, xerrors.Errorf("unmarshal %q: %w", path, err)
36+
}
37+
return sig, nil
38+
}
39+
40+
a, err := decode(args[0])
41+
if err != nil {
42+
return err
43+
}
44+
b, err := decode(args[1])
45+
if err != nil {
46+
return err
47+
}
48+
49+
_, _ = fmt.Fprintf(os.Stdout, "Signature A:%s\n", a)
50+
_, _ = fmt.Fprintf(os.Stdout, "Signature B:%s\n", b)
51+
err = a.Equal(b)
52+
if err != nil {
53+
return err
54+
}
55+
56+
_, _ = fmt.Fprintf(os.Stdout, "Signatures are equal\n")
57+
return nil
58+
},
59+
}
60+
return cmd
61+
}

internal/extensionsign/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package extensionsign is a Go implementation of https://github.com/filiptronicek/node-ovsx-sign
2+
package extensionsign

internal/extensionsign/key.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package extensionsign
2+
3+
import (
4+
"crypto/ed25519"
5+
"crypto/rand"
6+
)
7+
8+
func GenerateKey() (ed25519.PrivateKey, error) {
9+
_, private, err := ed25519.GenerateKey(rand.Reader)
10+
if err != nil {
11+
return nil, err
12+
}
13+
return private, nil
14+
}

internal/extensionsign/sigmanifest.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package extensionsign
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"errors"
7+
"fmt"
8+
"io"
9+
10+
"github.com/cloudflare/cfssl/scan/crypto/sha256"
11+
"golang.org/x/xerrors"
12+
13+
"github.com/coder/code-marketplace/storage"
14+
)
15+
16+
type SignatureManifest struct {
17+
Package File
18+
// Entries is base64(filepath) -> File
19+
Entries map[string]File
20+
}
21+
22+
func (a SignatureManifest) String() string {
23+
return fmt.Sprintf("Package %q with Entries: %d", a.Package.Digests.SHA256, len(a.Entries))
24+
}
25+
26+
// Equal is helpful for debugging
27+
func (a SignatureManifest) Equal(b SignatureManifest) error {
28+
var err error
29+
if err := a.Package.Equal(b.Package); err != nil {
30+
err = errors.Join(err, xerrors.Errorf("package: %w", err))
31+
}
32+
33+
if len(a.Entries) != len(b.Entries) {
34+
err = errors.Join(err, xerrors.Errorf("entry count mismatch: %d != %d", len(a.Entries), len(b.Entries)))
35+
}
36+
37+
for k, v := range a.Entries {
38+
if _, ok := b.Entries[k]; !ok {
39+
err = errors.Join(err, xerrors.Errorf("entry %q not found in second set", k))
40+
continue
41+
}
42+
if err := v.Equal(b.Entries[k]); err != nil {
43+
err = errors.Join(err, xerrors.Errorf("entry %q: %w", k, err))
44+
}
45+
}
46+
return err
47+
}
48+
49+
type File struct {
50+
Size int64 `json:"size"`
51+
Digests Digests `json:"digests"`
52+
}
53+
54+
func (f File) Equal(b File) error {
55+
if f.Size != b.Size {
56+
return xerrors.Errorf("size mismatch: %d != %d", f.Size, b.Size)
57+
}
58+
if f.Digests.SHA256 != b.Digests.SHA256 {
59+
return xerrors.Errorf("sha256 mismatch: %s != %s", f.Digests.SHA256, b.Digests.SHA256)
60+
}
61+
return nil
62+
}
63+
64+
func FileManifest(file io.Reader) (File, error) {
65+
hash := sha256.New()
66+
67+
n, err := io.Copy(hash, file)
68+
if err != nil {
69+
return File{}, xerrors.Errorf("hash file: %w", err)
70+
}
71+
72+
return File{
73+
Size: n,
74+
Digests: Digests{
75+
SHA256: base64.StdEncoding.EncodeToString(hash.Sum(nil)),
76+
},
77+
}, nil
78+
}
79+
80+
type Digests struct {
81+
SHA256 string `json:"sha256"`
82+
}
83+
84+
// GenerateSignatureManifest generates a signature manifest for a VSIX file.
85+
// It does not sign the manifest.
86+
func GenerateSignatureManifest(vsixFile []byte) (SignatureManifest, error) {
87+
pkgManifest, err := FileManifest(bytes.NewReader(vsixFile))
88+
if err != nil {
89+
return SignatureManifest{}, xerrors.Errorf("package manifest: %w", err)
90+
}
91+
92+
manifest := SignatureManifest{
93+
Package: pkgManifest,
94+
Entries: make(map[string]File),
95+
}
96+
97+
err = storage.ExtractZip(vsixFile, func(name string, reader io.Reader) error {
98+
fm, err := FileManifest(reader)
99+
if err != nil {
100+
return xerrors.Errorf("file %q: %w", name, err)
101+
}
102+
manifest.Entries[base64.StdEncoding.EncodeToString([]byte(name))] = fm
103+
return nil
104+
})
105+
106+
if err != nil {
107+
return SignatureManifest{}, err
108+
}
109+
110+
return manifest, nil
111+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package extensionsign_test
2+
3+
import "testing"
4+
5+
func TestManifestEqual(t *testing.T) {
6+
7+
}

internal/extensionsign/sigzip.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package extensionsign
33
import (
44
"archive/zip"
55
"bytes"
6+
"crypto/ed25519"
67
"encoding/json"
78

89
"golang.org/x/xerrors"
@@ -25,7 +26,10 @@ func ExtractSignatureManifest(zip []byte) (SignatureManifest, error) {
2526
return manifest, nil
2627
}
2728

28-
func Zip(manifest SignatureManifest) ([]byte, error) {
29+
// SignAndZip signs a manifest and zips it up
30+
// Should be a PCKS8 key
31+
// TODO: Support other key types
32+
func SignAndZip(key ed25519.PrivateKey, manifest SignatureManifest) ([]byte, error) {
2933
var buf bytes.Buffer
3034
w := zip.NewWriter(&buf)
3135

@@ -34,11 +38,40 @@ func Zip(manifest SignatureManifest) ([]byte, error) {
3438
return nil, xerrors.Errorf("create manifest: %w", err)
3539
}
3640

37-
err = json.NewEncoder(manFile).Encode(manifest)
41+
manifestData, err := json.Marshal(manifest)
3842
if err != nil {
3943
return nil, xerrors.Errorf("encode manifest: %w", err)
4044
}
4145

46+
_, err = manFile.Write(manifestData)
47+
if err != nil {
48+
return nil, xerrors.Errorf("write manifest: %w", err)
49+
}
50+
51+
// Empty file
52+
_, err = w.Create(".signature.p7s")
53+
if err != nil {
54+
return nil, xerrors.Errorf("create empty p7s signature: %w", err)
55+
}
56+
57+
// Actual sig
58+
sigFile, err := w.Create(".signature.sig")
59+
if err != nil {
60+
return nil, xerrors.Errorf("create signature: %w", err)
61+
}
62+
63+
signature := ed25519.Sign(key, manifestData)
64+
65+
//signature, err := key.Sign(rand.Reader, manifestData, crypto.SHA512)
66+
//if err != nil {
67+
// return nil, xerrors.Errorf("sign: %w", err)
68+
//}
69+
70+
_, err = sigFile.Write(signature)
71+
if err != nil {
72+
return nil, xerrors.Errorf("write signature: %w", err)
73+
}
74+
4275
err = w.Close()
4376
if err != nil {
4477
return nil, xerrors.Errorf("close zip: %w", err)

0 commit comments

Comments
 (0)