You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libiec61850/src/mms/iso_acse/acse.c

867 lines
24 KiB
C

/*
* acse.c
*
* Copyright 2013 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "libiec61850_platform_includes.h"
#include "acse.h"
#include "ber_encoder.h"
#include "ber_decode.h"
#if ((DEBUG_ISO_CLIENT == 1) || (DEBUG_ISO_SERVER == 1))
#define DEBUG_ACSE 1
#else
#define DEBUG_ACSE 0
#endif
static uint8_t appContextNameMms[] = { 0x28, 0xca, 0x22, 0x02, 0x03 };
static uint8_t auth_mech_password_oid[] = { 0x52, 0x03, 0x01 };
static uint8_t requirements_authentication[] = { 0x80 };
static AcseAuthenticationMechanism
checkAuthMechanismName(uint8_t* authMechanism, int authMechLen)
{
AcseAuthenticationMechanism authenticationMechanism = ACSE_AUTH_NONE;
if (authMechanism != NULL) {
if (authMechLen == 3) {
if (memcmp(auth_mech_password_oid, authMechanism, 3) == 0) {
authenticationMechanism = ACSE_AUTH_PASSWORD;
}
}
}
return authenticationMechanism;
}
static bool
authenticateClient(AcseConnection* self, AcseAuthenticationMechanism mechanism, uint8_t* authValue, int authValueLen)
{
struct sAcseAuthenticationParameter authParamStruct;
AcseAuthenticationParameter authParameter = &authParamStruct;
authParameter->mechanism = mechanism;
if (mechanism == ACSE_AUTH_PASSWORD) {
authParameter->value.password.octetString = authValue;
authParameter->value.password.passwordLength = authValueLen;
}
else if (mechanism == ACSE_AUTH_TLS) {
authParameter->value.certificate.buf = authValue;
authParameter->value.certificate.length = authValueLen;
}
return self->authenticator(self->authenticatorParameter, authParameter, &(self->securityToken), &(self->applicationReference));
}
static bool
checkAuthentication(AcseConnection* self, uint8_t* authMechanism, int authMechLen, uint8_t* authValue, int authValueLen)
{
self->securityToken = NULL;
if (self->authenticator != NULL) {
AcseAuthenticationMechanism mechanism = checkAuthMechanismName(authMechanism, authMechLen);
if (mechanism == ACSE_AUTH_NONE) {
#if (CONFIG_MMS_SUPPORT_TLS == 1)
if (self->tlsSocket) {
int certLen;
uint8_t* certBuf = TLSSocket_getPeerCertificate(self->tlsSocket, &certLen);
if (certBuf) {
mechanism = ACSE_AUTH_TLS;
authValue = certBuf;
authValueLen = certLen;
}
}
#endif /* (CONFIG_MMS_SUPPORT_TLS == 1) */
}
return authenticateClient(self, mechanism, authValue, authValueLen);
}
else
return true;
}
static int
parseUserInformation(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos, bool* userInfoValid)
{
if (DEBUG_ACSE)
printf("ACSE: parseUserInformation %i %i\n", bufPos, maxBufPos);
bool hasindirectReference = false;
bool isDataValid = false;
while (bufPos < maxBufPos) {
uint8_t tag = buffer[bufPos++];
int len;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (len == 0)
continue;
if ((bufPos < 0) || (bufPos + len > maxBufPos)) {
*userInfoValid = false;
return -1;
}
switch (tag)
{
case 0x02: /* indirect-reference */
self->nextReference = BerDecoder_decodeUint32(buffer, len, bufPos);
bufPos += len;
hasindirectReference = true;
break;
case 0xa0: /* encoding */
isDataValid = true;
self->userDataBufferSize = len;
self->userDataBuffer = buffer + bufPos;
bufPos += len;
break;
default: /* ignore unknown tag */
bufPos += len;
}
}
if (DEBUG_ACSE) {
if (!hasindirectReference)
printf("ACSE: User data has no indirect reference!\n");
if (!isDataValid)
printf("ACSE: No valid user data\n");
}
if (hasindirectReference && isDataValid)
*userInfoValid = true;
else
*userInfoValid = false;
return bufPos;
}
static AcseIndication
parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
{
if (DEBUG_ACSE)
printf("ACSE: parse AARE PDU\n");
bool userInfoValid = false;
uint32_t result = 99;
while (bufPos < maxBufPos) {
uint8_t tag = buffer[bufPos++];
int len;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0)
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ERROR;
}
if (len == 0)
continue;
if (bufPos + len > maxBufPos)
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ERROR;
}
switch (tag)
{
case 0xa1: /* application context name */
bufPos += len;
break;
case 0xa2: /* result */
bufPos++;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0)
return ACSE_ERROR;
result = BerDecoder_decodeUint32(buffer, len, bufPos);
bufPos += len;
break;
case 0xa3: /* result source diagnostic */
bufPos += len;
break;
case 0xbe: /* user information */
if (buffer[bufPos] != 0x28) {
if (DEBUG_ACSE)
printf("ACSE: invalid user info\n");
bufPos += len;
}
else {
bufPos++;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0)
return ACSE_ERROR;
bufPos = parseUserInformation(self, buffer, bufPos, bufPos + len, &userInfoValid);
if (bufPos < 0)
return ACSE_ERROR;
}
break;
case 0x00: /* indefinite length end tag -> ignore */
break;
default: /* ignore unknown tag */
if (DEBUG_ACSE)
printf("ACSE: parseAarePdu: unknown tag %02x\n", tag);
bufPos += len;
break;
}
}
if (!userInfoValid)
return ACSE_ERROR;
if (result != 0)
return ACSE_ASSOCIATE_FAILED;
return ACSE_ASSOCIATE;
}
static AcseIndication
parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
{
if (DEBUG_ACSE)
printf("ACSE: parse AARQ PDU\n");
uint8_t* authValue = NULL;
int authValueLen = 0;
uint8_t* authMechanism = NULL;
int authMechLen = 0;
bool userInfoValid = false;
while (bufPos < maxBufPos) {
uint8_t tag = buffer[bufPos++];
int len;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0)
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
}
if (len == 0)
continue;
if (bufPos + len > maxBufPos)
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
}
switch (tag)
{
case 0xa1: /* application context name */
bufPos += len;
break;
case 0xa2: /* called AP title */
bufPos += len;
break;
case 0xa3: /* called AE qualifier */
bufPos += len;
break;
case 0xa6: /* calling AP title */
{
if (buffer[bufPos] == 0x06) { /* ap-title-form2 */
int innerLength = buffer[bufPos + 1];
if (innerLength == len - 2)
BerDecoder_decodeOID(buffer, bufPos + 2, innerLength, &(self->applicationReference.apTitle));
}
}
bufPos += len;
break;
case 0xa7: /* calling AE qualifier */
{
if (buffer[bufPos] == 0x02) { /* ae-qualifier-form2 */
int innerLength = buffer[bufPos + 1];
if (innerLength == len - 2)
self->applicationReference.aeQualifier = BerDecoder_decodeInt32(buffer + 2, buffer[bufPos + 1], bufPos);
}
}
bufPos += len;
break;
case 0x8a: /* sender ACSE requirements */
bufPos += len;
break;
case 0x8b: /* (authentication) mechanism name */
authMechLen = len;
authMechanism = buffer + bufPos;
bufPos += len;
break;
case 0xac: /* authentication value */
bufPos++;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0) {
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
}
authValueLen = len;
authValue = buffer + bufPos;
bufPos += len;
break;
case 0xbe: /* user information */
if (buffer[bufPos] != 0x28) {
if (DEBUG_ACSE)
printf("ACSE: invalid user info\n");
bufPos += len;
}
else {
bufPos++;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0) {
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
}
bufPos = parseUserInformation(self, buffer, bufPos, bufPos + len, &userInfoValid);
if (bufPos < 0) {
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
}
}
break;
case 0x00: /* indefinite length end tag -> ignore */
break;
default: /* ignore unknown tag */
if (DEBUG_ACSE)
printf("ACSE: parseAarqPdu: unknown tag %02x\n", tag);
bufPos += len;
break;
}
}
if (checkAuthentication(self, authMechanism, authMechLen, authValue, authValueLen) == false) {
if (DEBUG_ACSE)
printf("ACSE: parseAarqPdu: check authentication failed!\n");
return ACSE_ASSOCIATE_FAILED;
}
if (userInfoValid == false) {
if (DEBUG_ACSE)
printf("ACSE: parseAarqPdu: user info invalid!\n");
return ACSE_ASSOCIATE_FAILED;
}
return ACSE_ASSOCIATE;
}
void
AcseConnection_init(AcseConnection* self, AcseAuthenticator authenticator, void* parameter, TLSSocket tlsSocket)
{
self->state = idle;
self->nextReference = 0;
self->userDataBuffer = NULL;
self->userDataBufferSize = 0;
self->authenticator = authenticator;
self->authenticatorParameter = parameter;
#if (CONFIG_MMS_SUPPORT_TLS == 1)
self->tlsSocket = tlsSocket;
#else
(void)tlsSocket;
#endif
memset(&(self->applicationReference), 0,
sizeof(self->applicationReference));
}
AcseIndication
AcseConnection_parseMessage(AcseConnection* self, ByteBuffer* message)
{
AcseIndication indication = ACSE_ERROR;
if (message == NULL || message->size < 1)
{
if (DEBUG_ACSE)
printf("ACSE: invalid message - no payload\n");
return ACSE_ERROR;
}
uint8_t* buffer = message->buffer;
int messageSize = message->size;
int bufPos = 0;
uint8_t messageType = buffer[bufPos++];
int len;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, messageSize);
if (bufPos < 0)
{
if (DEBUG_ACSE)
printf("ACSE: AcseConnection_parseMessage: invalid ACSE message!\n");
return ACSE_ERROR;
}
switch (messageType)
{
case 0x60:
indication = parseAarqPdu(self, buffer, bufPos, messageSize);
break;
case 0x61:
indication = parseAarePdu(self, buffer, bufPos, messageSize);
break;
case 0x62: /* A_RELEASE.request RLRQ-apdu */
indication = ACSE_RELEASE_REQUEST;
break;
case 0x63: /* A_RELEASE.response RLRE-apdu */
indication = ACSE_RELEASE_RESPONSE;
break;
case 0x64: /* A_ABORT */
indication = ACSE_ABORT;
break;
case 0x00: /* indefinite length end tag -> ignore */
break;
default:
if (DEBUG_ACSE)
printf("ACSE: Unknown ACSE message\n");
indication = ACSE_ERROR;
break;
}
return indication;
}
void
AcseConnection_createAssociateFailedMessage(AcseConnection* self, BufferChain writeBuffer)
{
AcseConnection_createAssociateResponseMessage(self, ACSE_RESULT_REJECT_PERMANENT, writeBuffer, NULL);
}
void
AcseConnection_createAssociateResponseMessage(AcseConnection* self,
uint8_t acseResult,
BufferChain writeBuffer,
BufferChain payload
)
{
assert(self != NULL);
assert(writeBuffer != NULL);
assert(payload != NULL);
int appContextLength = 9;
int resultLength = 5;
int resultDiagnosticLength = 5;
int fixedContentLength = appContextLength + resultLength
+ resultDiagnosticLength;
int variableContentLength = 0;
int assocDataLength;
int userInfoLength;
int nextRefLength;
int payloadLength = payload->length;
/* single ASN1 type tag */
variableContentLength += payloadLength;
variableContentLength += 1;
variableContentLength += BerEncoder_determineLengthSize(payloadLength);
/* indirect reference */
nextRefLength = BerEncoder_UInt32determineEncodedSize(self->nextReference);
variableContentLength += nextRefLength;
variableContentLength += 2;
/* association data */
assocDataLength = variableContentLength;
variableContentLength += BerEncoder_determineLengthSize(assocDataLength);
variableContentLength += 1;
/* user information */
userInfoLength = variableContentLength;
variableContentLength += BerEncoder_determineLengthSize(userInfoLength);
variableContentLength += 1;
variableContentLength += 2;
int contentLength = fixedContentLength + variableContentLength;
uint8_t* buffer = writeBuffer->buffer;
int bufPos = 0;
bufPos = BerEncoder_encodeTL(0x61, contentLength, buffer, bufPos);
/* application context name */
bufPos = BerEncoder_encodeTL(0xa1, 7, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x06, 5, buffer, bufPos);
memcpy(buffer + bufPos, appContextNameMms, 5);
bufPos += 5;
/* result */
bufPos = BerEncoder_encodeTL(0xa2, 3, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x02, 1, buffer, bufPos);
buffer[bufPos++] = acseResult;
/* result source diagnostics */
bufPos = BerEncoder_encodeTL(0xa3, 5, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0xa1, 3, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x02, 1, buffer, bufPos);
buffer[bufPos++] = 0;
/* user information */
bufPos = BerEncoder_encodeTL(0xbe, userInfoLength, buffer, bufPos);
/* association data */
bufPos = BerEncoder_encodeTL(0x28, assocDataLength, buffer, bufPos);
/* indirect-reference */
bufPos = BerEncoder_encodeTL(0x02, nextRefLength, buffer, bufPos);
bufPos = BerEncoder_encodeUInt32(self->nextReference, buffer, bufPos);
/* single ASN1 type */
bufPos = BerEncoder_encodeTL(0xa0, payloadLength, buffer, bufPos);
writeBuffer->partLength = bufPos;
writeBuffer->length = bufPos + payloadLength;
writeBuffer->nextPart = payload;
}
void
AcseConnection_createAssociateRequestMessage(AcseConnection* self,
IsoConnectionParameters isoParameters,
BufferChain writeBuffer,
BufferChain payload,
AcseAuthenticationParameter authParameter)
{
(void)self;
assert(self != NULL);
assert(writeBuffer != NULL);
assert(payload != NULL);
int payloadLength = payload->length;
int authValueStringLength = 0;
int passwordLength = 0;
int contentLength = 0;
/* application context name */
contentLength += 9;
int calledAEQualifierLength = 0;
if (isoParameters->remoteApTitleLen > 0)
{
/* called AP title */
contentLength += (4 + isoParameters->remoteApTitleLen);
calledAEQualifierLength = BerEncoder_UInt32determineEncodedSize(
isoParameters->remoteAEQualifier);
/* called AE qualifier */
contentLength += (4 + calledAEQualifierLength);
}
int callingAEQualifierLength = 0;
if (isoParameters->localApTitleLen > 0)
{
/* calling AP title */
contentLength += (4 + isoParameters->localApTitleLen);
callingAEQualifierLength = BerEncoder_UInt32determineEncodedSize(
isoParameters->localAEQualifier);
/* calling AE qualifier */
contentLength += (4 + callingAEQualifierLength);
}
if (authParameter != NULL)
{
/* sender ACSE requirements */
contentLength += 4;
/* mechanism name */
contentLength += 5;
/* authentication value */
if (authParameter->mechanism == ACSE_AUTH_PASSWORD)
{
contentLength += 2;
passwordLength = authParameter->value.password.passwordLength;
authValueStringLength = BerEncoder_determineLengthSize(
passwordLength);
contentLength += passwordLength + authValueStringLength;
int authValueLength = BerEncoder_determineLengthSize(
passwordLength + authValueStringLength + 1);
contentLength += authValueLength;
}
else
{
contentLength += 2;
}
}
/* user information */
int userInfoLength = 0;
/* single ASN1 type tag */
userInfoLength += payloadLength;
userInfoLength += 1;
userInfoLength += BerEncoder_determineLengthSize(payloadLength);
/* indirect reference */
userInfoLength += 1;
userInfoLength += 2;
/* association data */
int assocDataLength = userInfoLength;
userInfoLength += BerEncoder_determineLengthSize(assocDataLength);
userInfoLength += 1;
/* user information */
int userInfoLen = userInfoLength;
userInfoLength += BerEncoder_determineLengthSize(userInfoLength);
userInfoLength += 1;
contentLength += userInfoLength;
uint8_t* buffer = writeBuffer->buffer;
int bufPos = 0;
bufPos = BerEncoder_encodeTL(0x60, contentLength, buffer, bufPos);
/* application context name */
bufPos = BerEncoder_encodeTL(0xa1, 7, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x06, 5, buffer, bufPos);
memcpy(buffer + bufPos, appContextNameMms, 5);
bufPos += 5;
if (isoParameters->remoteApTitleLen > 0)
{
/* called AP title */
bufPos = BerEncoder_encodeTL(0xa2, isoParameters->remoteApTitleLen + 2,
buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x06, isoParameters->remoteApTitleLen,
buffer, bufPos);
memcpy(buffer + bufPos, isoParameters->remoteApTitle,
isoParameters->remoteApTitleLen);
bufPos += isoParameters->remoteApTitleLen;
/* called AE qualifier */
bufPos = BerEncoder_encodeTL(0xa3, calledAEQualifierLength + 2, buffer,
bufPos);
bufPos = BerEncoder_encodeTL(0x02, calledAEQualifierLength, buffer,
bufPos);
bufPos = BerEncoder_encodeUInt32(isoParameters->remoteAEQualifier,
buffer, bufPos);
}
if (isoParameters->localApTitleLen > 0)
{
/* calling AP title */
bufPos = BerEncoder_encodeTL(0xa6, isoParameters->localApTitleLen + 2,
buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x06, isoParameters->localApTitleLen,
buffer, bufPos);
memcpy(buffer + bufPos, isoParameters->localApTitle,
isoParameters->localApTitleLen);
bufPos += isoParameters->localApTitleLen;
/* calling AE qualifier */
bufPos = BerEncoder_encodeTL(0xa7, callingAEQualifierLength + 2, buffer,
bufPos);
bufPos = BerEncoder_encodeTL(0x02, callingAEQualifierLength, buffer,
bufPos);
bufPos = BerEncoder_encodeUInt32(isoParameters->localAEQualifier,
buffer, bufPos);
}
if (authParameter != NULL)
{
/* sender requirements */
bufPos = BerEncoder_encodeTL(0x8a, 2, buffer, bufPos);
buffer[bufPos++] = 0x04;
if (authParameter->mechanism == ACSE_AUTH_PASSWORD)
{
buffer[bufPos++] = requirements_authentication[0];
bufPos = BerEncoder_encodeTL(0x8b, 3, buffer, bufPos);
memcpy(buffer + bufPos, auth_mech_password_oid, 3);
bufPos += 3;
/* authentication value */
bufPos = BerEncoder_encodeTL(0xac,
authValueStringLength + passwordLength + 1, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x80, passwordLength, buffer, bufPos);
memcpy(buffer + bufPos, authParameter->value.password.octetString,
passwordLength);
bufPos += passwordLength;
}
else
{ /* AUTH_NONE */
buffer[bufPos++] = 0;
}
}
/* user information */
bufPos = BerEncoder_encodeTL(0xbe, userInfoLen, buffer, bufPos);
/* association data */
bufPos = BerEncoder_encodeTL(0x28, assocDataLength, buffer, bufPos);
/* indirect-reference */
bufPos = BerEncoder_encodeTL(0x02, 1, buffer, bufPos);
buffer[bufPos++] = 3;
/* single ASN1 type */
bufPos = BerEncoder_encodeTL(0xa0, payloadLength, buffer, bufPos);
writeBuffer->partLength = bufPos;
writeBuffer->length = bufPos + payload->length;
writeBuffer->nextPart = payload;
}
/**
* \param isProvider specifies abort source (false = user/client; true = provider/server)
*/
void
AcseConnection_createAbortMessage(AcseConnection* self, BufferChain writeBuffer, bool isProvider)
{
(void)self;
uint8_t* buffer = writeBuffer->buffer;
buffer[0] = 0x64; /* [APPLICATION 4] */
buffer[1] = 3;
buffer[2] = 0x80;
buffer[3] = 1;
if (isProvider)
buffer[4] = 1;
else
buffer[4] = 0;
writeBuffer->partLength = 5;
writeBuffer->length = 5;
writeBuffer->nextPart = NULL;
}
void
AcseConnection_createReleaseRequestMessage(AcseConnection* self, BufferChain writeBuffer)
{
(void)self;
uint8_t* buffer = writeBuffer->buffer;
buffer[0] = 0x62;
buffer[1] = 3;
buffer[2] = 0x80;
buffer[3] = 1;
buffer[4] = 0;
writeBuffer->partLength = 5;
writeBuffer->length = 5;
writeBuffer->nextPart = NULL;
}
void
AcseConnection_createReleaseResponseMessage(AcseConnection* self, BufferChain writeBuffer)
{
(void)self;
uint8_t* buffer = writeBuffer->buffer;
buffer[0] = 0x63;
buffer[1] = 0;
writeBuffer->partLength = 2;
writeBuffer->length = 2;
writeBuffer->nextPart = NULL;
}