diff --git a/pom.xml b/pom.xml index 7200545..2a363fd 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,13 @@ bcpkix-jdk15on ${bcpkix.version} + + com.sap.cloud.sdk + sdk-bom + 3.43.0 + pom + import + @@ -256,6 +263,15 @@ java-cfenv-boot 2.3.0 + + com.google.code.gson + gson + 2.8.6 + + + com.sap.cloud.sdk.cloudplatform + scp-cf + diff --git a/src/main/java/eu/europa/ec/dgc/issuance/config/btp/SapCredentialStoreCfEnvProcessor.java b/src/main/java/eu/europa/ec/dgc/issuance/config/btp/SapCredentialStoreCfEnvProcessor.java new file mode 100644 index 0000000..f34454e --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/config/btp/SapCredentialStoreCfEnvProcessor.java @@ -0,0 +1,67 @@ +package eu.europa.ec.dgc.issuance.config.btp; + +import io.pivotal.cfenv.core.CfCredentials; +import io.pivotal.cfenv.core.CfService; +import io.pivotal.cfenv.spring.boot.CfEnvProcessor; +import io.pivotal.cfenv.spring.boot.CfEnvProcessorProperties; +import java.util.Map; + +/** + * Custom implementation of {@link CfEnvProcessor} for reading the SAP credential store parameters from the + * VCAP_SERVICES environment variable and making them available as properties in the spring context. + *

+ * The following properties are available in the context after the processor is done: + * + * + * + * + * @see CfEnvProcessor + */ +public class SapCredentialStoreCfEnvProcessor implements CfEnvProcessor { + + private static final String CRED_STORE_SCHEME = "credstore"; + private static final String CRED_STORE_PROPERTY_PREFIX = "sap.btp.credstore"; + + @Override + public boolean accept(CfService service) { + return service.existsByTagIgnoreCase("credstore", "securestore", "keystore", "credentials") + || service.existsByLabelStartsWith("credstore") || service.existsByUriSchemeStartsWith(CRED_STORE_SCHEME); + } + + @Override + public void process(CfCredentials cfCredentials, Map properties) { + properties.put(CRED_STORE_PROPERTY_PREFIX + ".url", cfCredentials.getString("url")); + properties.put(CRED_STORE_PROPERTY_PREFIX + ".password", cfCredentials.getString("password")); + properties.put(CRED_STORE_PROPERTY_PREFIX + ".username", cfCredentials.getString("username")); + + @SuppressWarnings("unchecked") + Map encryption = (Map) cfCredentials.getMap().get("encryption"); + if (encryption == null) { + // Encryption features have been disabled on this BTP instance. + properties.put(CRED_STORE_PROPERTY_PREFIX + ".clientPrivateKey", "encryption-disabled"); + properties.put(CRED_STORE_PROPERTY_PREFIX + ".serverPublicKey", "encryption-disabled"); + return; + } + + String clientPrivateKey = encryption.get("client_private_key").toString(); + String serverPublicKey = encryption.get("server_public_key").toString(); + + properties.put(CRED_STORE_PROPERTY_PREFIX + ".clientPrivateKey", clientPrivateKey); + properties.put(CRED_STORE_PROPERTY_PREFIX + ".serverPublicKey", serverPublicKey); + } + + @Override + public CfEnvProcessorProperties getProperties() { + return CfEnvProcessorProperties.builder() + .propertyPrefixes(CRED_STORE_PROPERTY_PREFIX) + .serviceName("CredentialStore") + .build(); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherService.java b/src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherService.java index e5613b5..8b64878 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherService.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherService.java @@ -1,36 +1,8 @@ package eu.europa.ec.dgc.issuance.service; - -import eu.europa.ec.dgc.gateway.connector.DgcGatewayUploadConnector; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -@Component -@Slf4j -@RequiredArgsConstructor -public class CertKeyPublisherService { - private final CertificateService certificateService; - private final Optional dgcGatewayUploadConnector; - +public interface CertKeyPublisherService { /** - * publish signing certificate to gateway. + * Publishes the signing certificate to the DGC gateway. */ - public void publishKey() { - if (dgcGatewayUploadConnector.isPresent()) { - log.info("start publish certificate to gateway"); - DgcGatewayUploadConnector connector = dgcGatewayUploadConnector.get(); - try { - connector.uploadTrustedCertificate(certificateService.getCertficate()); - log.info("certificate uploaded to gateway"); - } catch (DgcGatewayUploadConnector.DgcCertificateUploadException e) { - log.error("can not upload certificate to gateway",e); - throw new DdcGatewayException("error during gateway connector communication",e); - } - } else { - log.warn("can not publish certificate to gateway, because the gateway connector is not enabled"); - throw new DdcGatewayException("gateway connector is configured as disabled"); - } - } + void publishKey(); } diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherServiceImpl.java b/src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherServiceImpl.java new file mode 100644 index 0000000..96bcb3e --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherServiceImpl.java @@ -0,0 +1,37 @@ +package eu.europa.ec.dgc.issuance.service; + + +import eu.europa.ec.dgc.gateway.connector.DgcGatewayUploadConnector; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component +@Profile("!btp") +@Slf4j +@RequiredArgsConstructor +public class CertKeyPublisherServiceImpl implements CertKeyPublisherService { + private final CertificateService certificateService; + private final Optional dgcGatewayUploadConnector; + + @Override + public void publishKey() { + if (dgcGatewayUploadConnector.isPresent()) { + log.info("start publish certificate to gateway"); + DgcGatewayUploadConnector connector = dgcGatewayUploadConnector.get(); + try { + connector.uploadTrustedCertificate(certificateService.getCertficate()); + log.info("certificate uploaded to gateway"); + } catch (DgcGatewayUploadConnector.DgcCertificateUploadException e) { + log.error("can not upload certificate to gateway", e); + throw new DdcGatewayException("error during gateway connector communication", e); + } + } else { + log.warn("can not publish certificate to gateway, because the gateway connector is not enabled"); + throw new DdcGatewayException("gateway connector is configured as disabled"); + } + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/CertificateService.java b/src/main/java/eu/europa/ec/dgc/issuance/service/CertificateService.java index 0c27f61..9d888c7 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/CertificateService.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/CertificateService.java @@ -31,16 +31,24 @@ 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; import org.springframework.stereotype.Component; @Component @Slf4j -@RequiredArgsConstructor public class CertificateService { private final CertificatePrivateKeyProvider certificatePrivateKeyProvider; private final SigningService signingService; private byte[] kid; + @Autowired + public CertificateService(@Qualifier("issuerKeyProvider") CertificatePrivateKeyProvider + certificatePrivateKeyProvider, SigningService signingService) { + this.certificatePrivateKeyProvider = certificatePrivateKeyProvider; + this.signingService = signingService; + } + /** * compute kid. * key identifier needed for cose @@ -48,7 +56,7 @@ public class CertificateService { @PostConstruct public void computeKid() { CertificateUtils certificateUtils = new CertificateUtils(); - String kidBase64 = certificateUtils.getCertKid((X509Certificate)getCertficate()); + String kidBase64 = certificateUtils.getCertKid(getCertficate()); kid = Base64.getDecoder().decode(kidBase64); } diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/DgciGenerator.java b/src/main/java/eu/europa/ec/dgc/issuance/service/DgciGenerator.java index 77d4806..7dc90cb 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/DgciGenerator.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/DgciGenerator.java @@ -21,8 +21,8 @@ package eu.europa.ec.dgc.issuance.service; import eu.europa.ec.dgc.issuance.config.IssuanceConfigProperties; +import eu.europa.ec.dgc.issuance.utils.DgciUtil; import java.math.BigInteger; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -41,15 +41,7 @@ public class DgciGenerator { public String newDgci() { StringBuilder sb = new StringBuilder(); sb.append(issuanceConfigProperties.getDgciPrefix()).append(':'); - // use uuid but encode to 0-9A-Z charset - UUID uuid = UUID.randomUUID(); - ByteBuffer bb = ByteBuffer.wrap(new byte[16]); - bb.putLong(uuid.getMostSignificantBits()); - bb.putLong(uuid.getLeastSignificantBits()); - BigInteger bint = new BigInteger(1, bb.array()); - int radix = 10 + ('Z' - 'A'); - String randomUuidEncoded = bint.toString(radix).toUpperCase(); - sb.append(randomUuidEncoded); + sb.append(DgciUtil.encodeDgci(UUID.randomUUID())); String checkSum = createDgciCheckSum(sb.toString()); sb.append(':').append(checkSum); return sb.toString(); diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java b/src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java index 71ffb89..4176b32 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/DgciService.java @@ -49,6 +49,7 @@ import eu.europa.ec.dgc.issuance.restapi.dto.EgdcCodeData; import eu.europa.ec.dgc.issuance.restapi.dto.IssueData; import eu.europa.ec.dgc.issuance.restapi.dto.SignatureData; +import eu.europa.ec.dgc.issuance.utils.HashUtil; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -83,7 +84,6 @@ public enum DgciStatus { } private final DgciRepository dgciRepository; - private final TanService tanService; private final CertificateService certificateService; private final IssuanceConfigProperties issuanceConfigProperties; private final DgciGenerator dgciGenerator; @@ -107,7 +107,7 @@ public DgciIdentifier initDgci(DgciInit dgciInit) { String dgci = generateDgci(); dgciEntity.setDgci(dgci); - dgciEntity.setDgciHash(dgciHash(dgci)); + dgciEntity.setDgciHash(HashUtil.sha256Base64(dgci)); dgciEntity.setGreenCertificateType(dgciInit.getGreenCertificateType()); log.info("init dgci: {} id: {}", dgci, dgciEntity.getId()); @@ -129,16 +129,6 @@ public DgciIdentifier initDgci(DgciInit dgciInit) { ); } - private String dgciHash(String dgci) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - final byte[] hashBytes = digest.digest(dgci.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(hashBytes); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - } - @NotNull private String generateDgci() { return dgciGenerator.newDgci(); @@ -151,17 +141,17 @@ private String generateDgci() { * @param issueData issueData * @return signature data */ - public SignatureData finishDgci(long dgciId, IssueData issueData) throws Exception { + public SignatureData finishDgci(long dgciId, IssueData issueData) { Optional dgciEntityOpt = dgciRepository.findById(dgciId); if (dgciEntityOpt.isPresent()) { var dgciEntity = dgciEntityOpt.get(); - String tan = tanService.generateNewTan(); - dgciEntity.setHashedTan(tanService.hashTan(tan)); + Tan tan = Tan.create(); + dgciEntity.setHashedTan(tan.getHashedTan()); dgciEntity.setCertHash(issueData.getHash()); dgciRepository.saveAndFlush(dgciEntity); log.info("signed for " + dgciId); String signatureBase64 = certificateService.signHash(issueData.getHash()); - return new SignatureData(tan, signatureBase64); + return new SignatureData(tan.getRawTan(), signatureBase64); } else { log.warn("can not find dgci with id " + dgciId); throw new DgciNotFound("dgci with id " + dgciId + " not found"); @@ -264,13 +254,13 @@ public ClaimResponse claim(ClaimRequest claimRequest) { dgciEntity.setClaimed(true); dgciEntity.setRetryCounter(dgciEntity.getRetryCounter() + 1); dgciEntity.setPublicKey(asJwk(claimRequest.getPublicKey())); - String newTan = tanService.generateNewTan(); - dgciEntity.setHashedTan(tanService.hashTan(newTan)); + Tan newTan = Tan.create(); + dgciEntity.setHashedTan(newTan.getHashedTan()); dgciEntity.setRetryCounter(0); log.info("dgci {} claimed", dgciEntity.getDgci()); dgciRepository.saveAndFlush(dgciEntity); ClaimResponse claimResponse = new ClaimResponse(); - claimResponse.setTan(newTan); + claimResponse.setTan(newTan.getRawTan()); return claimResponse; } else { log.info("can not find dgci {}", claimRequest.getDgci()); @@ -376,14 +366,14 @@ public EgdcCodeData createEdgc(Eudgc eudgc) { EgdcCodeData egdcCodeData = new EgdcCodeData(); egdcCodeData.setQrcCode(chainResult.getStep5Prefixed()); egdcCodeData.setDgci(dgci); - String tan = tanService.generateNewTan(); - egdcCodeData.setTan(tan); + Tan ta = Tan.create(); + egdcCodeData.setTan(ta.getRawTan()); DgciEntity dgciEntity = new DgciEntity(); dgciEntity.setDgci(dgci); dgciEntity.setCertHash(Base64.getEncoder().encodeToString(computeCoseSignHash(chainResult.getStep2Cose()))); - dgciEntity.setDgciHash(dgciHash(dgci)); - dgciEntity.setHashedTan(tanService.hashTan(tan)); + dgciEntity.setDgciHash(HashUtil.sha256Base64(dgci)); + dgciEntity.setHashedTan(ta.getHashedTan()); dgciEntity.setGreenCertificateType(greenCertificateType); dgciEntity.setCreatedAt(ZonedDateTime.now()); dgciEntity.setExpiresAt(ZonedDateTime.now().plus(expirationService.expirationForType(greenCertificateType))); diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/Tan.java b/src/main/java/eu/europa/ec/dgc/issuance/service/Tan.java new file mode 100644 index 0000000..2894789 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/Tan.java @@ -0,0 +1,55 @@ +package eu.europa.ec.dgc.issuance.service; + +import eu.europa.ec.dgc.issuance.utils.HashUtil; +import java.security.SecureRandom; +import org.apache.commons.lang3.RandomStringUtils; + +public final class Tan { + + private static final int TAN_LENGTH = 8; + private static final String HASH_ALGORITHM = "SHA-256"; + private static final char[] CHAR_SET_FOR_TAN = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".toCharArray(); + + private String rawTan; + private String hashedTan; + + private Tan() { + } + + /** + * Create new TAN object with a TAN and the hash of the TAN. The TAN is constructed from a charset consisting + * of A-Z (exclcuding I and O) and 2-9. + * + * @return the newly created TAN object + */ + public static Tan create() { + Tan retVal = new Tan(); + retVal.rawTan = retVal.generateNewTan(); + retVal.hashedTan = HashUtil.sha256Base64(retVal.rawTan); + return retVal; + } + + private String generateNewTan() { + SecureRandom random = new SecureRandom(); + long rnd = random.nextLong(); + int radixLen = CHAR_SET_FOR_TAN.length; + StringBuilder tan = new StringBuilder(); + while (tan.length() < TAN_LENGTH) { + if (rnd == 0) { + rnd = random.nextLong(); + continue; + } + tan.append(CHAR_SET_FOR_TAN[Math.abs((int) (rnd % radixLen))]); + rnd /= radixLen; + } + return tan.toString(); + } + + public String getRawTan() { + return rawTan; + } + + public String getHashedTan() { + return hashedTan; + } +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/TanService.java b/src/main/java/eu/europa/ec/dgc/issuance/service/TanService.java deleted file mode 100644 index f29f6f6..0000000 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/TanService.java +++ /dev/null @@ -1,91 +0,0 @@ -/*- - * ---license-start - * EU Digital Green Certificate Issuance Service / dgca-issuance-service - * --- - * Copyright (C) 2021 T-Systems International GmbH and all other contributors - * --- - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ---license-end - */ - -package eu.europa.ec.dgc.issuance.service; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Base64; -import java.util.Random; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class TanService { - - private final Random random = new SecureRandom(); - private final char[] charSet; - private static final int TAN_LENGTH = 8; - - /** - * Constructs TanService with Whitelist of allowed TAN-Chars. - */ - public TanService() { - StringBuilder chars = new StringBuilder(); - for (char i = '0'; i <= '9'; i++) { - chars.append(i); - } - for (char i = 'A'; i <= 'Z'; i++) { - if (i != 'I' && i != '0') { - chars.append(i); - } - } - charSet = chars.toString().toCharArray(); - } - - /** - * Generates a new TAN. - * The TAN has a length of 8 characters. The generated TAN does not include letter I and O. - * - * @return TAN String. - */ - public String generateNewTan() { - long rnd = random.nextLong(); - int radixLen = charSet.length; - StringBuilder tan = new StringBuilder(); - while (tan.length() < TAN_LENGTH) { - if (rnd == 0) { - rnd = random.nextLong(); - continue; - } - tan.append(charSet[Math.abs((int) (rnd % radixLen))]); - rnd /= radixLen; - } - return tan.toString(); - } - - /** - * compute tan hash. - * - * @return TAN hash - */ - public String hashTan(String tan) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - final byte[] hashBytes = digest.digest(tan.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(hashBytes); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - } -} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpAbstractKeyProvider.java b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpAbstractKeyProvider.java new file mode 100644 index 0000000..6b93866 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpAbstractKeyProvider.java @@ -0,0 +1,71 @@ +package eu.europa.ec.dgc.issuance.service.impl; + +import eu.europa.ec.dgc.issuance.service.CertificatePrivateKeyProvider; +import eu.europa.ec.dgc.issuance.utils.btp.CredentialStore; +import eu.europa.ec.dgc.issuance.utils.btp.SapCredential; +import java.io.ByteArrayInputStream; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

Abstract class with interfaces to the SAP BTP {@link CredentialStore}. It provides methods to get certificates + * as well as private keys from the credential store. Implementations of {@link CertificatePrivateKeyProvider} + * inheriting from this abstract class do not have to implement a connection to the credential store themselves.

+ *

Note: Keys in the credential store are supposed to be in X.509 or RSA format and base64 encoded. Raw keys + * will be stripped off line breaks and -----BEGIN / END KEY----- phrases.

+ */ +public abstract class BtpAbstractKeyProvider implements CertificatePrivateKeyProvider { + + private static final Logger log = LoggerFactory.getLogger(BtpAbstractKeyProvider.class); + + protected final CredentialStore credentialStore; + + public BtpAbstractKeyProvider(CredentialStore credentialStore) { + this.credentialStore = credentialStore; + } + + protected Certificate getCertificateFromStore(String certName) { + SapCredential cert = credentialStore.getKeyByName(certName); + String certContent = cleanKeyString(cert.getValue()); + + try { + byte[] certDecoded = Base64.getDecoder().decode(certContent); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return certFactory.generateCertificate(new ByteArrayInputStream(certDecoded)); + } catch (CertificateException e) { + log.error("Error building certificate: {}.", e.getMessage()); + throw new RuntimeException(e); + } + } + + protected PrivateKey getPrivateKeyFromStore(String keyName) { + SapCredential key = credentialStore.getKeyByName(keyName); + String keyContent = cleanKeyString(key.getValue()); + + try { + KeyFactory kf = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyContent)); + return kf.generatePrivate(pkcs8EncodedKeySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + log.error("Error building private key: {}", e.getMessage()); + throw new RuntimeException(e); + } + } + + private String cleanKeyString(String rawKey) { + return rawKey.replaceAll("\\n", "") + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replace("-----END PRIVATE KEY-----", ""); + } +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpCertKeyPublisherServiceImpl.java b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpCertKeyPublisherServiceImpl.java new file mode 100644 index 0000000..2b1ec6c --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpCertKeyPublisherServiceImpl.java @@ -0,0 +1,96 @@ +package eu.europa.ec.dgc.issuance.service.impl; + +import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import eu.europa.ec.dgc.issuance.service.CertKeyPublisherService; +import eu.europa.ec.dgc.issuance.service.CertificatePrivateKeyProvider; +import eu.europa.ec.dgc.signing.SignedCertificateMessageBuilder; +import eu.europa.ec.dgc.utils.CertificateUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.StringEntity; +import org.bouncycastle.cert.X509CertificateHolder; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +/** + * Publishes the issuer's public certificate to the DGC gateway. The public certificate will be signed with the upload + * key provided by the upload key provider. + * + * @see BtpUploadKeyProviderImpl + */ +@Component +@Profile("btp") +@Slf4j +public class BtpCertKeyPublisherServiceImpl implements CertKeyPublisherService { + + private static final String DGCG_DESTINATION = "dgcg-destination"; + private static final String DGCG_UPLOAD_ENDPOINT = "/signerCertificate"; + + private final CertificatePrivateKeyProvider uploadKeyProvider; + private final CertificatePrivateKeyProvider issuerKeyProvider; + private final CertificateUtils certificateUtils; + + /** + * Initializes the publisher service with all key provider and utilities needed for uploading certificates to + * the gateway. + * + * @param uploadKeyProvider the upload certificate needed to sign the request + * @param issuerKeyProvider the issuer certificate beeing uploaded + * @param certificateUtils utilities to convert different certificate formats + */ + public BtpCertKeyPublisherServiceImpl( + @Qualifier("uploadKeyProvider") CertificatePrivateKeyProvider uploadKeyProvider, + @Qualifier("issuerKeyProvider") CertificatePrivateKeyProvider issuerKeyProvider, + CertificateUtils certificateUtils) { + this.uploadKeyProvider = uploadKeyProvider; + this.issuerKeyProvider = issuerKeyProvider; + this.certificateUtils = certificateUtils; + } + + @Override + public void publishKey() { + log.debug("Uploading key to gateway."); + HttpDestination httpDestination = DestinationAccessor.getDestination(DGCG_DESTINATION).asHttp(); + HttpClient httpClient = HttpClientAccessor.getHttpClient(httpDestination); + + try { + X509CertificateHolder issuerCertHolder = certificateUtils + .convertCertificate((X509Certificate) issuerKeyProvider.getCertificate()); + X509CertificateHolder uploadCertHolder = certificateUtils + .convertCertificate((X509Certificate) uploadKeyProvider.getCertificate()); + + String payload = new SignedCertificateMessageBuilder() + .withPayloadCertificate(issuerCertHolder) + .withSigningCertificate(uploadCertHolder, uploadKeyProvider.getPrivateKey()).buildAsString(); + + HttpUriRequest postRequest = RequestBuilder.post(DGCG_UPLOAD_ENDPOINT) + .addHeader("Content-type", "application/cms") + .setEntity(new StringEntity(payload, StandardCharsets.UTF_8)) + .build(); + + HttpResponse response = httpClient.execute(postRequest); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED) { + log.info("Successfully upload certificate to gateway."); + } else { + log.warn("Gateway returned 'HTTP {}: {}'.", response.getStatusLine().getStatusCode(), + response.getStatusLine().getReasonPhrase()); + } + + } catch (CertificateEncodingException | IOException e) { + log.error("Error while upload certificate to gateway: '{}'.", e.getMessage()); + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpIssuerKeyProviderImpl.java b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpIssuerKeyProviderImpl.java new file mode 100644 index 0000000..35f2cab --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpIssuerKeyProviderImpl.java @@ -0,0 +1,34 @@ +package eu.europa.ec.dgc.issuance.service.impl; + +import eu.europa.ec.dgc.issuance.utils.btp.CredentialStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Slf4j +@Component("issuerKeyProvider") +@Profile("btp") +public class BtpIssuerKeyProviderImpl extends BtpAbstractKeyProvider { + + private static final String ISSUER_KEY_NAME = "issuer-key"; + private static final String ISSUER_CERT_NAME = "issuer-cert"; + + @Autowired + public BtpIssuerKeyProviderImpl(CredentialStore credentialStore) { + super(credentialStore); + } + + @Override + public Certificate getCertificate() { + return this.getCertificateFromStore(ISSUER_CERT_NAME); + } + + @Override + public PrivateKey getPrivateKey() { + return getPrivateKeyFromStore(ISSUER_KEY_NAME); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpUploadKeyProviderImpl.java b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpUploadKeyProviderImpl.java new file mode 100644 index 0000000..14581a1 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpUploadKeyProviderImpl.java @@ -0,0 +1,34 @@ +package eu.europa.ec.dgc.issuance.service.impl; + +import eu.europa.ec.dgc.issuance.utils.btp.CredentialStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component("uploadKeyProvider") +@Profile("btp") +@Slf4j +public class BtpUploadKeyProviderImpl extends BtpAbstractKeyProvider { + + private static final String UPLOAD_KEY_NAME = "upload-key"; + private static final String UPLOAD_CERT_NAME = "upload-cert"; + + @Autowired + public BtpUploadKeyProviderImpl(CredentialStore credentialStore) { + super(credentialStore); + } + + @Override + public Certificate getCertificate() { + return getCertificateFromStore(UPLOAD_CERT_NAME); + } + + @Override + public PrivateKey getPrivateKey() { + return getPrivateKeyFromStore(UPLOAD_KEY_NAME); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/service/impl/CertificatePrivateKeyProviderImpl.java b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/CertificatePrivateKeyProviderImpl.java index 662e150..8fb2ff9 100644 --- a/src/main/java/eu/europa/ec/dgc/issuance/service/impl/CertificatePrivateKeyProviderImpl.java +++ b/src/main/java/eu/europa/ec/dgc/issuance/service/impl/CertificatePrivateKeyProviderImpl.java @@ -22,9 +22,11 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; -@Component +@Component("issuerKeyProvider") +@Profile("!btp") @Slf4j @RequiredArgsConstructor public class CertificatePrivateKeyProviderImpl implements CertificatePrivateKeyProvider { diff --git a/src/main/java/eu/europa/ec/dgc/issuance/utils/DgciUtil.java b/src/main/java/eu/europa/ec/dgc/issuance/utils/DgciUtil.java new file mode 100644 index 0000000..75b9e1b --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/utils/DgciUtil.java @@ -0,0 +1,24 @@ +package eu.europa.ec.dgc.issuance.utils; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.UUID; + +public class DgciUtil { + + /** + * Encode UUID to charset of A-Z and 0-9. + * + * @param uuid the UUID to hash + * @return the hashed UUID + */ + public static String encodeDgci(UUID uuid) { + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + BigInteger bint = new BigInteger(1, bb.array()); + int radix = 10 + ('Z' - 'A'); + return bint.toString(radix).toUpperCase(); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/utils/HashUtil.java b/src/main/java/eu/europa/ec/dgc/issuance/utils/HashUtil.java new file mode 100644 index 0000000..d2b20a1 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/utils/HashUtil.java @@ -0,0 +1,26 @@ +package eu.europa.ec.dgc.issuance.utils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +public class HashUtil { + + /** + * Generates a SHA-256 hash and returns it as Base64 encoded string. + * + * @param raw the raw input + * @return the Base64 encode hash + */ + public static String sha256Base64(String raw) { + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + final byte[] hashBytes = digest.digest(raw.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(hashBytes); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStore.java b/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStore.java new file mode 100644 index 0000000..f966fa0 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStore.java @@ -0,0 +1,45 @@ +package eu.europa.ec.dgc.issuance.utils.btp; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +@Profile("btp") +public class CredentialStore { + + private static final Logger log = LoggerFactory.getLogger(CredentialStore.class); + + @Value("${sap.btp.credstore.url}") + private String url; + + private final CredentialStoreCryptoUtil cryptoUtil; + + private final RestTemplate restTemplate; + + @Autowired + public CredentialStore(CredentialStoreCryptoUtil cryptoUtil, RestTemplate restTemplate) { + this.cryptoUtil = cryptoUtil; + this.restTemplate = restTemplate; + } + + /** + * Return the key located under the given name in the credential store. + * + * @param name the name of the key + * @return the key from the credential store + */ + public SapCredential getKeyByName(String name) { + log.debug("Querying key with name '{}'.", name); + String response = restTemplate.getForEntity(url + "/key?name=" + URLEncoder.encode(name, + StandardCharsets.UTF_8), String.class).getBody(); + return SapCredential.fromJson(cryptoUtil.decrypt(response)); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStoreConfig.java b/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStoreConfig.java new file mode 100644 index 0000000..304bc3f --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStoreConfig.java @@ -0,0 +1,42 @@ +package eu.europa.ec.dgc.issuance.utils.btp; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.client.RestTemplate; + +@Configuration +@Profile("btp") +public class CredentialStoreConfig { + + @Value("${sap.btp.credstore.username}") + private String username; + + @Value("${sap.btp.credstore.password}") + private String password; + + @Value("${sap.btp.credstore.namespace}") + private String namespace; + + @Bean + RestTemplate restTemplate(RestTemplateBuilder builder) { + RestTemplate restTemplate = builder.build(); + restTemplate.getInterceptors().add((request, body, execution) -> { + request.getHeaders().set("Authorization", "Basic " + getAuthToken()); + request.getHeaders().set("sapcp-credstore-namespace", namespace); + return execution.execute(request, body); + }); + + return restTemplate; + } + + private String getAuthToken() { + String authHeader = username + ":" + password; + return Base64.getEncoder().encodeToString(authHeader.getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStoreCryptoUtil.java b/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStoreCryptoUtil.java new file mode 100644 index 0000000..2715126 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/CredentialStoreCryptoUtil.java @@ -0,0 +1,80 @@ +package eu.europa.ec.dgc.issuance.utils.btp; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWEObject; +import com.nimbusds.jose.Payload; +import com.nimbusds.jose.crypto.RSADecrypter; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.text.ParseException; +import java.util.Base64; +import javax.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.NotImplementedException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@Profile("btp") +public class CredentialStoreCryptoUtil { + + @Value("${sap.btp.credstore.clientPrivateKey}") + private String clientPrivateKeyBase64; + + @Value("${sap.btp.credstore.serverPublicKey}") + private String serverPublicKeyBase64; + + @Value("${sap.btp.credstore.encrypted}") + private boolean encryptionEnabled; + + private PrivateKey ownPrivateKey; + + private PublicKey serverPublicKey; + + @PostConstruct + private void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException { + if (!encryptionEnabled) { + return; + } + + KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder() + .decode(clientPrivateKeyBase64)); + this.ownPrivateKey = rsaKeyFactory.generatePrivate(pkcs8EncodedKeySpec); + + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder() + .decode(serverPublicKeyBase64)); + this.serverPublicKey = rsaKeyFactory.generatePublic(x509EncodedKeySpec); + } + + protected void encrypt() { + throw new NotImplementedException("Encryption is still to be implemented yet."); + } + + protected String decrypt(String jweResponse) { + if (!encryptionEnabled) { + return jweResponse; + } + + JWEObject jweObject; + + try { + RSADecrypter rsaDecrypter = new RSADecrypter(ownPrivateKey); + jweObject = JWEObject.parse(jweResponse); + jweObject.decrypt(rsaDecrypter); + + Payload payload = jweObject.getPayload(); + return payload.toString(); + } catch (ParseException | JOSEException e) { + log.error("Failed to parse JWE response: {}.", e.getMessage()); + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/SapCredential.java b/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/SapCredential.java new file mode 100644 index 0000000..1e5861b --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/issuance/utils/btp/SapCredential.java @@ -0,0 +1,31 @@ +package eu.europa.ec.dgc.issuance.utils.btp; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.util.Date; +import lombok.Data; + +@Data +public class SapCredential { + + private String id; + private String name; + private Date modifiedAt; + private String value; + private String status; + private String username; + private String format; + private String category; + private String type; + + public static SapCredential fromJson(String rawJson) { + return gson().fromJson(rawJson, SapCredential.class); + } + + private static Gson gson() { + return new GsonBuilder() + .enableComplexMapKeySerialization() + .create(); + } + +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..3178d86 --- /dev/null +++ b/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +io.pivotal.cfenv.spring.boot.CfEnvProcessor=eu.europa.ec.dgc.issuance.config.btp.SapCredentialStoreCfEnvProcessor diff --git a/src/main/resources/application-btp.yml b/src/main/resources/application-btp.yml new file mode 100644 index 0000000..b46af5d --- /dev/null +++ b/src/main/resources/application-btp.yml @@ -0,0 +1,10 @@ +sap: + btp: + credstore: + namespace: DgcaIssuerServiceCredentialStore + encrypted: false + username: + password: + url: + clientPrivateKey: + serverPublicKey: diff --git a/src/test/java/eu/europa/ec/dgc/issuance/Sh256HashTest.java b/src/test/java/eu/europa/ec/dgc/issuance/EncodingTest.java similarity index 51% rename from src/test/java/eu/europa/ec/dgc/issuance/Sh256HashTest.java rename to src/test/java/eu/europa/ec/dgc/issuance/EncodingTest.java index fd0bb86..3a1ae15 100644 --- a/src/test/java/eu/europa/ec/dgc/issuance/Sh256HashTest.java +++ b/src/test/java/eu/europa/ec/dgc/issuance/EncodingTest.java @@ -20,34 +20,32 @@ package eu.europa.ec.dgc.issuance; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.util.Base64; +import eu.europa.ec.dgc.issuance.utils.DgciUtil; +import eu.europa.ec.dgc.issuance.utils.HashUtil; import java.util.UUID; import org.junit.Test; -public class Sh256HashTest { +import static org.junit.Assert.assertEquals; + +public class EncodingTest { + + public static final String TEST_TAN = "U7ULCYZY"; + public static final String TEST_TAN_HASHED = "avmGz38ugM7uBePwKKlvh3IB8+7O+WFhQEbjIxhTxgY="; + + public static final String TEST_UUID = "cd7737d4-51ca-45f8-9f74-3a173b9a1f47"; + public static final String TEST_DGCI_REP = "NW393C1D87A44870V7TTFQMYC"; + @Test public void testCreateSHA256Hash() throws Exception { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - final byte[] hashbytes = digest.digest( - "some_data".getBytes(StandardCharsets.UTF_8)); - System.out.println(Base64.getEncoder().encodeToString(hashbytes)); + String output = HashUtil.sha256Base64(TEST_TAN); + assertEquals(TEST_TAN_HASHED, output); } @Test public void dgciEncoding() throws Exception { - UUID uuid = UUID.randomUUID(); - System.out.println(uuid.toString()); - ByteBuffer bb = ByteBuffer.wrap(new byte[16]); - bb.putLong(uuid.getMostSignificantBits()); - bb.putLong(uuid.getLeastSignificantBits()); - BigInteger bint = new BigInteger(1, bb.array()); - int radix = 10+('Z'-'A'); - String dgciRep = bint.toString(radix).toUpperCase(); - System.out.println(dgciRep); - System.out.println(dgciRep.length()); + UUID uuid = UUID.fromString(TEST_UUID); + String dgciRep = DgciUtil.encodeDgci(uuid); + assertEquals(25, dgciRep.length()); + assertEquals(TEST_DGCI_REP, dgciRep); } } diff --git a/src/test/java/eu/europa/ec/dgc/issuance/GenerateWalletRequestTest.java b/src/test/java/eu/europa/ec/dgc/issuance/GenerateWalletRequestTest.java index 40ba0f4..797949e 100644 --- a/src/test/java/eu/europa/ec/dgc/issuance/GenerateWalletRequestTest.java +++ b/src/test/java/eu/europa/ec/dgc/issuance/GenerateWalletRequestTest.java @@ -24,11 +24,11 @@ import com.fasterxml.jackson.databind.SerializationFeature; import eu.europa.ec.dgc.issuance.restapi.dto.ClaimRequest; import eu.europa.ec.dgc.issuance.restapi.dto.PublicKey; -import eu.europa.ec.dgc.issuance.service.TanService; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; @@ -40,15 +40,13 @@ public class GenerateWalletRequestTest { // This can be used to generate valid json structure for claim @Test public void testGenerateWalletRequest() throws Exception { - TanService tanService = new TanService(); - // Please adapt this to your certificate (the values can be get from browser network log // see POST /dgci // and PUT /dgci/{id} String dgci = "dgci:V1:DE:2e974b3b-d932-4bc9-bbae-d387f93f8bf3:edbcb873196f24be"; String certHash = "mfg0MI7wPFexNkOa4n9OKojrzhe9a9lcim4JzJO3WtY="; String tan = "U7ULCYZY"; - String tanHash = tanService.hashTan(tan); + String tanHash = "avmGz38ugM7uBePwKKlvh3IB8+7O+WFhQEbjIxhTxgY="; KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(2048); @@ -72,6 +70,16 @@ public void testGenerateWalletRequest() throws Exception { System.out.println(objectMapper.writeValueAsString(claimRequest)); } + public static void main(String[] args) { + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + final byte[] hashBytes = digest.digest("U7ULCYZY".getBytes(StandardCharsets.UTF_8)); + System.out.println(Base64.getEncoder().encodeToString(hashBytes)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + private void createClaimSignature(ClaimRequest claimRequest, PrivateKey privateKey, String sigAlg) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { StringBuilder sigValue = new StringBuilder(); sigValue.append(claimRequest.getTanHash()) @@ -86,15 +94,13 @@ private void createClaimSignature(ClaimRequest claimRequest, PrivateKey privateK @Test public void testGenerateWalletRequestEC() throws Exception { - TanService tanService = new TanService(); - // Please adapt this to your certificate (the values can be get from browser network log // see POST /dgci // and PUT /dgci/{id} String dgci = "dgci:V1:DE:2e974b3b-d932-4bc9-bbae-d387f93f8bf3:edbcb873196f24be"; String certHash = "mfg0MI7wPFexNkOa4n9OKojrzhe9a9lcim4JzJO3WtY="; String tan = "U7ULCYZY"; - String tanHash = tanService.hashTan(tan); + String tanHash = "avmGz38ugM7uBePwKKlvh3IB8+7O+WFhQEbjIxhTxgY="; KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); keyPairGen.initialize(256); diff --git a/src/test/java/eu/europa/ec/dgc/issuance/service/TanServiceTest.java b/src/test/java/eu/europa/ec/dgc/issuance/service/TanServiceTest.java index b351530..16f35a2 100644 --- a/src/test/java/eu/europa/ec/dgc/issuance/service/TanServiceTest.java +++ b/src/test/java/eu/europa/ec/dgc/issuance/service/TanServiceTest.java @@ -23,13 +23,15 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class TanServiceTest { + @Test public void testGenerateTan() throws Exception { - TanService tanService = new TanService(); - String tan = tanService.generateNewTan(); - System.out.println(tan); - assertEquals(8,tan.length()); + Tan tan = Tan.create(); + assertNotNull(tan); + assertEquals(8, tan.getRawTan().length()); } + } diff --git a/src/test/java/eu/europa/ec/dgc/issuance/utils/btp/SapCredentialParserTest.java b/src/test/java/eu/europa/ec/dgc/issuance/utils/btp/SapCredentialParserTest.java new file mode 100644 index 0000000..de28046 --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/issuance/utils/btp/SapCredentialParserTest.java @@ -0,0 +1,26 @@ +package eu.europa.ec.dgc.issuance.utils.btp; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +public class SapCredentialParserTest { + + private static final String EXPECTED_ID = "5f5fa34e-be21-4c7a-8548-4a538b7156ed"; + private static final String EXPECTED_VALUE = "SoMeBaSe64EnCoDeDkEy=="; + private static final String EXPECTED_NAME = "key-name"; + + @Test + public void testJsonParsing() throws IOException { + ClassPathResource jsonResource = new ClassPathResource("/data/fromCredStore.json"); + String json = Files.readString(jsonResource.getFile().toPath(), StandardCharsets.UTF_8); + + SapCredential sapCredential = SapCredential.fromJson(json); + Assert.assertEquals(EXPECTED_ID, sapCredential.getId()); + Assert.assertEquals(EXPECTED_VALUE, sapCredential.getValue()); + Assert.assertEquals(EXPECTED_NAME, sapCredential.getName()); + } +} diff --git a/src/test/resources/data/fromCredStore.json b/src/test/resources/data/fromCredStore.json new file mode 100644 index 0000000..5801a5d --- /dev/null +++ b/src/test/resources/data/fromCredStore.json @@ -0,0 +1,11 @@ +{ + "id": "5f5fa34e-be21-4c7a-8548-4a538b7156ed", + "name": "key-name", + "modifiedAt": "2021-05-12T22:16:55.806Z", + "value": "SoMeBaSe64EnCoDeDkEy==", + "status": "enabled", + "username": "key-name", + "format": "X.509", + "category": "someCategory", + "type": "someType" +}