Blame view

scripts/sign-file 12.2 KB
b37d1bfb5   David Howells   MODSIGN: perlify ...
1
  #!/usr/bin/perl -w
80d65e58e   David Howells   MODSIGN: Sign mod...
2
3
4
  #
  # Sign a module file using the given key.
  #
1c37c054a   Michal Marek   MODSIGN: Add -s <...
5
6
7
8
9
10
  
  my $USAGE =
  "Usage: scripts/sign-file [-v] <hash algo> <key> <x509> <module> [<dest>]
  " .
  "       scripts/sign-file [-v] -s <raw sig> <hash algo> <x509> <module> [<dest>]
  ";
b37d1bfb5   David Howells   MODSIGN: perlify ...
11
12
13
  use strict;
  use FileHandle;
  use IPC::Open2;
1c37c054a   Michal Marek   MODSIGN: Add -s <...
14
  use Getopt::Std;
b37d1bfb5   David Howells   MODSIGN: perlify ...
15

1c37c054a   Michal Marek   MODSIGN: Add -s <...
16
17
18
19
  my %opts;
  getopts('vs:', \%opts) or die $USAGE;
  my $verbose = $opts{'v'};
  my $signature_file = $opts{'s'};
b37d1bfb5   David Howells   MODSIGN: perlify ...
20

