+ Start a Discussion
philbophilbo 

Mutual SSL on WebSvc callouts - getting a jump on Summer '08

Hey,

Looking at the Summer '08 Apex dev guide re setting up two-way SSL in WebSvc callouts.  The instructions are as follows (pp. 144-146 of the guide):
  1. Set up your Web server to request the SSL client certificate.
  2. Generate a PKCS12 key store with your client certificate.
  3. Verify that the server's trust keystore contains/accepts your client certificate
  4. Encode your client certificate keystore in base64 and assign it to a variable on the stub (in this case, clientCert_x).
Steps 1 thru 3 are done on the web server (the callout endpoint), is that correct?

Step 4 is the key one.  There's an example in the doc right after these steps, that purports to work with the sample WSDL file shown a page or two further down.  The example refers to two stub variables:  'clientCert_x' (as in the instructions) and 'clientCertPasswd_x'.  These are nowhere to be found in the sample WSDL nor its derived Apex stub class.

Presumably these variables, minus the '_x' suffixes, need to appear in the WSDL from the WebSvc endpoint as header variables, is that correct?  Is this something that falls naturally out of the first three steps above (plus a re-generation of the WSDL itself)?  You can't just go in and modify the stub class and add these variables manually, surely, as they will have no meaning to whatever consumes the class.

Thanks!  Would like to hit the ground running on this, when Summer '08 makes its production debut.

-philbo
Best Answer chosen by Admin (Salesforce Developers) 
Steven LawranceSteven Lawrance
Hi philbo,

Sorry for the late reply. It appears that the base64 input that you are passing in is from the PEM file, which is a base64 encoded version of the DER format. Unfortunately, this will not work as a client certificate in Apex. The certificate will need to be a base64 encoded version of the PKCS12 certificate.

To get a base64 encoded version of the PKCS12 certificate, replace the last step that you listed -- the keytool invocation -- with a base64 encoder invocation. This assumes that you entered a private key password in the second-to-last step, `openssl pkcs12 -export -clcerts -in c:/temp/ssl/client/client1.pem -inkey c:/temp/ssl/client/client1.key -out c:/temp/ssl/client/client1.p12 -name myAlias`. It is the client1.p12 file that you will need to encode using base64 for clientCert_x, and the private key password that you entered when OpenSSL asked for it should be used for clientCertPassword_x.

Windows, unfortunately, does not ship with a base64 encoder command line tool as far as I am aware, so you will either need to download one and run it or use a web-based base64 encoder. Fortunately, because your private key is encrypted in the client1.p12 file, using an online base64 encoder should be okay as long as your password is sufficiently strong. A local encoder is thus favorable to an online encoder.

The reason why Apex needs a PKCS12 file and not a DER or PEM file is that a single PKCS12 file can contain certificates and a key in a standardized manner, and the key can optionally be encrypted using a passphrase. A PEM file can technically contain multiple certificates and keys through concatenation, since it's a text file, but not all tools and applications support this. As a result, PEM and DER based solutions typically use at least three different files: key, certificate, and signing certificate (the signing certificate's signing certificate and any other signing certificates in the chain might be required, too). Thus, PKCS12 simplifies things by putting it all into one file. The base64-encoded DER content that you tried to use from the PEM file likely contained only the certificate and not the private key nor the certificate's signing certificate.

Hopefully, this helps. Please let this thread know if the above worked for you.

Thanks,

Steven Lawrance

All Answers

cheenathcheenath
You will have to regenerate the stub from wsdl (wsdl2apex).
Once you do that, the new stub should have the clientCert_x
and clientCertPasswd_x variable in them.

You are right, step 1-3 are done one the callout endpoint(your server).

HTHs,


philbophilbo
OK, thanks.  I take it then, that the 'client certificate' is the one generated on the SF org itself - via Setup -> Develop -> API -> Download Client Certificate.  It appears to be already base64-encoded from the get-go, so presumably if that's palatable to the Web Server in question (the end-point), that should suffice.

