Protect your data against unauthorized manipulation (Second part)

Asterios Raptis
6 min readNov 16, 2020

In the first part of the blog we prepared the data for digital signing. In this part we learn the signing process.

We have to create a digital signature from our entity, therefore we need first of all a Signer object that can sign our entity. We will use the already from the JDK provided class java.security.Signature

But before we start showing the Signer object lets see the model object

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
import java.security.PrivateKey;@Data
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@SuperBuilder
public class SignatureBean
{
PrivateKey privateKey;
String signatureAlgorithm;
}

The SignatureBean holds the data for process the digital signature. That are the private key and the sign algorithm. Now that we now the model object lets proceed with the Signer class

import de.alpharogroup.throwable.RuntimeExceptionDecorator;import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.util.Objects;
public final class Signer
{
private final Signature signature; private final SignatureBean signatureBean; public Signer(SignatureBean signatureBean)
{
Objects.requireNonNull(signatureBean);
Objects.requireNonNull(signatureBean.getPrivateKey());
Objects.requireNonNull(signatureBean.getSignatureAlgorithm());
this.signatureBean = signatureBean;
try
{
this.signature = Signature.getInstance(this.signatureBean.getSignatureAlgorithm());
this.signature.initSign(this.signatureBean.getPrivateKey());
}
catch (InvalidKeyException | NoSuchAlgorithmException exception)
{
throw new RuntimeException(exception);
}
}
public synchronized byte[] sign(byte[] bytesToSign)
{
return RuntimeExceptionDecorator.decorate(() -> {
signature.update(bytesToSign);
return signature.sign();
});
}
}

With the above Signer class we can sign now byte arrays. But what we want is to sign java objects. So we can decorate the above Signer class with an ObjectSigner class as follow:

import de.alpharogroup.io.Serializer;import java.io.Serializable;
import java.util.Base64;
public final class ObjectSigner<T extends Serializable> { private final Signer signer;
public ObjectSigner(SignatureBean signatureBean) {
this.signer = new Signer(signatureBean);
}
public synchronized String sign(final T object) {
byte[] signature = this.signer.sign(Serializer.toByteArray(object));
String encodedSignature = Base64.getEncoder().encodeToString(signature);
return encodedSignature;
}
}

The Serializer class is only a utility class that convert the given object to a byte array and the decorated signer object gets the digital signature. As second step the digital signature is encoded to the base64 format and returned the digital signature as String object.

I created a library project from this classes and there are hosted at github and can be viewed and downloaded at https://github.com/astrapi69/sign-and-verify

The sign-and-verify library provides several classes for sign and verify from byte arrays, serialiable objects and even not serialiable objects in a generic way.

Now that we can create a digital signature from any java object we can sign our entity. For that issue we will use a JPA feature called entity listeners. But before we implement our listener we have to fulfill some preconditions. For create and using digital signature we need a private key that we encapsulate in a Keystore file. In the example application we load the Keystore file from the classpath. The information for that we store in the application.yml

app:
name: gamble-boom
dir: ${user.home}/.${app.name}
db-name: gambleboom
keystore-filename: keystore.jks
keystore-password: keystore-pw
pk-alias: app-priv-key
signature-algorithm: SHA256withRSA
db-host: localhost
db-port: 5432
db-username: postgres
db-password: postgres
db-url-prefix: jdbc:postgresql://

The corresponding configuration properties class is the ApplicationProperties

import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@ConfigurationProperties(prefix = "app")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ApplicationProperties
{
String dbHost;
String dbName;
int dbPort;
String dbUrlPrefix;
String dbUsername;
String dbPassword;
String dir;
String name;
String keystoreFilename;
String keystorePassword;
String pkAlias;
String signatureAlgorithm;
}

Now we can create the needed spring beans in the ApplicationConfiguration class that are needed for the sign and verfifying process.

public class ApplicationConfiguration implements WebMvcConfigurer
{
...
ApplicationProperties applicationProperties; Environment env; ... @Bean
public KeyStore keyStore()
{
String keystoreFilename = applicationProperties.getKeystoreFilename();
File keystoreFile = RuntimeExceptionDecorator
.decorate(() -> ClassExtensions.getResourceAsFile(keystoreFilename));
return RuntimeExceptionDecorator
.decorate(() -> KeyStoreFactory.loadKeyStore(keystoreFile, KeystoreType.JKS.name(),
applicationProperties.getKeystorePassword()));
}
@Bean
public SignatureBean signatureBean()
{
String pkAlias = applicationProperties.getPkAlias();
char[] chars = applicationProperties.getKeystorePassword().toCharArray();
KeyStore keyStore = keyStore();
PrivateKey privateKey = RuntimeExceptionDecorator
.decorate(() -> (PrivateKey)keyStore.getKey(pkAlias, chars));
String signatureAlgorithm = applicationProperties.getSignatureAlgorithm();
return SignatureBean.builder().privateKey(privateKey).signatureAlgorithm(signatureAlgorithm)
.build();
}
@Bean
public VerifyBean verifyBean()
{
String pkAlias = applicationProperties.getPkAlias();
char[] chars = applicationProperties.getKeystorePassword().toCharArray();
String signatureAlgorithm = applicationProperties.getSignatureAlgorithm();
KeyStore keyStore = keyStore();
Certificate certificate = RuntimeExceptionDecorator
.decorate(() -> keyStore.getCertificate(pkAlias));
return VerifyBean.builder().certificate(certificate).signatureAlgorithm(signatureAlgorithm)
.build();
}
@Bean
public Gson gson()
{
return GsonFactory.newGsonBuilder(new GenericExclusionStrategy<>(SignatureExclude.class),
"dd-MM-yyyy hh:mm:ss");
}
@Bean
public JsonSigner<Draws> drawsJsonSigner()
{
return new JsonSigner<>(signatureBean(), gson());
}
@Bean
public JsonVerifier<Draws> drawsJsonVerifier()
{
return new JsonVerifier<>(verifyBean(), gson());
}
}

