Skip to content

Commit

Permalink
Merge pull request #16 from OSGP/feature/FDP-1737-add-do-after-verify…
Browse files Browse the repository at this point in the history
…-with-exception

FDP-1737: verify functions throwing an exception
  • Loading branch information
loesimmens authored Feb 29, 2024
2 parents eca1e5f + e556483 commit 6f18a53
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 25 deletions.
1 change: 1 addition & 0 deletions kafka-message-signing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
testImplementation("org.springframework:spring-test")
testImplementation("org.springframework.boot:spring-boot-test")
testImplementation("org.springframework.boot:spring-boot-starter")
testImplementation(libs.mockitoKotlin)
}

tasks.test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,23 @@ class MessageSigner(properties: MessageSigningProperties) {
* @throws UncheckedSecurityException if the signature verification process throws a
* SignatureException.
*/
fun verifyUsingField(message: SignableMessageWrapper<*>): Boolean {
fun <T> verifyUsingField(message: SignableMessageWrapper<T>): T {
check(this.canVerifyMessageSignatures()) { "This MessageSigner is not configured for verification, it can only be used for signing" }

val messageSignature = message.getSignature() ?: return false
val messageSignature = message.getSignature() ?: throw IllegalStateException(
"This message does not contain a signature"
)
messageSignature.mark()
val signatureBytes = ByteArray(messageSignature.remaining())
messageSignature.get(signatureBytes)
messageSignature[signatureBytes]

try {
message.setSignature(null)
return this.verifySignatureBytes(signatureBytes, this.toByteBuffer(message))
if(this.verifySignatureBytes(signatureBytes, this.toByteBuffer(message))) {
return message.message
} else {
throw VerificationException("Verification of message signing failed")
}
} catch (e: SignatureException) {
throw UncheckedSecurityException("Unable to verify message signature", e)
} finally {
Expand All @@ -202,25 +208,24 @@ class MessageSigner(properties: MessageSigningProperties) {
* @throws UncheckedSecurityException if the signature verification process throws a
* SignatureException.
*/
fun verifyUsingHeader(consumerRecord: ConsumerRecord<String, out SpecificRecordBase>): Boolean {
fun verifyUsingHeader(consumerRecord: ConsumerRecord<String, out SpecificRecordBase>): ConsumerRecord<String, out SpecificRecordBase> {
check(this.canVerifyMessageSignatures()) { "This MessageSigner is not configured for verification, it can only be used for signing" }

val header = consumerRecord.headers().lastHeader(RECORD_HEADER_KEY_SIGNATURE)
?: throw IllegalStateException(
"This ProducerRecord does not contain a signature header"
)
val signatureBytes = header.value()
if (signatureBytes == null || signatureBytes.isEmpty()) {
return false
}
check(!(signatureBytes == null || signatureBytes.isEmpty())) { "Signature header is empty" }

try {
consumerRecord.headers().remove(RECORD_HEADER_KEY_SIGNATURE)
val specificRecordBase: SpecificRecordBase = consumerRecord.value()
return this.verifySignatureBytes(
signatureBytes,
this.toByteBuffer(specificRecordBase)
)
if(this.verifySignatureBytes(signatureBytes, this.toByteBuffer(specificRecordBase))) {
return consumerRecord
} else {
throw VerificationException("Verification of record signing failed")
}
} catch (e: SignatureException) {
throw UncheckedSecurityException("Unable to verify message signature", e)
}
Expand All @@ -246,7 +251,7 @@ class MessageSigner(properties: MessageSigningProperties) {

private fun stripAvroHeader(byteBuffer: ByteBuffer?): ByteArray {
val bytes = ByteArray(byteBuffer!!.remaining())
byteBuffer.get(bytes)
byteBuffer[bytes]
if (this.hasAvroHeader(bytes)) {
return Arrays.copyOfRange(bytes, AVRO_HEADER_LENGTH, bytes.size)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: Copyright Contributors to the GXF project
//
// SPDX-License-Identifier: Apache-2.0

package com.gxf.utilities.kafka.message.signing

class VerificationException(message: String? = null): Exception(message)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.clients.producer.ProducerRecord
import org.apache.kafka.common.header.Header
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.catchThrowable
import org.junit.jupiter.api.Test
import org.springframework.core.io.ClassPathResource
import java.nio.ByteBuffer
Expand Down Expand Up @@ -87,52 +88,77 @@ class MessageSignerTest {

val signatureWasVerified = messageSigner.verifyUsingField(message)

assertThat(signatureWasVerified).isTrue()
assertThat(signatureWasVerified).isEqualTo(message.message)
}

@Test
fun verifiesRecordsWithValidSignature() {
val signedRecord = this.properlySignedRecord()

val signatureWasVerified: Boolean = messageSigner.verifyUsingHeader(signedRecord)
val result = messageSigner.verifyUsingHeader(signedRecord)

assertThat(signatureWasVerified).isTrue()
assertThat(result).isEqualTo(signedRecord)
}

@Test
fun doesNotVerifyMessagesWithoutSignature() {
val messageWrapper = this.messageWrapper()
val expectedMessage = "This message does not contain a signature"

val signatureWasVerified = messageSigner.verifyUsingField(messageWrapper)
val throwable = catchThrowable {
messageSigner.verifyUsingField(messageWrapper)
}

assertThat(signatureWasVerified).isFalse()
assertThat(throwable)
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining(expectedMessage)
}

@Test
fun doesNotVerifyRecordsWithoutSignature() {
val expectedMessage = "This ProducerRecord does not contain a signature header"
val consumerRecord = this.consumerRecord()

val exception: Exception = org.junit.jupiter.api.Assertions.assertThrows(
IllegalStateException::class.java
) {
val throwable = catchThrowable {
messageSigner.verifyUsingHeader(
consumerRecord
)
}
val actualMessage = exception.message

assertThat(actualMessage).contains(expectedMessage)
assertThat(throwable)
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining(expectedMessage)
}

@Test
fun doesNotVerifyMessagesWithIncorrectSignature() {
val randomSignature = this.randomSignature()
val messageWrapper = this.messageWrapper(randomSignature)
val expectedMessage = "Verification of message signing failed"

val throwable = catchThrowable {
messageSigner.verifyUsingField(messageWrapper)
}

assertThat(throwable)
.isInstanceOf(VerificationException::class.java)
.hasMessageContaining(expectedMessage)
}

val signatureWasVerified = messageSigner.verifyUsingField(messageWrapper)
@Test
fun doesNotVerifyRecordsWithIncorrectSignature() {
val consumerRecord = this.consumerRecord()
val randomSignature = this.randomSignature()
consumerRecord.headers().add(MessageSigner.RECORD_HEADER_KEY_SIGNATURE, randomSignature)
val expectedMessage = "Verification of record signing failed"

val throwable = catchThrowable {
messageSigner.verifyUsingHeader(consumerRecord)
}

assertThat(signatureWasVerified).isFalse()
assertThat(throwable)
.isInstanceOf(VerificationException::class.java)
.hasMessageContaining(expectedMessage)
}

@Test
Expand Down
6 changes: 6 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("avro", "1.11.3")
version("mockitoKotlin", "5.1.0")

library("avro", "org.apache.avro", "avro").versionRef("avro")
library(
"mockitoKotlin",
"org.mockito.kotlin",
"mockito-kotlin"
).versionRef("mockitoKotlin")
}
}
}
Expand Down

0 comments on commit 6f18a53

Please sign in to comment.