Skip to content

Commit

Permalink
FDP-2684: introduced device, moved secret from PSK to device (OSGP#60)
Browse files Browse the repository at this point in the history
FDP-2684: introduced device, moved secret from PSK to device

Signed-off-by: Levi Hoogenberg <[email protected]>
  • Loading branch information
levi-h authored Oct 24, 2024
1 parent 60080eb commit 604e3e8
Show file tree
Hide file tree
Showing 24 changed files with 253 additions and 69 deletions.
6 changes: 4 additions & 2 deletions application/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ dependencies {
implementation("org.springframework.security:spring-security-core")
implementation("org.springframework.kafka:spring-kafka")

implementation(project(":components:psk"))
implementation(project(":components:device"))
implementation(project(":components:firmware"))
implementation(project(":components:psk"))

implementation(kotlin("reflect"))
implementation(libs.logging)
Expand Down Expand Up @@ -72,8 +73,9 @@ testing {
useJUnitJupiter()
dependencies {
implementation(project())
implementation(project(":components:psk"))
implementation(project(":components:device"))
implementation(project(":components:firmware"))
implementation(project(":components:psk"))
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation(libs.kafkaAvro)
implementation(libs.avro)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import org.awaitility.Awaitility
import org.gxf.crestdeviceservice.IntegrationTestHelper.getFileContentAsString
import org.gxf.crestdeviceservice.command.entity.Command
import org.gxf.crestdeviceservice.command.repository.CommandRepository
import org.gxf.crestdeviceservice.device.entity.Device
import org.gxf.crestdeviceservice.device.repository.DeviceRepository
import org.gxf.crestdeviceservice.psk.entity.PreSharedKey
import org.gxf.crestdeviceservice.psk.entity.PreSharedKeyStatus
import org.gxf.crestdeviceservice.psk.repository.PskRepository
Expand Down Expand Up @@ -45,6 +47,8 @@ class CoapMessageHandlingTest {

@Autowired private lateinit var restTemplate: TestRestTemplate

@Autowired private lateinit var deviceRepository: DeviceRepository

@Autowired private lateinit var pskRepository: PskRepository

@Autowired private lateinit var commandRepository: CommandRepository
Expand All @@ -55,20 +59,20 @@ class CoapMessageHandlingTest {

@BeforeEach
fun setup() {
pskRepository.save(
PreSharedKey(DEVICE_ID, 0, Instant.MIN, PRE_SHARED_KEY_FIRST, SECRET, PreSharedKeyStatus.ACTIVE))
deviceRepository.save(Device(DEVICE_ID, SECRET))
pskRepository.save(PreSharedKey(DEVICE_ID, 0, Instant.MIN, PRE_SHARED_KEY_FIRST, PreSharedKeyStatus.ACTIVE))
}

@AfterEach
fun cleanup() {
deviceRepository.deleteAll()
pskRepository.deleteAll()
commandRepository.deleteAll()
}

@Test
fun shouldReturnADownLinkContainingPskCommands() {
pskRepository.save(
PreSharedKey(DEVICE_ID, 1, Instant.now(), PRE_SHARED_KEY_NEW, SECRET, PreSharedKeyStatus.READY))
pskRepository.save(PreSharedKey(DEVICE_ID, 1, Instant.now(), PRE_SHARED_KEY_NEW, PreSharedKeyStatus.READY))
commandRepository.save(
Command(
UUID.randomUUID(),
Expand Down Expand Up @@ -99,8 +103,7 @@ class CoapMessageHandlingTest {
@Test
fun shouldChangeActiveKey() {
// pending psk, waiting for URC in next message from device
pskRepository.save(
PreSharedKey(DEVICE_ID, 1, Instant.now(), PRE_SHARED_KEY_NEW, SECRET, PreSharedKeyStatus.PENDING))
pskRepository.save(PreSharedKey(DEVICE_ID, 1, Instant.now(), PRE_SHARED_KEY_NEW, PreSharedKeyStatus.PENDING))
commandRepository.save(
Command(
UUID.randomUUID(),
Expand Down Expand Up @@ -136,8 +139,7 @@ class CoapMessageHandlingTest {
@Test
fun shouldSetPendingKeyAsInvalidWhenFailureURCReceived() {
// pending psk, waiting for URC in next message from device
pskRepository.save(
PreSharedKey(DEVICE_ID, 1, Instant.MIN, PRE_SHARED_KEY_NEW, SECRET, PreSharedKeyStatus.PENDING))
pskRepository.save(PreSharedKey(DEVICE_ID, 1, Instant.MIN, PRE_SHARED_KEY_NEW, PreSharedKeyStatus.PENDING))
commandRepository.save(
Command(
UUID.randomUUID(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package org.gxf.crestdeviceservice

import java.time.Instant
import org.assertj.core.api.Assertions.assertThat
import org.gxf.crestdeviceservice.device.entity.Device
import org.gxf.crestdeviceservice.device.repository.DeviceRepository
import org.gxf.crestdeviceservice.psk.entity.PreSharedKey
import org.gxf.crestdeviceservice.psk.entity.PreSharedKeyStatus
import org.gxf.crestdeviceservice.psk.repository.PskRepository
Expand All @@ -25,7 +27,6 @@ import org.springframework.kafka.test.context.EmbeddedKafka
topics = ["\${kafka.producers.device-message.topic}"],
)
class DeviceCredentialsRetrievalTest {

companion object {
private const val IDENTITY = "1234"
private const val PRE_SHARED_KEY = "1234567890123456"
Expand All @@ -34,23 +35,25 @@ class DeviceCredentialsRetrievalTest {

@Autowired private lateinit var restTemplate: TestRestTemplate

@Autowired private lateinit var deviceRepository: DeviceRepository
@Autowired private lateinit var pskRepository: PskRepository

@BeforeEach
fun setup() {
pskRepository.save(PreSharedKey(IDENTITY, 0, Instant.MIN, PRE_SHARED_KEY, SECRET, PreSharedKeyStatus.ACTIVE))
deviceRepository.save(Device(IDENTITY, SECRET))
pskRepository.save(PreSharedKey(IDENTITY, 0, Instant.MIN, PRE_SHARED_KEY, PreSharedKeyStatus.ACTIVE))
}

@AfterEach
fun cleanup() {
deviceRepository.deleteAll()
pskRepository.deleteAll()
}

@Test
fun shouldReturnTheLatestPskWhenThereAreMoreFoundForIdentity() {
// create second PSK for identity this one should be returned
pskRepository.save(
PreSharedKey(IDENTITY, 1, Instant.MIN, "0000111122223333", SECRET, PreSharedKeyStatus.ACTIVE))
pskRepository.save(PreSharedKey(IDENTITY, 1, Instant.MIN, "0000111122223333", PreSharedKeyStatus.ACTIVE))

val headers = HttpHeaders().apply { add("x-device-identity", IDENTITY) }
val result = restTemplate.exchange("/psk", HttpMethod.GET, HttpEntity<Unit>(headers), String::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import java.util.UUID
import org.gxf.crestdeviceservice.command.entity.Command
import org.gxf.crestdeviceservice.command.entity.Command.CommandStatus
import org.gxf.crestdeviceservice.command.service.CommandService
import org.gxf.crestdeviceservice.device.service.DeviceService
import org.gxf.crestdeviceservice.psk.service.PskDecryptionService
import org.gxf.crestdeviceservice.psk.service.PskService
import org.springframework.kafka.annotation.KafkaListener
import org.springframework.stereotype.Service

@Service
class IncomingDeviceCredentialsConsumer(
private val deviceService: DeviceService,
private val pskService: PskService,
private val pskDecryptionService: PskDecryptionService,
private val commandService: CommandService
) {

private val logger = KotlinLogging.logger {}

@KafkaListener(id = "pre-shared-key", idIsGroup = false, topics = ["\${kafka.consumers.pre-shared-key.topic}"])
Expand All @@ -31,24 +32,21 @@ class IncomingDeviceCredentialsConsumer(
val deviceId = deviceCredentials.imei

try {
setInitialKey(deviceCredentials, deviceId)
val decryptedPsk = pskDecryptionService.decryptSecret(deviceCredentials.psk, deviceCredentials.keyRef)
val decryptedSecret = pskDecryptionService.decryptSecret(deviceCredentials.secret, deviceCredentials.keyRef)

deviceService.createDevice(deviceId, decryptedSecret)
pskService.setInitialKeyForDevice(deviceId, decryptedPsk)

if (pskService.changeInitialPsk()) {
pskService.generateNewReadyKeyForDevice(deviceId)
preparePskCommands(deviceId)
}
} catch (e: Exception) {
logger.error(e) { "Failed to set device credentials for $deviceId" }
} catch (exception: Exception) {
logger.error(exception) { "Failed to set device credentials for $deviceId" }
}
}

private fun setInitialKey(deviceCredentials: DeviceCredentials, deviceId: String) {
val decryptedPsk = pskDecryptionService.decryptSecret(deviceCredentials.psk, deviceCredentials.keyRef)
val decryptedSecret = pskDecryptionService.decryptSecret(deviceCredentials.secret, deviceCredentials.keyRef)

pskService.setInitialKeyForDevice(deviceId, decryptedPsk, decryptedSecret)
}

private fun preparePskCommands(deviceId: String) {
logger.info { "Prepare pending PSK and PSK_SET commands for PSK change for device $deviceId." }
val pskCommand =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import org.apache.commons.codec.digest.DigestUtils
import org.gxf.crestdeviceservice.command.entity.Command
import org.gxf.crestdeviceservice.command.service.CommandService
import org.gxf.crestdeviceservice.config.MessageProperties
import org.gxf.crestdeviceservice.device.entity.Device
import org.gxf.crestdeviceservice.device.service.DeviceService
import org.gxf.crestdeviceservice.model.Downlink
import org.gxf.crestdeviceservice.psk.entity.PreSharedKey
import org.gxf.crestdeviceservice.psk.exception.NoExistingPskException
Expand All @@ -18,6 +20,7 @@ import org.springframework.stereotype.Service

@Service
class DownlinkService(
private val deviceService: DeviceService,
private val pskService: PskService,
private val commandService: CommandService,
private val messageProperties: MessageProperties
Expand Down Expand Up @@ -75,36 +78,40 @@ class DownlinkService(
commandService.saveCommandWithNewStatus(command, Command.CommandStatus.IN_PROGRESS)
}

private fun getDownlinkPerCommand(command: Command): String {
if (command.type == Command.CommandType.PSK) {
val newKey = getCurrentReadyPsk(command)
private fun getDownlinkPerCommand(command: Command) =
when (command.type) {
Command.CommandType.PSK -> {
val device = deviceService.getDevice(command.deviceId)
val newKey = getCurrentReadyPsk(command)

return createPskCommand(newKey)
}
if (command.type == Command.CommandType.PSK_SET) {
val newKey = getCurrentReadyPsk(command)
logger.debug {
"Create PSK set command for key for device ${newKey.identity} with revision ${newKey.revision} and status ${newKey.status}"
createPskCommand(device, newKey)
}
return createPskSetCommand(newKey)
}
Command.CommandType.PSK_SET -> {
val device = deviceService.getDevice(command.deviceId)
val newKey = getCurrentReadyPsk(command)

return command.type.downlink
}
logger.debug {
"Create PSK set command for key for device ${device.id} with revision ${newKey.revision} and status ${newKey.status}"
}

createPskSetCommand(device, newKey)
}
else -> command.type.downlink
}

private fun getCurrentReadyPsk(command: Command) =
pskService.getCurrentReadyPsk(command.deviceId)
?: throw NoExistingPskException("There is no new key ready to be set")

fun createPskCommand(newPreSharedKey: PreSharedKey): String {
fun createPskCommand(device: Device, newPreSharedKey: PreSharedKey): String {
val newKey = newPreSharedKey.preSharedKey
val hash = DigestUtils.sha256Hex("${newPreSharedKey.secret}${newKey}")
val hash = DigestUtils.sha256Hex("${device.secret}${newKey}")
return "PSK:${newKey}:${hash}"
}

fun createPskSetCommand(newPreSharedKey: PreSharedKey): String {
fun createPskSetCommand(device: Device, newPreSharedKey: PreSharedKey): String {
val newKey = newPreSharedKey.preSharedKey
val hash = DigestUtils.sha256Hex("${newPreSharedKey.secret}${newKey}")
val hash = DigestUtils.sha256Hex("${device.secret}${newKey}")
return "PSK:${newKey}:${hash}:SET"
}
}
13 changes: 13 additions & 0 deletions application/src/main/resources/db/migration/V9__device.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
create table device (
id char(15) not null,
secret char(64) not null,

primary key (id)
);

insert into device (id, secret)
select identity, secret
from pre_shared_key;

alter table pre_shared_key
drop column secret;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package org.gxf.crestdeviceservice.consumer
import com.alliander.sng.DeviceCredentials
import org.gxf.crestdeviceservice.command.entity.Command
import org.gxf.crestdeviceservice.command.service.CommandService
import org.gxf.crestdeviceservice.device.service.DeviceService
import org.gxf.crestdeviceservice.psk.service.PskDecryptionService
import org.gxf.crestdeviceservice.psk.service.PskService
import org.junit.jupiter.api.Test
Expand All @@ -16,11 +17,12 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class IncomingDeviceCredentialsConsumerTest {
private val deviceService = mock<DeviceService>()
private val pskService = mock<PskService>()
private val pskDecryptionService = mock<PskDecryptionService>()
private val commandService = mock<CommandService>()
private val incomingDeviceCredentialsConsumer =
IncomingDeviceCredentialsConsumer(pskService, pskDecryptionService, commandService)
IncomingDeviceCredentialsConsumer(deviceService, pskService, pskDecryptionService, commandService)

@Test
fun handleIncomingDeviceCredentialsChangeInitialPsk() {
Expand All @@ -38,7 +40,8 @@ class IncomingDeviceCredentialsConsumerTest {

incomingDeviceCredentialsConsumer.handleIncomingDeviceCredentials(deviceCredentials)

verify(pskService).setInitialKeyForDevice(imei, decryptedPsk, decryptedSecret)
verify(deviceService).createDevice(imei, decryptedSecret)
verify(pskService).setInitialKeyForDevice(imei, decryptedPsk)
verify(pskService).generateNewReadyKeyForDevice(imei)
verify(commandService).saveCommandEntities(any<List<Command>>())
}
Expand All @@ -59,7 +62,8 @@ class IncomingDeviceCredentialsConsumerTest {

incomingDeviceCredentialsConsumer.handleIncomingDeviceCredentials(deviceCredentials)

verify(pskService).setInitialKeyForDevice(imei, decryptedPsk, decryptedSecret)
verify(deviceService).createDevice(imei, decryptedSecret)
verify(pskService).setInitialKeyForDevice(imei, decryptedPsk)
verify(pskService, times(0)).generateNewReadyKeyForDevice(imei)
verify(commandService, times(0)).saveCommandEntities(any<List<Command>>())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import org.gxf.crestdeviceservice.TestHelper
import org.gxf.crestdeviceservice.command.entity.Command
import org.gxf.crestdeviceservice.command.service.CommandService
import org.gxf.crestdeviceservice.config.MessageProperties
import org.gxf.crestdeviceservice.device.entity.Device
import org.gxf.crestdeviceservice.device.service.DeviceService
import org.gxf.crestdeviceservice.psk.entity.PreSharedKey
import org.gxf.crestdeviceservice.psk.entity.PreSharedKeyStatus
import org.gxf.crestdeviceservice.psk.service.PskService
Expand All @@ -21,26 +23,29 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

class DownlinkServiceTest {
private val deviceService = mock<DeviceService>()
private val pskService = mock<PskService>()
private val commandService = mock<CommandService>()
private val messageProperties = MessageProperties(1024)
private val downlinkService = DownlinkService(pskService, commandService, messageProperties)
private val downlinkService = DownlinkService(deviceService, pskService, commandService, messageProperties)
private val message = TestHelper.messageTemplate()
private val deviceId = TestConstants.DEVICE_ID

@Test
fun shouldReturnPskDownlinkWhenThereIsANewPsk() {
val expectedKey = "key"
val expectedHash = "ad165b11320bc91501ab08613cc3a48a62a6caca4d5c8b14ca82cc313b3b96cd"
val pskReady = PreSharedKey(deviceId, 1, Instant.now(), expectedKey, "secret", PreSharedKeyStatus.READY)
val pskPending = PreSharedKey(deviceId, 1, Instant.now(), expectedKey, "secret", PreSharedKeyStatus.PENDING)
val device = Device(deviceId, "secret")
val pskReady = PreSharedKey(deviceId, 1, Instant.now(), expectedKey, PreSharedKeyStatus.READY)
val pskPending = PreSharedKey(deviceId, 1, Instant.now(), expectedKey, PreSharedKeyStatus.PENDING)
val pskCommandPending = CommandFactory.pendingPskCommand()
val pskSetCommandPending = CommandFactory.pendingPskSetCommand()
val pskCommandsPending = listOf(pskCommandPending, pskSetCommandPending)
val pskCommandInProgress = pskCommandPending.copy(status = Command.CommandStatus.IN_PROGRESS)
val pskSetCommandInProgress = pskSetCommandPending.copy(status = Command.CommandStatus.IN_PROGRESS)

whenever(commandService.getAllPendingCommandsForDevice(deviceId)).thenReturn(pskCommandsPending)
whenever(deviceService.getDevice(deviceId)).thenReturn(device)
whenever(pskService.readyForPskSetCommand(deviceId)).thenReturn(true)
whenever(commandService.saveCommandWithNewStatus(pskCommandPending, Command.CommandStatus.IN_PROGRESS))
.thenReturn(pskCommandInProgress)
Expand Down Expand Up @@ -99,9 +104,10 @@ class DownlinkServiceTest {
"6543210987654321,5e15cf0f8a55b58a54f51dda17c1d1645ebc145f912888ec2e02a55d7b7baea4,secret",
"6543210987654321,64904d94590a354cecd8e65630289bcc22103c07b08c009b0b12a8ef0d58af9d,different-secret")
fun shouldCreateACorrectPskSetCommandWithHash(key: String, expectedHash: String, usedSecret: String) {
val preSharedKey = PreSharedKey("identity", 0, Instant.now(), key, usedSecret, PreSharedKeyStatus.PENDING)
val device = Device(deviceId, usedSecret)
val preSharedKey = PreSharedKey(deviceId, 0, Instant.now(), key, PreSharedKeyStatus.PENDING)

val result = downlinkService.createPskSetCommand(preSharedKey)
val result = downlinkService.createPskSetCommand(device, preSharedKey)

// PSK:[Key]:[Hash]:SET
assertThat(result).isEqualTo("PSK:${key}:${expectedHash}:SET")
Expand Down
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.github.davidmc24.gradle.plugin.avro.GenerateAvroJavaTask
import io.spring.gradle.dependencymanagement.internal.dsl.StandardDependencyManagementExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage

plugins {
id("org.springframework.boot") version "3.3.4" apply false
Expand Down
12 changes: 12 additions & 0 deletions components/device/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: Copyright Contributors to the GXF project
//
// SPDX-License-Identifier: Apache-2.0

dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")

implementation(project(":components:shared"))

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation(libs.mockitoKotlin)
}
Loading

0 comments on commit 604e3e8

Please sign in to comment.