この前の記事でGoogleが提供しているREST APIを、サービス・アカウントを作成してアクセスしました。DBMS_CRYPTO.SIGNを使用しているため、Oracle Databaseは19c以上である必要があります。また、RSA鍵の扱いも気をつける必要があります。そのため、もう少し良い方法がないか考えてみました。
OCI VaultにGoogle側のサービス・アカウントの鍵をインポートし、Vaultで署名を行なうことによりJWTが作れないか試してみました。
結論としては、できませんでした。
JWTの署名アルゴリズムがRS256であれば、Vaultでの署名アルゴリズムとしてSHA_256_RSA_PKCS1_V1_5を選択します。
Vaultを使った署名リクエストの定義を確認するとmessageの最大長は4096となっています。
https://docs.oracle.com/en-us/iaas/api/#/en/key/release/datatypes/SignDataDetails
しかし、以下のエラーが返されます。最大長は選択したアルゴリズム(SHA_256_RSA_PKCS1_V1_5を指定しています)に依存しているかもしれませんが、JWTのヘッダーとペイロードを署名するのに、245バイトでは足りません。
{"code":"InvalidParameter","message":"Message cannot be longer than 245 bytes. Message size = 284"}
そのためmessageTypeにDIGESTを指定し、messageとしてSHA-256によるダイジェストの値をBase64でエンコードした文字列を指定する必要があります。
以下の処理でダイジェストの取得とBASE64でのエンコードを実施すればよい、と思ったのですが、処理に誤りがあるようです。
echo -n $MESSAGE | openssl sha256 -binary | base64StackOverflowにCreate SHA256withRSA in two stepsという、同じ状況についての質問があります。この質問の回答と同様に、DigestInfoに含めずに、メッセージ・ダイジェストを直接署名していることが原因のようです。
うまくいっていませんが、行なった作業は記録しておきます。
RSAキーの形式変換
Googleのサービス・アカウントのキーの画面からは、PKCS#12形式でキーをダウンロードしています。
これをPKCS#1のPEM形式に変換します。最初にPKCS#8に変換します。
openssl pkcs12 -in <PKCS#12形式のファイル> -nocerts -nodes -out <出力ファイル>
出力するファイルはpkey-pkcs8.pemを指定しています。Enter Import Passwordとして秘密のパスワードを入力します。
% openssl pkcs12 -in my-first-api-project-******.p12 -nocerts -nodes -out pkey-pkcs8.pem
Enter Import Password: ********
MAC verified OK
%
続いてPKCS#1形式に変換します。
openssl rsa -in <PKCS#8形式のファイル> -out <PKCS#1形式のファイル>
-inには直前に実行したコマンドで出力したファイル名を与えます。今回の例ではpkey-pkcs8.pemです。出力するファイルはprivate.pemとしました。
% openssl rsa -in pkey-pkcs8.pem -out private.pem
writing RSA key
%
ここで作成したprivate.pemをOCI Vaultにインポートします。
非対称キーのインポート
非対称キーをインポートするには、パッチを適用したOpenSSLを作成する必要があります。
パッチの適用方法は、Oracle Cloud Infrastructureドキュメントの非対称キーのインポートとして説明があります。
ドキュメントに書いてある通りの作業を行うためにAlways FreeのVM.Standard.E2.1.Microのインスタンスを作成します。そのインスタンスでOpenSSLへのパッチ適用とキー・マテリアルのラップを行います。
特別に作業ユーザーは作成せず、ユーザーopcを使用して作業します。
ドキュメントには1から10の作業が書かれています。ひとつひとつコピー&ペーストを繰り返かえすことにより、エラーも発生することなく作業を完了できました。
[opc@mylinux ~]$ mkdir $HOME/build
[opc@mylinux ~]$ mkdir -p $HOME/local/ssl
[opc@mylinux ~]$ cd $HOME/build
[opc@mylinux build]$ openssl version
OpenSSL 1.1.1s 1 Nov 2022 (Library: OpenSSL 1.1.1k FIPS 25 Mar 2021)
[opc@mylinux build]$ curl -O https://www.openssl.org/source/openssl-1.1.1d.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 8638k 100 8638k 0 0 36.8M 0 --:--:-- --:--:-- --:--:-- 36.8M
[opc@mylinux build]$ tar -zxf openssl-1.1.1d.tar.gz
[opc@mylinux build]$ sudo yum install patch make gcc -y
Failed to set locale, defaulting to C.UTF-8
Last metadata expiration check: 0:33:42 ago on Mon Jan 16 08:17:45 2023.
Package patch-2.7.6-11.el8.x86_64 is already installed.
Package make-1:4.2.1-11.el8.x86_64 is already installed.
Package gcc-8.5.0-16.0.1.el8_7.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!
[opc@mylinux build]$ cat <<-EOF | patch -d $HOME/build/ -p0
> diff -ur orig/openssl-1.1.1d/apps/enc.c openssl-1.1.1d/apps/enc.c
> --- orig/openssl-1.1.1d/apps/enc.c
> +++ openssl-1.1.1d/apps/enc.c
> @@ -533,6 +533,7 @@
> */
>
> BIO_get_cipher_ctx(benc, &ctx);
> + EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
>
> if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, enc)) {
> BIO_printf(bio_err, "Error setting cipher %s\n",
> EOF
patching file openssl-1.1.1d/apps/enc.c
[opc@mylinux build]$ cd $HOME/build/openssl-1.1.1d/
[opc@mylinux openssl-1.1.1d]$ ./config --prefix=$HOME/local --
[省略]
[opc@mylinux openssl-1.1.1d]$ make -j$(grep -c ^processor /proc/cpuinfo)
[省略]
[opc@mylinux openssl-1.1.1d]$ make install
[省略]
[opc@mylinux openssl-1.1.1d]$ cd $HOME/local/bin/
[opc@mylinux bin]$
[opc@mylinux bin]$ echo -e '#!/bin/bash \nenv LD_LIBRARY_PATH=$HOME/local/lib/ $HOME/local/bin/openssl "$@"' > ./openssl.sh
[opc@mylinux bin]$ chmod 755 ./openssl.sh
[opc@mylinux bin]$ $HOME/local/bin/openssl.sh
OpenSSL> version
OpenSSL 1.1.1d 10 Sep 2019
OpenSSL> exit
[opc@mylinux bin]$
10の作業が終わるとOpenSSLが起動された状態になります。versionを実行して1.1.1dであることを確認し、exitで終了します。
RSAキーのインポート
以下に記載されているスクリプトをkeywrap.shとして、OpenSSLをインストールしたインスタンスに作成します。
ファイルの先頭の指定は、以下のように変更します。
OPENSSL_PATH=$HOME/local/bin/openssl.sh
PRIVATE_KEY=$HOME/private.pem
WRAPPING_KEY=$HOME/public_wrapping_key.pem
すでに作成しているキー・ファイルprivate.pemを/home/opc/private.pemとしてアップロードしておきます。
OCIコンソールのボールトの画面より、リソースのマスター暗号化キーを開きます。暗号エンドポイントはAPIの呼び出し時に指定するため、メモっておきます。
キーの作成をクリックします。
画面右に、キーの作成を行うドロワーが開きます。
保護モードはソフトウェアとします。名前を設定し(ここではBYOK RSA Key)、キーのシェイプ:アルゴリズムとしてRSA、キーのシェイプ:長さに2048ビットを選んでいます。これらの値はインポートするキーに合わせて指定を変えます。
鍵をインポートするため、外部キーのインポートにチェックを入れます。
公開ラッピング・キーにカーソルを当てます。
公開ラッピング・キーが表示されるので、コピーを実行しOpenSSLを準備したインスタンスに/home/opc/public_wrapping_key.pemというファイルとして作成します。
ファイルを作成したのち、keywrap.shを実行します。Binary wrapped key for console is available atとして、インポートできるファイルが作成されます。
[opc@mylinux ~]$ sh keywrap.sh
Openssl Path: /home/opc/local/bin/openssl.sh
Work Dir: /tmp/kms_WehX
Binary wrapped key for console is available at: /tmp/kms_WehX/wrapped_key_material.bin
Base64 encoded wrapped key for CLI or API is available at: /tmp/kms_WehX/wrapped_key_material.base64
[opc@mylinux ~]$
手元にダウンロードします。
% sftp -i mylinux.key opc@***.***.***.***
Connected to ***.***.***.***.
sftp> get /tmp/kms_WehX/wrapped_key_material.bin
Fetching /tmp/kms_WehX/wrapped_key_material.bin to wrapped_key_material.bin
wrapped_key_material.bin 100% 1736 5.0KB/s 00:00
sftp> exit
%
外部キーのデータ・ソースとして、ダウンロードしたwrapped_key_material.binを指定します。
キーの作成を実行します。
少々時間がかかりますが、状態が有効になればキーのインポートは完了です。
電子署名や検証にはマスター暗号化キーのOCIDを使うので、コピーを取っておきます。
OCI Vault側の作業は以上で完了です。
Web資格証明の準備
OCI VaultのREST APIを呼び出すユーザーは、以下の記事で作成したapex_api_agentを使用します。
APEXからOCIオブジェクト・ストレージを操作する(1) - APIユーザーの作成http://apexugj.blogspot.com/2020/02/apex-with-oci-object-storage-1-apiuser.html
Web資格証明OCI_API_ACCESSの作成は以下の手順で実施します。
APEXからOCIオブジェクト・ストレージを操作する(4) - Web資格証明の作成
ボールトを操作するためのポリシーの追加は、以下の記事の手順で実施します。
OCI PL/SQL SDKを使ってOCI Vaultのシークレットを操作するhttp://apexugj.blogspot.com/2022/12/oci-secret-plsql-sdk.html
Allow group APEXObjectManagers to use keys in tenancy
動作確認
{"isSignatureValid":true}