--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use locale;
+
+use POSIX qw(mktime strftime setlocale LC_COLLATE);
+
+setlocale(LC_COLLATE, "en_US.UTF-8");
+
+my $page = 'https://lede-project.org/docs/user-guide/signatures';
+
+my @keytypes = (
+ undef,
+ 'RSA',
+ 'RSA, encrypt only',
+ 'RSA, sign only',
+ undef,
+ undef,
+ undef,
+ undef,
+ undef,
+ undef,
+ undef,
+ undef,
+ undef,
+ undef,
+ undef,
+ undef,
+ 'Elgamal, encrypt only',
+ 'DSA',
+ 'EC',
+ 'ECDSA',
+ 'Elgamal'
+);
+
+sub format_title {
+ my ($key) = @_;
+
+ if ($key->{is_system_key}) {
+ return $key->{comment};
+ }
+
+ return sprintf 'Public key of %s', $key->{name};
+}
+
+sub format_keytype {
+ my ($key, $is_subkey) = @_;
+
+ my $type = $key->{$is_subkey ? 'stype' : 'type'};
+ my $size = $key->{$is_subkey ? 'ssize' : 'size'};
+
+ my ($d, $m, $y, $s);
+
+ if (defined($size) && $size > 0) {
+ $s = sprintf '%d Bit %s', $size, $keytypes[$type];
+ }
+ else {
+ $s = $keytypes[$type];
+ }
+
+ (undef, undef, undef, $d, $m, $y) =
+ localtime $key->{$is_subkey ? 'sctime' : 'ctime'};
+
+ $s .= sprintf ', created %04d-%02d-%02d', $y + 1900, $m + 1, $d;
+
+ (undef, undef, undef, $d, $m, $y) =
+ localtime $key->{$is_subkey ? 'setime' : 'etime'};
+
+ if ($d && $m && $y) {
+ $s .= sprintf ', expires %04d-%02d-%02d', $y + 1900, $m + 1, $d;
+ }
+
+ return $s;
+}
+
+sub format_fingerprint {
+ my ($key, $is_subkey) = @_;
+
+ my $fprint = $key->{$is_subkey ? 'sfprint' : 'fprint'};
+ my (@fields) = $fprint =~ m!([A-F0-9]{4})!g;
+
+ return join(' ', @fields[0..4]) . ' ' . join(' ', @fields[5..9]);
+}
+
+sub format_download {
+ my ($key) = @_;
+
+ my $mtime = $key->{ctime};
+
+ if (open GIT, '-|', qw(git log -1 --format=%ct --), $key->{filename}) {
+ if (defined(my $line = readline GIT)) {
+ chomp $line;
+ $mtime = $line;
+ }
+ close GIT;
+ }
+
+ my $ts = strftime '%F %T %z', gmtime $mtime;
+
+ return sprintf
+ "[[https://git.lede-project.org/?p=keyring.git;a=history;f=%s|Last change: %s]] | " .
+ "[[https://git.lede-project.org/?p=keyring.git;a=blob_plain;f=%s|Download]]\n" ,
+ $key->{filename}, $ts, $key->{filename};
+}
+
+sub parse_timestamp {
+ my ($s) = @_;
+
+ if ($s =~ m!^(\d\d\d\d)-(\d\d)-(\d\d)$!) {
+ return mktime(0, 0, 0, $3 + 0, $2 - 1, $1 - 1900);
+ }
+
+ return int $s;
+}
+
+
+my $markup_template = '';
+
+if (open RAW, '-|', 'curl', '-s', "$page?do=export_raw") {
+ local $/;
+ $markup_template = readline RAW;
+ close RAW;
+}
+
+
+my @pubkeys;
+
+if (open KEYS, '-|', qw(find gpg/ -type f -name *.asc -print)) {
+ while (defined(my $file = readline KEYS)) {
+ chomp $file;
+ if (open GPG, '-|', qw(gpg --with-fingerprint --with-fingerprint --with-colons), $file) {
+ my %data;
+
+ while (defined(my $line = readline GPG)) {
+ chomp $line;
+ my @fields = split ':', $line;
+ if ($fields[0] eq 'uid' && !exists $data{name}) {
+ ($data{name}, $data{comment}, $data{email}) =
+ $fields[9] =~ m!^([^()]+)(?: \((.+?)\))? <(.+)>$!;
+ }
+ elsif ($fields[0] eq 'pub') {
+ $data{size} = int $fields[2];
+ $data{type} = int $fields[3];
+ $data{eid} = $fields[4];
+ $data{ctime} = parse_timestamp($fields[5]);
+ $data{etime} = $fields[6] ? parse_timestamp($fields[6]) : 0;
+ if ($fields[9] && !exists $data{name}) {
+ ($data{name}, $data{comment}, $data{email}) =
+ $fields[9] =~ m!^([^()]+)(?: \((.+?)\))? <(.+)>$!;
+ }
+ }
+ elsif ($fields[0] eq 'sub') {
+ $data{ssize} = int $fields[2];
+ $data{stype} = int $fields[3];
+ $data{seid} = $fields[4];
+ $data{sctime} = parse_timestamp($fields[5]);
+ $data{setime} = $fields[6] ? parse_timestamp($fields[6]) : 0;
+ }
+ elsif ($fields[0] eq 'fpr') {
+ $data{exists($data{stype}) ? 'sfprint' : 'fprint'} = $fields[9];
+ }
+ }
+
+ close GPG;
+
+ $data{filename} = $file;
+ $data{is_system_key} =
+ (index($data{email}, 'lede-project.org') >= 0) ||
+ (index($data{email}, 'lists.infradead.org') >= 0);
+
+ push @pubkeys, \%data;
+ }
+ }
+
+ close KEYS;
+}
+
+my $gpg_markup = '';
+
+foreach my $key (sort {
+ !$a->{is_system_key} <=> !$b->{is_system_key} ||
+ $a->{name} cmp $b->{name}
+} @pubkeys) {
+
+ $gpg_markup .= sprintf "---\n\n=== %s ===\n\n",
+ format_title($key);
+
+ $gpg_markup .= sprintf "User ID: **%s** <%s>\\\\\n",
+ $key->{name}, $key->{email};
+
+ $gpg_markup .= sprintf "Public Key: 0x%s**%s** (%s)\\\\\n",
+ substr($key->{eid}, 0, 8), substr($key->{eid}, 8),
+ format_keytype($key, 0);
+
+ $gpg_markup .= sprintf "Fingerprint: ''%%%%%s%%%%''\\\\\n",
+ format_fingerprint($key, 0);
+
+ if (exists $key->{stype}) {
+ $gpg_markup .= sprintf "Signing Subkey: 0x%s **%s** (%s)\\\\\n",
+ substr($key->{seid}, 0, 8), substr($key->{seid}, 8),
+ format_keytype($key, 1);
+
+ $gpg_markup .= sprintf "Fingerprint: ''%%%%%s%%%%''\\\\\n",
+ format_fingerprint($key, 1);
+ }
+
+ $gpg_markup .= sprintf "%s\n", format_download($key);
+}
+
+
+my @usignkeys;
+
+if (open KEYS, '-|', qw(find usign/ -type f -name *[0-9a-f] -print)) {
+ while (defined(my $file = readline KEYS)) {
+ chomp $file;
+
+ if (open USIGN, '<', $file) {
+ my %data;
+
+ while (defined(my $line = readline USIGN)) {
+ chomp $line;
+
+ if ($line =~ m!^untrusted comment: (.+)$!) {
+ $data{comment} = $1;
+ }
+ else {
+ $data{key} = $line;
+ }
+ }
+
+ close USIGN;
+
+ $file =~ m!/([0-9a-f]{16})$!;
+
+ $data{id} = $1;
+ $data{filename} = $file;
+
+ push @usignkeys, \%data;
+ }
+ }
+
+ close KEYS;
+}
+
+my $usign_markup = '';
+
+foreach my $key (sort { $a->{comment} cmp $b->{comment} } @usignkeys) {
+ $usign_markup .= sprintf "---\n\n=== %s ===\n\n",
+ $key->{comment};
+
+ $usign_markup .= sprintf " * Key-ID: ''%%%%%s%%%%''\n",
+ $key->{id};
+
+ $usign_markup .= sprintf " * Key-Data: ''%%%%%s%%%%''\n\n",
+ $key->{key};
+
+ $usign_markup .= sprintf "%s\n",
+ format_download($key);
+}
+
+
+$markup_template =~ s!
+ ( /\*\sBEGIN\sGPG\sKEYS\s\*/ )
+ .+
+ ( /\*\sEND\sGPG\sKEYS\s\*/ )
+!
+ $1 . "\n\n" . $gpg_markup . $2;
+!esx;
+
+$markup_template =~ s!
+ ( /\*\sBEGIN\sUSIGN\sKEYS\s\*/ )
+ .+
+ ( /\*\sEND\sUSIGN\sKEYS\s\*/ )
+!
+ $1 . "\n\n" . $usign_markup . $2;
+!esx;
+
+
+print $markup_template;