zkat’s diary

技術ブログ

Chromeの履歴はSQLiteらしいのでいじってみる

概要

  • Chromeの履歴はSQLiteで管理されているらしく、容易に中身を見れそうなので試してみました。
  • 自分のChromeの履歴を用いて、どのウェブサイトに頻繁にアクセス(回数、滞在時間)しているか見てみます。

おことわり

  • SQLiteの.schemaコマンドを用いることで、どのようなテーブルが定義されているかは分かりますが、その使われ方(どのようなイベントが起きた時に、レコードが積まれるのか・レコードが更新されるのか)について明確に説明している公式情報を見つけられませんでした。
  • そのため、今回実行したSQLが目的の情報を正しく取得するものとなっているかは微妙なところがあります。

テーブル定義を見てみる

Historyファイル*1をsqlite3コマンドがある環境にコピーして開きます。.schemaコマンドで、定義されているテーブルを確認すると 以下、17のテーブルが定義されていました。

urlsvisitskeyword_search_termsあたりに面白そうなデータが入っていそうです。

CREATE TABLE meta(
    key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,
     value LONGVARCHAR);

CREATE TABLE downloads (
    id INTEGER PRIMARY KEY,
    guid VARCHAR NOT NULL,
    current_path LONGVARCHAR NOT NULL,
    target_path LONGVARCHAR NOT NULL,
    start_time INTEGER NOT NULL,
    received_bytes INTEGER NOT NULL,
    total_bytes INTEGER NOT NULL,
    state INTEGER NOT NULL,
    danger_type INTEGER NOT NULL,
    interrupt_reason INTEGER NOT NULL,
    hash BLOB NOT NULL,
    end_time INTEGER NOT NULL,
    opened INTEGER NOT NULL,
    last_access_time INTEGER NOT NULL,
    transient INTEGER NOT NULL,
    referrer VARCHAR NOT NULL,
    site_url VARCHAR NOT NULL,
    tab_url VARCHAR NOT NULL,
    tab_referrer_url VARCHAR NOT NULL,
    http_method VARCHAR NOT NULL,
    by_ext_id VARCHAR NOT NULL,
    by_ext_name VARCHAR NOT NULL,
    etag VARCHAR NOT NULL,
    last_modified VARCHAR NOT NULL,
    mime_type VARCHAR(
    255) NOT NULL,
    original_mime_type VARCHAR(
    255) NOT NULL,
     embedder_download_data VARCHAR NOT NULL DEFAULT '');

CREATE TABLE downloads_url_chains (
    id INTEGER NOT NULL,
    chain_index INTEGER NOT NULL,
    url LONGVARCHAR NOT NULL,
    PRIMARY KEY (id, chain_index) );

CREATE TABLE downloads_slices (
    download_id INTEGER NOT NULL,
    offset INTEGER NOT NULL,
    received_bytes INTEGER NOT NULL,
     finished INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (download_id, offset) );

CREATE TABLE typed_url_sync_metadata (
    storage_key INTEGER PRIMARY KEY NOT NULL,
    value BLOB);

CREATE TABLE IF NOT EXISTS "urls"(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    url LONGVARCHAR,
    title LONGVARCHAR,
    visit_count INTEGER DEFAULT 0 NOT NULL,
    typed_count INTEGER DEFAULT 0 NOT NULL,
    last_visit_time INTEGER NOT NULL,
    hidden INTEGER DEFAULT 0 NOT NULL);

CREATE TABLE sqlite_sequence(
    name,
    seq);

CREATE TABLE visit_source(
    id INTEGER PRIMARY KEY,
    source INTEGER NOT NULL);

CREATE TABLE keyword_search_terms (
    keyword_id INTEGER NOT NULL,
    url_id INTEGER NOT NULL,
    term LONGVARCHAR NOT NULL,
    normalized_term LONGVARCHAR NOT NULL);

CREATE TABLE segments (
    id INTEGER PRIMARY KEY,
    name VARCHAR,
    url_id INTEGER NON NULL);

