Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: add handling of HS keys #163

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 83 additions & 5 deletions cmd/jwt/README.md
Original file line number Diff line number Diff line change
@@ -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
```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"
}
```
36 changes: 34 additions & 2 deletions cmd/jwt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package main

import (
"bytes"
"encoding/json"
"flag"
"fmt"
Expand All @@ -16,6 +17,7 @@ import (
"regexp"
"sort"
"strings"
"unicode"

"github.com/golang-jwt/jwt/v4"
)
Expand Down Expand Up @@ -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
})
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
}