SSO with SAP Logon Tickets and Java

To validate/verify a SAP Logon Ticket in a non-SAP Java environment you have to call into native libraries. Fortunately there’s some Java sample code provided in the SAPSSOEXT library archive.

Go to service.sap.com (you need a valid user and download permissions) -> Download -> Support Packages and Patches -> Entry by Application Group -> Additional Components, follow the SAPSECULIB and SAPSSOEXT links and download the library versions for your operating system. You’ll also need SAPCAR to extract those .SAR files. Both libraries - sapsecu.dll and sapssoext.dll - are needed to validate a SAP Logon Ticket.

What to do if you get a java.lang.UnsatisfiedLinkError: getVersion

Make sure both (sapsecu.dll and sapssoext.dll) libraries are accessible from your “java.library.path” system property. On Windows servers you may want to copy them to c:\windows\system32.
If you’re still seeing this error you may have renamed the class file. You have to leave it at SSO2Ticket. The provided Java sample SSO2Ticket.java doesn’t come with any package statement so make sure you dont’ use a package statement at all or if you don’t like classes in the default package use com.mysap.sso. It won’t work with your own package path as it’s natively tied with JNI to a library.

package com.mysap.sso;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;

public class SSO2Ticket {
...

Ticket and server certificate

The provided sample ticket is useless as it won’t validate without the correct certificate. You have to extract your own sample ticket from a HTTP header (i.e. using the sample below or a browser extension like Live HTTP Headers). You also need the verify.pse certificate which your administrator can extract from the keystore.

Obtaining a SAP Logon Ticket with validation

This code sample receives a SAP Logon Ticket from a SAP Enterprise Portal server and validates it using the SAPSSOEXT and SAPSECU libraries. It uses the SSO2Ticket class for the native calls.

package com.trick77.sapsso;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.mysap.sso.SSO2Ticket;

/**
 * Extracts a SAP Logon Ticket by remotely logging into SAP Enterprise Portal and validates the received ticket using
 * native SAPSSOEXT and SAPSECU libraries.
 *
 * @author Jan
 */
public class TicketSample {

	/**
	 * SAP Enterprise Portal host and port number if needed.
	 */
	static final String PORTAL_HOST = "myepserver:56800";

	/**
	 * Valid Enterprise Portal username.
	 */
	static final String PORTAL_USER_NAME = "username";

	/**
	 * Valid SAP Enterprise Portal password.
	 */
	static final String PORTAL_PASSWORD = "givemeticket;

	/**
	 * Path and filename of the exported certificate.
	 */
	static final String CERTIFICATE_FILENAME = "c:/temp/verify.pse";

	/**
	 * Password needed to extract the certificate (same as used when the certificate was created). Can be null but
	 * shouldn't for security reasons.
	 */
	static final String CERTIFICATE_PASSWORD = null;

	public static void main(String[] args) throws Exception {
		// Skip login dialog by sending login form data directly to the server.
		URL _url = new URL("http://" + PORTAL_HOST
				+ "/irj/portal?login_submit=on&login_do_redirect=1&no_cert_storing=on&j_user=" + PORTAL_USER_NAME
				+ "&j_password=" + PORTAL_PASSWORD + "&j_authscheme=default&uidPasswordLogon=Log+on");
		System.out.print("Establishing connection to remote server...");
		HttpURLConnection _conn = (HttpURLConnection) _url.openConnection();
		try {
			if (_conn.getResponseCode() != 200) {
				throw new Exception("Server responded with status code " + _conn.getResponseCode());
			}
			System.out.println("OK");
		}
		catch (ConnectException cex) {
			throw new Exception("Unable to connect using URL " + _url.toString());
		}

		Collection _cookies = null;
		for (Iterator _iter = _conn.getHeaderFields().entrySet().iterator(); _iter.hasNext();) {
			// Can't use getHeaderField("Set-Cookie") as it won't return duplicate "Path=" entries
			Map.Entry _entry = (Map.Entry) _iter.next();
			// System.out.println(_entry.getKey() + " -> " + _entry.getValue());
			String _key = (String) _entry.getKey();
			if (_key != null && _key.toLowerCase().equals("set-cookie")) {
				_cookies = (Collection) _entry.getValue();
				break;
			}
		}

		// Extracting the SAP Logon Ticket from the cookies.
		String _ticket = null;
		for (Iterator _iter = _cookies.iterator(); _iter.hasNext();) {
			String _cookieStr = (String) _iter.next();
			Pattern _pattern = Pattern.compile("MYSAPSSO2=(.*?);");
			Matcher _matcher = _pattern.matcher(_cookieStr);
			if (_matcher.find()) {
				_ticket = _matcher.group(1);
				break;
			}
		}

		File _pab = new File(CERTIFICATE_FILENAME);
		if (!_pab.exists()) {
			throw new Exception(CERTIFICATE_FILENAME + " does not exist");
		}
		if (_ticket == null) {
			throw new Exception(
					"Ticket not found in HTTP header. Authentication failed? Paste this URL in your web browser to check:\n"
							+ _url.toString());
		}
		System.out.println("Version of SAPSSOEXT: " + SSO2Ticket.getVersion());

		try {
			// You have to declare init public in SSO2Ticket.java in order to access it from here
			SSO2Ticket.init(SSO2Ticket.SECLIBRARY);
			Object _obj[] = SSO2Ticket.evalLogonTicket(_ticket, _pab.getAbsolutePath(), null);
			// You have to declare PrintResults public in SSO2Ticket.java in order to access it from here
			SSO2Ticket.PrintResults((String) _obj[0], (String) _obj[1], (String) _obj[2], SSO2Ticket.parseCertificate(
					(byte[]) _obj[3], SSO2Ticket.ISSUER_CERT_SUBJECT), SSO2Ticket.parseCertificate((byte[]) _obj[3],
					SSO2Ticket.ISSUER_CERT_ISSUER), _ticket, (String) _obj[4], (String) _obj[5], (String) _obj[6]);
		}
		catch (Exception ex) {
			System.out.println(ex);

			// Extract the error codes in the exception and map the codes to an error text
			StringWriter _sw = new StringWriter();
			PrintWriter _ps = new PrintWriter(_sw);
			ex.printStackTrace(_ps);

			int _stdError = -1;
			int _ssfError = -1;
			StringTokenizer _st = new StringTokenizer(_sw.toString());
			while (_st.hasMoreTokens()) {
				String _token = _st.nextToken();
				if (_token.equals("error=")) {
					StringBuffer _numberStr = new StringBuffer(_st.nextToken());
					for (int i = 0; i < _numberStr.length(); i++) {
						if (_numberStr.charAt(i) < '0' || _numberStr.charAt(i) > '9') {
							_numberStr.deleteCharAt(i);
						}
					}
					if (_stdError == -1) {
						_stdError = Integer.parseInt(_numberStr.toString());
					}
					else if (_stdError > -1) {
						_ssfError = Integer.parseInt(_numberStr.toString());
						break;
					}
				}
			}
			System.out.println(getStdErrorMessage(_stdError) + " - " + getSsfErrorMessage(_ssfError));
		}
	}

	static String getStdErrorMessage(int aCode) {
		switch (aCode) {
		case 0:
			return "Ok";
		case 1:
			return "No user provided to function (1)";
		case 3:
			return "Provided buffer too small (3)";
		case 4:
			return "Ticket expired (4)";
		case 5:
			return "Ticket syntactically invalid (5)";
		case 6:
			return "Provided buffer too small (6)";
		case 8:
			return "No ticket provided to function (8)";
		case 9:
			return "Internal error (9)";
		case 11:
			return "Memory allocation failed (11)";
		case 12:
			return "Wrong action (12)";
		case 13:
			return "Tried to call nullpointer function (13)";
		case 14:
			return "Error occurred in security ticket (14)";
		case 15:
			return "Pointer was null (15)";
		case 16:
			return "Incomplete information in ticket (16)";
		case 17:
			return "Couldn't get certificate (17)";
		case 19:
			return "Missing authorization (19)";
		case 20:
			return "Signature couldn't be verified (20)";
		case 21:
			return "Ticket too new for library (21)";
		case 22:
			return "Conversion error (22)";
		default:
			return "Unknown standard error (" + aCode + ")";
		}
	}

	static String getSsfErrorMessage(int aCode) {
		switch (aCode) {
		case 0:
			return "No SSF error (0)";
		case 1:
			return "No security toolkit found (1)";
		case 2:
			return "Unknown wrapper format (2)";
		case 3:
			return "Input data length zero (3)";
		case 4:
			return "Insufficient main memory (4)";
		case 5:
			return "There are signer errors (5)";
		case 6:
			return "No memory for result list (6)";
		case 7:
			return "Private address book (PAB) not found (7)";
		case 8:
			return "Invalid PAB password (8)";
		case 9:
			return "There are recipient errors (9)";
		case 10:
			return "Unknown MD algorithm (10)";
		case 11:
			return "Could not encode output (11)";
		case 12:
			return "Could not decode input (12)";
		case 13:
			return "Unknown security toolkit error (13)";
		case 21:
			return "Security profile is locked (21)";
		case 22:
			return "Unknown signer or recipient (22)";
		case 23:
			return "Security profile not found (23)";
		case 24:
			return "Security profile not usable (24)";
		case 25:
			return "Invalid password for signer (25)";
		case 26:
			return "Certificate not found (26)";
		case 27:
			return "Signature invalid or operation not OK for signer (27)";
		case 51:
			return "No more memory output data (51)";
		case 52:
			return "Signer/recipient ID is empty (52)";
		case 53:
			return "Signer/recipient info empty (53)";
		case 54:
			return "Signer/recipient info list empty (54)";
		default:
			return "Unknown SSF error (" + aCode + ")";
		}
	}
}

Pure Java ticket validation solution

I came across a pure Java SAP Logon Ticket validation solution and looking at the source code it looks pretty well. I haven’t tested it though and even if it works you won’t have any SAP support with this solution. But it might be worth a try.


You may also be interested to read:

3 Comments so far

  1. Sheldon on March 28th, 2008

    I am getting a return error of 1:

    “No user provided to function (1)”;

    Can’t figure out what the problem could be.

    Any suggestions?

  2. Jan on March 30th, 2008

    Looks like a problem with the ticket. Where/How did you obtain it?

  3. Tanguy on May 23rd, 2008

    How can you get a SAP logon ticket delivered by the AS Java itselft without a Portal ticket issuer installed? Is this possible?

Leave a reply