Skip to content

Commit

Permalink
add VaultTemplate to SigningServiceImpl
Browse files Browse the repository at this point in the history
  • Loading branch information
skreisigTSI committed Jul 18, 2022
1 parent aed1ff2 commit 262b4f7
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 59 deletions.
24 changes: 20 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- dependencies -->
<dgclib.version>1.3.1</dgclib.version>
<owasp.version>6.5.3</owasp.version>
<spring.boot.version>2.6.8</spring.boot.version>
<spring.test.version>5.3.19</spring.test.version>
<owasp.version>7.1.1</owasp.version>
<spring.boot.version>2.7.1</spring.boot.version>
<spring.cloud.version>2021.0.3</spring.cloud.version>
<spring.test.version>5.3.22</spring.test.version>
<spring.security.version>5.7.1</spring.security.version>
<hibernate.version>5.6.5.Final</hibernate.version>
<lombok.version>1.18.22</lombok.version>
<lombok.version>1.18.24</lombok.version>
<liquibase.version>4.9.1</liquibase.version>
<hcert-kotlin.version>1.2.0</hcert-kotlin.version>
<kotlin.version>1.6.21</kotlin.version>
Expand Down Expand Up @@ -106,6 +107,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down Expand Up @@ -185,6 +193,14 @@
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
Expand Down Expand Up @@ -88,7 +87,7 @@ public PrivateKey getPrivateKey() {
*/
public String signHash(String base64Hash) {
byte[] hashBytes = Base64.getDecoder().decode(base64Hash);
byte[] signature = signingService.signHash(hashBytes, certificatePrivateKeyProvider.getPrivateKey());
byte[] signature = signingService.signHash(hashBytes);
return Base64.getEncoder().encodeToString(signature);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,21 @@ public interface SigningService {
* The it is only the Part of regular signing functionality.
* Do not use regular Signing API because it will cause to hash the data twice and produce wrong
* signature
* @param hash SHA256 hash of content
*
* @param hash SHA256 hash of content
* @param privateKey RSA or EC
* @return signature as raw byte array
*/
byte[] signHash(byte[] hash, PrivateKey privateKey);

/**
* continue signing on already SHA256 generated content hash.
* The it is only the Part of regular signing functionality.
* Do not use regular Signing API because it will cause to hash the data twice and produce wrong
* signature
*
* @param hash SHA256 hash of content
* @return signature as raw byte array
*/
byte[] signHash(byte[] hash);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import eu.europa.ec.dgc.issuance.service.SigningService;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import lombok.RequiredArgsConstructor;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
Expand All @@ -15,10 +17,21 @@
import org.bouncycastle.crypto.signers.PSSSigner;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.Plaintext;

@Component

@Service
@RequiredArgsConstructor
public class SigningServiceImpl implements SigningService {

private final VaultTemplate vaultTemplate;

@Value("${dgc.signKey:issuerkey}")
private String signKey;

@Override
public byte[] signHash(byte[] hashBytes, PrivateKey privateKey) {
byte[] signature;
Expand All @@ -34,6 +47,15 @@ public byte[] signHash(byte[] hashBytes, PrivateKey privateKey) {
return signature;
}

@Override
public byte[] signHash(byte[] hash) {
String signature = vaultTemplate.opsForTransit().sign(signKey, Plaintext.of(hash)).getSignature();
if (signature.startsWith("vault:v1:")) {
signature = signature.substring(9);
}
return signature.getBytes(StandardCharsets.UTF_8);
}

private byte[] signRsapss(byte[] hashBytes, PrivateKey privateKey) throws CryptoException {
Digest contentDigest = new CopyDigest();
Digest mgfDigest = new SHA256Digest();
Expand Down
23 changes: 23 additions & 0 deletions src/main/resources/bootstrap-cloud.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
spring:
application:
name: cwa-dcc-rules
cloud:
vault:
ssl:
trust-store: file:${SSL_VAULT_TRUSTSTORE_PATH}
trust-store-password: ${SSL_VAULT_TRUSTSTORE_PASSWORD}
enabled: true
generic:
enabled: false
fail-fast: true
authentication: KUBERNETES
kubernetes:
role: ${VAULT_ROLE}
kubernetes-path: kubernetes
service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token
uri: ${VAULT_URI}
connection-timeout: 5000
read-timeout: 15000
config:
order: -10
14 changes: 14 additions & 0 deletions src/main/resources/bootstrap-vaultlocal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
spring:
cloud:
vault:
enabled: true
# use for local vault transit test (change the access token)
token: hvs.qWwAqjjaWnWtFXZkY1q7OYWX
scheme: http
fail-fast: true
# use for local vault transit test (change the uri)
uri: http://127.0.0.1:8200
connection-timeout: 5000
read-timeout: 15000
config:
order: -10
5 changes: 5 additions & 0 deletions src/main/resources/bootstrap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
spring:
cloud:
vault:
enabled: false
5 changes: 5 additions & 0 deletions src/test/java/OpenApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.vault.core.VaultTemplate;

@Slf4j
@SpringBootTest(
Expand All @@ -19,6 +21,9 @@
)
public class OpenApiTest {

@MockBean
private VaultTemplate vaultTemplate;

@Test
public void apiDocs() {
try (BufferedInputStream in = new BufferedInputStream(new URL("http://localhost:8080/openapi").openStream());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.vault.core.VaultTemplate;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand All @@ -19,6 +21,9 @@ class CertControllerTest {
@Autowired
CertController certController;

@MockBean
private VaultTemplate vaultTemplate;

@Test
public void testPublicKey() throws Exception {
ResponseEntity<PublicKeyInfo> publicKeyResponse = certController.getPublic();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.vault.core.VaultTemplate;

import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
Expand All @@ -19,6 +21,9 @@ class ContextControllerTest {
@Autowired
private MockMvc mockMvc;

@MockBean
private VaultTemplate vaultTemplate;

@Test
void getContext() throws Exception {
mockMvc.perform(get("/context"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.ResponseEntity;
import org.springframework.vault.core.VaultTemplate;

import static org.junit.jupiter.api.Assertions.assertNotNull;

Expand All @@ -13,6 +15,9 @@ public class DgciControllerTest {
@Autowired
DgciBackendController dgciController;

@MockBean
private VaultTemplate vaultTemplate;

@Test
void checkBackendIssuing() throws Exception {
String edgc = "{\"ver\":\"1.0.0\",\"nam\":{\"fn\":\"Garcia\",\"fnt\":\"GARCIA\"," +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.vault.core.VaultTemplate;

import static org.junit.jupiter.api.Assertions.*;

Expand All @@ -17,6 +19,9 @@ class ContextServiceTest {
@Autowired
ContextService contextService;

@MockBean
private VaultTemplate vaultTemplate;

@Test
void getContextFromEnvironment() {
JsonNode json = contextService.getContextDefinition();
Expand Down
37 changes: 22 additions & 15 deletions src/test/java/eu/europa/ec/dgc/issuance/service/DGCGenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterInputStream;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.vault.core.VaultTemplate;

import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -41,6 +44,10 @@ class DGCGenTest {
@Autowired
CertificateService certificateService;

@MockBean
private VaultTemplate vaultTemplate;

@Disabled("Disabled since it doesn't work with the Vault implementation")
@Test
void genEDGC() throws IOException {
String edgcJson = "{\"ver\":\"1.0.0\",\"nam\":{\"fn\":\"Garcia\",\"fnt\":\"GARCIA\"," +
Expand All @@ -61,12 +68,12 @@ void genEDGC() throws IOException {

byte[] dgcCbor = genDGCCbor(edgcJson, countryCode, issuedAt, certData.getExpired());
byte[] coseBytes = genCoseUnsigned(dgcCbor, Base64.getDecoder().decode(certData.getKid())
,certData.getAlgId());
, certData.getAlgId());
byte[] hash = dgciService.computeCoseSignHash(coseBytes);
IssueData issueData = new IssueData();
issueData.setHash(Base64.getEncoder().encodeToString(hash));
SignatureData sign = dgciService.finishDgci(certData.getId(), issueData);
byte[] coseSigned = genSetSignature(coseBytes,Base64.getDecoder().decode(sign.getSignature()));
byte[] coseSigned = genSetSignature(coseBytes, Base64.getDecoder().decode(sign.getSignature()));
String edgcQR = coseToQRCode(coseSigned);

EgcDecodeResult validationResult = edgcValidator.decodeEdgc(edgcQR);
Expand All @@ -76,20 +83,20 @@ void genEDGC() throws IOException {

private byte[] genDGCCbor(String edgcJson, String countryCode, long issuedAt, long expirationSec) {
CBORObject map = CBORObject.NewMap();
map.set(CBORObject.FromObject(1),CBORObject.FromObject(countryCode));
map.set(CBORObject.FromObject(6),CBORObject.FromObject(issuedAt));
map.set(CBORObject.FromObject(4),CBORObject.FromObject(expirationSec));
map.set(CBORObject.FromObject(1), CBORObject.FromObject(countryCode));
map.set(CBORObject.FromObject(6), CBORObject.FromObject(issuedAt));
map.set(CBORObject.FromObject(4), CBORObject.FromObject(expirationSec));
CBORObject hcertVersion = CBORObject.NewMap();
CBORObject hcert = CBORObject.FromJSONString(edgcJson);
hcertVersion.set(CBORObject.FromObject(1),hcert);
map.set(CBORObject.FromObject(-260),hcertVersion);
hcertVersion.set(CBORObject.FromObject(1), hcert);
map.set(CBORObject.FromObject(-260), hcertVersion);
return map.EncodeToBytes();
}

private byte[] genCoseUnsigned(byte[] payload,byte[] keyId,int algId) {
private byte[] genCoseUnsigned(byte[] payload, byte[] keyId, int algId) {
CBORObject protectedHeader = CBORObject.NewMap();
protectedHeader.set(CBORObject.FromObject(1),CBORObject.FromObject(algId));
protectedHeader.set(CBORObject.FromObject(4),CBORObject.FromObject(keyId));
protectedHeader.set(CBORObject.FromObject(1), CBORObject.FromObject(algId));
protectedHeader.set(CBORObject.FromObject(4), CBORObject.FromObject(keyId));
byte[] protectedHeaderBytes = protectedHeader.EncodeToBytes();

CBORObject coseObject = CBORObject.NewArray();
Expand All @@ -98,13 +105,13 @@ private byte[] genCoseUnsigned(byte[] payload,byte[] keyId,int algId) {
coseObject.Add(CBORObject.FromObject(payload));
byte[] sigDummy = new byte[0];
coseObject.Add(CBORObject.FromObject(sigDummy));
return CBORObject.FromObjectAndTag(coseObject,18).EncodeToBytes();
return CBORObject.FromObjectAndTag(coseObject, 18).EncodeToBytes();
}

private byte[] genSetSignature(byte[] coseData,byte[] signature) {
private byte[] genSetSignature(byte[] coseData, byte[] signature) {
CBORObject cborObject = CBORObject.DecodeFromBytes(coseData);
if (cborObject.getType() == CBORType.Array && cborObject.getValues().size()==4) {
cborObject.set(3,CBORObject.FromObject(signature));
if (cborObject.getType() == CBORType.Array && cborObject.getValues().size() == 4) {
cborObject.set(3, CBORObject.FromObject(signature));
} else {
throw new IllegalArgumentException("seems not to be cose");
}
Expand All @@ -117,6 +124,6 @@ private String coseToQRCode(byte[] cose) throws IOException {
byte[] coseCompressed = compessedInput.readAllBytes();
Base45Service base45Service = new DefaultBase45Service();
String coded = base45Service.encode(coseCompressed);
return "HC1:"+coded;
return "HC1:" + coded;
}
}
Loading

0 comments on commit 262b4f7

Please sign in to comment.