All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmuc.jdlms.internal.lnassociation.InitialmessageProcessor Maven / Gradle / Ivy

Go to download

jDLMS is a library implementing the DLMS/COSEM (IEC 62056) communication standard.

There is a newer version: 1.8.0
Show newest version
package org.openmuc.jdlms.internal.lnassociation;

import java.io.IOException;
import java.util.Arrays;
import java.util.Map;

import org.openmuc.jdlms.AuthenticationMechanism;
import org.openmuc.jdlms.LogicalDevice;
import org.openmuc.jdlms.SecuritySuite;
import org.openmuc.jdlms.SecuritySuite.EncryptionMechanism;
import org.openmuc.jdlms.internal.APdu;
import org.openmuc.jdlms.internal.AssociateSourceDiagnostic.AcseServiceUser;
import org.openmuc.jdlms.internal.ContextId;
import org.openmuc.jdlms.internal.DataDirectory.DlmsLogicalDevice;
import org.openmuc.jdlms.internal.ObjectIdentifier;
import org.openmuc.jdlms.internal.ServerConnectionData;
import org.openmuc.jdlms.internal.asn1.cosem.COSEMpdu;
import org.openmuc.jdlms.internal.asn1.cosem.Conformance;
import org.openmuc.jdlms.internal.asn1.cosem.InitiateRequest;
import org.openmuc.jdlms.internal.asn1.iso.acse.AARQApdu;
import org.openmuc.jdlms.internal.asn1.iso.acse.ACSEApdu;
import org.openmuc.jdlms.internal.asn1.iso.acse.MechanismName;
import org.openmuc.jdlms.internal.security.HlsProcessorGmac;
import org.openmuc.jdlms.internal.security.HlsSecretProcessor;
import org.openmuc.jdlms.internal.security.RandomSequenceGenerator;

public class InitialmessageProcessor {

    private final ServerConnectionData connectionData;
    private final DlmsLogicalDevice dlmsLogicalDevice;
    private final LogicalDevice logicalDevice;

    public InitialmessageProcessor(ServerConnectionData connectionData, DlmsLogicalDevice dlmsLogicalDevice) {
        this.connectionData = connectionData;
        this.dlmsLogicalDevice = dlmsLogicalDevice;
        this.logicalDevice = dlmsLogicalDevice.getLogicalDevice();

        Map restrictions = dlmsLogicalDevice.getLogicalDevice().getRestrictions();
        this.connectionData.securitySuite = restrictions.get(this.connectionData.clientId);
    }

    public APdu processInitialMessage(byte[] messageData) throws IOException, GenericAssociationException {
        InitialResponseBuilder initialResponseBuilder = new InitialResponseBuilder(conformance());

        if (dlmsLogicalDevice == null) {
            throw new AssociatRequestException(AcseServiceUser.NO_REASON_GIVEN);
        }

        LogicalDevice logicalDevice = dlmsLogicalDevice.getLogicalDevice();
        Map restrictions = logicalDevice.getRestrictions();
        if (restrictions.isEmpty()) {
            this.connectionData.authenticated = true;
            this.connectionData.securitySuite = SecuritySuite.builder().build();
            return initialResponseBuilder.setContextId(ContextId.LOGICAL_NAME_REFERENCING_NO_CIPHERING).build();
        }

        SecuritySuite securitySuite = this.connectionData.securitySuite;
        if (securitySuite == null) {
            // unknown client ID
            throw new AssociatRequestException(AcseServiceUser.NO_REASON_GIVEN);
        }

        APdu aPdu = decodeAPdu(messageData, securitySuite);

        if (aPdu.getCosemPdu() == null) {
            throw new AssociatRequestException(AcseServiceUser.NO_REASON_GIVEN);
        }

        COSEMpdu cosemPdu = aPdu.getCosemPdu();

        if (cosemPdu.getChoiceIndex() != COSEMpdu.Choices.INITIATEREQUEST) {
            throw new AssociatRequestException(AcseServiceUser.NO_REASON_GIVEN);
        }
        InitiateRequest initiateRequest = cosemPdu.initiateRequest;
        this.connectionData.clientMaxReceivePduSize = initiateRequest.client_max_receive_pdu_size.getValue() & 0xFFFF;

        ACSEApdu acseAPdu = aPdu.getAcseAPdu();

        if (acseAPdu == null) {
            throw new AssociatRequestException(AcseServiceUser.NO_REASON_GIVEN);
        }
        AARQApdu aarq = acseAPdu.aarq;

        return tryToAuthenticate(initialResponseBuilder, aarq, securitySuite);
    }

    private APdu decodeAPdu(byte[] messageData, SecuritySuite sec) throws IOException {
        APdu aPdu;

        if (sec.getEncryptionMechanism() != EncryptionMechanism.NONE) {
            if (this.connectionData.clientSystemTitle == null) {
                this.connectionData.clientSystemTitle = systemTitle();
            }

            aPdu = APdu.decode(messageData, this.connectionData.clientSystemTitle, connectionData.frameCounter, sec,
                    null);
        }
        else {
            aPdu = APdu.decode(messageData, null);
        }
        return aPdu;
    }

