package traefikoidc import ( "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/sha256" "strings" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "time" ) // JWT represents a JSON Web Token type JWT struct { Header map[string]interface{} Claims map[string]interface{} Signature []byte Token string } // parseJWT parses a JWT token string into a JWT struct func parseJWT(tokenString string) (*JWT, error) { parts := strings.Split(tokenString, ".") if len(parts) != 3 { return nil, fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts)) } jwt := &JWT{ Token: tokenString, } // Decode and unmarshal the header headerBytes, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { return nil, fmt.Errorf("invalid JWT format: failed to decode header: %v", err) } if err := json.Unmarshal(headerBytes, &jwt.Header); err != nil { return nil, fmt.Errorf("invalid JWT format: failed to unmarshal header: %v", err) } // Decode and unmarshal the claims claimsBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return nil, fmt.Errorf("invalid JWT format: failed to decode claims: %v", err) } if err := json.Unmarshal(claimsBytes, &jwt.Claims); err != nil { return nil, fmt.Errorf("invalid JWT format: failed to unmarshal claims: %v", err) } // Decode the signature signatureBytes, err := base64.RawURLEncoding.DecodeString(parts[2]) if err != nil { return nil, fmt.Errorf("invalid JWT format: failed to decode signature: %v", err) } jwt.Signature = signatureBytes return jwt, nil } // Verify verifies the standard claims in the JWT func (j *JWT) Verify(issuerURL, clientID string) error { claims := j.Claims iss, ok := claims["iss"].(string) if !ok { return fmt.Errorf("missing 'iss' claim") } if err := verifyIssuer(iss, issuerURL); err != nil { return err } aud, ok := claims["aud"] if !ok { return fmt.Errorf("missing 'aud' claim") } if err := verifyAudience(aud, clientID); err != nil { return err } exp, ok := claims["exp"].(float64) if !ok { return fmt.Errorf("missing or invalid 'exp' claim") } if err := verifyExpiration(exp); err != nil { return err } iat, ok := claims["iat"].(float64) if !ok { return fmt.Errorf("missing or invalid 'iat' claim") } if err := verifyIssuedAt(iat); err != nil { return err } sub, ok := claims["sub"].(string) if !ok || sub == "" { return fmt.Errorf("missing or empty 'sub' claim") } return nil } // verifyAudience verifies the audience claim func verifyAudience(tokenAudience interface{}, expectedAudience string) error { switch aud := tokenAudience.(type) { case string: if aud != expectedAudience { return fmt.Errorf("invalid audience") } case []interface{}: found := false for _, v := range aud { if str, ok := v.(string); ok && str == expectedAudience { found = true break } } if !found { return fmt.Errorf("invalid audience") } default: return fmt.Errorf("invalid 'aud' claim type") } return nil } // verifyIssuer verifies the issuer claim func verifyIssuer(tokenIssuer, expectedIssuer string) error { if tokenIssuer != expectedIssuer { return fmt.Errorf("invalid issuer") } return nil } // verifyExpiration checks if the token has expired func verifyExpiration(expiration float64) error { expirationTime := time.Unix(int64(expiration), 0) if time.Now().After(expirationTime) { return fmt.Errorf("token has expired") } return nil } // verifyIssuedAt checks if the token was issued in the future func verifyIssuedAt(issuedAt float64) error { issuedAtTime := time.Unix(int64(issuedAt), 0) if time.Now().Before(issuedAtTime) { return fmt.Errorf("token used before issued") } return nil } // verifySignature verifies the token signature func verifySignature(tokenString string, publicKeyPEM []byte, alg string) error { parts := strings.Split(tokenString, ".") signedContent := parts[0] + "." + parts[1] signature, err := base64.RawURLEncoding.DecodeString(parts[2]) if err != nil { return fmt.Errorf("failed to decode signature: %w", err) } block, _ := pem.Decode(publicKeyPEM) if block == nil { return fmt.Errorf("failed to parse PEM block containing the public key") } pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return fmt.Errorf("failed to parse public key: %w", err) } h := sha256.New() h.Write([]byte(signedContent)) hashed := h.Sum(nil) switch pubKey := pubKey.(type) { case *rsa.PublicKey: return rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, hashed, signature) case *ecdsa.PublicKey: if !ecdsa.VerifyASN1(pubKey, hashed, signature) { return fmt.Errorf("invalid ECDSA signature") } return nil default: return fmt.Errorf("unsupported public key type: %T", pubKey) } }