あらまし
2024年もよろしくお願いいたします。SRE部のm-ishizukaです。 Timestamp(RFC3161)の実装中にlibreofficeからのリクエストがうまくparseできない自体に陥った。調査をした我々の前に訪れた非情な現実とは??
timestampとは
近年だと電子帳簿法と向き合っている人には馴染みがあるのでないでしょうか。 とあるhash値がとある時間に存在していたことを保証してくれるものです。
たとえばWebアーカイブを取得しても、それが後から改竄されていないことは保証することはできないですが、 timestampを取得していれば、手元のスクリーンショットの画像・PDFなどのtimestampを付与することで、 その画像・PDFがtimestampで保証された時刻に存在していた・そのデータ(のハッシュ値)はその時点から改竄されていないことを保証することができます。
freeの実装もあるようです。他にも調べればあるかも? https://freetsa.org/index_en.php
timestampと聞くと、"2006-01-02T15:04:05Z07:00" を想像しますよね。今回は違います。
突然現れたParse error(一部掻い摘み)
Request parse error, request is not timestamp der format.
MD8CAQEwMTANBglghkgBZQMEAgEFAAQgnni7omOquW5qU+3/EAm3lw8ty8S5TrRrICRvnmi1wCICBCBaWrsBAQE=
2024/01/26 16:57:12 http: panic serving [::1]:59764: asn1: syntax error: invalid boolean
goroutine 19 [running]:
net/http.(*conn).serve.func1()
/usr/local/Cellar/go/1.21.5/libexec/src/net/http/server.go:1868 +0xb9
panic({0x10033ca20?, 0xc00018c680?})
/usr/local/Cellar/go/1.21.5/libexec/src/runtime/panic.go:920 +0x270
net/http.HandlerFunc.ServeHTTP(0x10?, {0x100412928?, 0xc00020a000?}, 0xc0001c646c?)
/usr/local/Cellar/go/1.21.5/libexec/src/net/http/server.go:2136 +0x29
net/http.(*ServeMux).ServeHTTP(0x1000128a5?, {0x100412928, 0xc00020a000}, 0xc0001fe000)
/usr/local/Cellar/go/1.21.5/libexec/src/net/http/server.go:2514 +0x142
net/http.serverHandler.ServeHTTP({0x100411948?}, {0x100412928?, 0xc00020a000?}, 0x6?)
/usr/local/Cellar/go/1.21.5/libexec/src/net/http/server.go:2938 +0x8e
net/http.(*conn).serve(0xc0001fa000, {0x1004131e8, 0xc000181320})
/usr/local/Cellar/go/1.21.5/libexec/src/net/http/server.go:2009 +0x5f4
created by net/http.(*Server).Serve in goroutine 1
/usr/local/Cellar/go/1.21.5/libexec/src/net/http/server.go:3086 +0x5cb
opensslでパースしてみた。
まずは疑問に思ったのでopensslでparseしてみましょう。
ishizuka@mbp /tmp % echo MD8CAQEwMTANBglghkgBZQMEAgEFAAQgnni7omOquW5qU+3/EAm3lw8ty8S5TrRrICRvnmi1wCICBCBaWrsBAQE= | base64 -d | openssl ts -query -text
Version: 1
Hash Algorithm: sha256
Message data:
0000 - e7 88 89 9c 64 01 e5 83-cf 50 6b 64 f3 ca 31 8d ....d....Pkd..1.
0010 - bc 71 c8 80 17 1a 48 1f-18 89 d3 c5 d1 af 1f 0d .q....H.........
Policy OID: unspecified
Nonce: 0x56F8AA90E293EB1B
Certificate required: no
Extensions:
parseできるぞ・・・?
ishizuka@mbp /tmp % echo MD8CAQEwMTANBglghkgBZQMEAgEFAAQgnni7omOquW5qU+3/EAm3lw8ty8S5TrRrICRvnmi1wCICBCBaWrsBAQE= | base64 -d | openssl asn1parse -inform der
0:d=0 hl=2 l= 63 cons: SEQUENCE
2:d=1 hl=2 l= 1 prim: INTEGER :01
5:d=1 hl=2 l= 49 cons: SEQUENCE
7:d=2 hl=2 l= 13 cons: SEQUENCE
9:d=3 hl=2 l= 9 prim: OBJECT :sha256
20:d=3 hl=2 l= 0 prim: NULL
22:d=2 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:9E78BBA263AAB96E6A53EDFF1009B7970F2DCBC4B94EB46B20246F9E68B5C022
56:d=1 hl=2 l= 4 prim: INTEGER :205A5ABB
62:d=1 hl=2 l= 1 prim: BOOLEAN :1
しょうがないのでASN.1レベルで処理できる証明書と比較
Adobe Acrobat Readerからのリクエストの場合
ishizuka@mbp /tmp % echo MEMCAQEwMTANBglghkgBZQMEAgEFAAQgtwhCmGn9cA41oN7ld0zRDn6s+M7nsaVojFCjPtTZVBQCCAwpp2w4gIFyAQH/ | base64 -d | openssl asn1parse -inform der
0:d=0 hl=2 l= 67 cons: SEQUENCE
2:d=1 hl=2 l= 1 prim: INTEGER :01
5:d=1 hl=2 l= 49 cons: SEQUENCE
7:d=2 hl=2 l= 13 cons: SEQUENCE
9:d=3 hl=2 l= 9 prim: OBJECT :sha256
20:d=3 hl=2 l= 0 prim: NULL
22:d=2 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:B708429869FD700E35A0DEE5774CD10E7EACF8CEE7B1A5688C50A33ED4D95414
56:d=1 hl=2 l= 8 prim: INTEGER :0C29A76C38808172
66:d=1 hl=2 l= 1 prim: BOOLEAN :255
おやおやおや??? ここで違いのわかる漢であれば分かるらしい。
※私は30分ググりました。
ご安心下さい。すぐに気づける人はX.690 DER encodingを脳内でデコードできる人です。 違いのわかる漢に早くなりたいです。
答え。
62:d=1 hl=2 l= 1 prim: BOOLEAN :1
ASN1 / DER encodedにおけるBOOLEAN trueは、0xffのみで、0x01は未定義。
#エラーが正しい。というのがgolang #00じゃなければTrueにしちゃったのが昔の一部の実装
過去にも似たように問題があったとかなんとか
- WIDE project のCA https://www.wide.ad.jp/About/report/pdf2005/part19.pdf
- 他の開発者のMR(golang) https://github.com/golang/go/issues/11091
booleanで0x01が未定義って結構ハマりますよね。解ります。
どこに問題があるか調べよう。
今回は運良くコードにRFC3161のフレーズがコメントがあったので、サクッと当該コードが見つかる。
ishizuka@mbp ~/github/libreoffice-core % grep -ri rfc3161 .
./svl/source/crypto/cryptosign.cxx
1039 unsigned char cOne = 1;
1058 src.certReq.type = siUnsignedInteger;
1059 src.certReq.data = &cOne;
1060 src.certReq.len = sizeof(cOne);
ここがいけなさそう。多分。
unsigned char cBoolTrue = 255;
src.certReq.data = &cBoolTrue;
src.certReq.len = sizeof(cBoolTrue);
なら多分動くんじゃないかな?
答えもわかったので、とりあえず報告。
https://bugs.documentfoundation.org/show_bug.cgi?id=159381
- 2024-01-26 09:21:51 UTC 報告
- 2024-01-27 16:44:47 UTC 24.8.0(2024/08ごろリリースバージョン?)に取り込まれ
- 2024-01-27 21:48:16 UTC 24.2.1(2024/02ごろリリースバージョン?)に取り込まれ
大勝利!!
実際に取り込まれたコード
https://git.libreoffice.org/core/+/42e06fb686d4c02fc6efaf0a792319147e3d62bf%5E%21
unsigned char cTRUE = 0xff; // under DER rules true is 0xff, false is 0x00
src.certReq.data = &cTRUE;
src.certReq.len = sizeof(cTRUE);
おしい。わたしの詰めの甘さが露呈してしまいました
まとめ。
- 古から散見していたDERエンコードのTRUEは0xFF問題を一つ駆逐した
- timestampの利用者が少しでも増えてくれれば