/*
    setkeys.c - NWDSGenerateObjectKeyPair implementation
    Copyright (C) 2000  Petr Vandrovec

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Revision history:

	1.00  2000, April 15		Petr Vandrovec <vandrove@vc.cvut.cz>
		Initial release.

	1.01  2000, April 17		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NWDSVerifyObjectPassword.
		
	1.02  2000, April 23		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NWDSChangeObjectPassword. Moved nds_login from ndslib
		here.
		
	1.03  2000, May 22		Petr Vandrovec <vandrove@vc.cvut.cz>
		Fixed NWDSChangeObjectPassword. It generated wrong key type.

	1.04  2000, July 7		Petr Vandrovec <vandrove@vc.cvut.cz>
		Modified nds_login to use unaliased name in NWDSSetKeys.

 */

#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>

#include "ncplib_i.h"
#include "nwnet_i.h"
#include "ndslib_i.h"
#include "ndscrypt.h"

static NWDSCCODE __NWDSSetKeysV0(
		NWCONN_HANDLE	conn,
		NWObjectID	objectID,
		Buf_T*		keyPair) {
	NW_FRAGMENT rq_frag[2];
	nuint8 rq_b[12];
	size_t len;
	
	rq_frag[1].fragAddr.ro = NWDSBufRetrieve(keyPair, &len);
	rq_frag[1].fragSize = len;
	DSET_LH(rq_b, 0, 0);
	DSET_HL(rq_b, 4, objectID);
	DSET_LH(rq_b, 8, len);
	rq_frag[0].fragAddr.ro = rq_b;
	rq_frag[0].fragSize = sizeof(rq_b);
	
	return NWCFragmentRequest(conn, DSV_SET_KEYS, 2, rq_frag, 0, NULL, NULL);
}

static NWDSCCODE __NWDSVerifyPasswordV1(
		NWCONN_HANDLE	conn,
		NWObjectID	objectID,
		Buf_T*		key) {
	NW_FRAGMENT rq_frag[2];
	nuint8 rq_b[12];
	size_t len;
	
	rq_frag[1].fragAddr.ro = NWDSBufRetrieve(key, &len);
	rq_frag[1].fragSize = len;
	DSET_LH(rq_b, 0, 1);
	DSET_HL(rq_b, 4, objectID);
	DSET_LH(rq_b, 8, len);
	rq_frag[0].fragAddr.ro = rq_b;
	rq_frag[0].fragSize = sizeof(rq_b);
	
	return NWCFragmentRequest(conn, DSV_VERIFY_PASSWORD, 2, rq_frag, 0, NULL, NULL);
}

static NWDSCCODE __NWDSChangePasswordV0(
		NWCONN_HANDLE	conn,
		NWObjectID	objectID,
		Buf_T*		key) {
	NW_FRAGMENT rq_frag[2];
	nuint8 rq_b[12];
	size_t len;
	
	rq_frag[1].fragAddr.ro = NWDSBufRetrieve(key, &len);
	rq_frag[1].fragSize = len;
	DSET_LH(rq_b, 0, 0);
	DSET_HL(rq_b, 4, objectID);
	DSET_LH(rq_b, 8, len);
	rq_frag[0].fragAddr.ro = rq_b;
	rq_frag[0].fragSize = sizeof(rq_b);
	
	return NWCFragmentRequest(conn, DSV_CHANGE_PASSWORD, 2, rq_frag, 0, NULL, NULL);
}

