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