diff --git a/README.md b/README.md
index 39bc995..5fa42e0 100644
--- a/README.md
+++ b/README.md
@@ -8,10 +8,11 @@
HTTPS Wrench, a wrench not to bench
-**HTTPS Wrench** is a CLI program to make Yaml defined HTTPS requests and to
-inspect x.509 certificates and keys.\
+**HTTPS Wrench** is a tool for maintainers of secure HTTP endpoints.
+It enables executing YAML-defined HTTPS requests, inspecting x.509 certificates, private keys, JSON Web Tokens (JWT), and
+generating JSON Web Key Sets (JWKS).\
**HTTPS Wrench** was born from the desire of a disposable Bash script to become
-a reliable tool for mechanics of the World Wide Web.\
+a reliable companion for mechanics of the World Wide Web.\
`https-wrench` will, one day, take the place of `curl` in the hearts and the
eyes of whoever is about to migrate a DNS record from a webserver to a load
balancer, reverse proxy, Ingress Gateway, CloudFront distribution.
@@ -26,30 +27,37 @@ Check the help:
```plain
❯ https-wrench -h
-HTTPS Wrench is a tool to make HTTPS requests according to a Yaml configuration file and to inspect x.509 certificates and keys.
+HTTPS Wrench is a tool for maintainers of secure HTTP endpoints.
+It enables executing YAML-defined HTTPS requests and performing in-depth
+inspection of x.509 certificates, private keys, and JSON Web Tokens.
-https-wrench has two subcommands: requests and certinfo.
+https-wrench provides several specialized subcommands:
-requests is the subcommand that does HTTPS requests according to the configuration provided
-by the --config flag.
+requests: Execute HTTPS requests according to a structured YAML configuration,
+supporting custom CA bundles and verbose output.
-certinfo is a subcommand that reads information from PEM encoded x.509 certificates and keys. The certificates
-can be read from local files or TLS enabled endpoints.
+certinfo: Inspect PEM-encoded certificates and keys from local files or remote
+TLS endpoints. Verify certificate chains and key pairings.
-certinfo can compare public keys extracted from certificates and private keys to check if they match.
+jwtinfo: Decode, inspect, and validate JSON Web Tokens (JWT) using local files
+or remote JWKS endpoints.
-HTTPS Wrench is distributed with an open source license and available at the following address:
-https://github.com/xenOs76/https-wrench
+jwks: Generate pretty-printed JSON Web Key Sets (JWKS) from public keys for
+exposure on well-known endpoints.
+
+Distributed under an open-source license: https://github.com/xenOs76/https-wrench
Usage:
https-wrench [flags]
https-wrench [command]
Available Commands:
- certinfo Shows information about x.509 certificates and keys
+ certinfo Inspect and verify x.509 certificates and keys
completion Generate the autocompletion script for the specified shell
help Help about any command
- requests Make HTTPS requests defined in the YAML configuration file
+ jwks Generate a JSON Web Key Set (JWKS) from a public key
+ jwtinfo Inspect and validate JSON Web Tokens (JWT)
+ requests Execute YAML-defined HTTPS requests
Flags:
--config string config file (default is $HOME/.https-wrench.yaml)
@@ -71,15 +79,15 @@ Get the help:
```plain
❯ https-wrench requests -h
-https-wrench requests is the subcommand that does HTTPS requests according to the configuration
+https-wrench requests is the subcommand that does HTTPS requests according to the configuration
pointed by the --config flag.
A sample configuration can be generated as a starting point (--show-sample-config).
-The Github repository has more configuration examples:
+The Github repository has more configuration examples:
https://github.com/xenOs76/https-wrench/tree/main/assets/examples
-It also provides a JSON schema that can be used to validate new configuration files:
+It also provides a JSON schema that can be used to validate new configuration files:
https://github.com/xenOs76/https-wrench/blob/main/https-wrench.schema.json
Examples:
@@ -90,7 +98,7 @@ Usage:
https-wrench requests [flags]
Flags:
- --ca-bundle string Path to bundle file with CA certificates
+ --ca-bundle string Path to bundle file with CA certificates
to use for validation
-h, --help help for requests
--show-sample-config Show a sample YAML configuration
@@ -131,16 +139,16 @@ Get the help:
```plain
❯ https-wrench certinfo -h
-HTTPS Wrench certinfo: shows information about PEM certificates and keys.
+Inspect and verify PEM encoded x.509 certificates and keys.
-https-wrench certinfo can fetch certificates from a TLS endpoint, read from a PEM bundle file, and check if a
+https-wrench certinfo can fetch certificates from a TLS endpoint, read from a PEM bundle file, and check if a
private key matches any of the certificates.
-The certificates can be verified against the system root CAs or a custom CA bundle file.
+The certificates can be verified against the system root CAs or a custom CA bundle file.
The validation can be skipped.
-If the private key is password protected, the password can be provided via the CERTINFO_PKEY_PW
+If the private key is password protected, the password can be provided via the CERTINFO_PKEY_PW
environment variable or will be prompted on stdin.
Examples:
@@ -159,13 +167,13 @@ Usage:
https-wrench certinfo [flags]
Flags:
- --ca-bundle string Path to bundle file with CA certificates
+ --ca-bundle string Path to bundle file with CA certificates
to use for validation
--cert-bundle string Path to PEM Certificate bundle file
-h, --help help for certinfo
--key-file string Path to PEM Key file
- --tls-endpoint string TLS enabled endpoint exposing certificates to fetch.
- Forms: 'host:port', '[host]:port'.
+ --tls-endpoint string TLS enabled endpoint exposing certificates to fetch.
+ Forms: 'host:port', '[host]:port'.
IPv6 addresses must be enclosed in square brackets, as in '[::1]:80'
--tls-insecure Skip certificate validation when connecting to a TLS endpoint
--tls-servername string ServerName to use when connecting to an SNI enabled TLS endpoint
@@ -197,10 +205,106 @@ been used to generate the certificate:
❯ https-wrench certinfo --tls-endpoint localhost:9443 --ca-bundle rootCA.pem --key-file key.pem
```
+### HTTPS Wrench jwtinfo
+
+`jwtinfo` allows you to decode and inspect the claims of a JSON Web Token. It can also validate the token signature if a JWKS endpoint is provided.
+
+
+View Jwtinfo Help (`https-wrench jwtinfo -h`)
+
+```plain
+❯ https-wrench jwtinfo -h
+
+Inspect and validate JSON Web Tokens (JWT) from files or remote providers.
+
+Examples:
+ export REQ_URL="https://sample.provider/oauth/token"
+ export REQ_VALUES="{\"login\":\"values\"}"
+ export VALIDATION_URL="https://url.to/jwks.json"
+
+ # Read a JWT token from a local file
+ https-wrench jwtinfo --token-file /var/run/secrets/kubernetes.io/serviceaccount/token
+
+ # Request a JWT token using inline values
+ https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES
+
+ # Request a JWT token using values file
+ https-wrench jwtinfo --request-url $REQ_URL --request-values-file request-values.json
+
+ # Request and validate a JWT token
+ https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL
+
+Usage:
+ https-wrench jwtinfo [flags]
+
+Flags:
+ -h, --help help for jwtinfo
+ --request-url string HTTP address to use for the JWT token request
+ --request-values-file string File containing the JSON encoded values to use for the JWT token request
+ --request-values-json string JSON encoded values to use for the JWT token request
+ --token-file string File containing the JWT token
+ --validation-url string Url of the JSON Web Key Set (JWKS) to use for validating the JWT token
+
+Global Flags:
+ --config string config file (default is $HOME/.https-wrench.yaml)
+ --version Display the version
+```
+
+
+
+Decode a token from a file:
+
+```shell
+❯ https-wrench jwtinfo --token-file mytoken.jwt
+```
+
+### HTTPS Wrench jwks
+
+`jwks` generates a public JSON Web Key Set from a PEM-encoded public key. This is useful for exposing your public keys at a `.well-known/jwks.json` endpoint.
+
+
+View Jwks Help (`https-wrench jwks -h`)
+
+```plain
+❯ https-wrench jwks -h
+
+Generate a pretty-printed JSON Web Key Set (JWKS) from a public key file.
+
+The generated JWKS contains only public key parameters and is safe
+to be exposed (e.g. at a /.well-known/jwks.json endpoint).
+
+Examples:
+ # Generate a public JWKS from an RSA public key
+ https-wrench jwks --public-key-file rsa-public.pem
+
+ # Generate a public JWKS with a custom Key ID (kid)
+ https-wrench jwks --public-key-file ec-public.pem --kid "my-custom-key-id"
+
+Usage:
+ https-wrench jwks [flags]
+
+Flags:
+ -h, --help help for jwks
+ --kid string Optional explicit Key ID (kid) to use. If not provided, a SHA-256-derived ID is generated.
+ --public-key-file string File containing the PEM-encoded public key
+
+Global Flags:
+ --config string config file (default is $HOME/.https-wrench.yaml)
+ --version Display the version
+```
+
+
+
+Generate a JWKS with a SHA-256-derived KID:
+
+```shell
+❯ https-wrench jwks --public-key-file public.pem
+```
+
### Sample output
-HTTPS Wrench requests, (long) sample configuration output
+HTTPS Wrench requests, sample configuration output
@@ -219,6 +323,16 @@ been used to generate the certificate:
+
+HTTPS Wrench jwtinfo, request token
+
+
+
+
+HTTPS Wrench jwtinfo, read token and validate
+
+
+
## How to install
diff --git a/assets/img/https-wrench_jwtinfo_read_validate_token.png b/assets/img/https-wrench_jwtinfo_read_validate_token.png
new file mode 100644
index 0000000..80eb27f
Binary files /dev/null and b/assets/img/https-wrench_jwtinfo_read_validate_token.png differ
diff --git a/assets/img/https-wrench_jwtinfo_request_token.png b/assets/img/https-wrench_jwtinfo_request_token.png
new file mode 100644
index 0000000..ff416d0
Binary files /dev/null and b/assets/img/https-wrench_jwtinfo_request_token.png differ
diff --git a/cmd/certinfo.go b/cmd/certinfo.go
index e5f8497..03cac8c 100644
--- a/cmd/certinfo.go
+++ b/cmd/certinfo.go
@@ -19,9 +19,8 @@ var (
var certinfoCmd = &cobra.Command{
Use: "certinfo",
- Short: "Shows information about x.509 certificates and keys",
- Long: `
-HTTPS Wrench certinfo: shows information about PEM encoded x.509 certificates and keys.
+ Short: "Inspect and verify x.509 certificates and keys",
+ Long: `Inspect and verify PEM encoded x.509 certificates and keys.
https-wrench certinfo can fetch certificates from a TLS endpoint, read from a PEM bundle file, and check if a
private key matches any of the certificates.
diff --git a/cmd/jwks.go b/cmd/jwks.go
new file mode 100644
index 0000000..07c99b4
--- /dev/null
+++ b/cmd/jwks.go
@@ -0,0 +1,67 @@
+package cmd
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+ "github.com/xenos76/https-wrench/internal/jwks"
+ "github.com/xenos76/https-wrench/internal/style"
+)
+
+var (
+ jwksPublicKeyFile string
+ jwksKID string
+)
+
+var jwksCmd = &cobra.Command{
+ Use: "jwks",
+ Short: "Generate a JSON Web Key Set (JWKS) from a public key",
+ Long: `Generate a pretty-printed JSON Web Key Set (JWKS) from a public key file.
+
+The generated JWKS contains only public key parameters and is safe
+to be exposed (e.g. at a /.well-known/jwks.json endpoint).
+
+Examples:
+ # Generate a public JWKS from an RSA public key
+ https-wrench jwks --public-key-file rsa-public.pem
+
+ # Generate a public JWKS with a custom Key ID (kid)
+ https-wrench jwks --public-key-file ec-public.pem --kid "my-custom-key-id"
+`,
+ Run: func(cmd *cobra.Command, _ []string) {
+ jwksJSON, err := jwks.GenerateJWKS(cmd.Context(), jwksPublicKeyFile, jwksKID)
+ if err != nil {
+ cmd.PrintErrf("Error generating JWKS: %s\n", err)
+
+ return
+ }
+
+ // Print a nice title and then the formatted JSON
+ w := cmd.OutOrStdout()
+ fmt.Fprintln(w)
+ fmt.Fprintln(w, style.LgSprintf(style.Cmd, "Jwks"))
+ fmt.Fprintln(w)
+
+ fmt.Fprint(w, style.CodeSyntaxHighlight("json", jwksJSON))
+ fmt.Fprintln(w)
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(jwksCmd)
+
+ jwksCmd.Flags().StringVar(
+ &jwksPublicKeyFile,
+ "public-key-file",
+ "",
+ "File containing the PEM-encoded public key",
+ )
+ _ = jwksCmd.MarkFlagRequired("public-key-file")
+
+ jwksCmd.Flags().StringVar(
+ &jwksKID,
+ "kid",
+ "",
+ "Optional explicit Key ID (kid) to use. If not provided, a SHA-256-derived ID is generated.",
+ )
+}
diff --git a/cmd/jwtinfo.go b/cmd/jwtinfo.go
index 07ca6ac..c9e26f6 100644
--- a/cmd/jwtinfo.go
+++ b/cmd/jwtinfo.go
@@ -29,13 +29,13 @@ var (
var jwtinfoCmd = &cobra.Command{
Use: "jwtinfo",
- Short: "JwtInfo shows data from a JWT token",
- Long: `JwtInfo shows data from a JWT token
+ Short: "Inspect and validate JSON Web Tokens (JWT)",
+ Long: `Inspect and validate JSON Web Tokens (JWT) from files or remote providers.
Examples:
export REQ_URL="https://sample.provider/oauth/token"
export REQ_VALUES="{\"login\":\"values\"}"
- export VALIDATION_URL="https://url.to/jkws.json"
+ export VALIDATION_URL="https://url.to/jwks.json"
# Read a JWT token from a local file
https-wrench jwtinfo --token-file /var/run/secrets/kubernetes.io/serviceaccount/token
@@ -50,11 +50,12 @@ Examples:
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL
`,
Run: func(cmd *cobra.Command, _ []string) {
- // TODO: remove global --config option
-
- var err error
- var tokenData jwtinfo.JwtTokenData
+ var (
+ err error
+ tokenData jwtinfo.JwtTokenData
+ )
+ // TODO: remove global --config option
if tokenFile != "" {
tokenData, err = jwtinfo.ReadTokenFromFile(tokenFile)
if err != nil {
@@ -62,6 +63,7 @@ Examples:
"error while reading token value from file: %s",
err,
)
+
return
}
}
@@ -80,6 +82,7 @@ Examples:
"error while reading request's values from file: %s",
err,
)
+
return
}
}
@@ -94,11 +97,13 @@ Examples:
"error while parsing request's values JSON string: %s",
err,
)
+
return
}
}
tokenData, err = jwtinfo.RequestToken(
+ cmd.Context(),
requestURL,
requestValuesMap,
client,
@@ -118,7 +123,7 @@ Examples:
}
if jwksURL != "" {
- err = tokenData.ParseWithJWKS(jwksURL, keyfuncDefOverride)
+ err = tokenData.ParseWithJWKS(cmd.Context(), jwksURL, keyfuncDefOverride)
if err != nil {
cmd.Printf("error while parsing token data: %s\n", err)
return
diff --git a/cmd/man.go b/cmd/man.go
index 41c664a..69da0db 100644
--- a/cmd/man.go
+++ b/cmd/man.go
@@ -27,6 +27,7 @@ var manCmd = &cobra.Command{
Date: &now,
Source: "https-wrench",
}
+
err := doc.GenManTree(rootCmd, rootHeader, manPagesDestDir)
if err != nil {
fmt.Print(err)
diff --git a/cmd/requests.go b/cmd/requests.go
index 3d6469c..13a7e0e 100644
--- a/cmd/requests.go
+++ b/cmd/requests.go
@@ -22,7 +22,7 @@ var (
var requestsCmd = &cobra.Command{
Use: "requests",
- Short: "Make HTTPS requests defined in the YAML configuration file",
+ Short: "Execute YAML-defined HTTPS requests",
Long: `
https-wrench requests is the subcommand that does HTTPS requests according to the configuration
pointed by the --config flag.
@@ -62,6 +62,7 @@ Examples:
if err != nil {
cmd.Printf("\nConfig file not found: %s\n", viper.ConfigFileUsed())
_ = cmd.Help()
+
return
}
diff --git a/cmd/root.go b/cmd/root.go
index fcc1222..1cd977a 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -49,23 +49,27 @@ var (
var rootCmd = &cobra.Command{
Use: "https-wrench",
- Short: "HTTPS Wrench, a tool to make Yaml defined HTTPS requests and inspect x.509 certificates and keys",
+ Short: "HTTPS Wrench, a tool for maintainers of secure HTTP endpoints",
Long: `
-HTTPS Wrench is a tool to make HTTPS requests according to a Yaml configuration file
-and to inspect x.509 certificates and keys.
+HTTPS Wrench is a tool for maintainers of secure HTTP endpoints.
+It enables executing YAML-defined HTTPS requests and performing in-depth
+inspection of x.509 certificates, private keys, and JSON Web Tokens.
-https-wrench has two subcommands: requests and certinfo.
+https-wrench provides several specialized subcommands:
-requests is the subcommand that does HTTPS requests according to the configuration provided
-by the --config flag.
+requests: Execute HTTPS requests according to a structured YAML configuration,
+supporting custom CA bundles and verbose output.
-certinfo is a subcommand that reads information from PEM encoded x.509 certificates and keys. The certificates
-can be read from local files or TLS enabled endpoints.
+certinfo: Inspect PEM-encoded certificates and keys from local files or remote
+TLS endpoints. Verify certificate chains and key pairings.
-certinfo can compare public keys extracted from certificates and private keys to check if they match.
+jwtinfo: Decode, inspect, and validate JSON Web Tokens (JWT) using local files
+or remote JWKS endpoints.
-HTTPS Wrench is distributed with an open source license and available at the following address:
-https://github.com/xenOs76/https-wrench`,
+jwks: Generate pretty-printed JSON Web Key Sets (JWKS) from public keys for
+exposure on well-known endpoints.
+
+Distributed under an open-source license: https://github.com/xenOs76/https-wrench`,
Run: func(cmd *cobra.Command, _ []string) {
showVersion, _ := cmd.Flags().GetBool("version")
diff --git a/devenv.nix b/devenv.nix
index bb96e39..3114c12 100644
--- a/devenv.nix
+++ b/devenv.nix
@@ -205,17 +205,28 @@ in {
test -f $CAROOT/dhparam || curl https://ssl-config.mozilla.org/ffdhe2048.txt > $CAROOT/dhparam
test -f $CAROOT/cert.pem || mkcert -key-file $CAROOT/key.pem -cert-file $CAROOT/cert.pem localhost 127.0.0.1 ::1 example.com *.example.com
test -f $CAROOT/full-cert.pem || cat $CAROOT/cert.pem $CAROOT/rootCA.pem > $CAROOT/full-cert.pem
+ test -f $CAROOT/key.pub || openssl rsa -in $CAROOT/key.pem -pubout -out $CAROOT/key.pub
+
test -f $CAROOT/rsa-private_traditional.key || openssl rsa -in $CAROOT/key.pem -traditional -out $CAROOT/rsa-private_traditional.key
+ test -f $CAROOT/rsa-private_traditional.pub || openssl rsa -in $CAROOT/rsa-private_traditional.key -pubout -out $CAROOT/rsa-private_traditional.pub
test -f $CAROOT/rsa-private_traditional_encrypted.key || openssl rsa -passout pass:$KEY_TEST_PW -in $CAROOT/rsa-private_traditional.key -out $CAROOT/rsa-private_traditional_encrypted.key -aes256
+ test -f $CAROOT/rsa-private_traditional_encrypted.pub || openssl rsa -passin pass:$KEY_TEST_PW -in $CAROOT/rsa-private_traditional_encrypted.key -pubout -out $CAROOT/rsa-private_traditional_encrypted.pub
test -f $CAROOT/private.ec.key || openssl ecparam -name prime256v1 -genkey -noout -out $CAROOT/private.ec.key
+ test -f $CAROOT/private.ec.pub || openssl ec -in $CAROOT/private.ec.key -pubout -out $CAROOT/private.ec.pub
+
test -f $CAROOT/encrypted.rsa.key || openssl genrsa -aes128 -passout pass:$KEY_TEST_PW -out $CAROOT/encrypted.rsa.key 4096
+ test -f $CAROOT/encrypted.rsa.pub || openssl rsa -passin pass:$KEY_TEST_PW -in $CAROOT/encrypted.rsa.key -pubout -out $CAROOT/encrypted.rsa.pub
# ECDSA_DIR=$CAROOT/ecdsa-cert
test -d $ECDSA_DIR || mkdir $ECDSA_DIR
test -f $ECDSA_DIR/ecdsa.key || openssl ecparam -name prime256v1 -genkey -noout -out $ECDSA_DIR/ecdsa.key
+ test -f $ECDSA_DIR/ecdsa.pub || openssl ec -in $ECDSA_DIR/ecdsa.key -pubout -out $ECDSA_DIR/ecdsa.pub
+
test -f $ECDSA_DIR/encrypted.ecdsa.key || openssl ec -in $ECDSA_DIR/ecdsa.key -out $ECDSA_DIR/encrypted.ecdsa.key -aes256 -passout pass:$KEY_TEST_PW
+ test -f $ECDSA_DIR/encrypted.ecdsa.pub || openssl ec -passin pass:$KEY_TEST_PW -in $ECDSA_DIR/encrypted.ecdsa.key -pubout -out $ECDSA_DIR/encrypted.ecdsa.pub
+
test -f $ECDSA_DIR/ecdsa.crt || openssl req -new -x509 -key $ECDSA_DIR/ecdsa.key -days 825 -out $ECDSA_DIR/ecdsa.crt \
-subj "/CN=example.com/O=Example Org" \
-addext "subjectAltName=DNS:example.com,DNS:alt.example.com,IP:10.0.0.5"
@@ -223,7 +234,11 @@ in {
# ED25519_DIR=$CAROOT/ed25519_cert
test -d $ED25519_DIR || mkdir $ED25519_DIR
test -f $ED25519_DIR/ed25519.key || openssl genpkey -algorithm Ed25519 -out $ED25519_DIR/ed25519.key
+ test -f $ED25519_DIR/ed25519.pub || openssl pkey -in $ED25519_DIR/ed25519.key -pubout -out $ED25519_DIR/ed25519.pub
+
test -f $ED25519_DIR/encrypted.ed25519.key || openssl pkey -in $ED25519_DIR/ed25519.key -out $ED25519_DIR/encrypted.ed25519.key -aes256 -passout pass:$KEY_TEST_PW
+ test -f $ED25519_DIR/encrypted.ed25519.pub || openssl pkey -passin pass:$KEY_TEST_PW -in $ED25519_DIR/encrypted.ed25519.key -pubout -out $ED25519_DIR/encrypted.ed25519.pub
+
test -f $ED25519_DIR/ed25519.crt || openssl req -new -x509 -key $ED25519_DIR/ed25519.key -days 365 -out $ED25519_DIR/ed25519.crt \
-subj "/CN=example.com/O=Example Org" -addext "subjectAltName=DNS:example.com,IP:127.0.0.1"
'';
@@ -600,6 +615,15 @@ in {
./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-json "$JWTINFO_TEST_AUTH0" --validation-url "$VALIDATION_URL"
'';
+ scripts.run-jwtinfo-test-auth0-wrong-validation-url.exec = ''
+ gum format "### JwtInfo request against Auth0 with wrong validation URL"
+
+ REQ_URL="https://dev-x3cci6dykofnlj5z.eu.auth0.com/oauth/token"
+ VALIDATION_URL="https://keycloak.k3s.os76.xyz/realms/os76/protocol/openid-connect/certs"
+
+ ./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-json "$JWTINFO_TEST_AUTH0" --validation-url "$VALIDATION_URL"
+ '';
+
scripts.run-jwtinfo-test-auth0-no-validation.exec = ''
gum format "### JwtInfo request against Auth0: no validation"
@@ -667,13 +691,6 @@ in {
enterShell = ''
gum format "# Devenv shell"
- export GITEA_TOKEN=$(cat ~/.config/goreleaser/gitea_token)
- export GITHUB_TOKEN=$(cat ~/.config/goreleaser/github_token)
-
- # JwtInfo tests against authentication providers when not on CI
- # test -f ~/.config/https-wrench/jwtinfo_test_auth0_req_values.json && export JWTINFO_TEST_AUTH0=$(cat ~/.config/https-wrench/jwtinfo_test_auth0_req_values.json)
- # test -f ~/.config/https-wrench/jwtinfo_test_keycloak_req_values.json && export JWTINFO_TEST_KEYCLOAK=$(cat ~/.config/https-wrench/jwtinfo_test_keycloak_req_values.json)
-
go version
create-certs
'';
diff --git a/go.mod b/go.mod
index b5f7a67..65de9e0 100644
--- a/go.mod
+++ b/go.mod
@@ -6,44 +6,43 @@ require (
github.com/MicahParks/jwkset v0.11.0
github.com/MicahParks/keyfunc/v3 v3.8.0
github.com/alecthomas/assert/v2 v2.11.0
- github.com/alecthomas/chroma/v2 v2.23.1
- github.com/breml/rootcerts v0.3.4
+ github.com/alecthomas/chroma/v2 v2.24.0
+ github.com/breml/rootcerts v0.3.5
github.com/catppuccin/go v0.3.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/dustin/go-humanize v1.0.1
- github.com/goforj/godump v1.9.1
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-cmp v0.7.0
- github.com/gookit/goutil v0.7.3
- github.com/pires/go-proxyproto v0.11.0
+ github.com/gookit/goutil v0.7.4
+ github.com/pires/go-proxyproto v0.12.0
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78
- golang.org/x/term v0.40.0
+ golang.org/x/term v0.42.0
)
require (
github.com/alecthomas/repr v0.5.2 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
- github.com/charmbracelet/colorprofile v0.4.2 // indirect
- github.com/charmbracelet/x/ansi v0.11.6 // indirect
+ github.com/charmbracelet/colorprofile v0.4.3 // indirect
+ github.com/charmbracelet/x/ansi v0.11.7 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/dlclark/regexp2 v1.11.5 // indirect
+ github.com/dlclark/regexp2 v1.12.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
- github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-runewidth v0.0.20 // indirect
+ github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
+ github.com/mattn/go-isatty v0.0.22 // indirect
+ github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/muesli/termenv v0.16.0 // indirect
- github.com/pelletier/go-toml/v2 v2.2.4 // indirect
+ github.com/pelletier/go-toml/v2 v2.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -54,10 +53,10 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
- golang.org/x/crypto v0.48.0 // indirect
- golang.org/x/sys v0.41.0 // indirect
- golang.org/x/text v0.34.0 // indirect
- golang.org/x/time v0.14.0 // indirect
+ golang.org/x/crypto v0.50.0 // indirect
+ golang.org/x/sys v0.43.0 // indirect
+ golang.org/x/text v0.36.0 // indirect
+ golang.org/x/time v0.15.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index a9c19c7..071bf91 100644
--- a/go.sum
+++ b/go.sum
@@ -4,24 +4,24 @@ github.com/MicahParks/keyfunc/v3 v3.8.0 h1:Hx2dgIjAXGk9slakM6rV9BOeaWDPEXXZ4Us8g
github.com/MicahParks/keyfunc/v3 v3.8.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
-github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
-github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
+github.com/alecthomas/chroma/v2 v2.24.0 h1:zrg+k0tAaVbM8whaT2hR5DOUqAdopsDaH998EGi6Llk=
+github.com/alecthomas/chroma/v2 v2.24.0/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
-github.com/breml/rootcerts v0.3.4 h1:9i7WNl/ctd9OEAOaTfLy//Wrlfxq/tRQ7v4okYFN9Ys=
-github.com/breml/rootcerts v0.3.4/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
+github.com/breml/rootcerts v0.3.5 h1:oi7YiZ25HH52+mrKyjrMkcAFfnRDUf6HO8aUDr7RlJI=
+github.com/breml/rootcerts v0.3.5/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
-github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
-github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
+github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
+github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
-github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
-github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
+github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
+github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
@@ -37,8 +37,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
-github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
+github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -47,14 +47,12 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
-github.com/goforj/godump v1.9.1 h1:9OGpb978Ytz3B59d5Yi2PzRYYLid6UkmhYDIDNiF15Y=
-github.com/goforj/godump v1.9.1/go.mod h1:JsuL6AEZfKIU+iR5ewL6iQ2fIuhvLtPmJDH47M9Ptrc=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/gookit/goutil v0.7.3 h1:nXDd/AB17nEjqVCNDGioDhVL/gVqdlqRMfFergKDjHE=
-github.com/gookit/goutil v0.7.3/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
+github.com/gookit/goutil v0.7.4 h1:OWgUngToNz+bPlX5aP+EMG31DraEU63uvKMwwT3vseM=
+github.com/gookit/goutil v0.7.4/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -63,18 +61,18 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
-github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
-github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
+github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
+github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
+github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
+github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
+github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
-github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
-github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
-github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
-github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
+github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
+github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
+github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -106,19 +104,18 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zU
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
-golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
-golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
+golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
+golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
-golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
-golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
-golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
-golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
-golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
-golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
+golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
+golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
+golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
+golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
+golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/certinfo/certinfo.go b/internal/certinfo/certinfo.go
index 0d4e28f..7e308f0 100644
--- a/internal/certinfo/certinfo.go
+++ b/internal/certinfo/certinfo.go
@@ -120,7 +120,8 @@ func (c *CertinfoConfig) SetCertsFromFile(filePath string, fileReader Reader) er
}
// SetPrivateKeyFromFile loads a private key from the specified PEM file.
-// If the key is encrypted, it will attempt to retrieve the passphrase from an environment variable or interactive prompt.
+// If the key is encrypted, it will attempt to retrieve the passphrase from an environment variable or
+// interactive prompt.
func (c *CertinfoConfig) SetPrivateKeyFromFile(
filePath string,
keyPwEnvVar string,
diff --git a/internal/jwks/jwks.go b/internal/jwks/jwks.go
new file mode 100644
index 0000000..f90832c
--- /dev/null
+++ b/internal/jwks/jwks.go
@@ -0,0 +1,91 @@
+// Package jwks provides functionality for generating JSON Web Key Sets (JWKS) from public keys.
+package jwks
+
+import (
+ "bytes"
+ "context"
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "os"
+
+ "github.com/MicahParks/jwkset"
+)
+
+// GenerateJWKS reads a public key from a file, parses it, and returns its JSON Web Key Set (JWKS) representation.
+// If kid is provided, it sets the Key ID explicitly; otherwise, it computes a SHA-256-derived kid from the public key.
+func GenerateJWKS(ctx context.Context, publicKeyFile string, kid string) (string, error) {
+ keyPEM, err := os.ReadFile(publicKeyFile)
+ if err != nil {
+ return "", fmt.Errorf("unable to read public key from %s: %w", publicKeyFile, err)
+ }
+
+ block, _ := pem.Decode(keyPEM)
+ if block == nil {
+ return "", errors.New("failed to decode PEM block from public key file")
+ }
+
+ key, err := jwkset.LoadX509KeyInfer(block)
+ if err != nil {
+ return "", fmt.Errorf("unsupported or invalid public key format: %w", err)
+ }
+
+ // Ensure the key is a public key
+ switch key.(type) {
+ case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
+ // Valid public key types
+ default:
+ return "", errors.New("the provided file does not contain a supported public key " +
+ "(it might be a private key or an unsupported format)")
+ }
+
+ if kid == "" {
+ pubBytes, err := x509.MarshalPKIXPublicKey(key)
+ if err != nil {
+ return "", fmt.Errorf("failed to marshal public key for SHA-256-derived kid: %w", err)
+ }
+
+ hash := sha256.Sum256(pubBytes)
+ kid = base64.RawURLEncoding.EncodeToString(hash[:])
+ }
+
+ options := jwkset.JWKOptions{
+ Metadata: jwkset.JWKMetadataOptions{
+ KID: kid,
+ },
+ }
+
+ // Create JWK from the parsed public key
+ jwk, err := jwkset.NewJWKFromKey(key, options)
+ if err != nil {
+ return "", fmt.Errorf("failed to create JWK from public key: %w", err)
+ }
+
+ // Initialize in-memory storage for the JWK Set
+ storage := jwkset.NewMemoryStorage()
+
+ err = storage.KeyWrite(ctx, jwk)
+ if err != nil {
+ return "", fmt.Errorf("failed to write key to JWK Set storage: %w", err)
+ }
+
+ jwksBytes, err := storage.JSONPublic(ctx)
+ if err != nil {
+ return "", fmt.Errorf("failed to generate JWKS JSON: %w", err)
+ }
+
+ // Pretty-print the JSON
+ var prettyJWKS bytes.Buffer
+ if err := json.Indent(&prettyJWKS, jwksBytes, "", " "); err != nil {
+ return string(jwksBytes), nil
+ }
+
+ return prettyJWKS.String(), nil
+}
diff --git a/internal/jwks/jwks_test.go b/internal/jwks/jwks_test.go
new file mode 100644
index 0000000..0226e4d
--- /dev/null
+++ b/internal/jwks/jwks_test.go
@@ -0,0 +1,135 @@
+package jwks
+
+import (
+ "context"
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestGenerateJWKS_Success(t *testing.T) {
+ tmpDir := t.TempDir()
+
+ // Helper to create a PEM file
+ createPEM := func(t *testing.T, filename, blockType string, bytes []byte) string {
+ t.Helper()
+
+ path := filepath.Join(tmpDir, filename)
+ block := &pem.Block{
+ Type: blockType,
+ Bytes: bytes,
+ }
+ file, err := os.Create(path)
+ require.NoError(t, err)
+ err = pem.Encode(file, block)
+ require.NoError(t, err)
+ file.Close()
+
+ return path
+ }
+
+ // RSA Setup
+ rsaPriv, err := rsa.GenerateKey(rand.Reader, 2048)
+ require.NoError(t, err)
+ rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPriv.PublicKey)
+ require.NoError(t, err)
+ rsaPubFile := createPEM(t, "rsa_public.pem", "PUBLIC KEY", rsaPubBytes)
+
+ // ECDSA Setup
+ ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ require.NoError(t, err)
+ ecdsaPubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPriv.PublicKey)
+ require.NoError(t, err)
+ ecdsaPubFile := createPEM(t, "ecdsa_public.pem", "PUBLIC KEY", ecdsaPubBytes)
+
+ // Ed25519 Setup
+ edPub, _, err := ed25519.GenerateKey(rand.Reader)
+ require.NoError(t, err)
+ edPubBytes, err := x509.MarshalPKIXPublicKey(edPub)
+ require.NoError(t, err)
+ edPubFile := createPEM(t, "ed25519_public.pem", "PUBLIC KEY", edPubBytes)
+
+ t.Run("RSA", func(t *testing.T) {
+ jwksJSON, err := GenerateJWKS(context.Background(), rsaPubFile, "")
+ require.NoError(t, err)
+ require.Contains(t, jwksJSON, `"kty": "RSA"`)
+ require.Contains(t, jwksJSON, `"kid":`)
+ })
+
+ t.Run("ECDSA", func(t *testing.T) {
+ jwksJSON, err := GenerateJWKS(context.Background(), ecdsaPubFile, "")
+ require.NoError(t, err)
+ require.Contains(t, jwksJSON, `"kty": "EC"`)
+ require.Contains(t, jwksJSON, `"crv": "P-256"`)
+ require.Contains(t, jwksJSON, `"kid":`)
+ })
+
+ t.Run("Ed25519", func(t *testing.T) {
+ jwksJSON, err := GenerateJWKS(context.Background(), edPubFile, "")
+ require.NoError(t, err)
+ require.Contains(t, jwksJSON, `"kty": "OKP"`)
+ require.Contains(t, jwksJSON, `"crv": "Ed25519"`)
+ require.Contains(t, jwksJSON, `"kid":`)
+ })
+
+ t.Run("Explicit KID", func(t *testing.T) {
+ expectedKID := "my-custom-kid"
+ jwksJSON, err := GenerateJWKS(context.Background(), rsaPubFile, expectedKID)
+ require.NoError(t, err)
+ require.Contains(t, jwksJSON, `"kid": "`+expectedKID+`"`)
+ })
+}
+
+func TestGenerateJWKS_Errors(t *testing.T) {
+ tmpDir := t.TempDir()
+
+ t.Run("Invalid file", func(t *testing.T) {
+ _, err := GenerateJWKS(context.Background(), "non-existent-file.pem", "")
+ require.Error(t, err)
+ require.ErrorContains(t, err, "unable to read public key from")
+ })
+
+ t.Run("Invalid PEM", func(t *testing.T) {
+ invalidFile := filepath.Join(tmpDir, "invalid.pem")
+ err := os.WriteFile(invalidFile, []byte("not a pem"), 0644)
+ require.NoError(t, err)
+
+ _, err = GenerateJWKS(context.Background(), invalidFile, "")
+ require.Error(t, err)
+ require.ErrorContains(t, err, "failed to decode PEM block")
+ })
+
+ t.Run("Unsupported block type", func(t *testing.T) {
+ path := filepath.Join(tmpDir, "unsupported.pem")
+ block := &pem.Block{Type: "NOT A KEY", Bytes: []byte("random data")}
+ file, _ := os.Create(path)
+ _ = pem.Encode(file, block)
+ file.Close()
+
+ _, err := GenerateJWKS(context.Background(), path, "")
+ require.Error(t, err)
+ require.ErrorContains(t, err, "unsupported or invalid public key format")
+ })
+
+ t.Run("Private key rejected", func(t *testing.T) {
+ priv, _ := rsa.GenerateKey(rand.Reader, 2048)
+ path := filepath.Join(tmpDir, "private.pem")
+ block := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}
+ file, _ := os.Create(path)
+ _ = pem.Encode(file, block)
+ file.Close()
+
+ _, err := GenerateJWKS(context.Background(), path, "")
+ require.Error(t, err)
+ require.ErrorContains(t, err, "does not contain a supported public key")
+ })
+}
diff --git a/internal/jwtinfo/jwtinfo.go b/internal/jwtinfo/jwtinfo.go
index 0517812..92eb532 100644
--- a/internal/jwtinfo/jwtinfo.go
+++ b/internal/jwtinfo/jwtinfo.go
@@ -49,7 +49,7 @@ type allReader func(io.Reader) ([]byte, error)
// response types.
//
//nolint:revive
-func RequestToken(reqURL string, reqValues map[string]string, client *http.Client, readAll allReader) (JwtTokenData, error) {
+func RequestToken(ctx context.Context, reqURL string, reqValues map[string]string, client *http.Client, readAll allReader) (JwtTokenData, error) {
if reqURL == emptyString {
return JwtTokenData{}, errors.New("empty string provided as request URL")
}
@@ -65,7 +65,8 @@ func RequestToken(reqURL string, reqValues map[string]string, client *http.Clien
urlReqValues.Add(k, v)
}
- req, err := http.NewRequest(
+ req, err := http.NewRequestWithContext(
+ ctx,
"POST",
reqURL,
strings.NewReader(urlReqValues.Encode()),
@@ -177,9 +178,14 @@ func ParseRequestJSONValues(
return nil, fmt.Errorf("unable to parse Json request values: %w", err)
}
- maps.Copy(reqValuesMap, objmap)
+ newMap := maps.Clone(reqValuesMap)
+ if newMap == nil {
+ newMap = make(map[string]string)
+ }
+
+ maps.Copy(newMap, objmap)
- return reqValuesMap, nil
+ return newMap, nil
}
// ReadRequestValuesFile reads request values from a JSON file and merges them
@@ -204,10 +210,18 @@ func ReadRequestValuesFile(
return returnValuesMap, nil
}
-// isValidJSON checks if the provided byte slice contains valid JSON data.
+// isValidJSON checks if the provided byte slice contains valid JSON object data.
func isValidJSON(data []byte) bool {
- var v any
- return json.Unmarshal(data, &v) == nil
+ if !json.Valid(data) {
+ return false
+ }
+
+ trimmed := bytes.TrimSpace(data)
+ if len(trimmed) < 2 {
+ return false
+ }
+
+ return trimmed[0] == '{' && trimmed[len(trimmed)-1] == '}'
}
// DecodeBase64 decodes the base64-encoded header and claims of the access and
@@ -313,14 +327,11 @@ func (jtd *JwtTokenData) ParseUnverified() error {
// ParseWithJWKS parses and verifies the access token against the JSON Web Key Set (JWKS)
// provided at the given URL.
-func (jtd *JwtTokenData) ParseWithJWKS(jwksURL string, keyfuncOverride keyfunc.Override) error {
+func (jtd *JwtTokenData) ParseWithJWKS(ctx context.Context, jwksURL string, keyfuncOverride keyfunc.Override) error {
if jwksURL == emptyString {
return errors.New("emptyString string provided as JWKS url")
}
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
jwks, err := keyfunc.NewDefaultOverrideCtx(
ctx,
[]string{jwksURL},
@@ -395,7 +406,7 @@ func PrintTokenInfo(jtd JwtTokenData, w io.Writer) error {
fmt.Fprintln(w, style.LgSprintf(style.Title2, "%s", token.name))
fmt.Fprintln(w)
- if token.name == "AccessToken" {
+ if token.name == "AccessToken" && jtd.AccessTokenJwt != nil {
fmt.Fprintln(w, style.LgSprintf(style.ItemKey, "Valid %s", validString))
fmt.Fprintln(w)
}
@@ -417,9 +428,9 @@ func PrintTokenInfo(jtd JwtTokenData, w io.Writer) error {
fmt.Fprintln(w)
fmt.Fprintln(w, style.LgSprintf(style.ItemKey, "Claims"))
- tokenTimeClaims, err := unmarshallTokenTimeClaims(token.claims)
+ tokenTimeClaims, err := unmarshalTokenTimeClaims(token.claims)
if err != nil {
- return fmt.Errorf("unable to unmashall time claims from %s: %w", token.name, err)
+ return fmt.Errorf("unable to unmarshal time claims from %s: %w", token.name, err)
}
cTable := table.New().Border(style.LGDefBorder)
@@ -442,15 +453,15 @@ func PrintTokenInfo(jtd JwtTokenData, w io.Writer) error {
return nil
}
-// unmarshallTokenTimeClaims extracts and converts numeric "iat" and "exp" claims
+// unmarshalTokenTimeClaims extracts and converts numeric "iat" and "exp" claims
// from a JSON byte slice into human-readable date strings.
-func unmarshallTokenTimeClaims(claims []byte) (map[string]string, error) {
+func unmarshalTokenTimeClaims(claims []byte) (map[string]string, error) {
tokenClaims := make(map[string]string)
genericClaims := make(map[string]any)
if err := json.Unmarshal(claims, &genericClaims); err != nil {
- return nil, fmt.Errorf("unable to unmarshall claims: %w", err)
+ return nil, fmt.Errorf("unable to unmarshal claims: %w", err)
}
if _, ok := genericClaims["iat"]; !ok {
@@ -470,174 +481,15 @@ func unmarshallTokenTimeClaims(claims []byte) (map[string]string, error) {
}
for k, v := range genericClaims {
- vi := v
-
- if vf, ok := vi.(float64); ok {
- vInt64 := int64(vf)
- t := time.Unix(vInt64, 0)
- dateUTC := t.UTC().Format(time.UnixDate)
- tokenClaims[k] = fmt.Sprintf("%v", dateUTC)
-
- continue
+ if k == "iat" || k == "exp" || k == "nbf" {
+ if vf, ok := v.(float64); ok {
+ vInt64 := int64(vf)
+ t := time.Unix(vInt64, 0)
+ dateUTC := t.UTC().Format(time.UnixDate)
+ tokenClaims[k] = dateUTC
+ }
}
}
return tokenClaims, nil
}
-
-// func unmarshallTokenClaims(claims []byte) (map[string]string, error) {
-// tokenClaims := make(map[string]string)
-//
-// genericClaims := make(map[string]any)
-//
-// if err := json.Unmarshal(claims, &genericClaims); err != nil {
-// return nil, err
-// }
-//
-// for k, v := range genericClaims {
-// var vi any = v
-//
-// if vs, ok := vi.(map[string]any); ok {
-// tokenClaims[k] = fmt.Sprintf("%s", vs)
-// continue
-// }
-//
-// if vf, ok := vi.(float64); ok {
-// vInt64 := int64(vf)
-// t := time.Unix(vInt64, 0)
-// dateUtc := t.UTC().String()
-//
-// outString := fmt.Sprintf("%v (%s)", int64(vf), dateUtc)
-//
-// tokenClaims[k] = fmt.Sprintf("%v", outString)
-//
-// continue
-// }
-//
-// if vls, ok := vi.([]string); ok {
-// tokenClaims[k] = strings.Join(vls, ",")
-// continue
-// }
-//
-// if vla, ok := vi.([]any); ok {
-// tokenClaims[k] = fmt.Sprintf("%v", vla)
-// continue
-// }
-//
-// if vb, ok := vi.(bool); ok {
-// tokenClaims[k] = fmt.Sprintf("%v", vb)
-// continue
-// }
-//
-// if vs, ok := vi.(string); ok {
-// tokenClaims[k] = vs
-// } else {
-// fmt.Printf("not asserted: %v\n", v)
-// }
-// }
-//
-// return tokenClaims, nil
-// }
-//
-// func unmarshallTokenHeader(header []byte) (map[string]string, error) {
-// tokenHeader := make(map[string]string)
-//
-// if err := json.Unmarshal(header, &tokenHeader); err != nil {
-// return nil, err
-// }
-//
-// return tokenHeader, nil
-// }
-//
-// func getTokenClaimsMap(t *jwt.Token) (map[string]string, error) {
-// m := make(map[string]string)
-//
-// // Mandatory Registered Claims
-// issuer, err := t.Claims.GetIssuer()
-// if err != nil || issuer == emptyString {
-// return nil, fmt.Errorf("unable to get issuer: %w", err)
-// }
-//
-// subject, err := t.Claims.GetSubject()
-// if err != nil || subject == emptyString {
-// return nil, fmt.Errorf("unable to get subject: %w", err)
-// }
-//
-// issuedAt, err := t.Claims.GetIssuedAt()
-// if err != nil || issuedAt == nil {
-// return nil, fmt.Errorf("unable to get issuedAt: %w", err)
-// }
-//
-// expiresAt, err := t.Claims.GetExpirationTime()
-// if err != nil || expiresAt == nil {
-// return nil, fmt.Errorf("unable to get expiration time: %w", err)
-// }
-//
-// audienceElems, err := t.Claims.GetAudience()
-// if err != nil {
-// return nil, fmt.Errorf("unable to get audience: %w", err)
-// }
-//
-// audience := strings.Join(audienceElems, ",")
-//
-// m["iss"] = issuer
-// m["sub"] = subject
-// m["iat"] = issuedAt.UTC().String()
-// m["exp"] = expiresAt.UTC().String()
-// m["aud"] = audience
-//
-// // Optional Registered Claims
-// notBefore, err := t.Claims.GetNotBefore()
-// if err != nil {
-// return nil, fmt.Errorf("unable to get notBefore time: %w", err)
-// }
-//
-// if notBefore != nil {
-// m["nbf"] = notBefore.UTC().String()
-// }
-//
-// return m, nil
-// }
-//
-// func getUnregisteredClaimsMap(t *jwt.Token, existingClaims map[string]string) map[string]string {
-// unregistreredClaims := make(map[string]string)
-//
-// var claimsInt any = t.Claims
-//
-// if claimsMap, ok := claimsInt.(jwt.MapClaims); ok {
-// for ck := range claimsMap {
-// if _, alreadyPresent := existingClaims[ck]; alreadyPresent {
-// continue
-// }
-//
-// cki := claimsMap[ck]
-//
-// if cStringValue, ok := cki.(string); ok {
-// unregistreredClaims[ck] = cStringValue
-// }
-//
-// if cIntList, ok := cki.([]any); ok {
-// unregistreredClaims[ck] = fmt.Sprintf("%s", cIntList)
-// }
-// }
-// }
-//
-// return unregistreredClaims
-// }
-//
-// func getTokenHeadersMap(t *jwt.Token) map[string]string {
-// m := make(map[string]string)
-//
-// for k, v := range t.Header {
-// headerValue := "undefined"
-// i := v
-//
-// if v, ok := i.(string); ok {
-// headerValue = v
-// }
-//
-// m[k] = headerValue
-// }
-//
-// return m
-// }
diff --git a/internal/jwtinfo/jwtinfo_test.go b/internal/jwtinfo/jwtinfo_test.go
index 3a964cf..5cfbe0e 100644
--- a/internal/jwtinfo/jwtinfo_test.go
+++ b/internal/jwtinfo/jwtinfo_test.go
@@ -2,6 +2,7 @@ package jwtinfo
import (
"bytes"
+ "context"
"encoding/base64"
"fmt"
"io"
@@ -281,6 +282,7 @@ func TestRequestToken(t *testing.T) {
}
_, err = RequestToken(
+ context.Background(),
serverJwtEndpoint,
reqValues,
client,
@@ -398,6 +400,7 @@ func TestParseWithJWKS(t *testing.T) {
reqValues["scope"] = tt.scope
td, err := RequestToken(
+ context.Background(),
serverJwtEndpoint,
reqValues,
client,
@@ -442,6 +445,7 @@ func TestParseWithJWKS(t *testing.T) {
)
err = td.ParseWithJWKS(
+ context.Background(),
serverJwksEmptyEndpoint,
keyfuncOverrideTesting,
)
@@ -470,6 +474,7 @@ func TestParseWithJWKS(t *testing.T) {
)
err = td.ParseWithJWKS(
+ context.Background(),
serverJwksFaultyEndpoint,
keyfuncOverrideTesting,
)
@@ -483,6 +488,7 @@ func TestParseWithJWKS(t *testing.T) {
}
err = td.ParseWithJWKS(
+ context.Background(),
serverJwksEndpoint,
keyfuncOverrideTesting,
)
@@ -536,6 +542,7 @@ func TestParseWithJWKS_Errors(t *testing.T) {
td := JwtTokenData{AccessTokenRaw: token}
err = td.ParseWithJWKS(
+ context.Background(),
"",
keyfunc.Override{},
)
@@ -555,6 +562,7 @@ func TestParseWithJWKS_Errors(t *testing.T) {
td := JwtTokenData{AccessTokenRaw: token}
err = td.ParseWithJWKS(
+ context.Background(),
"https://localhost:54321/jkws.wrong.json",
keyfunc.Override{},
)
@@ -574,7 +582,8 @@ func TestParseWithJWKS_Errors(t *testing.T) {
td := JwtTokenData{AccessTokenRaw: token}
err = td.ParseWithJWKS(
- "https://loca#$%^/jkws.json",
+ context.Background(),
+ "https://loca#$%^/jwks.json",
keyfunc.Override{},
)
require.ErrorContains(
@@ -688,8 +697,8 @@ func TestDecodeBase64(t *testing.T) {
}
}
-func TestUnmarshallTokenTimeClaims(t *testing.T) {
- t.Run("unmarshallTokenTimeClaims", func(t *testing.T) {
+func TestUnmarshalTokenTimeClaims(t *testing.T) {
+ t.Run("unmarshalTokenTimeClaims", func(t *testing.T) {
t.Parallel()
var jtd JwtTokenData
@@ -722,7 +731,7 @@ func TestUnmarshallTokenTimeClaims(t *testing.T) {
err = jtd.DecodeBase64()
require.NoError(t, err)
- claimsMap, err := unmarshallTokenTimeClaims(
+ claimsMap, err := unmarshalTokenTimeClaims(
jtd.AccessTokenClaims,
)
require.NoError(t, err)
@@ -740,7 +749,7 @@ func TestUnmarshallTokenTimeClaims(t *testing.T) {
})
}
-func TestUnmarshallTokenTimeClaims_MapErrors(t *testing.T) {
+func TestUnmarshalTokenTimeClaims_MapErrors(t *testing.T) {
invalidJSONClaims := "can not unmarshal"
noIatClaims := "{\"exp\":1}"
@@ -757,7 +766,7 @@ func TestUnmarshallTokenTimeClaims_MapErrors(t *testing.T) {
{
name: "invalid JSON",
claims: []byte(invalidJSONClaims),
- errMsg: "unable to unmarshall claims",
+ errMsg: "unable to unmarshal claims",
},
{
name: "missing Issued At",
@@ -787,7 +796,7 @@ func TestUnmarshallTokenTimeClaims_MapErrors(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- _, err := unmarshallTokenTimeClaims(tt.claims)
+ _, err := unmarshalTokenTimeClaims(tt.claims)
require.ErrorContains(t, err, tt.errMsg)
//nolint:revive
})
@@ -801,13 +810,14 @@ func TestUnmarshallTokenTimeClaims_MapErrors(t *testing.T) {
//nolint:revive
func TestPrintTokenInfo(t *testing.T) {
tests := []struct {
- name string
- user string
- pass string
- scope string
- bodyReader allReader
- expError bool
- expReqError bool
+ name string
+ user string
+ pass string
+ scope string
+ bodyReader allReader
+ skipValidation bool
+ expError bool
+ expReqError bool
}{
{
name: "default case",
@@ -816,6 +826,14 @@ func TestPrintTokenInfo(t *testing.T) {
bodyReader: io.ReadAll,
scope: "default",
},
+ {
+ name: "without validation",
+ user: "test",
+ pass: "known",
+ bodyReader: io.ReadAll,
+ scope: "default",
+ skipValidation: true,
+ },
}
for _, tc := range tests {
@@ -850,6 +868,7 @@ func TestPrintTokenInfo(t *testing.T) {
reqValues["scope"] = tt.scope
td, err := RequestToken(
+ context.Background(),
serverJwtEndpoint,
reqValues,
client,
@@ -857,20 +876,23 @@ func TestPrintTokenInfo(t *testing.T) {
)
require.NoError(t, err)
- keyfuncOverrideTesting := keyfunc.Override{
- Client: server.Client(),
- }
+ if !tt.skipValidation {
+ keyfuncOverrideTesting := keyfunc.Override{
+ Client: server.Client(),
+ }
- err = td.ParseWithJWKS(
- serverJwksEndpoint,
- keyfuncOverrideTesting,
- )
- require.NoError(t, err)
- require.True(
- t,
- td.AccessTokenJwt.Valid,
- "JWT token must be valid",
- )
+ err = td.ParseWithJWKS(
+ context.Background(),
+ serverJwksEndpoint,
+ keyfuncOverrideTesting,
+ )
+ require.NoError(t, err)
+ require.True(
+ t,
+ td.AccessTokenJwt.Valid,
+ "JWT token must be valid",
+ )
+ }
err = td.DecodeBase64()
require.NoError(t, err)
@@ -883,7 +905,6 @@ func TestPrintTokenInfo(t *testing.T) {
stringsToCheck := []string{
"JwtInfo",
- "Valid",
"Header",
"Claims",
"alg",
@@ -896,9 +917,17 @@ func TestPrintTokenInfo(t *testing.T) {
"iat",
}
+ if !tt.skipValidation {
+ stringsToCheck = append(stringsToCheck, "Valid")
+ }
+
for _, outStr := range stringsToCheck {
require.Contains(t, got, outStr)
}
+
+ if tt.skipValidation {
+ require.NotContains(t, got, "Valid")
+ }
})
}
}
@@ -939,7 +968,7 @@ func TestPrintTokenInfo_Errors(t *testing.T) {
//nolint:revive
buffer := bytes.Buffer{}
- // Valid claims so unmarshallTokenTimeClaims succeeds.
+ // Valid claims so unmarshalTokenTimeClaims succeeds.
now := time.Now().Unix()
exp := now + 3600
claimsJSON := fmt.Sprintf(`{"iat": %d, "exp": %d}`, now, exp)
@@ -957,7 +986,7 @@ func TestPrintTokenInfo_Errors(t *testing.T) {
require.Positive(t, buffer.Len())
})
- t.Run("unmarshallTokenTimeClaims error", func(t *testing.T) {
+ t.Run("unmarshalTokenTimeClaims error", func(t *testing.T) {
buffer := bytes.Buffer{}
jtd := JwtTokenData{
@@ -967,6 +996,6 @@ func TestPrintTokenInfo_Errors(t *testing.T) {
err := PrintTokenInfo(jtd, &buffer)
require.Error(t, err)
- require.ErrorContains(t, err, "unable to unmashall time claims from AccessToken")
+ require.ErrorContains(t, err, "unable to unmarshal time claims from AccessToken")
})
}
diff --git a/internal/style/style.go b/internal/style/style.go
index 2a5eb13..ce659d1 100644
--- a/internal/style/style.go
+++ b/internal/style/style.go
@@ -8,7 +8,7 @@ import (
var (
glamourDefStyle = "tokyo-night"
- chromaDefStyle = "dracula"
+ chromaDefStyle = "catppuccin-frappe"
// LGDefBorder is the default hidden border for lipgloss tables.
LGDefBorder = lipgloss.HiddenBorder()