Guide to authenticated web service callouts using two way ssl


Published on

This presentation takes a step by step approach to integrating and webservices using mutual SSL.

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • Creative Commons Attribution-Share Alike 3.0 United States License
  • Guide to authenticated web service callouts using two way ssl

    1. 1. Guide to Force.comAuthenticated Web ServiceCallouts Using Two-Way SSLByIvo Brett@ivobrettCertified Professional
    2. 2.  Key Concepts Step by Step instruction  Step 1: Profile Permissions  Step 2: Public Certificate & Set up Remote Site  Step 3: Custom Object for Security Credentials  Step 4: WSDL APEX Class Generation  Step 5: Modify the WSDL generated APEX classes  Step 6: Write Controller Class to call webservice Conclusions
    3. 3. Key ConceptsMutual SSL authentication ensures that verifies the identity of the web services endpoint and thatthe web services endpoint verifies the identity of through an exchange of certificates. Controller class WSDL apex class MUTUAL SSL* SECURITY WEB generator GATEWAY SERVICE Cert & Remote Keystore Sites [Optional] Web Service FORCE.COM Web Service Implementation Gateway • Cert & Keystore: Used for managing certificates for external site authentication • Remote Sites: Contains a list of Web addresses that your org can invoke from • WSDL apex class: Allows for the generation of apex class from WSDL file • Controller class: Controller class that implements 2 options: • Option 1: HTTP based XML request • Option 2: webservice callout request using wsdl generated classes • Security GW: Mutual SSL authentication may be implemented by an external Security GW*Public certificates returned by the web services endpoints are dynamically validated with the Certificate Authority by the platform
    4. 4. Key Concepts (WS Security Header)In addition to the Mutual SSL, the <wsse:Security> header block provides a mechanism for attaching security-related information (see WSSE specification) SOAP <wsse:Security xmlns:wsse="http://docs.oasis- <Security> 1.0.xsd"> <wsse:UsernameToken> <wsse:Username>USERNAME</wsse:Username> <wsse:Password>PASSWORD</wsse:Password> </wsse:UsernameToken> </Security> </wsse:Security>
    5. 5. Step 1: Profile Permissions In order to provide a secure implementation, it is recommended to create a new profile called “API Administrator” with the following system permission: • Enable the “Customize Application” permission so that the user can access the “Certificate and Key Management” and “Remote Site Settings” under “Security Controls” • Enable the “View Encrypted Data” permission so that the user can View the value of encrypted fields in plain text*. This means that allow those users with the API administrator profile can access or change the certificates and security credentials for the webservice integration.*It is recommended to use Encrypted Fields to store the username and password for the WS Security Header so thatonly users with the “View Encrypted Data” permission can see this information.
    6. 6. Step 2: Public Certificate & Set up Remote Site • Create a self-signed certificate using the “Certificate and Key Management” under “Security Controls” and create a self-signed certificate • Download the certificate (public) and provide to the webservices endpoint provider*. They may need to configure on their side (e.g. in their security gateway). • Create a new Remote Site using the “Remote Site Settings” option under “Security Controls” and provide state the webservice url in the “Remote Site URL”*Note – there is no need to store the public certificate on the side as will automatically receivedetails on the public certificate from the web services endpoint and will contact the certificate authority to verify
    7. 7. Step 3: Custom Object for Security Credentials • Create a new Custom Object called “wsSecurity” containing • Encrypted text fields for Username and Password • Non-encrypted text field for endpoint • Create a new Custom Object Tab for Object “wsSecurity” • Create an instance of the wsSecurity custom object and data fill with your username, password and endpoint • Create a Utility Class (see next slide for apex class) that will be used to search the table of wsSecurity Custom Objects to find the correct username/password matching a particular endpoint**Note – this approach is useful as it allows different security credentials to be defined per WS endpoint
    8. 8. public class WSSecurityUtil { WSSecurityUtil Apex Class private static map<String, credential> crs = new map<String, credential>(); private class credential { private String userName; private String password; private String endpoint; } public static String getUserName() { return getUserName(null);} public static String getPassword() { return getPassword(null);} public static String getUserName(String endpoint) { return getCredential(endpoint).userName;} public static String getPassword(String endpoint) { return getCredential(endpoint).password;} private static credential getCredential(String endpoint) { credential cr = null; if (crs.isEmpty()) { getFieldValues();} if(crs.size()>0) { if(crs.size()!=1) { for(String ep : crs.keySet()) { if(endpoint!=null && ep!=null && endpoint.contains(ep)) { cr = crs.get(ep);} } } if(cr==null || crs.size()==1) { cr = crs.values()[0]; } } return cr; } private static void getFieldValues() { for(wsSecurity__c wss : [Select Username__c, Password__c, Endpoint__c From wsSecurity__c Order By CreatedDate desc]) { credential c = new credential(); c.username = wss.Username__c; c.password = wss.Password__c; c.endpoint = wss.Endpoint__c; crs.put(wss.Endpoint__c, c); } }}
    9. 9. Step 4: APEX Class Generation from WSDL (requires edit) WSDL generator does not properly support WSDL imports andannotation. Therefore the wsdl file to generate the APEX classes for the webservice integration needs to be manually edited as follows:• Remove all annotations. e.g. anywhere you see the following remove: <xsd:annotation> <xsd:documentation> e.g. This is a documentation comment </xsd:documentation> </xsd:annotation>• Manually insert the XSD that is referenced as an import. e.g. If you see thefollowing – manually import the XSD into the file <xsd:import namespace="" schemaLocation="data_1_0.xsd"/>• Create APEX classes from the WSDL generator
    10. 10. Step 5: Modify the WSDL generated APEX classes The APEX class generated from the WSDL needs to be modified to support the WS Security Header. • Add the following variables at the top of the “SoapPort” class //Added: for webservices username/password in webservice headers public SOASecurityElement.Security_element Security; private String Security_hns = Security= wss-wssecurity-secext-1.0.xsd; //End of Added: for webservices username/password in webservice headers • In the SOAP methods add the following at the start of the method code //Added: for webservices username/password in webservice headers Security = new SOASecurityElement.Security_element(endpoint_x); //End of Added: for webservices username/password in webservice headersNote – the system generated WebServiceCallout.invoke method is called with the “this” variable which will pass theSecurity Variable to the system code which will be able to access the correct credentials based on endpoint
    11. 11. Step 5a: Supporting “choice” in XSDThe APEX class generator doesnt not support “choice” XSD formattingresulting in the webservice methods being defined with all input variablesand this can lead to incorrect XML being sent. <xsd:complexType name="getMyChoiceExample"> <xsd:sequence> <xsd:choice> <xsd:element name=“variableOne" type="cpd:stringSize255"/> <xsd:element name=“variableTwo" type="cpd:stringSize255"/> </xsd:choice> <xsd:element name=“variableThree" type="cpd:stringSize255"/> </xsd:sequence> </xsd:complexType> public class getMyChoiceExample { public String variableOne; public String variableTwo; public String variableThree; private String[] variableOne_type_info = new String[]{variableOne,,stringSize255,1,1,false}; private String[] variableTwo_type_info = new String[]{variableTwo,,stringSize255,1,1,false}; private String[] variableThree_type_info = new String[]{variableThree,,stringSize255,1,1,false}; private String[] apex_schema_type_info = new String[]{,false,false}; // code in generated class commented out // private String[] field_order_type_info = new String[]{‘variableOne,‘variableTwo,‘variableThree}; // code added to generated class private String[] field_order_type_info { get{ List<String> fieldOrder = new List<String>(); if(variableOne!=null) fieldOrder.add(‘variableOne); if(variableTwo!=null) fieldOrder.add(‘variableTwo); fieldOrder.add(‘variableThree); return fieldOrder; }} }
    12. 12. Step 6a: Write Controller Class to call webservice (apex) The following code is used in a controller class to call the wsdl generated apex class. try { YOURWS.METHODSoapPort port= new YOURWS.METHODSoapPort(); port.clientCertName_x = CertificateName; //this is the name of the certificate created in Step 2 port.timeout_x = 60000; //currently set at 60000 for all services calls YOURWS.getMyResponse upgradeEligibility = port.getMy(null,this.variableTwo,this.variableThree); } catch (Exception e) { System.debug(Exception Message: + e.getMessage()); }Note – unfortunately, the level of error messaging that is returned in the exception is not low enough to allow fordebugging. Therefore in early stages it is recommended to use the HTTP method (e.g. Step 6b) in troubleshooting
    13. 13. Step 6b: Write Controller Class to call webservice (http)The following code is used in a controller class to call the webservice using“hand crafted” XML strings and HTTP request String endpoint =; String envelopeXML = <soapenv:Envelope xmlns:soapenv="" xmlns:man="http:// ">; String innerXML = <man:getMyChoiceExample> + <man:variableOne>+this.variableOne+</man:variableOne> + <man:variableThree>+this.variableTwo+</man:variableThree> + </man:getUpgradeEligibility>; try { soaResponse = sendRequest(endpoint,envelopeXML,innerXML); } catch (Exception e) { System.debug(Exception Message: + e.getMessage()); }
    14. 14. Functions used by the HTTP method//The following is the HTTP way to call the SOA wenservice (alternative from the wsdlgenerated code which does not allow such good fault debugging) // Common method to get the complete request SOAP message public String sendRequest(String endpoint, String EnvelopeXML, String innerBodyXML){ String resultXML; String soapMessage = EnvelopeXML; soapMessage += getHeaderXML(endpoint); soapMessage += <soapenv:Body>; soapMessage += innerBodyXML; soapMessage += </soapenv:Body>; soapMessage += </soapenv:Envelope>; //reqXML = soapMessage; System.debug(soapMessage: + soapMessage); Dom.Document doc = processRequest(endpoint, soapMessage); if (doc != null ) { resultXML =doc.toXmlString(); } return resultXML; }
    15. 15. Functions used by the HTTP method// This is common header code, the credentials are supplied by the SOA gateway team andfor us need to be held in encrypted fields // The transaction Id is for traceability it identifies each message and its origin private Static String getHeaderXML(String endpoint) { String header = ; header = <soapenv:Header> + <wsse:Security xmlns:wsse=""> + <wsse:UsernameToken> + <wsse:Username> + WSSecurityUtil.getUserName(endpoint) +</wsse:Username> + <wsse:Password> + WSSecurityUtil.getPassword(endpoint) +</wsse:Password> + </wsse:UsernameToken> + </wsse:Security> + <cor:SOAConsumerTransactionID xmlns:cor="http://">+SFDC- + + : +UserInfo.getOrganizationId() + - + UserInfo.getUserId()+ - + UserInfo.getUserName()+ </cor:SOAConsumerTransactionID> + <cor:debugFlagxmlns:cor="">true</cor:debugFlag> + </soapenv:Header>; return header; }
    16. 16. Functions used by the HTTP methodprivate static Dom.Document processRequest(String endpoint, String message) { Dom.Document doc; Http h = new Http(); HttpRequest req = new HttpRequest(); req.setMethod(POST); req.setEndpoint(endpoint); req.setBody(message); req.setTimeout(60000); // set this for the timeout req.setClientCertificateName(CertificateName); // Set this to your cert name HttpResponse res = h.send(req); System.debug(res.getBody()); doc = res.getBodyDocument(); return doc;}
    17. 17. ConclusionsThe process for implementing 2-way mutual SSL authenticated web services isa challenging exerciseThere are few examples that show fully how this is done.The WSDL generation has several limitations that need to be overcome bymanually editing the wsdl files.The apex classes implementing webservice callouts that are generated fromWSDL need to be modified to allow for security header addition and also tohandle a limitation around XSD “choice”The best approach is to use HTTP request with “hand-crafted” XML requestsin order to troubleshoot and ensure that the security headers and certificateexchange are working.Once the HTTP request method is working then use the WSDL generated apexclasses.
    18. 18. THANK YOUQuestions and ideas: Brett@ivobrett