| /* 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-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
| * Copyright 2014, 2016-2017 (c) Florian Palm |
| * Copyright 2015-2016 (c) Sten GrĂ¼ner |
| * Copyright 2015 (c) Oleksiy Vasylyev |
| * Copyright 2016 (c) TorbenD |
| * Copyright 2017 (c) Stefan Profanter, fortiss GmbH |
| * Copyright 2017-2018 (c) Mark Giraud, Fraunhofer IOSB |
| */ |
| |
| #include "ua_securechannel.h" |
| |
| #include <open62541/plugin/securitypolicy.h> |
| #include <open62541/transport_generated_encoding_binary.h> |
| #include <open62541/transport_generated_handling.h> |
| #include <open62541/types_generated_encoding_binary.h> |
| #include <open62541/types_generated_handling.h> |
| |
| #include "ua_types_encoding_binary.h" |
| #include "ua_util_internal.h" |
| |
| #define UA_BITMASK_MESSAGETYPE 0x00ffffffu |
| #define UA_BITMASK_CHUNKTYPE 0xff000000u |
| #define UA_ASYMMETRIC_ALG_SECURITY_HEADER_FIXED_LENGTH 12 |
| #define UA_SYMMETRIC_ALG_SECURITY_HEADER_LENGTH 4 |
| #define UA_SEQUENCE_HEADER_LENGTH 8 |
| #define UA_SECUREMH_AND_SYMALGH_LENGTH \ |
| (UA_SECURE_CONVERSATION_MESSAGE_HEADER_LENGTH + \ |
| UA_SYMMETRIC_ALG_SECURITY_HEADER_LENGTH) |
| |
| const UA_ByteString UA_SECURITY_POLICY_NONE_URI = |
| {47, (UA_Byte *)"http://opcfoundation.org/UA/SecurityPolicy#None"}; |
| |
| #ifdef UA_ENABLE_UNIT_TEST_FAILURE_HOOKS |
| UA_StatusCode decrypt_verifySignatureFailure; |
| UA_StatusCode sendAsym_sendFailure; |
| UA_StatusCode processSym_seqNumberFailure; |
| #endif |
| |
| void |
| UA_SecureChannel_init(UA_SecureChannel *channel) { |
| /* Linked lists are also initialized by zeroing out */ |
| memset(channel, 0, sizeof(UA_SecureChannel)); |
| channel->state = UA_SECURECHANNELSTATE_FRESH; |
| TAILQ_INIT(&channel->messages); |
| } |
| |
| UA_StatusCode |
| UA_SecureChannel_setSecurityPolicy(UA_SecureChannel *channel, |
| const UA_SecurityPolicy *securityPolicy, |
| const UA_ByteString *remoteCertificate) { |
| /* Is a policy already configured? */ |
| if(channel->securityPolicy) { |
| UA_LOG_ERROR(securityPolicy->logger, UA_LOGCATEGORY_SECURITYPOLICY, |
| "Security policy already configured"); |
| return UA_STATUSCODE_BADINTERNALERROR; |
| } |
| |
| UA_StatusCode retval; |
| if(securityPolicy->certificateVerification != NULL) { |
| retval = securityPolicy->certificateVerification-> |
| verifyCertificate(securityPolicy->certificateVerification->context, |
| remoteCertificate); |
| |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| } else { |
| UA_LOG_WARNING(securityPolicy->logger, UA_LOGCATEGORY_SECURITYPOLICY, |
| "Security policy None is used to create SecureChannel. Accepting all certificates"); |
| } |
| |
| retval = securityPolicy->channelModule. |
| newContext(securityPolicy, remoteCertificate, &channel->channelContext); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| retval = UA_ByteString_copy(remoteCertificate, &channel->remoteCertificate); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| UA_ByteString remoteCertificateThumbprint = {20, channel->remoteCertificateThumbprint}; |
| retval = securityPolicy->asymmetricModule. |
| makeCertificateThumbprint(securityPolicy, &channel->remoteCertificate, |
| &remoteCertificateThumbprint); |
| |
| if(retval == UA_STATUSCODE_GOOD) |
| channel->securityPolicy = securityPolicy; |
| |
| return retval; |
| } |
| |
| static void |
| deleteMessage(UA_Message *me) { |
| UA_ChunkPayload *cp; |
| while((cp = SIMPLEQ_FIRST(&me->chunkPayloads))) { |
| if(cp->copied) |
| UA_ByteString_deleteMembers(&cp->bytes); |
| SIMPLEQ_REMOVE_HEAD(&me->chunkPayloads, pointers); |
| UA_free(cp); |
| } |
| UA_free(me); |
| } |
| |
| static void |
| deleteLatestMessage(UA_SecureChannel *channel, UA_UInt32 requestId) { |
| UA_Message *me = TAILQ_LAST(&channel->messages, UA_MessageQueue); |
| if(!me) |
| return; |
| if(me->requestId != requestId) |
| return; |
| |
| TAILQ_REMOVE(&channel->messages, me, pointers); |
| deleteMessage(me); |
| } |
| |
| void |
| UA_SecureChannel_deleteMessages(UA_SecureChannel *channel) { |
| UA_Message *me, *me_tmp; |
| TAILQ_FOREACH_SAFE(me, &channel->messages, pointers, me_tmp) { |
| TAILQ_REMOVE(&channel->messages, me, pointers); |
| deleteMessage(me); |
| } |
| } |
| |
| void |
| UA_SecureChannel_deleteMembers(UA_SecureChannel *channel) { |
| /* Delete members */ |
| UA_ByteString_deleteMembers(&channel->remoteCertificate); |
| UA_ByteString_deleteMembers(&channel->localNonce); |
| UA_ByteString_deleteMembers(&channel->remoteNonce); |
| UA_ChannelSecurityToken_deleteMembers(&channel->securityToken); |
| UA_ChannelSecurityToken_deleteMembers(&channel->nextSecurityToken); |
| |
| /* Delete the channel context for the security policy */ |
| if(channel->securityPolicy) { |
| channel->securityPolicy->channelModule.deleteContext(channel->channelContext); |
| channel->securityPolicy = NULL; |
| } |
| |
| /* Remove the buffered messages */ |
| UA_SecureChannel_deleteMessages(channel); |
| |
| UA_SecureChannel_init(channel); |
| } |
| |
| void |
| UA_SecureChannel_close(UA_SecureChannel *channel) { |
| /* Set the status to closed */ |
| channel->state = UA_SECURECHANNELSTATE_CLOSED; |
| |
| /* Detach from the connection and close the connection */ |
| if(channel->connection) { |
| if(channel->connection->state != UA_CONNECTION_CLOSED) |
| channel->connection->close(channel->connection); |
| UA_Connection_detachSecureChannel(channel->connection); |
| } |
| |
| /* Remove session pointers (not the sessions) and NULL the pointers back to |
| * the SecureChannel in the Session */ |
| UA_SessionHeader *sh, *temp; |
| LIST_FOREACH_SAFE(sh, &channel->sessions, pointers, temp) { |
| sh->channel = NULL; |
| LIST_REMOVE(sh, pointers); |
| } |
| } |
| |
| UA_StatusCode |
| UA_SecureChannel_generateLocalNonce(UA_SecureChannel *channel) { |
| if(!channel->securityPolicy) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| /* Is the length of the previous nonce correct? */ |
| size_t nonceLength = channel->securityPolicy->symmetricModule.secureChannelNonceLength; |
| if(channel->localNonce.length != nonceLength) { |
| UA_ByteString_deleteMembers(&channel->localNonce); |
| UA_StatusCode retval = UA_ByteString_allocBuffer(&channel->localNonce, nonceLength); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| } |
| |
| return channel->securityPolicy->symmetricModule. |
| generateNonce(channel->securityPolicy, &channel->localNonce); |
| } |
| |
| static UA_StatusCode |
| UA_SecureChannel_generateLocalKeys(const UA_SecureChannel *const channel, |
| const UA_SecurityPolicy *const securityPolicy) { |
| UA_LOG_TRACE_CHANNEL(securityPolicy->logger, channel, "Generating new local keys"); |
| const UA_SecurityPolicyChannelModule *channelModule = &securityPolicy->channelModule; |
| const UA_SecurityPolicySymmetricModule *symmetricModule = &securityPolicy->symmetricModule; |
| const UA_SecurityPolicyCryptoModule *const cryptoModule = |
| &securityPolicy->symmetricModule.cryptoModule; |
| |
| /* Symmetric key length */ |
| size_t encryptionKeyLength = |
| cryptoModule->encryptionAlgorithm.getLocalKeyLength(securityPolicy, channel->channelContext); |
| size_t encryptionBlockSize = |
| cryptoModule->encryptionAlgorithm.getLocalBlockSize(securityPolicy, channel->channelContext); |
| size_t signingKeyLength = |
| cryptoModule->signatureAlgorithm.getLocalKeyLength(securityPolicy, channel->channelContext); |
| const size_t bufSize = encryptionBlockSize + signingKeyLength + encryptionKeyLength; |
| UA_STACKARRAY(UA_Byte, bufBytes, bufSize); |
| UA_ByteString buffer = {bufSize, bufBytes}; |
| |
| /* Local keys */ |
| UA_StatusCode retval = symmetricModule->generateKey(securityPolicy, &channel->remoteNonce, |
| &channel->localNonce, &buffer); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| const UA_ByteString localSigningKey = {signingKeyLength, buffer.data}; |
| const UA_ByteString localEncryptingKey = {encryptionKeyLength, |
| buffer.data + signingKeyLength}; |
| const UA_ByteString localIv = {encryptionBlockSize, |
| buffer.data + signingKeyLength + |
| encryptionKeyLength}; |
| |
| retval = channelModule->setLocalSymSigningKey(channel->channelContext, &localSigningKey); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| retval = channelModule->setLocalSymEncryptingKey(channel->channelContext, &localEncryptingKey); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| retval = channelModule->setLocalSymIv(channel->channelContext, &localIv); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| return retval; |
| } |
| |
| static UA_StatusCode |
| UA_SecureChannel_generateRemoteKeys(const UA_SecureChannel *const channel, |
| const UA_SecurityPolicy *const securityPolicy) { |
| UA_LOG_TRACE_CHANNEL(securityPolicy->logger, channel, "Generating new remote keys"); |
| const UA_SecurityPolicyChannelModule *channelModule = &securityPolicy->channelModule; |
| const UA_SecurityPolicySymmetricModule *symmetricModule = &securityPolicy->symmetricModule; |
| const UA_SecurityPolicyCryptoModule *const cryptoModule = |
| &securityPolicy->symmetricModule.cryptoModule; |
| |
| /* Symmetric key length */ |
| size_t encryptionKeyLength = |
| cryptoModule->encryptionAlgorithm.getRemoteKeyLength(securityPolicy, channel->channelContext); |
| size_t encryptionBlockSize = |
| cryptoModule->encryptionAlgorithm.getRemoteBlockSize(securityPolicy, channel->channelContext); |
| size_t signingKeyLength = |
| cryptoModule->signatureAlgorithm.getRemoteKeyLength(securityPolicy, channel->channelContext); |
| const size_t bufSize = encryptionBlockSize + signingKeyLength + encryptionKeyLength; |
| UA_STACKARRAY(UA_Byte, bufBytes, bufSize); |
| UA_ByteString buffer = {bufSize, bufBytes}; |
| |
| /* Remote keys */ |
| UA_StatusCode retval = symmetricModule->generateKey(securityPolicy, &channel->localNonce, |
| &channel->remoteNonce, &buffer); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| const UA_ByteString remoteSigningKey = {signingKeyLength, buffer.data}; |
| const UA_ByteString remoteEncryptingKey = {encryptionKeyLength, |
| buffer.data + signingKeyLength}; |
| const UA_ByteString remoteIv = {encryptionBlockSize, |
| buffer.data + signingKeyLength + |
| encryptionKeyLength}; |
| |
| retval = channelModule->setRemoteSymSigningKey(channel->channelContext, &remoteSigningKey); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| retval = channelModule->setRemoteSymEncryptingKey(channel->channelContext, &remoteEncryptingKey); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| retval = channelModule->setRemoteSymIv(channel->channelContext, &remoteIv); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| return retval; |
| } |
| |
| UA_StatusCode |
| UA_SecureChannel_generateNewKeys(UA_SecureChannel *channel) { |
| UA_StatusCode retval = |
| UA_SecureChannel_generateLocalKeys(channel, channel->securityPolicy); |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_LOG_ERROR(channel->securityPolicy->logger, UA_LOGCATEGORY_SECURECHANNEL, |
| "Could not generate a local key"); |
| return retval; |
| } |
| |
| retval = UA_SecureChannel_generateRemoteKeys(channel, channel->securityPolicy); |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_LOG_ERROR(channel->securityPolicy->logger, UA_LOGCATEGORY_SECURECHANNEL, |
| "Could not generate a remote key"); |
| return retval; |
| } |
| |
| return retval; |
| } |
| |
| UA_SessionHeader * |
| UA_SecureChannel_getSession(UA_SecureChannel *channel, |
| const UA_NodeId *authenticationToken) { |
| UA_SessionHeader *sh; |
| LIST_FOREACH(sh, &channel->sessions, pointers) { |
| if(UA_NodeId_equal(&sh->authenticationToken, authenticationToken)) |
| break; |
| } |
| return sh; |
| } |
| |
| UA_StatusCode |
| UA_SecureChannel_revolveTokens(UA_SecureChannel *channel) { |
| if(channel->nextSecurityToken.tokenId == 0) // no security token issued |
| return UA_STATUSCODE_BADSECURECHANNELTOKENUNKNOWN; |
| |
| //FIXME: not thread-safe ???? Why is this not thread safe? |
| UA_ChannelSecurityToken_deleteMembers(&channel->previousSecurityToken); |
| UA_ChannelSecurityToken_copy(&channel->securityToken, &channel->previousSecurityToken); |
| |
| UA_ChannelSecurityToken_deleteMembers(&channel->securityToken); |
| UA_ChannelSecurityToken_copy(&channel->nextSecurityToken, &channel->securityToken); |
| |
| UA_ChannelSecurityToken_deleteMembers(&channel->nextSecurityToken); |
| UA_ChannelSecurityToken_init(&channel->nextSecurityToken); |
| |
| /* remote keys are generated later on */ |
| return UA_SecureChannel_generateLocalKeys(channel, channel->securityPolicy); |
| } |
| /***************************/ |
| /* Send Asymmetric Message */ |
| /***************************/ |
| |
| static size_t |
| calculateAsymAlgSecurityHeaderLength(const UA_SecureChannel *channel) { |
| size_t asymHeaderLength = UA_ASYMMETRIC_ALG_SECURITY_HEADER_FIXED_LENGTH + |
| channel->securityPolicy->policyUri.length; |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && |
| channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return asymHeaderLength; |
| |
| /* OPN is always encrypted even if the mode is sign only */ |
| asymHeaderLength += 20; /* Thumbprints are always 20 byte long */ |
| asymHeaderLength += channel->securityPolicy->localCertificate.length; |
| return asymHeaderLength; |
| } |
| |
| static UA_StatusCode |
| prependHeadersAsym(UA_SecureChannel *const channel, UA_Byte *header_pos, |
| const UA_Byte *buf_end, size_t totalLength, |
| size_t securityHeaderLength, UA_UInt32 requestId, |
| size_t *const finalLength) { |
| UA_StatusCode retval; |
| size_t dataToEncryptLength = |
| totalLength - (UA_SECURE_CONVERSATION_MESSAGE_HEADER_LENGTH + securityHeaderLength); |
| |
| UA_SecureConversationMessageHeader respHeader; |
| respHeader.messageHeader.messageTypeAndChunkType = UA_MESSAGETYPE_OPN + UA_CHUNKTYPE_FINAL; |
| respHeader.messageHeader.messageSize = (UA_UInt32) |
| (totalLength + |
| UA_SecurityPolicy_getRemoteAsymEncryptionBufferLengthOverhead(channel->securityPolicy, |
| channel->channelContext, |
| dataToEncryptLength)); |
| respHeader.secureChannelId = channel->securityToken.channelId; |
| retval = UA_encodeBinary(&respHeader, |
| &UA_TRANSPORT[UA_TRANSPORT_SECURECONVERSATIONMESSAGEHEADER], |
| &header_pos, &buf_end, NULL, NULL); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| UA_AsymmetricAlgorithmSecurityHeader asymHeader; |
| UA_AsymmetricAlgorithmSecurityHeader_init(&asymHeader); |
| asymHeader.securityPolicyUri = channel->securityPolicy->policyUri; |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || |
| channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { |
| asymHeader.senderCertificate = channel->securityPolicy->localCertificate; |
| asymHeader.receiverCertificateThumbprint.length = 20; |
| asymHeader.receiverCertificateThumbprint.data = channel->remoteCertificateThumbprint; |
| } |
| retval = UA_encodeBinary(&asymHeader, |
| &UA_TRANSPORT[UA_TRANSPORT_ASYMMETRICALGORITHMSECURITYHEADER], |
| &header_pos, &buf_end, NULL, NULL); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| UA_SequenceHeader seqHeader; |
| seqHeader.requestId = requestId; |
| seqHeader.sequenceNumber = UA_atomic_addUInt32(&channel->sendSequenceNumber, 1); |
| retval = UA_encodeBinary(&seqHeader, &UA_TRANSPORT[UA_TRANSPORT_SEQUENCEHEADER], |
| &header_pos, &buf_end, NULL, NULL); |
| |
| *finalLength = respHeader.messageHeader.messageSize; |
| |
| return retval; |
| } |
| |
| static void |
| hideBytesAsym(const UA_SecureChannel *channel, UA_Byte **buf_start, |
| const UA_Byte **buf_end) { |
| *buf_start += UA_SECURE_CONVERSATION_MESSAGE_HEADER_LENGTH; |
| *buf_start += calculateAsymAlgSecurityHeaderLength(channel); |
| *buf_start += UA_SEQUENCE_HEADER_LENGTH; |
| |
| #ifdef UA_ENABLE_ENCRYPTION |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && |
| channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return; |
| |
| const UA_SecurityPolicy *securityPolicy = channel->securityPolicy; |
| |
| /* Hide bytes for signature and padding */ |
| size_t potentialEncryptMaxSize = (size_t)(*buf_end - *buf_start) + UA_SEQUENCE_HEADER_LENGTH; |
| *buf_end -= securityPolicy->asymmetricModule.cryptoModule.signatureAlgorithm. |
| getLocalSignatureSize(securityPolicy, channel->channelContext); |
| *buf_end -= 2; /* padding byte and extraPadding byte */ |
| |
| /* Add some overhead length due to RSA implementations adding a signature themselves */ |
| *buf_end -= UA_SecurityPolicy_getRemoteAsymEncryptionBufferLengthOverhead(securityPolicy, |
| channel->channelContext, |
| potentialEncryptMaxSize); |
| #endif |
| } |
| |
| #ifdef UA_ENABLE_ENCRYPTION |
| |
| static void |
| padChunkAsym(UA_SecureChannel *channel, const UA_ByteString *const buf, |
| size_t securityHeaderLength, UA_Byte **buf_pos) { |
| const UA_SecurityPolicy *const securityPolicy = channel->securityPolicy; |
| |
| /* Also pad if the securityMode is SIGN_ONLY, since we are using |
| * asymmetric communication to exchange keys and thus need to encrypt. */ |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && |
| channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return; |
| |
| const UA_Byte *buf_body_start = |
| &buf->data[UA_SECURE_CONVERSATION_MESSAGE_HEADER_LENGTH + |
| UA_SEQUENCE_HEADER_LENGTH + securityHeaderLength]; |
| const size_t bytesToWrite = |
| (uintptr_t)*buf_pos - (uintptr_t)buf_body_start + UA_SEQUENCE_HEADER_LENGTH; |
| |
| /* Compute the padding length */ |
| size_t plainTextBlockSize = securityPolicy->asymmetricModule.cryptoModule.encryptionAlgorithm. |
| getRemotePlainTextBlockSize(securityPolicy, channel->channelContext); |
| size_t signatureSize = securityPolicy->asymmetricModule.cryptoModule.signatureAlgorithm. |
| getLocalSignatureSize(securityPolicy, channel->channelContext); |
| size_t paddingBytes = 1; |
| if(securityPolicy->asymmetricModule.cryptoModule.encryptionAlgorithm. |
| getRemoteKeyLength(securityPolicy, channel->channelContext) > 2048) |
| ++paddingBytes; /* extra padding */ |
| size_t totalPaddingSize = |
| (plainTextBlockSize - ((bytesToWrite + signatureSize + paddingBytes) % plainTextBlockSize)); |
| |
| /* Write the padding. This is <= because the paddingSize byte also has to be written */ |
| UA_Byte paddingSize = (UA_Byte)(totalPaddingSize & 0xffu); |
| for(UA_UInt16 i = 0; i <= totalPaddingSize; ++i) { |
| **buf_pos = paddingSize; |
| ++*buf_pos; |
| } |
| |
| /* Write the extra padding byte if required */ |
| if(securityPolicy->asymmetricModule.cryptoModule.encryptionAlgorithm. |
| getRemoteKeyLength(securityPolicy, channel->channelContext) > 2048) { |
| UA_Byte extraPaddingSize = (UA_Byte)(totalPaddingSize >> 8u); |
| **buf_pos = extraPaddingSize; |
| ++*buf_pos; |
| } |
| } |
| |
| static UA_StatusCode |
| signAndEncryptAsym(UA_SecureChannel *const channel, size_t preSignLength, |
| UA_ByteString *buf, size_t securityHeaderLength, |
| size_t totalLength) { |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && |
| channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return UA_STATUSCODE_GOOD; |
| |
| const UA_SecurityPolicy *const securityPolicy = channel->securityPolicy; |
| |
| /* Sign message */ |
| const UA_ByteString dataToSign = {preSignLength, buf->data}; |
| size_t sigsize = securityPolicy->asymmetricModule.cryptoModule.signatureAlgorithm. |
| getLocalSignatureSize(securityPolicy, channel->channelContext); |
| UA_ByteString signature = {sigsize, buf->data + preSignLength}; |
| UA_StatusCode retval = securityPolicy->asymmetricModule.cryptoModule.signatureAlgorithm. |
| sign(securityPolicy, channel->channelContext, &dataToSign, &signature); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Specification part 6, 6.7.4: The OpenSecureChannel Messages are |
| * signed and encrypted if the SecurityMode is not None (even if the |
| * SecurityMode is SignOnly). */ |
| size_t unencrypted_length = |
| UA_SECURE_CONVERSATION_MESSAGE_HEADER_LENGTH + securityHeaderLength; |
| UA_ByteString dataToEncrypt = {totalLength - unencrypted_length, |
| &buf->data[unencrypted_length]}; |
| return securityPolicy->asymmetricModule.cryptoModule.encryptionAlgorithm. |
| encrypt(securityPolicy, channel->channelContext, &dataToEncrypt); |
| } |
| |
| #endif /* UA_ENABLE_ENCRYPTION */ |
| |
| /* Sends an OPN message using asymmetric encryption if defined */ |
| UA_StatusCode |
| UA_SecureChannel_sendAsymmetricOPNMessage(UA_SecureChannel *channel, |
| UA_UInt32 requestId, const void *content, |
| const UA_DataType *contentType) { |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_INVALID) |
| return UA_STATUSCODE_BADSECURITYMODEREJECTED; |
| |
| const UA_SecurityPolicy *const securityPolicy = channel->securityPolicy; |
| UA_Connection *connection = channel->connection; |
| if(!connection) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| /* Allocate the message buffer */ |
| UA_ByteString buf = UA_BYTESTRING_NULL; |
| UA_StatusCode retval = |
| connection->getSendBuffer(connection, connection->config.sendBufferSize, &buf); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Restrict buffer to the available space for the payload */ |
| UA_Byte *buf_pos = buf.data; |
| const UA_Byte *buf_end = &buf.data[buf.length]; |
| hideBytesAsym(channel, &buf_pos, &buf_end); |
| |
| /* Encode the message type and content */ |
| UA_NodeId typeId = UA_NODEID_NUMERIC(0, contentType->binaryEncodingId); |
| retval |= UA_encodeBinary(&typeId, &UA_TYPES[UA_TYPES_NODEID], |
| &buf_pos, &buf_end, NULL, NULL); |
| retval |= UA_encodeBinary(content, contentType, |
| &buf_pos, &buf_end, NULL, NULL); |
| if(retval != UA_STATUSCODE_GOOD) { |
| connection->releaseSendBuffer(connection, &buf); |
| return retval; |
| } |
| |
| const size_t securityHeaderLength = calculateAsymAlgSecurityHeaderLength(channel); |
| |
| /* Add padding to the chunk */ |
| #ifdef UA_ENABLE_ENCRYPTION |
| padChunkAsym(channel, &buf, securityHeaderLength, &buf_pos); |
| #endif |
| |
| /* The total message length */ |
| size_t pre_sig_length = (uintptr_t)buf_pos - (uintptr_t)buf.data; |
| size_t total_length = pre_sig_length; |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || |
| channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| total_length += securityPolicy->asymmetricModule.cryptoModule.signatureAlgorithm. |
| getLocalSignatureSize(securityPolicy, channel->channelContext); |
| |
| /* The total message length is known here which is why we encode the headers |
| * at this step and not earlier. */ |
| size_t finalLength = 0; |
| retval = prependHeadersAsym(channel, buf.data, buf_end, total_length, |
| securityHeaderLength, requestId, &finalLength); |
| if(retval != UA_STATUSCODE_GOOD) |
| goto error; |
| |
| #ifdef UA_ENABLE_ENCRYPTION |
| retval = signAndEncryptAsym(channel, pre_sig_length, &buf, securityHeaderLength, total_length); |
| if(retval != UA_STATUSCODE_GOOD) |
| goto error; |
| #endif |
| |
| /* Send the message, the buffer is freed in the network layer */ |
| buf.length = finalLength; |
| retval = connection->send(connection, &buf); |
| #ifdef UA_ENABLE_UNIT_TEST_FAILURE_HOOKS |
| retval |= sendAsym_sendFailure; |
| #endif |
| return retval; |
| |
| error: |
| connection->releaseSendBuffer(connection, &buf); |
| return retval; |
| } |
| |
| /**************************/ |
| /* Send Symmetric Message */ |
| /**************************/ |
| |
| #ifdef UA_ENABLE_ENCRYPTION |
| |
| static UA_UInt16 |
| calculatePaddingSym(const UA_SecurityPolicy *securityPolicy, const void *channelContext, |
| size_t bytesToWrite, UA_Byte *paddingSize, UA_Byte *extraPaddingSize) { |
| size_t encryptionBlockSize = securityPolicy->symmetricModule.cryptoModule. |
| encryptionAlgorithm.getLocalBlockSize(securityPolicy, channelContext); |
| size_t signatureSize = securityPolicy->symmetricModule.cryptoModule.signatureAlgorithm. |
| getLocalSignatureSize(securityPolicy, channelContext); |
| |
| size_t padding = (encryptionBlockSize - |
| ((bytesToWrite + signatureSize + 1) % encryptionBlockSize)); |
| *paddingSize = (UA_Byte)padding; |
| *extraPaddingSize = (UA_Byte)(padding >> 8u); |
| return (UA_UInt16)padding; |
| } |
| |
| static void |
| padChunkSym(UA_MessageContext *messageContext, size_t bodyLength) { |
| if(messageContext->channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return; |
| |
| /* The bytes for the padding and signature were removed from buf_end before |
| * encoding the payload. So we don't have to check if there is enough |
| * space. */ |
| |
| size_t bytesToWrite = bodyLength + UA_SEQUENCE_HEADER_LENGTH; |
| UA_Byte paddingSize = 0; |
| UA_Byte extraPaddingSize = 0; |
| UA_UInt16 totalPaddingSize = |
| calculatePaddingSym(messageContext->channel->securityPolicy, |
| messageContext->channel->channelContext, |
| bytesToWrite, &paddingSize, &extraPaddingSize); |
| |
| /* This is <= because the paddingSize byte also has to be written. */ |
| for(UA_UInt16 i = 0; i <= totalPaddingSize; ++i) { |
| *messageContext->buf_pos = paddingSize; |
| ++(messageContext->buf_pos); |
| } |
| if(extraPaddingSize > 0) { |
| *messageContext->buf_pos = extraPaddingSize; |
| ++(messageContext->buf_pos); |
| } |
| } |
| |
| static UA_StatusCode |
| signChunkSym(UA_MessageContext *const messageContext, size_t preSigLength) { |
| const UA_SecureChannel *channel = messageContext->channel; |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && |
| channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return UA_STATUSCODE_GOOD; |
| |
| const UA_SecurityPolicy *securityPolicy = channel->securityPolicy; |
| UA_ByteString dataToSign = messageContext->messageBuffer; |
| dataToSign.length = preSigLength; |
| UA_ByteString signature; |
| signature.length = securityPolicy->symmetricModule.cryptoModule.signatureAlgorithm. |
| getLocalSignatureSize(securityPolicy, channel->channelContext); |
| signature.data = messageContext->buf_pos; |
| |
| return securityPolicy->symmetricModule.cryptoModule.signatureAlgorithm. |
| sign(securityPolicy, channel->channelContext, &dataToSign, &signature); |
| } |
| |
| static UA_StatusCode |
| encryptChunkSym(UA_MessageContext *const messageContext, size_t totalLength) { |
| const UA_SecureChannel *channel = messageContext->channel; |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return UA_STATUSCODE_GOOD; |
| |
| UA_ByteString dataToEncrypt; |
| dataToEncrypt.data = messageContext->messageBuffer.data + UA_SECUREMH_AND_SYMALGH_LENGTH; |
| dataToEncrypt.length = totalLength - UA_SECUREMH_AND_SYMALGH_LENGTH; |
| |
| const UA_SecurityPolicy *securityPolicy = channel->securityPolicy; |
| return securityPolicy->symmetricModule.cryptoModule.encryptionAlgorithm. |
| encrypt(securityPolicy, channel->channelContext, &dataToEncrypt); |
| } |
| |
| #endif /* UA_ENABLE_ENCRYPTION */ |
| |
| static void |
| setBufPos(UA_MessageContext *mc) { |
| /* Forward the data pointer so that the payload is encoded after the |
| * message header */ |
| mc->buf_pos = &mc->messageBuffer.data[UA_SECURE_MESSAGE_HEADER_LENGTH]; |
| mc->buf_end = &mc->messageBuffer.data[mc->messageBuffer.length]; |
| |
| #ifdef UA_ENABLE_ENCRYPTION |
| const UA_SecureChannel *channel = mc->channel; |
| const UA_SecurityPolicy *securityPolicy = channel->securityPolicy; |
| |
| /* Reserve space for the message footer at the end of the chunk if the chunk |
| * is signed and/or encrypted. The footer includes the fields PaddingSize, |
| * Padding, ExtraPadding and Signature. The padding fields are only present |
| * if the chunk is encrypted. */ |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || |
| channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| mc->buf_end -= securityPolicy->symmetricModule.cryptoModule.signatureAlgorithm. |
| getLocalSignatureSize(securityPolicy, channel->channelContext); |
| |
| /* The size of the padding depends on the amount of data that shall be sent |
| * and is unknown at this point. Reserve space for the PaddingSize byte, |
| * the maximum amount of Padding which equals the block size of the |
| * symmetric encryption algorithm and last 1 byte for the ExtraPaddingSize |
| * field that is present if the encryption key is larger than 2048 bits. |
| * The actual padding size is later calculated by the function |
| * calculatePaddingSym(). */ |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { |
| /* PaddingSize and ExtraPaddingSize fields */ |
| size_t encryptionBlockSize = securityPolicy->symmetricModule.cryptoModule. |
| encryptionAlgorithm.getLocalBlockSize(securityPolicy, channel->channelContext); |
| mc->buf_end -= 1 + ((encryptionBlockSize >> 8u) ? 1 : 0); |
| |
| /* Reduce the message body size with the remainder of the operation |
| * maxEncryptedDataSize modulo EncryptionBlockSize to get a whole |
| * number of blocks to encrypt later. Also reserve one byte for |
| * padding (1 <= paddingSize <= encryptionBlockSize). */ |
| size_t maxEncryptDataSize = mc->messageBuffer.length - |
| UA_SECURE_CONVERSATION_MESSAGE_HEADER_LENGTH - |
| UA_SYMMETRIC_ALG_SECURITY_HEADER_LENGTH; |
| mc->buf_end -= (maxEncryptDataSize % encryptionBlockSize) + 1; |
| } |
| #endif |
| } |
| |
| static UA_StatusCode |
| checkLimitsSym(UA_MessageContext *const messageContext, size_t *const bodyLength) { |
| /* Will this chunk surpass the capacity of the SecureChannel for the message? */ |
| UA_Connection *const connection = messageContext->channel->connection; |
| if(!connection) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| UA_Byte *buf_body_start = messageContext->messageBuffer.data + UA_SECURE_MESSAGE_HEADER_LENGTH; |
| const UA_Byte *buf_body_end = messageContext->buf_pos; |
| *bodyLength = (uintptr_t)buf_body_end - (uintptr_t)buf_body_start; |
| messageContext->messageSizeSoFar += *bodyLength; |
| messageContext->chunksSoFar++; |
| |
| if(messageContext->messageSizeSoFar > connection->config.maxMessageSize && |
| connection->config.maxMessageSize != 0) |
| return UA_STATUSCODE_BADRESPONSETOOLARGE; |
| |
| if(messageContext->chunksSoFar > connection->config.maxChunkCount && |
| connection->config.maxChunkCount != 0) |
| return UA_STATUSCODE_BADRESPONSETOOLARGE; |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_StatusCode |
| encodeHeadersSym(UA_MessageContext *const messageContext, size_t totalLength) { |
| UA_SecureChannel *channel = messageContext->channel; |
| UA_Byte *header_pos = messageContext->messageBuffer.data; |
| |
| UA_SecureConversationMessageHeader respHeader; |
| respHeader.secureChannelId = channel->securityToken.channelId; |
| respHeader.messageHeader.messageTypeAndChunkType = messageContext->messageType; |
| respHeader.messageHeader.messageSize = (UA_UInt32)totalLength; |
| if(messageContext->final) |
| respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_FINAL; |
| else |
| respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_INTERMEDIATE; |
| |
| UA_StatusCode res = |
| UA_encodeBinary(&respHeader, &UA_TRANSPORT[UA_TRANSPORT_SECURECONVERSATIONMESSAGEHEADER], |
| &header_pos, &messageContext->buf_end, NULL, NULL); |
| |
| UA_SymmetricAlgorithmSecurityHeader symSecHeader; |
| symSecHeader.tokenId = channel->securityToken.tokenId; |
| res |= UA_encodeBinary(&symSecHeader.tokenId, |
| &UA_TRANSPORT[UA_TRANSPORT_SYMMETRICALGORITHMSECURITYHEADER], |
| &header_pos, &messageContext->buf_end, NULL, NULL); |
| |
| UA_SequenceHeader seqHeader; |
| seqHeader.requestId = messageContext->requestId; |
| seqHeader.sequenceNumber = UA_atomic_addUInt32(&channel->sendSequenceNumber, 1); |
| res |= UA_encodeBinary(&seqHeader, &UA_TRANSPORT[UA_TRANSPORT_SEQUENCEHEADER], |
| &header_pos, &messageContext->buf_end, NULL, NULL); |
| |
| return res; |
| } |
| |
| static UA_StatusCode |
| sendSymmetricChunk(UA_MessageContext *messageContext) { |
| UA_SecureChannel *const channel = messageContext->channel; |
| const UA_SecurityPolicy *securityPolicy = channel->securityPolicy; |
| UA_Connection *const connection = channel->connection; |
| if(!connection) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| size_t bodyLength = 0; |
| UA_StatusCode res = checkLimitsSym(messageContext, &bodyLength); |
| if(res != UA_STATUSCODE_GOOD) |
| goto error; |
| |
| /* Add padding */ |
| #ifdef UA_ENABLE_ENCRYPTION |
| padChunkSym(messageContext, bodyLength); |
| #endif |
| |
| /* The total message length */ |
| size_t pre_sig_length = (uintptr_t)(messageContext->buf_pos) - |
| (uintptr_t)messageContext->messageBuffer.data; |
| size_t total_length = pre_sig_length; |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || |
| channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| total_length += securityPolicy->symmetricModule.cryptoModule.signatureAlgorithm. |
| getLocalSignatureSize(securityPolicy, channel->channelContext); |
| /* Space for the padding and the signature have been reserved in setBufPos() */ |
| UA_assert(total_length <= connection->config.sendBufferSize); |
| |
| /* For giving the buffer to the network layer */ |
| messageContext->messageBuffer.length = total_length; |
| |
| UA_assert(res == UA_STATUSCODE_GOOD); |
| res = encodeHeadersSym(messageContext, total_length); |
| if(res != UA_STATUSCODE_GOOD) |
| goto error; |
| |
| #ifdef UA_ENABLE_ENCRYPTION |
| res = signChunkSym(messageContext, pre_sig_length); |
| if(res != UA_STATUSCODE_GOOD) |
| goto error; |
| |
| res = encryptChunkSym(messageContext, total_length); |
| if(res != UA_STATUSCODE_GOOD) |
| goto error; |
| #endif |
| |
| /* Send the chunk, the buffer is freed in the network layer */ |
| return connection->send(channel->connection, &messageContext->messageBuffer); |
| |
| error: |
| connection->releaseSendBuffer(channel->connection, &messageContext->messageBuffer); |
| return res; |
| } |
| |
| /* Callback from the encoding layer. Send the chunk and replace the buffer. */ |
| static UA_StatusCode |
| sendSymmetricEncodingCallback(void *data, UA_Byte **buf_pos, const UA_Byte **buf_end) { |
| /* Set buf values from encoding in the messagecontext */ |
| UA_MessageContext *mc = (UA_MessageContext *)data; |
| mc->buf_pos = *buf_pos; |
| mc->buf_end = *buf_end; |
| |
| /* Send out */ |
| UA_StatusCode retval = sendSymmetricChunk(mc); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Set a new buffer for the next chunk */ |
| UA_Connection *connection = mc->channel->connection; |
| if(!connection) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| retval = connection->getSendBuffer(connection, connection->config.sendBufferSize, |
| &mc->messageBuffer); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Hide bytes for header, padding and signature */ |
| 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_Connection *connection = channel->connection; |
| if(!connection) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| if(messageType != UA_MESSAGETYPE_MSG && messageType != UA_MESSAGETYPE_CLO) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| /* Create the chunking info structure */ |
| mc->channel = channel; |
| mc->requestId = requestId; |
| mc->chunksSoFar = 0; |
| mc->messageSizeSoFar = 0; |
| mc->final = false; |
| mc->messageBuffer = UA_BYTESTRING_NULL; |
| mc->messageType = messageType; |
| |
| /* Allocate the message buffer */ |
| UA_StatusCode retval = |
| connection->getSendBuffer(connection, connection->config.sendBufferSize, |
| &mc->messageBuffer); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Hide bytes for header, padding and signature */ |
| setBufPos(mc); |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| UA_StatusCode |
| UA_MessageContext_encode(UA_MessageContext *mc, const void *content, |
| const UA_DataType *contentType) { |
| UA_StatusCode retval = UA_encodeBinary(content, contentType, &mc->buf_pos, &mc->buf_end, |
| sendSymmetricEncodingCallback, mc); |
| if(retval != UA_STATUSCODE_GOOD && mc->messageBuffer.length > 0) |
| UA_MessageContext_abort(mc); |
| return retval; |
| } |
| |
| UA_StatusCode |
| UA_MessageContext_finish(UA_MessageContext *mc) { |
| mc->final = true; |
| return sendSymmetricChunk(mc); |
| } |
| |
| void |
| UA_MessageContext_abort(UA_MessageContext *mc) { |
| UA_Connection *connection = mc->channel->connection; |
| connection->releaseSendBuffer(connection, &mc->messageBuffer); |
| } |
| |
| UA_StatusCode |
| UA_SecureChannel_sendSymmetricMessage(UA_SecureChannel *channel, UA_UInt32 requestId, |
| UA_MessageType messageType, void *payload, |
| const UA_DataType *payloadType) { |
| if(!channel || !channel->connection || !payload || !payloadType) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| if(channel->connection->state == UA_CONNECTION_CLOSED) |
| return UA_STATUSCODE_BADCONNECTIONCLOSED; |
| |
| UA_MessageContext mc; |
| UA_StatusCode retval = UA_MessageContext_begin(&mc, channel, requestId, messageType); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Assert's required for clang-analyzer */ |
| UA_assert(mc.buf_pos == &mc.messageBuffer.data[UA_SECURE_MESSAGE_HEADER_LENGTH]); |
| UA_assert(mc.buf_end <= &mc.messageBuffer.data[mc.messageBuffer.length]); |
| |
| UA_NodeId typeId = UA_NODEID_NUMERIC(0, payloadType->binaryEncodingId); |
| retval = UA_MessageContext_encode(&mc, &typeId, &UA_TYPES[UA_TYPES_NODEID]); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| retval = UA_MessageContext_encode(&mc, payload, payloadType); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| return UA_MessageContext_finish(&mc); |
| } |
| |
| /*****************************/ |
| /* Assemble Complete Message */ |
| /*****************************/ |
| |
| static UA_StatusCode |
| addChunkPayload(UA_SecureChannel *channel, UA_UInt32 requestId, |
| UA_MessageType messageType, UA_ByteString *chunkPayload, |
| UA_Boolean final) { |
| UA_Message *latest = TAILQ_LAST(&channel->messages, UA_MessageQueue); |
| if(latest) { |
| if(latest->requestId != requestId) { |
| /* Start of a new message */ |
| if(!latest->final) |
| return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; |
| latest = NULL; |
| } else { |
| if(latest->messageType != messageType) /* MessageType mismatch */ |
| return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; |
| if(latest->final) /* Correct message, but already finalized */ |
| return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; |
| } |
| } |
| |
| /* Create a new message entry */ |
| if(!latest) { |
| latest = (UA_Message *)UA_malloc(sizeof(UA_Message)); |
| if(!latest) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| memset(latest, 0, sizeof(UA_Message)); |
| latest->requestId = requestId; |
| latest->messageType = messageType; |
| SIMPLEQ_INIT(&latest->chunkPayloads); |
| TAILQ_INSERT_TAIL(&channel->messages, latest, pointers); |
| } |
| |
| /* Test against the connection settings */ |
| const UA_ConnectionConfig *config = &channel->connection->config; |
| UA_assert(config != NULL); /* clang-analyzer false positive */ |
| |
| if(config->maxChunkCount > 0 && |
| config->maxChunkCount <= latest->chunkPayloadsSize) |
| return UA_STATUSCODE_BADRESPONSETOOLARGE; |
| |
| if(config->maxMessageSize > 0 && |
| config->maxMessageSize < latest->messageSize + chunkPayload->length) |
| return UA_STATUSCODE_BADRESPONSETOOLARGE; |
| |
| /* Create a new chunk entry */ |
| UA_ChunkPayload *cp = (UA_ChunkPayload *)UA_malloc(sizeof(UA_ChunkPayload)); |
| if(!cp) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| cp->bytes = *chunkPayload; |
| cp->copied = false; |
| |
| /* Add the chunk */ |
| SIMPLEQ_INSERT_TAIL(&latest->chunkPayloads, cp, pointers); |
| latest->chunkPayloadsSize += 1; |
| latest->messageSize += chunkPayload->length; |
| latest->final = final; |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_StatusCode |
| processMessage(UA_SecureChannel *channel, const UA_Message *message, |
| void *application, UA_ProcessMessageCallback callback) { |
| if(message->chunkPayloadsSize == 1) { |
| /* No need to combine chunks */ |
| UA_ChunkPayload *cp = SIMPLEQ_FIRST(&message->chunkPayloads); |
| callback(application, channel, message->messageType, message->requestId, &cp->bytes); |
| } else { |
| /* Allocate memory */ |
| UA_ByteString bytes; |
| bytes.data = (UA_Byte *)UA_malloc(message->messageSize); |
| if(!bytes.data) { |
| UA_LOG_ERROR(channel->securityPolicy->logger, UA_LOGCATEGORY_SECURECHANNEL, |
| "Could not allocate the memory to assemble the message"); |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| } |
| bytes.length = message->messageSize; |
| |
| /* Assemble the full message */ |
| size_t curPos = 0; |
| UA_ChunkPayload *cp; |
| SIMPLEQ_FOREACH(cp, &message->chunkPayloads, pointers) { |
| memcpy(&bytes.data[curPos], cp->bytes.data, cp->bytes.length); |
| curPos += cp->bytes.length; |
| } |
| |
| /* Process the message */ |
| callback(application, channel, message->messageType, message->requestId, &bytes); |
| UA_ByteString_deleteMembers(&bytes); |
| } |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| UA_StatusCode |
| UA_SecureChannel_processCompleteMessages(UA_SecureChannel *channel, void *application, |
| UA_ProcessMessageCallback callback) { |
| UA_Message *message, *tmp_message; |
| UA_StatusCode retval = UA_STATUSCODE_GOOD; |
| TAILQ_FOREACH_SAFE(message, &channel->messages, pointers, tmp_message) { |
| /* Stop at the first incomplete message */ |
| if(!message->final) |
| break; |
| |
| /* Has the channel been closed (during the last message)? */ |
| if(channel->state == UA_SECURECHANNELSTATE_CLOSED) |
| break; |
| |
| /* Remove the current message before processing */ |
| TAILQ_REMOVE(&channel->messages, message, pointers); |
| |
| /* Process */ |
| retval = processMessage(channel, message, application, callback); |
| if(retval != UA_STATUSCODE_GOOD) |
| break; |
| |
| /* Clean up the message */ |
| UA_ChunkPayload *payload; |
| while((payload = SIMPLEQ_FIRST(&message->chunkPayloads))) { |
| if(payload->copied) |
| UA_ByteString_deleteMembers(&payload->bytes); |
| SIMPLEQ_REMOVE_HEAD(&message->chunkPayloads, pointers); |
| UA_free(payload); |
| } |
| UA_free(message); |
| } |
| return retval; |
| } |
| |
| /****************************/ |
| /* Process a received Chunk */ |
| /****************************/ |
| |
| static UA_StatusCode |
| decryptChunk(const UA_SecureChannel *const channel, |
| const UA_SecurityPolicyCryptoModule *const cryptoModule, |
| UA_MessageType const messageType, const UA_ByteString *const chunk, |
| size_t const offset, size_t *const chunkSizeAfterDecryption) { |
| UA_LOG_TRACE_CHANNEL(channel->securityPolicy->logger, channel, "Decrypting chunk"); |
| |
| UA_ByteString cipherText = {chunk->length - offset, chunk->data + offset}; |
| size_t sizeBeforeDecryption = cipherText.length; |
| size_t chunkSizeBeforeDecryption = *chunkSizeAfterDecryption; |
| |
| /* Always decrypt opn messages if mode not none */ |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT || |
| messageType == UA_MESSAGETYPE_OPN) { |
| UA_StatusCode retval = cryptoModule->encryptionAlgorithm. |
| decrypt(channel->securityPolicy, channel->channelContext, &cipherText); |
| *chunkSizeAfterDecryption -= (sizeBeforeDecryption - cipherText.length); |
| if(retval != UA_STATUSCODE_GOOD) { |
| return retval; |
| } |
| } |
| |
| UA_LOG_TRACE_CHANNEL(channel->securityPolicy->logger, channel, |
| "Chunk size before and after decryption: %lu, %lu", |
| (long unsigned int)chunkSizeBeforeDecryption, |
| (long unsigned int)*chunkSizeAfterDecryption); |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_UInt16 |
| decodeChunkPaddingSize(const UA_SecureChannel *const channel, |
| const UA_SecurityPolicyCryptoModule *const cryptoModule, |
| UA_MessageType const messageType, const UA_ByteString *const chunk, |
| size_t const chunkSizeAfterDecryption, size_t sigsize) { |
| /* Is padding used? */ |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT && |
| !(messageType == UA_MESSAGETYPE_OPN && channel->securityMode > UA_MESSAGESECURITYMODE_NONE)) |
| return 0; |
| |
| size_t paddingSize = chunk->data[chunkSizeAfterDecryption - sigsize - 1]; |
| |
| /* Extra padding size */ |
| size_t keyLength = cryptoModule->encryptionAlgorithm. |
| getRemoteKeyLength(channel->securityPolicy, channel->channelContext); |
| if(keyLength > 2048) { |
| paddingSize <<= 8u; |
| paddingSize += 1; |
| paddingSize += chunk->data[chunkSizeAfterDecryption - sigsize - 2]; |
| } |
| |
| /* We need to add one to the padding size since the paddingSize byte itself |
| * need to be removed as well. */ |
| paddingSize += 1; |
| |
| UA_LOG_TRACE_CHANNEL(channel->securityPolicy->logger, channel, |
| "Calculated padding size to be %lu", |
| (long unsigned int)paddingSize); |
| return (UA_UInt16)paddingSize; |
| } |
| |
| static UA_StatusCode |
| verifyChunk(const UA_SecureChannel *const channel, |
| const UA_SecurityPolicyCryptoModule *const cryptoModule, |
| const UA_ByteString *const chunk, |
| size_t const chunkSizeAfterDecryption, size_t sigsize) { |
| UA_LOG_TRACE_CHANNEL(channel->securityPolicy->logger, channel, |
| "Verifying chunk signature"); |
| |
| /* Verify the signature */ |
| const UA_ByteString chunkDataToVerify = {chunkSizeAfterDecryption - sigsize, chunk->data}; |
| const UA_ByteString signature = {sigsize, chunk->data + chunkSizeAfterDecryption - sigsize}; |
| UA_StatusCode retval = cryptoModule->signatureAlgorithm. |
| verify(channel->securityPolicy, channel->channelContext, &chunkDataToVerify, &signature); |
| #ifdef UA_ENABLE_UNIT_TEST_FAILURE_HOOKS |
| retval |= decrypt_verifySignatureFailure; |
| #endif |
| |
| return retval; |
| } |
| |
| /* Sets the payload to a pointer inside the chunk buffer. Returns the requestId |
| * and the sequenceNumber */ |
| static UA_StatusCode |
| decryptAndVerifyChunk(const UA_SecureChannel *channel, |
| const UA_SecurityPolicyCryptoModule *cryptoModule, |
| UA_MessageType messageType, const UA_ByteString *chunk, |
| size_t offset, UA_UInt32 *requestId, |
| UA_UInt32 *sequenceNumber, UA_ByteString *payload) { |
| size_t chunkSizeAfterDecryption = chunk->length; |
| UA_StatusCode retval = decryptChunk(channel, cryptoModule, messageType, |
| chunk, offset, &chunkSizeAfterDecryption); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Verify the chunk signature */ |
| size_t sigsize = 0; |
| size_t paddingSize = 0; |
| const UA_SecurityPolicy *securityPolicy = channel->securityPolicy; |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || |
| channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT || |
| messageType == UA_MESSAGETYPE_OPN) { |
| sigsize = cryptoModule->signatureAlgorithm. |
| getRemoteSignatureSize(securityPolicy, channel->channelContext); |
| paddingSize = decodeChunkPaddingSize(channel, cryptoModule, messageType, chunk, |
| chunkSizeAfterDecryption, sigsize); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| if(offset + paddingSize + sigsize >= chunkSizeAfterDecryption) |
| return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
| |
| retval = verifyChunk(channel, cryptoModule, chunk, chunkSizeAfterDecryption, sigsize); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| } |
| |
| /* Decode the sequence header */ |
| UA_SequenceHeader sequenceHeader; |
| retval = UA_SequenceHeader_decodeBinary(chunk, &offset, &sequenceHeader); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| if(offset + paddingSize + sigsize >= chunk->length) |
| return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
| |
| *requestId = sequenceHeader.requestId; |
| *sequenceNumber = sequenceHeader.sequenceNumber; |
| payload->data = chunk->data + offset; |
| payload->length = chunkSizeAfterDecryption - offset - sigsize - paddingSize; |
| UA_LOG_TRACE_CHANNEL(channel->securityPolicy->logger, channel, |
| "Decrypted and verified chunk with request id %u and " |
| "sequence number %u", *requestId, *sequenceNumber); |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| typedef UA_StatusCode |
| (*UA_SequenceNumberCallback)(UA_SecureChannel *channel, UA_UInt32 sequenceNumber); |
| |
| static UA_StatusCode |
| processSequenceNumberAsym(UA_SecureChannel *channel, UA_UInt32 sequenceNumber) { |
| UA_LOG_TRACE_CHANNEL(channel->securityPolicy->logger, channel, |
| "Sequence Number processed: %i", sequenceNumber); |
| channel->receiveSequenceNumber = sequenceNumber; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_StatusCode |
| processSequenceNumberSym(UA_SecureChannel *channel, UA_UInt32 sequenceNumber) { |
| /* Failure mode hook for unit tests */ |
| #ifdef UA_ENABLE_UNIT_TEST_FAILURE_HOOKS |
| if(processSym_seqNumberFailure != UA_STATUSCODE_GOOD) |
| return processSym_seqNumberFailure; |
| #endif |
| |
| UA_LOG_TRACE_CHANNEL(channel->securityPolicy->logger, channel, |
| "Sequence Number processed: %i", sequenceNumber); |
| /* Does the sequence number match? */ |
| if(sequenceNumber != channel->receiveSequenceNumber + 1) { |
| /* FIXME: Remove magic numbers :( */ |
| if(channel->receiveSequenceNumber + 1 > 4294966271 && sequenceNumber < 1024) |
| channel->receiveSequenceNumber = sequenceNumber - 1; /* Roll over */ |
| else |
| return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
| } |
| ++channel->receiveSequenceNumber; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_StatusCode |
| checkAsymHeader(UA_SecureChannel *const channel, |
| UA_AsymmetricAlgorithmSecurityHeader *const asymHeader) { |
| const UA_SecurityPolicy *const securityPolicy = channel->securityPolicy; |
| |
| if(!UA_ByteString_equal(&securityPolicy->policyUri, |
| &asymHeader->securityPolicyUri)) { |
| return UA_STATUSCODE_BADSECURITYPOLICYREJECTED; |
| } |
| |
| // TODO: Verify certificate using certificate plugin. This will come with a new PR |
| /* Something like this |
| retval = certificateManager->verify(certificateStore??, &asymHeader->senderCertificate); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| */ |
| UA_StatusCode retval = securityPolicy->asymmetricModule. |
| compareCertificateThumbprint(securityPolicy, |
| &asymHeader->receiverCertificateThumbprint); |
| if(retval != UA_STATUSCODE_GOOD) { |
| return retval; |
| } |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_StatusCode |
| checkPreviousToken(UA_SecureChannel *const channel, const UA_UInt32 tokenId) { |
| if(tokenId != channel->previousSecurityToken.tokenId) |
| return UA_STATUSCODE_BADSECURECHANNELTOKENUNKNOWN; |
| |
| UA_DateTime timeout = channel->previousSecurityToken.createdAt + |
| (UA_DateTime)((UA_Double)channel->previousSecurityToken.revisedLifetime * |
| (UA_Double)UA_DATETIME_MSEC * 1.25); |
| |
| if(timeout < UA_DateTime_nowMonotonic()) |
| return UA_STATUSCODE_BADSECURECHANNELTOKENUNKNOWN; |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_StatusCode |
| checkSymHeader(UA_SecureChannel *const channel, |
| const UA_UInt32 tokenId, UA_Boolean allowPreviousToken) { |
| |
| /* If the message uses the currently active token, check if it is still valid */ |
| if(tokenId == channel->securityToken.tokenId) { |
| if(channel->state == UA_SECURECHANNELSTATE_OPEN && |
| (channel->securityToken.createdAt + |
| (channel->securityToken.revisedLifetime * UA_DATETIME_MSEC)) |
| < UA_DateTime_nowMonotonic()) { |
| UA_SecureChannel_close(channel); |
| return UA_STATUSCODE_BADSECURECHANNELCLOSED; |
| } |
| } |
| |
| /* If the message uses a different token, check if it is the next token. */ |
| if(tokenId != channel->securityToken.tokenId) { |
| /* If it isn't the next token, we might be dealing with a message, that |
| * still uses the old token, so check if the old one is still valid.*/ |
| if(tokenId != channel->nextSecurityToken.tokenId) { |
| if(allowPreviousToken) |
| return checkPreviousToken(channel, tokenId); |
| |
| return UA_STATUSCODE_BADSECURECHANNELTOKENUNKNOWN; |
| } |
| /* If the token is indeed the next token, revolve the tokens */ |
| UA_StatusCode retval = UA_SecureChannel_revolveTokens(channel); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* If the message now uses the currently active token also generate |
| * new remote keys to correctly decrypt. */ |
| if(channel->securityToken.tokenId == tokenId) { |
| retval = UA_SecureChannel_generateRemoteKeys(channel, channel->securityPolicy); |
| UA_ChannelSecurityToken_deleteMembers(&channel->previousSecurityToken); |
| return retval; |
| } |
| } |
| |
| /* It is possible that the sent messages already use the new token, but |
| * the received messages still use the old token. If we receive a message |
| * with the new token, we will need to generate the keys and discard the |
| * old token now*/ |
| if(channel->previousSecurityToken.tokenId != 0) { |
| UA_StatusCode retval = |
| UA_SecureChannel_generateRemoteKeys(channel, channel->securityPolicy); |
| UA_ChannelSecurityToken_deleteMembers(&channel->previousSecurityToken); |
| return retval; |
| } |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_StatusCode |
| putPayload(UA_SecureChannel *const channel, UA_UInt32 const requestId, |
| UA_MessageType const messageType, UA_ChunkType const chunkType, |
| UA_ByteString *chunkPayload) { |
| switch(chunkType) { |
| case UA_CHUNKTYPE_INTERMEDIATE: |
| case UA_CHUNKTYPE_FINAL: |
| return addChunkPayload(channel, requestId, messageType, |
| chunkPayload, chunkType == UA_CHUNKTYPE_FINAL); |
| case UA_CHUNKTYPE_ABORT: |
| deleteLatestMessage(channel, requestId); |
| return UA_STATUSCODE_GOOD; |
| default: |
| return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; |
| } |
| } |
| |
| /* The chunk body begins after the SecureConversationMessageHeader */ |
| static UA_StatusCode |
| decryptAddChunk(UA_SecureChannel *channel, const UA_ByteString *chunk, |
| UA_Boolean allowPreviousToken) { |
| /* Decode the MessageHeader */ |
| size_t offset = 0; |
| UA_SecureConversationMessageHeader messageHeader; |
| UA_StatusCode retval = |
| UA_SecureConversationMessageHeader_decodeBinary(chunk, &offset, &messageHeader); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) |
| /* The wrong ChannelId. Non-opened channels have the id zero. */ |
| if(messageHeader.secureChannelId != channel->securityToken.channelId && |
| channel->state != UA_SECURECHANNELSTATE_FRESH) |
| return UA_STATUSCODE_BADSECURECHANNELIDINVALID; |
| #endif |
| |
| UA_MessageType messageType = (UA_MessageType) |
| (messageHeader.messageHeader.messageTypeAndChunkType & UA_BITMASK_MESSAGETYPE); |
| UA_ChunkType chunkType = (UA_ChunkType) |
| (messageHeader.messageHeader.messageTypeAndChunkType & UA_BITMASK_CHUNKTYPE); |
| UA_UInt32 requestId = 0; |
| UA_UInt32 sequenceNumber = 0; |
| UA_ByteString chunkPayload; |
| const UA_SecurityPolicyCryptoModule *cryptoModule = NULL; |
| UA_SequenceNumberCallback sequenceNumberCallback = NULL; |
| |
| switch(messageType) { |
| /* ERR message (not encrypted) */ |
| case UA_MESSAGETYPE_ERR: |
| if(chunkType != UA_CHUNKTYPE_FINAL) |
| return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; |
| chunkPayload.length = chunk->length - offset; |
| chunkPayload.data = chunk->data + offset; |
| return putPayload(channel, requestId, messageType, chunkType, &chunkPayload); |
| |
| /* MSG and CLO: Symmetric encryption */ |
| case UA_MESSAGETYPE_MSG: |
| case UA_MESSAGETYPE_CLO: { |
| /* Decode and check the symmetric security header (tokenId) */ |
| UA_SymmetricAlgorithmSecurityHeader symmetricSecurityHeader; |
| UA_SymmetricAlgorithmSecurityHeader_init(&symmetricSecurityHeader); |
| retval = UA_SymmetricAlgorithmSecurityHeader_decodeBinary(chunk, &offset, |
| &symmetricSecurityHeader); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
| /* Help fuzzing by always setting the correct tokenId */ |
| symmetricSecurityHeader.tokenId = channel->securityToken.tokenId; |
| #endif |
| |
| retval = checkSymHeader(channel, symmetricSecurityHeader.tokenId, allowPreviousToken); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| cryptoModule = &channel->securityPolicy->symmetricModule.cryptoModule; |
| sequenceNumberCallback = processSequenceNumberSym; |
| break; |
| } |
| |
| /* OPN: Asymmetric encryption */ |
| case UA_MESSAGETYPE_OPN: { |
| /* Chunking not allowed for OPN */ |
| if(chunkType != UA_CHUNKTYPE_FINAL) |
| return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; |
| |
| /* Decode the asymmetric algorithm security header and call the callback |
| * to perform checks. */ |
| UA_AsymmetricAlgorithmSecurityHeader asymHeader; |
| UA_AsymmetricAlgorithmSecurityHeader_init(&asymHeader); |
| offset = UA_SECURE_CONVERSATION_MESSAGE_HEADER_LENGTH; |
| retval = UA_AsymmetricAlgorithmSecurityHeader_decodeBinary(chunk, &offset, &asymHeader); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| retval = checkAsymHeader(channel, &asymHeader); |
| UA_AsymmetricAlgorithmSecurityHeader_deleteMembers(&asymHeader); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| cryptoModule = &channel->securityPolicy->asymmetricModule.cryptoModule; |
| sequenceNumberCallback = processSequenceNumberAsym; |
| break; |
| } |
| |
| /* Invalid message type */ |
| default:return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; |
| } |
| |
| UA_assert(cryptoModule != NULL); |
| retval = decryptAndVerifyChunk(channel, cryptoModule, messageType, chunk, offset, |
| &requestId, &sequenceNumber, &chunkPayload); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Check the sequence number. Skip sequence number checking for fuzzer to |
| * improve coverage */ |
| if(sequenceNumberCallback == NULL) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) |
| retval = UA_STATUSCODE_GOOD; |
| #else |
| retval = sequenceNumberCallback(channel, sequenceNumber); |
| #endif |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| return putPayload(channel, requestId, messageType, chunkType, &chunkPayload); |
| } |
| |
| UA_StatusCode |
| UA_SecureChannel_decryptAddChunk(UA_SecureChannel *channel, const UA_ByteString *chunk, |
| UA_Boolean allowPreviousToken) { |
| /* Has the SecureChannel timed out? */ |
| if(channel->state == UA_SECURECHANNELSTATE_CLOSED) |
| return UA_STATUSCODE_BADSECURECHANNELCLOSED; |
| |
| /* Is the SecureChannel configured? */ |
| if(!channel->connection) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| UA_StatusCode retval = decryptAddChunk(channel, chunk, allowPreviousToken); |
| if(retval != UA_STATUSCODE_GOOD) |
| UA_SecureChannel_close(channel); |
| |
| return retval; |
| } |
| |
| UA_StatusCode |
| UA_SecureChannel_persistIncompleteMessages(UA_SecureChannel *channel) { |
| UA_Message *me; |
| TAILQ_FOREACH(me, &channel->messages, pointers) { |
| UA_ChunkPayload *cp; |
| SIMPLEQ_FOREACH(cp, &me->chunkPayloads, pointers) { |
| if(cp->copied) |
| continue; |
| UA_ByteString copy; |
| UA_StatusCode retval = UA_ByteString_copy(&cp->bytes, ©); |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_SecureChannel_close(channel); |
| return retval; |
| } |
| cp->bytes = copy; |
| cp->copied = true; |
| } |
| } |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Functionality used by both the SecureChannel and the SecurityPolicy */ |
| |
| size_t |
| UA_SecurityPolicy_getRemoteAsymEncryptionBufferLengthOverhead(const UA_SecurityPolicy *securityPolicy, |
| const void *channelContext, |
| size_t maxEncryptionLength) { |
| if(maxEncryptionLength == 0) |
| return 0; |
| |
| size_t plainTextBlockSize = securityPolicy->asymmetricModule.cryptoModule. |
| encryptionAlgorithm.getRemotePlainTextBlockSize(securityPolicy, channelContext); |
| size_t encryptedBlockSize = securityPolicy->asymmetricModule.cryptoModule. |
| encryptionAlgorithm.getRemoteBlockSize(securityPolicy, channelContext); |
| if(plainTextBlockSize == 0) |
| return 0; |
| |
| size_t maxNumberOfBlocks = maxEncryptionLength / plainTextBlockSize; |
| return maxNumberOfBlocks * (encryptedBlockSize - plainTextBlockSize); |
| } |