BouncyCastle(.NET)を使ってみた

# はじめに
C#にて証明書を発行するプログラムを作ったのですが、
.NETに慣れているとBouncyCastleの設計は少々特殊に感じる部分もあるので備忘録として残します。
OpenSSLを利用する場合と、BouncyCastleを利用する場合の２パターンを紹介します。
今回はオレオレ証明書を作ってみます。

# 環境
- .NET 6
- BouncyCastle.NetCore (1.8.10)

# オレオレ証明書の作成手順

鍵を作成⇒証明書発行要求を作成⇒証明書作成
の流れでオレオレ証明書（自己著名証明書）を作成していきます。

## 秘密鍵の作成

```bash: OpenSSL
openssl genrsa 2048 > ca.key
```

```cs: BouncyCastle
var keyGen = new RsaKeyPairGenerator();
var keyGenParam = new KeyGenerationParameters(new SecureRandom(), 2048);
keyGen.Init(keyGenParam);
var keyPair = keyGen.GenerateKeyPair();

using (var sw = new StreamWriter("ca.key"))
{
    var writer = new PemWriter(sw);
    writer.WriteObject(keyPair.Private);
}
```
- **備考**
上記OpenSSLで作成した鍵はPKCS#1フォーマットとなりますが、
そのままではBouncyCastleはPKCS#1未対応のため読み込めません。
BouncyCastleで利用するには以下のコマンドによりPKCS#8に変換する必要があります。
```bash:
openssl pkcs8 -in rs256.key -out rs256.key.pkcs8 -topk8 -nocrypt
```

## 証明書著名要求の作成


```bash: OpenSSL
openssl req -new -key ca.key -subj "/CN=oreoreca" > ca.csr
```

```cs: BouncyCastle
// Subject
var attributes = new Dictionary<DerObjectIdentifier, string>()
{
    { X509Name.CN, "oreoreca" },
};
var attributeOrder = new []
{
    X509Name.CN,
};
var subject = new X509Name(attributeOrder, attributes);

// 拡張情報
var extGen = new X509ExtensionsGenerator();

extGen.AddExtension(X509Extensions.SubjectKeyIdentifier, false,
    new SubjectKeyIdentifierStructure(keyPair.Public));
extGen.AddExtension(X509Extensions.KeyUsage, false,
    new KeyUsage(KeyUsage.DigitalSignature));

var extensions = extGen.Generate();
var extAttr = new AttributeX509(
    PkcsObjectIdentifiers.Pkcs9AtExtensionRequest,
    new DerSet(extensions));

// CSR作成
var csr = new Pkcs10CertificationRequest(
    "SHA256withRSA",
    subject,
    keyPair.Public,
    new DerSet(extAttr),
    keyPair.Private);

using (var sw = new StreamWriter("ca.csr"))
{
    var writer = new PemWriter(sw);
    writer.WriteObject(csr);
}
```

## オレオレ証明書の発行
```bash: OpenSSL
openssl x509 -req -in ca.csr -signkey ca.key -days 365 -out ca.crt 
```

```cs: BouncyCastle
var random = new SecureRandom();

// CSR読み込み
using var csrStreamReader = new StreamReader("ca.csr");
var csrReader = new PemReader(csrStreamReader);
var csrPemObj = csrReader.ReadPemObject();
var csr = new Pkcs10CertificationRequest(csrPemObj.Content);

// CSR検証
if (!csr.Verify()) throw new Exception();

// 証明書著名用秘密鍵読み込み
using var keyStreamReader = new StreamReader("ca.key");
var keyReader = new PemReader(keyStreamReader);
var keyObj = keyReader.ReadObject() as AsymmetricCipherKeyPair;
var privateKey = keyObj?.Private;
if (privateKey == null) throw new Exception("Failed to read private key.");

// Issure
var attributes = new Dictionary<DerObjectIdentifier, string>()
{
    { X509Name.CN, "oreoreca" },
};
var attributeOrder = new []
{
    X509Name.CN,
};
var issure = new X509Name(attributeOrder, attributes);

// 証明書作成
var certGen = new X509V3CertificateGenerator();
certGen.SetSerialNumber(new Org.BouncyCastle.Math.BigInteger(256, random));
certGen.SetIssuerDN(issure);
certGen.SetSubjectDN(csr.GetCertificationRequestInfo().Subject);

var today = DateTime.UtcNow.Date;
certGen.SetNotBefore(today);
certGen.SetNotAfter(today.AddYears(1));

certGen.SetPublicKey(csr.GetPublicKey());

certGen.AddExtension(X509Extensions.BasicConstraints, false,
    new BasicConstraints(true));
certGen.AddExtension(X509Extensions.SubjectKeyIdentifier, false,
    new SubjectKeyIdentifierStructure(csr.GetPublicKey()));

var factory = new Asn1SignatureFactory(
    "SHA256withRSA",
    privateKey,
    random);
var cert = certGen.Generate(factory);

using (var sw = new StreamWriter("ca.crt"))
{
    var writer = new PemWriter(sw);
    writer.WriteObject(cert);
}
```

# さいごに
このサンプルソースで作成した証明書は非常にミニマムな構成となっております。
実際に公開するような証明書を作る場合は、キー使用法の制限等しっかりと設定してやる必要があるかと思います。

次回、別のユースケースでのサンプルソースを載せていく予定です。