Skip to content

Commit

Permalink
Merge pull request #67 from CDOT-CV/Update/optional-signing
Browse files Browse the repository at this point in the history
Optional RSU/SDX TIM Signing
  • Loading branch information
drewjj authored May 7, 2024
2 parents d7b8786 + 782553f commit e05c1d8
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 74 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,8 @@ The ODE is capable of communicating with RSUs to:
- Deposit TIMs
- Delete TIMs

By default, the ODE will not sign TIMs that are delivered to RSUs. This can be changed by setting the value of the DATA_SIGNING_ENABLED_RSU environment variable found in the provided sample.env file. Additionally, signing of SDX-delivery TIMs can be configured by setting the value of the environment variable DATA_SIGNING_ENABLED_SDW found in sample.env.

The following SNMP protocols are supported for communication with RSUs:
- DSRC 4.1 (defined in 'Dedicated Short-Range Communications Roadside Unit Specifications')
- NTCIP1218 (defined in 'National Transportation Communications for ITS Protocol')
Expand Down
2 changes: 2 additions & 0 deletions docker-compose-confluent-cloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ services:
KAFKA_TYPE: ${KAFKA_TYPE}
CONFLUENT_KEY: ${CONFLUENT_KEY}
CONFLUENT_SECRET: ${CONFLUENT_SECRET}
DATA_SIGNING_ENABLED_RSU: ${DATA_SIGNING_ENABLED_RSU}
DATA_SIGNING_ENABLED_SDW: ${DATA_SIGNING_ENABLED_SDW}
DEFAULT_SNMP_PROTOCOL: ${DEFAULT_SNMP_PROTOCOL}
# Commented out, will use SDW depositor module by default
#ODE_DEPOSIT_SDW_MESSAGES_OVER_WEBSOCKET: ${ODE_DEPOSIT_SDW_MESSAGES_OVER_WEBSOCKET}
Expand Down
2 changes: 2 additions & 0 deletions docker-compose-ppm-nsv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ services:
ODE_SECURITY_SVCS_SIGNATURE_URI: ${ODE_SECURITY_SVCS_SIGNATURE_URI}
ODE_RSU_USERNAME: ${ODE_RSU_USERNAME}
ODE_RSU_PASSWORD: ${ODE_RSU_PASSWORD}
DATA_SIGNING_ENABLED_RSU: ${DATA_SIGNING_ENABLED_RSU}
DATA_SIGNING_ENABLED_SDW: ${DATA_SIGNING_ENABLED_SDW}
DEFAULT_SNMP_PROTOCOL: ${DEFAULT_SNMP_PROTOCOL}
# Commented out, will use SDW depositor module by default
#ODE_DEPOSIT_SDW_MESSAGES_OVER_WEBSOCKET: ${ODE_DEPOSIT_SDW_MESSAGES_OVER_WEBSOCKET}
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ services:
ODE_SECURITY_SVCS_SIGNATURE_URI: ${ODE_SECURITY_SVCS_SIGNATURE_URI}
ODE_RSU_USERNAME: ${ODE_RSU_USERNAME}
ODE_RSU_PASSWORD: ${ODE_RSU_PASSWORD}
DATA_SIGNING_ENABLED_RSU: ${DATA_SIGNING_ENABLED_RSU}
DATA_SIGNING_ENABLED_SDW: ${DATA_SIGNING_ENABLED_SDW}
DEFAULT_SNMP_PROTOCOL: ${DEFAULT_SNMP_PROTOCOL}
# Commented out, will use SDW depositor module by default
#ODE_DEPOSIT_SDW_MESSAGES_OVER_WEBSOCKET: ${ODE_DEPOSIT_SDW_MESSAGES_OVER_WEBSOCKET}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,6 @@ public void setVersion(String version) {
this.version = version;
}

public boolean dataSigningEnabled() {
return getSecuritySvcsSignatureUri() != null && !StringUtils.isEmptyOrWhitespace(getSecuritySvcsSignatureUri())
&& !getSecuritySvcsSignatureUri().startsWith("UNSECURE");
}

