💡
Python:Authlibを使用して,pem形式の証明書からJWK(JWKS)を生成する.
はじめに
- pem形式の公開鍵証明書ファイルから,JWKを生成するプログラムをpython3で記述しました.
- この記事で紹介するプログラムは,jwk中の
x5c
(証明書チェーン)の設定も加味しています.ですので,実行する際はサーバー証明書,中間証明書,ルート証明書を用意していることを前提としています. - また,
kid
は,Authlibを使用してRFC 7638で策定されたJSON Web Key (JWK) Thumbprintになるよう設定しています. - ※私の方で試した証明書の公開鍵はRSA公開鍵です.
プログラムについて
ライブラリ(インストールする必要があるもの)
- pyca/cryptography(バージョン43.0.1を使用している)
- Authlib(バージョン1.3.2を使用している.)
ライブラリのインストール
pip
を利用してインストールを行う.
- pyca/cryptography
$ pip install cryptography
- Authlib
$ pip install Authlib
プログラム詳細
- プログラムの使用方法
$ python3 pem_to_jwks.py <サーバー証明書のファイルパス> <中間証明書のファイルパス> <ルート証明書のファイルパス>
# pem_to_jwks.pyと同じディレクトリ配下に,jwks.jsonファイルが作成されます.
- プログラムの使用例(各証明書ファイルが,
pem_to_jwks.py
と同じディレクトリ配下にある場合)
$ python3 pem_to_jwks.py server.cer intermediate.cer root.cer
# pem_to_jwks.pyと同じディレクトリ配下に,```jwks.json```ファイルが保存されます.
pem_to_jwks.py
import argparse, json
from authlib.jose import JsonWebKey
from cryptography import x509
from cryptography.hazmat.primitives import serialization
def generate_x5c_in_jwk(cert_data_list):
x5c = []
for cert_data in cert_data_list:
# Read the certificate, remove the header/footer, and join lines
cert = cert_data.splitlines()
cert = [line for line in cert if not line.startswith('-----')] # Remove header/footer
cert_data_without_header_and_footer = ''.join(cert)
x5c.append(cert_data_without_header_and_footer)
return x5c
def pem_to_jwk(public_key_data, cert_data_list, use="sig", ext=True, alg="RS256"):
# Create a JsonWebKey object from the public key data
jwk = JsonWebKey.import_key(public_key_data)
jwk_as_dict = jwk.as_dict()
# Add the use, ext, and alg fields
jwk_as_dict["use"] = use
jwk_as_dict["ext"] = ext
jwk_as_dict["alg"] = alg
# Add the x5c field to the JWK
x5c = generate_x5c_in_jwk(cert_data_list)
jwk_as_dict["x5c"] = x5c
return jwk_as_dict
def extract_public_key_from_cert(pem_data):
# Extract the public key from the certificate pem data
pem_byte_data = pem_data.encode()
cert = x509.load_pem_x509_certificate(pem_byte_data)
public_key = cert.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
return public_key
def get_pem_data_from_cert(cert_file_name):
with open(cert_file_name, "r") as cert_file:
pem_data = cert_file.read()
return pem_data
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert a PEM-encoded public key to JWK format.')
parser.add_argument('server_cert', help='Path to the server certificate file')
parser.add_argument('intermediate_cert', help='Path to the intermediate certificate file')
parser.add_argument('root_cert', help='Path to the root certificate file')
args = parser.parse_args()
# get the cert data
server_cert = get_pem_data_from_cert(args.server_cert)
intermediate_cert = get_pem_data_from_cert(args.intermediate_cert)
root_cert = get_pem_data_from_cert(args.root_cert)
cert_data_list = [server_cert, intermediate_cert, root_cert]
# Extract the public key from the server certificate
public_key_data = extract_public_key_from_cert(server_cert)
# print(public_key_data,"\n")
# Convert the public key to JWK format
jwk = pem_to_jwk(public_key_data, cert_data_list)
print(jwk)
# Generate the JWKS
keys = [jwk]
jwks = {"keys": keys}
# sava jwks json file
with open("jwks.json", "w") as jwks_file:
json.dump(jwks, jwks_file, indent=2)
解説
証明書データを読み込む
- argparseを用いて,コマンドラインから,各証明書のファイルパスを取得します.
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert a PEM-encoded public key to JWK format.')
parser.add_argument('server_cert', help='Path to the server certificate file')
parser.add_argument('intermediate_cert', help='Path to the intermediate certificate file')
parser.add_argument('root_cert', help='Path to the root certificate file')
args = parser.parse_args()
- その後,ファイルパスを元に,pem形式の証明書データを読み込みます.
cert_data_list
は,x5c
の作成時に利用します.
def get_pem_data_from_cert(cert_file_name):
with open(cert_file_name, "r") as cert_file:
pem_data = cert_file.read()
return pem_data
if __name__ == "__main__":
# ....
# get the cert data
server_cert = get_pem_data_from_cert(args.server_cert)
intermediate_cert = get_pem_data_from_cert(args.intermediate_cert)
root_cert = get_pem_data_from_cert(args.root_cert)
cert_data_list = [server_cert, intermediate_cert, root_cert]
サーバー証明書から公開鍵データのみを抽出
-
pyca/cryptography
ライブラリを利用して,pem形式の公開鍵データを取得します.
使用するメソッドやクラス
- cryptography.x509.load_pem_x509_certificate
- cryptography.x509.Certificate
- cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey
プログラム部分
from cryptography import x509
from cryptography.hazmat.primitives import serialization
# ....
def extract_public_key_from_cert(pem_data):
# Extract the public key from the certificate pem data
"""読み込んだサーバ証明書のデータをバイナリ化する."""
pem_byte_data = pem_data.encode()
"""
バイナリ化した証明書データを引数として,
cryptography.x509.load_pem_x509_certificateメソッドから,
cryptography.x509.Certificateクラスの証明書データを取得します.
"""
cert = x509.load_pem_x509_certificate(pem_byte_data)
"""
cryptography.x509.Certificateクラスには,public_key()メソッド
があり,公開鍵データを取得できる.
(私の場合だと
cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyのインスタンス
を取得している.)
その後,public_bytes()メソッドを用いてPEM形式の公開鍵データを取得する.
"""
public_key = cert.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
return public_key
# ....
if __name__ == "__main__":
# ....
# Extract the public key from the server certificate
public_key_data = extract_public_key_from_cert(server_cert)
# print(public_key_data,"\n")
jwk(jwks)生成
-
Authlib
を使用して,pem形式の公開鍵データpublic_key_data
と各証明書データを格納しているリストcert_data_list
からjwk(jwks)を生成します.
使用するメソッドやクラス
- JSON Web Key (JWK)
プログラム部分
import argparse, json
from authlib.jose import JsonWebKey
def generate_x5c_in_jwk(cert_data_list):
"""
cert_data_listから,x5cデータを生成する.
"""
x5c = []
"""
各証明書データに対して,
最初の行の「-----BEGIN CERTIFICATE-----」と
最後の行の「-----END CERTIFICATE-----」を
取り除く操作を行う.取り除いたら,x5cリストに追加する.
具体的な処理手順:
- 行毎にデータを分割してリスト変換.
- '-----'で始まる要素だけを取り除き,''.join(cert)で再度結合する.
"""
for cert_data in cert_data_list:
# Read the certificate, remove the header/footer, and join lines
cert = cert_data.splitlines()
cert = [line for line in cert if not line.startswith('-----')] # Remove header/footer
cert_data_without_header_and_footer = ''.join(cert)
x5c.append(cert_data_without_header_and_footer)
return x5c
def pem_to_jwk(public_key_data, cert_data_list, use="sig", ext=True, alg="RS256"):
# Create a JsonWebKey object from the public key data
"""
PEM形式のデータをインポートし,jwkを生成する.
この時点でkidは,jwk thumbprintに設定される.
as_dict()メソッドを使用して,dict型のデータに直す.
"""
jwk = JsonWebKey.import_key(public_key_data)
jwk_as_dict = jwk.as_dict()
"""
以下,公開鍵以外のjwkフィールド値を設定している.
"""
# Add the use, ext, and alg fields
jwk_as_dict["use"] = use
jwk_as_dict["ext"] = ext
jwk_as_dict["alg"] = alg
# Add the x5c field to the JWK
x5c = generate_x5c_in_jwk(cert_data_list)
jwk_as_dict["x5c"] = x5c
return jwk_as_dict
if __name__ == "__main__":
# ....
cert_data_list = [server_cert, intermediate_cert, root_cert]
# ....
public_key_data = extract_public_key_from_cert(server_cert)
# Convert the public key to JWK format
jwk = pem_to_jwk(public_key_data, cert_data_list)
print(jwk)
# Generate the JWKS
keys = [jwk]
jwks = {"keys": keys}
# save jwks json file
"""
jsonライブラリを使用して,jwksファイルを保存する.
"""
with open("jwks.json", "w") as jwks_file:
json.dump(jwks, jwks_file, indent=2)
参考資料
pyca/cryptography
- pyca/cryptography lateset
- pyca/cryptography 43.0.1
X.509 Reference — Cryptography 43.0.1 documentation
Authlib
- Authlib latest(2024年10月22日時点では,バージョン1.3.2に相当)
- JSON Web Key (JWK) - Authlib 1.3.2 documentation
Discussion