    private void checkContextId(ContextId contextId) throws AssociatRequestException {
        boolean snConnection = contextId == ContextId.SHORT_NAME_REFERENCING_NO_CIPHERING
                || contextId == ContextId.SHORT_NAME_REFERENCING_WITH_CIPHERING;
        if (snConnection) {
            throw new AssociatRequestException(AcseServiceUser.APPLICATION_CONTEXT_NAME_NOT_SUPPORTED);
        }
    }

    private APdu tryToAuthenticate(InitialResponseBuilder initialResponseBuilder, AARQApdu aarq,
            SecuritySuite securitySuite) throws AssociatRequestException, IOException {
        ContextId contextId = ObjectIdentifier.applicationContextIdFor(aarq.applicationContextName);

        checkContextId(contextId);

        MechanismName mechanismName = aarq.mechanismName;

        if (mechanismName == null && securitySuite.getAuthenticationMechanism() != AuthenticationMechanism.NONE) {
            throw new AssociatRequestException(AcseServiceUser.AUTHENTICATION_MECHANISM_NAME_REQUIRED);
        }
        else if (mechanismName == null && securitySuite.getAuthenticationMechanism() == AuthenticationMechanism.NONE) {
            this.connectionData.authenticated = true;
            return initialResponseBuilder.build();
        }

        if (mechanismName == null) {
            throw new AssociatRequestException(AcseServiceUser.NO_REASON_GIVEN);
        }
        AuthenticationMechanism authenticationLevel = ObjectIdentifier.mechanismIdFor(mechanismName);

        if (authenticationLevel == AuthenticationMechanism.NONE) {
            throw new AssociatRequestException(AcseServiceUser.AUTHENTICATION_REQUIRED);
        }

        this.connectionData.clientToServerChallenge = aarq.callingAuthenticationValue.charstring.value;

        if (contextId == ContextId.LOGICAL_NAME_REFERENCING_WITH_CIPHERING) {
            // TODO
        }

        if (authenticationLevel == AuthenticationMechanism.NONE
                && securitySuite.getAuthenticationMechanism() != AuthenticationMechanism.NONE) {
            this.connectionData.authenticated = true;
            return initialResponseBuilder.setContextId(ContextId.LOGICAL_NAME_REFERENCING_NO_CIPHERING).build();
        }

        if (authenticationLevel != securitySuite.getAuthenticationMechanism()) {
            throw new AssociatRequestException(AcseServiceUser.AUTHENTICATION_FAILURE);
        }

        switch (authenticationLevel) {
        case LOW:
            return processLowAuthentciationRequest(aarq, securitySuite.getPassword());

        case HLS5_GMAC:
            return processHls5GmacAuthentciationRequest(aarq, securitySuite);
        default:
            throw new AssociatRequestException(AcseServiceUser.APPLICATION_CONTEXT_NAME_NOT_SUPPORTED);
        }
    }

    private APdu processHls5GmacAuthentciationRequest(AARQApdu aarq, SecuritySuite sec)
            throws IOException, AssociatRequestException {
        byte[] clientToServerChallenge = this.connectionData.clientToServerChallenge;

        this.connectionData.clientSystemTitle = aarq.callingAPTitle.apTitleForm2.value;
        byte[] clientSystemTitle = this.connectionData.clientSystemTitle;

        int challengeLength = clientToServerChallenge.length;

        checkChallangeLength(challengeLength);

        this.connectionData.frameCounter = 1;

        byte[] serverToClientChallenge = RandomSequenceGenerator.generate(challengeLength);

        HlsSecretProcessor hlSecretProcessor = new HlsProcessorGmac();

        this.connectionData.processedServerToClientChallenge = hlSecretProcessor.process(serverToClientChallenge,
                sec.getAuthenticationKey(), sec.getGlobalUnicastEncryptionKey(), clientSystemTitle,
                ++this.connectionData.frameCounter);

        return new InitialResponseBuilder(conformance()).setContextId(ContextId.LOGICAL_NAME_REFERENCING_NO_CIPHERING)
                .setAuthenticationValue(serverToClientChallenge)
                .setSystemTitle(systemTitle())
                .build();

    }

    private void checkChallangeLength(int challengeLength) throws AssociatRequestException {
        if (challengeLength < 8 || challengeLength > 64) {
            throw new AssociatRequestException(AcseServiceUser.AUTHENTICATION_FAILURE);
        }
    }

    private APdu processLowAuthentciationRequest(AARQApdu aarq, byte[] authenticationKey)
            throws AssociatRequestException {
        byte[] clientAuthenticaionValue = aarq.callingAuthenticationValue.charstring.value;

        if (Arrays.equals(clientAuthenticaionValue, authenticationKey)) {
            this.connectionData.authenticated = true;
            return new InitialResponseBuilder(conformance()).build();
        }

        throw new AssociatRequestException(AcseServiceUser.AUTHENTICATION_FAILURE);
    }

    private Conformance conformance() {
        return this.logicalDevice.getConformance();
    }

    private byte[] systemTitle() {
        return this.logicalDevice.getSystemTitle();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy