Minecraft MODの署名方法

# はじめに
MinecraftのMODが読み込まれる際に、MODのJARファイルが未署名である旨のメッセージがログに出力される場合があります。
未署名であってもMODの動作に影響はありませんが、メッセージログを抑制したい方のために署名手順を記しておきます。

# 必要なツール
- Java Development Kit
- OpenSSL

# 証明書を作ろう
署名を行うには証明書が必要になります。
OpenSSLやWindows SDKの`makecert.exe`などで生成したり、認証局に申請してコードサイニング証明書を発行して貰ってもかまいませんが、このようにして入手したPKCS12形式のファイルがあり、フィンガープリントの値がわかっている場合はMODのソースコードへの記述まで飛ばして下さい。不明の場合はフィンガープリントの確認まで飛ばして下さい。

## JDKのツールで作成してみる
ここでは、JDKのツールを使った証明書の作成手順を記します。
認証局から発行されていない、いわゆる「オレオレ証明書」ですが、Minecraft Forge的には問題無いようです。

### 生成ツールの実行

```shell:
$ keytool -genkeypair -storetype pkcs12 -keystore <FILENAME>.p12 -alias <FRIENDLYNAME> -keyalg ec -keysize 384 -validity 3650
```

- `-storetype`は、証明書のファイル形式  
より汎用的な形式であるPKCS12で出力するには`pkcs12`を指定します。  
何も指定しなかった場合はJKS形式となりますが、キー生成後にワーニングが出力されます。  
また、後にOpenSSLで処理する必要があるため、ここでは必ず`pkcs12`にしてください。
- `-keystore`は、作成した証明書が保存されるファイルの名前を指定
- `-alias`は、JARファイルに追加される署名関係のファイルのベースとなる名前  
PKCS12形式ではフレンドリ名として表されます。
- `-keyalg`は、署名アルゴリズム  
`rsa`または`ec`が指定可能です。
- `-keysize`は、キーの長さ  
アルゴリズムによって指定できる値が異なります。  
`-keyalg rsa`の場合は`2048`や`4096`など2の乗数になります。  
`-keyalg ec`の場合は`256`と`384`、`521`のいずれかとなります。
- `validity`は、作成日から起算した有効期限の日数  
10年間なら`3650`とします。

### パスワードの指定
コマンドを実行すると、最初にパスワードの入力を求められます。
build.gradleのタスクを使用して署名を行わせる場合は、ここでのパスワードはコミットしても問題無い文字列にするのがいいでしょう。この場合、証明書ファイルは絶対に公開してはいけません。

### 各種情報の入力
次に証明書のコモンネームを設定しますが、これには複数のフィールドから成り立っています。
表示される順に追って説明します。

1. 姓名  
ニックネームとかツイッターアカウントの名前とかでかまいません。
* 組織単位名  
会社のそれを要求されるように見えますが、個人の場合は活動のジャンルの名称を入れてもいいでしょう。  
たとえば、今回作成する証明書はMODのJARファイルに署名するので`mcmod`にする、といった具合です。
* 組織名  
これも個人の場合は姓名と同じにするか、ウェブサイトのドメイン名などでかまいません。
* 都市名または地域名  
住所を知られたくない場合は適当な地域で。
* 都道府県名または州名は何ですか。  
4に同じく。
* 国コード  
日本ならJPになります。

国コードの入力が終わると`[いいえ]:`と訊かれます。
意味不明な質問ですが、これは`y`だけ入力して下さい。そうしないと最初からやり直しとなってしまいます。

### パスワードの指定
最後にパスワードを訊かれますが、最初のパスワードと同じで良ければそのままEnterキーを押して下さい。

## PKCS12形式にフレンドリ名が含まれているか確認する
前述した`keytool`を使用した場合はフレンドリ名が`-alias`で指定した文字列として含まれていますが、認証局から発行されたりOpenSSLで生成した場合はフレンドリ名が設定されていないことがあります。
また、この手順はPKCS12形式のパスワード変更にも応用できます。

## フレンドリ名を確認する
次のコマンドを実行して、Bag Attributesの下にfriendlyNameがあればOKです。

```shell:
$ openssl pkcs12 -in <FILENAME>.p12 -info -nokeys
```

## PKCS12形式にフレンドリ名を設定する
フレンドリ名が設定されていない場合は追加します。

①バックアップ

```shell:
$ mv <FILENAME>.p12 <FILENAME>.p12.bak
```
②PKCS12から証明書と鍵を取り出す

```shell:
$ openssl pkcs12 -in <FILENAME>.p12.bak -info -nodes -out <FILENAME>.txt
```
③取り出した証明書と鍵からフレンドリ名を設定しつつPKCS12を生成

```shell:
$ openssl pkcs12 -export -in <FILENAME>.txt -name <FRIENDLYNAME> -out <FILENAME>.p12
```
注意点として、パスワードは必ず6文字以上にしなければなりません。これは`jarsigner`の仕様で、これを満たさないパスワードはエラーになってしまうためです。

