Skip to content

Commit 8f31788

Browse files
authored
fix cert hash (#40)
* fix cert hash * claim signature * checkstyle * tan expiration
1 parent de42c2e commit 8f31788

7 files changed

Lines changed: 232 additions & 111 deletions

File tree

src/main/java/eu/europa/ec/dgc/issuance/config/IssuanceConfigProperties.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ public class IssuanceConfigProperties {
3535
private String certAlias;
3636
private String privateKeyPassword;
3737
private String countryCode;
38+
private int tanExpirationHours = 24;
3839

3940
}

src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@
4343
import eu.europa.ec.dgc.issuance.restapi.dto.EgdcCodeData;
4444
import eu.europa.ec.dgc.issuance.restapi.dto.IssueData;
4545
import eu.europa.ec.dgc.issuance.restapi.dto.SignatureData;
46-
import java.io.ByteArrayOutputStream;
47-
import java.io.IOException;
46+
import java.nio.charset.StandardCharsets;
4847
import java.security.InvalidKeyException;
4948
import java.security.KeyFactory;
5049
import java.security.MessageDigest;
@@ -54,6 +53,7 @@
5453
import java.security.SignatureException;
5554
import java.security.spec.InvalidKeySpecException;
5655
import java.security.spec.X509EncodedKeySpec;
56+
import java.time.Duration;
5757
import java.time.ZonedDateTime;
5858
import java.time.temporal.ChronoUnit;
5959
import java.util.ArrayList;
@@ -140,12 +140,12 @@ public SignatureData finishDgci(long dgciId, IssueData issueData) throws Excepti
140140
Optional<DgciEntity> dgciEntityOpt = dgciRepository.findById(dgciId);
141141
if (dgciEntityOpt.isPresent()) {
142142
var dgciEntity = dgciEntityOpt.get();
143-
String signatureBase64 = certificateService.signHash(issueData.getHash());
144143
String tan = tanService.generateNewTan();
145144
dgciEntity.setHashedTan(tanService.hashTan(tan));
146-
dgciEntity.setCertHash(signatureBase64);
145+
dgciEntity.setCertHash(issueData.getHash());
147146
dgciRepository.saveAndFlush(dgciEntity);
148147
log.info("signed for " + dgciId);
148+
String signatureBase64 = certificateService.signHash(issueData.getHash());
149149
return new SignatureData(tan, signatureBase64);
150150
} else {
151151
log.warn("can not find dgci with id " + dgciId);
@@ -180,10 +180,11 @@ public DidDocument getDidDocument(String hash) {
180180

181181
/**
182182
* compute cose sign hash.
183+
*
183184
* @param coseMessage cose message
184185
* @return hash value
185186
*/
186-
public byte[] computeCoseSignHash(byte[] coseMessage) {
187+
public byte[] computeCoseSignHash(byte[] coseMessage) {
187188
try {
188189
CBORObject coseForSign = CBORObject.NewArray();
189190
CBORObject cborCose = CBORObject.DecodeFromBytes(coseMessage);
@@ -212,11 +213,11 @@ public ClaimResponse claimUpdate(ClaimRequest claimRequest) {
212213
}
213214

214215
/**
215-
* TODO: Add Comment.
216+
* claim dgci to wallet app.
217+
* means bind dgci with some public key from wallet app
218+
* @param claimRequest claim request
216219
*/
217-
public void claim(ClaimRequest claimRequest)
218-
throws IOException, NoSuchAlgorithmException, SignatureException,
219-
InvalidKeySpecException, InvalidKeyException {
220+
public void claim(ClaimRequest claimRequest) {
220221
if (!verifySignature(claimRequest)) {
221222
throw new WrongRequest("signature verification failed");
222223
}
@@ -237,6 +238,11 @@ public void claim(ClaimRequest claimRequest)
237238
dgciRepository.saveAndFlush(dgciEntity);
238239
throw new WrongRequest("tan mismatch");
239240
}
241+
ZonedDateTime tanExpireTime = dgciEntity.getCreatedAt()
242+
.plus(Duration.ofHours(issuanceConfigProperties.getTanExpirationHours()));
243+
if (tanExpireTime.isBefore(ZonedDateTime.now())) {
244+
throw new WrongRequest("tan expired");
245+
}
240246
dgciEntity.setClaimed(true);
241247
dgciEntity.setRetryCounter(dgciEntity.getRetryCounter() + 1);
242248
dgciEntity.setPublicKey(claimRequest.getPublicKey().getValue());
@@ -249,26 +255,47 @@ public void claim(ClaimRequest claimRequest)
249255
}
250256
}
251257

252-
private boolean verifySignature(ClaimRequest claimRequest)
253-
throws IOException, NoSuchAlgorithmException, SignatureException,
254-
InvalidKeyException, InvalidKeySpecException {
255-
ByteArrayOutputStream bos = new ByteArrayOutputStream();
256-
bos.write(claimRequest.getDgci().getBytes());
257-
bos.write(Base64.getDecoder().decode(claimRequest.getTanHash()));
258+
private boolean verifySignature(ClaimRequest claimRequest) {
258259
byte[] keyBytes = Base64.getDecoder().decode(claimRequest.getPublicKey().getValue());
259-
bos.write(keyBytes);
260260
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
261-
KeyFactory kf = KeyFactory.getInstance(claimRequest.getPublicKey().getType());
262-
PublicKey publicKey = kf.generatePublic(spec);
263-
Signature signature = Signature.getInstance(claimRequest.getSigAlg());
264-
signature.initVerify(publicKey);
265-
signature.update(bos.toByteArray());
266-
byte[] sigBytes = Base64.getDecoder().decode(claimRequest.getSignature());
267-
return signature.verify(sigBytes);
261+
KeyFactory kf;
262+
try {
263+
kf = KeyFactory.getInstance(claimRequest.getPublicKey().getType());
264+
} catch (NoSuchAlgorithmException e) {
265+
throw new WrongRequest("key type not supported: '" + claimRequest.getPublicKey().getType()
266+
+ "', try RSA or EC");
267+
}
268+
PublicKey publicKey;
269+
try {
270+
publicKey = kf.generatePublic(spec);
271+
} catch (InvalidKeySpecException e) {
272+
throw new WrongRequest("invalid key");
273+
}
274+
Signature signature;
275+
try {
276+
signature = Signature.getInstance(claimRequest.getSigAlg());
277+
} catch (NoSuchAlgorithmException e) {
278+
throw new WrongRequest("signature algorithm not supported: '" + claimRequest.getSigAlg() + "'");
279+
}
280+
StringBuilder dataToSign = new StringBuilder();
281+
dataToSign.append(claimRequest.getTanHash())
282+
.append(claimRequest.getCertHash())
283+
.append(claimRequest.getPublicKey().getValue());
284+
try {
285+
signature.initVerify(publicKey);
286+
signature.update(dataToSign.toString().getBytes(StandardCharsets.UTF_8));
287+
byte[] sigBytes = Base64.getDecoder().decode(claimRequest.getSignature());
288+
return signature.verify(sigBytes);
289+
} catch (InvalidKeyException e) {
290+
throw new WrongRequest("invalid key for signature");
291+
} catch (SignatureException e) {
292+
throw new WrongRequest("can not validity signature", e);
293+
}
268294
}
269295

270296
/**
271297
* Create edgc in backend.
298+
*
272299
* @param eudgc certificate
273300
* @return edgc qr code and tan
274301
*/

src/main/resources/application.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ issuance:
4646
certAlias: edgc_dev_ec
4747
privateKeyPassword: dgca
4848
countryCode: DE
49+
tanExpirationHours: 24
4950
dgc:
5051
gateway:
5152
connector:
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*-
2+
* ---license-start
3+
* EU Digital Green Certificate Issuance Service / dgca-issuance-service
4+
* ---
5+
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
21+
package eu.europa.ec.dgc.issuance;
22+
23+
import com.fasterxml.jackson.databind.ObjectMapper;
24+
import com.fasterxml.jackson.databind.SerializationFeature;
25+
import eu.europa.ec.dgc.issuance.restapi.dto.ClaimRequest;
26+
import eu.europa.ec.dgc.issuance.restapi.dto.PublicKey;
27+
import eu.europa.ec.dgc.issuance.service.TanService;
28+
import java.nio.charset.StandardCharsets;
29+
import java.security.InvalidKeyException;
30+
import java.security.KeyPair;
31+
import java.security.KeyPairGenerator;
32+
import java.security.NoSuchAlgorithmException;
33+
import java.security.PrivateKey;
34+
import java.security.Signature;
35+
import java.security.SignatureException;
36+
import java.util.Base64;
37+
import org.junit.Test;
38+
39+
public class GenerateWalletRequestTest {
40+
// This can be used to generate valid json structure for claim
41+
@Test
42+
public void testGenerateWalletRequest() throws Exception {
43+
TanService tanService = new TanService();
44+
45+
// Please adapt this to your certificate (the values can be get from browser network log
46+
// see POST /dgci
47+
// and PUT /dgci/{id}
48+
String dgci = "dgci:V1:DE:2e974b3b-d932-4bc9-bbae-d387f93f8bf3:edbcb873196f24be";
49+
String certHash = "mfg0MI7wPFexNkOa4n9OKojrzhe9a9lcim4JzJO3WtY=";
50+
String tan = "U7ULCYZY";
51+
String tanHash = tanService.hashTan(tan);
52+
53+
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
54+
keyPairGen.initialize(2048);
55+
KeyPair keyPair = keyPairGen.generateKeyPair();
56+
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
57+
String sigAlg = "SHA256WithRSA";
58+
59+
ClaimRequest claimRequest = new ClaimRequest();
60+
claimRequest.setDgci(dgci);
61+
claimRequest.setTanHash(tanHash);
62+
PublicKey publicKeyDTO = new PublicKey();
63+
publicKeyDTO.setType(keyPair.getPublic().getAlgorithm());
64+
publicKeyDTO.setValue(Base64.getEncoder().encodeToString(publicKeyBytes));
65+
claimRequest.setPublicKey(publicKeyDTO);
66+
67+
claimRequest.setSigAlg(sigAlg);
68+
claimRequest.setCertHash(certHash);
69+
createClaimSignature(claimRequest, keyPair.getPrivate(), sigAlg);
70+
71+
ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
72+
System.out.println(objectMapper.writeValueAsString(claimRequest));
73+
}
74+
75+
private void createClaimSignature(ClaimRequest claimRequest, PrivateKey privateKey, String sigAlg) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
76+
StringBuilder sigValue = new StringBuilder();
77+
sigValue.append(claimRequest.getTanHash())
78+
.append(claimRequest.getCertHash())
79+
.append(claimRequest.getPublicKey().getValue());
80+
Signature signature = Signature.getInstance(sigAlg);
81+
signature.initSign(privateKey);
82+
signature.update(sigValue.toString().getBytes(StandardCharsets.UTF_8));
83+
byte[] sigData = signature.sign();
84+
claimRequest.setSignature(Base64.getEncoder().encodeToString(sigData));
85+
}
86+
87+
@Test
88+
public void testGenerateWalletRequestEC() throws Exception {
89+
TanService tanService = new TanService();
90+
91+
// Please adapt this to your certificate (the values can be get from browser network log
92+
// see POST /dgci
93+
// and PUT /dgci/{id}
94+
String dgci = "dgci:V1:DE:2e974b3b-d932-4bc9-bbae-d387f93f8bf3:edbcb873196f24be";
95+
String certHash = "mfg0MI7wPFexNkOa4n9OKojrzhe9a9lcim4JzJO3WtY=";
96+
String tan = "U7ULCYZY";
97+
String tanHash = tanService.hashTan(tan);
98+
99+
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
100+
keyPairGen.initialize(256);
101+
KeyPair keyPair = keyPairGen.generateKeyPair();
102+
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
103+
String sigAlg = "SHA256withECDSA";
104+
105+
ClaimRequest claimRequest = new ClaimRequest();
106+
claimRequest.setDgci(dgci);
107+
claimRequest.setTanHash(tanHash);
108+
PublicKey publicKeyDTO = new PublicKey();
109+
publicKeyDTO.setType(keyPair.getPublic().getAlgorithm());
110+
publicKeyDTO.setValue(Base64.getEncoder().encodeToString(publicKeyBytes));
111+
claimRequest.setPublicKey(publicKeyDTO);
112+
113+
claimRequest.setSigAlg(sigAlg);
114+
claimRequest.setCertHash(certHash);
115+
createClaimSignature(claimRequest,keyPair.getPrivate(),sigAlg);
116+
117+
ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
118+
System.out.println(objectMapper.writeValueAsString(claimRequest));
119+
}
120+
121+
}

src/test/java/eu/europa/ec/dgc/issuance/GerateWalletRequestTest.java

Lines changed: 0 additions & 72 deletions
This file was deleted.

0 commit comments

Comments
 (0)