【詳解】クライアント証明書 認証を実装しながら理解する – 後編:証明書認証の実装・テスト編(Azure App Serviceを利用)

こんばんは。今週はクライアント認証について勉強をしていたので、学んだことを記事にまとめておこうと思います。こちらは、全2回の後編になります。前編は以下をご参照ください。

今回の記事は、前編でクライアント証明書を作成した後の段階からスタートします。

それではまいります!

クライアント証明書認証を実装する

今回、クライアント証明書認証の実装とテスト用に、Microsoft AzureのAzure App Serviceを利用します。Azure App Serviceを利用すると、Webサーバ(Webアプリ)が数ステップで利用可能になります。

Azure App Serviceを利用したクライアント証明書認証の構成手順は以下にまとめられているので、今回はこちらを参考に進めていきます。

https://docs.microsoft.com/ja-jp/azure/app-service/app-service-web-configure-tls-mutual-auth

Azure App Serviceリソースの作成

最初にAzure App Serviceのリソースを作成します。

ランタイムは何でもよいのですが、馴染みのあるNode.jsにしました。

リソースをデプロイしたら、URLをたたいてアプリが起動していることを確認しておきます。

当然ながらPOSTMANでも200が返る状態です。

Webサーバ側でクライアント証明書を必須とするように構成変更

続けて、クライアント証明書を必須とするよう構成変更を行います。

PaaSを使うと、このあたりがボタン一つでできてしまって便利ですね。

この状態でアプリURLへアクセスすると、証明書の選択画面が現れるようになりました。

キャンセルをすると、403 Client Certificate Requiedエラー画面が表示されました。

POSTMANも。

ただ、この状態だと、何等かのクライアント証明書をセットすれば、リクエストが通ってしまう状態です。クライアント証明書を検証して、特定のデバイス(クライアント証明書)のみからのアクセスを許可するようにするには、アプリコードの実装が必要です。

アプリコードの実装

アプリコードの実装サンプルも公式Docにあるので、こちらに従って実装していきます。

https://docs.microsoft.com/ja-jp/azure/app-service/app-service-web-configure-tls-mutual-auth#nodejs-sample

Node.jsプロジェクトの作成

はじめに、Visual StudioでNode.jsのプロジェクトを作成します。(サンプルコードは、Express + TypeScriptで書かれていそうだったので、合わせておきます)

いったん、このデフォルト構成の状態で、Azureへデプロイして画面を確認しておきます。

node-forgeパッケージのインストール

続いて、今回の処理に必要な追加のパッケージ、node-forgeをインストールしておきます。

アプリケーションコードの実装

Expressについてはあまり詳しくなかったので、デフォルトのコードにサンプルコードを無理やり埋め込む形で実装しました、が動いているのでよしとします。

<index.ts>

・証明書の検証は、本当はサンプルコードのようにIssuerやTimeなど検証項目がたくさんあるのでしょうが、今回は簡単な動作確認ですので、サムプリントの検証だけとしました。(他検証はコメントアウト)

・そして、なんとなくアプリケーションが受け取ったHTTP Headerの中身を見てみたいなと思ったので、認証完了後にはHTTP Header内容を表示させるようにちょっと変更。

/*
 * GET home page.
 */

import { pki, md, asn1 } from 'node-forge';
import express = require('express');

const router = express.Router();

router.get('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
    //res.render('index', { title: 'Express' });

    console.info("Process Start")
    try {
        // Get header
        console.info("Header Validation")

        const header = req.get('X-ARR-ClientCert');

        if (!header) throw new Error('UNAUTHORIZED');

        // Convert from PEM to pki.CERT
        console.info("Convert PEM to PKI.cert")
        const pem = `-----BEGIN CERTIFICATE-----${header}-----END CERTIFICATE-----`;
        const incomingCert: pki.Certificate = pki.certificateFromPem(pem);

        const allowedFingerPrint = "92261dee456250bee8e75c2287070b511d3e5e51";

        // Validate certificate thumbprint
        console.info("Validate Certificate Thumbprint")
        const fingerPrint = md.sha1.create().update(asn1.toDer(pki.certificateToAsn1(incomingCert)).getBytes()).digest().toHex();
        console.info(fingerPrint)

        if (fingerPrint.toLowerCase() !== allowedFingerPrint) throw new Error('UNAUTHORIZED');

        /*
        // Validate time validity
        console.info("Validate Time Validity")
        const currentDate = new Date();
        if (currentDate < incomingCert.validity.notBefore || currentDate > incomingCert.validity.notAfter) throw new Error('UNAUTHORIZED');

        // Validate issuer
        console.info("Validate Issuer")
        if (incomingCert.issuer.hash.toLowerCase() !== fingerPrint) throw new Error('UNAUTHORIZED');

        // Validate subject
        console.info("Validate Subject")
        if (incomingCert.subject.hash.toLowerCase() !== fingerPrint) throw new Error('UNAUTHORIZED');
        */

        res.render('index', { title: JSON.stringify(req.headers) });
        
        next();
    } catch (e) {
        if (e instanceof Error && e.message === 'UNAUTHORIZED') {
            res.status(401).send();
        } else {
            next(e);
        }
    }

}); 

