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

Generate JWK #84

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions cmd/jwks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cmd

import (
"os"

"github.com/cloudentity/oauth2c/internal/jwks"
"github.com/go-jose/go-jose/v3"
"github.com/spf13/cobra"
)

var jwksCmd = &cobra.Command{
Use: "jwks",
Short: "JWKs operations",
}

var jwksConfig jwks.Config

var generateJwkCmd = &cobra.Command{
Use: "generate",
Short: "Generate JWK",
Long: `
# Supported algorithms

Signing | Algorithm
------------------------- | -------------------------
RSASSA-PKCS#1v1.5 | RS256, RS384, RS512
RSASSA-PSS | PS256, PS384, PS512
ECDSA | ES256, ES384, ES512

Encryption | Algorithm
------------------------- | -------------------------
RSA-PKCS#1v1.5 | RSA1_5
RSA-OAEP | RSA-OAEP, RSA-OAEP-256
ECDH-ES | ECDH-ES
ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
`,
Run: func(cmd *cobra.Command, args []string) {
var (
jwk jose.JSONWebKey
err error
)

if jwk, err = jwks.Generate(jwksConfig); err != nil {
LogError(err)
os.Exit(1)
}

LogHeader("Generate JWK")

LogSection("Private JWK")
LogJson(jwk)

LogSection("Public JWK")
LogJson(jwk.Public())

LogSection("Private Key")
LogKey(jwk.Key)

LogSection("Public Key")
LogKey(jwk.Public().Key)
},
}

func init() {
generateJwkCmd.Flags().StringVar(&jwksConfig.Type, "type", "rsa", "key type (rsa, ec)")
generateJwkCmd.Flags().StringVar(&jwksConfig.Alg, "alg", "", "key algorithm")
generateJwkCmd.Flags().IntVar(&jwksConfig.Size, "size", 2048, "key size (2048, 3072, 4096)")
generateJwkCmd.Flags().IntVar(&jwksConfig.Curve, "curve", 256, "key curve (224, 256, 384, 521)")
generateJwkCmd.Flags().StringVar(&jwksConfig.Use, "use", "sig", "key use (sig, enc)")

jwksCmd.AddCommand(generateJwkCmd)
}
13 changes: 7 additions & 6 deletions cmd/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,13 @@ func LogRequestObject(r oauth2.Request) {
pterm.Println()

if r.SigningKey != nil {
LogKey("Signing key", r.SigningKey)
pterm.Println("Signing key")
LogKey(r.SigningKey)
}

if r.EncryptionKey != nil {
LogKey("Encryption key", r.EncryptionKey)
pterm.Println("Encryption key")
LogKey(r.EncryptionKey)
}
}
}
Expand Down Expand Up @@ -364,14 +366,13 @@ func LogAssertion(request oauth2.Request, title string, name string) {
LogJson(claims)
pterm.Println("")

LogKey("Signing key", request.SigningKey)
pterm.Println("Signing key")
LogKey(request.SigningKey)
}