CREATE TABLE segment_usage (
    id INTEGER PRIMARY KEY,
    segment_id INTEGER NOT NULL,
    time_slot INTEGER NOT NULL,
    visit_count INTEGER DEFAULT 0 NOT NULL);

CREATE TABLE content_annotations (
    visit_id INTEGER PRIMARY KEY,
    floc_protected_score DECIMAL(
    3,
     2),
    categories VARCHAR,
    page_topics_model_version INTEGER,
    annotation_flags INTEGER DEFAULT 0 NOT NULL,
     entities VARCHAR,
     related_searches VARCHAR,
     visibility_score NUMERIC DEFAULT -1,
     search_normalized_url,
     search_terms LONGVARCHAR,
     alternative_title);

CREATE TABLE context_annotations(
    visit_id INTEGER PRIMARY KEY,
    context_annotation_flags INTEGER DEFAULT 0 NOT NULL,
    duration_since_last_visit INTEGER,
    page_end_reason INTEGER,
     total_foreground_duration NUMERIC DEFAULT -1000000);

CREATE TABLE clusters(
    cluster_id INTEGER PRIMARY KEY,
    score NUMERIC NOT NULL);

CREATE TABLE clusters_and_visits(
    cluster_id INTEGER NOT NULL,
    visit_id INTEGER NOT NULL,
    score NUMERIC NOT NULL,
    PRIMARY KEY(cluster_id, visit_id))WITHOUT ROWID;

CREATE TABLE downloads_reroute_info (
    download_id INTEGER NOT NULL,
    reroute_info_serialized  VARCHAR NOT NULL,
    PRIMARY KEY (download_id) );

CREATE TABLE IF NOT EXISTS "visits"(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    url INTEGER NOT NULL,
    visit_time INTEGER NOT NULL,
    from_visit INTEGER,
    transition INTEGER DEFAULT 0 NOT NULL,
    segment_id INTEGER,
    visit_duration INTEGER DEFAULT 0 NOT NULL,
    incremented_omnibox_typed_score BOOLEAN DEFAULT FALSE NOT NULL,
    opener_visit INTEGER,
     originator_cache_guid TEXT,
     originator_visit_id INTEGER);

集計してみる

どれくらい頻繁にウェブサイトにアクセスしているか確認したいので、urlsvisitsのテーブルを用いれば良さそうです。 ドメイン単位に、どれくらいの時間閲覧していたか、どれくらいの回数アクセスしたかを集計するSQLを書いてみます。

閲覧時間について

閲覧時間が長いドメイン順にソートして、上位5件を取得しました。

sqlite> WITH joined_http_url AS (
   ...> SELECT replace(replace(urls.url, 'https://', ''), 'http://', '') AS replaced,
   ...>        visits.visit_duration AS duration
   ...> FROM visits
   ...> LEFT JOIN urls
   ...> ON visits.url = urls.id
   ...> WHERE urls.url LIKE 'https://%' or urls.url LIKE 'http://%'
   ...> ), per_domain AS (
   ...> SELECT CASE
   ...>            WHEN instr(replaced, '/') > 0 THEN substr(replaced, 1, instr(replaced, '/') -1)
   ...>            ELSE replaced
   ...>        END AS domain,
   ...>        duration
   ...>     FROM joined_http_url
   ...> ), aggregation AS (
   ...> SELECT domain,
   ...>        sum(duration) AS total
   ...> FROM per_domain
   ...> GROUP BY domain
   ...> )
   ...> SELECT (total / 3600 / 1000000) || ' hours ' || strftime('%M minutes %S seconds', total / 1000000 / 86400.0) as total_duration,
   ...>        domain
   ...> FROM aggregation
   ...> ORDER BY total DESC
   ...> LIMIT 5
   ...> ;
total_duration  domain
107 hours 44 minutes 31 seconds www.google.com
99 hours 44 minutes 21 seconds  docs.python.org
82 hours 56 minutes 20 seconds  github.com
64 hours 38 minutes 15 seconds  www.rfc-editor.org
64 hours 36 minutes 36 seconds  www.ietf.org

