Application Security scripts

Application Security

The application security scripts and packages I've written, with their source shown directly below โ€” no need to open GitHub to read them.

OAuth PKCE (S256) verifier and generator

GoRead-onlyGo test suite

pkce.go Go ยท 85 lines
appsec/scripts/oauth-pkce/pkce.go
1// Package oauthpkce is a bounded educational S256-only OAuth PKCE verifier.
2// It deliberately never logs verifiers or challenges.
3package oauthpkce
4 
5import (
6	"crypto/rand"
7	"crypto/sha256"
8	"crypto/subtle"
9	"encoding/base64"
10	"errors"
11	"fmt"
12	"math/big"
13	"regexp"
14)
15 
16const (
17	MinVerifierLength = 43
18	MaxVerifierLength = 128
19)
20 
21var verifierPattern = regexp.MustCompile(`^[A-Za-z0-9\-._~]+$`)
22 
23// IsValidCodeVerifier enforces the RFC 7636 length and unreserved-character rules.
24func IsValidCodeVerifier(verifier string) bool {
25	length := len(verifier)
26	return length >= MinVerifierLength &&
27		length <= MaxVerifierLength &&
28		verifierPattern.MatchString(verifier)
29}
30 
31// GenerateRandomVerifier returns an RFC 7636 verifier generated from crypto/rand.
32func GenerateRandomVerifier(length int) (string, error) {
33	if length < MinVerifierLength || length > MaxVerifierLength {
34		return "", fmt.Errorf(
35			"verifier length must be between %d and %d characters",
36			MinVerifierLength,
37			MaxVerifierLength,
38		)
39	}
40 
41	const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
42	alphabetLength := big.NewInt(int64(len(alphabet)))
43	verifier := make([]byte, length)
44	for index := range verifier {
45		randomIndex, err := rand.Int(rand.Reader, alphabetLength)
46		if err != nil {
47			return "", fmt.Errorf("generate verifier entropy: %w", err)
48		}
49		verifier[index] = alphabet[randomIndex.Int64()]
50	}
51	return string(verifier), nil
52}
53 
54// ComputeChallengeS256 derives BASE64URL(SHA-256(ASCII(code_verifier))).
55func ComputeChallengeS256(verifier string) (string, error) {
56	if !IsValidCodeVerifier(verifier) {
57		return "", errors.New("code_verifier does not satisfy RFC 7636 syntax")
58	}
59	sum := sha256.Sum256([]byte(verifier))
60	return base64.RawURLEncoding.EncodeToString(sum[:]), nil
61}
62 
63func isValidS256Challenge(challenge string) bool {
64	decoded, err := base64.RawURLEncoding.Strict().DecodeString(challenge)
65	return err == nil && len(decoded) == sha256.Size
66}
67 
68// ValidatePKCES256 validates one token request against its stored authorization
69// transaction. It rejects method downgrade and malformed inputs before performing a
70// constant-time comparison of equal-length S256 challenges.
71func ValidatePKCES256(codeVerifier, storedChallenge, method string) bool {
72	if method != "S256" || !IsValidCodeVerifier(codeVerifier) ||
73		!isValidS256Challenge(storedChallenge) {
74		return false
75	}
76 
77	computedChallenge, err := ComputeChallengeS256(codeVerifier)
78	if err != nil {
79		return false
80	}
81	return subtle.ConstantTimeCompare(
82		[]byte(computedChallenge),
83		[]byte(storedChallenge),
84	) == 1
85}

What it does

Implements RFC 7636 S256 verifier generation, challenge derivation, and constant-time verification.

Why it exists

The OAuth/OIDC research needed a runnable PKCE reference implementation behind its boundary tests.

Permissions and safety

None. It performs no filesystem, network, or credential access.

Usage

go test ./appsec/scripts/oauth-pkce/...

What was tested

The suite covers successful round trips, wrong verifiers, downgrade attempts, invalid lengths and characters, and malformed challenges.

Limitations

  • It covers PKCE mechanics only, not the complete OAuth authorization flow.
  • It is a package rather than a standalone CLI.