/* 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 2017 (c) Mark Giraud, Fraunhofer IOSB * Copyright 2017-2018 (c) Thomas Stalder, Blue Time Concept SA * Copyright 2017-2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer) * Copyright 2017 (c) Stefan Profanter, fortiss GmbH * Copyright 2018 (c) Kalycito Infotech Private Limited */ #include #include #include #include #include "ua_client_internal.h" /* Size are refered in bytes */ #define UA_MINMESSAGESIZE 8192 #define UA_SESSION_LOCALNONCELENGTH 32 #define MAX_DATA_SIZE 4096 /********************/ /* Set client state */ /********************/ void setClientState(UA_Client *client, UA_ClientState state) { if(client->state != state) { client->state = state; if(client->config.stateCallback) client->config.stateCallback(client, client->state); } } /***********************/ /* Open the Connection */ /***********************/ #define UA_BITMASK_MESSAGETYPE 0x00ffffff #define UA_BITMASK_CHUNKTYPE 0xff000000 static UA_StatusCode processACKResponse(void *application, UA_Connection *connection, UA_ByteString *chunk) { UA_Client *client = (UA_Client*)application; /* Decode the message */ size_t offset = 0; UA_StatusCode retval; UA_TcpMessageHeader messageHeader; UA_TcpAcknowledgeMessage ackMessage; retval = UA_TcpMessageHeader_decodeBinary(chunk, &offset, &messageHeader); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Decoding ACK message failed"); return retval; } // check if we got an error response from the server UA_MessageType messageType = (UA_MessageType) (messageHeader.messageTypeAndChunkType & UA_BITMASK_MESSAGETYPE); UA_ChunkType chunkType = (UA_ChunkType) (messageHeader.messageTypeAndChunkType & UA_BITMASK_CHUNKTYPE); if (messageType == UA_MESSAGETYPE_ERR) { // Header + ErrorMessage (error + reasonLength_field + length) UA_StatusCode error = *(UA_StatusCode*)(&chunk->data[offset]); UA_UInt32 len = *((UA_UInt32*)&chunk->data[offset + 4]); UA_Byte *data = (UA_Byte*)&chunk->data[offset + 4+4]; UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Received ERR response. %s - %.*s", UA_StatusCode_name(error), len, data); return error; } if (chunkType != UA_CHUNKTYPE_FINAL) { return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; } /* Decode the ACK message */ retval = UA_TcpAcknowledgeMessage_decodeBinary(chunk, &offset, &ackMessage); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Decoding ACK message failed"); return retval; } UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Received ACK message"); /* Process the ACK message */ return UA_Connection_processHELACK(connection, &client->config.localConnectionConfig, (const UA_ConnectionConfig*)&ackMessage); } static UA_StatusCode HelAckHandshake(UA_Client *client, const UA_String endpointUrl) { /* Get a buffer */ UA_ByteString message; UA_Connection *conn = &client->connection; UA_StatusCode retval = conn->getSendBuffer(conn, UA_MINMESSAGESIZE, &message); if(retval != UA_STATUSCODE_GOOD) return retval; /* Prepare the HEL message and encode at offset 8 */ UA_TcpHelloMessage hello; UA_String_copy(&endpointUrl, &hello.endpointUrl); /* must be less than 4096 bytes */ memcpy(&hello, &client->config.localConnectionConfig, sizeof(UA_ConnectionConfig)); /* same struct layout */ UA_Byte *bufPos = &message.data[8]; /* skip the header */ const UA_Byte *bufEnd = &message.data[message.length]; retval = UA_TcpHelloMessage_encodeBinary(&hello, &bufPos, bufEnd); UA_TcpHelloMessage_deleteMembers(&hello); if(retval != UA_STATUSCODE_GOOD) { conn->releaseSendBuffer(conn, &message); return retval; } /* Encode the message header at offset 0 */ UA_TcpMessageHeader messageHeader; messageHeader.messageTypeAndChunkType = UA_CHUNKTYPE_FINAL + UA_MESSAGETYPE_HEL; messageHeader.messageSize = (UA_UInt32)((uintptr_t)bufPos - (uintptr_t)message.data); bufPos = message.data; retval = UA_TcpMessageHeader_encodeBinary(&messageHeader, &bufPos, bufEnd); if(retval != UA_STATUSCODE_GOOD) { conn->releaseSendBuffer(conn, &message); return retval; } /* Send the HEL message */ message.length = messageHeader.messageSize; retval = conn->send(conn, &message); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Sending HEL failed"); return retval; } UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Sent HEL message"); /* Loop until we have a complete chunk */ retval = UA_Connection_receiveChunksBlocking(conn, client, processACKResponse, client->config.timeout); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Receiving ACK message failed with %s", UA_StatusCode_name(retval)); if(retval == UA_STATUSCODE_BADCONNECTIONCLOSED) client->state = UA_CLIENTSTATE_DISCONNECTED; UA_Client_disconnect(client); } return retval; } UA_SecurityPolicy * getSecurityPolicy(UA_Client *client, UA_String policyUri) { for(size_t i = 0; i < client->config.securityPoliciesSize; i++) { if(UA_String_equal(&policyUri, &client->config.securityPolicies[i].policyUri)) return &client->config.securityPolicies[i]; } return NULL; } static void processDecodedOPNResponse(UA_Client *client, UA_OpenSecureChannelResponse *response, UA_Boolean renew) { /* Replace the token */ if(renew) client->channel.nextSecurityToken = response->securityToken; else client->channel.securityToken = response->securityToken; /* Replace the nonce */ UA_ByteString_deleteMembers(&client->channel.remoteNonce); client->channel.remoteNonce = response->serverNonce; UA_ByteString_init(&response->serverNonce); if(client->channel.state == UA_SECURECHANNELSTATE_OPEN) UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "SecureChannel renewed"); else UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Opened SecureChannel with SecurityPolicy %.*s", (int)client->channel.securityPolicy->policyUri.length, client->channel.securityPolicy->policyUri.data); /* Response.securityToken.revisedLifetime is UInt32 we need to cast it to * DateTime=Int64 we take 75% of lifetime to start renewing as described in * standard */ client->channel.state = UA_SECURECHANNELSTATE_OPEN; client->nextChannelRenewal = UA_DateTime_nowMonotonic() + (UA_DateTime) (client->channel.securityToken.revisedLifetime * (UA_Double)UA_DATETIME_MSEC * 0.75); } UA_StatusCode openSecureChannel(UA_Client *client, UA_Boolean renew) { /* Check if sc is still valid */ if(renew && client->nextChannelRenewal > UA_DateTime_nowMonotonic()) return UA_STATUSCODE_GOOD; UA_Connection *conn = &client->connection; if(conn->state != UA_CONNECTION_ESTABLISHED) return UA_STATUSCODE_BADSERVERNOTCONNECTED; /* Prepare the OpenSecureChannelRequest */ UA_OpenSecureChannelRequest opnSecRq; UA_OpenSecureChannelRequest_init(&opnSecRq); opnSecRq.requestHeader.timestamp = UA_DateTime_now(); opnSecRq.requestHeader.authenticationToken = client->authenticationToken; if(renew) { opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_RENEW; UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "Requesting to renew the SecureChannel"); } else { opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_ISSUE; UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "Requesting to open a SecureChannel"); } /* Set the securityMode to input securityMode from client data */ opnSecRq.securityMode = client->channel.securityMode; opnSecRq.clientNonce = client->channel.localNonce; opnSecRq.requestedLifetime = client->config.secureChannelLifeTime; /* Send the OPN message */ UA_UInt32 requestId = ++client->requestId; UA_StatusCode retval = UA_SecureChannel_sendAsymmetricOPNMessage(&client->channel, requestId, &opnSecRq, &UA_TYPES[UA_TYPES_OPENSECURECHANNELREQUEST]); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "Sending OPN message failed with error %s", UA_StatusCode_name(retval)); UA_Client_disconnect(client); return retval; } UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "OPN message sent"); /* Increase nextChannelRenewal to avoid that we re-start renewal when * publish responses are received before the OPN response arrives. */ client->nextChannelRenewal = UA_DateTime_nowMonotonic() + (2 * ((UA_DateTime)client->config.timeout * UA_DATETIME_MSEC)); /* Receive / decrypt / decode the OPN response. Process async services in * the background until the OPN response arrives. */ UA_OpenSecureChannelResponse response; retval = receiveServiceResponse(client, &response, &UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE], UA_DateTime_nowMonotonic() + ((UA_DateTime)client->config.timeout * UA_DATETIME_MSEC), &requestId); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_SECURECHANNEL, "Receiving service response failed with error %s", UA_StatusCode_name(retval)); UA_Client_disconnect(client); return retval; } processDecodedOPNResponse(client, &response, renew); UA_OpenSecureChannelResponse_deleteMembers(&response); return retval; } /* Function to verify the signature corresponds to ClientNonce * using the local certificate */ static UA_StatusCode checkClientSignature(const UA_SecureChannel *channel, const UA_CreateSessionResponse *response) { if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) return UA_STATUSCODE_GOOD; if(!channel->securityPolicy) return UA_STATUSCODE_BADINTERNALERROR; const UA_SecurityPolicy *sp = channel->securityPolicy; const UA_ByteString *lc = &sp->localCertificate; size_t dataToVerifySize = lc->length + channel->localNonce.length; UA_ByteString dataToVerify = UA_BYTESTRING_NULL; UA_StatusCode retval = UA_ByteString_allocBuffer(&dataToVerify, dataToVerifySize); if(retval != UA_STATUSCODE_GOOD) return retval; memcpy(dataToVerify.data, lc->data, lc->length); memcpy(dataToVerify.data + lc->length, channel->localNonce.data, channel->localNonce.length); retval = sp->certificateSigningAlgorithm. verify(sp, channel->channelContext, &dataToVerify, &response->serverSignature.signature); UA_ByteString_deleteMembers(&dataToVerify); return retval; } /* Function to create a signature using remote certificate and nonce */ #ifdef UA_ENABLE_ENCRYPTION UA_StatusCode signActivateSessionRequest(UA_SecureChannel *channel, UA_ActivateSessionRequest *request) { if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) return UA_STATUSCODE_GOOD; const UA_SecurityPolicy *sp = channel->securityPolicy; UA_SignatureData *sd = &request->clientSignature; /* Prepare the signature */ size_t signatureSize = sp->certificateSigningAlgorithm. getLocalSignatureSize(sp, channel->channelContext); UA_StatusCode retval = UA_String_copy(&sp->certificateSigningAlgorithm.uri, &sd->algorithm); if(retval != UA_STATUSCODE_GOOD) return retval; retval = UA_ByteString_allocBuffer(&sd->signature, signatureSize); if(retval != UA_STATUSCODE_GOOD) return retval; /* Allocate a temporary buffer */ size_t dataToSignSize = channel->remoteCertificate.length + channel->remoteNonce.length; if(dataToSignSize > MAX_DATA_SIZE) return UA_STATUSCODE_BADINTERNALERROR; UA_ByteString dataToSign; retval = UA_ByteString_allocBuffer(&dataToSign, dataToSignSize); if(retval != UA_STATUSCODE_GOOD) return retval; /* sd->signature is cleaned up with the response */ /* Sign the signature */ memcpy(dataToSign.data, channel->remoteCertificate.data, channel->remoteCertificate.length); memcpy(dataToSign.data + channel->remoteCertificate.length, channel->remoteNonce.data, channel->remoteNonce.length); retval = sp->certificateSigningAlgorithm.sign(sp, channel->channelContext, &dataToSign, &sd->signature); /* Clean up */ UA_ByteString_deleteMembers(&dataToSign); return retval; } UA_StatusCode encryptUserIdentityToken(UA_Client *client, const UA_String *userTokenSecurityPolicy, UA_ExtensionObject *userIdentityToken) { UA_IssuedIdentityToken *iit = NULL; UA_UserNameIdentityToken *unit = NULL; UA_ByteString *tokenData; if(userIdentityToken->content.decoded.type == &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN]) { iit = (UA_IssuedIdentityToken*)userIdentityToken->content.decoded.data; tokenData = &iit->tokenData; } else if(userIdentityToken->content.decoded.type == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) { unit = (UA_UserNameIdentityToken*)userIdentityToken->content.decoded.data; tokenData = &unit->password; } else { return UA_STATUSCODE_GOOD; } /* No encryption */ const UA_String none = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None"); if(userTokenSecurityPolicy->length == 0 || UA_String_equal(userTokenSecurityPolicy, &none)) { return UA_STATUSCODE_GOOD; } UA_SecurityPolicy *sp = getSecurityPolicy(client, *userTokenSecurityPolicy); if(!sp) { UA_LOG_WARNING(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Could not find the required SecurityPolicy for the UserToken"); return UA_STATUSCODE_BADSECURITYPOLICYREJECTED; } /* Create a temp channel context */ void *channelContext; UA_StatusCode retval = sp->channelModule. newContext(sp, &client->config.endpoint.serverCertificate, &channelContext); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(&client->config.logger, UA_LOGCATEGORY_NETWORK, "Could not instantiate the SecurityPolicy for the UserToken"); return UA_STATUSCODE_BADINTERNALERROR; } /* Compute the encrypted length (at least one byte padding) */ size_t plainTextBlockSize = sp->asymmetricModule.cryptoModule. encryptionAlgorithm.getRemotePlainTextBlockSize(sp, channelContext); UA_UInt32 length = (UA_UInt32)(tokenData->length + client->channel.remoteNonce.length); UA_UInt32 totalLength = length + 4; /* Including the length field */ size_t blocks = totalLength / plainTextBlockSize; if(totalLength % plainTextBlockSize != 0) blocks++; size_t overHead = UA_SecurityPolicy_getRemoteAsymEncryptionBufferLengthOverhead(sp, channelContext, blocks * plainTextBlockSize); /* Allocate memory for encryption overhead */ UA_ByteString encrypted; retval = UA_ByteString_allocBuffer(&encrypted, (blocks * plainTextBlockSize) + overHead); if(retval != UA_STATUSCODE_GOOD) { sp->channelModule.deleteContext(channelContext); return UA_STATUSCODE_BADOUTOFMEMORY; } UA_Byte *pos = encrypted.data; const UA_Byte *end = &encrypted.data[encrypted.length]; UA_UInt32_encodeBinary(&length, &pos, end); memcpy(pos, tokenData->data, tokenData->length); memcpy(&pos[tokenData->length], client->channel.remoteNonce.data, client->channel.remoteNonce.length); /* Add padding * * 7.36.2.2 Legacy Encrypted Token Secret Format: A Client should not add any * padding after the secret. If a Client adds padding then all bytes shall * be zero. A Server shall check for padding added by Clients and ensure * that all padding bytes are zeros. */ size_t paddedLength = plainTextBlockSize * blocks; for(size_t i = totalLength; i < paddedLength; i++) encrypted.data[i] = 0; encrypted.length = paddedLength; retval = sp->asymmetricModule.cryptoModule.encryptionAlgorithm.encrypt(sp, channelContext, &encrypted); encrypted.length = (blocks * plainTextBlockSize) + overHead; if(iit) { retval |= UA_String_copy(&sp->asymmetricModule.cryptoModule.encryptionAlgorithm.uri, &iit->encryptionAlgorithm); } else { retval |= UA_String_copy(&sp->asymmetricModule.cryptoModule.encryptionAlgorithm.uri, &unit->encryptionAlgorithm); } UA_ByteString_deleteMembers(tokenData); *tokenData = encrypted; /* Delete the temp channel context */ sp->channelModule.deleteContext(channelContext); return retval; } #endif static UA_StatusCode activateSession(UA_Client *client) { UA_ActivateSessionRequest request; UA_ActivateSessionRequest_init(&request); request.requestHeader.requestHandle = ++client->requestHandle; request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 600000; UA_StatusCode retval = UA_ExtensionObject_copy(&client->config.userIdentityToken, &request.userIdentityToken); if(retval != UA_STATUSCODE_GOOD) return retval; /* If not token is set, use anonymous */ if(request.userIdentityToken.encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) { UA_AnonymousIdentityToken *t = UA_AnonymousIdentityToken_new(); if(!t) { UA_ActivateSessionRequest_deleteMembers(&request); return UA_STATUSCODE_BADOUTOFMEMORY; } request.userIdentityToken.content.decoded.data = t; request.userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]; request.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED; } /* Set the policy-Id from the endpoint. Every IdentityToken starts with a * string. */ retval = UA_String_copy(&client->config.userTokenPolicy.policyId, (UA_String*)request.userIdentityToken.content.decoded.data); #ifdef UA_ENABLE_ENCRYPTION /* Encrypt the UserIdentityToken */ const UA_String *userTokenPolicy = &client->channel.securityPolicy->policyUri; if(client->config.userTokenPolicy.securityPolicyUri.length > 0) userTokenPolicy = &client->config.userTokenPolicy.securityPolicyUri; retval |= encryptUserIdentityToken(client, userTokenPolicy, &request.userIdentityToken); /* This function call is to prepare a client signature */ retval |= signActivateSessionRequest(&client->channel, &request); #endif if(retval != UA_STATUSCODE_GOOD) { UA_ActivateSessionRequest_deleteMembers(&request); return retval; } UA_ActivateSessionResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST], &response, &UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE]); if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "ActivateSession failed with error code %s", UA_StatusCode_name(response.responseHeader.serviceResult)); } retval = response.responseHeader.serviceResult; UA_ActivateSessionRequest_deleteMembers(&request); UA_ActivateSessionResponse_deleteMembers(&response); return retval; } /* Gets a list of endpoints. Memory is allocated for endpointDescription array */ UA_StatusCode UA_Client_getEndpointsInternal(UA_Client *client, const UA_String endpointUrl, size_t *endpointDescriptionsSize, UA_EndpointDescription **endpointDescriptions) { UA_GetEndpointsRequest request; UA_GetEndpointsRequest_init(&request); request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; // assume the endpointurl outlives the service call request.endpointUrl = endpointUrl; UA_GetEndpointsResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST], &response, &UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE]); if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_StatusCode retval = response.responseHeader.serviceResult; UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "GetEndpointRequest failed with error code %s", UA_StatusCode_name(retval)); UA_GetEndpointsResponse_deleteMembers(&response); return retval; } *endpointDescriptions = response.endpoints; *endpointDescriptionsSize = response.endpointsSize; response.endpoints = NULL; response.endpointsSize = 0; UA_GetEndpointsResponse_deleteMembers(&response); return UA_STATUSCODE_GOOD; } static UA_StatusCode selectEndpoint(UA_Client *client, const UA_String endpointUrl) { UA_EndpointDescription* endpointArray = NULL; size_t endpointArraySize = 0; UA_StatusCode retval = UA_Client_getEndpointsInternal(client, endpointUrl, &endpointArraySize, &endpointArray); if(retval != UA_STATUSCODE_GOOD) return retval; UA_Boolean endpointFound = false; UA_Boolean tokenFound = false; UA_String binaryTransport = UA_STRING("http://opcfoundation.org/UA-Profile/" "Transport/uatcp-uasc-uabinary"); UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Found %lu endpoints", (long unsigned)endpointArraySize); for(size_t i = 0; i < endpointArraySize; ++i) { UA_EndpointDescription* endpoint = &endpointArray[i]; /* Match Binary TransportProfile? * Note: Siemens returns empty ProfileUrl, we will accept it as binary */ if(endpoint->transportProfileUri.length != 0 && !UA_String_equal(&endpoint->transportProfileUri, &binaryTransport)) continue; /* Valid SecurityMode? */ if(endpoint->securityMode < 1 || endpoint->securityMode > 3) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting endpoint %lu: invalid security mode", (long unsigned)i); continue; } /* Selected SecurityMode? */ if(client->config.securityMode > 0 && client->config.securityMode != endpoint->securityMode) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting endpoint %lu: security mode doesn't match", (long unsigned)i); continue; } /* Matching SecurityPolicy? */ if(client->config.securityPolicyUri.length > 0 && !UA_String_equal(&client->config.securityPolicyUri, &endpoint->securityPolicyUri)) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting endpoint %lu: security policy doesn't match", (long unsigned)i); continue; } /* SecurityPolicy available? */ if(!getSecurityPolicy(client, endpoint->securityPolicyUri)) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting endpoint %lu: security policy not available", (long unsigned)i); continue; } endpointFound = true; /* Select a matching UserTokenPolicy inside the endpoint */ UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Endpoint %lu has %lu user token policies", (long unsigned)i, (long unsigned)endpoint->userIdentityTokensSize); for(size_t j = 0; j < endpoint->userIdentityTokensSize; ++j) { UA_UserTokenPolicy* userToken = &endpoint->userIdentityTokens[j]; /* Usertokens also have a security policy... */ if (userToken->securityPolicyUri.length > 0 && !getSecurityPolicy(client, userToken->securityPolicyUri)) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting UserTokenPolicy %lu in endpoint %lu: security policy '%.*s' not available", (long unsigned)j, (long unsigned)i, (int)userToken->securityPolicyUri.length, userToken->securityPolicyUri.data); continue; } if(userToken->tokenType > 3) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting UserTokenPolicy %lu in endpoint %lu: invalid token type", (long unsigned)j, (long unsigned)i); continue; } /* Does the token type match the client configuration? */ if (userToken->tokenType == UA_USERTOKENTYPE_ANONYMOUS && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] && client->config.userIdentityToken.content.decoded.type != NULL) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting UserTokenPolicy %lu (anonymous) in endpoint %lu: configuration doesn't match", (long unsigned)j, (long unsigned)i); continue; } if (userToken->tokenType == UA_USERTOKENTYPE_USERNAME && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting UserTokenPolicy %lu (username) in endpoint %lu: configuration doesn't match", (long unsigned)j, (long unsigned)i); continue; } if (userToken->tokenType == UA_USERTOKENTYPE_CERTIFICATE && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting UserTokenPolicy %lu (certificate) in endpoint %lu: configuration doesn't match", (long unsigned)j, (long unsigned)i); continue; } if (userToken->tokenType == UA_USERTOKENTYPE_ISSUEDTOKEN && client->config.userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN]) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Rejecting UserTokenPolicy %lu (token) in endpoint %lu: configuration doesn't match", (long unsigned)j, (long unsigned)i); continue; } /* Endpoint with matching UserTokenPolicy found. Copy to the configuration. */ tokenFound = true; UA_EndpointDescription_deleteMembers(&client->config.endpoint); UA_EndpointDescription temp = *endpoint; temp.userIdentityTokensSize = 0; temp.userIdentityTokens = NULL; UA_UserTokenPolicy_deleteMembers(&client->config.userTokenPolicy); retval = UA_EndpointDescription_copy(&temp, &client->config.endpoint); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Copying endpoint description failed with error code %s", UA_StatusCode_name(retval)); break; } retval = UA_UserTokenPolicy_copy(userToken, &client->config.userTokenPolicy); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Copying user token policy failed with error code %s", UA_StatusCode_name(retval)); break; } #if UA_LOGLEVEL <= 300 const char *securityModeNames[3] = {"None", "Sign", "SignAndEncrypt"}; const char *userTokenTypeNames[4] = {"Anonymous", "UserName", "Certificate", "IssuedToken"}; UA_String *securityPolicyUri = &userToken->securityPolicyUri; if(securityPolicyUri->length == 0) securityPolicyUri = &endpoint->securityPolicyUri; /* Log the selected endpoint */ UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Selected Endpoint %.*s with SecurityMode %s and SecurityPolicy %.*s", (int)endpoint->endpointUrl.length, endpoint->endpointUrl.data, securityModeNames[endpoint->securityMode - 1], (int)endpoint->securityPolicyUri.length, endpoint->securityPolicyUri.data); /* Log the selected UserTokenPolicy */ UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Selected UserTokenPolicy %.*s with UserTokenType %s and SecurityPolicy %.*s", (int)userToken->policyId.length, userToken->policyId.data, userTokenTypeNames[userToken->tokenType], (int)securityPolicyUri->length, securityPolicyUri->data); #endif break; } if(tokenFound) break; } UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); if(retval != UA_STATUSCODE_GOOD) return retval; if(!endpointFound) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "No suitable endpoint found"); retval = UA_STATUSCODE_BADINTERNALERROR; } else if(!tokenFound) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "No suitable UserTokenPolicy found for the possible endpoints"); retval = UA_STATUSCODE_BADINTERNALERROR; } return retval; } static UA_StatusCode createSession(UA_Client *client) { UA_CreateSessionRequest request; UA_CreateSessionRequest_init(&request); UA_StatusCode retval = UA_STATUSCODE_GOOD; if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN || client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { if(client->channel.localNonce.length != UA_SESSION_LOCALNONCELENGTH) { UA_ByteString_deleteMembers(&client->channel.localNonce); retval = UA_ByteString_allocBuffer(&client->channel.localNonce, UA_SESSION_LOCALNONCELENGTH); if(retval != UA_STATUSCODE_GOOD) return retval; } retval = client->channel.securityPolicy->symmetricModule. generateNonce(client->channel.securityPolicy, &client->channel.localNonce); if(retval != UA_STATUSCODE_GOOD) return retval; } request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; UA_ByteString_copy(&client->channel.localNonce, &request.clientNonce); request.requestedSessionTimeout = client->config.requestedSessionTimeout; request.maxResponseMessageSize = UA_INT32_MAX; UA_String_copy(&client->config.endpoint.endpointUrl, &request.endpointUrl); UA_ApplicationDescription_copy(&client->config.clientDescription, &request.clientDescription); if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN || client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { UA_ByteString_copy(&client->channel.securityPolicy->localCertificate, &request.clientCertificate); } UA_CreateSessionResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST], &response, &UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE]); if(response.responseHeader.serviceResult == UA_STATUSCODE_GOOD) { /* Verify the encrypted response */ if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN || client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { if(!UA_ByteString_equal(&response.serverCertificate, &client->channel.remoteCertificate)) { retval = UA_STATUSCODE_BADCERTIFICATEINVALID; goto cleanup; } /* Verify the client signature */ retval = checkClientSignature(&client->channel, &response); if(retval != UA_STATUSCODE_GOOD) goto cleanup; } /* Copy nonce and and authenticationtoken */ UA_ByteString_deleteMembers(&client->channel.remoteNonce); retval |= UA_ByteString_copy(&response.serverNonce, &client->channel.remoteNonce); UA_NodeId_deleteMembers(&client->authenticationToken); retval |= UA_NodeId_copy(&response.authenticationToken, &client->authenticationToken); } retval |= response.responseHeader.serviceResult; cleanup: UA_CreateSessionRequest_deleteMembers(&request); UA_CreateSessionResponse_deleteMembers(&response); return retval; } UA_StatusCode UA_Client_connectTCPSecureChannel(UA_Client *client, const UA_String endpointUrl) { if(client->state >= UA_CLIENTSTATE_CONNECTED) return UA_STATUSCODE_GOOD; UA_ChannelSecurityToken_init(&client->channel.securityToken); client->channel.state = UA_SECURECHANNELSTATE_FRESH; client->channel.sendSequenceNumber = 0; client->requestId = 0; /* Set the channel SecurityMode */ client->channel.securityMode = client->config.endpoint.securityMode; if(client->channel.securityMode == UA_MESSAGESECURITYMODE_INVALID) client->channel.securityMode = UA_MESSAGESECURITYMODE_NONE; /* Initialized the SecureChannel */ UA_StatusCode retval = UA_STATUSCODE_GOOD; UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Initialize the SecurityPolicy context"); if(!client->channel.securityPolicy) { /* Set the channel SecurityPolicy to #None if no endpoint is selected */ UA_String sps = client->config.endpoint.securityPolicyUri; if(sps.length == 0) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "SecurityPolicy not specified -> use default #None"); sps = UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#None"); } UA_SecurityPolicy *sp = getSecurityPolicy(client, sps); if(!sp) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Failed to find the required security policy"); retval = UA_STATUSCODE_BADINTERNALERROR; goto cleanup; } retval = UA_SecureChannel_setSecurityPolicy(&client->channel, sp, &client->config.endpoint.serverCertificate); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Failed to set the security policy"); goto cleanup; } } /* Open a TCP connection */ client->connection = client->config.connectionFunc(client->config.localConnectionConfig, endpointUrl, client->config.timeout, &client->config.logger); if(client->connection.state != UA_CONNECTION_OPENING) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Opening the TCP socket failed"); retval = UA_STATUSCODE_BADCONNECTIONCLOSED; goto cleanup; } UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "TCP connection established"); /* Perform the HEL/ACK handshake */ client->connection.config = client->config.localConnectionConfig; retval = HelAckHandshake(client, endpointUrl); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "HEL/ACK handshake failed"); goto cleanup; } setClientState(client, UA_CLIENTSTATE_CONNECTED); /* Open a SecureChannel. */ retval = UA_SecureChannel_generateLocalNonce(&client->channel); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Generating a local nonce failed"); goto cleanup; } client->channel.connection = &client->connection; retval = openSecureChannel(client, false); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Opening a secure channel failed"); goto cleanup; } retval = UA_SecureChannel_generateNewKeys(&client->channel); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Generating new keys failed"); return retval; } setClientState(client, UA_CLIENTSTATE_SECURECHANNEL); return retval; cleanup: UA_Client_disconnect(client); return retval; } UA_StatusCode UA_Client_connectSession(UA_Client *client) { if(client->state < UA_CLIENTSTATE_SECURECHANNEL) return UA_STATUSCODE_BADINTERNALERROR; /* Delete async service. TODO: Move this from connect to the disconnect/cleanup phase */ UA_Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSHUTDOWN); // TODO: actually, reactivate an existing session is working, but currently // republish is not implemented This option is disabled until we have a good // implementation of the subscription recovery. #ifdef UA_SESSION_RECOVERY /* Try to activate an existing Session for this SecureChannel */ if((!UA_NodeId_equal(&client->authenticationToken, &UA_NODEID_NULL)) && (createNewSession)) { UA_StatusCode res = activateSession(client); if(res != UA_STATUSCODE_BADSESSIONIDINVALID) { if(res == UA_STATUSCODE_GOOD) setClientState(client, UA_CLIENTSTATE_SESSION_RENEWED); else UA_Client_disconnect(client); return res; } } #endif /* UA_SESSION_RECOVERY */ /* Could not recover an old session. Remove authenticationToken */ UA_NodeId_deleteMembers(&client->authenticationToken); /* Create a session */ UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Create a new session"); UA_StatusCode retval = createSession(client); if(retval != UA_STATUSCODE_GOOD) { UA_Client_disconnect(client); return retval; } /* A new session has been created. We need to clean up the subscriptions */ #ifdef UA_ENABLE_SUBSCRIPTIONS UA_Client_Subscriptions_clean(client); client->currentlyOutStandingPublishRequests = 0; #endif /* Activate the session */ retval = activateSession(client); if(retval != UA_STATUSCODE_GOOD) { UA_Client_disconnect(client); return retval; } setClientState(client, UA_CLIENTSTATE_SESSION); return retval; } UA_StatusCode UA_Client_connectInternal(UA_Client *client, const UA_String endpointUrl) { if(client->state >= UA_CLIENTSTATE_CONNECTED) return UA_STATUSCODE_GOOD; UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Connecting to endpoint %.*s", (int)endpointUrl.length, endpointUrl.data); /* Get endpoints only if the description has not been touched (memset to zero) */ UA_Byte test = 0; UA_Byte *pos = (UA_Byte*)&client->config.endpoint; for(size_t i = 0; i < sizeof(UA_EndpointDescription); i++) test = test | pos[i]; pos = (UA_Byte*)&client->config.userTokenPolicy; for(size_t i = 0; i < sizeof(UA_UserTokenPolicy); i++) test = test | pos[i]; UA_Boolean getEndpoints = (test == 0); /* Connect up to the SecureChannel */ UA_StatusCode retval = UA_Client_connectTCPSecureChannel(client, endpointUrl); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Couldn't connect the client to a TCP secure channel"); goto cleanup; } /* Get and select endpoints if required */ if(getEndpoints) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Endpoint and UserTokenPolicy unconfigured, perform GetEndpoints"); retval = selectEndpoint(client, endpointUrl); if(retval != UA_STATUSCODE_GOOD) goto cleanup; /* Reconnect with a new SecureChannel if the current one does not match * the selected endpoint */ if(!UA_String_equal(&client->config.endpoint.securityPolicyUri, &client->channel.securityPolicy->policyUri)) { UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, "Disconnect to switch to a different SecurityPolicy"); UA_Client_disconnect(client); return UA_Client_connectInternal(client, endpointUrl); } } retval = UA_Client_connectSession(client); if(retval != UA_STATUSCODE_GOOD) goto cleanup; return retval; cleanup: UA_Client_disconnect(client); return retval; } UA_StatusCode UA_Client_connect(UA_Client *client, const char *endpointUrl) { return UA_Client_connectInternal(client, UA_STRING((char*)(uintptr_t)endpointUrl)); } UA_StatusCode UA_Client_connect_noSession(UA_Client *client, const char *endpointUrl) { return UA_Client_connectTCPSecureChannel(client, UA_STRING((char*)(uintptr_t)endpointUrl)); } UA_StatusCode UA_Client_connect_username(UA_Client *client, const char *endpointUrl, const char *username, const char *password) { UA_UserNameIdentityToken* identityToken = UA_UserNameIdentityToken_new(); if(!identityToken) return UA_STATUSCODE_BADOUTOFMEMORY; identityToken->userName = UA_STRING_ALLOC(username); identityToken->password = UA_STRING_ALLOC(password); UA_ExtensionObject_deleteMembers(&client->config.userIdentityToken); client->config.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED; client->config.userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]; client->config.userIdentityToken.content.decoded.data = identityToken; return UA_Client_connect(client, endpointUrl); } /************************/ /* Close the Connection */ /************************/ static void sendCloseSession(UA_Client *client) { UA_CloseSessionRequest request; UA_CloseSessionRequest_init(&request); request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; request.deleteSubscriptions = true; UA_CloseSessionResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST], &response, &UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE]); UA_CloseSessionRequest_deleteMembers(&request); UA_CloseSessionResponse_deleteMembers(&response); } static void sendCloseSecureChannel(UA_Client *client) { UA_SecureChannel *channel = &client->channel; UA_CloseSecureChannelRequest request; UA_CloseSecureChannelRequest_init(&request); request.requestHeader.requestHandle = ++client->requestHandle; request.requestHeader.timestamp = UA_DateTime_now(); request.requestHeader.timeoutHint = 10000; request.requestHeader.authenticationToken = client->authenticationToken; UA_SecureChannel_sendSymmetricMessage(channel, ++client->requestId, UA_MESSAGETYPE_CLO, &request, &UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST]); UA_CloseSecureChannelRequest_deleteMembers(&request); UA_SecureChannel_close(&client->channel); UA_SecureChannel_deleteMembers(&client->channel); } UA_StatusCode UA_Client_disconnect(UA_Client *client) { /* Is a session established? */ if(client->state >= UA_CLIENTSTATE_SESSION) { client->state = UA_CLIENTSTATE_SECURECHANNEL; sendCloseSession(client); } UA_NodeId_deleteMembers(&client->authenticationToken); client->requestHandle = 0; /* Is a secure channel established? */ if(client->state >= UA_CLIENTSTATE_SECURECHANNEL) { client->state = UA_CLIENTSTATE_CONNECTED; sendCloseSecureChannel(client); } /* Close the TCP connection */ if(client->connection.state != UA_CONNECTION_CLOSED && client->connection.state != UA_CONNECTION_OPENING) /* UA_ClientConnectionTCP_init sets initial state to opening */ if(client->connection.close != NULL) client->connection.close(&client->connection); #ifdef UA_ENABLE_SUBSCRIPTIONS // TODO REMOVE WHEN UA_SESSION_RECOVERY IS READY /* We need to clean up the subscriptions */ UA_Client_Subscriptions_clean(client); #endif UA_SecureChannel_deleteMembers(&client->channel); setClientState(client, UA_CLIENTSTATE_DISCONNECTED); return UA_STATUSCODE_GOOD; }