アクセス回数について

アクセス回数が多いドメイン順にソートして、上位5件を取得しました。

sqlite> WITH http_url AS (
   ...> SELECT replace(replace(urls.url, 'https://', ''), 'http://', '') AS replaced,
   ...>            urls.visit_count
   ...> FROM urls
   ...> WHERE urls.url LIKE 'https://%' or urls.url LIKE 'http://%'
   ...> ), per_domain AS (
   ...> SELECT CASE
   ...>            WHEN instr(replaced, '/') > 0 THEN substr(replaced, 1, instr(replaced, '/') -1)
   ...>            ELSE replaced
   ...>        END AS domain,
   ...>        visit_count
   ...>     FROM http_url
   ...> ), aggregation AS (
   ...> SELECT domain,
   ...>        sum(visit_count) AS total
   ...> FROM per_domain
   ...> GROUP BY domain
   ...> )
   ...> SELECT total,
   ...>        domain
   ...> FROM aggregation
   ...> ORDER BY total DESC
   ...> LIMIT 5
   ...> ;
total   domain
3240    www.google.com
1340    github.com
1326    docs.google.com
872     twitter.com
395     www.cambly.com

それらしいデータを得ることはできましたが、どうも閲覧時間の方の集計について 主観よりも長い時間になっているように思えます。ページを開きっぱなしにして、別のタブに移動していたとしても、アクセス時間として計上される仕様なのかもしれません。(そのあたりは十分に調べられていません)

*1:windowsであれば、C:\Users\ユーザー名\AppData\Local\Google\Chrome\User Data\Default配下に存在する模様

what3wordsをDNSで引けるようにした

概要

what3wordsというサービスを使うと、地球上のあらゆる場所を3つの単語で表せます。

例えば自由の女神の松明の場所はtoned.melt.shipで表せます。

https://w3w.co/toned.melt.ship

これを次のように、ドメイン名部分に単語を含ませる形でアクセスできるようにしてみました。

http://toned.melt.ship.point.place

仕組み

上記のpoint.placeドメインにアクセスすると、w3w.coが提供する本来のurlへリダイレクトするようにしています。

IDN対応

what3wordsでは、英単語のみならず日本語の単語でも場所を表すことができます。

例えば、次の単語も自由の女神の場所を表します。

あかり。ふえた。くねくね

ドメイン名は、英単語(ascii文字)のみならず日本語等も表現することができます。

そのため、以下を用いても同様にアクセスすることができます。

http://あかり。ふえた。くねくね.point.place

完備化の問題をいくつか解いてみたーその2

概要

拙作の完備化ツールをいろいろ使ってみたメモのその2です。 今回は、簡単な状態遷移図に対して用いてみます。 その1はこちらです。115個の完備化の問題を解いています。

zkat.hatenablog.com

やってみた

今回ツールに与える状態遷移図は以下のようにします。 これは、RFC 8555に出てくるとある状態遷移図をアレンジしたものです。

state-transition

ツールに入力できる形式で表すと以下の通りです。 trnという関数が、状態遷移を示しています。第二引数の状態に於いて、第一引数で示すトリガーが生じると、次の状態に遷移するということを表しています。

$ cat data
(VAR x y z)
(RULES

 trn(INIT, INIT)                      -> PENDING
 trn(ERROR, PENDING)                  -> INVALID
 trn(RECEIVEREQUEST, PENDING)         -> READY
 trn(ERROR, READY)                    -> INVALID
 trn(RECEIVEDATA, READY)              -> PROCESSING
 trn(ERROR, PROCESSING)               -> INVALID
 trn(FINISHCREATION, PROCESSING)      -> VALID

 car(cons(x,y)) -> x
 cdr(cons(x,y)) -> y
 cdr(NIL) -> NIL
 apply(NIL, x) -> x
 apply(x, y) -> apply(cdr(x), trn(car(x),y))
 trace(x) -> apply(x, INIT)
)