④後始末

```shell:
$ rm <FILENAME>.txt
```

## フィンガープリントの値を調べる
MinecraftのMODのJARファイルを署名付きで公開する場合、必要となってくるのがフィンガープリントの値です。
フィンガープリントとは、証明書をバイナリデータ(ASN-1形式)で表現したときにそのデータをハッシュ関数で処理した値のことです。
同じ証明書からは同じフィンガープリントの値が得られるので、期限が切れた等で作り直さない限りはここで調べた値を流用出来ます。
Minecraft ForgeではSHA-1ハッシュアルゴリズムでのフィンガープリントの値が要求されます。

### OpenSSLを使用する

```shell:
$ openssl pkcs12 -in <FILENAME>.p12 -info -nokeys|openssl x509 -fingerprint -noout
```

実行するとパスワードを入力後、フィンガープリントの値が「:」で区切られた16進数表記で出力されます。

### フィンガープリントの値をMODで記述する文字列にする
この表記から「:」を削ってつなげた後、大文字を小文字に書き直します。
これがMOD内に記述するフィンガープリントの値となります。

# MODのソースコードに記述しよう
`@Mod`アノテーションに`certificateFingerprint`を書き加え、その後にフィンガープリントの値を文字列として記述します。

```java:HogeMod.java
@Mod(modid = "hogemod",
    /*…中略…*/
    certificateFingerprint = "フィンガープリントの値")
public class HogeMod {
/*…中略…*/
}
```

## 署名が一致しないときの処理
必須ではありませんが、Forgeのイベントハンドラに署名が一致しなかった場合に呼ばれるものがあります。
`FMLPreInitializationEvent`や`FMLInitializationEvent`と同じように、`FMLFingerprintViolationEvent`の引数をもつメソッドを`@Mod.EventHandler`アノテーションを使い宣言します。

```java:HogeMod.java
@Mod.EventHandler
public void badSignature(FMLFingerprintViolationEvent event)
{
    System.err.println("署名が一致しません!オリジナルの配布ファイルではありません!");
}
```

記述が終わったらgradleでJARファイルを生成します。

# JARファイルに署名しよう
署名処理を行いますが、これにはコマンドを実行するのとgradleで行わせるの二つの方法があります。

## コマンドで署名する
```shell:
$ jarsigner -sigalg SHA256withECDSA -digestalg SHA-256 -tsa http://timestamp.digicert.com -keystore <FILENAME>.p12 <JARFILE> <FRIENDLYNAME>
```

- `-sigalg`は、署名アルゴリズム  
楕円曲線DSAを使用する場合は`SHA256withECDSA`とします。
- `-digestalg`は、ハッシュのアルゴリズム  
`-sigalg`と同じハッシュを指定します。この場合は`SHA-256`となります。
- `-tsa`は、タイムスタンプサーバーのURL  
無くても署名は出来ますが、証明書の有効期限が切れると署名が無効になってしまいます。  
タイムスタンプサーバーにはいろいろなサーバーがあります。  
SHA-256でタイムスタンプ署名を返してくれるサーバーはこれとか。  
http://sha256timestamp.ws.symantec.com/sha256/timestamp

署名とハッシュに使用するアルゴリズムですが、Javaが対応していれば何でもかまいません。
https://docs.oracle.com/javase/jp/9/docs/specs/security/standard-names.html
`-sigalg`はここのSignatureアルゴリズム、`-digestalg`はここのMessageDigestアルゴリズムから選択できます。

パスワードを尋ねられますので、正しいパスワードを指定すれば署名が完了します。

## Gradleで署名する
`build.gradle`の最後に次のコードを追加します。
`ant.signjar`に渡す各引数は前項の`jarsigner`と同じです。

```gradle:build.gradle
task signJar<<{
 ant.signjar(
  jar:jar.archivePath,
  alias:"<FRIENDLYNAME>",
  keystore:"<FILENAME>.p12",
  storepass:"<PASSWORD>",
  sigalg:"SHA256withECDSA",
  digestalg:"SHA-256",
  tsaurl:"http://timestamp.digicert.com"
 )
}

signJar.dependsOn reobfJar
assemble.dependsOn signJar
```

この`build.gradle`を使いビルドを実行すると署名済みJARファイルが生成されます。

# 動作確認をしてみよう
生成された署名済みJARファイルをmodsフォルダーに配置して、ForgeがインストールされたMinecraftを起動します。
タイトル画面に進んだら、`options.txt`と同じ階層にある`logs\debug.log`を開きます。

正常に署名が認識されていれば、このような文言が現れます。

```terminal:debug.log
[HH:MM:SS] [main/DEBUG] [FML]: Mod signature data
[HH:MM:SS] [main/DEBUG] [FML]:  	Valid Signatures:
…中略…
[HH:MM:SS] [main/DEBUG] [FML]: 		(フィンガープリントの値) <MOD ID>	(<MOD名>	<MODバージョン>)	<JARファイル名>
…中略…
```