HTTPSのお勉強

どうも用語が散乱してて、分かりにくかった。
公開鍵、秘密鍵、共通鍵、サーバー証明書認証局、その他もろもろ。
1つにつなげてみたい。

HTTPSとは

HTTPSとは、SSLという公開鍵暗号方式PKI)でサーバー/クライアント間の通信内容を暗号化する通信方式のこと。
または、SSLという公開鍵暗号方式PKIと共通鍵でサーバー/クライアント間の通信内容を暗号化する通信方式のこと。
後者だと思うけど、はっきりそうだと言っている文章が見つからない。

SSLによる暗号化通信

  1. サーバー側にSSLの公開鍵と秘密鍵を置く
  2. サーバーはクライアントに公開鍵を送る
  3. クライアントは送信内容を公開鍵で暗号化する
  4. サーバーはクライアントからの送信内容を秘密鍵で復号化する

これで片方向、つまりクライアントからサーバーへの送信内容を暗号化できる。
サーバーからクライアントへの送信内容は暗号化できない。
なぜなら、公開鍵は誰でも手に入れられるから、いくらサーバーが秘密鍵で暗号化したところで誰でも復号できてしまうし、サーバーが公開鍵で暗号化したって、サーバー以外に誰も復号できなくて、意味がない。

双方向、つまりサーバーからクライアントへの送信内容暗号化するには、共通鍵も使う必要がある。(クライアントも公開鍵と秘密鍵を作って、公開鍵を送ったらいいようなもんだけど、そういうことはあまりしない様子)
それは次のように行なわれる。

  1. サーバー側にSSLの公開鍵と秘密鍵を置く
  2. サーバーはクライアントに公開鍵を送る
  3. クライアントは共通鍵を作る
  4. クライアントは共通鍵を公開鍵で暗号化する
  5. サーバーはクライアントから送られた共通鍵秘密鍵で復号化する
  6. これ以降、サーバーとクライアントは送信内容を共通鍵で暗号化し、受信内容を共通鍵で復号化する

共通鍵を使う理由として、よく「秘密鍵での復号化は負荷が高いので、負荷の低い共通鍵で通信する」と言われているが、共通鍵はなにもそれだけのために使うわけじゃなくて、共通鍵によって初めて双方向の送信内容を暗号化できるようになる。それが書いていない文章があって、とても混乱した。だって、それじゃあ、僕の口座を表示した瞬間残高がダダ漏れってことになる。

実在証明のためにSSLを利用する

ここから、暗号の話というよりは、社会的な信頼関係の話になる。

通信内容を内緒にするには、あれでよかった。
でも、通信先のサーバーが信用できるとは限らない。よく似せられたフィッシングサイトかもしれない。
そこで、SSLを使って、そのサーバーが信用できる主体に運営されているか確かめるための仕組みができた。これが実在証明といわれている。

実在証明にはサーバー証明書を使う。
サーバー証明書は、サーバーの公開鍵のメッセージダイジェストとサーバー運営者の情報に認証局が署名したものといわれているが、認証局とは?署名とは?

認証局は、要するになんだか知らないけど、とてつもなく信用できるすっごい主体のこと。ベリサインとか、そういうの。国営もありえる。
署名は、要するに暗号化のこと。いきなり署名とか言われても困る。

認証局は自分用のSSL公開鍵/秘密鍵を持っていて、我々を信用するにふさわしい人物/組織なのか審査したうえで問題なければ、我々のサーバーの公開鍵のメッセージダイジェスト我々の情報をまとめて認証局秘密鍵で暗号化してくれる。この暗号化でできたファイルがサーバー証明書だ。
三者サーバー証明書が渡らない限り、すっごく信用できる認証局の公開鍵で復号したメッセージダイジェストと同じメッセージダイジェストの公開鍵を持っているサーバーは信用していいことにするという仮定が実在証明ということみたい。
ちまたのブラウザには認証局の公開鍵(よく認証局証明書とか言われる)が初めから組み込まれていて、いちいち探さなくていい。

本当は誰でも認証局になれるし、その認証局の公開鍵をあとからブラウザにインストールすることもできるけど、おれが認証局だ!と言ったところで、信用してもらえるかどうかはまた別の話。

