いしぐめも

プログラミングとかしたことを書きます。

Personium の access token をデコードして理解する

この記事では、Personiumの動作の理解を深めるためにアクセストークンについて調査を行った内容を記載しています。本調査を行うことで、Personiumを運用する上での理解を深めていきたいと思います。

なお、本記事の記載内容は、2021年9月現在GitHubで公開されているソースコードから読み解ける内容を記載したものであり、一切の秘密情報を含みません。

トークン?

基本的に、PersoniumはAPIリクエストを行ったユーザーを識別するのにHTTPのAuthorization ヘッダに指定された「トークン(アクセストークン)」を用いています。ユーザーの識別に使用できるということは、アクセストークンは簡単に外部の人が偽造できないような仕組みになっている必要があるのですが、Personiumはどのようにそれを行っているのでしょうか?

今回読むソースコード

Personiumのアクセストークン生成まわりのコードは personium-lib-commonio.personium.common.auth.token あたりに記載されていそうです。

personium-lib-common/src/main/java/io/personium/common/auth/token at develop · personium/personium-lib-common · GitHub

アクセストークンの種類

用語集 · Personium

Personiumのドキュメントによれば、トークンには「セルローカルトークン」 と、「トランスセルトークン」 という2種類が存在します。

「セルローカルトークン」とは、名前の通り、ローカルに使用可能なトークンです。自分のセルに対して、「私はこのセルのアカウントAだ」というのを示すのに使用できるトークンです。

一方、「トランスセルトークン」は、セルをまたいで「隣のセルのものですが・・・」という感じで自分のセル以外に対して自身の身元を証明するために使用するトークンです。

今回の記事ではこの辺の違いをソースコードを読み解いていき、各トークンが示す内容などを明らかにしていければな、と考えています。

セルローカルトーク

まずは「セルローカルトークン」です。セルローカルトークンはResidentLocalAccessToken クラスとして実装されており、文字列としてのトークン化が実装されているのは、継承元のAbstractLocalTokenです。

personium-lib-common/AbstractLocalToken.java at develop · personium/personium-lib-common · GitHub

アクセストークンの生成

では、さっそくアクセストークンを見てみましょう。

String issuer = "https://cell.pds.example.com/";
ResidentLocalAccessToken.setKeyString("hogehogefugafuga");
ResidentLocalAccessToken token = new ResidentLocalAccessToken(1000, 3600, issuer, "me", "schema", new String[] { "scopeA", "scopeB" });
System.out.println(token.toTokenString());

こんな感じでセルローカルトークンを生成してみます。

AR~6LIBaVj_ICPpQvu2lt8M3Q0weZ0DayiQilJNkb4-220DBR4h9S5ky6Smu1op3XwZG62w3s4N--vDLViGFyQZqRgJ9r4m9Bs0rBIlTuZaXjw

それっぽい文字列が出てきました。この文字は以下のような加工をされたBase64の文字列です。

  • AR~ という文字列を先頭に付ける
  • +- に置換する
  • \_に置換する
  • 末尾の = を取り除く

というわけで、これを逆に変換してみます

6LIBaVj/ICPpQvu2lt8M3Q0weZ0DayiQilJNkb4+220DBR4h9S5ky6Smu1op3XwZG62w3s4N++vDLViGFyQZqRgJ9r4m9Bs0rBIlTuZaXjw=

最後の= の数は文字の長さが4の倍数になるように調整してください。これを Base64 でデコードすればトークン情報のバイト列になります。この「トークン情報のバイト列」ですが、 AbstractLocalToken#doCreateTokenString で生成したトークン情報文字列を128bit長の鍵を使用したAES方式による暗号化が施されたものです。

アクセストークンの復号化

では、復号化してみましょう。opensslコマンドに -base64 オプションを付けて、Base64デコードしながら復号化してみます。

echo '6LIBaVj/ICPpQvu2lt8M3Q0weZ0DayiQilJNkb4+220DBR4h9S5ky6Smu1op3XwZG62w3s4N++vDLViGFyQZqRgJ9r4m9Bs0rBIlTuZaXjw=' | openssl aes-128-cbc -d -base64 -K '686F6765686F67656675676166756761' -iv 'ae03f002bb60aa0ce591f214984c6d23'

出力↓

0001    0       3600    me      schema  scopeA scopeB   https://cell.pds.example.com/

と、こんな感じでトークン情報が出てきます。ここで使用した K とか iv とかのパラメータは後述します。

アクセストークンに入っている情報

というわけで、まとめるとこんな感じです。以下の情報がタブ(\t)区切りで入ってます。

