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.
问题:
回答1:
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. if(prefix.equals("KI-")) return "KI-" + UUID.randomUUID().toString().replace("-", "").toUpperCase(); //e.g. 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. wsEncryptionParts = new ArrayList(); //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
ojqiqHiXxPWIaEumCOO3bKJZ73A= cm3KGHFWHyJcBU9MEQzw6Ru04z0= 6nM3ONVPyHtiupcznWiixpNG82k= removed== removed 2016-01-27T23:59:36.352Z 2016-01-28T00:59:36.352Z 2015 0 O T Health Systems Rockville MD X I 1000 1 1094/1095B application/xml 5bae956d7c6a01c95ce570dd11debe78 5938 1094B_Request_BBBBB_20151019T121002000Z.xml d81ead9b-1223-4d28-8d46-f7af58710268:SYS12:BBBBB::T 2016-01-27T23:59:36Z RequestSubmissionStatusDetail
The key really for us was this from the IRS documentation because we were using Apache CXF v2.1.4:
Big Hack for 7bit content type encoding and content type
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> 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");
回答2:
Prabhat, I think MTOM and SwA cannot go hand in hand. You can use either one of them. When you enable MTOM, you cannot use Attachment APIs (SwA).