状態遷移図を辿る際に便利な関数traceを定義する為に、trn以外の諸々の関数を定義しています。 traceは、引数に1つのリストをとります。リストは状態遷移の為のトリガーを並べたものです。 traceは、リスト内のトリガー(操作)を全て実行した際の状態を返す関数です。

上記を完備化した書き換え系の下で、次の2つの項が何になるか確認してみたいと思います。 \begin{align} trace([INIT, RECEIVEREQUEST, RECEIVEDATA])\\ trace([INIT, ERROR]) \end{align}

$ wget https://github.com/moratori/clover/releases/download/v2.4.2/clover-linux-x86_64_v2.4.2 && chmod +x clover-linux-x86_64_v2.4.2
$ ./clover-linux-x86_64_v2.4.2 rewrite data 'trace([INIT, RECEIVEREQUEST, RECEIVEDATA])' 'trace([INIT, ERROR])'
YES

(VAR z x y)
(RULES
 trn(car(NIL),x) -> x
 apply(cons(y,x),z) -> apply(x,trn(y,z))
 apply(cdr(x),trn(car(x),y)) -> apply(x,y)
 trace(x) -> apply(x,INIT)
 apply(NIL,x) -> x
 cdr(NIL) -> NIL
 cdr(cons(x,y)) -> y
 car(cons(y,x)) -> y
 trn(FINISHCREATION,PROCESSING) -> VALID
 trn(ERROR,PROCESSING) -> INVALID
 trn(RECEIVEDATA,READY) -> PROCESSING
 trn(ERROR,READY) -> INVALID
 trn(RECEIVEREQUEST,PENDING) -> READY
 trn(ERROR,PENDING) -> INVALID
 trn(INIT,INIT) -> PENDING
)
(COMMENT
 ERROR < FINISHCREATION < INIT < INVALID < NIL < PENDING < PROCESSING < READY < RECEIVEDATA < RECEIVEREQUEST < VALID < TRN < APPLY < TRACE < CAR < CDR < CONS
)
(COMMENT
 trace([INIT, RECEIVEREQUEST, RECEIVEDATA]) reduced PROCESSING
 trace([INIT, ERROR]) reduced INVALID
)

最後に出力されている通り、 $trace([INIT, RECEIVEREQUEST, RECEIVEDATA])$は、$PROCESSING$となることが分かりました。 また、$trace([INIT, ERROR])$は、$INVALID$となることが分かりました。

完備化の問題をいくつか解いてみた

概要

先の記事に書きましたとおり、等式の完備化ツールをCommon Lispで作ってみています。

zkat.hatenablog.com

このツールを使って、いくつかの問題を実際に完備化してみたいと思いました。

試してみる

とは言っても、いい感じの入力となる問題を自分で作るのは難しいなと思っていたところ、以下を見つけました。

https://www.jaist.ac.jp/project/maxcomp/experiments/eq_systems.tar.gz

こちらは、JAISTの廣川研究室で開発されているツールMaxcomp(Maximal Completion)の 評価に用いられている問題のようです。

こちらの問題(合計115個)を利用させていただき、実行してみたところ以下の通りの結果となりました。

項目
実行環境(CPU) Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
実行環境(メモリ) 6GB
実行環境(OS) Ubuntu 20.04.1 LTS on WSL2
完備化できた問題の数 65
完備化に失敗した問題の数 6
タイムアウトした問題の数 44
合計問題数 115

一つの問題ごとに、タイムアウトは30秒を設定しています。 だいたい、半分程度の問題を完備化するにとどまりました。

結果の詳細

Results of the Knuth-Bendix completion algorithm implemented in the tool Clover https://github.com/moratori/clover · GitHub

補足

以下のように実行しています。

