From 8d73b0d4ce8c23bab64d4694f4af2e15d3f4d7da Mon Sep 17 00:00:00 2001 From: Mateusz Bilski Date: Tue, 10 Oct 2023 21:02:45 +0200 Subject: [PATCH 1/3] Add cmd to generate jwk --- cmd/jwks.go | 55 +++++++++++++++++++++++ cmd/log.go | 13 +++--- cmd/oauth2.go | 100 ++++++++++++++++++++++++------------------ internal/jwks/jwks.go | 75 +++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 48 deletions(-) create mode 100644 cmd/jwks.go create mode 100644 internal/jwks/jwks.go diff --git a/cmd/jwks.go b/cmd/jwks.go new file mode 100644 index 0000000..fed75a0 --- /dev/null +++ b/cmd/jwks.go @@ -0,0 +1,55 @@ +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", + 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, ps, ec)") + generateJwkCmd.Flags().IntVar(&jwksConfig.Size, "size", 2048, "key size (rsa)") + generateJwkCmd.Flags().IntVar(&jwksConfig.Curve, "curve", 256, "curve (ec)") + generateJwkCmd.Flags().StringVar(&jwksConfig.Use, "use", "sig", "key use (sig, enc)") + + jwksCmd.AddCommand(generateJwkCmd) +} diff --git a/cmd/log.go b/cmd/log.go index a887c11..e1ed3d5 100644 --- a/cmd/log.go +++ b/cmd/log.go @@ -317,11 +317,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) } } } @@ -354,14 +356,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{} diff --git a/cmd/oauth2.go b/cmd/oauth2.go index 983f895..058f096 100644 --- a/cmd/oauth2.go +++ b/cmd/oauth2.go @@ -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 } @@ -32,9 +45,11 @@ func NewOAuth2Cmd() (cmd *OAuth2Cmd) { cmd = &OAuth2Cmd{ Command: &cobra.Command{ - Use: "oauthc [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), }, } @@ -42,45 +57,46 @@ func NewOAuth2Cmd() (cmd *OAuth2Cmd) { cmd.AddCommand(versionCmd) 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.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") return cmd } diff --git a/internal/jwks/jwks.go b/internal/jwks/jwks.go new file mode 100644 index 0000000..3454475 --- /dev/null +++ b/internal/jwks/jwks.go @@ -0,0 +1,75 @@ +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 + Use string +} + +func Generate(config Config) (jose.JSONWebKey, error) { + var ( + jwk = jose.JSONWebKey{ + KeyID: uuid.New().String(), + } + err error + ) + + switch config.Use { + case "sig", "enc": + jwk.Use = config.Use + default: + return jwk, fmt.Errorf("invalid use: %s (use sig or enc)", config.Use) + } + + switch config.Type { + case "rsa", "ps": + if jwk.Key, err = rsa.GenerateKey(rand.Reader, config.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, ec or ps)", config.Type) + } + + switch config.Type { + + case "rsa": + jwk.Algorithm = "RS256" + case "ps": + jwk.Algorithm = "RS256" + case "ec": + jwk.Algorithm = "ES256" + } + + return jwk, nil +} From b5ff8f8f78a807fd03394a5e984b7a9715d8bcab Mon Sep 17 00:00:00 2001 From: Mateusz Bilski Date: Thu, 2 Nov 2023 10:59:52 +0100 Subject: [PATCH 2/3] Alg --- internal/jwks/jwks.go | 59 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/internal/jwks/jwks.go b/internal/jwks/jwks.go index 3454475..2514db1 100644 --- a/internal/jwks/jwks.go +++ b/internal/jwks/jwks.go @@ -15,6 +15,7 @@ type Config struct { Type string Size int Curve int + Alg string Use string } @@ -27,8 +28,52 @@ func Generate(config Config) (jose.JSONWebKey, error) { ) switch config.Use { - case "sig", "enc": - jwk.Use = config.Use + case "sig": + switch config.Type { + case "rsa": + switch config.Alg { + case "RS256", "RS384", "RS512": + jwk.Algorithm = config.Alg + default: + return jwk, fmt.Errorf("unknown algorithm: %s (use RS256, RS384 or RS512)", config.Alg) + } + case "ps": + switch config.Alg { + case "PS256", "PS384", "PS512": + jwk.Algorithm = config.Alg + default: + return jwk, fmt.Errorf("unknown algorithm: %s (use PS256, PS384 or PS512)", config.Alg) + } + case "ec": + switch config.Alg { + case "ES256", "ES384", "ES512": + jwk.Algorithm = "ES256" + } + + jwk.Use = "sig" + case "enc": + jwk.Use = "enc" + + switch config.Type { + case "rsa": + switch config.Alg { + case "RS256", "RS384", "RS512": + jwk.Algorithm = config.Alg + default: + return jwk, fmt.Errorf("unknown algorithm: %s (use RS256, RS384 or RS512)", config.Alg) + } + case "ps": + switch config.Alg { + case "PS256", "PS384", "PS512": + jwk.Algorithm = config.Alg + default: + return jwk, fmt.Errorf("unknown algorithm: %s (use PS256, PS384 or PS512)", config.Alg) + } + case "ec": + switch config.Alg { + case "ES256", "ES384", "ES512": + jwk.Algorithm = "ES256" + } default: return jwk, fmt.Errorf("invalid use: %s (use sig or enc)", config.Use) } @@ -61,15 +106,5 @@ func Generate(config Config) (jose.JSONWebKey, error) { return jwk, fmt.Errorf("uknown key type: %s (use rsa, ec or ps)", config.Type) } - switch config.Type { - - case "rsa": - jwk.Algorithm = "RS256" - case "ps": - jwk.Algorithm = "RS256" - case "ec": - jwk.Algorithm = "ES256" - } - return jwk, nil } From 18725abdb75c370280a8a48cf90cfe37a28acf78 Mon Sep 17 00:00:00 2001 From: Mateusz Bilski Date: Tue, 16 Jan 2024 14:14:10 +0100 Subject: [PATCH 3/3] Algs --- cmd/jwks.go | 23 ++++++++++++++--- internal/jwks/jwks.go | 59 +++++++++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/cmd/jwks.go b/cmd/jwks.go index fed75a0..e59372b 100644 --- a/cmd/jwks.go +++ b/cmd/jwks.go @@ -18,6 +18,22 @@ 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 @@ -46,9 +62,10 @@ var generateJwkCmd = &cobra.Command{ } func init() { - generateJwkCmd.Flags().StringVar(&jwksConfig.Type, "type", "rsa", "key type (rsa, ps, ec)") - generateJwkCmd.Flags().IntVar(&jwksConfig.Size, "size", 2048, "key size (rsa)") - generateJwkCmd.Flags().IntVar(&jwksConfig.Curve, "curve", 256, "curve (ec)") + 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) diff --git a/internal/jwks/jwks.go b/internal/jwks/jwks.go index b0462e2..3c5da7b 100644 --- a/internal/jwks/jwks.go +++ b/internal/jwks/jwks.go @@ -29,62 +29,72 @@ func Generate(config Config) (jose.JSONWebKey, error) { switch config.Use { case "sig": + jwk.Use = "sig" + switch config.Type { case "rsa": switch config.Alg { - case "RS256", "RS384", "RS512": + 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 or RS512)", config.Alg) - } - case "ps": - switch config.Alg { - case "PS256", "PS384", "PS512": - jwk.Algorithm = config.Alg - default: - return jwk, fmt.Errorf("unknown algorithm: %s (use PS256, PS384 or PS512)", config.Alg) + 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) } } - jwk.Use = "sig" case "enc": jwk.Use = "enc" switch config.Type { case "rsa": switch config.Alg { - case "RS256", "RS384", "RS512": + 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 RS256, RS384 or RS512)", config.Alg) - } - case "ps": - switch config.Alg { - case "PS256", "PS384", "PS512": - jwk.Algorithm = config.Alg - default: - return jwk, fmt.Errorf("unknown algorithm: %s (use PS256, PS384 or PS512)", config.Alg) + return jwk, fmt.Errorf("unknown algorithm: %s (use RSA1_5, RSA-OAEP, RSA-OAEP-256)", config.Alg) } + case "ec": switch config.Alg { - case "ES256", "ES384", "ES512": - jwk.Algorithm = "ES256" + 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", "ps": - if jwk.Key, err = rsa.GenerateKey(rand.Reader, config.Size); err != nil { + 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 @@ -104,8 +114,9 @@ func Generate(config Config) (jose.JSONWebKey, error) { 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, ec or ps)", config.Type) + return jwk, fmt.Errorf("uknown key type: %s (use rsa or ec)", config.Type) } return jwk, nil