Skip to content

Commit

Permalink
FDP-1897: Review comments processed and docs updated
Browse files Browse the repository at this point in the history
Signed-off-by: Sander Verbruggen <[email protected]>
  • Loading branch information
sanderv committed Feb 22, 2024
1 parent a301efe commit db55938
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 105 deletions.
54 changes: 30 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ It can be configured with the encoder / decoder of a specific object using:
```java
new DefaultKafkaConsumerFactory(
kafkaProperties.buildConsumerProperties(),

ErrorHandlingDeserializer(AvroSerializer(AvroMessage.getEncoder())),

ErrorHandlingDeserializer(AvroDeserializer(AvroMessage.getDecoder()))
)
ErrorHandlingDeserializer(AvroSerializer(AvroMessage.getEncoder())),
ErrorHandlingDeserializer(AvroDeserializer(AvroMessage.getDecoder()))
)
```

## kafka-message-signing
Expand All @@ -42,21 +40,13 @@ Two variations are supported:

The `MessageSigner` class is used for both signing and verifying a signature.

To sign a message, use `MessageSigner`'s `sign()` method: choose between `SignableMessageWrapper` or `ProducerRecord`.

To verify a signature, use `MessageSigner`'s `verify()` method: choose between `SignableMessageWrapper`
or `ProducerRecord`.

The `MessageSigner` class can be created using `MessageSigner.newBuilder()` with the following configuration options:
To sign a message, use one of `MessageSigner`'s sign methods:
- `signUsingField(...)` using the `SignableMessageWrapper` to wrap your Avro object;
- `signUsingHeader(...)` to add the signature to the Avro object's header.

- signingEnabled
- stripAvroHeader
- signatureAlgorithm
- signatureProvider
- signatureKeyAlgorithm
- signatureKeySize
- privateKeyFile
- publicKeyFile
To verify a signature, use one of `MessageSigner`'s verify methods:
- `verifyUsingField(...)` using the `SignableMessageWrapper` to wrap your Avro object;
- `verifyUsingHeader(...)` to read the signature from the Avro object's header.

### Spring Auto Configuration

