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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html