diff --git a/kernel-sign-file b/kernel-sign-file new file mode 100644 index 0000000..8f4d8b0 --- /dev/null +++ b/kernel-sign-file @@ -0,0 +1,510 @@ +#!/usr/bin/perl -w +# +# Sign a module file using the given key. +# + +my $USAGE = +"Usage: scripts/sign-file [-dkpv] [-i ] []\n" . +" scripts/sign-file [-dkpv] [-i ] -s []\n"; + +use strict; +use FileHandle; +use IPC::Open2; +use Getopt::Std; + +my %opts; +getopts('vkpds:i:', \%opts) or die $USAGE; +my $verbose = $opts{'v'}; +my $signature_file = $opts{'s'}; +my $use_keyid = $opts{'k'}; +my $sign_only = $opts{'d'}; +my $save_sig = $opts{'p'}; +$save_sig = 1 if $sign_only; +my $id_type_name = $opts{'i'}; + +die $USAGE if ($#ARGV > 4); +die $USAGE if (!$signature_file && $#ARGV < 3 || $signature_file && $#ARGV < 2); + +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 . "~"; +} + +die "Can't read private key\n" if (!$signature_file && !-r $private_key); +die "Can't read signature file\n" if ($signature_file && !-r $signature_file); +die "Can't read X.509 certificate\n" unless (-r $x509); +die "Can't read module\n" unless (-r $module); + +# +# 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, "\n" + if ($len != $st[7]); + return $contents; +} + +sub openssl_pipe($$) { + my ($input, $cmd) = @_; + my ($pid, $res); + + $pid = open2(*read_from, *write_to, $cmd) || die $cmd; + binmode write_to; + print write_to $input || die "pipe to $cmd"; + close(write_to) || die "pipe to $cmd"; + + binmode read_from; + read(read_from, $res, 4096) || die "pipe from $cmd"; + close(read_from) || die "pipe from $cmd"; + waitpid($pid, 0) || die; + die "$cmd died: $?" if ($? >> 8); + return $res; +} + +############################################################################### +# +# 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); + +my $UNIV = 0 << 6; +my $APPL = 1 << 6; +my $CONT = 2 << 6; +my $PRIV = 3 << 6; + +my $CONS = 0x20; + +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; + +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], ")\n" + 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, ")\n"; + } + + $cursor->[0] += 2; + $cursor->[1] -= 2; + + die $x509, ": ", $cursor->[0], ": ASN.1 long tag\n" + if (($tag & 0x1f) == 0x1f); + die $x509, ": ", $cursor->[0], ": ASN.1 indefinite length\n" + if ($len == 0x80); + + if ($len > 0x80) { + my $l = $len - 0x80; + die $x509, ": ", $cursor->[0], ": ASN.1 data underrun (len len $l)\n" + if ($cursor->[1] < $l); + + if ($l == 0x1) { + $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)); + } elsif ($l == 0x2) { + $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0], 2)); + } elsif ($l == 0x3) { + $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)) << 16; + $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0] + 1, 2)); + } elsif ($l == 0x4) { + $len = unpack("N", substr(${$cursor->[2]}, $cursor->[0], 4)); + } else { + die $x509, ": ", $cursor->[0], ": ASN.1 element too long (", $l, ")\n"; + } + + $cursor->[0] += $l; + $cursor->[1] -= $l; + } + + die $x509, ": ", $cursor->[0], ": ASN.1 data underrun (", $len, ")\n" + 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); +} + + +sub asn1_pack($@) +{ + my ($tag, @data) = @_; + my $ret = pack("C", $tag); + my $data = join('', @data); + my $l = length($data); + return pack("CC", $tag, $l) . $data if $l < 127; + my $ll = $l >> 8 ? $l >> 16 ? $l >> 24 ? 4 : 3 : 2 : 1; + return pack("CCa*", $tag, $ll | 0x80, substr(pack("N", $l), -$ll)) . $data; +} + +############################################################################### +# +# 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\n" + if (!$subject_key_id); + +my $key_identifier = asn1_retrieve($subject_key_id->[1]); + +############################################################################### +# +# Create and attach the module signature +# +############################################################################### + +# +# Signature parameters +# +my $algo = 1; # Public-key crypto algorithm: RSA +my $hash = 0; # Digest algorithm +my $id_type = 1; # Identifier type: X.509 + +if ($id_type_name) { + if ($id_type_name eq 'x509') { + $id_type = 1; # Identifier type: X.509 + } elsif ($id_type_name eq 'pkcs7') { + $id_type = 2; # Identifier type: PKCS7 + } else { + die("unknown id type: $id_type_name\n"); + } +} + +# +# Digest the data +# +my $prologue; +if ($dgst eq "sha1") { + $prologue = pack("C*", + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, + 0x2B, 0x0E, 0x03, 0x02, 0x1A, + 0x05, 0x00, 0x04, 0x14); + $hash = 2; +} elsif ($dgst eq "sha224") { + $prologue = pack("C*", + 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, + 0x05, 0x00, 0x04, 0x1C); + $hash = 7; +} elsif ($dgst eq "sha256") { + $prologue = pack("C*", + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, 0x04, 0x20); + $hash = 4; +} elsif ($dgst eq "sha384") { + $prologue = pack("C*", + 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, + 0x05, 0x00, 0x04, 0x30); + $hash = 5; +} elsif ($dgst eq "sha512") { + $prologue = pack("C*", + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, 0x04, 0x40); + $hash = 6; +} else { + die "Unknown hash algorithm: $dgst\n"; +} + +my $unsigned_module = read_file($module); + +my $magic_number = "~Module signature appended~\n"; +my $magic_len = length($magic_number); +my $info_len = 12; + +# Truncate existing signarure, if any +if (substr($unsigned_module, -$magic_len) eq $magic_number) { + my $info = substr($unsigned_module, -$magic_len - $info_len, $info_len); + my ($name_len, $key_len, $sig_len) = unpack("xxxCCxxxN", $info); + my $subtract = $name_len + $key_len + $sig_len + $info_len + $magic_len; + if ($subtract > length($unsigned_module)) { + die "$module: Existing signature is malformed\n"; + } + $unsigned_module = substr($unsigned_module, 0, + length($unsigned_module) - $subtract); +} + +my $signature; +if ($signature_file) { + $signature = read_file($signature_file); +} else { + # + # Generate the digest and read from openssl's stdout + # + my $digest = openssl_pipe($unsigned_module, + "openssl dgst -$dgst -binary"); + + # + # Generate the binary signature, which will be just the integer that + # comprises the signature with no metadata attached. + # + $signature = openssl_pipe($prologue . $digest, + "openssl rsautl -sign -inkey $private_key -keyform PEM"); +} + +if ($id_type == 1) { + $signature = pack("n", length($signature)) . $signature, +} elsif ($id_type == 2) { + # create PKCS7 signature + $signature = asn1_pack($UNIV | $OCTET_STRING, $signature); + my $digest_algo = substr($prologue, 4, 2 + unpack('C', substr($prologue, 5, 1))); + my $digest_algo_seq = asn1_pack($UNIV | $CONS | $SEQUENCE, $digest_algo); + my $digest_algo_seq_set = asn1_pack($UNIV | $CONS | $SET, $digest_algo_seq); + my $si_verstion = asn1_pack($UNIV | $INTEGER, pack('C', $use_keyid ? 3 : 1)); + my $si_issuer = asn1_pack($issuer->[0], asn1_retrieve($issuer->[1])); + my $si_serial = asn1_pack($serial_number->[0], asn1_retrieve($serial_number->[1])); + my $si_issuer_serial = asn1_pack($UNIV | $CONS | $SEQUENCE, $si_issuer, $si_serial); + my $si_keyid = asn1_pack($CONT | 0, asn1_retrieve($subject_key_id->[1])); + my $rsa_encryption = asn1_pack($UNIV | $OBJ_ID, pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 1)); + my $encryption_seq = asn1_pack($UNIV | $CONS | $SEQUENCE, $rsa_encryption, asn1_pack($UNIV | $NULL)); + my $signer_identifier = $use_keyid ? $si_keyid : $si_issuer_serial; + my $si = asn1_pack($UNIV | $CONS | $SEQUENCE, $si_verstion, $signer_identifier, $digest_algo_seq, $encryption_seq, $signature); + my $si_set = asn1_pack($UNIV | $CONS | $SET, $si); + my $sid_version = asn1_pack($UNIV | $INTEGER, pack('C', $use_keyid ? 3 : 1)); + my $pkcs7_data = asn1_pack($UNIV | $OBJ_ID, pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 7, 1)); + my $pkcs7_data_seq = asn1_pack($UNIV | $CONS | $SEQUENCE, $pkcs7_data); + my $sid = asn1_pack($UNIV | $CONS | $SEQUENCE, $sid_version, $digest_algo_seq_set, $pkcs7_data_seq, $si_set); + my $pkcs7_signed_data = asn1_pack($UNIV | $OBJ_ID, pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 7, 2)); + $signature = asn1_pack($UNIV | $CONS | $SEQUENCE, $pkcs7_signed_data, asn1_pack($CONT | $CONS | 0, $sid)); + # zero out unneeded entries + $signers_name = ''; + $key_identifier = ''; + $algo = $hash = 0; +} else { + die("unknown signature id type: $id_type\n"); +} + +if ($save_sig) { + open(FD, ">$module.p7s") || die "$module.p7s"; + binmode FD; + print FD $signature; +} +exit(0) if $sign_only; + +# +# Build the signed binary +# +my $info = pack("CCCCCxxxN", + $algo, $hash, $id_type, + length($signers_name), + length($key_identifier), + length($signature)); +# Make sure that $info_len value used above matches reality +if (length($info) != $info_len) { + die "Signature info size changed ($info_len -> @{[length($info)]}"; +} + +if ($verbose) { + print "Size of unsigned module: ", length($unsigned_module), "\n"; + print "Size of signer's name : ", length($signers_name), "\n"; + print "Size of key identifier : ", length($key_identifier), "\n"; + print "Size of signature : ", length($signature), "\n"; + print "Size of informaton : ", length($info), "\n"; + print "Size of magic number : ", length($magic_number), "\n"; + print "Signer's name : '", $signers_name, "'\n"; + print "Digest : $dgst\n"; +} + +open(FD, ">$dest") || die $dest; +binmode FD; +print FD + $unsigned_module, + $signers_name, + $key_identifier, + $signature, + $info, + $magic_number + ; +close FD || die $dest; + +if (!$keep_orig) { + rename($dest, $module) || die $module; +} diff --git a/modsign-repackage b/modsign-repackage index 7350e2d..e0c2324 100644 --- a/modsign-repackage +++ b/modsign-repackage @@ -119,16 +119,16 @@ set -e echo "Signing kernel modules..." for module in $(find "$buildroot" -type f -name '*.ko' -printf '%P\n'); do if test -n "$key"; then - /usr/lib/rpm/pesign/sign-file \ - sha256 "$key" "$cert" "$buildroot/$module" + /usr/lib/rpm/pesign/kernel-sign-file \ + -i pkcs7 sha256 "$key" "$cert" "$buildroot/$module" else raw_sig="$sig_dir/$module.sig" if test ! -e "$raw_sig"; then echo "$module.sig not found in $sig_dir" >&2 exit 1 fi - /usr/lib/rpm/pesign/sign-file \ - -s "$raw_sig" sha256 "$cert" "$buildroot/$module" + /usr/lib/rpm/pesign/kernel-sign-file \ + -i pkcs7 -s "$raw_sig" sha256 "$cert" "$buildroot/$module" fi done diff --git a/pesign-obs-integration.changes b/pesign-obs-integration.changes index 15204ae..55c81e3 100644 --- a/pesign-obs-integration.changes +++ b/pesign-obs-integration.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Wed Sep 27 10:53:39 UTC 2017 - jlee@suse.com + +- Michael Schröder improved the original kernel-sign-file script to + support PKCS#7 kernel module signing. Replacing sign-file.c with + new kernel-sign-file script. (bsc#1049122) + ------------------------------------------------------------------- Sun Sep 24 09:20:31 UTC 2017 - coolo@suse.com diff --git a/pesign-obs-integration.spec b/pesign-obs-integration.spec index 219ba8e..311d509 100644 --- a/pesign-obs-integration.spec +++ b/pesign-obs-integration.spec @@ -29,7 +29,6 @@ Requires: openssl %ifarch %ix86 x86_64 ia64 aarch64 Requires: pesign %endif -BuildRequires: libopenssl-devel BuildRequires: openssl Url: http://en.opensuse.org/openSUSE:UEFI_Image_File_Sign_Tools Source2: pesign-repackage.spec.in @@ -37,7 +36,7 @@ Source3: pesign-gen-repackage-spec Source4: brp-99-pesign Source5: COPYING Source6: README -Source7: sign-file.c +Source7: kernel-sign-file Source8: modsign-repackage Source9: gen-hmac Source10: brp-99-compress-vmlinux @@ -52,13 +51,12 @@ boot loader, kernel and kernel modules in the openSUSE Buildservice. cp %_sourcedir/{COPYING,README} . %build -gcc $RPM_OPT_FLAGS %_sourcedir/sign-file.c -o %_sourcedir/sign-file -lcrypto %install mkdir -p %buildroot/usr/lib/rpm/brp-suse.d %buildroot/usr/lib/rpm/pesign cd %_sourcedir -install pesign-gen-repackage-spec sign-file gen-hmac %buildroot/usr/lib/rpm/pesign +install pesign-gen-repackage-spec kernel-sign-file gen-hmac %buildroot/usr/lib/rpm/pesign install brp-99-pesign %buildroot/usr/lib/rpm/brp-suse.d # brp-99-compress-vmlinux has nothing to do with signing. It is packaged in # pesign-obs-integration because this package is already used by the kernel diff --git a/pesign-repackage.spec.in b/pesign-repackage.spec.in index c95d165..ed07436 100644 --- a/pesign-repackage.spec.in +++ b/pesign-repackage.spec.in @@ -119,7 +119,7 @@ for sig in "${sigs[@]}"; do f=%buildroot/${sig%.sig} case "/$sig" in *.ko.sig) - /usr/lib/rpm/pesign/sign-file -s "$sig" sha256 "$cert" "$f" + /usr/lib/rpm/pesign/kernel-sign-file -i pkcs7 -s "$sig" sha256 "$cert" "$f" ;; /boot/* | *.efi.sig) infile=${sig%.sig} diff --git a/sign-file.c b/sign-file.c deleted file mode 100644 index 599971f..0000000 --- a/sign-file.c +++ /dev/null @@ -1,399 +0,0 @@ -/* Sign a module file using the given key. - * - * Copyright © 2014-2016 Red Hat, Inc. All Rights Reserved. - * Copyright © 2015 Intel Corporation. - * Copyright © 2016 Hewlett Packard Enterprise Development LP - * - * Authors: David Howells - * David Woodhouse - * Juerg Haefliger - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 2.1 - * of the licence, or (at your option) any later version. - */ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Use CMS if we have openssl-1.0.0 or newer available - otherwise we have to - * assume that it's not available and its header file is missing and that we - * should use PKCS#7 instead. Switching to the older PKCS#7 format restricts - * the options we have on specifying the X.509 certificate we want. - * - * Further, older versions of OpenSSL don't support manually adding signers to - * the PKCS#7 message so have to accept that we get a certificate included in - * the signature message. Nor do such older versions of OpenSSL support - * signing with anything other than SHA1 - so we're stuck with that if such is - * the case. - */ -#if defined(LIBRESSL_VERSION_NUMBER) || \ - OPENSSL_VERSION_NUMBER < 0x10000000L || \ - defined(OPENSSL_NO_CMS) -#define USE_PKCS7 -#endif -#ifndef USE_PKCS7 -#include -#else -#include -#endif - -struct module_signature { - uint8_t algo; /* Public-key crypto algorithm [0] */ - uint8_t hash; /* Digest algorithm [0] */ - uint8_t id_type; /* Key identifier type [PKEY_ID_PKCS7] */ - uint8_t signer_len; /* Length of signer's name [0] */ - uint8_t key_id_len; /* Length of key identifier [0] */ - uint8_t __pad[3]; - uint32_t sig_len; /* Length of signature data */ -}; - -#define PKEY_ID_PKCS7 2 - -static char magic_number[] = "~Module signature appended~\n"; - -static __attribute__((noreturn)) -void format(void) -{ - fprintf(stderr, - "Usage: scripts/sign-file [-dp] []\n"); - fprintf(stderr, - " scripts/sign-file -s []\n"); - exit(2); -} - -static void display_openssl_errors(int l) -{ - const char *file; - char buf[120]; - int e, line; - - if (ERR_peek_error() == 0) - return; - fprintf(stderr, "At main.c:%d:\n", l); - - while ((e = ERR_get_error_line(&file, &line))) { - ERR_error_string(e, buf); - fprintf(stderr, "- SSL %s: %s:%d\n", buf, file, line); - } -} - -static void drain_openssl_errors(void) -{ - const char *file; - int line; - - if (ERR_peek_error() == 0) - return; - while (ERR_get_error_line(&file, &line)) {} -} - -#define ERR(cond, fmt, ...) \ - do { \ - bool __cond = (cond); \ - display_openssl_errors(__LINE__); \ - if (__cond) { \ - err(1, fmt, ## __VA_ARGS__); \ - } \ - } while(0) - -static const char *key_pass; - -static int pem_pw_cb(char *buf, int len, int w, void *v) -{ - int pwlen; - - if (!key_pass) - return -1; - - pwlen = strlen(key_pass); - if (pwlen >= len) - return -1; - - strcpy(buf, key_pass); - - /* If it's wrong, don't keep trying it. */ - key_pass = NULL; - - return pwlen; -} - -static EVP_PKEY *read_private_key(const char *private_key_name) -{ - EVP_PKEY *private_key; - - if (!strncmp(private_key_name, "pkcs11:", 7)) { - ENGINE *e; - - ENGINE_load_builtin_engines(); - drain_openssl_errors(); - e = ENGINE_by_id("pkcs11"); - ERR(!e, "Load PKCS#11 ENGINE"); - if (ENGINE_init(e)) - drain_openssl_errors(); - else - ERR(1, "ENGINE_init"); - if (key_pass) - ERR(!ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0), - "Set PKCS#11 PIN"); - private_key = ENGINE_load_private_key(e, private_key_name, - NULL, NULL); - ERR(!private_key, "%s", private_key_name); - } else { - BIO *b; - - b = BIO_new_file(private_key_name, "rb"); - ERR(!b, "%s", private_key_name); - private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, - NULL); - ERR(!private_key, "%s", private_key_name); - BIO_free(b); - } - - return private_key; -} - -static X509 *read_x509(const char *x509_name) -{ - unsigned char buf[2]; - X509 *x509; - BIO *b; - int n; - - b = BIO_new_file(x509_name, "rb"); - ERR(!b, "%s", x509_name); - - /* Look at the first two bytes of the file to determine the encoding */ - n = BIO_read(b, buf, 2); - if (n != 2) { - if (BIO_should_retry(b)) { - fprintf(stderr, "%s: Read wanted retry\n", x509_name); - exit(1); - } - if (n >= 0) { - fprintf(stderr, "%s: Short read\n", x509_name); - exit(1); - } - ERR(1, "%s", x509_name); - } - - ERR(BIO_reset(b) != 0, "%s", x509_name); - - if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84) - /* Assume raw DER encoded X.509 */ - x509 = d2i_X509_bio(b, NULL); - else - /* Assume PEM encoded X.509 */ - x509 = PEM_read_bio_X509(b, NULL, NULL, NULL); - - BIO_free(b); - ERR(!x509, "%s", x509_name); - - return x509; -} - -int main(int argc, char **argv) -{ - struct module_signature sig_info = { .id_type = PKEY_ID_PKCS7 }; - char *hash_algo = NULL; - char *private_key_name = NULL, *raw_sig_name = NULL; - char *x509_name, *module_name, *dest_name; - bool save_sig = false, replace_orig; - bool sign_only = false; - bool raw_sig = false; - unsigned char buf[4096]; - unsigned long module_size, sig_size; - unsigned int use_signed_attrs; - const EVP_MD *digest_algo; - EVP_PKEY *private_key; -#ifndef USE_PKCS7 - CMS_ContentInfo *cms = NULL; - unsigned int use_keyid = 0; -#else - PKCS7 *pkcs7 = NULL; -#endif - X509 *x509; - BIO *bd, *bm; - int opt, n; - OpenSSL_add_all_algorithms(); - ERR_load_crypto_strings(); - ERR_clear_error(); - - key_pass = getenv("KBUILD_SIGN_PIN"); - -#ifndef USE_PKCS7 - use_signed_attrs = CMS_NOATTR; -#else - use_signed_attrs = PKCS7_NOATTR; -#endif - - do { - opt = getopt(argc, argv, "sdpk"); - switch (opt) { - case 's': raw_sig = true; break; - case 'p': save_sig = true; break; - case 'd': sign_only = true; save_sig = true; break; -#ifndef USE_PKCS7 - case 'k': use_keyid = CMS_USE_KEYID; break; -#endif - case -1: break; - default: format(); - } - } while (opt != -1); - - argc -= optind; - argv += optind; - if (argc < 4 || argc > 5) - format(); - - if (raw_sig) { - raw_sig_name = argv[0]; - hash_algo = argv[1]; - } else { - hash_algo = argv[0]; - private_key_name = argv[1]; - } - x509_name = argv[2]; - module_name = argv[3]; - if (argc == 5 && strcmp(argv[3], argv[4]) != 0) { - dest_name = argv[4]; - replace_orig = false; - } else { - ERR(asprintf(&dest_name, "%s.~signed~", module_name) < 0, - "asprintf"); - replace_orig = true; - } - -#ifdef USE_PKCS7 - if (strcmp(hash_algo, "sha1") != 0) { - fprintf(stderr, "sign-file: %s only supports SHA1 signing\n", - OPENSSL_VERSION_TEXT); - exit(3); - } -#endif - - /* Open the module file */ - bm = BIO_new_file(module_name, "rb"); - ERR(!bm, "%s", module_name); - - if (!raw_sig) { - /* Read the private key and the X.509 cert the PKCS#7 message - * will point to. - */ - private_key = read_private_key(private_key_name); - x509 = read_x509(x509_name); - - /* Digest the module data. */ - OpenSSL_add_all_digests(); - display_openssl_errors(__LINE__); - digest_algo = EVP_get_digestbyname(hash_algo); - ERR(!digest_algo, "EVP_get_digestbyname"); - -#ifndef USE_PKCS7 - /* Load the signature message from the digest buffer. */ - cms = CMS_sign(NULL, NULL, NULL, NULL, - CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | - CMS_DETACHED | CMS_STREAM); - ERR(!cms, "CMS_sign"); - - ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo, - CMS_NOCERTS | CMS_BINARY | - CMS_NOSMIMECAP | use_keyid | - use_signed_attrs), - "CMS_add1_signer"); - ERR(CMS_final(cms, bm, NULL, CMS_NOCERTS | CMS_BINARY) < 0, - "CMS_final"); - -#else - pkcs7 = PKCS7_sign(x509, private_key, NULL, bm, - PKCS7_NOCERTS | PKCS7_BINARY | - PKCS7_DETACHED | use_signed_attrs); - ERR(!pkcs7, "PKCS7_sign"); -#endif - - if (save_sig) { - char *sig_file_name; - BIO *b; - - ERR(asprintf(&sig_file_name, "%s.p7s", module_name) < 0, - "asprintf"); - b = BIO_new_file(sig_file_name, "wb"); - ERR(!b, "%s", sig_file_name); -#ifndef USE_PKCS7 - ERR(i2d_CMS_bio_stream(b, cms, NULL, 0) < 0, - "%s", sig_file_name); -#else - ERR(i2d_PKCS7_bio(b, pkcs7) < 0, - "%s", sig_file_name); -#endif - BIO_free(b); - } - - if (sign_only) { - BIO_free(bm); - return 0; - } - } - - /* Open the destination file now so that we can shovel the module data - * across as we read it. - */ - bd = BIO_new_file(dest_name, "wb"); - ERR(!bd, "%s", dest_name); - - /* Append the marker and the PKCS#7 message to the destination file */ - ERR(BIO_reset(bm) < 0, "%s", module_name); - while ((n = BIO_read(bm, buf, sizeof(buf))), - n > 0) { - ERR(BIO_write(bd, buf, n) < 0, "%s", dest_name); - } - BIO_free(bm); - ERR(n < 0, "%s", module_name); - module_size = BIO_number_written(bd); - - if (!raw_sig) { -#ifndef USE_PKCS7 - ERR(i2d_CMS_bio_stream(bd, cms, NULL, 0) < 0, "%s", dest_name); -#else - ERR(i2d_PKCS7_bio(bd, pkcs7) < 0, "%s", dest_name); -#endif - } else { - BIO *b; - - /* Read the raw signature file and write the data to the - * destination file - */ - b = BIO_new_file(raw_sig_name, "rb"); - ERR(!b, "%s", raw_sig_name); - while ((n = BIO_read(b, buf, sizeof(buf))), n > 0) - ERR(BIO_write(bd, buf, n) < 0, "%s", dest_name); - BIO_free(b); - } - - sig_size = BIO_number_written(bd) - module_size; - sig_info.sig_len = htonl(sig_size); - ERR(BIO_write(bd, &sig_info, sizeof(sig_info)) < 0, "%s", dest_name); - ERR(BIO_write(bd, magic_number, sizeof(magic_number) - 1) < 0, "%s", dest_name); - - ERR(BIO_free(bd) < 0, "%s", dest_name); - - /* Finally, if we're signing in place, replace the original. */ - if (replace_orig) - ERR(rename(dest_name, module_name) < 0, "%s", dest_name); - - return 0; -}