Skip to content

Commit

Permalink
Start giving some love that it deserves to the jwx command
Browse files Browse the repository at this point in the history
  • Loading branch information
lestrrat committed Feb 2, 2021
1 parent 68ee9b2 commit 3c928ed
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 148 deletions.
11 changes: 11 additions & 0 deletions cmd/jwx/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/lestrrat-go/jwx/cmd/jwx

go 1.16

require (
github.com/lestrrat-go/jwx v1.1.0
github.com/pkg/errors v0.9.1
github.com/urfave/cli/v2 v2.3.0
)

replace github.com/lestrrat-go/jwx => ../..
68 changes: 68 additions & 0 deletions cmd/jwx/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/goccy/go-json v0.3.5 h1:HqrLjEWx7hD62JRhBh+mHv+rEEzBANIu6O0kbDlaLzU=
github.com/goccy/go-json v0.3.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/lestrrat-go/backoff/v2 v2.0.7 h1:i2SeK33aOFJlUNJZzf2IpXRBvqBBnaGXfY5Xaop/GsE=
github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
github.com/lestrrat-go/iter v1.0.0 h1:QD+hHQPDSHC4rCJkZYY/yXChYr/vjfBopKekTc+7l4Q=
github.com/lestrrat-go/iter v1.0.0/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/pdebug/v3 v3.0.1 h1:3G5sX/aw/TbMTtVc9U7IHBWRZtMvwvBziF1e4HoQtv8=
github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
136 changes: 136 additions & 0 deletions cmd/jwx/jwk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package main

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/lestrrat-go/jwx/jwk"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)

func getSource(c *cli.Context) (io.ReadCloser, error) {
var src io.ReadCloser
if c.Bool("stdin") {
src = io.NopCloser(os.Stdin)
} else {
file := c.Args().Get(0)
if file == "" {
return nil, errors.New(`filename required withot -stdin`)
}
f, err := os.Open(file)
if err != nil {
return nil, errors.Wrapf(err, `failed to open file %s`, file)
}
src = f
}
return src, nil
}

func makeJwkCmd() *cli.Command {
var cmd cli.Command
cmd.Name = "jwk"
cmd.Usage = "Work with JWK and JWK sets"

// jwk pem ...
cmd.Subcommands = []*cli.Command{
makeJwkParseCmd(),
makeJwkFormatCmd(),
}
return &cmd
}

func makeJwkFormatCmd() *cli.Command {
var cmd cli.Command
cmd.Name = "format"
cmd.Usage = "Format JWK"
cmd.Flags = []cli.Flag{
&cli.StringFlag{Name: "format", Value: "json"},
&cli.BoolFlag{Name: "stdin", Value: false},
}

// jwx jwk format <file>
cmd.Action = func(c *cli.Context) error {
src, err := getSource(c)
if err != nil {
return err
}
defer src.Close()

buf, err := ioutil.ReadAll(src)
if err != nil {
return errors.Wrap(err, `failed to read data from source`)
}

key, err := jwk.ParseKey(buf)
if err != nil {
return errors.Wrap(err, `failed to parse key`)
}

switch format := c.String("format"); format {
case "json":
buf, err = json.MarshalIndent(key, "", " ")
if err != nil {
return errors.Wrap(err, `failed to format key in JSON format`)
}
case "pem":
buf, err = jwk.Pem(key)
if err != nil {
return errors.Wrap(err, `failed to format key in PEM format`)
}
}

fmt.Printf("%s\n", buf)
return nil
}
return &cmd
}

