...

Source file src/github.com/lestrrat-go/jwx/internal/jose/jose.go

Documentation: github.com/lestrrat-go/jwx/internal/jose

     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  // GenerateJwk creates a new key using the jose tool, and returns its filename and
    87  // a cleanup function.
    88  // The caller is responsible for calling the cleanup
    89  // function and make sure all resources are released
    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  // EncryptJwe creates an encrypted JWE message and returns its filename and
   106  // a cleanup function.
   107  // The caller is responsible for calling the cleanup
   108  // function and make sure all resources are released
   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  // SignJws signs a message and returns its filename and
   192  // a cleanup function.
   193  // The caller is responsible for calling the cleanup
   194  // function and make sure all resources are released
   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