1c37c054a   Michal Marek   MODSIGN: Add -s <...
21
22
  die $USAGE if ($#ARGV > 4);
  die $USAGE if (!$signature_file && $#ARGV < 3 || $signature_file && $#ARGV < 2);
b37d1bfb5   David Howells   MODSIGN: perlify ...
23

1c37c054a   Michal Marek   MODSIGN: Add -s <...
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  my $dgst = shift @ARGV;
  my $private_key;
  if (!$signature_file) {
  	$private_key = shift @ARGV;
  }
  my $x509 = shift @ARGV;
  my $module = shift @ARGV;
  my ($dest, $keep_orig);
  if (@ARGV) {
  	$dest = $ARGV[0];
  	$keep_orig = 1;
  } else {
  	$dest = $module . "~";
  }
b37d1bfb5   David Howells   MODSIGN: perlify ...
38

1c37c054a   Michal Marek   MODSIGN: Add -s <...
39
40
41
42
  die "Can't read private key
  " if (!$signature_file && !-r $private_key);
  die "Can't read signature file
  " if ($signature_file && !-r $signature_file);
b37d1bfb5   David Howells   MODSIGN: perlify ...
43
44
45
46
47
48
  die "Can't read X.509 certificate
  " unless (-r $x509);
  die "Can't read module
  " unless (-r $module);
  
  #
b37d1bfb5   David Howells   MODSIGN: perlify ...
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
  # Function to read the contents of a file into a variable.
  #
  sub read_file($)
  {
      my ($file) = @_;
      my $contents;
      my $len;
  
      open(FD, "<$file") || die $file;
      binmode FD;
      my @st = stat(FD);
      die $file if (!@st);
      $len = read(FD, $contents, $st[7]) || die $file;
      close(FD) || die $file;
      die "$file: Wanted length ", $st[7], ", got ", $len, "
  "
  	if ($len != $st[7]);
      return $contents;
  }
  
  ###############################################################################
  #
  # First of all, we have to parse the X.509 certificate to find certain details
  # about it.
  #
  # We read the DER-encoded X509 certificate and parse it to extract the Subject
  # name and Subject Key Identifier.  Theis provides the data we need to build
  # the certificate identifier.
  #
  # The signer's name part of the identifier is fabricated from the commonName,
  # the organizationName or the emailAddress components of the X.509 subject
  # name.
  #
  # The subject key ID is used to select which of that signer's certificates
  # we're intending to use to sign the module.
  #
  ###############################################################################
  my $x509_certificate = read_file($x509);
80d65e58e   David Howells   MODSIGN: Sign mod...
87

b37d1bfb5   David Howells   MODSIGN: perlify ...
88
89
90
91
  my $UNIV = 0 << 6;
  my $APPL = 1 << 6;
  my $CONT = 2 << 6;
  my $PRIV = 3 << 6;
80d65e58e   David Howells   MODSIGN: Sign mod...
92

b37d1bfb5   David Howells   MODSIGN: perlify ...
93
  my $CONS = 0x20;
80d65e58e   David Howells   MODSIGN: Sign mod...
94

b37d1bfb5   David Howells   MODSIGN: perlify ...
95
96
97
98
99
100
101
102
103
104
105
  my $BOOLEAN	= 0x01;
  my $INTEGER	= 0x02;
  my $BIT_STRING	= 0x03;
  my $OCTET_STRING = 0x04;
  my $NULL	= 0x05;
  my $OBJ_ID	= 0x06;
  my $UTF8String	= 0x0c;
  my $SEQUENCE	= 0x10;
  my $SET		= 0x11;
  my $UTCTime	= 0x17;
  my $GeneralizedTime = 0x18;
80d65e58e   David Howells   MODSIGN: Sign mod...
106

b37d1bfb5   David Howells   MODSIGN: perlify ...
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
  my %OIDs = (
      pack("CCC", 85, 4, 3)	=> "commonName",
      pack("CCC", 85, 4, 6)	=> "countryName",
      pack("CCC", 85, 4, 10)	=> "organizationName",
      pack("CCC", 85, 4, 11)	=> "organizationUnitName",
      pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 1) => "rsaEncryption",
      pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 5) => "sha1WithRSAEncryption",
      pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 9, 1) => "emailAddress",
      pack("CCC", 85, 29, 35)	=> "authorityKeyIdentifier",
      pack("CCC", 85, 29, 14)	=> "subjectKeyIdentifier",
      pack("CCC", 85, 29, 19)	=> "basicConstraints"
  );
  
  ###############################################################################
  #
  # Extract an ASN.1 element from a string and return information about it.
  #
  ###############################################################################
  sub asn1_extract($$@)
  {
      my ($cursor, $expected_tag, $optional) = @_;
  
      return [ -1 ]
  	if ($cursor->[1] == 0 && $optional);
  
      die $x509, ": ", $cursor->[0], ": ASN.1 data underrun (elem ", $cursor->[1], ")
  "
  	if ($cursor->[1] < 2);
  
      my ($tag, $len) = unpack("CC", substr(${$cursor->[2]}, $cursor->[0], 2));
  
      if ($expected_tag != -1 && $tag != $expected_tag) {
  	return [ -1 ]
  	    if ($optional);
  	die $x509, ": ", $cursor->[0], ": ASN.1 unexpected tag (", $tag,
  	" not ", $expected_tag, ")
  ";
      }
  
      $cursor->[0] += 2;
      $cursor->[1] -= 2;
  
      die $x509, ": ", $cursor->[0], ": ASN.1 long tag
  "
  	if (($tag & 0x1f) == 0x1f);
      die $x509, ": ", $cursor->[0], ": ASN.1 indefinite length
  "
  	if ($len == 0x80);
  
      if ($len > 0x80) {
  	my $l = $len - 0x80;
  	die $x509, ": ", $cursor->[0], ": ASN.1 data underrun (len len $l)
  "
  	    if ($cursor->[1] < $l);
  
  	if ($l == 0x1) {
  	    $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1));
916492b1e   Chun-Yi Lee   sign-file: fix th...
164
  	} elsif ($l == 0x2) {
b37d1bfb5   David Howells   MODSIGN: perlify ...
165
  	    $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0], 2));
916492b1e   Chun-Yi Lee   sign-file: fix th...
166
  	} elsif ($l == 0x3) {
b37d1bfb5   David Howells   MODSIGN: perlify ...
167
168
  	    $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)) << 16;
  	    $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0] + 1, 2));
