/***************************************************************
 *
 * Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/


#ifndef CONDOR_AUTH_PASSWD
#define CONDOR_AUTH_PASSWD

#include "condor_auth.h"        // Condor_Auth_Base class is defined here
#include "condor_crypt_3des.h"

#include <string>
#include <set>


#define AUTH_PW_KEY_LEN 256
// Symmetric key strength, in bytes.  Only used in V2
#define AUTH_PW_KEY_STRENGTH (256/8)
#define AUTH_PW_MAX_NAME_LEN 1024

#define AUTH_PW_ERROR          -1
#define AUTH_PW_A_OK           0
#define AUTH_PW_ABORT          1

class CondorError;
namespace classad {
	class ExprTree;
	class ClassAd;
}
namespace jwt {
	template<typename json_traits>
	class decoded_jwt;
	namespace traits {
		struct kazuho_picojson;
	}
}

/** A class to implement the AKEP2 protocol for password-based authentication.
 
  Refer to _The Handbook of Applied Cryptography_, Menezes, et. al.,
  fifth printing, p. 499, section 12.20, for more info on the AKEP2 protocol used
  here.  The protocol uses 1 1/2 round trips for a total of three
  messages.  ISBN number 0-8493-8523-7.  Note this book is available online
  at URL http://www.cacr.math.uwaterloo.ca/hac/.

  An overview of the protocol follows:

    SUMMARY: A and B exchange 3 messages to derive a session key W.

    RESULT: mutual entity authentication, and implicit key authentication of W.

        1. Setup: The pool sets up a long-term symmetric master key, M.  kdfM is a keyed
        derivation function from key K.  seed_a and seed_b are fixed, compile-time seeds.
        For some text message t, the keys K and K' are generated by

          K = kdfM(seed_a, t)
          K' = kdfM(seed_b, t)

        The client (A) is assumed to either have access to M or to both K and K'.  In the
        latter case, (A) does not have access to M and B may reject the key K if text t
        indicates the derived key K / K' is too old or invalid.
        hK is a MAC (keyed hash function) used for entity
        authentication.  kdfK' is a pseudorandom permutation or keyed
        one-way function used for key derivation.

    2. Protocol messages.  Define  T = (B, A, rA, rB).
        A -> B : A, t, rA               (1)
        A <- B : T, hK(T)               (2)
        A -> B : (A, rB), hK(A, rB)     (3)
        W = kdfK'(rB)

    3. Protocol actions. Perform the following steps for each shared key required.

    (a) A selects and sends to B a random number rA.

    (b) B selects a random number rB and sends to A the values (B,A,t,rA,rB), along
    with a MAC over these quantities generated using h with key K.

    (c) Upon receiving message (2), A checks the identities are proper, that the
    rA received matches that in (1), and verifies the MAC.

    (d) A then sends to B the values (A,rB), along with a MAC thereon.

    (e) Upon receiving (3), B verifies that the MAC is correct, and that the received
    value rB matches that sent earlier.

    (f) Both A and B compute the session key as W = kdfK'(rB).

  As a result of this exchange, A and B have:
    1.  Demonstrated possession of keys K and K' (which were derived from the master key M, but
        does not demonstrate either have possession of M).
    2.  From using a strong KDF, we assume this demonstrates text t was used to derive keys
        K and K'.
    3.  Does not transfer any key in the clear.
    4.  Generated a mutual session key W, generated from K'.
  Note that this protocol does NOT guarantee forward secrecy: anyone with a record of the
  conversation that, at some point in the future, acquires key M (or K and K') may also
  generate the session key W.
 */


class Condor_Auth_Passwd : public Condor_Auth_Base {
 public:

	///  Ctor
	Condor_Auth_Passwd(ReliSock * sock, int version);

	/// Dtor
	~Condor_Auth_Passwd();

	int authenticate(const char * remoteHost, CondorError* errstack, bool non_blocking);
	int authenticate_continue(CondorError*, bool);

	/** Tells whether the authenticator is in a valid state, i.e.
		authentication has been successfully performed sometime in the past,
		whether or not calls to wrap/unwrap are permitted, etc.
		@returns 1 if state is valid, 0 if invalid
	*/
	int isValid() const;

	/** Encrypt the input buffer into an output buffer.  The output
		buffer must be dealloated by the caller with free().
		@returns 1 on success, 0 on failure
	*/
	int wrap(const char* input, int input_len, char*& output, int& output_len);

	/** Decrypt the input buffer into an output buffer.  The output
		buffer must be dealloated by the caller with free().
		@returns 1 on success, 0 on failure
	*/    
	int unwrap(const char* input, int input_len, char*& output, int& output_len);

	/** Generate a token.  Note the "ident" should be a unique ID for use in the audit
	    log trail; typically, this is something like Sock::getUniqueId() */
	static bool generate_token(const std::string &id, const std::string &key_id,
		const std::vector<std::string> &authz_list, long lifetime, std::string &token,
		int ident, CondorError *err);

	/** Metadata needed prior to starting authorization */
	static bool preauth_metadata(classad::ClassAd &ad);

	void set_remote_issuer(const std::string &issuer) {m_server_issuer = issuer;}
	void set_remote_keys(const std::vector<std::string> &keys);

	/** Determines if any credentials (tokens, master passwords) are available;
	 *  if not, then there's no point in trying auth'n. */
	static bool should_try_auth();

	/** Retry search for tokens */
	static void retry_token_search() {m_should_search_for_tokens = true;}

	/** Check and generate a POOL token signing key if not already done */
	static void create_pool_signing_key_if_needed();
	static void create_signing_key(const std::string & file, const char * name);

		/** Simple wrapper around the OpenSSL HKDF function. */
	static int hkdf(const unsigned char *sk, size_t sk_len,
		const unsigned char *salt, size_t salt_len,
		const unsigned char *label, size_t label_len,
		unsigned char *result, size_t result_len);

 private:

	enum CondorAuthPasswordState {
		ServerRec1 = 100,
		ServerRec2
	};

	enum CondorAuthPasswordRetval {
		Fail = 0,
		Success,
		WouldBlock,
		Continue
	};


		/// This structure stores the message referred to as T in the book.
	struct msg_t_buf {
		char *a;              // The name of the client.
		std::string a_token;  // The text used to derive the keys from the master.
		char *b;              // The name of the server.
		unsigned char *ra;    // The random string produced by the client.
		unsigned char *rb;    // The random string produced by the server.
		unsigned char *hkt;   // The result of hashing T with K.
		unsigned int hkt_len; 
		unsigned char *hk;    // The result of hashing a,rb with K
		unsigned int hk_len;
	};

		/// This structure stores the shared key and its derivatives.
	struct sk_buf {
		char *shared_key;          // The shared key.
		int len;                   // Of the shared key.
		unsigned char *ka;         // K
		int ka_len;
		unsigned char *kb;         // K'
		int kb_len;
	};

		/** This stores the shared session key produced as output of
			the protocol. 
		*/
	Condor_Crypt_3des* m_crypto;
	Condor_Crypto_State* m_crypto_state;

		/** Produce the shared key object from raw key material.
		 */
	bool setupCrypto(const unsigned char* key, const int keylen);

		/** Lookup a shared key based on the correspondent's
			information.  
		*/
	static char* fetchPoolSharedKey(int & len);
	static char* fetchTokenSharedKey(const std::string & token, int & len);
		/** Lookup the legacy pool password 
		*/
	char* fetchPoolPassword(int & len);

		/** Return a malloc-ed string "user@domain" that represents who we
		 	are.
		 */
	char* fetchLogin();

		/** Encrypt data using the session key.  Called by wrap. 
		 */
	bool encrypt(const unsigned char *  input,
                 int              input_len, 
                 unsigned char *& output, 
                 int&             output_len);
	
		/* Decrypt data based on the session key.  Called by unwrap.
		 */
	bool decrypt(const unsigned char *  input,
                 int              input_len, 
                 unsigned char *& output, 
                 int&             output_len);

		/** Decrypt or encrypt based on first argument. 
		 */
	bool encrypt_or_decrypt(bool want_encrypt,
							const unsigned char *  input,
							int              input_len,
							unsigned char *& output,
							int&             output_len);
	
		/** The protocol in the book uses long term symmetric keys K
			and K'.  "These should differ but need not be
			independent."  We derive these keys from the shared key by
			hmacing it with two different keys.  This relies on the
			difficulty of calculating the input to an hmac given its
			output, even when the key is known.

			Note that in protocol v2, the symmetric keys are derived
			from both the compile-time seed and a fixed text.
		*/
	bool setup_shared_keys(struct sk_buf *sk, const std::string &init_token);

