2
0
mirror of https://github.com/therootcompany/keypairs synced 2025-04-20 22:40:47 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
88a0b05c2f update usage example 2020-11-25 03:15:34 -07:00
a62ae2ea87 add inspect with OIDC key fetch 2020-11-25 03:10:33 -07:00
be38720eba doc: update README and help 2020-10-21 04:26:53 -06:00
2 changed files with 140 additions and 3 deletions

View File

@ -1,5 +1,44 @@
# [keypairs](https://git.rootprojects.org/root/keypairs) # [keypairs](https://git.rootprojects.org/root/keypairs)
A cross-platform Command Line Tool and Golang Library that works
with RSA, ECDSA, PEM, DER, JWK, and the JOSE suite.
# Keypairs CLI
Generates, signs, and verifies with NIST-strength asymmetric keys.
```bash
# Generate JSON Web Keys (JWKs)
keypairs gen > key.jwk.json 2> pub.jwk.json
# Generate PEM (or DER) Keys, by extension
keypairs gen --key key.pem --pub pub.pem
# Sign a payload
keypairs sign key.jwk.json --exp 1h '{ "sub": "me@example.com" }' > token.jwt 2> sig.jws
# Verify a signature
keypairs verify pub.jwk.json token.jwt
```
Cheat Sheet at <https://webinstall.dev/keypairs>.
### Install
**Mac**, **Linux**:
```bash
curl -sS https://webinstall.dev/keypairs | bash
```
**Windows 10**:
```bash
curl.exe -A MS https://webinstall.dev/keypairs | powershell
```
# Keypairs Go Library
JSON Web Key (JWK) support and type safety lightly placed over top of Go's `crypto/ecdsa` and `crypto/rsa` JSON Web Key (JWK) support and type safety lightly placed over top of Go's `crypto/ecdsa` and `crypto/rsa`
Useful for JWT, JOSE, etc. Useful for JWT, JOSE, etc.

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"git.rootprojects.org/root/keypairs" "git.rootprojects.org/root/keypairs"
"git.rootprojects.org/root/keypairs/keyfetch"
) )
var ( var (
@ -31,14 +32,17 @@ func usage() {
fmt.Println(" version") fmt.Println(" version")
fmt.Println(" gen") fmt.Println(" gen")
fmt.Println(" sign") fmt.Println(" sign")
fmt.Println(" inspect (decode)")
fmt.Println(" verify") fmt.Println(" verify")
fmt.Println("") fmt.Println("")
fmt.Println("Examples:") fmt.Println("Examples:")
fmt.Println(" keypairs gen -o key.jwk.json [--pub <public-key>]") fmt.Println(" keypairs gen --key key.jwk.json [--pub <public-key>]")
fmt.Println("") fmt.Println("")
fmt.Println(" keypairs sign --exp 15m key.jwk.json payload.json") fmt.Println(" keypairs sign --exp 15m key.jwk.json payload.json")
fmt.Println(" keypairs sign --exp 15m key.jwk.json '{ \"sub\": \"xxxx\" }'") fmt.Println(" keypairs sign --exp 15m key.jwk.json '{ \"sub\": \"xxxx\" }'")
fmt.Println("") fmt.Println("")
fmt.Println(" keypairs inspect --verbose 'xxxx.yyyy.zzzz'")
fmt.Println("")
fmt.Println(" keypairs verify ./pub.jwk.json 'xxxx.yyyy.zzzz'") fmt.Println(" keypairs verify ./pub.jwk.json 'xxxx.yyyy.zzzz'")
// TODO fmt.Println(" keypairs verify --issuer https://example.com '{ \"sub\": \"xxxx\" }'") // TODO fmt.Println(" keypairs verify --issuer https://example.com '{ \"sub\": \"xxxx\" }'")
fmt.Println("") fmt.Println("")
@ -73,6 +77,10 @@ func main() {
gen(args[2:]) gen(args[2:])
case "sign": case "sign":
sign(args[2:]) sign(args[2:])
case "decode":
fallthrough
case "inspect":
inspect(args[2:])
case "verify": case "verify":
verify(args[2:]) verify(args[2:])
default: default:
@ -176,19 +184,109 @@ func sign(args []string) {
fmt.Fprintf(os.Stdout, "%s\n", keypairs.JWSToJWT(jws)) fmt.Fprintf(os.Stdout, "%s\n", keypairs.JWSToJWT(jws))
} }
func inspect(args []string) {
var verbose bool
flags := flag.NewFlagSet("inspect", flag.ExitOnError)
flags.BoolVar(&verbose, "verbose", true, "print extra info")
flags.Usage = func() {
fmt.Println("Usage: keypairs inspect --verbose <jwt-or-jwt>")
fmt.Println("")
fmt.Println(" <jwt-or-jws>: a JWT or JWS File or String, if JWS the payload must be Base64")
fmt.Println("")
}
flags.Parse(args)
if len(flags.Args()) < 1 {
flags.Usage()
os.Exit(1)
return
}
payload := flags.Args()[0]
jws, err := readJWS(payload)
if nil != err {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
return
}
var pub keypairs.PublicKey = nil
// because interfaces are never truly nil
hasPub := false
jwk, _ := jws.Header["jwk"].(map[string]interface{})
jwkE, _ := jwk["e"].(string)
jwkX, _ := jwk["x"].(string)
kid, _ := jws.Header["kid"].(string)
if len(jwkE) > 0 || len(jwkX) > 0 {
// TODO verify self-signed certificate
//b, _ := json.MarshalIndent(&jwk, "", " ")
if len(kid) > 0 {
fmt.Fprintf(os.Stderr, "[warn] jws header has both 'kid' (Key ID) and 'jwk' (for self-signed only)\n")
} else {
fmt.Fprintf(os.Stderr, "[debug] token is self-signed (jwk)\n")
//pub = pubx
//hasPub = true
}
} else if len(kid) > 0 {
iss, _ := jws.Claims["iss"].(string)
if strings.HasPrefix(iss, "http:") || strings.HasPrefix(iss, "https:") {
//fmt.Printf("iss: %s\n", iss)
//fmt.Printf("kid: %s\n", kid)
fmt.Fprintf(os.Stderr, "Checking for OIDC key... ")
pubx, err := keyfetch.OIDCJWK(kid, iss)
if nil != err {
fmt.Fprintf(os.Stderr, "not found.\n")
// ignore
} else {
fmt.Fprintf(os.Stderr, "found:\n")
if verbose {
b := keypairs.MarshalJWKPublicKey(pubx)
fmt.Fprintf(os.Stderr, "%s\n", indentJSON(b))
}
pub = pubx
hasPub = true
}
}
}
validSig := false
if hasPub {
errs := keypairs.VerifyClaims(pub, jws)
if len(errs) > 0 {
fmt.Fprintf(os.Stderr, "error:\n")
for _, err := range errs {
fmt.Fprintf(os.Stderr, "\t%v\n", err)
}
} else {
validSig = true
}
}
b, _ := json.MarshalIndent(&jws, "", " ")
fmt.Fprintf(os.Stdout, "%s\n", b)
if validSig {
fmt.Fprintf(os.Stderr, "Signature is Valid\n")
}
}
func verify(args []string) { func verify(args []string) {
flags := flag.NewFlagSet("verify", flag.ExitOnError) flags := flag.NewFlagSet("verify", flag.ExitOnError)
flags.Usage = func() { flags.Usage = func() {
fmt.Println("Usage: keypairs verify <public key> <jwt-or-jwt>") fmt.Println("Usage: keypairs verify [public key] <jwt-or-jwt>")
fmt.Println("") fmt.Println("")
fmt.Println(" <public key>: a File or String of an EC or RSA key in JWK or PEM format") fmt.Println(" <public key>: a File or String of an EC or RSA key in JWK or PEM format")
fmt.Println(" <jwt-or-jws>: a JWT or JWS File or String, if JWS the payload must be Base64") fmt.Println(" <jwt-or-jws>: a JWT or JWS File or String, if JWS the payload must be Base64")
fmt.Println("") fmt.Println("")
} }
flags.Parse(args) flags.Parse(args)
if len(flags.Args()) <= 1 { if len(flags.Args()) < 1 {
flags.Usage() flags.Usage()
os.Exit(1) os.Exit(1)
return
}
if 1 == len(flags.Args()) {
inspect(args)
return
} }
pubname := flags.Args()[0] pubname := flags.Args()[0]