[CWT入門その2] CBOR Object Signing and Encryption (COSE)

どーも、ritouです。

## アドカレ

これは[認証認可技術 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/identity) 21日めの記事です。
次回は gebo さんが CTAP のお話を書いてくれるようです。楽しみですね！

## 概要

個人的なモチベーション低下により第1弾からだいぶ期間が空いてしまいましたがこの投稿はCBOR Web Token(CWT)入門3部作の第2弾という位置付けです。

[[CWT入門その1] CBORによるオブジェクトのバイナリ表現](https://qiita.com/ritou/items/f3eafedab038d17e3066) では、CBORの概要を紹介しました。
これは2018年03月の投稿ですが、最近はWebAuthn以外にもCBORが使われているようで時代が追いついてきたな！って感じですね。

今回は、シリアライズにCBORを用いた署名、MAC値計算、暗号化の処理が定義されているCOSEの立ち位置と一部の機能を紹介します。
WebAuthnではCBORによるエンコードと共に、COSEの鍵表現が使われていることをご存知の方もいらっしゃるでしょう。

CBOR vs JSONと言う比較の流れで、今回の比較対象としてはJavascript Object Signing and Encryption(JOSE)となります。JOSEはたくさんのRFCがあることで有名です。
[Javascript Object Signing and Encryption (jose) Documents](https://datatracker.ietf.org/wg/jose/documents/)
この記事では、JOSEとの比較を用いて説明していくため、JOSEに関する知識があるのが望ましいかもしれません。

COSEの仕様は、たった１つです。
[RFC 8152 - CBOR Object Signing and Encryption (COSE)](https://tools.ietf.org/html/rfc8152)
JOSE相当の内容が詰め込まれていることもあり、当然ながら仕様自体は結構なボリュームです。

RFC8152の目次とJOSEの仕様の対応表を無理やり作ってみると

| COSE | 内容　| JOSE |
|---|---|---|
| Signing | 署名付きオブジェクト | JSON Web Signature(JWS) の RSA/ECDSA あたり |
| Encryption | コンテンツを暗号化したオブジェクト | JSON Web Encryption(JWE) |
| Message Authentication Code (MAC) | (MAC値を用いた)署名付きオブジェクト | JWS の HMAC あたり(細かいところは微妙に違う) |
| Algorithms | 使用されるアルゴリズム | JSON Web Algorithm(JWA) |
| Key | 鍵の表現 | JSON Web Key(JWK) |

のようになります。

JOSE の JWS 相当の Signing と MAC が分離されているなど、COSE と JOSE で異なる部分はありますが、大体のイメージはつかめるのではないでしょうか。

この記事では

* Signing, MAC : JSON Web Signature相当のことができそうなやつ

について、

* Structure : どのような構成になっているか
* Algorithm : 対応するアルゴリズム
* Key : 鍵の表現方法

という視点から紹介します(Encryptはしんどいのでやめた)。

## Basic COSE Structure

全てのCOSEのメッセージは、CBORの配列形式となっており、先頭からの3要素は共通です。

1. バイナリ文字列で表現された `protected header` の集合. 暗号処理に関わるパラメータ
2. Map で表現された `unprotected header` の集合
3. 平文(Signing, MAC)もしくは暗号化された(Encryption)メッセージの内容

その後ろの要素については、メッセージの種類(Signing, MAC, Encryption)により異なります。

ちなみに、JOSEでも、JWSとJWEで "." でつなぐ文字列の数が違ったりしますね。

## Message Identification

COSEメッセージの種類を識別する方法が定義されています。

* CBOR tag付きメッセージ
* `application/cose` + `cose-type`

CBOR TagがついていればCBORメッセージ単体で識別できますが、ついていない場合はHTTP Headerの `Content-Type` のように指定することで識別できます。

あとはConstrained Application Protocol(CoAP)というプロトコルでの使われ方も定義されていますが、省略します。

## Signing

COSEでは、２種類の署名付きオブジェクトが定義されています。

* `COSE_Sign` : **１つのコンテンツに対する複数の署名を含む**形式。 JOSE で言う所の `JWS JSON Serialization`。
* `COSE_Sign1` : **１つのコンテンツに対して1つの署名を含む**形式。 JOSE で言う所の `JWS Compact Serialization`

JOSEの現状を見ても、COSEが使われるようになっても、まずは `COSE_Sign1` の方からかなーと思っています。

### Signing Objects

上述の通り、オブジェクトは CBOR の配列となります。
`COSE_Sign` と `COSE_Sign1` を識別するために、異なるCBOR Tagが用意されています。

* `COSE_Sign` : 98
* `COSE_Sign1` : 18

COSEの仕様では、CDDL(Concise data definition language)と言う表現方法を用いてオブジェクトの構成を表現しています。

`COSE_Sign` から見ていきます。

```plaintext
# COSE_Sign 用の CBOR Tag は 98
COSE_Sign_Tagged = #6.98(COSE_Sign)

# ヘッダー、ペイロード、署名のリストで構成される
COSE_Sign = [
  Headers,
  payload : bstr / nil, # payload を分けて送る場合は nil
  signatures : [+ COSE_Signature]
]

# 個々の署名もヘッダー、署名部分から構成される
COSE_Signature =  [
  Headers,
  signature : bstr
]
```

`COSE_Sign1` はシンプルです。

```plaintext
# COSE_Sign1 用の CBOR Tag は 18
COSE_Sign1_Tagged = #6.18(COSE_Sign1)

# ヘッダー、ペイロード、署名から構成される
COSE_Sign1 = [
  Headers,
  payload : bstr / nil,
  signature : bstr
]
```

JSON Web Signature でもよく使われているシリアライズ方式なので、理解しやすいかと思います。
仕様に記載されているサンプルも見てみましょう。

```
# C.2.  Single Signer Examples - C.2.1.  Single ECDSA Signature
18(
  [
    / protected / h'a10126' / {
        \ alg \ 1:-7 \ ECDSA 256 \
      } / ,
    / unprotected / {
      / kid / 4:'11'
    },
    / payload / 'This is the content.',
    / signature / h'8eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4d25a91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b0916e5a4c345cacb36'
  ]
)
```

構成としては

* CBOR Tab : 18
* Headers
  * protected : 署名生成に用いるアルゴリズムを指定. key/value ともに一意に決められた **整数** で表現されている
  * unprotected : kid の値はこちらに含まれる
* paylaod : バイナリデータ
* signature : バイナリデータ

となっています。
実際にどんな値になるかについては、[cbor.me](http://cbor.me/) を用いてエンコードすることで確認できます。

```plaintext

# 注釈を省略したCBOR Object
18(
  [
    h'a10126',
    {4:'11'},
    'This is the content.',
    h'8eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4d25a91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b0916e5a4c345cacb36'
  ]
)

# cbor.me でエンコード
D2                                      # tag(18)
   84                                   # array(4)
      43                                # bytes(3)
         A10126                         # "\xA1\x01&"
      A1                                # map(1)
         04                             # unsigned(4)
         42                             # bytes(2)
            3131                        # "11"
      54                                # bytes(20)
         546869732069732074686520636F6E74656E742E # "This is the content."
      58 40                             # bytes(64)
         8EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB36 # "\x8E\xB3>L\xA3\x1D\x1CFZ\xB0Z\xAC4\xCCk#\xD5\x8F\xEF\\\b1\x06\xC4\xD2Z\x91\xAE\xF0\xB0\x11~*\xF9\xA2\x91\xAA2\xE1J\xB84\xDCV\xED*\"4DT~\x01\xF1\x1D;\t\x16\xE5\xA4\xC3E\xCA\xCB6"

# 注釈を省略したBase16エンコードされた値
D28443A10126A10442313154546869732069732074686520636F6E74656E742E58408EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB36
```

署名の生成方法については後述します。

### Signature Algorithms

JSON Web Algorithm でいう所の `RSXXX`, `ESXXX` 系に対応している部分です。
RFC8152 にて定義されている署名生成向けアルゴリズムは以下の通りです。

| Name   | Value | Hash    | Description      |
|--------|-------|---------|------------------|
| ES256  | -7    | SHA-256 | ECDSA w/ SHA-256 |
| ES384  | -35   | SHA-384 | ECDSA w/ SHA-384 |
| ES512  | -36   | SHA-512 | ECDSA w/ SHA-512 |
| EdDSA  | -8    |         | EdDSA            |

JWSで割と使われている `RSXXX` 系がありません。
[COSE and JOSE Registrations for WebAuthn Algorithms](https://tools.ietf.org/html/draft-ietf-cose-webauthn-algorithms-02) にて以下の4つが追加で定義されています。

| Name   | Value | Hash    | Description                      |
|--------|-------|---------|----------------------------------|
| RS256  | -257  | SHA-256 | RSASSA-PKCS1-v1_5 w/ SHA-256                       |
| RS384  | -258  | SHA-384 | RSASSA-PKCS1-v1_5 w/ SHA-384                       |
| RS512  | -259  | SHA-512 | RSASSA-PKCS1-v1_5 w/ SHA-512                       |
| RS1    | -262  | SHA-1   | RSASSA-PKCS1-v1_5 w/ SHA-1       |

また、[Web Authentication:
An API for accessing Public Key Credentials
Level 1](https://www.w3.org/TR/webauthn/#sctn-cose-alg-reg) ではED２５６、ED512の値が定義されています。

| Name   | Value | Hash    | Description                      |
|--------|-------|---------|----------------------------------|
| ED256  | -260  | SHA-256 | TPM_ECC_BN_P256 curve w/ SHA-256(Recommended: Yes) |
| ED512  | -261  | SHA-512 | ECC_BN_ISOP512 curve(Recommended: Yes)             |

JWSと同等の表現ができそうですね。

### Signing and Verification Process

`COSE_Sign1` 用の署名の生成/検証処理について紹介します。
Base64 URLエンコードした文字列を繋げただけなJWSとは異なり、それなりの正規化が必要です。
署名の生成/検証に必要な構造体は以下のものから構成されます。

1. `context` : 署名のコンテキストを識別する文字列 : `Signature1`
2. `body_protected` : `protected` ヘッダーの中身をCBORエンコードしたバイナリ
3. `external_aad` : アプリケーション固有の追加データ的なやつをCBORエンコードしたバイナリ。CoAPとかで使うらしい。
4. `payload` : payloadをCBORエンコードしたバイナリ

```
Sig_structure = [
  context : "Signature1",
  body_protected : empty_or_serialized_map,
  external_aad : bstr,
  payload : bstr
]
```

説明には `body_protected` も `bstr` っぽく書いてあったのにここでは `empty_or_serialized_map` ですね。なんでだろ...まぁいいか。

とりあえず、署名の生成手順は以下のようになります。

1. `Sig_structure` を作成
2. `Sig_structure` を CBOR エンコードしてバイト列(`ToBeSigned`)を生成する
3. 署名生成アルゴリズムに `K`(鍵) と `alg`(アルゴリズム), `ToBeSigned` を渡して署名生成(バイト列)
4. `COSE_Sign1` オブジェクトの `signature` 部分にその値を配置

署名の検証手順

1. `Sig_structure` オブジェクトを作成し、適切な値を埋める
2. `Sig_structure` をCBOR エンコードしてバイト列を生成する
3. 署名検証アルゴリズムに `K`(鍵) と `alg`(アルゴリズム), `ToBeSigned`, `Signature` を渡して署名検証を行う

`Sig_structure` さえ作ってしまえばこっちのもんだという感じなので、ハマりどころもそこになりそうです。

## Message Authentication Code (MAC)

MAC値を利用する方を紹介します。最もシンプルなもので JWS の `alg=HSXXX` 相当となります。
システム内でバイナリデータの生成と検証を同じ場所で行うユースケースで使えるでしょう。

### MAC Objects

MACの方では、Signingとは別の切り口で２種類のオブジェクトが定義されています。

* `COSE_MAC0` : 使用される鍵が暗黙的に知られて(共有されて)おり、**受信者情報を含まない**形式。
* `COSE_MAC` : **受信者情報を含む**形式。 

例から見ていきましょう。`COSE_MAC0` の例です。

```
   17(
     [
       / protected / h'a1010f' / {
           \ alg \ 1:15 \ AES-CBC-MAC-256//64 \
         } / ,
       / unprotected / {},
       / payload / 'This is the content.',
       / tag / h'726043745027214f'
     ]
   )
```

これは JWS で HSXXX を利用、kidすら使わないパターンと同等と言えます。
構造を見ていきます。

```
# COSE_Mac0 用の CBOR Tag は 17
COSE_Mac0_Tagged = #6.17(COSE_Mac0)

COSE_Mac0 = [
    Headers,
    payload : bstr / nil, # Payloadを分けて送る場合は nil
    tag : bstr, # Mac値が入る
]
```

これだけであれば、tag の計算方法だけわかれば検証もできますね。

受信者情報を含む `COSE_MAC` の方も見ていきましょう。

```
   97(
     [
       / protected / h'a1010f' / {
           \ alg \ 1:15 \ AES-CBC-MAC-256//64 \
         } / ,
       / unprotected / {},
       / payload / 'This is the content.',
       / tag / h'9e1226ba1f81b848',
       / recipients / [
         [
           / protected / h'',
           / unprotected / {
             / alg / 1:-6 / direct /,
             / kid / 4:'our-secret'
           },
           / ciphertext / h''
         ]
       ]
     ]
   )
```

recipients ってところが受信者情報で、COSE_MAC0との違いです。
構成も見ていきましょう。

```
# CBOR Tag
COSE_Mac_Tagged = #6.97(COSE_Mac)

# recipients が追加されている
COSE_Mac = [
   Headers,
   payload : bstr / nil,
   tag : bstr,
   recipients :[+COSE_recipient]
]
```

Signingの２種類の違いに比べるとこっちはあまり変わらない気もしますね。
興味がある方は recipients にどんな値が入るかなどを調べてみてください。

### MAC値の計算方法

署名計算と同じような構造体を用いてMAC値を計算します。

```
MAC_structure = [
    context : "MAC" / "MAC0",
    protected : empty_or_serialized_map,
    external_aad : bstr,
    payload : bstr
]
```

計算手順も同様です。

1. `MAC_structure` を作成
2. `MAC_structure` を CBOR エンコードしてバイト列(`ToBeMaced`)を生成する
3. MAC値の生成アルゴリズムに `K`(鍵) と `alg`(アルゴリズム), `ToBeMaced` を渡して署名生成(バイト列)
4. `COSE_MAC0` / `COSE_MAC` オブジェクトの `tag` 部分にその値を配置

検証もそれなりに似ています。

1. `MAC_structure` オブジェクトを作成し、適切な値を埋める
2. `MAC_structure` をCBOR エンコードしてバイト列を生成する
3. 鍵を取得する
4. MAC値の生成アルゴリズムに `K`(鍵) と `alg`(アルゴリズム), `ToBeMaced` を渡してMac値を生成する
5. tag部分と比較

ここでもキモは `MAC_structure` の作成になりそうです。

### MAC Algorithms

MAC値の計算に利用するアルゴリズムは、HMACとAES-CBC-MACをサポートしています。
ハッシュ関数、tagの長さによって4種類にわかれています。

| Name        | Value | Hash    | tag length | Description |
|-------------|-------|---------|------------|-------------|
| HMAC256/64  | 4     | SHA-256 |         64 | HMAC w/ SHA-256 truncated to 64 bits |
| HMAC256/256 | 5     | SHA-256 |        256 | HMAC w/ SHA-256 |
| HMAC384/384 | 6     | SHA-384 |        384 | HMAC w/ SHA-384 |
| HMAC512/512 | 7     | SHA-512 |        512 | HMAC w/ SHA-512 |

とりあえずHMACがあれば既存のJWS + HSXXX相当のことはできそうです。
AES-MACについても、鍵の長さ、tagの長さによって値がわかれています。

| Name          | Value | Key length | tag length | Description |
|---------------|-------|------------|------------|-------------|
| AES-MAC128/64 | 14    | 128        |         64 | AES-MAC 128-bit key, 64bit tag |
| AES-MAC256/64 | 15    | 256        |         64 | AES-MAC 256-bit key, 64bit tag |
| AES-MAC128/128| 25    | 128        |        128 | AES-MAC 128-bit key, 128bit tag |
| AES-MAC256/128| 26    | 256        |        128 | AES-MAC 256-bit key, 128bit tag |

## Key

COSEにおける鍵の表現方法についても紹介します。
表現方法として、鍵単体を表す `COSE_Key` とそのリストである `COSE_KeySet` があります。

```
   COSE_Key = {
       1 => tstr / int,          ; kty
       ? 2 => bstr,              ; kid
       ? 3 => tstr / int,        ; alg
       ? 4 => [+ (tstr / int) ], ; key_ops
       ? 5 => bstr,              ; Base IV
       * label => values
   }

   COSE_KeySet = [+COSE_Key]
```

* kty : 鍵の種類
  * OKP(1) : 
  * EC2(2) : 
  * Symmetric(4)
* kid : JWSでもおなじみ kid
* alg : ここまでで紹介したアルゴリズムの値
* key_ops : 鍵の用途 `sign`, `verify`, `MAC create`, `MAC verify`など

これらの値を基本として、あとはアルゴリズムによって必要なパラメータを用いて表現されます。

[WebAuthnの仕様](https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples)でも説明があります。

```
{
  1:   2,  ; kty: EC2 key type
  3:  -7,  ; alg: ES256 signature algorithm
 -1:   1,  ; crv: P-256 curve
 -2:   x,  ; x-coordinate as byte string 32 bytes in length
           ; e.g., in hex: 65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d
 -3:   y   ; y-coordinate as byte string 32 bytes in length
           ; e.g., in hex: 1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c
}

↓

A5
   01  02

   03  26

   20  01

   21  58 20   65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d

   22  58 20   1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c
```

最初にWebAuthnの仕様見た時はなんだこの表現って思われたかもしれませんが、これでCOSEの鍵も怖くないですね！！！

## JOSE との違い

> COSE is not a direct copy of the JOSE specification.

とある通り、細かいところで COSE と JOSE には違いがあります。

* JOSE は Sign/Encrypt の2種類だけど COSE は最初から Sign/Encrypt/Mac の3つに分かれている
* CBOR Tagがついてたら一発で構造がわかる
* MAC値を使うやつを署名と分離
* バイナリエンコード(CBOR)を使う
* 暗号化周り(省略)

というあたりの違いは、ここまでの紹介でもなんとなく理解いただけるかと思います。

## 実装、ライブラリ

最後に、実装はあるのかと気にされる方もいらっしゃるでしょう。
Githubのこのリポジトリをご覧ください。

[https://github.com/cose-wg](https://github.com/cose-wg)

色々リポジトリがありますが、標準化仕様と言えばとりあえずJavaでしょう。Javaのライブラリがあればとりあえずヨシ！
これを参考に、私の手元にはElixirのライブラリを作っていた記録がありますが、記憶がありません。どうやら途中で挫折した模様です。

あとは人気があるGoでの実装とかだと、Mozillaのパッケージが引っかかるのも興味深いですね。

https://godoc.org/?q=COSE

年末年始の課題にいかがでしょうか？

## まとめ

JOSEのバイナリ版であるCOSEの中でJSON Web Signature相当の部分について紹介しました。
既存のJWxを置き換えるわけではなく、JWxが適用しにくい「バイナリが飛び交うシステムで構造化されるデータをそこそこ小さめのデータに抑えてやりとりしたい」みたいなところで使えるんじゃないかと思います。

CWT入門などというタイトルでやってきましたが、最後は RFC7519 JSON Web Token (JWT) の CBOR 版である CBOR Web Token (CWT)について取り上げます。

[[CWT入門その3] クレームもコンパクトに表現された CBOR Web Token (RFC8392)](https://qiita.com/ritou/items/d6a6c635ce72b14982f6#_reference-b191e7294b9c35e4de88)

内容は簡単なので大丈夫です。
ではまた！