/* $Id: userkey.c,v 1.8 1999/06/25 05:33:44 levitte Exp $ */

#include "gnu_extras.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#if defined(__DECC) || defined(__GNUC__)
#include <unistd.h>
#endif
#include <stsdef.h>
#include <ssdef.h>
#include <starlet.h>
#include <lib$routines.h>
#ifdef __GNUC__
#define __DECC
#endif
#include <openssl/md5.h>
#include <openssl/rsa.h>
#include <openssl/rand.h>
#ifdef __GNUC__
#undef __DECC
#endif
#include "fish.h"
#include "ssh.h"
#include "erf.h"
#include "userkey.h"
#include "buffer.h"
#include "util.h"
#include "fishmsg.h"

void userkey_init_static(userkey *uk)
{
    memset(uk, 0, sizeof(userkey));
}

userkey *userkey_init(void)
{
    userkey *uk = xmalloc(sizeof(userkey));

    userkey_init_static(uk);

    return uk;
}

void userkey_destroy_static(userkey *uk)
{
    if (uk == 0) return;
    if (uk->key) RSA_free(uk->key);
    if (uk->comment) xfree(uk->comment);
    if (uk->encrypted_data) xfree(uk->encrypted_data);
}

void userkey_destroy(userkey *uk)
{
    if (uk == 0) return;
    xfree(uk);

    return;
}

int userkey_decode_buffer(userkey *uk, general_buffer *buf)
{
    int status = SS$_NORMAL;
    char id_buffer[] = AUTHFILE_ID_STRING;
    unsigned long dummy = strlen(id_buffer);
    general_buffer_reader gbr;

    bufread_init_static(&gbr, buf);
    userkey_init_static(uk);

    uk->key = RSA_new();
    if (!bufread_bytes(&gbr, id_buffer, dummy+1)
	|| id_buffer[dummy] != '\0') goto pubformerr;

    if (strcmp(id_buffer, AUTHFILE_ID_STRING) != 0) goto pubbadid;

    if (!bufread_bytes(&gbr, &uk->cipher_type, 1)) goto pubformerr;

    if (!bufread_int(&gbr, &dummy)
	|| !bufread_int(&gbr, &uk->bits)
	|| !bufread_bn(&gbr, &uk->key->n)
	|| !bufread_bn(&gbr, &uk->key->e)
	|| !bufread_strdup(&gbr, &uk->comment)) goto pubformerr;

    uk->encrypted_data = xmalloc(uk->encrypted_len = bufread_remaining(&gbr));
    if (!bufread_bytes(&gbr, uk->encrypted_data, uk->encrypted_len))
	goto pubformerr;

    return SS$_NORMAL;

 pubcleanup:
    userkey_destroy(uk);
    return status;

 pubformerr:
    status = FISH_M_UKFORMERR;
    goto pubcleanup;

 pubbadid:
    status = FISH_M_UKBADID;
    goto pubcleanup;
}

int userkey_encode_buffer(userkey *uk, general_buffer **buf)
{
    *buf = buf_init(0);
    
    if (uk->encrypted_data == 0)
	goto privnodata;

    if (!buf_append_bytes(*buf, AUTHFILE_ID_STRING,
			  strlen(AUTHFILE_ID_STRING)+1)
	|| !buf_append_bytes(*buf, &uk->cipher_type, 1)
	|| !buf_append_int(*buf, 0)
	|| !buf_append_int(*buf, uk->bits)
	|| !buf_append_bn(*buf, uk->key->n)
	|| !buf_append_bn(*buf, uk->key->e)
	|| !buf_append_chars(*buf, uk->comment)
	|| !buf_append_bytes(*buf, uk->encrypted_data, uk->encrypted_len))
	goto privmemfull;

    return SS$_NORMAL;

 privnodata:
    buf_destroy(*buf);
    return FISH_M_UKNODATA;

 privmemfull:
    buf_destroy(*buf);
    return FISH_M_MEMFULL;
}