static NWDSCCODE __NWDSGenerateObjectKeyPairStep1(
		NWDSContextHandle	ctx, 
		const NWDSChar*		objectName,
		NWCONN_HANDLE*		conn,
		NWObjectID*		objectID,
		NWObjectID*		oldPseudoID,
		nuint8			rndseed[4],
		nuint8**		connPublicKey
) {
	NWDSCCODE err;
	NWDSContextHandle unictx;
	NWCONN_HANDLE lconn;

	*connPublicKey = NULL;

	err = NWDSResolveName2DR(ctx, objectName, DS_RESOLVE_DEREF_ALIASES | DS_RESOLVE_WRITEABLE,
		&lconn, objectID);
	if (err)
		return err;
	err = __NWDSBeginLoginV0(lconn, *objectID, oldPseudoID, rndseed);
	if (err)
		goto err_free_conn;
	err = NWDSDuplicateContextHandle(ctx, &unictx);
	if (err)
		goto err_free_conn;
	unictx->dck.flags = DCV_TYPELESS_NAMES | DCV_XLATE_STRINGS;
	err = NWDSSetContext(unictx, DCK_LOCAL_CHARSET, "WCHAR_T//");
	if (err)
		goto err_free_ctx_conn;
	err = __NWDSGetPublicKeyFromConnection(unictx, lconn, connPublicKey);
	NWDSFreeContext(unictx);
	if (err)
		goto err_free_conn;
	*conn = lconn;
	return 0;
err_free_ctx_conn:;
	NWDSFreeContext(unictx);
err_free_conn:;
	NWCCCloseConn(*conn);
	return err;
}

static NWDSCCODE __NWDSGenerateObjectKeyPairStep2(
		NWCONN_HANDLE		conn,
		NWObjectID		objectID,
		nuint8			rndseed[4],
		NWObjectID		pseudoID, 
		size_t			pwdLen,
		const nuint8*		pwdHash, 
		const nuint8*		connPublicKey
) {
	nuint8		privkey[4096];
	size_t		privkey_len;
	nuint8		pubkey[4096];
	size_t		pubkey_len;
	nuint8		ec_privkey[4096];
	size_t		ec_privkey_len;
	NWDSCCODE	err;
	Buf_T		*ib;
	Buf_T		*ob;

	pubkey_len = sizeof(pubkey);
	privkey_len = sizeof(privkey);
	/* 0, NULL, 0 == keylen, exp, exp_len */
	err = __NWGenerateKeyPair(0, NULL, 0, pubkey, &pubkey_len, privkey, &privkey_len);
	if (err)
		return err;
	ec_privkey_len = sizeof(ec_privkey);
	err = __NWEncryptWithSK(pwdHash, 16, privkey, privkey_len, ec_privkey, &ec_privkey_len);
	memset(privkey, 0, sizeof(privkey));
	if (err)
		goto err_free_pub;
	err = NWDSAllocBuf(ec_privkey_len + pubkey_len + 0x2C + 8 + 8, &ib);
	if (err)
		goto err_free_ec_pub;
	NWDSBufPutLE32(ib, 0);
	NWDSBufPut(ib, rndseed, 4);
	DSET_HL(NWDSBufPutPtr(ib, 4), 0, pseudoID);
	NWDSBufPutLE32(ib, pwdLen);
	NWDSBufPutBuffer(ib, pwdHash, 16);
	NWDSBufPutBuffer(ib, ec_privkey, ec_privkey_len);
	NWDSBufPutBuffer(ib, pubkey, pubkey_len);

	memset(ec_privkey, 0, sizeof(ec_privkey));
	memset(pubkey, 0, sizeof(pubkey));

	err = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &ob);
	if (err) {
		NWDSClearFreeBuf(ib);
		return err;
	}
	
	err = rsa_crypt2(connPublicKey, ib, ob);
	NWDSClearFreeBuf(ib);
	if (!err) {
		err = __NWDSSetKeysV0(conn, objectID, ob);
	}
	NWDSClearFreeBuf(ob);
	return err;
err_free_ec_pub:;
	memset(ec_privkey, 0, sizeof(ec_privkey));
err_free_pub:;
	memset(pubkey, 0, sizeof(pubkey));
	return err;
}

static NWDSCCODE __NWDSGenerateObjectKeyPairStep3(
		NWCONN_HANDLE		conn,
		nuint8*			connPublicKey
) {
	free(connPublicKey);
	NWCCCloseConn(conn);
	return 0;
}

