I am trying to send a Soap Request to IRS and facing the same error like others in this group - 'Invalid WS Security Header'. can someone guide me with a sample Soap Request? One more question is - as part of the Enrollment process, we submitted our X509 certificate(public key) to IRS website which will be used to authenticate/decrypt your message digests. Which certificate file did you upload for this process? We are really stuck with this error for days now. Appreciate any help on this. I have seen 2 questions on this topic, but there are no helping answers.
I am assuming this is for ACA Air IRS submissions. We uploaded the .cer file to the IRS site, where you associate your TCC (in the format BBBBB, for example) with the .cer you uploaded. The stack we used were: Oracle's JDK 8, WSS4J v2.1.4, and CXF v3.1.4. Here is sample Java code we used for signing the reference elements that the IRS wants signed:
public static SOAPMessage signSoapMessage(SOAPMessage message,
String keystorePassword, String irsPrivateKeyPassword, Class<?> clazz) throws WSSecurityException {
//TODO remove below hard coded
final String _irsPrivateKeyPassword = "yourprivatekeypasswordyougotfromCA";
final String _keystorePassword = "yourpasswordtoyourJKS";
keystorePassword = _keystorePassword;
irsPrivateKeyPassword = _irsPrivateKeyPassword;
PrivateKeyEntry privateKeyEntry = getPrivateKeyEntry(keystorePassword,
irsPrivateKeyPassword);
PrivateKey signingKey = privateKeyEntry.getPrivateKey();
X509Certificate signingCert = (X509Certificate) privateKeyEntry
.getCertificate();
//TODO add alias to database
final String alias = "thealiasforthiscertandprivatekey";
final int signatureValidityTime = 3600; // 1hour in seconds
WSSConfig config = WSSConfig.getNewInstance();
//config.setWsiBSPCompliant(true);
WsuIdAllocator idAllocator = new WsuIdAllocator() {
@Override
public String createSecureId(String prefix, Object o) {
//e.g. <ds:KeyInfo Id="KI-9F6A3A6B473244859D59710683FABFE1">
if(prefix.equals("KI-"))
return "KI-" + UUID.randomUUID().toString().replace("-", "").toUpperCase();
//e.g. <wsse:SecurityTokenReference wsu:Id="STR-E6C0BA1EC73A4AB3BECFEBF6075EF175">
else if (prefix.equals("STR-"))
return "STR-" + UUID.randomUUID().toString().replace("-", "").toUpperCase();
//TODO why is there a condition where prefix.equals("X509") and o.toString() is the public cert?
else
return null;
}
//e.g. <ds:Signature Id="SIG-9850525DA06CE28ED91448475206411147"
@Override
public String createId(String prefix, Object o) {
return "SIG-" + UUID.randomUUID().toString().replace("-", "").toUpperCase();
}
};
config.setIdAllocator(idAllocator );
//WSSecSignature wsSecSignature = new WSSecSignature(config);
WSSecSignature wsSecSignature = new WSSecSignature();
wsSecSignature.setX509Certificate(signingCert);
wsSecSignature.setUserInfo(alias, new String(keystorePassword.toCharArray()));
wsSecSignature.setUseSingleCertificate(true);
wsSecSignature.setKeyIdentifierType(WSConstants.X509_KEY_IDENTIFIER);
//wsSecSignature.setKeyIdentifierType(WSConstants.SKI_KEY_IDENTIFIER);
wsSecSignature.setDigestAlgo(WSConstants.SHA1);
wsSecSignature.setSignatureAlgorithm(WSConstants.RSA_SHA1);
wsSecSignature.setSigCanonicalization(WSConstants.C14N_EXCL_WITH_COMMENTS);
try {
Document document = toDocument(message);
WSSecHeader secHeader = new WSSecHeader(document);
//secHeader.setMustUnderstand(true);
secHeader.insertSecurityHeader();
WSSecTimestamp timestamp = new WSSecTimestamp();
timestamp.setTimeToLive(signatureValidityTime);
document = timestamp.build(document, secHeader);
List<WSEncryptionPart> wsEncryptionParts = new ArrayList<WSEncryptionPart>();
//Very important, ordering of the parts is critical: refer to page 34 of the guide
//for ACAGetTransmitterBulkRequestService, it is Timestamp, ACATransmitterManifestReqDtl, ACABusinessHeader
if(clazz.equals(ACATransmitterManifestReqDtl.class)){
WSEncryptionPart timestampPart = new WSEncryptionPart("Timestamp",
WSConstants.WSU_NS, "");
//This is very important, Timestamp needs to be fist
wsEncryptionParts.add(timestampPart);
WSEncryptionPart aCATransmitterManifestReqDtlPart = new WSEncryptionPart(
"ACATransmitterManifestReqDtl",
"urn:us:gov:treasury:irs:ext:aca:air:7.0", "");
wsEncryptionParts.add(aCATransmitterManifestReqDtlPart);
WSEncryptionPart aCABusinessHeaderPart = new WSEncryptionPart(
"ACABusinessHeader",
"urn:us:gov:treasury:irs:msg:acabusinessheader", "");
wsEncryptionParts.add(aCABusinessHeaderPart);
}
//for ACAGetTransmitterBulkRequestStatus, it is Timestamp, ACABusinessHeader, ACABulkRequestTransmitterStatusDetailRequest
else if(clazz.equals(ACABulkRequestTransmitterStatusDetailRequest.class)){
WSEncryptionPart timestampPart = new WSEncryptionPart("Timestamp",
WSConstants.WSU_NS, "");
//This is very important, Timestamp needs to be fist
wsEncryptionParts.add(timestampPart);
WSEncryptionPart aCABusinessHeaderPart = new WSEncryptionPart(
"ACABusinessHeader",
"urn:us:gov:treasury:irs:msg:acabusinessheader", "");
wsEncryptionParts.add(aCABusinessHeaderPart);
WSEncryptionPart aCABulkRequestTransmitterStatusDetailRequestPart = new WSEncryptionPart(
"ACABulkRequestTransmitterStatusDetailRequest",
"urn:us:gov:treasury:irs:msg:irstransmitterstatusrequest", "");
wsEncryptionParts.add(aCABulkRequestTransmitterStatusDetailRequestPart);
}
wsSecSignature.getParts().addAll(wsEncryptionParts);
Properties properties = new Properties();
properties.setProperty("org.apache.ws.security.crypto.provider",
"org.apache.ws.security.components.crypto.Merlin");
Crypto crypto = CryptoFactory.getInstance(properties);
KeyStore keystore = KeyStore.getInstance("JKS");
java.io.FileInputStream fis = null;
try {
fis = new java.io.FileInputStream(System.getProperty("java.home") + "//lib//security//meckeystore.jks");
if(fis != null) {
keystore.load(fis, keystorePassword.toCharArray());
} else {
//TODO: replace with custom MEC exception
throw new Exception("Unable to read keystore file.");
}
} finally {
if (fis != null) {
fis.close();
}
}
keystore.setKeyEntry(alias, signingKey, keystorePassword.toCharArray(),
new Certificate[]{signingCert});
((Merlin) crypto).setKeyStore(keystore);
crypto.loadCertificate(new ByteArrayInputStream(signingCert.getEncoded()));
document = wsSecSignature.build(document, crypto, secHeader);
updateSOAPMessage(document, message);
} catch (Exception e) {
// throw new
// WSSecurityException(WSSecurityException.Reason.SIGNING_ISSUE, e);
e.printStackTrace();
}
return message;
}
/**
* Changes the SOAPMessage to a dom.Document.
*/
private static Document toDocument(SOAPMessage soapMsg) throws TransformerException,
SOAPException, IOException {
Source src = soapMsg.getSOAPPart().getContent();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMResult result = new DOMResult();
transformer.transform(src, result);
return (Document) result.getNode();
}
//https://svn.apache.org/repos/asf/webservices/wss4j/branches/WSS4J_1_1_0_FINAL/test/wssec/SOAPUtil.java
private static SOAPMessage updateSOAPMessage(Document doc,
SOAPMessage message)
throws Exception {
DOMSource domSource = new DOMSource(doc);
message.getSOAPPart().setContent(domSource);
return message;
}
Here is the sample SOAP request
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:us:gov:treasury:irs:ext:aca:air:7.0" xmlns:urn1="urn:us:gov:treasury:irs:common">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:Security SOAP-ENV:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<ds:Signature Id="SIG-d62ad452-5219-4baf-9708-3ae1d2cf7e92" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#WithComments"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#TS-6450a75d-45e4-463b-a1e8-2d3ae3b4c57c">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<InclusiveNamespaces PrefixList="wsse SOAP-ENV soap urn urn1" xmlns="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>ojqiqHiXxPWIaEumCOO3bKJZ73A=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#id-0EB7188D138D494EA44AC09FE03F6BEE">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<InclusiveNamespaces PrefixList="SOAP-ENV soap urn urn1" xmlns="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>cm3KGHFWHyJcBU9MEQzw6Ru04z0=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#id-1183235E8ED44DE99B069411CD4837DC">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<InclusiveNamespaces PrefixList="SOAP-ENV soap urn urn1" xmlns="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>6nM3ONVPyHtiupcznWiixpNG82k=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>removed==</ds:SignatureValue>
<ds:KeyInfo Id="KI-e6a6c681-ccf7-49ab-a37f-dac69c52d32a">
<wsse:SecurityTokenReference wsu:Id="STR-c1b4d47e-fda6-49b0-a58a-7df24ab43e13">
<wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">removed</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
<wsu:Timestamp wsu:Id="TS-6450a75d-45e4-463b-a1e8-2d3ae3b4c57c">
<wsu:Created>2016-01-27T23:59:36.352Z</wsu:Created>
<wsu:Expires>2016-01-28T00:59:36.352Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
<ACATransmitterManifestReqDtl ns3:Id="id-0EB7188D138D494EA44AC09FE03F6BEE" xmlns="urn:us:gov:treasury:irs:ext:aca:air:7.0" xmlns:ns2="urn:us:gov:treasury:irs:common" xmlns:ns3="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<PaymentYr>2015</PaymentYr>
<PriorYearDataInd>0</PriorYearDataInd>
<ns2:EIN></ns2:EIN>
<TransmissionTypeCd>O</TransmissionTypeCd>
<TestFileCd>T</TestFileCd>
<TransmitterNameGrp>
<BusinessNameLine1Txt></BusinessNameLine1Txt>
<BusinessNameLine2Txt>Health Systems</BusinessNameLine2Txt>
</TransmitterNameGrp>
<CompanyInformationGrp>
<CompanyNm></CompanyNm>
<MailingAddressGrp>
<USAddressGrp>
<AddressLine1Txt></AddressLine1Txt>
<ns2:CityNm>Rockville</ns2:CityNm>
<USStateCd>MD</USStateCd>
<ns2:USZIPCd></ns2:USZIPCd>
</USAddressGrp>
</MailingAddressGrp>
<ContactNameGrp>
<PersonFirstNm></PersonFirstNm>
<PersonMiddleNm>X</PersonMiddleNm>
<PersonLastNm></PersonLastNm>
</ContactNameGrp>
<ContactPhoneNum></ContactPhoneNum>
</CompanyInformationGrp>
<VendorInformationGrp>
<VendorCd>I</VendorCd>
<ContactNameGrp>
<PersonFirstNm></PersonFirstNm>
<PersonMiddleNm></PersonMiddleNm>
<PersonLastNm></PersonLastNm>
</ContactNameGrp>
<ContactPhoneNum></ContactPhoneNum>
</VendorInformationGrp>
<TotalPayeeRecordCnt>1000</TotalPayeeRecordCnt>
<TotalPayerRecordCnt>1</TotalPayerRecordCnt>
<SoftwareId></SoftwareId>
<FormTypeCd>1094/1095B</FormTypeCd>
<ns2:BinaryFormatCd>application/xml</ns2:BinaryFormatCd>
<ns2:ChecksumAugmentationNum>5bae956d7c6a01c95ce570dd11debe78</ns2:ChecksumAugmentationNum>
<ns2:AttachmentByteSizeNum>5938</ns2:AttachmentByteSizeNum>
<DocumentSystemFileNm>1094B_Request_BBBBB_20151019T121002000Z.xml</DocumentSystemFileNm>
</ACATransmitterManifestReqDtl>
<urn2:ACABusinessHeader wsu:Id="id-1183235E8ED44DE99B069411CD4837DC" xmlns:urn2="urn:us:gov:treasury:irs:msg:acabusinessheader" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<urn:UniqueTransmissionId>d81ead9b-1223-4d28-8d46-f7af58710268:SYS12:BBBBB::T</urn:UniqueTransmissionId>
<urn1:Timestamp>2016-01-27T23:59:36Z</urn1:Timestamp>
</urn2:ACABusinessHeader>
<Action xmlns="http://www.w3.org/2005/08/addressing">RequestSubmissionStatusDetail</Action>
The key really for us was this from the IRS documentation because we were using Apache CXF v2.1.4:
5.4.2 (from IRS documentation) Message Attachment Content Type ISS-A2AAIR web services require transmitters to use SOAP-over-HTTP messaging with MTOM to send XML data files. The file that is encoded in the MTOM attachment must be uncompressed native XML. The content type for the MTOM encoded binary object identified in the Manifest header must be “application/xml”. The content-transfer-encoding of the Form Data File must be 7-bit.
Inside apache-cxf-3.1.4-src/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java
194 private static void writeHeaders(String contentType, String attachmentId,
195 Map<String, List<String>> headers, Writer writer) throws IOException {
196 // writer.write("\r\nContent-Type: ");
197 // writer.write(contentType);
198 writer.write("\r\nContent-Type: application/xml");
199 // writer.write("\r\nContent-Transfer-Encoding: binary\r\n");
200 writer.write("\r\nContent-Transfer-Encoding: 7bit\r\n");