実在証明の手続きをまとめると、次のようになる。

  1. サーバー運営者は、SSLの公開鍵と秘密鍵を用意する
  2. サーバー運営者は認証局公開鍵のメッセージダイジェストとサーバー運営者の情報(個人情報とか会社情報)を預けて、自分を審査するように頼む
  3. 認証局はサーバー運営者が信用するにふさわしい人物/組織なのか審査して、問題なければ次に進む
  4. 認証局サーバーの運営者から預かった公開鍵のメッセージダイジェストとサーバー運営者の情報をまとめて認証局秘密鍵で暗号化して、サーバー証明書にする。
  5. 認証局は、サーバー証明書すっごく厳重な郵便かなにかでサーバー運営者に渡す
  6. サーバー運営者は、サーバーにサーバー証明書と公開鍵と秘密鍵を置く
  7. クライアントがサーバーにアクセスする
  8. サーバーはクライアントにサーバー証明書と公開鍵を送る
  9. クライアントはサーバーの公開鍵のメッセージダイジェストを作る
  10. クライアントはあらかじめ持っていた認証局の公開鍵サーバー証明書を復号する
  11. サーバー証明書を復号して得たメッセージダイジェストが、サーバーから送られてきた公開鍵で作ったメッセージダイジェストと一致すれば、クライアントはそのサーバーを信用していいと判断する

あとはSSLによる暗号化通信だ。

  1. クライアントは共通鍵を作る
  2. クライアントはサーバーの公開鍵で共通鍵を暗号化して、サーバーに送る
  3. サーバーは共通鍵を秘密鍵で復号する
  4. 以降、共通鍵で双方向の暗号化通信をする

というわけで、HTTPSというか、むしろSSLのお勉強をした。
SSLは通信の暗号化と、実在証明に使われているのだった。

CSRF対策がないフォームへの投稿は、投稿者を特定できなくて困る

投稿内容が犯行予告だったりすると困る。(最近ニュースになってる一件がCSRFがらみなのかは知らない)
ちょっと調べて考えてみたところ、条件がよくても投稿可能だったひとを絞り込めるのは2人までで、特定は無理じゃないか?という結論に至った。

  1. 投稿を実行した当時に、投稿に使われたIPアドレスを利用可能だったひと。
  2. 投稿時のリファラーがフォームと異なるホストを指していれば、そのホストの持ち主。

リファラーのホストにXSSCSRFページが仕込まれていたら?とか、無線LANをただ乗りされたら?とか、考え始めるときりがないけど「CSRF対策がないフォームへの投稿者を特定できるか?」という問いのためには、この2人について考えれば十分だと思う。

リファラーがあるなら2で決まりじゃん?と思うけど、決まらないんだなこれが。
リファラーを根拠に投稿者を特定しようとする限り、手書きでウソのリファラーをつけたHTTPヘッダをsocketで送れば、ウソのリファラーが指すURIの持ち主を投稿者に仕立てられてしまう。

この点をはっきりさせるには、どうすればいいんだろう。

  1. 法令で、レンタルサーバー事業者に、ユーザーのドキュメントルート配下の全ファイルについて、一定期間さかのぼれるSubversion的な記録を義務づける。
  2. 法令で、ISPに個人回線のInbound Port 80 Blockingを義務づける。

決定打って思いつかないけど、こういうことって警察庁総務省のえらいひとが考えてそうだなあ。
残念だけど、インターネットってもう少し不自由でないといけないのかも。

VirtualBoxはホストのスリープOK!?

「は」という割に他のエミュレーター知らないけど…。
ゲストOSの更新中に出かける時間がきて、壊れてもいいや!えい!とホストをスリープ。再開すると、何事もなくゲストOSの更新が進んで終わった。その後、使用可能。
「スリープ」だから当たり前なんだけど、WindowsME世代なもんで、こういうことをすると直感的になぜか壊れる気がするんだよなあ…。

vimのメモ

愛用のvimrc

前にも書いたけど、1ページにまとめたい。

colorscheme delek
nohlsearch
syntax on
set nolist
set nocursorline
set guicursor=a:blinkon0
set guifont=Osaka−等幅:h12
set lines=46
set columns=80

set nobackup

set fileformat=unix

set laststatus=2
set statusline=%<%f\ %m%r%h%w%{'['.(&fenc!=''?&fenc:&enc).']['.&ff.']'}%=%l,%c%V%8P

set tabstop=2 expandtab shiftwidth=2
set autoindent

autocmd FileType python setl textwidth=80
autocmd FileType python setl smartindent cinwords=if,elif,else,for,while,try,except,finally,def,class

autocmd FileType html setl autoindent
autocmd FileType html setl smartindent

qbuf.vim

qbuf.vimは、複数のファイルを開いているとき、編集するファイルをホットキーで切り替えられるようにするプラグイン

ホットキー>jkでファイル選択>Enter

ここでダウンロード
http://www.vim.org/scripts/script.php?script_id=1910

~/.vim/plugins/qbuf.vimに置く。
デフォルトのホットキーはF4だけど、好きなキーを設定できる。
自分はセミコロンを割り当てている。

let g:qb_hotkey = ";"

編集中のスクリプトを実行するホットキー

どこで見つけたんだか、vimrcに次を追記するとCtrl+Pで編集中のスクリプトを実行できる。

function! s:Exec()
  exe "!" . &ft . " %"
:endfunction
command! Exec call <SID>Exec()
map <silent> <C-P> :call <SID>Exec()<CR>

さくらのレンタルサーバーでvimを使う。

インストール

今まで見た中で最善のインストール手順。
http://greenleaf.sakuraweb.com/index.php?vim%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%20-%E3%81%95%E3%81%8F%E3%82%89%E3%81%AE%E3%83%AC%E3%83%B3%E3%82%BF%E3%83%AB%E3%82%B5%E3%83%BC%E3%83%90%E7%B7%A8-

wget ftp://ftp.vim.org/pub/vim/unix/vim-7.2.tar.bz2
wget ftp://ftp.vim.org/pub/vim/extra/vim-7.2-extra.tar.gz
wget ftp://ftp.vim.org/pub/vim/extra/vim-7.2-lang.tar.gz
tar jxvf vim-7.2.tar.bz2
tar zxvf vim-7.2-extra.tar.gz
tar zxvf vim-7.2-lang.tar.gz
cd vim72
mkdir patch
cd patch
curl -O 'ftp://ftp.vim.org/pub/vim/patches/7.2/7.2.[001-446]'
cd ..
cat patch/7.2.* | patch -p0
./configure --enable-multibyte --enable-xim --enable-fontset --with-features=big --prefix=$HOME/vim
make; make install

$HOME/vim/bin/vimにパスを通せばOK。

バックスペースやCtrl+Hが効かないとき

どこで見つけたんだか、.vimrcに次を追加するといい。

set backspace=indent,eol,start

Pythonでアクセスカウンターを作る

なぜかカウントが1に戻ってしまったり、キリ番報告が3人もいるとかいうミステリアスなカウンターを作らないためには、排他制御のおさらいが必要だった。

わりとマトモなカウンターになったと思う。

ものすごく分かりやすかった排他制御の解説。
CGIやDBのロックと同時実行制御: CANO-Lab
http://jn.swee.to/cano/lock/index.shtml

access_counter.cgi

#!/usr/local/bin/python
#coding: utf-8

import fcntl, os, sys, time

COUNTER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "counter.txt")

def counter():
  lock_ex = False
  fd_open = False
  try:
    fd = os.open(COUNTER, os.O_CREAT|os.O_RDWR, 0755)
    fd_open = True
    for i in range(100):
      try:
        fcntl.flock(fd, fcntl.LOCK_EX|fcntl.LOCK_NB)
        lock_ex = True
      except IOError:
        # IOError: ロックを取得できなかった
        time.sleep(0.1)
        continue
      else:
        break
    if not lock_ex:
      sys.stderr.write("access_counter.cgi: File lock timeout.")
      return "File lock timeout."
    eof = os.lseek(fd, 0, os.SEEK_END)
    os.lseek(fd, 0, os.SEEK_SET)
    count = os.read(fd, eof)
    try:
      count = str(int(count) + 1)
    except ValueError:
      count = "1"
    os.lseek(fd, 0, os.SEEK_SET)
    os.ftruncate(fd, len(count))
    os.write(fd, count)
    os.fsync(fd)
  except AttributeError:
    # AttributeError: OSの制約で利用できない関数や定数があった
    return "Your environment not supported."
  finally:
    if lock_ex:
      fcntl.flock(fd, fcntl.LOCK_UN)
    if fd_open:
      os.close(fd)
  return count

if __name__ == "__main__":
  sys.stdout.write("Content-Type: text/html\n\n%s\n" % counter())

test_main.py
完全に同時とはいかないけど、コンテキストスイッチの影響を確かめるには、これでいいと思う。

#!/usr/local/bin/python
#coding: utf-8

import os, time
from subprocess import *

execute = os.path.join(os.path.dirname(os.path.abspath(__file__)), "access_counter.py")
total_start = time.time()
timeout = 0
for i in range(10000):
  rap_start = time.time()
  childs = [Popen(execute, stdin=PIPE, stdout=PIPE, stderr=PIPE) for ii in range(150)]
  popen_done = time.time()
  for p in childs:
    p.wait()
    (out, err) = p.communicate()
    if err:
      print err
      timeout = timeout + 1
  rap_done = time.time()
  print "%d rap done, childs born=%f, rap time=%f" % (i, popen_done - rap_start, rap_done - rap_start)
total_done = time.time()
print "total time %f" % (total_done - total_start)
print "timeout = %d" % timeout

test_sub.py

#!/usr/local/bin/python
#coding: utf-8

import os, time
from subprocess import *

execute = os.path.join(os.path.dirname(os.path.abspath(__file__)), "access_counter.py")
total_start = time.time()
timeout = 0
for i in xrange(500000):
  rap_start = time.time()
  p = Popen(execute, stdin=PIPE, stdout=PIPE, stderr=PIPE)
  p.wait()
  (out, err) = p.communicate()
  if err:
    print err
    timeout = timeout + 1
  rap_done = time.time()
  print "%d rap, time=%f" % (i, rap_done - rap_start)
total_done = time.time()
print "total time %f" % (total_done - total_start)
print "timeout = %d" % timeout

テスト方法

端末Aで次を実行
$ python test_main.py > main.log

その後まもなく、端末Bで次を実行
$ python test_sub.py > sub.log

mainで150万回、subで50万回、合計200万回カウンターを回す。

main.log、sub.logに記録されたtimeoutの数と、counter.txtの数を合わせて200万になればOK。

テスト結果

counter.txt
  1999703
main.log
  timeout = 295
sub.log
  timeout = 2

1999703 + 295 + 2 = 2000000

main.log, sub.logともにタイムアウトしたと思える実行時間が記録されていた。

>>> import pprint
>>> lines = open("main.log").readlines()
>>> raptimes = []
>>> for l in lines:
>>>   if not l.startswith("access_counter.cgi"):
>>>     raptimes.append(float(l.split(",")[2].strip().split("=")[1]))
>>> raptimes.sort()
>>> pprint(raptimes[-10:])
[6.8269399999999996,
 6.8799440000000001,
 7.1228569999999998,
 7.3894970000000004,
 7.6184669999999999,
 8.3026210000000003,
 9.5717949999999998,
 10.554797000000001,
 10.622182,
 10.824996000000001]
    
>>> times = [] >>> for l in lines: >>> if not l.startswith("access_counter.cgi"): >>> times.append(float(l.strip().split("=")[1])) >>> times.sort() >>> pprint(times[-10:]) [5.1106069999999999, 5.1604809999999999, 5.2510870000000001, 5.4495550000000001, 5.643491, 6.1125689999999997, 7.9487379999999996, 9.470364, 10.060624000000001, 10.087431]

Ubuntu端末のウィンドウサイズ・拡大・縮小を保存する。

端末を開くたびに字が小さいのはかなわん!
どうにも端末のGUIからそういう設定ができなくて、しょうがなく/usr/binにスクリプトを置いた。
保存するっていうか…。

#!/bin/sh

gnome-terminal --geometry=80x46 --zoom=1.5

Pygmentsに好きなスタイルを追加する。

こっちにも書いたけど、Tracに限った話でもなさそうだし、記事一覧に出なくて見失いそうなので、改めて別記事にしておく。

ここではhonzaさんのvim2pygmentsを使って、気に入っているVimのカラースキームを追加してみた。
vim2pygments.tar.gzのダウンロードページはここ

tar zxvf honza-vim2pygments-19a9339.tar.gz
cd honza-vim2pygments-19a9339
cp $HOME/usr/local/share/vim/vim73/colors/desert.vim ./
python vimpygments.py desert.vim > vim_desert.py
cp vim_desert.py path/to/site-packages/Pygments-1.5-py2.7.egg/pygments/styles/

エディターでpath/to/site-packages/Pygments-1.5-py2.7.egg/pygments/styles/__init__.pyを開く。

#: Maps style names to 'submodule::classname'.
STYLE_MAP = {
  ...
  "Vim_Desert": "vim_desert::DesertStyle", # この行を追加する。vim_desert.pyにDesertStyleというクラスがあるってこと。
}