mirror of
https://github.com/therootcompany/golib.git
synced 2026-02-09 21:38:05 +00:00
feat: add cmd/cookie-inspect for decoding and verifying a cookie
This commit is contained in:
parent
448ac5f1c2
commit
944674b750
145
cmd/cookie-inspect/main.go
Normal file
145
cmd/cookie-inspect/main.go
Normal file
@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MainConfig struct {
|
||||
name string
|
||||
cookie string
|
||||
secret string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := MainConfig{}
|
||||
|
||||
flag.StringVar(&cfg.secret, "secret", "", "The secret used to sign the cookie (same as in Express)")
|
||||
flag.StringVar(&cfg.name, "name", "", "Optional: cookie name (just for display)")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, `Usage: express-cookie-parse [flags]
|
||||
|
||||
Parses a raw browser Cookie: header string and inspects each cookie.
|
||||
Also verifies Express.js signed cookie (cookie-parser) format.
|
||||
|
||||
Examples:
|
||||
cookie-inspect "session=s:user123.J%%2BsOPk...; lang=en"
|
||||
echo 'session=s:payload.sig; theme=dark' | cookie-inspector --secret "my-secret"
|
||||
|
||||
Flags:
|
||||
`)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) > 0 {
|
||||
cfg.cookie = args[0]
|
||||
}
|
||||
if cfg.cookie == "" {
|
||||
// Try reading from stdin
|
||||
data, err := os.ReadFile("/dev/stdin")
|
||||
if err != nil || len(data) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Error: Provide cookie value via --cookie or pipe to stdin")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.cookie = strings.TrimSpace(string(data))
|
||||
}
|
||||
fmt.Println("Cookies:", cfg.cookie)
|
||||
|
||||
var cookies []*http.Cookie
|
||||
c, err := http.ParseSetCookie(cfg.cookie)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to parse Cookie header: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cookies = append(cookies, c)
|
||||
|
||||
for _, c := range cookies {
|
||||
inspectCookie(c, cfg.secret)
|
||||
}
|
||||
}
|
||||
|
||||
func inspectCookie(c *http.Cookie, secret string) {
|
||||
fmt.Printf("Cookie: %s\n", c.Name)
|
||||
fmt.Printf("%+#v\n\n", c)
|
||||
cookieVal, err := url.QueryUnescape(c.Value)
|
||||
if err != nil {
|
||||
cookieVal = c.Value
|
||||
}
|
||||
fmt.Printf("%s\n\n", c.Value)
|
||||
fmt.Printf("%s\n\n", cookieVal)
|
||||
|
||||
if !strings.HasPrefix(cookieVal, "s:") {
|
||||
fmt.Println(" Not an express cookie (no 's:' prefix)")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(" looks like express.js signed format (s:payload.signature)")
|
||||
payload64, sig64, err := decodeExpress(cookieVal)
|
||||
if err != nil {
|
||||
fmt.Printf(" Decoding express payload failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
data64 := payload64
|
||||
parts := strings.Split(payload64, ".")
|
||||
if len(parts) == 3 {
|
||||
data64 = parts[1]
|
||||
}
|
||||
if data, err := base64.StdEncoding.DecodeString(data64); err != nil {
|
||||
fmt.Printf(" Base64 decode failed: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" Base64 decoded (std): %s\n", string(data))
|
||||
}
|
||||
|
||||
if secret == "" {
|
||||
fmt.Println(" (Verification skipped — provide --secret to check signature)")
|
||||
} else {
|
||||
if err := verifyExpress(payload64, sig64, secret); err != nil {
|
||||
fmt.Printf(" Verification failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf(" Verified payload.\n")
|
||||
}
|
||||
}
|
||||
|
||||
func decodeExpress(signed string) (string, string, error) {
|
||||
withoutPrefix := signed[2:]
|
||||
|
||||
dotIdx := strings.LastIndex(withoutPrefix, ".")
|
||||
if dotIdx == -1 {
|
||||
return "", "", fmt.Errorf("missing '.' separator")
|
||||
}
|
||||
|
||||
payload := withoutPrefix[:dotIdx]
|
||||
sigReceived := withoutPrefix[dotIdx+1:]
|
||||
|
||||
return payload, sigReceived, nil
|
||||
}
|
||||
|
||||
func verifyExpress(expressPayload, expressSig, secret string) error {
|
||||
if secret == "" {
|
||||
return fmt.Errorf("no secret provided")
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write([]byte(expressPayload))
|
||||
expectedSig := mac.Sum(nil)
|
||||
|
||||
expectedB64 := base64.RawURLEncoding.EncodeToString(expectedSig)
|
||||
|
||||
if !hmac.Equal([]byte(expectedB64), []byte(expressSig)) {
|
||||
return fmt.Errorf("signature mismatch")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user