static NWDSCCODE __NWDSGenerateObjectKeyPair(
		NWDSContextHandle	ctx,
		const NWDSChar*		objectName,
		const char*		objectPassword
) {
	NWCONN_HANDLE	conn;
	NWObjectID	objectID;
	NWObjectID	pseudoID;
	nuint8		rndseed[4];
	nuint8*		serverPublicKey;
	NWDSCCODE	err;
	size_t		pwdLen;
	nuint8		pwdHash[16];
	
	err = __NWDSGenerateObjectKeyPairStep1(ctx, objectName,
			&conn, &objectID, &pseudoID, rndseed,
			&serverPublicKey);
	if (err)
		return err;
	/* compute key... */
	pwdLen = strlen(objectPassword);
	/* BEWARE! other NWDS*Password functions do NOT uppercase password */
	{
		nuint8 newPwd[pwdLen + 1];
		size_t i;
		nuint8 tmpID[4];
		
		for (i = 0; i < pwdLen; i++)
			newPwd[i] = toupper(*((const nuint8*)objectPassword)++);
		newPwd[i] = 0;
		
		DSET_HL(tmpID, 0, pseudoID);
		shuffle(tmpID, newPwd, pwdLen, pwdHash);
	}
	err = __NWDSGenerateObjectKeyPairStep2(conn, objectID,
			rndseed, pseudoID, pwdLen, pwdHash,
			serverPublicKey);
	__NWDSGenerateObjectKeyPairStep3(conn, serverPublicKey);
	return err;
}

NWDSCCODE NWDSGenerateObjectKeyPair2(
		NWDSContextHandle	ctx, 
		const NWDSChar*		objectName, 
		NWObjectID		pseudoID, 
		size_t			pwdLen,
		const nuint8*		pwdHash, 
	UNUSED(	nflag32			optionsFlag)
) {
	NWCONN_HANDLE	conn;
	NWObjectID	objectID;
	NWObjectID	pseudoID2;
	nuint8		rndseed[4];
	nuint8*		serverPublicKey;
	NWDSCCODE	err;
	
	err = __NWDSGenerateObjectKeyPairStep1(ctx, objectName,
			&conn, &objectID, &pseudoID2, rndseed,
			&serverPublicKey);
	if (err)
		return err;
	err = __NWDSGenerateObjectKeyPairStep2(conn, objectID,
			rndseed, pseudoID, pwdLen, pwdHash,
			serverPublicKey);
	__NWDSGenerateObjectKeyPairStep3(conn, serverPublicKey);
	return err;
}

NWDSCCODE NWDSGenerateObjectKeyPair(
		NWDSContextHandle ctx, 
		const NWDSChar* objectName, 
		const char* objectPassword, 
		nflag32 optionsFlag
) {
	switch (optionsFlag) {
		case 0:
		case NDS_PASSWORD:
			return __NWDSGenerateObjectKeyPair(ctx, objectName, objectPassword);
		default:
			return NWE_PARAM_INVALID;
	}
}

static NWDSCCODE __NWDSVerifyObjectPasswordStep2(
		NWCONN_HANDLE		conn,
		NWObjectID		objectID,
		nuint8			rndseed[4],
		const nuint8*		pwdHash, 
		const nuint8*		connPublicKey
) {
	nuint8*		ec_privkey;
	size_t		ec_privkey_len;
	NWDSCCODE	err;
	Buf_T		*ib;
	Buf_T		*ob;

	err = NWDSAllocBuf(64, &ib);
	if (err)
		return err;
	ec_privkey = NWDSBufPutPtrLen(ib, &ec_privkey_len);
	err = __NWEncryptWithSK(pwdHash, 16, rndseed, 4, ec_privkey, &ec_privkey_len);
	if (err)
		return err;
	NWDSBufPutSkip(ib, ec_privkey_len);
	
	err = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &ob);
	if (err) {
		NWDSClearFreeBuf(ib);
		return err;
	}
	
	err = rsa_crypt2(connPublicKey, ib, ob);
	NWDSClearFreeBuf(ib);
	if (!err) {
		err = __NWDSVerifyPasswordV1(conn, objectID, ob);
	}
	NWDSClearFreeBuf(ob);
	return err;
}