func LogKey(name string, key interface{}) {
func LogKey(key interface{}) {
var err error

pterm.Println(name)

switch key := key.(type) {
case *rsa.PublicKey:
p := bytes.Buffer{}
Expand Down
102 changes: 59 additions & 43 deletions cmd/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ var (
noPrompt bool
)

var example = `oauth2c https://oauth2c.us.authz.cloudentity.io/oauth2c/demo \
--client-id cauktionbud6q8ftlqq0 \
--client-secret HCwQ5uuUWBRHd04ivjX5Kl0Rz8zxMOekeLtqzki0GPc \
--grant-type client_credentials \
--auth-method client_secret_basic \
--scopes introspect_tokens,revoke_tokens`

var desc = `oauth2c is a command-line tool for interacting with OAuth 2.0 authorization servers.

Its goal is to make it easy to fetch access tokens using any grant type or client authentication method.

It is compliant with almost all basic and advanced OAuth 2.0, OIDC, OIDF FAPI and JWT profiles.`

type OAuth2Cmd struct {
*cobra.Command
}
Expand All @@ -32,56 +45,59 @@ func NewOAuth2Cmd(version, commit, date string) (cmd *OAuth2Cmd) {

cmd = &OAuth2Cmd{
Command: &cobra.Command{
Use: "oauth2c [issuer url]",
Short: "User-friendly command-line for OAuth2",
Args: cobra.ExactArgs(1),
Use: "oauth2c issuerURL",
Short: "User-friendly command-line for OAuth2",
Example: example,
Long: desc,
Args: cobra.ExactArgs(1),
},
}

cmd.Command.Run = cmd.Run(&cconfig)

cmd.AddCommand(NewVersionCmd(version, commit, date))
cmd.AddCommand(docsCmd)

cmd.PersistentFlags().StringVar(&cconfig.RedirectURL, "redirect-url", "http://localhost:9876/callback", "client redirect url")
cmd.PersistentFlags().StringVar(&cconfig.ClientID, "client-id", "", "client identifier")
cmd.PersistentFlags().StringVar(&cconfig.ClientSecret, "client-secret", "", "client secret")
cmd.PersistentFlags().StringVar(&cconfig.GrantType, "grant-type", "", "grant type")
cmd.PersistentFlags().StringVar(&cconfig.AuthMethod, "auth-method", "", "token endpoint authentication method")
cmd.PersistentFlags().StringVar(&cconfig.Username, "username", "", "resource owner password credentials grant flow username")
cmd.PersistentFlags().StringVar(&cconfig.Password, "password", "", "resource owner password credentials grant flow password")
cmd.PersistentFlags().StringVar(&cconfig.RefreshToken, "refresh-token", "", "refresh token")
cmd.PersistentFlags().StringSliceVar(&cconfig.ResponseType, "response-types", []string{""}, "response type")
cmd.PersistentFlags().StringVar(&cconfig.ResponseMode, "response-mode", "", "response mode")
cmd.PersistentFlags().StringSliceVar(&cconfig.Scopes, "scopes", []string{}, "requested scopes")
cmd.PersistentFlags().StringSliceVar(&cconfig.Audience, "audience", []string{}, "requested audience")
cmd.PersistentFlags().BoolVar(&cconfig.PKCE, "pkce", false, "enable proof key for code exchange (PKCE)")
cmd.PersistentFlags().BoolVar(&cconfig.PAR, "par", false, "enable pushed authorization requests (PAR)")
cmd.PersistentFlags().BoolVar(&cconfig.RequestObject, "request-object", false, "pass request parameters as jwt")
cmd.PersistentFlags().BoolVar(&cconfig.EncryptedRequestObject, "encrypted-request-object", false, "pass request parameters as encrypted jwt")
cmd.PersistentFlags().StringVar(&cconfig.Assertion, "assertion", "", "claims for jwt bearer assertion")
cmd.PersistentFlags().StringVar(&cconfig.SigningKey, "signing-key", "", "path or url to signing key in jwks format")
cmd.PersistentFlags().StringVar(&cconfig.EncryptionKey, "encryption-key", "", "path or url to encryption key in jwks format")
cmd.PersistentFlags().StringVar(&cconfig.SubjectToken, "subject-token", "", "third party token")
cmd.PersistentFlags().StringVar(&cconfig.SubjectTokenType, "subject-token-type", "", "third party token type")
cmd.PersistentFlags().StringVar(&cconfig.ActorToken, "actor-token", "", "acting party token")
cmd.PersistentFlags().StringVar(&cconfig.ActorTokenType, "actor-token-type", "", "acting party token type")
cmd.PersistentFlags().StringVar(&cconfig.IDTokenHint, "id-token-hint", "", "id token hint")
cmd.PersistentFlags().StringVar(&cconfig.LoginHint, "login-hint", "", "user identifier hint")
cmd.PersistentFlags().StringVar(&cconfig.IDPHint, "idp-hint", "", "identity provider hint")
cmd.PersistentFlags().StringVar(&cconfig.TLSCert, "tls-cert", "", "path to tls cert pem file")
cmd.PersistentFlags().StringVar(&cconfig.TLSKey, "tls-key", "", "path to tls key pem file")
cmd.PersistentFlags().StringVar(&cconfig.TLSRootCA, "tls-root-ca", "", "path to tls root ca pem file")
cmd.PersistentFlags().DurationVar(&cconfig.HTTPTimeout, "http-timeout", time.Minute, "http client timeout")
cmd.PersistentFlags().DurationVar(&cconfig.BrowserTimeout, "browser-timeout", 10*time.Minute, "browser timeout")
cmd.PersistentFlags().BoolVar(&cconfig.Insecure, "insecure", false, "allow insecure connections")
cmd.PersistentFlags().BoolVarP(&silent, "silent", "s", false, "silent mode")
cmd.PersistentFlags().BoolVar(&noPrompt, "no-prompt", false, "disable prompt")
cmd.PersistentFlags().BoolVar(&cconfig.DPoP, "dpop", false, "use DPoP")
cmd.PersistentFlags().StringVar(&cconfig.Claims, "claims", "", "use claims")
cmd.PersistentFlags().StringVar(&cconfig.RAR, "rar", "", "use rich authorization request (RAR)")
cmd.PersistentFlags().StringSliceVar(&cconfig.ACRValues, "acr-values", []string{}, "ACR values")
cmd.PersistentFlags().StringVar(&cconfig.Purpose, "purpose", "", "string describing the purpose for obtaining End-User authorization")
cmd.AddCommand(jwksCmd)

cmd.Flags().StringVar(&cconfig.RedirectURL, "redirect-url", "http://localhost:9876/callback", "client redirect url")
cmd.Flags().StringVar(&cconfig.ClientID, "client-id", "", "client identifier")
cmd.Flags().StringVar(&cconfig.ClientSecret, "client-secret", "", "client secret")
cmd.Flags().StringVar(&cconfig.GrantType, "grant-type", "", "grant type")
cmd.Flags().StringVar(&cconfig.AuthMethod, "auth-method", "", "token endpoint authentication method")
cmd.Flags().StringVar(&cconfig.Username, "username", "", "resource owner password credentials grant flow username")
cmd.Flags().StringVar(&cconfig.Password, "password", "", "resource owner password credentials grant flow password")
cmd.Flags().StringVar(&cconfig.RefreshToken, "refresh-token", "", "refresh token")
cmd.Flags().StringSliceVar(&cconfig.ResponseType, "response-types", []string{""}, "response type")
cmd.Flags().StringVar(&cconfig.ResponseMode, "response-mode", "", "response mode")
cmd.Flags().StringSliceVar(&cconfig.Scopes, "scopes", []string{}, "requested scopes")
cmd.Flags().StringSliceVar(&cconfig.Audience, "audience", []string{}, "requested audience")
cmd.Flags().BoolVar(&cconfig.PKCE, "pkce", false, "enable proof key for code exchange (PKCE)")
cmd.Flags().BoolVar(&cconfig.PAR, "par", false, "enable pushed authorization requests (PAR)")
cmd.Flags().BoolVar(&cconfig.RequestObject, "request-object", false, "pass request parameters as jwt")
cmd.Flags().BoolVar(&cconfig.EncryptedRequestObject, "encrypted-request-object", false, "pass request parameters as encrypted jwt")
cmd.Flags().StringVar(&cconfig.Assertion, "assertion", "", "claims for jwt bearer assertion")
cmd.Flags().StringVar(&cconfig.SigningKey, "signing-key", "", "path or url to signing key in jwks format")
cmd.Flags().StringVar(&cconfig.EncryptionKey, "encryption-key", "", "path or url to encryption key in jwks format")
cmd.Flags().StringVar(&cconfig.SubjectToken, "subject-token", "", "third party token")
cmd.Flags().StringVar(&cconfig.SubjectTokenType, "subject-token-type", "", "third party token type")
cmd.Flags().StringVar(&cconfig.ActorToken, "actor-token", "", "acting party token")
cmd.Flags().StringVar(&cconfig.ActorTokenType, "actor-token-type", "", "acting party token type")
cmd.Flags().StringVar(&cconfig.IDTokenHint, "id-token-hint", "", "id token hint")
cmd.Flags().StringVar(&cconfig.LoginHint, "login-hint", "", "user identifier hint")
cmd.Flags().StringVar(&cconfig.IDPHint, "idp-hint", "", "identity provider hint")
cmd.Flags().StringVar(&cconfig.TLSCert, "tls-cert", "", "path to tls cert pem file")
cmd.Flags().StringVar(&cconfig.TLSKey, "tls-key", "", "path to tls key pem file")
cmd.Flags().StringVar(&cconfig.TLSRootCA, "tls-root-ca", "", "path to tls root ca pem file")
cmd.Flags().DurationVar(&cconfig.HTTPTimeout, "http-timeout", time.Minute, "http client timeout")
cmd.Flags().DurationVar(&cconfig.BrowserTimeout, "browser-timeout", 10*time.Minute, "browser timeout")
cmd.Flags().BoolVar(&cconfig.Insecure, "insecure", false, "allow insecure connections")
cmd.Flags().BoolVarP(&silent, "silent", "s", false, "silent mode")
cmd.Flags().BoolVar(&noPrompt, "no-prompt", false, "disable prompt")
cmd.Flags().BoolVar(&cconfig.DPoP, "dpop", false, "use DPoP")
cmd.Flags().StringVar(&cconfig.Claims, "claims", "", "use claims")
cmd.Flags().StringVar(&cconfig.RAR, "rar", "", "use rich authorization request (RAR)")
cmd.Flags().StringSliceVar(&cconfig.ACRValues, "acr-values", []string{}, "ACR values")
cmd.Flags().StringVar(&cconfig.Purpose, "purpose", "", "string describing the purpose for obtaining End-User authorization")

return cmd
}
Expand Down
123 changes: 123 additions & 0 deletions internal/jwks/jwks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package jwks

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"fmt"

"github.com/go-jose/go-jose/v3"
"github.com/google/uuid"
)

type Config struct {
Type string
Size int
Curve int
Alg string
Use string
}

func Generate(config Config) (jose.JSONWebKey, error) {
var (
jwk = jose.JSONWebKey{
KeyID: uuid.New().String(),
}
err error
)

switch config.Use {
case "sig":
jwk.Use = "sig"

switch config.Type {
case "rsa":
switch config.Alg {
case "RS256", "RS384", "RS512", "PS256", "PS384", "PS512":
jwk.Algorithm = config.Alg
case "":
jwk.Algorithm = "RS256"
default:
return jwk, fmt.Errorf("unknown algorithm: %s (use RS256, RS384, RS512, PS256, PS384, PS512)", config.Alg)
}

case "ec":
switch config.Alg {
case "ES256", "ES384", "ES512":
jwk.Algorithm = config.Alg
case "":
jwk.Algorithm = "ES256"
default:
return jwk, fmt.Errorf("unknown algorithm: %s (use ES256, ES384 or ES512)", config.Alg)
}
}

case "enc":
jwk.Use = "enc"

switch config.Type {
case "rsa":
switch config.Alg {
case "RSA1_5", "RSA-OAEP", "RSA-OAEP-256":
jwk.Algorithm = config.Alg
case "":
jwk.Algorithm = "RSA-OAEP-256"
default:
return jwk, fmt.Errorf("unknown algorithm: %s (use RSA1_5, RSA-OAEP, RSA-OAEP-256)", config.Alg)
}

case "ec":
switch config.Alg {
case "ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW":
jwk.Algorithm = config.Alg
case "":
jwk.Algorithm = "ECDH-ES+A128KW"
}
}

default:
return jwk, fmt.Errorf("invalid use: %s (use sig or enc)", config.Use)
}

switch config.Type {
case "rsa":
var size int

switch config.Size {
case 2048, 3072, 4096:
size = config.Size
default:
return jwk, fmt.Errorf("invalid key size: %d (use 2048, 3072 or 4096)", config.Size)
}

if jwk.Key, err = rsa.GenerateKey(rand.Reader, size); err != nil {
return jwk, err
}

case "ec":
var curve elliptic.Curve

switch config.Curve {
case 224:
curve = elliptic.P224()
case 256:
curve = elliptic.P256()
case 384:
curve = elliptic.P384()
case 521:
curve = elliptic.P521()
default:
return jwk, fmt.Errorf("unknown elliptic curve: %d (use 224, 256, 284 or 521)", config.Curve)
}

if jwk.Key, err = ecdsa.GenerateKey(curve, rand.Reader); err != nil {
return jwk, err
}

default:
return jwk, fmt.Errorf("uknown key type: %s (use rsa or ec)", config.Type)
}

return jwk, nil
}
Loading