916492b1e   Chun-Yi Lee   sign-file: fix th...
169
  	} elsif ($l == 0x4) {
b37d1bfb5   David Howells   MODSIGN: perlify ...
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
  	    $len = unpack("N", substr(${$cursor->[2]}, $cursor->[0], 4));
  	} else {
  	    die $x509, ": ", $cursor->[0], ": ASN.1 element too long (", $l, ")
  ";
  	}
  
  	$cursor->[0] += $l;
  	$cursor->[1] -= $l;
      }
  
      die $x509, ": ", $cursor->[0], ": ASN.1 data underrun (", $len, ")
  "
  	if ($cursor->[1] < $len);
  
      my $ret = [ $tag, [ $cursor->[0], $len, $cursor->[2] ] ];
      $cursor->[0] += $len;
      $cursor->[1] -= $len;
  
      return $ret;
  }
  
  ###############################################################################
  #
  # Retrieve the data referred to by a cursor
  #
  ###############################################################################
  sub asn1_retrieve($)
  {
      my ($cursor) = @_;
      my ($offset, $len, $data) = @$cursor;
      return substr($$data, $offset, $len);
  }
  
  ###############################################################################
  #
  # Roughly parse the X.509 certificate
  #
  ###############################################################################
  my $cursor = [ 0, length($x509_certificate), \$x509_certificate ];
  
  my $cert = asn1_extract($cursor, $UNIV | $CONS | $SEQUENCE);
  my $tbs = asn1_extract($cert->[1], $UNIV | $CONS | $SEQUENCE);
  my $version = asn1_extract($tbs->[1], $CONT | $CONS | 0, 1);
  my $serial_number = asn1_extract($tbs->[1], $UNIV | $INTEGER);
  my $sig_type = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE);
  my $issuer = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE);
  my $validity = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE);
  my $subject = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE);
  my $key = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE);
  my $issuer_uid = asn1_extract($tbs->[1], $CONT | $CONS | 1, 1);
  my $subject_uid = asn1_extract($tbs->[1], $CONT | $CONS | 2, 1);
  my $extension_list = asn1_extract($tbs->[1], $CONT | $CONS | 3, 1);
  
  my $subject_key_id = ();
  my $authority_key_id = ();
  
  #
  # Parse the extension list
  #
  if ($extension_list->[0] != -1) {
      my $extensions = asn1_extract($extension_list->[1], $UNIV | $CONS | $SEQUENCE);
  
      while ($extensions->[1]->[1] > 0) {
  	my $ext = asn1_extract($extensions->[1], $UNIV | $CONS | $SEQUENCE);
  	my $x_oid = asn1_extract($ext->[1], $UNIV | $OBJ_ID);
  	my $x_crit = asn1_extract($ext->[1], $UNIV | $BOOLEAN, 1);
  	my $x_val = asn1_extract($ext->[1], $UNIV | $OCTET_STRING);
  
  	my $raw_oid = asn1_retrieve($x_oid->[1]);
  	next if (!exists($OIDs{$raw_oid}));
  	my $x_type = $OIDs{$raw_oid};
  
  	my $raw_value = asn1_retrieve($x_val->[1]);
  
  	if ($x_type eq "subjectKeyIdentifier") {
  	    my $vcursor = [ 0, length($raw_value), \$raw_value ];
  
  	    $subject_key_id = asn1_extract($vcursor, $UNIV | $OCTET_STRING);
  	}
      }
  }
  
  ###############################################################################
  #
  # Determine what we're going to use as the signer's name.  In order of
  # preference, take one of: commonName, organizationName or emailAddress.
  #
  ###############################################################################
  my $org = "";
  my $cn = "";
  my $email = "";
  
  while ($subject->[1]->[1] > 0) {
      my $rdn = asn1_extract($subject->[1], $UNIV | $CONS | $SET);
      my $attr = asn1_extract($rdn->[1], $UNIV | $CONS | $SEQUENCE);
      my $n_oid = asn1_extract($attr->[1], $UNIV | $OBJ_ID);
      my $n_val = asn1_extract($attr->[1], -1);
  
      my $raw_oid = asn1_retrieve($n_oid->[1]);
      next if (!exists($OIDs{$raw_oid}));
      my $n_type = $OIDs{$raw_oid};
  
      my $raw_value = asn1_retrieve($n_val->[1]);
  
      if ($n_type eq "organizationName") {
  	$org = $raw_value;
      } elsif ($n_type eq "commonName") {
  	$cn = $raw_value;
      } elsif ($n_type eq "emailAddress") {
  	$email = $raw_value;
      }
  }
  
  my $signers_name = $email;
  
  if ($org && $cn) {
      # Don't use the organizationName if the commonName repeats it
      if (length($org) <= length($cn) &&
  	substr($cn, 0, length($org)) eq $org) {
  	$signers_name = $cn;
  	goto got_id_name;
      }
  
      # Or a signifcant chunk of it
      if (length($org) >= 7 &&
  	length($cn) >= 7 &&
  	substr($cn, 0, 7) eq substr($org, 0, 7)) {
  	$signers_name = $cn;
  	goto got_id_name;
      }
  
      $signers_name = $org . ": " . $cn;
  } elsif ($org) {
      $signers_name = $org;
  } elsif ($cn) {
      $signers_name = $cn;
  }
  
  got_id_name:
  
  die $x509, ": ", "X.509: Couldn't find the Subject Key Identifier extension
  "
      if (!$subject_key_id);
  
  my $key_identifier = asn1_retrieve($subject_key_id->[1]);
  
  ###############################################################################
  #
  # Create and attach the module signature
  #
  ###############################################################################
