どーも、ritouです。
アドカレ
これは認証認可技術 Advent Calendar 2019 21日めの記事です。
次回は gebo さんが CTAP のお話を書いてくれるようです。楽しみですね!
概要
個人的なモチベーション低下により第1弾からだいぶ期間が空いてしまいましたがこの投稿はCBOR Web Token(CWT)入門3部作の第2弾という位置付けです。
[CWT入門その1] CBORによるオブジェクトのバイナリ表現 では、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
この記事では、JOSEとの比較を用いて説明していくため、JOSEに関する知識があるのが望ましいかもしれません。
COSEの仕様は、たった1つです。
RFC 8152 - CBOR Object Signing and Encryption (COSE)
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要素は共通です。
- バイナリ文字列で表現された
protected header
の集合. 暗号処理に関わるパラメータ - Map で表現された
unprotected header
の集合 - 平文(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では、2種類の署名付きオブジェクトが定義されています。
-
COSE_Sign
: 1つのコンテンツに対する複数の署名を含む形式。 JOSE で言う所のJWS JSON Serialization
。 -
COSE_Sign1
: 1つのコンテンツに対して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
から見ていきます。
# 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
はシンプルです。
# 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 を用いてエンコードすることで確認できます。
# 注釈を省略した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 にて以下の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 ではED256、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とは異なり、それなりの正規化が必要です。
署名の生成/検証に必要な構造体は以下のものから構成されます。
-
context
: 署名のコンテキストを識別する文字列 :Signature1
-
body_protected
:protected
ヘッダーの中身をCBORエンコードしたバイナリ -
external_aad
: アプリケーション固有の追加データ的なやつをCBORエンコードしたバイナリ。CoAPとかで使うらしい。 -
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
ですね。なんでだろ...まぁいいか。
とりあえず、署名の生成手順は以下のようになります。
-
Sig_structure
を作成 -
Sig_structure
を CBOR エンコードしてバイト列(ToBeSigned
)を生成する - 署名生成アルゴリズムに
K
(鍵) とalg
(アルゴリズム),ToBeSigned
を渡して署名生成(バイト列) -
COSE_Sign1
オブジェクトのsignature
部分にその値を配置
署名の検証手順
-
Sig_structure
オブジェクトを作成し、適切な値を埋める -
Sig_structure
をCBOR エンコードしてバイト列を生成する - 署名検証アルゴリズムに
K
(鍵) とalg
(アルゴリズム),ToBeSigned
,Signature
を渡して署名検証を行う
Sig_structure
さえ作ってしまえばこっちのもんだという感じなので、ハマりどころもそこになりそうです。
Message Authentication Code (MAC)
MAC値を利用する方を紹介します。最もシンプルなもので JWS の alg=HSXXX
相当となります。
システム内でバイナリデータの生成と検証を同じ場所で行うユースケースで使えるでしょう。
MAC Objects
MACの方では、Signingとは別の切り口で2種類のオブジェクトが定義されています。
-
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の2種類の違いに比べるとこっちはあまり変わらない気もしますね。
興味がある方は recipients にどんな値が入るかなどを調べてみてください。
MAC値の計算方法
署名計算と同じような構造体を用いてMAC値を計算します。
MAC_structure = [
context : "MAC" / "MAC0",
protected : empty_or_serialized_map,
external_aad : bstr,
payload : bstr
]
計算手順も同様です。
-
MAC_structure
を作成 -
MAC_structure
を CBOR エンコードしてバイト列(ToBeMaced
)を生成する - MAC値の生成アルゴリズムに
K
(鍵) とalg
(アルゴリズム),ToBeMaced
を渡して署名生成(バイト列) -
COSE_MAC0
/COSE_MAC
オブジェクトのtag
部分にその値を配置
検証もそれなりに似ています。
-
MAC_structure
オブジェクトを作成し、適切な値を埋める -
MAC_structure
をCBOR エンコードしてバイト列を生成する - 鍵を取得する
- MAC値の生成アルゴリズムに
K
(鍵) とalg
(アルゴリズム),ToBeMaced
を渡してMac値を生成する - 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の仕様でも説明があります。
{
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のこのリポジトリをご覧ください。
色々リポジトリがありますが、標準化仕様と言えばとりあえずJavaでしょう。Javaのライブラリがあればとりあえずヨシ!
これを参考に、私の手元にはElixirのライブラリを作っていた記録がありますが、記憶がありません。どうやら途中で挫折した模様です。
あとは人気があるGoでの実装とかだと、Mozillaのパッケージが引っかかるのも興味深いですね。
年末年始の課題にいかがでしょうか?
まとめ
JOSEのバイナリ版であるCOSEの中でJSON Web Signature相当の部分について紹介しました。
既存のJWxを置き換えるわけではなく、JWxが適用しにくい「バイナリが飛び交うシステムで構造化されるデータをそこそこ小さめのデータに抑えてやりとりしたい」みたいなところで使えるんじゃないかと思います。
CWT入門などというタイトルでやってきましたが、最後は RFC7519 JSON Web Token (JWT) の CBOR 版である CBOR Web Token (CWT)について取り上げます。
[CWT入門その3] クレームもコンパクトに表現された CBOR Web Token (RFC8392)
内容は簡単なので大丈夫です。
ではまた!