#include <open62541/types.h>
#include <open62541/transport_generated.h>
#include "ua_securechannel.h"
#include "ua_types_encoding_binary.h"
#define UA_BITMASK_MESSAGETYPE 0x00ffffffu
#define UA_BITMASK_CHUNKTYPE 0xff000000u
const UA_String UA_SECURITY_POLICY_NONE_URI =
{47, (UA_Byte *)"http://opcfoundation.org/UA/SecurityPolicy#None"};
void
UA_SecureChannel_init(UA_SecureChannel *channel) {
memset(channel, 0, sizeof(UA_SecureChannel));
TAILQ_INIT(&channel->chunks);
}
UA_StatusCode
UA_SecureChannel_setSecurityPolicy(UA_SecureChannel *channel, UA_SecurityPolicy *sp,
const UA_ByteString *remoteCertificate) {
UA_CHECK_ERROR(!channel->securityPolicy, return UA_STATUSCODE_BADINTERNALERROR,
sp->logger, UA_LOGCATEGORY_SECURITYPOLICY,
"Security policy already configured");
UA_StatusCode res = sp->newChannelContext(sp, remoteCertificate,
&channel->channelContext);
res |= UA_ByteString_copy(remoteCertificate, &channel->remoteCertificate);
UA_CHECK_STATUS_ERROR(res, return res, sp->logger, UA_LOGCATEGORY_SECURITYPOLICY,
"Could not set up the SecureChannel context");
UA_ByteString remoteCertificateThumbprint =
{20, channel->remoteCertificateThumbprint};
res = sp->makeCertThumbprint(sp, &channel->remoteCertificate,
&remoteCertificateThumbprint);
UA_CHECK_STATUS_ERROR(res, return res, sp->logger, UA_LOGCATEGORY_SECURITYPOLICY,
"Could not create the certificate thumbprint");
channel->securityPolicy = sp;
channel->enhancedSecurity = UA_SecurityPolicy_isEnhancedSecurity(sp);
channel->legacySequenceNumbers = UA_SecurityPolicy_useLegacySequenceNumbers(sp);
if(sp->policyType == UA_SECURITYPOLICYTYPE_NONE)
channel->securityMode = UA_MESSAGESECURITYMODE_NONE;
else
channel->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_SecureChannel_setSecurityMode(UA_SecureChannel *channel,
UA_MessageSecurityMode securityMode) {
if(securityMode == UA_MESSAGESECURITYMODE_INVALID ||
securityMode > UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
return UA_STATUSCODE_BADSECURITYMODEREJECTED;
UA_SecurityPolicy *sp = channel->securityPolicy;
if(!sp)
return UA_STATUSCODE_BADSECURITYMODEREJECTED;
UA_Boolean isNonePolicy = (sp->policyType == UA_SECURITYPOLICYTYPE_NONE);
UA_Boolean isNoneMode = (securityMode == UA_MESSAGESECURITYMODE_NONE);
if(isNonePolicy != isNoneMode)
return UA_STATUSCODE_BADSECURITYMODEREJECTED;
channel->securityMode = securityMode;
return UA_STATUSCODE_GOOD;
}
static void
hideErrors(UA_TcpErrorMessage *const error) {
switch(error->error) {
case UA_STATUSCODE_BADCERTIFICATEINVALID:
case UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE:
case UA_STATUSCODE_BADCERTIFICATEPOLICYCHECKFAILED:
case UA_STATUSCODE_BADCERTIFICATEUNTRUSTED:
case UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN:
case UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN:
case UA_STATUSCODE_BADCERTIFICATEREVOKED:
case UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED:
case UA_STATUSCODE_BADCERTIFICATEISSUERUSENOTALLOWED:
error->error = UA_STATUSCODE_BADSECURITYCHECKSFAILED;
error->reason = UA_STRING_NULL;
break;
default:
break;
}
}
UA_Boolean
UA_SecureChannel_isConnected(UA_SecureChannel *channel) {
return (channel->state > UA_SECURECHANNELSTATE_CLOSED &&
channel->state < UA_SECURECHANNELSTATE_CLOSING);
}
void
UA_SecureChannel_sendERR(UA_SecureChannel *channel, UA_TcpErrorMessage *error) {
if(!UA_SecureChannel_isConnected(channel))
return;
hideErrors(error);
UA_TcpMessageHeader header;
header.messageTypeAndChunkType = UA_MESSAGETYPE_ERR + UA_CHUNKTYPE_FINAL;
header.messageSize = 8 + (4 + 4 + (UA_UInt32)error->reason.length);
UA_ConnectionManager *cm = channel->connectionManager;
UA_ByteString msg = UA_BYTESTRING_NULL;
UA_StatusCode retval = cm->allocNetworkBuffer(cm, channel->connectionId,
&msg, header.messageSize);
if(retval != UA_STATUSCODE_GOOD)
return;
UA_Byte *bufPos = msg.data;
const UA_Byte *bufEnd = &msg.data[msg.length];
retval |= UA_encodeBinaryInternal(&header,
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER],
&bufPos, &bufEnd, NULL, NULL, NULL);
retval |= UA_encodeBinaryInternal(error,
&UA_TRANSPORT[UA_TRANSPORT_TCPERRORMESSAGE],
&bufPos, &bufEnd, NULL, NULL, NULL);
(void)retval;
msg.length = header.messageSize;
cm->sendWithConnection(cm, channel->connectionId, &UA_KEYVALUEMAP_NULL, &msg);
}
static void
UA_Chunk_delete(UA_Chunk *chunk) {
if(chunk->copied)
UA_ByteString_clear(&chunk->bytes);
UA_free(chunk);
}
static void
deleteChunks(UA_SecureChannel *channel) {
UA_Chunk *chunk, *chunk_tmp;
TAILQ_FOREACH_SAFE(chunk, &channel->chunks, pointers, chunk_tmp) {
TAILQ_REMOVE(&channel->chunks, chunk, pointers);
UA_Chunk_delete(chunk);
}
channel->chunksCount = 0;
channel->chunksLength = 0;
}
void
UA_SecureChannel_deleteBuffered(UA_SecureChannel *channel) {
deleteChunks(channel);
if(channel->unprocessedCopied)
UA_ByteString_clear(&channel->unprocessed);
}
void
UA_SecureChannel_shutdown(UA_SecureChannel *channel,
UA_ShutdownReason shutdownReason) {
if(!UA_SecureChannel_isConnected(channel))
return;
channel->shutdownReason= shutdownReason;
UA_ConnectionManager *cm = channel->connectionManager;
cm->closeConnection(cm, channel->connectionId);
channel->state = UA_SECURECHANNELSTATE_CLOSING;
}
void
UA_SecureChannel_clear(UA_SecureChannel *channel) {
UA_assert(channel->sessions == NULL);
UA_SecurityPolicy *sp = channel->securityPolicy;
if(sp) {
sp->deleteChannelContext(sp, channel->channelContext);
channel->securityPolicy = NULL;
channel->channelContext = NULL;
channel->enhancedSecurity = false;
channel->legacySequenceNumbers = true;
}
if(channel->connectionManager &&
channel->connectionManager->eventSource.eventLoop) {
UA_EventLoop *el = channel->connectionManager->eventSource.eventLoop;
el->removeDelayedCallback(el, &channel->unprocessedDelayed);
}
channel->connectionId = 0;
channel->connectionManager = NULL;
UA_ChannelSecurityToken_clear(&channel->securityToken);
UA_ChannelSecurityToken_clear(&channel->altSecurityToken);
UA_ByteString_clear(&channel->remoteCertificate);
UA_ByteString_clear(&channel->localNonce);
UA_ByteString_clear(&channel->remoteNonce);
UA_ByteString_clear(&channel->firstRequestSignature);
UA_ByteString_clear(&channel->currentIKM);
UA_ByteString_clear(&channel->channelThumbprint);
UA_String_clear(&channel->endpointUrl);
UA_String_clear(&channel->remoteAddress);
UA_SecureChannel_deleteBuffered(channel);
UA_NamespaceMapping_delete(channel->namespaceMapping);
channel->namespaceMapping = NULL;
channel->securityMode = UA_MESSAGESECURITYMODE_INVALID;
channel->shutdownReason = UA_SHUTDOWNREASON_CLOSE;
memset(&channel->config, 0, sizeof(UA_ConnectionConfig));
channel->receiveSequenceNumber = 0;
channel->sendSequenceNumber = 0;
channel->state = UA_SECURECHANNELSTATE_CLOSED;
channel->renewState = UA_SECURECHANNELRENEWSTATE_NORMAL;
}
UA_StatusCode
UA_SecureChannel_processHELACK(UA_SecureChannel *channel,
const UA_TcpAcknowledgeMessage *remoteConfig) {
if(channel->config.protocolVersion > remoteConfig->protocolVersion)
channel->config.protocolVersion = remoteConfig->protocolVersion;
if(channel->config.sendBufferSize > remoteConfig->receiveBufferSize)
channel->config.sendBufferSize = remoteConfig->receiveBufferSize;
if(channel->config.recvBufferSize > remoteConfig->sendBufferSize)
channel->config.recvBufferSize = remoteConfig->sendBufferSize;
channel->config.remoteMaxMessageSize = remoteConfig->maxMessageSize;
channel->config.remoteMaxChunkCount = remoteConfig->maxChunkCount;
if(channel->config.recvBufferSize < 8192 ||
channel->config.sendBufferSize < 8192 ||
(channel->config.remoteMaxMessageSize != 0 &&
channel->config.remoteMaxMessageSize < 8192))
return UA_STATUSCODE_BADINTERNALERROR;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_SecureChannel_sendOPN(UA_SecureChannel *channel,
UA_UInt32 requestId, const void *content,
const UA_DataType *contentType) {
if(!content || !contentType)
return UA_STATUSCODE_BADINTERNALERROR;
const UA_SecurityPolicy *sp = channel->securityPolicy;
UA_CHECK_MEM(sp, return UA_STATUSCODE_BADINTERNALERROR);
UA_assert(channel->securityMode > UA_MESSAGESECURITYMODE_INVALID &&
channel->securityMode <= UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
UA_assert((sp->policyType == UA_SECURITYPOLICYTYPE_NONE) ==
(channel->securityMode == UA_MESSAGESECURITYMODE_NONE));
UA_ConnectionManager *cm = channel->connectionManager;
if(!UA_SecureChannel_isConnected(channel))
return UA_STATUSCODE_BADCONNECTIONCLOSED;
UA_ByteString buf = UA_BYTESTRING_NULL;
UA_StatusCode res = cm->allocNetworkBuffer(cm, channel->connectionId, &buf,
channel->config.sendBufferSize);
UA_CHECK_STATUS(res, return res);
UA_Byte *buf_pos = buf.data;
const UA_Byte *buf_end = &buf.data[buf.length];
hideBytesAsym(channel, &buf_pos, &buf_end);
size_t securityHeaderLength, pre_sig_length, total_length, encryptedLength;
UA_EncodeBinaryOptions encOpts;
memset(&encOpts, 0, sizeof(UA_EncodeBinaryOptions));
encOpts.namespaceMapping = channel->namespaceMapping;
res |= UA_NodeId_encodeBinary(&contentType->binaryEncodingId, &buf_pos, buf_end);
res |= UA_encodeBinaryInternal(content, contentType, &buf_pos, &buf_end,
&encOpts, NULL, NULL);
UA_CHECK_STATUS(res, goto error);
securityHeaderLength = calculateAsymAlgSecurityHeaderLength(channel);
if(channel->securityMode != UA_MESSAGESECURITYMODE_NONE)
padChunk(channel, &sp->asymSignatureAlgorithm, &sp->asymEncryptionAlgorithm,
&buf.data[UA_SECURECHANNEL_CHANNELHEADER_LENGTH + securityHeaderLength],
&buf_pos);
pre_sig_length = (uintptr_t)buf_pos - (uintptr_t)buf.data;
total_length = pre_sig_length;
if(channel->securityMode != UA_MESSAGESECURITYMODE_NONE)
total_length += sp->asymSignatureAlgorithm.
getLocalSignatureSize(sp, channel->channelContext);
res = prependHeadersAsym(channel, buf.data, buf_end, total_length,
securityHeaderLength, requestId, &encryptedLength);
UA_CHECK_STATUS(res, goto error);
res = signAndEncryptAsym(channel, pre_sig_length, &buf,
securityHeaderLength, total_length);
UA_CHECK_STATUS(res, goto error);
buf.length = encryptedLength;
return cm->sendWithConnection(cm, channel->connectionId, &UA_KEYVALUEMAP_NULL, &buf);
error:
cm->freeNetworkBuffer(cm, channel->connectionId, &buf);
return res;
}
static UA_StatusCode
adjustCheckMessageLimitsSym(UA_MessageContext *mc, size_t bodyLength) {
mc->messageSizeSoFar += bodyLength;
mc->chunksSoFar++;
UA_SecureChannel *channel = mc->channel;
if(mc->messageSizeSoFar > channel->config.localMaxMessageSize &&
channel->config.localMaxMessageSize != 0)
return UA_STATUSCODE_BADRESPONSETOOLARGE;
if(mc->chunksSoFar > channel->config.localMaxChunkCount &&
channel->config.localMaxChunkCount != 0)
return UA_STATUSCODE_BADRESPONSETOOLARGE;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
encodeHeadersSym(UA_MessageContext *mc, size_t totalLength) {
UA_SecureChannel *channel = mc->channel;
UA_Byte *header_pos = mc->messageBuffer.data;
UA_TcpMessageHeader header;
header.messageTypeAndChunkType = mc->messageType;
header.messageSize = (UA_UInt32)totalLength;
if(mc->final)
header.messageTypeAndChunkType += UA_CHUNKTYPE_FINAL;
else
header.messageTypeAndChunkType += UA_CHUNKTYPE_INTERMEDIATE;
UA_SequenceHeader seqHeader;
seqHeader.requestId = mc->requestId;
seqHeader.sequenceNumber = UA_SecureChannel_nextSequenceNumber(channel);
UA_StatusCode res = UA_STATUSCODE_GOOD;
res |= UA_encodeBinaryInternal(&header, &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER],
&header_pos, &mc->buf_end, NULL, NULL, NULL);
res |= UA_UInt32_encodeBinary(&channel->securityToken.channelId,
&header_pos, mc->buf_end);
res |= UA_UInt32_encodeBinary(&channel->securityToken.tokenId,
&header_pos, mc->buf_end);
res |= UA_encodeBinaryInternal(&seqHeader, &UA_TRANSPORT[UA_TRANSPORT_SEQUENCEHEADER],
&header_pos, &mc->buf_end, NULL, NULL, NULL);
return res;
}
static UA_StatusCode
sendSymmetricChunk(UA_MessageContext *mc) {
UA_SecureChannel *channel = mc->channel;
const UA_SecurityPolicy *sp = channel->securityPolicy;
UA_ConnectionManager *cm = channel->connectionManager;
if(!UA_SecureChannel_isConnected(channel))
return UA_STATUSCODE_BADCONNECTIONCLOSED;
size_t bodyLength = (uintptr_t)mc->buf_pos -
(uintptr_t)&mc->messageBuffer.data[UA_SECURECHANNEL_SYMMETRIC_HEADER_TOTALLENGTH];
size_t total_length = 0;
size_t pre_sig_length = 0;
UA_StatusCode res = adjustCheckMessageLimitsSym(mc, bodyLength);
UA_CHECK_STATUS(res, goto error);
UA_LOG_TRACE_CHANNEL(sp->logger, channel,
"Send from a symmetric message buffer of length %lu "
"a message of header+payload length of %lu",
(long unsigned int)mc->messageBuffer.length,
(long unsigned int)
((uintptr_t)mc->buf_pos - (uintptr_t)mc->messageBuffer.data));
if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT &&
!UA_SecurityPolicy_isAead(sp))
padChunk(channel, &sp->symSignatureAlgorithm, &sp->symEncryptionAlgorithm,
&mc->messageBuffer.data[UA_SECURECHANNEL_SYMMETRIC_HEADER_UNENCRYPTEDLENGTH],
&mc->buf_pos);
pre_sig_length = (uintptr_t)mc->buf_pos - (uintptr_t)mc->messageBuffer.data;
total_length = pre_sig_length;
if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
total_length += sp->symSignatureAlgorithm.
getLocalSignatureSize(sp, channel->channelContext);
UA_LOG_TRACE_CHANNEL(sp->logger, channel,
"Send from a symmetric message buffer of length %lu "
"a message of length %lu",
(long unsigned int)mc->messageBuffer.length,
(long unsigned int)total_length);
UA_assert(total_length <= channel->config.sendBufferSize);
mc->messageBuffer.length = total_length;
res = encodeHeadersSym(mc, total_length);
UA_CHECK_STATUS(res, goto error);
res = signAndEncryptSym(mc, pre_sig_length, total_length);
UA_CHECK_STATUS(res, goto error);
res = cm->sendWithConnection(cm, channel->connectionId,
&UA_KEYVALUEMAP_NULL, &mc->messageBuffer);
if(res != UA_STATUSCODE_GOOD && UA_SecureChannel_isConnected(channel))
channel->state = UA_SECURECHANNELSTATE_CLOSING;
return res;
error:
cm->freeNetworkBuffer(cm, channel->connectionId, &mc->messageBuffer);
return res;
}
static UA_StatusCode
sendSymmetricEncodingCallback(void *data, UA_Byte **buf_pos,
const UA_Byte **buf_end) {
UA_MessageContext *mc = (UA_MessageContext *)data;
mc->buf_pos = *buf_pos;
mc->buf_end = *buf_end;
UA_StatusCode res = sendSymmetricChunk(mc);
UA_CHECK_STATUS(res, return res);
UA_ConnectionManager *cm = mc->channel->connectionManager;
if(!UA_SecureChannel_isConnected(mc->channel))
return UA_STATUSCODE_BADCONNECTIONCLOSED;
res = cm->allocNetworkBuffer(cm, mc->channel->connectionId,
&mc->messageBuffer,
mc->channel->config.sendBufferSize);
UA_CHECK_STATUS(res, return res);
setBufPos(mc);
*buf_pos = mc->buf_pos;
*buf_end = mc->buf_end;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_MessageContext_begin(UA_MessageContext *mc, UA_SecureChannel *channel,
UA_UInt32 requestId, UA_MessageType messageType) {
UA_CHECK(messageType == UA_MESSAGETYPE_MSG || messageType == UA_MESSAGETYPE_CLO,
return UA_STATUSCODE_BADINTERNALERROR);
UA_ConnectionManager *cm = channel->connectionManager;
if(!UA_SecureChannel_isConnected(channel))
return UA_STATUSCODE_BADCONNECTIONCLOSED;
mc->channel = channel;
mc->requestId = requestId;
mc->chunksSoFar = 0;
mc->messageSizeSoFar = 0;
mc->final = false;
mc->messageBuffer = UA_BYTESTRING_NULL;
mc->messageType = messageType;
UA_StatusCode res =
cm->allocNetworkBuffer(cm, channel->connectionId,
&mc->messageBuffer,
channel->config.sendBufferSize);
UA_CHECK_STATUS(res, return res);
setBufPos(mc);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_MessageContext_encode(UA_MessageContext *mc, const void *content,
const UA_DataType *contentType) {
UA_EncodeBinaryOptions encOpts;
memset(&encOpts, 0, sizeof(UA_EncodeBinaryOptions));
encOpts.namespaceMapping = mc->channel->namespaceMapping;
UA_StatusCode res =
UA_encodeBinaryInternal(content, contentType, &mc->buf_pos, &mc->buf_end,
&encOpts, sendSymmetricEncodingCallback, mc);
if(res != UA_STATUSCODE_GOOD && mc->messageBuffer.length > 0)
UA_MessageContext_abort(mc);
return res;
}
UA_StatusCode
UA_MessageContext_finish(UA_MessageContext *mc) {
mc->final = true;
return sendSymmetricChunk(mc);
}
void
UA_MessageContext_abort(UA_MessageContext *mc) {
UA_ConnectionManager *cm = mc->channel->connectionManager;
if(!UA_SecureChannel_isConnected(mc->channel))
return;
cm->freeNetworkBuffer(cm, mc->channel->connectionId, &mc->messageBuffer);
}
static UA_StatusCode
sendSymmetric(UA_SecureChannel *channel, UA_UInt32 requestId,
UA_MessageType messageType, void *payload,
const UA_DataType *payloadType) {
if(!channel || !payload || !payloadType)
return UA_STATUSCODE_BADINTERNALERROR;
if(channel->state != UA_SECURECHANNELSTATE_OPEN)
return UA_STATUSCODE_BADCONNECTIONCLOSED;
UA_MessageContext mc;
UA_StatusCode res = UA_MessageContext_begin(&mc, channel, requestId, messageType);
UA_CHECK_STATUS(res, return res);
UA_assert(mc.buf_pos ==
&mc.messageBuffer.data[UA_SECURECHANNEL_SYMMETRIC_HEADER_TOTALLENGTH]);
UA_assert(mc.buf_end <= &mc.messageBuffer.data[mc.messageBuffer.length]);
res = UA_MessageContext_encode(&mc, &payloadType->binaryEncodingId,
&UA_TYPES[UA_TYPES_NODEID]);
UA_CHECK_STATUS(res, return res);
res = UA_MessageContext_encode(&mc, payload, payloadType);
UA_CHECK_STATUS(res, return res);
return UA_MessageContext_finish(&mc);
}
UA_StatusCode
UA_SecureChannel_sendMSG(UA_SecureChannel *channel, UA_UInt32 requestId,
void *payload, const UA_DataType *payloadType) {
return sendSymmetric(channel, requestId, UA_MESSAGETYPE_MSG,
payload, payloadType);
}
UA_StatusCode
UA_SecureChannel_sendCLO(UA_SecureChannel *channel, UA_UInt32 requestId,
UA_CloseSecureChannelRequest *req) {
return sendSymmetric(channel, requestId, UA_MESSAGETYPE_CLO, req,
&UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST]);
}
#define UA_SEQUENCENUMBER_ROLLOVER 4294966271
UA_UInt32
UA_SecureChannel_nextSequenceNumber(UA_SecureChannel *channel) {
UA_UInt64 next = (UA_UInt64)channel->sendSequenceNumber + 1;
if(channel->legacySequenceNumbers) {
if(next > UA_SEQUENCENUMBER_ROLLOVER)
next = 1;
channel->sendSequenceNumber = (UA_UInt32)next;
return channel->sendSequenceNumber;
}
if(next > UA_UINT32_MAX)
next = 0;
channel->sendSequenceNumber = (UA_UInt32)next;
return (channel->sendSequenceNumber == 0) ?
UA_UINT32_MAX : (channel->sendSequenceNumber - 1);
}
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
static UA_StatusCode
processSequenceNumberSym(UA_SecureChannel *channel, UA_UInt32 sequenceNumber) {
if(sequenceNumber != channel->receiveSequenceNumber + 1) {
if(!channel->legacySequenceNumbers ||
channel->receiveSequenceNumber + 1 <= UA_SEQUENCENUMBER_ROLLOVER ||
sequenceNumber >= 1024)
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
channel->receiveSequenceNumber = sequenceNumber - 1;
}
++channel->receiveSequenceNumber;
return UA_STATUSCODE_GOOD;
}
#endif
static UA_StatusCode
unpackPayloadOPN(UA_SecureChannel *channel, UA_Chunk *chunk) {
UA_assert(chunk->bytes.length >= UA_SECURECHANNEL_MESSAGE_MIN_LENGTH);
size_t offset = UA_SECURECHANNEL_MESSAGEHEADER_LENGTH;
UA_UInt32 secureChannelId;
UA_StatusCode res = UA_UInt32_decodeBinary(&chunk->bytes, &offset, &secureChannelId);
UA_assert(res == UA_STATUSCODE_GOOD);
UA_AsymmetricAlgorithmSecurityHeader asymHeader;
res = UA_decodeBinaryInternal(&chunk->bytes, &offset, &asymHeader,
&UA_TRANSPORT[UA_TRANSPORT_ASYMMETRICALGORITHMSECURITYHEADER], NULL);
UA_CHECK_STATUS(res, return res);
UA_SecurityPolicy *sp = NULL;
UA_assert(channel->processOPNHeader);
res = channel->processOPNHeader(channel->processOPNHeaderApplication,
channel, &asymHeader);
UA_CHECK_STATUS(res, goto error);
if(secureChannelId != 0 && channel->securityToken.channelId == 0)
channel->securityToken.channelId = secureChannelId;
#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
if(secureChannelId != channel->securityToken.channelId) {
if(secureChannelId != 0 || channel->securityToken.tokenId != 0) {
res = UA_STATUSCODE_BADSECURECHANNELIDINVALID;
goto error;
}
}
#endif
res = checkAsymHeader(channel, &asymHeader);
UA_CHECK_STATUS(res, goto error);
UA_AsymmetricAlgorithmSecurityHeader_clear(&asymHeader);
sp = channel->securityPolicy;
res = decryptAndVerifyChunk(channel, &sp->asymSignatureAlgorithm,
&sp->asymEncryptionAlgorithm,
chunk->messageType, &chunk->bytes, offset);
UA_CHECK_STATUS(res, return res);
UA_SequenceHeader sequenceHeader;
res = UA_decodeBinaryInternal(&chunk->bytes, &offset, &sequenceHeader,
&UA_TRANSPORT[UA_TRANSPORT_SEQUENCEHEADER], NULL);
UA_CHECK_STATUS(res, return res);
channel->receiveSequenceNumber = sequenceHeader.sequenceNumber;
chunk->requestId = sequenceHeader.requestId;
chunk->bytes.data += offset;
chunk->bytes.length -= offset;
return UA_STATUSCODE_GOOD;
error:
UA_AsymmetricAlgorithmSecurityHeader_clear(&asymHeader);
return res;
}
static UA_StatusCode
unpackPayloadMSG(UA_SecureChannel *channel, UA_Chunk *chunk,
UA_DateTime nowMonotonic) {
UA_CHECK_MEM(channel->securityPolicy, return UA_STATUSCODE_BADINTERNALERROR);
UA_assert(chunk->bytes.length >= UA_SECURECHANNEL_MESSAGE_MIN_LENGTH);
size_t offset = UA_SECURECHANNEL_MESSAGEHEADER_LENGTH;
UA_UInt32 secureChannelId;
UA_UInt32 tokenId;
UA_StatusCode res = UA_STATUSCODE_GOOD;
res |= UA_UInt32_decodeBinary(&chunk->bytes, &offset, &secureChannelId);
res |= UA_UInt32_decodeBinary(&chunk->bytes, &offset, &tokenId);
UA_assert(offset == UA_SECURECHANNEL_MESSAGE_MIN_LENGTH);
UA_assert(res == UA_STATUSCODE_GOOD);
#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
if(secureChannelId != channel->securityToken.channelId)
return UA_STATUSCODE_BADSECURECHANNELIDINVALID;
#endif
res = checkSymHeader(channel, tokenId, nowMonotonic);
UA_CHECK_STATUS(res, return res);
UA_SecurityPolicy *sp = channel->securityPolicy;
res = decryptAndVerifyChunk(channel, &sp->symSignatureAlgorithm,
&sp->symEncryptionAlgorithm,
chunk->messageType, &chunk->bytes, offset);
UA_CHECK_STATUS(res, return res);
UA_SequenceHeader sequenceHeader;
res = UA_decodeBinaryInternal(&chunk->bytes, &offset, &sequenceHeader,
&UA_TRANSPORT[UA_TRANSPORT_SEQUENCEHEADER], NULL);
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
res |= processSequenceNumberSym(channel, sequenceHeader.sequenceNumber);
#endif
UA_CHECK_STATUS(res, return res);
chunk->requestId = sequenceHeader.requestId;
chunk->bytes.data += offset;
chunk->bytes.length -= offset;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
extractCompleteChunk(UA_SecureChannel *channel, UA_Chunk *chunk,
UA_DateTime nowMonotonic) {
size_t offset = channel->unprocessedOffset;
size_t remaining = channel->unprocessed.length - offset;
if(remaining < UA_SECURECHANNEL_MESSAGEHEADER_LENGTH)
return UA_STATUSCODE_GOOD;
UA_TcpMessageHeader hdr;
UA_StatusCode res =
UA_decodeBinaryInternal(&channel->unprocessed, &offset, &hdr,
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], NULL);
UA_assert(res == UA_STATUSCODE_GOOD);
(void)res;
UA_MessageType msgType = (UA_MessageType)
(hdr.messageTypeAndChunkType & UA_BITMASK_MESSAGETYPE);
UA_ChunkType chunkType = (UA_ChunkType)
(hdr.messageTypeAndChunkType & UA_BITMASK_CHUNKTYPE);
if(hdr.messageSize < UA_SECURECHANNEL_MESSAGE_MIN_LENGTH)
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
if(hdr.messageSize > channel->config.recvBufferSize)
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
if(hdr.messageSize > remaining)
return UA_STATUSCODE_GOOD;
chunk->bytes.data = channel->unprocessed.data + channel->unprocessedOffset;
chunk->bytes.length = hdr.messageSize;
chunk->messageType = msgType;
chunk->chunkType = chunkType;
chunk->requestId = 0;
chunk->copied = false;
channel->unprocessedOffset += hdr.messageSize;
switch(msgType) {
case UA_MESSAGETYPE_OPN:
if(chunkType != UA_CHUNKTYPE_FINAL)
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
if(channel->state != UA_SECURECHANNELSTATE_OPEN &&
channel->state != UA_SECURECHANNELSTATE_OPN_SENT &&
channel->state != UA_SECURECHANNELSTATE_ACK_SENT)
return UA_STATUSCODE_BADINVALIDSTATE;
res = unpackPayloadOPN(channel, chunk);
break;
case UA_MESSAGETYPE_MSG:
case UA_MESSAGETYPE_CLO:
if(chunkType != UA_CHUNKTYPE_FINAL &&
chunkType != UA_CHUNKTYPE_INTERMEDIATE &&
chunkType != UA_CHUNKTYPE_ABORT)
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
if(channel->state != UA_SECURECHANNELSTATE_OPEN)
return UA_STATUSCODE_BADINVALIDSTATE;
res = unpackPayloadMSG(channel, chunk, nowMonotonic);
break;
case UA_MESSAGETYPE_RHE:
case UA_MESSAGETYPE_HEL:
case UA_MESSAGETYPE_ACK:
case UA_MESSAGETYPE_ERR:
if(chunkType != UA_CHUNKTYPE_FINAL)
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
chunk->bytes.data += UA_SECURECHANNEL_MESSAGEHEADER_LENGTH;
chunk->bytes.length -= UA_SECURECHANNEL_MESSAGEHEADER_LENGTH;
break;
default:
res = UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
break;
}
return res;
}
UA_StatusCode
UA_SecureChannel_loadBuffer(UA_SecureChannel *channel, const UA_ByteString buffer) {
if(channel->unprocessed.length > 0) {
UA_assert(channel->unprocessedCopied == true);
UA_Byte *t = (UA_Byte*)
UA_realloc(channel->unprocessed.data,
channel->unprocessed.length + buffer.length);
if(!t)
return UA_STATUSCODE_BADOUTOFMEMORY;
if(buffer.length)
memcpy(t + channel->unprocessed.length, buffer.data, buffer.length);
channel->unprocessed.data = t;
channel->unprocessed.length += buffer.length;
return UA_STATUSCODE_GOOD;
}
channel->unprocessed = buffer;
channel->unprocessedCopied = false;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_SecureChannel_getCompleteMessage(UA_SecureChannel *channel,
UA_MessageType *messageType, UA_UInt32 *requestId,
UA_ByteString *payload, UA_Boolean *copied,
UA_DateTime nowMonotonic) {
UA_Chunk chunk, *pchunk;
UA_StatusCode res = UA_STATUSCODE_GOOD;
extract_chunk:
memset(&chunk, 0, sizeof(UA_Chunk));
res = extractCompleteChunk(channel, &chunk, nowMonotonic);
if(chunk.bytes.length == 0 || res != UA_STATUSCODE_GOOD)
return res;
switch(chunk.chunkType) {
case UA_CHUNKTYPE_ABORT:
deleteChunks(channel);
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
goto extract_chunk;
case UA_CHUNKTYPE_INTERMEDIATE:
if((channel->config.localMaxChunkCount != 0 &&
channel->chunksCount >= channel->config.localMaxChunkCount) ||
(channel->config.localMaxMessageSize != 0 &&
channel->chunksLength + chunk.bytes.length > channel->config.localMaxMessageSize)) {
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
}
pchunk = (UA_Chunk*)UA_malloc(sizeof(UA_Chunk));
if(!pchunk) {
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
return UA_STATUSCODE_BADOUTOFMEMORY;
}
*pchunk = chunk;
TAILQ_INSERT_TAIL(&channel->chunks, pchunk, pointers);
channel->chunksCount++;
channel->chunksLength += pchunk->bytes.length;
goto extract_chunk;
case UA_CHUNKTYPE_FINAL:
default:
UA_assert(chunk.chunkType == UA_CHUNKTYPE_FINAL);
break;
}
size_t messageSize = chunk.bytes.length;
UA_Chunk *first = NULL;
TAILQ_FOREACH(pchunk, &channel->chunks, pointers) {
if(chunk.requestId != pchunk->requestId)
continue;
if(chunk.messageType != pchunk->messageType) {
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
}
if(!first)
first = pchunk;
messageSize += pchunk->bytes.length;
}
if(channel->config.localMaxMessageSize != 0 &&
messageSize > channel->config.localMaxMessageSize) {
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
}
if(messageSize > chunk.bytes.length) {
UA_assert(first != NULL);
UA_ByteString message;
if(first->copied) {
message.data = (UA_Byte*)UA_realloc(first->bytes.data, messageSize);
} else {
message.data = (UA_Byte*)UA_malloc(messageSize);
if(message.data)
memcpy(message.data, first->bytes.data, first->bytes.length);
}
if(!message.data) {
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
return UA_STATUSCODE_BADOUTOFMEMORY;
}
message.length = first->bytes.length;
pchunk = TAILQ_NEXT(first, pointers);
first->copied = false;
channel->chunksCount--;
channel->chunksLength -= first->bytes.length;
TAILQ_REMOVE(&channel->chunks, first, pointers);
UA_Chunk_delete(first);
UA_Chunk *next;
for(; pchunk; pchunk = next) {
next = TAILQ_NEXT(pchunk, pointers);
if(chunk.requestId != pchunk->requestId)
continue;
memcpy(message.data + message.length, pchunk->bytes.data, pchunk->bytes.length);
message.length += pchunk->bytes.length;
channel->chunksCount--;
channel->chunksLength -= pchunk->bytes.length;
TAILQ_REMOVE(&channel->chunks, pchunk, pointers);
UA_Chunk_delete(pchunk);
}
memcpy(message.data + message.length, chunk.bytes.data, chunk.bytes.length);
message.length += chunk.bytes.length;
UA_assert(message.length == messageSize);
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
chunk.bytes = message;
chunk.copied = true;
}
*requestId = chunk.requestId;
*messageType = chunk.messageType;
*payload = chunk.bytes;
*copied = chunk.copied;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_SecureChannel_persistBuffer(UA_SecureChannel *channel) {
UA_StatusCode res = UA_STATUSCODE_GOOD;
UA_Chunk *chunk;
TAILQ_FOREACH(chunk, &channel->chunks, pointers) {
if(chunk->copied)
continue;
UA_ByteString tmp = UA_BYTESTRING_NULL;
res |= UA_ByteString_copy(&chunk->bytes, &tmp);
chunk->bytes = tmp;
chunk->copied = true;
}
UA_assert(channel->unprocessed.length >= channel->unprocessedOffset);
if(channel->unprocessed.length == channel->unprocessedOffset) {
if(channel->unprocessedCopied)
UA_ByteString_clear(&channel->unprocessed);
else
UA_ByteString_init(&channel->unprocessed);
channel->unprocessedOffset = 0;
return res;
}
UA_ByteString tmp = UA_BYTESTRING_NULL;
UA_ByteString remaining = channel->unprocessed;
remaining.data += channel->unprocessedOffset;
remaining.length -= channel->unprocessedOffset;
res |= UA_ByteString_copy(&remaining, &tmp);
if(channel->unprocessedCopied)
UA_ByteString_clear(&channel->unprocessed);
channel->unprocessed = tmp;
channel->unprocessedOffset = 0;
channel->unprocessedCopied = true;
return res;
}