/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <malloc.h>

/*
*	These headers are part of the library, *not* the system.
*/

#include "crypto/nanu.h"
#include "crypto/rsa.h"

/****************************************************************************/
/*						Definitions											*/
/****************************************************************************/

#define	MAXLEN			16384		// Max length of the keyfile
#define	MODE_PRIV		1			// Private key must be extracted
#define	MODE_PUBL		2			// Public key must be extracted

/*
*	Structure to hold a big number
*/

typedef struct {

	unsigned char	*data;			// Data representing a number, big-endian
	unsigned int	dlen;			// Length of the number, in bytes

} NUMBER;

/****************************************************************************/
/*						Prototypes											*/
/****************************************************************************/

/*
*	Note that most of these functions return pointers to objects
*	that are statically allocated. That is BAD PRACTICE and you
*	should avoid it in general. I did that because on the one hand
*	I was lazy to call malloc()/free() and on the other I knew that
*	in this particular case it was safe to do it.
*/

static	int		CommandLine( int argc, const char **argv );
static	char	*LoadKeyfile( void );
static	char	*SkipHeaders( char * );
static	int		DecodeBase64( char *, char * );
static	NUMBER	*DerExtract( char *str, int len );
static	NUMBER	*SshExtract( char *str, int rem );
static	int		DerGetLen( char *str, int len, int *ret );
static	int		DerGetInt( char *str, int len, NUMBER *num );
static	void	DumpKeys( NUMBER *num );
static	void	DumpNumber( NUMBER *num, const char *str );

/****************************************************************************/
/*						Data												*/
/****************************************************************************/

/*
*	These data objects are not actually used. They are here so that the
*	compiler generates an error if the definitions of the structures in
*	the nanu.h or rsa.h header files change in an incompatible way.
*/

const BC_NANU_CONS	err1 = {

	.size = 3,
	.data = { 0xfffffffu, 0x77777777, 0 }
};

const BC_RSA_PUBLIC_KEY err2 = {

	.n = &err1,
	.e = &err1
};

const BC_RSA_PRIVATE_KEY err3 = {

	.p = &err1,
	.q = &err1,
	.P = &err1,
	.Q = &err1,
	.I = &err1
};

/*
*	Globals
*/

static int			mode = 0;			// Extraction mode
static int			publ = 0;			// Input is an SSH public key file
static const char	*stem = "rsa";		// Default object name stem

/*
*	Usage string
*/

static const char	*usage =
	"\n%s - a program to process RSA keys\n\n"
	"This program can be used to get the key structures used by the RSA\n"
	"functions in the embedded ARM crypto library. First you have to\n"
	"generate an RSA key using a tool which is part of the SSH package:\n\n"
	"ssh-keygen -b <klen> -t rsa -f <some_file>\n\n"
	"where <klen> is the length of the key in bits and <some_file> is a\n"
	"file where the key will be stored. The minimum key length is 768 bits.\n"
	"The ARM library functions accept keys from 256 to 8192 bits in 256 bit\n"
	"increments. You can add comments to the file using the -C switch, but\n"
	"do *not* use a passphrase for the file. The SSH package is installed\n"
	"by default on any modern unix-ish system (Linux, BSD, MacOS-X etc.)\n\n"
	"To use the key with the embedded crypto library, you have to use this\n"
	"program to convert the keyfile you've just generated to a C file that\n"
	"you can compile into your project and pass the objects defined in it\n"
	"to the crypto library functions.\n\n"
	"This program reads the keyfile from the standard input and writes the\n"
	"resulting C file to the standard output. There are two command line\n"
	"switches. One, which is mandatory, is one of \"-public\", \"-private\"\n"
	"or \"-both\". It makes the program to extract the public key, the\n"
	"private key or both keys, respectively, from the keyfile.\n"
	"If you specify the extraction of the public key only, then the program\n"
	"also accepts the SSH public key file as input.\n"
	"The second switch is optional and has the format of \"-stem <name>\"\n"
	"where <name> is any string that is a valid C identifier. This\n"
	"identifier will be the stem of the C objects containing the key data.\n"
	"The public key object will have \"_public_key\" appended to the stem\n"
	"and the private key will get a \"_private_key\" suffix. If this command\n"
	"line switch is not given, the default stem is \"rsa\".\n"
	"If the program receives an incorrect comman line, it issues an error\n"
	"message and exits with an exit code of 2.\n"
	"If the program detects a format error in the keyfile, it issues an\n"
	"error message on the standard error and exits, with an exit code of 1.\n"
	"If the transformation was successful, it writes the result to the\n"
	"standard output and the exit code will be 0.\n\n";

