FIDO2セキュリティキーで電子署名をする試み

#はじめに
世界のパスワード問題を解決し、パスワードのない世界 を目指して策定されたFIDO規格。
FIDO2のWebAuthn-JavaScriptを各社のウェブブラウザが実装したことでWeb認証はもはやFIDOがスタンダードです。
FIDO2のセキュリティキーは 堅牢 かつ パスワードがいらないシンプルな操作 でログインすることができます。

一方で　デジタルガバメント を目指して策定されたGPKI、JPKI。
マイナンバーカードは券面に個人情報印刷しまくりで、人に見せてはいけない、という意味不明のアナウンスで絶賛普及推進中。
・・・何やっても全く普及せず、ひたすら残念な状況のマイナンバーカードですが、ICカードの中の仕組みはちゃんとしていて、認証だけでなく`電子署名`の機能も備えています。つまりログインだけでなく、電子文書へのデジタル署名（電子的な実印）を作成することができます。カード中の秘密鍵はセキュアに保護されており、外に出すことのできる公開鍵はスキャンされても問題ない、マイナンバーカードの機能は携帯する個人認証デバイスとしてはとってもいいものなのです。

こうやって見ると

|デバイス|認証|電子署名|状況|
|:-:|:-:|:-:|:-:|
|FIDO2セキュリティーキー|〇|   |イケイケ|
|マイナンバーカード|〇|〇|残念|

**FIDO2セキュリティキーで電子署名ができれば最強では。**

というわけで、この投稿ではFIDO2セキュリティキーで電子署名をする、というツッコミどころ満載な試みにトライしてみました。

#環境
要はFIDO2セキュリティキーを使った電子署名のプログラムを作成する、ということです。
セキュリティキーは指紋スキャナの付いたBioPassを使いますが、BioPassでないといけない、ということはありません。YubikeyなどFIDO2のAuthenticatorであればOKです。

<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/e369970d-5490-54fc-4078-fb2f8b086a15.png" width=60%>

OSはWindows、言語はC#、暗号化ライブラリはBouncy Castle、セキュリティキーの制御はWebAuthnModokiDesktopを使います。