$ wget https://github.com/moratori/clover/releases/download/v2.2.1/clover-linux-x86_64_ver2.2.1
$ chmod +x clover-linux-x86_64_ver2.2.1
$ time timeout -k 3 30 ./clover-linux-x86_64_ver2.2.1 complete slothrop_ackermann.trs
a(s(x),s(y))=>a(x,a(s(x),y))
a(s(x),Z)=>a(x,s(Z))
a(Z,x)=>s(x)

real    0m0.170s
user    0m0.227s
sys     0m0.042s
$ 

Common LispでKnuth-Bendixの完備化アルゴリズムを実装した

概要

Knuth-Bendixの完備化アルゴリズムを実装したツールを作ってみました。いくつかの簡単な問題に対して使ってみながら紹介します。

github.com

なお、Knuth-Bendixの完備化アルゴリズムの詳細自体については、こちらのページが分かりやすいです。

www.nue.ie.niigata-u.ac.jp

インストールの仕方

Linux/Windowsのそれぞれ用に、実行可能バイナリをビルドしました。 こちらからダウンロードできます。

Releases · moratori/clover · GitHub

Common Lisp(と、roswell)の実行環境がある場合は、リポジトリをクローンしてコードを取得しても大丈夫です。

実行してみる(その1)

実行権限を確認して、ダブルクリックなりで起動します。 コードを取得しroswell経由で実行する場合は以下の通り、起動スクリプトを叩いてください。

$ ./roswell/clover.ros

実行するとREPLに入るので、そこに完備化したい等式を入力していきます。 例えば、群の公理を示す以下の3つの等式を入力します。

\begin{align} plus(ZERO, x)& = x\\ plus(plus(x,y),z)& = plus(x, plus(y,z))\\ plus(i(x),x)& = ZERO \end{align}

(NIL)>>> :def-axiom group
input . to finish definition
axiom[1]>>> plus(ZERO,x) = x
axiom[2]>>> plus(plus(x,y),z) = plus(x, plus(y,z))
axiom[3]>>> plus(i(x),x) = ZERO
axiom[4]>>> .
Detected that a set of equations has been inputted.
Do you want to execute completion algorithm?  (yes or no) yes

The completion process was successful:
i(plus(y,x))=>plus(i(x),i(y))
i(i(x))=>x
i(ZERO)=>ZERO
plus(x,ZERO)=>x
plus(x,i(x))=>ZERO
plus(x,plus(i(x),y))=>y
plus(i(x),plus(x,y))=>y
plus(ZERO,x)=>x
plus(plus(x,y),z)=>plus(x,plus(y,z))
plus(i(x),x)=>ZERO
(group)>>>

完備化が成功し、10個の項書き換え規則が出力されたことが分かります。 この状態で証明したい等式、例えば $plus(plus(x,y),plus(z,w)) = plus(x, plus(y, plus(z, w)))$ を入力してみます。

すると、想定通り証明することができます。

(group)>>> plus(plus(x,y),plus(z,w)) = plus(x, plus(y, plus(z, w)))
The equation can be PROVED under the axiom group

irreducible form under the group:
plus(x,plus(y,plus(z,w))) = plus(x,plus(y,plus(z,w)))

(group)>>>

先ほどの項書き換え規則の下で両辺の既約形が同じになった為、等式が成り立つことが分かります。

実行してみる(その2)

続いて別の問題として、以下リンクに記載のある「グラス置き換えパズル」を解いてみようと思います。

http://www.nue.ie.niigata-u.ac.jp/toyama/lab-intro/TRS-intro/index.html#glass

グラスの列の置き換えを、以下の等式で表してみます。列は $cons$ を用いたリストで表すこととしてます。

\begin{align} cons(S, cons(W, x))& = cons(W, x)\\ cons(x, cons(S, cons(W, y)))& = cons(x, cons(W, y))\\ cons(W, cons(B, x))& = cons(S, x)\\ cons(x, cons(W, cons(B, y)))& = cons(x, cons(S, y)) \end{align}

上2つは、列 $SW$ が列 $W$ と等しいことを示しています。

1つは、列の先頭に $SW$ が存在した場合、1つは、列の途中又は最後に存在した場合を示しています。 下2つも同様です。