Expand All @@ -66,20 +56,36 @@ You can configure the settings in your `application.yaml` (or properties), for e
message-signing:
signing-enabled: true
strip-avro-header: true
algorithm: SHA256withRSA
provider: SunRsaSign
signature-algorithm: SHA256withRSA
signature-provider: SunRsaSign
key-algorithm: RSA
key-size: 2048
private-key-file: classpath:rsa-private.pem
public-key-file: classpath:rsa-public.pem
```
### Custom or multiple certificates configuration
You can create your own `MessageSigningProperties` object and use `MessageSigner.newMessageSigner(props)`.
Spring Boot users can extend the `MessageSigningProperties` to add @ConfigurationProperties capabilities and/or to
support multiple configurations
Spring Boot users can extend the `MessageSigningProperties` to add `@ConfigurationProperties` capabilities and/or to
support multiple configurations.

If you want to support multiple keys, you also have to instantiate multiple `MessageSigner` beans.

Auto configuration (see above) will see when you defined your own MessageSigner or MessageSigningProperties bean and will not auto-create one.

```java
@ConfigurationProperties(prefix = "your-app.your-message-signing")
class YourMessageSigningProperties extends MessageSigningProperties {
}
@Configuration
class MessageSigningConfiguration {
@Bean
public MessageSigner yourMessageSigner(YourMessageSigningProperties yourMessageSigningProperties) {
return new MessageSigner(yourMessageSigningProperties);
}
}
```
### Creating a public/private key for signing

To generate a public/private keypair, use these two commands:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,21 @@ import java.util.regex.Pattern
@ConditionalOnMissingBean(MessageSigner::class)
class MessageSigner(properties: MessageSigningProperties) {

private val signingEnabled: Boolean = properties.signingEnabled
val signingEnabled: Boolean = properties.signingEnabled
private val stripAvroHeader: Boolean = properties.stripAvroHeader

private val signatureAlgorithm: String = properties.algorithm
private val signatureAlgorithm: String = properties.signatureAlgorithm
private val signatureProvider: String? = determineProvider(properties)
private val signatureKeyAlgorithm: String = properties.keyAlgorithm
private val keyAlgorithm: String = properties.keyAlgorithm

private var signingKey: PrivateKey? = readPrivateKey(properties.privateKeyFile)
private var verificationKey: PublicKey? = readPublicKey(properties.keyAlgorithm, properties.publicKeyFile)
private var verificationKey: PublicKey? = readPublicKey(keyAlgorithm, properties.publicKeyFile)

private val signingSignature: Signature? = signatureInstance(properties.algorithm, signatureProvider, signingKey)
private val verificationSignature: Signature? =
signatureInstance(properties.algorithm, signatureProvider, verificationKey)
private val signingSignature: Signature? = signatureInstance(signatureAlgorithm, signatureProvider, signingKey)
private val verificationSignature: Signature? = signatureInstance(signatureAlgorithm, signatureProvider, verificationKey)

init {
if (this.signingEnabled) {
if (properties.signingEnabled) {
require(!(signingKey == null && verificationKey == null)) { "A signing key (PrivateKey) or verification key (PublicKey) must be provided" }
}
}
Expand All @@ -52,7 +51,7 @@ class MessageSigner(properties: MessageSigningProperties) {
}

/**
* Signs the provided `message`, overwriting an existing signature.
* Signs the provided `message`, overwriting an existing signature field inside the message object.
*
* @param message the message to be signed
* @return Returns the signed (unwrapped) message. If signing is disabled through configuration, the message will be returned unchanged.
Expand All @@ -62,7 +61,7 @@ class MessageSigner(properties: MessageSigningProperties) {
* @throws UncheckedIOException if determining the bytes for the message throws an IOException.
* @throws UncheckedSecurityException if the signing process throws a SignatureException.
*/
fun <T> sign(message: SignableMessageWrapper<T>): T {
fun <T> signUsingField(message: SignableMessageWrapper<T>): T {
if (this.signingEnabled) {
val signatureBytes = this.signature(message)
message.setSignature(ByteBuffer.wrap(signatureBytes))
Expand All @@ -80,7 +79,7 @@ class MessageSigner(properties: MessageSigningProperties) {
* @throws UncheckedIOException if determining the bytes for the message throws an IOException.
* @throws UncheckedSecurityException if the signing process throws a SignatureException.
*/
fun sign(producerRecord: ProducerRecord<String, out SpecificRecordBase>): ProducerRecord<String, out SpecificRecordBase> {
fun signUsingHeader(producerRecord: ProducerRecord<String, out SpecificRecordBase>): ProducerRecord<String, out SpecificRecordBase> {
if (this.signingEnabled) {
val signature = this.signature(producerRecord)
producerRecord.headers().add(RECORD_HEADER_KEY_SIGNATURE, signature)
Expand All @@ -102,7 +101,7 @@ class MessageSigner(properties: MessageSigningProperties) {
* @throws UncheckedIOException if determining the bytes for the message throws an IOException.
* @throws UncheckedSecurityException if the signing process throws a SignatureException.
*/
fun signature(message: SignableMessageWrapper<*>): ByteArray {
private fun signature(message: SignableMessageWrapper<*>): ByteArray {
check(this.canSignMessages()) { "This MessageSigner is not configured for signing, it can only be used for verification" }
val oldSignature = message.getSignature()
try {
Expand Down Expand Up @@ -137,7 +136,7 @@ class MessageSigner(properties: MessageSigningProperties) {
* @throws UncheckedIOException if determining the bytes throws an IOException.
* @throws UncheckedSecurityException if the signing process throws a SignatureException.
*/
fun signature(producerRecord: ProducerRecord<String, out SpecificRecordBase>): ByteArray {
private fun signature(producerRecord: ProducerRecord<String, out SpecificRecordBase>): ByteArray {
check(this.canSignMessages()) { "This MessageSigner is not configured for signing, it can only be used for verification" }
val oldSignatureHeader = producerRecord.headers().lastHeader(RECORD_HEADER_KEY_SIGNATURE)
try {
Expand Down Expand Up @@ -166,7 +165,7 @@ class MessageSigner(properties: MessageSigningProperties) {
}

/**
* Verifies the signature of the provided `message`.
* Verifies the signature of the provided `message` using the signature in a message field.
*
* @param message the message to be verified
* @return `true` if the signature of the given `message` was verified; `false`
Expand All @@ -177,7 +176,7 @@ class MessageSigner(properties: MessageSigningProperties) {
* @throws UncheckedSecurityException if the signature verification process throws a
* SignatureException.
*/
fun verify(message: SignableMessageWrapper<*>): Boolean {
fun verifyUsingField(message: SignableMessageWrapper<*>): Boolean {
check(this.canVerifyMessageSignatures()) { "This MessageSigner is not configured for verification, it can only be used for signing" }

val messageSignature = message.getSignature() ?: return false
Expand All @@ -199,7 +198,7 @@ class MessageSigner(properties: MessageSigningProperties) {
}

/**
* Verifies the signature of the provided `consumerRecord`.
* Verifies the signature of the provided `consumerRecord` using the signature from the message header.
*
* @param consumerRecord the record to be verified
* @return `true` if the signature of the given `consumerRecord` was verified; `false`
Expand All @@ -210,7 +209,7 @@ class MessageSigner(properties: MessageSigningProperties) {
* @throws UncheckedSecurityException if the signature verification process throws a
* SignatureException.
*/
fun verify(consumerRecord: ConsumerRecord<String, out SpecificRecordBase>): Boolean {
fun verifyUsingHeader(consumerRecord: ConsumerRecord<String, out SpecificRecordBase>): Boolean {
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)
Expand Down Expand Up @@ -278,40 +277,9 @@ class MessageSigner(properties: MessageSigningProperties) {
}
}

fun isSigningEnabled(): Boolean {
return this.signingEnabled
}

fun signingKey(): Optional<PrivateKey> {
return Optional.ofNullable(this.signingKey)
}

fun signingKeyPem(): Optional<String> {
return signingKey().map { key: PrivateKey ->
this.keyAsMem(key, key.algorithm + " PRIVATE KEY")
}
}

fun verificationKey(): Optional<PublicKey> {
return Optional.ofNullable(this.verificationKey)
}

fun verificationKeyPem(): Optional<String> {
return verificationKey()
.map { key: PublicKey ->
this.keyAsMem(key, key.algorithm + " PUBLIC KEY")
}
}

private fun keyAsMem(key: Key, label: String): String {
return ("-----BEGIN $label-----" + "\r\n"
+ Base64.getMimeEncoder().encodeToString(key.encoded) + "\r\n"
+ "-----END $label-----" + "\r\n")
}

private fun determineProvider(properties: MessageSigningProperties): String? {
return when {
properties.provider != null -> properties.provider
properties.signatureProvider != null -> properties.signatureProvider
this.signingSignature != null -> signingSignature.getProvider()?.name
this.verificationSignature != null -> verificationSignature.getProvider()?.name
// Should not happen, set to null and ignore.
Expand All @@ -323,7 +291,7 @@ class MessageSigner(properties: MessageSigningProperties) {
return String.format(
"MessageSigner[algorithm=\"%s\"-\"%s\", provider=\"%s\", sign=%b, verify=%b]",
this.signatureAlgorithm,
this.signatureKeyAlgorithm,
this.keyAlgorithm,
this.signatureProvider,
this.canSignMessages(),
this.canVerifyMessageSignatures()
Expand All @@ -336,7 +304,7 @@ class MessageSigner(properties: MessageSigningProperties) {

const val DEFAULT_SIGNATURE_ALGORITHM: String = "SHA256withRSA"
const val DEFAULT_SIGNATURE_PROVIDER: String = "SunRsaSign"
const val DEFAULT_SIGNATURE_KEY_ALGORITHM: String = "RSA"
const val DEFAULT_KEY_ALGORITHM: String = "RSA"

const val RECORD_HEADER_KEY_SIGNATURE: String = "signature"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import org.springframework.core.io.Resource
open class MessageSigningProperties(
/** Enable or disable signing */
var signingEnabled: Boolean = false,
/* Strip the Avro header containing the schema fingerprint */
/** Strip the Avro header containing the schema fingerprint */
var stripAvroHeader: Boolean = false,

/** Signing algorithm */
var algorithm: String = MessageSigner.DEFAULT_SIGNATURE_ALGORITHM,
/** Signing algorithm provider */
var provider: String? = MessageSigner.DEFAULT_SIGNATURE_PROVIDER,
/** Private key algorithm */
var keyAlgorithm: String = MessageSigner.DEFAULT_SIGNATURE_KEY_ALGORITHM,
/** Signature algorithm */
var signatureAlgorithm: String = MessageSigner.DEFAULT_SIGNATURE_ALGORITHM,
/** Signature algorithm provider */
var signatureProvider: String? = MessageSigner.DEFAULT_SIGNATURE_PROVIDER,
/** Public key algorithm */
var keyAlgorithm: String = MessageSigner.DEFAULT_KEY_ALGORITHM,
/** PEM file containing the private key */
var privateKeyFile: Resource? = null,
/** PEM file containing the public key */
Expand Down
Loading

0 comments on commit db55938

Please sign in to comment.