1
(************************************************************************)
2
(* This file is part of SKS. SKS is free software; you can
3
redistribute it and/or modify it under the terms of the GNU General
4
Public License as published by the Free Software Foundation; either
5
version 2 of the License, or (at your option) any later version.
7
This program is distributed in the hope that it will be useful, but
8
WITHOUT ANY WARRANTY; without even the implied warranty of
9
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
General Public License for more details.
12
You should have received a copy of the GNU General Public License
13
along with this program; if not, write to the Free Software
14
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
16
(***********************************************************************)
18
(** code for generating pretty PGP key indices *)
1
(***********************************************************************)
2
(* index.ml - code for generating pretty PGP key indices *)
4
(* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, *)
5
(* 2011, 2012 Yaron Minsky and Contributors *)
7
(* This file is part of SKS. SKS is free software; you can *)
8
(* redistribute it and/or modify it under the terms of the GNU General *)
9
(* Public License as published by the Free Software Foundation; either *)
10
(* version 2 of the License, or (at your option) any later version. *)
12
(* This program is distributed in the hope that it will be useful, but *)
13
(* WITHOUT ANY WARRANTY; without even the implied warranty of *)
14
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *)
15
(* General Public License for more details. *)
17
(* You should have received a copy of the GNU General Public License *)
18
(* along with this program; if not, write to the Free Software *)
19
(* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *)
20
(* USA or see <http://www.gnu.org/licenses/>. *)
21
(***********************************************************************)
30
33
(********************************************************************)
32
35
type siginfo = { mutable userid: string option;
33
mutable policy_url: string option;
34
mutable notation_data: (string * string) option;
35
mutable revocation_key: string option;
36
mutable is_primary_uid: bool;
37
mutable keyid: string option;
39
mutable sig_creation_time: int64 option;
40
mutable sig_expiration_time: int64 option;
41
mutable key_expiration_time: int64 option;
36
mutable policy_url: string option;
37
mutable notation_data: (string * string) option;
38
mutable revocation_key: string option;
39
mutable is_primary_uid: bool;
40
mutable keyid: string option;
42
mutable sig_creation_time: int64 option;
43
mutable sig_expiration_time: int64 option;
44
mutable key_expiration_time: int64 option;
44
47
(********************************************************************)
46
let empty_siginfo () =
49
let empty_siginfo () =
49
52
notation_data = None;
67
70
(********************************************************************)
69
let sig_to_siginfo sign =
72
let sig_to_siginfo sign =
70
73
let siginfo = empty_siginfo () in
72
75
match ParsePGP.parse_signature sign with
74
siginfo.sigtype <- s.v3s_sigtype;
75
siginfo.keyid <- Some s.v3s_keyid;
76
siginfo.sig_creation_time <- Some s.v3s_ctime
77
siginfo.sigtype <- s.v3s_sigtype;
78
siginfo.keyid <- Some s.v3s_keyid;
79
siginfo.sig_creation_time <- Some s.v3s_ctime
78
let update_siginfo ssp =
79
match ssp.ssp_type with
81
| 2 -> (* sign. expiration time *)
82
if ssp.ssp_length = 4 then
83
siginfo.sig_creation_time <-
84
Some (ParsePGP.int64_of_string ssp.ssp_body)
86
| 3 -> (* sign. expiration time *)
87
if ssp.ssp_length = 4 then
88
siginfo.sig_expiration_time <-
89
let exp = ParsePGP.int64_of_string ssp.ssp_body in
90
if Int64.compare exp Int64.zero = 0
91
then None else Some exp
93
| 9 -> (* key expiration time *)
94
if ssp.ssp_length = 4 then
95
siginfo.key_expiration_time <-
96
let exp = ParsePGP.int64_of_string ssp.ssp_body in
97
if Int64.compare exp Int64.zero = 0
98
then None else Some exp
100
| 12 -> (* revocation key *)
101
let cin = new Channel.string_in_channel ssp.ssp_body 0 in
102
let _revclass = cin#read_int_size 1 in
103
let _algid = cin#read_int_size 1 in
104
let fingerprint = cin#read_string 20 in
105
siginfo.revocation_key <- Some fingerprint
107
| 16 -> (* issuer keyid *)
108
if ssp.ssp_length = 8 then
109
siginfo.keyid <- Some ssp.ssp_body
111
printf "Argh! that makes no sense: %d\n" ssp.ssp_length
113
| 20 -> (* notation data *)
114
let cin = new Channel.string_in_channel ssp.ssp_body 0 in
115
let flags = cin#read_string 4 in
116
let name_len = cin#read_int_size 2 in
117
let value_len = cin#read_int_size 2 in
118
let name_data = cin#read_string name_len in
119
let value_data = cin#read_string value_len in
121
if Char.code flags.[0] = 0x80 then
122
(* human-readable notation data *)
123
siginfo.notation_data <- Some (name_data,value_data)
125
| 25 -> (* primary userid (bool) *)
126
if ssp.ssp_length = 1 then
127
let v = int_of_char ssp.ssp_body.[0] in
128
siginfo.is_primary_uid <- v <> 0
130
| 26 -> (* policy URL *)
131
siginfo.policy_url <- Some ssp.ssp_body
133
| 28 -> (* signer's userid *)
134
siginfo.userid <- Some ssp.ssp_body
136
| _ -> (* miscellaneous other packet *)
139
siginfo.sigtype <- s.v4s_sigtype;
140
List.iter (s.v4s_hashed_subpackets @ s.v4s_unhashed_subpackets)
141
~f:(fun ssp -> try update_siginfo ssp with End_of_file -> ())
81
let update_siginfo ssp =
82
match ssp.ssp_type with
84
| 2 -> (* sign. expiration time *)
85
if ssp.ssp_length = 4 then
86
siginfo.sig_creation_time <-
87
Some (ParsePGP.int64_of_string ssp.ssp_body)
89
| 3 -> (* sign. expiration time *)
90
if ssp.ssp_length = 4 then
91
siginfo.sig_expiration_time <-
92
let exp = ParsePGP.int64_of_string ssp.ssp_body in
93
if Int64.compare exp Int64.zero = 0
94
then None else Some exp
96
| 9 -> (* key expiration time *)
97
if ssp.ssp_length = 4 then
98
siginfo.key_expiration_time <-
99
let exp = ParsePGP.int64_of_string ssp.ssp_body in
100
if Int64.compare exp Int64.zero = 0
101
then None else Some exp
103
| 12 -> (* revocation key *)
104
let cin = new Channel.string_in_channel ssp.ssp_body 0 in
105
let _revclass = cin#read_int_size 1 in
106
let _algid = cin#read_int_size 1 in
107
let fingerprint = cin#read_string 20 in
108
siginfo.revocation_key <- Some fingerprint
110
| 16 -> (* issuer keyid *)
111
if ssp.ssp_length = 8 then
112
siginfo.keyid <- Some ssp.ssp_body
114
printf "Argh! that makes no sense: %d\n" ssp.ssp_length
116
| 20 -> (* notation data *)
117
let cin = new Channel.string_in_channel ssp.ssp_body 0 in
118
let flags = cin#read_string 4 in
119
let name_len = cin#read_int_size 2 in
120
let value_len = cin#read_int_size 2 in
121
let name_data = cin#read_string name_len in
122
let value_data = cin#read_string value_len in
124
if Char.code flags.[0] = 0x80 then
125
(* human-readable notation data *)
126
siginfo.notation_data <- Some (name_data,value_data)
128
| 25 -> (* primary userid (bool) *)
129
if ssp.ssp_length = 1 then
130
let v = int_of_char ssp.ssp_body.[0] in
131
siginfo.is_primary_uid <- v <> 0
133
| 26 -> (* policy URL *)
134
siginfo.policy_url <- Some ssp.ssp_body
136
| 28 -> (* signer's userid *)
137
siginfo.userid <- Some ssp.ssp_body
139
| _ -> (* miscellaneous other packet *)
142
siginfo.sigtype <- s.v4s_sigtype;
143
List.iter (s.v4s_hashed_subpackets @ s.v4s_unhashed_subpackets)
144
~f:(fun ssp -> try update_siginfo ssp with End_of_file -> ())
145
148
(********************************************************************)
147
150
(** sort signatures in ascending time order *)
148
let sort_siginfo_list list =
151
let sort_siginfo_list list =
149
152
List.stable_sort list
150
153
~cmp:(fun x y -> compare x.sig_creation_time y.sig_creation_time)
156
159
(********************************************************************)
158
161
let is_primary ~keyid (uid,siginfo_list) =
159
List.exists ~f:(fun siginfo ->
160
is_selfsig ~keyid siginfo
161
&& siginfo.is_primary_uid
162
&& uid.packet_type = User_ID_Packet
162
List.exists ~f:(fun siginfo ->
163
is_selfsig ~keyid siginfo
164
&& siginfo.is_primary_uid
165
&& uid.packet_type = User_ID_Packet
166
169
(********************************************************************)
168
171
(** returns time of most recent self-sig on uid *)
169
let max_selfsig_time ~keyid (uid,siginfo_list) =
170
let selfsigs = List.filter ~f:(fun si -> is_selfsig ~keyid si)
172
let max_selfsig_time ~keyid (uid,siginfo_list) =
173
let selfsigs = List.filter ~f:(fun si -> is_selfsig ~keyid si)
172
175
let times = filter_opts
174
~f:(function x -> match x.sig_creation_time with
176
| Some time -> Some (Int64.to_float time)))
177
~f:(function x -> match x.sig_creation_time with
179
| Some time -> Some (Int64.to_float time)))
178
181
List.fold_left ~init:min_float ~f:max times
180
183
(********************************************************************)
182
let split_list ~f l =
185
let split_list ~f l =
183
186
let rec loop l a b = match l with
184
187
[] -> (List.rev a, List.rev b)
186
if f hd then loop tl (hd::a) b
187
else loop tl a (hd::b)
189
if f hd then loop tl (hd::a) b
190
else loop tl a (hd::b)
191
194
(********************************************************************)
193
let move_primary_to_front ~keyid uids =
196
let move_primary_to_front ~keyid uids =
194
197
let (primary,normal) = split_list ~f:(is_primary ~keyid) uids in
195
198
let primary = List.stable_sort primary
196
~cmp:(fun x y -> compare
197
(max_selfsig_time ~keyid y)
198
(max_selfsig_time ~keyid x)
199
~cmp:(fun x y -> compare
200
(max_selfsig_time ~keyid y)
201
(max_selfsig_time ~keyid x)
203
206
(********************************************************************)
205
let convert_sigpair (uid,sigs) =
208
let convert_sigpair (uid,sigs) =
206
209
(uid,List.map ~f:sig_to_siginfo sigs)
208
211
(********************************************************************)
210
213
let blank_datestr = "__________"
211
214
let no_datestr = " "
212
let datestr_of_int64 i =
215
let datestr_of_int64 i =
213
216
let tm = Unix.gmtime (Int64.to_float i) in
214
sprintf "%04d-%02d-%02d" (1900 + tm.Unix.tm_year) (1 + tm.Unix.tm_mon)
217
sprintf "%04d-%02d-%02d" (1900 + tm.Unix.tm_year) (1 + tm.Unix.tm_mon)
215
218
(tm.Unix.tm_mday)
217
220
(********************************************************************)
219
let siginfo_to_lines ~get_uid ?key_creation_time request self_keyid today siginfo =
222
let siginfo_to_lines ~get_uid ?key_creation_time request self_keyid today siginfo =
221
224
let sig_creation_string = match siginfo.sig_creation_time with
222
225
| None -> blank_datestr
223
226
| Some time -> datestr_of_int64 time
226
let key_expiration_string =
229
let key_expiration_string =
227
230
match (key_creation_time,
228
siginfo.key_expiration_time)
231
siginfo.key_expiration_time)
230
233
| (None,_) | (_,None) -> blank_datestr
231
234
| (Some x,Some y) -> datestr_of_int64 (Int64.add x y)
234
let sig_expiration_string =
237
let sig_expiration_string =
235
238
match (siginfo.sig_creation_time,
236
siginfo.sig_expiration_time)
239
siginfo.sig_expiration_time)
238
241
| (None,_) | (_,None) -> blank_datestr
239
242
| (Some x,Some y) -> datestr_of_int64 (Int64.add x y)
242
245
let sig_expired =
243
246
match (siginfo.sig_creation_time,
244
247
siginfo.sig_expiration_time)
270
273
let uid_string = match siginfo.userid with
273
if Some self_keyid = siginfo.keyid then "[selfsig]"
275
match apply_opt get_uid siginfo.keyid with
276
| None | Some None -> "[]"
277
| Some (Some uid) -> uid
276
if Some self_keyid = siginfo.keyid then "[selfsig]"
278
match apply_opt get_uid siginfo.keyid with
279
| None | Some None -> "[]"
280
| Some (Some uid) -> uid
279
282
let uid_string = HtmlTemplates.html_quote uid_string in
280
283
let uid_string = match siginfo.keyid with
281
284
None -> uid_string
283
if uid_string = "" then ""
285
let long = Fingerprint.keyid_to_string ~short:false keyid in
287
HtmlTemplates.link ~op:"vindex"
288
~hash:request.hash ~fingerprint:request.fingerprint ~keyid:long
290
sprintf "<a href=\"%s\">%s</a>" link uid_string
286
if uid_string = "" then ""
288
let long = Fingerprint.keyid_to_string ~short:false keyid in
290
HtmlTemplates.link ~op:"vindex"
291
~hash:request.hash ~fingerprint:request.fingerprint ~keyid:long
293
sprintf "<a href=\"%s\">%s</a>" link uid_string
293
296
let keyid_string = match siginfo.keyid with
295
let short = Fingerprint.keyid_to_string ~short:true keyid in
296
let long = Fingerprint.keyid_to_string ~short:false keyid in
298
HtmlTemplates.link ~op:"get"
299
~hash:request.hash ~fingerprint:request.fingerprint ~keyid:long
301
sprintf "<a href=\"%s\">%s</a>" link short
298
let short = Fingerprint.keyid_to_string ~short:true keyid in
299
let long = Fingerprint.keyid_to_string ~short:false keyid in
301
HtmlTemplates.link ~op:"get"
302
~hash:request.hash ~fingerprint:request.fingerprint ~keyid:long
304
sprintf "<a href=\"%s\">%s</a>" link short
306
309
let firstline = sprintf "sig %-6s %s %s %s %s %s"
307
sigtype_string keyid_string
308
sig_creation_string sig_expiration_string
309
key_expiration_string
310
sigtype_string keyid_string
311
sig_creation_string sig_expiration_string
312
key_expiration_string
314
317
apply_opt siginfo.policy_url
315
~f:(fun policy_url ->
316
let policy_url = HtmlTemplates.html_quote policy_url in
317
sprintf " Policy URL: <a href=\"%s\">%s</a>"
318
policy_url policy_url
318
~f:(fun policy_url ->
319
let policy_url = HtmlTemplates.html_quote policy_url in
320
sprintf " Policy URL: <a href=\"%s\">%s</a>"
321
policy_url policy_url
321
let notation_data_opt =
324
let notation_data_opt =
322
325
apply_opt siginfo.notation_data
323
326
~f:(fun (name,value) ->
324
sprintf " Notation data: <span class=\"text-decoration: underline;\">%s</span> %s"
325
(HtmlTemplates.html_quote name)
326
(HtmlTemplates.html_quote value)
327
sprintf " Notation data: <span class=\"text-decoration: underline;\">%s</span> %s"
328
(HtmlTemplates.html_quote name)
329
(HtmlTemplates.html_quote value)
329
let revocation_key_opt =
330
apply_opt siginfo.revocation_key
332
let revocation_key_opt =
333
apply_opt siginfo.revocation_key
331
334
~f:(fun fingerprint ->
332
sprintf " Revocation key fingerprint: <a href=\"%s\">%s</a>"
333
(HtmlTemplates.link ~hash:request.hash ~op:"vindex"
334
~fingerprint:request.fingerprint
335
~keyid:(Utils.hexstring fingerprint)
337
(Fingerprint.fp_to_string fingerprint)
335
sprintf " Revocation key fingerprint: <a href=\"%s\">%s</a>"
336
(HtmlTemplates.link ~hash:request.hash ~op:"vindex"
337
~fingerprint:request.fingerprint
338
~keyid:(Utils.hexstring fingerprint)
340
(Fingerprint.fp_to_string fingerprint)
340
343
firstline :: filter_opts [policy_url_opt; notation_data_opt;
344
347
(********************************************************************)
346
let selfsigs_to_lines request key_creation_time keyid selfsigs today =
349
let selfsigs_to_lines request key_creation_time keyid selfsigs today =
348
351
List.map ~f:(fun sign -> siginfo_to_lines ~get_uid:(fun _ -> None)
349
~key_creation_time request keyid today
350
(sig_to_siginfo sign))
352
~key_creation_time request keyid today
353
(sig_to_siginfo sign))
353
356
List.concat lines
355
358
(********************************************************************)
357
360
let uid_to_lines ~get_uid request key_creation_time keyid today
359
362
let siginfo_list = sort_siginfo_list siginfo_list in
360
363
let uid_line = match uid.packet_type with
362
sprintf "<strong>uid</strong> <span class=\"uid\">%s</span>"
363
(HtmlTemplates.html_quote uid.packet_body)
365
sprintf "<strong>uid</strong> <span class=\"uid\">%s</span>"
366
(HtmlTemplates.html_quote uid.packet_body)
365
368
| _ -> sprintf "<strong>uat</strong> [contents omitted]"
369
372
(List.map ~f:(siginfo_to_lines ~get_uid ~key_creation_time
373
376
""::uid_line::siginfo_lines
375
378
let uids_to_lines ~get_uid request key_creation_time keyid uids today =
377
380
(List.map ~f:(uid_to_lines ~get_uid request key_creation_time keyid today) uids)
379
382
(********************************************************************)
381
let key_packet_to_line ~is_subkey pki keyid =
384
let key_packet_to_line ~is_subkey pki keyid =
382
385
let prefix = if is_subkey then "<strong>sub</strong>" else "<strong>pub</strong>" in
383
386
let creation_string = datestr_of_int64 pki.pk_ctime in
384
let expiration_string =
387
let expiration_string =
385
388
if pki.pk_version = 4 then no_datestr
387
390
match pki.pk_expiration with
388
| None -> blank_datestr
390
let time = Int64.add (Int64.of_int (days * 24 * 60 * 60))
392
datestr_of_int64 time
391
| None -> blank_datestr
393
let time = Int64.add (Int64.of_int (days * 24 * 60 * 60))
395
datestr_of_int64 time
394
397
let keyid = keyid in
395
398
let keyid_short = Fingerprint.keyid_to_string ~short:true keyid in
396
399
let keyid_long = Fingerprint.keyid_to_string ~short:false keyid in
399
402
if is_subkey then sprintf "%8s" keyid_short
401
404
sprintf "<a href=\"%s\">%8s</a>"
402
(HtmlTemplates.link ~op:"get" ~hash:false ~fingerprint:false
405
(HtmlTemplates.link ~op:"get" ~hash:false ~fingerprint:false
406
409
let algo = pk_alg_to_ident pki.pk_alg in
407
410
let line = sprintf "%s %4d%s/%s %s %s "
411
creation_string expiration_string
414
creation_string expiration_string
415
418
(********************************************************************)
417
let subkey_to_lines request today (subkey,siginfo_list) =
420
let subkey_to_lines request today (subkey,siginfo_list) =
418
421
let pki = ParsePGP.parse_pubkey_info subkey in
419
422
let keyid = (Fingerprint.from_packet subkey).Fingerprint.keyid in
420
423
let (subkey_line,keyid) = key_packet_to_line ~is_subkey:true pki keyid in
421
424
let key_creation_time = pki.pk_ctime in
423
426
List.concat (List.map ~f:(siginfo_to_lines ~get_uid:(fun _ -> None)
424
~key_creation_time request keyid today)
427
~key_creation_time request keyid today)
427
430
""::subkey_line::siginfo_lines
429
let subkeys_to_lines request subkeys today =
432
let subkeys_to_lines request subkeys today =
430
433
List.concat (List.map ~f:(subkey_to_lines request today) subkeys)
432
435
(********************************************************************)
453
456
(********************************************************************)
455
458
(** fetches UID from keyid, stopping fater first [max_uid_fetches] *)
456
let get_uid get_uids =
459
let get_uid get_uids =
457
460
let ctr = ref 0 in
461
464
if !ctr > !Settings.max_uid_fetches then None
463
let uids = get_uids keyid in
464
let uids = List.filter uids
465
~f:(fun (uid,_) -> uid.packet_type = User_ID_Packet) in
466
let uids = List.map ~f:convert_sigpair uids in
467
match move_primary_to_front ~keyid uids with
469
| (uid,_)::tl -> Some uid.packet_body
466
let uids = get_uids keyid in
467
let uids = List.filter uids
468
~f:(fun (uid,_) -> uid.packet_type = User_ID_Packet) in
469
let uids = List.map ~f:convert_sigpair uids in
470
match move_primary_to_front ~keyid uids with
472
| (uid,_)::tl -> Some uid.packet_body
473
"Error fetching uid during VIndex for keyid 0x%s"
474
(KeyHash.hexify keyid);
476
"Error fetching uid during VIndex for keyid 0x%s"
477
(KeyHash.hexify keyid);
478
481
(********************************************************************)
480
483
(** computes fingerprint and hash lines if required *)
481
let get_extra_lines request key hash meta =
484
let get_extra_lines request key hash meta =
484
487
if request.fingerprint then
485
488
[HtmlTemplates.fingerprint ~fp:(Fingerprint.fp_to_string
486
meta.Fingerprint.fp)]
489
meta.Fingerprint.fp)]
491
494
if request.hash then
492
495
let hash_line = HtmlTemplates.hash ~hash:(KeyHash.hexify hash) in
493
496
hash_line::extra_lines
580
583
let keyid = meta.Fingerprint.keyid in
581
584
let keyid_short = Fingerprint.keyid_to_string ~short:true keyid in
582
585
let keyid_long = Fingerprint.keyid_to_string ~short:false keyid in
583
let link = HtmlTemplates.link ~op:"get" ~hash:false ~fingerprint:false
585
let ilink = HtmlTemplates.link ~op:"vindex"
586
~hash:request.hash ~fingerprint:request.fingerprint
586
let link = HtmlTemplates.link ~op:"get" ~hash:false ~fingerprint:false
588
let ilink = HtmlTemplates.link ~op:"vindex"
589
~hash:request.hash ~fingerprint:request.fingerprint
589
592
let uids = move_primary_to_front ~keyid uids in
592
List.map ~f:(fun (uid,sigs) ->
593
match uid.packet_type with
595
HtmlTemplates.html_quote uid.packet_body
596
| User_Attribute_Packet -> "[user attribute packet]"
597
| _ -> "[unexpected packet type]"
595
List.map ~f:(fun (uid,sigs) ->
596
match uid.packet_type with
598
HtmlTemplates.html_quote uid.packet_body
599
| User_Attribute_Packet -> "[user attribute packet]"
600
| _ -> "[unexpected packet type]"
601
604
let userids = match userids with [] -> []
602
605
| hd::tl -> (sprintf "<a href=\"%s\">%s</a>" ilink hd)::tl in
603
606
let pki = ParsePGP.parse_pubkey_info (List.hd key) in
604
let keystr = HtmlTemplates.keyinfo_pks pki (is_revoked key)
605
~keyid:keyid_short ~link ~userids in
607
let keystr = HtmlTemplates.keyinfo_pks pki (is_revoked key)
608
~keyid:keyid_short ~link ~userids in
606
609
let lines = [] in
608
611
if request.fingerprint then
609
let fingerprint = HtmlTemplates.fingerprint
610
~fp:(Fingerprint.fp_to_string
611
(meta.Fingerprint.fp))
612
let fingerprint = HtmlTemplates.fingerprint
613
~fp:(Fingerprint.fp_to_string
614
(meta.Fingerprint.fp))
618
621
if request.hash then
619
let hash = HtmlTemplates.hash ~hash:(KeyHash.hexify hash) in
622
let hash = HtmlTemplates.hash ~hash:(KeyHash.hexify hash) in
627
630
"</pre><hr /><pre>"::lines
629
632
| Sys.Break | Eventloop.SigAlarm as e -> raise e
632
"Unable to print key from query '%s'"
633
(String.concat ~sep:" " request.search);
635
"Unable to print key from query '%s'"
636
(String.concat ~sep:" " request.search);