@@ -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