/****************************************************************************/
/*						Public functons										*/
/****************************************************************************/

/*
*	Entry point of the program
*	~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

int		main( int argc, const char *argv[] )
{
int		len;
char	*ssh, *str;
NUMBER	*num;

	// Process the options, load the file, skip the headers and decode the
	// content, extract the keys and print the result.

	if ( ! CommandLine( argc, argv ) )

		return( 1 );

	if ( ! ( str = LoadKeyfile() ) )

		return( 2 );

	if ( ! ( ssh = SkipHeaders( str ) ) )

		return( 2 );

	if ( ! ( len = DecodeBase64( str, ssh ) ) )

		return( 2 );

	if ( ! ( num = publ ? SshExtract( str, len ) : DerExtract( str, len ) ) )

		return( 2 );

	DumpKeys( num );
	return( 0 );
}

/****************************************************************************/
/*						Local functons										*/
/****************************************************************************/

/*
*	Processes the command line switches
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		CommandLine( int argc, const char **argv )
{
int		i;

	if ( argc < 2 ) {

		printf( usage, argv[ 0 ] );
		return( 0 );
	}

	for ( i = 1 ; i < argc ; i++ ) {

		if ( ! strcmp( argv[ i ], "-private" ) ) {

			mode |= MODE_PRIV;
			continue;
		}

		if ( ! strcmp( argv[ i ], "-public" ) ) {

			mode |= MODE_PUBL;
			continue;
		}

		if ( ! strcmp( argv[ i ], "-both" ) ) {

			mode |= MODE_PUBL | MODE_PRIV;
			continue;
		}

		if ( ! strcmp( argv[ i ], "-stem" ) ) {

			if ( i + 1 >= argc ) {

				fprintf( stderr, "The -stem option needs a name\n" );
				return( 0 );
			}

			stem = argv[ ++i ];
			continue;
		}

		fprintf( stderr, "Unknown switch %s\n", argv[ i ] );
		fprintf( stderr, "Start the program without arguments to get help\n" );
		return( 0 );
	}

	if ( ! mode ) {

		fprintf( stderr,
				 "One of -private, -public or -both must be specified\n" );
		return( 0 );
	}

	return( 1 );
}

/*
*	Loads the entire keyfile from the standard input
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	char	*LoadKeyfile( void )
{
static char	buff[ MAXLEN + 1 ];
int			i, c;

	for ( i = 0 ; i < MAXLEN ; i++ ) {

		if ( ( c = getchar() ) == EOF ) {

			buff[ i ] = 0;
			return( buff );
		}
		else {

			buff[ i ] = c;
		}
	}

	fprintf( stderr, "Key is too long\n" );
	return( NULL );
}

/*
*	Skips the headers in the keyfile
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It also recognises if we're dealing with a public key file and
*	sets the 'publ' global accordingly. The function returns NULL
*	if the file is not recognised or a pointer to the base-64 encoded
*	content if the headers were fine.
*/

