OCI Vault / Key Management で作成した秘密鍵をエクスポートする

# はじめに
OCI(Oracle Cloud Infrastructure)の鍵管理を行うサービスであるVaultで作成した秘密鍵をエクスポートする手順です。ソフトウェア保管の鍵のみになります。HSMに保管した鍵はエクスポートできません。

:::note info
[OCIドキュメント](https://docs.oracle.com/en-us/iaas/Content/KeyManagement/Tasks/exportingkeys_topic-To_export_a_key_by_applying_rsa_oaep_without_a_temporary_aes_key.htm)の手順をそのまま実行してもできないです。
引っかかるポイントは以下2点。

1. OS内のOpenSSL利用するのではなく、最新版をDLし、バイナリレベルでのパッチ適用が必要
    - インポート側の手順[こちら](https://docs.oracle.com/en-us/iaas/Content/KeyManagement/Tasks/to_configure_and_patch_openssl.htm)には書いてるけどわかりにくい
1. エクスポートされた鍵ファイルはバイナリ形式になっているのでそのままでは使うことができない（テキストのPEM型式に変換しないといけない）
:::

----

# １．OpenSSLの準備

:::note info
鍵をエクスポートするためには OpenSSL -id-aes256-wrap-pad 暗号が必要ですが、ではデフォルトで有効になっていません。 また、RSA_AES_KEY_WRAPに必要なエンベロープ ラッピングを許可するように OpenSSL にパッチを適用する必要があります。
:::

- たとえばYumリポジトリから最新版にアップデートしただけの以下ではスクリプトがエラーになり、エクスポートできなかった
```bash
[opc@kms_export]$ openssl
OpenSSL> version
OpenSSL 1.1.1k  FIPS 25 Mar 2021
```

## OpenSSL1.1.1の最新版をダウンロード
 From：https://www.openssl.org/source/old/1.1.1/index.html

```bash:OSでの操作状況
#作業用ディレクトリ作成
mkdir kms_export
cd kms_export
mkdir build
cd build

#ダウンロード
wget https://www.openssl.org/source/old/1.1.1/openssl-1.1.1w.tar.gz

#解凍
tar -zxf https://www.openssl.org/source/old/1.1.1/openssl-1.1.1w.tar.gz

#コンパイル用にインストール
sudo yum install patch make gcc -y
```

## enc.c バイナリへのパッチ適用
ダウンロードしたOpenSSLに対してバイナリレベルでパッチ適用します。
以下の4行のみを実環境に合わせて変更します。
特にOpenSSLのバージョン記号など。
- cat <<-EOF | patch -d /home/opc/work/kms_export/build -p0
- diff -ur orig/openssl-1.1.1w/apps/enc.c openssl-1.1.1w/apps/enc.c
- --- orig/openssl-1.1.1w/apps/enc.c      
- +++ openssl-1.1.1w/apps/enc.c

```bash:OS内での実行例：以下を編集後、以下すべてそのままコピーしてOS内で実行する
cat <<-EOF | patch -d /home/opc/work/kms_export/build -p0
diff -ur orig/openssl-1.1.1w/apps/enc.c openssl-1.1.1w/apps/enc.c
--- orig/openssl-1.1.1w/apps/enc.c      
+++ openssl-1.1.1w/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
```


## コンパイルしてインストール

```bash:OS内での操作
# インストール先を作成
mkdir $HOME/build
mkdir -p $HOME/local/ssl

# ダウンロードしたOpenSSLディレクトリ名でConfigコマンドを実行しています。環境に合わせて変更します。
cd /home/opc/work/kms_export/build/openssl-1.1.1w/
./config --prefix=$HOME/local --openssldir=$HOME/local/ssl
make -j$(grep -c ^processor /proc/cpuinfo)

# 一回プロンプトが止まるので以下を実行してインストール
make install

#インストール後の確認
[opc@openssl-1.1.1w]$ ls $HOME/local/ssl
certs  ct_log_list.cnf  ct_log_list.cnf.dist  misc  openssl.cnf  openssl.cnf.dist  private
```

## openssl.shを作成
関連するライブラリが OpenSSL で使用できるように環境変数LD_LIBRARY_PATHに、$HOME/local/ssl/lib/を設定するOpenssl用のシェルを作成

```shell: $HOME/local/bin/openssl.sh
echo -e '#!/bin/bash \nenv LD_LIBRARY_PATH=$HOME/local/lib/ $HOME/local/bin/openssl "$@"' > ./openssl.sh
```

動作確認

:::note info
ここで作るopenssl.sh を 手順「2.鍵のエクスポート」 で利用するシェルスクリプトで指定することになります。
:::

```shell:OS内での操作
[opc@openssl-1.1.1w]$ . $HOME/local/bin/openssl.sh
OpenSSL>  version
OpenSSL 1.1.1w  11 Sep 2023
```

# 2.鍵をエクスポート

- 説明
    - 次のサンプルスクリプトは、最適非対称暗号化パディング (OAEP) と呼ばれるメカニズムを使用して、ソフトウェアで保護されたマスター暗号化キーを変換します。
    - スクリプトは、ソフトウェアで保護されたマスター暗号化キーを、提供された公開 RSA ラッピング キーでラップし、その後、秘密 RSA ラッピング キーでラップを解除してエクスポートします。
    - ラップされたマスター暗号化キーを復号化できるのは、秘密 RSA ラッピング キーの所有者のみです。


:::note info
[OCIドキュメント](https://docs.oracle.com/en-us/iaas/Content/KeyManagement/Tasks/exportingkeys_topic-To_export_a_key_by_applying_rsa_oaep_without_a_temporary_aes_key.htm)の内容からエクスポートされた鍵（バイナリ）を、一般的なPEM型式のテキストで出力するようにカスタムしています。

そのために追加した行
- 34行目付近：PEM_SOFTWARE_KEY_PATH="./coverted_software_rsakey_e2e.pem"
- 最後あたりの「base64_encoded=$(base64 ${SOFTWARE_KEY_PATH} | tr -d '\n')」以後 
:::

以下のファイルを作成します。
環境に合わせて変数を変更してください。
今回は３つの変数 KEY_OCID, OPENSSL, VAULT_CRYPTO_ENDPOINT のみ変更しています。


```shell:exportkey.sh
#!/usr/bin/env bash

#
# This script is for demonstration purposes only. It provides
# a functioning set of calls to show how to export software-protected AES key material 
# from the Vault service by using the RSA_OAEP_SHA256 algorithm.
#
set -x
private_key_path=private.pem # private wrapping key which is generated within the script itself, do not change
public_key_path=public.pem ## public wrapping key which is generated within the script itself, do not change
rsa_key_size=2048
OPENSSL="$HOME/local/bin/openssl.sh" # path to openssl

#
# Generate key pair
#
${OPENSSL} genrsa -out ${private_key_path} ${rsa_key_size}
${OPENSSL} rsa -in ${private_key_path} -outform PEM -pubout -out ${public_key_path}



KEY_OCID="ocid1.key.oc1.phx.bbqbotd7aafqw.abs{amplesamplesample}" # The Oracle Cloud Identifier (OCID) of the software-protected master encryption key to export.
ENCRYPTION_ALGORITHM="RSA_OAEP_AES_SHA256"
RSA_KEY_SIZE_IN_BYTES="256" # Specify 256 (for 2048 bits) or 512 (for 4096 bits).
VAULT_CRYPTO_ENDPOINT="https://{sample}-crypto.kms.us-phoenix-1.oraclecloud.com" # The cryptographic endpoint of the vault that contains the software-protected master encryption key.
PUBLIC_KEY_STRING="`cat ${public_key_path}`" # The content of the public key.
PRIVATE_KEY_PATH=${private_key_path} # The location of the private key.
SOFTWARE_KEY_PATH="./outputted_software_rsakey_e2e.pem" # The location for outputting the software-protected master encryption key.
TEMP_AES_KEY_PATH="./outputted_temp_aes_key2.pem" # The location for outputting the temporary AES key.
TEMP_WRAPPED_AES_PATH="./outputted_wrapped_temporary_AES_key2.pem" # The location for outputting the wrapped temporary AES key.
WRAPPED_SOFTWARE_KEY_PATH="./outputted_wrapped_master_encryption_rsakey2.pem" # The location for outputting the wrapped software-protected master encryption key, otherwise known as the wrapped target key.

# ------------------- 追加
PEM_SOFTWARE_KEY_PATH="./coverted_software_rsakey_e2e.pem"
# -------------------

declare -a hex_array wrapped_temp_aes_key_array wrapped_targetKey_array wrapped_targetKey_array_length
# Invoke the CLI to export a software-protected master encryption key. (The response contains the wrapped data in two parts.
# The first part is a wrapped temporary AES key. The second part is the wrapped software-protected master encryption key, 
# also known as the wrapped target key.)
wrapped_data=$(oci kms crypto key export --key-id ${KEY_OCID} --algorithm ${ENCRYPTION_ALGORITHM} --public-key "${PUBLIC_KEY_STRING}" --endpoint ${VAULT_CRYPTO_ENDPOINT} | grep encrypted-key | cut -d: -f2  | sed 's# "\(.*\)",#\1#g')

# Decode the encoded wrapped data and convert it to hexadecimal format.
wrapped_data_hex_array=(`echo ${wrapped_data} | base64 -d | xxd -p -c1`)
wrapped_data_hex_array_length=${#wrapped_data_hex_array[*]}

# Extract the wrapped temporary AES key. (The length of this first portion of the wrapped data is equal to the length of the private RSA wrapping key.)
wrapped_temp_aes_key_array=("${wrapped_data_hex_array[@]:0:${RSA_KEY_SIZE_IN_BYTES}}")
start_index_target_key=${#wrapped_temp_aes_key_array[*]}

# Extract the wrapped target key. (This second portion of the wrapped data is the software-protected master encryption key.)
wrapped_targetKey_array=("${wrapped_data_hex_array[@]:${start_index_target_key}:$(( ${wrapped_data_hex_array_length} - ${start_index_target_key} ))}")
wrapped_targetKey_array_length=${#wrapped_targetKey_array[*]}

# Trim spaces so that only hexadecimals remain. Convert hexadecimals to data and write to file.
wrapped_temp_aes_key_data="${wrapped_temp_aes_key_array[@]} | tr -d ' ' | xxd -p -r"
eval "echo -n $wrapped_temp_aes_key_data > $TEMP_WRAPPED_AES_PATH"

# Trim spaces so that only hexadecimals remain. Convert hexadecimals to data and write to file.
wrapped_target_key_data="${wrapped_targetKey_array[@]} | tr -d ' ' | xxd -p -r"
eval "echo $wrapped_target_key_data > $WRAPPED_SOFTWARE_KEY_PATH"

# Unwrap the wrapped_temp_aes_key by using the private RSA wrapping key.
${OPENSSL} pkeyutl -decrypt -inkey ${PRIVATE_KEY_PATH} -in ${TEMP_WRAPPED_AES_PATH} -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256 -out ${TEMP_AES_KEY_PATH}

TEMP_AES_KEY_HEX=$(hexdump -v -e '/1 "%02X"' < ${TEMP_AES_KEY_PATH})

# Unwrap the wrapped software-protected key material by using the unwrapped temporary AES key. The -id-aes256-wrap-pad OpenSSL cipher value specifies the RFC-3394-compliant CKM_RSA_AES_KEY_WRAP mechanism to use for unwrapping. As required by RFC 5649, -iv specifies an "alternative initial value" that is a 32-bit message length indicator expressed in hexadecimal.
${OPENSSL} enc -iv A65959A6 -in $WRAPPED_SOFTWARE_KEY_PATH -d -id-aes256-wrap-pad -K ${TEMP_AES_KEY_HEX} -out ${SOFTWARE_KEY_PATH}

# ------------------- 追加
# バイナリ ファイルを Base64 エンコードされたテキストに変換する
base64_encoded=$(base64 ${SOFTWARE_KEY_PATH} | tr -d '\n')

# Base64 エンコードされたデータを PEM 形式に変換し、改行を追加する
pem_data="-----BEGIN PRIVATE KEY-----"
line_length=64
index=0
while [ $index -lt ${#base64_encoded} ]; do
  pem_data="${pem_data}\n${base64_encoded:index:line_length}"
  index=$((index + line_length))
done
pem_data="${pem_data}\n-----END PRIVATE KEY-----"

# PEM 形式のデータを出力する
echo -e ${pem_data} > ${PEM_SOFTWARE_KEY_PATH}
# -----------------------
```

### 作成したexportkey.shを実行するとconverted_software_rsakey_e2e.pemが出力されます。


# 3.エクスポートされた秘密鍵ファイルの確認

## テキストレベルでの確認
converted_software_rsakey_e2e.pemの中身はPEM型式の鍵情報であることがわかります。
```plaintext:
$ cat converted_software_rsakey_e2e.pem
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9FTkk42mynoly
kXEYekdj2zLhmzILKn3bfh944D32BAZZZmJaM+y0qXxEW0c7+pBS3a2qBm88ZLBB
lbOIiSUEDNUELtVa41xK7UwFwHfhtxnYSj2pkQEqzh2Wk7AGlF3zTa5iQkMY1qIT
+pEJzrWOBKVT5AZZtKeVjXch/uPVQl6jFiVV4c9Ry4D5gBHQzRJgGgpRJb/Kiiej
tPaBuMxidJXmHq/Gcr2e4SfkCXEIwyLPCV/dbjWfFy0CevBBKsZhhAXO/xN9BWCf
7b1IJMUmeJC3nK7sl+9x9ZRkuOW1CsFQ92rxTr//xcq3ng3zlT7G7jc7vGy9oGO+
.
.
.
```

## opensslコマンドでの有効可否を確認
以下のコマンドでエラーが出なければOKです。

```bash:
$ openssl rsa -in converted_software_rsakey_e2e.pem -text -noout

RSA Private-Key: (2048 bit, 2 primes)
modulus:
    00:bd:15:39:24:e3:69:b2:9e:89:72:91:71:18:7a:
    47:63:db:32:e1:9b:32:0b:2a:7d:db:7e:1f:78:e0:
    3d:f6:04:06:59:66:62:5a:33:ec:b4:a9:7c:44:5b:
    47:3b:fa:90:52:dd:ad:aa:06:6f:3c:64:b0:41:95:
    b3:88:89:25:04:0c:d5:04:2e:d5:5a:e3:5c:4a:ed:
    4c:05:c0:77:e1:b7:19:d8:4a:3d:a9:91:01:2a:ce:
    1d:96:93:b0:06:94:5d:f3:4d:ae:62:42:43:18:d6:
    a2:13:fa:91:09:ce:b5:8e:04:a5:53:e4:06:59:b4:
    a7:95:8d:77:21:fe:e3:d5:42:5e:a3:16:25:55:e1:
    cf:51:cb:80:f9:80:11:d0:cd:12:60:1a:0a:51:25:
    bf:ca:8a:27:a3:b4:f6:81:b8:cc:62:74:95:e6:1e:
    af:c6:72:bd:9e:e1:27:e4:09:71:08:c3:22:cf:09:
    5f:dd:6e:35:9f:17:2d:02:7a:f0:41:2a:c6:61:84:
    05:ce:ff:13:7d:05:60:9f:ed:bd:48:24:c5:26:78:
    90:b7:9c:ae:ec:97:ef:71:f5:94:64:b8:e5:b5:0a:
    c1:50:f7:6a:f1:4e:bf:ff:c5:ca:b7:9e:0d:f3:95:
    3e:c6:ee:37:3b:bc:6c:bd:a0:63:be:0a:36:0e:a9:
    19:2b
publicExponent: 65537 (0x10001)
・
・
・
・
```


# 参考情報

- https://docs.oracle.com/en-us/iaas/Content/KeyManagement/Tasks/exportingkeys_topic-To_export_a_key_by_applying_rsa_oaep_without_a_temporary_aes_key.htm
- https://docs.oracle.com/en-us/iaas/Content/KeyManagement/Tasks/to_configure_and_patch_openssl.htmー



以上