/* 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/. */ #include "ua_util.h" #include "ua_securechannel.h" #include "ua_session.h" #include "ua_types_encoding_binary.h" #include "ua_types_generated_encoding_binary.h" #include "ua_transport_generated_encoding_binary.h" #include "ua_types_generated_handling.h" #include "ua_transport_generated_handling.h" #define UA_SECURE_MESSAGE_HEADER_LENGTH 24 void UA_SecureChannel_init(UA_SecureChannel *channel) { memset(channel, 0, sizeof(UA_SecureChannel)); /* Linked lists are also initialized by zeroing out */ /* LIST_INIT(&channel->sessions); */ /* LIST_INIT(&channel->chunks); */ } void UA_SecureChannel_deleteMembersCleanup(UA_SecureChannel *channel) { /* Delete members */ UA_AsymmetricAlgorithmSecurityHeader_deleteMembers(&channel->serverAsymAlgSettings); UA_ByteString_deleteMembers(&channel->serverNonce); UA_AsymmetricAlgorithmSecurityHeader_deleteMembers(&channel->clientAsymAlgSettings); UA_ByteString_deleteMembers(&channel->clientNonce); UA_ChannelSecurityToken_deleteMembers(&channel->securityToken); UA_ChannelSecurityToken_deleteMembers(&channel->nextSecurityToken); /* Detach from the channel */ if(channel->connection) UA_Connection_detachSecureChannel(channel->connection); /* Remove session pointers (not the sessions) */ struct SessionEntry *se, *temp; LIST_FOREACH_SAFE(se, &channel->sessions, pointers, temp) { if(se->session) se->session->channel = NULL; LIST_REMOVE(se, pointers); UA_free(se); } /* Remove the buffered chunks */ struct ChunkEntry *ch, *temp_ch; LIST_FOREACH_SAFE(ch, &channel->chunks, pointers, temp_ch) { UA_ByteString_deleteMembers(&ch->bytes); LIST_REMOVE(ch, pointers); UA_free(ch); } } //TODO implement real nonce generator - DUMMY function UA_StatusCode UA_SecureChannel_generateNonce(UA_ByteString *nonce) { if(!(nonce->data = (UA_Byte *)UA_malloc(1))) return UA_STATUSCODE_BADOUTOFMEMORY; nonce->length = 1; nonce->data[0] = 'a'; return UA_STATUSCODE_GOOD; } #if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 6) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wextra" #pragma GCC diagnostic ignored "-Wcast-qual" #pragma GCC diagnostic ignored "-Wunused-value" #endif void UA_SecureChannel_attachSession(UA_SecureChannel *channel, UA_Session *session) { struct SessionEntry *se = (struct SessionEntry *)UA_malloc(sizeof(struct SessionEntry)); if(!se) return; se->session = session; if(UA_atomic_cmpxchg((void**)&session->channel, NULL, channel) != NULL) { UA_free(se); return; } LIST_INSERT_HEAD(&channel->sessions, se, pointers); } #if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 6) #pragma GCC diagnostic pop #endif void UA_SecureChannel_detachSession(UA_SecureChannel *channel, UA_Session *session) { if(session) session->channel = NULL; struct SessionEntry *se; LIST_FOREACH(se, &channel->sessions, pointers) { if(se->session == session) break; } if(!se) return; LIST_REMOVE(se, pointers); UA_free(se); } UA_Session * UA_SecureChannel_getSession(UA_SecureChannel *channel, UA_NodeId *token) { struct SessionEntry *se; LIST_FOREACH(se, &channel->sessions, pointers) { if(UA_NodeId_equal(&se->session->authenticationToken, token)) break; } if(!se) return NULL; return se->session; } void UA_SecureChannel_revolveTokens(UA_SecureChannel *channel) { if(channel->nextSecurityToken.tokenId == 0) //no security token issued return; //FIXME: not thread-safe memcpy(&channel->securityToken, &channel->nextSecurityToken, sizeof(UA_ChannelSecurityToken)); UA_ChannelSecurityToken_init(&channel->nextSecurityToken); } /***********************/ /* Send Binary Message */ /***********************/ static UA_StatusCode UA_SecureChannel_sendChunk(UA_ChunkInfo *ci, UA_Byte **bufPos, const UA_Byte **bufEnd) { UA_SecureChannel *channel = ci->channel; UA_Connection *connection = channel->connection; if(!connection) return UA_STATUSCODE_BADINTERNALERROR; /* Adjust the buffer where the header was hidden */ UA_ByteString *dst = &ci->buffer; UA_Byte *chunkEndPos = *bufPos; size_t offset = (uintptr_t)*bufPos - (uintptr_t)dst->data; if(ci->messageSizeSoFar + offset > connection->remoteConf.maxMessageSize && connection->remoteConf.maxMessageSize > 0) ci->errorCode = UA_STATUSCODE_BADRESPONSETOOLARGE; if(++ci->chunksSoFar > connection->remoteConf.maxChunkCount && connection->remoteConf.maxChunkCount > 0) ci->errorCode = UA_STATUSCODE_BADRESPONSETOOLARGE; /* Prepare the chunk headers */ UA_SecureConversationMessageHeader respHeader; respHeader.secureChannelId = channel->securityToken.channelId; respHeader.messageHeader.messageTypeAndChunkType = ci->messageType; if(ci->errorCode == UA_STATUSCODE_GOOD) { if(ci->final) respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_FINAL; else respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_INTERMEDIATE; } else { /* abort message */ ci->final = true; /* mark as finished */ respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_ABORT; UA_String errorMsg; UA_String_init(&errorMsg); chunkEndPos = &dst->data[UA_SECURE_MESSAGE_HEADER_LENGTH]; UA_UInt32_encodeBinary(&ci->errorCode, &chunkEndPos, bufEnd); UA_String_encodeBinary(&errorMsg, &chunkEndPos, bufEnd); offset = (uintptr_t)*bufEnd - (uintptr_t)dst->data; } respHeader.messageHeader.messageSize = (UA_UInt32)offset; ci->messageSizeSoFar += offset; /* Encode the header at the beginning of the buffer */ UA_SymmetricAlgorithmSecurityHeader symSecHeader; symSecHeader.tokenId = channel->securityToken.tokenId; UA_SequenceHeader seqHeader; seqHeader.requestId = ci->requestId; seqHeader.sequenceNumber = UA_atomic_add(&channel->sendSequenceNumber, 1); UA_Byte *beginPos = dst->data; UA_SecureConversationMessageHeader_encodeBinary(&respHeader, &beginPos, bufEnd); UA_SymmetricAlgorithmSecurityHeader_encodeBinary(&symSecHeader, &beginPos, bufEnd); UA_SequenceHeader_encodeBinary(&seqHeader, &beginPos, bufEnd); /* Send the chunk, the buffer is freed in the network layer */ dst->length = offset; /* set the buffer length to the content length */ connection->send(channel->connection, dst); /* Replace with the buffer for the next chunk */ if(!ci->final) { UA_StatusCode retval = connection->getSendBuffer(connection, connection->localConf.sendBufferSize, dst); if(retval != UA_STATUSCODE_GOOD) return retval; /* Forward the data pointer so that the payload is encoded after the message header. */ *bufPos = &dst->data[UA_SECURE_MESSAGE_HEADER_LENGTH]; *bufEnd = &dst->data[dst->length]; } return ci->errorCode; } UA_StatusCode UA_SecureChannel_sendBinaryMessage(UA_SecureChannel *channel, UA_UInt32 requestId, const void *content, const UA_DataType *contentType) { UA_Connection *connection = channel->connection; if(!connection) return UA_STATUSCODE_BADINTERNALERROR; /* Create the chunking info structure */ UA_ChunkInfo ci; ci.channel = channel; ci.requestId = requestId; ci.chunksSoFar = 0; ci.messageSizeSoFar = 0; ci.final = false; ci.messageType = UA_MESSAGETYPE_MSG; ci.errorCode = UA_STATUSCODE_GOOD; /* Allocate the message buffer */ UA_StatusCode retval = connection->getSendBuffer(connection, connection->localConf.sendBufferSize, &ci.buffer); if(retval != UA_STATUSCODE_GOOD) return retval; /* Hide the message beginning where the header will be encoded */ UA_Byte *bufPos = &ci.buffer.data[UA_SECURE_MESSAGE_HEADER_LENGTH]; const UA_Byte *bufEnd = &ci.buffer.data[ci.buffer.length]; /* Encode the message type */ UA_NodeId typeId = contentType->typeId; /* always numeric */ typeId.identifier.numeric = contentType->binaryEncodingId; UA_NodeId_encodeBinary(&typeId, &bufPos, &bufEnd); /* Encode with the chunking callback */ if(typeId.identifier.numeric == 446 || typeId.identifier.numeric == 449) ci.messageType = UA_MESSAGETYPE_OPN; else if(typeId.identifier.numeric == 452 || typeId.identifier.numeric == 455) ci.messageType = UA_MESSAGETYPE_CLO; retval = UA_encodeBinary(content, contentType, &bufPos, &bufEnd, (UA_exchangeEncodeBuffer)UA_SecureChannel_sendChunk, &ci); /* Encoding failed, release the message */ if(retval != UA_STATUSCODE_GOOD) { if(!ci.final) { /* the abort message was not send */ ci.errorCode = retval; UA_SecureChannel_sendChunk(&ci, &bufPos, &bufEnd); } return retval; } /* Encoding finished, send the final chunk */ ci.final = UA_TRUE; return UA_SecureChannel_sendChunk(&ci, &bufPos, &bufEnd); } /***************************/ /* Process Received Chunks */ /***************************/ static void UA_SecureChannel_removeChunk(UA_SecureChannel *channel, UA_UInt32 requestId) { struct ChunkEntry *ch; LIST_FOREACH(ch, &channel->chunks, pointers) { if(ch->requestId == requestId) { UA_ByteString_deleteMembers(&ch->bytes); LIST_REMOVE(ch, pointers); UA_free(ch); return; } } } /* assume that chunklength fits */ static void appendChunk(struct ChunkEntry *ch, const UA_ByteString *msg, size_t offset, size_t chunklength) { UA_Byte* new_bytes = (UA_Byte *)UA_realloc(ch->bytes.data, ch->bytes.length + chunklength); if(!new_bytes) { UA_ByteString_deleteMembers(&ch->bytes); return; } ch->bytes.data = new_bytes; memcpy(&ch->bytes.data[ch->bytes.length], &msg->data[offset], chunklength); ch->bytes.length += chunklength; } static void UA_SecureChannel_appendChunk(UA_SecureChannel *channel, UA_UInt32 requestId, const UA_ByteString *msg, size_t offset, size_t chunklength) { /* Check if the chunk fits into the message */ if(msg->length - offset < chunklength) { /* can't process all chunks for that request */ UA_SecureChannel_removeChunk(channel, requestId); return; } /* Get the chunkentry */ struct ChunkEntry *ch; LIST_FOREACH(ch, &channel->chunks, pointers) { if(ch->requestId == requestId) break; } /* No chunkentry on the channel, create one */ if(!ch) { ch = (struct ChunkEntry *)UA_malloc(sizeof(struct ChunkEntry)); if(!ch) return; ch->requestId = requestId; UA_ByteString_init(&ch->bytes); LIST_INSERT_HEAD(&channel->chunks, ch, pointers); } appendChunk(ch, msg, offset, chunklength); } static UA_ByteString UA_SecureChannel_finalizeChunk(UA_SecureChannel *channel, UA_UInt32 requestId, const UA_ByteString *msg, size_t offset, size_t chunklength, UA_Boolean *deleteChunk) { if(msg->length - offset < chunklength) { /* can't process all chunks for that request */ UA_SecureChannel_removeChunk(channel, requestId); return UA_BYTESTRING_NULL; } struct ChunkEntry *ch; LIST_FOREACH(ch, &channel->chunks, pointers) { if(ch->requestId == requestId) break; } UA_ByteString bytes; if(!ch) { *deleteChunk = false; bytes.length = chunklength; bytes.data = msg->data + offset; } else { *deleteChunk = true; appendChunk(ch, msg, offset, chunklength); bytes = ch->bytes; LIST_REMOVE(ch, pointers); UA_free(ch); } return bytes; } static UA_StatusCode UA_SecureChannel_processSequenceNumber(UA_SecureChannel *channel, UA_UInt32 SequenceNumber) { /* Does the sequence number match? */ if(SequenceNumber != channel->receiveSequenceNumber + 1) { if(channel->receiveSequenceNumber + 1 > 4294966271 && SequenceNumber < 1024) channel->receiveSequenceNumber = SequenceNumber - 1; /* Roll over */ else return UA_STATUSCODE_BADSECURITYCHECKSFAILED; } ++channel->receiveSequenceNumber; return UA_STATUSCODE_GOOD; } UA_StatusCode UA_SecureChannel_processChunks(UA_SecureChannel *channel, const UA_ByteString *chunks, UA_ProcessMessageCallback callback, void *application) { UA_StatusCode retval = UA_STATUSCODE_GOOD; size_t offset= 0; do { if (chunks->length > 3 && chunks->data[offset] == 'E' && chunks->data[offset+1] == 'R' && chunks->data[offset+2] == 'R') { UA_TcpMessageHeader header; retval = UA_TcpMessageHeader_decodeBinary(chunks, &offset, &header); if(retval != UA_STATUSCODE_GOOD) break; UA_TcpErrorMessage errorMessage; retval = UA_TcpErrorMessage_decodeBinary(chunks, &offset, &errorMessage); if(retval != UA_STATUSCODE_GOOD) break; // dirty cast to pass errorMessage UA_UInt32 val = 0; callback(application, (UA_SecureChannel *)channel, (UA_MessageType)UA_MESSAGETYPE_ERR, val, (const UA_ByteString*)&errorMessage); continue; } /* Store the initial offset to compute the header length */ size_t initial_offset = offset; /* Decode header */ UA_SecureConversationMessageHeader header; retval = UA_SecureConversationMessageHeader_decodeBinary(chunks, &offset, &header); if(retval != UA_STATUSCODE_GOOD) break; /* Is the channel attached to connection? */ if(header.secureChannelId != channel->securityToken.channelId) { //Service_CloseSecureChannel(server, channel); //connection->close(connection); return UA_STATUSCODE_BADCOMMUNICATIONERROR; } /* Use requestId = 0 with OPN as argument for the callback */ UA_SequenceHeader sequenceHeader; UA_SequenceHeader_init(&sequenceHeader); if((header.messageHeader.messageTypeAndChunkType & 0x00ffffff) != UA_MESSAGETYPE_OPN) { /* Check the symmetric security header (not for OPN) */ UA_UInt32 tokenId = 0; retval |= UA_UInt32_decodeBinary(chunks, &offset, &tokenId); retval |= UA_SequenceHeader_decodeBinary(chunks, &offset, &sequenceHeader); if(retval != UA_STATUSCODE_GOOD) return UA_STATUSCODE_BADCOMMUNICATIONERROR; /* Does the token match? */ if(tokenId != channel->securityToken.tokenId) { if(tokenId != channel->nextSecurityToken.tokenId) return UA_STATUSCODE_BADCOMMUNICATIONERROR; UA_SecureChannel_revolveTokens(channel); } /* Does the sequence number match? */ retval = UA_SecureChannel_processSequenceNumber(channel, sequenceHeader.sequenceNumber); if(retval != UA_STATUSCODE_GOOD) return UA_STATUSCODE_BADCOMMUNICATIONERROR; } /* Process chunk */ size_t processed_header = offset - initial_offset; switch(header.messageHeader.messageTypeAndChunkType & 0xff000000) { case UA_CHUNKTYPE_INTERMEDIATE: UA_SecureChannel_appendChunk(channel, sequenceHeader.requestId, chunks, offset, header.messageHeader.messageSize - processed_header); break; case UA_CHUNKTYPE_FINAL: { UA_Boolean realloced = false; UA_ByteString message = UA_SecureChannel_finalizeChunk(channel, sequenceHeader.requestId, chunks, offset, header.messageHeader.messageSize - processed_header, &realloced); if(message.length > 0) { callback(application,(UA_SecureChannel *)channel,(UA_MessageType)(header.messageHeader.messageTypeAndChunkType & 0x00ffffff), sequenceHeader.requestId, &message); if(realloced) UA_ByteString_deleteMembers(&message); } break; } case UA_CHUNKTYPE_ABORT: UA_SecureChannel_removeChunk(channel, sequenceHeader.requestId); break; default: return UA_STATUSCODE_BADDECODINGERROR; } /* Jump to the end of the chunk */ offset += (header.messageHeader.messageSize - processed_header); } while(chunks->length > offset); return retval; }