And this is the cert that is assigned to the 'clientCert_x' stub variable, right?

Not sure about the passwd, though - the 'clientCertPasswd_x' variable referred to in the doc.  What's this supposed to be set to?  Or can it be set to anything?  (???)

Thanks again

-philbo
cheenathcheenath
No, you should not use sfdc client cert.

1. You should get/buy a cert from one of the public signing authority, or generate a demo cert.
2. Modify your server to only accept two way SSL connection. (ie, client should present a cert)
3. Modify your servers trust store to only accept the cert you got in step 1.

Then follow the step in docs to setup the cert in apex.

This will make sure that only your org in sfdc can call your server.

If you are using tomcat for your server, here is a blog that explains how to setup 2 way ssl:
http://www.vorburger.ch/blog1/2006/08/setting-up-two-way-mutual-ssl-with.html


HTHs,


philbophilbo
Hey,

OK, I think I have generated a proper PKCS12 client certificate, but when I embed it and its password into my WSDL stub class, I get the following exception when attempting to invoke it:

IO Exception: DER input, Integer tag error

Information on what this error means is quite scanty on the Net.  Best I can see, it's because the cert is not PKCS12 format.

Here are the steps I took, on my Win32 box, to create the cert, following instructions scraped from the Net.
 
openssl req -new -newkey rsa:1024 -nodes -out c:/temp/ssl/ca/ca.csr 
-keyout c:/temp/ssl/ca/ca.key openssl x509 -trustout -signkey c:/temp/ssl/ca/ca.key -days 3650 -req
-in c:/temp/ssl/ca/ca.csr -out c:/temp/ssl/ca/ca.pem keytool -import -keystore %JAVA_HOME%/lib/security/cacerts -file c:/temp/ssl/ca/ca.pem
-alias myAlias

openssl req -new -newkey rsa:1024 -nodes -out c:/temp/ssl/client/client1.req
-keyout c:/temp/ssl/client/client1.key openssl x509 -CA c:/temp/ssl/ca/ca.pem -CAkey c:/temp/ssl/ca/ca.key
-CAserial c:/temp/ssl/ca/ca.srl -req -in c:/temp/ssl/client/client1.req
-out c:/temp/ssl/client/client1.pem -days 3650 openssl pkcs12 -export -clcerts -in c:/temp/ssl/client/client1.pem
-inkey c:/temp/ssl/client/client1.key -out c:/temp/ssl/client/client1.p12
-name myAlias

keytool -export -alias myAlias -keystore c:/temp/ssl/client/client1.p12
-storetype PKCS12 -storepass myPasswd -rfc
-file c:/temp/ssl/client/client1.cert

and the contents of the resulting client1.cert, plus 'myPasswd', are what go into the WSDL stub class' clientCert_x and clientCertPassword_x variables, respectively. 

I'm just following a recipe here, though it does seem quite straightforward.  The cert itself appears to be valid, or at least parseable, as I am able to succesully import it into my Web browser.  I did arrange for the cert to be installed on the Web server (IIS), but the exception appears to be thrown before the message makes it outside of Salesforce.

Any ideas where I've gone astray?  Are there any resources (other than www.vorburger.ch) that might help shed light?  Is there any more detail I can supply, here?

thanks

-philbo
 

cheenathcheenath
Did you base64 encode before setting the pkcs12 keystore to clientCert_x variable?


philbophilbo
Hey,

Well, I thought that final step produced base64-encoded output...but maybe not.

The resulting file's contents look like:
-----BEGIN CERTIFICATE-----
MIIB7jCCAVcCAQMwDQYJKoZIhvcNAQEFBQAwLjELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFy
. . . 7 more 78-char lines . . .
5ZKkmCN8wsLe7t8Gr30hYK80ZqZriA0sB30+VLkvtcsyQuMvzqvJBsJq
-----END CERTIFICATE-----

 I took off the CERT header/footer and assigned the remainder to clientCert_x.

Is this not base64?  Is there one additional piece of processing I need to do?

Thanks!

-philbo