# 情報
0 生成日時(エポックミリ秒)
1 種類
2 有効期間(ミリ秒)
3 アカウント名
4 認証クライアント情報( 参照: アプリ認証 · Personium
5 スコープ
6 発行元(セル)

アクセストークンの復号化に必要なパラメータ

openssl コマンドで使用した K とか iv に使用したパラメータですが、それぞれ以下のように作っています。

K(AES 128bit暗号鍵)

printf 'hogehogefugafuga' | hexdump -e '16/1 "%X"'

iv(AES 初期化ベクトル)

printf 'https://cell.pds.example.com/' | md5sum

K の生成に使用したのは hogehogefugafuga という16バイトの文字列です。これはアクセストークン生成時の setKeyString 関数で指定したものですが、Personiumではデフォルトでユニットの設定 io.personium.core.security.secret16 で指定されたものが使用されます。iv は発行元セルのURLをmd5ハッシュ値です。

トランスセルトーク

トランスセルトークンとは、自身のセルでしか使えない(復号化できない)セルローカルトークンに対し、他のセルでも使えるように定められた全く別の種類のトークンです。

https://personium.io/docs/ja/server-operator/unit_operation_design/#personium%E3%81%A8pki

PersoniumでCellをまたがったやり取りに用いられているTrans-Cell AccessTokenはSAML2.0のAssertion形式となっており、Assertionで用いる電子署名としてPKIの代表的な仕様であるx.509を使用したものを採用しています

なるほどわからん

とりえあず、ソースコードを読むとSAML Assertionをbase64エンコードしたものがトランスセルトークンらしいので、base64 -d でデコードしてみることにします。

トランスセルトークンの取得

トランスセルトークンは通常のアクセストークン取得のボディに p_target として宛先セルURLを指定すると発行することができます。

curl https://cell1.pds.example.com/__token -d 'grant_type=password&username=USERNAME&password=PASSWORD&p_target=https://cell2.pds.example.com/' -X POST | jq .access_token --raw-output

上記コマンドを実行して得られるトークンは「持ち主はcell1の○○さん」ということを示す名札のようなものです。文字数は5000文字近くありました。

トランスセルトークンのデコード

base64エンコードされているので、デコードしてみます。 $TRANSCELL_TOKEN は上記コマンドで取得したアクセストークンで読み替えてください。

echo $TRANSCELL_TOKEN | sed -e 's/-/+/g' -e 's/_/\\/g' | base64 -d

このコマンドで実施しているのは 「-を+に置換、_を\に置換」と「base64デコード」の2つの操作です。そして出力されたのがコチラ↓(成形は筆者によるもの)

<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="aa9cfc1f-8a86-4e57-a64c-b3e405de97e7" IssueInstant="2021-10-04T06:21:29.639Z" Version="2.0">
  <Issuer>https://cell1.pds.example.com/</Issuer>
  <Subject>
    <NameID>https://cell1.pds.example.com/#username</NameID>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <SubjectConfirmationData NotOnOrAfter="2021-10-04T07:21:29.639Z" Recipient="https://cell2.pds.example.com/__token" />
    </SubjectConfirmation>
  </Subject>
  <Conditions>
    <AudienceRestriction>
      <Audience>https://cell2.pds.example.com/</Audience>
      <Audience />
    </AudienceRestriction>
  </Conditions>
  <AuthnStatement AuthnInstant="2021-10-04T06:21:29.639Z">
    <AuthnContext>
      <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
    </AuthnContext>
  </AuthnStatement>
  <AttributeStatement>
    <Attribute Name="Roles" NameFormat="urn:x-personium:xmlns">
      <AttributeValue>https://cell1.pds.example.com/__role/__/admin</AttributeValue>
    </Attribute>
    <Attribute Name="Scopes">
      <AttributeValue>root</AttributeValue>
    </Attribute>
  </AttributeStatement>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>DIGEST_VALUE</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>SIGNATURE_VALUE</SignatureValue>
    <KeyInfo>
      <X509Data>
        <X509SubjectName>CN=pds.example.com,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU</X509SubjectName>
        <X509Certificate>X509_CERT</X509Certificate>
      </X509Data>
    </KeyInfo>
  </Signature>
</Assertion>

もろ SAML Assertionですね。セルローカルなアクセストークン(タブ区切り)とは打って変わって、ガッツリXMLが入っていました。

トランスセルトークンの検証

Personiumにおいて、トランスセルトークンに記載されている「cell1セルの○○さん」という情報は、XML署名の手法に基づいて ユニット(pds.example.com)が署名 します。トランスセルトークンの SAML Assertion の KeyInfo には X509の証明書が含まれています。

これを検証することで、このトークンは確かに pds.example.comユニットによって発行されたもの なんだなと受け取った側は知ることができます。

検証プロセスについては後で調べます・・・

終わりに

学習のためにトークンを手動でパースしてみました。トークンに含まれている情報や生成に必要な秘密情報を知っておくことで、Personiumがどのようにデータへのアクセスコントロールを行っているのか、また、どういったパラメータが漏洩するとマズいのか、理解を深める一助になれば幸いです。