/* * 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 . * * 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; }