この等式を完備化してみますと、3つの書き換え規則に変換されます。

(NIL)>>> :def-axiom glass
input . to finish definition
axiom[1]>>> cons(S, cons(W, x)) = cons(W, x)
axiom[2]>>> cons(x, cons(S, cons(W, y))) = cons(x, cons(W, y))
axiom[3]>>> cons(W, cons(B, x)) = cons(S, x)
axiom[4]>>> cons(x, cons(W, cons(B, y))) = cons(x, cons(S, y))
axiom[5]>>> .
Detected that a set of equations has been inputted.
Do you want to execute completion algorithm?  (yes or no) yes

The completion process was successful:
cons(S,cons(S,x))=>cons(S,x)
cons(S,cons(W,x))=>cons(W,x)
cons(W,cons(B,x))=>cons(S,x)
(glass)>>>

この状態で、次の2つの等式 $[S,S,W,B] = [W,B,W,B]$ と $[S,S,S,W] \neq [W,B,W,B]$ を証明してみます。

(glass)>>> [S,S,W,B] = [W,B,W,B]
The equation can be PROVED under the axiom glass

irreducible form under the glass:
cons(S,NIL) = cons(S,NIL)

(glass)>>> [S,S,S,W] != [W,B,W,B]
The equation can be PROVED under the axiom glass

irreducible form under the glass:
cons(W,NIL) ≠ cons(S,NIL)

想定通り証明することができました。

実装の為に参考にした情報

資料

  • Knuth-Bendixの完備化手続きとその応用

www.jstage.jst.go.jp

https://lat.inf.tu-dresden.de/webcms/studium/lehrveranstaltungen/sommersemester-2018/term-rewriting-systems/trs1Handout.pdf

実装関連

Maxcomp

JAISTの以下のページで公開されているHaskell実装を確認させていただきました。

www.jaist.ac.jp

egison-trs

EgisonおよびHaskellで実装されているコードを確認させていただきました。

github.com

ACMEでサーバー証明書を発行するプロセスを理解する

この記事の概要

  • Automatic Certificate Management Environment(ACME)で証明書を発行するプロセスを理解する為のメモ書きです。
  • ACMEサーバーを実際に立ち上げ手を動かすことで理解を深めたいと思います。
  • この記事では、ACMEのアカウントを作成するところまで書きます。

ACMEについて

  • ACMEは、RFC 8555で定められた証明書発行に関する仕組みです。
  • 様々な認証局で、アドホックに行われていたサーバー証明書発行手順を標準化する目的で作られています。
  • Let's EncryptなどのサービスがACMEを利用しています。

RFC 8555 - Automatic Certificate Management Environment (ACME)

進め方

RFCの22ページ目(7.1 Resources)に、次の通りフローが記載されていましたので、これに沿って進めてみます。

The following table illustrates a typical sequence of requests required to establish a new account with the server, prove control of an identifier, issue a certificate, and fetch an updated certificate some time after issuance. The "->" is a mnemonic for a Location header field pointing to a created resource.

Action Request Response
Get Directory GET directory 200
Get nonce HEAD newNonce 200
Create account POST newAccount 201 -> account
Submit order POST newOrder 201 -> order
Fetch challenges POST-as-GET order's authorization urls 200
Respond to challenges POST authorization challenge urls 200
Poll for status POST-as-GET order 200
Finalize order POST order's finalize url 200
Poll for status POST-as-GET order 200
Download certificate POST-as-GET order's certificate url 200

準備

ACMEに対応した認証局システムを準備します。 Let's Encryptより、ACMEプロトコルのテストベッドとして、Pebbleというシステムが提供されているので、 それを動かしてみます。

github.com

$ go get -u github.com/letsencrypt/pebble/...
$ cd $GOPATH/src/github.com/letsencrypt/pebble && go install ./...

pebbleを起動しておきます。

