package at.asitplus.eidas.specific.modules.auth.eidas.v2.service; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Provider; import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.UUID; import javax.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; import org.jose4j.lang.JoseException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import at.asitplus.eidas.specific.core.MsEidasNodeConstants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.JoseUtils; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; import at.gv.egiz.eaaf.core.impl.data.Pair; import lombok.Data; import lombok.extern.slf4j.Slf4j; /** * Service to build and sign AuthBlock's for E-ID system. * * @author tlenz * */ @Slf4j @Service("authBlockSigningService") public class AuthBlockSigningService { private static final String KEYSTORE_FRIENDLYNAME = "AuthBlock_Signing"; private static ObjectMapper mapper = new ObjectMapper(); @Autowired IConfiguration basicConfig; @Autowired EaafKeyStoreFactory keyStoreFactory; private Pair keyStore; /** * Build and sign an AuthBlock for E-ID system. * * @param pendingReq data that should be added into AuthBlock * @return serialized JWS * @throws JsonProcessingException In case of a AuthBlock generation error * @throws JoseException In case of a JWS signing error * @throws EaafException In case of a KeyStore or Key error */ public String buildSignedAuthBlock(IRequest pendingReq) throws JsonProcessingException, EaafException, JoseException { //TODO: set Challenge to SAML2 requestId to create link between authentication request and authBlock // build AuthBlock EidasAuchBlock authBlock = new EidasAuchBlock(); authBlock.setChallenge(UUID.randomUUID().toString()); authBlock.setTimestamp(LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); authBlock.setUniqueId(pendingReq.getRawData(MsEidasNodeConstants.DATA_REQUESTERID, String.class)); authBlock.setPiiTransactionId(pendingReq.getUniquePiiTransactionIdentifier()); //set Binding PublicKey if available Object bindingPubKey = pendingReq.getRawData(MsEidasNodeConstants.EID_BINDING_PUBLIC_KEY_NAME); if (bindingPubKey instanceof String) { authBlock.setBindingPublicKey((String) bindingPubKey); } String jwsPayload = mapper.writeValueAsString(authBlock); log.debug("Building and sign authBlock with data: {}", jwsPayload); //sign JWS return JoseUtils .createSignature(keyStore, getKeyAlias(), getKeyPassword(), jwsPayload, false, KEYSTORE_FRIENDLYNAME); } /** * Get the Base64 encoded PublicKey that is used to sign the AuthBlock. * * @return Base64 encoded PublicKey * @throws EaafKeyAccessException In case of an unknown or invalid key */ public String getBase64EncodedPublicKey() throws EaafKeyAccessException { Pair keyPair = EaafKeyStoreUtils.getPrivateKeyAndCertificates( keyStore.getFirst(), getKeyAlias(), getKeyPassword(), true, KEYSTORE_FRIENDLYNAME); return Base64.getEncoder().encodeToString(keyPair.getSecond()[0].getPublicKey().getEncoded()); } @PostConstruct private void initialize() throws KeyStoreException, EaafException { log.debug("Initializing AuthBlock signing service ... "); // read Connector wide config data TODO connector wide! String keyStoreName = basicConfig .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEYSTORE_NAME); String keyStorePw = basicConfig .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEYSTORE_PASSWORD); String keyStorePath = basicConfig .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEYSTORE_PATH); String keyStoreType = basicConfig .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEYSTORE_TYPE); //build new KeyStore configuration KeyStoreConfiguration keyStoreConfiguration = new KeyStoreConfiguration(); keyStoreConfiguration.setFriendlyName(KEYSTORE_FRIENDLYNAME); keyStoreConfiguration.setSoftKeyStoreFilePath(keyStorePath); keyStoreConfiguration.setSoftKeyStorePassword(keyStorePw); keyStoreConfiguration.setKeyStoreType(KeyStoreConfiguration.KeyStoreType.fromString(keyStoreType)); keyStoreConfiguration.setKeyStoreName(keyStoreName); //validate KeyStore configuration keyStoreConfiguration.validate(); //validate key alias if (StringUtils.isEmpty(getKeyAlias())) { throw new EaafConfigurationException("config.08", new Object[] {MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEY_ALIAS}); } //build new KeyStore based on configuration keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfiguration); //check if Key is accessible EaafKeyStoreUtils.getPrivateKeyAndCertificates( keyStore.getFirst(), getKeyAlias(), getKeyPassword(), true, KEYSTORE_FRIENDLYNAME); log.info("AuthBlock signing-service successful initialized"); } private char[] getKeyPassword() { final String value = basicConfig.getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEY_PASSWORD); if (value != null) { return value.trim().toCharArray(); } return null; } private String getKeyAlias() { return basicConfig .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEY_ALIAS); } /** * Technical AuthBlock for eIDAS Authentication. * * @author tlenz * */ @Data @JsonInclude(JsonInclude.Include.NON_NULL) private static class EidasAuchBlock { @JsonProperty("challenge") private String challenge; @JsonProperty("timestamp") @JsonSerialize(using = LocalDateTimeSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") private LocalDateTime timestamp; @JsonProperty("appId") private String uniqueId; @JsonProperty("piiTransactionId") private String piiTransactionId; @JsonProperty("bindingPublicKey") private String bindingPublicKey; } }