Skip to content

Commit 8846c22

Browse files
authored
Move sig verification from installer to resolver (#257)
1 parent 6633251 commit 8846c22

6 files changed

Lines changed: 260 additions & 86 deletions

File tree

pkg/pm/installer/installer.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121

2222
"go.wpm.so/cli/pkg/archive"
2323
"go.wpm.so/cli/pkg/pm/registry"
24-
"go.wpm.so/cli/pkg/pm/signatures"
2524
"go.wpm.so/cli/pkg/pm/wpmjson/types"
2625
"go.wpm.so/cli/pkg/pm/wpmjson/validator"
2726
)
@@ -40,7 +39,6 @@ type Installer struct {
4039

4140
client registry.Client
4241
extractSem chan struct{}
43-
keysJson signatures.KeysJson
4442
logger func(format string, args ...any)
4543
}
4644

@@ -106,12 +104,6 @@ func sweepStaleRunDirs(tmpDir string) {
106104
}
107105

108106
func (i *Installer) InstallAll(ctx context.Context, plan []Action, progressFn func(Action)) error {
109-
keys, err := i.client.GetKeysJson(ctx)
110-
if err != nil {
111-
return fmt.Errorf("failed to fetch public keys for signature verification: %w", err)
112-
}
113-
i.keysJson = keys
114-
115107
g, ctx := errgroup.WithContext(ctx)
116108
g.SetLimit(i.concurrency)
117109

@@ -151,21 +143,6 @@ func (i *Installer) install(ctx context.Context, action Action) error {
151143
}
152144

153145
func (i *Installer) installOrUpdate(ctx context.Context, action Action, targetDir string) error {
154-
sigs := action.Signatures
155-
if len(sigs) == 0 {
156-
return fmt.Errorf("no signatures found for package %s@%s", action.Name, action.Version)
157-
}
158-
159-
err := signatures.Verify(
160-
i.keysJson,
161-
sigs[0].KeyID,
162-
sigs[0].Sig,
163-
fmt.Appendf(nil, "%s:%s:%s", action.Name, action.Version, action.Digest),
164-
)
165-
if err != nil {
166-
return fmt.Errorf("signature verification failed for package %s@%s: %w", action.Name, action.Version, err)
167-
}
168-
169146
path := tarballPath(action.Name, action.Version)
170147
resp, err := i.client.DownloadTarball(ctx, path)
171148
if err != nil {

pkg/pm/installer/plan.go

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66

77
"go.wpm.so/cli/pkg/pm/resolution"
88
"go.wpm.so/cli/pkg/pm/wpmjson"
9-
"go.wpm.so/cli/pkg/pm/wpmjson/manifest"
109
"go.wpm.so/cli/pkg/pm/wpmjson/types"
1110
"go.wpm.so/cli/pkg/pm/wpmlock"
1211
)
@@ -21,12 +20,11 @@ const (
2120

2221
// Action represents a single operation to be performed on the filesystem
2322
type Action struct {
24-
Type ActionType
25-
Name string
26-
Version string
27-
Signatures []manifest.Signature
28-
Digest string // Sha256 digest
29-
PkgType types.PackageType
23+
Type ActionType
24+
Name string
25+
Version string
26+
Digest string // Sha256 digest
27+
PkgType types.PackageType
3028
}
3129

3230
// CalculatePlan determines filesystem operations based on lockfile, resolved tree, and flags.
@@ -109,34 +107,31 @@ func resolveAction(name string, node resolution.Node, lock *wpmlock.Lockfile, ex
109107
oldPkg, inLock := lock.Packages[name]
110108
if !inLock {
111109
return Action{
112-
Type: ActionInstall,
113-
Name: name,
114-
Version: node.Version,
115-
Signatures: node.Signatures,
116-
Digest: node.Digest,
117-
PkgType: node.Type,
110+
Type: ActionInstall,
111+
Name: name,
112+
Version: node.Version,
113+
Digest: node.Digest,
114+
PkgType: node.Type,
118115
}, true
119116
}
120117

121118
if oldPkg.Version != node.Version || oldPkg.Digest != node.Digest {
122119
return Action{
123-
Type: ActionUpdate,
124-
Name: name,
125-
Version: node.Version,
126-
Signatures: node.Signatures,
127-
Digest: node.Digest,
128-
PkgType: node.Type,
120+
Type: ActionUpdate,
121+
Name: name,
122+
Version: node.Version,
123+
Digest: node.Digest,
124+
PkgType: node.Type,
129125
}, true
130126
}
131127

132128
if !exists {
133129
return Action{
134-
Type: ActionInstall,
135-
Name: name,
136-
Version: node.Version,
137-
Signatures: node.Signatures,
138-
Digest: node.Digest,
139-
PkgType: node.Type,
130+
Type: ActionInstall,
131+
Name: name,
132+
Version: node.Version,
133+
Digest: node.Digest,
134+
PkgType: node.Type,
140135
}, true
141136
}
142137

pkg/pm/registry/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type client struct {
2828
// registry
2929
type Client interface {
3030
Whoami(ctx context.Context, token string) (string, error)
31-
GetKeysJson(ctx context.Context) (signatures.KeysJson, error)
31+
GetKeysJson(ctx context.Context) (signatures.Keys, error)
3232
DownloadTarball(ctx context.Context, url string) (io.ReadCloser, error)
3333
PutPackage(ctx context.Context, data *manifest.Package, tarball io.Reader) error
3434
GetPackageManifest(ctx context.Context, packageName, versionOrTag string, force bool) (*manifest.Package, error)
@@ -170,8 +170,8 @@ func (c *client) Whoami(ctx context.Context, token string) (string, error) {
170170
}
171171

172172
// GetKeysJson retrieves the public keys from the registry
173-
func (c *client) GetKeysJson(ctx context.Context) (signatures.KeysJson, error) {
174-
var keys signatures.KeysJson
173+
func (c *client) GetKeysJson(ctx context.Context) (signatures.Keys, error) {
174+
var keys signatures.Keys
175175

176176
err := c.restClient.DoWithContext(
177177
ctx,

pkg/pm/resolution/resolver.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"golang.org/x/sync/errgroup"
1212

1313
"go.wpm.so/cli/pkg/pm/registry"
14+
"go.wpm.so/cli/pkg/pm/signatures"
1415
"go.wpm.so/cli/pkg/pm/wpmjson"
1516
"go.wpm.so/cli/pkg/pm/wpmjson/manifest"
1617
"go.wpm.so/cli/pkg/pm/wpmjson/types"
@@ -37,6 +38,7 @@ type Resolver struct {
3738
rootConfig *wpmjson.Config
3839
lockfile *wpmlock.Lockfile
3940
client registry.Client
41+
verifier *signatures.Verifier
4042
}
4143

4244
func New(rootConfig *wpmjson.Config, lockfile *wpmlock.Lockfile, client registry.Client) *Resolver {
@@ -59,6 +61,12 @@ type fetchResult struct {
5961
}
6062

6163
func (r *Resolver) Resolve(ctx context.Context, progress ProgressReporter, w io.Writer) (map[string]Node, error) {
64+
keys, err := r.client.GetKeysJson(ctx)
65+
if err != nil {
66+
return nil, fmt.Errorf("failed to fetch signing keys: %w", err)
67+
}
68+
r.verifier = signatures.New(keys)
69+
6270
resolved := make(map[string]Node)
6371
queue := r.seedQueue()
6472

@@ -94,7 +102,15 @@ func (r *Resolver) Resolve(ctx context.Context, progress ProgressReporter, w io.
94102
}
95103

96104
func (r *Resolver) seedQueue() []dependencyRequest {
97-
var queue []dependencyRequest
105+
n := 0
106+
if r.rootConfig.Dependencies != nil {
107+
n += len(*r.rootConfig.Dependencies)
108+
}
109+
if r.rootConfig.DevDependencies != nil {
110+
n += len(*r.rootConfig.DevDependencies)
111+
}
112+
113+
queue := make([]dependencyRequest, 0, n)
98114
if r.rootConfig.Dependencies != nil {
99115
for name, version := range *r.rootConfig.Dependencies {
100116
queue = append(queue, dependencyRequest{name: name, version: version, requestor: "<root>"})
@@ -108,21 +124,26 @@ func (r *Resolver) seedQueue() []dependencyRequest {
108124
return queue
109125
}
110126

127+
type requestKey struct {
128+
name string
129+
version string
130+
}
131+
111132
// dedupeRequests drops requests already satisfied at the same version
112133
// and folds identical name@version pairs in this iteration into one entry.
113-
func dedupeRequests(queue []dependencyRequest, resolved map[string]Node) map[string]dependencyRequest {
114-
uniqueRequests := make(map[string]dependencyRequest)
134+
func dedupeRequests(queue []dependencyRequest, resolved map[string]Node) map[requestKey]dependencyRequest {
135+
unique := make(map[requestKey]dependencyRequest, len(queue))
115136
for _, req := range queue {
116137
if exists, ok := resolved[req.name]; ok && exists.Version == req.version {
117138
continue
118139
}
119-
uniqueRequests[req.name+"@"+req.version] = req
140+
unique[requestKey{req.name, req.version}] = req
120141
}
121-
return uniqueRequests
142+
return unique
122143
}
123144

124145
// fetchAll fetches metadata for every request concurrently and returns the collected results.
125-
func (r *Resolver) fetchAll(ctx context.Context, requests map[string]dependencyRequest, progress ProgressReporter, w io.Writer) ([]fetchResult, error) {
146+
func (r *Resolver) fetchAll(ctx context.Context, requests map[requestKey]dependencyRequest, progress ProgressReporter, w io.Writer) ([]fetchResult, error) {
126147
results := make(chan fetchResult, len(requests))
127148
g, gtx := errgroup.WithContext(ctx)
128149
g.SetLimit(16)
@@ -137,6 +158,9 @@ func (r *Resolver) fetchAll(ctx context.Context, requests map[string]dependencyR
137158
if err != nil {
138159
return fmt.Errorf("failed to fetch metadata for %s@%s required by %s: %w", req.name, req.version, req.requestor, err)
139160
}
161+
if err := r.verifier.Verify(manifest); err != nil {
162+
return fmt.Errorf("signature verification failed for %s@%s required by %s: %w", req.name, req.version, req.requestor, err)
163+
}
140164
results <- fetchResult{req: req, manifest: manifest}
141165
return nil
142166
})

0 commit comments

Comments
 (0)