$ pebble -config ./test/config/pebble-config.json
Pebble 2020/10/10 15:16:39 Starting Pebble ACME server
省略
Pebble 2020/10/10 15:16:39 Listening on: 0.0.0.0:14000
Pebble 2020/10/10 15:16:39 ACME directory available at: https://0.0.0.0:14000/dir

やってみる

Get Directory

まずは、Get Directoryを行うことで、ACMEで行う各種操作のためのURLを取得します。 DirectoryのURL自体は、事前にクライアントに設定しておく必要があります。 Pebbleであれば、起動時にURLが出力されていました。

$ curl --insecure https://localhost:14000/dir
{
   "keyChange": "https://0.0.0.0:14000/rollover-account-key",
   "newAccount": "https://0.0.0.0:14000/sign-me-up",
   "newNonce": "https://0.0.0.0:14000/nonce-plz",
   "newOrder": "https://0.0.0.0:14000/order-plz",
   "revokeCert": "https://0.0.0.0:14000/revoke-cert",
   "meta": {
      "externalAccountRequired": false,
      "termsOfService": "data:text/plain,Do%20what%20thou%20wilt"
   },
}

応答として以下の6つとmetaというフィールドが定義されています。

  • newNonce
  • newAccount
  • newOrder
  • newAuthz
  • revokeCert
  • keyChange

newAuthzについては、ACMEサーバー側がpre-authorizationという仕組みに対応しない場合は、省略されます。 上記応答を見ると、pebbleでは対応していないようです。

更に、metaフィールドの中身については、次のフィールドがあります。

  • termsOfService
  • website
  • caaIdentities
  • externalAccountRequired

上記4つのmetaフィールドの使用例は以下の通りです。

"meta": {
  "termsOfService": "https://example.com/acme/terms/2017-5-30",
  "website": "https://www.example.com/",
  "caaIdentities": ["example.com"],
  "externalAccountRequired": false
}

Get nonce

ACMEクライアントが、サーバーにPOSTでアクセスする際にはnonceを送信する必要があります。 既存のnonceが有効でなくなってしまったり、初めてサーバーにアクセスする際に、nonceを取得する必要があります。

先ほどのDirectoryのnewNonceのURLに対してHEADメソッドで要求すると、nonceを取得できます。 nonceはReplay-Nonceフィールドに記載されています。

$ curl --insecure --head https://localhost:14000/nonce-plz
HTTP/2 200
cache-control: public, max-age=0, no-cache
link: <https://localhost:14000/dir>;rel="index"
replay-nonce: wUoLKrGFi1i8rmbHn40Wyg
date: Sat, 10 Oct 2020 14:20:54 GMT

取得したnonceは、リクエストボディのJWSの中に記載します。 具体的には以下のような形で使用します。

POST /acme/new-account HTTP/1.1
Host: example.com
Content-Type: application/jose+json

{"protected": base64url(
  {"alg": "ES256",
   "jwk": {...},
   "nonce": "6S8IqOGY7eL2lsGoTZYifg",
   "url": "https://example.com/acme/new-account" }), 
 "payload": base64url(
  {"termsOfServiceAgreed": true,
   "contact": ["mailto:cert-admin@example.org",
               "mailto:admin@example.org"]}),
 "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I" 
}

Create account

ディレクトリのnewAccountにて取得したURLに対して、以下のようにJWSメッセージをPOSTすることでアカウントを作成します。