		/** The "seed" consists of the two keys used to hmac the
			shared key.  Each byte in the seed keys is specified in
			this function.  
		*/
	static void setup_seed(unsigned char *ka, unsigned char *kb);

		/** This is just a wrapper for the openssl HMAC function. */
	void hmac(unsigned char *sk, int sk_len,
			  unsigned char *key, int key_len,
			  unsigned char *result, unsigned int *result_len);

		/** Fill the structure with NULLS. */
	void init_sk(struct sk_buf *sk);
		/** Free the elements of the structure, explicitly zeroing key
			material. 
		*/
	void destroy_sk(struct sk_buf *sk);
		/** Fill the stucture with NULLS. */
	void init_t_buf(struct msg_t_buf *t);
		/** Free the elements of the structure. */
	void destroy_t_buf(struct msg_t_buf *t);
		/** Generate the random string. */
	int client_gen_rand_ra(struct msg_t_buf *t);
		/** Generate the random string. */
	int server_gen_rand_rb(struct msg_t_buf *t);
		/** Client side of the first protocol mesage. */
	int client_send_one(int client_status, struct msg_t_buf *t_client);
		/** Client side of the first protocol message. */
	int client_receive(int *client_status, struct msg_t_buf *t_server);
		/** Client check of second message validity. */
	int client_check_t_validity(struct msg_t_buf *t_client, 
								struct msg_t_buf *t_server, 
								struct sk_buf *sk);
		/** Client side of the third message. */
	int client_send_two(int client_status, 
						struct msg_t_buf *t_client, 
						struct sk_buf *sk);
//	void set_session_key(struct msg_t_buf *t_client);
		/** Calculate the hash on message three. */
	bool calculate_hk(struct msg_t_buf *t_buf, struct sk_buf *sk);
		/** Calculate the hash on message two. */
	bool calculate_hkt(struct msg_t_buf *t_buf, struct sk_buf *sk);
		/** Server side of the first message. */
	int server_receive_one(int *server_status, struct msg_t_buf *t_client);
		/** Server side of the second message. */
	int server_send(int server_status, 
					struct msg_t_buf *t_server, struct sk_buf *sk);
		/** Server check of third message hash. */
	int server_check_hk_validity(struct msg_t_buf *t_client, 
								 struct msg_t_buf *t_server, 
								 struct sk_buf *sk);
		/** Server side of the third message. */
	int server_receive_two(int *server_status, struct msg_t_buf *t_client);
		/** Both sides call this when complete to set the session key. */
	bool set_session_key(struct msg_t_buf *t_buf, struct sk_buf *sk);

		/** Desired key strength for symmetric keys. */
	int key_strength_bytes() const;
	static int key_strength_bytes_v2() {return AUTH_PW_KEY_STRENGTH;}

		// state machine drivers
	CondorAuthPasswordRetval doServerRec1(CondorError* errstack, bool non_blocking);
	CondorAuthPasswordRetval doServerRec2(CondorError* errstack, bool non_blocking);

		/** Check to see if a given token has been revoked; returns
		    true if the token is revoked. */
	bool isTokenRevoked(const jwt::decoded_jwt<jwt::traits::kazuho_picojson> &jwt);

	int m_client_status;
	int m_server_status;
	int m_ret_value;

	struct msg_t_buf m_t_client;
	struct msg_t_buf m_t_server;

		// In order to create the shared keys used by the client and
		// the server in this protocol, we take a single shared secret
		// and hmac it twice with two different keys.  The original
		// password buffer and the two generated keys are stored in
		// the sk_buf structure.
	struct sk_buf m_sk;

		// Version of the protocol.  In version 1, the text parameter (t)
		// was non-existent.
	int m_version;

		// Derived key, loaded from the client's optional identity file.
	unsigned char *m_k;
	unsigned char *m_k_prime;
	size_t m_k_len;
	size_t m_k_prime_len;
	std::string m_keyfile_token;
	std::string m_server_issuer;
	std::set<std::string> m_server_keys;
	std::unique_ptr<classad::ExprTree> m_token_revocation_expr;

	CondorAuthPasswordState m_state;

		// Status of token auth.
	static bool m_should_search_for_tokens; // Should we search for tokens?
	static bool m_tokens_avail; // Are any tokens known to be available?
};

#endif /* CONDOR_AUTH_PASSWD */
