| /* 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; |
| } |