#include <daemon.h>
-ENUM(tls_version_names, SSL_2_0, TLS_1_2,
- "SSLv2",
+ENUM_BEGIN(tls_version_names, SSL_2_0, SSL_2_0,
+ "SSLv2");
+ENUM_NEXT(tls_version_names, SSL_3_0, TLS_1_2, SSL_2_0,
"SSLv3",
"TLS 1.0",
"TLS 1.1",
- "TLS 1.2",
-);
+ "TLS 1.2");
+ENUM_END(tls_version_names, TLS_1_2);
ENUM(tls_content_type_names, TLS_CHANGE_CIPHER_SPEC, TLS_APPLICATION_DATA,
"ChangeCipherSpec",
*/
bool is_server;
+ /**
+ * Negotiated TLS version
+ */
+ tls_version_t version;
+
/**
* TLS record protection layer
*/
return this->protection->build(this->protection, type, data);
}
+METHOD(tls_t, get_version, tls_version_t,
+ private_tls_t *this)
+{
+ return this->version;
+}
+
+METHOD(tls_t, set_version, void,
+ private_tls_t *this, tls_version_t version)
+{
+ this->version = version;
+}
+
METHOD(tls_t, destroy, void,
private_tls_t *this)
{
.public = {
.process = _process,
.build = _build,
+ .get_version = _get_version,
+ .set_version = _set_version,
.destroy = _destroy,
},
.is_server = is_server,
.crypto = tls_crypto_create(),
+ .version = TLS_1_2,
);
if (is_server)
{
- this->handshake = &tls_server_create(this->crypto)->handshake;
+ this->handshake = &tls_server_create(&this->public,
+ this->crypto)->handshake;
}
else
{
- this->handshake = &tls_peer_create(this->crypto)->handshake;
+ this->handshake = &tls_peer_create(&this->public,
+ this->crypto)->handshake;
}
this->fragmentation = tls_fragmentation_create(this->handshake);
this->compression = tls_compression_create(this->fragmentation);
typedef enum {
STATE_INIT,
STATE_HELLO_SENT,
+ STATE_HELLO_DONE,
} peer_state_t;
/**
*/
tls_peer_t public;
+ /**
+ * TLS stack
+ */
+ tls_t *tls;
+
/**
* TLS crypto context
*/
peer_state_t state;
};
+/**
+ * Process a server hello message
+ */
+static status_t process_server_hello(private_tls_peer_t *this, chunk_t data)
+{
+ if (data.len >= 38)
+ {
+ tls_version_t version;
+
+ struct __attribute__((packed)) {
+ u_int16_t version;
+ struct __attribute__((packed)) {
+ u_int32_t gmt;
+ u_int8_t bytes[28];
+ } random;
+ struct __attribute__((packed)) {
+ u_int8_t len;
+ /* points to len */
+ u_int8_t id[data.ptr[34]];
+ } session;
+ u_int16_t cipher;
+ u_int8_t compression;
+ char extensions[];
+ } *hello = (void*)data.ptr;
+
+ if (sizeof(*hello) > data.len)
+ {
+ DBG1(DBG_IKE, "received invalid ServerHello");
+ return FAILED;
+ }
+
+ version = untoh16(&hello->version);
+ if (version < this->tls->get_version(this->tls))
+ {
+ this->tls->set_version(this->tls, version);
+ }
+ return NEED_MORE;
+ }
+ DBG1(DBG_IKE, "server hello has %d bytes", data.len);
+ return FAILED;
+}
+
+/**
+ * Process a Certificate message
+ */
+static status_t process_certificate(private_tls_peer_t *this, chunk_t data)
+{
+ if (data.len > 3)
+ {
+ u_int32_t total;
+
+ total = untoh32(data.ptr) >> 8;
+ data = chunk_skip(data, 3);
+ if (total != data.len)
+ {
+ DBG1(DBG_IKE, "certificate chain length invalid");
+ return FAILED;
+ }
+ while (data.len > 3)
+ {
+ certificate_t *cert;
+ u_int32_t len;
+
+ len = untoh32(data.ptr) >> 8;
+ data = chunk_skip(data, 3);
+ if (len > data.len)
+ {
+ DBG1(DBG_IKE, "certificate length invalid");
+ return FAILED;
+ }
+ cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
+ BUILD_BLOB_ASN1_DER, chunk_create(data.ptr, len), BUILD_END);
+ if (cert)
+ {
+ DBG1(DBG_IKE, "got certificate: %Y", cert->get_subject(cert));
+ cert->destroy(cert);
+ }
+ data = chunk_skip(data, len);
+ }
+ }
+ return NEED_MORE;
+}
+
+/**
+ * Process a Certificate message
+ */
+static status_t process_certreq(private_tls_peer_t *this, chunk_t data)
+{
+ struct __attribute__((packed)) {
+ u_int8_t len;
+ u_int8_t types[];
+ } *certificate;
+ struct __attribute__((packed)) {
+ u_int16_t len;
+ struct __attribute__((packed)) {
+ u_int8_t hash;
+ u_int8_t sig;
+ } types[];
+ } *alg;
+ u_int16_t len;
+ identification_t *id;
+
+ certificate = (void*)data.ptr;
+ data = chunk_skip(data, 1);
+ if (!data.len || certificate->len > data.len)
+ {
+ return FAILED;
+ }
+ data = chunk_skip(data, certificate->len);
+
+ if (this->tls->get_version(this->tls) >= TLS_1_2)
+ {
+ alg = (void*)data.ptr;
+ data = chunk_skip(data, 2);
+ if (!data.len || untoh16(&alg->len) > data.len)
+ {
+ return FAILED;
+ }
+ data = chunk_skip(data, untoh16(&alg->len));
+ }
+ if (data.len < 2 || untoh16(data.ptr) != data.len - 2)
+ {
+ return FAILED;
+ }
+ data = chunk_skip(data, 2);
+
+ while (data.len >= 2)
+ {
+ len = untoh16(data.ptr);
+ data = chunk_skip(data, 2);
+ if (len > data.len)
+ {
+ return FAILED;
+ }
+ id = identification_create_from_encoding(ID_DER_ASN1_DN,
+ chunk_create(data.ptr, len));
+ DBG1(DBG_IKE, "received certificate request for %Y", id);
+ id->destroy(id);
+ data = chunk_skip(data, len);
+ }
+ return NEED_MORE;
+}
+
METHOD(tls_handshake_t, process, status_t,
private_tls_peer_t *this, tls_handshake_type_t type, chunk_t data)
{
+ switch (this->state)
+ {
+ case STATE_HELLO_SENT:
+ switch (type)
+ {
+ case TLS_SERVER_HELLO:
+ return process_server_hello(this, data);
+ case TLS_CERTIFICATE:
+ return process_certificate(this, data);
+ case TLS_CERTIFICATE_REQUEST:
+ return process_certreq(this, data);
+ case TLS_SERVER_HELLO_DONE:
+ this->state = STATE_HELLO_DONE;
+ return NEED_MORE;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ DBG1(DBG_IKE, "received TLS handshake message %N, ignored",
+ tls_handshake_type_names, type);
return NEED_MORE;
}
/**
* Build the Client Hello using a given set of ciphers
*/
-static chunk_t build_hello(int count, tls_cipher_suite_t *suite, rng_t *rng)
+static chunk_t build_hello(private_tls_peer_t *this,
+ int count, tls_cipher_suite_t *suite, rng_t *rng)
{
int i;
} hello;
htoun16(&hello.session.len, 0);
- htoun16(&hello.version, TLS_1_2);
+ htoun16(&hello.version, this->tls->get_version(this->tls));
htoun32(&hello.random.gmt, time(NULL));
rng->get_bytes(rng, sizeof(hello.random.bytes), (char*)&hello.random.bytes);
htoun16(&hello.cipher.len, count * 2);
return FAILED;
}
count = this->crypto->get_cipher_suites(this->crypto, &suite);
- *data = build_hello(count, suite, rng);
+ *data = build_hello(this, count, suite, rng);
*type = TLS_CLIENT_HELLO;
free(suite);
rng->destroy(rng);
+ this->state = STATE_HELLO_SENT;
return NEED_MORE;
}
switch (this->state)
{
case STATE_INIT:
- this->state = STATE_HELLO_SENT;
return send_hello(this, type, data);
default:
return INVALID_STATE;
/**
* See header
*/
-tls_peer_t *tls_peer_create(tls_crypto_t *crypto)
+tls_peer_t *tls_peer_create(tls_t *tls, tls_crypto_t *crypto)
{
private_tls_peer_t *this;
.destroy = _destroy,
},
.state = STATE_INIT,
+ .tls = tls,
.crypto = crypto,
);