nts: add support for multiple sets of trusted certificates
Modify the session, NTS-KE, and NTS-NTP code to support multiple sets of trusted certificates and identify the sets by a 32-bit ID.
This commit is contained in:
parent
f650b8c515
commit
6615bb1b78
14 changed files with 72 additions and 36 deletions
|
@ -161,11 +161,12 @@ NAU_CreateSymmetricInstance(uint32_t key_id)
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
NAU_Instance
|
NAU_Instance
|
||||||
NAU_CreateNtsInstance(IPSockAddr *nts_address, const char *name, uint16_t ntp_port)
|
NAU_CreateNtsInstance(IPSockAddr *nts_address, const char *name, uint32_t cert_set,
|
||||||
|
uint16_t ntp_port)
|
||||||
{
|
{
|
||||||
NAU_Instance instance = create_instance(NTP_AUTH_NTS);
|
NAU_Instance instance = create_instance(NTP_AUTH_NTS);
|
||||||
|
|
||||||
instance->nts = NNC_CreateInstance(nts_address, name, ntp_port);
|
instance->nts = NNC_CreateInstance(nts_address, name, cert_set, ntp_port);
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ typedef struct NAU_Instance_Record *NAU_Instance;
|
||||||
extern NAU_Instance NAU_CreateNoneInstance(void);
|
extern NAU_Instance NAU_CreateNoneInstance(void);
|
||||||
extern NAU_Instance NAU_CreateSymmetricInstance(uint32_t key_id);
|
extern NAU_Instance NAU_CreateSymmetricInstance(uint32_t key_id);
|
||||||
extern NAU_Instance NAU_CreateNtsInstance(IPSockAddr *nts_address, const char *name,
|
extern NAU_Instance NAU_CreateNtsInstance(IPSockAddr *nts_address, const char *name,
|
||||||
uint16_t ntp_port);
|
uint32_t cert_set, uint16_t ntp_port);
|
||||||
|
|
||||||
/* Destroy an instance */
|
/* Destroy an instance */
|
||||||
extern void NAU_DestroyInstance(NAU_Instance instance);
|
extern void NAU_DestroyInstance(NAU_Instance instance);
|
||||||
|
|
|
@ -571,7 +571,8 @@ NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
|
||||||
nts_address.ip_addr = remote_addr->ip_addr;
|
nts_address.ip_addr = remote_addr->ip_addr;
|
||||||
nts_address.port = params->nts_port;
|
nts_address.port = params->nts_port;
|
||||||
|
|
||||||
result->auth = NAU_CreateNtsInstance(&nts_address, name, result->remote_addr.port);
|
result->auth = NAU_CreateNtsInstance(&nts_address, name, 0,
|
||||||
|
result->remote_addr.port);
|
||||||
} else if (params->authkey != INACTIVE_AUTHKEY) {
|
} else if (params->authkey != INACTIVE_AUTHKEY) {
|
||||||
result->auth = NAU_CreateSymmetricInstance(params->authkey);
|
result->auth = NAU_CreateSymmetricInstance(params->authkey);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
struct NKC_Instance_Record {
|
struct NKC_Instance_Record {
|
||||||
char *name;
|
char *name;
|
||||||
IPSockAddr address;
|
IPSockAddr address;
|
||||||
|
NKSN_Credentials credentials;
|
||||||
NKSN_Instance session;
|
NKSN_Instance session;
|
||||||
int destroying;
|
int destroying;
|
||||||
int got_response;
|
int got_response;
|
||||||
|
@ -58,8 +59,8 @@ struct NKC_Instance_Record {
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
static NKSN_Credentials client_credentials = NULL;
|
static NKSN_Credentials default_credentials = NULL;
|
||||||
static int client_credentials_refs = 0;
|
static int default_credentials_refs = 0;
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
|
@ -266,9 +267,10 @@ handle_message(void *arg)
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
NKC_Instance
|
NKC_Instance
|
||||||
NKC_CreateInstance(IPSockAddr *address, const char *name)
|
NKC_CreateInstance(IPSockAddr *address, const char *name, uint32_t cert_set)
|
||||||
{
|
{
|
||||||
const char **trusted_certs;
|
const char **trusted_certs;
|
||||||
|
uint32_t *certs_ids;
|
||||||
NKC_Instance inst;
|
NKC_Instance inst;
|
||||||
int n_certs;
|
int n_certs;
|
||||||
|
|
||||||
|
@ -282,11 +284,24 @@ NKC_CreateInstance(IPSockAddr *address, const char *name)
|
||||||
inst->got_response = 0;
|
inst->got_response = 0;
|
||||||
|
|
||||||
n_certs = CNF_GetNtsTrustedCertsPaths(&trusted_certs);
|
n_certs = CNF_GetNtsTrustedCertsPaths(&trusted_certs);
|
||||||
|
certs_ids = MallocArray(uint32_t, n_certs);
|
||||||
|
memset(certs_ids, 0, sizeof (uint32_t) * n_certs);
|
||||||
|
|
||||||
/* Share the credentials with other client instances */
|
/* Share the credentials among clients using the default set of trusted
|
||||||
if (!client_credentials)
|
certificates, which likely contains most certificates */
|
||||||
client_credentials = NKSN_CreateClientCertCredentials(trusted_certs, n_certs);
|
if (cert_set == 0) {
|
||||||
client_credentials_refs++;
|
if (!default_credentials)
|
||||||
|
default_credentials = NKSN_CreateClientCertCredentials(trusted_certs, certs_ids,
|
||||||
|
n_certs, cert_set);
|
||||||
|
inst->credentials = default_credentials;
|
||||||
|
if (default_credentials)
|
||||||
|
default_credentials_refs++;
|
||||||
|
} else {
|
||||||
|
inst->credentials = NKSN_CreateClientCertCredentials(trusted_certs, certs_ids,
|
||||||
|
n_certs, cert_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(certs_ids);
|
||||||
|
|
||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
|
@ -300,10 +315,16 @@ NKC_DestroyInstance(NKC_Instance inst)
|
||||||
|
|
||||||
Free(inst->name);
|
Free(inst->name);
|
||||||
|
|
||||||
client_credentials_refs--;
|
if (inst->credentials) {
|
||||||
if (client_credentials_refs <= 0 && client_credentials) {
|
if (inst->credentials == default_credentials) {
|
||||||
NKSN_DestroyCertCredentials(client_credentials);
|
default_credentials_refs--;
|
||||||
client_credentials = NULL;
|
if (default_credentials_refs <= 0) {
|
||||||
|
NKSN_DestroyCertCredentials(default_credentials);
|
||||||
|
default_credentials = NULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NKSN_DestroyCertCredentials(inst->credentials);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If the asynchronous resolver is running, let the handler free
|
/* If the asynchronous resolver is running, let the handler free
|
||||||
|
@ -329,7 +350,7 @@ NKC_Start(NKC_Instance inst)
|
||||||
|
|
||||||
inst->got_response = 0;
|
inst->got_response = 0;
|
||||||
|
|
||||||
if (!client_credentials) {
|
if (!inst->credentials) {
|
||||||
DEBUG_LOG("Missing client credentials");
|
DEBUG_LOG("Missing client credentials");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -351,7 +372,7 @@ NKC_Start(NKC_Instance inst)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start an NTS-KE session */
|
/* Start an NTS-KE session */
|
||||||
if (!NKSN_StartSession(inst->session, sock_fd, label, client_credentials, CLIENT_TIMEOUT)) {
|
if (!NKSN_StartSession(inst->session, sock_fd, label, inst->credentials, CLIENT_TIMEOUT)) {
|
||||||
SCK_CloseSocket(sock_fd);
|
SCK_CloseSocket(sock_fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
typedef struct NKC_Instance_Record *NKC_Instance;
|
typedef struct NKC_Instance_Record *NKC_Instance;
|
||||||
|
|
||||||
/* Create a client NTS-KE instance */
|
/* Create a client NTS-KE instance */
|
||||||
extern NKC_Instance NKC_CreateInstance(IPSockAddr *address, const char *name);
|
extern NKC_Instance NKC_CreateInstance(IPSockAddr *address, const char *name, uint32_t cert_set);
|
||||||
|
|
||||||
/* Destroy an instance */
|
/* Destroy an instance */
|
||||||
extern void NKC_DestroyInstance(NKC_Instance inst);
|
extern void NKC_DestroyInstance(NKC_Instance inst);
|
||||||
|
|
|
@ -643,7 +643,8 @@ deinit_gnutls(void)
|
||||||
|
|
||||||
static NKSN_Credentials
|
static NKSN_Credentials
|
||||||
create_credentials(const char **certs, const char **keys, int n_certs_keys,
|
create_credentials(const char **certs, const char **keys, int n_certs_keys,
|
||||||
const char **trusted_certs, int n_trusted_certs)
|
const char **trusted_certs, uint32_t *trusted_certs_ids,
|
||||||
|
int n_trusted_certs, uint32_t trusted_cert_set)
|
||||||
{
|
{
|
||||||
gnutls_certificate_credentials_t credentials = NULL;
|
gnutls_certificate_credentials_t credentials = NULL;
|
||||||
int i, r;
|
int i, r;
|
||||||
|
@ -655,7 +656,8 @@ create_credentials(const char **certs, const char **keys, int n_certs_keys,
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
if (certs && keys) {
|
if (certs && keys) {
|
||||||
assert(!trusted_certs);
|
if (trusted_certs || trusted_certs_ids)
|
||||||
|
assert(0);
|
||||||
|
|
||||||
for (i = 0; i < n_certs_keys; i++) {
|
for (i = 0; i < n_certs_keys; i++) {
|
||||||
r = gnutls_certificate_set_x509_key_file(credentials, certs[i], keys[i],
|
r = gnutls_certificate_set_x509_key_file(credentials, certs[i], keys[i],
|
||||||
|
@ -667,16 +669,19 @@ create_credentials(const char **certs, const char **keys, int n_certs_keys,
|
||||||
if (certs || keys || n_certs_keys > 0)
|
if (certs || keys || n_certs_keys > 0)
|
||||||
assert(0);
|
assert(0);
|
||||||
|
|
||||||
if (!CNF_GetNoSystemCert()) {
|
if (trusted_cert_set == 0 && !CNF_GetNoSystemCert()) {
|
||||||
r = gnutls_certificate_set_x509_system_trust(credentials);
|
r = gnutls_certificate_set_x509_system_trust(credentials);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trusted_certs) {
|
if (trusted_certs && trusted_certs_ids) {
|
||||||
for (i = 0; i < n_trusted_certs; i++) {
|
for (i = 0; i < n_trusted_certs; i++) {
|
||||||
struct stat buf;
|
struct stat buf;
|
||||||
|
|
||||||
|
if (trusted_certs_ids[i] != trusted_cert_set)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (stat(trusted_certs[i], &buf) == 0 && S_ISDIR(buf.st_mode))
|
if (stat(trusted_certs[i], &buf) == 0 && S_ISDIR(buf.st_mode))
|
||||||
r = gnutls_certificate_set_x509_trust_dir(credentials, trusted_certs[i],
|
r = gnutls_certificate_set_x509_trust_dir(credentials, trusted_certs[i],
|
||||||
GNUTLS_X509_FMT_PEM);
|
GNUTLS_X509_FMT_PEM);
|
||||||
|
@ -708,15 +713,16 @@ error:
|
||||||
NKSN_Credentials
|
NKSN_Credentials
|
||||||
NKSN_CreateServerCertCredentials(const char **certs, const char **keys, int n_certs_keys)
|
NKSN_CreateServerCertCredentials(const char **certs, const char **keys, int n_certs_keys)
|
||||||
{
|
{
|
||||||
return create_credentials(certs, keys, n_certs_keys, NULL, 0);
|
return create_credentials(certs, keys, n_certs_keys, NULL, NULL, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
NKSN_Credentials
|
NKSN_Credentials
|
||||||
NKSN_CreateClientCertCredentials(const char **trusted_certs, int n_certs)
|
NKSN_CreateClientCertCredentials(const char **certs, uint32_t *ids,
|
||||||
|
int n_certs_ids, uint32_t trusted_cert_set)
|
||||||
{
|
{
|
||||||
return create_credentials(NULL, NULL, 0, trusted_certs, n_certs);
|
return create_credentials(NULL, NULL, 0, certs, ids, n_certs_ids, trusted_cert_set);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
|
@ -43,8 +43,9 @@ typedef int (*NKSN_MessageHandler)(void *arg);
|
||||||
different clients or servers. */
|
different clients or servers. */
|
||||||
extern NKSN_Credentials NKSN_CreateServerCertCredentials(const char **certs, const char **keys,
|
extern NKSN_Credentials NKSN_CreateServerCertCredentials(const char **certs, const char **keys,
|
||||||
int n_certs_keys);
|
int n_certs_keys);
|
||||||
extern NKSN_Credentials NKSN_CreateClientCertCredentials(const char **trusted_certs,
|
extern NKSN_Credentials NKSN_CreateClientCertCredentials(const char **certs, uint32_t *ids,
|
||||||
int n_certs);
|
int n_certs_ids,
|
||||||
|
uint32_t trusted_cert_set);
|
||||||
|
|
||||||
/* Destroy the credentials */
|
/* Destroy the credentials */
|
||||||
extern void NKSN_DestroyCertCredentials(NKSN_Credentials credentials);
|
extern void NKSN_DestroyCertCredentials(NKSN_Credentials credentials);
|
||||||
|
|
|
@ -54,6 +54,8 @@ struct NNC_Instance_Record {
|
||||||
IPSockAddr nts_address;
|
IPSockAddr nts_address;
|
||||||
/* Hostname or IP address for certificate verification */
|
/* Hostname or IP address for certificate verification */
|
||||||
char *name;
|
char *name;
|
||||||
|
/* ID of trusted certificates */
|
||||||
|
uint32_t cert_set;
|
||||||
/* Configured NTP port */
|
/* Configured NTP port */
|
||||||
uint16_t default_ntp_port;
|
uint16_t default_ntp_port;
|
||||||
/* Address of NTP server (can be negotiated in NTS-KE) */
|
/* Address of NTP server (can be negotiated in NTS-KE) */
|
||||||
|
@ -114,7 +116,7 @@ reset_instance(NNC_Instance inst)
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
|
||||||
NNC_Instance
|
NNC_Instance
|
||||||
NNC_CreateInstance(IPSockAddr *nts_address, const char *name, uint16_t ntp_port)
|
NNC_CreateInstance(IPSockAddr *nts_address, const char *name, uint32_t cert_set, uint16_t ntp_port)
|
||||||
{
|
{
|
||||||
NNC_Instance inst;
|
NNC_Instance inst;
|
||||||
|
|
||||||
|
@ -122,6 +124,7 @@ NNC_CreateInstance(IPSockAddr *nts_address, const char *name, uint16_t ntp_port)
|
||||||
|
|
||||||
inst->nts_address = *nts_address;
|
inst->nts_address = *nts_address;
|
||||||
inst->name = Strdup(name);
|
inst->name = Strdup(name);
|
||||||
|
inst->cert_set = cert_set;
|
||||||
inst->default_ntp_port = ntp_port;
|
inst->default_ntp_port = ntp_port;
|
||||||
inst->ntp_address.ip_addr = nts_address->ip_addr;
|
inst->ntp_address.ip_addr = nts_address->ip_addr;
|
||||||
inst->ntp_address.port = ntp_port;
|
inst->ntp_address.port = ntp_port;
|
||||||
|
@ -233,7 +236,7 @@ get_cookies(NNC_Instance inst)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
inst->nke = NKC_CreateInstance(&inst->nts_address, inst->name);
|
inst->nke = NKC_CreateInstance(&inst->nts_address, inst->name, inst->cert_set);
|
||||||
|
|
||||||
inst->nke_attempts++;
|
inst->nke_attempts++;
|
||||||
update_next_nke_attempt(inst, now);
|
update_next_nke_attempt(inst, now);
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
typedef struct NNC_Instance_Record *NNC_Instance;
|
typedef struct NNC_Instance_Record *NNC_Instance;
|
||||||
|
|
||||||
extern NNC_Instance NNC_CreateInstance(IPSockAddr *nts_address, const char *name,
|
extern NNC_Instance NNC_CreateInstance(IPSockAddr *nts_address, const char *name,
|
||||||
uint16_t ntp_port);
|
uint32_t cert_set, uint16_t ntp_port);
|
||||||
extern void NNC_DestroyInstance(NNC_Instance inst);
|
extern void NNC_DestroyInstance(NNC_Instance inst);
|
||||||
extern int NNC_PrepareForAuth(NNC_Instance inst);
|
extern int NNC_PrepareForAuth(NNC_Instance inst);
|
||||||
extern int NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet,
|
extern int NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet,
|
||||||
|
|
3
stubs.c
3
stubs.c
|
@ -491,7 +491,8 @@ NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
NNC_Instance
|
NNC_Instance
|
||||||
NNC_CreateInstance(IPSockAddr *nts_address, const char *name, uint16_t ntp_port)
|
NNC_CreateInstance(IPSockAddr *nts_address, const char *name, uint32_t cert_set,
|
||||||
|
uint16_t ntp_port)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,7 +177,7 @@ test_unit(void)
|
||||||
can_auth_res = can_auth_req;
|
can_auth_res = can_auth_req;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
inst = NAU_CreateNtsInstance(&nts_addr, "test", 0);
|
inst = NAU_CreateNtsInstance(&nts_addr, "test", 0, 0);
|
||||||
TEST_CHECK(NAU_IsAuthEnabled(inst));
|
TEST_CHECK(NAU_IsAuthEnabled(inst));
|
||||||
TEST_CHECK(NAU_GetSuggestedNtpVersion(inst) == 4);
|
TEST_CHECK(NAU_GetSuggestedNtpVersion(inst) == 4);
|
||||||
mode = NTP_AUTH_NTS;
|
mode = NTP_AUTH_NTS;
|
||||||
|
|
|
@ -120,7 +120,7 @@ test_unit(void)
|
||||||
SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr);
|
SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr);
|
||||||
addr.port = 0;
|
addr.port = 0;
|
||||||
|
|
||||||
inst = NKC_CreateInstance(&addr, "test");
|
inst = NKC_CreateInstance(&addr, "test", 0);
|
||||||
TEST_CHECK(inst);
|
TEST_CHECK(inst);
|
||||||
|
|
||||||
for (i = 0; i < 10000; i++) {
|
for (i = 0; i < 10000; i++) {
|
||||||
|
|
|
@ -165,12 +165,14 @@ test_unit(void)
|
||||||
NKSN_Credentials client_cred, server_cred;
|
NKSN_Credentials client_cred, server_cred;
|
||||||
const char *cert, *key;
|
const char *cert, *key;
|
||||||
int sock_fds[2], i;
|
int sock_fds[2], i;
|
||||||
|
uint32_t cert_id;
|
||||||
|
|
||||||
LCL_Initialise();
|
LCL_Initialise();
|
||||||
TST_RegisterDummyDrivers();
|
TST_RegisterDummyDrivers();
|
||||||
|
|
||||||
cert = "nts_ke.crt";
|
cert = "nts_ke.crt";
|
||||||
key = "nts_ke.key";
|
key = "nts_ke.key";
|
||||||
|
cert_id = 0;
|
||||||
|
|
||||||
for (i = 0; i < 50; i++) {
|
for (i = 0; i < 50; i++) {
|
||||||
SCH_Initialise();
|
SCH_Initialise();
|
||||||
|
@ -179,7 +181,7 @@ test_unit(void)
|
||||||
client = NKSN_CreateInstance(0, "test", handle_response, NULL);
|
client = NKSN_CreateInstance(0, "test", handle_response, NULL);
|
||||||
|
|
||||||
server_cred = NKSN_CreateServerCertCredentials(&cert, &key, 1);
|
server_cred = NKSN_CreateServerCertCredentials(&cert, &key, 1);
|
||||||
client_cred = NKSN_CreateClientCertCredentials(&cert, 1);
|
client_cred = NKSN_CreateClientCertCredentials(&cert, &cert_id, 1, 0);
|
||||||
|
|
||||||
TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds) == 0);
|
TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds) == 0);
|
||||||
TEST_CHECK(fcntl(sock_fds[0], F_SETFL, O_NONBLOCK) == 0);
|
TEST_CHECK(fcntl(sock_fds[0], F_SETFL, O_NONBLOCK) == 0);
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
#include "ntp.h"
|
#include "ntp.h"
|
||||||
#include "nts_ke_client.h"
|
#include "nts_ke_client.h"
|
||||||
|
|
||||||
#define NKC_CreateInstance(address, name) Malloc(1)
|
#define NKC_CreateInstance(address, name, cert_set) Malloc(1)
|
||||||
#define NKC_DestroyInstance(inst) Free(inst)
|
#define NKC_DestroyInstance(inst) Free(inst)
|
||||||
#define NKC_Start(inst) (random() % 2)
|
#define NKC_Start(inst) (random() % 2)
|
||||||
#define NKC_IsActive(inst) (random() % 2)
|
#define NKC_IsActive(inst) (random() % 2)
|
||||||
|
@ -227,7 +227,7 @@ test_unit(void)
|
||||||
SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr);
|
SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr);
|
||||||
addr.port = 0;
|
addr.port = 0;
|
||||||
|
|
||||||
inst = NNC_CreateInstance(&addr, "test", 0);
|
inst = NNC_CreateInstance(&addr, "test", 0, 0);
|
||||||
TEST_CHECK(inst);
|
TEST_CHECK(inst);
|
||||||
|
|
||||||
for (i = 0; i < 100000; i++) {
|
for (i = 0; i < 100000; i++) {
|
||||||
|
|
Loading…
Reference in a new issue