using System.Buffers.Text;
using System.Security.Cryptography;
using System.Text;
namespace DodoSSH.Client.Auth;
///
/// A PKCE code verifier and its S256 challenge. See RFC 7636.
///
///
///
/// A public client cannot keep a secret, so PKCE is what stops an authorization code being
/// redeemed by anyone who intercepts it — on a loopback redirect that means any other local
/// process that manages to receive the callback. The token endpoint only accepts the code
/// alongside the verifier whose hash it saw at authorization time.
///
///
/// S256 only. The plain method is still in the RFC and offers no protection
/// whatsoever against an attacker who saw the authorization request.
///
///
public sealed class PkcePair
{
/// The challenge method sent to the authorization endpoint.
public const string Method = "S256";
///
/// Entropy behind the verifier. 32 bytes renders as 43 base64url characters, the RFC's
/// minimum length and comfortably beyond guessing.
///
private const int VerifierEntropyBytes = 32;
private PkcePair(string codeVerifier, string codeChallenge)
{
CodeVerifier = codeVerifier;
CodeChallenge = codeChallenge;
}
/// The secret held until the token exchange. Never leaves the process.
public string CodeVerifier { get; }
/// The hash sent with the authorization request.
public string CodeChallenge { get; }
/// Generates a fresh pair.
public static PkcePair Create()
{
// Base64url of random bytes, which satisfies the RFC's unreserved-character set without
// any escaping. Generating characters directly from an alphabet would be one more place to
// get a modulo bias wrong for no benefit.
var verifier = Base64Url.EncodeToString(RandomNumberGenerator.GetBytes(VerifierEntropyBytes));
var challenge = Base64Url.EncodeToString(SHA256.HashData(Encoding.ASCII.GetBytes(verifier)));
return new PkcePair(verifier, challenge);
}
}