Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2010-2017 Norwegian Agency for Public Management and eGovernment (Difi)
*
* Licensed under the EUPL, Version 1.1 or – as soon they
* will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
*
* You may not use this work except in compliance with the Licence.
*
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/community/eupl/og_page/eupl
*
* Unless required by applicable law or agreed to in
* writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied.
* See the Licence for the specific language governing
* permissions and limitations under the Licence.
*/
package no.difi.oxalis.as2.outbound;
import brave.Span;
import brave.Tracer;
import com.google.inject.Inject;
import com.google.inject.Provider;
import no.difi.oxalis.api.lang.OxalisTransmissionException;
import no.difi.oxalis.api.lang.TimestampException;
import no.difi.oxalis.api.model.Direction;
import no.difi.oxalis.api.model.TransmissionIdentifier;
import no.difi.oxalis.api.outbound.TransmissionRequest;
import no.difi.oxalis.api.outbound.TransmissionResponse;
import no.difi.oxalis.api.timestamp.Timestamp;
import no.difi.oxalis.api.timestamp.TimestampProvider;
import no.difi.oxalis.as2.code.As2Header;
import no.difi.oxalis.as2.code.MdnHeader;
import no.difi.oxalis.as2.model.As2DispositionNotificationOptions;
import no.difi.oxalis.as2.model.Mic;
import no.difi.oxalis.as2.util.*;
import no.difi.oxalis.commons.bouncycastle.BCHelper;
import no.difi.oxalis.commons.security.CertificateUtils;
import no.difi.oxalis.commons.tracing.Traceable;
import no.difi.vefa.peppol.common.model.Digest;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
import javax.net.ssl.SSLHandshakeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* Not thread safe implementation of sender, which sends messages using the AS2 protocol.
* Stores the outbound MIC for verification against the mic received from the MDN later.
*
* @author steinar
* @author thore
* @author erlend
*/
class As2MessageSender extends Traceable {
private static final Logger LOGGER = LoggerFactory.getLogger(As2MessageSender.class);
/**
* Provider of HTTP clients.
*/
private final Provider httpClientProvider;
private final SMimeMessageFactory sMimeMessageFactory;
/**
* Timestamp provider used to create timestamp "t3" (time of reception of transport specific receipt, MDN).
*/
private final TimestampProvider timestampProvider;
/**
* Identifier from sender's certificate used during transmission in "AS2-From" header.
*/
private final String fromIdentifier;
private TransmissionRequest transmissionRequest;
private TransmissionIdentifier transmissionIdentifier;
private Span root;
private Digest outboundMic;
/**
* Constructor expecting resources needed to perform transmission using AS2. All task required to be done once for
* all requests using this instance is done here.
*
* @param httpClientProvider Provider of HTTP clients.
* @param certificate Certificate of sender.
* @param sMimeMessageFactory Factory prepared to create S/MIME messages using our private key.
* @param timestampProvider Provider used to fetch timestamps.
* @param tracer Tracing tool.
*/
@Inject
public As2MessageSender(Provider httpClientProvider, X509Certificate certificate,
SMimeMessageFactory sMimeMessageFactory, TimestampProvider timestampProvider,
Tracer tracer) {
super(tracer);
this.httpClientProvider = httpClientProvider;
this.sMimeMessageFactory = sMimeMessageFactory;
this.timestampProvider = timestampProvider;
// Establishes our AS2 System Identifier based upon the contents of the CN= field of the certificate
this.fromIdentifier = CertificateUtils.extractCommonName(certificate);
}
public TransmissionResponse send(TransmissionRequest transmissionRequest, Span root)
throws OxalisTransmissionException {
this.transmissionRequest = transmissionRequest;
this.root = tracer.newChild(root.context()).name("Send AS2 message").start();
try {
return sendHttpRequest(prepareHttpRequest());
} catch (OxalisTransmissionException e) {
this.root.tag("exception", e.getMessage());
throw e;
} finally {
root.finish();
}
}
@SuppressWarnings("unchecked")
protected HttpPost prepareHttpRequest() throws OxalisTransmissionException {
Span span = tracer.newChild(root.context()).name("request").start();
try {
final HttpPost httpPost;
// Create the body part of the MIME message containing our content to be transmitted.
MimeBodyPart mimeBodyPart = MimeMessageHelper
.createMimeBodyPart(transmissionRequest.getPayload(), "application/xml");
// Digest method to use.
SMimeDigestMethod digestMethod = SMimeDigestMethod.findByTransportProfile(
transmissionRequest.getEndpoint().getTransportProfile());
outboundMic = MimeMessageHelper.calculateMic(mimeBodyPart, digestMethod);
span.tag("mic", outboundMic.toString());
span.tag("endpoint url", transmissionRequest.getEndpoint().getAddress().toString());
// Create a complete S/MIME message using the body part containing our content as the
// signed part of the S/MIME message.
MimeMessage signedMimeMessage = sMimeMessageFactory
.createSignedMimeMessage(mimeBodyPart, digestMethod);
// .createSignedMimeMessageNew(mimeBodyPart, outboundMic, digestMethod);
// Initiate POST request
httpPost = new HttpPost(transmissionRequest.getEndpoint().getAddress());
// Get all headers in S/MIME message.
List headers = Collections.list(signedMimeMessage.getAllHeaders());
List headerNames = headers.stream()
// Tag for tracing.
.peek(h -> span.tag(h.getName(), h.getValue()))
// Add headers to httpPost object (remove new lines according to HTTP 1.1).
.peek(h -> httpPost.addHeader(h.getName(), h.getValue().replace("\r\n\t", "")))
// Collect header names....
.map(javax.mail.Header::getName)
// ... in a list.
.collect(Collectors.toList());
// Write content to OutputStream without headers.
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
signedMimeMessage.writeTo(byteArrayOutputStream, headerNames.toArray(new String[headerNames.size()]));
transmissionIdentifier = TransmissionIdentifier.fromHeader(
httpPost.getFirstHeader(As2Header.MESSAGE_ID).getValue());
// Inserts the S/MIME message to be posted. Make sure we pass the same content type as the
// SignedMimeMessage, it'll end up as content-type HTTP header
httpPost.setEntity(new ByteArrayEntity(byteArrayOutputStream.toByteArray()));
// Set all headers specific to AS2 (not MIME).
httpPost.addHeader(As2Header.AS2_FROM, fromIdentifier);
httpPost.setHeader(As2Header.AS2_TO, CertificateUtils.extractCommonName(
transmissionRequest.getEndpoint().getCertificate()));
httpPost.addHeader(As2Header.DISPOSITION_NOTIFICATION_TO, "[email protected]");
httpPost.addHeader(As2Header.DISPOSITION_NOTIFICATION_OPTIONS,
As2DispositionNotificationOptions.getDefault(digestMethod).toString());
httpPost.addHeader(As2Header.AS2_VERSION, As2Header.VERSION);
httpPost.addHeader(As2Header.SUBJECT, "AS2 message from OXALIS");
httpPost.addHeader(As2Header.DATE, As2DateUtil.RFC822.format(new Date()));
return httpPost;
} catch (MessagingException | IOException e) {
throw new OxalisTransmissionException("Unable to stream S/MIME message into byte array output stream");
} finally {
span.finish();
}
}
protected TransmissionResponse sendHttpRequest(HttpPost httpPost) throws OxalisTransmissionException {
Span span = tracer.newChild(root.context()).name("execute").start();
try (CloseableHttpClient httpClient = httpClientProvider.get()) {
CloseableHttpResponse response = httpClient.execute(httpPost);
span.finish();
return handleResponse(response);
} catch (HttpHostConnectException e) {
span.tag("exception", e.getMessage());
throw new OxalisTransmissionException("Receiving server does not seem to be running.",
transmissionRequest.getEndpoint().getAddress(), e);
} catch (SSLHandshakeException e) {
span.tag("exception", e.getMessage());
throw new OxalisTransmissionException("Possible invalid SSL Certificate at the other end.",
transmissionRequest.getEndpoint().getAddress(), e);
} catch (IOException e) {
span.tag("exception", String.valueOf(e.getMessage()));
throw new OxalisTransmissionException(transmissionRequest.getEndpoint().getAddress(), e);
} finally {
span.finish();
}
}
protected TransmissionResponse handleResponse(CloseableHttpResponse closeableHttpResponse)
throws OxalisTransmissionException {
Span span = tracer.newChild(root.context()).name("response").start();
try (CloseableHttpResponse response = closeableHttpResponse) {
span.tag("code", String.valueOf(response.getStatusLine().getStatusCode()));
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
LOGGER.error("AS2 HTTP POST expected HTTP OK, but got : {} from {}",
response.getStatusLine().getStatusCode(), transmissionRequest.getEndpoint().getAddress());
// Throws exception
handleFailedRequest(response);
}
// handle normal HTTP OK response
LOGGER.debug("AS2 transmission to {} returned HTTP OK, verify MDN response",
transmissionRequest.getEndpoint().getAddress());
Header contentTypeHeader = response.getFirstHeader("Content-Type");
if (contentTypeHeader == null)
throw new OxalisTransmissionException("No Content-Type header in response, probably a server error.");
// Read MIME Message
MimeMessage mimeMessage = MimeMessageHelper.parseMultipart(
response.getEntity().getContent(), contentTypeHeader.getValue());
// Add headers to MIME Message
for (Header header : response.getAllHeaders())
mimeMessage.addHeader(header.getName(), header.getValue());
SMimeReader sMimeReader = new SMimeReader(mimeMessage);
// Timestamp of reception of MDN
Timestamp t3 = timestampProvider.generate(sMimeReader.getSignature(), Direction.OUT);
// Extract signed digest and digest algorithm
SMimeDigestMethod digestMethod = sMimeReader.getDigestMethod();
// Preparing calculation of digest
MessageDigest messageDigest = BCHelper.getMessageDigest(digestMethod.getIdentifier());
InputStream digestInputStream = new DigestInputStream(sMimeReader.getBodyInputStream(), messageDigest);
// Reading report
MimeMultipart mimeMultipart = new MimeMultipart(
new ByteArrayDataSource(digestInputStream, mimeMessage.getContentType()));
// Create digest object
Digest digest = Digest.of(digestMethod.getDigestMethod(), messageDigest.digest());
// Verify signature
/*
X509Certificate certificate = SMimeBC.verifySignature(
ImmutableMap.of(digestMethod.getOid(), digest.getValue()),
sMimeReader.getSignature()
);
*/
// verify the signature of the MDN, we warn about dodgy signatures
SignedMimeMessage signedMimeMessage = new SignedMimeMessage(mimeMessage);
X509Certificate certificate = signedMimeMessage.getSignersX509Certificate();
// Verify if the certificate used by the receiving Access Point in
// the response message does not match its certificate published by the SMP
if (!transmissionRequest.getEndpoint().getCertificate().equals(certificate))
throw new OxalisTransmissionException(String.format(
"Certificate in MDN ('%s') does not match certificate from SMP ('%s').",
certificate.getSubjectX500Principal().getName(),
transmissionRequest.getEndpoint().getCertificate().getSubjectX500Principal().getName()));
LOGGER.debug("MDN signature was verified for : " + certificate.getSubjectDN().toString());
// Verifies the actual MDN
MdnMimeMessageInspector mdnMimeMessageInspector = new MdnMimeMessageInspector(mimeMessage);
String msg = mdnMimeMessageInspector.getPlainTextPartAsText();
if (!mdnMimeMessageInspector.isOkOrWarning(new Mic(outboundMic))) {
LOGGER.error("AS2 transmission failed with some error message '{}'.", msg);
throw new OxalisTransmissionException(String.format("AS2 transmission failed : %s", msg));
}
// Read structured content
MimeBodyPart mimeBodyPart = (MimeBodyPart) mdnMimeMessageInspector.getMessageDispositionNotificationPart();
InternetHeaders internetHeaders = new InternetHeaders((InputStream) mimeBodyPart.getContent());
// Fetch timestamp if set
Date date = t3.getDate();
if (internetHeaders.getHeader(MdnHeader.DATE) != null)
date = As2DateUtil.RFC822.parse(internetHeaders.getHeader(MdnHeader.DATE)[0]);
// Return TransmissionResponse
return new As2TransmissionResponse(transmissionIdentifier, transmissionRequest,
outboundMic, MimeMessageHelper.toBytes(mimeMessage), t3, date);
} catch (TimestampException | IOException e) {
throw new OxalisTransmissionException(e.getMessage(), e);
} catch (NoSuchAlgorithmException | MessagingException e) {
throw new OxalisTransmissionException("Unable to parse received content.", e);
} finally {
span.finish();
}
}
protected void handleFailedRequest(HttpResponse response) throws OxalisTransmissionException {
HttpEntity entity = response.getEntity(); // Any results?
try {
if (entity == null) {
// No content returned
throw new OxalisTransmissionException(
String.format("Request failed with rc=%s, no content returned in HTTP response",
response.getStatusLine().getStatusCode()));
} else {
String contents = EntityUtils.toString(entity);
throw new OxalisTransmissionException(
String.format("Request failed with rc=%s, contents received (%s characters): %s",
response.getStatusLine().getStatusCode(), contents.trim().length(), contents));
}
} catch (IOException e) {
throw new OxalisTransmissionException(
String.format("Request failed with rc=%s, ERROR while retrieving the contents of the response: %s",
response.getStatusLine().getStatusCode(), e.getMessage()), e);
}
}
}