IRS Soap Fault - Invalid WS Security Header

3.5k views Asked by At

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.

2

There are 2 answers

6
Tim Schumacher On BEST ANSWER

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:

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<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");
0
UserAlpha On

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).