/*
 * Generic SSH public-key handling operations. In particular,
 * reading of SSH public-key files, and also the generic `sign'
 * operation for ssh2 (which checks the type of the key and
 * dispatches to the appropriate key-type specific function).
 */


#include <stdio.h>
#include <string.h>

#include "macros.h"
#include "sshmd5.h"
#include "sshrsa.h"
#include "ssh.h"
#include "cipher.h"
#include "xmalloc.h"

#define rsa_signature "SSH PRIVATE KEY FILE FORMAT 1.1\n"

static int loadrsakey_main(FILE * fp, RSAKey *key, int pub_only,
			   char **commentptr, char *passphrase)
{
    unsigned char buf[16384];
    unsigned char keybuf[16];
    int len;
    int i, j, ciphertype;
    int ret = 0;
    char *comment;

    /* Slurp the whole file (minus the header) into a buffer. */
    len = fread(buf, 1, sizeof(buf), fp);
    fclose(fp);
    if (len < 0 || len == sizeof(buf))
	goto end;		       /* file too big or not read */

    i = 0;

    /*
     * A zero byte. (The signature includes a terminating NUL.)
     */
    if (len - i < 1 || buf[i] != 0)
	goto end;
    i++;

    /* One byte giving encryption type, and one reserved uint32. */
    if (len - i < 1)
	goto end;
    ciphertype = buf[i];
    if (ciphertype != 0 && ciphertype != SSH_CIPHER_3DES)
	goto end;
    i++;
    if (len - i < 4)
	goto end;		       /* reserved field not present */
    if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0
	|| buf[i + 3] != 0) goto end;  /* reserved field nonzero, panic! */
    i += 4;

    /* Now the serious stuff. An ordinary SSH 1 public key. */
    i += makekey(buf + i, key, NULL, 1);
    if (len - i < 0)
	goto end;		       /* overran */

    if (pub_only) {
	ret = 1;
	goto end;
    }

    /* Next, the comment field. */
    j = GET_32BIT_MSB_FIRST(buf + i);
    i += 4;
    if (len - i < j)
	goto end;
    comment = xmalloc(j + 1);
    if (comment) {
	memcpy(comment, buf + i, j);
	comment[j] = '\0';
    }
    i += j;
    if (commentptr)
	*commentptr = comment;
    if (key)
	key->comment = comment;
    if (!key) {
	return ciphertype != 0;
    }

    /*
     * Decrypt remainder of buffer.
     */
    if (ciphertype) {
	MD5Init();
	MD5Update(passphrase, strlen(passphrase));
	MD5Final(keybuf);
	des3_decrypt_pubkey(keybuf, buf + i, (len - i + 7) & ~7);
	memset(keybuf, 0, sizeof(keybuf));	/* burn the evidence */
    }

    /*
     * We are now in the secret part of the key. The first four
     * bytes should be of the form a, b, a, b.
     */
    if (len - i < 4)
	goto end;
    if (buf[i] != buf[i + 2] || buf[i + 1] != buf[i + 3]) {
	ret = -1;
	goto end;
    }
    i += 4;

    /*
     * After that, we have one further bignum which is our
     * decryption exponent, and then the three auxiliary values
     * (iqmp, q, p).
     */
    i += makeprivate(buf + i, key);
    if (len - i < 0)
	goto end;
    i += ssh1_read_bignum(buf + i, &key->iqmp);
    if (len - i < 0)
	goto end;
    i += ssh1_read_bignum(buf + i, &key->q);
    if (len - i < 0)
	goto end;
    i += ssh1_read_bignum(buf + i, &key->p);
    if (len - i < 0)
	goto end;

    if (!rsa_verify(key)) {
	freersakey(key);
	ret = 0;
    } else
	ret = 1;

  end:
    memset(buf, 0, sizeof(buf));       /* burn the evidence */
    return ret;
}

int loadrsakey(char *filename, RSAKey *key, char *passphrase)
{
    FILE *fp;
    unsigned char buf[64];

    fp = fopen(filename, "rb");
    if (!fp)
	return 0;		       /* doesn't even exist */

    /*
     * Read the first line of the file and see if it's a v1 private
     * key file.
     */
    if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
	return loadrsakey_main(fp, key, 0, NULL, passphrase);
    }

    /*
     * Otherwise, we have nothing. Return empty-handed.
     */
    fclose(fp);
    return 0;
}

/*
 * See whether an RSA key is encrypted. Return its comment field as
 * well.
 */
int rsakey_encrypted(char *filename, char **comment)
{
    FILE *fp;
    unsigned char buf[64];

    fp = fopen(filename, "rb");
    if (!fp)
	return 0;		       /* doesn't even exist */

    /*
     * Read the first line of the file and see if it's a v1 private
     * key file.
     */
    if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
	return loadrsakey_main(fp, NULL, 0, comment, NULL);
    }
    fclose(fp);
    return 0;			       /* wasn't the right kind of file */
}

/* ----------------------------------------------------------------------
 * A function to determine the type of a private key file. Returns
 * 0 on failure, 1 or 2 on success.
 */
int key_type(char *filename)
{
    FILE *fp;
    char buf[32];
    const char putty2_sig[] = "PuTTY-User-Key-File-";
    const char sshcom_sig[] = "---- BEGIN SSH2 ENCRYPTED PRIVAT";
    const char openssh_sig[] = "-----BEGIN ";
    int i;

    fp = fopen(filename, "r");
    if (!fp)
	return SSH_KEYTYPE_UNOPENABLE;
    i = fread(buf, 1, sizeof(buf), fp);
    fclose(fp);
    if (i < 0)
	return SSH_KEYTYPE_UNOPENABLE;
    if (i < 32)
	return SSH_KEYTYPE_UNKNOWN;
    if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1))
	return SSH_KEYTYPE_SSH1;
    if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1))
	return SSH_KEYTYPE_SSH2;
    if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1))
	return SSH_KEYTYPE_OPENSSH;
    if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1))
	return SSH_KEYTYPE_SSHCOM;
    return SSH_KEYTYPE_UNKNOWN;	       /* unrecognised or EOF */
}

/*
 * Convert the type word to a string, for `wrong type' error
 * messages.
 */
char *key_type_to_str(int type)
{
    switch (type) {
	case SSH_KEYTYPE_UNOPENABLE:
		return "unable to open file";
	case SSH_KEYTYPE_UNKNOWN:
		return "not a private key";
	case SSH_KEYTYPE_SSH1:
		return "SSH1 private key";
	case SSH_KEYTYPE_SSH2:
		return "PuTTY SSH2 private key";
	case SSH_KEYTYPE_OPENSSH:
		return "OpenSSH SSH2 private key";
	case SSH_KEYTYPE_SSHCOM:
		return "ssh.com SSH2 private key";
	default:
		return "INTERNAL ERROR";
    }
}