cheenathcheenath
That looks like base 64.



philbophilbo
All righty then. 

Anybody have any ideas as to what the deal is with this cert?

One thing I can confirm - it's got nothing to do with anything going on at the endpoint - I can call out to any bogus non-existent URL I want, and get the same error.

Thanks!

-philbo


Message Edited by philbo on 06-17-2008 09:38 AM
Steven LawranceSteven Lawrance
Hi philbo,

Sorry for the late reply. It appears that the base64 input that you are passing in is from the PEM file, which is a base64 encoded version of the DER format. Unfortunately, this will not work as a client certificate in Apex. The certificate will need to be a base64 encoded version of the PKCS12 certificate.

To get a base64 encoded version of the PKCS12 certificate, replace the last step that you listed -- the keytool invocation -- with a base64 encoder invocation. This assumes that you entered a private key password in the second-to-last step, `openssl pkcs12 -export -clcerts -in c:/temp/ssl/client/client1.pem -inkey c:/temp/ssl/client/client1.key -out c:/temp/ssl/client/client1.p12 -name myAlias`. It is the client1.p12 file that you will need to encode using base64 for clientCert_x, and the private key password that you entered when OpenSSL asked for it should be used for clientCertPassword_x.

Windows, unfortunately, does not ship with a base64 encoder command line tool as far as I am aware, so you will either need to download one and run it or use a web-based base64 encoder. Fortunately, because your private key is encrypted in the client1.p12 file, using an online base64 encoder should be okay as long as your password is sufficiently strong. A local encoder is thus favorable to an online encoder.

The reason why Apex needs a PKCS12 file and not a DER or PEM file is that a single PKCS12 file can contain certificates and a key in a standardized manner, and the key can optionally be encrypted using a passphrase. A PEM file can technically contain multiple certificates and keys through concatenation, since it's a text file, but not all tools and applications support this. As a result, PEM and DER based solutions typically use at least three different files: key, certificate, and signing certificate (the signing certificate's signing certificate and any other signing certificates in the chain might be required, too). Thus, PKCS12 simplifies things by putting it all into one file. The base64-encoded DER content that you tried to use from the PEM file likely contained only the certificate and not the private key nor the certificate's signing certificate.

Hopefully, this helps. Please let this thread know if the above worked for you.

Thanks,

Steven Lawrance

This was selected as the best answer
philbophilbo
OK, thanks for the great response.  I think I'm nearing a solution to my issue...not quite there yet...but close.

Running the .p12 file thru a base64 encoder produces output that does seem to be acceptable to Salesforce; i.e. I assign it to the clientCert_x variable in my stub class and no longer get that 'Integer tag error' exception.

Now - on the server side - presumably the (IIS) web server requires the exact same cert installed - not the PEM 'variant' but the .p12 cert (or the base64-encoded image of it), is that right?

Thanks!

-philbo
philbophilbo
Hey,

This has been fully resolved, thanks largely to the feedback on this thread.  The procedure was a simplified version of what was originally posted:

keytool -genkey -v -alias <myAlias> -keyalg RSA -validity 3650 
-storetype PKCS12 -keystore myFile.p12 -dname "<domainInfo>"
-storepass <myPasswd> -keypass <myPasswd>
...to generate the PKCS12 file

keytool -export -alias <myAlias> -keystore keystore.p12 -storetype PKCS12
-storepass <myPasswd> -rfc -file myFile.cert
...to export the PKCS12 file into a base64-encoded cert file

<online base64-encoder> --> myFile.cer64
...to directly translate the PKCS12 file into a base64-encoded image of itself

 The second file, myFile.cert, was imported into the IIS Web server, which also had to be "told" to trust the (self-signed) certificate.

The third file, myFile.cer64, along with the password myPasswd, was embedded in the WSDL stub class in the clientCert_x and clientCertPasswd_x variables.

With these pieces in place, the whole thing worked as desired.

Thanks again for the guidance on this!

-philbo

