-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
196 lines (170 loc) · 5.6 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package main
import (
"bufio"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/MicahParks/keyfunc/v3"
"github.com/fatih/color"
"github.com/golang-jwt/jwt/v5"
"github.com/spf13/cobra"
)
// Function to create an HTTP client with an optional custom root certificate from an environment variable
func createHTTPClientWithCustomCert() (*http.Client, error) {
// Start with the system's certificate pool
certPool, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("failed to load system cert pool: %v", err)
}
// Check for the environment variable containing the custom root certificate path
certFile := os.Getenv("CUSTOM_ROOT_CERT")
if certFile != "" {
// Read the custom root certificate
rootCert, err := os.ReadFile(certFile)
if err != nil {
return nil, fmt.Errorf("failed to read root certificate file: %v", err)
}
// Append the custom root certificate to the certificate pool
if ok := certPool.AppendCertsFromPEM(rootCert); !ok {
return nil, fmt.Errorf("failed to append custom root certificate")
}
}
// Create a custom TLS configuration using the updated certificate pool
tlsConfig := &tls.Config{
RootCAs: certPool,
}
// Create an HTTP transport that uses this TLS configuration
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
// Return an HTTP client using this custom transport
return &http.Client{
Transport: transport,
}, nil
}
// Function to parse and validate JWT
func parseAndValidateJWT(tokenStr string, jwksURL string) (*jwt.Token, jwt.MapClaims, error) {
var token *jwt.Token
var err error
if jwksURL != "" {
// Create an HTTP client with a custom root certificate if provided
// Create the HTTP client, potentially including a custom root certificate
client, err := createHTTPClientWithCustomCert()
if err != nil {
return nil, nil, fmt.Errorf("failed to create HTTP client: %v", err)
}
// Load the JWKS from the provided URL using net/http and encoding/json
resp, err := client.Get(jwksURL)
if err != nil {
return nil, nil, fmt.Errorf("failed to get JWKS: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("failed to get JWKS: received status code %d", resp.StatusCode)
}
// Read the response body into a byte slice
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("failed to read JWKS response body: %v", err)
}
// Create the keyfunc from the JWKS
jwksKeyfunc, err := keyfunc.NewJWKSetJSON(json.RawMessage(body))
if err != nil {
return nil, nil, fmt.Errorf("failed to create keyfunc: %v", err)
}
// Parse and validate the JWT using the JWKS key function
token, err = jwt.Parse(tokenStr, jwt.Keyfunc(jwksKeyfunc.Keyfunc))
if err != nil {
return nil, nil, fmt.Errorf("failed to parse JWT with validation: %v", err)
}
} else {
// Parse the JWT without validating the signature using ParseUnverified
// Create a Parser object with the default parser configuration
defaultParser := jwt.Parser{}
// Parse the token without validating the signature
token, _, err = defaultParser.ParseUnverified(tokenStr, jwt.MapClaims{})
if err != nil {
return nil, nil, fmt.Errorf("failed to parse JWT without validation: %v", err)
}
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, nil, fmt.Errorf("invalid token claims")
}
return token, claims, nil
}
// Helper function to print time information
func printTimeInfo(title string, timestamp interface{}) {
validColor := color.New(color.FgGreen)
expiredColor := color.New(color.FgRed)
pendingColor := color.New(color.FgYellow)
now := time.Now()
if float64Time, ok := timestamp.(float64); ok {
tm := time.Unix(int64(float64Time), 0)
durationSince := now.Sub(tm)
durationUntil := tm.Sub(now)
if tm.Before(now) {
if title == "Expiration" && durationSince > 0 {
// Expired token
expiredColor.Printf("❌ %s: %v (expired %v ago)\n", title, tm, durationSince)
} else {
// Valid date in the past
validColor.Printf("✅ %s: %v (since: %v)\n", title, tm, durationSince)
}
} else {
// Future date, not yet valid
pendingColor.Printf("⏳ %s: %v (in %v)\n", title, tm, durationUntil)
}
} else {
expiredColor.Printf("❌ %s: invalid date\n", title)
}
}
func main() {
var jwksURL string
var rootCmd = &cobra.Command{
Use: "jwt-examine",
Short: "Decode and validate a JWT",
Run: func(cmd *cobra.Command, args []string) {
reader := bufio.NewReader(os.Stdin)
fmt.Println("Paste your JWT, then press Enter:")
tokenStr, _ := reader.ReadString('\n')
tokenStr = tokenStr[:len(tokenStr)-1] // Remove newline character
token, claims, err := parseAndValidateJWT(tokenStr, jwksURL)
if err != nil {
color.Red("Error: %v", err)
return
}
// Print the decoded JWT claims
prettyClaims, _ := json.MarshalIndent(claims, "", " ")
color.Cyan("🔓 Decoded JWT Claims:\n%s", string(prettyClaims))
// Print important time-related claims
if exp, ok := claims["exp"]; ok {
printTimeInfo("Expiration", exp)
}
if iat, ok := claims["iat"]; ok {
printTimeInfo("Issued At", iat)
}
if nbf, ok := claims["nbf"]; ok {
printTimeInfo("Not Before", nbf)
}
// Print validation result if JWKS URL is provided
if jwksURL != "" {
if token.Valid {
color.Green("✅ JWT signature is valid!")
} else {
color.Red("❌ JWT signature is invalid!")
}
}
},
}
rootCmd.Flags().StringVarP(&jwksURL, "jwks-url", "j", "", "URL of the JWKS for signature validation")
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}