このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
次に、VNC 認証をハンドラ化する。
まあ、以前の実装をそのまま移すだけなので手間ではない。
これも好きなパスワードを指定できるように汎用化しよう。
========== VNCAuthenticationHandler.java ==========
package jp.loafer.rfb.server;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import jp.loafer.rfb.RFBContext;
import jp.loafer.rfb.RFBHandler;
import jp.loafer.rfb.io.RFBInputStream;
import jp.loafer.rfb.io.RFBOutputStream;
import jp.loafer.rfb.message.SecurityResultMessage;
/**
* サーバ側の VNC 認証処理。
* @author kes
*/
public class VNCAuthenticationHandler implements SecurityHandler {
/**
* {@link VNCAuthenticationHandler} のインスタンスを作成。
* @param password 認証用のパスワード。最大 8 文字までしか利用されない。
*/
public VNCAuthenticationHandler(String password) {
this.password = password;
}
/**
* @see SecurityHandler#getType()
*/
public int getType() {
return SecurityHandler.VNC_AUTHENTICATION;
}
/**
* @see RFBHandler#execute(RFBContext, RFBInputStream, RFBOutputStream)
*/
public void execute(RFBContext context, RFBInputStream in,
RFBOutputStream out) throws IOException {
// ランダムな 16 バイトチャレンジを生成
byte[] challenge = new byte[16];
{
SecureRandom r = new SecureRandom();
r.nextBytes(challenge);
}
// パスワード照合用のハッシュを作成
byte[] hash;
try {
// ビットの順番を入れ替えながら byte に格納
byte[] secret = new byte[8];
for (int i = 0; i < 8 && i < password.length(); ++i) {
// US-ASCII へ
int code = password.charAt(i);
// 下位 8 ビットを左右反転
code = (code >>> 4) & 0x0f | (code & 0x0f) << 4; // 45670123
code = (code >>> 2) & 0x33 | (code & 0x33) << 2; // 67452301
code = (code >>> 1) & 0x55 | (code & 0x55) << 1; // 76543210
secret[i] = (byte)code;
}
Cipher c = Cipher.getInstance("DES/ECB/NoPadding");
SecretKeySpec key = new SecretKeySpec(secret, "DES");
c.init(Cipher.ENCRYPT_MODE, key);
hash = c.doFinal(challenge);
} catch (Exception e) {
e.printStackTrace();
throw new IOException(e);
}
// チャレンジを送信
out.write(challenge);
out.flush();
// レスポンスを受信
byte[] response = new byte[16];
in.readFully(response);
// 比較
boolean succeeded = Arrays.equals(response, hash);
// SecurityResult で可否を返す
SecurityResultMessage result;
if (!succeeded) {
result = new SecurityResultMessage(
SecurityResultMessage.STATUS_FAILED,
"VNC Authentication: Invalid password.");
out.writeMessage(context, result);
out.flush();
throw new IOException(result.getErrorMessage());
}
result = new SecurityResultMessage(SecurityResultMessage.STATUS_OK);
out.writeMessage(context, result);
out.flush();
}
private String password;
}
========== end of VNCAuthenticationHandler.java ==========
VNC 認証のパスワードをコンストラクタで受け取っているが、
本来はセキュリティ上、String で受け取るべきではない。
String は定数なのでデータの書き換えができないため、
String インスタンスがスコープを離れても、
ガベッジコレクションが発生するまでは、
文字列はプロセスメモリのどこかに残ってしまう。
また、VM の実装によっては、String は特殊な領域に
インターン(拘束)されたまま再利用される可能性もある。
つまりパスワードがメモリ上に残ってしまう危険があるのだ。
より安全にするためにはパスワードを char[] 等で受け取り、
dispose メソッド等(最悪ファイナライザ)を実装し、
そこで char[] 内に適当な文字を代入する措置を取ればよい。