🐷

KeyCloakをつかってTLS Client Auth実装してみた

2024/10/02に公開

KeyCloakを用いたOAuth2.0のクライアント認証方式でTLS Client Authを実装してみた
また、認証に成功したあとのアクセストークンを使ったリソースサーバー側でのアクセストークンの検証方法も一緒に記載しておく。

証明書の作成

TLS Client Authを使用する場合、本来PKI証明書を使用する必要があるが金銭の関係で自作のルート認証局、中間認証局(サーバー用とクライアント用で2つ)作成する。

証明書のマッピングとしては以下のようなイメージで作成する。
証明書マッピング

ルート認証局の証明書作成

  1. ルートキーの作成
> openssl genrsa-out rootCA1.key 2048
  1. 証明書の作成
> openssl req -new -x509 -key rootCA1.key -sha256 -days 3650 -extensions v3_ca -out rootCA1.pem -subj "/C=JP/ST=Tokyo/O=rootCA1/CN=rootCA1"

中間認証局の証明書作成

  1. 中間キーの作成
openssl genrsa -out inCA1.key 2048
  1. 証明書署名要求作成
openssl req -new -key inCA1.key -sha256 -outform PEM -out inCA1.csr -subj "/C=JP/ST=Tokyo/O=inCA1/CN=inCA1"
  1. 証明書作成
openssl x509 -extfile openssl_sign_inca.cnf -req -in inCA1.csr -sha256 -CA rootCA1.pem -CAkey rootCA1.key -CAcreateserial -extensions v3_ca -days 365 -out inCA1.pem

KeyCloakサーバーのSSLサーバー証明書作成

  1. KeyCloakの証明書要求と秘密鍵作成用のコンフィグファイルを作成する
openssl_sign_server.cnf
[ ca ]
default_ca = CA_default

[ CA_default ]
x509_extensions = user_cert
private_key = inCA1.key
new_certs_dir = ./

[ v3_ca ]
basicConstraints = CA:true,pathlen:0
keyUsage = cRLSign,keyCertSign
nsCertType = sslCA,emailCA

[ req ]
default_bits = 2048
default_keyfile = server.key
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca
req_extensions = v3_req

[ req_distinguished_name ]
countryName = JP
countryName_default = JP
countryName_min = 2
countryName_max = 2

stateOrProvinceName = Tokyo
0.organizationName = Keycloak
commonName = localhost

emailAddress = server@example.jp

[ req_attributes ]

[ usr_cert ]
basicConstraints=CA:FALSE
nsComment = "OpenSSL Generated Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier=keyid,issuer:always
subjectAltName = @alt_names

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation,digitalSignature,keyEncipherment
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = localhost![](https://storage.googleapis.com/zenn-user-upload/835b7ba9acf2-20240926.png)
  1. 秘密鍵と証明書要求を作成
openssl req -new -newkey rsa:4096 -nodes -out server.csr -keyout server.key -sha256 -config openssl_sign_server.cnf -subj "/C=JP/ST=Tokyo/O=Keycloak/CN=localhost"
  1. KeyCloakサーバの証明書を作成
    subjectAltNameを追加するためのextファイルの作成
server.ext
subjectAltName = DNS:localhost

証明書をリクエスト

openssl x509 -req -in server.csr -sha256 -CA inCA1.pem -CAkey inCA1.key -CAcreateserial -days 365 -extfile server.ext -out server.pem

クライアント用証明書の作成

  1. クライアントの秘密鍵作成
openssl genrsa -out client.key 2048
  1. クライアント証明書署名要求作成
openssl req -new -key client.key -subj "/C=jp/ST=tokyo/O=Client/CN=localclient/emailAddress=client@example.jp" -out client.csr
  1. クライアント証明書作成
openssl x509 -req -days 365 -in client.csr -CA inCA2.pem -CAkey inCA2.key -CAcreateserial -extfile server.ext -out client.pem
  1. PKCS12形式に変換しWindowsPCへインストール
openssl pkcs12 -export -in client.pem -inkey client.key -certfile inCA2.pem -out client.p12 -passin pass:password01 -passout pass:password01

作成したP12ファイルをダブルクリックすると、証明書のインポートウィザードが起動
現在のユーザー→インポートする証明書を選択→パスワードの入力→証明書をすべて次のストアへ配置するを選択し参照ボタンを押し、信頼っされたルート証明機関を選択→インストールが完了。

Keycloakでの設定と起動

Keycloakを立ち上げる前に証明書を登録しておく

  1. KeyCloakのトラストストアに証明書を登録しておく
    conf/truststores内にクライアント証明書を作成した中間認証局とルート認証局を入れる

    • rootCA2.pem
    • inCA2.pem
  2. Keycloak用のサーバー証明書と秘密鍵を格納する
    conf/内にサーバー証明書と秘密鍵を格納する

    • server.pem
    • server.key
  3. Keycloakのコンフィグファイルに起動時の設定を追記

    conf/keycloak.conf
    https-certificate-file={keycloak用証明書のフルパス/server.pem}
    https-certificate-key-file={keycloak用秘密鍵のフルパス/server.key}
    
    https-port=8443 //ポートの指定
    https-client-auth=required //クライアント証明書の必須化
    https-protocols=TLSv1.3,TLSv1.2 //通信プロトコルの指定
    

Keycloakの起動

  1. 下記のコマンドでkeycloakを起動する

    > bin/kc.bat start-dev
    
  2. ブラウザからhttps://localhost:8443へアクセス

  3. 下記のような画面が出るので証明書を選択

  4. Keycloakにログインできることを確認する

TLS Clinet Authを実装するためのKeycloak側の設定

Keycloakでテスト用のRealmは事前に作成されているものとする。

Clientの作成

  1. Clientsタブを選択し、「Create client」を選択。下記の設定で入力を進めていく。

    • ClientIDを入力して次へ

    • Client authenticationとAuthorizationをオンにして次へ

    • 保存を押してクライアントを作成


  1. 作成されたクライアントを選択して「Credentials」タブから下記を設定

    • Client Authenticator : X509 Certificate
    • Allow regex pattern comparison : ON
    • Subject DN : EMAILADDRESS=client@example.jp, CN=localclient, O=Client, ST=tokyo, C=jp


  1. アクセストークンにクライアント証明書のフィンガープリントをバインドする設定

    「Advanced」タグ内の[Advanced Settings」から下記の設定をONにする

    • OAuth2.0 Mutual TLS Certificate Bound Access Tokens Enabled : ON

アクセストークンの取得

今回はNode.jsからKeyCloakへアクセスしトークンの取得を行う。
下記の構成で実行をした

|
|-certs
|  | client.key
|  | client.pem
|  | rootCA1.pem
|  | inCA1.pem
|
| client.js

  • client.jsのコードを下記のように作成する
const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');
const https = require('https');

const CLIENT_CERT = path.join('certs', 'client.pem');
const CLIENT_KEY = path.join('certs', 'client.key');
const SERVER_CA1 = path.join('certs', 'rootCA1.pem');
const INTERMEDIATE_CA1 = path.join('certs', 'inCA1.pem');

const req_url = '/realms/test/protocol/openid-connect/token';
const clientID = { 作成したクライアント名 };


const params = new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: clientID,
});

