転載元: http://www.geocities.co.jp/SiliconValley-SanJose/3377/asn1Body.html
感謝: manaslu_eiger さん
2004. 3.25:
-
ASN.1 デコード JavaScriptに、「OID 説明文付加」「Mozilla Firefox 0.8対応」「結果をクリップボードに送る」機能を追加。オフラインで使用する方法を追記。
-
2004. 3.22:
-
MS04-007について、読者の方からのご教鞭をうけ、誤記等を修正。ありがとうございました。
-
2004. 2.13:
-
MS04-007について、補足および誤記を少し修正。
-
2004. 2.11:
-
Windows の ASN.1 ライブラリに含まれる脆弱性
MS04-007について
少し追記。
-
2003.10. 2:
-
変換用 perl スクリプトのリファクタリング、および"Encapsulateされたデータ"について追記。
-
2003. 4. 2:
-
ASN.1 デコード JavaScriptが、BASE64 に対応しました。
■ASN.1 とは
ASN.1 は、「情報の構造を定義する」ための言語です。
Abstract
Syntax
Notation One =
抽象構文記法1 の略で、「アセンワン」または「エーエスエヌワン」と発音します。
ISO と
ITU-T
(別ウィンドウ)によって規定されました。
本来の目的は、PDU
(Protocol Data Unit ≒ プロトコルの内容)の定義で、コンピュータ間でやりとりするメッセージ
(プロトコル)の記述によく使用されています。
たとえば、「NBAP」「RRC」といった、FOMA等の第3世代携帯電話網
(3G)で用いられるプロトコルの定義にも、ASN.1 が使われています。
ただし ASN.1 は、機能的には、プロトコルにとどまらず、階層的なデータの表現に万能に使用できます。
ASN.1 によって構造を定義された情報を、「ASN.1 オブジェクト」と呼びます。
■ASN.1 オブジェクト
一般にコンピュータの世界で「オブジェクト」というと、何らかの情報や機能を持ったデータの固まりの事を指します。「オブジェクト」にはそれぞれ「型」というものがあり(これは人間が適当に決める)、「型」ごとに、「情報」の格納方法や「機能」を発動させる方法が決まっています。
ASN.1 オブジェクトも、「ASN.1 オブジェクト型」固有の情報や機能を保持する、オブジェクトの一種です。
ASN.1 は、情報の構造を、次の2つの属性によって定義します。逆に言うと、次の2つの属性を指定する事により、ASN.1 オブジェクトを定義できます。
- 情報の名前
- 情報の型
ASN.1 は、前節で述べたように「言語」ですから、ふつうの ASCII テキストでこれを記述できます。「ASN.1 のテキスト」と言った場合、この形式のテキストのことを指します。
↑は、
- Age という名前を持った ASN.1 オブジェクトは、INTEGER という型の情報を保持する
という意味を定義する ASN.1 です。ここで
::= は、
「左辺の値を、右辺に記述したデータ構造のシンボル(変数名のようなもの)として使用します」という意味を表す記号です。
ASN.1 の担当範囲は、「情報の構造を定義する」ことまでですから、実際に値を明記する事はしません。これは、ASN.1 を解釈する別な処理系の仕事です。
この「処理系」
(具体的には、プログラム)は、たとえば次のような順序で動作します。
- ASN.1 のテキストを、テキストファイルから読み込む
- ユーザが入力した値(数値、文字列など)を、キーボードなどから読み込む
- バイナリ変換規則(たとえば、後述のBER)に基づき、入力された値をバイナリに変換する。すなわち、バイナリオブジェクトに「値を格納する」。
ASN.1 オブジェクトの
型を定義すると、そのオブジェクトが保持できる
値の種類や数が、自動的に決まります。たとえば、「整数」という
型をもつASN.1 オブジェクトは、
50,
-126,
9999 といった
値しか保持できません。
型には、
INTEGER
(整数),
OCTET STRING
(オクテット列),
PrintableString
(文字列),
BOOLEAN
(真偽) といったものがあります。
また、このような
型の値を複数持たせることもできます。複数の
型の集合もまた
型です。たとえば次のようなものです。
SEQUENCE は、複数の値を持たせるための、特別な
型と考えて下さい。
Price ::= SEQUENCE { amount INTEGER scale PrintableString } |
↑は、
- Price という名前を持った ASN.1 オブジェクトは、amount という識別名のメンバと、scale という識別名のメンバを保持する
- amount という識別名のメンバは、INTEGER(整数)という型の情報を保持する
- scale という識別名のメンバは、PrintableString(文字列)という型の情報を保持する
という意味を定義する ASN.1 です。
複数の値を持たせる場合、それらを一意に識別するための
識別名を付加できます。
更に、他の ASN.1 オブジェクトを
型として使用することもできます。つまり、他の ASN.1 オブジェクトを値として保持する事ができる訳です(この場合、
型は、他の ASN.1 オブジェクトの
名前になります)。C 言語の構造体を想像すると分かりやすいでしょう。例えば次のようなものです。
IsFemale ::= BOOLEAN Price ::= SEQUENCE { amount INTEGER scale PrintableString } Person ::= SEQUENCE { age INTEGER gender IsFemale price Price } |
構造体と同様、ASN.1 オブジェクトの構造は、ユーザーが独自に決めることができます。
C 言語と異なるのは、情報をバイナリ列に変換できる所まで細かく定義しておかねばならない、という点です。情報の構造の定義と、ビットの並びを相互変換するのが、ASN.1 の責務なのです。
バイナリに即座に変換できる型を『プリミティブな型』と言います。
『プリミティブな型』には、
BOOLEAN
(真偽)、
INTEGER
(整数)、
ENUMERATED
(列挙)、
REAL
(実数)、
BIT STRING
(ビット列)、
OCTET STRING
(オクテット列)、
NULL
(ヌル)、
OBJECT IDENTIFIER
(オブジェクトID)、
SEQUENCE
(列挙)等があります。詳しくは
タグフィールドの節をご覧ください。
逆にプリミティブでない型とは、他の ASN.1 オブジェクト等、更に細分化が可能なものの事です。
■ASN.1 オブジェクトのインスタンス(実体)
ASN.1 で情報の構造を定義したら、次は、その定義に基づいた ASN.1 オブジェクトを実際に生成することができます。ここで、「生成する」とは、「具体的な値を持たせる」という意味です。値を埋め込まれた ASN.1 オブジェクトを、
「ASN.1 オブジェクトのインスタンス」といいます。
例えば、
ASN.1 |
→ |
インスタンス |
Age ::= INTEGER |
Age = 25 |
ASN.1 は、情報の構造を定義するだけで、具体的な値までは定義しません。ASN.1 を元に具体的な値を埋め込んでいくのは、その他のアプリケーションの仕事です。
インスタンスが、具体的にどのような形でコンピュータ内で格納されているかは、これまた処理系に依存します。
perl のハッシュや、C言語の構造体のような形で格納されているかもしれません。
ただしこれでは、他の処理系にインスタンスを渡したとき、不都合が生じるかもしれません。他の処理系では、別な方法でインスタンスを処理しているかもしれないからです。
そこで、インスタンスを表現する、共通の約束が考え出されました。これが、
BER と呼ばれるものです。次の章では、この
BER について述べます。
■変換プログラム(JavaScript)
ASN.1 オブジェクトのインスタンスをダンプする
JavaScript プログラムです。
30 03 02 01 ff といった16進数、または
MIIDcjCCAloCAQEwDQY...といった BASE64 で記述された ASN.1 オブジェクトのインスタンスを、
SEQUENCE { INTEGER 255 } のようにダンプします。
ご利用にあたっては、次の点にご注意下さい。
- 海外のユーザーの方にもご利用頂けるよう、メッセージは全て英語です。
- 再帰処理を多用しているため、あまり階層の深い ASN.1 をダンプすると、ブラウザが固まる場合があります。
- ソースコードの転載はご自由にどうぞ。
- 運用した結果、何らかの損害を被ったとしても、責任を負いかねます。
- 逆変換(ASN.1 → バイナリ)は、現在作成中です。
→それでは、どうぞご利用下さい!
■他に変換する方法は無いの?
perl によるコーディングが苦にならない方なら、
CPAN で公開されている perl モジュール が便利です。
このモジュールで、ASN.1 オブジェクトのダンプ
(テキストデータ)と、バイナリデータの相互変換が可能です。
■Windows 脆弱性 MS04-007
2004/2/13: 立川の A 様より、誤記2点と、『「長さフィールドの値」と、「長さフィールドから抽出した、値フィールドの長さ」の書き分けが曖昧である』点をご指摘いただきました。 ありがとうございました。
2004/3/22: 項番 5.~6. を、さかね様http://www.tanu.org/~sakane/
より頂いたご教鞭により、大幅に追記させて頂きました。「なぜ3を加えるのか?」というモヤモヤが解消し、大変嬉しく存じます。ありがとうございました。
米国時間の2004年2月10日に公開された、Windows の脆弱性
MS04-007は、Windows に組み込まれている ASN.1 処理ルーチンのバグによるものです。
この ASN.1 ルーチンに、特定のデータを送りつけることにより、バッファオーバーフローを引き起こし、その Windows 上で、任意のプログラムを実行できる、という物騒な物です。
ここでいう「データを送りつける」という行為は、ローカルシステム上からだけでなく、リモートからも可能です。なぜなら、このルーチンは、Kerberos、NTLM などの認証サブシステムや、SSL を使用するアプリケーション
(IIS や Internet Explorer、Outlook Express)が共通で使っているからです。
例えば IE は、https で他のサイトに接続するとき、相手のサーバから送られてくる電子証明書の解析を、この ASN.1 ルーチンを使って行います。よって、悪意あるサイトが、この脆弱性を利用した変な証明書を送りつけてくると、IE を走らせているパソコンがヤラれます。
逆に、IIS 上で SSL サイトを走らせている場合、クライアント認証の過程で、えげつない証明書を食わされてサーバが壊される危険性も孕んでいます。怖いですね。
この脆弱性の具体的な仕組みを説明します。
今回問題になっているのは、"MSASN1" というライブラリです。これは、
msasn1.dll
というダイナミックリンクライブラリ上に実装されています。
MSASN1 が、ASN.1 オブジェクトの「
長さフィールド」を処理する部分にバグがあります。「長さフィールド」に、
0xFF FF FF FD
、
0xFF FF FF FE
、
0xFF FF FF FF
の3つのうち、いずれかの値が含まれていると、ヒープメモリの上書きが起き、バッファオーバーフローを起こせるのです。
- MSASN1 は、内部に
ASN1BERDecLength
、ASN1BERDecCheck
、DecMemAlloc()
という、3つの関数(サブルーチン)を持っています。
ASN1BERDecLength
は、「長さフィールド」の値、つまり、値フィールドの長さを読み出す関数です。この読み出した値を、ASN1BERDecCheck
に渡します。
なお、渡す値は、長さフィールドが、「長さフィールドの長さ + 長さ」の形式になっていた場合でも、「値フィールドの長さ」に相当する値だけです。
ASN1BERDecCheck
は、「渡された値」が、「残りのバイナリデータの長さ」よりも長くないか調べます。長くなければ、その事をASN1BERDecLength
に返します。
ASN1BERDecLength
は、呼び出し元の関数に、「長さフィールド」から抽出した、「値フィールドの長さ」と、(ASN1BERDecCheck
が調べた)正常にデータが存在する旨を返します。
- 以上の結果を基に、
ASN1BERDecLength
を呼び出した関数は、次に、「値フィールド」の処理に移ります。もし、「値フィールド」を、適当なメモリ領域にコピーするような動作を行うのであれば(だいたいのアプリケーションは、そうします)、ASN1BERDecLength
から返ってきた値を、DecMemAlloc()
という、メモリ確保のための関数に渡します。コピー先のメモリ領域を確保するためです。
DecMemAlloc()
は、受け取った値を、4の倍数(=32ビットの倍数)に切り上げた後、4オクテットに収まるよう、切り捨てを行います(4オクテットは、32ビットマシンのメモリ空間の最大サイズ)
つまり、確保されるメモリサイズ(DecMemAlloc()
が確保できるメモリサイズ)をB
と置くと、常に、0≦B≦0xffffffff, B mod 4 = 0
です。
- 前項の、「4の倍数に切り上げ→4オクテットに収まるよう切り捨てをする」という操作の過程で、
DecMemAlloc()
は、「受け取った値に、3を加算する」という演算を実行します。3の加算と、論理演算を組み合わせて、「切り上げ + 切り捨て」がスマートに実現できるのです。
具体例を挙げます。
今、値B
に対し、「n
の倍数に切り上げ→n
オクテット幅に収まるように切り捨てる」(ただし、n
は2の累乗数)という演算を行いたい場合、
B = ( B + ( n - 1) ) & ~( n - 1 )
で OK です。if
文などを組み合わすより数段美しいですね。私も、初めて見たときは驚愕しました。
サンプルコード:
main(){ unsigned int x, y; for (x = 0xfffffff5; x != 0 && x <= 0xffffffff; x++) { y = (x + 3) & ~3; printf("%x\t%x\n", x, y); } }
32ビット環境での実行結果:
fffffff5 fffffff8 fffffff6 fffffff8 fffffff7 fffffff8 fffffff8 fffffff8 fffffff9 fffffffc fffffffa fffffffc fffffffb fffffffc fffffffc fffffffc fffffffd 0 fffffffe 0 ffffffff 0
- 上記で分かるとおり、「値フィールド」の長さとして、
0xFF FF FF FD
~0xFF FF FF FF
が来てしまうと、3 を加算された結果、4オクテットの範囲を桁あふれしてしまいます。そのあと、4オクテットに収まるように切り捨てられる訳ですから、確保されるメモリのサイズは、0オクテットになってしまいます。
DecMemAlloc()
は、メモリ確保が成功したか否かを、呼び出し元の関数に返します。成功していたら、呼び出し元の関数は、おもむろに、「値フィールド」を、このメモリ領域にコピーします。呼び出し元の関数は、ASN1BERDecLength
の戻り値しか知らないので、もともと「値フィールド」に入っていた、凄く長い値をコピーしてしまいます。
DecMemAlloc()
は、0オクテットしか確保していないにも関わらず、です。
- 以上の結果、確保されていないはずのメモリ領域に、「値フィールド」が上書きされます。これを使って、攻撃者は、バッファオーバーフローを起こせる訳ですね。
■概要
『プリミティブな型』
(BOOLEAN, INTEGER など)をビット列に変換する時に用いる規則を、BER
(Basic Encoding Rules) 呼びます。BER という統一された規則に基づく事で、異なる処理系間での互換性が保たれます。すなわち、どの処理系で生成した ASN.1 オブジェクトのインスタンスも、一貫した手順で扱えます。
また、CER
(Canonical Encoding Rules), DER
(Distinguished Encoding Rules) と呼ばれる規則もあります。これらは BER のサブセット
(部分集合、つまり『風俗』に対する『ソープ』のような関係)で、ある特定の種類の ASN.1 オブジェクトをバイナリ変換するとき、特に用いられます。が、違いはほとんどありません。詳しくは、
BER, CER, DER の違いで説明します。
さて、すべての ASN.1 オブジェクトは、最終的には全部プリミティブな型で定義されます。よって、すべて BER に基づいてバイナリに変換できる、という事になります。
BER に基づいて生成されたバイナリは、次の4つの規則に則ります。
- タグ、値(内容)の長さ、値という 3 つのフィールドを持つ。ただし、それらの間に区切り符号等は無い。※2
- 物理的なデータ長は、オクテット(マシンアーキテクチャによらず 1 オクテット = 8 ビット)単位となる。
- オクテットは、最上位のビットを[ビット8]または[MSB(Most Significant Bit)]とし、最下位のビットを[ビット1]または[LSB(Least Significant Bit)]とする。
- 送信の際は、MSB → LSB の順で行われる。
タグフィールドで、ASN.1 オブジェクトの型
(INTEGER, PrintableString など)を。
値の長さフィールドで
値フィールドが何オクテットあるかを表します。
値フィールドに、バイナリに変換された ASN.1 オブジェクトの値が入っています。
以上をまとめると、ASN.1 オブジェクトはプレゼンテーション層
(バイナリの状態)において、次のような形をしています。
ASN.1 オブジェクトをやり取りするには、このバイナリを隙間なく並べたバイナリ列の固まりを作り、相手に送れば良い訳です。
次の節では、各フィールドの詳細な変換規則を説明します。
-
※2
-
不定長のデータを扱える、不定形式と呼ばれる表記方法もあります。構成は、
タグ+
0x80+
値+
0x0000(終端)です。
■BER, CER, DER の違い
BER(Basic Encoding Rules) と CER(Canonical Encoding Rules), DER(Distinguished Encoding Rules) の相違点を述べます。
|
BER |
CER |
DER |
定義 |
X.209, ISO8825-1 |
未稿 |
X.509 (BERのサブセット) |
概要 |
ASN.1 オブジェクトの「値」を1オクテットで表現またはエンコードする方法を記述するときに使用される。 |
未稿 |
簡略化された BER。ASN.1 に一対一で対応したオクテット配列形式。 |
一般に、ASN.1 オブジェクトは、BER または DER のどちらかでエンコードされます。
ASN.1 オブジェクトの型を表すフィールドです。型とは、ASN.1 オブジェクトの持つ値の種類を指します。
すべての ASN.1 オブジェクトは、最終的には以下に書かれているものだけで定義しなければなりません。以下に書かれているものだけで定義すれば、最初の節で述べたように、バイナリに直接変換できるまで細かく定義できた事になります。
ASN.1 オブジェクトの処理系は、タグフィールドを見て、
値フィールドの処理方法、つまりどのようにして
値フィールドに格納されたバイナリを実値に戻せばよいか判断します。プログラム的に言えば、ここの値に応じて、
値フィールドをデコードするアルゴリズムを決めます。
『プリミティブな型』には、以下のものがあります。
タグ種が、型の種類。
タグ番号が、タグ(型)種を一意に決める ID 番号となります。
使いたい型が決まったら、その ID 、つまりタグ番号を、タグフィールドにセットします。
上の表を見ると、タグ番号はすべて1オクテットの数値なので、タグフィールドは1オクテットあれば充分に見えます。実際、ほとんどの場合1オクテットしか使われません。が、将来、数オクテット必要なタグが現れないとも限らないので、何オクテットでも大丈夫なようになっています。
また、タグ番号をそのまま格納すれば良い訳ではありません。タグフィールドには他にも、
クラスと、
構造化フラグという2つの情報が書き込まれます。
以上の話を下図にまとめて説明します。
タグフィールドのオクテット数は、タグ番号によって変わります。タグ番号が10進数で30
(16進数で 0x1e)以下の時は1オクテット。31
(16進数で 0x1f)以上の時は2オクテットかそれ以上になります。
タグ番号が30以下の時 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
クラス |
構造化 フラグ |
タグ番号 |
|
タグ番号が31以上の時 |
第1オクテット
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
クラス |
構造化 フラグ |
1 |
1 |
1 |
1 |
1 |
|
第2オクテット
|
… |
最終オクテット
|
|
↑これがタグ番号になる |
クラスは、オブジェクトの使用目的を表します。
8 |
7 |
使用目的 |
0 |
0 |
汎用。ISO, ITU-T で定義されたタグ型。上の表にある INTEGER などの型は、すべてこれに属する。 |
0 |
1 |
応用。 |
1 |
0 |
コンテキスト特定。目印に使うタグ型。たくさんの同型オブジェクトが同階層に並列して存在する場合(または存在する可能性がある場合)、処理するアプリケーションが迷うことなく目的のデータを取り出せるよう用意されている。この時アプリケーションは、そのオブジェクトの型(INTEGER, BOOLEANなど)は、改めて明示されるまでもなく分かっているはずなので、タグ番号の部分は、単なるシーケンス番号としての意味しか持たない。 |
1 |
1 |
プライベート。独自定義のタグ型である事を表す。 |
(2003/10/2追記)
コンテキスト特定クラスは、例えば次のような状況で使用されています。これは、X.509 公開鍵証明書の一部
(PrivateKeyUsagePeriod, 日本語では「私有鍵有効期間」という拡張領域)を抜粋したものです。
PrivateKeyUsagePeriod ::= SEQUENCE { notBefore [0] GeneralizedTime OPTIONAL, notAfter [1] GeneralizedTime OPTIONAL } |
PrivateKeyUsagePeriod
には、最大で2つの要素
(notBefore
とnotAfter
)が含まれている可能性がありますが、そのうちどれを含め、どれを含めないかは、この公開鍵証明書を作るときの方針により異なります。
2つとも含まれていれば、最初(1番目)にあるのが
notBefore
で、次(2番目)にあるのが
notAfter
であることは、
PrivateKeyUsagePeriod
の定義から、明白です。
(ちなみに、公開鍵証明書の定義は、RFC3280 http://www.ietf.org/rfc/rfc3280.txt )で細かく定義されています
しかし、もしどちらか1つしか含まれていなかったらどうなるでしょう?
notBefore
と
notAfter
は、どちらもまったく同じ型
(GeneralizedTime
)なので、ばっと見た目は、どちらが省略されているのか区別できなくなってしまいます。
そこで、これらのメンバは、
GeneralizedTime
といった
汎用クラスではなく、
コンテキスト特定クラスとして書いておきます。この場合、前述のように、タグ番号の領域には、シーケンシャル番号(ID番号と考えてよいです)が入るわけですが、この番号には、
notBefore
なら
0を。
notAfter
なら
1を使う訳です。すると、どちらか一方だけ省略した場合でも、そのシーケンシャル番号を見れば、どちらが書かれているか確実に分かります。
構造化フラグは、オブジェクトの構造を表します。
6 |
意味 |
0 |
単一型。オブジェクトが、1つの値だけを持っていることを表す。 |
1 |
構造型。オブジェクトが、複数の値を持っていることを表す。このオブジェクトの値フィールドは、更に別の ASN.1 オブジェクト(タグ番号+値の長さ+値)が記載されている。 |
以上すべて、符号ビットなしの整数値として処理します。
値フィールドが何オクテットあるか示すフィールドです。ASN.1 オブジェクトの処理系は、ここを見て、値フィールドを読み出します。
ASN.1 のバイナリストリームには、オブジェクト間に区切り文字などが無いので、この長さぶんのオクテットの次のオクテットから、次の ASN.1 オブジェクトが始まっているものとして処理されます。
値フィールドが 127 オクテット以下の場合と、128 オクテット以上の場合で、値の長さフィールドの形式は異なります。
それぞれを示したものが次の図です。
127 オクテット以下の時 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
値フィールドの長さ |
|
128 オクテット以上の時 |
第1オクテット
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
1 |
長さ部の長さ n |
|
第2オクテット
|
… |
最終オクテット
|
|
↑これが値フィールドの長さになる |
ASN.1 オブジェクトの値を格納するフィールドです。
値フィールドのバイナリ化規則は、タグ(型)種によって異なります。
ASN.1 処理系は、タグフィールドを読み、その結果に応じて値フィールドを解析します。
以下、型ごとにサンプルプログラムを交え、バイナリ化規則を説明してゆきます。
■タグ番号 0x01
■概要
真(TRUE)または偽(FALSE)のいずれかの状態をとるデータ型。
表現できる状態は2つだけなので、3つ以上の状態を表したい場合、列挙型(ENUMERATED)または
整数型(INTEGER)を用いる。
■変換規則
偽(FALSE)の場合 … 0x00
真(TRUE)の場合 … 0x00 以外の値
■補足
真(TRUE)の場合、値は 0x00 以外であれば良いので、実際の値は処理系によって異なる。
perl における真偽判定と等価である。
■変換プログラム
### 実値 -> バイナリ
$binary = ( $org =~ m/^TRUE$/i or $org > 0 ) ? 0xff : 0x00;
### バイナリ -> 実値
$org = pack("H2", $binary) ? 'TRUE' : 'FALSE';
■タグ番号 0x02
■概要
整数を値とするデータ型。C 言語や Java における整数型と異なり、桁数は可変となっている。値の許容範囲は であり、事実上ほぼ制限がない。
BER エンコーディングの場合、値フィールドが、「その整数値を表現するビット列のうち、最短のものである」事を保証するため、先頭から 9 ビット連続して 0 または 1 が現れてはならない事になっている。
たまに、100オクテットを越えるような非常に長い整数が使用されることがあるが、これは、多くの場合、Encapsulateされたデータである。すなわち、この整数値(値フィールド)は、SEQUENCEの値フィールド同様、別なASN.1オブジェクトとなっている。X.509 証明書に含まれる公開鍵は、この方法でカプセル化されている。
■変換規則
整数値を、2 の補数表現で符号化したものをバイナリ値とする。
ただし長さは、1 オクテット(8 ビット)の整数倍長となる。
■変換プログラム
### 実値 -> バイナリ
$binary = pack("S", $org);
### バイナリ -> 実値
$org = unpack("S", $binary);
■タグ番号 0x03
■概要
ビット単位で可変長のデータを処理するためのデータ型。
BER エンコーディングはオクテット単位の処理系であるため、BIT STRING では、「どこまでが、本当に扱いたいデータなのか」を記述できるようになっている。
具体的には、「末端の xx ビットは余計なデータです」のように指定する。これを、
未使用ビット数という。
■変換規則
値フィールドの第 1 オクテットで、
未使用ビット数を指定する。
そして、第 2 オクテットの先頭ビット
(MSB)から順に、値にしたいビット列を格納する。
すると最終オクテットの末尾には、最終ビット
(LSB)から先頭ビット
(MSB)に向かって数えて
未使用ビット数ぶんだけ、不要なビットが残る。
不要なビットには、どのような値が入っていても構わない。処理系はそれを無視するだろう。
逆にバイナリから実値を復号する場合、まず第 1 オクテット
(未使用ビット数)を読んで、最終オクテットのうちどこまでが有効であるか知る必要がある。
たまに、100オクテットを越えるような非常に長いビット列が使用されることがあるが、これは、多くの場合、Encapsulateされたデータである。すなわち、このビット列(値フィールド)は、SEQUENCEの値フィールド同様、別なASN.1オブジェクトとなっている。X.509 証明書に含まれる公開鍵は、この方法でカプセル化されている。
値フィールドの 第1オクテット
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
未使用ビット数 x |
|
値フィールドの 第2オクテット
|
… |
値フィールドの 最終オクテット
8 |
7 |
… |
x+1 |
x |
… |
1 |
トークンn |
未使用 |
|
↑本当に扱いたい値 |
無駄なデータを含めない、というBER の思想に基づき、
未使用ビット数は、0 ~ 7 の範囲に収めるべきである。
ビット長 0 のバイナリ列、つまり値が何も含んでいない場合、
未使用ビット数には 0x00 を格納し、値フィールドにはそれ以外に、何もデータを含めないようにする。
構造型の BIT STRING の場合、
未使用ビット数は記述しない。
■変換プログラム
### 実値 -> バイナリ
my $len = length($bit_string);
my $bytes = int(($len+7)/8);
my $unused_bits = $bytes * 8 - $len;
foreach $i(1..$bytes){
$binary .= pack("b8", substr($unused_bits."00000000", ($i-1)*8, 8));
}
$binary = pack("S", $unused_bits).$binary;
### バイナリ -> 実値
my $unused_bits = unpack("C1", substr($binary, 0, 1));
my $bit_string = unpack("B*", substr($binary, 1));
■タグ番号 0x04
■概要
オクテット単位のデータ列を格納するためのデータ型。各データトークンは、0 ~ 255 の範囲をとることができる。
たまに、100オクテットを越えるような非常に長い整数が使用されることがあるが、これは、多くの場合、Encapsulateされたデータである。すなわち、この整数値(値フィールド)は、SEQUENCEの値フィールド同様、別なASN.1オブジェクトとなっている。X.509 証明書に含まれる公開鍵は、この方法でカプセル化されている。
■変換規則
データを単純に格納する。データの値の先頭ビットを、最上位ビットにマッピングする。
■変換プログラム
■タグ番号 0x05
■概要
値が存在しない事を表すためのデータ型。
■変換規則
値の長さ(LENGTH)フィールドは常に 0x00 で、値(VALUE)フィールドは常に存在しない。
■変換プログラム
■タグ番号 0x06
■概要
オブジェクトの識別記号
(OID)を記述するためのデータ型。
この識別記号は、ITU-T, ISO によって
割り当て規則が定義されており、ASN.1 オブジェクトの種類を明示的に記述するために使用される。
例えば次のように、PrintableString 文字列に併記する事によって、この PrintableString が国名を表す、という事を明確にしたりする。
SEQUENCE { OBJECT IDENTIFIER "countryName" PrintableString "JP" } |
実際には OBJECT IDENTIFIER は、上記のような "countryName" といった文字列ではなく、
2.5.4.6 のような 10 進数数値の並びで表現される(アプリケーション層において)。
何桁の 10 進数をいくつ並べるかはオブジェクト毎に決まっているが、BER 的には制限が無い。つまり、何桁の数字をいくつ連ねたものであっても、バイナリに変換できる。
各々の OID の意味は、
割り当て規則から読み取るか、
dumpasn1.cfgなどの一覧表を使って調べる。
■変換規則
1.2.34.56789という OID を例に、バイナリ化の規則を説明する。
説明のため、ピリオドで区切られたひとつひとつの 10 進数を、
A[n] で表すことにする。ただし n は 自然数で、一番先頭を n = 1、一番末尾を n = N とする。
1.2.34.56789 の場合、次の通りとなる。
A[1] = 1
A[2] = 2
A[3] = 34
A[4] = 56789
N = 4
また、バイナリに変換された OID の第 m オクテットを、
B[m] で表すことにする。m も自然数で、第 1 オクテットが
B[1] である。
- A[1], A[2] に関しては、無条件で次のように変換し、B[1]を得る。( 図中(1)(2)(3) )
B[1] = A[1]×40 + A[2]
→ B[1] = 1 × 40 + 2 = 42 = 00101010b
現在の ISO/ITU-T 定義では 0≦A[1]≦2 と決まっているため、桁あふれが起こる事は無い。
- A[3]~A[N] の各々を、符号ビット(正負の区別)なしで2進数化する。( 図中(4) )
A[3] = 34 = 100010b
A[4] = 56789 = 1101110111010101b
- 2進数化した A[3]~A[N] の各々を、7桁(7ビット)の 2進数に分割する。A[3]~A[N] の長さが 7 の倍数でない場合、A[3]~A[N]( 図中(5) )
A[3] = 34 = 100010b → 0100010b
A[4] = 56789 = 1101110111010101b → 0000011b, 0111011b, 1010101b
- A[3]~A[N] の各々について、トークン群の最後の一つ(一番末尾)のものの先頭に 0 をつけ、それ以外のものの先頭に 1 をつける。結果各トークンは、8 ビット長(=1 オクテット)となる。( 図中(6) )
0100010b → 00100010b
0000011b, 0111011b, 1010101b → 10000011b, 10111011b, 01010101b
- 8ビット長となったトークンを、B[2] 以降に順次格納してゆく。( 図中(6) )
■変換プログラム
### 実値 -> バイナリ
my @token = split(/[\-\.\s]+/, $oid_string);
$binary = pack("S", $token[0] * 40 + $token[1])
.join("", map{ pack("w", $_) } @token[2..$#token]);
### バイナリ -> 実値
my $num1 = unpack("C1", $binary);
my @num2 = eval { unpack("w*", substr($binary, 1)) };
$oid_string = join(".", int( $num1/40 ), $num1 % 40, @num2 );
■OID の割り当て規則
OID を構成する 10 進数のうち、最初の 2 つに関しては、次のように割り当て規則が決められている。
1 つめ |
2 つめ |
0 - ITU-T が規定 |
0 - 勧告 (recommendation) 1 - 課題 (question) 2 - 管理組織 (administration) 3 - ネットワークオペレータ (network-operator) |
1 - ISO が規定 |
0 - 標準 (standard) 1 - 登録機関 (registration-authority) 2 - 加盟団体 (member-body) 3 - 身元が明らかな組織 (identified-organization) |
2 - ITU-T とISO が規定 |
|
例えば、OID の先頭が 1.2 のオブジェクトは、ISO の加盟団体によって管理されている。
3 つ目以降については、各々管理する組織が別にあり、そこが規定している。ある OID の管理を任された組織は、その数字以降の OID 空間に対し、割り当て規則を管理する責任を同時に負う。
どのくらいの深さまで規定するか、等は、各組織のポリシーや事情を鑑み自由に決めることができる。
■タグ番号 0x07
■概要
OBJECT IDENTIFIER で示されるオブジェクトの説明文を記述するための型。実体は、単純な文字列で、使用可能な文字セットは GraphicString と同等である。
■変換規則
文字コードをバイナリ化し、格納する。
■変換プログラム
■タグ番号 0x09
■概要
浮動小数点の実数を扱う為のデータ型。
±∞(無限大)という特殊な値も扱える。
他の処理系でもおなじみの
仮数、
指数、
基数を用いた表記法
(ISO 6093 NR3 形式)と、数字列をそのまま ASCII 文字列として扱う方法
(ISO 6093 NR1, 2 形式)の両方をサポートする。
ただし基数に使用できるのは、
10 と
2 のみとなる。
■変換規則
値が
0か、
±∞の場合、値フィールドの長さは常に 1 オクテットで、それぞれ次の値をとる。
値 |
値フィールド |
0 |
0x00 (00000000) |
+∞ |
0x40 (01000000) |
-∞ |
0x41 (01000001) |
それ以外の値の場合、基数が 10 か 2 かによって規則が異なる。
◆基数が 10 の時
値フィールドの第 2 オクテット以降に、実際の値が書き込まれる。第 1 オクテットには、そのエンコード規則を示す情報が書き込まれる。
このエンコード規則は、ISO 6093 で規定されており、JIS 規格では
JIS X 0210-1986
(別ウィンドウ)が対応している。
概要は次の通りであるが、更に詳しく知りたい場合、同規格を参考にされたい。
値フィールドの 第1オクテット
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
R1 |
R0 |
|
値フィールドの 第2オクテット
|
… |
値フィールドの 最終オクテット
|
↑実値。 これを更に、R1, R0 の値に基づいて処理する。 |
R1 |
R0 |
処理方法 |
例 |
0 |
1 |
ISO 6093 NR1 形式。数を数字列(ASCII 文字列)で表し、小数点記号は明示しない。つまり処理系に任せる(暗黙小数点表現と呼ばれる)。 |
1024 +1024 -1024 001024 +0001024 |
1 |
0 |
ISO 6093 NR2 形式。数を数字列(ASCII 文字列)で表し、小数点記号は . (ピリオド)または , (カンマ)で表記する。 |
12.34 +12.34 -12.34 1234.0 +1234. |
1 |
1 |
ISO 6093 NR3 形式。±[仮数]E±[指数]という文字列で表す。先頭の±は省略でき、省略した場合は + とみなされる。E は大文字でも小文字でも良い。 実値 = 仮数 × 10指数 で得られる |
1.2E+3 +1.2e-3 -1.234E+5 +0.123E-4 -0.1234e+5 |
いずれの場合も、値フィールドの第 2 オクテット以降には、数字・各種記号(+-.)を文字列として扱い、ASCII コードをそのままバイナリ化して格納する。 |
◆基数が 2 の時
まず、値を次のように因数分解する。
値 = 符号(S) × 正の整数(N) × 2一時指数(F) × 原始基数(R) 指数(E) |
|
ただし、 符号(S) = -1 or 1 一時指数(F) = 0 or 1 or 2 or 3 原始基数(R) = 2 or 8 or 16 |
|
値フィールドの第 1 オクテットの各ビットは、
S,
F,
R,
E の値に基づき次のように設定される。
値フィールドの 第1オクテット
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
1 |
s0 |
r1 |
r0 |
f1 |
f0 |
e1 |
e0 |
|
|
r1 |
r0 |
説明 |
0 |
0 |
R = 2 |
0 |
1 |
R = 4 |
1 |
0 |
R = 8 |
1 |
1 |
R = 16 |
|
f1 |
f0 |
説明 |
0 |
0 |
F = 0 |
0 |
1 |
F = 1 |
1 |
0 |
F = 2 |
1 |
1 |
F = 3 |
|
e1 |
e0 |
説明 |
0 |
0 |
Len(E) = 1 |
0 |
1 |
Len(E) = 2 |
1 |
0 |
Len(E) = 3 |
1 |
1 |
Len(E) = 第 2 オクテット の値 |
|
また、値フィールドの第 2 ~ 最終オクテットは、次のように設定される。
値フィールドの 第2オクテット
|
… |
値フィールドの 第 2 + Len(E) オクテット
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
E トークン Len(E) |
|
値フィールドの 第 2 + Len(E) + 1 オクテット
|
… |
値フィールドの 最終オクテット
|
↑実際の指数 E(符号付き 2 の補数表現整数) |
↑実際の仮数 N(2 の補数表現・正の整数) |
■変換プログラム
■タグ番号 0x0c
■概要
UTF-8 でエンコードされた Unicode 文字列を扱うための型。
■変換規則
文字コードをバイナリ化し、格納する。
■変換プログラム
■UTF-8 とは?
Unicode は、世界の主要文字を1つの文字コード体系で表現することを目的に規定され、その文字コードは、1文字あたり 16bit となっている。
しかし、C言語等の char* 型は1オクテット(8bit)である。このため、そのままでは Unicode を取り扱うことができない。そこで、Unicode の文字コードを、マルチバイト処理により 8bit 単位に分割したものを使用する。この規格が、UTF-8
(Unicode Transformation Format 8 の略)である。
■タグ番号 0x10
(通常は階層型のため、16進ダンプするとタグ部が 0x30 に見える事に注意)
■概要
ASN.1 オブジェクトの集合を表記するための型。
SEQUENCE 型と SEQUENCE OF 型の 2 種類があり、後者は、構成要素が意味においても型においてもすべて同じである場合に使用する。前者は、複数の型が混在する場合に使用する。
いずれの場合であっても、SEQUENCE は、各要素の出現順序に意味を持たせたい時に使用する。出現順序が不定でも良いデータ集合の場合、SEQUENCE でなく
SET を用いる。もっとも、どちらの場合であっても、出現順序を解釈するシステムにより処理されなければ意味がない。
■変換規則
集合の各要素を、ASN.1 のバイナリ規則に従ってバイナリ化したものを、値とする。
■変換プログラム
■タグ番号 0x11
(通常は階層型のため、16進ダンプするとタグ部が 0x31 に見える事に注意)
■概要
ASN.1 オブジェクトの集合を表記するための型。
SET 型と SET OF 型の 2 種類があり、後者は、構成要素が意味においても型においてもすべて同じである場合に使用する。前者は、複数の型が混在する場合に使用する。
いずれの場合であっても、SET は、各要素の出現順序が意味を持たなくても良い場合に使用する。出現順序に意味があり、それを保持させなければならないデータ集合の場合、SET でなく
SEQUENCE を用いる。
■変換規則
集合の各要素を、ASN.1 のバイナリ規則に従ってバイナリ化したものを、値とする。
■変換プログラム
■タグ番号 0x12
■概要
数字(0..9)から構成される文字列を表す型。
■変換規則
文字コードをバイナリ化し、格納する。
■変換プログラム
■タグ番号 0x13
■概要
英語大文字小文字(A..Z, a..z)、数字(0..9)、スペース、コンマ(,)、ピリオド(.)、ハイフン(-)、スラッシュ(/)、クォーテーション('")、丸括弧( () )、プラス符号(+)、等号(=)、クエスチョンマーク(?)から構成される文字列を表す型。
■変換規則
文字コードをバイナリ化し、格納する。
■変換プログラム
■タグ番号 0x14
■概要
ISO 2375 の reference 87, 102, 103, 106, 107 で規定された文字、スペース、および DELETE から成る文字列。
■変換規則
文字コードをバイナリ化し、格納する。
■変換プログラム
■タグ番号 0x16
■概要
IA5
(International Alphabet 5 の略)文字列を表すための型。IA5 は、ISO 2375 の reference 1, 2 で規定された文字、スペース、および DELETE から成る。これは、ASCII 文字集合と同等である。
ASN.1 には、文字を表す型がたくさん定義されているが、ASCII 文字集合を過不足無く、しかも Unicode のように大がかりにすることなく表現できるのは、この IA5String だけである。
よって、次のような文字列を記述する場合、よく用いられる。
例えば、URI
(Uniformed Resource Identifier) やメールアドレスといった文字列である。
■変換規則
文字コードをバイナリ化し、格納する。
■変換プログラム
■タグ番号 0x17
■概要
時刻を表す型。グリニッジ標準時との時差を表現する事ができる。
以下のいずれかの文字列で記述される。
- YYMMDDhhmmssZ
- YYMMDDhhmmss±hhmm
末尾に Z がつけられた場合、グリニッジ標準時であることを示す。
±hhmm の場合、ローカル時刻であることを示し、±hhmm はグリニッジ標準時との時差を表す。(日本の場合、+0900)
各記号の意味は次の通りで、桁数は固定長である。
YY |
西暦年の下 2 桁。上 2 桁に仮定する値は、処理系ごとに異なる。 (例えば X.509 証明書の場合、00~49 は 2000年代、50~99 は 1900年代として処理するよう規定されている) |
MM |
月。perl の localtime 関数と異なり、1月は 01 で表す。 |
DD |
日付。 |
hh |
24 時間制の時間。日付が変わった直後が 00 時となる。 |
mm |
分。 |
ss |
秒。このフィールドは省略できる。 |
"2000年問題"を抱えている為、長期的に利用するデータの場合、
GeneralizedTime の使用が推奨される。
■変換規則
数字列を ASCII 文字列とみなし、各文字の ASCII コードをそのまま格納する。
■変換プログラム
(変換作業は必要ありません)
オマケ: UTCTime/GeneralizedTime ←→ epoch, 整形された時刻の相互変換ルーチン
# Usage(1):
# $STRING = &getTime( $FORMAT, $ORDER, $TIME_IN_EPOCH )
# $FORMAT ... As same as 'printf', which is like "%4d/%2d/%2d/(%2s) %2d:%02d:02d"
# $ORDER ... y..Year, m..Month, d..Day, w..Week(English), h..Hour, i..Minute, s..Second
# Expresses like "ymdwhis"
# $TIME_IN_EPOCH ... Time in Epoch expression.
#
# Usage(2):
# $TIME_IN_EPOCH = &getTime( $UTCTime_or_GeneralizedTime )
# $UTCTime_or_GeneralizedTime ... UTCTime or GeneralizedTime (autodetect)
@WEEK = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
# @WEEK = ['日', '月', '火', '水', '木', '金', '土'];
sub getTime{
my @arg = @_;
return (@arg > 1) ? &getTime_Epoch2Str(@arg) : &getTime_Str2Epoch($arg[0]);
}
sub getTime_Epoch2Str{
my ($layout, $order, $epoch, %item) = @_;
@_ = gmtime($epoch);
@item{qw(s i h d m y w)} = (@_[0..3], $_[4]+1, $_[5]+1900, $WEEK[$_[6]]);
return sprintf($layout, map { $item{$_} } $order =~ /(.)/g );
}
sub getTime_Str2Epoch{
my ($year, $month, $day, $hour, $min, $sec,
$time_string, $time_difference, $separator, $type);
my $string = shift;
($separator) = $string =~ /$REGEX_TIMEDIFF/o;
($time_string, $time_difference) = split($REGEX_TIMEDIFF, $string);
if ( $separator eq 'Z' or !$separator ){
$time_difference = 0;
}
else{
$time_difference = substr($time_difference, 0, 2) * 3600
+ substr($time_difference, 2) * 60;
$time_difference = -1 * $time_difference if ($separator eq '-');
}
if ( $time_string =~ /$REGEX_INCLUDE_DOT/o or length($time_string) > 12 ){
$time_string =~ /$REGEX_GTIME/;
($year, $month, $day, $hour, $min, $sec) = ($1, $2, $3, $4, $5, $6);
$sec = 0 if ( $sec =~ /$REGEX_INCLUDE_DOT/o );
}
else{
$time_string =~ /$REGEX_UTIME/o;
($year, $month, $day, $hour, $min) = ($1, $2, $3, $4, $5);
$year += $year < 50 ? 2000 : 1900;
$sec = length( $time_string ) >10 ? substr($time_string, 10, 2) : 0;
}
my ( $epoch, $error ) = &timegm($sec, $min, $hour, $day, $month-1, $year);
$error .= ", since the source is '$string'" if ($error);
die ($error) if ($error);
return $epoch + $time_difference;
}
sub timegm {
my @date = @_;
my ($month, $year) = @date[4..5];
my $guess = $TIME_GUESS || $^T;
my @g = gmtime($guess);
my ($year_diff, $month_diff, $error, $count);
while ($year_diff = $year - $g[5]-1900) {
if ( $count++ > 50 ){ return("", "Year ($year) overflow"); }
$guess += $year_diff * (363 * 86400);
@g = gmtime($guess);
}
$count = 0;
while ($month_diff = $month - $g[4]) {
if ( $count++ > 24 ){ return("", "Month ($month) overflow"); }
$guess += $month_diff * (27 * 86400);
@g = gmtime($guess);
}
$TIME_GUESS = $guess;
return ($guess
+ ( $date[0] - $g[0] ) * 1
+ ( $date[1] - $g[1] ) * 60
+ ( $date[2] - $g[2] ) * 3600
+ ( $date[3] - $g[3] ) * 86400, $error);
}
■タグ番号 0x18
■概要
時刻を表す型。グリニッジ標準時との時差を表現する事ができる。
以下のいずれかの文字列で記述される。
- YYYYMMDDhhmmss.dZ
- YYYYMMDDhhmmss.d±hhmm
末尾に Z がつけられた場合、グリニッジ標準時であることを示す。
±hhmm の場合、ローカル時刻であることを示し、±hhmm はグリニッジ標準時との時差を表す。(日本の場合、+0900)
各記号の意味は次の通りで、桁数は、.d を除き固定長である。
YYYY |
西暦年 |
MM |
月。perl の localtime 関数と異なり、1月は 01 で表す。 |
DD |
日付。 |
hh |
24 時間制の時間。日付が変わった直後が 00 時となる。 |
mm |
分。 |
ss |
秒。このフィールドは省略できる。 |
.d |
秒の小数点以下の部分で、任意の桁数を取ることができる。 このフィールドは省略できる。 |
■変換規則
数字列を ASCII 文字列とみなし、各文字の ASCII コードをそのまま格納する。
■変換プログラム
(変換作業は必要ありません)
オマケ: UTCTime/GeneralizedTime ←→ epoch, 整形された時刻の相互変換ルーチン
(UTCTime の項をご覧下さい)
-
■ASN.1 コンソーシアム
-
"ASN.1 コンソーシアムは、ASN.1 という技術の利用を推進する事を目的とした非営利団体です。"(トップページより抜粋)
ASN.1 に関する様々なリソースにリンクされています。
-
■dumpasn1
-
バイナリ状態(プレゼンテーション層の形態)の ASN.1 をダンプするプログラムです。
リンク先にある
ソースコード
(別ウィンドウ)を gcc などでコンパイルすると使用できます。
-
■dumpasn1.cfg
-
前項 dumpasn1 が読み込む設定ファイルです。具体的には、OID から意味(説明文)を調べるために使用されます。
実態は単純なテキストファイルです。ある OID に関する説明を、次のルールに従って記述します。
- OID = の後に、BER エンコーディングされた ASN.1 のインスタンス(タグフィールド + 値の長さフィールド + 値フィールド となった状態)を、スペースで区切った 16 進数で書く
- Comment = の後に、OID の説明文を書く(これはどこにも出力されません)
- Description = の後に、dumpasn1 に出力させたい説明文を書く
- 各 OID は、空行で区切る
以下に例を示します。
OID = 06 03 55 04 06 Comment = X.520 id-at (2 5 4) Description = countryName (2 5 4 6)
OID = 06 03 55 04 07 Comment = X.520 id-at (2 5 4) Description = localityName (2 5 4 7)
… |
ただ dumpasn1 は元々、セキュリティの分野で使用することを前提に作られた(?)プログラムなので、この設定ファイルに含まれているのは、その分野に関連の深い OID だけです。
とはいっても、X.509 証明書、CRL などのダンプには、これだけあれば充分です。
未対応の OID を発見したら、上記の書式を参考に、自分で追記すると良いでしょう。とても便利になります。
-
■OSS ASN.1 Resources
-
OSS Nokalva 社のページ。ASN.1 オブジェクトを処理する C, C++, Java 用ライブラリ(有償)や、ASN.1 バイナリファイルの構文チェックプログラム、ASN.1 のリファレンス PDF (無償の割に詳しい)が利用できます。
-
■ASN.1 Site - Tools - OID Tree
-
OID の割り当て先をツリー図で参照/検索できるシステム。
ただ、
iso(1) member-body(2) の直下に、
f(250)
(フランス)、
uk(826)
(イギリス)、
us(840)
(アメリカ)しか載っていない。
iso(1) member-body(2) の直下は「国」を表すフィールドで、本来
jp(392)
(日本) なども規定されているので、完全収録に向けてがんばってほしいところです。
■参考文献
■転載について
本ページに含まれる記述、プログラムは、自由に配布、使用して頂いて結構です。転載の許可も必要ありません。リンクに関しても、ご自由に貼って頂いて結構です。
但し、運用した結果、何らかの損害を被ったとしても、責任を負いかねます。ご了承ください。
と言いつつ虫の好い話ですが、バグや無駄な部分等を発見されましたら、下記アドレスまでご一報頂ければ幸いです。
また、転載、リンクの報告も、もしよかったら、下記アドレスまでお送り頂けたら嬉しいです。
■作者へメールでツッコミ
[email protected] まで!