#!/usr/local/bin/perl5
#
# der_chop ... this is one total hack that Eric is really not proud of
#              so don't look at it and don't ask for support
#
# The "documentation" for this (i.e. all the comments) are my fault --tjh
#
# This program takes the "raw" output of derparse/asn1parse and 
# converts it into tokens and then runs regular expression matches
# to try to figure out what to grab to get the things that are needed
# and it is possible that this will do the wrong thing as it is a *hack*
#
# SSLeay 0.5.2+ should have direct read support for x509 (via -inform NET)
# [I know ... promises promises :-)]
#
# To convert a Netscape Certificate:
#    der_chop < ServerCert.der > cert.pem
# To convert a Netscape Key (and encrypt it again to protect it)
#    rsa -inform NET -in ServerKey.der -des > key.pem
#
# 23-Apr-96 eay    Added the extra ASN.1 string types, I still think this
#		   is an evil hack.  If nothing else the parsing should
#		   be relative, not absolute.
# 19-Apr-96 tjh    hacked (with eay) into 0.5.x format
#
# Tim Hudson
# tjh@cryptsoft.com
#


require 'getopts.pl';

$debug=0;

# this was the 0.4.x way of doing things ...
$cmd="derparse";
$x509_cmd="x509";
$crl_cmd="crl";
$rc4_cmd="rc4";
$md2_cmd="md2";
$md4_cmd="md4";
$rsa_cmd="rsa -des -inform der ";

# this was the 0.5.x way of doing things ...
$cmd="openssl asn1parse";
$x509_cmd="openssl x509";
$crl_cmd="openssl crl";
$rc4_cmd="openssl rc4";
$md2_cmd="openssl md2";
$md4_cmd="openssl md4";
$rsa_cmd="openssl rsa -des -inform der ";

&Getopts('vd:') || die "usage:$0 [-v] [-d num] file";
$depth=($opt_d =~ /^\d+$/)?$opt_d:0;

&init_der();

