LoginSignup
2
3

More than 5 years have passed since last update.

SwiftでPKCEのcode_challengeを生成する

Last updated at Posted at 2018-10-28

PKCE(Proof Key for Code Exchange)とは

概要

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

「7. 認可コード横取り攻撃への対抗策 (RFC 7636)」

認可コードフローにより発行された認可コードをクライアントアプリケーションが受け取る際、悪意のあるアプリケーションがその認可コードを横取りするという攻撃 (authorization code interception attack) があります。この攻撃への対抗策として定められた仕様が RFC 7636 (Proof Key for Code Exchange by OAuth Public Clients) です。

クライアント側で実装すべきこと

認証リクエストにcode_challenge_methodとcode_challengeを含め、認証後のトークン取得リクエストにcode_verifierを含めます。

「クライアント側実装でやること」

一つは、認可リクエストを投げる際、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として生成する。

let codeVerifier = String.randomAlphaNumeric()
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の生成

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

2-1. SHA256ダイジェストの生成

SHA256でcode_verifierのハッシュを生成する。

Bridging-Header.h
#import <CommonCrypto/CommonHMAC.h>
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拡張版がないので、自前で置換処理する。

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

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

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3