static	char	*SkipHeaders( char *str )
{
char		*tmp;
const char	*pkb = "ssh-rsa ";

	// We have the content of the file as a string. The first line
	// should contain the text "BEGIN RSA PRIVATE KEY".

	if ( ! ( tmp = strstr( str, "BEGIN RSA PRIVATE KEY" ) ) ) {

		// OK, this is not a private key file. If the user
		// wants only the public key, we check if it is an
		// SSH public key file.

		if ( ! ( mode & MODE_PRIV ) && str == strstr( str, pkb ) ) {

			// Yes, it is a public key file and only the public key
			// is needed. Make note of that and return.

			publ = 1;
			return( str + strlen( pkb ) );
		}

		fprintf( stderr, "Start marker not found\n" );
		return( NULL );
	}

	// It seems to be an SSH private key file. Find the end of the line.

	for ( str = tmp ; *str && *str++ != '\n' ; );

	// As long as we have a ':' anywhere, it is a comment line, because
	// base-64 encoding does not contain ':'.

	while ( ( tmp = strchr( str, ':' ) ) ) {

		// Find the newline after the colon

		if ( ! ( tmp = strchr( tmp, '\n' ) ) ) {

			fprintf( stderr, "Private key file format error\n" );
			return( NULL );
		}

		str = tmp + 1;
	}

	return( str );
}

/*
*	Decodes the base-64 encoded key parts
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	src is the source string, dst is the destination string. They can
*	overlap, as long as dst is less than or equal to src. The function
*	returns the length of the decoded result, or 0 if there was an error.
*/

static	int		DecodeBase64( char *dst, char *src )
{
unsigned char	a, b, c;
int				l, s, k;

	a = b = c = 0;

	for ( l = s = 0 ;; s++ ) {

		k = *src++;

		switch ( k ) {

			case 'A' ... 'Z':

				k -= 'A';
				break;

			case 'a' ... 'z':

				k -= 'a' - 26;
				break;

			case '0' ... '9':

				k -= '0' - 52;
				break;

			case '+':

				k = 62;
				break;

			case '/':

				k = 63;
				break;

			case '\n':

				s--;
				continue;

			case '=':

				if ( s == 2 || s == 3 ) {

					k = 0;
					l--;
					break;
				}
				else {

					goto error;
				}

			case '-':

				if ( s-- ) goto error;
				break;

			case ' ':

				if ( s ) goto error;
				return( l );

			default:

				goto error;
		}

		switch( s ) {

			case 0:

				a = k << 2;
				continue;

			case 1:

				a |= k >> 4;
				b  = k << 4;
				continue;

			case 2:

				b |= k >> 2;
				c  = k << 6;
				continue;

			case 3:

				c |= k;
				s  = -1;
				*dst++ = a;
				*dst++ = b;
				*dst++ = c;
				l += 3;
				continue;
		}

		break;
	}

	return( l );

	error: {

		fprintf( stderr,
				 "The key file Violates the base-64 encoding rules.\n" );
		return( 0 );
	}
}

/*
*	Extracts the ssh-rsa public key structure
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns a pointer to a 3-element array of NUMBER structures or
*	NULL if it cound not extract the key info. See the comments in
*	the next function for the data in the array.
*/

static	NUMBER	*SshExtract( char *str, int rem )
{
int				len;
static NUMBER	numbers[ 3 ];

	// First, we have to fetch a big-endian 32-bit word, which is the length
	// of the type identifier.

	if ( ( rem -= 4 ) < 0 ) goto error;

	len  = ( *str++ & 255 ) << 24;
	len |= ( *str++ & 255 ) << 16;
	len |= ( *str++ & 255 ) <<  8;
	len |= ( *str++ & 255 ) <<  0;

	// Then we have to skip that many bytes, we simply don't care about
	// the type identifier. If is, by the way, the string "ssh-rsa".

	rem -= len;
	str += len;

	// Read an other 32-bit word, the length of the public exponent

	if ( ( rem -= 4 ) < 0 ) goto error;

	len  = ( *str++ & 255 ) << 24;
	len |= ( *str++ & 255 ) << 16;
	len |= ( *str++ & 255 ) <<  8;
	len |= ( *str++ & 255 ) <<  0;

	// Now we have to process len bytes for the public exponent.
	// If the number has a leading 0, we skip it.

	if ( rem < len ) goto error;

	if ( len && ! *str ) {

		len--;
		str++;
	}

	numbers[ 2 ].dlen = len;
	numbers[ 2 ].data = (void *) str;

	rem -= len;
	str += len;

	// Get the next 32-bit word, the length of the modulus.

	if ( ( rem -= 4 ) < 0 ) goto error;

	len  = ( *str++ & 255 ) << 24;
	len |= ( *str++ & 255 ) << 16;
	len |= ( *str++ & 255 ) <<  8;
	len |= ( *str++ & 255 ) <<  0;

	// Now we have to process len bytes for the modulus
	// If the number has a leading 0, we skip it.

	if ( rem < len ) goto error;

	if ( len && ! *str ) {

		len--;
		str++;
	}

	numbers[ 1 ].dlen = len;
	numbers[ 1 ].data = (void *) str;

	return( numbers );

	error: {

		fprintf( stderr, "SSH public key file format error\n" );
		return( NULL );
	}
}

