PKE

Files CCore/inc/net/PSecCore.h CCore/src/net/PSecCore.cpp

Files CCore/inc/net/PKE.h CCore/src/net/PKE.cpp

PKE is a key exchange and authentication protocol intended to be used with the PSec. Two peers communicate using PKE to confirm peers identity and to generate a session key. This key can be used further to open a PSec communication channel. CCore provides an implementation of this protocol enclosed in the namespace PSec.

Utilities

PKError

PKError is a error code type. It is used to identify a particular error situation during the PKE negotiation.


/* enum PKError */

enum PKError : uint32
 {
  PKENoError = 0 ,
  PKError_NoClientID,
  PKError_Exhausted,
  PKError_NoAlgo,
  PKError_NoAccess,
  PKError_NoLimit
 };

const char * GetTextDesc(PKError error);

PKENoError means no error.

PKError_NoClientID means the server has not recognized the provided client identity.

PKError_Exhausted means the server cannot proceed due to a lack of some processing resource.

PKError_NoAlgo means there is no common algorithm selection between the client and the server.

PKError_NoAccess means the client access has been denied.

PKError_NoLimit means there is too many negotiations on the server side.

Prime Key

A prime key is an abstract hash function. This key is used to identify a peer and participates in the session key generation process.


/* type PrimeKeyPtr */

using PrimeKeyPtr = OwnPtr<AbstractHashFunc> ;

To carry a prime key an OwnPtr to an abstract hash function is used. This type is not copyable, but movable.

AbstractClientID

AbstractClientID is an abstract class. It is used to present a client identity ("user name"). A client identity consists of the range of octets. The meaning of these octets is transparent. It can be a plain user name of a hash digest of a plain user name. It is transported to the server to start the negotiation process.


/* struct AbstractClientID */

struct AbstractClientID : MemBase_nocopy
 {
  virtual ~AbstractClientID() {}

  virtual uint8 getLen() const =0;

  virtual void getID(uint8 buf[ /* Len */ ]) const =0;
 };

/* type ClientIDPtr */

using ClientIDPtr = OwnPtr<AbstractClientID> ;

getLen() returns the id length.

getID() copies the id octets to the provided buffer of the proper length.

SessionKeyParam

SessionKeyParam is a set of session key parameters.


/* consts */

const uint16 MinKeySetLen     = 5 ;
const uint16 DefaultKeySetLen = 10 ;
const uint16 MaxKeySetLen     = 100 ;

const uint32 MinTTL     = 360 ;   // 6 min
const uint32 DefaultTTL = 3600 ;  // 1 hour
const uint32 MaxTTL     = 36000 ; // 10 hour

const uint32 MinUTL     = 1'000'000 ;
const uint32 DefaultUTL = 100'000'000 ;
const uint32 MaxUTL     = 1'000'000'000 ;

/* struct SessionKeyParam */

struct SessionKeyParam
 {
  uint16 keyset_len = DefaultKeySetLen ;
  uint32 ttl = DefaultTTL ;
  uint32 utl = DefaultUTL ;

  // constructors

  SessionKeyParam() noexcept {}

  SessionKeyParam(uint16 keyset_len_,uint32 ttl_,uint32 utl_) : keyset_len(keyset_len_),ttl(ttl_),utl(utl_) {}

  ....
 };

keyset_len is the keyset length. It must be in the range [MinKeySetLen,MaxKeySetLen]. The default value is DefaultKeySetLen.

ttl is the time-to-live key parameter. It must be in the range [MinTTL,MaxTTL]. The default value is DefaultTTL.

utl is the key traffic limit. It must be in the range [MinUTL,MaxUTL]. The default value is DefaultUTL.

CryptAlgoSelect

To generate a session key three cryptography algorithms must be selected. Both server and client propose lists of such triples and negotiate selection.


/* enum CryptID */

enum CryptID : uint8
 {
  CryptID_AES128,
  CryptID_AES192,
  CryptID_AES256

  // may be continued
 };

const char * GetTextDesc(CryptID crypt_id);

class ProxySet_CryptID : public StringSetScan
 {
  public:

   ProxySet_CryptID();

   void map(CryptID &ret) const;
 };

/* enum HashID */

enum HashID : uint8
 {
  HashID_SHA1,
  HashID_SHA224,
  HashID_SHA256,
  HashID_SHA384,
  HashID_SHA512

  // may be continued
 };

const char * GetTextDesc(HashID hash_id);

class ProxySet_HashID : public StringSetScan
 {
  public:

   ProxySet_HashID();

   void map(HashID &ret) const;
 };

/* enum DHGroupID */

enum DHGroupID : uint8
 {
  DHGroupID_I,
  DHGroupID_II

  // may be continued
 };

const char * GetTextDesc(DHGroupID dhg_id);

class ProxySet_DHGroupID : public StringSetScan
 {
  public:

   ProxySet_DHGroupID();

   void map(DHGroupID &ret) const;
 };

/* struct CryptAlgoSelect */

struct CryptAlgoSelect
 {
  uint8 crypt_id = CryptID_AES256 ;
  uint8 hash_id = HashID_SHA256 ;
  uint8 dhg_id = DHGroupID_II ;

  // constructors

  CryptAlgoSelect() noexcept {}

  CryptAlgoSelect(CryptID crypt_id_,HashID hash_id_,DHGroupID dhg_id_) : crypt_id(crypt_id_),hash_id(hash_id_),dhg_id(dhg_id_) {}

  ....
 };

crypt_id identifies a block cipher algorithm.

hash_id identifies a hash algorithm.

dhg_id identifies a Diffie-Hellman group algorithm.

ClientDatabase

