initial working version

This commit is contained in:
Abel Luck 2026-02-26 11:05:16 +01:00
parent db6b90134d
commit d986a0b31a
19 changed files with 1430 additions and 0 deletions

25
internal/pkce/pkce.go Normal file
View file

@ -0,0 +1,25 @@
package pkce
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
)
const verifierLength = 43
// Generate creates a PKCE code verifier and its S256 challenge.
func Generate() (verifier, challenge string, err error) {
// Generate random bytes and encode to URL-safe base64 (no padding)
buf := make([]byte, 32)
if _, err := rand.Read(buf); err != nil {
return "", "", err
}
verifier = base64.RawURLEncoding.EncodeToString(buf)
// Derive challenge: base64url(sha256(verifier))
h := sha256.Sum256([]byte(verifier))
challenge = base64.RawURLEncoding.EncodeToString(h[:])
return verifier, challenge, nil
}

View file

@ -0,0 +1,63 @@
package pkce
import (
"crypto/sha256"
"encoding/base64"
"regexp"
"testing"
)
func TestGenerateVerifierLength(t *testing.T) {
verifier, _, err := Generate()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(verifier) < 43 || len(verifier) > 128 {
t.Errorf("verifier length = %d, want 43-128", len(verifier))
}
}
func TestGenerateVerifierCharacterSet(t *testing.T) {
verifier, _, err := Generate()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// RFC 7636: unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
valid := regexp.MustCompile(`^[A-Za-z0-9\-._~]+$`)
if !valid.MatchString(verifier) {
t.Errorf("verifier contains invalid characters: %q", verifier)
}
}
func TestGenerateChallengeCorrectness(t *testing.T) {
verifier, challenge, err := Generate()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Recompute challenge from verifier
h := sha256.Sum256([]byte(verifier))
expected := base64.RawURLEncoding.EncodeToString(h[:])
if challenge != expected {
t.Errorf("challenge = %q, want %q", challenge, expected)
}
}
func TestGenerateUniqueness(t *testing.T) {
v1, _, err := Generate()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
v2, _, err := Generate()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if v1 == v2 {
t.Error("two Generate() calls produced identical verifiers")
}
}