どこに「OpenPGP公開鍵サーバにおいて鍵や署名の追記しかできない」と規定されているか
OpenPGP 公開鍵サーバにおいて鍵や署名の追記しかできない
deluid
- Note that it is not possible to retract a user id, once it has been send to the public (i.e. to a keyserver). In that case you better userevuid
.
MIT PGP Serverの後継、OpenPGP Public Key Server (PKS) pks-0.9.6+cvs
のソースコードkd_add.c
とllist.c
をざっくり読んだところ、
- 追加リクエストで得られた公開鍵パケットをできるだけ廃棄せず既存の公開鍵パケットにマージする
- 同じユーザーIDに付随する署名パケットは最近のものを先にマージする
ようだ。
伝統的な OpenPGP 鍵サーバについては以前にちょっとだけ調べたことがあるのですが Marc Horowitz という人が作った MIT PGP Server がオリジナルと言われています。 HKP (HTTP Keyserver Protocol) を備えている鍵サーバは大抵これのクローンか改良版です。 HKP 鍵サーバを RFC 化しようという動きもあったようですがドラフトのまま終わってるみたいです(なので明確な規格と言えるものはありません)。
上で紹介していただいた記事の最後でこっそり紹介しているのですが,新しい鍵サーバが提案されていて keys.openpgp.org として実装されています。こちらは鍵の持ち主であると証明できれば色々と操作できるっぽいです。また,メールソフト Thunderbird の最近のバージョンはこれを既定の鍵サーバとしているようです。
ありがとうございました!
MIT PGP Serverはバージョンの進んだものがOpenPGP Public Key Serverとして公開されていて、ざっくりソースコードを読んで公開鍵が追加された時の動作をなんとなく把握することができたように思います。
最近、公開鍵に添付する自己署名証明書のnotationを利用してオンラインでのアイデンティティを証明しようという https://keyoxide.org/ を試してみていて、公開鍵サーバにnotationを削除した公開鍵を追加した時の動作が直感とは異なっていたので調べてみた次第です。もうすこし理解を深めてから公開鍵サーバの動作を https://mitome.in/sns/keyoxide.html に追記します。
公開鍵サーバの実装から探してみる
- https://github.com/cmars/sks-keyserver OCamlによる実装。READMEには規格への言及はなかった
hockeypuck/openpgp
https://code.launchpad.net/hockeypuck Goによる実装。規格への言及は見つけられなかったが、 https://github.com/hockeypuck/hockeypuck/tree/master/src/hockeypuck/openpgp にキーサーバー用の実装がありそうだ。
This package supports the unique concerns of a keyserver, which is not (and probably should not be) addressed in a typical OpenPGP implementation oriented toward user agents.
hockeypuck/pghkp/storage.goを眺めるとsubkeyやkeyの削除やkeyの更新やsubkeyの追加は実装されているようだ。このファイルにはsubpacketの操作は実装されていない。
OpenPGP Public Key Server (PKS)
http://pks.sourceforge.net/pks-current.tgz をダウンロードすると、configure.in
のAC_INIT
よりバージョンは0.9.6+cvs
だった。
pks_help.ja
ファイルに下記のような記述が見られた。
各々の鍵サーバでの処理には所定の電子メールのメッセージ形式が必要です。サーバへの命令はSubject: 行に記述します。
(中略)
たとえば、あなたの鍵を鍵サーバへ加える、あるいは既に鍵サーバに登録している鍵を更新するためには以下のようなメッセージをいずれかの鍵サーバに送ります。
To: pgp-public-keys@keys.pgp.net From: johndoe@some.site.edu Subject: add -----BEGIN PGP PUBLIC KEY BLOCK----- Version: 2.6 <blah blah blah> -----END PGP PUBLIC KEY BLOCK-----
盗まれた鍵: 鍵廃棄証明書 (Key Revocation Certificate : PGPのドキュメントには、どのように行うかが書かれています)を作成し、ADDコマンドを使ってメールであなたの鍵をサーバにもう一度送ってください。
利用できるコマンド:
コマンド 結果 HELP このメッセージが送られます HELP language 翻訳されたヘルプテキスト(DE, EN, ES, FI, FR, HR, NO, JA) ADD 本文に含まれたPGP公開鍵を加えます。 INDEX userid ユーザIDを含む全てのPGP鍵リスト VERBOSE INDEX userid ユーザIDを含む全てのPGP鍵冗長リスト GET userid ユーザIDにマッチした鍵を得る LAST days days
で指定した間に更新された鍵
kd_add.c
に署名をマージする実装がありそうだ。全文を https://gist.github.com/zunda/ecda12811f27ebfe6701289d5c358571 にペーストした。
/* 前略 */
/* for all of these, the first element will come from the database,
the second from the request */
typedef struct _merge_state
{
/* statistics */
int new_sigs;
int repl_sigs;
int new_userids;
int changed_primary_userids;
int new_revocations;
int new_pubkeys;
int not_changed_revocation_sig;
/* added stuff */
int save_add;
llist add_keys;
llist add_userids;
llist add_sigs;
xbuffer *add_xb;
/* verbose printing state */
int verbose;
keys_elem *this_ke;
unsigned char *this_userid;
int this_userid_len;
}
merge_state;
/* 中略 */
/* this function has the potential problem that if there are two keys
out there with the same keyid, if both of them have signatures,
they are indistinguishable without access to the public keys. Even
then, the only way to tell is to attempt to verify the signature
with each public key to see if that is, in fact, the correct public
key. And even then, you can't ever decide that a signature is
invalid, because you could just be missing the public key. This
seems to be a fundamental flaw in the keyring packet formats. */
int
kd_sigs_elem_merge (llist * lout, void *e1, void *e2, void *c)
{
/* 中略 */
}
/* 中略 */
int
kd_userids_elem_merge (llist * lout, void *e1, void *e2, void *c)
{
/* 中略 */
o = sigs_elem_order (e1, e2);
/* if the sig keyids are different, move the smaller keyid
to the output list */
/* 中略 */
/* if the sig keyids are the same, move the more recent signature
to the output list and delete the less recent. If they are
the same, take the database sig so the stats work out right */
if (se1->sig_time >= se2->sig_time)
{
if (!llist_add (lout, e1))
{
dabort ();
return (LLIST_MERGE_FAIL);
}
sigs_elem_free (se2, NULL);
}
else
{
/* 中略 */
}
/* 後略 */
kd_sigs_elem_merge (llist * lout, void *e1, void *e2, void *c)
sigs_elem_order()
でe1
とe2
のkeyid
を比較する。keyid
が異なる場合には小さい方を出力リストlout
に移動する。e2
の方が小さければ、署名も出力に追加する。keyid
が同じ場合には、最近のものを出力リストに追加し、e2
の方が最近ならば、署名も出力に追加する。keyid
が同じ場合はe1
とe2
の双方を入力リストから削除する。
kd_userids_elem_merge (llist * lout, void *e1, void *e2, void *c)
userids_elem_order()
でe1
とe2
のuserid
を比較する。userid
が異なる場合には小さい方を出力リストlout
に移動する。e2
の方が小さければ、署名も出力に追加する。userid
が同じ場合にはkd_sigs_elem_merge
で署名をマージする。
kd_userids_elem_merge()
じたいはllist.c
に実装されているllist_merge()
を通して呼ばれる。llist_merge()
は2つのリンクリストを、与えられた関数で最初の要素どうしを比較させ必要なら出力リストに追加させ、与えられた関数の返り値によって2つのリンクリストの最初の要素のうち削除の必要なものを削除する。この動作は両方のリンクリストが空になるまで繰り返される。
この関数の外ではkd_keys_elem_merge()
←db_key_merge_1()
←db_key_merge()
←kd_add_1()
←kd_add()
と呼び出しが起きる。
PGP Public Key Server
Spiegelさんに教えていただいた、Marc HorowitzさんによるMIT PGP Server。ページの最終更新は2005年5月でコードのバージョンは0.9.4。
上記のOpenPGP Public Key Serverに続くものだろうか?例えばkd_add.c
にはint kd_sigs_elem_merge(llist *lout, void *e1, void *e2, void *c)
の定義があり上記と同じコメントが付いている。OpenPGP Public Key ServerがMIT PGP Serverの後継と思って間違いないだろう。
どのsignature packetが有効になるのか再確認する
Keyoxideの実装を追ったところ、鍵サーバから得られた自己署名証明書のうち一番最初のものを有効なものとしているようだ。
verifications = await doip.claims.verify(keyData, fingerprint, doipOpts)
でkeyData
に含まれるnotationを検証しているように見える。verify
メソッド内で呼んでいるkeys.getUserData
の返り値には有効なnotationのみが含まれ、無効なパケットは見られない。getUserData
が返却するのはconst keyData = await process(publicKey)
よりkeyData.users
。
const process = (publicKey) => {
return new Promise(async (resolve, reject) => {
if (!publicKey) {
reject('Invalid public key')
}
const fingerprint = await publicKey.primaryKey.getFingerprint()
const primaryUser = await publicKey.getPrimaryUser()
const users = publicKey.users
let primaryUserIndex,
usersOutput = []
users.forEach((user, i) => {
usersOutput[i] = {
userData: {
id: user.userId ? user.userId.userid : null,
name: user.userId ? user.userId.name : null,
email: user.userId ? user.userId.email : null,
comment: user.userId ? user.userId.comment : null,
isPrimary: primaryUser.index === i,
},
}
if ('selfCertifications' in user && user.selfCertifications.length > 0) {
const notations = user.selfCertifications[0].rawNotations
// このuser.selfCertificationsには無効になったものも含まれるが、そのうち最初のもののみを取り上げている
usersOutput[i].notations = notations.map(
({ name, value, humanReadable }) => {
if (humanReadable && name === 'proof@metacode.biz') {
return openpgp.util.decode_utf8(value)
}
}
)
} else {
usersOutput[i].notations = []
}
})
resolve({
fingerprint: fingerprint,
users: usersOutput,
primaryUserIndex: primaryUser.index,
})
})
}
openpgp.jsのコードに続く。selfCertifications
にパケットをpushする時点でパケットがcreated
の逆順に並んでいる。
for (let i = 0; i < packetlist.length; i++) {
switch (packetlist[i].tag) {
:
case enums.packet.signature:
switch (packetlist[i].signatureType) {
case enums.signature.cert_generic:
case enums.signature.cert_persona:
case enums.signature.cert_casual:
case enums.signature.cert_positive:
if (!user) {
util.print_debug('Dropping certification signatures without preceding user packet');
continue;
}
if (packetlist[i].issuerKeyId.equals(primaryKeyId)) {
user.selfCertifications.push(packetlist[i]);
} else {
user.otherCertifications.push(packetlist[i]);
}
break;
:
console.trace()
すると、得られたarmored鍵をそのまま読んでいる。自己署名証明書を日付の逆順に並べているのはキーサーバーのようだ。
公開鍵サーバのRFC (ドラフト版)
SpiegelさんからRFCのドラフト版があることを教えていただいた。The OpenPGP HTTP Keyserver Protocol (HKP)のSubmitting Keys To A Keyserver章には、既存の公開鍵とのマージの方針等は記述されていない。