org.openmuc.jdlms.internal.lnassociation.InitialmessageProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jdlms Show documentation
Show all versions of jdlms Show documentation
jDLMS is a library implementing the DLMS/COSEM (IEC 62056) communication standard.
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();
}
}