How about the attached?  I knew perl had to be good for something...

David
---
#!/usr/bin/perl -w
#
# Generate an X.509 certificate from a public key.
#
# Format:
#
#       gen-x509-cert <private-key> \
#          [C=<country>] [O=<org>] [CN=<cn>] [Email=<email>] \
#          [--from=<secs-before-now>] [--to=<secs-after-now] >output
#
use strict;
use POSIX qw(strftime);

my $UNIV = 0 << 6;
my $APPL = 1 << 6;
my $CONT = 2 << 6;
my $PRIV = 3 << 6;

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 $GeneralizedTime = 0x18;

my %OIDs = (
    commonName                  => pack("CCC", 85, 4, 3),
    countryName                 => pack("CCC", 85, 4, 6),
    organizationName            => pack("CCC", 85, 4, 10),
    organizationUnitName        => pack("CCC", 85, 4, 11),
    rsaEncryption               => pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 
1, 1, 1),
    sha1WithRSAEncryption       => pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 
1, 1, 5),
    emailAddress                => pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 
1, 9, 1),
    authorityKeyIdentifier      => pack("CCC", 85, 29, 35),
    subjectKeyIdentifier        => pack("CCC", 85, 29, 14),
    keyUsage                    => pack("CCC", 85, 29, 15),
    basicConstraints            => pack("CCC", 85, 29, 19)
);


