@@ -7,35 +7,53 @@ import (
77 "io"
88 "io/fs"
99 "path/filepath"
10+ "strings"
1011
1112 "github.com/spf13/afero/mem"
1213 "golang.org/x/xerrors"
1314
15+ "cdr.dev/slog"
16+
1417 "github.com/coder/code-marketplace/extensionsign"
1518)
1619
1720var _ Storage = (* Signature )(nil )
1821
1922const (
20- SigzipFilename = "extension.sigzip "
21- sigManifestName = ".signature.manifest"
23+ SigzipFileExtension = ".signature.p7s "
24+ sigManifestName = ".signature.manifest"
2225)
2326
27+ func SignatureZipFilename (manifest * VSIXManifest ) string {
28+ return ExtensionVSIXNameFromManifest (manifest ) + SigzipFileExtension
29+ }
30+
2431// Signature is a storage wrapper that can sign extensions on demand.
2532type Signature struct {
2633 // Signer if provided, will be used to sign extensions. If not provided,
2734 // no extensions will be signed.
2835 Signer crypto.Signer
36+ Logger slog.Logger
37+ // SaveSigZips is a flag that will save the signed extension to disk.
38+ // This is useful for debugging, but the server will never use this file.
39+ saveSigZips bool
2940 Storage
3041}
3142
32- func NewSignatureStorage (signer crypto.Signer , s Storage ) * Signature {
43+ func NewSignatureStorage (logger slog. Logger , signer crypto.Signer , s Storage ) * Signature {
3344 return & Signature {
3445 Signer : signer ,
3546 Storage : s ,
3647 }
3748}
3849
50+ func (s * Signature ) SaveSigZips () {
51+ if ! s .saveSigZips {
52+ s .Logger .Info (context .Background (), "extension signatures will be saved to disk, do not use this in production" )
53+ }
54+ s .saveSigZips = true
55+ }
56+
3957func (s * Signature ) SigningEnabled () bool {
4058 return s .Signer != nil
4159}
@@ -49,14 +67,26 @@ func (s *Signature) AddExtension(ctx context.Context, manifest *VSIXManifest, vs
4967 return "" , xerrors .Errorf ("generate signature manifest: %w" , err )
5068 }
5169
52- data , err := json .Marshal (sigManifest )
70+ sigManifestJSON , err := json .Marshal (sigManifest )
5371 if err != nil {
5472 return "" , xerrors .Errorf ("encode signature manifest: %w" , err )
5573 }
5674
75+ if s .SigningEnabled () && s .saveSigZips {
76+ signed , err := s .SigZip (ctx , vsix , sigManifestJSON )
77+ if err != nil {
78+ s .Logger .Error (ctx , "signing manifest" , slog .Error (err ))
79+ return "" , xerrors .Errorf ("sign and zip manifest: %w" , err )
80+ }
81+ extra = append (extra , File {
82+ RelativePath : SignatureZipFilename (manifest ),
83+ Content : signed ,
84+ })
85+ }
86+
5787 return s .Storage .AddExtension (ctx , manifest , vsix , append (extra , File {
5888 RelativePath : sigManifestName ,
59- Content : data ,
89+ Content : sigManifestJSON ,
6090 })... )
6191}
6292
@@ -68,14 +98,14 @@ func (s *Signature) Manifest(ctx context.Context, publisher, name string, versio
6898
6999 if s .SigningEnabled () {
70100 for _ , asset := range manifest .Assets .Asset {
71- if asset .Path == SigzipFilename {
101+ if asset .Path == SignatureZipFilename ( manifest ) {
72102 // Already signed
73103 return manifest , nil
74104 }
75105 }
76106 manifest .Assets .Asset = append (manifest .Assets .Asset , VSIXAsset {
77107 Type : VSIXSignatureType ,
78- Path : SigzipFilename ,
108+ Path : SignatureZipFilename ( manifest ) ,
79109 Addressable : "true" ,
80110 })
81111 return manifest , nil
@@ -84,7 +114,7 @@ func (s *Signature) Manifest(ctx context.Context, publisher, name string, versio
84114}
85115
86116// Open will intercept requests for signed extensions payload.
87- // It does this by looking for 'SigzipFilename ' or p7s.sig.
117+ // It does this by looking for 'SigzipFileExtension ' or p7s.sig.
88118//
89119// The signed payload and signing process is taken from:
90120// https://github.com/filiptronicek/node-ovsx-sign
@@ -105,7 +135,10 @@ func (s *Signature) Manifest(ctx context.Context, publisher, name string, versio
105135// source implementation. Ideally this marketplace would match Microsoft's
106136// marketplace API.
107137func (s * Signature ) Open (ctx context.Context , fp string ) (fs.File , error ) {
108- if s .SigningEnabled () && filepath .Base (fp ) == SigzipFilename {
138+ if s .SigningEnabled () && strings .HasSuffix (filepath .Base (fp ), SigzipFileExtension ) {
139+ base := filepath .Base (fp )
140+ vsixPath := strings .TrimSuffix (base , SigzipFileExtension )
141+
109142 // hijack this request, sign the sig manifest
110143 manifest , err := s .Storage .Open (ctx , filepath .Join (filepath .Dir (fp ), sigManifestName ))
111144 if err != nil {
@@ -121,15 +154,39 @@ func (s *Signature) Open(ctx context.Context, fp string) (fs.File, error) {
121154 return nil , xerrors .Errorf ("read signature manifest: %w" , err )
122155 }
123156
124- signed , err := extensionsign .SignAndZipManifest (s .Signer , manifestData )
157+ vsix , err := s .Storage .Open (ctx , filepath .Join (filepath .Dir (fp ), vsixPath + ".vsix" ))
158+ if err != nil {
159+ // If this file is missing, it means the extension was added before
160+ // signatures were handled by the marketplace.
161+ // TODO: Generate the sig manifest payload and insert it?
162+ return nil , xerrors .Errorf ("open signature manifest: %w" , err )
163+ }
164+ defer vsix .Close ()
165+
166+ vsixData , err := io .ReadAll (vsix )
167+ if err != nil {
168+ return nil , xerrors .Errorf ("read signature manifest: %w" , err )
169+ }
170+
171+ // TODO: Fetch the VSIX payload from the storage
172+ signed , err := s .SigZip (ctx , vsixData , manifestData )
125173 if err != nil {
126174 return nil , xerrors .Errorf ("sign and zip manifest: %w" , err )
127175 }
128176
129- f := mem .NewFileHandle (mem .CreateFile (SigzipFilename ))
177+ f := mem .NewFileHandle (mem .CreateFile (fp ))
130178 _ , err = f .Write (signed )
131179 return f , err
132180 }
133181
134182 return s .Storage .Open (ctx , fp )
135183}
184+
185+ func (s * Signature ) SigZip (ctx context.Context , vsix []byte , sigManifest []byte ) ([]byte , error ) {
186+ signed , err := extensionsign .SignAndZipManifest (s .Signer , vsix , sigManifest )
187+ if err != nil {
188+ s .Logger .Error (ctx , "signing manifest" , slog .Error (err ))
189+ return nil , xerrors .Errorf ("sign and zip manifest: %w" , err )
190+ }
191+ return signed , nil
192+ }
0 commit comments