Skip to content
This repository was archived by the owner on Sep 15, 2023. It is now read-only.

Commit 728445d

Browse files
Merge pull request #6 from admin-ch/feature/VACCINECER-2278--extend-configuration-to-use-two-HSM-slots
Feature/vaccinecer 2278 extend configuration to use two hsm slots
2 parents 33802b1 + 8ac74b2 commit 728445d

35 files changed

+539
-386
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</parent>
1111
<groupId>ch.admin.bag.covidcertificate</groupId>
1212
<artifactId>cc-signing-service</artifactId>
13-
<version>1.0.0-SNAPSHOT</version>
13+
<version>4.4.3</version>
1414
<packaging>war</packaging>
1515
<name>cc-signing-service</name>
1616
<description>Service for signing Covid Certificates</description>
Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1-
# Crs keystore used for decryption
2-
crs.decryption.keyStoreSlotNumber=@crs.decryption.keyStoreSlotNumber@
3-
crs.decryption.keyStorePassword=@crs.decryption.keyStorePassword@
4-
crs.decryption.aliasSign=@crs.decryption.aliasSign@
5-
crs.decryption.aliasSignLight=@crs.decryption.aliasSignLight@
6-
crs.decryption.aliasVerify=@crs.decryption.aliasVerify@
7-
81
app.signing-service.monitor.prometheus.user=prometheus
92
app.signing-service.monitor.prometheus.password=@monitor.prometheus.password@
103

4+
## HSM SLOT AND SIGNING KEY CONFIGURATION ##
5+
6+
# passwords for both slots
7+
crs.decryption.keyStorePasswordSlot0=@crs.decryption.keyStorePasswordSlot0@
8+
crs.decryption.keyStorePasswordSlot1=@crs.decryption.keyStorePasswordSlot1@
9+
10+
# default slot (only for backwards compatibility with management-service when switching)
11+
# TODO: can be removed after August 2022 if desired
12+
crs.decryption.defaultKeyStoreSlot=@crs.decryption.defaultKeyStoreSlot@
13+
14+
15+
# slot and name of certificate used for liveness check by loadbalancer
16+
crs.decryption.pingCertificateKeyStoreSlot=@crs.decryption.pingCertificateKeyStoreSlot@
17+
app.signing-service.keystore.monitoring.liveness-test-private-key=@app.signing-service.keystore.monitoring.liveness-test-private-key@
18+
19+
# slot and name of certificate used for upload to EU gateway
20+
crs.decryption.euCertificateKeyStoreSlot=@crs.decryption.euCertificateKeyStoreSlot@
1121
app.signing-service.keystore.private-key-alias=@app.signing-service.keystore.private-key-alias@
1222
app.signing-service.keystore.signing-certificate-alias=@app.signing-service.keystore.signing-certificate-alias@
23+
24+
# slot and name of certificate used to sign light certificates
25+
crs.decryption.lightKeyStoreSlot=@crs.decryption.lightKeyStoreSlot@
26+
crs.decryption.aliasSignLight=@crs.decryption.aliasSignLight@
27+
28+
Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
package ch.admin.bag.covidcertificate.signature;
22

33

4+
import lombok.extern.slf4j.Slf4j;
45
import org.springframework.boot.SpringApplication;
56
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.core.env.Environment;
68

79

810
@SpringBootApplication
11+
@Slf4j
912
public class SigningApplication {
1013

1114
public static void main(String[] args) {
12-
SpringApplication.run(SigningApplication.class, args);
13-
}
14-
15+
Environment env = SpringApplication.run(SigningApplication.class, args).getEnvironment();
1516

17+
log.info("\n----------------------------------------------------------\n\t" +
18+
"cc-signing-service is running! \n\t" +
19+
"\n\t" +
20+
"Profile(s): \t{}" +
21+
"\n----------------------------------------------------------",
22+
(Object) env.getActiveProfiles()
23+
);
24+
}
1625
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ch.admin.bag.covidcertificate.signature.api;
22

3+
import ch.admin.bag.covidcertificate.signature.service.KeyStoreSlot;
34
import lombok.AllArgsConstructor;
45
import lombok.Getter;
56