- FIDO2セキュリティキー [BioPass](https://www.ftsafe.com/Products/FIDO2)
- Windows10 1809
- .Net Framework 4.6.1
- Visual Studio 2017 C#
- [Bouncy Castle 1.8.5](https://www.bouncycastle.org/csharp/index.html)
- [WebAuthnModokiDesktop](https://github.com/gebogebogebo/WebAuthnModokiDesktop)
- ソース [GitHub](https://github.com/gebogebogebo/GeboSig)

#ユースケース
おおざっぱに以下の構成です。

Phase
- Administrator-Register
- User-Register
- Signature
- Veriy

Actor
- システム管理者
- ユーザー
- マネージャ

![01.png](https://qiita-image-store.s3.amazonaws.com/0/252291/8512d557-8818-117c-6dc4-651be5de257c.png)

---

株式会社GEBOは三文判による紙の社内文書の運用から電子署名運用に切り替えました。という体で。
- システム管理者＝社内システム担当：下暮田
- ユーザー＝新入社員：ゲボ子
- マネージャ＝ゲボ子の上司：毛保川

#◆Administrator-Register
情報処理室の社内システム担当：下暮田さんは本日入社する社員のゲボ子さんに渡すセキュリティキーを作成します。
セキュリティキー(BioPass）は新品のものを使いますが、念のために初期化しておきましょう。
初期化はBio Pass FIDO2 Managerというメーカー提供のツールでリセットしておきます。(Microsoft StoreからGETできます）
<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/d93608f7-144e-5c50-cf38-89930c9b391d.png" width=50%>

登録は[GeboSigRegister](https://github.com/gebogebogebo/GeboSig/tree/master/Source/GeboSigRegister)というアプリで行います。

###登録アプリ処理
- RSA1024キーペア生成
- 秘密鍵をPEM形式でGET
- PEM形式の秘密鍵をDER形式に変換する
- DER形式の秘密鍵をAES256で暗号化する
- セキュリティキーにPINを設定する
- セキュリティキーに秘密鍵を書き込む
- 証明書を作成する


####RSA1024キーペア生成
RSA-1024bitキーペアを生成します。
Bouncy CastleのAPIを使います。
※Bouncy Castleはusingの追加がたくさん必要なので、Alt+Enterでどんどん追加していきましょう。

```CS:RSA-1024bitキーペアを生成
private AsymmetricCipherKeyPair createKeyPair()
{
    var randGen = new CryptoApiRandomGenerator();
    var rand = new SecureRandom(randGen);
    var param = new KeyGenerationParameters(rand, 1024);

    var keyGen = new RsaKeyPairGenerator();
    keyGen.Init(param);
    var keyPair = keyGen.GenerateKeyPair();

    return (keyPair);
}
```

####秘密鍵をPEM形式でGET
Bouncy CastleのPemWriterを使えば簡単です。

```CS:秘密鍵をPEM形式でGET
private string getPrivatekyPEM(AsymmetricCipherKeyPair keyPair)
{
    var mem = new MemoryStream();
    using (var writer = new StreamWriter(mem, Encoding.ASCII)) {
        var pemWriter = new PemWriter(writer);
        pemWriter.WriteObject(keyPair.Private);
        pemWriter.Writer.Flush();
    }
    var pem = Encoding.UTF8.GetString(mem.ToArray());

    return (pem);
}
```

####PEM形式の秘密鍵をDER形式に変換する
少しでもサイズを小さくしたいのでDERにします。これはBouncy CastleのAPIが見つからず、定番の方法(?)でやります。

```CS:PEMからDERにする
public static byte[] ConvertPEMtoDER(string pem)
{
    var pems = pem.Trim('\n').Split('\n').ToList();

    // ヘッダとフッダは飛ばす
    pems.RemoveAt(0);
    pems.RemoveAt(pems.Count - 1);

    // つなげる
    var base64 = String.Join("", pems);

    // もどす
    return (Convert.FromBase64String(base64));
}
```

####DER形式の秘密鍵をAES256で暗号化する
Bouncy Castleを使います。
こちらの素晴らしいサンプルをコピペさせていただきました
https://kagasu.hatenablog.com/entry/2017/01/04/213533

keyとivは普通はオープンにしてはいけませんよ。

```CS:AES256
// inDataがDER形式の秘密鍵です
public static byte[] Encrypt(byte[] inData)
{
    key = Encoding.UTF8.GetBytes("ygmh8zudlw5u0a9w4vc29whc4b8wuech");
    iv = Encoding.UTF8.GetBytes( "o10wi1q3x2f98cobfkyisnwy9s9wxop7");

    // Rijndael
    // Mode = CBC
    // BlockSize = 256bit
    // PaddingMode = Zero
    var cbcBlockCipher = new CbcBlockCipher(new RijndaelEngine(256));
    cipher = new PaddedBufferedBlockCipher(cbcBlockCipher, new ZeroBytePadding());
    parametersWithIV = new ParametersWithIV(new KeyParameter(key), iv);

    cipher.Init(true, parametersWithIV);
    var bytes = new byte[cipher.GetOutputSize(inData.Length)];
    var length = cipher.ProcessBytes(inData, bytes, 0);
    cipher.DoFinal(bytes, length);

    return bytes;
}
```

####セキュリティキーにPINを設定する
初期PINをセットします。
USBにセキュリティキーを挿してから、WebAuthnModokiDesktopのAPIで一発です。

```CS:PIN設定
// PINは1234
private async Task<bool> setNewPIN()
{
    var status = await gebo.CTAP2.WebAuthnModokiDesktop.Credentials.SetPin(gebo.CTAP2.DevParam.GetDefaultParams(), "1234");
    if( status.isSuccess == false) {
        // Error
        return false;
    }
    return true;
}

```

####セキュリティキーに秘密鍵を書き込む
さて、登録のヤマ場です。
FIDO2セキュリティキーにどうやって秘密鍵を書き込むのか、ですが、ResidentKeyの機能を利用します。
**ResidentKeyはRPのユーザー情報を書き込むための機能で、秘密鍵などというものを書き込むための機能ではありません。** なのですが、やってみましょう。

**※注意※**
**この制限はFIDOとかCTAPの仕様によるものではないと思われますので、モノによってこの通りでないと思われます。**

実際に試したところ、1回で書き込める情報は以下の通りでした。
- UserID領域に64byte（byte型）
- UserName領域に64文字（string型）
- DisplayName領域に64文字（string型）

これ以上のデータを書き込もうするとエラーになります。

string型は1byteを2文字のHEXにすればいいか、ということで、64byte+32byte(64文字)+32byte(64文字)の合計128byteを1回で書き込むことができます。
でありますため、何回かに分けて書き込むことにします。

1回に書き込むデータを1レコードとして、レコードの構造は以下の通り。

| Area | DataType | Size | 内容 |
|:-:|:-:|:-:|:--|
|UserID領域|レコードNo|1byte|0から始まるレコード番号|
|UserID領域|予備|1byte|0xFF固定|
|UserID領域|データ|62byte|書き込むデータ|
|UserName領域|データ|64文字|32byteのデータをHEX64文字にする|
|DisplayName領域|データ|64文字|32byteのデータをHEX64文字にする|

先ほど作成した秘密鍵(AES256で暗号化したもの）は640byteでした。
これだと6レコードになります。
6回に分けて書き込みます。

書き込み自体はWebAuthnModokiDesktopのAPIを使えば簡単です。
- 引数pinはさっき設定した初期PINを指定します。
- 引数recは[こんなかんじ](https://github.com/gebogebogebo/GeboSig/blob/master/Source/GeboSigCommon/WriteData.cs)
- BioBassだと1回の書き込みにつきUser Presenceのチェックが走るのでキーがピカピカ光ってタッチが必要です、つまり、6回タッチしないいけないです。
- これがNFCタイプ(例えばYubikey5）だとUser Presenceがされないのでいちいちタッチする必要がなく快適です。


```CS:1レコードの書き込み
private async Task<string> writeRec(string pin,WriteData rec)
{
    string result = "";
    try {
        result = await Task<string>.Run(async () => {
            byte[] challenge = System.Text.Encoding.ASCII.GetBytes("this is challenge");

            byte[] userid = new byte[] { rec.recno, rec.filler };
            userid = userid.ToList().Concat(rec.data1).ToArray();

            string username = (rec.data2 == null) ? "" : gebo.CTAP2.Common.BytesToHexString(rec.data2);
            string userdisplayname = (rec.data3 == null) ? "" : gebo.CTAP2.Common.BytesToHexString(rec.data3);

            string json =
                    "{" +
                        @"rp : {" +
                            string.Format($"id : 'GeboSig.gebo.com',") +
                        @"}," +
                        @"user : {" +
                            string.Format($"id_bytearray:[{string.Join(",", userid)}],") +
                            string.Format($"name :'{username}',") +
                            string.Format($"displayName :'{userdisplayname}',") +
                        @"}," +
                        @"pubKeyCredParams: [{type: 'public-key',alg: -7}]," +
                        @"attestation: 'direct'," +
                        @"timeout: 60000," +
                        @"authenticatorSelection : {" +
                            string.Format($"requireResidentKey : true,") +
                            @"authenticatorAttachment : 'cross-platform'," +
                            string.Format($"userVerification : 'discouraged'") +
                        @"}," +
                        string.Format($"challenge:[{string.Join(",", challenge)}],") +
                    "}";

            var ret = await gebo.CTAP2.WebAuthnModokiDesktop.Credentials.Create(gebo.CTAP2.DevParam.GetDefaultParams(), json, pin);
            if (ret.isSuccess == false) {
                return ret.msg;
            }
            return ("Success");
        });

    } catch (Exception ex) {
        result = ex.Message;
    } finally {

    }
    return result;
}

```

####証明書を作成する
最後に秘密鍵のペアとなる公開鍵の処理です。
公開鍵のままでも別にいいんですけど、せっかくなので(?)X.509形式の証明書にしておきましょう。
Bouncy CastleのAPIなら簡単です。
※自己署名なんで、証明書の意味はないです。
- 証明書のCNには対象者のゲボ子さんの情報を入れておきます。
- 証明書は本日より10年間有効です。
- 証明書に今作成したキーペアの公開鍵を格納し、自分自身の秘密鍵で署名します。
- 証明書はPEM形式で作成します。
- 作成した証明書は保管しておきます。本ユースケースでは社内のRepositoryで保管することにします。

```CS:
private string createCertificate(AsymmetricCipherKeyPair keyPair)
{

    // 証明書の属性
    var attr = new Dictionary<DerObjectIdentifier, string>()
    {
        { X509Name.CN, geboko@gebo.com },
        { X509Name.C, "Japan" },
        { X509Name.ST, "None" },
        { X509Name.L, "None" }
        { X509Name.O, "gebo" },
        { X509Name.OU, "None" },
    };
    var ord = new List<DerObjectIdentifier>()
    {
        X509Name.CN,
        X509Name.C,
        X509Name.ST,
        X509Name.L,
        X509Name.O,
        X509Name.OU,
    };

    // 証明書の生成
    var name = new X509Name(ord, attr);
    var certGen = new X509V3CertificateGenerator();
    certGen.SetSerialNumber(BigInteger.One);
    certGen.SetIssuerDN(name);
    certGen.SetSubjectDN(name);
    certGen.SetNotBefore(DateTime.Now);
    certGen.SetNotAfter(DateTime.Now.AddYears(10));
    certGen.SetPublicKey(keyPair.Public);
    var cert = certGen.Generate(new Asn1SignatureFactory(PkcsObjectIdentifiers.Sha256WithRsaEncryption.Id, keyPair.Private));

    // 証明書の出力
    var mem = new MemoryStream();
    using (var writer = new StreamWriter(mem, Encoding.ASCII)) {
        var pemWriter = new PemWriter(writer);
        pemWriter.WriteObject(cert);
        pemWriter.Writer.Flush();
    }
    var pem = Encoding.UTF8.GetString(mem.ToArray());

    return (pem);
}
```

###登録後の運用
下暮田さんの作業です。
- 生成されたゲボ子さん用の証明書(geboko.crt）は社内のRepositoryにしまいます。
- 初期化が済んだセキュリティキーをゲボ子さんに渡します。

---

#◆User-Register
システム管理者：下暮田さんから自分用のセキュリティキーを受け取ったゲボ子さんは自分用の初期設定をします。
- PINの変更
- 指紋登録

PINの変更は[GeboSigChangePIN](https://github.com/gebogebogebo/GeboSig/tree/master/Source/GeboSigChangePIN)というアプリで行います。

###PIN変更アプリ処理
WebAuthnMODOKIDesktopのAPIで一発です。

```CS:
// newpin=新しいPIN、currentpin=現在のPIN
var devParam = gebo.CTAP2.DevParam.GetDefaultParams();
var ret = await gebo.CTAP2.WebAuthnModokiDesktop.Credentials.ChangePin(devParam, newpin, currentpin);
if (ret.isSuccess == true) {
    // OK
} else {
    // NG
}
```

###指紋登録
指紋登録はBio Pass FIDO2 Managerというメーカー提供のツールで行います。(Microsoft StoreからGETできます）
<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/d93608f7-144e-5c50-cf38-89930c9b391d.png" width=50%>

これでこのセキュリティキーのPINを知っているのはゲボ子さんだけす。指紋もゲボ子さんのものを登録しているので、**セキュリティキーを使えるのはゲボ子さんだけ、ということになります**。

---

#◆Signature
ここからが通常運用です。
ゲボ子さんは社内用の報告書を作成しました。上司に電子署名付きのファイル（報告書）を送ります。

署名は[GeboSigSignature](https://github.com/gebogebogebo/GeboSig/tree/master/Source/GeboSigSignature)というアプリで行います。

###署名アプリ処理
- 暗号化された秘密鍵を取り出す
- 復号する
- 復号データからパディングデータを除去する
- DERからPEMに変換する
- 電子署名を作成する
- 署名の付いた文書を作成する

####暗号化された秘密鍵を取り出す
セキュリティキーの中から(暗号化された)秘密鍵を取り出します。
**セキュリティキーのアクセスはUV-指紋認証です。つまり ゲボ子さん本人 しかアクセスできません。**
非常用でPINでのアクセスもできるようにしとかないといけないですが、PINも本人しか知らないはずです。

秘密鍵はセキュリティキーの中に複数レコードに分割して書き込みました。
セキュリティキーからレコード情報を取りだすのはWebAuthnModokiDesktopのAPIで一発です。
取り出したデータのUserID、UserName、DisplayNameを格納時と逆の方法でつなぎ合わせてbyte配列にします。
- 戻り値ReadDataは[こんなかんじ](https://github.com/gebogebogebo/GeboSig/blob/master/Source/GeboSigSignature/ReadData.cs)
- credentialid は使わないので空です。
- 指紋認証なのでuserVerification : 'preferred'にします

```cs:データ取り出し
private async Task<ReadData> readRecs()
{
    ReadData result;
    try {
        result = await Task<ReadData>.Run(async () => {
            var readData = new ReadData();

            byte[] challenge = System.Text.Encoding.ASCII.GetBytes("this is challenge");
            var credentialid = new byte[0];

            string json =
               "{" +
                    string.Format($"timeout : 60000,") +
                    string.Format($"challenge:[{string.Join(",", challenge)}],") +
                    string.Format($"rpId : 'GeboSig.gebo.com',") +
                   @"allowCredentials : [{" +
                       string.Format($"id : [{string.Join(",", credentialid)}],") +
                       string.Format($"type : 'public-key',") +
                   @"}]," +
                   string.Format($"requireUserPresence : 'false',") +
                   string.Format($"userVerification : 'preferred',") +
                "}";

            var ret = await gebo.CTAP2.WebAuthnModokiDesktop.Credentials.Get(gebo.CTAP2.DevParam.GetDefaultParams(), json, "");
            if (ret.isSuccess == false) {
                readData.isSuccess = false;
                readData.msg = ret.msg;
                return readData;
            }

            // dataList
            var dataList = new List<WriteData>();
            foreach (var assertion in ret.assertions) {
                dataList.Add(new WriteData(assertion.User_Id, assertion.User_Name, assertion.User_DisplayName));
            }
            dataList = dataList.OrderBy(x => x.recno).ToList();

            // data
            readData.data = new byte[0];
            foreach (var data in dataList) {
                var tmp = data.data1.ToList().Concat(data.data2).Concat(data.data3).ToList();
                readData.data = readData.data.ToList().Concat(tmp).ToArray();
            }

            readData.isSuccess = true;
            readData.msg = "Success";
            return readData;
        });

    } finally {

    }
    return result;
}

```

####復号する
さて、取り出した秘密鍵はAES256で暗号化されているので復号します。
復号するときのkeyとivは暗号化するときと同じものを指定しましょう。

keyとivは普通はオープンにしてはいけませんよ。

```cs:復号する
// inDataが暗号化されているデータです
public static byte[] Decrypt(byte[] inData)
{
    // AES-256
    key = Encoding.UTF8.GetBytes("ygmh8zudlw5u0a9w4vc29whc4b8wuech");
    iv = Encoding.UTF8.GetBytes( "o10wi1q3x2f98cobfkyisnwy9s9wxop7");

    // Rijndael
    // Mode = CBC
    // BlockSize = 256bit
    // PaddingMode = Zero
    var cbcBlockCipher = new CbcBlockCipher(new RijndaelEngine(256));
    cipher = new PaddedBufferedBlockCipher(cbcBlockCipher, new ZeroBytePadding());
    parametersWithIV = new ParametersWithIV(new KeyParameter(key), iv);

    cipher.Init(false, parametersWithIV);
    var bytes = new byte[cipher.GetOutputSize(inData.Length)];
    var length = cipher.ProcessBytes(inData, bytes, 0);
    var ret = cipher.DoFinal(bytes, length);

    return bytes;
}
```

####復号データからパディングデータを除去する
ここが今一つわからなかったのですが、AESの復号ではパティングデータが付いたままでモドされるようです。PaddingMode = Zeroなので後ろに0x00が何個かひっついてきます。
これがあると非常に具合が悪いので除去します。
幸いなことにDERは先頭SEQにデータレングスがあるので、その情報をもとにパディングデータをとっぱらいます。
これでちゃんとしたDERになります。

```cs:パディングデータを除去する
private byte[] getPrivateKey(byte[] decData)
{
    if(decData[0] != 0x30){
        return (null);
    }
    if (decData[1] != 0x82){
        return (null);
    }

    var datasize = (int)ChangeEndian.Reverse(BitConverter.ToUInt16(decData, 2));

    // add header-4byte
    datasize = datasize + 4;

    return(decData.ToList().Take(datasize).ToArray());
}
```

####DERからPEMに変換する
さてさて、Bouncy Castleで署名するためにPEMにしないといけないです。
*めんどくさいなぁ、Bouncy CastleってほんとにDERつかえないのかなぁ*

```CS:DERからPEMに変換する
// 1.Base64エンコード
// 2.64文字ごとに改行コードをいれる
// 3.ヘッダとフッタを入れる
public static string ConvertPrivateKeyDERtoPEM(byte[] der)
{
    string pemdata = "-----BEGIN RSA PRIVATE KEY-----\n" + ConvertDERtoPEM(der) + "-----END RSA PRIVATE KEY-----\n";
    return pemdata;
}
private static string ConvertDERtoPEM(byte[] der)
{

    var b64cert = Convert.ToBase64String(der);

    string pemdata = "";
    int roopcount = (int)Math.Ceiling(b64cert.Length / 64.0f);
    for (int intIc = 0; intIc < roopcount; intIc++) {
        int start = 64 * intIc;
        if (intIc == roopcount - 1) {
            pemdata = pemdata + b64cert.Substring(start) + "\n";
        } else {
            pemdata = pemdata + b64cert.Substring(start, 64) + "\n";
        }
    }
    return pemdata;
}

```

####電子署名を作成する
ついにこのときが来ました。ファイルの署名を作成します。
Bouncy Castleで署名を作成します。
なにやら色々手順がありますが、思考停止のおまじないということで。
- pemPrivateKeyはPEM形式の秘密鍵。
- targetfilepathは署名対象のファイルのパスとファイル名です。
- ReadAllBytesしているんでファイルを一回全部取り込むっぽいです。巨大なファイルだとヤバいです。

```CS:電子署名を作成する！
private byte[] createSign(string pemPrivateKey, string targetfilepath)
{
    byte[] data = System.IO.File.ReadAllBytes(targetfilepath);

    // PEMフォーマットの秘密鍵を読み込んで KeyPair オブジェクトを生成
    var privateKeyReader = new PemReader(new StringReader(pemPrivateKey));
    var keyPair = (AsymmetricCipherKeyPair)privateKeyReader.ReadObject();

    RsaKeyParameters key = (RsaKeyParameters)keyPair.Private;
    ISigner sig = SignerUtilities.GetSigner("SHA1withRSA");
    sig.Init(true, key);

    var bytes = data;
    sig.BlockUpdate(bytes, 0, bytes.Length);
    byte[] signature = sig.GenerateSignature();

    return signature;
}
```

####署名の付いた文書を作成する
ターゲットファイルと署名を一つのzipにして固めてデスクトップに吐き出すだけです。
- .netFrameworkのSystem.IO.Compressionで簡単です。
- 署名はsig.sigというファイル名にします。

```CS:zipに固める
using System.IO.Compression;
private bool createZip(string targetFile, byte[] sig)
{
    var rootDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
    var targetFileTitle = System.IO.Path.GetFileNameWithoutExtension(targetFile);
    var targetFileName = System.IO.Path.GetFileName(targetFile);
    var zipFile = $@"{rootDir}\{targetFileTitle}.zip";

    // zipに固める
    using (var z = ZipFile.Open(zipFile, ZipArchiveMode.Update))
    {
        z.CreateEntryFromFile(targetFile, targetFileName, CompressionLevel.Optimal);

        ZipArchiveEntry item = z.CreateEntry("sig.sig",CompressionLevel.Optimal);
        using (Stream stream = item.Open())
        {
            stream.Write(sig, 0, sig.Length);
            stream.Flush();
        }
    }

    return true;
}
```

###署名後の運用
ゲボ子さんの作業です。
- デスクトップに署名付き報告書ファイル(zip)ができるので、それを上司に送り付けます。

---

#◆Verify
**このフェーズではセキュリティキーは使いません**

ゲボ子さんの上司：毛保川はゲボ子さんからメールを受け取りました。添付を見ると署名付きzipです。これが本当に本物かどうか検証アプリでチェックします。

とあるデータのVerifyには
- 署名
- 署名した人の公開鍵
が必要になります。

検証は[GeboSigVerify](https://github.com/gebogebogebo/GeboSig/tree/master/Source/GeboSigVerify)というアプリで行います。

###検証アプリ処理
- zipからファイルと署名データを取り出す
- 証明書から公開鍵をGETする
- Verifyする
- memo:OpenSSLでVerify

####zipからファイルと署名データを取り出す
.netFrameworkのSystem.IO.Compressionで簡単です。
- ディスクにワークファイルを残したくないのでstreamでやってます。

```CS:zipからデータを取り出す
private bool getVerifyFileandSig(string zip,out byte[] target, out byte[] sig)
{
    target = null;
    sig = null;
    using (System.IO.Compression.ZipArchive archive = System.IO.Compression.ZipFile.OpenRead(zip)) {
        if( archive.Entries.Count != 2) {
            return false;
        }

        foreach (System.IO.Compression.ZipArchiveEntry entry in archive.Entries) {
            if (entry.Name == "sig.sig") {
                sig = new byte[entry.Length];
                using (Stream stream = entry.Open()) {
                    var result = stream.Read(sig, 0, (int)entry.Length);
                }
            } else {
                target = new byte[entry.Length];
                using (Stream stream = entry.Open()) {
                    var result = stream.Read(target, 0, (int)entry.Length);
                }
            }
        }
    }
    return true;
}

```

####証明書から公開鍵をGETする
ここで、[Administrator-Registerフェーズ]()で作成したゲボ子さんの証明書が必要になります。
社内のRepositoryからゲボ子さんの証明書を探してきてその中から公開鍵をGETします。

```CS:証明書から公開鍵をGETする
// certFileは証明書のパスファイル名です
private AsymmetricKeyParameter readPublicKeyfromCert(string certFile)
{
    Org.BouncyCastle.X509.X509Certificate readedCert;

    // 証明書の読み込み
    using (var reader = new StreamReader(certFile, Encoding.ASCII)) {
        var pemReader = new PemReader(reader);
        readedCert = (Org.BouncyCastle.X509.X509Certificate)pemReader.ReadObject();
    }

    var publicKey = readedCert.GetPublicKey();

    return (publicKey);
}
```

####Verifyする
いよいよVerifyです、署名は検証するためにあります。
VerifyはBouncy Castleでやります。
以下のメソッドでtrueとなれば検証OKです。
**Verifyによって間違いなくゲボ子さんが書いたものであり、改ざんされていないということが確認できるのです。**

*Bouncy Castleめちゃ楽*

```CS:Verify
private bool verify(byte[] target,byte[] sig,AsymmetricKeyParameter publicKey)
{
    ISigner signer = SignerUtilities.GetSigner("SHA1withRSA");
    signer.Init(false, publicKey);

    signer.BlockUpdate(target, 0, target.Length);
    var result = signer.VerifySignature(sig);

    return result;
}
```

####memo:OpenSSLでVerify
ちなみにVerifyはこのアプリでなくともできます。
openSSLでやる場合は以下のコマンドでVerifyできます。

```cmd:OpenSSLでVerify
openssl x509 -in TestUser.crt -pubkey -noout>public-key.pem
openssl dgst -sha1 -verify public-key.pem -signature sig.sig とっても大事な文書.pdf
```

###検証後の運用
毛保川の作業です。
- ゲボ子さんから送られてきた報告書を確認し、保管します。
- 電子署名がついているzipのまま保管すればOK

---

#おつかれさまでした
FIDO2セキュリティキーに署名の機能を追加することによって、認証と署名ができるようになります。
認証の機能で社内への入退室をしたり、システムにログインし、署名の機能を使って社内文書の署名したりすることができますね。
生体認証もできるのでいい感じでは。

今回はかなり簡易的なやり方でやっているので、このような方法で作られた電子署名がどの程度信頼できるものなのか、ちょっとわかんないんですけど。
しかし署名まわりのいい勉強になりました。

おわり。