NWDSCCODE NWDSVerifyObjectPassword(NWDSContextHandle ctx, nflag32 flags, 
		const NWDSChar* objectName, const char* objectPassword
) {
	NWCONN_HANDLE	conn;
	NWObjectID	objectID;
	NWObjectID	pseudoID;
	nuint8		rndseed[4];
	nuint8*		serverPublicKey;
	NWDSCCODE	err;
	size_t		pwdLen;
	nuint8		pwdHash[16];
	
	err = __NWDSGenerateObjectKeyPairStep1(ctx, objectName,
			&conn, &objectID, &pseudoID, rndseed,
			&serverPublicKey);
	if (err)
		return err;
	/* compute key... */
	pwdLen = strlen(objectPassword);
	{
		nuint8 tmpID[4];
		
		DSET_HL(tmpID, 0, pseudoID);
		shuffle(tmpID, objectPassword, pwdLen, pwdHash);
	}
	err = __NWDSVerifyObjectPasswordStep2(conn, objectID, rndseed, 
			pwdHash, serverPublicKey);
	__NWDSGenerateObjectKeyPairStep3(conn, serverPublicKey);
	return err;
}


static NWDSCCODE __NWDSChangeObjectPasswordStep2(
		NWCONN_HANDLE conn,
		NWObjectID objectID,
		NWObjectID pseudoID,
		nuint8 rndseed[4],
		nuint8 *connPublicKey,
		const char* oldPassword,
		const char* newPassword
) {
	nuint8* privateKey;
	size_t privateKeyLen;
	nuint8 ec_newpwd[4096];
	size_t ec_newpwd_len;
	nuint8 oldPwdHash[16];
	nuint8 newPwdHash[16];
	NWDSCCODE err;
	NWDSCCODE gpk_err;
	size_t di;
	Buf_T *ib;
	Buf_T *ob;
	nuint8 tmpID[4];
	
	DSET_HL(tmpID, 0, pseudoID);
	shuffle(tmpID, oldPassword, strlen(oldPassword), oldPwdHash);
	shuffle(tmpID, newPassword, strlen(newPassword), newPwdHash);
	
	err = __NWDSGetPrivateKey(conn, connPublicKey, rndseed, objectID, oldPwdHash, NULL, &privateKey, &privateKeyLen);
	if (err != 0 && err != NWE_PASSWORD_EXPIRED)
		goto quit;
	if (privateKeyLen < 10) {
		err = ERR_INVALID_SERVER_RESPONSE;
		goto free_privkey;
	}
	DSET_LH(privateKey, 2, 1);
	WSET_LH(privateKey, 6, 2);

	gpk_err = err;
	ec_newpwd_len = sizeof(ec_newpwd);
	err = __NWEncryptWithSK(newPwdHash, 16, privateKey + 2, privateKeyLen - 2, ec_newpwd, &ec_newpwd_len);
	if (err)
		goto free_privkey;
	di = ec_newpwd_len + 0x34;
	
	err = NWDSAllocBuf(di + 8, &ib);
	if (err)
		goto free_privkey_ecnewpwd;
	NWDSBufPut(ib, rndseed, 4);
	NWDSBufPutBuffer(ib, oldPwdHash, 16);
	NWDSBufPutLE32(ib, strlen(newPassword));
	NWDSBufPutBuffer(ib, newPwdHash, 16);
	NWDSBufPutBuffer(ib, ec_newpwd, ec_newpwd_len);
	
	err = NWDSAllocBuf(di + 256, &ob);
	if (err)
		goto free_privkey_ecnewpwd_ib;
	err = rsa_crypt2(connPublicKey, ib, ob);
	if (err)
		goto free_privkey_ecnewpwd_ib_ob;
	err = __NWDSChangePasswordV0(conn, objectID, ob);
	if (!err)
		err = gpk_err;
free_privkey_ecnewpwd_ib_ob:;
	NWDSClearFreeBuf(ob);
free_privkey_ecnewpwd_ib:;
	NWDSClearFreeBuf(ib);
free_privkey_ecnewpwd:;
	memset(ec_newpwd, 0, sizeof(ec_newpwd));
free_privkey:;
	memset(privateKey, 0, privateKeyLen);
	free(privateKey);
quit:;
	memset(oldPwdHash, 0, sizeof(oldPwdHash));
	memset(newPwdHash, 0, sizeof(newPwdHash));
	return err;
}

