-
Notifications
You must be signed in to change notification settings - Fork 0
/
ensign
executable file
·255 lines (206 loc) · 9 KB
/
ensign
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#!/usr/bin/perl
#*******************************************************************************
# Mozilla Extension Installation Package Signer, version 1.0
# Copyright (C) 2008, 2009 Sergei Zhirikov ([email protected])
# This software is available under the GNU General Public License v3.0
# (http://www.gnu.org/licenses/gpl-3.0.txt)
#*******************************************************************************
use strict;
use warnings;
use Pod::Usage;
use File::Find;
use Getopt::Std;
use Digest::MD5;
use Digest::SHA1;
use File::Spec::Functions qw(splitdir catfile curdir abs2rel);
#*******************************************************************************
use constant CreatedBy => 'MX-Tools 1.0';
#*******************************************************************************
@ARGV or pod2usage(-exitval => 1, -verbose => 99, -sections => 'NAME|SYNOPSIS');
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$Getopt::Std::STANDARD_HELP_VERSION = 1;
sub VERSION_MESSAGE {
pod2usage(-exitval => 'NOEXIT', -verbose => 99, -sections => 'NAME');
}
sub HELP_MESSAGE {
pod2usage(-exitval => 'NOEXIT', -verbose => 1);
}
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
our %opt;
getopts('k:p:c:', \%opt) or die "Use '--help' for available options\n";
our ($key, $pwd, $inc) = @opt{qw[k p c]};
@ARGV == 3 or pod2usage(-exitval => 1, -verbose => 99, -sections => 'SYNOPSIS');
our ($src, $xpi, $cer) = @ARGV;
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# http://www.openssl.org/docs/apps/openssl.html#PASS_PHRASE_ARGUMENTS
(!$pwd || (($pwd eq '-')? ($pwd = 'stdin'): ($pwd =~ s{^([=\@\$\&])}
{{'=' => 'pass:', '@' => 'file:', '$' => 'env:', '&' => 'fd:'}->{$1}}e)))
or die "Invalid private key password parameter: '$pwd'\n";
#*******************************************************************************
our @src = ();
if ($src eq '-') {
!$pwd or $pwd ne 'stdin'
or die "Can't read both password and source list from stdin\n";
chomp(@src = <STDIN>);
} elsif ($src eq '.') {
my $dir = curdir();
find({follow => 0, no_chdir => 1, wanted => sub {
push(@src, abs2rel($_, $dir)) if (-f $_);
}}, $dir);
} else {
open(SRC, '<', $src) or die "Failed to open the source list '$src': $!\n";
chomp(@src = <SRC>);
close(SRC);
}
@src > 0 or die "The source file set does not contain files to process\n";
our %src = map { join('/', splitdir($_)) => $_ } @src;
@src = sort(keys(%src));
#*******************************************************************************
our %inf = (
'manifest.mf' => catfile('META-INF', 'manifest.mf'),
'zigbert.rsa' => catfile('META-INF', 'zigbert.rsa'),
'zigbert.sf' => catfile('META-INF', 'zigbert.sf'),
);
mkdir('META-INF')
or die "Failed to create directory 'META-INF': $!\n" unless (-d 'META-INF');
open(MMF, '>', $inf{'manifest.mf'})
or die "Failed to create file 'manifest.mf': $!\n";
open(ZSF, '>', $inf{'zigbert.sf'})
or die "Failed to create file 'zigbert.sf': $!\n";
binmode(MMF); binmode(ZSF);
our $md5 = Digest::MD5->new;
our $sha = Digest::SHA1->new;
our $hdr =
"Manifest-Version: 1.0\n".
"Created-By: ".CreatedBy()."\n";
print MMF $hdr;
print ZSF
"Signature-Version: 1.0\n".
"Created-By: ".CreatedBy()."\n".
"Digest-Algorithms: MD5 SHA1\n".
"MD5-Digest: ".$md5->add($hdr)->b64digest."==\n".
"SHA1-Digest: ".$sha->add($hdr)->b64digest."=\n";
for my $src (@src) {
my $hdr =
"Name: $src\n".
"Digest-Algorithms: MD5 SHA1\n";
open(SRC, '<', $src{$src}) or die "Failed to open file '$src{$src}': $!\n";
binmode(SRC);
$md5->reset->addfile(*SRC);
seek(SRC, 0, 0) or die "Failed to rewind file '$src{$src}': $!\n";
$sha->reset->addfile(*SRC);
close(SRC);
my $txt = $hdr.
"MD5-Digest: ".$md5->b64digest."==\n".
"SHA1-Digest: ".$sha->b64digest."=\n";
print MMF "\n$txt";
print ZSF "\n".$hdr.
"MD5-Digest: ".$md5->reset->add($txt)->b64digest."==\n".
"SHA1-Digest: ".$sha->reset->add($txt)->b64digest."=\n";
}
close(ZSF);
close(MMF);
#*******************************************************************************
our ($bin, $sig) = @inf{'zigbert.sf', 'zigbert.rsa'};
our $cmd = qq[openssl smime -in "$bin" -sign -nosmimecap -binary -outform der].
qq[ -out "$sig" -signer "$cer"].($key? qq[ -inkey "$key"]: '').
($pwd? qq[ -passin "$pwd"]: '').($inc? qq[ -certfile "$inc"]: '');
system($cmd) == 0 or die "OpenSSL failed to generate the signature\n";
if ($xpi) {
unlink($xpi) or die "Failed to delete file '$xpi': $!\n" if (-e $xpi);
open(ZIP, '|-', qq[zip -qoD9@ "$xpi"])
or die "Failed to invoke 'zip' to create the package: $!\n";
print ZIP $inf{'zigbert.rsa'}, "\n";
print ZIP $inf{'manifest.mf'}, "\n";
print ZIP $inf{'zigbert.sf'}, "\n";
print ZIP "$src{$_}\n" for (@src);
close(ZIP);
$? == 0 or die "Failure during creating the package\n";
}
#*******************************************************************************
=pod
=head1 NAME
ensign - Mozilla Extension Installation Package Signer, v1.0
=head1 SYNOPSIS
ensign [options] source package.xpi signer.crt
=head1 ARGUMENTS
=over 2
=item B<source>
The list of the files to be included in the installation package. This should
be a text file containing one file name per line with the path relative to the
current directory. If the value of this parameter is C<-> (dash) the file list
will be read from the standard input. If the value is C<.> (dot) the file list
will be constructed internally based on the contents of the current directory,
which means that all the files in the current directory and subdirectories will
be included.
=item B<package.xpi>
The file name of the output installation package. If the file exists it will
be overwritten.
=item B<signer.crt>
The certificate to sign the package with. It must be an x509 code signing
certificate in PEM format.
=back
Additionally, the following options may be specified:
=over 2
=item B<-k keyfile.pem>
The private key for the signer certificate. If this parameter is omitted the
key must be contained in the same file as the certificate itself (B<signer.crt>
above).
=item B<-p passwarg>
The password for the private key. This parameter is required when the key is
encrypted (which is almost always the case).
The B<passwarg> parameter can have one of the following forms (where the first
character indicates which form is used):
=over 2
=item B<=password>
The password is specified literally. This is the easiest way, but in many cases
it may be insecure on a multi-user system, since any user can see the password
using C<ps> utility or alike.
=item B<@filename>
The password is read from the specified file. The first line of the file is
assumed to contain the password.
=item B<$ENV_VAR>
The password is fetched from the specified environment variable. This is not
the same as having the environment variable expanded by the shell when invoking
the command. The syntax is very similar to most UNIX shells, but here the C<$>
character is passed literally (thus must be escaped properly when using a
UNIX-like shell).
=item B<&fd>
The password is read from the specified file descriptor. Depending on the OS
this may or may not be supported (usually supported on UNIX-like OS). The first
line read from the file descriptor is assumed to contain the password.
=item B<->
The password is read from the standard input. The first line of the input
stream is assumed to contain the password.
=back
The password argument is converted to one of the forms accepted by OpenSSL
B<-passin> argument, as described in the OpenSSL manual:
L<http://www.openssl.org/docs/apps/openssl.html#PASS_PHRASE_ARGUMENTS>. So the
security considerations applicable to OpenSSL invocation also apply here.
=item B<-c cacerts.pem>
Extra certificates to be included in the signature. Typically these are the
intermediate CA certificates. The file must contain one or more concatenated
x509 certificates in PEM format.
=back
=head1 DESCRIPTION
The command must be run in the root of the directory hierarchy to be included
in the installation package. The names of the subdirectories (if any) will be
a part of each file path inside the package.
In the current directory Ensign creates B<META-INF> subdirectory (unless it
already exists) and there it creates the files that constitute the signature:
B<manifest.mf>, B<zigbert.sf>, and B<zigbert.rsa>. If some of those files
already exist they are overwritten.
Subsequently the signature files and the files specified by the source argument
are put into a zip file with the signature files first. If the source list is
specified (either as a file name or via the standard input stream) it must
B<not> contain the signature files (regardless of whether those exist prior to
invoking the command).
=head1 KNOWN ISSUES
None so far.
=head1 DEPENDENCIES
Digest::MD5, Digest::SHA1, OpenSSL, zip
=head1 HOME PAGE
L<http://www.softlights.net/projects/mxtools/>
=head1 AUTHORS
Copyright (C) 2008, 2009 Sergei Zhirikov ([email protected])
=cut