export default router;

<index.pug>

extends layout

block content
  h1= "HTTP HEADER"
  p #{title}

動作確認

それでは、アプリをAzure App Serviceにデプロイして、URLにアクセスしてみます。

前編でインストールしたクライアント証明書を選択すると・・

クライアント認証が通って、トップページが表示されました!

ちなみに、指定のクライアント証明書以外を使うと・・・ちゃんとはじかれました。

また、Headerの内容について、これはAzure App Service(というかIIS?)固有の動きだと思いますが、アプリケーションが受け取った証明書は確かにARR-Client-Certificateヘッダに挿入されていることが分かります。

https://docs.microsoft.com/ja-jp/azure/app-service/app-service-web-configure-tls-mutual-auth#access-client-certificate

App Service では、要求の TLS 終了がフロントエンドのロード バランサー側で行われます。 クライアント証明書を有効にした状態で要求をアプリ コードに転送すると、App Service によって X-ARR-ClientCert 要求ヘッダーにクライアント証明書が挿入されます。 App Service がこのクライアント証明書に対して行うのは、この証明書をアプリに転送する処理だけです。 クライアント証明書の検証はアプリ コードが行います。

公式Docより

Postmanを使ってテストする

最後に、上記をPostmanを使ってテストする方法についても調べてみたので、まとめておきます。

Postmanを利用する場合、Postman自体の”設定 (Settings)”>Certificatesから、証明書利用の設定をしておくことができます。

今回の場合、以下のように、前編で作成した秘密鍵(key)、公開鍵証明書(crt)、クライアント証明書(pfx)と、秘密鍵作成時に設定したパスワードを設定します。

この状態で、GETリクエストを投げると・・・

通りました!

cURLを使ってテストする

次は、cURLを使ってテストする方法です。

cURLのクライアント認証ですが、調べているといくつかの方法が見つかりました。

curl -E ./cert.pem https://example.com

https://qiita.com/libra_lt/items/8102e9b10a17f2f7fb3b

curl --key ~/key.pem --cert ~/cert.pem  https://example.com
curl https://ssl.example.com -E file.p12:password

やり方は一つではないのですね・・・

-Eオプションはこちらに解説があった。

https://www.mit.edu/afs.new/sipb/user/ssen/src/curl-7.11.1/docs/curl.html

(HTTPS) Tells curl to use the specified certificate file when getting a file with HTTPS. The certificate must be in PEM format. If the optional password isn’t specified, it will be queried for on the terminal. Note that this certificate is the private key and the private certificate concatenated!

If this option is used several times, the last one will be used.

ここにも書いていますが、uURLで証明書を指定するときは、pem拡張子じゃないとだめなのですね。

今回は、3番目の、-Eオプションを使って認証する方法を試してみました。無事、結果が取得できました。

curl -E <path to pk12 file> <URL>

本日も最後までご覧いただきありがとうございました。この記事が少しでもお役に立ちましたら、いいねボタンをぽちっていただけると励みになります・・!

つづく

この記事を気に入っていただけたらシェアをお願いします!

コメントを残す

メールアドレスが公開されることはありません。

ABOUT US

Yuu113
初めまして。Yuu113と申します。 兵庫県出身、東京でシステムエンジニアをしております。現在は主にデータ分析、機械学習を活用してビジネスモデリングに取り組んでいます。日々学んだことや経験したことを整理していきたいと思い、ブログを始めました。旅行、カメラ、IT技術、江戸文化が大好きですので、これらについても記事にしていきたいと思っています。