Cryptography.pm (4557B)
1 package SBO::Lib::Cryptography; 2 3 use 5.016; 4 use strict; 5 use warnings; 6 7 our $VERSION = '2.9.0'; 8 9 use Cwd; 10 use File::Temp "tempdir"; 11 use IPC::Open3; 12 13 use Exporter 'import'; 14 15 # Minimal definitions of some GPG raw output messages. 16 use constant { 17 BADSIG => 'bad signature', 18 EXPSIG => 'signature expired', 19 EXPKEYSIG => 'signed by expired key', 20 ERRSIG => 'signature verification not possible', 21 GOODSIG => 'good signature', 22 REVKEYSIG => 'good signature by revoked public key', 23 NO_PUBKEY => 'public key unavailable', 24 VALIDSIG => 'valid signature', 25 # An unknown message type. 26 UNKNOWN => 'unknown', 27 }; 28 29 our @EXPORT_OK = qw{ 30 import_gpg_key 31 parse_gpg_output 32 verify_gpg_signed_file 33 34 BADSIG 35 EXPSIG 36 EXPKEYSIG 37 ERRSIG 38 GOODSIG 39 REVKEYSIG 40 NO_PUBKEY 41 VALIDSIG 42 UNKNOWN 43 }; 44 45 our %EXPORT_TAGS = ( 46 all => \@EXPORT_OK, 47 ); 48 49 =pod 50 51 =encoding UTF-8 52 53 =cut 54 55 # Messages on Signature status which should only turn up once. 56 our %is_signature_status = ( 57 &BADSIG => 1, 58 &ERRSIG => 1, 59 &EXPSIG => 1, 60 &EXPKEYSIG => 1, 61 &GOODSIG => 1, 62 &REVKEYSIG => 1, 63 ); 64 65 =head2 66 67 parse_gpg_output(@output, $key_id); 68 69 =cut 70 71 sub parse_gpg_output { 72 my $output = shift; 73 my $key_id = shift; 74 75 76 my $line; 77 my $status = ''; 78 79 foreach $line (@$output) { 80 my $msg = parse_gpg_line($line, $key_id); 81 82 if (exists($is_signature_status{$msg})) { 83 # Only one signature expected. 84 if ($status ne '') { 85 return UNKNOWN; 86 } 87 88 $status = $msg; 89 } 90 91 if ($msg eq NO_PUBKEY) { 92 return NO_PUBKEY; 93 } elsif ($msg eq VALIDSIG) { 94 # VALIDSIG contains the full hex key ID to be certain it is signed by the 95 # right key. information can be found in 'DETAILS' in the gnupg2 96 # documentation folder. 97 return $status; 98 } 99 } 100 101 return UNKNOWN; 102 } 103 104 sub parse_gpg_line { 105 my $line = shift; 106 my $key_id = shift; 107 108 if ($line =~ /^\[GNUPG\:] BADSIG/) { 109 return BADSIG; 110 } elsif ($line =~ /^\[GNUPG\:] EXPSIG/) { 111 return EXPSIG; 112 } elsif ($line =~ /^\[GNUPG\:] EXPKEYSIG/) { 113 return EXPKEYSIG; 114 } elsif ($line =~ /^\[GNUPG\:] ERRSIG/) { 115 return ERRSIG; 116 } elsif ($line =~ /^\[GNUPG\:] GOODSIG/) { 117 return GOODSIG; 118 } elsif ($line =~ /^\[GNUPG\:] REVKEYSIG/) { 119 return REVKEYSIG; 120 } elsif ($line =~ /^\[GNUPG\:] NO_PUBKEY/) { 121 return NO_PUBKEY; 122 } elsif ($line =~ /^\[GNUPG\:] VALIDSIG $key_id/) { 123 return VALIDSIG; 124 } else { 125 return UNKNOWN; 126 } 127 } 128 129 =head2 import_gpg_key 130 131 import_gpg_key($key); 132 133 C<import_gpg_key()> will import the key into the systems keychain. An error will 134 be reported if the configured key fingerprint does not match the imported one. 135 136 =cut 137 138 sub import_gpg_key { 139 script_error('import_gpg_key requires two arguments.') unless @_ == 2; 140 141 my $key = shift; 142 my $key_id = shift; 143 144 my $key_source; 145 if ($key =~ m!^http://! || $key =~ m!^https://!) { 146 open3(undef, $key_source, ">&STDERR", "wget", $key, "-O", "-") || die("could not download key from $key"); 147 } else { 148 open($key_source, "<", $key) || die("could not read '$key': $!"); 149 } 150 151 my $old = $ENV{'GNUPGHOME'}; 152 153 $ENV{'GNUPGHOME'} = tempdir(CLEANUP => 1); 154 155 my $gpg_cmd; 156 open3($gpg_cmd, undef, undef, "gpg", "--batch", "--yes", "--import", "-") || die("could not import key: $!\n"); 157 158 while (my $line = <$key_source>) { 159 print($gpg_cmd $line); 160 } 161 162 close($gpg_cmd); 163 close($key_source); 164 165 sleep(1); 166 167 if (system(">/dev/null gpg --list-keys $key_id")) { 168 die("GPG key '$key_id' not found. Confirm the correct key is configured or is being imported.\n"); 169 } 170 171 my $gpg_export; 172 open($gpg_export, "-|", "gpg", "--export", $key_id) || die("could not export key: $!\n"); 173 174 $ENV{'GNUPGHOME'} = $old; 175 176 my $gpg_import; 177 open3($gpg_import, ">&STDOUT", ">&STDOUT", "gpg", "--batch", "--yes", "--import", "-") || die("could not import key: $!\n"); 178 179 while (my $line = <$gpg_export>) { 180 print($gpg_import $line); 181 } 182 183 close($gpg_export); 184 close($gpg_import); 185 186 sleep(1); 187 188 print("key imported\n"); 189 } 190 191 =head2 verify_gpg_signed_file 192 193 verify_gpg_signed_file($file_path, $key_id); 194 195 C<verify_gpg_signed_file()> verifies the C<file_path> is signed by C<key_id>. 196 197 =cut 198 199 sub verify_gpg_signed_file { 200 script_error('verify_gpg_signed_file requires two arguments.') unless @_ == 2; 201 202 my $file_path = shift; 203 my $key_id = shift; 204 205 my @output; 206 open3(undef, my $std_out, undef, "gpg", "--status-fd=1", "--verify", $file_path) or die("dead"); 207 while (my $line = <$std_out>) { 208 push(@output, $line); 209 } 210 close($std_out); 211 212 return parse_gpg_output(\@output, $key_id); 213 }