For now we have all objects that we need and can continue with the implementation of the jpa entity listener that looks as follows

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import org.springframework.beans.factory.annotation.Autowired;import de.alpharogroup.sign.JsonSigner;
import de.alpharogroup.spring.autowire.AutowireAware;
import io.github.astrapi69.gambleboom.jpa.entities.Draws;
public class DrawsSignatureListener
{
@Autowired
JsonSigner<Draws> drawsJsonSigner;
@PrePersist
@PreUpdate
public void onPrePersistSign(Draws verifiable)
{
sign(verifiable);
}
private void sign(Draws verifiable)
{
AutowireAware.autowire(this, drawsJsonSigner);
String digitalSignature = drawsJsonSigner.sign(verifiable);
verifiable.setSignature(digitalSignature);
}
}

We want to sign the objects on perstist and update process so we tag the method onPrePersistSign with the appropriate jpa annotations for do the job.

In the DrawsSignatureListener we have the needed signer class JsonSigner that comes from the sign-and-verify library. The JsonSigner transform our object into json that can exclude Fields like our signature field in our Draws entity. Under the hood it uses gson features for exclusion of fields. In the sign method we also use the AutowireAware for autowire our signer object and not get a NPE, and create the digital signature for the given Draws object and finally set the corresponding field for it. We have also alter the Draws class and tag the signature field with the annotation SignatureExclude. The SignatureExclude annotation signals the gson parser to exclude this field in the json object. This is done with an GenericExclusionStrategy as we can see in the factory method gson() in the ApplicationConfiguration. So the Draws entity looks now as follows:

import de.alpharogroup.db.entity.uniqueable.UUIDEntity;
import de.alpharogroup.db.entity.verifiable.Verifiable;
import lombok.*;
import lombok.experimental.*;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Set;
@Entity @Table
@EntityListeners({ DrawsSignatureListener.class })
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Draws implements Verifiable
{
@Id @GeneratedValue(generator = "UUID")
@Column(name = "id", updatable = false, nullable = false)
private UUID id;
@Column LocalDateTime drawnDate; @ElementCollection
@Column(name = "lottery_number")
Set<Integer> lotteryNumbers;
/* new annotation signature exclude */
@SignatureExclude
@Column String signature;
}

The coresponding repository class looks as follows

import java.util.UUID;import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import io.github.astrapi69.gambleboom.jpa.entities.Draws;@Repository
public interface DrawsRepository extends JpaRepository<Draws, UUID>
{
}

This is spring-data magic and provides all crud methods for our Draws entity.

At this point the implementation of the sign process is finished and ready for test. So we write an integration test for it. As we told before we will use testcontainers library for the integration tests.

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;import java.time.LocalDateTime;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import de.alpharogroup.collections.set.SetFactory;
import io.github.astrapi69.gambleboom.AbstractIntegrationTest;
import io.github.astrapi69.gambleboom.config.ApplicationConfiguration;
import io.github.astrapi69.gambleboom.config.ApplicationProperties;
import io.github.astrapi69.gambleboom.jpa.entities.Draws;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import lombok.extern.java.Log;
@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@DataJpaTest
@FieldDefaults(level = AccessLevel.PRIVATE)
@Log
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import({ ApplicationConfiguration.class })
@EnableConfigurationProperties({ ApplicationProperties.class })
public class DrawsRepositoryTest extends AbstractIntegrationTest
{
@Autowired
private DrawsRepository drawsRepository;
@Test
public void testSave() {
// new scenario...
// draw the first object
Draws firstDraw = drawsRepository.saveAndFlush(
Draws.builder()
.drawnDate(LocalDateTime.now())
.lotteryNumbers(
SetFactory.newHashSet(2, 5, 11, 23, 25, 45))
.build());
String signature = firstDraw.getSignature();
assertThat(firstDraw).isNotNull();
assertThat(signature).isNotNull();
// new scenario...
// draw the second object
Draws secondDraw = drawsRepository.saveAndFlush(
Draws.builder()
.drawnDate(LocalDateTime.now())
.lotteryNumbers(
SetFactory.newHashSet(1, 6, 17, 23, 26, 47))
.build());
String newSignature = secondDraw.getSignature();
assertThat(secondDraw).isNotNull();
assertThat(signature).isNotEqualTo(newSignature);
// change draw date of first draw
firstDraw.setDrawnDate(LocalDateTime.now());
firstDraw = drawsRepository.saveAndFlush(firstDraw);
newSignature = firstDraw.getSignature();
assertThat(signature).isNotEqualTo(newSignature);
}
}

For now we prooved that we can sign the Draws entity. The final state till now can be viewed in the branch ‘feature/verify-of-digital-signature’.

Whats missing now is the verifying process for it so our next step is to provide the verification process that will be introduced in the next part of this post.

--

--

Asterios Raptis

Asterios Raptis is a Fullstack Developer and Software Consultant with over three decades of experience in the software development