80d65e58e   David Howells   MODSIGN: Sign mod...
321
322
323
324
  
  #
  # Signature parameters
  #
b37d1bfb5   David Howells   MODSIGN: perlify ...
325
326
327
  my $algo = 1;		# Public-key crypto algorithm: RSA
  my $hash = 0;		# Digest algorithm
  my $id_type = 1;	# Identifier type: X.509
80d65e58e   David Howells   MODSIGN: Sign mod...
328
329
330
331
  
  #
  # Digest the data
  #
4bc9410c0   Michal Marek   MODSIGN: Specify ...
332
333
  my $prologue;
  if ($dgst eq "sha1") {
b37d1bfb5   David Howells   MODSIGN: perlify ...
334
335
336
337
      $prologue = pack("C*",
  		     0x30, 0x21, 0x30, 0x09, 0x06, 0x05,
  		     0x2B, 0x0E, 0x03, 0x02, 0x1A,
  		     0x05, 0x00, 0x04, 0x14);
b37d1bfb5   David Howells   MODSIGN: perlify ...
338
      $hash = 2;
4bc9410c0   Michal Marek   MODSIGN: Specify ...
339
  } elsif ($dgst eq "sha224") {
b37d1bfb5   David Howells   MODSIGN: perlify ...
340
341
342
343
      $prologue = pack("C*",
  		     0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09,
  		     0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04,
  		     0x05, 0x00, 0x04, 0x1C);
b37d1bfb5   David Howells   MODSIGN: perlify ...
344
      $hash = 7;
4bc9410c0   Michal Marek   MODSIGN: Specify ...
345
  } elsif ($dgst eq "sha256") {
b37d1bfb5   David Howells   MODSIGN: perlify ...
346
347
348
349
      $prologue = pack("C*",
  		     0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
  		     0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
  		     0x05, 0x00, 0x04, 0x20);
b37d1bfb5   David Howells   MODSIGN: perlify ...
350
      $hash = 4;
4bc9410c0   Michal Marek   MODSIGN: Specify ...
351
  } elsif ($dgst eq "sha384") {
b37d1bfb5   David Howells   MODSIGN: perlify ...
352
353
354
355
      $prologue = pack("C*",
  		     0x30, 0x41, 0x30, 0x0d, 0x06, 0x09,
  		     0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
  		     0x05, 0x00, 0x04, 0x30);
b37d1bfb5   David Howells   MODSIGN: perlify ...
356
      $hash = 5;
4bc9410c0   Michal Marek   MODSIGN: Specify ...
357
  } elsif ($dgst eq "sha512") {
b37d1bfb5   David Howells   MODSIGN: perlify ...
358
359
360
361
      $prologue = pack("C*",
  		     0x30, 0x51, 0x30, 0x0d, 0x06, 0x09,
  		     0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
  		     0x05, 0x00, 0x04, 0x40);
b37d1bfb5   David Howells   MODSIGN: perlify ...
362
363
      $hash = 6;
  } else {
4bc9410c0   Michal Marek   MODSIGN: Specify ...
364
365
      die "Unknown hash algorithm: $dgst
  ";
b37d1bfb5   David Howells   MODSIGN: perlify ...
366
  }
