SwiftでPKCEのcode_challengeを生成する


## PKCE（Proof Key for Code Exchange）とは
### 概要
PKCEは、OAuth2.0の拡張仕様でPublic Clientに対する「認可コード横取り攻撃」を防ぐためのものです。
https://tools.ietf.org/html/rfc7636

[「7. 認可コード横取り攻撃への対抗策 (RFC 7636)」](https://qiita.com/TakahikoKawasaki/items/185d34814eb9f7ac7ef3#7-%E8%AA%8D%E5%8F%AF%E3%82%B3%E3%83%BC%E3%83%89%E6%A8%AA%E5%8F%96%E3%82%8A%E6%94%BB%E6%92%83%E3%81%B8%E3%81%AE%E5%AF%BE%E6%8A%97%E7%AD%96-rfc-7636)
> 認可コードフローにより発行された認可コードをクライアントアプリケーションが受け取る際、悪意のあるアプリケーションがその認可コードを横取りするという攻撃 (authorization code interception attack) があります。この攻撃への対抗策として定められた仕様が RFC 7636 (Proof Key for Code Exchange by OAuth Public Clients) です。

### クライアント側で実装すべきこと
認証リクエストにcode_challenge_methodとcode_challengeを含め、認証後のトークン取得リクエストにcode_verifierを含めます。

[「クライアント側実装でやること」](https://qiita.com/TakahikoKawasaki/items/00f333c72ed96c4da659#%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E5%81%B4%E5%AE%9F%E8%A3%85%E3%81%A7%E3%82%84%E3%82%8B%E3%81%93%E3%81%A8)
> 一つは、認可リクエストを投げる際、43 ～ 128 文字の code_verifier を生成して、使用する code_challenge_method のロジック (plain もしくは S256) で code_challenge を計算し、code_challenge_method と code_challenge をリクエストに含めることです。
（中略）
> もう一つは、トークンリクエストを投げる際、code_verifier をリクエストに含めることです。

## code_challengeの実装手順
1. code_verifierの生成
2. code_challengeの生成  
	2-1. SHA256ダイジェストの生成  
	2-2. BASE64URLエンコード  

### 1. code_verifierの生成
128文字のランダムな英数字をcode_verifierとして生成する。

```swift
let codeVerifier = String.randomAlphaNumeric()
```

```swift
extension String {
	static func randomAlphaNumeric(_ length: Int = 128) -> String {
        let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        var randomString: String = ""

        for _ in 0..<length {
            let randomValue = arc4random_uniform(UInt32(base.count))
            let index = base.index(base.startIndex, offsetBy: Int(randomValue))
            randomString += "\(base[index])"
        }
        return randomString
	}
}
```

### 2. code_challengeの生成

```swift
let codeChallenge = codeVerifier.sha256().base64URLEncodedString()
```

#### 2-1. SHA256ダイジェストの生成
SHA256でcode_verifierのハッシュを生成する。

```Bridging-Header.h
#import <CommonCrypto/CommonHMAC.h>
```

```swift
import Foundation

extension String {
	func sha256() -> Data {
        guard let data = self.data(using: .utf8) else {
            fatalError("data initialize is failed")
        }
        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        data.withUnsafeBytes {
            _ = CC_SHA256($0, CC_LONG(data.count), &hash)
        }
        return Data(bytes: hash)
    }
}
```

#### 2-2.BASE64URLエンコード
Dataクラスにbase64EncodedString()はあるが、+と/をURLに使っても問題ないように変換したURL拡張版がないので、自前で置換処理する。

```swift
import Foundation

extension Data {
    func base64URLEncodedString() -> String {
        let base64 = base64EncodedString()
        let base64url = String(base64
            .dropLast()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_"))
        return base64url
    }
}
```

##### ※ SHA256ダイジェスト生成処理を自作した場合のロジックの確認方法
code_verifierが```dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk```のときに code_challengeの値が```E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM```になるかどうかを確認する。
https://tools.ietf.org/html/rfc7636#appendix-B

```swift
let codeVerifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
let codeChallenge = codeVerifier.sha256().base64URLEncodedString()
assert(codeChallenge == "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
```

## 参考
https://qiita.com/TakahikoKawasaki/items/185d34814eb9f7ac7ef3
https://qiita.com/TakahikoKawasaki/items/00f333c72ed96c4da659
https://qiita.com/SAM-l/items/9574d1e237228c718cd6
https://stackoverflow.com/questions/26845307/generate-random-alphanumeric-string-in-swift/33860834
https://stackoverflow.com/questions/25388747/sha256-in-swift
https://stackoverflow.com/questions/43499651/decode-base64url-to-base64-swift