func makeJwkParseCmd() *cli.Command {
var cmd cli.Command
cmd.Name = "parse"
cmd.Usage = "Parse JWK"
cmd.Flags = []cli.Flag{
&cli.StringFlag{Name: "format", Value: "json"},
&cli.BoolFlag{Name: "stdin", Value: false},
}

// jwx jwk parse <file>
cmd.Action = func(c *cli.Context) error {
src, err := getSource(c)
if err != nil {
return err
}
defer src.Close()

buf, err := ioutil.ReadAll(src)
if err != nil {
return errors.Wrap(err, `failed to read data from source`)
}

var options []jwk.ParseKeyOption
switch format := c.String("format"); format {
case "json":
case "pem":
options = append(options, jwk.WithPEM(true))
default:
return errors.Errorf(`invalid format %s`, format)
}

key, err := jwk.ParseKey(buf, options...)
if err != nil {
return errors.Wrap(err, `failed to parse key`)
}

buf, err = json.Marshal(key)
if err != nil {
return errors.Wrap(err, `failed to marshal key into JSON format`)
}
fmt.Fprintf(os.Stdout, "%s\n", buf)
return nil
}
return &cmd
}
153 changes: 6 additions & 147 deletions cmd/jwx/jwx.go
Original file line number Diff line number Diff line change
@@ -1,159 +1,18 @@
package main

import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"os"

"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jws"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)

func main() {
os.Exit(_main())
}

type JWKConfig struct {
JWKLocation string
Payload string
}

type JWEConfig struct {
Algorithm string
}

func _main() int {
var f func() int

if len(os.Args) < 2 {
f = doHelp
} else {
switch os.Args[1] {
case "jwk":
f = doJWK
case "jwe":
f = doJWE
default:
f = doHelp
}

os.Args = os.Args[1:]
}
return f()
}

func doHelp() int {
fmt.Println(`jwx [command] [args]`)
return 0
}

func doJWE() int {
c := JWEConfig{}
flag.StringVar(&c.Algorithm, "alg", "", "Key encryption algorithm")
flag.Parse()

return 0
}

func doJWK() int {
c := JWKConfig{}
flag.StringVar(&c.JWKLocation, "jwk", "", "JWK location, either a local file or a URL")
flag.Parse()

if c.JWKLocation == "" {
fmt.Printf("-jwk must be specified\n")
return 1
}
var app cli.App
app.Commands = append(app.Commands, makeJwkCmd())

key, err := jwk.Fetch(context.TODO(), c.JWKLocation)
if err != nil {
log.Printf("%s", err)
return 0
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}

keybuf, err := json.MarshalIndent(key, "", " ")
if err != nil {
log.Printf("%s", err)
return 0
}
log.Printf("=== JWK ===")
for _, l := range bytes.Split(keybuf, []byte{'\n'}) {
log.Printf("%s", l)
}

// TODO make it flexible
firstKey, ok := key.Get(0)
if !ok {
log.Printf("empty keyset")
return 0
}

var pubkey interface{}
if err := firstKey.Raw(&pubkey); err != nil {
log.Printf("%s", err)
return 0
}

var src io.Reader
if c.Payload == "" {
src = os.Stdin
} else {
f, err := os.Open(c.Payload)
if err != nil {
log.Printf("%s", errors.Wrap(err, "failed to open file "+c.Payload))
return 1
}
src = f
defer f.Close()
}

var buf bytes.Buffer
src = io.TeeReader(src, &buf)

message, err := jws.ParseReader(src)
if err != nil {
log.Printf("%s", err)
return 0
}

log.Printf("=== Payload ===")
// See if this is JSON. if it is, display it nicely
m := map[string]interface{}{}
if err := json.Unmarshal(message.Payload(), &m); err == nil {
payloadbuf, err := json.MarshalIndent(m, "", " ")
if err != nil {
log.Printf("%s", errors.Wrap(err, "failed to marshal payload"))
return 0
}
for _, l := range bytes.Split(payloadbuf, []byte{'\n'}) {
log.Printf("%s", l)
}
} else {
log.Printf("%s", message.Payload())
}

for i, sig := range message.Signatures() {
log.Printf("=== Signature %d ===", i)
sigbuf, err := json.MarshalIndent(sig, "", " ")
if err != nil {
log.Printf("%s", errors.Wrap(err, "failed to marshal signature as JSON"))
return 0
}
for _, l := range bytes.Split(sigbuf, []byte{'\n'}) {
log.Printf("%s", l)
}

alg := sig.ProtectedHeaders().Algorithm()
if _, err := jws.Verify(buf.Bytes(), alg, pubkey); err == nil {
log.Printf("=== Verified with signature %d! ===", i)
}
}

return 1
}
Loading

0 comments on commit 3c928ed

Please sign in to comment.