b37d1bfb5   David Howells   MODSIGN: perlify ...
367
  my $signature;
1c37c054a   Michal Marek   MODSIGN: Add -s <...
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
  if ($signature_file) {
  	$signature = read_file($signature_file);
  } else {
  	#
  	# Generate the digest and read from openssl's stdout
  	#
  	my $digest;
  	$digest = readpipe("openssl dgst -$dgst -binary $module") || die "openssl dgst";
  
  	#
  	# Generate the binary signature, which will be just the integer that
  	# comprises the signature with no metadata attached.
  	#
  	my $pid;
  	$pid = open2(*read_from, *write_to,
  		     "openssl rsautl -sign -inkey $private_key -keyform PEM") ||
  	    die "openssl rsautl";
  	binmode write_to;
  	print write_to $prologue . $digest || die "pipe to openssl rsautl";
  	close(write_to) || die "pipe to openssl rsautl";
  
  	binmode read_from;
  	read(read_from, $signature, 4096) || die "pipe from openssl rsautl";
  	close(read_from) || die "pipe from openssl rsautl";
  	waitpid($pid, 0) || die;
  	die "openssl rsautl died: $?" if ($? >> 8);
  }
b37d1bfb5   David Howells   MODSIGN: perlify ...
395
  $signature = pack("n", length($signature)) . $signature,
e2a666d52   Rusty Russell   kbuild: sign the ...
396

80d65e58e   David Howells   MODSIGN: Sign mod...
397
398
399
  #
  # Build the signed binary
  #
b37d1bfb5   David Howells   MODSIGN: perlify ...
400
401
402
403
404
405
406
407
408
409
  my $unsigned_module = read_file($module);
  
  my $magic_number = "~Module signature appended~
  ";
  
  my $info = pack("CCCCCxxxN",
  		$algo, $hash, $id_type,
  		length($signers_name),
  		length($key_identifier),
  		length($signature));
80d65e58e   David Howells   MODSIGN: Sign mod...
410

b37d1bfb5   David Howells   MODSIGN: perlify ...
411
412
413
  if ($verbose) {
      print "Size of unsigned module: ", length($unsigned_module), "
  ";
b37d1bfb5   David Howells   MODSIGN: perlify ...
414
415
416
417
418
419
      print "Size of signer's name  : ", length($signers_name), "
  ";
      print "Size of key identifier : ", length($key_identifier), "
  ";
      print "Size of signature      : ", length($signature), "
  ";
1a84db567   Masanari Iida   treewide: fix err...
420
421
      print "Size of information    : ", length($info), "
  ";
caabe2405   David Howells   MODSIGN: Move the...
422
423
      print "Size of magic number   : ", length($magic_number), "
  ";
b37d1bfb5   David Howells   MODSIGN: perlify ...
424
425
426
427
428
      print "Signer's name          : '", $signers_name, "'
  ";
      print "Digest                 : $dgst
  ";
  }
80d65e58e   David Howells   MODSIGN: Sign mod...
429

b37d1bfb5   David Howells   MODSIGN: perlify ...
430
431
432
433
  open(FD, ">$dest") || die $dest;
  binmode FD;
  print FD
      $unsigned_module,
b37d1bfb5   David Howells   MODSIGN: perlify ...
434
435
436
      $signers_name,
      $key_identifier,
      $signature,
caabe2405   David Howells   MODSIGN: Move the...
437
438
      $info,
      $magic_number
b37d1bfb5   David Howells   MODSIGN: perlify ...
439
440
      ;
  close FD || die $dest;
80d65e58e   David Howells   MODSIGN: Sign mod...
441

1c37c054a   Michal Marek   MODSIGN: Add -s <...
442
  if (!$keep_orig) {
b37d1bfb5   David Howells   MODSIGN: perlify ...
443
444
      rename($dest, $module) || die $module;
  }