/*
*	Extracts the ASN.1 DER encoded RSA key structure
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns a pointer to a 9-element array of NUMBER structures or
*	NULL if it cound not extract the key info.
*/

static	NUMBER	*DerExtract( char *str, int len )
{
static NUMBER	numbers[ 9 ];
int				tmp, seq;

	// The RSA private key structure is encoded using the Direct Encoding
	// Rules (DER) as per the ASN.1 standard. The key is a SEQUENCE containing
	// 9 INTEGERs. They are, (using the usual notation for the RSA numbers):
	// - -			version number (should be 0)
	// - n			the modulus
	// - e			the public exponent
	// - d			the private exponent
	// - p			the larger prime
	// - q			the smaller prime
	// - dP			the private exponent modulo p-1
	// - dQ			the private exponent modulo q-1
	// - Q-1 mod p	the modular inverse of q over p
	//
	// A SEQUENCE is the marker, the length and the content.
	// For the SEQEUNCE the marker is a byte of 0x30. The length is either
	// a single byte which is less than 128, in which case it is the length
	// of the sequence in bytes. Otherwise, it is a byte which is greater
	// than 128 and this byte - 128 is the number of bytes following this
	// byte that contain the length of the sequence as a big-endian number.
	// That is, an 5 byte object would be 05 xx xx xx xx xx but an 516 byte
	// object is encoded as 0x82 0x02 0x01 xx xx xx ... xx xx where the
	// 0x82 indicates that the length is 2 bytes, 0x02 0x04 are the big-endian
	// representation of 0x204 (==516) followed by 516 data bytes (xx).
	//
	// An INTEGER is the marker, the length and the value itself. The marker
	// is 0x02. The length is the same as for the sequence. The number itself
	// is a 2's complement signed integer, in big-endian byte order. That is,
	// if you have a number of which the most significant byte has its most
	// significant bit set (which is the case with the RSA modulus), then you
	// must prepend a 0x00 byte in front of it (and increase the length by 1)
	// to make it a positive number.
	//
	// The standard allows additional information to be added after the
	// 9th number, therefore if we manage to extract the 9 numbers the
	// program does not signal an error if there are leftovers in the
	// SEQUENCE. However, the length of the SEQUENCE must match the length
	// of the data we decoded from the base-64 representation.

	if ( *str++ != 0x30 ) {

		// This is not a DER SEQUENCE

		fprintf( stderr, "Key SEQUENCE not found\n" );
		return( NULL );
	}

	if ( ! ( tmp = DerGetLen( str, len, &seq ) ) ) {

		// Length format error

		return( NULL );
	}

	if ( len != tmp + seq + 1 ) {

		// The length of the sequence does not match the
		// length of the string that we decoded

		fprintf( stderr, "Key SEQUENCE length mismatch\n" );
		return( NULL );
	}

	str += tmp;

	// Sequence marker processed, now get the integers

	for ( len = 0 ; len < 9 ; len++ ) {

		if ( ! ( tmp = DerGetInt( str, seq, numbers + len ) ) ) {

			// Could not decode the integer

			return( NULL );
		}

		str += tmp;
		seq -= tmp;
	}

	// We extracted the 9 integers, success.

	return( numbers );
}

