バージョン0.24.0
本文書は以下の条項の下にパブリックドメインに置かれます。
Creative Commons CC0 1.0 Universal Public Domain Dedication
本文書はファイル転送のためのGeminiプロトコルを規定します。HTTP [RFC7230] の削ぎ落としというよりGopher [RFC1436] の累積的な改善と見ることができます。TCP [STD7] のポート番号1965上で走り、TLS [RFC8446] が提供する暗号化を伴う単純な要求と応答のトランザクションです。所定のMIME種別 [RFC2045] 付きで任意のデジタル内容を送れますが、最もよく使われている軽量ハイパーテキスト文書があり、関連して個別に規定する形式が使われます。
Geminiハイパーテキスト形式(別名「gemtext」)の仕様も参照
本文書中のキーワード「しなければならない (MUST)」「してはならない (MUST NOT)」「必須である (REQUIRED)」「することになっている (SHALL)」「しないことになっている (SHALL NOT)」「すべき (SHOULD)」「すべきでない (SHOULD NOT)」「推奨する (RECOMMENDED)」「してもよい (MAY)」「省略できる (OPTIONAL)」は [BCP14] 中に記載されているように解釈されます。
Geminiの最重要の目標は実装が容易(1日2日を掛ければせいぜい数百行でサーバーやクライアントができる)でありつつも有用な単純な手続きを提供することです。
Geminiで送り届ける上で、TCP上の既定ではポート番号1965上で(最初の有人のGeminiミッションであるGemini 3は1965年3月に飛行しました)、暗号化されたトランザクションを提供するためにTLSを使います。サーバーとクライアントはTLS 1.2以上に対応しなければなりません。使用されるTLS証明書の種類(CAベースないし自己書名)はまだ議論中のため「ベストプラクティス」文書に規定されます。既定のポート番号1965はホストシステムで非特権ポートであり、サービスを走らせる上で管理者アカウントの使用は必要ありません。
GeminiのアドレスはURI [STD66] に基づき、以下の変更点があります。
1. 使われるスキームは「gemini」です
2. URIの利用者情報の部分は使ってはなりません
3. 空パス構成要素と「/」のパス構成要素は等価で、サーバーは両方をリダイレクトに送ることなく対応しなければなりません。
4. 指定されなければポート番号は既定で1965です。
5. 認可節でのIPアドレスの使用は、使われるべきではありません。
本文書はクライアントとサーバーがしなければいけないことを負託するプロトコルを押さえるのみですが、中核プロトコルの外側の仕様にありここでは押さえられていないGeminiの別の側面もあります。クライアントとサーバー両方の実装者はGeminiプロトコルのベストプラクティスの手引きに従うことが推奨されます。
クライアントはサーバーに接続し要求を送ります。要求は絶対URIにCR(文字13)とLF(文字10)が続いたものからなります。これを拡張BNF [STD68] で表すと以下になります。
request = absolute-URI CRLF ; absolute-URIは [STD66] より ; CRLFは [STD68] より
要求を作るとき、URIは1024バイトを超えてはなりません。またサーバーはURIがこの制限を超える要求を拒絶しなければなりません。サーバーは利用者情報付きの要求を拒絶しなければなりません。クライアントは要求の一部としてフラグメントを送ってはなりません。サーバーはこのような要求も同様に拒絶しなければなりません。クライアントが空パスで要求を作るとき、クライアントは要求の末尾に「/」を加えるべきです。しかしサーバーは空のパスを扱えなければなりません。
要求があったとき、サーバーは状態と、要求が成功した場合はクライアントが要求した内容も送り返すことになります。状態は2桁の応答コードがあり、(送られる応答次第で)CRとLFに続けて追加情報を付けられます。拡張BNFでは以下の通りです。
reply = input / success / redirect / tempfail / permfail / auth input = "1" DIGIT SP prompt CRLF success = "2" DIGIT SP mimetype CRLF body redirect = "3" DIGIT SP URI-reference CRLF ; 補足:[STD66] は 「」 を有効なURI参照として許しています。 ; これはリダイレクトの場合には有効とされません。 tempfail = "4" DIGIT [SP errormsg] CRLF permfail = "5" DIGIT [SP errormsg] CRLF auth = "6" DIGIT [SP errormsg] CRLF prompt = 1*(SP / VCHAR) mimetype = type "/" subtype *(";" parameter) errormsg = 1*(SP / VCHAR) body = *OCTET VCHAR =/ UTF8-2v / UTF8-3 / UTF8-4 UTF8-2v = %xC2 %xA0-BF UTF8-tail ; C1制御集合はありません / %xC3-DF UTF8-tail ; URI-referenceは[STD66]より ; ; type は[RFC2045]より ; subtype は[RFC2045]より ; parameter は[RFC2045]より ; ; CRLF は[STD68]より ; DIGIT は[STD68]より ; SP は[STD68]より ; VCHAR は[STD68]より ; OCTET は[STD68]より ; WSP は[STD68]より ; ; UTF8-3 は[STD63]より ; UTF8-4 は[STD63]より ; UTF8-tail は[STD63]より
[STD68]のVCHAR規則はユニコードの非制御コードポイントを含めるべく拡張されます(またUTF-8 [STD63] で符号化されます)。本文の種類はここでは指定されませんが、それは内容が送り届けられる内容のMIME種別によるためです。完了の応答(内容を含められます)を送るにあたって、サーバーは接続を閉じます。このときTLSのclose_notifyの仕組みを使って、これ以上データは送られないことをクライアントに伝えなければなりません。
状態値は10から69の範囲にあり、両端を含みます。ただし現在全ての値が定義されてはいません。クライアントが1桁目を使って応答を扱ってもよいようにグループ化されていますが、2桁目は状態を更に明らかにするためにあり、クライアントは何をすべきか決める際に追加の桁を使うことが推奨されます。サーバーは定義されていない状態コードを送ってはなりません。
6グループの状態コードがあります。
クライアントは「10」より小さいか「69」より大きい状態コードを拒絶し、利用者にそのことを警告しなければなりません。クライアントは「10」から「69」の間の未定義状態コードを1ビット目の既定の動作により扱うべきです。そのため「14」状態ではあたかもクライアントが「10」を受け取ったかのように動作するべきです。状態「22」ではクライアントがあたかも「20」を受け取ったかのように動作するべきです。
サーバーはクライアントの利用者の入力を待っています。状態コードの後に送られる追加情報はテキストであり、クライアントは利用者に情報を求めるべくプロンプトを出すときに使わなければなりません。またその情報は問い合わせ部分として同じURIに送り返されます。空白は「%20」に符号化されなければなりません。クライアントは複数行からなる入力項目を受けても構いません。またその場合に改行は「%0A」として符号化されるべきです。サーバーは「%0A」と「%0D%0A」の両方を改行として認識すべきです。現在この分類には2つの状態コードが定義されています。
input = "1" DIGIT SP prompt CRLF prompt = 1*(SP / VCHAR)
クライアントが既に問い合わせ文字列を含むURIで1xの応答を受け取ったときは、クライアントは問い合わせ文字列を利用者の入力で置き換えなければなりません。例えば与えられたURIの結果が次のように応答10であったとします。
gemini://example.net/search?hello
クライアントは次の要求を送ります。
gemini://example.net/search?the%20user%20input
基本の入力状態コードです。クライアントは利用者に入力のプロンプトを出さなければなりません。入力は [STD66] によりURI符号化してこの応答を生成した同じURIに問い合わせとして送るべきです。
状態コード10に従いますが、パスワードのような機密性のある入力に使用します。クライアントは状態コード10に従って質問を表示すべきですが、利用者の入力は「肩越しに覗いて」読み取られることを防ぐために、画面に表示すべきではありません。
要求が扱われサーバーはクライアントに内容を送りました。追加情報は内容のMIME種別で [RFC2045] により指定されます。クライアントは理解できないMIME仮引数を単に無視して扱わなければなりません。
応答本文は単なる生の内容であり、gopher [RFC1436] よろしくテキストないしバイナリです。圧縮や断片化、内容や転送の符号化といった類の対応はありません。サーバーは最終バイトの後に接続を閉じ、「応答末尾」信号はありません。
インターネットメディア種別は正統な形式で登録されます。Geminiを介して転送される内容は転送に先立って適切且つ正統な形式で表現されなければなりません。ただし次の段落で定義されるように「text」種別は例外です。
正統な形式において、「text」のメディア副種別はテキスト行の改行としてCRLFを使います。Geminiはこの要件を緩和し、単にLF単独のテキストメディアの転送を許します(ただしCR単独は許しません)。これは応答本文全体で一貫性を持って改行されていることを表現します。GeminiのクライアントはGeminiを介して受け取ったテキストメディア中のCRLFとLFを改行の表現として受け付けなければなりません。
クライアントは、文字集合がUTF-8のtext/geminiと(構造的にUTF-8の部分集合である)US-ASCII [STD80] ないしUTF-8の文字集合のtext/plainのMIME種別に対応しなければなりません。クライアントは他の文字集合のtext/plainに対応しても構いません。指定された文字集合なしにtext/*のMIME種別が示されているとき、クライアントは文字集合がUTF-8と仮定すべきです。ディスクに保存されるものであったり、他のプログラムに渡されるものであったりしても、クライアントは他のMIME種別も扱うべきです。
text/geminiの仕様はtext/geminiの仕様で与えられます。
この分類に定義された状態は20だけです。
success = "2" DIGIT SP mimetype CRLF body mimetype = type "/" subtype *(";" parameter) body = *OCTET
サーバーは正常に解析して要求を理解し、与えられたMIME種別の内容を送り届けます。
サーバーはクライアントに内容がある新しい場所を送ります。追加情報は絶対URIまたは相対URIです。サーバーが問い合わせ文字列付きの要求に対して応答でリダイレクトを送ったとき、クライアントは問い合わせ文字列を新しい場所に適用してはなりません。問い合わせ文字列が新しい場所に重要であれば、サーバーは問い合わせをリダイレクトの一部として含めても構いません。サーバーはリダイレクトにフラグメントを含めるべきではありません。しかしこれが与えられているとき、かつクライアントに(元のURIから)適用できるフラグメントがあるとき、どちらのフラグメントを適用するかはクライアントに委ねられます。クライアントは従うリダイレクトの数を5回に制限しなければなりません。この分類には2つの定義されたコードがあります。
redirect = "3" DIGIT SP URI-reference CRLF ; 補足:RFC-3987/3987では「」を有効なURI参照にできます。 ; これはリダイレクトの場合に有効であることを意味しません。
基本のリダイレクトコード。リダイレクトは一時的でクライアントは元のURIで内容を要求し続けるべきです。
内容の位置は永続的に新しい場所に移りました。クライアントは以後、与えられた内容を取得するために新しい場所を使うべきです。
要求が失敗しました。応答本文はありません。失敗の性質は一時的なものです。つまり将来的に同一の要求は成功する可能性があります。省略可能な文言で失敗の追加情報を提供して構いませんし、もしあるならクライアントは人間の利用者に表示すべきです。この分類の下には5つの状態コードがあります。
tempfail = "4" DIGIT [SP errormsg] CRLF errormsg = 1*(SP / VCHAR)
サーバーに規定されていない条件が存在し、送り届けることはしかねる内容であるものの、クライアントは改めて内容の獲得を試みることができます。
オーバーロードやサーバーの保守作業のため利用できません(HTTP 503と関連)。
CGIやそれに似た動的な内容を生成するシステムの処理が予期せず異常終了したり時間切れになったりしました。
サーバーが正常にリモートホストとのトランザクションを完了できなかったため、プロキシの要求が失敗しました(HTTP 502、504と関連)。
サーバーはクライアントに要求を失速させるよう要求しており、指数的バックオフを使うべきです。要求の合間の連続する遅延はこの状態が返されなくなるまで二倍されます。
要求が失敗しました。応答本文はありません。失敗の性質は永久であって、この内容への更なる要求には同じ状態が返され、クライアントは同じ要求をするべきではありません。省略できる伝文で失敗についての追加情報を提供しても良く、その情報があるときはクライアントは利用者に表示すべきです。この分類には5つの状態コードがあります。
permfail = "5" DIGIT [SP errormsg] CRLF errormsg = 1*(SP / VCHAR)
これは汎用の永続的な失敗コードです。
要求された資源は見つからず(エリア51では��も見つかりません)これ以上の情報はありません。将来は存在するかもしれませんし、そうでないかもしれません。誰が分かるでしょうか。
要求された資源は最早入手できず再び手に入ることもありません。検索エンジンや類するツールはこの資源を索引から削除すべきです。内容収集器は資源の要求を止めて人間の利用者に購読していた資源が散逸したことを伝えるべきです(HTTP 410が関連)。
ドメインの資源への要求がサーバーから送られず、サーバーがプロキシの要求を受け付けません。
サーバーがクライアントの要求を解析できませんでした。不正な要求か、「要求」節に挙げられた制限に違反する要求であるものと見られます。
要求された資源を利用するにはクライアント証明書が必要です。必要となる理由の例として、アクセス制御やアプリケーションでのサーバー側の状態の維持に活用することがあります。要求が証明書なしにされた場合、その1度限りにすべきです。要求が証明書付きでされた場合、サーバーは受け付けず要求は別の証明書で繰り返されるべきです。追加情報には証明書が必要な理由や拒絶した理由についての詳細を含められます。サーバーはそうした情報を含めるべきであり、クライアントは利用者に表示すべきです。この分類には3つの状態コードが定義されています。
auth = "6" DIGIT [SP errormsg] CRLF errormsg = 1*(SP / VCHAR)
内容はクライアント証明書を要求します。クライアントは内容を利用するために証明書を提供しなければならず、それ以上要求を繰り返すべきではありません。この状態コードに応答する上で生成された証明書のスコープは、その状態コードが受け取られたホストとポート番号と元の要求のURLのパスにその下にある全てのパスを加えたものに制限されるべきです。サーバーは同じホストとポート番号の異なるパスで異なる証明書を要求しても構いません。サーバーは同じ証明書を違うパスにある任意の内容に使えるようにすべきです。以下に例を示します。
gemini://example.com/private/ -- 証明書Aを要求 gemini://example.com/private/r1 -- 証明書Aを要求 gemini://example.com/private/r2/r3 -- 証明書Aを要求 gemini://example.com/other/ -- 証明書Bを要求 gemini://example.com/other/r1 -- 証明書Bを要求 gemini://example.com/other/r2/r3 -- 証明書Bを要求 gemini://example.com/random -- 証明書は不要
クライアントは自動でクライアント証明書を生成してはならず、その証明書を使って利用者の主体的な関与なく要求を繰り返してはなりません。クライアントは利用者に指示されない限り、異なるホスト、異なるポート番号、元の要求のURLのパスより上の同じホストとポート番号のパスに対する要求で、この状態コードへの応答に生成されたクライアント証明書を使ってはなりません。
与えられたクライアント証明書は特定の要求された資源にアクセスする権限がありません。問題は証明書自体には無く、他の資源については権限がある可能性があります。
与えられたクライアント証明書が不当であるため受け付けられませんでした。これは証明書の内容やそれ自体に問題があることを示しており、特定の要求された資源については考慮されません。最もありそうな原因は、証明書の妥当性の開始日が未来になっているか期限日が過ぎているかですが、このコードは不当な書名やX509標準要件の違反を示している可能性もあります。
執筆時点 (2021) で、全ての既存のTLSライブラリがTLS 1.3に対応しているわけではありませんが、多数(全て?)がTLS 1.2に対応しており、そのためTLS 1.2が最小バージョン要件となります。実装者はTLS 1.2がプロトコルの暗号化の交渉段階の一部としてサーバー名と(もし使われる場合は)クライアント証明書を平で送ることにご留意ください。クライアントはTLS 1.2接続が確立されたときに警告しても構いません。またクライアント証明書がTLS 1.2を介して転送されたときに利用者に警告すべきです。
Geminiサーバーは接続を閉じるためにTLSのclose_notifyの実装を使わなければなりません。クライアントは既定で接続を閉じるべきではありませんが、利用者により設定された制限を超えた内容の場合は構いません。クライアントとサーバーの両方ともTLSのclose_notifyの仕組みが使われなかったとき(例えば適切にTLS接続を終了させることなくソケットを閉じた低層のソケットエラー)を扱うべきです。クライアントはそうした場合に利用者に通知すべきです。サーバーはそうした場合をログに出しても構いません。
クライアントとサーバーの実装はTLSサーバー名表示 (Server Name Indication; SNI) に対応しなければなりません。クライアントは権威節がホスト名であるURLへの要求時にホスト名の情報を含めなければなりません。
Geminiプロトコル仕様は、クライアントが受け取ったサーバー証明書を検証するために使わなければいけない特定のメソッドを規定しません。Geminiの実装はオペレーティングシステムやウェブブラウザで提供される、予め信頼された認証局の一覧に依存する広く使われる手法以外の「代替」検証スキームについて、プロトコルの起源以来、非公式に試みてきました。こうした手法の正式な仕様は実践を積み重ねていく中で進展していくかもしれません。
クライアントはTrust On First Useを使うことが強く推奨されます。「TOFU」とも呼ばれる証明書をピン留めする仕組みであり、検証の仕組みの原則として、自己書名証明書を不当として拒絶しません。そのような仕組みにおいては、まずGeminiクライアントがサーバーに接続したとき、何であれ提示された証明書を受け付けます。その証明書の指紋と期限日は永続的なデータベースに保存され、サーバーのホスト名とポート番号に紐付きます。以降のそのホスト名とポート番号への接続では毎回、受け取った証明書の指紋が計算されデータベースに保管されているものと比較されます。指紋が合致しないが以前の証明書の期限日が過ぎていなければ、これは中間者攻撃の証拠の可能性があると考えられます。クライアントはこの基本的な仕組みを追加のセキュリティ対策で拡張しても構いません。例えば複数のネットワークの視点で受け取った証明書を比較したりDNS Based Authentication of Named Entities (DANE) [RFC6698] を使ったりするなどです。
資源を要求したいGeminiクライアントは通常、資源のgemini://のURLの権威部分で示されるサーバーに適切な要求を送ることが期待されます。しかしクライアントは代えて先んじて元のサーバーに接続し応答を中継する中継サーバーに送るよう構成しても構いません。そうした中継器(以降「プロキシ」)自体、他のプロキシを使っても構いません。こうしてユーザーエージェントと元のサーバーの間にプロキシの連鎖が形成されます。
Geminiプロキシが提供する補助サービスには以下が含まれますが、この限りではありません。
Geminiプロキシについてプロトコル水準に特別なことはありません。クライアントがそれらに接続し要求を送ると、上で詳述した通り「普通の」Geminiサーバーに接続するのと全く同じ方法で応答を解釈します。もちろんGeminiプロキシもまたGeminiサーバーです(ただし全てのサーバーがプロキシとは限りません)。Geminiプロキシの他とは違う特徴は、「所有していない」資源への要求を扱えることです。プロキシはそうした要求だけを扱う必要はありません。同じマシンで走る同じプログラムは、要求にプロキシとして応答したりしなかったりして構いません。
Geminiプロキシが元のサーバーに接続して標準的なGeminiの要求と応答のやり取りが完了したとき、かつ元のサーバーから受け取った応答の状態コードがエラーコードであるとき(つまり1桁目が4か5)、プロキシは同じ応答ヘッダをユーザーエージェントに送るべきです。
状態コード43「プロキシエラー」はGeminiプロキシが遠隔ホストとのやり取りを正常に完了できなかった場合で使うことを目的としています。ここでの「正常に」の意味は応答コード20「成功」を受け取ることではありません。プロキシが状態コード43の応答を返すべき状況には以下がありますが、この限りではありません。
プロキシは元のサーバーとのGeminiトランザクションを完了してクライアント証明書の要求を受け取った後に、プロキシがそれまでに関係する資源に使うためのクライアント証明書と紐付く私有鍵で構成されていなければ、状態コード43を使っても構いません。ユーザーエージェントがこれらを生成して元のサーバーに接続する上で再利用するためにプロキシに渡す直感的かつインバンドな方法はありません。
Geminiプロキシはユーザーエージェントと元のサーバーの間の「中間者」でもあり、プライバシーとセキュリティに影響します。Geminiプロキシはユーザーエージェントと元のサーバーの間のエンドツーエンドのTLS暗号が機能していません(これが理由でプロキシと呼ばれトンネルとは呼ばれません)。Geminiプロキシには応答と要求の両方を好きに変更したり永久にログに控えたりする能力があります。
したがってGeminiプロキシは信頼された集団を念頭に置いています。期待されているよくある用途は、ユーザーエージェントと同じマシンでローカルで走っているか、同じローカルネットワークにあり同じ利用者により運用されている違うマシンで走っているプロキシです。サードパーティにより運用されている遠隔Geminiプロキシを使うことはそのサードパーティへの高度な信頼を要します。利用者と遠隔プロキシの運用者の両方がTLS証明書の指紋を注意深く確認すれば高度な内部の信頼のある小さなコミュニティ内でプロキシの共有を活用できます。
上の考慮事項を平易に言うと以下となります。
以下の例はサーバーとクライアント2つの集団があります。それぞれの動作は角括弧「[]」で囲まれており、文字コード13と10を示す「CRLF」のような幾つかの終端記号と共にテキスト表記は引用符で囲まれます(ただし引用符は入力に含まれません)。「mime種別」は [RFC2045] によるMIME種別を表し「内容……」は要求された内容の意味です。
以下の例では、サーバーが利用者の入力を要求し、クライアントが収集し、利用者の入力込みで要求を再提出しています。
Client: [接続を開く] Client: "gemini://example.net/search" CRLF Server: "10 検索する言葉を入力してください" CRLF Server: [接続を閉じる] Client: [利用者にプロンプトが出て入力が得られます] Client: [接続を開く] Client: "gemini://example.net/search?gemini%20search%20engines" CRLF Server: "20 " mime種別 CRLF 内容…… Server: [接続を閉じる]
クライアントが内容を要求します。この例では画像ファイルです。
Client: [接続を開く] Client: "gemini://example.net/image.jpg" CRLF Server: "20 image/jpeg" CRLF <binary data of JPEG image> Server: [接続を閉じる]
この例ではサーバーが資源の新しい場所へクライアントをリダイレクトしています。
Client: [接続を開く] Client: "gemini://example.net/current" CRLF Server: "30 /new" CRLF Server: [接続を閉じる] Client: [接続を開く] Client: "gemini://example.net/new" CRLF Server: "20 " mime種別 CRLF 内容…… Server: [接続を閉じる]
以下はサーバーがクライアント証明書を要求し、クライアントが続く要求で証明書を提供しています。
Client: [接続を開く。クライアント証明書は送られていない] Client: "gemini://example.net/protected/" CRLF Server: "60 この資源にアクセスするには証明書が必要です" CRLF Server: [接続を閉じる] Client: [アプリケーションが証明書を得るために何らかの動作をする] Client: [接続を開く。クライアントは証明書を送る] Client: "gemini://example.net/protected/" CRLF Server: "20 " mime種別 CRLF 内容…… Server: [接続を閉じる]
この例ではクライアント証明書と利用者の入力を束ねて利用者毎のサーバー側の状態を維持して2つの数字を加算する最小の例を与えています。
Client: [接続を開く。クライアント証明書は送られていない] Client: "gemini://example.net/application/" CRLF Server: "60 サーバー側状態を保つために証明書が必要です" CRLF Server: [接続を閉じる] Client: [アプリケーションが証明書を得るために何らかの動作をする] Client: [接続を開く。クライアント証明書が送られる] Client: "gemini://example.net/application/" CRLF Server: "10 0と9000の間の数値を入れてください" CRLF Server: [接続を閉じる] Client: [利用者にプロンプトを出し、入力を得る] Client: [接続を開き、クライアント証明書が送られる] Client: "gemini://example.net/application/?42" CRLF Server: [クライアント証明書を認識し、証明書に紐付くメモリないしディスクに値「42」を保管する] Server: "10 0と9000の間の別の数値を入れてください" CRLF Server: [接続を閉じる] Client: [利用者のプロンプトを出し、入力を得る] Client: [接続を開く。クライアント証明書が送られる] Client: "gemini://example.net/application/?1923" CRLF Server: [クライアント証明書を認識し、保管された値「42」を取り出し、受け取った値「1923」に加える] Server: "20 text/plain" CRLF "42足す1923は1965です。ごきげんよう" Server: [接続を閉じる]
この例ではサーバーはエラーを説明する追加テキスト付きで一時的な失敗を送ります。
Client: [接続を開く] Client: "gemini://example.net/data" CRLF Server: "41 現在は保守作業中" CRLF Server: [接続を閉じる]
最後の例は、これ以上の説明のない永続的な失敗のものです。
Client: [接続を開く] Client: "gemini://example.net/data" CRLF Server: "50" CRLF Server: [接続を閉じる]