Kodexempel för SMP-Klient samt O2O-kryptering
Detta dokument innehåller kodexempel gällande applicering av kryptografi samt integration med Diggs SMP och Diggs CertPub-tjänst.
Syftet är att vara ett stöd för implementation av de lokala SDK-komponenterna meddelandetjänst/meddelandeklient. Generella förmågor i lokal meddelandetjänst och övriga lokala SDK-komponenter beskrivs i Checklista för anpassning av meddelandesystem.
Observera att nedanstående kod är exempel som distribueras i befintligt skick utan support.
Inga garantier eller utfästelser gäller för denna kod. Användaren av denna kod måste själv tillgodose korrekthet och tillämplighet.
Introduktion
Kodexempel baseras på följande signifikanta tekniker och ramverk:
- Java (11)
- Spring-Boot
- Lombok
- Apache Santuario
Länk till annan webbplats.
OBS! Notera att detta är ett äldre exempel med en utdaterad Java-version. Versionen av dynamic-discovery-client är gammal. Vid en egen implementation bör en senare version användas.
Nedladdning av SMP-klientmjukvara “CEF Dynamic Discovery Client”
Gradle
// Below is an excerpt from build.gradlerepositories { if (System.env.JENKINS_HOME != null) { ... maven { url "https://ec.europa.eu/cefdigital/artifact/content/groups/public/"} ...}dependencies { implementation 'eu.europa.ec.dynamic-discovery:dynamic-discovery-client:1.12' // or use a variable ...}
Maven
<!-- Below is an excerpt from pom.xml --><!- Add this to repository configuration --><repositories> <repository> <id>cefdigital-releases</id> <url>https://ec.europa.eu/cefdigital/artifact/content/groups/public/</url> <releases> <enabled>true</enabled> <updatePolicy>daily</updatePolicy> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <!-- ... other repos here ... --></repositories><!-- Add this to dependency section of pom.xml --><dependency> <groupId>eu.europa.ec.dynamic-discovery</groupId> <artifactId>dynamic-discovery-client</artifactId> <version>${dynamic.discovery.client.version}</version> <!-- 1.12 --> </dependency>
Anrop till SMP och “CertPub” med NAPTR
Koden nedan använder sig av CEF Dynamic Discovery Client för att hämta metadata från SMP/CertPub.
SMP Metadata med DNS / NAPTR
import eu.europa.ec.dynamicdiscovery.model.ServiceMetadata;import lombok.extern.slf4j.Slf4j;import org.oasis_open.docs.bdxr.ns.smp._2016._05.SignedServiceMetadataType;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;import java.io.IOException;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.cert.CertificateException;import java.util.Optional;import java.util.regex.Matcher;import java.util.regex.Pattern;import static java.util.Objects.requireNonNull;/** * Code example last updated on 2022-02-08. * Added some more comments. * * License: LGPL v3 : https://www.gnu.org/licenses/lgpl-3.0.html * * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */@Service@Slf4jpublic class SmpClient { @Value("${smp.scheme.participantidentifier:iso6523-actorid-upis}") String participantIdentifierScheme; @Value("${smp.scheme.documentidentifier:busdox-docid-qns}") String documentIdentifierScheme; @Value("${smp.smlzone:smldigg.com}") String smlZone; // defaults to test String truststoreFileLocation; String truststorePassword; String truststoreType; KeyStore trustStore; private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("^(?<scheme>.+?)::(?<value>.+)$"); private static final String DOUBLE_COLON = "::"; public SmpService(@Value("${security.truststore.filename}") String truststoreFileLocation, @Value("${security.truststore.password}") String truststorePassword, @Value("${security.truststore.type:PKCS12}") String truststoreType) { this.truststoreFileLocation = truststoreFileLocation; this.truststorePassword = truststorePassword; this.truststoreType = truststoreType; this.trustStore = initTrustStore(truststoreFileLocation, truststorePassword, truststoreType); } public KeyStore initTrustStore(final String truststoreFileLocation, final String truststorePassword, final String truststoreType) { KeyStore trustStore = null; try { trustStore = KeyStore.getInstance(truststoreType); String password = truststorePassword; trustStore.load(SmpClient.class.getClassLoader().getResourceAsStream(truststoreFileLocation), password.toCharArray()); } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { log.error("Could not initialize Truststore from '{}'. Check configuration.", truststoreFileLocation, e); } return trustStore; } /** USE THIS METHOD TO EXTRACT THE END-2-END CERTIFICATE FROM THE SignedServiceMetadataType */ public SignedServiceMetadataType resolveServiceMetadata(final String smlZone, final String participantIdentifier, final String participantIdentifierScheme, final String documentIdentifier, final String documentIdentifierScheme) { if(participantIdentifier == null) { return null; } SignedServiceMetadataType returnValue = null; try { // use configured defaults if not passed as argument var effectiveSmlZone = smlZone != null && !smlZone.isBlank() ? smlZone : this.smlZone; var effectiveParticipantIdentifierScheme = participantIdentifierScheme != null && !participantIdentifierScheme.isBlank() ? participantIdentifierScheme : this.participantIdentifierScheme; var effectiveDocumentIdentifierScheme = documentIdentifierScheme != null && !documentIdentifierScheme.isBlank() ? documentIdentifierScheme : this.documentIdentifierScheme; ParticipantIdentifier participantIdentifierObj = new ParticipantIdentifier(participantIdentifier, effectiveParticipantIdentifierScheme); DocumentIdentifier documentIdentifierObj = new DocumentIdentifier( documentIdentifier, effectiveDocumentIdentifierScheme); log.debug("Resolving {} and {} at SML Zone '{}'", participantIdentifierObj, documentIdentifierObj, effectiveSmlZone); var start = System.currentTimeMillis(); ServiceMetadata serviceMetadata = createClient(smlZone, trustStore).getServiceMetadata(participantIdentifierObj, documentIdentifierObj); log.trace("Successfully resolved {} in {} ms", serviceMetadata.getOriginalServiceMetadata().getServiceMetadata().getServiceInformation().getParticipantIdentifier().getValue(), System.currentTimeMillis() - start); returnValue = serviceMetadata.getOriginalServiceMetadata(); } catch (DNSLookupException e) { log.warn("Could not resolve host in DNS by either NAPTR or (fallback) CNAME : '{}'", e.getCause().getMessage()); } catch (TechnicalException e) { log.warn("An unexpected problem occurred : '{}'", e.getCause().getMessage(), e); } return returnValue; } /** USE THIS METHOD TO VERIFY THAT A PARTICIPANT IS ACTUALLY PART OF THE FEDERATION BY QUERYING THE SMP METADATA SERVICE (OVERLAPS METHOD resolveServiceMetadata - IS A BIT QUICKER IN SCENARIOS WHEN THE ACTUAL SERVICE METADATA IS NOT NEEDED) */ public ServiceGroup resolveServiceGroup(String smlZone, String qualifiedParticipantIdentifier) { if(qualifiedParticipantIdentifier == null) { return null; } // use configured default if not passed as argument var effectiveSmlZone = smlZone != null && !smlZone.isBlank() ? smlZone : this.smlZone; log.trace("Attempting to resolve {} at SML Zone '{}'", qualifiedParticipantIdentifier, effectiveSmlZone); return toParticipantIdentifier(qualifiedParticipantIdentifier) .map(participantIdentifier -> { ServiceGroup returnValue = null; try { returnValue = createClient(smlZone, trustStore).getServiceGroup(participantIdentifier); log.debug("Successfully resolved {} at SML Zone '{}'", returnValue.getOriginalServiceGroup().getParticipantIdentifier(), effectiveSmlZone); } catch (TechnicalException e) { log.warn("Could not resolve host in DNS by either NAPTR or (fallback) CNAME : '{}'", e.getCause().getMessage()); } return returnValue; }).orElse(null); } private Optional<ParticipantIdentifier> toParticipantIdentifier(@NonNull String participantIdentifier) { requireNonNull(participantIdentifier); try { var scheme = participantIdentifierScheme; // default var value = participantIdentifier; // default if(participantIdentifier.contains(DOUBLE_COLON)) { scheme = extract(participantIdentifier, "scheme"); value = extract(participantIdentifier, "value"); } return Optional.of(new ParticipantIdentifier(value, scheme)); } catch (IllegalStateException ise) { log.warn("Could not extract @scheme and @value from [{}]. Check input value.", participantIdentifier); return Optional.empty(); } } private static String extract(String doubleColonDelimitedId, String groupName) { Matcher m = IDENTIFIER_PATTERN.matcher(doubleColonDelimitedId); m.matches(); return m.group(groupName); } private static DynamicDiscovery createClient(String smlZone, KeyStore trustStore) { var start = System.currentTimeMillis(); DynamicDiscovery smpClient = null; try { // Internals are cached - first instance is approx 10 times slower to create. Tests indicate around 20-30 ms to build once initialized. smpClient = DynamicDiscoveryBuilder.newInstance() .locator(new DefaultBDXRLocator(smlZone)) .reader(new DefaultBDXRReader(new DefaultSignatureValidator(trustStore))) .build(); } catch (TechnicalException e) { log.error("Could not construct DynamicDiscovery client.", e); } log.trace("Created client in {} ms.", System.currentTimeMillis() - start); return smpClient; }}
Kryptering och signering av utgående meddelande
Utgående meddelande signeras och krypteras med StAX, https://en.wikipedia.org/wiki/StAX
Kryptohanteringen sker i två steg;
- Konfiguration skapas, resulterar i CryptoSessionDto
- Kryptering appliceras i metod applyCryptoFunctions(CryptoSessionDto sessionDto, ByteArrayOutputStream xml)

Illustrationen visar kryptering och signering av utgående meddelande. Se bilden i ett större format.
Utgående Meddelande
package se.inera.sdk.connector.crypto;import lombok.Data;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.apache.xml.security.exceptions.XMLSecurityException;import org.apache.xml.security.stax.ext.*;import org.apache.xml.security.stax.securityToken.SecurityTokenConstants;import se.inera.sdk.connector.xml.stax.XmlReaderToWriter; // Se https://github.com/coheigea/testcases/blob/master/apache/santuario/santuario-xml-encryption/src/test/java/org/apache/coheigea/santuario/xmlencryption/XmlReaderToWriter.javaimport javax.crypto.SecretKey;import javax.xml.namespace.QName;import javax.xml.stream.XMLInputFactory;import javax.xml.stream.XMLStreamException;import javax.xml.stream.XMLStreamReader;import javax.xml.stream.XMLStreamWriter;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.InputStream;import java.security.PrivateKey;import java.security.PublicKey;import java.security.cert.X509Certificate;import java.util.ArrayList;import java.util.List;import static org.apache.xml.security.stax.securityToken.SecurityTokenConstants.KeyIdentifier_X509KeyIdentifier;/** * Created on 2021-06-18. * Last reviewed 2022-02-08. * * License: LGPL v3 : https://www.gnu.org/licenses/lgpl-3.0.html * * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */@Slf4jpublic class OutgoingXMLSecurityProcessor extends AbstractSecurityProcessor { /* Extends from AbstractSecurityProcessor - which a static (and quite vital) init block: static { System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true"); // See https://issues.apache.org/jira/browse/SANTUARIO-482 and http://santuario.apache.org/java212releasenotes.html (from v 2.1.2) org.apache.xml.security.Init.init(); } */ public static final QName DEFAULT_VERSION3_ENCRYPTION_ELEMENT_QNAME = new QName("urn:riv:infrastructure:messaging:MessageWithAttachments:3", "messagePayload"); public static final QName DEFAULT_SIGNATURE_ELEMENT_XHE_ROOT_QNAME = new QName("http://docs.oasis-open.org/bdxr/ns/XHE/1/ExchangeHeaderEnvelope", "XHE"); public static final QName DEFAULT_XHE_PLACE_SIGNATURE_AFTER_ELEMENT_QNAME = new QName("http://docs.oasis-open.org/bdxr/ns/XHE/1/AggregateComponents", "Payloads"); static PrivateKey senderPrivateKey; static X509Certificate senderCertificate; static X509Certificate finalRecipientCertificate; static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); public OutgoingXMLSecurityProcessor(PrivateKey senderPrivateKey, X509Certificate senderCertificate, X509Certificate finalRecipientCertificate) { this.senderPrivateKey = senderPrivateKey; this.senderCertificate = senderCertificate; this.finalRecipientCertificate = finalRecipientCertificate; log.debug("Initializing done."); } public OutgoingXMLSecurityProcessor(PrivateKey senderPrivateKey, X509Certificate senderCertificate) { this.senderPrivateKey = senderPrivateKey; this.senderCertificate = senderCertificate; log.debug("Initializing of SIGN only processor done."); } @Data @RequiredArgsConstructor(staticName = "of") public static class CryptoSessionDto { private final XMLSecurityProperties properties; private final SecretKey secretSymmetricSessionKey; private final PublicKey encryptionKeyForSymmetricKey; private final boolean sign; private final boolean encrypt; } /** * @return default SDK configuration for outgoing SDK messages (SIGN and ENCRYPT) */ public static CryptoSessionDto defaultSdkMessagePayloadCryptoSession() { return cryptoSession(DEFAULT_VERSION3_ENCRYPTION_ELEMENT_QNAME, DEFAULT_SIGNATURE_ELEMENT_XHE_ROOT_QNAME, DEFAULT_XHE_PLACE_SIGNATURE_AFTER_ELEMENT_QNAME, true, true, CryptoConstants.XMLENC_ENCRYPTION_ALGO); // http://www.w3.org/2001/04/xmlenc#aes256-cbc } /** * @return default SDK configuration for outgoing Application Response receipts (SIGN only) */ public static CryptoSessionDto defaultSdkSignApplicationResponseCryptoSession() { return cryptoSession(null, DEFAULT_SIGNATURE_ELEMENT_XHE_ROOT_QNAME, DEFAULT_XHE_PLACE_SIGNATURE_AFTER_ELEMENT_QNAME, true, false, null); // just pass null - no encryption } /** * A factory method for configuration of crypto functions to apply to an outgoing XML message. * @param elementToEncrypt sets at what element should encryption start, e.g. "messagePayload" * @param elementToSign what element to sign, always root element when using XHE * @param signatureAfterThisElement where to place the signature element * @param sign configures signing if true * @param encrypt configures encryption if true * @param encryptionMethod what encryption method to use, DIGG spec default is http://www.w3.org/2001/04/xmlenc#aes256-cbc * @return crypto configuration to apply to outgoing message using {@link #applyCryptoFunctions(CryptoSessionDto, ByteArrayOutputStream)} */ private static CryptoSessionDto cryptoSession(final QName elementToEncrypt, QName elementToSign, QName signatureAfterThisElement, final boolean sign, final boolean encrypt, final String encryptionMethod) { if (!(sign || encrypt)) { return null; // TODO : do something? } XMLSecurityProperties properties = new XMLSecurityProperties(); properties.setActions(getOrderedActions(sign, encrypt)); if (sign) { // Setup according to Digg Edelivery E2E: log.debug("Configuring signature for Element '{}'", elementToSign.getLocalPart()); final var digestAlgorithm = CryptoConstants.XMLENC_DIGEST_ALGO_SHA256; // http://www.w3.org/2001/04/xmlenc#sha256 final var canonicalizationAlgorithm = CryptoConstants.SIGNATURE_CANONICALIZATION_ALGO; // http://www.w3.org/TR/2001/REC-xml-c14n-20010315 properties.setSignatureCanonicalizationAlgorithm(canonicalizationAlgorithm); properties.setSignatureAlgorithm(CryptoConstants.SIGNATURE_ALGO); // http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 properties.setSignatureDigestAlgorithm(digestAlgorithm); // http://www.w3.org/2001/04/xmlenc#sha256 properties.setSignatureKey(getKeyForSignature()); // Senders' private properties.setSignatureKeyIdentifier(KeyIdentifier_X509KeyIdentifier); properties.setSignatureCerts(new X509Certificate[]{getCertificateForSignature()}); // sender certificate properties.setSignatureGenerateIds(false); if (signatureAfterThisElement != null) { log.trace("Signature to be placed after element '{}'", signatureAfterThisElement.getLocalPart()); properties.setSignaturePositionQName(signatureAfterThisElement); } SecurePart signatureSecurePart = new SecurePart( elementToSign, SecurePart.Modifier.Element, new String[]{ CryptoConstants.XMLDSIG_ENVELOPED_SIGNATURE, canonicalizationAlgorithm }, digestAlgorithm ); properties.addSignaturePart(signatureSecurePart); } SecretKey encryptionKey = null; PublicKey encryptedKey = null; if (encrypt && getRecipientCertificateForEncryption() != null && elementToEncrypt != null) { log.debug("Configuring encryption for Element '{}'", elementToEncrypt.getLocalPart()); // Set the key up encryptionKey = CryptoUtils.getSessionKey(); properties.setEncryptionKey(encryptionKey); // The encrypting key. Typically a SecretKey instance. properties.setEncryptionSymAlgorithm(encryptionMethod); // Symmetric encryption Algorithm to use. SecurePart encryptionSecurePart = new SecurePart(elementToEncrypt, SecurePart.Modifier.Element, null, null); properties.addEncryptionPart(encryptionSecurePart); // Add a SecurePart to encrypt, e.g. encrypt a given QName. encryptedKey = getRecipientCertificateForEncryption().getPublicKey(); // THis key is used to nectypt the generated symmetric key var encryptedKeyAlgo = CryptoConstants.XMLENC_ENCRYPTED_KEY_ALGO; // http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p var digestMethodAlgo = CryptoConstants.XMLDSIG_DIGEST_ALGO_SHA1; // http://www.w3.org/2000/09/xmldsig#sha1 // Set up the Configuration properties.setEncryptionTransportKey(encryptedKey); // The key to use to encrypt the secret key (if desired). In SDK it should be the public key of the recipient. properties.setEncryptionKeyTransportAlgorithm(encryptedKeyAlgo); // The encryption key transport algorithm to use, to encrypt the secret key (if desired). Default is RSA OAEP. properties.setEncryptionKeyIdentifier(SecurityTokenConstants.KeyIdentifier_X509KeyIdentifier); properties.setEncryptionUseThisCertificate(getRecipientCertificateForEncryption()); properties.setEncryptionKeyTransportDigestAlgorithm(digestMethodAlgo); } else { log.debug("Encryption disabled for this session."); } return CryptoSessionDto.of(properties, encryptionKey, encryptedKey, sign, encrypt); } /** * Signs and (possibly) encrypts an outgoing XHE message. * @param sessionDto the Crypto configuration to apply. * @param xml the XHE xml message * @return the XHE document with crypto functions applied. */ public ByteArrayOutputStream applyCryptoFunctions(CryptoSessionDto sessionDto, ByteArrayOutputStream xml) { InputStream targetStream = new ByteArrayInputStream(xml.toByteArray()); return applyCryptoFunctions(sessionDto, targetStream); } private static ByteArrayOutputStream applyCryptoFunctions(CryptoSessionDto sessionDto, InputStream xml) { log.debug("Applying OUTBOUND Crypto functions : encrypt:'{}', sign:'{}'", sessionDto.isEncrypt(), sessionDto.isSign()); ByteArrayOutputStream baos = null; try { OutboundXMLSec outboundXMLSec = XMLSec.getOutboundXMLSec(sessionDto.getProperties()); baos = new ByteArrayOutputStream(); XMLStreamWriter xmlStreamWriter = outboundXMLSec.processOutMessage(baos, "UTF-8"); XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(xml); XmlReaderToWriter.writeAll(xmlStreamReader, xmlStreamWriter); //see example https://github.com/apache/santuario-xml-security-java/blob/521d93b86e08ea13a9c9580dc82d5111a50728fa/src/test/java/org/apache/xml/security/test/stax/utils/XmlReaderToWriter.java xmlStreamWriter.close(); } catch (XMLSecurityException | XMLStreamException e) { log.error("{} occurred when applying Crypto functions. Session : \n {}", e.getMessage(), sessionDto, e); } return baos; } public static PrivateKey getKeyForSignature() { return senderPrivateKey; } public static X509Certificate getCertificateForSignature() { return senderCertificate; } public static X509Certificate getRecipientCertificateForEncryption() { return finalRecipientCertificate; } private static List<XMLSecurityConstants.Action> getOrderedActions(boolean sign, boolean encrypt) { List<XMLSecurityConstants.Action> actions = new ArrayList<>(); // this will set the order if (encrypt) { actions.add(XMLSecurityConstants.ENCRYPTION); } if (sign) { actions.add(XMLSecurityConstants.SIGNATURE); } return actions; }}
Dekryptering och validering av signering av inkommande meddelande
Inkommande meddelande valideras och dekrypteras med DOM Länk till annan webbplats. https://en.wikipedia.org/wiki/Document_Object_Model
Länk till annan webbplats.
Hantering av inkommande meddelande sker stegvis, implementering av en meddelandetjänst måste hantera de eventuella fel som upptäcks för att kunna skicka tillbaka en korrekt kvittering.
Kodexemplet innefattar de blåmarkerade boxarna i diagrammet nedan.

Illustrationen visar Dekryptering och validering av signering av inkommande meddelande. Se bilden i ett större format.
Inkommande meddelande
package se.inera.sdk.connector.crypto;import lombok.extern.slf4j.Slf4j;import org.apache.xml.security.encryption.EncryptedData;import org.apache.xml.security.encryption.EncryptedKey;import org.apache.xml.security.encryption.XMLCipher;import org.apache.xml.security.exceptions.XMLSecurityException;import org.apache.xml.security.keys.KeyInfo;import org.apache.xml.security.signature.XMLSignature;import org.w3c.dom.Document;import org.w3c.dom.Element;import javax.xml.xpath.XPath;import javax.xml.xpath.XPathConstants;import javax.xml.xpath.XPathExpressionException;import javax.xml.xpath.XPathFactory;import java.io.ByteArrayInputStream;import java.io.InputStream;import java.nio.charset.StandardCharsets;import java.security.Key;import java.security.PrivateKey;import java.security.cert.X509Certificate;/** * Created on 2021-06-18. * Last reviewed 2022-02-08. * * License: LGPL v3 : https://www.gnu.org/licenses/lgpl-3.0.html * * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */@Slf4jpublic class IncomingXMLSecurityProcessor extends AbstractSecurityProcessor { /* AbstractSecurityProcessor has only a static (but vital) init block: static { System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true"); // See https://issues.apache.org/jira/browse/SANTUARIO-482 and http://santuario.apache.org/java212releasenotes.html (from v 2.1.2) org.apache.xml.security.Init.init(); } */ protected static final String DSIG_SIGNATURE_XPATH = "//dsig:Signature[1]"; protected static final String XMLENC_NAMESPACE = "http://www.w3.org/2001/04/xmlenc#"; protected static final String XMLENC_ROOT_ELEMENT = "EncryptedData"; public static Document decryptMessage(Document xml, PrivateKey privateKeyForKeyUnwrap, X509Certificate certificateForPayloadCheck) { log.debug("Decrypting W3C Dom Document using default settings."); return decrypt(xml, privateKeyForKeyUnwrap, certificateForPayloadCheck); } /** * Takes a key, cert and a document then finds an encrypted element decrypts it and returns the resulting document. * * @param encryptedDocument the incoming XML message to decrypt * @param privateKeyForKeyUnwrap private key from local Keystore * @param localCertificate cert from local Keystore * @return the decrypted document or null if errors */ private static Document decrypt(final Document encryptedDocument, final PrivateKey privateKeyForKeyUnwrap, final X509Certificate localCertificate) { Document decryptedDocument = null; try { // Need to pre-load the Encrypted Data so we can get the key info Element ee = (Element) encryptedDocument.getElementsByTagNameNS(XMLENC_NAMESPACE, XMLENC_ROOT_ELEMENT).item(0); if (ee != null) { // Create the XMLCipher element XMLCipher cipher = XMLCipher.getInstance(); cipher.init(XMLCipher.DECRYPT_MODE, null); EncryptedData encryptedData = cipher.loadEncryptedData(encryptedDocument, ee); log.debug("Found EncryptedData element, Encryption Algorithm is : '{}'", encryptedData.getEncryptionMethod().getAlgorithm()); EncryptedKey encryptedKey = encryptedData.getKeyInfo().itemEncryptedKey(0); // The cert that corresponds to the public used for encrypting the wrapped key X509Certificate payloadCertificate = encryptedKey.getKeyInfo().itemX509Data(0).itemCertificate(0).getX509Certificate(); if (verifySame(payloadCertificate, localCertificate)) { // Step 1: Unwrap encryption key - i.e. decrypt key for data decryption XMLCipher keyUnwrapCipher = XMLCipher.getInstance(); keyUnwrapCipher.init(XMLCipher.UNWRAP_MODE, privateKeyForKeyUnwrap); Key key = keyUnwrapCipher.decryptKey(encryptedKey, encryptedData.getEncryptionMethod().getAlgorithm()); log.trace("Wrapped key for data decryption successfully "); // Step 2: decrypt actual data cipher.init(XMLCipher.DECRYPT_MODE, key); decryptedDocument = cipher.doFinal(encryptedDocument, ee); } else { log.error("Incorrect certificate in payload. Aborting."); } } else { log.warn("Could not find '{}' element. Aborting.", XMLENC_ROOT_ELEMENT); } } catch (Exception e) { log.error("An unexpected problem occurred.", e); } if (decryptedDocument == null) { log.warn("No data was decrypted. Returning raw document."); } return decryptedDocument; } public static boolean verifySignature(String xml, X509Certificate originalSenderCertificate) throws Exception { log.debug("Verifying signature using default settings."); Document document; try (InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { document = org.apache.xml.security.utils.XMLUtils.read(is, false); } return verifySignatureUsingDom(document, originalSenderCertificate); } public static boolean verifySignature(InputStream xml, X509Certificate originalSenderCertificate) throws Exception { log.debug("Verifying signature using default settings."); Document document = org.apache.xml.security.utils.XMLUtils.read(xml, false); return verifySignatureUsingDom(document, originalSenderCertificate); } public static boolean verifySignature(Document xml, X509Certificate originalSenderCertificate) { log.debug("Verifying signature using default settings."); return verifySignatureUsingDom(xml, originalSenderCertificate); } /** * Verifies signature. * @param document the incoming XML message to verify * @param originalSenderCertificate the cert of the sending party * @return true if signature correct, false otherwise. */ private static boolean verifySignatureUsingDom(final Document document, final X509Certificate originalSenderCertificate) { boolean signatureCorrect = false; // Find the Signature Element Element sigElement = getSignatureElement(document); if (sigElement != null) { log.trace("Found signature element '{}' in '{}'", sigElement.getTagName(), sigElement.getOwnerDocument().getDocumentElement().getNamespaceURI()); try { XMLSignature signature = new XMLSignature(sigElement, ""); KeyInfo signatureKeyInfo = signature.getKeyInfo(); if (verifySame(signatureKeyInfo.getX509Certificate(), originalSenderCertificate)) { // Check the Signature value signatureCorrect = signature.checkSignatureValue(originalSenderCertificate); } else { log.error("Incorrect certificate in payload. Aborting."); } } catch (XMLSecurityException e) { log.error("Something went wrong when validating Signature of incoming message.", e); } } else { log.error("Signature element missing."); } return signatureCorrect; } private static boolean verifySame(X509Certificate certificateInPayload, X509Certificate certificateFromCertPubSmp) { log.debug("Cert in Payload : '{}'", certificateInPayload != null ? certificateInPayload.getSubjectDN().getName() : null); log.debug("Local Certificate : '{}'", certificateFromCertPubSmp != null ? certificateFromCertPubSmp.getSubjectDN().getName() : null); return certificateInPayload != null && certificateInPayload.equals(certificateFromCertPubSmp); } public static Element getSignatureElement(Document document) { XPathFactory xpf = XPathFactory.newInstance(); XPath xpath = xpf.newXPath(); xpath.setNamespaceContext(new DSNamespaceContext()); // Find the Signature Element Element sigElement = null; try { sigElement = (Element) xpath.evaluate(DSIG_SIGNATURE_XPATH, document, XPathConstants.NODE); } catch (XPathExpressionException e) { log.error("Could not find signature element in payload.", e); } return sigElement; }}
Ditt svar hjälper oss att förbättra sidan
Senast uppdaterad: