In this Blog we are going to see How to validate MYSAPSSO2 cookies with SAP Cloud Integration

Introduction

Recently, as part of an integration project in one of our clients, I was faced with the challenge of validating MYSAPSSO2 cookies with SAP BTP. After some research I was able to come up with 2 different solutions. The first one uses a JAVA Web Application deployed in Cloud Foundry and the second one uses a JAR file in an Integration Flow with Cloud Integration. The second option will be the one detailed in this blog.

SAP SSO Extension Library (SAPSSOEXT)

This is the library that SAP provides to implement SSO Authentication with third-party applications. In this scenario, this library will be used to decrypt/validate a given MYSAPSSO2 cookie encoded as a Base64 string. The validation itself only requires the cookie and the public certificate of the signer. The library comes with implementation samples in several languages. As Cloud Integration allows JAR resources, we will take full advantage of the JAR provided in the JAVA sample.

The essential part required to work with this library is to download the right version from the SAP Marketplace that corresponds with the environment where the solution is to be deployed.  

 

Below is the summary of the steps we will be following:

  1. Download SAPSSOEXT library
  2. Download SAPCAR software
  3. Decompress SAPSSOEXT library
  4. Download GSON library
  5. Create JAVA project with logic to validate the cookie
  6. Export Project as JAR file
  7. Create Integration Flow in SAP Cloud Integration
  8. Test with POSTMAN

 

STEP1:

Go to SAP Support Launchpad and download the SAPSSOEXT library, link .

Select SO version where SAP Cloud Integration is running, Linux 64bit in my case, and latest version, 17.

 

STEP2:

Go to SAP Support Launchpad and download SAPCAR, link . SAPCAR is a utility used by SAP to compress and/or decompress SAP archive files. This will be used to decompress the SAPSSOEXT library downloaded in the step before.

Select SO version where files will be decompressed (local desktop), Windows 64bit in my case, and latest version.

 

STEP3:

Go to your Downloads folder, you should have these 2 files

Open Terminal and execute the following command line where the SAPCAR EXE file is located adapting the path to your environment:

SAPCAR_1115-70006231.EXE -xvf C:UsersfilferreDownloadsSAPSSOEXT_17-20001128.SAR

 

If successful, the extraction will create the following 3 folders:

STEP4:

Download the latest GSON library using link

 

STEP5:

Open Eclipse and create a new Java Project.

 

Add external JARs to project

 

Create the following objects:

  • Package: com.sap.cookie.validation
  • Class: ResponseModel (contains attributes of the Response)
package com.sap.cookie.validation;

public class ResponseModel {

	private String User;
	private String Sysid;
	private String Client;
	private String PrtUsr;

	private String SubjectDN;
	private String IssuerDN;

	private int errorCode;
	private String errorMessage;

	public ResponseModel(String User, String Sysid, String Client, String PrtUsr) {
		this.setUser(User);
		this.setSysid(Sysid);
		this.setClient(Client);
		this.setPrtUsr(PrtUsr);
	}

	public ResponseModel(int errorCode, String errorMessage) {
		this.setErrorCode(errorCode);
		this.setErrorMessage(errorMessage);
	}

	public String getUser() {
		return User;
	}

	public void setUser(String user) {
		User = user;
	}

	public String getSysid() {
		return Sysid;
	}

	public void setSysid(String sysid) {
		Sysid = sysid;
	}

	public String getClient() {
		return Client;
	}

	public void setClient(String client) {
		Client = client;
	}

	public String getPrtUsr() {
		return PrtUsr;
	}

	public void setPrtUsr(String prtUsr) {
		PrtUsr = prtUsr;
	}

	public int getErrorCode() {
		return errorCode;
	}

	public void setErrorCode(int errorCode) {
		this.errorCode = errorCode;
	}

	public String getErrorMessage() {
		return errorMessage;
	}

	public void setErrorMessage(String errorMessage) {
		this.errorMessage = errorMessage;
	}

	public String getSubjectDN() {
		return SubjectDN;
	}

	public void setSubjectDN(String subjectDN) {
		SubjectDN = subjectDN;
	}

	public String getIssuerDN() {
		return IssuerDN;
	}

	public void setIssuerDN(String issuerDN) {
		IssuerDN = issuerDN;
	}

}

 

  • Class: Ticket (contains function to decode/validate the Base64 cookie with a given Base64 CERT certificate)
package com.sap.cookie.validation;

import java.io.ByteArrayInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mysap.sso.SSO2Ticket;

public class Ticket {