@@ -8,5 +9,5 @@
89
public class SigningRequestDto {
910
private final String dataToSign;
1011
private final String signingKeyAlias;
11-
private final String certificateAlias;
12+
private final KeyStoreSlot keyStoreSlot;
1213
}

src/main/java/ch/admin/bag/covidcertificate/signature/api/VerifyRequestDto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ch.admin.bag.covidcertificate.signature.api;
22

3+
import ch.admin.bag.covidcertificate.signature.service.KeyStoreSlot;
34
import lombok.AllArgsConstructor;
45
import lombok.Getter;
56

@@ -9,4 +10,5 @@ public class VerifyRequestDto {
910
private String dataToSign;
1011
private String signature;
1112
private String certificateAlias;
13+
private KeyStoreSlot keyStoreSlot;
1214
}

src/main/java/ch/admin/bag/covidcertificate/signature/config/HsmConfig.java

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@
22

33
import ch.admin.bag.covidcertificate.signature.config.error.SignatureCreationException;
44
import ch.admin.bag.covidcertificate.signature.service.KeyStoreEntryReader;
5-
import ch.admin.bag.covidcertificate.signature.service.LunaSAKeyStoreProvider;
5+
import ch.admin.bag.covidcertificate.signature.service.KeyStoreSlot;
6+
import ch.admin.bag.covidcertificate.signature.service.LunaKeyStoreProvider;
67
import com.safenetinc.luna.provider.LunaProvider;
78
import lombok.extern.slf4j.Slf4j;
89
import org.springframework.beans.factory.annotation.Value;
910
import org.springframework.context.annotation.Bean;
1011
import org.springframework.context.annotation.Configuration;
1112
import org.springframework.context.annotation.Profile;
1213

13-
import java.security.*;
14+
import java.security.NoSuchAlgorithmException;
15+
import java.security.NoSuchProviderException;
16+
import java.security.Security;
17+
import java.security.Signature;
18+
import java.util.HashMap;
19+
import java.util.Map;
1420
import java.util.function.Supplier;
1521