public List<Path> getUploadLocations() {
return this.uploadLocations;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public Asn1EncodedDataRouterException(String string) {
private OdeProperties odeProperties;
private MessageProducer<String, String> stringMsgProducer;
private Asn1CommandManager asn1CommandManager;
private boolean dataSigningEnabledRSU;
private boolean dataSigningEnabledSDW;

public Asn1EncodedDataRouter(OdeProperties odeProperties) {
super();
Expand All @@ -72,6 +74,12 @@ public Asn1EncodedDataRouter(OdeProperties odeProperties) {

this.asn1CommandManager = new Asn1CommandManager(odeProperties);

this.dataSigningEnabledRSU = System.getenv("DATA_SIGNING_ENABLED_RSU") != null && !System.getenv("DATA_SIGNING_ENABLED_RSU").isEmpty()
? Boolean.parseBoolean(System.getenv("DATA_SIGNING_ENABLED_RSU"))
: false;
this.dataSigningEnabledSDW = System.getenv("DATA_SIGNING_ENABLED_SDW") != null && !System.getenv("DATA_SIGNING_ENABLED_SDW").isEmpty()
? Boolean.parseBoolean(System.getenv("DATA_SIGNING_ENABLED_SDW"))
: true;
}

@Override
Expand All @@ -90,26 +98,28 @@ public Object process(String consumedData) {

if (metadata.has(TimTransmogrifier.REQUEST_STRING)) {
JSONObject request = metadata.getJSONObject(TimTransmogrifier.REQUEST_STRING);

if (request.has(TimTransmogrifier.RSUS_STRING)) {
JSONObject rsusIn = (JSONObject) request.get(TimTransmogrifier.RSUS_STRING);
if (rsusIn.has(TimTransmogrifier.RSUS_STRING)) {
Object rsu = rsusIn.get(TimTransmogrifier.RSUS_STRING);
JSONArray rsusOut = new JSONArray();
if (rsu instanceof JSONArray) {
logger.debug("Multiple RSUs exist in the request: {}", request);
JSONArray rsusInArray = (JSONArray) rsu;
for (int i = 0; i < rsusInArray.length(); i++) {
rsusOut.put(rsusInArray.get(i));
Object rsus = request.get(TimTransmogrifier.RSUS_STRING);
if (rsus instanceof JSONObject) {
JSONObject rsusIn = (JSONObject) request.get(TimTransmogrifier.RSUS_STRING);
if (rsusIn.has(TimTransmogrifier.RSUS_STRING)) {
Object rsu = rsusIn.get(TimTransmogrifier.RSUS_STRING);
JSONArray rsusOut = new JSONArray();
if (rsu instanceof JSONArray) {
logger.debug("Multiple RSUs exist in the request: {}", request);
JSONArray rsusInArray = (JSONArray) rsu;
for (int i = 0; i < rsusInArray.length(); i++) {
rsusOut.put(rsusInArray.get(i));
}
request.put(TimTransmogrifier.RSUS_STRING, rsusOut);
} else if (rsu instanceof JSONObject) {
logger.debug("Single RSU exists in the request: {}", request);
rsusOut.put(rsu);
request.put(TimTransmogrifier.RSUS_STRING, rsusOut);
} else {
logger.debug("No RSUs exist in the request: {}", request);
request.remove(TimTransmogrifier.RSUS_STRING);
}
request.put(TimTransmogrifier.RSUS_STRING, rsusOut);
} else if (rsu instanceof JSONObject) {
logger.debug("Single RSU exists in the request: {}", request);
rsusOut.put(rsu);
request.put(TimTransmogrifier.RSUS_STRING, rsusOut);
} else {
logger.debug("No RSUs exist in the request: {}", request);
request.remove(TimTransmogrifier.RSUS_STRING);
}
}
}
Expand Down Expand Up @@ -177,54 +187,9 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) {
logger.debug("Encoded message - phase 1: {}", hexEncodedTim);
//use Asnc1 library to decode the encoded tim returned from ASNC1; another class two blockers: decode the tim and decode the message-sign

if (odeProperties.dataSigningEnabled()) {
logger.debug("Sending message for signature! ");
String base64EncodedTim = CodecUtils.toBase64(
CodecUtils.fromHex(hexEncodedTim));
JSONObject matadataObjs = consumedObj.getJSONObject(AppContext.METADATA_STRING);
// get max duration time and convert from minutes to milliseconds (unsigned
// integer valid 0 to 2^32-1 in units of
// milliseconds.) from metadata
int maxDurationTime = Integer.valueOf(matadataObjs.get("maxDurationTime").toString()) * 60 * 1000;
String timpacketID = matadataObjs.getString("odePacketID");
String timStartDateTime = matadataObjs.getString("odeTimStartDateTime");
String signedResponse = asn1CommandManager.sendForSignature(base64EncodedTim,maxDurationTime);
try {
hexEncodedTim = CodecUtils.toHex(
CodecUtils.fromBase64(
JsonUtils.toJSONObject(JsonUtils.toJSONObject(signedResponse).getString("result")).getString("message-signed")));

JSONObject TimWithExpiration = new JSONObject();
TimWithExpiration.put("packetID", timpacketID);
TimWithExpiration.put("startDateTime", timStartDateTime);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
try {
JSONObject jsonResult = JsonUtils
.toJSONObject((JsonUtils.toJSONObject(signedResponse).getString("result")));
// messageExpiry uses unit of seconds
long messageExpiry = Long.valueOf(jsonResult.getString("message-expiry"));
TimWithExpiration.put("expirationDate", dateFormat.format(new Date(messageExpiry * 1000)));
} catch (Exception e) {
logger.error("Unable to get expiration date from signed messages response {}", e);
TimWithExpiration.put("expirationDate", "null");
}

try {
Date parsedtimTimeStamp = dateFormat.parse(timStartDateTime);
Date requiredExpirationDate = new Date();
requiredExpirationDate.setTime(parsedtimTimeStamp.getTime() + maxDurationTime);
TimWithExpiration.put("requiredExpirationDate", dateFormat.format(requiredExpirationDate));
} catch (Exception e) {
logger.error("Unable to parse requiredExpirationDate {}", e);
TimWithExpiration.put("requiredExpirationDate", "null");
}
//publish to Tim expiration kafka
stringMsgProducer.send(odeProperties.getKafkaTopicSignedOdeTimJsonExpiration(), null,
TimWithExpiration.toString());

} catch (JsonUtilsException e1) {
logger.error("Unable to parse signed message response {}", e1);
}
// Case 1: SNMP-deposit
if (dataSigningEnabledRSU && request.getRsus() != null) {
hexEncodedTim = signTIM(hexEncodedTim, consumedObj);
}
else {
// if header is present, strip it
Expand All @@ -246,6 +211,13 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) {
asn1CommandManager.sendToRsus(request, hexEncodedTim);
}

hexEncodedTim = mfObj.getString(BYTES);

// Case 2: SDX-deposit
if (dataSigningEnabledSDW && request.getSdw() != null) {
hexEncodedTim = signTIM(hexEncodedTim, consumedObj);
}

if (request.getSdw() != null) {
// Case 2 only

Expand All @@ -258,8 +230,7 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) {
} else {
//We have encoded ASD. It could be either UNSECURED or secured.
logger.debug("securitySvcsSignatureUri = {}", odeProperties.getSecuritySvcsSignatureUri());

if (odeProperties.dataSigningEnabled()) {
if (dataSigningEnabledSDW && request.getSdw() != null) {
logger.debug("Signed message received. Depositing it to SDW.");
// We have a ASD with signed MessageFrame
// Case 3
Expand Down Expand Up @@ -353,6 +324,59 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum
logger.info("TIM deposit response {}", responseList);
}

public String signTIM(String encodedTIM, JSONObject consumedObj) {
logger.debug("Sending message for signature! ");
String base64EncodedTim = CodecUtils.toBase64(
CodecUtils.fromHex(encodedTIM));
JSONObject metadataObjs = consumedObj.getJSONObject(AppContext.METADATA_STRING);
// get max duration time and convert from minutes to milliseconds (unsigned
// integer valid 0 to 2^32-1 in units of
// milliseconds.) from metadata
int maxDurationTime = Integer.valueOf(metadataObjs.get("maxDurationTime").toString()) * 60 * 1000;
String timpacketID = metadataObjs.getString("odePacketID");
String timStartDateTime = metadataObjs.getString("odeTimStartDateTime");
String signedResponse = asn1CommandManager.sendForSignature(base64EncodedTim,maxDurationTime);
try {
String hexEncodedTim = CodecUtils.toHex(
CodecUtils.fromBase64(
JsonUtils.toJSONObject(JsonUtils.toJSONObject(signedResponse).getString("result")).getString("message-signed")));

JSONObject timWithExpiration = new JSONObject();
timWithExpiration.put("packetID", timpacketID);
timWithExpiration.put("startDateTime", timStartDateTime);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
try {
JSONObject jsonResult = JsonUtils
.toJSONObject((JsonUtils.toJSONObject(signedResponse).getString("result")));
// messageExpiry uses unit of seconds
long messageExpiry = Long.valueOf(jsonResult.getString("message-expiry"));
timWithExpiration.put("expirationDate", dateFormat.format(new Date(messageExpiry * 1000)));
} catch (Exception e) {
logger.error("Unable to get expiration date from signed messages response {}", e);
timWithExpiration.put("expirationDate", "null");
}

try {
Date parsedtimTimeStamp = dateFormat.parse(timStartDateTime);
Date requiredExpirationDate = new Date();
requiredExpirationDate.setTime(parsedtimTimeStamp.getTime() + maxDurationTime);
timWithExpiration.put("requiredExpirationDate", dateFormat.format(requiredExpirationDate));
} catch (Exception e) {
logger.error("Unable to parse requiredExpirationDate {}", e);
timWithExpiration.put("requiredExpirationDate", "null");
}
//publish to Tim expiration kafka
stringMsgProducer.send(odeProperties.getKafkaTopicSignedOdeTimJsonExpiration(), null,
timWithExpiration.toString());

return hexEncodedTim;

} catch (JsonUtilsException e1) {
logger.error("Unable to parse signed message response {}", e1);
}
return encodedTIM;
}

/**
* Checks if header is present in encoded message
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public class TimDepositController {
private MessageProducer<String, String> stringMsgProducer;
private MessageProducer<String, OdeObject> timProducer;

private boolean dataSigningEnabledSDW;

public static class TimDepositControllerException extends Exception {

private static final long serialVersionUID = 1L;
Expand All @@ -99,6 +101,10 @@ public TimDepositController(OdeProperties odeProperties) {
this.timProducer = new MessageProducer<>(odeProperties.getKafkaBrokers(), odeProperties.getKafkaProducerType(),
null, OdeTimSerializer.class.getName(), odeProperties.getKafkaTopicsDisabledSet());

this.dataSigningEnabledSDW = System.getenv("DATA_SIGNING_ENABLED_SDW") != null && !System.getenv("DATA_SIGNING_ENABLED_SDW").isEmpty()
? Boolean.parseBoolean(System.getenv("DATA_SIGNING_ENABLED_SDW"))
: true;

}

/**
Expand Down Expand Up @@ -226,7 +232,7 @@ public synchronized ResponseEntity<String> depositTim(String jsonString, Request
logger.debug("securitySvcsSignatureUri = {}", odeProperties.getSecuritySvcsSignatureUri());
String xmlMsg;
DdsAdvisorySituationData asd = null;
if (!odeProperties.dataSigningEnabled()) {
if (!this.dataSigningEnabledSDW) {
// We need to send data UNSECURED, so we should try to build the ASD as well as
// MessageFrame
asd = TimTransmogrifier.buildASD(odeTID.getRequest());
Expand Down
6 changes: 6 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ DOCKER_SHARED_VOLUME_WINDOWS=
ODE_RSU_USERNAME=
ODE_RSU_PASSWORD=

# Values to sign TIM messages delivered to RSUs/SDX (accepted values are true or false)
# If not set, DATA_SIGNING_ENABLED_RSU will default to false
# If not set, DATA_SIGNING_ENABLED_SDW will default to true
DATA_SIGNING_ENABLED_RSU=
DATA_SIGNING_ENABLED_SDW=

# Default SNMP protocol version when not specified in the request
# Current supported values are FOURDOT1 and NTCIP1218
# If no protocol is specified the FOURDOT1 protocol will be used
Expand Down

0 comments on commit e05c1d8

Please sign in to comment.