ClientDatabase is an interface to work to a client database.


struct ClientDatabase
 {
  static const Unid TypeUnid;

  enum FindErrorCode : uint32
   {
    Find_Ok = 0,

    FindError_NoMemory,
    FindError_NoClientID
   };

  virtual FindErrorCode findClient(PtrLen<const uint8> client_id,PrimeKeyPtr &client_key,ClientProfilePtr &client_profile) const =0;
 };

The only method is the findClient(). This method looks up in the client database and returns the client credentials, if any. client_id is a client identity. client_key is used to return the prime client key. client_profile is used to return the client profile.

AlgoSet

AlgoSet is an algorithm selection list.


class AlgoSet : NoCopy
 {
   ....

  public:

   AlgoSet();

   enum DefaultType { DefaultSet };

   explicit AlgoSet(DefaultType) { addDefault(); }

   ~AlgoSet();

   void add(CryptID crypt_id,HashID hash_id,DHGroupID dhg_id);

   void addDefault();

   PtrLen<const CryptAlgoSelect> getAlgoList() const;

   PtrLen<const AlgoLen> getAlgoLens() const;
 };

Default constructor creates an empty list. The second constructor with the DefaultType argument creates a default algorithm selection list.

add() adds the algorithm selection with the given crypt_id, hash_id and dhg_id.

addDefault() adds some default algorithm selection list.

getAlgoList() returns the range of the algorithm selections.

getAlgoLens() returns the range of AlgoLens, correspondent to the algorithm selections.

ClientNegotiant

ClientNegotiant is used by client to start a PKE exchange. When the exchange is finished a signal is asserted and the outcome of the negotiation can be retrieved.


class ClientNegotiant : NoCopy
 {
   ....

  public:

   enum State
    {
     State_Null,
     State_Ready,
     State_Started,
     State_Done,
     State_ClientError,
     State_ServerError,
     State_Timeout
    };

   friend const char * GetTextDesc(State state);

   ClientNegotiant(StrLen ep_dev_name,Function<void (void)> done_func);

   ~ClientNegotiant();

   State getState() const;

   PKError getError() const;

   void prepare(XPoint psec_port,const AbstractClientID &client_id,PrimeKeyPtr &&client_key,PrimeKeyPtr &&server_key,SessionKeyParam param={});

   void start(PtrLen<const CryptAlgoSelect> algo_list);

   void start(const AlgoSet &algo_set);

   void getSessionKey(MasterKeyPtr &ret);
 };

The first constructor argument is the PacketEndpoint device name. This device is used to communicate with the server. The second is an alert function. This function is called inside the inbound processing when the negotiation is finished. All class methods are thread-safe.

getState() returns the state of the object. The state is State_Null right after the creation of the object. prepare() method turns it into the State_Ready. start() method turns it into the State_Started. After the negotiation is finished the state is one of the following: State_Done is the negotiation is successful, State_ClientError if the client is reported a error, State_ServerError is the server is reported a error, State_Timeout is a protocol timeout is expired. If the state is State_Done the session key can be retrieved by the method getSessionKey().

getError() returns the error code. This method can be called if the negotiation is finished with a error.

prepare() prepares the object for the negotiation. The following data must be provided: psec_port is a local port for the further PSec communication channel. client_id is a client identity. client_key is a primary client key. server_key is a primary server key. param is a proposed session parameters. The final choice of these parameters is a result of the negotiation with the server.

start() starts the negotiation process. A list of algorithm selections must be provided, either as a range or as an AlgoSet reference. The algorithm with less index is more preferred. The final choice of algorithms is a result of the negotiation with the server.

getSessionKey() returns the session key. This method can be called if the negotiation is finished successfully.

ServerNegotiant

ServerNegotiant is used by client to start a PKE exchange. When the exchange is finished a signal is asserted and the outcome of the negotiation can be retrieved. This class serves multiple clients from multiple endpoints.


class ServerNegotiant : NoCopy
 {
   ....

  public:

   static const ulen DefaultMaxClients = 10'000 ;

   ServerNegotiant(StrLen mp_dev_name,const ClientDatabase &client_db,EndpointManager &epman,ulen max_clients=DefaultMaxClients,MSec final_timeout=5_sec);

    // client_db and epman methods are called under the mutex protection

   ~ServerNegotiant();

   void prepare(PrimeKeyPtr &&server_key,SessionKeyParam param={});

   void start(PtrLen<const CryptAlgoSelect> algo_list);

   void start(const AlgoSet &algo_set);
 };

The first argument of the constructor is the EndpointDevice name. This device is used to communicate with clients from multiple endpoints. client_db is a reference to a client database. This object must persist during the negotiant object life-time. epman is a reference to a endpoint manager. This object must persist during the negotiant object life-time. Once some negotiation is successfully finished the open method of this manager is used to submit the session key and other client information to open a communication with the client. max_clients is a limit of the simultaneously going negotiations. final_timeout is a time to keep the negotiation state alive after the negotiation is finished. During this time the client can resend the final packet to get confirmation the negotiation is succeeded.

prepare() prepares the server for negotiations. The following data must be provided: server_key is a primary server key. param is a proposed session parameters. A final choice of these parameters is a result of a negotiation with a client.

start() starts the server. A list of algorithm selections must be provided, either as a range or as an AlgoSet reference. The algorithm selection with less index is more preferred. The final choice of algorithms is a result of the negotiation with the server. Server is responding on a client negotiation prompt. Once such prompt is recognized in the inbound stream of packets a negotiation with the client is started. The negotiation state is created. During the negotiation this state is updated and finally the negotiation is finished with some outcome. If the negotiation is successful, the endpoint manager is used to open a communicated with this client.