blob: 2da1b90fc5521a3ba20db022e07ae0fb3fdb5f9a [file] [log] [blame]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2014-2017 (c) Florian Palm
* Copyright 2015-2016 (c) Sten GrĂ¼ner
* Copyright 2015 (c) Oleksiy Vasylyev
* Copyright 2017 (c) Stefan Profanter, fortiss GmbH
* Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB
*/
#include "ua_securechannel_manager.h"
#include <open62541/transport_generated.h>
#include "ua_server_internal.h"
#include "ua_session.h"
#define STARTCHANNELID 1
#define STARTTOKENID 1
UA_StatusCode
UA_SecureChannelManager_init(UA_SecureChannelManager *cm, UA_Server *server) {
TAILQ_INIT(&cm->channels);
// TODO: use an ID that is likely to be unique after a restart
cm->lastChannelId = STARTCHANNELID;
cm->lastTokenId = STARTTOKENID;
cm->currentChannelCount = 0;
cm->server = server;
return UA_STATUSCODE_GOOD;
}
void
UA_SecureChannelManager_deleteMembers(UA_SecureChannelManager *cm) {
channel_entry *entry, *temp;
TAILQ_FOREACH_SAFE(entry, &cm->channels, pointers, temp) {
TAILQ_REMOVE(&cm->channels, entry, pointers);
UA_SecureChannel_close(&entry->channel);
UA_SecureChannel_deleteMembers(&entry->channel);
UA_free(entry);
}
}
static void
removeSecureChannelCallback(void *_, channel_entry *entry) {
UA_SecureChannel_deleteMembers(&entry->channel);
}
static void
removeSecureChannel(UA_SecureChannelManager *cm, channel_entry *entry) {
/* Close the SecureChannel */
UA_SecureChannel_close(&entry->channel);
/* Detach the channel and make the capacity available */
TAILQ_REMOVE(&cm->channels, entry, pointers);
UA_atomic_subUInt32(&cm->currentChannelCount, 1);
/* Add a delayed callback to remove the channel when the currently
* scheduled jobs have completed */
entry->cleanupCallback.callback = (UA_ApplicationCallback)removeSecureChannelCallback;
entry->cleanupCallback.application = NULL;
entry->cleanupCallback.data = entry;
UA_WorkQueue_enqueueDelayed(&cm->server->workQueue, &entry->cleanupCallback);
}
/* remove channels that were not renewed or who have no connection attached */
void
UA_SecureChannelManager_cleanupTimedOut(UA_SecureChannelManager *cm,
UA_DateTime nowMonotonic) {
channel_entry *entry, *temp;
TAILQ_FOREACH_SAFE(entry, &cm->channels, pointers, temp) {
/* The channel was closed internally */
if(entry->channel.state == UA_SECURECHANNELSTATE_CLOSED ||
!entry->channel.connection) {
removeSecureChannel(cm, entry);
continue;
}
/* The channel has timed out */
UA_DateTime timeout =
entry->channel.securityToken.createdAt +
(UA_DateTime)(entry->channel.securityToken.revisedLifetime * UA_DATETIME_MSEC);
if(timeout < nowMonotonic) {
UA_LOG_INFO_CHANNEL(&cm->server->config.logger, &entry->channel,
"SecureChannel has timed out");
removeSecureChannel(cm, entry);
continue;
}
/* Revolve the channel tokens */
if(entry->channel.nextSecurityToken.tokenId > 0) {
UA_SecureChannel_revolveTokens(&entry->channel);
}
}
}
/* remove the first channel that has no session attached */
static UA_Boolean
purgeFirstChannelWithoutSession(UA_SecureChannelManager *cm) {
channel_entry *entry;
TAILQ_FOREACH(entry, &cm->channels, pointers) {
if(LIST_EMPTY(&entry->channel.sessions)) {
UA_LOG_INFO_CHANNEL(&cm->server->config.logger, &entry->channel,
"Channel was purged since maxSecureChannels was "
"reached and channel had no session attached");
removeSecureChannel(cm, entry);
return true;
}
}
return false;
}
UA_StatusCode
UA_SecureChannelManager_create(UA_SecureChannelManager *const cm, UA_Connection *const connection,
const UA_SecurityPolicy *const securityPolicy,
const UA_AsymmetricAlgorithmSecurityHeader *const asymHeader) {
/* connection already has a channel attached. */
if(connection->channel != NULL)
return UA_STATUSCODE_BADINTERNALERROR;
/* Check if there exists a free SC, otherwise try to purge one SC without a
* session the purge has been introduced to pass CTT, it is not clear what
* strategy is expected here */
if(cm->currentChannelCount >= cm->server->config.maxSecureChannels &&
!purgeFirstChannelWithoutSession(cm))
return UA_STATUSCODE_BADOUTOFMEMORY;
UA_LOG_INFO(&cm->server->config.logger, UA_LOGCATEGORY_SECURECHANNEL,
"Creating a new SecureChannel");
channel_entry *entry = (channel_entry *)UA_malloc(sizeof(channel_entry));
if(!entry)
return UA_STATUSCODE_BADOUTOFMEMORY;
/* Create the channel context and parse the sender (remote) certificate used for the
* secureChannel. */
UA_SecureChannel_init(&entry->channel);
UA_StatusCode retval =
UA_SecureChannel_setSecurityPolicy(&entry->channel, securityPolicy,
&asymHeader->senderCertificate);
if(retval != UA_STATUSCODE_GOOD) {
UA_free(entry);
return retval;
}
/* Channel state is fresh (0) */
entry->channel.securityToken.channelId = 0;
entry->channel.securityToken.tokenId = cm->lastTokenId++;
entry->channel.securityToken.createdAt = UA_DateTime_now();
entry->channel.securityToken.revisedLifetime = cm->server->config.maxSecurityTokenLifetime;
TAILQ_INSERT_TAIL(&cm->channels, entry, pointers);
UA_atomic_addUInt32(&cm->currentChannelCount, 1);
UA_Connection_attachSecureChannel(connection, &entry->channel);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_SecureChannelManager_open(UA_SecureChannelManager *cm, UA_SecureChannel *channel,
const UA_OpenSecureChannelRequest *request,
UA_OpenSecureChannelResponse *response) {
if(channel->state != UA_SECURECHANNELSTATE_FRESH) {
UA_LOG_ERROR_CHANNEL(&cm->server->config.logger, channel,
"Called open on already open or closed channel");
return UA_STATUSCODE_BADINTERNALERROR;
}
if(request->securityMode != UA_MESSAGESECURITYMODE_NONE &&
UA_ByteString_equal(&channel->securityPolicy->policyUri, &UA_SECURITY_POLICY_NONE_URI)) {
return UA_STATUSCODE_BADSECURITYMODEREJECTED;
}
channel->securityMode = request->securityMode;
channel->securityToken.createdAt = UA_DateTime_nowMonotonic();
channel->securityToken.channelId = cm->lastChannelId++;
channel->securityToken.createdAt = UA_DateTime_now();
/* Set the lifetime. Lifetime 0 -> set the maximum possible */
channel->securityToken.revisedLifetime =
(request->requestedLifetime > cm->server->config.maxSecurityTokenLifetime) ?
cm->server->config.maxSecurityTokenLifetime : request->requestedLifetime;
if(channel->securityToken.revisedLifetime == 0)
channel->securityToken.revisedLifetime = cm->server->config.maxSecurityTokenLifetime;
/* Set the nonces and generate the keys */
UA_StatusCode retval = UA_ByteString_copy(&request->clientNonce, &channel->remoteNonce);
if(retval != UA_STATUSCODE_GOOD)
return retval;
retval = UA_SecureChannel_generateLocalNonce(channel);
if(retval != UA_STATUSCODE_GOOD)
return retval;
retval = UA_SecureChannel_generateNewKeys(channel);
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* Set the response */
retval = UA_ByteString_copy(&channel->localNonce, &response->serverNonce);
if(retval != UA_STATUSCODE_GOOD)
return retval;
retval = UA_ChannelSecurityToken_copy(&channel->securityToken, &response->securityToken);
if(retval != UA_STATUSCODE_GOOD)
return retval;
response->responseHeader.timestamp = UA_DateTime_now();
response->responseHeader.requestHandle = request->requestHeader.requestHandle;
/* The channel is open */
channel->state = UA_SECURECHANNELSTATE_OPEN;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_SecureChannelManager_renew(UA_SecureChannelManager *cm, UA_SecureChannel *channel,
const UA_OpenSecureChannelRequest *request,
UA_OpenSecureChannelResponse *response) {
if(channel->state != UA_SECURECHANNELSTATE_OPEN) {
UA_LOG_ERROR_CHANNEL(&cm->server->config.logger, channel,
"Called renew on channel which is not open");
return UA_STATUSCODE_BADINTERNALERROR;
}
/* If no security token is already issued */
if(channel->nextSecurityToken.tokenId == 0) {
channel->nextSecurityToken.channelId = channel->securityToken.channelId;
channel->nextSecurityToken.tokenId = cm->lastTokenId++;
channel->nextSecurityToken.createdAt = UA_DateTime_now();
channel->nextSecurityToken.revisedLifetime =
(request->requestedLifetime > cm->server->config.maxSecurityTokenLifetime) ?
cm->server->config.maxSecurityTokenLifetime : request->requestedLifetime;
if(channel->nextSecurityToken.revisedLifetime == 0) /* lifetime 0 -> return the max lifetime */
channel->nextSecurityToken.revisedLifetime = cm->server->config.maxSecurityTokenLifetime;
}
/* Replace the nonces */
UA_ByteString_deleteMembers(&channel->remoteNonce);
UA_StatusCode retval = UA_ByteString_copy(&request->clientNonce, &channel->remoteNonce);
if(retval != UA_STATUSCODE_GOOD)
return retval;
retval = UA_SecureChannel_generateLocalNonce(channel);
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* Set the response */
response->responseHeader.requestHandle = request->requestHeader.requestHandle;
retval = UA_ByteString_copy(&channel->localNonce, &response->serverNonce);
if(retval != UA_STATUSCODE_GOOD)
return retval;
retval = UA_ChannelSecurityToken_copy(&channel->nextSecurityToken, &response->securityToken);
if(retval != UA_STATUSCODE_GOOD)
return retval;
/* Reset the internal creation date to the monotonic clock */
channel->nextSecurityToken.createdAt = UA_DateTime_nowMonotonic();
return UA_STATUSCODE_GOOD;
}
UA_SecureChannel *
UA_SecureChannelManager_get(UA_SecureChannelManager *cm, UA_UInt32 channelId) {
channel_entry *entry;
TAILQ_FOREACH(entry, &cm->channels, pointers) {
if(entry->channel.securityToken.channelId == channelId)
return &entry->channel;
}
return NULL;
}
UA_StatusCode
UA_SecureChannelManager_close(UA_SecureChannelManager *cm, UA_UInt32 channelId) {
channel_entry *entry;
TAILQ_FOREACH(entry, &cm->channels, pointers) {
if(entry->channel.securityToken.channelId == channelId)
break;
}
if(!entry)
return UA_STATUSCODE_BADINTERNALERROR;
removeSecureChannel(cm, entry);
return UA_STATUSCODE_GOOD;
}