1 package jose
2
3 import (
4 "bytes"
5 "context"
6 "fmt"
7 "io"
8 "os/exec"
9 "strings"
10 "sync"
11 "testing"
12
13 "github.com/lestrrat-go/jwx/internal/jwxtest"
14 "github.com/pkg/errors"
15 )
16
17 var executablePath string
18 var muExecutablePath sync.RWMutex
19
20 func init() {
21 findExecutable()
22 }
23
24 func SetExecutable(path string) {
25 muExecutablePath.Lock()
26 defer muExecutablePath.Unlock()
27 executablePath = path
28 }
29
30 func findExecutable() {
31 p, err := exec.LookPath("jose")
32 if err == nil {
33 SetExecutable(p)
34 }
35 }
36
37 func ExecutablePath() string {
38 muExecutablePath.RLock()
39 defer muExecutablePath.RUnlock()
40
41 return executablePath
42 }
43
44 func Available() bool {
45 muExecutablePath.RLock()
46 defer muExecutablePath.RUnlock()
47
48 return executablePath != ""
49 }
50
51 func RunJoseCommand(ctx context.Context, t *testing.T, args []string, outw, errw io.Writer) error {
52 var errout bytes.Buffer
53 var capout bytes.Buffer
54
55 cmd := exec.CommandContext(ctx, ExecutablePath(), args...)
56 if outw == nil {
57 cmd.Stdout = &capout
58 } else {
59 cmd.Stdout = io.MultiWriter(outw, &capout)
60 }
61
62 if errw == nil {
63 cmd.Stderr = &errout
64 } else {
65 cmd.Stderr = io.MultiWriter(outw, &errout)
66 }
67
68 t.Logf("Executing `%s %s`\n", ExecutablePath(), strings.Join(args, " "))
69 if err := cmd.Run(); err != nil {
70 t.Logf(`failed to execute command: %s`, err)
71
72 if capout.Len() > 0 {
73 t.Logf("captured output: %s", capout.String())
74 }
75
76 if errout.Len() > 0 {
77 t.Logf("captured error: %s", errout.String())
78 }
79
80 return errors.Wrap(err, `failed to execute command`)
81 }
82
83 return nil
84 }
85
86
87
88
89
90 func GenerateJwk(ctx context.Context, t *testing.T, template string) (string, func(), error) {
91 t.Helper()
92
93 file, cleanup, err := jwxtest.CreateTempFile("jwx-jose-key-*.jwk")
94 if err != nil {
95 return "", nil, errors.Wrap(err, "failed to create temporary file")
96 }
97
98 if err := RunJoseCommand(ctx, t, []string{"jwk", "gen", "-i", template, "-o", file.Name()}, nil, nil); err != nil {
99 return "", nil, errors.Wrap(err, `failed to generate key`)
100 }
101
102 return file.Name(), cleanup, nil
103 }
104
105
106
107
108
109 func EncryptJwe(ctx context.Context, t *testing.T, payload []byte, alg string, keyfile string, enc string, compact bool) (string, func(), error) {
110 t.Helper()
111
112 var arg string
113 if alg == "dir" {
114 arg = fmt.Sprintf(`{"protected":{"alg":"dir","enc":"%s"}}`, enc)
115 } else {
116 arg = fmt.Sprintf(`{"protected":{"enc":"%s"}}`, enc)
117 }
118
119 cmdargs := []string{"jwe", "enc", "-k", keyfile, "-i", arg}
120 if compact {
121 cmdargs = append(cmdargs, "-c")
122 }
123
124 var pfile string
125 if len(payload) > 0 {
126 fn, pcleanup, perr := jwxtest.WriteFile("jwx-jose-payload-*", bytes.NewReader(payload))
127 if perr != nil {
128 return "", nil, errors.Wrap(perr, `failed to write payload to file`)
129 }
130
131 cmdargs = append(cmdargs, "-I", fn)
132 pfile = fn
133 defer pcleanup()
134 }
135
136 ofile, ocleanup, oerr := jwxtest.CreateTempFile(`jwx-jose-key-*.jwe`)
137 if oerr != nil {
138 return "", nil, errors.Wrap(oerr, "failed to create temporary file")
139 }
140
141 cmdargs = append(cmdargs, "-o", ofile.Name())
142
143 if err := RunJoseCommand(ctx, t, cmdargs, nil, nil); err != nil {
144 defer ocleanup()
145 if pfile != "" {
146 jwxtest.DumpFile(t, pfile)
147 }
148 jwxtest.DumpFile(t, keyfile)
149 return "", nil, errors.Wrap(err, `failed to encrypt message`)
150 }
151
152 return ofile.Name(), ocleanup, nil
153 }
154
155 func DecryptJwe(ctx context.Context, t *testing.T, cfile, kfile string) ([]byte, error) {
156 t.Helper()
157
158 cmdargs := []string{"jwe", "dec", "-i", cfile, "-k", kfile}
159 var output bytes.Buffer
160 if err := RunJoseCommand(ctx, t, cmdargs, &output, nil); err != nil {
161 jwxtest.DumpFile(t, cfile)
162 jwxtest.DumpFile(t, kfile)
163
164 return nil, errors.Wrap(err, `failed to decrypt message`)
165 }
166
167 return output.Bytes(), nil
168 }
169
170 func FmtJwe(ctx context.Context, t *testing.T, data []byte) ([]byte, error) {
171 t.Helper()
172
173 fn, pcleanup, perr := jwxtest.WriteFile("jwx-jose-fmt-data-*", bytes.NewReader(data))
174 if perr != nil {
175 return nil, errors.Wrap(perr, `failed to write data to file`)
176 }
177 defer pcleanup()
178
179 cmdargs := []string{"jwe", "fmt", "-i", fn}
180
181 var output bytes.Buffer
182 if err := RunJoseCommand(ctx, t, cmdargs, &output, nil); err != nil {
183 jwxtest.DumpFile(t, fn)
184
185 return nil, errors.Wrap(err, `failed to format JWE message`)
186 }
187
188 return output.Bytes(), nil
189 }
190
191
192
193
194
195 func SignJws(ctx context.Context, t *testing.T, payload []byte, keyfile string, compact bool) (string, func(), error) {
196 t.Helper()
197
198 cmdargs := []string{"jws", "sig", "-k", keyfile}
199 if compact {
200 cmdargs = append(cmdargs, "-c")
201 }
202
203 var pfile string
204 if len(payload) > 0 {
205 fn, pcleanup, perr := jwxtest.WriteFile("jwx-jose-payload-*", bytes.NewReader(payload))
206 if perr != nil {
207 return "", nil, errors.Wrap(perr, `failed to write payload to file`)
208 }
209
210 cmdargs = append(cmdargs, "-I", fn)
211 pfile = fn
212 defer pcleanup()
213 }
214
215 ofile, ocleanup, oerr := jwxtest.CreateTempFile(`jwx-jose-sig-*.jws`)
216 if oerr != nil {
217 return "", nil, errors.Wrap(oerr, "failed to create temporary file")
218 }
219
220 cmdargs = append(cmdargs, "-o", ofile.Name())
221
222 if err := RunJoseCommand(ctx, t, cmdargs, nil, nil); err != nil {
223 defer ocleanup()
224 if pfile != "" {
225 jwxtest.DumpFile(t, pfile)
226 }
227 jwxtest.DumpFile(t, keyfile)
228 return "", nil, errors.Wrap(err, `failed to sign message`)
229 }
230
231 return ofile.Name(), ocleanup, nil
232 }
233
234 func VerifyJws(ctx context.Context, t *testing.T, cfile, kfile string) ([]byte, error) {
235 t.Helper()
236
237 cmdargs := []string{"jws", "ver", "-i", cfile, "-k", kfile, "-O-"}
238 var output bytes.Buffer
239 if err := RunJoseCommand(ctx, t, cmdargs, &output, nil); err != nil {
240 jwxtest.DumpFile(t, cfile)
241 jwxtest.DumpFile(t, kfile)
242
243 return nil, errors.Wrap(err, `failed to decrypt message`)
244 }
245
246 return output.Bytes(), nil
247 }
248
View as plain text