diff -r 675a964f4eb5 -r 35751d3474b7 cryptoservices/certificateandkeymgmt/tder/dergen.pl --- a/cryptoservices/certificateandkeymgmt/tder/dergen.pl Tue Jul 21 01:04:32 2009 +0100 +++ b/cryptoservices/certificateandkeymgmt/tder/dergen.pl Thu Sep 10 14:01:51 2009 +0300 @@ -1,1560 +1,1560 @@ -# -# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). -# All rights reserved. -# This component and the accompanying materials are made available -# under the terms of the License "Eclipse Public License v1.0" -# which accompanies this distribution, and is available -# at the URL "http://www.eclipse.org/legal/epl-v10.html". -# -# Initial Contributors: -# Nokia Corporation - initial contribution. -# -# Contributors: -# -# Description: -# -#!/bin/perl -w - -# Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies). -# All rights reserved. -# This component and the accompanying materials are made available -# under the terms of the License "Symbian Foundation License v1.0" -# which accompanies this distribution, and is available -# at the URL "http://www.symbianfoundation.org/legal/sfl-v10.html". -# -# Initial Contributors: -# Nokia Corporation - initial contribution. -# -# Contributors: -# -# Description: -# Basic ASN.1 encoding library -# Some parts of this program requrie OpenSSL which may be freely downloaded -# from www.openssl.org -# -# - -use strict; -use Digest::HMAC_MD5; -use Digest::HMAC_SHA1; -use Getopt::Long; - -# 0 = off -# 1 = log parsing -# 2 = log parsing + encoding -# 3 = really verbose stuff -my $DEBUG=0; - -# Turn on validation checks that attempt to only generate -# valid DER encodings. -my $VALIDATE=0; - -my $OID_PKCS = "1.2.840.113549.1"; -my $OID_PKCS7 ="${OID_PKCS}.7"; -my $OID_PKCS9 = "${OID_PKCS}.9"; -my $OID_PKCS9_CERTTYPES = "${OID_PKCS9}.22"; -my $OID_PKCS12 = "${OID_PKCS}.12"; -my $OID_PKCS12_BAGTYPES = "${OID_PKCS12}.10.1"; -my $OID_PKCS12_PBEIDS = "${OID_PKCS12}.1"; - -my %OIDS = - ( - "MD5" => "1.2.840.113549.2.5", - "SHA1" => "1.3.14.3.2.26", - "X509CRL" => "1.3.6.1.4.1.3627.4", - - "PKCS7_DATA" => "${OID_PKCS7}.1", - "PKCS7_SIGNEDDATA" => "${OID_PKCS7}.2", - "PKCS7_ENVELOPEDDATA" => "${OID_PKCS7}.3", - "PKCS7_SIGNEDANDENVELOPEDDATA" => "${OID_PKCS7}.4", - "PKCS7_DIGESTEDDATA" => "${OID_PKCS7}.5", - "PKCS7_ENCRYPTEDDATA" => "${OID_PKCS7}.6", - - "PKCS9_CERTTYPES_PKCS12_X509" => "${OID_PKCS9_CERTTYPES}.1", - "PKCS9_FRIENDLY_NAME" => "${OID_PKCS9}.20", - "PKCS9_LOCAL_KEYID" => "${OID_PKCS9}.21", - - "PKCS12_BAGTYPES_KEYBAG" => "${OID_PKCS12_BAGTYPES}.1", - "PKCS12_BAGTYPES_PKCS8SHROUDEDKEYBAG" => "${OID_PKCS12_BAGTYPES}.2", - "PKCS12_BAGTYPES_CERTBAG" => "${OID_PKCS12_BAGTYPES}.3", - "PKCS12_BAGTYPES_CRLBAG" => "${OID_PKCS12_BAGTYPES}.4", - "PKCS12_BAGTYPES_SECRETBAG" => "${OID_PKCS12_BAGTYPES}.5", - "PKCS12_BAGTYPES_SAFECONTENTSBAG" => "${OID_PKCS12_BAGTYPES}.6", - - "PKCS12_PBEIDS_SHAAND128BITRC4" => "${OID_PKCS12_PBEIDS}.1", - "PKCS12_PBEIDS_SHAAND40BITRC4" => "${OID_PKCS12_PBEIDS}.2", - "PKCS12_PBEIDS_SHAAND3KEYTRIPLEDESCBC" => "${OID_PKCS12_PBEIDS}.3", - "PKCS12_PBEIDS_SHAAND2KEYTRIPLEDESCBC" => "${OID_PKCS12_PBEIDS}.4", - "PKCS12_PBEIDS_SHAAND128BITRC2CBC" => "${OID_PKCS12_PBEIDS}.5", - "PKCS12_PBEIDS_SHAAND40BITRC2CBC" => "${OID_PKCS12_PBEIDS}.6", - - # Symbian dev cert extensions - "SYMBIAN_DEVICE_ID_LIST" => "1.2.826.0.1.1796587.1.1.1.1", - "SYMBIAN_SID_LIST" => "1.2.826.0.1.1796587.1.1.1.4", - "SYMBIAN_VID_LIST" => "1.2.826.0.1.1796587.1.1.1.5", - "SYMBIAN_CAPABILITIES" => "1.2.826.0.1.1796587.1.1.1.6" - -); - -my $DER_BOOLEAN_TAG="01"; -my $DER_INTEGER_TAG="02"; -my $DER_BITSTRING_TAG="03"; -my $DER_OCTETSTRING_TAG="04"; -my $DER_NULL_TAG="05"; -my $DER_OID_TAG="06"; -my $DER_ENUMERATED_TAG="0A"; -my $DER_SEQUENCE_TAG="10"; -my $DER_SET_TAG="11"; -my $DER_UTF8STRING_TAG="0C"; -my $DER_PRINTABLESTRING_TAG="13"; -my $DER_IA5STRING_TAG="16"; -my $DER_UTCTIME_TAG="17"; -my $DER_BMPSTRING_TAG="1E"; - -my $UNIVERSAL_CLASS="UNIVERSAL"; -my $APPLICATION_CLASS="APPLICATION"; -my $CONTEXT_SPECIFIC_CLASS="CONTEXT-SPECIFIC"; -my $PRIVATE_CLASS="PRIVATE"; - -my %PARSE = - ( - "BOOL" => \&parseBoolean, - "BOOLEAN" => \&parseBoolean, - "BIGINTEGER" => \&parseBigInteger, - "BITSTRING" => \&parseBitString, - "BITSTRING_WRAPPER" => \&parseBitStringWrapper, - "BMPSTRING" => \&parseBmpString, - "BMPSTRING_FILE" => \&parseBmpStringFile, - "ENUMERATED" => \&parseEnumerated, - "IA5STRING" => \&parseIA5String, - "IA5STRING_FILE" => \&parseIA5StringFile, - "INCLUDE" => \&parseInclude, - "INCLUDE_BINARY_FILE" => \&parseIncludeBinaryFile, - "INTEGER" => \&parseInteger, - "INT" => \&parseInteger, - "IMPLICIT" => \&parseImplicit, - "ENCRYPT" => \&parseEncrypt, - "EXPLICIT" => \&parseExplicit, - "HASH" => \&parseHash, - "HMAC" => \&parseHmac, - "NULL" => \&parseNull, - "OCTETSTRING" => \&parseOctetString, - "OUTPUT_BINARY_FILE" => \&parseOutputFile, - "OID" => \&parseOid, - "PRINTABLESTRING" => \&parsePrintableString, - "PRINTABLESTRING_FILE" => \&parsePrintableStringFile, - "RAW" => \&parseRaw, - "SEQUENCE" => \&parseSequence, - "SEQ" => \&parseSequence, - "SET" => \&parseSet, - "SHELL" => \&parseShell, - "SIGN" => \&parseSign, - "UTCTIME" => \&parseUtcTime, - "UTF8STRING" => \&parseUtf8String, - "UTF8STRING_FILE" => \&parseUtf8StringFile, - ); - -my $TABS = ""; - -&main; -exit(0); - -sub main() { - my $hex; - my $out; - my $in; - my @lines; - - GetOptions('debug=i' => \$DEBUG, - 'hex' => \$hex, - 'in=s' => \$in, - 'out=s' => \$out); - - if (! defined $in) { - $in = $ARGV[0]; - } - - if (! defined $out) { - $out = $ARGV[1]; - } - - if (defined $in) { - @lines = readFile($in); - } - else { - die "No input file specified.\n"; - } - - if (defined $out) { - open OUT, ">$out" || die "Cannot open output file $out"; - } - else { - *OUT = *STDOUT; - } - - my $oc = 0; - my $asnHex = parseScript(\@lines, \$oc); - $asnHex = tidyHex($asnHex); - - if ((!defined $hex) && (defined $out)) { - binmode(OUT); - print OUT toBin($asnHex); - } - elsif (defined $out) { - print OUT $asnHex; - } - else { - print $asnHex; - } - - close OUT; -} - -sub tidyHex($) { - my ($input) = @_; - $input =~ s/:+/:/g; - $input =~ s/(^:|:$)//g; - return uc($input); -} - -sub toBin($) { - my ($asnHex) = @_; - - $asnHex =~ s/[\s:]//g; - $asnHex = uc($asnHex); - - my $len = length($asnHex); - if ($len % 2 != 0) { - die "toBin: hex string contains an odd number ($len) of octets.\n$asnHex\n"; - } - - my $binary; - $binary .= pack("H${len}", $asnHex); -# for (my $i = 0; $i < length($asnHex); $i+=2) { -# $binary .= pack('C', substr($asnHex, $i, 2)); -# } - return $binary; -} - -sub parseScript($$;$) { - my ($lines, $oc, $params) = @_; - my $derHex = ""; - - nest(); - substVars($lines, $params); - - while (my $line = shift @$lines) { - chomp($line); - - # Remove leading spaces - $line =~ s/^\s*//g; - - # skip comments - next if ($line =~ /^\/\//); - - if ($DEBUG == 3) { - print "${TABS}:PARSE parseScript: $line\n"; - } - - my $argString; - my $cmd; - if ($line =~ /(\w+)\s*\{/ ) { - # parse block commands e.g. large integer - $cmd = uc($1); - - $line =~ s/.*\{//g; - while (defined $line && !($line =~ /(^|[^\\]+)\}/) ) { - $argString .= $line; - $line = shift(@$lines); - } - if (defined $line) { - # append everything up to the closing curly bracket - $line =~ s/(^|[^\\])\}.*/$1/g; - $argString .= $line; - } - } - elsif ($line =~ /(\w+)\s*=*(.*)/) { - # parse commands of the form key = value - $cmd = uc($1); - $argString = defined $2 ? $2 : ""; - } - - if (defined $cmd) { - if ($cmd =~ /^END/) { - leaveNest(); - if ($DEBUG) { - print "${TABS}:PARSE END\n"; - } - return $derHex; - } - elsif (! defined $PARSE{$cmd}) { - die "parseScript: Unknown command: $cmd\n"; - } - else { - if ($DEBUG) { - print "${TABS}:PARSE CMD=$cmd"; - if ($argString ne "") {print " ARG: $argString";} - print "\n"; - } - - # Substitue variables in argString - $derHex .= ":" . &{$PARSE{$cmd}}($argString, $oc, $lines); - } - } - - } - leaveNest(); - return $derHex; -} - -sub substVars($$) { - my ($lines, $params) = @_; - - if (! defined $params) { - @$params = (); - } - - for (my $i = 0; $i < scalar(@$lines); $i++) { - my $line = @$lines[$i]; - my $paramIndex = 1; - - # For each parameter search for the a use of $N where - # N is the index of the parameter and replace $N with the - # value of the parameter - foreach (@$params) { - $line =~ s/\$${paramIndex}(\D|$)/$_$1/g; - ++$paramIndex; - } - - # Remove any unused parameters - $line =~ s/\$\d+//g; - @$lines[$i] = $line; - } -} - -sub readFile($) { - my ($fileName) = @_; - my $inFile; - - if ($DEBUG) { - print "readFile, $fileName\n"; - } - - open($inFile, $fileName) || die "readFile: cannot open $fileName\n"; - my @lines = <$inFile>; - close $inFile; - - return @lines; -} - -sub parseBitString($$;$) { - my ($argString, $oc, $lines) = @_; - return encodeBitString($argString, $oc); -} - -sub parseBitStringWrapper($$;$) { - my ($argString, $oc, $lines) = @_; - - my $contents_oc = 0; - my $contents = parseScript($lines, \$contents_oc); - - my $binary = toBin($contents); - my $bitCount = $contents_oc * 8; - my $bitStr = unpack("B${bitCount}", $binary); - - # remove trailing zeros - breaks signatures so disable for the moment - # $bitStr =~ s/0*$//g; - - return encodeBitString($bitStr, $oc); -} - -sub parseBmpString($$;$) { - my ($argString, $oc, $lines) = @_; - - my $bmpString_oc = 0; - my $bmpString = asciiToBmpString($argString, \$bmpString_oc); - return encodeBmpString($bmpString, $bmpString_oc, $oc); -} - -sub parseBmpStringFile($$;$) { - my ($binFName, $oc, $lines) = @_; - $binFName =~ s/\s*//g; - - my $bmpString_oc = 0; - my $bmpString = encodeBinaryFile($binFName, \$bmpString_oc); - - return encodeBmpString($bmpString, $bmpString_oc, $oc); -} - -sub parseBoolean($$;$) { - my ($argString, $oc, $lines) = @_; - - $argString =~ s/\s//g; - $argString = lc($argString); - - my $bool; - if ($argString eq "t" || $argString eq "true" || $argString eq "1") { - $bool = 1; - } - elsif ($argString eq "f" || $argString eq "false" || $argString eq "0") { - $bool = 0; - } - else { - die "parseBoolean: Invalid boolean value \'$argString\'"; - } - - return encodeBoolean($bool, $oc); -} - -sub parseHash($$;$) { - my ($argString, $oc, $lines) = @_; - my ($algorithm) = getArgs($argString); - - if (! defined $algorithm) { - die "parseHash: missing algortithm"; - } - - my $hashIn_oc = 0; - my $hashIn = parseScript($lines, \$hashIn_oc); - - my $hashInFName = '_hashin.tmp'; - my $hashOutFName = '_hashout.tmp'; - - # Create binary hash file - my $hashInFh; - open($hashInFh, ">$hashInFName") or die "Cannot create $hashInFName"; - binmode($hashInFh); - print $hashInFh toBin($hashIn); - close $hashInFh; - - my @command = ("cmd", - "/C \"openssl dgst -${algorithm} -binary $hashInFName > $hashOutFName\""); - if ($DEBUG == 1) { - print "${TABS}:parseHash:" . join(" ", @command) . "\n"; - } - - if ((my $err = system(@command)) != 0) { - die "parseHash: " . join(" ", @command) . "\nreturned error $err"; - } - - my $derHex = parseIncludeBinaryFile($hashOutFName, $oc); - - if (! $DEBUG) { - unlink($hashInFName); - unlink($hashOutFName); - } - return $derHex; -} - -sub parseHmac($$;$) { - my ($argString, $oc, $lines) = @_; - my ($algorithm, $key) = getArgs($argString); - - if (! defined $algorithm) { - die "parseHmac: missing algortithm"; - } - $algorithm = uc($algorithm); - if (! $algorithm =~ /MD5|SHA1/) { - die "parseHmac: invalid algorithm $algorithm"; - } - - if (! defined $key) { - die "parseHmac: missing key"; - } - - my $hmacIn_oc = 0; - my $hmacIn = toBin(parseScript($lines, \$hmacIn_oc)); - my $hmac; - my $binKey = toBin($key); - - if ($algorithm eq "SHA1") { - - $hmac = Digest::HMAC_SHA1->new($binKey); - } - else { - $hmac = Digest::HMAC_MD5->new($binKey); - } - $hmac->add($hmacIn); - my $digest = $hmac->digest; - $$oc += length($digest); - - return toHex($digest); -} - -sub parseIA5String($$;$) { - my ($argString, $oc, $lines) = @_; - - my $ia5String_oc = 0; - my $ia5String = asciiToIA5String($argString, \$ia5String_oc); - return encodeIA5String($ia5String, $ia5String_oc, $oc); -} - - -sub parseIA5StringFile($$;$) { - my ($binFName, $oc, $lines) = @_; - $binFName =~ s/\s*//g; - - my $ia5String_oc = 0; - my $ia5String = encodeBinaryFile($binFName, \$ia5String_oc); - - return encodeIA5String($ia5String, $ia5String_oc, $oc); -} - -sub parseIncludeBinaryFile($$;$) { - my ($binFName, $oc, $lines) = @_; - $binFName =~ s/\s*//g; - - return encodeBinaryFile($binFName, $oc); -} - -sub parseInclude($$$) { - my ($argString, $oc, $lines) = @_; - my @args = getArgs($argString); - - my $fileName = shift(@args); - if (! (defined $fileName && $fileName ne "")) { - die "parseInclude: Filename not specified\n"; - } - - my $derHex = ""; - my @lines = readFile($fileName); - $derHex = parseScript(\@lines, $oc, \@args); - return $derHex; -} - -sub parseInteger($$;$) { - my ($argString, $oc, $lines) = @_; - - $argString =~ s/\s//g; - return encodeInteger($argString, $oc); -} - -sub parseBigInteger($$;$) { - my ($argString, $oc, $lines) = @_; - - $argString =~ s/\s//g; - return encodeBigInteger($argString, $oc); -} - -sub parseEncrypt($$;$) { - my ($argString, $oc, $lines) = @_; - my ($cipher, $key, $iv) = getArgs($argString); - - if (! defined $cipher) { - die "parseEncrypt: missing cipher\n"; - } - - if (! defined $key) { - die "parseEncrypt: missing key\n"; - } - - my $plainText_oc = 0; - my $plainText = parseScript($lines, \$plainText_oc); - - my $plainTextFName = '_plaintext.tmp'; - my $cipherTextFName = '_ciphertext.tmp'; - - # Create binary plaintext file - my $plainTextFh; - open($plainTextFh, ">$plainTextFName") or die "Cannot create $plainTextFName"; - binmode($plainTextFh); - print $plainTextFh toBin($plainText); - close $plainTextFh; - - my @command = ('openssl', - 'enc', - "-${cipher}", - '-e', - '-K', $key, - '-in', $plainTextFName, - '-out', $cipherTextFName); - - if (defined $iv) { - push @command, '-iv', $iv; - } - - if ($DEBUG == 1) { - print "${TABS}:parseEncrypt:" . join(" ", @command) . "\n"; - } - - if ((my $err = system(@command)) != 0) { - die "parseEncrypt: " . join(" ", @command) . "\nreturned error $err"; - } - - my $derHex = parseIncludeBinaryFile($cipherTextFName, $oc); - - if (! $DEBUG) { - unlink($plainTextFName); - unlink($cipherTextFName); - } - return $derHex; -} - -sub parseEnumerated($$;$) { - my ($argString, $oc, $lines) = @_; - - $argString =~ s/\s//g; - return encodeEnumerated($argString, $oc); -} - -sub parseExplicit($$;$) { - my ($argString, $oc, $lines) = @_; - my ($tagNumber, $class) = getArgs($argString); - - if (! defined $tagNumber || $tagNumber =~ /^\s*$/) { - $tagNumber = "0"; - } - elsif (!($tagNumber =~ /^[0-9A-Fa-f]+$/)) { - die "parseExplicit: invalid tag number: \'$tagNumber\'"; - } - $tagNumber = hex($tagNumber); - - if (!defined $class || $class =~ /^\s*$/) { - $class = $CONTEXT_SPECIFIC_CLASS; - } - else { - $class =~ s/\s*//g; - $class = uc($class); - } - - if (! isValidClass($class)) { - die "parseExplicit: invalid class \'$class\'"; - } - - my $nested_oc = 0; - my $nested = parseScript($lines, \$nested_oc); - - return encodeExplicit($class, $tagNumber, $nested, $nested_oc, $oc); -} - -sub parseImplicit($$;$) { - my ($argString, $oc, $lines) = @_; - my ($tagNumber, $class) = getArgs($argString); - - if (! defined $tagNumber || $tagNumber =~ /^\s*$/) { - $tagNumber = "0"; - } - elsif (!($tagNumber =~ /^[0-9A-Fa-f]+$/)) { - die "parseImplicit: invalid tag number: \'$tagNumber\'"; - } - $tagNumber = hex($tagNumber); - - if (!defined $class || $class =~ /^\s*$/) { - $class = $CONTEXT_SPECIFIC_CLASS; - } - else { - $class =~ s/\s*//g; - $class = uc($class); - } - - if (! isValidClass($class)) { - die "parseImplicit: invalid class \'$class\'"; - } - - my $nested_oc = 0; - my $nested = tidyHex(parseScript($lines, \$nested_oc)); - - # De-construct the nested data to allow the underlying type tag to be - # changed. The output of parseScript had better be valid DER or this - # will go horribly wrong ! - my $uClass = ""; - my $uConstructed = 0; - my $uTag = 0; - my $uLength = 0; - my $uValue = ""; - getTlv($nested, \$uClass, \$uConstructed, \$uTag, \$uLength, \$uValue); - - if ($DEBUG == 2) { - print "${TABS}parseImplicit: underlyingType \'$uTag\'\n"; - } - - # This only works for low tag numbers because we are assuming that the type - # tag is a single octet - return encodeImplicit($class, $uConstructed, $tagNumber, $uValue, $uLength, $oc); -} - -sub parseNull($$;$) { - my ($argString, $oc, $lines) = @_; - - return encodeNull($oc); -} - -sub parseOctetString($$;$) { - my ($argString, $oc, $lines) = @_; - - my $octetString_oc = 0; - my $octetString = parseScript($lines, \$octetString_oc); - - return encodeOctetString($octetString, $octetString_oc, $oc); -} - -sub parseOid($$;$) { - my ($argString, $oc, $lines) = @_; - $argString =~ s/\s//g; - $argString = uc($argString); - - if (! defined $argString) { - die "parseOid: Missing OID value."; - } - - foreach (keys %OIDS) { - if ($argString =~ /$_/) { - $argString =~ s/\Q$_\E/$OIDS{$_}/g; - } - } - return encodeOid($argString, $oc); -} - -sub parseOutputFile($$;$) { - my ($argString, $oc, $lines) = @_; - my ($outputFile,$echo) = split(/,/, $argString); - - if (! defined $outputFile) { - die "parseOutputFile: Missing file-name.\n"; - } - - my $content_oc = 0; - my $content = parseScript($lines, \$content_oc); - - my $outFh; - if (! open($outFh, ">${outputFile}")) { - die "parseOutputFile: Cannot create $outputFile\n"; - } - binmode($outFh); - print $outFh toBin($content); - close $outFh; - - # If echo is specified then include then contents of the output - # file at this point in the stream. - if (defined $echo && $echo =~ /(1|t|true)/i) { - $$oc += $content_oc; - return $content; - } - else { - return ""; - } -} - -sub parsePrintableString($$;$) { - my ($argString, $oc, $lines) = @_; - - my $printableString_oc = 0; - my $printableString = asciiToPrintableString($argString, \$printableString_oc); - return encodePrintableString($printableString, $printableString_oc, $oc); -} - -sub parsePrintableStringFile($$;$) { - my ($binFName, $oc, $lines) = @_; - $binFName =~ s/\s*//g; - - my $printableString_oc = 0; - my $printableString = encodeBinaryFile($binFName, \$printableString_oc); - - return encodePrintableString($printableString, $printableString_oc, $oc); -} - -sub parseRaw($$;$) { - my ($argString, $oc, $lines) = @_; - $argString =~ s/\s//g; - $argString = uc($argString); - - my $asnHex = ""; - if (! ($argString =~ /(([A-Fa-f\d][A-Fa-f\d])[ :]*)+/)) { - die "parseRaw: Invalid hex string: $argString\n"; - } - my $binary = toBin($argString); - $$oc += length($binary); - return tidyHex(toHex($binary)); -} - -sub parseSequence($$;$) { - my ($argString, $oc, $lines) = @_; - - my $sequence_oc = 0; - my $sequence = parseScript($lines, \$sequence_oc); - - return encodeSequence($sequence, $sequence_oc, $oc); -} - -sub parseSet($$;$) { - my ($argString, $oc, $lines) = @_; - - my $set_oc = 0; - my $set = parseScript($lines, \$set_oc); - - return encodeSet($set, $set_oc, $oc); -} - -# Create a PKCS#7 signed data object for a chunk of data using -# OpenSSL's SMIME command -sub parseSign($$;$) { - my ($argString, $oc, $lines) = @_; - my ($signerCert, $signerKey) = getArgs($argString); - - if (! defined $signerCert) { - die "parseSign: missing signing certificate"; - } - elsif (! -f $signerCert) { - die "parseSign: signing certificate \'$signerCert\' does not exist."; - } - - if (! defined $signerKey) { - die "parseSign: missing signing certificate"; - } - elsif (! -f $signerKey) { - die "parseSign: signing key \'$signerKey\' does not exist."; - } - - my $unsigned_oc = 0; - my $unsigned = parseScript($lines, \$unsigned_oc); - - my $unsignedFName = '_unsigned.tmp'; - my $signedFName = '_signed.tmp'; - - # Create binary unsigned data file - my $unsignedFh; - open($unsignedFh, ">$unsignedFName") or die "Cannot create $unsignedFName"; - binmode($unsignedFh); - print $unsignedFh toBin($unsigned); - close $unsignedFh; - - my @command = ('openssl', - 'smime', - '-pk7out', - '-nodetach', - '-outform', - 'der', - '-sign', - '-signer', - $signerCert, - '-inkey', - $signerKey, - '-in', $unsignedFName, - '-out', $signedFName); - - if ($DEBUG == 1) { - print "${TABS}:parseSign:" . join(" ", @command) . "\n"; - } - - if ((my $err = system(@command)) != 0) { - die "parseSign: " . join(" ", @command) . "\nreturned error $err"; - } - - my $derHex = parseIncludeBinaryFile($signedFName, $oc); - - if (! $DEBUG) { - unlink($unsignedFName); - unlink($signedFName); - } - return $derHex; -} - -sub parseShell($$;$) { - my ($argString, $oc, $lines) = @_; - my @command = getArgs($argString); - - if (scalar(@command) < 1) { - die "parseShell: no arguments"; - } - - if ($DEBUG == 1) { - print "${TABS}:parseShell:" . join(" ", @command) . "\n"; - } - - if ((my $err = system(@command)) != 0) { - die "parseShell: " . join(" ", @command) . "\nreturned error $err"; - } - return ""; -} - -sub parseUtcTime($$;$) { - my ($time, $oc, $lines) = @_; - $time =~ s/\s//g; - - my $time_oc = length($time); - return encodeUtcTime(toHex($time), $time_oc, $oc); -} - -sub parseUtf8String($$;$) { - my ($argString, $oc, $lines) = @_; - - my $utf8String_oc = 0; - my $utf8String = asciiToUtf8String($argString, \$utf8String_oc); - return encodeUtf8String($utf8String, $utf8String_oc, $oc); -} - -sub parseUtf8StringFile($$;$) { - my ($binFName, $oc, $lines) = @_; - $binFName =~ s/\s*//g; - - my $utf8String_oc = 0; - my $utf8String = encodeBinaryFile($binFName, \$utf8String_oc); - - return encodeUtf8String($utf8String, $utf8String_oc, $oc); -} - -sub toHex($) { - my ($bin) = @_; - my $hex = unpack("H" . (length($bin) * 2), $bin); - $hex =~ s/(..)/$1:/g; - return $hex; -} - -sub encodeBinaryFile($$) { - my ($binFName, $oc) = @_; - - my $binFH; - open($binFH, "$binFName") || die "encodeBinaryFile: Cannot open $binFName\n"; - binmode($binFH); - - my $binBuf; - my $readBuf; - my $derHex = ""; - while (my $len = sysread($binFH, $readBuf, 1024)) { - $binBuf .= $readBuf; - $$oc += $len; - } - close $binFH; - - return toHex($binBuf);; -} - -# Creates a hex representation of the DER encoding of an arbitrary length bit string -sub encodeBitString($$) { - my ($text, $oc) = @_; - - # Bit string in hex including padding length octet - my $bit_str = ""; - my $bit_str_oc = 1; # one octet for padding - - # Current byte - my $byte = 0; - my $len = length($text); - - if ($len == 0) { - $$oc+=2; - return "03:00"; - } - - my $i = 0; - while ($i < $len) { - - # Read the ith character and insert it in the correct place in the byte - # (fill from the left) - my $c = substr($text, $i, 1); - if ($c eq "1") { - $byte |= (1 << (7 - ($i % 8))); - } - elsif ($c ne "0") { - die "Invalid character $c in bit string $text"; - } - - if (++$i % 8 == 0) { - # Received 8 bits so output byte in hex - if ($bit_str ne "") { - $bit_str .= ":"; - } - $bit_str .= sprintf("%2.2x", $byte); - $bit_str_oc++; - $byte = 0; - } - } - # Pad any remaining bits / make sure 0 is output for empty string - if ($byte != 0 || $bit_str_oc == 1) { - if ($bit_str ne "") { - $bit_str .= ":"; - } - $bit_str .= sprintf("%2.2x", $byte); - $bit_str_oc++; - } - - my $pad_length = "00"; - if ($len % 8 > 0) { - # If this isn't a multiple of 8 bits then calculated - # the number of padding bits added. - $pad_length = sprintf("%2.2x", 8 - ($len % 8)); - } - - if ($DEBUG == 2) { - print "${TABS}:ENC:encodeBitString, $bit_str_oc\n"; - } - return encodeTlv($oc, $DER_BITSTRING_TAG, $bit_str_oc, "$pad_length:$bit_str"); -} - -# Creates a hex represenation of the DER encoding of a BMPSTRING -sub encodeBmpString($$$) { - my ($bmpString, $bmpString_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC:encodeBmpString, $bmpString_oc\n"; - } - return encodeTlv($oc, $DER_BMPSTRING_TAG, $bmpString_oc, $bmpString); -} - -sub encodeBoolean($$) { - my ($value, $oc) = @_; - - my $boolean = "00"; - if ($value) { - $boolean = "FF"; - } - - if ($DEBUG == 2) { - print "${TABS}:ENC:encodeBoolean, 1\n"; - } - return encodeTlv($oc, $DER_BOOLEAN_TAG, 1, $boolean); -} - -sub encodeEnumerated($$) { - my ($int, $oc) = @_; - - $int =~ s/\s//g; - - if (! ($int =~ /^-??\d+$/ || $int =~ /0x[0-9A-Fa-f]+/)) { - die "encodeEnumerated: Invalid argument: $int\n"; - } - - if ($int =~ s/^0x//) { - $int = hex; - } - - # Convert the enumerated to base 256 hex and find out how - # many octets were required - my $hex_enumerated_oc = 0; - my $hex_enumerated = ""; - - if ($int ne "") { - $hex_enumerated = encodeBase256($int, \$hex_enumerated_oc); - } - - if ($DEBUG == 2) { - print "${TABS}:ENC: , $hex_enumerated_oc\n"; - } - - return encodeTlv($oc, $DER_ENUMERATED_TAG, $hex_enumerated_oc, $hex_enumerated); -} - -# explicit tags are always constructed -sub encodeExplicit($$$$) { - my ($class, $tagNumber, $explicit, $explicit_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC: explicit, $explicit_oc\n"; - } - return encodeTlv($oc, $tagNumber, $explicit_oc, $explicit, 1, $class); -} - -# Creates a hex represenation of the DER encoding of an IA5 string -sub encodeIA5String($$) { - my ($ia5String, $ia5String_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC:encodeIA5String, $ia5String_oc\n"; - } - return encodeTlv($oc, $DER_IA5STRING_TAG, $ia5String_oc, $ia5String); -} - -sub encodeImplicit($$$$$) { - my ($class, $constructed, $tagNumber, $implicit, $implicit_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC: implicit, $implicit_oc\n"; - } - return encodeTlv($oc, $tagNumber, $implicit_oc, $implicit, $constructed, $class); -} - -sub encodeBigInteger($$) { - my ($hexString, $oc) = @_; - - my $bin = toBin($hexString); - my $int = toHex($bin); - my $int_oc = length($bin); - - if ($DEBUG == 2) { - print "${TABS}:ENC: bigInteger, $int_oc\n"; - } - return encodeTlv($oc, $DER_INTEGER_TAG, $int_oc, $int) -} - -sub encodeInteger($$) { - my ($int, $oc) = @_; - - $int =~ s/\s//g; - - if (! ($int =~ /^-??\d+$/ || $int =~ /0x[0-9A-Fa-f]+/)) { - die "encodeInteger: Invalid argument: $int\n"; - } - - if ($int =~ s/^0x//) { - $int = hex; - } - - # Convert the integer to base 256 hex and find out how - # many octets were required - my $hex_integer_oc = 0; - my $hex_integer = ""; - - if ($int ne "") { - $hex_integer = encodeBase256($int, \$hex_integer_oc); - } - - if ($DEBUG == 2) { - print "${TABS}:ENC: integer, $hex_integer_oc\n"; - } - - return encodeTlv($oc, $DER_INTEGER_TAG, $hex_integer_oc, $hex_integer); -} - -sub encodeNull($) { - my ($oc) = @_; - return encodeTlv($oc, $DER_NULL_TAG, 0, ""); -} - -sub encodeOctetString($$$) { - my ($octetString, $octetString_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC: octetString, $octetString_oc\n"; - } - return encodeTlv($oc, $DER_OCTETSTRING_TAG, $octetString_oc, $octetString); -} - -sub encodeOid($$) { - my ($text, $oc) = @_; - - my @fields = split /\./, $text; - - if (! ($fields[0] >= 0 && $fields[0] <=2) ) { - die "Invalid OID: $text\n"; - } - if (! ($fields[1] >= 0 && $fields[1] <= 39) ) { - die "Invalid OID: $text"; - } - - my $oid = sprintf("%2.2x", (40 * $fields[0]) + $fields[1]); - my $oid_oc = 1; - shift @fields; - shift @fields; - - foreach (@fields) { - $oid .= ":" . encodeBase128($_, \$oid_oc); - } - - if ($DEBUG == 2) { - print "${TABS}:ENC:encodeOid, $oid_oc\n"; - } - return encodeTlv($oc, $DER_OID_TAG, $oid_oc, $oid); -} - -# Creates a hex represenation of the DER encoding of a PRINTABLE string -sub encodePrintableString($$$) { - my ($printableString, $printableString_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC:encodePrintableString, $printableString_oc\n"; - } - return encodeTlv($oc, $DER_PRINTABLESTRING_TAG, $printableString_oc, $printableString); -} - -sub encodeSet($$$) { - my ($set, $set_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC: set, $set_oc\n"; - } - return encodeTlv($oc, $DER_SET_TAG, $set_oc, $set, 1); -} - -sub encodeSequence($$$) { - my ($sequence, $sequence_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC: sequence, $sequence_oc\n"; - } - return encodeTlv($oc, $DER_SEQUENCE_TAG, $sequence_oc, $sequence, 1); -} - -sub encodeUtcTime($$$) { - my ($utcTime, $utcTime_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC: UTCTime, $utcTime_oc\n"; - } - return encodeTlv($oc, $DER_UTCTIME_TAG, $utcTime_oc, $utcTime); -} - -# Creates a hex represenation of the DER encoding of a UTF-8 string. -sub encodeUtf8String($$) { - my ($utf8String, $utf8String_oc, $oc) = @_; - - if ($DEBUG == 2) { - print "${TABS}:ENC:encodeUTF8String, $utf8String_oc\n"; - } - return encodeTlv($oc, $DER_UTF8STRING_TAG, $utf8String_oc, $utf8String); -} - -sub asciiToBmpString($$) { - my ($input, $oc) = @_; - - my $bmpString = ""; - my $input_len = length($input); - $$oc += $input_len * 2; - - for (my $i = 0; $i < $input_len; ++$i) { - my $hex_val = ord(substr($input, $i, 1)); - if ($bmpString ne "") { - $bmpString .= ":"; - } - $bmpString .= sprintf(":00:%2.2x", $hex_val); - } - return $bmpString; -} - -sub asciiToIA5String($$) { - my ($input, $oc) = @_; - - my $printableString = ""; - my $input_len = length($input); - $$oc += $input_len; - - for (my $i = 0; $i < $input_len; ++$i) { - my $hex_val = ord(substr($input, $i, 1)); - if ($printableString ne "") { - $printableString .= ":"; - } - $printableString .= sprintf(":%2.2x", $hex_val); - } - return $printableString; -} - -sub asciiToPrintableString($$) { - my ($input, $oc) = @_; - - my $ia5String = ""; - my $input_len = length($input); - $$oc += $input_len; - - for (my $i = 0; $i < $input_len; ++$i) { - my $hex_val = ord(substr($input, $i, 1)); - if ($ia5String ne "") { - $ia5String .= ":"; - } - $ia5String .= sprintf(":%2.2x", $hex_val); - } - return $ia5String; -} - -sub asciiToUtf8String($$) { - my ($input, $oc) = @_; - - my $utf8String = ""; - my $input_len = length($input); - $$oc += $input_len; - - for (my $i = 0; $i < $input_len; ++$i) { - my $hex_val = ord(substr($input, $i, 1)); - if ($utf8String ne "") { - $utf8String .= ":"; - } - $utf8String .= sprintf(":%2.2x", $hex_val); - } - return $utf8String; -} - -sub encodeBase128($$$) { - my ($num, $oc) = @_; - - my $base128 = ""; - $num = int($num); - my $base128_length = 0; - - while ($num > 0) { - my $hexoctet; - - if ($base128 eq "") { - $hexoctet = sprintf("%2.2x", $num & 0x7f); - } - else { - $hexoctet = sprintf("%2.2x", ($num & 0x7f) | 0x80); - } - - if ($base128 eq "") { - $base128 = $hexoctet; - } - else { - $base128 = "$hexoctet:$base128"; - } - - $num >>= 7; - $base128_length++; - } - if ($base128 eq "") { - $base128 = "00"; - $base128_length++; - } - - $$oc += $base128_length; - - if ($DEBUG == 2) { - print "${TABS}:ENC: base128, $base128_length, $$oc\n"; - } - - return $base128; -} - -# Return a hex represenation of the length using DER primitive (definate length encoding) -sub encodeLength($$) { - my ($num, $oc) = @_; - - if ($num < 128) { - # Number is < 128 so encode in short form - $$oc++; - return sprintf("%2.2x", $num); - } - else { - # Number >= 128 so encode in long form - my $length_oc = 0; - my $base256 = &encodeBase256($num, \$length_oc, 1); - if ($length_oc > 127) {die "Encoding overflow.";} - - $$oc += 1 + $length_oc; - - # Set the top bit of the length octet to indicate long form - return "" . sprintf("%2.2x", ($length_oc | 0x80)) . ":$base256"; - } -} - -# Convert an integer into an ascii hex representation in base 256 -# $num - the number to encode -# $octets - refernce to the octet count to increment -# $unsigned - assume unsigned -sub encodeBase256($$) { - my ($numIn, $oc, $unsigned) = @_; - - my $base256 = ""; - my $num = int($numIn); - - while ($num != 0) { - my $hexoctet = sprintf("%2.2x", $num & 0xFF); - if ($base256 ne "") { - $base256 = "$hexoctet:$base256"; - } - else { - $base256 = $hexoctet; - } - $num >>= 8; - $$oc++; - } - if ($base256 eq "") { - $base256 = "00"; - $$oc++; - } - - # If the integer is +ve and the MSB is 1 then padd with a leading zero - # octet otherwise it will look -ve - if ((! $unsigned) && $numIn > 0 && $base256 =~ /^:*[8ABCDEF]/i) { - $base256 = "00:$base256"; - $$oc++; - } - - # If the first octet is all ones and the msb of the next bit - # is also one then drop the first octet because negative - # numbers should not be padded - while ($base256 =~ s/^(FF:)([8ABCDEF][0-9A-F].*)/$2/i) { - $$oc--; - } - - return $base256; -} - -# Encode the Type -# Only low tag form is supported at the moment -sub encodeType($$;$$) { - my ($oc, $tagNumber, $constructed, $class) = @_; - - $tagNumber = hex($tagNumber); - - if ($tagNumber < 0 || $tagNumber > 30) { - die "encodeType: Currently, only low tag numbers (0 - 30) are supported."; - } - - if (! defined $class) { - $class = "UNIVERSAL"; - } - - $class = uc($class); - if (! isValidClass($class)) { - die "encodeType: invalid class \'$class\'"; - } - - # If the type is constructed then set bit 6 - if (defined $constructed && $constructed == 1) { - $tagNumber |= 0x20; - } - - if ($class eq $UNIVERSAL_CLASS) { - # do nothing, bits 7 and 8 are zero - } - elsif ($class eq $APPLICATION_CLASS) { - # set bit 7 - $tagNumber |= 0x40; - } - elsif ($class eq $CONTEXT_SPECIFIC_CLASS) { - # set bit 8 - $tagNumber |= 0x80; - } - elsif ($class eq $PRIVATE_CLASS) { - # set bits 7 and 8 - $tagNumber |= 0xC0; - } - $$oc++; - return sprintf("%2.2x", $tagNumber); -} - -sub encodeTlv($$$$;$$) { - my ($oc, $tag, $length, $value, $constructed, $class) = @_; - - if ($DEBUG == 3) { - print "${TABS}encodeTlv\n"; - print "${TABS}oc=$$oc\n"; - print "${TABS}tag=$tag\n"; - print "${TABS}length=$length\n"; - print "${TABS}value=$value\n"; - if (defined $constructed) { - print "${TABS}constructed=$constructed\n"; - } - if (defined $class) { - print "${TABS}class=$class\n"; - } - } - - my $hex; - $hex = encodeType($oc, $tag, $constructed, $class); - $hex .= ":" . encodeLength($length, $oc); - $$oc += $length; - $hex .= ":" . $value; - - if ($DEBUG == 3) { - print "${TABS}oc=$$oc\n"; - print "${TABS}encoding=$hex\n"; - print "${TABS}end\n"; - - toBin($hex); - } - return $hex; -} - -# increment debug tabbing level -sub nest() { - $TABS .= " "; -} - -# decrement debug tabbing level -sub leaveNest() { - $TABS =~ s/^...//; -} - -sub isValidClass($) { - my ($class) = @_; - - if (defined $class && - $class =~ /^(UNIVERSAL|APPLICATION|CONTEXT-SPECIFIC|PRIVATE)$/) { - return 1; - } - return 0; -} - -# Parse a DER field -sub getTlv($$$$$$) { - my ($input, $class, $constructed, $tag, $length, $value) = @_; - - my @hexOctets = split(/:+/,tidyHex($input)); - - if (scalar(@hexOctets) < 2) { - die "getTlv: too short"; - } - - my $type = hex(shift @hexOctets); - if (($type & 0xC0) == 0x00) { - # universal: bit 8 = 0, bit 7 = 0 - $$class = $UNIVERSAL_CLASS; - } - elsif (($type & 0xC0) == 0x40) { - # application: bit 8 = 0, bit 7 = 1 - $$class = $APPLICATION_CLASS; - } - elsif (($type & 0xC0) == 0x80) { - # application: bit 8 = 1, bit 7 = 0 - $$class = $CONTEXT_SPECIFIC_CLASS; - } - elsif (($type & 0xC0) == 0xC0) { - # application: bit 8 = 1, bit 7 = 1 - $$class = $PRIVATE_CLASS; - } - else { - die "getTlv: assert"; - } - - if ($type & 0x20) { - # constructed if bit 6 = 1 - $$constructed = 1; - } - else { - $$constructed = 0; - } - - # We assumme the tag number is in low form - # and just look at the bottom 5 hits - $$tag = $type & 0x1F; - - $$length = hex(shift @hexOctets); - if ($$length & 0x80) { - # long form - my $length_oc = $$length & 0x7F; - $$length = 0; - for (my $i = 0; $i < $length_oc; $i++) { - # length is encoded base 256 - $$length *= 256; - $$length += hex(shift @hexOctets); - } - } - else { - # short form - # don't do anything here, length is just bits 7 - 1 and - # we already know bit 8 is zero. - } - - $$value = ""; - foreach (@hexOctets) { - $$value .= ":$_"; - } - - if ($DEBUG == 3) { - print "${TABS} class=$$class\n"; - print "${TABS} constructed=$$constructed\n"; - print "${TABS} tag=$$tag\n"; - print "${TABS} length=$$length\n"; - } -} - -# parse an escaped (\) comma seperated argument string -# into an array -sub getArgs($) { - my ($argString) = @_; - my @args = (); - - while ($argString =~ /(^|.*?[^\\]),(.*)/ ) { - my $match = $1; - $argString = $2; - if ($match ne "") { - - # unescape - $match =~ s/(\\)([^\\])/$2/g; - push @args, $match; - } - } - if ($argString ne "") { - push @args, $argString; - } - return @args; -} +# +# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of the License "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# +#!/bin/perl -w + +# Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of the License "Symbian Foundation License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.symbianfoundation.org/legal/sfl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# Basic ASN.1 encoding library +# Some parts of this program requrie OpenSSL which may be freely downloaded +# from www.openssl.org +# +# + +use strict; +use Digest::HMAC_MD5; +use Digest::HMAC_SHA1; +use Getopt::Long; + +# 0 = off +# 1 = log parsing +# 2 = log parsing + encoding +# 3 = really verbose stuff +my $DEBUG=0; + +# Turn on validation checks that attempt to only generate +# valid DER encodings. +my $VALIDATE=0; + +my $OID_PKCS = "1.2.840.113549.1"; +my $OID_PKCS7 ="${OID_PKCS}.7"; +my $OID_PKCS9 = "${OID_PKCS}.9"; +my $OID_PKCS9_CERTTYPES = "${OID_PKCS9}.22"; +my $OID_PKCS12 = "${OID_PKCS}.12"; +my $OID_PKCS12_BAGTYPES = "${OID_PKCS12}.10.1"; +my $OID_PKCS12_PBEIDS = "${OID_PKCS12}.1"; + +my %OIDS = + ( + "MD5" => "1.2.840.113549.2.5", + "SHA1" => "1.3.14.3.2.26", + "X509CRL" => "1.3.6.1.4.1.3627.4", + + "PKCS7_DATA" => "${OID_PKCS7}.1", + "PKCS7_SIGNEDDATA" => "${OID_PKCS7}.2", + "PKCS7_ENVELOPEDDATA" => "${OID_PKCS7}.3", + "PKCS7_SIGNEDANDENVELOPEDDATA" => "${OID_PKCS7}.4", + "PKCS7_DIGESTEDDATA" => "${OID_PKCS7}.5", + "PKCS7_ENCRYPTEDDATA" => "${OID_PKCS7}.6", + + "PKCS9_CERTTYPES_PKCS12_X509" => "${OID_PKCS9_CERTTYPES}.1", + "PKCS9_FRIENDLY_NAME" => "${OID_PKCS9}.20", + "PKCS9_LOCAL_KEYID" => "${OID_PKCS9}.21", + + "PKCS12_BAGTYPES_KEYBAG" => "${OID_PKCS12_BAGTYPES}.1", + "PKCS12_BAGTYPES_PKCS8SHROUDEDKEYBAG" => "${OID_PKCS12_BAGTYPES}.2", + "PKCS12_BAGTYPES_CERTBAG" => "${OID_PKCS12_BAGTYPES}.3", + "PKCS12_BAGTYPES_CRLBAG" => "${OID_PKCS12_BAGTYPES}.4", + "PKCS12_BAGTYPES_SECRETBAG" => "${OID_PKCS12_BAGTYPES}.5", + "PKCS12_BAGTYPES_SAFECONTENTSBAG" => "${OID_PKCS12_BAGTYPES}.6", + + "PKCS12_PBEIDS_SHAAND128BITRC4" => "${OID_PKCS12_PBEIDS}.1", + "PKCS12_PBEIDS_SHAAND40BITRC4" => "${OID_PKCS12_PBEIDS}.2", + "PKCS12_PBEIDS_SHAAND3KEYTRIPLEDESCBC" => "${OID_PKCS12_PBEIDS}.3", + "PKCS12_PBEIDS_SHAAND2KEYTRIPLEDESCBC" => "${OID_PKCS12_PBEIDS}.4", + "PKCS12_PBEIDS_SHAAND128BITRC2CBC" => "${OID_PKCS12_PBEIDS}.5", + "PKCS12_PBEIDS_SHAAND40BITRC2CBC" => "${OID_PKCS12_PBEIDS}.6", + + # Symbian dev cert extensions + "SYMBIAN_DEVICE_ID_LIST" => "1.2.826.0.1.1796587.1.1.1.1", + "SYMBIAN_SID_LIST" => "1.2.826.0.1.1796587.1.1.1.4", + "SYMBIAN_VID_LIST" => "1.2.826.0.1.1796587.1.1.1.5", + "SYMBIAN_CAPABILITIES" => "1.2.826.0.1.1796587.1.1.1.6" + +); + +my $DER_BOOLEAN_TAG="01"; +my $DER_INTEGER_TAG="02"; +my $DER_BITSTRING_TAG="03"; +my $DER_OCTETSTRING_TAG="04"; +my $DER_NULL_TAG="05"; +my $DER_OID_TAG="06"; +my $DER_ENUMERATED_TAG="0A"; +my $DER_SEQUENCE_TAG="10"; +my $DER_SET_TAG="11"; +my $DER_UTF8STRING_TAG="0C"; +my $DER_PRINTABLESTRING_TAG="13"; +my $DER_IA5STRING_TAG="16"; +my $DER_UTCTIME_TAG="17"; +my $DER_BMPSTRING_TAG="1E"; + +my $UNIVERSAL_CLASS="UNIVERSAL"; +my $APPLICATION_CLASS="APPLICATION"; +my $CONTEXT_SPECIFIC_CLASS="CONTEXT-SPECIFIC"; +my $PRIVATE_CLASS="PRIVATE"; + +my %PARSE = + ( + "BOOL" => \&parseBoolean, + "BOOLEAN" => \&parseBoolean, + "BIGINTEGER" => \&parseBigInteger, + "BITSTRING" => \&parseBitString, + "BITSTRING_WRAPPER" => \&parseBitStringWrapper, + "BMPSTRING" => \&parseBmpString, + "BMPSTRING_FILE" => \&parseBmpStringFile, + "ENUMERATED" => \&parseEnumerated, + "IA5STRING" => \&parseIA5String, + "IA5STRING_FILE" => \&parseIA5StringFile, + "INCLUDE" => \&parseInclude, + "INCLUDE_BINARY_FILE" => \&parseIncludeBinaryFile, + "INTEGER" => \&parseInteger, + "INT" => \&parseInteger, + "IMPLICIT" => \&parseImplicit, + "ENCRYPT" => \&parseEncrypt, + "EXPLICIT" => \&parseExplicit, + "HASH" => \&parseHash, + "HMAC" => \&parseHmac, + "NULL" => \&parseNull, + "OCTETSTRING" => \&parseOctetString, + "OUTPUT_BINARY_FILE" => \&parseOutputFile, + "OID" => \&parseOid, + "PRINTABLESTRING" => \&parsePrintableString, + "PRINTABLESTRING_FILE" => \&parsePrintableStringFile, + "RAW" => \&parseRaw, + "SEQUENCE" => \&parseSequence, + "SEQ" => \&parseSequence, + "SET" => \&parseSet, + "SHELL" => \&parseShell, + "SIGN" => \&parseSign, + "UTCTIME" => \&parseUtcTime, + "UTF8STRING" => \&parseUtf8String, + "UTF8STRING_FILE" => \&parseUtf8StringFile, + ); + +my $TABS = ""; + +&main; +exit(0); + +sub main() { + my $hex; + my $out; + my $in; + my @lines; + + GetOptions('debug=i' => \$DEBUG, + 'hex' => \$hex, + 'in=s' => \$in, + 'out=s' => \$out); + + if (! defined $in) { + $in = $ARGV[0]; + } + + if (! defined $out) { + $out = $ARGV[1]; + } + + if (defined $in) { + @lines = readFile($in); + } + else { + die "No input file specified.\n"; + } + + if (defined $out) { + open OUT, ">$out" || die "Cannot open output file $out"; + } + else { + *OUT = *STDOUT; + } + + my $oc = 0; + my $asnHex = parseScript(\@lines, \$oc); + $asnHex = tidyHex($asnHex); + + if ((!defined $hex) && (defined $out)) { + binmode(OUT); + print OUT toBin($asnHex); + } + elsif (defined $out) { + print OUT $asnHex; + } + else { + print $asnHex; + } + + close OUT; +} + +sub tidyHex($) { + my ($input) = @_; + $input =~ s/:+/:/g; + $input =~ s/(^:|:$)//g; + return uc($input); +} + +sub toBin($) { + my ($asnHex) = @_; + + $asnHex =~ s/[\s:]//g; + $asnHex = uc($asnHex); + + my $len = length($asnHex); + if ($len % 2 != 0) { + die "toBin: hex string contains an odd number ($len) of octets.\n$asnHex\n"; + } + + my $binary; + $binary .= pack("H${len}", $asnHex); +# for (my $i = 0; $i < length($asnHex); $i+=2) { +# $binary .= pack('C', substr($asnHex, $i, 2)); +# } + return $binary; +} + +sub parseScript($$;$) { + my ($lines, $oc, $params) = @_; + my $derHex = ""; + + nest(); + substVars($lines, $params); + + while (my $line = shift @$lines) { + chomp($line); + + # Remove leading spaces + $line =~ s/^\s*//g; + + # skip comments + next if ($line =~ /^\/\//); + + if ($DEBUG == 3) { + print "${TABS}:PARSE parseScript: $line\n"; + } + + my $argString; + my $cmd; + if ($line =~ /(\w+)\s*\{/ ) { + # parse block commands e.g. large integer + $cmd = uc($1); + + $line =~ s/.*\{//g; + while (defined $line && !($line =~ /(^|[^\\]+)\}/) ) { + $argString .= $line; + $line = shift(@$lines); + } + if (defined $line) { + # append everything up to the closing curly bracket + $line =~ s/(^|[^\\])\}.*/$1/g; + $argString .= $line; + } + } + elsif ($line =~ /(\w+)\s*=*(.*)/) { + # parse commands of the form key = value + $cmd = uc($1); + $argString = defined $2 ? $2 : ""; + } + + if (defined $cmd) { + if ($cmd =~ /^END/) { + leaveNest(); + if ($DEBUG) { + print "${TABS}:PARSE END\n"; + } + return $derHex; + } + elsif (! defined $PARSE{$cmd}) { + die "parseScript: Unknown command: $cmd\n"; + } + else { + if ($DEBUG) { + print "${TABS}:PARSE CMD=$cmd"; + if ($argString ne "") {print " ARG: $argString";} + print "\n"; + } + + # Substitue variables in argString + $derHex .= ":" . &{$PARSE{$cmd}}($argString, $oc, $lines); + } + } + + } + leaveNest(); + return $derHex; +} + +sub substVars($$) { + my ($lines, $params) = @_; + + if (! defined $params) { + @$params = (); + } + + for (my $i = 0; $i < scalar(@$lines); $i++) { + my $line = @$lines[$i]; + my $paramIndex = 1; + + # For each parameter search for the a use of $N where + # N is the index of the parameter and replace $N with the + # value of the parameter + foreach (@$params) { + $line =~ s/\$${paramIndex}(\D|$)/$_$1/g; + ++$paramIndex; + } + + # Remove any unused parameters + $line =~ s/\$\d+//g; + @$lines[$i] = $line; + } +} + +sub readFile($) { + my ($fileName) = @_; + my $inFile; + + if ($DEBUG) { + print "readFile, $fileName\n"; + } + + open($inFile, $fileName) || die "readFile: cannot open $fileName\n"; + my @lines = <$inFile>; + close $inFile; + + return @lines; +} + +sub parseBitString($$;$) { + my ($argString, $oc, $lines) = @_; + return encodeBitString($argString, $oc); +} + +sub parseBitStringWrapper($$;$) { + my ($argString, $oc, $lines) = @_; + + my $contents_oc = 0; + my $contents = parseScript($lines, \$contents_oc); + + my $binary = toBin($contents); + my $bitCount = $contents_oc * 8; + my $bitStr = unpack("B${bitCount}", $binary); + + # remove trailing zeros - breaks signatures so disable for the moment + # $bitStr =~ s/0*$//g; + + return encodeBitString($bitStr, $oc); +} + +sub parseBmpString($$;$) { + my ($argString, $oc, $lines) = @_; + + my $bmpString_oc = 0; + my $bmpString = asciiToBmpString($argString, \$bmpString_oc); + return encodeBmpString($bmpString, $bmpString_oc, $oc); +} + +sub parseBmpStringFile($$;$) { + my ($binFName, $oc, $lines) = @_; + $binFName =~ s/\s*//g; + + my $bmpString_oc = 0; + my $bmpString = encodeBinaryFile($binFName, \$bmpString_oc); + + return encodeBmpString($bmpString, $bmpString_oc, $oc); +} + +sub parseBoolean($$;$) { + my ($argString, $oc, $lines) = @_; + + $argString =~ s/\s//g; + $argString = lc($argString); + + my $bool; + if ($argString eq "t" || $argString eq "true" || $argString eq "1") { + $bool = 1; + } + elsif ($argString eq "f" || $argString eq "false" || $argString eq "0") { + $bool = 0; + } + else { + die "parseBoolean: Invalid boolean value \'$argString\'"; + } + + return encodeBoolean($bool, $oc); +} + +sub parseHash($$;$) { + my ($argString, $oc, $lines) = @_; + my ($algorithm) = getArgs($argString); + + if (! defined $algorithm) { + die "parseHash: missing algortithm"; + } + + my $hashIn_oc = 0; + my $hashIn = parseScript($lines, \$hashIn_oc); + + my $hashInFName = '_hashin.tmp'; + my $hashOutFName = '_hashout.tmp'; + + # Create binary hash file + my $hashInFh; + open($hashInFh, ">$hashInFName") or die "Cannot create $hashInFName"; + binmode($hashInFh); + print $hashInFh toBin($hashIn); + close $hashInFh; + + my @command = ("cmd", + "/C \"openssl dgst -${algorithm} -binary $hashInFName > $hashOutFName\""); + if ($DEBUG == 1) { + print "${TABS}:parseHash:" . join(" ", @command) . "\n"; + } + + if ((my $err = system(@command)) != 0) { + die "parseHash: " . join(" ", @command) . "\nreturned error $err"; + } + + my $derHex = parseIncludeBinaryFile($hashOutFName, $oc); + + if (! $DEBUG) { + unlink($hashInFName); + unlink($hashOutFName); + } + return $derHex; +} + +sub parseHmac($$;$) { + my ($argString, $oc, $lines) = @_; + my ($algorithm, $key) = getArgs($argString); + + if (! defined $algorithm) { + die "parseHmac: missing algortithm"; + } + $algorithm = uc($algorithm); + if (! $algorithm =~ /MD5|SHA1/) { + die "parseHmac: invalid algorithm $algorithm"; + } + + if (! defined $key) { + die "parseHmac: missing key"; + } + + my $hmacIn_oc = 0; + my $hmacIn = toBin(parseScript($lines, \$hmacIn_oc)); + my $hmac; + my $binKey = toBin($key); + + if ($algorithm eq "SHA1") { + + $hmac = Digest::HMAC_SHA1->new($binKey); + } + else { + $hmac = Digest::HMAC_MD5->new($binKey); + } + $hmac->add($hmacIn); + my $digest = $hmac->digest; + $$oc += length($digest); + + return toHex($digest); +} + +sub parseIA5String($$;$) { + my ($argString, $oc, $lines) = @_; + + my $ia5String_oc = 0; + my $ia5String = asciiToIA5String($argString, \$ia5String_oc); + return encodeIA5String($ia5String, $ia5String_oc, $oc); +} + + +sub parseIA5StringFile($$;$) { + my ($binFName, $oc, $lines) = @_; + $binFName =~ s/\s*//g; + + my $ia5String_oc = 0; + my $ia5String = encodeBinaryFile($binFName, \$ia5String_oc); + + return encodeIA5String($ia5String, $ia5String_oc, $oc); +} + +sub parseIncludeBinaryFile($$;$) { + my ($binFName, $oc, $lines) = @_; + $binFName =~ s/\s*//g; + + return encodeBinaryFile($binFName, $oc); +} + +sub parseInclude($$$) { + my ($argString, $oc, $lines) = @_; + my @args = getArgs($argString); + + my $fileName = shift(@args); + if (! (defined $fileName && $fileName ne "")) { + die "parseInclude: Filename not specified\n"; + } + + my $derHex = ""; + my @lines = readFile($fileName); + $derHex = parseScript(\@lines, $oc, \@args); + return $derHex; +} + +sub parseInteger($$;$) { + my ($argString, $oc, $lines) = @_; + + $argString =~ s/\s//g; + return encodeInteger($argString, $oc); +} + +sub parseBigInteger($$;$) { + my ($argString, $oc, $lines) = @_; + + $argString =~ s/\s//g; + return encodeBigInteger($argString, $oc); +} + +sub parseEncrypt($$;$) { + my ($argString, $oc, $lines) = @_; + my ($cipher, $key, $iv) = getArgs($argString); + + if (! defined $cipher) { + die "parseEncrypt: missing cipher\n"; + } + + if (! defined $key) { + die "parseEncrypt: missing key\n"; + } + + my $plainText_oc = 0; + my $plainText = parseScript($lines, \$plainText_oc); + + my $plainTextFName = '_plaintext.tmp'; + my $cipherTextFName = '_ciphertext.tmp'; + + # Create binary plaintext file + my $plainTextFh; + open($plainTextFh, ">$plainTextFName") or die "Cannot create $plainTextFName"; + binmode($plainTextFh); + print $plainTextFh toBin($plainText); + close $plainTextFh; + + my @command = ('openssl', + 'enc', + "-${cipher}", + '-e', + '-K', $key, + '-in', $plainTextFName, + '-out', $cipherTextFName); + + if (defined $iv) { + push @command, '-iv', $iv; + } + + if ($DEBUG == 1) { + print "${TABS}:parseEncrypt:" . join(" ", @command) . "\n"; + } + + if ((my $err = system(@command)) != 0) { + die "parseEncrypt: " . join(" ", @command) . "\nreturned error $err"; + } + + my $derHex = parseIncludeBinaryFile($cipherTextFName, $oc); + + if (! $DEBUG) { + unlink($plainTextFName); + unlink($cipherTextFName); + } + return $derHex; +} + +sub parseEnumerated($$;$) { + my ($argString, $oc, $lines) = @_; + + $argString =~ s/\s//g; + return encodeEnumerated($argString, $oc); +} + +sub parseExplicit($$;$) { + my ($argString, $oc, $lines) = @_; + my ($tagNumber, $class) = getArgs($argString); + + if (! defined $tagNumber || $tagNumber =~ /^\s*$/) { + $tagNumber = "0"; + } + elsif (!($tagNumber =~ /^[0-9A-Fa-f]+$/)) { + die "parseExplicit: invalid tag number: \'$tagNumber\'"; + } + $tagNumber = hex($tagNumber); + + if (!defined $class || $class =~ /^\s*$/) { + $class = $CONTEXT_SPECIFIC_CLASS; + } + else { + $class =~ s/\s*//g; + $class = uc($class); + } + + if (! isValidClass($class)) { + die "parseExplicit: invalid class \'$class\'"; + } + + my $nested_oc = 0; + my $nested = parseScript($lines, \$nested_oc); + + return encodeExplicit($class, $tagNumber, $nested, $nested_oc, $oc); +} + +sub parseImplicit($$;$) { + my ($argString, $oc, $lines) = @_; + my ($tagNumber, $class) = getArgs($argString); + + if (! defined $tagNumber || $tagNumber =~ /^\s*$/) { + $tagNumber = "0"; + } + elsif (!($tagNumber =~ /^[0-9A-Fa-f]+$/)) { + die "parseImplicit: invalid tag number: \'$tagNumber\'"; + } + $tagNumber = hex($tagNumber); + + if (!defined $class || $class =~ /^\s*$/) { + $class = $CONTEXT_SPECIFIC_CLASS; + } + else { + $class =~ s/\s*//g; + $class = uc($class); + } + + if (! isValidClass($class)) { + die "parseImplicit: invalid class \'$class\'"; + } + + my $nested_oc = 0; + my $nested = tidyHex(parseScript($lines, \$nested_oc)); + + # De-construct the nested data to allow the underlying type tag to be + # changed. The output of parseScript had better be valid DER or this + # will go horribly wrong ! + my $uClass = ""; + my $uConstructed = 0; + my $uTag = 0; + my $uLength = 0; + my $uValue = ""; + getTlv($nested, \$uClass, \$uConstructed, \$uTag, \$uLength, \$uValue); + + if ($DEBUG == 2) { + print "${TABS}parseImplicit: underlyingType \'$uTag\'\n"; + } + + # This only works for low tag numbers because we are assuming that the type + # tag is a single octet + return encodeImplicit($class, $uConstructed, $tagNumber, $uValue, $uLength, $oc); +} + +sub parseNull($$;$) { + my ($argString, $oc, $lines) = @_; + + return encodeNull($oc); +} + +sub parseOctetString($$;$) { + my ($argString, $oc, $lines) = @_; + + my $octetString_oc = 0; + my $octetString = parseScript($lines, \$octetString_oc); + + return encodeOctetString($octetString, $octetString_oc, $oc); +} + +sub parseOid($$;$) { + my ($argString, $oc, $lines) = @_; + $argString =~ s/\s//g; + $argString = uc($argString); + + if (! defined $argString) { + die "parseOid: Missing OID value."; + } + + foreach (keys %OIDS) { + if ($argString =~ /$_/) { + $argString =~ s/\Q$_\E/$OIDS{$_}/g; + } + } + return encodeOid($argString, $oc); +} + +sub parseOutputFile($$;$) { + my ($argString, $oc, $lines) = @_; + my ($outputFile,$echo) = split(/,/, $argString); + + if (! defined $outputFile) { + die "parseOutputFile: Missing file-name.\n"; + } + + my $content_oc = 0; + my $content = parseScript($lines, \$content_oc); + + my $outFh; + if (! open($outFh, ">${outputFile}")) { + die "parseOutputFile: Cannot create $outputFile\n"; + } + binmode($outFh); + print $outFh toBin($content); + close $outFh; + + # If echo is specified then include then contents of the output + # file at this point in the stream. + if (defined $echo && $echo =~ /(1|t|true)/i) { + $$oc += $content_oc; + return $content; + } + else { + return ""; + } +} + +sub parsePrintableString($$;$) { + my ($argString, $oc, $lines) = @_; + + my $printableString_oc = 0; + my $printableString = asciiToPrintableString($argString, \$printableString_oc); + return encodePrintableString($printableString, $printableString_oc, $oc); +} + +sub parsePrintableStringFile($$;$) { + my ($binFName, $oc, $lines) = @_; + $binFName =~ s/\s*//g; + + my $printableString_oc = 0; + my $printableString = encodeBinaryFile($binFName, \$printableString_oc); + + return encodePrintableString($printableString, $printableString_oc, $oc); +} + +sub parseRaw($$;$) { + my ($argString, $oc, $lines) = @_; + $argString =~ s/\s//g; + $argString = uc($argString); + + my $asnHex = ""; + if (! ($argString =~ /(([A-Fa-f\d][A-Fa-f\d])[ :]*)+/)) { + die "parseRaw: Invalid hex string: $argString\n"; + } + my $binary = toBin($argString); + $$oc += length($binary); + return tidyHex(toHex($binary)); +} + +sub parseSequence($$;$) { + my ($argString, $oc, $lines) = @_; + + my $sequence_oc = 0; + my $sequence = parseScript($lines, \$sequence_oc); + + return encodeSequence($sequence, $sequence_oc, $oc); +} + +sub parseSet($$;$) { + my ($argString, $oc, $lines) = @_; + + my $set_oc = 0; + my $set = parseScript($lines, \$set_oc); + + return encodeSet($set, $set_oc, $oc); +} + +# Create a PKCS#7 signed data object for a chunk of data using +# OpenSSL's SMIME command +sub parseSign($$;$) { + my ($argString, $oc, $lines) = @_; + my ($signerCert, $signerKey) = getArgs($argString); + + if (! defined $signerCert) { + die "parseSign: missing signing certificate"; + } + elsif (! -f $signerCert) { + die "parseSign: signing certificate \'$signerCert\' does not exist."; + } + + if (! defined $signerKey) { + die "parseSign: missing signing certificate"; + } + elsif (! -f $signerKey) { + die "parseSign: signing key \'$signerKey\' does not exist."; + } + + my $unsigned_oc = 0; + my $unsigned = parseScript($lines, \$unsigned_oc); + + my $unsignedFName = '_unsigned.tmp'; + my $signedFName = '_signed.tmp'; + + # Create binary unsigned data file + my $unsignedFh; + open($unsignedFh, ">$unsignedFName") or die "Cannot create $unsignedFName"; + binmode($unsignedFh); + print $unsignedFh toBin($unsigned); + close $unsignedFh; + + my @command = ('openssl', + 'smime', + '-pk7out', + '-nodetach', + '-outform', + 'der', + '-sign', + '-signer', + $signerCert, + '-inkey', + $signerKey, + '-in', $unsignedFName, + '-out', $signedFName); + + if ($DEBUG == 1) { + print "${TABS}:parseSign:" . join(" ", @command) . "\n"; + } + + if ((my $err = system(@command)) != 0) { + die "parseSign: " . join(" ", @command) . "\nreturned error $err"; + } + + my $derHex = parseIncludeBinaryFile($signedFName, $oc); + + if (! $DEBUG) { + unlink($unsignedFName); + unlink($signedFName); + } + return $derHex; +} + +sub parseShell($$;$) { + my ($argString, $oc, $lines) = @_; + my @command = getArgs($argString); + + if (scalar(@command) < 1) { + die "parseShell: no arguments"; + } + + if ($DEBUG == 1) { + print "${TABS}:parseShell:" . join(" ", @command) . "\n"; + } + + if ((my $err = system(@command)) != 0) { + die "parseShell: " . join(" ", @command) . "\nreturned error $err"; + } + return ""; +} + +sub parseUtcTime($$;$) { + my ($time, $oc, $lines) = @_; + $time =~ s/\s//g; + + my $time_oc = length($time); + return encodeUtcTime(toHex($time), $time_oc, $oc); +} + +sub parseUtf8String($$;$) { + my ($argString, $oc, $lines) = @_; + + my $utf8String_oc = 0; + my $utf8String = asciiToUtf8String($argString, \$utf8String_oc); + return encodeUtf8String($utf8String, $utf8String_oc, $oc); +} + +sub parseUtf8StringFile($$;$) { + my ($binFName, $oc, $lines) = @_; + $binFName =~ s/\s*//g; + + my $utf8String_oc = 0; + my $utf8String = encodeBinaryFile($binFName, \$utf8String_oc); + + return encodeUtf8String($utf8String, $utf8String_oc, $oc); +} + +sub toHex($) { + my ($bin) = @_; + my $hex = unpack("H" . (length($bin) * 2), $bin); + $hex =~ s/(..)/$1:/g; + return $hex; +} + +sub encodeBinaryFile($$) { + my ($binFName, $oc) = @_; + + my $binFH; + open($binFH, "$binFName") || die "encodeBinaryFile: Cannot open $binFName\n"; + binmode($binFH); + + my $binBuf; + my $readBuf; + my $derHex = ""; + while (my $len = sysread($binFH, $readBuf, 1024)) { + $binBuf .= $readBuf; + $$oc += $len; + } + close $binFH; + + return toHex($binBuf);; +} + +# Creates a hex representation of the DER encoding of an arbitrary length bit string +sub encodeBitString($$) { + my ($text, $oc) = @_; + + # Bit string in hex including padding length octet + my $bit_str = ""; + my $bit_str_oc = 1; # one octet for padding + + # Current byte + my $byte = 0; + my $len = length($text); + + if ($len == 0) { + $$oc+=2; + return "03:00"; + } + + my $i = 0; + while ($i < $len) { + + # Read the ith character and insert it in the correct place in the byte + # (fill from the left) + my $c = substr($text, $i, 1); + if ($c eq "1") { + $byte |= (1 << (7 - ($i % 8))); + } + elsif ($c ne "0") { + die "Invalid character $c in bit string $text"; + } + + if (++$i % 8 == 0) { + # Received 8 bits so output byte in hex + if ($bit_str ne "") { + $bit_str .= ":"; + } + $bit_str .= sprintf("%2.2x", $byte); + $bit_str_oc++; + $byte = 0; + } + } + # Pad any remaining bits / make sure 0 is output for empty string + if ($byte != 0 || $bit_str_oc == 1) { + if ($bit_str ne "") { + $bit_str .= ":"; + } + $bit_str .= sprintf("%2.2x", $byte); + $bit_str_oc++; + } + + my $pad_length = "00"; + if ($len % 8 > 0) { + # If this isn't a multiple of 8 bits then calculated + # the number of padding bits added. + $pad_length = sprintf("%2.2x", 8 - ($len % 8)); + } + + if ($DEBUG == 2) { + print "${TABS}:ENC:encodeBitString, $bit_str_oc\n"; + } + return encodeTlv($oc, $DER_BITSTRING_TAG, $bit_str_oc, "$pad_length:$bit_str"); +} + +# Creates a hex represenation of the DER encoding of a BMPSTRING +sub encodeBmpString($$$) { + my ($bmpString, $bmpString_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC:encodeBmpString, $bmpString_oc\n"; + } + return encodeTlv($oc, $DER_BMPSTRING_TAG, $bmpString_oc, $bmpString); +} + +sub encodeBoolean($$) { + my ($value, $oc) = @_; + + my $boolean = "00"; + if ($value) { + $boolean = "FF"; + } + + if ($DEBUG == 2) { + print "${TABS}:ENC:encodeBoolean, 1\n"; + } + return encodeTlv($oc, $DER_BOOLEAN_TAG, 1, $boolean); +} + +sub encodeEnumerated($$) { + my ($int, $oc) = @_; + + $int =~ s/\s//g; + + if (! ($int =~ /^-??\d+$/ || $int =~ /0x[0-9A-Fa-f]+/)) { + die "encodeEnumerated: Invalid argument: $int\n"; + } + + if ($int =~ s/^0x//) { + $int = hex; + } + + # Convert the enumerated to base 256 hex and find out how + # many octets were required + my $hex_enumerated_oc = 0; + my $hex_enumerated = ""; + + if ($int ne "") { + $hex_enumerated = encodeBase256($int, \$hex_enumerated_oc); + } + + if ($DEBUG == 2) { + print "${TABS}:ENC: , $hex_enumerated_oc\n"; + } + + return encodeTlv($oc, $DER_ENUMERATED_TAG, $hex_enumerated_oc, $hex_enumerated); +} + +# explicit tags are always constructed +sub encodeExplicit($$$$) { + my ($class, $tagNumber, $explicit, $explicit_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC: explicit, $explicit_oc\n"; + } + return encodeTlv($oc, $tagNumber, $explicit_oc, $explicit, 1, $class); +} + +# Creates a hex represenation of the DER encoding of an IA5 string +sub encodeIA5String($$) { + my ($ia5String, $ia5String_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC:encodeIA5String, $ia5String_oc\n"; + } + return encodeTlv($oc, $DER_IA5STRING_TAG, $ia5String_oc, $ia5String); +} + +sub encodeImplicit($$$$$) { + my ($class, $constructed, $tagNumber, $implicit, $implicit_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC: implicit, $implicit_oc\n"; + } + return encodeTlv($oc, $tagNumber, $implicit_oc, $implicit, $constructed, $class); +} + +sub encodeBigInteger($$) { + my ($hexString, $oc) = @_; + + my $bin = toBin($hexString); + my $int = toHex($bin); + my $int_oc = length($bin); + + if ($DEBUG == 2) { + print "${TABS}:ENC: bigInteger, $int_oc\n"; + } + return encodeTlv($oc, $DER_INTEGER_TAG, $int_oc, $int) +} + +sub encodeInteger($$) { + my ($int, $oc) = @_; + + $int =~ s/\s//g; + + if (! ($int =~ /^-??\d+$/ || $int =~ /0x[0-9A-Fa-f]+/)) { + die "encodeInteger: Invalid argument: $int\n"; + } + + if ($int =~ s/^0x//) { + $int = hex; + } + + # Convert the integer to base 256 hex and find out how + # many octets were required + my $hex_integer_oc = 0; + my $hex_integer = ""; + + if ($int ne "") { + $hex_integer = encodeBase256($int, \$hex_integer_oc); + } + + if ($DEBUG == 2) { + print "${TABS}:ENC: integer, $hex_integer_oc\n"; + } + + return encodeTlv($oc, $DER_INTEGER_TAG, $hex_integer_oc, $hex_integer); +} + +sub encodeNull($) { + my ($oc) = @_; + return encodeTlv($oc, $DER_NULL_TAG, 0, ""); +} + +sub encodeOctetString($$$) { + my ($octetString, $octetString_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC: octetString, $octetString_oc\n"; + } + return encodeTlv($oc, $DER_OCTETSTRING_TAG, $octetString_oc, $octetString); +} + +sub encodeOid($$) { + my ($text, $oc) = @_; + + my @fields = split /\./, $text; + + if (! ($fields[0] >= 0 && $fields[0] <=2) ) { + die "Invalid OID: $text\n"; + } + if (! ($fields[1] >= 0 && $fields[1] <= 39) ) { + die "Invalid OID: $text"; + } + + my $oid = sprintf("%2.2x", (40 * $fields[0]) + $fields[1]); + my $oid_oc = 1; + shift @fields; + shift @fields; + + foreach (@fields) { + $oid .= ":" . encodeBase128($_, \$oid_oc); + } + + if ($DEBUG == 2) { + print "${TABS}:ENC:encodeOid, $oid_oc\n"; + } + return encodeTlv($oc, $DER_OID_TAG, $oid_oc, $oid); +} + +# Creates a hex represenation of the DER encoding of a PRINTABLE string +sub encodePrintableString($$$) { + my ($printableString, $printableString_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC:encodePrintableString, $printableString_oc\n"; + } + return encodeTlv($oc, $DER_PRINTABLESTRING_TAG, $printableString_oc, $printableString); +} + +sub encodeSet($$$) { + my ($set, $set_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC: set, $set_oc\n"; + } + return encodeTlv($oc, $DER_SET_TAG, $set_oc, $set, 1); +} + +sub encodeSequence($$$) { + my ($sequence, $sequence_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC: sequence, $sequence_oc\n"; + } + return encodeTlv($oc, $DER_SEQUENCE_TAG, $sequence_oc, $sequence, 1); +} + +sub encodeUtcTime($$$) { + my ($utcTime, $utcTime_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC: UTCTime, $utcTime_oc\n"; + } + return encodeTlv($oc, $DER_UTCTIME_TAG, $utcTime_oc, $utcTime); +} + +# Creates a hex represenation of the DER encoding of a UTF-8 string. +sub encodeUtf8String($$) { + my ($utf8String, $utf8String_oc, $oc) = @_; + + if ($DEBUG == 2) { + print "${TABS}:ENC:encodeUTF8String, $utf8String_oc\n"; + } + return encodeTlv($oc, $DER_UTF8STRING_TAG, $utf8String_oc, $utf8String); +} + +sub asciiToBmpString($$) { + my ($input, $oc) = @_; + + my $bmpString = ""; + my $input_len = length($input); + $$oc += $input_len * 2; + + for (my $i = 0; $i < $input_len; ++$i) { + my $hex_val = ord(substr($input, $i, 1)); + if ($bmpString ne "") { + $bmpString .= ":"; + } + $bmpString .= sprintf(":00:%2.2x", $hex_val); + } + return $bmpString; +} + +sub asciiToIA5String($$) { + my ($input, $oc) = @_; + + my $printableString = ""; + my $input_len = length($input); + $$oc += $input_len; + + for (my $i = 0; $i < $input_len; ++$i) { + my $hex_val = ord(substr($input, $i, 1)); + if ($printableString ne "") { + $printableString .= ":"; + } + $printableString .= sprintf(":%2.2x", $hex_val); + } + return $printableString; +} + +sub asciiToPrintableString($$) { + my ($input, $oc) = @_; + + my $ia5String = ""; + my $input_len = length($input); + $$oc += $input_len; + + for (my $i = 0; $i < $input_len; ++$i) { + my $hex_val = ord(substr($input, $i, 1)); + if ($ia5String ne "") { + $ia5String .= ":"; + } + $ia5String .= sprintf(":%2.2x", $hex_val); + } + return $ia5String; +} + +sub asciiToUtf8String($$) { + my ($input, $oc) = @_; + + my $utf8String = ""; + my $input_len = length($input); + $$oc += $input_len; + + for (my $i = 0; $i < $input_len; ++$i) { + my $hex_val = ord(substr($input, $i, 1)); + if ($utf8String ne "") { + $utf8String .= ":"; + } + $utf8String .= sprintf(":%2.2x", $hex_val); + } + return $utf8String; +} + +sub encodeBase128($$$) { + my ($num, $oc) = @_; + + my $base128 = ""; + $num = int($num); + my $base128_length = 0; + + while ($num > 0) { + my $hexoctet; + + if ($base128 eq "") { + $hexoctet = sprintf("%2.2x", $num & 0x7f); + } + else { + $hexoctet = sprintf("%2.2x", ($num & 0x7f) | 0x80); + } + + if ($base128 eq "") { + $base128 = $hexoctet; + } + else { + $base128 = "$hexoctet:$base128"; + } + + $num >>= 7; + $base128_length++; + } + if ($base128 eq "") { + $base128 = "00"; + $base128_length++; + } + + $$oc += $base128_length; + + if ($DEBUG == 2) { + print "${TABS}:ENC: base128, $base128_length, $$oc\n"; + } + + return $base128; +} + +# Return a hex represenation of the length using DER primitive (definate length encoding) +sub encodeLength($$) { + my ($num, $oc) = @_; + + if ($num < 128) { + # Number is < 128 so encode in short form + $$oc++; + return sprintf("%2.2x", $num); + } + else { + # Number >= 128 so encode in long form + my $length_oc = 0; + my $base256 = &encodeBase256($num, \$length_oc, 1); + if ($length_oc > 127) {die "Encoding overflow.";} + + $$oc += 1 + $length_oc; + + # Set the top bit of the length octet to indicate long form + return "" . sprintf("%2.2x", ($length_oc | 0x80)) . ":$base256"; + } +} + +# Convert an integer into an ascii hex representation in base 256 +# $num - the number to encode +# $octets - refernce to the octet count to increment +# $unsigned - assume unsigned +sub encodeBase256($$) { + my ($numIn, $oc, $unsigned) = @_; + + my $base256 = ""; + my $num = int($numIn); + + while ($num != 0) { + my $hexoctet = sprintf("%2.2x", $num & 0xFF); + if ($base256 ne "") { + $base256 = "$hexoctet:$base256"; + } + else { + $base256 = $hexoctet; + } + $num >>= 8; + $$oc++; + } + if ($base256 eq "") { + $base256 = "00"; + $$oc++; + } + + # If the integer is +ve and the MSB is 1 then padd with a leading zero + # octet otherwise it will look -ve + if ((! $unsigned) && $numIn > 0 && $base256 =~ /^:*[8ABCDEF]/i) { + $base256 = "00:$base256"; + $$oc++; + } + + # If the first octet is all ones and the msb of the next bit + # is also one then drop the first octet because negative + # numbers should not be padded + while ($base256 =~ s/^(FF:)([8ABCDEF][0-9A-F].*)/$2/i) { + $$oc--; + } + + return $base256; +} + +# Encode the Type +# Only low tag form is supported at the moment +sub encodeType($$;$$) { + my ($oc, $tagNumber, $constructed, $class) = @_; + + $tagNumber = hex($tagNumber); + + if ($tagNumber < 0 || $tagNumber > 30) { + die "encodeType: Currently, only low tag numbers (0 - 30) are supported."; + } + + if (! defined $class) { + $class = "UNIVERSAL"; + } + + $class = uc($class); + if (! isValidClass($class)) { + die "encodeType: invalid class \'$class\'"; + } + + # If the type is constructed then set bit 6 + if (defined $constructed && $constructed == 1) { + $tagNumber |= 0x20; + } + + if ($class eq $UNIVERSAL_CLASS) { + # do nothing, bits 7 and 8 are zero + } + elsif ($class eq $APPLICATION_CLASS) { + # set bit 7 + $tagNumber |= 0x40; + } + elsif ($class eq $CONTEXT_SPECIFIC_CLASS) { + # set bit 8 + $tagNumber |= 0x80; + } + elsif ($class eq $PRIVATE_CLASS) { + # set bits 7 and 8 + $tagNumber |= 0xC0; + } + $$oc++; + return sprintf("%2.2x", $tagNumber); +} + +sub encodeTlv($$$$;$$) { + my ($oc, $tag, $length, $value, $constructed, $class) = @_; + + if ($DEBUG == 3) { + print "${TABS}encodeTlv\n"; + print "${TABS}oc=$$oc\n"; + print "${TABS}tag=$tag\n"; + print "${TABS}length=$length\n"; + print "${TABS}value=$value\n"; + if (defined $constructed) { + print "${TABS}constructed=$constructed\n"; + } + if (defined $class) { + print "${TABS}class=$class\n"; + } + } + + my $hex; + $hex = encodeType($oc, $tag, $constructed, $class); + $hex .= ":" . encodeLength($length, $oc); + $$oc += $length; + $hex .= ":" . $value; + + if ($DEBUG == 3) { + print "${TABS}oc=$$oc\n"; + print "${TABS}encoding=$hex\n"; + print "${TABS}end\n"; + + toBin($hex); + } + return $hex; +} + +# increment debug tabbing level +sub nest() { + $TABS .= " "; +} + +# decrement debug tabbing level +sub leaveNest() { + $TABS =~ s/^...//; +} + +sub isValidClass($) { + my ($class) = @_; + + if (defined $class && + $class =~ /^(UNIVERSAL|APPLICATION|CONTEXT-SPECIFIC|PRIVATE)$/) { + return 1; + } + return 0; +} + +# Parse a DER field +sub getTlv($$$$$$) { + my ($input, $class, $constructed, $tag, $length, $value) = @_; + + my @hexOctets = split(/:+/,tidyHex($input)); + + if (scalar(@hexOctets) < 2) { + die "getTlv: too short"; + } + + my $type = hex(shift @hexOctets); + if (($type & 0xC0) == 0x00) { + # universal: bit 8 = 0, bit 7 = 0 + $$class = $UNIVERSAL_CLASS; + } + elsif (($type & 0xC0) == 0x40) { + # application: bit 8 = 0, bit 7 = 1 + $$class = $APPLICATION_CLASS; + } + elsif (($type & 0xC0) == 0x80) { + # application: bit 8 = 1, bit 7 = 0 + $$class = $CONTEXT_SPECIFIC_CLASS; + } + elsif (($type & 0xC0) == 0xC0) { + # application: bit 8 = 1, bit 7 = 1 + $$class = $PRIVATE_CLASS; + } + else { + die "getTlv: assert"; + } + + if ($type & 0x20) { + # constructed if bit 6 = 1 + $$constructed = 1; + } + else { + $$constructed = 0; + } + + # We assumme the tag number is in low form + # and just look at the bottom 5 hits + $$tag = $type & 0x1F; + + $$length = hex(shift @hexOctets); + if ($$length & 0x80) { + # long form + my $length_oc = $$length & 0x7F; + $$length = 0; + for (my $i = 0; $i < $length_oc; $i++) { + # length is encoded base 256 + $$length *= 256; + $$length += hex(shift @hexOctets); + } + } + else { + # short form + # don't do anything here, length is just bits 7 - 1 and + # we already know bit 8 is zero. + } + + $$value = ""; + foreach (@hexOctets) { + $$value .= ":$_"; + } + + if ($DEBUG == 3) { + print "${TABS} class=$$class\n"; + print "${TABS} constructed=$$constructed\n"; + print "${TABS} tag=$$tag\n"; + print "${TABS} length=$$length\n"; + } +} + +# parse an escaped (\) comma seperated argument string +# into an array +sub getArgs($) { + my ($argString) = @_; + my @args = (); + + while ($argString =~ /(^|.*?[^\\]),(.*)/ ) { + my $match = $1; + $argString = $2; + if ($match ne "") { + + # unescape + $match =~ s/(\\)([^\\])/$2/g; + push @args, $match; + } + } + if ($argString ne "") { + push @args, $argString; + } + return @args; +}