Files
OpenVPNAdapter/Sources/OpenVPN3/openvpn/ssl/sess_ticket.hpp

243 lines
6.4 KiB
C++

// OpenVPN -- An application to securely tunnel IP networks
// over a single port, with support for SSL/TLS-based
// session authentication and key exchange,
// packet encryption, packet authentication, and
// packet compression.
//
// Copyright (C) 2012-2019 OpenVPN Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License Version 3
// as published by the Free Software Foundation.
//
// 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program in the COPYING file.
// If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include <cstring>
#include <memory>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/hash.hpp>
#include <openvpn/common/base64.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/random/randapi.hpp>
namespace openvpn {
class OpenSSLContext;
class MbedTLSContext;
// Abstract base class used to provide an interface for TLS
// Session Ticket keying originally described by RFC 5077.
class TLSSessionTicketBase
{
public:
typedef std::unique_ptr<TLSSessionTicketBase> UPtr;
OPENVPN_EXCEPTION(sess_ticket_error);
enum Status {
NO_TICKET,
TICKET_AVAILABLE,
TICKET_EXPIRING,
};
class Name
{
public:
static constexpr size_t SIZE = 16;
explicit Name(RandomAPI& rng)
{
rng.rand_bytes(value_, SIZE);
}
explicit Name(const std::string& name_b64)
{
b64_to_key(name_b64, "key name", value_, SIZE);
}
explicit Name(const unsigned char name[SIZE])
{
std::memcpy(value_, name, SIZE);
}
bool operator==(const Name& rhs) const
{
return std::memcmp(value_, rhs.value_, SIZE) == 0;
}
bool operator!=(const Name& rhs) const
{
return std::memcmp(value_, rhs.value_, SIZE) != 0;
}
bool operator<(const Name& rhs) const
{
return std::memcmp(value_, rhs.value_, SIZE) < 0;
}
std::string to_string() const
{
return "TLSTicketName[" + b64() + ']';
}
std::string b64() const
{
return base64->encode(value_, SIZE);
}
template <typename HASH>
void hash(HASH& h) const
{
h(value_, SIZE);
}
#ifdef HAVE_CITYHASH
std::size_t hashval() const
{
HashSizeT h;
hash(h);
return h.value();
}
#endif
private:
// we need to friend SSL implementation classes
friend class OpenSSLContext;
friend class MbedTLSContext;
Name() {} // note that default constructor leaves object in an undefined state
unsigned char value_[SIZE];
};
class Key
{
public:
static constexpr size_t CIPHER_KEY_SIZE = 32;
static constexpr size_t HMAC_KEY_SIZE = 16;
explicit Key(RandomAPI& rng)
{
rng.assert_crypto();
rng.rand_bytes(cipher_value_, CIPHER_KEY_SIZE);
rng.rand_bytes(hmac_value_, HMAC_KEY_SIZE);
}
explicit Key(const std::string& cipher_key_b64, const std::string& hmac_key_b64)
{
b64_to_key(cipher_key_b64, "cipher key", cipher_value_, CIPHER_KEY_SIZE);
b64_to_key(hmac_key_b64, "hmac key", hmac_value_, HMAC_KEY_SIZE);
}
~Key()
{
// wipe keys
std::memset(cipher_value_, 0, CIPHER_KEY_SIZE);
std::memset(hmac_value_, 0, HMAC_KEY_SIZE);
}
std::string to_string() const
{
return "TLSTicketKey[cipher=" + cipher_b64() + " hmac=" + hmac_b64() + ']';
}
std::string cipher_b64() const
{
return base64->encode(cipher_value_, CIPHER_KEY_SIZE);
}
std::string hmac_b64() const
{
return base64->encode(hmac_value_, HMAC_KEY_SIZE);
}
bool operator==(const Key& rhs) const
{
return std::memcmp(cipher_value_, rhs.cipher_value_, CIPHER_KEY_SIZE) == 0 && std::memcmp(hmac_value_, rhs.hmac_value_, HMAC_KEY_SIZE) == 0;
}
bool operator!=(const Key& rhs) const
{
return !operator==(rhs);
}
template <typename KEY_TRANSFORM>
void key_transform(KEY_TRANSFORM& t)
{
unsigned char out[KEY_TRANSFORM::MAX_HMAC_SIZE];
// cipher
{
t.cipher_transform.reset();
t.cipher_transform.update(cipher_value_, CIPHER_KEY_SIZE);
const size_t size = t.cipher_transform.final(out);
if (size < CIPHER_KEY_SIZE)
throw sess_ticket_error("insufficient key material for cipher transform");
std::memcpy(cipher_value_, out, CIPHER_KEY_SIZE);
}
// hmac
{
t.hmac_transform.reset();
t.hmac_transform.update(hmac_value_, HMAC_KEY_SIZE);
const size_t size = t.hmac_transform.final(out);
if (size < HMAC_KEY_SIZE)
throw sess_ticket_error("insufficient key material for hmac transform");
std::memcpy(hmac_value_, out, HMAC_KEY_SIZE);
}
}
private:
// we need to friend SSL implementation classes
friend class OpenSSLContext;
friend class MbedTLSContext;
Key() {} // note that default constructor leaves object in an undefined state
unsigned char cipher_value_[CIPHER_KEY_SIZE];
unsigned char hmac_value_[HMAC_KEY_SIZE];
};
// method returns name and key
virtual Status create_session_ticket_key(Name& name, Key& key) const = 0;
// method is given name and returns key
virtual Status lookup_session_ticket_key(const Name& name, Key& key) const = 0;
// return string that identifies the app
virtual std::string session_id_context() const = 0;
virtual ~TLSSessionTicketBase() {}
private:
static void b64_to_key(const std::string& b64, const char *title, unsigned char *out, const size_t outlen)
{
Buffer srcbuf(out, outlen, false);
try {
base64->decode(srcbuf, b64);
}
catch (const std::exception& e)
{
throw sess_ticket_error(std::string("base64 decode for ") + title + ": " + std::string(e.what()));
}
if (srcbuf.size() != outlen)
throw sess_ticket_error(std::string("wrong input size for ") + title + ", actual=" + std::to_string(srcbuf.size()) + " expected=" + std::to_string(outlen));
}
};
}
#ifdef HAVE_CITYHASH
OPENVPN_HASH_METHOD(openvpn::TLSSessionTicketBase::Name, hashval);
#endif