|
12 | 12 | // prefix or the deprecated raw ssh-* form, are written to a temporary key |
13 | 13 | // file and passed with -U so ssh-keygen signs via ssh-agent. |
14 | 14 | // |
15 | | -// The program must be a bare binary name resolvable on $PATH (e.g. "gpg", |
16 | | -// not "/usr/bin/gpg" or "./gpg"). |
| 15 | +// The program may be a bare binary name resolved on $PATH (e.g. "gpg") or |
| 16 | +// a path to an executable (e.g. "/usr/bin/gpg" or "./gpg"). |
17 | 17 | package program |
18 | 18 |
|
19 | 19 | import ( |
|
49 | 49 | ErrUnsupportedFormat = errors.New("unsupported signing format") |
50 | 50 | // ErrEmptyProgram is returned when no program name was provided. |
51 | 51 | ErrEmptyProgram = errors.New("program is empty") |
52 | | - // ErrInvalidProgram is returned when program is not a bare binary name |
53 | | - // (i.e. contains a path separator). |
54 | | - ErrInvalidProgram = errors.New("program must be a bare binary name") |
55 | | - // ErrProgramNotFound is returned when program cannot be resolved on $PATH. |
56 | | - ErrProgramNotFound = errors.New("program not found in PATH") |
| 52 | + // ErrProgramNotFound is returned when program cannot be resolved as |
| 53 | + // either a path to an existing executable or a bare name on $PATH. |
| 54 | + ErrProgramNotFound = errors.New("program not found") |
57 | 55 | // ErrEmptySigningKey is returned when no signing key was provided. |
58 | 56 | ErrEmptySigningKey = errors.New("signing key is empty") |
59 | 57 | // ErrNilMessage is returned when a nil message is passed to Sign. |
|
69 | 67 | // New returns a signer that invokes program to produce a signature in the |
70 | 68 | // given format using the provided signing key. |
71 | 69 | // |
72 | | -// program must be a bare binary name (no path separators) and must resolve |
73 | | -// on $PATH at the time of this call; otherwise [ErrInvalidProgram] or |
| 70 | +// program is either a bare binary name resolved on $PATH or a path to an |
| 71 | +// executable; it must resolve at the time of this call, otherwise |
74 | 72 | // [ErrProgramNotFound] is returned. |
75 | 73 | // |
76 | 74 | // For OpenPGP and X.509, signingKey is the key ID or fingerprint passed to |
@@ -125,25 +123,30 @@ func newSigner( |
125 | 123 | }, nil |
126 | 124 | } |
127 | 125 |
|
128 | | -// resolveProgram validates that program is a bare binary name and returns |
129 | | -// the absolute path that lookPath resolves it to. The resolved path is |
130 | | -// captured at New() time and reused by Sign() so a $PATH change between the |
131 | | -// two calls cannot redirect execution to a different binary. |
| 126 | +// resolveProgram returns the absolute path that lookPath resolves program |
| 127 | +// to. When program contains a path separator, lookPath (exec.LookPath in |
| 128 | +// production) verifies the file exists and is executable and returns the |
| 129 | +// path as given; otherwise it is searched for on $PATH. Relative results |
| 130 | +// (e.g. from a "./gpg" input) are anchored to the current working directory |
| 131 | +// via filepath.Abs so that the path captured at New() time cannot be |
| 132 | +// redirected by a $PATH change or a working-directory change before Sign() |
| 133 | +// runs. |
132 | 134 | func resolveProgram(program string, lookPath func(string) (string, error)) (string, error) { |
133 | 135 | if program == "" { |
134 | 136 | return "", ErrEmptyProgram |
135 | 137 | } |
136 | 138 |
|
137 | | - if filepath.Base(program) != program { |
138 | | - return "", fmt.Errorf("%w: %q", ErrInvalidProgram, program) |
139 | | - } |
140 | | - |
141 | 139 | resolved, err := lookPath(program) |
142 | 140 | if err != nil { |
143 | 141 | return "", fmt.Errorf("%w: %q: %w", ErrProgramNotFound, program, err) |
144 | 142 | } |
145 | 143 |
|
146 | | - return resolved, nil |
| 144 | + absolute, err := filepath.Abs(resolved) |
| 145 | + if err != nil { |
| 146 | + return "", fmt.Errorf("resolving absolute path for %q: %w", program, err) |
| 147 | + } |
| 148 | + |
| 149 | + return absolute, nil |
147 | 150 | } |
148 | 151 |
|
149 | 152 | // Sign reads message and returns the signature produced by the external |
|
0 commit comments