1622
@Configuration
@@ -21,38 +27,51 @@ public class HsmConfig {
2127

2228
public static final String LUNA_PROVIDER;
2329

24-
@Value("${crs.decryption.keyStorePassword}")
25-
private String keyStorePassword;
30+
@Value("${crs.decryption.keyStorePasswordSlot0}")
31+
private String keyStorePasswordSlot0;
2632

27-
@Value("${crs.decryption.aliasSign}")
28-
private String aliasSign;
29-
30-
@Value("${crs.decryption.aliasSignLight}")
31-
private String aliasSignLight;
33+
@Value("${crs.decryption.keyStorePasswordSlot1}")
34+
private String keyStorePasswordSlot1;
3235

3336
static {
3437
try {
3538
var lunaProvider = new LunaProvider();
3639
Security.addProvider(lunaProvider);
3740
LUNA_PROVIDER = lunaProvider.getName();
38-
}catch (Exception | ExceptionInInitializerError e){
41+
} catch (Exception | ExceptionInInitializerError e) {
3942
throw new IllegalStateException("Failed to register the Luna Provider", e);
4043
}
4144
}
4245

4346
@Bean
44-
KeyStoreEntryReader keyStoreEntryReader(LunaSAKeyStoreProvider lunaSAKeyStoreProvider) {
45-
var keyStore = lunaSAKeyStoreProvider.loadKeyStore();
46-
return new KeyStoreEntryReader(lunaSAKeyStoreProvider, keyStore, keyStorePassword.toCharArray());
47+
public Map<KeyStoreSlot, KeyStoreEntryReader> keyStoreEntryReaderMap() {
48+
Map<KeyStoreSlot, KeyStoreEntryReader> map = new HashMap<>();
49+
map.put(KeyStoreSlot.SLOT_NUMBER_0, keyStoreEntryReaderSlot0());
50+
map.put(KeyStoreSlot.SLOT_NUMBER_1, keyStoreEntryReaderSlot1());
51+
return map;
52+
}
53+
54+
private KeyStoreEntryReader keyStoreEntryReaderSlot0() {
55+
KeyStoreSlot slot = KeyStoreSlot.SLOT_NUMBER_0;
56+
LunaKeyStoreProvider lunaKeyStoreProvider = new LunaKeyStoreProvider(slot, keyStorePasswordSlot0);
57+
var keyStore = lunaKeyStoreProvider.loadKeyStore();
58+
return new KeyStoreEntryReader(lunaKeyStoreProvider, keyStore, slot, keyStorePasswordSlot0.toCharArray());
59+
}
60+
61+
private KeyStoreEntryReader keyStoreEntryReaderSlot1() {
62+
KeyStoreSlot slot = KeyStoreSlot.SLOT_NUMBER_1;
63+
LunaKeyStoreProvider lunaKeyStoreProvider = new LunaKeyStoreProvider(slot, keyStorePasswordSlot1);
64+
var keyStore = lunaKeyStoreProvider.loadKeyStore();
65+
return new KeyStoreEntryReader(lunaKeyStoreProvider, keyStore, slot, keyStorePasswordSlot1.toCharArray());
4766
}
4867

4968
@Bean
5069
Supplier<Signature> signatureSupplier() {
5170
return this::getSignatureInstance;
5271
}
5372

54-
private Signature getSignatureInstance() {
55-
log.debug("signingSignature with {} and {}", SIGNING_ALGORITHM, LUNA_PROVIDER);
73+
private Signature getSignatureInstance() {
74+
log.debug("Get Signature Instance with {} and {}", SIGNING_ALGORITHM, LUNA_PROVIDER);
5675
Signature signature;
5776

5877
try {

src/main/java/ch/admin/bag/covidcertificate/signature/config/HsmMockConfig.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import ch.admin.bag.covidcertificate.signature.config.error.SignatureCreationException;
44
import ch.admin.bag.covidcertificate.signature.service.JksKeystoreProvider;
55
import ch.admin.bag.covidcertificate.signature.service.KeyStoreEntryReader;
6+
import ch.admin.bag.covidcertificate.signature.service.KeyStoreSlot;
67
import lombok.extern.slf4j.Slf4j;
78
import org.springframework.context.annotation.Bean;
89
import org.springframework.context.annotation.Configuration;
910
import org.springframework.context.annotation.Profile;
1011

1112
import java.security.NoSuchAlgorithmException;
1213
import java.security.Signature;
14+
import java.util.HashMap;
15+
import java.util.Map;
1316
import java.util.function.Supplier;
1417

1518
@Configuration
@@ -18,25 +21,43 @@
1821
public class HsmMockConfig {
1922
private static final String SIGNING_ALGORITHM = "SHA512withRSA";
2023

21-
private static final String KEYSTORE_RESOURCE = "keystore.jks";
24+
private static final String KEYSTORE_RESOURCE_SLOT_0 = "keystore_slot_0.jks";
25+
private static final String KEYSTORE_RESOURCE_SLOT_1 = "keystore_slot_1.jks";
2226

23-
private static final String KEY_STORE_PASSWORD = "secret";
27+
private static final String KEY_STORE_PASSWORD_SLOT_0 = "secret";
28+
private static final String KEY_STORE_PASSWORD_SLOT_1 = "secret";
2429

2530
@Bean
26-
public KeyStoreEntryReader keyStoreEntryReader() {
27-
log.info("--------------> Login MOCK HSM");
28-
var keystoreProvider = new JksKeystoreProvider(KEYSTORE_RESOURCE, KEY_STORE_PASSWORD);
31+
public Map<KeyStoreSlot, KeyStoreEntryReader> keyStoreEntryReaderMap() {
32+
Map<KeyStoreSlot, KeyStoreEntryReader> map = new HashMap<>();
33+
map.put(KeyStoreSlot.SLOT_NUMBER_0, keyStoreEntryReaderSlot0());
34+
map.put(KeyStoreSlot.SLOT_NUMBER_1, keyStoreEntryReaderSlot1());
35+
return map;
36+
}
37+
38+
private KeyStoreEntryReader keyStoreEntryReaderSlot0() {
39+
KeyStoreSlot slot = KeyStoreSlot.SLOT_NUMBER_0;
40+
log.info("--------------> Login MOCK HSM slot {}", slot);
41+
var keystoreProvider = new JksKeystoreProvider(KEYSTORE_RESOURCE_SLOT_0, KEY_STORE_PASSWORD_SLOT_0);
42+
var keyStore = keystoreProvider.loadKeyStore();
43+
return new KeyStoreEntryReader(keystoreProvider, keyStore, slot, KEY_STORE_PASSWORD_SLOT_0.toCharArray());
44+
}
45+
46+
private KeyStoreEntryReader keyStoreEntryReaderSlot1() {
47+
KeyStoreSlot slot = KeyStoreSlot.SLOT_NUMBER_1;
48+
log.info("--------------> Login MOCK HSM slot {}", slot);
49+
var keystoreProvider = new JksKeystoreProvider(KEYSTORE_RESOURCE_SLOT_1, KEY_STORE_PASSWORD_SLOT_1);
2950
var keyStore = keystoreProvider.loadKeyStore();
30-
return new KeyStoreEntryReader(keystoreProvider, keyStore, KEY_STORE_PASSWORD.toCharArray());
51+
return new KeyStoreEntryReader(keystoreProvider, keyStore, slot, KEY_STORE_PASSWORD_SLOT_1.toCharArray());
3152
}
3253

3354
@Bean
3455
Supplier<Signature> signatureSupplier() {
3556
return this::getSignatureInstance;
3657
}
3758

38-
private Signature getSignatureInstance() {
39-
log.debug("signingSignature with {}", SIGNING_ALGORITHM);
59+
private Signature getSignatureInstance() {
60+
log.debug("Get Signature Instance with {}", SIGNING_ALGORITHM);
4061
Signature signature;
4162

4263
try {

src/main/java/ch/admin/bag/covidcertificate/signature/service/JksKeystoreProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public KeyStore loadKeyStore() {
2929

3030
return keyStore;
3131
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
32-
throw new IllegalStateException("Could not open the " + keystoreResource + " keystore: " + e.getMessage(), e);
32+
throw new IllegalStateException(String.format("Could not open the keystore for resource %s: %s", keystoreResource, e.getMessage()), e);
3333
}
3434
}
3535

src/main/java/ch/admin/bag/covidcertificate/signature/service/KeyStoreEntryReader.java

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,24 @@
1010

1111
@Slf4j
1212
public class KeyStoreEntryReader {
13-
private static final String MOCK_AND_TEST_PASS = "secret";
13+
14+
/** An email alert is triggered when this message appears in the logs. */
15+
private static final String WARNING_KEYSTORE_LOST_WITH_MONITORING = "Connection to Keystore was lost. Reloading keystore.";
1416

1517
private final KeyStoreProvider keyStoreProvider;
1618
private KeyStore keyStore;
19+
private final KeyStoreSlot slot;
1720
private final char[] keyPassword;
1821

19-
public KeyStoreEntryReader(KeyStoreProvider keyStoreProvider, KeyStore keyStore, char[] keyPassword) {
22+
public KeyStoreEntryReader(KeyStoreProvider keyStoreProvider, KeyStore keyStore, KeyStoreSlot slot, char[] keyPassword) {
2023
this.keyStoreProvider = keyStoreProvider;
2124
this.keyStore = keyStore;
25+
this.slot = slot;
2226
this.keyPassword = keyPassword;
2327
}
2428

25-
public KeyStoreEntryReader(KeyStoreProvider keyStoreProvider, KeyStore keyStore) {
26-
this.keyStoreProvider = keyStoreProvider;
27-
this.keyStore = keyStore;
28-
this.keyPassword = MOCK_AND_TEST_PASS.toCharArray();
29-
}
30-
31-
public PrivateKey getPrivateKey(String alias) {
32-
33-
log.debug("GET PRIVATE KEY {}", alias);
29+
public PrivateKey getPrivateKey(String alias) {
30+
log.debug("GET PRIVATE KEY {} from slot {}", alias, slot.getSlotNumber());
3431
rebuildKeystoreIfNeeded(alias);
3532

3633
try {
@@ -39,40 +36,37 @@ public PrivateKey getPrivateKey(String alias) {
3936
return privateKey;
4037
}
4138
} catch (Exception e) {
42-
throw new SecurityException("Failed to retrieve the key for alias " + alias + " from the keystore: ", e);
43-
}
44-
throw new SecurityException("The KeyStore has no privateKey for alias: " + alias);
45-
}
46-
47-
private void rebuildKeystoreIfNeeded(String alias){
48-
var warning = "Connection to Keystore was lost. Reloading keystore.";
49-
try {
50-
if(!keyStore.containsAlias(alias)) {
51-
log.warn(warning);
52-
keyStore = keyStoreProvider.loadKeyStore();
53-
}
54-
}catch (KeyStoreException| RuntimeException e){
55-
log.warn(warning);
56-
if(log.isDebugEnabled()) {
57-
log.debug(warning, e);
58-
}
59-
keyStore = keyStoreProvider.loadKeyStore();
39+
throw new SecurityException(String.format("KeyStore slot %s: Failed to retrieve the privateKey for alias %s", slot.getSlotNumber(), alias), e);
6040
}
41+
throw new SecurityException(String.format("KeyStore slot %s: Has no privateKey for alias %s", slot.getSlotNumber(), alias));
6142
}
6243

6344
public X509Certificate getCertificate(String alias) {
64-
log.debug("GET Certificate {}", alias);
45+
log.debug("GET Certificate {} from slot {}", alias, slot.getSlotNumber());
6546
rebuildKeystoreIfNeeded(alias);
6647

6748
try {
49+
6850
var certificate = keyStore.getCertificate(alias);
6951
if (certificate != null) {
7052
return (X509Certificate) certificate;
7153
}
7254
} catch (Exception e) {
73-
throw new CertificateNotFoundException("Failed to retrieve the certificate from the keyStore for alias: " + alias, e);
55+
throw new CertificateNotFoundException(String.format("KeyStore slot %s: Failed to retrieve the certificate for alias %s", slot.getSlotNumber(), alias), e);
7456
}
75-
throw new CertificateNotFoundException("The KeyStore has no certificate for alias: " + alias);
57+
throw new CertificateNotFoundException(String.format("KeyStore slot %s: Has no certificate for alias %s", slot.getSlotNumber(), alias));
7658
}
7759

60+
private void rebuildKeystoreIfNeeded(String alias) {
61+
try {
62+
if (!keyStore.containsAlias(alias)) {
63+
log.info("Alias {} not found. Reloading keystore for slot {}.", alias, slot);
64+
keyStore = keyStoreProvider.loadKeyStore();
65+
}
66+
} catch (KeyStoreException | RuntimeException e) {
67+
log.warn("Could not reload keystore for slot {}, trying again", slot, e);
68+
log.warn(WARNING_KEYSTORE_LOST_WITH_MONITORING);
69+
keyStore = keyStoreProvider.loadKeyStore();
70+
}
71+
}
7872
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package ch.admin.bag.covidcertificate.signature.service;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* The HSM can be configured to use multiple slots.
7+
* Each slot can be viewed like a separate HSM and has to be accessed separately.
8+
*/
9+
public enum KeyStoreSlot {
10+
SLOT_NUMBER_0(0),
11+
SLOT_NUMBER_1(1);
12+
13+
private final Integer slotNumber;
14+
15+
KeyStoreSlot(Integer slotNumber) {
16+
this.slotNumber = slotNumber;
17+
}
18+
19+
public Integer getSlotNumber() {
20+
return this.slotNumber;
21+
}
22+
23+
public static KeyStoreSlot fromSlotNumber(Integer slotNumber) {
24+
for (KeyStoreSlot slot : KeyStoreSlot.values()) {
25+
if (Objects.equals(slot.getSlotNumber(), slotNumber)) {
26+
return slot;
27+
}
28+
}
29+
throw new IllegalArgumentException(String.format("SlotNumber %s is not a valid value.", slotNumber));
30+
}
31+
}

0 commit comments

Comments
 (0)