	public String decode(String sapCookie, String certificate) {

		Gson gson = new GsonBuilder().serializeNulls().create();
		ResponseModel response = null;

		Object[] o = null;
		String code = "";
		try {

			// Decode Base64 Certificate into Array of bytes
			byte[] decodedBytes = Base64.getDecoder().decode(certificate);

			// Load Certificate into memory
			boolean result = SSO2Ticket.loadKey(decodedBytes, null, 0, 1);
			if (result == false) {

				// Add error code and error message to response
				response = new ResponseModel(500, "Certificate failed to load into memory!");

				// Return error response in JSON
				return gson.toJson(response);
			}

			// Call function to decrypt a given MYSAPSSO2 Cookie. No need to pass the
			// certificate here as it was loaded into memory on the step before
			o = SSO2Ticket.evalLogonTicket(sapCookie, null, null);
			if (o != null) {

				// Decryption was successful, create response with user details retrieved from
				// cookie object
				response = new ResponseModel(o[0].toString(), o[1].toString(), o[2].toString(), o[4].toString());

				// The third element is the certificate in byte
				// representation, to get its contents we need
				// to first convert it into a cert structure
				byte[] cert_;
				X509Certificate cert = null;
				if (o.length > 3) {
					cert_ = (byte[]) o[3];
					CertificateFactory cf = CertificateFactory.getInstance("X.509");
					cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(cert_));

					// Add certificate details to response
					response.setSubjectDN(cert.getSubjectDN().toString());
					response.setIssuerDN(cert.getIssuerDN().toString());
				} else {
					cert = null;
				}
			}
		} catch (Exception e) {

			String error = e.getMessage();
			// Get error code from exception message
			code = error.substring(47, error.lastIndexOf(','));
			String errorMessage = "";

			// Map error code to relevant error message
			switch (code) {
			case "1":
				errorMessage = "No user provided to function";
				break;
			case "3":
				errorMessage = "Provided buffer too small";
				break;
			case "4":
				errorMessage = "Ticket expired";
				break;
			case "5":
				errorMessage = "Ticket syntactically invalid";
				break;
			case "6":
				errorMessage = "provided buffer too small";
				break;
			case "8":
				errorMessage = "no ticket provided to func";
				break;
			case "9":
				errorMessage = "Internal error";
				break;
			case "11":
				errorMessage = "malloc faile";
				break;
			case "12":
				errorMessage = "wrong action";
				break;
			case "13":
				errorMessage = "tried to call NULL p. function";
				break;
			case "14":
				errorMessage = "Error occured in security tk";
				break;
			case "15":
				errorMessage = "pointer was NULL";
				break;
			case "16":
				errorMessage = "incomplete information in t.";
				break;
			case "17":
				errorMessage = "couldn’t get certificate";
				break;
			case "20":
				errorMessage = "Signature couldn’t be verified";
				break;
			case "21":
				errorMessage = "Ticket too new for lib";
				break;
			case "22":
				errorMessage = "Conversion error";
				break;
			case "23":
				errorMessage = "Recipient missing";
				break;
			case "24":
				errorMessage = "Recipient different";
				break;
			default:
				errorMessage = "Decryption default error";
			}
			// Add error code and error message to response
			response = new ResponseModel(Integer.parseInt(code), errorMessage);

		}

		// Return response in JSON
		return gson.toJson(response);

	}
}

 

STEP6:

Export entire project as JAR file.

 

 

 

STEP7:

Go to Cloud Integration and create an Integration Flow.

 

Add Archive SAPCookieValidation file.

 

Add Archive SAPSSOEXT file from location ssosamplejavasapssoext.jar

 

 

Create HTTPS connection

 

Create script to parse Input Parameters

 

/* Refer the link below to learn more about the use cases of script.
https://help.sap.com/viewer/368c481cd6954bdfa5d0435479fd4eaf/Cloud/en-US/148851bf8192412cba1f9d2c17f4bd25.html

If you want to know more about the SCRIPT APIs, refer the link below
https://help.sap.com/doc/a56f52e1a58e4e2bac7f7adbf45b2e26/Cloud/en-US/index.html */
import com.sap.gateway.ip.core.customdev.util.Message;
import java.nio.charset.StandardCharsets;

def Message parse(Message message) {
    
    String query = message.getHeader('CamelHttpQuery', String)
    query = (query) ? URLDecoder.decode(query, StandardCharsets.UTF_8.name()) : ''

    Map<String, String> queryParameters = query.tokenize('&').collectEntries { entry ->
        entry.tokenize('=').toSpreadMap().collectEntries { parameter ->
            ['QueryParameter_' + parameter.key, parameter.value]
        }
    }

    message.setProperties(queryParameters)

    return message;

}

 

Create script to decode the cookie using the libraries

 

/* Refer the link below to learn more about the use cases of script.
https://help.sap.com/viewer/368c481cd6954bdfa5d0435479fd4eaf/Cloud/en-US/148851bf8192412cba1f9d2c17f4bd25.html

If you want to know more about the SCRIPT APIs, refer the link below
https://help.sap.com/doc/a56f52e1a58e4e2bac7f7adbf45b2e26/Cloud/en-US/index.html */
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import com.mysap.sso.SSO2Ticket;
import com.sap.cookie.decryption.Ticket;

def Message processData(Message message) {
    
     //Properties
    def properties = message.getProperties();
    def sapCookie = properties.get("QueryParameter_SapCookie");   
    def certificate = properties.get("QueryParameter_Certificate");   
    
    Ticket ticket = new Ticket();
    def result = ticket.decode(sapCookie,certificate);
    
    message.setBody(result);
    message.setHeader("Content-Type", "application/json" + "; charset=utf-8" );
    return message;  

}

 

Complete IFlow

 

STEP8:

To test the web service, please retrieve the corresponding URL from Cloud Integration.

Query parameters:

  • SAPCookie (Encoded as Base64 String)
  • Certificate  (CERT extension Encoded as Base64 String)

 

Decoding was successful and user details are returned

 

Decoding failed with error “Ticked expired”

Conclusion

In this article, we talked about the steps used to create a web service to validate MYSAPSSO2 cookies in Cloud Integration using the SAPSSOEXT library. The additional JAVA project to handle the validation and response of the cookie is not essential and you could use the SAPSSOEXT library JAR straight away, but this method is useful as it wraps the entire logic in one place and provides the foundation for other types of implementation like deploying as a JAVA Application in Cloud Foundry.

Thanks for reading this blog…

Hope this blog will be useful. If you enjoyed this blog post please give it a like and follow my profile! If you have questions, feel free to comment.

If you are interested please follow the Cloud Integration Topic page here

Post or answer Cloud Integration questions here

Read other posts on the topic here

Sara Sampaio

Sara Sampaio

Author Since: March 10, 2022

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x