V0.12.x (#215)
* fix #203 NetworkTransport race, with main-isolated variables (#206) Co-authored-by: wti <wti@users.noreply.github.com> * feat/auth: Authentication implementation (#205) * feat: added authentication * feat(auth): improvement to error handling * feat(auth): updated errors and tests in OAuthClientRegistrar * feat(auth): updated ci conformance package version * feat(auth): bumped node version * feat(auth): updated ci conformance step * feat(auth): improvement to Linux support * chore: added docc plugin * chore: added spi yml * chore: added documentation step to ci * chore: increased timeout for test from 5 to 10m --------- Co-authored-by: Paal Øye-Strømme <paal.o.eye@gmail.com> Co-authored-by: wti <wti@users.noreply.github.com> Co-authored-by: yehorsobko <yehorsobko@macpaw.com>
This commit is contained in:
parent
973b46b8f7
commit
6132fd4b5b
1 changed files with 10925 additions and 199 deletions
62
Sources/MCP/Base/Authorization/PKCE.swift
Normal file
62
Sources/MCP/Base/Authorization/PKCE.swift
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import Foundation
|
||||
|
||||
#if canImport(CryptoKit)
|
||||
import CryptoKit
|
||||
#endif
|
||||
|
||||
/// Pure PKCE (RFC 7636) helpers required by the authorization_code flow.
|
||||
public enum PKCE {
|
||||
|
||||
/// Generates a cryptographically random PKCE code verifier.
|
||||
///
|
||||
/// - Parameter length: Number of characters in the verifier. Defaults to 64.
|
||||
/// RFC 7636 requires 43–128 characters.
|
||||
/// - Returns: A URL-safe random string suitable for use as a PKCE code verifier.
|
||||
public static func makeVerifier(length: Int = 64) -> String {
|
||||
let charset = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
|
||||
// 66 characters; 256 % 66 == 52, so reject bytes > 252 to eliminate modulo bias.
|
||||
let limit = UInt8(255 - (255 % charset.count)) // 252
|
||||
var rng = SystemRandomNumberGenerator()
|
||||
var result = ""
|
||||
result.reserveCapacity(length)
|
||||
while result.count < length {
|
||||
let byte = UInt8.random(in: 0...255, using: &rng)
|
||||
if byte <= limit {
|
||||
result.append(charset[Int(byte % UInt8(charset.count))])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// Derives the PKCE S256 code challenge from a verifier.
|
||||
///
|
||||
/// - Parameter verifier: A code verifier produced by ``makeVerifier(length:)``.
|
||||
/// - Returns: The base64url-encoded SHA-256 hash of the verifier.
|
||||
/// - Throws: ``OAuthAuthorizationError/pkceS256Unavailable`` on platforms without CryptoKit.
|
||||
public static func makeChallenge(from verifier: String) throws -> String {
|
||||
#if canImport(CryptoKit)
|
||||
let hash = SHA256.hash(data: Data(verifier.utf8))
|
||||
return Data(hash).base64URLEncodedString()
|
||||
#else
|
||||
throw OAuthAuthorizationError.pkceS256Unavailable
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Verifies that the authorization server metadata advertises S256 PKCE support.
|
||||
///
|
||||
/// - Parameter metadata: Authorization server metadata to inspect.
|
||||
/// - Throws: ``OAuthAuthorizationError/pkceCodeChallengeMethodsMissing`` if
|
||||
/// `code_challenge_methods_supported` is absent or empty, or
|
||||
/// ``OAuthAuthorizationError/pkceS256NotSupported(advertisedMethods:)`` if S256 is not listed.
|
||||
static func checkSupport(in metadata: OAuthAuthorizationServerMetadata) throws {
|
||||
guard let methods = metadata.codeChallengeMethodsSupported, !methods.isEmpty else {
|
||||
throw OAuthAuthorizationError.pkceCodeChallengeMethodsMissing
|
||||
}
|
||||
let supportsS256 = methods.contains {
|
||||
$0.caseInsensitiveCompare(OAuthCodeChallengeMethod.s256) == .orderedSame
|
||||
}
|
||||
guard supportsS256 else {
|
||||
throw OAuthAuthorizationError.pkceS256NotSupported(advertisedMethods: methods)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue