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