$ cat createaccount_message | jq .
{
  "protected": "eyJhbGciOiJSUzI1NiIsIm5vbmNlIjoiWjBMeVpkUWtlNzlURndmdDRYMExJdyIsInVybCI6Imh0dHBzOi8vbG9jYWxob3N0OjE0MDAwL3NpZ24tbWUtdXAiLCJqd2siOnsibiI6IndRcFJQVDZPN1RNSkExWURtazdVLVhsSnZ6TElMSTl3U0ZKSmQ2Z1ZmTzBuOVRUU2VGbHlMYXBVMWZYeU4xU2xJM3dhWm9pTElSSnN3ME9OMUd3NkFLVDhvd1BVZ0lPWTFVLXZRTHF2OTVKOXJVcUlnRGdMQ3dsQmNiVzZJUV9Dckdlb2RuR3VnWFZKYXRoTmt4NmJIS2xOT2ZHUmpMTTJFOWZvbWhReDVIWkRvUTV2R3NtUUVsX01SOUVkd042SzNnVGx5YzBTb1FxU0dwOTJEZ19XODdhelg0eF9hU0pNUDFrLXJSU0V5anc2cVVyVUlKcGdZazdYY3BiS01ZTGR0Si1QdmtlM2x6c0pHdGV3YnRRSWhmZzczZF90WU1Kd3ZfMHlZQ3kySGRaekVkRU43RjZZQjczaFBjQVB1MUdhMjNjVzRZNUFHZFJfSlNRbDFEWTRuUSIsImUiOiJBUUFCIiwia3R5IjoiUlNBIn19",
  "signature": "VrYli0NVuAY4YeX8X97fBPHP4a6KQv0fLph4WB6e4J1tu-aCaZtrWpB2vICCjfjQH9rjnjTO5cdNjZAJSn_C-rYH2WIuIcfqH4k73aU6YPJnLHKB8CBTOLUWGtS3bSlOCuA7sKT294lpcc7VOE30yRbit65OxaPW7m45junVtOQ4SdZveP-11Kiw0QPlhFXgcjT4y4n23g4AOw3JBtjPirUYUnAiF3L15wqMWSIr8tUrbj4rNCO37esxvOKQO8gXpI6udSGZ2D9u1jQhSEocFP0tEGG4YMG5b_D_H56YPWDeJxpZx-zqU7wwnV2wYv5R3aiRvV4K6m_ewrdRr3OHAg",
  "payload": "eyJ0ZXJtc09mU2VydmljZUFncmVlZCI6dHJ1ZSwiY29udGFjdCI6WyJtYWlsdG86Y2VydC1hZG1pbjFAZXhhbXBsZS5vcmciXX0"
}

$ curl -v --insecure -H'Content-Type: application/jose+json' -X POST --data-binary @createaccount_message https://localhost:14000/sign-me-up

< HTTP/2 201
< cache-control: public, max-age=0, no-cache
< content-type: application/json; charset=utf-8
< link: <https://localhost:14000/dir>;rel="index"
< location: https://localhost:14000/my-account/1
< replay-nonce: 9IQ52Zb5rNG7J0AwAoV-pA
< content-length: 552
< date: Sun, 11 Oct 2020 17:19:26 GMT
<
{
  "status": "valid",
  "contact": [
    "mailto:cert-admin1@example.org"
  ],
  "orders": "https://localhost:14000/list-orderz/1",
  "key": {
    "kty": "RSA",
    "n": "wQpRPT6O7TMJA1YDmk7U-XlJvzLILI9wSFJJd6gVfO0n9TTSeFlyLapU1fXyN1SlI3waZoiLIRJsw0ON1Gw6AKT8owPUgIOY1U-vQLqv95J9rUqIgDgLCwlBcbW6IQ_CrGeodnGugXVJathNkx6bHKlNOfGRjLM2E9fomhQx5HZDoQ5vGsmQEl_MR9EdwN6K3gTlyc0SoQqSGp92Dg_W87azX4x_aSJMP1k-rRSEyjw6qUrUIJpgYk7XcpbKMYLdtJ-Pvke3lzsJGtewbtQIhfg73d_tYMJwv_0yYCy2HdZzEdEN7F6YB73hPcAPu1Ga23cW4Y5AGdR_JSQl1DY4nQ",      "e": "AQAB"
  }
}

上記JWSのメッセージは、Pythonのauthlibを利用して作成しました。

ちなみにACMEクライアントのJWSメッセージの全てに共通ですが、以下のフィールドは必須となります。

  • alg
  • nonce
  • url

更に、jwkまたは、kidのどちらかが必要です。newAccountの場合は、jwkが必要になります。