int userkey_decrypt_private_part(userkey *uk, general_buffer *passphrase)
{
    int status = SS$_NORMAL;
    unsigned char *decrypted = 0;
    general_buffer decryptbuf;
    general_buffer_reader decryptreader;

    MD5_CTX md;
    unsigned char digest[16];
    ssh_state internal_state;
    Erf internal_erf = NULL;
    void *internal_erfp = NULL;

    unsigned char check1_1;
    unsigned char check1_2;
    unsigned char check2_1;
    unsigned char check2_2;

#if 0
    if (uk->cipher_type != SSH_CIPHER_NONE
	&& ((1 << uk->cipher_type) & cipher_mask) == 0) goto privbadcipher;
#endif

    decrypted = xmalloc(uk->encrypted_len);
    buf_init_static(&decryptbuf, decrypted, uk->encrypted_len);

    /* Generate a MD5 checksum from the passphrase */
    MD5_Init(&md);
    MD5_Update(&md, buf_chars_noadjust(passphrase), buf_amount(passphrase));
    MD5_Final(digest, &md);

    /* Build a 32 byte string from the digest, with padding */
    memset(internal_state.sesskey, 0, sizeof(internal_state.sesskey));
    memcpy(internal_state.sesskey, digest, 16);

    internal_state.info = "RSA key cipher";
    ssh_crypto_setup(uk->cipher_type, &internal_state, 16,
		     internal_erf, internal_erfp);
    internal_state.info = 0;
    if (internal_state.decrypt) {
	(internal_state.decrypt)(uk->encrypted_data,
				 decrypted,
				 uk->encrypted_len,
				 internal_state.decryptstate);
    }
    buf_adjust_amount_by(&decryptbuf, uk->encrypted_len);

    bufread_init_static(&decryptreader, &decryptbuf);

    if (!bufread_bytes(&decryptreader, &check1_1, 1)
	|| !bufread_bytes(&decryptreader, &check1_2, 1)
	|| !bufread_bytes(&decryptreader, &check2_1, 1)
	|| !bufread_bytes(&decryptreader, &check2_2, 1)) goto privformerr;

    if (check1_1 != check2_1 || check1_2 != check2_2) goto privbadpass;

    if (!bufread_bn(&decryptreader, &uk->key->d)
	|| !bufread_bn(&decryptreader, &uk->key->iqmp)
	|| !bufread_bn(&decryptreader, &uk->key->p)
	|| !bufread_bn(&decryptreader, &uk->key->q)) goto privformerr;

    xfree(decrypted);

    return status;

 privcleanup:
    userkey_destroy_static(uk);
 privcleanup2:
    if (decrypted) xfree(decrypted);
    return status;

 privformerr:
    status = FISH_M_UKFORMERR;
    goto privcleanup;

#if 0 /* Uhmmm, removed right now... */
 privbadcipher:
    status = FISH_M_UKBADCIPHER;
    goto privcleanup;
#endif

 privbadpass:
    status = FISH_M_UKBADPASS;
    goto privcleanup2;
}

int userkey_encrypt_private_part(userkey *uk, general_buffer *passphrase)
{
    unsigned char byte1, byte2;

    ssh_state internal_state;
    MD5_CTX md;
    unsigned char digest[16];

    general_buffer *encrypted = buf_init(0);

    if (uk->encrypted_data != 0)
	goto privnodata;

    MD5_Init(&md);
    MD5_Update(&md, buf_bytes(passphrase), buf_amount(passphrase));
    MD5_Final(digest, &md);
    
    /* Build a 32 byte string from the digest, with padding */
    memset(internal_state.sesskey, 0, sizeof(internal_state.sesskey));
    memcpy(internal_state.sesskey, digest, 16);

    internal_state.info = "RSA key cipher";
    ssh_crypto_setup(uk->cipher_type, &internal_state, 16, NULL, NULL);
    internal_state.info = 0;

    /* Build private part */
    RAND_bytes(&byte1, 1);
    RAND_bytes(&byte2, 1);
    buf_append_bytes(encrypted, &byte1, 1);
    buf_append_bytes(encrypted, &byte2, 1);
    buf_append_bytes(encrypted, &byte1, 1);
    buf_append_bytes(encrypted, &byte2, 1);
    buf_append_bn(encrypted, uk->key->d);
    buf_append_bn(encrypted, uk->key->iqmp);
    buf_append_bn(encrypted, uk->key->p);
    buf_append_bn(encrypted, uk->key->q);
    if ((buf_amount(encrypted) % 8) != 0)
	buf_append_bytes(encrypted, "\0\0\0\0\0\0\0\0",
			 8 - (buf_amount(encrypted) % 8));

    uk->encrypted_len = buf_amount(encrypted);
    uk->encrypted_data = xmalloc(uk->encrypted_len);

    if (internal_state.encrypt) {
	(internal_state.encrypt)(buf_chars_noadjust(encrypted),
				 uk->encrypted_data,
				 uk->encrypted_len,
				 internal_state.encryptstate);
    } else {
	memcpy(uk->encrypted_data, buf_chars_noadjust(encrypted),
	       uk->encrypted_len);
    }

    buf_destroy(encrypted);

    return SS$_NORMAL;

 privnodata:
    buf_destroy(encrypted);
    return FISH_M_UKNODATA;
}

int userkey_read_keyfile(userkey *uk, char *filename, unsigned long uic)
{
    int status;
    struct stat statb;
    int keyfh;
    unsigned long n;
    int save_errno;
    general_buffer *tmpbuf;

    if (stat(filename, &statb) < 0) {
	return FISH_M_UKNOFILE;
    } else if (statb.st_uid != uic) {
	char errbuf[1024];

	ssh_F_uknotowner(filename);
	return FISH_M_UKNOTOWNER | 0x10000000;
    }

    if (!(tmpbuf = buf_init(statb.st_size))) {
	return FISH_M_MEMFULL;
    }

    keyfh = open(filename, O_RDONLY, 0);
    while((n = read(keyfh, buf_chars_noadjust(tmpbuf) + buf_amount(tmpbuf),
		    statb.st_size - buf_amount(tmpbuf))) > 0)
	buf_adjust_amount_by(tmpbuf, n);
    save_errno = errno;
    close(keyfh);

    if (buf_amount(tmpbuf) != statb.st_size) {
	lib$signal(FISH_M_UKSHORTIDFILE, 3, filename,
		   buf_amount(tmpbuf), statb.st_size);
	return FISH_M_UKSHORTIDFILE | 0x10000000;
    }

    status = userkey_decode_buffer(uk, tmpbuf);
    if (!$VMS_STATUS_SUCCESS(status)) {
	buf_destroy(tmpbuf);
	return status;
    }

    return SS$_NORMAL;
}

/* Emacs local variables

Local variables:
eval: (set-c-style "BSD")
end:

*/
