BouncyCastleで自己署名証明書を発行してみよう(Java)

# はじめに
JVM上でキーペアを作成してAWS Secrets Managerに秘密鍵を、ローカルに公開鍵証明書を保存したくなったりしたことありませんか？
私はあります。

ので、BouncyCastleを使ってそれをやってみました。

# 概要
BouncyCastle APIを使って以下を行います。

- キーペアの発行
- 公開鍵から自己署名証明書の作成

## そもそもBouncyCastleとはなんぞや

BouncyCastleは、The Legion of the Bouncy Castleがサポートしている暗号化APIです。

https://www.bouncycastle.org/

JavaおよびC#で軽量の暗号化に関連するAPIが提供されており、「almost 20 years」(すごい！)と歴史の長いプロジェクトです。
2000年からなので21年ですね。

原義は風船でできたお城だそう。

## 何ができるのか
BouncyCastleを使うことで、暗号化に関連する処理を簡単に行うことができます。
例えばそう、キーペアを作成して自己署名証明書を作成するとかね。

# やってみた
ということでやってみましょう。

## 準備
用意するものは以下2つです。

Bouncy Castle Provider

https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on

Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs

https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on

MavenなりGradleなりでライブラリを落としてきます。

その上で、セキュリティプロバイダにBouncyCastleが含まれていなければ追加します。

```java
Provider provider = new BouncyCastleProvider();
Security.addProvider(provider);
```

## キーペアを作成する
まずキーペアを作成します。
現在主流のRSAで2048bitにします。

```java:
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair keyPair = generator.generateKeyPair();
```

## キーペアから自己署名証明書を作成する
ここからがBouncyCastleの出番です。

## その前に、X.509証明書とは

・・・の前に、X.509証明書がどのような構造になっているのか確認しておきましょう。

https://qiita.com/TakahikoKawasaki/items/4c35ac38c52978805c69

こちらに記載の通り、X.509証明書には以下の要素が必要になります。

- 発行者(署名する側)
- 主体者(署名される側)
- 公開鍵
- 署名

これらに加えて、有効期限の情報やシリアルナンバーも必要になります。

## 公開鍵証明書に必要な情報をそろえる
では本題に進んでいきましょう。

このX509v3CertificateBuilderを利用すればきわめて簡単に公開鍵証明書を作成できます。

https://www.bouncycastle.org/docs/pkixdocs1.5on/org/bouncycastle/cert/X509v3CertificateBuilder.html

```java
X509v3CertificateBuilder(X500Name issuer, BigInteger serial, Date notBefore, Date notAfter, X500Name subject, SubjectPublicKeyInfo publicKeyInfo)
```

とある通り、

- 発行者 X500Name
- シリアルナンバー BigInteger
- 有効期限開始日時 Date
- 有効期限終了日時 Date
- 主体者 X500Name
- 公開鍵情報 SubjectPublicKeyInfo

が必要になるので、まずそれらを用意します。
今回作成するのは自己署名証明書なので、発行者と主体者は同じになります。

X.500に従って識別名を作成します。

```java
X500Name dnName = new X500Name("cn=hoge, ou=fuga, o=\"Foo Co., Ltd.\", c=JP");
```

次にシリアルナンバーですが、これは世界中でユニークでなくても構いませんが、発行機関ごとにはユニークでなければなりません。

https://www.ietf.org/rfc/rfc2459.txt

```text
4.1.2.2  Serial number

   The serial number is an integer assigned by the CA to each
   certificate.  It MUST be unique for each certificate issued by a
   given CA (i.e., the issuer name and serial number identify a unique
   certificate).
```

ランダムなシリアルナンバーを作成します。
ランダムであることは必須ではありませんが、選択的プレフィックス攻撃を避けるためにはシリアルナンバーと有効期限を予測できない形が良いでしょう。

```java
long serialNumber =new RandomDataGenerator().nextLong(1L, 10L);
BigInteger certSerialNumber = new BigInteger(Long.toString(serialNumber));
```

次に有効期限です。
とりあえず発行した今日から有効にしたいので、システムミリ秒を使います。

```java
long now = System.currentTimeMillis();
Date notBefore = new Date(now);
```

有効期限の終了日は開始日に任意の年数を足します。(ここではとりあえず3年)

```java
Calendar calendar = Calendar.getInstance();
calendar.setTime(notBefore);
calendar.add(Calendar.YEAR, 3);
Date notAfter = calendar.getTime();
```

最後に署名対象になる公開鍵の情報です。

```java
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
```

これで `X509v3CertificateBuilder` に必要な、

- 発行者 X500Name
- シリアルナンバー BigInteger
- 有効期限開始日時 Date
- 有効期限終了日時 Date
- 主体者 X500Name
- 公開鍵情報 SubjectPublicKeyInfo

の情報がそろったので、いよいよ署名処理に進みます。

## 署名する
まず、先ほど用意したオブジェクトをビルダーに渡します。

```java
X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(dnName, certSerialNumber, notBefore, notAfter, dnName, subjectPublicKeyInfo);
```

次に、署名オペレータである `ContentSigner` の実装クラスを初期化します。
このとき、JCA(Java Cryptography Architecture)を利用し、準備でセキュリティプロバイダに追加した `BouncyCastleProvider` をプロバイダとして使います。
署名アルゴリズムとしてSHA256 with RSAを選んでみます。

```java
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").setProvider(bcProvider).build(keyPair.getPrivate());
```

ようやく実際の署名処理です。
X.509証明書を作成するために必要とした情報を持った `certificateBuilder` に、OutputStreamに署名する `contentSigner` を渡して署名します。

```java
X509CertificateHolder certificateHolder = certificateBuilder.build(contentSigner);
```

すると `X509Certificate` を持つホルダークラスが返ってくるので、

```java
X509Certificate selfSignedCertificate = new JcaX509CertificateConverter().getCertificate(certificateHolder);
```

とJCAを使ってホルダーから証明書を抽出してやれば、みんな大好き `X509Certificate` の出来上がりです。

# まとめ
ということで、キーペアを作成して自己署名証明書を発行するところまででした。
あとは `X509Certificate` をBase64エンコードしてやればPEMとして扱うことができるはず。

以上です。