Steven LawranceSteven Lawrance
As a follow-up, I should note that on Microsoft IIS, a certificate revocation list (CRL) callout from IIS may occur and, if your server does not have outbound Internet access, that CRL callout might fail and result in Salesforce.com not being able to successfully call out to your server.

If enabling outbound access to VeriSign's CRL of proxy.salesforce.com's intermediate certificate at http://crl.verisign.com/pca3.crl from your server is not an option, then you can either import the latest CRL into the certificate store via the certificates snap-in for the computer account or disable IIS's CRL callout.

To import the latest VeriSign intermediate CRL into the computer account's intermediate CRL list, perform the following steps:
1. Download the CRL from http://crl.verisign.com/pca3.crl
2. Open Microsoft's Management Console (MMC) by typing mmc into the dialog that pops up from Start | Run and press the OK button
3. In File | Add/Remove Snap-In, click on "Add..."
4. Select "Certificates" and press "Add"
5. Select "Computer account" and press "Next >". Press "Finish" on the next page to finish the wizard
6. Press "Close" on the "Add Standalone Snap-in" window
7. Press "OK" on the "Add/Remove Snap-in" window
8. Right-click on "Intermediate Certification Authorities" underneath Console Root | Certificates (Local Computer) and select All Tasks | Import...
9. Press "Next >" to get to the "File to Import" page
10. Click on "Browse..." and change the "Files of type" drop-down to "Certificate Revocation List (*.crl)"
11. Navigate to the pca3.crl file that you downloaded, select it, and press "Open"
12. Press "Next >" until you see the "Finish" button. Press the "Finish" button
13. The CRL is now saved in Console Root | Certificates (Local Computer) | Intermediate Certification Authorities | Certificate Revocation List. Note the "Next Update" date. Typically, it's best to update the CRL on the day before that date. This is partly why it's desirable to let IIS perform callouts instead of manually importing the CRL periodically

It's possible that IIS might still insist on calling out even if a current CRL is in the certificate store. I haven't specifically tested this, but I mention this in case if the above works.


To disable the CRL callout, you will need to run a command using adsutil.vbs in c:\inetpub\adminscripts to set CertCheckMode to 1. To do this, you will need to determine the ID of your web server instance. I am presently not aware of an easy way to do this other than guessing which of the numbers returned from the enumeration is your web server via exhaustive checking, though it's possible that an easier method exists. To get this list using cmd.exe, change the current directory to C:\Inetpub\AdminScripts and run the following command:

cscript adsutil.vbs ENUM /P W3SVC

One of the paths listed in the output is your server. To determine which one it is, enumerate each one until you find the one that has the "ServerComment" field set to the name of your server as it is set in the IIS services manager. Example to query a server:

cscript adsutil.vbs ENUM W3SVC/121530

The line with "ServerComment" on it contains the name of your server. Once you found the ID of the server that you are looking for it, then it's possible to move onto the next step.

Set CertCheckMode to 1 on the root of your server. The following accomplishes that. Replace your_id with the number that identifies your server as found in the previous step.

cscript adsutil.vbs SET W3SVC/your_id/CertCheckMode 1

You can also query it with the following:

cscript adsutil.vbs GET W3SVC/your_id/CertCheckMode

To remove this setting to let IIS perform its default behavior of calling out for the CRL, perform the following:

cscript adsutil.vbs DELETE W3SVC/your_id/CertCheckMode


Special thanks to http://blogs.msdn.com/saurabh_singh/archive/2007/06/09/client-certificate-revisited-how-to-troubleshoot-client-certificate-related-issues.aspx for inspiring this post.

aholetecaholetec

Hi,

Here's another question about invoking an external Web service.

I'm trying to open a connection to an Web service that is HTTPS from Apex.

After I received WSDL and I generated the classes in Salesforce (using the WSDL) then I added the client certificate

that these guys sent me. They say it's a it's class 3 VeriSign cert and there is no password for this cert.

Here's the steps that I fallowed:

http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_web_services_wsdl2apex.htm

I get and error when I try to connect that says the password field can not be null. In order to avoid the error that I

