CryptoKit を使って ECDSA を用いた署名を実装する

ECDSAでJWTによる電子署名を作成することがあったのでメモ :pencil: 

## ECDSAとは
* [楕円曲線暗号 - Wikipedia](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9A%E6%9A%97%E5%8F%B7) の一つ

> 楕円曲線暗号（だえんきょくせんあんごう、Elliptic Curve Cryptography、ECC）とは、 [楕円曲線](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9A) 上の [離散対数問題](https://ja.wikipedia.org/wiki/%E9%9B%A2%E6%95%A3%E5%AF%BE%E6%95%B0%E5%95%8F%E9%A1%8C)  (EC-DLP) の困難性を安全性の根拠とする [暗号](https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7)。

* 暗号化と復号とで異なる2つの鍵を使用し、暗号化の鍵を公開できるようにした公開鍵暗号
* [RSA暗号](https://ja.wikipedia.org/wiki/RSA%E6%9A%97%E5%8F%B7) と比べて、短いデータ長で処理速度も早いが同レベル安全性が実現できるのがメリット

## CryptoKit を使ってやること
CryptoKitを使って以下の手順により署名を作成していきます。

1. 鍵の生成
2. 電子署名の作成

全体のコードは[こちら](https://github.com/yoshimin/ECDSADemo)に上げてあります。

## Key Pairの生成
CryptoKitの [P256.Signing.PrivateKey](https://developer.apple.com/documentation/cryptokit/p256/signing/privatekey) を使えば2行で書けます

```swift
let privateKey = P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey
```

`P256.Signing`はECDSA(P-256)を使用した署名、検証のためのもの
[P256.KeyAgreement](https://developer.apple.com/documentation/cryptokit/p256/keyagreement) という[鍵交換](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9A%E3%83%87%E3%82%A3%E3%83%95%E3%82%A3%E3%83%BC%E3%83%BB%E3%83%98%E3%83%AB%E3%83%9E%E3%83%B3%E9%8D%B5%E5%85%B1%E6%9C%89)に使うためのものも用意されている

## 署名の作成
### JWT Header
Headerに指定できる各パラメータについての説明は [Registered Header Parameter Names](https://tools.ietf.org/html/rfc7515#section-4.1) を参照

```json:例
{ "alg": "ES256", "typ": "JWT" }
```

### JWT Claims
Claimsに指定できる各パラメータについての説明は [Registered Claim Names](https://tools.ietf.org/html/rfc7519#section-4.1) を参照

```json:例
{
  "aud": "my-project",
  "iat": 1509650801,
  "exp": 1509654401
}
```


### JWT signature
ECDSA P-256 の署名の例が [JSON Web Signature (JWS)](https://tools.ietf.org/html/rfc7515#appendix-A.3) に書いてあります
Base64urlエンコードした JWT Header と JWT claims を `.` でつなげたものを署名し、以下の順序で `.` でつないだものがJWTとなります

```
{base64url-encoded header}.{base64url-encoded claim set}.{base64url-encoded signature}
```

署名にはP256.Signing.PrivateKey が持っている [signature(for:)](https://developer.apple.com/documentation/cryptokit/p256/signing/privatekey/3237703-signature) を使います

```swift
let input = "{base64url-encoded header}.{base64url-encoded claim set}"
let data = input.data(using: .utf8)!
let signature = try privateKey.signature(for: data)
```
ここで注意が必要なのが署名のフォーマットです。
ECDSA P-256 SHA-256の署名は、符号なし整数のECポイント（各32オクテットのR, S）で表されます。
 [signature(for:)](https://developer.apple.com/documentation/cryptokit/p256/signing/privatekey/3237703-signature) が返してくる [P256.Signing.ECDSASignature](https://developer.apple.com/documentation/cryptokit/p256/signing/ecdsasignature)  は [derRepresentation](https://developer.apple.com/documentation/cryptokit/p256/signing/ecdsasignature/3237692-derrepresentation) と [rawRepresentation](https://developer.apple.com/documentation/cryptokit/p256/signing/ecdsasignature/3237695-rawrepresentation) の2つをもっていて、それぞれ以下の値が返ってきます。

* `derRepresentation` [ASN.1 DER](https://ja.wikipedia.org/wiki/Abstract_Syntax_Notation_One) でフォーマットされた署名
* `rawRepresentation` 署名のrawデータ

ECDSA P-256 SHA-256の署名では `rawRepresentation` の方を使用します。
先述の通りJWTはbase64urlエンコードした header と claim set と signature をドットでつなぐので

```swift
extension Data {
    func base64urlEncodedString() -> String {
        return base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}
```
```swift
let raw = signature.rawRepresentation
let signedJWT = "\(input).\(raw.base64urlEncodedString())"
```
これでJWTの完成です。

ちなみに、ASN.1 DERフォーマットは以下のような構造になっており、この中からrとsを取り出しつなげたものが `rawRepresentation` に相当します。

`0x30|b1|0x02|b2|r|0x02|b3|s`

* b1: 0x02以降に続くバイト列の長さ
* b2: rのバイト列の長さ
* b3: sのバイト列の長さ
をそれぞれ表す

この仕様に基づいて以下のように `derRepresentation` から raw data に変換してみると `rawRepresentation` と同じ値が得られます。

```swift
let der = signature.derRepresentation
let sequence = der.removeFirst() // 0x30

let b1 = der.removeFirst()

let tag1 = der.removeFirst() // 0x02
let b2 = der.removeFirst()
var r = der.prefix(Int(b2))
der = der.advanced(by: Int(b2))

let tag2 = der.removeFirst() // 0x02
let b3 = der.removeFirst()
var s = der.prefix(Int(b3))

let octetLength = 32
guard r.count <= octetLength + 1, s.count <= octetLength + 1 else {
    throw SignatureError.invalidLength
}
r = (r.count == octetLength + 1) ? r.dropFirst() : r
s = (s.count == octetLength + 1) ? s.dropFirst() : s

return r+s
```

Security Frameworkの [SecKeyCreateSignature](https://developer.apple.com/documentation/security/1643916-seckeycreatesignature) を使う場合、こちらは ASN.1 DERフォーマット で返されるので上記のような変換が必要です。
CryptoKitを使えば `signature.rawRepresentation` だけで取得できるのですごく便利です。

## まとめ
CryptoKitを使うと鍵の生成や署名が1行くらいでできてしまうのでとても簡単に使えて驚きました。
ただし、CryptoKitで生成した鍵は自分で保存する必要があるのでそれを考えると生成はSecurity Frameworkの [SecKeyGeneratePair](https://developer.apple.com/documentation/security/1395339-seckeygeneratepair) をつかってもいい気がしました。
CryptoKitで生成した鍵をキーチェーンに保存する方法はAppleがSampleコードを提供しているのでこれを参考に。
[Storing CryptoKit Keys in the Keychain](https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain)