Hatena::ブログ(Diary)

あどけない話

2017-09-19

PatternSynonymsのススメ

PatternSynonymsは、その名の通り、パターンの別名である。GHC 7.8.1 で導入された。GHC 7系のPatternSynonymsは、モジュール内に閉じて入れば何の問題もなかったが、モジュールの外へexportする際は、patternキーワードが必要であり、構成子らしくなかった。

{-# LANGUAGE PatternSynonyms #-}

module A (Foo, pattern Zero) where

newtype Foo = Foo Int

pattern Zero :: Foo
pattern Zero = Foo 0

GHC 8 からは、patternキーワードが不要となり、構成子らしくなった。

{-# LANGUAGE PatternSynonyms #-}

module A (Foo(Zero)) where

newtype Foo = Foo Int

pattern Zero :: Foo
pattern Zero = Foo 0

PatternSynonymsの使いどころ

(ビット幅が決まっている)数値が何らかの意味を持つような問題を考える。たとえば、

0A
1B
2C
その他予約

のような対応があった場合、Haskell では以下のようなコードを書くことが多いだろう。

data Foo = A | B | C deriving (Show,Eq,Ord,Enum,Bounded)

簡潔でいいのだが、このコードには以下のような問題がある。

  • 「その他」の数値が発生しうる場合にはどうするのか?

以下のように構成子を増やすと、Enum を導出できなくなる。

data Foo = A | B | C | Other Int deriving (Show,Eq,Ord)

他にも問題がある。

  • 値が0から始まらない場合どうするのか?
  • 値が連続してない場合どうするのか?

という訳で、この方法は筋が悪い。そこで、PatternSynonymsの登場である。

module A (Foo(A,B,C),fromFoo,toFoo) where

newtype Foo = Foo {
    fromFoo :: Int
  } deriving (Eq)

toFoo :: Int -> Foo
toFoo = Foo

pattern A :: Foo
pattern A = Foo 0

pattern B :: Foo
pattern B = Foo 1

pattern C :: Foo
pattern C = Foo 2

instance Show Foo where
    show A = "A"
    show B = "B"
    show C = "C"
    show x = "Foo " ++ (show $ fromFoo x)

少しコード量は増えるが、問題に柔軟に対応できるのでストレスがない。

2017-09-08

DNSの書式

何度読んでもよく分からないDNS関連のRFCを自分なりに読みやすいようにまとめる私的メモ。

ちなみに、僕は Haskell DNS ライブラリの作者で今も精力的に開発中。

RFC 1035

RFC 1035で定められている構造をトップダウン的に列挙する:

メッセージ全体
    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+

「問い合わせ」も「返答」も同じ形。ヘッダのQAフラグで区別される。

「返答」には「問い合わせ」のQuestionがコピーされる。

後述のように、Questionだけ独自の書式で、Answer、Authority、Additional は同じ書式。


ヘッダ
                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

基本的にUDP通信するので、IDでどの「問い合わせ」に対する「応答」なのか判断する。つまり、「問い合わせ」のIDが、応答にコピーされる。

XYCOUNTは、引き続くそれぞれのセクションに何個エントリがあるかを示す。


Question
                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                     QNAME                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QTYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QCLASS                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Resource record

Answer、Authority、Additional 用の書式。

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
RDATAの例 (A)
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ADDRESS                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

IPv4なので4バイト。

RDATAの例 (SOA)
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    /                     MNAME                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    /                     RNAME                     /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    SERIAL                     |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    REFRESH                    |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     RETRY                     |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    EXPIRE                     |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    MINIMUM                    |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

END0 (RFC 6891)

OPT RR

ヘッダを拡張する目的で、OPT RR が定義されている。OPT RR は Additional セクションに1つだけ格納される。

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                  NAME == 0                    /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |               TYPE == OPT (41)                |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                UPD payload size               |  was CLASS
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |     EXTENDED-RCODE    |       VERSION         |  was TTL
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |DO|                  Z                         |  was TTL
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

CLASSTTL の意味が変わっている。

昔のCLASSには、問い合わせ側の UDP バッファサイズが入る。

昔のTTLは、ヘッダのRCODEやフラグが含まれている部分を拡張するために使う。

ヘッダ中のRCODEとEXTENDED-RCODEで拡張RCODEを構成する。EXTENDED-RCODEが上位8ビット、RCODEが下位4ビットで、合計12ビット。RRだけからは、拡張RCODEが得られない恐ろしい仕様。

VERSIONは0、すなわちEND0。

D0 は DNSEC OK ビット。Zは予約ビット。

OPT RDATA

RDATAに入るオプションの書式:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                 OPTION-CODE                   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                OPTION-LENGTH                  |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                 OPTION-DATA                   /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
オプションの例

RFC 7871でClient Subnetが定義されている:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                 OPTION-CODE (8)               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                 OPTION-LENGTH                 |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     FAMILY                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    | SOURCE PREFIX-LENGTH  | SCOPE PREFIX-LENGTH   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   ADDRESS...                  /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

2017-07-20

TLS 1.3 開発日記 その20 TLS 1.3 ID21

TLS 1.3 ID21に追従した。

  • Add a per-ticket nonce so that each ticket is associated with a different PSK.

NewSessionTicket に ticket_nounce が増えた。

struct {
    uint32 ticket_lifetime;
    uint32 ticket_age_add;
    opaque ticket_nonce<1..255>;
    opaque ticket<1..2^16-1>;
    Extension extensions<0..2^16-2>;
} NewSessionTicket;

ID 20 までは、key schedule でいうところの PSK の値は、resumption_master_secret そのものだった。ID 21 では、以下のように算出するようになった。

PSK = HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length)

セッションで複数回NewSessionTicketが発行される場合は、ticket_nounceはユニークな値でなければならない。それを守れば、チケットごとにユニークなPSKの値が生成される。ID 20の仕様に対する明らかな攻撃方法は発見されていないが、ID 21の方が直感的に安全だろうと思われている。

セッション1数回しかNewSessionTicketが発行されない場合は、固定の文字列でもよい。(空文字列も許容するよう使用を変更すべき?)

なお、ID 21の HKDF-Expand-Label は曖昧になってしまったので、そのまま実装すると他の実装とは通信できない。具体的には、HKDF-Expand-Label は以下のように定められている。

HKDF-Expand-Label(Secret, Label, HashValue, Length) = HKDF-Expand(Secret, HkdfLabel, Length)

struct {
    uint16 length = Length;
    opaque label<7..255> = "tls13 " + Label;
    opaque hash_value<0..255> = HashValue;
} HkdfLabel;

ID 20 までは、HashValue の部分は、本当にハッシュ値か空文字列しか取らなかった。空文字列は、特殊なハッシュ値だと解釈されていた。HashValue の長さと、HKDF-Expand-Label の出力の長さを表す Length は、たまたま一緒であった。

今回、ticket_nounce も取るようになったので、その前提が崩れたことを OpenSLL の担当者が発見した。OpenSSLでは、上記の仕様を以下のように解釈して、実装している。

HKDF-Expand-Label(Secret, Label, Value, OutputLength) = HKDF-Expand(Secret, HkdfLabel, OutputLength)

struct {
    uint16 length = OutputLength;
    opaque label<7..255> = "tls13 " + Label; // フィールドの長さが先頭に付く
    opaque value<0..255> = Value; // フィールドの長さが先頭に付く
} HkdfLabel;

picotls と Haskell tls も、OpenSSLの解釈を採用している。

2017-05-30

TLS 1.3 開発日記 その19 OpenSSL

OpenSSLで、TLS 1.3を使う方法の覚書き。以下が参考になる。

ビルド

OpenSSL が現在サポートしているのは draft 20。そのソースの取り出し方はこう:

% git clone https://github.com/openssl/openssl.git

draft 18のソースの取り出し方はこう:

% git clone https://github.com/openssl/openssl.git -b tls1.3-draft-18 openssl-18

ビルドの仕方はこう:

% cd openssl
% ./config enable-tls1_3
% make
% make test

OpenSSL サーバ

s_server の使い方:

% cd util
% ./opensslwrap.sh s_server -accept 13443 -www -key $DIR/key.pem -cert $DIR/certificate.pem
  • ORTT を受けるには -early_data が必要。現時点で、-www とは両立しない。

OpenSSL クライアント

Full nego:

./opensslwrap.sh s_client -debug -connect 127.0.0.1:13443

HRR:

  • curves の先頭が keyshare となる。
./opensslwrap.sh s_client -debug -connect 127.0.0.1:13443 -curves P-521:P-256

PSK:

./opensslwrap.sh s_client -debug -connect 127.0.0.1:13443 -sess_out ticket
./opensslwrap.sh s_client -debug -connect 127.0.0.1:13443 -sess_in ticket

0RTT:

./opensslwrap.sh s_client -debug -connect 127.0.0.1:13443 -sess_out ticket
./opensslwrap.sh s_client -debug -connect 127.0.0.1:13443 -sess_in ticket -early_data ~/early-data.txt

2017-05-29

TLS 1.3 開発日記 その18 TLS 1.3 ID20

TLS 1.3 ID20に追従した。

やったのは、これだけ。

  • Shorten labels for HKDF-Expand-Label so that we can fit within one compression block

つまり、ラベルの文字列を変えただけ。現在、OpenSSL相互試験中。

ちなみに、Haskell tls ライブラリTLS 1.3 対応状況は、issue 167 にまとめてある。

追記:

サーバが early data を受け入れなかった場合、EndOfEarlyData は送ってはならず、従って Client Finished の計算に含めてはならない。