From a77d4cd5f203ade6da9934b00494d2b05ca6be30 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 19 May 2014 20:13:39 -0700 Subject: [PATCH 1/6] a useful example app --- .gitignore | 4 ++ cmd/jwt/app.go | 148 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/jwt/app.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80bed65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +bin + + diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go new file mode 100644 index 0000000..332b832 --- /dev/null +++ b/cmd/jwt/app.go @@ -0,0 +1,148 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/dgrijalva/jwt-go" +) + +var ( + // Options + flagAlg = flag.String("alg", "", "signing algorithm identifier") + flagKey = flag.String("key", "", "path to key file or '-' to read from stdin") + flagPretty = flag.Bool("pretty", true, "output pretty JSON") + + // Modes - exactly one of these is required + flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin") + flagVerify = flag.String("verify", "", "path to JWT token to verify or '-' to read from stdin") +) + +func main() { + // Usage message if you ask for -help or if you mess up inputs. + // TODO: make this better + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " One of the following flags is required: sign, verify\n") + flag.PrintDefaults() + } + + // Parse command line options + flag.Parse() + + // Do the thing. If something goes wrong, print error to stderr + // and exit with a non-zero status code + if err := start(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +// Figure out which thing +func start() error { + if *flagSign != "" { + return signToken() + } else if *flagVerify != "" { + return verifyToken() + } else { + flag.Usage() + return fmt.Errorf("None of the required flags are present. What do you want me to do?") + } +} + +// Read input from specified file or stdin +func loadData(p string) ([]byte, error) { + if p == "" { + return nil, fmt.Errorf("No path specified") + } + + var rdr io.Reader + if p == "-" { + rdr = os.Stdin + } else { + if f, err := os.Open(p); err == nil { + rdr = f + defer f.Close() + } else { + return nil, err + } + } + return ioutil.ReadAll(rdr) +} + +// verify a token and output the claims +func verifyToken() error { + // get the token + tokData, err := loadData(*flagVerify) + if err != nil { + return fmt.Errorf("Couldn't read token: %v", err) + } + + // Parse the token. Load the key from command line option + token, err := jwt.Parse(string(tokData), func(t *jwt.Token) ([]byte, error) { + return loadData(*flagKey) + }) + + // Print an error if we can't parse for some reason + if err != nil { + return err + } + + // Is token invalid? + if !token.Valid { + return fmt.Errorf("Token is invalid") + } + + // Print the token details + // TODO: observe the pretty flag + if out, err := json.MarshalIndent(token.Claims, "", " "); err == nil { + fmt.Println(string(out)) + } else { + return fmt.Errorf("Failed to output claims: %v", err) + } + + return nil +} + +// Create, sign, and output a token +func signToken() error { + // get the token + tokData, err := loadData(*flagSign) + if err != nil { + return fmt.Errorf("Couldn't read token: %v", err) + } + + // parse the JSON of the claims + var claims map[string]interface{} + if err := json.Unmarshal(tokData, &claims); err != nil { + return fmt.Errorf("Couldn't parse claims JSON: %v", err) + } + + // get the key + keyData, err := loadData(*flagKey) + if err != nil { + return fmt.Errorf("Couldn't read key: %v", err) + } + + // get the signing alg + alg := jwt.GetSigningMethod(*flagAlg) + if alg == nil { + return fmt.Errorf("Couldn't find signing method: %v", *flagAlg) + } + + // create a new token + token := jwt.New(alg) + token.Claims = claims + + if out, err := token.SignedString(keyData); err == nil { + fmt.Println(out) + } else { + return fmt.Errorf("Error signing token: %v", err) + } + + return nil +} From a94bf1842fd5d10613a5900e5c2ded61b2e96e33 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 19 May 2014 20:20:38 -0700 Subject: [PATCH 2/6] improved documentation --- cmd/jwt/app.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index 332b832..0cab439 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -1,3 +1,9 @@ +// A useful example app. You can use this to debug your tokens on the command line. +// This is also a great place to look at how you might use this library. +// +// Example usage: +// The following will create and sign a token, then verify it and output the original claims. +// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify - package main import ( @@ -42,7 +48,7 @@ func main() { } } -// Figure out which thing +// Figure out which thing to do and then do that func start() error { if *flagSign != "" { return signToken() @@ -54,7 +60,7 @@ func start() error { } } -// Read input from specified file or stdin +// Helper func: Read input from specified file or stdin func loadData(p string) ([]byte, error) { if p == "" { return nil, fmt.Errorf("No path specified") @@ -74,7 +80,7 @@ func loadData(p string) ([]byte, error) { return ioutil.ReadAll(rdr) } -// verify a token and output the claims +// Verify a token and output the claims func verifyToken() error { // get the token tokData, err := loadData(*flagVerify) From e066f616660b05ad064e06f0382558347a4457be Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 19 May 2014 20:25:30 -0700 Subject: [PATCH 3/6] yet more documentation --- cmd/jwt/app.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index 0cab439..fdefe98 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -80,7 +80,8 @@ func loadData(p string) ([]byte, error) { return ioutil.ReadAll(rdr) } -// Verify a token and output the claims +// Verify a token and output the claims. This is a great example +// of how to verify and view a token. func verifyToken() error { // get the token tokData, err := loadData(*flagVerify) @@ -95,7 +96,7 @@ func verifyToken() error { // Print an error if we can't parse for some reason if err != nil { - return err + return fmt.Errorf("Couldn't parse token: %v", err) } // Is token invalid? @@ -114,9 +115,10 @@ func verifyToken() error { return nil } -// Create, sign, and output a token +// Create, sign, and output a token. This is a great, simple example of +// how to use this library to create and sign a token. func signToken() error { - // get the token + // get the token data from command line arguments tokData, err := loadData(*flagSign) if err != nil { return fmt.Errorf("Couldn't read token: %v", err) From 8724cca5ea67dbe5fca0eaa722614826f0bbb47d Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 19 May 2014 22:19:42 -0700 Subject: [PATCH 4/6] debug flag --- cmd/jwt/app.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index fdefe98..70f82ed 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -22,6 +22,7 @@ var ( flagAlg = flag.String("alg", "", "signing algorithm identifier") flagKey = flag.String("key", "", "path to key file or '-' to read from stdin") flagPretty = flag.Bool("pretty", true, "output pretty JSON") + flagDebug = flag.Bool("debug", false, "print out all kinds of debug data") // Modes - exactly one of these is required flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin") @@ -94,6 +95,12 @@ func verifyToken() error { return loadData(*flagKey) }) + // Print some debug data + if *flagDebug && token != nil { + fmt.Printf("Header:\n%v\n", token.Header) + fmt.Printf("Claims:\n%v\n", token.Claims) + } + // Print an error if we can't parse for some reason if err != nil { return fmt.Errorf("Couldn't parse token: %v", err) From 6b310a4cc29cd93afc29365067b17ea99dffbce8 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 19 May 2014 22:38:15 -0700 Subject: [PATCH 5/6] fixed whitespace issue when reading tokens from stdin --- cmd/jwt/app.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index 70f82ed..1830942 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -13,6 +13,7 @@ import ( "io" "io/ioutil" "os" + "regexp" "github.com/dgrijalva/jwt-go" ) @@ -22,7 +23,7 @@ var ( flagAlg = flag.String("alg", "", "signing algorithm identifier") flagKey = flag.String("key", "", "path to key file or '-' to read from stdin") flagPretty = flag.Bool("pretty", true, "output pretty JSON") - flagDebug = flag.Bool("debug", false, "print out all kinds of debug data") + flagDebug = flag.Bool("debug", false, "print out all kinds of debug data") // Modes - exactly one of these is required flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin") @@ -90,6 +91,12 @@ func verifyToken() error { return fmt.Errorf("Couldn't read token: %v", err) } + // trim possible whitespace from token + tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{}) + if *flagDebug { + fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData)) + } + // Parse the token. Load the key from command line option token, err := jwt.Parse(string(tokData), func(t *jwt.Token) ([]byte, error) { return loadData(*flagKey) @@ -97,8 +104,8 @@ func verifyToken() error { // Print some debug data if *flagDebug && token != nil { - fmt.Printf("Header:\n%v\n", token.Header) - fmt.Printf("Claims:\n%v\n", token.Claims) + fmt.Fprintf(os.Stderr, "Header:\n%v\n", token.Header) + fmt.Fprintf(os.Stderr, "Claims:\n%v\n", token.Claims) } // Print an error if we can't parse for some reason @@ -129,6 +136,8 @@ func signToken() error { tokData, err := loadData(*flagSign) if err != nil { return fmt.Errorf("Couldn't read token: %v", err) + } else if *flagDebug { + fmt.Println("Token: %v bytes", len(tokData)) } // parse the JSON of the claims From 11c8f1f10b2ef6dfae898a07c80ba4e345fe5ad7 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 20 May 2014 14:54:44 -0700 Subject: [PATCH 6/6] clean up todos --- cmd/jwt/app.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index 1830942..8ac5cad 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -32,7 +32,6 @@ var ( func main() { // Usage message if you ask for -help or if you mess up inputs. - // TODO: make this better flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, " One of the following flags is required: sign, verify\n") @@ -82,6 +81,24 @@ func loadData(p string) ([]byte, error) { return ioutil.ReadAll(rdr) } +// Print a json object in accordance with the prophecy (or the command line options) +func printJSON(j interface{}) error { + var out []byte + var err error + + if *flagPretty { + out, err = json.MarshalIndent(j, "", " ") + } else { + out, err = json.Marshal(j) + } + + if err == nil { + fmt.Println(string(out)) + } + + return err +} + // Verify a token and output the claims. This is a great example // of how to verify and view a token. func verifyToken() error { @@ -119,10 +136,7 @@ func verifyToken() error { } // Print the token details - // TODO: observe the pretty flag - if out, err := json.MarshalIndent(token.Claims, "", " "); err == nil { - fmt.Println(string(out)) - } else { + if err := printJSON(token.Claims); err != nil { return fmt.Errorf("Failed to output claims: %v", err) } @@ -137,7 +151,7 @@ func signToken() error { if err != nil { return fmt.Errorf("Couldn't read token: %v", err) } else if *flagDebug { - fmt.Println("Token: %v bytes", len(tokData)) + fmt.Fprintf(os.Stderr, "Token: %v bytes", len(tokData)) } // parse the JSON of the claims