/* 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-2017 (c) Florian Palm * Copyright 2014-2016 (c) Sten Grüner * Copyright 2015 (c) Chris Iatrou * Copyright 2015 (c) Oleksiy Vasylyev * Copyright 2017 (c) Stefan Profanter, fortiss GmbH * Copyright 2017-2018 (c) Mark Giraud, Fraunhofer IOSB * Copyright 2019 (c) Kalycito Infotech Private Limited */ #include "ua_services.h" #include "ua_server_internal.h" #include "ua_session_manager.h" static UA_StatusCode signCreateSessionResponse(UA_Server *server, UA_SecureChannel *channel, const UA_CreateSessionRequest *request, UA_CreateSessionResponse *response) { if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) return UA_STATUSCODE_GOOD; const UA_SecurityPolicy *const securityPolicy = channel->securityPolicy; UA_SignatureData *signatureData = &response->serverSignature; /* Prepare the signature */ size_t signatureSize = securityPolicy->certificateSigningAlgorithm. getLocalSignatureSize(securityPolicy, channel->channelContext); UA_StatusCode retval = UA_String_copy(&securityPolicy->certificateSigningAlgorithm.uri, &signatureData->algorithm); retval |= UA_ByteString_allocBuffer(&signatureData->signature, signatureSize); if(retval != UA_STATUSCODE_GOOD) return retval; /* Allocate a temp buffer */ size_t dataToSignSize = request->clientCertificate.length + request->clientNonce.length; UA_ByteString dataToSign; retval = UA_ByteString_allocBuffer(&dataToSign, dataToSignSize); if(retval != UA_STATUSCODE_GOOD) return retval; /* signatureData->signature is cleaned up with the response */ /* Sign the signature */ memcpy(dataToSign.data, request->clientCertificate.data, request->clientCertificate.length); memcpy(dataToSign.data + request->clientCertificate.length, request->clientNonce.data, request->clientNonce.length); retval = securityPolicy->certificateSigningAlgorithm. sign(securityPolicy, channel->channelContext, &dataToSign, &signatureData->signature); /* Clean up */ UA_ByteString_deleteMembers(&dataToSign); return retval; } void Service_CreateSession(UA_Server *server, UA_SecureChannel *channel, const UA_CreateSessionRequest *request, UA_CreateSessionResponse *response) { if(!channel) { response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; return; } if(!channel->connection) { response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; return; } UA_LOG_DEBUG_CHANNEL(&server->config.logger, channel, "Trying to create session"); if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { /* Compare the clientCertificate with the remoteCertificate of the channel. * Both the clientCertificate of this request and the remoteCertificate * of the channel may contain a partial or a complete certificate chain. * The compareCertificate function of the channelModule will compare the * first certificate of each chain. The end certificate shall be located * first in the chain according to the OPC UA specification Part 6 (1.04), * chapter 6.2.3.*/ UA_StatusCode retval = channel->securityPolicy->channelModule. compareCertificate(channel->channelContext, &request->clientCertificate); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_CHANNEL(&server->config.logger, channel, "The client certificate did not validate"); response->responseHeader.serviceResult = UA_STATUSCODE_BADCERTIFICATEINVALID; return; } } if(channel->securityToken.channelId == 0) { response->responseHeader.serviceResult = UA_STATUSCODE_BADSECURECHANNELIDINVALID; return; } if(!UA_ByteString_equal(&channel->securityPolicy->policyUri, &UA_SECURITY_POLICY_NONE_URI) && request->clientNonce.length < 32) { response->responseHeader.serviceResult = UA_STATUSCODE_BADNONCEINVALID; return; } /* TODO: Compare application URI with certificate uri (decode certificate) */ UA_CertificateVerification *cv = channel->securityPolicy->certificateVerification; if(cv && cv->verifyApplicationURI) { response->responseHeader.serviceResult = cv->verifyApplicationURI(cv->context, &request->clientCertificate, &request->clientDescription.applicationUri); if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_CHANNEL(&server->config.logger, channel, "The client's ApplicationURI did not match the certificate"); return; } } UA_Session *newSession = NULL; response->responseHeader.serviceResult = UA_SessionManager_createSession(&server->sessionManager, channel, request, &newSession); if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_CHANNEL(&server->config.logger, channel, "Processing CreateSessionRequest failed"); return; } UA_assert(newSession != NULL); /* Allocate the response */ response->serverEndpoints = (UA_EndpointDescription *) UA_Array_new(server->config.endpointsSize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); if(!response->serverEndpoints) { response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; UA_SessionManager_removeSession(&server->sessionManager, &newSession->header.authenticationToken); return; } response->serverEndpointsSize = server->config.endpointsSize; /* Copy the server's endpointdescriptions into the response */ for(size_t i = 0; i < server->config.endpointsSize; ++i) response->responseHeader.serviceResult |= UA_EndpointDescription_copy(&server->config.endpoints[i], &response->serverEndpoints[i]); if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_SessionManager_removeSession(&server->sessionManager, &newSession->header.authenticationToken); return; } /* Mirror back the endpointUrl */ for(size_t i = 0; i < response->serverEndpointsSize; ++i) { UA_String_deleteMembers(&response->serverEndpoints[i].endpointUrl); response->responseHeader.serviceResult |= UA_String_copy(&request->endpointUrl, &response->serverEndpoints[i].endpointUrl); } /* Attach the session to the channel. But don't activate for now. */ UA_Session_attachToSecureChannel(newSession, channel); /* Fill the session information */ newSession->maxResponseMessageSize = request->maxResponseMessageSize; newSession->maxRequestMessageSize = channel->connection->config.maxMessageSize; response->responseHeader.serviceResult |= UA_ApplicationDescription_copy(&request->clientDescription, &newSession->clientDescription); /* Prepare the response */ response->sessionId = newSession->sessionId; response->revisedSessionTimeout = (UA_Double)newSession->timeout; response->authenticationToken = newSession->header.authenticationToken; response->responseHeader.serviceResult |= UA_String_copy(&request->sessionName, &newSession->sessionName); UA_ByteString_init(&response->serverCertificate); if(server->config.endpointsSize > 0) for(size_t i = 0; i < response->serverEndpointsSize; ++i) { if(response->serverEndpoints[i].securityMode==channel->securityMode && UA_ByteString_equal(&response->serverEndpoints[i].securityPolicyUri, &channel->securityPolicy->policyUri) && UA_String_equal(&response->serverEndpoints[i].endpointUrl, &request->endpointUrl)) { response->responseHeader.serviceResult |= UA_ByteString_copy(&response->serverEndpoints[i].serverCertificate, &response->serverCertificate); } } /* Create a session nonce */ response->responseHeader.serviceResult |= UA_Session_generateNonce(newSession); response->responseHeader.serviceResult |= UA_ByteString_copy(&newSession->serverNonce, &response->serverNonce); /* Sign the signature */ response->responseHeader.serviceResult |= signCreateSessionResponse(server, channel, request, response); /* Failure -> remove the session */ if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_SessionManager_removeSession(&server->sessionManager, &newSession->header.authenticationToken); return; } UA_LOG_INFO_CHANNEL(&server->config.logger, channel, "Session " UA_PRINTF_GUID_FORMAT " created", UA_PRINTF_GUID_DATA(newSession->sessionId.identifier.guid)); } static UA_StatusCode checkSignature(const UA_Server *server, const UA_SecureChannel *channel, UA_Session *session, const UA_ActivateSessionRequest *request) { if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) return UA_STATUSCODE_GOOD; /* Check for zero signature length in client signature */ if(request->clientSignature.signature.length == 0) { return UA_STATUSCODE_BADAPPLICATIONSIGNATUREINVALID; } if(!channel->securityPolicy) return UA_STATUSCODE_BADINTERNALERROR; const UA_SecurityPolicy *securityPolicy = channel->securityPolicy; const UA_ByteString *localCertificate = &securityPolicy->localCertificate; size_t dataToVerifySize = localCertificate->length + session->serverNonce.length; UA_ByteString dataToVerify; UA_StatusCode retval = UA_ByteString_allocBuffer(&dataToVerify, dataToVerifySize); if(retval != UA_STATUSCODE_GOOD) return retval; memcpy(dataToVerify.data, localCertificate->data, localCertificate->length); memcpy(dataToVerify.data + localCertificate->length, session->serverNonce.data, session->serverNonce.length); retval = securityPolicy->certificateSigningAlgorithm.verify(securityPolicy, channel->channelContext, &dataToVerify, &request->clientSignature.signature); UA_ByteString_deleteMembers(&dataToVerify); return retval; } #ifdef UA_ENABLE_ENCRYPTION static UA_StatusCode decryptPassword(UA_SecurityPolicy *securityPolicy, void *tempChannelContext, const UA_ByteString *serverNonce, UA_UserNameIdentityToken *userToken) { UA_SecurityPolicyEncryptionAlgorithm *asymEnc = &securityPolicy->asymmetricModule.cryptoModule.encryptionAlgorithm; if(!UA_String_equal(&userToken->encryptionAlgorithm, &asymEnc->uri)) return UA_STATUSCODE_BADIDENTITYTOKENINVALID; UA_UInt32 tokenSecretLength; UA_ByteString decryptedTokenSecret, tokenServerNonce; if(UA_ByteString_copy(&userToken->password, &decryptedTokenSecret) != UA_STATUSCODE_GOOD) return UA_STATUSCODE_BADIDENTITYTOKENINVALID; UA_StatusCode retval = UA_STATUSCODE_BADIDENTITYTOKENINVALID; if(asymEnc->decrypt(securityPolicy, tempChannelContext, &decryptedTokenSecret) != UA_STATUSCODE_GOOD) goto cleanup; memcpy(&tokenSecretLength, decryptedTokenSecret.data, sizeof(UA_UInt32)); /* The decrypted data must be large enough to include the Encrypted Token * Secret Format and the length field must indicate enough data to include * the server nonce. */ if(decryptedTokenSecret.length < sizeof(UA_UInt32) + serverNonce->length || decryptedTokenSecret.length < sizeof(UA_UInt32) + tokenSecretLength || tokenSecretLength < serverNonce->length) goto cleanup; /* If the Encrypted Token Secret contains padding, the padding must be * zeroes according to the 1.04.1 specification errata, chapter 3. */ for(size_t i = sizeof(UA_UInt32) + tokenSecretLength; i < decryptedTokenSecret.length; i++) { if(decryptedTokenSecret.data[i] != 0) goto cleanup; } /* The server nonce must match according to the 1.04.1 specification errata, * chapter 3. */ tokenServerNonce.length = serverNonce->length; tokenServerNonce.data = &decryptedTokenSecret.data[sizeof(UA_UInt32) + tokenSecretLength - serverNonce->length]; if(!UA_ByteString_equal(serverNonce, &tokenServerNonce)) goto cleanup; /* The password was decrypted successfully. Replace usertoken with the * decrypted password. The encryptionAlgorithm and policyId fields are left * in the UserToken as an indication for the AccessControl plugin that * evaluates the decrypted content. */ memcpy(userToken->password.data, &decryptedTokenSecret.data[sizeof(UA_UInt32)], tokenSecretLength - serverNonce->length); userToken->password.length = tokenSecretLength - serverNonce->length; retval = UA_STATUSCODE_GOOD; cleanup: UA_ByteString_deleteMembers(&decryptedTokenSecret); return retval; } #endif /* TODO: Check all of the following: * * Part 4, §5.6.3: When the ActivateSession Service is called for the first time * then the Server shall reject the request if the SecureChannel is not same as * the one associated with the CreateSession request. Subsequent calls to * ActivateSession may be associated with different SecureChannels. If this is * the case then the Server shall verify that the Certificate the Client used to * create the new SecureChannel is the same as the Certificate used to create * the original SecureChannel. In addition, the Server shall verify that the * Client supplied a UserIdentityToken that is identical to the token currently * associated with the Session. Once the Server accepts the new SecureChannel it * shall reject requests sent via the old SecureChannel. */ void Service_ActivateSession(UA_Server *server, UA_SecureChannel *channel, UA_Session *session, const UA_ActivateSessionRequest *request, UA_ActivateSessionResponse *response) { UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Execute ActivateSession"); if(session->validTill < UA_DateTime_nowMonotonic()) { UA_LOG_INFO_SESSION(&server->config.logger, session, "ActivateSession: SecureChannel %i wants " "to activate, but the session has timed out", channel->securityToken.channelId); response->responseHeader.serviceResult = UA_STATUSCODE_BADSESSIONIDINVALID; return; } /* Check if the signature corresponds to the ServerNonce that was last sent * to the client */ response->responseHeader.serviceResult = checkSignature(server, channel, session, request); if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_LOG_INFO_SESSION(&server->config.logger, session, "Signature check failed with status code %s", UA_StatusCode_name(response->responseHeader.serviceResult)); return; } /* Find the matching endpoint */ const UA_EndpointDescription *ed = NULL; for(size_t i = 0; ed == NULL && i < server->config.endpointsSize; ++i) { const UA_EndpointDescription *e = &server->config.endpoints[i]; /* Match the Security Mode */ if(e->securityMode != channel->securityMode) continue; /* Match the SecurityPolicy */ if(!UA_String_equal(&e->securityPolicyUri, &channel->securityPolicy->policyUri)) continue; /* Match the UserTokenType */ for(size_t j = 0; j < e->userIdentityTokensSize; j++) { const UA_UserTokenPolicy *u = &e->userIdentityTokens[j]; if(u->tokenType == UA_USERTOKENTYPE_ANONYMOUS) { /* Part 4, Section 5.6.3.2, Table 17: A NULL or empty * UserIdentityToken should be treated as Anonymous */ if(request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] && request->userIdentityToken.encoding != UA_EXTENSIONOBJECT_ENCODED_NOBODY) continue; } else if(u->tokenType == UA_USERTOKENTYPE_USERNAME) { if(request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) continue; } else if(u->tokenType == UA_USERTOKENTYPE_CERTIFICATE) { if(request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]) continue; } else if(u->tokenType == UA_USERTOKENTYPE_ISSUEDTOKEN) { if(request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN]) continue; } else { response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } /* Match found */ ed = e; break; } } /* No matching endpoint found */ if(!ed) { response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } #ifdef UA_ENABLE_ENCRYPTION /* If it is a UserNameIdentityToken, decrypt the password if encrypted */ if((request->userIdentityToken.encoding == UA_EXTENSIONOBJECT_DECODED) && (request->userIdentityToken.content.decoded.type == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN])) { UA_UserNameIdentityToken *userToken = (UA_UserNameIdentityToken *) request->userIdentityToken.content.decoded.data; /* Find the UserTokenPolicy */ UA_Byte tokenIndex = 0; for(; tokenIndex < ed->userIdentityTokensSize; tokenIndex++) { if(ed->userIdentityTokens[tokenIndex].tokenType != UA_USERTOKENTYPE_USERNAME) continue; if(UA_String_equal(&userToken->policyId, &ed->userIdentityTokens[tokenIndex].policyId)) break; } if(tokenIndex == ed->userIdentityTokensSize) { response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } /* Get the SecurityPolicy. If the userTokenPolicy doesn't specify a * security policy the security policy of the secure channel is used. */ UA_SecurityPolicy* securityPolicy; if(ed->userIdentityTokens[tokenIndex].securityPolicyUri.data == NULL) securityPolicy = UA_SecurityPolicy_getSecurityPolicyByUri(server, &ed->securityPolicyUri); else securityPolicy = UA_SecurityPolicy_getSecurityPolicyByUri(server, &ed->userIdentityTokens[tokenIndex].securityPolicyUri); if(!securityPolicy) { response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; return; } /* Encrypted password? */ if(!UA_String_equal(&securityPolicy->policyUri, &UA_SECURITY_POLICY_NONE_URI)) { /* Test if the encryption algorithm is correctly specified */ if(!UA_String_equal(&userToken->encryptionAlgorithm, &securityPolicy->asymmetricModule.cryptoModule. encryptionAlgorithm.uri)) { response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; return; } /* Create a temporary channel context if a different SecurityPolicy is * used for the password from the SecureChannel */ void *tempChannelContext = channel->channelContext; if(securityPolicy != channel->securityPolicy) { /* TODO: This is a hack. We use our own certificate to create a * channel context. Because the client does not provide one in a * #None SecureChannel. We should not need a ChannelContext at all * for asymmetric decryption where the remote certificate is not * used. */ response->responseHeader.serviceResult = securityPolicy->channelModule.newContext(securityPolicy, &securityPolicy->localCertificate, &tempChannelContext); if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_SESSION(&server->config.logger, session, "ActivateSession: " "Failed to create a context for the SecurityPolicy %.*s", (int)securityPolicy->policyUri.length, securityPolicy->policyUri.data); return; } } /* Decrypt */ response->responseHeader.serviceResult = decryptPassword(securityPolicy, tempChannelContext, &session->serverNonce, userToken); /* Remove the temporary channel context */ if(securityPolicy != channel->securityPolicy) securityPolicy->channelModule.deleteContext(tempChannelContext); } if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_LOG_INFO_SESSION(&server->config.logger, session, "ActivateSession: " "Failed to decrypt the password with the status code %s", UA_StatusCode_name(response->responseHeader.serviceResult)); } } #endif /* Callback into userland access control */ response->responseHeader.serviceResult = server->config.accessControl.activateSession(server, &server->config.accessControl, ed, &channel->remoteCertificate, &session->sessionId, &request->userIdentityToken, &session->sessionHandle); if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_LOG_INFO_SESSION(&server->config.logger, session, "ActivateSession: The AccessControl plugin " "denied the access with the status code %s", UA_StatusCode_name(response->responseHeader.serviceResult)); return; } if(session->header.channel && session->header.channel != channel) { UA_LOG_INFO_SESSION(&server->config.logger, session, "ActivateSession: Detach from old channel"); /* Detach the old SecureChannel and attach the new */ UA_Session_detachFromSecureChannel(session); UA_Session_attachToSecureChannel(session, channel); } /* Activate the session */ session->activated = true; UA_Session_updateLifetime(session); /* Generate a new session nonce for the next time ActivateSession is called */ response->responseHeader.serviceResult = UA_Session_generateNonce(session); response->responseHeader.serviceResult |= UA_ByteString_copy(&session->serverNonce, &response->serverNonce); if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { UA_Session_detachFromSecureChannel(session); session->activated = false; UA_LOG_INFO_SESSION(&server->config.logger, session, "ActivateSession: Could not generate a server nonce"); return; } UA_LOG_INFO_SESSION(&server->config.logger, session, "ActivateSession: Session activated"); } void Service_CloseSession(UA_Server *server, UA_Session *session, const UA_CloseSessionRequest *request, UA_CloseSessionResponse *response) { UA_LOG_INFO_SESSION(&server->config.logger, session, "CloseSession"); response->responseHeader.serviceResult = UA_SessionManager_removeSession(&server->sessionManager, &session->header.authenticationToken); }