#
# Set up the X.509 params
#
die "Format: <private-key> [options]"
    if ($#ARGV == -1);

my $privfilename = shift @ARGV;

my %subject_name;

if ($#ARGV == -1) {
    # Make something up if they don't want to admit to it
    $subject_name{"C"}          = 'h2g2',
    $subject_name{"O"}          = 'Magrathea',
    $subject_name{"CN"}         = 'Glacier signing key',
    $subject_name{"Email"}      = 'slartibartfast@magrathea.h2g2'
}

my $from = 7 * 24 * 60 * 60;
my $to = 36500 * 24 * 60 * 60;
foreach my $_ (@ARGV) {
    if (/--from=(.*)/) {
        $from = $1;
    } elsif (/--to=(.*)/) {
        $to = $1;
    } elsif (/([A-Z][A-Za-z]*)=(.*)/) {
        $subject_name{$1} = $2;
    } else {
        last;
    }
}

my $now = time();
my $valid_from = strftime("%Y%m%d%H%M%SZ", gmtime($now - $from));
my $valid_to   = strftime("%Y%m%d%H%M%SZ", gmtime($now + $to));

#
# openssl can be used to give us the public key in exactly the form we need -
# including ASN.1 wrappings - for inclusion in the certificate.
#
open PUBKEYFD, "openssl rsa -in $privfilename -pubout -outform DER 2>/dev/null 
|" ||
    die "Unable to process $privfilename through openssl rsa: $!\n";
binmode PUBKEYFD;

my $pubkey = "";
my $tmp;
while (read(PUBKEYFD, $tmp, 512)) {
    $pubkey .= $tmp;
}
close PUBKEYFD ||
    die "Unable to close channel to openssl rsa: $!\n";

#
# Generate a serial number
#
my $serial = "";
for (my $i = int(rand(6)) + 6; $i > 0; $i--) {
    $serial .= pack("C", rand(256));
}
$serial = pack("x") . $serial
    if (unpack("C", substr($serial, 0, 1)) >= 0x80);

#
# Generate the SubjectKeyIdentifier.  This is the ASN.1 sum of the contents of
# the bit string element from the public key.
#
die "Can't disassemble RSA public key wrapping\n"
    if (substr($pubkey,  0, 2) ne pack("n", 0x3082) ||
        substr($pubkey,  4, 4) ne pack("N", 0x300d0609) ||
        substr($pubkey,  8, 9) ne $OIDs{"rsaEncryption"} ||
        substr($pubkey, 17, 2) ne pack("n", 0x0500) ||
        substr($pubkey, 19, 2) ne pack("n", 0x0382) ||
        substr($pubkey, 23, 1) ne pack("C", 0x00));

my $key_data = substr($pubkey, 24);

sub sha1sum($)
{
    my ($data) = @_;

    my ($TO_RD, $TO_WR, $FROM_RD, $FROM_WR);
    pipe $TO_RD, $TO_WR;
    pipe $FROM_RD, $FROM_WR;

    my $sha1output;
    my $child = fork();
    if ($child == 0) {
        close $TO_WR;
        close $FROM_RD;
        open(STDIN, ">&", $TO_RD) or die "Can't direct $TO_RD to STDIN: $!";
        open(STDOUT, ">&", $FROM_WR) or die "Can't direct $FROM_WR to STDOUT: 
$!";
        close $TO_RD;
        close $FROM_WR;
        exec("sha1sum");
    } elsif (!$child) {
        die;
    } else {
        close $TO_RD;
        close $FROM_WR;
        binmode $TO_WR;
        syswrite $TO_WR, $data || die;
        close $TO_WR || die;
        $sha1output = <$FROM_RD> || die;
        close $FROM_RD;
        die "sha1sum failed\n"
            if (waitpid($child, 0) != $child);
    }

    return pack("H*", substr($sha1output, 0, 40));
}

my $keyid = sha1sum($key_data);

###############################################################################
#
# Generate a header
#
###############################################################################
sub emit_asn1_hdr($$)
{
    my ($tag, $len) = @_;

    if ($len < 0x80) {
        return pack("CC",   $tag, $len);
    } elsif ($len <= 0xff) {
        return pack("CCC",   $tag, 0x81, $len);
    } elsif ($len <= 0xffff) {
        return pack("CCn",  $tag, 0x82, $len);
    } elsif ($len <= 0xffffff) {
        return pack("CCCn", $tag, 0x83, $len >> 16, $len & 0xffff);
    } else {
        return pack("CCN",  $tag, 0x84, $len);
    }
}

###############################################################################
#
# Generate a primitive containing some data
#
###############################################################################
sub emit_asn1_prim(@)
{
    my ($class, $tag, $data) = @_;

    $data = ""
        if ($#_ == 1);

    $tag |= $class;
    return emit_asn1_hdr($tag, length($data)) . $data;
}

###############################################################################
#
# Generate an object identifier
#
###############################################################################
sub emit_asn1_OID($$$)
{
    my ($class, $tag, $oid_name) = @_;
    my $oid;

    if (!exists($OIDs{$oid_name})) {
        print STDERR "Unknown OID: $oid_name\n";
        exit(2);
    }

    $oid = $OIDs{$oid_name};
    return emit_asn1_hdr($class | $tag, length($oid)) . $oid;
}

###############################################################################
#
# Generate a bit string.  This has a leading byte indicating the number of
# trailing bits that should be ignored.
#
###############################################################################
sub emit_asn1_bts($$$)
{
    my ($class, $tag, $content) = @_;
    return emit_asn1_prim($class, $tag, pack("x") . $content);
}

###############################################################################
#
# Generate a construct
#
###############################################################################
sub emit_asn1_cons($$$)
{
    my ($class, $tag, $content) = @_;

    return emit_asn1_hdr($class | 0x20 | $tag, length($content)) . $content;
}

###############################################################################
#
# Generate a name
#
###############################################################################
sub emit_x509_AttributeValueAssertion($$$)
{
    my ($type, $name, $sym) = @_;
    my $output;
    my $data;

    return "" if (!exists($name->{$sym}));

    $data = $name->{$sym};
    $output  = emit_asn1_OID($UNIV, $OBJ_ID, $type);            # attributeType
    $output .= emit_asn1_prim($UNIV, $UTF8String, $data);       # attributeValue
    return emit_asn1_cons($UNIV, $SET,
                          emit_asn1_cons($UNIV, $SEQUENCE, $output));
}

sub emit_x509_RelativeDistinguishedName($)
{
    my ($name) = @_;
    my $output;

    # SET OF AttributeValueAssertion
    $output .= emit_x509_AttributeValueAssertion("countryName", $name, "C");
    $output .= emit_x509_AttributeValueAssertion("organizationName", $name, 
"O");
    $output .= emit_x509_AttributeValueAssertion("organizationUnitName", $name, 
"OU");
    $output .= emit_x509_AttributeValueAssertion("commonName", $name, "CN");
    $output .= emit_x509_AttributeValueAssertion("emailAddress", $name, 
"Email");
    return $output;
}

sub emit_x509_Name($)
{
    my ($name) = @_;
    my $output;

    # SEQUENCE OF RDN
    $output  = emit_x509_RelativeDistinguishedName($name);
    return emit_asn1_cons($UNIV, $SEQUENCE, $output);
}

###############################################################################
#
# Generate some X.509 extensions
#
###############################################################################
sub emit_x509_SubjectKeyIdentifier()
{
    return emit_asn1_prim($UNIV, $OCTET_STRING, $keyid);
}

sub emit_x509_AuthorityKeyIdentifier()
{
    my $content = emit_asn1_prim($CONT, 0, $keyid);
    return emit_asn1_cons($UNIV, $SEQUENCE, $content);
}

sub emit_x509_BasicConstraints()
{
    return pack("CC", 0x30, 0x00);
}

sub emit_x509_KeyUsage()
{
    return emit_asn1_prim($UNIV, $BIT_STRING, pack("CC", 0x07, 0x80));
}

sub emit_x509_Extension($@)
{
    my ($ext, $crit) = @_;
    my $output;
    my $value = "";

    if ($ext eq "authorityKeyIdentifier") {
        $output = emit_asn1_OID($UNIV, $OBJ_ID, $ext);
        $value = emit_x509_AuthorityKeyIdentifier();
    } elsif ($ext eq "subjectKeyIdentifier") {
        $output = emit_asn1_OID($UNIV, $OBJ_ID, $ext);
        $value = emit_x509_SubjectKeyIdentifier();
    } elsif ($ext eq "basicConstraints") {
        $output = emit_asn1_OID($UNIV, $OBJ_ID, $ext);
        $value = emit_x509_BasicConstraints();
    } elsif ($ext eq "keyUsage") {
        $output = emit_asn1_OID($UNIV, $OBJ_ID, $ext);
        $value = emit_x509_KeyUsage();
    } else {
        die;
    }

    $output .= emit_asn1_prim($UNIV, $BOOLEAN, pack("C", 0x01)) # critical
        if ($crit);
    $output .= emit_asn1_hdr($UNIV | $OCTET_STRING, length($value)) . $value;

    return emit_asn1_cons($UNIV, $SEQUENCE, $output);
}

sub emit_x509_Extensions()
{
    my $output = "";

    # Probably do want a sequence of extensions here
    $output .= emit_x509_Extension("basicConstraints", 1);
    $output .= emit_x509_Extension("keyUsage");
    $output .= emit_x509_Extension("authorityKeyIdentifier");
    $output .= emit_x509_Extension("subjectKeyIdentifier");

    return emit_asn1_cons($CONT, 3, emit_asn1_cons($UNIV, $SEQUENCE, $output));
}

###############################################################################
#
# Sign a digest with an RSA key
#
###############################################################################
sub rsa_sign($)
{
    my ($digest) = @_;

    my ($TO_RD, $TO_WR, $FROM_RD, $FROM_WR);
    pipe $TO_RD, $TO_WR;
    pipe $FROM_RD, $FROM_WR;

    my $output;
    my $child = fork();
    if ($child == 0) {
        close $TO_WR;
        close $FROM_RD;
        open(STDIN, ">&", $TO_RD) or die "Can't direct $TO_RD to STDIN: $!";
        open(STDOUT, ">&", $FROM_WR) or die "Can't direct $FROM_WR to STDOUT: 
$!";
        close $TO_RD;
        close $FROM_WR;
        exec("openssl rsautl -sign -inkey $privfilename");
    } elsif (!$child) {
        die;
    } else {
        close $TO_RD;
        close $FROM_WR;
        binmode $TO_WR;
        syswrite $TO_WR, $digest || die;
        close $TO_WR || die;

        my $tmp;
        while (read($FROM_RD, $tmp, 512)) {
            $output .= $tmp;
        }
        close $FROM_RD;
        die "openssl rsautl failed\n"
            if (waitpid($child, 0) != $child);
    }

    return $output;
}

###############################################################################
#
# Generate an X.509 certificate
#
###############################################################################
sub emit_x509_Validity()
{
    my $output;
    $output  = emit_asn1_prim($UNIV, $GeneralizedTime, $valid_from);    # 
notBefore
    $output .= emit_asn1_prim($UNIV, $GeneralizedTime, $valid_to);      # 
notAfter
    return emit_asn1_cons($UNIV, $SEQUENCE, $output);
}

sub emit_x509_AlgorithmIdentifier($)
{
    my ($oid) = @_;
    my $output;

    $output  = emit_asn1_OID($UNIV, $OBJ_ID, $oid);     # algorithm
    $output .= emit_asn1_prim($UNIV, $NULL);            # parameters
    return emit_asn1_cons($UNIV, $SEQUENCE, $output);
}

sub emit_x509_Version()
{
    # Version 3
    my $output = emit_asn1_prim($UNIV, $INTEGER, pack("C", 3 - 1));
    return emit_asn1_cons($CONT, 0, $output);
}

sub emit_x509_SubjectPublicKeyInfo()
{
    my $output;
    $output  = emit_x509_AlgorithmIdentifier("rsaEncryption");  # algorithm
    $output .= emit_asn1_prim($UNIV, $BIT_STRING);      # subjectPublicKey
    return emit_asn1_cons($UNIV, $SEQUENCE, $output);
}

sub emit_x509_TBSCertificate()
{
    my $output;

    $output  = emit_x509_Version;                       # version
    $output .= emit_asn1_prim($UNIV, $INTEGER, $serial); # serialNumber
    $output .= emit_x509_AlgorithmIdentifier("sha1WithRSAEncryption");  # 
signature
    $output .= emit_x509_Name(\%subject_name);          # issuer
    $output .= emit_x509_Validity();                    # validity
    $output .= emit_x509_Name(\%subject_name);          # subject
    #$output .= emit_x509_SubjectPublicKeyInfo();       # subjectPublicKeyInfo
    $output .= $pubkey;
    $output .= emit_x509_Extensions();                  # extensions

    return emit_asn1_cons($UNIV, $SEQUENCE, $output);
}

sub emit_x509_Certificate()
{
    my $output;

    $output  = emit_x509_TBSCertificate();              # tbsCertificate

    # We digest the TBS and sign it.
    my $tbs_digest =
        pack("C*", 0x30, 0x21, 0x30, 0x09, 0x06, 0x05,
             0x2B, 0x0E, 0x03, 0x02, 0x1A,
             0x05, 0x00, 0x04, 0x14) .
             sha1sum($output);

    my $sig = rsa_sign($tbs_digest);

    $output .= emit_x509_AlgorithmIdentifier("sha1WithRSAEncryption");  # 
signatureAlgorithm
    $output .= emit_asn1_bts($UNIV, $BIT_STRING, $sig); # signature

    return emit_asn1_cons($UNIV, $SEQUENCE, $output);
}

print emit_x509_Certificate();
--
To unsubscribe from this list: send the line "unsubscribe linux-crypto" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to