Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | magnus | 1 | package DebPool::GnuPG; |
2 | |||
3 | ### |
||
4 | # |
||
5 | # DebPool::GnuPG - Module for all interactions with GNU Privacy Guard |
||
6 | # |
||
7 | # Copyright 2003-2004 Joel Aelwyn. All rights reserved. |
||
8 | # |
||
9 | # Redistribution and use in source and binary forms, with or without |
||
10 | # modification, are permitted provided that the following conditions |
||
11 | # are met: |
||
12 | # 1. Redistributions of source code must retain the above copyright |
||
13 | # notice, this list of conditions and the following disclaimer. |
||
14 | # 2. Redistributions in binary form must reproduce the above copyright |
||
15 | # notice, this list of conditions and the following disclaimer in the |
||
16 | # documentation and/or other materials provided with the distribution. |
||
17 | # 3. Neither the name of the Author nor the names of any contributors |
||
18 | # may be used to endorse or promote products derived from this software |
||
19 | # without specific prior written permission. |
||
20 | # |
||
21 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
||
22 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||
23 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||
24 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
||
25 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||
26 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
||
27 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
||
28 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||
29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
||
30 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
||
31 | # SUCH DAMAGE. |
||
32 | # |
||
33 | # $Id: GnuPG.pm 46 2005-02-12 17:52:37Z joel $ |
||
34 | # |
||
35 | ### |
||
36 | |||
37 | # We use 'our', so we must have at least Perl 5.6 |
||
38 | |||
39 | require 5.006_000; |
||
40 | |||
41 | # Always good ideas. |
||
42 | |||
43 | use strict; |
||
44 | use warnings; |
||
45 | |||
46 | use POSIX; # WEXITSTATUS |
||
47 | use File::Temp qw(tempfile); |
||
48 | |||
49 | # We need these for open2() |
||
50 | |||
51 | use Fcntl; |
||
52 | use IPC::Open2; |
||
53 | |||
54 | ### Module setup |
||
55 | |||
56 | BEGIN { |
||
57 | use Exporter (); |
||
58 | our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); |
||
59 | |||
60 | # Version checking |
||
61 | $VERSION = '0.1.5'; |
||
62 | |||
63 | @ISA = qw(Exporter); |
||
64 | |||
65 | @EXPORT = qw( |
||
66 | ); |
||
67 | |||
68 | @EXPORT_OK = qw( |
||
69 | &Check_Signature |
||
70 | &Sign_Release |
||
71 | &Strip_GPG |
||
72 | ); |
||
73 | |||
74 | %EXPORT_TAGS = ( |
||
75 | 'functions' => [qw(&Check_Signature &Sign_Release &Strip_GPG)], |
||
76 | 'vars' => [qw()], |
||
77 | ); |
||
78 | } |
||
79 | |||
80 | ### Exported package globals |
||
81 | |||
82 | ### Non-exported package globals |
||
83 | |||
84 | # Thread-safe? What's that? Package global error value. We don't export |
||
85 | # this directly, because it would conflict with other modules. |
||
86 | |||
87 | our($Error); |
||
88 | |||
89 | ### File lexicals |
||
90 | |||
91 | # None |
||
92 | |||
93 | ### Constant functions |
||
94 | |||
95 | # None |
||
96 | |||
97 | ### Meaningful functions |
||
98 | |||
99 | # Check_Signature($file, $signature) |
||
100 | # |
||
101 | # Checks the GPG signature of $file (using $signature as an external |
||
102 | # signature file, if it is defined; if it isn't, $file is assumed to have |
||
103 | # an internal signature). Returns 0 on failure, 1 on success. |
||
104 | |||
105 | sub Check_Signature { |
||
106 | use DebPool::Config qw(:vars); |
||
107 | use DebPool::Logging qw(:functions :facility :level); |
||
108 | |||
109 | my($file, $signature) = @_; |
||
110 | |||
111 | my(@args) = ("--homedir=$Options{'gpg_home'}"); |
||
112 | push (@args, '--no-default-keyring', '--logger-fd=1'); |
||
113 | |||
114 | my($keyring); |
||
115 | |||
116 | foreach $keyring (@{$Options{'gpg_keyrings'}}) { |
||
117 | push(@args, "--keyring=$keyring"); |
||
118 | } |
||
119 | |||
120 | push(@args, '--verify'); |
||
121 | |||
122 | if (defined($signature)) { |
||
123 | push(@args, $signature); |
||
124 | } |
||
125 | |||
126 | push(@args, $file); |
||
127 | |||
128 | my($pid) = IPC::Open2::open2(*GPG_IN, *GPG_OUT, $Options{'gpg_bin'}, @args); |
||
129 | close(GPG_IN); # No input |
||
130 | close(GPG_OUT); # Don't care about output, really, either |
||
131 | |||
132 | waitpid($pid,0); # No flags, just wait. |
||
133 | my($sysret) = WEXITSTATUS($?); |
||
134 | |||
135 | if (0 != $sysret) { # Failure |
||
136 | my($msg) = "Failed signature check on '$file' "; |
||
137 | if (defined($signature)) { |
||
138 | $msg .= "(signature file '$signature')"; |
||
139 | } else { |
||
140 | $msg .= "(internal signature)"; |
||
141 | } |
||
142 | Log_Message($msg, LOG_GPG, LOG_WARNING); |
||
143 | |||
144 | return 0; |
||
145 | } |
||
146 | |||
147 | return 1; |
||
148 | } |
||
149 | |||
150 | # Sign_Release($release_file) |
||
151 | # |
||
152 | # Generates a detached GPG signature file for $release_file, and returns |
||
153 | # the filename. Returns undef, if an error occurs (and sets $Error). |
||
154 | |||
155 | sub Sign_Release { |
||
156 | use DebPool::Config; |
||
157 | use DebPool::Logging qw(:functions :facility :level); |
||
158 | |||
159 | my($release_file) = @_; |
||
160 | |||
161 | # Check that we have everything we need |
||
162 | |||
163 | if (!defined($Options{'gpg_sign_key'})) { |
||
164 | $Error = "No GPG signature key enabled"; |
||
165 | return undef; |
||
166 | } |
||
167 | |||
168 | if (!defined($Options{'gpg_passfile'})) { |
||
169 | $Error = "No GPG passphrase file enabled"; |
||
170 | return undef; |
||
171 | } |
||
172 | |||
173 | # Open a secure tempfile to write the signature to |
||
174 | |||
175 | my($tmpfile_handle, $tmpfile_name) = tempfile(); |
||
176 | |||
177 | # Open the Release file and grab the data from it |
||
178 | |||
179 | if (!open(RELEASE, '<', $release_file)) { |
||
180 | $Error = "Couldn't open Release file '$release_file': $!"; |
||
181 | return undef; |
||
182 | } |
||
183 | my(@release_text) = <RELEASE>; |
||
184 | close(RELEASE); |
||
185 | |||
186 | # Open the passphrase file and grab the data from it |
||
187 | |||
188 | if (!open(PASS, '<', $Options{'gpg_passfile'})) { |
||
189 | $Error = "Couldn't open passphrase file '$Options{'gpg_passfile'}': $!"; |
||
190 | return undef; |
||
191 | } |
||
192 | my($passphrase) = <PASS>; # This is only safe because we don't care. |
||
193 | close(PASS); |
||
194 | |||
195 | # We are go for main engine start |
||
196 | |||
197 | my(@args) = ("--homedir=$Options{'gpg_home'}"); |
||
198 | push(@args, "--default-key=$Options{'gpg_sign_key'}"); |
||
199 | push(@args, '--passphrase-fd=0', '--batch', '--no-tty', '--detach-sign'); |
||
200 | push(@args, '--armor', '--output=-'); |
||
201 | |||
202 | my($gnupg_pid) = IPC::Open2::open2(*GPG_IN, *GPG_OUT, $Options{'gpg_bin'}, @args); |
||
203 | |||
204 | my($child_pid); |
||
205 | my(@signature); |
||
206 | if ($child_pid = fork) { # In the parent |
||
207 | # Close filehandles used by the child. |
||
208 | |||
209 | close(GPG_IN); |
||
210 | close($tmpfile_handle); |
||
211 | |||
212 | # Send all the data to GnuPG |
||
213 | |||
214 | print GPG_OUT $passphrase; |
||
215 | print GPG_OUT @release_text; |
||
216 | close(GPG_OUT); |
||
217 | |||
218 | waitpid($child_pid, 0); |
||
219 | } else { # In the child - we hope |
||
220 | if (!defined($child_pid)) { |
||
221 | die "Couldn't fork: $!\n"; |
||
222 | } |
||
223 | |||
224 | # Close filehandle used by the parent. |
||
225 | |||
226 | close(GPG_OUT); |
||
227 | |||
228 | # And read back the results |
||
229 | |||
230 | @signature = <GPG_IN>; |
||
231 | close(GPG_IN); |
||
232 | |||
233 | # Finally, print the results to the tempfile |
||
234 | |||
235 | print $tmpfile_handle @signature; |
||
236 | close($tmpfile_handle); |
||
237 | |||
238 | exit(0); |
||
239 | } |
||
240 | |||
241 | |||
242 | # And we're done |
||
243 | |||
244 | return $tmpfile_name; |
||
245 | } |
||
246 | |||
247 | # Strip_GPG(@text) |
||
248 | # |
||
249 | # Goes through @text and determine if it has GnuPG headers; if so, strip |
||
250 | # out the headers, and undo GnuPG's header protection ('^-' -> '^-- -'). |
||
251 | |||
252 | sub Strip_GPG { |
||
253 | my(@text) = @_; |
||
254 | |||
255 | my($count); |
||
256 | my($header, $firstblank, $sigstart, $sigend); |
||
257 | |||
258 | for $count (0..$#text) { |
||
259 | if ($text[$count] =~ m/^-----BEGIN PGP SIGNED MESSAGE-----$/) { |
||
260 | $header = $count; |
||
261 | } elsif (!defined($firstblank) && $text[$count] =~ m/^$/) { |
||
262 | $firstblank = $count; |
||
263 | } elsif ($text[$count] =~ m/^-----BEGIN PGP SIGNATURE-----$/) { |
||
264 | $sigstart = $count; |
||
265 | } elsif ($text[$count] =~ m/^-----END PGP SIGNATURE-----$/) { |
||
266 | $sigend = $count; |
||
267 | } |
||
268 | } |
||
269 | |||
270 | # If we didn't find all three parts, it isn't a validly signed message |
||
271 | # (or it's externally signed, but that might as well be the same |
||
272 | # thing for our purposes - there's nothing to remove). |
||
273 | |||
274 | if (!defined($header) || !defined($sigstart) || !defined($sigend)) { |
||
275 | return @text; |
||
276 | } |
||
277 | |||
278 | # Okay. Back to front, so that we don't muck up reference numbers. |
||
279 | # First, we rip out the signature data by splicing it with an empty |
||
280 | # list. |
||
281 | |||
282 | splice(@text, $sigstart, ($sigend - $sigstart) + 1); |
||
283 | |||
284 | # We used to just rip off the first 3 lines (BEGIN line, hash header, |
||
285 | # and a blank line). However, this was a cheap shortcut that broke as |
||
286 | # of GnuPG 1.0.7, because it relied on there being exactly one GnuPG |
||
287 | # header line. |
||
288 | # |
||
289 | # Now, we rip out everything from the header line to the first blank, |
||
290 | # which should always be correct. |
||
291 | |||
292 | splice(@text, $header, ($firstblank - $header) + 1); |
||
293 | |||
294 | # All done. Fire it back. |
||
295 | |||
296 | return @text; |
||
297 | } |
||
298 | |||
299 | END {} |
||
300 | |||
301 | 1; |
||
302 | |||
303 | __END__ |
||
304 | |||
305 | # vim:set tabstop=4 expandtab: |