-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: connection to gateway and credential store on BTP
- Loading branch information
Showing
19 changed files
with
617 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
src/main/java/eu/europa/ec/dgc/issuance/config/btp/SapCredentialStoreCfEnvProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <code> | ||
* VCAP_SERVICES</code> environment variable and making them available as properties in the spring context. | ||
* <br/><br/> | ||
* The following properties are available in the context after the processor is done: | ||
* <code> | ||
* <ul> | ||
* <li>sap.btp.credstore.url</li> | ||
* <li>sap.btp.credstore.password</li> | ||
* <li>sap.btp.credstore.username</li> | ||
* <li>sap.btp.credstore.clientPrivateKey</li> | ||
* <li>sap.btp.credstore.serverPublicKey</li> | ||
* </ul> | ||
* </code> | ||
* | ||
* @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<String, Object> 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<String, Object> encryption = (Map<String, Object>) 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(); | ||
} | ||
|
||
} |
34 changes: 3 additions & 31 deletions
34
src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> 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(); | ||
} |
37 changes: 37 additions & 0 deletions
37
src/main/java/eu/europa/ec/dgc/issuance/service/CertKeyPublisherServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> 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"); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpAbstractKeyProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/** | ||
* <p>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.</p> | ||
* <p><b>Note: </b>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 <code>-----BEGIN / END KEY-----</code> phrases.</p> | ||
*/ | ||
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-----", ""); | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
src/main/java/eu/europa/ec/dgc/issuance/service/impl/BtpCertKeyPublisherServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.