/*
*	Extract the length field of an ASN.1 DER object
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		DerGetLen( char *str, int len, int *ret )
{
int		t, i, n;

	// A length must be at least 1 byte long

	if ( ! len ) goto error;

	// Get the length byte

	t = *str++ & 255;

	if ( t < 128 ) {

		// This is a single-byte length

		*ret = t;
		return( 1 );
	}
	else {

		n = t & 127;
		t = 0;

		if ( n > 2 || n > len - 1 ) {

			// The object is more that 64K long, impossible since
			// we rejected anything over 8K. Alternatively, we don't
			// have enough bytes to extract the length.

			goto error;
		}

		// Get the length, in big-endian format

		for ( i = 0 ; i < n ; i++ )

			t = ( t << 8 ) + ( *str++ & 255 );

		*ret = t;
		return( n + 1 );
	}

	error: {

		fprintf( stderr, "Key DER object length error\n" );
		return( 0 );
	}
}

/*
*	Extracts an INTEGER ASN.1 DER object
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		DerGetInt( char *str, int len, NUMBER *num )
{
int		off, tmp;

	// We can't extract from an empty string

	if ( ! len-- ) goto error;

	// Check if we can find the INTEGER marker

	if ( *str++ != 0x02 ) goto error;

	// Get the length of the object and check if we can decode it

	if ( ! ( off = DerGetLen( str, len, &tmp ) ) ) return( 0 );
	if ( len < off + tmp ) goto error;

	str += off;

	// If the length is larger than 1 and the first byte of the big-endian
	// byte stream is 0, then we omit that byte and decrement the length by
	// 1. Otherwise, we keep it as it is.

	if ( tmp > 1 && *str == 0 ) {

		num->data = (unsigned char *) str + 1;
		num->dlen = tmp - 1;
	}
	else {

		num->data = (unsigned char *) str;
		num->dlen = tmp;
	}

	return( 1 + off + tmp );

	error: {

		fprintf( stderr, "Key INTEGER object error\n" );
		return( 0 );
	}
}

/*
*	Outputs the key data
*	~~~~~~~~~~~~~~~~~~~~
*/

static	void	DumpKeys( NUMBER *num )
{
	printf( "/*\n" );
	printf( "*\tThis file contains RSA key objects.\n" );
	printf( "*\tIt was automatically generated and should NOT be edited.\n" );
	printf( "*/\n\n" );
	printf( "#include <crypto/rsa.h>\n\n" );

	if ( mode & MODE_PUBL ) {

		DumpNumber( num + 1, "n1" );
		DumpNumber( num + 2, "n2" );

		printf( "const BC_RSA_PUBLIC_KEY %s_public_key =\n{\n", stem );
		printf( "\t.n = &n1,\n\t.e = &n2\n};\n\n" );
	}

	if ( mode & MODE_PRIV ) {

		DumpNumber( num + 4, "n4" );
		DumpNumber( num + 5, "n5" );
		DumpNumber( num + 6, "n6" );
		DumpNumber( num + 7, "n7" );
		DumpNumber( num + 8, "n8" );

		printf( "const BC_RSA_PRIVATE_KEY %s_private_key =\n{\n", stem );
		printf( "\t.p = &n4,\n\t.q = &n5,\n\t.P = &n6,\n" );
		printf( "\t.Q = &n7,\n\t.I = &n8\n};\n" );
	}
}

/*
*	Outputs a single number
*	~~~~~~~~~~~~~~~~~~~~~~~
*/

static	void	DumpNumber( NUMBER *num, const char *str )
{
int				len, obj, cnt;
unsigned char	*ptr;

	printf( "static const BC_NANU_CONS %s =\n{\n", str );
	printf( "\t.size = %d,\n",  ( num->dlen + 3 ) >> 2 );
	printf( "\t.data =\n\t{" );

	len = num->dlen;
	ptr = num->data + len - 4;
	obj = 0;

	while ( len > 0 ) {

		if ( obj == 0 )

			printf( "\n\t\t0x" );
		else
			printf( "0x" );

		for ( cnt = 0 ; cnt < 4 ; cnt++ ) {

			if ( len + cnt >= 4 )

				printf( "%02x", ptr[ cnt ] );
			else
				printf( "00" );
		}

		printf( ", " );

		ptr -= 4;
		len -= 4;

		if ( ++obj == 5 ) obj = 0;
	}

	printf( "\n\t}\n};\n\n" );
}

