We need to create a WS client to interface with the IRS. We are following the steps the IRS defines on page 31 of their guide https://www.irs.gov/PUP/for_taxpros/software_developers/information_returns/AIR%20Submission%20Composition%20and%20Reference%20Guide%20TY2015.pdf
It looks like standard WS-Security that WSS4J can handle. But when using WS44J and hitting the IRS WS we keep on getting "Invalid WS Security Header. Please try again"
So, can someone please tell me if the below WSS4J code is in fact (1) calculating the digest of elements ACABusinessHeader, ACATransmitterManifestReqDtl and Timestamp (2) collecting these reference elements within a SignedInfo element (3) calculating the digest of the SignedInfo element, signing that digest and putting the signature value in a SignatureValue (4) placing keying information in the KeyInfo element:
public static SOAPMessage signSoapMessage(SOAPMessage message,
String keystorePassword, String irsPrivateKeyPassword,
char[] passphrase) throws WSSecurityException {
PrivateKeyEntry privateKeyEntry = getPrivateKeyEntry(keystorePassword,
irsPrivateKeyPassword);
PrivateKey signingKey = privateKeyEntry.getPrivateKey();
X509Certificate signingCert = (X509Certificate) privateKeyEntry
.getCertificate();
final String alias = "signingKey";
final int signatureValidityTime = 3600; // 1hour in seconds
WSSConfig config = new WSSConfig();
config.setWsiBSPCompliant(true);
WSSecSignature builder = new WSSecSignature(config);
builder.setX509Certificate(signingCert);
builder.setUserInfo(alias, new String(passphrase));
builder.setUseSingleCertificate(true);
builder.setKeyIdentifierType(WSConstants.X509_KEY_IDENTIFIER);
builder.setDigestAlgo(WSConstants.SHA1);
builder.setSignatureAlgorithm(WSConstants.RSA_SHA1);
builder.setSigCanonicalization(WSConstants.C14N_EXCL_WITH_COMMENTS);
try {
Document document = toDocument(message);
WSSecHeader secHeader = new WSSecHeader();
//secHeader.setMustUnderstand(true);
secHeader.insertSecurityHeader(document);
WSSecTimestamp timestamp = new WSSecTimestamp();
timestamp.setTimeToLive(signatureValidityTime);
document = timestamp.build(document, secHeader);
List<WSEncryptionPart> parts = new ArrayList<WSEncryptionPart>();
WSEncryptionPart timestampPart = new WSEncryptionPart("Timestamp",
WSConstants.WSU_NS, "");
WSEncryptionPart aCATransmitterManifestReqDtlPart = new WSEncryptionPart(
"ACATransmitterManifestReqDtl",
"urn:us:gov:treasury:irs:ext:aca:air:7.0", "");
WSEncryptionPart aCABusinessHeaderPart = new WSEncryptionPart(
"ACABusinessHeader",
"urn:us:gov:treasury:irs:msg:acabusinessheader", "");
parts.add(timestampPart);
parts.add(aCATransmitterManifestReqDtlPart);
parts.add(aCABusinessHeaderPart);
builder.setParts(parts);
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");
keystore.load(null, passphrase);
keystore.setKeyEntry(alias, signingKey, passphrase,
new Certificate[] { signingCert });
((Merlin) crypto).setKeyStore(keystore);
crypto.loadCertificate(new ByteArrayInputStream(signingCert
.getEncoded()));
document = builder.build(document, crypto, secHeader);
updateSOAPMessage(document, message);
} catch (Exception e) {
// throw new
// WSSecurityException(WSSecurityException.Reason.SIGNING_ISSUE, e);
e.printStackTrace();
}
return message;
}
private static Document toDocument(SOAPMessage soapMsg)
throws TransformerConfigurationException, 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 our output:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:oas="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:urn="urn:us:gov:treasury:irs:ext:aca:air:7.0" xmlns:urn1="urn:us:gov:treasury:irs:common"
xmlns:urn2="urn:us:gov:treasury:irs:msg:acabusinessheader" xmlns:urn3="urn:us:gov:treasury:irs:msg:acasecurityheader"
xmlns:urn4="urn:us:gov:treasury:irs:msg:irsacabulkrequesttransmitter"
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">
<S:Header>
<wsse:Security S:mustUnderstand="1">
<ds:Signature Id="SIG-2">
<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-1">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces
PrefixList="wsse S ds oas urn urn1 urn2 urn3 urn4 wsse"
xmlns:ec="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>nTyiWyyyQd+JQZvU2QPY1QnInd4=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#aCATransmitterManifestReqDtl">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces
PrefixList="S ds oas urn urn1 urn2 urn3 urn4 wsse wsu"
xmlns:ec="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>3pUoQp4S5sKXa6o9+MamJbji8vs=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#acabusinessheader">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces PrefixList="S ds oas urn urn1 urn3 urn4 wsse"
xmlns:ec="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>9SvmkYlD+ItpUctUaQZTH5pGXjc=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>qgmh8LN8KwR0KZf508heS6whaM7ZGeW88dl/0awLYCd3lzle8THHUUstYKCaZJi6XF1DGJjDrIq81FEszUSp9Pa1akZ3r6rB8Oi2dOlgzHq6H+lYaHFVwYnMHHyFpEHRQJO36OaXKl25SILDHWxvrFRXf21NDnWszXKXnvvbSjrkzTTzWo4wRO2ftUQq2F69MPM3OsG981rmPWUd5z/KC5jVTsqELBtSM5L8ehOihXoJ0uNwdw1HZzh7xYXme6bXU++4w2I8x5vLjvnCcD1TIuNLvrK6HN414KylAEoxAUqAkWo69GJyx/18soLFXVaLKbwAhQkplkaJwcWKoEiRaw==
</ds:SignatureValue>
<ds:KeyInfo Id="KI-4E5E7B5A7DFD75103414509160809772">
<wsse:SecurityTokenReference wsu:Id="STR-4E5E7B5A7DFD75103414509160809783">
<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-1">
<wsu:Created>2015-12-24T00:14:40.968Z</wsu:Created>
<wsu:Expires>2015-12-24T01:14:40.968Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
<ACATransmitterManifestReqDtl
xmlns:ns3="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
ns3:Id="aCATransmitterManifestReqDtl" xmlns="urn:us:gov:treasury:irs:ext:aca:air:7.0"
xmlns:ns2="urn:us:gov:treasury:irs:common">
<PaymentYr>2015</PaymentYr>
<PriorYearDataInd>0</PriorYearDataInd>
<ns2:EIN>blah</ns2:EIN>
<TransmissionTypeCd>O</TransmissionTypeCd>
<TestFileCd>T</TestFileCd>
<TransmitterNameGrp>
<BusinessNameLine1Txt>blah</BusinessNameLine1Txt>
<BusinessNameLine2Txt>blah</BusinessNameLine2Txt>
</TransmitterNameGrp>
<CompanyInformationGrp>
<CompanyNm>blah</CompanyNm>
<MailingAddressGrp>
<USAddressGrp>
<AddressLine1Txt>blah</AddressLine1Txt>
<AddressLine2Txt>3rd Floor</AddressLine2Txt>
<ns2:CityNm>blah</ns2:CityNm>
<USStateCd>CA</USStateCd>
<ns2:USZIPCd>12345</ns2:USZIPCd>
<ns2:USZIPExtensionCd>1234</ns2:USZIPExtensionCd>
</USAddressGrp>
</MailingAddressGrp>
<ContactNameGrp>
<PersonFirstNm>blah</PersonFirstNm>
<PersonMiddleNm>X</PersonMiddleNm>
<PersonLastNm>blah</PersonLastNm>
<SuffixNm />
</ContactNameGrp>
<ContactPhoneNum>1231231234</ContactPhoneNum>
</CompanyInformationGrp>
<VendorInformationGrp>
<VendorCd>I</VendorCd>
<ContactNameGrp>
<PersonFirstNm>blah</PersonFirstNm>
<PersonMiddleNm>X</PersonMiddleNm>
<PersonLastNm>blah</PersonLastNm>
<SuffixNm />
</ContactNameGrp>
<ContactPhoneNum>5556651212</ContactPhoneNum>
</VendorInformationGrp>
<TotalPayeeRecordCnt>1000</TotalPayeeRecordCnt>
<TotalPayerRecordCnt>1</TotalPayerRecordCnt>
<SoftwareId>blah</SoftwareId>
<FormTypeCd>1094/1095B</FormTypeCd>
<ns2:BinaryFormatCd>application/xml</ns2:BinaryFormatCd>
<ns2:ChecksumAugmentationNum>04ff9f93f9b797ae51ea2ac8bf9c24d2
</ns2:ChecksumAugmentationNum>
<ns2:AttachmentByteSizeNum>237018</ns2:AttachmentByteSizeNum>
<DocumentSystemFileNm>1000_form1094BUpstreamDetailType.xml
</DocumentSystemFileNm>
</ACATransmitterManifestReqDtl>
<urn2:ACABusinessHeader wsu:Id="acabusinessheader">
<urn:UniqueTransmissionId>1</urn:UniqueTransmissionId>
<urn1:Timestamp>2015-12-24T00:19:40.826Z</urn1:Timestamp>
</urn2:ACABusinessHeader>
</S:Header>
<S:Body>
<ns8:ACABulkRequestTransmitter xmlns="http://www.w3.org/2000/09/xmldsig#"
xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:ns3="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:ns4="urn:us:gov:treasury:irs:common" xmlns:ns5="urn:us:gov:treasury:irs:ext:aca:air:7.0"
xmlns:ns6="urn:us:gov:treasury:irs:msg:acabusinessheader" xmlns:ns7="urn:us:gov:treasury:irs:msg:acasecurityheader"
xmlns:ns8="urn:us:gov:treasury:irs:msg:irsacabulkrequesttransmitter">
<ns4:BulkExchangeFile>H4sIAAAAA</ns4:BulkExchangeFile>
</ns8:ACABulkRequestTransmitter>
</S:Body>
EDIT Here is the improved Java code that can bypass the infamous "Invalid WS Security Header. Please try again." message. Our stack is Apache CXF 3.1.4 and WSS4J 2.1.4
https://stackoverflow.com/a/34959348/3724142
Refer to the above link for latest source