set the password to empty string. I still get an error message that does not tell me much.

System.CalloutException: IO Exception : DER input, Integer tag error

Is it possible that this certificate will not reqiore a password ?

Anyone has some feedback ?

Thank you !

Alex Holetec

Steven LawranceSteven Lawrance
Hi Alex,

It's possible that Salesforce.com requires a key passphrase, but I am not certain about that. Some things to check include the following:
  • Is the certificate a PKCS#12 file?
    • It will need to be in the PKCS#12 format and include a certificate as well as its corresponding private key. Because this is a VeriSign Class 3 certificate, it was probably signed by an intermediate certificate, so that intermediate certificate should be included in the PKCS#12 file, too, permitting it to be sent in the certificate chain that Salesforce.com sends to the external web service
  • Was the PKCS#12 file converted as-is to base64?
    • A local base64 encoder or a web-based base64 encoder can be used. If you use a web-based base64 encoder, then it is strongly recommended that the key is encrypted using a good passphrase
If your file is a PEM text file that concatenates the certificates and keys, then it can be converted to PKCS#12 easily using OpenSSL. Split each part of that file into their own files, such as client.crt for the client certificate, client.pem for the client key, and signer.crt for the VeriSign intermediate certificate. The following command will output a PKCS#12 file from these inputs:  openssl pkcs12 -in client.pem -inkey client.pem -out client.p12 -export -CAfile signer.crt -chain    After running that, base64 encode client.p12 and put the base64 encoded output into your Apex code along with the passphrase that you entered when openssl prompted you for a passphrase.

If your file is already a PKCS#12 file, then try to reencrypt it using a passphrase. To do this, extract the parts of the PKCS#12 file using openssl pkcs12 -in helloworld.p12 -nodes . Copy/paste your client certificate into a file named client.crt, your signing certificate into a file named signer.crt, and your client certificate's private key into a file named client.pem. Look at the "subject=" lines to determine which certificate is which, and your private key is the one between "-----BEGIN RSA PRIVATE KEY-----" and "-----END RSA PRIVATE KEY-----". The following command will output a PKCS#12 file from these inputs:  openssl pkcs12 -in client.pem -inkey client.pem -out client.p12 -export -CAfile signer.crt -chain    After running that, base64 encode client.p12 and put the base64 encoded output into your Apex code along with the passphrase that you entered when openssl prompted you for a passphrase.

Does this work?

kruel intent.ax1188kruel intent.ax1188

Hi all, I had problems with all this for weeks so, now resolved, I made a detailed blog post take people through getting the certificate code and using it correctly via apex classes. Hope it helps.

 

http://techblog.kruelintent.com/post/13821841289/how-to-connecting-to-a-secure-web-service-https-from

 

Thanks

Tom.

 

kruelintent.com web design.

Hari KrishnanHari Krishnan

Hello 'Kruel intent',

It's an excellent writeup. You have mentioned the following:

The code you want is everything between BEGIN PRIVATE KEY and END PRIVATE KEY. Mark and copy this out to a text editor and remove the line breaks so you have one long LONG string…. THIS IS THE MAGIC CODE!!!

I think this is incorrect; you have to embed the ceritificate and not the private key. 

 

I have made a detailed five part series in my blog about making authenticated web service callouts using SSL/certificates and here are the links:

 

sfdcFanBoysfdcFanBoy

Hi All, 

 

I have generated Apex class from .net wsdl file successfully.  Created another apex class andam  calling the getOrderDetails() method by passing the orderID.  But, I get the following error on debug.

 

System.CalloutException: Web service callout failed: WebService returned a SOAP Fault: MissingToken: Relay security token is required.  faultcode=a:AuthorizationFailedFault faultactor=

 

 The wsdl file (server) uses .dll file for some bindings which has a issuer name and key, that needs to be passed for it be authorized to connect.

 

Issuer name: owner

issuer key: ugviygveiebittuziwvihhZhigiygIhgigwi=

 

can you please give the exact code as how to use these in the class that i call the webservice method.