2007 年 1 月 19 日 20 時 16 分

VNCAuthenticationHandler クラス


このアーカイブは同期化されません。 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[] 内に適当な文字を代入する措置を取ればよい。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.