From c6a6a5f45b29cdfce777aa2478270a0b763f7058 Mon Sep 17 00:00:00 2001 From: Paul Greenberg Date: Tue, 8 Feb 2022 14:17:53 -0500 Subject: [PATCH] cmd: add handling of HS keys Signed-off-by: Paul Greenberg --- cmd/jwt/README.md | 88 ++++++++++++++++++++++++++++++++++++++++++++--- cmd/jwt/main.go | 36 +++++++++++++++++-- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/cmd/jwt/README.md b/cmd/jwt/README.md index 4388e5f9..ed515e4d 100644 --- a/cmd/jwt/README.md +++ b/cmd/jwt/README.md @@ -1,19 +1,97 @@ -`jwt` command-line tool -======================= +# `jwt` command-line tool This is a simple tool to sign, verify and show JSON Web Tokens from the command line. +## Getting Started + The following will create and sign a token, then verify it and output the original claims: - echo {\"foo\":\"bar\"} | ./jwt -key ../../test/sample_key -alg RS256 -sign - | ./jwt -key ../../test/sample_key.pub -alg RS256 -verify - +```bash +echo {\"foo\":\"bar\"} | ./jwt -key ../../test/sample_key -alg RS256 -sign - | ./jwt -key ../../test/sample_key.pub -alg RS256 -verify - +``` Key files should be in PEM format. Other formats are not supported by this tool. To simply display a token, use: - echo $JWT | ./jwt -show - +```bash +echo $JWT | ./jwt -show - +``` You can install this tool with the following command: - go install github.com/golang-jwt/jwt/v4/cmd/jwt \ No newline at end of file +```bash +go install github.com/golang-jwt/jwt/v4/cmd/jwt +``` + +## Sign/Verify with Shared Secret + +First, create a JSON document with token payload, e.g. `~/experimental/jwt/data`. + +```json +{ + "email": "jsmith@foo.bar", + "aud": "foo.bar", + "exp": 2559489932, + "iat": 1612805132, + "iss": "foo.bar", + "sub": "jsmith" +} +``` + +Then, create a file with shared secret key, e.g. `~/experimental/jwt/token.key`. + +``` +foobarbaz +``` + +Next, sign the token: + +```bash +./jwt -key ~/experimental/jwt/token.key -alg HS512 -sign ~/experimental/jwt/data > ~/experimental/jwt/token.jwt +``` + +After that, review the token: + +```bash +./jwt -show ~/experimental/jwt/token.jwt +``` + +The expected output follows: + +``` +Header: +{ + "alg": "HS512", + "typ": "JWT" +} +Claims: +{ + "aud": "foo.bar", + "email": "jsmith@foo.bar", + "exp": 2559489932, + "iat": 1612805132, + "iss": "foo.bar", + "sub": "jsmith" +} +``` + +Subsequently, validate the token: + +```bash +./jwt -key ~/experimental/jwt/token.key -alg HS512 -verify ~/experimental/jwt/token.jwt +``` + +The expected output follows: + +``` +{ + "aud": "foo.bar", + "email": "jsmith@foo.bar", + "exp": 2559489932, + "iat": 1612805132, + "iss": "foo.bar", + "sub": "jsmith" +} +``` diff --git a/cmd/jwt/main.go b/cmd/jwt/main.go index 8706ab01..ce5e7b32 100644 --- a/cmd/jwt/main.go +++ b/cmd/jwt/main.go @@ -7,6 +7,7 @@ package main import ( + "bytes" "encoding/json" "flag" "fmt" @@ -16,6 +17,7 @@ import ( "regexp" "sort" "strings" + "unicode" "github.com/golang-jwt/jwt/v4" ) @@ -142,6 +144,8 @@ func verifyToken() error { return jwt.ParseRSAPublicKeyFromPEM(data) } else if isEd() { return jwt.ParseEdPublicKeyFromPEM(data) + } else if isHs() { + return parseHSKey(data) } return data, nil }) @@ -196,9 +200,19 @@ func signToken() error { // get the key var key interface{} - if isNone() { + switch { + case isNone(): key = jwt.UnsafeAllowNoneSignatureType - } else { + case isHs(): + kb, err := loadData(*flagKey) + if err != nil { + return fmt.Errorf("couldn't read key: %w", err) + } + key, err = parseHSKey(kb) + if err != nil { + return err + } + default: key, err = loadData(*flagKey) if err != nil { return fmt.Errorf("couldn't read key: %w", err) @@ -292,6 +306,10 @@ func showToken() error { return nil } +func isHs() bool { + return strings.HasPrefix(*flagAlg, "HS") +} + func isEs() bool { return strings.HasPrefix(*flagAlg, "ES") } @@ -342,3 +360,17 @@ func (l ArgList) Set(arg string) error { l[parts[0]] = parts[1] return nil } + +func parseHSKey(b []byte) ([]byte, error) { + if len(b) == 0 { + return nil, fmt.Errorf("shared key is empty") + } + f := func(c rune) bool { + return unicode.IsSpace(c) + } + i := bytes.IndexFunc(b, f) + if i < 0 { + return b, nil + } + return b[:i], nil +}