const options = {
    host: 'localhost',
    port: 8443,
    path: req_url,
    method: 'POST',
    maxVersion: 'TLSv1.3',
    minVersion: 'TLSv1.2',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': params.toString().length,
    },
    key: fs.readFileSync(CLIENT_KEY),
    cert: fs.readFileSync(CLIENT_CERT),
    // mtls用のトラストストア
    ca: [fs.readFileSync(SERVER_CA1), fs.readFileSync(INTERMEDIATE_CA1)],
    rejectUnauthorized: true,
};

const req = https.request(options, (res) => {
    let data = '';

    res.on('data', (chunk) => {
        data += chunk;
    });

    res.on('end', async () => {
        console.log(data);
    });
});

req.on('error', (e) => {
    console.error(`Request error : `, e);
});

req.write(params.toString());
req.end();

  • nodeからclient.jsを実行して下記のようにコンソールに表示されればOK
    (アクセストークンは念のため省略してます)
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJrcUVBY1A0b0dLSENCcm9VSGE3MUNqM3BRNkdDRmJHOGpyQjlQU0t6c3lBIn0.eyJleHAiOjE3Mjc4MzQ5MzQsImlhdCI6MTcyNzgzNDYzNCwianRpIjoiMDhmZDMyY2YtNjUwNi00ODYwLWJlODMtM2QyYmRiMTk2M2Y5IiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ.............cmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtbG9jYWxjbGllbnQiLCJjbGllbnRBZGRyZXNzIjoiMDowOjA6MDowOjA6MDoxIiwiY2xpZW50X2lkIjoibG9jYWxjbGllbnQifQ.IXK0cdhtDW-7HMYfruR9xu_JIc6-GVwNTkPhtpZ9gu-90vu8NwOGJkMrnBfO_plqI2PBqkkl57iJUydcJIsvlOQLZPpggtRv4B0kIxspQ-dr99lYCrkMt0M1ZDjQSCeeuRPvLGLsbtpP15XUKKUOzYikhep3nCcGck0LVGUzPBbDeecaH-7zyd7B4HWOSlYIMB35JrS7OvSuJ5fQOSyTsiD3OMHvb-pCdD06QZqPSssKeXrmKlDmZZJgPiqD9FtllIq7Sw279HeQCwZwYN_F5m7lmOPo0CU5CQKg89KN2LkdHzbx7uOTZSdk0jZ2XHYR1kbIKbk3pn4LnrTFIEtSTw","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","not-before-policy":0,"scope":"profile email"}
  • access_tokenを下記のサイトに張り付け、内容を確認する
    証明書がバインドされている(cnf['x5t#S256']がある)ことを確認する。

https://jwt.io/

 "cnf": {
    "x5t#S256": "8bws-ywOtS5a97JvQiKgmcFxSNksnN0M4GZuJMi6-Eg"
  },

まとめ

KeyCloakを用いたMTLSの接続とクライアント証明書認証の手順をまとめました。
KeyCloak側で登録するSubject DNがうまくいかず認証が成功しなかったところがありましたが、その他は比較的順調に進んだと思います。

リソースサーバー側でもクライアント証明書を取得する必要があるため、MTLSの設定を行います。
他の認証方式と同様にKeyCloakの公開鍵を取得しアクセストークンの署名の検証を行った後にMTLSで取得したクライアント証明書をアクセストークン内の"x5t#S256"と比較し同じであればリクエストに応えるという流れになるようです。

Discussion