static NWDSCCODE __NWDSChangeObjectPassword(
		NWDSContextHandle ctx,
		const NWDSChar* objectName,
		const char* oldPassword,
		const char* newPassword) {
	NWCONN_HANDLE conn;
	NWObjectID objectID;
	NWObjectID pseudoID;
	nuint8 rndseed[4];
	nuint8* connPublicKey;
	NWDSCCODE err;
	
	err = __NWDSGenerateObjectKeyPairStep1(ctx, objectName,
			&conn, &objectID, &pseudoID, rndseed,
			&connPublicKey);
	if (err)
		return err;
	err =  __NWDSChangeObjectPasswordStep2(conn, objectID, pseudoID, rndseed, connPublicKey, oldPassword, newPassword);
	__NWDSGenerateObjectKeyPairStep3(conn, connPublicKey);
	return err;
}

NWDSCCODE NWDSChangeObjectPassword(
		NWDSContextHandle ctx,
		nflag32 flags,
		const NWDSChar* objectName,
		const char* oldPassword,
		const char* newPassword
) {
	switch (flags) {
		case 0:
		case NDS_PASSWORD:
			return __NWDSChangeObjectPassword(ctx, objectName, oldPassword, newPassword);
		default:
			return NWE_PARAM_INVALID;
	}
}


NWDSCCODE nds_login(
		NWDSContextHandle ctx,
		const NWDSChar* objectName,
		const char *objectPassword) {
	NWCONN_HANDLE	conn;
	NWObjectID	objectID;
	NWObjectID	pseudoID;
	nuint8		rndseed[4];
	nuint8*		serverPublicKey;
	NWDSCCODE	err;
	size_t		pwdLen;
	nuint8		pwdHash[16];
	nuint8*		privKey;
	size_t		privKeyLen;
	nuint8		logindata[8];
	NWDSCCODE	grace_err;
	wchar_t		unaliasedName[MAX_DN_CHARS + 1];
	NWDSContextHandle wctx;

	err = __NWDSGenerateObjectKeyPairStep1(ctx, objectName,
			&conn, &objectID, &pseudoID, rndseed,
			&serverPublicKey);
	if (err)
		return err;
	err = NWDSDuplicateContextHandleInt(ctx, &wctx);
	if (err) {
		__NWDSGenerateObjectKeyPairStep3(conn, serverPublicKey);
		return err;
	}
	err = NWDSMapIDToName(wctx, conn, objectID, (NWDSChar*)unaliasedName);
	if (err) {
		NWDSFreeContext(wctx);
		__NWDSGenerateObjectKeyPairStep3(conn, serverPublicKey);
		return err;
	}
	/* compute key... */
	pwdLen = strlen(objectPassword);
	/* BEWARE! other NWDS*Password functions do NOT uppercase password */
	{
		nuint8 newPwd[pwdLen + 1];
		size_t i;
		nuint8 tmpID[4];
		
		for (i = 0; i < pwdLen; i++)
			newPwd[i] = toupper(*((const nuint8*)objectPassword)++);
		newPwd[i] = 0;
		
		DSET_HL(tmpID, 0, pseudoID);
		shuffle(tmpID, newPwd, pwdLen, pwdHash);
	}
	
	grace_err = __NWDSGetPrivateKey(conn, serverPublicKey,
	    rndseed, objectID, pwdHash, logindata, &privKey, &privKeyLen);
	__NWDSGenerateObjectKeyPairStep3(conn, serverPublicKey);
	if (!grace_err || grace_err == NWE_PASSWORD_EXPIRED) {
		err = NWDSSetKeys(wctx, logindata, unaliasedName, privKey, privKeyLen);
		memset(privKey, 0, privKeyLen);
		free(privKey);
		if (err)
			goto err_exit;
	}
	err = grace_err;
err_exit:
	NWDSFreeContext(wctx);
	memset(logindata, 0, sizeof(logindata));
	return err; 
}