if ($#ARGV != -1)
	{
	foreach $file (@ARGV)
		{
		print STDERR "doing $file\n";
		&dofile($file);
		}
	}
else
	{
	$file="/tmp/a$$.DER";
	open(OUT,">$file") || die "unable to open $file:$!\n";
	for (;;)
		{
		$i=sysread(STDIN,$b,1024*10);
		last if ($i <= 0);
		$i=syswrite(OUT,$b,$i);
		}
	&dofile($file);
	unlink($file);
	}
	
sub dofile
	{
	local($file)=@_;
	local(@p);

	$b=&load_file($file);
	@p=&load_file_parse($file);

	foreach $_ (@p)
		{
		($off,$d,$hl,$len)=&parse_line($_);
		$d-=$depth;
		next if ($d != 0);
		next if ($len == 0);

		$o=substr($b,$off,$len+$hl);
		($str,@data)=&der_str($o);
		print "$str\n" if ($opt_v);
		if ($str =~ /^$crl/)
			{
			open(OUT,"|$crl_cmd -inform d -hash -issuer") ||
				die "unable to run $crl_cmd:$!\n";
			print OUT $o;
			close(OUT);
			}
		elsif ($str =~ /^$x509/)
			{
			open(OUT,"|$x509_cmd -inform d -hash -subject -issuer")
				|| die "unable to run $x509_cmd:$!\n";
			print OUT $o;
			close(OUT);
			}
		elsif ($str =~ /^$rsa/)
			{
			($type)=($data[3] =~ /OBJECT_IDENTIFIER :(.*)\s*$/);
			next unless ($type eq "rsaEncryption");
			($off,$d,$hl,$len)=&parse_line($data[5]);
			$os=substr($o,$off+$hl,$len);
			open(OUT,"|$rsa_cmd")
				|| die "unable to run $rsa_cmd:$!\n";
			print OUT $os;
			close(OUT);
			}
		elsif ($str =~ /^0G-1D-1G/)
			{
			($off,$d,$hl,$len)=&parse_line($data[1]);
			$os=substr($o,$off+$hl,$len);
			print STDERR "<$os>\n" if $opt_v;
			&do_certificate($o,@data)
				if (($os eq "certificate") &&
				    ($str =! /^0G-1D-1G-2G-3F-3E-2D/));
			&do_private_key($o,@data)
				if (($os eq "private-key") &&
				    ($str =! /^0G-1D-1G-2G-3F-3E-2D/));
			}
		}
	}

sub der_str
	{
	local($str)=@_;
	local(*OUT,*IN,@a,$t,$d,$ret);
	local($file)="/tmp/b$$.DER";
	local(@ret);

	open(OUT,">$file");
	print OUT $str;
	close(OUT);
	open(IN,"$cmd -inform 'd' -in $file |") ||
		die "unable to run $cmd:$!\n";
	$ret="";
	while (<IN>)
		{
		chop;
		push(@ret,$_);

		print STDERR "$_\n" if ($debug);

		@a=split(/\s*:\s*/);
		($d)=($a[1] =~ /d=\s*(\d+)/);
		$a[2] =~ s/\s+$//;
		$t=$DER_s2i{$a[2]};
		$ret.="$d$t-";
		}
	close(IN);
	unlink($file);
	chop $ret;
	$ret =~ s/(-3H(-4G-5F-5[IJKMQRS])+)+/-NAME/g;
	$ret =~ s/(-3G-4B-4L)+/-RCERT/g;
	return($ret,@ret);
	}

sub init_der
	{
	$crl= "0G-1G-2G-3F-3E-2G-NAME-2L-2L-2G-RCERT-1G-2F-2E-1C";
	$x509="0G-1G-2B-2G-3F-3E-2G-NAME-2G-3L-3L-2G-NAME-2G-3G-4F-4E-3C-1G-2F-2E-1C";
	$rsa= "0G-1B-1G-2F-2E-1D";

	%DER_i2s=(
		# SSLeay 0.4.x has this list
		"A","EOC",
		"B","INTEGER",
		"C","BIT STRING",
		"D","OCTET STRING",
		"E","NULL",
		"F","OBJECT",
		"G","SEQUENCE",
		"H","SET",
		"I","PRINTABLESTRING",
		"J","T61STRING",
		"K","IA5STRING",
		"L","UTCTIME",
		"M","NUMERICSTRING",
		"N","VIDEOTEXSTRING",
		"O","GENERALIZEDTIME",
		"P","GRAPHICSTRING",
		"Q","ISO64STRING",
		"R","GENERALSTRING",
		"S","UNIVERSALSTRING",

		# SSLeay 0.5.x changed some things ... and I'm
		# leaving in the old stuff but adding in these
		# to handle the new as well --tjh
		# - Well I've just taken them out and added the extra new
		# ones :-) - eay
		);

	foreach (keys %DER_i2s)
		{ $DER_s2i{$DER_i2s{$_}}=$_; }
	}

sub parse_line
	{
	local($_)=@_;

	return(/\s*(\d+):d=\s*(\d+)\s+hl=\s*(\d+)\s+l=\s*(\d+|inf)\s/);
	}

#  0:d=0 hl=4 l=377 cons: univ: SEQUENCE          
#  4:d=1 hl=2 l= 11 prim: univ: OCTET_STRING      
# 17:d=1 hl=4 l=360 cons: univ: SEQUENCE          
# 21:d=2 hl=2 l= 12 cons: univ: SEQUENCE          
# 23:d=3 hl=2 l=  8 prim: univ: OBJECT_IDENTIFIER :rc4
# 33:d=3 hl=2 l=  0 prim: univ: NULL              
# 35:d=2 hl=4 l=342 prim: univ: OCTET_STRING
sub do_private_key
	{
	local($data,@struct)=@_;
	local($file)="/tmp/b$$.DER";
	local($off,$d,$hl,$len,$_,$b,@p,$s);

	($type)=($struct[4] =~ /OBJECT_IDENTIFIER :(.*)\s*$/);
	if ($type eq "rc4")
		{
		($off,$d,$hl,$len)=&parse_line($struct[6]);
		open(OUT,"|$rc4_cmd >$file") ||
			die "unable to run $rc4_cmd:$!\n";
		print OUT substr($data,$off+$hl,$len);
		close(OUT);

		$b=&load_file($file);
		unlink($file);

		($s,@p)=&der_str($b);
		die "unknown rsa key type\n$s\n"
			if ($s ne '0G-1B-1G-2F-2E-1D');
		local($off,$d,$hl,$len)=&parse_line($p[5]);
		$b=substr($b,$off+$hl,$len);
		($s,@p)=&der_str($b);
		open(OUT,"|$rsa_cmd") || die "unable to run $rsa_cmd:$!\n";
		print OUT $b;
		close(OUT);
		}
	else
		{
		print "'$type' is unknown\n";
		exit(1);
		}
	}

sub do_certificate
	{
	local($data,@struct)=@_;
	local($file)="/tmp/b$$.DER";
	local($off,$d,$hl,$len,$_,$b,@p,$s);

	($off,$d,$hl,$len)=&parse_line($struct[2]);
	$b=substr($data,$off,$len+$hl);

	open(OUT,"|$x509_cmd -inform d") || die "unable to run $x509_cmd:$!\n";
	print OUT $b;
	close(OUT);
	}

sub load_file
	{
	local($file)=@_;
	local(*IN,$r,$b,$i);

	$r="";
	open(IN,"<$file") || die "unable to open $file:$!\n";
	for (;;)
		{
		$i=sysread(IN,$b,10240);
		last if ($i <= 0);
		$r.=$b;
		}
	close(IN);
	return($r);
	}

sub load_file_parse
	{
	local($file)=@_;
	local(*IN,$r,@ret,$_,$i,$n,$b);

	open(IN,"$cmd -inform d -in $file|")
		|| die "unable to run der_parse\n";
	while (<IN>)
		{
		chop;
		push(@ret,$_);
		}
	return($r,@ret);
	}

