Source code for woob.tools.pkce

# Copyright(C) 2023 Powens
#
# This file is part of woob.
#
# woob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# woob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with woob. If not, see <http://www.gnu.org/licenses/>.

# flake8: compatible

"""Utilities for handling PKCE (RFC 7636)."""

from __future__ import annotations

from base64 import urlsafe_b64encode
from enum import Enum
from hashlib import sha256
from os import urandom
from typing import NamedTuple

__all__ = [
    'PKCEChallengeType',
    'PKCEData',
]


[docs]class PKCEChallengeType(str, Enum): """PKCE challenge type.""" PLAIN = ('PLAIN') """Plaintext challenge.""" S256 = ('S256') """SHA-256 challenge."""
[docs]class PKCEData(NamedTuple): """PKCE data to generate.""" verifier: str """The verifier to transmit at token call.""" challenge: str """The challenge to transmit in the authorization URL.""" method: str """The method to transmit in the authorization URL."""
[docs] @classmethod def build( cls: type[PKCEData], type_: PKCEChallengeType = PKCEChallengeType.S256, ) -> PKCEData: r"""Build random data for OAuth2 PKCE extension. :param type\_: The type of challenge to produce. :return: The PKCE data. """ # While RFC 7636 section 4.1 allows verifiers to use the whole uppercase and # lowercase latin alphabet, some APIs restrict the verifier charset to hex # characters (0-9, a-f), hence the limitation employed here. # # RFC 7636 section 7.1 mandates 256 bits of entropy (32 8-bit bytes), and # section 4.1 mandates between 43 and 128 characters in length; this method # makes us generate 64 characters with enough entropy, we're in the clear! verifier = urandom(32).hex() if type_ == PKCEChallengeType.S256: digest = sha256(verifier.encode('ascii')).digest() challenge = urlsafe_b64encode(digest).rstrip(b'=').decode('ascii') return cls(verifier=verifier, method='S256', challenge=challenge) if type_ == PKCEChallengeType.PLAIN: return cls(verifier=verifier, method='plain', challenge=verifier) raise ValueError(f'Unsupported PKCE challenge type "{type_}"')