Bouncycastle, OSGi and uber-jars

I have recently tried to use SSHj in an OSGi bundle.
I had decided to wrap this library and its dependencies in my own bundle (something equivalent to an uber-jar but compliant with OSGi). In a classic context, you would use the Maven Shade plug-in. In an OSGi context, you can simply use the Maven Bundle plug-in, with the embedded-dependency directive.

Anyway, the difficulty here came because SSHj uses Bouncycastle as a security provider. And you cannot do what you want with it. The first attempt to build my all-in-one bundle resulted in a signature error during the Maven build.

Invalid signature file digest for Manifest main attributes

Indeed, some files in the JAR were signed, and some others were not. I solved it by using the Maven ANT plugin, removing signature files from my jar and repackaging it. The JAR could finally be built. Unfortunately, later at runtime, another error came out.

JCE cannot authenticate the provider BC

Looking at the logs of SSHj, no provider was found while the right classes were in the bundle’s class path. And everything was working outside of OSGi. So, there was no error with compilation levels or in my code. For some reason, Bouncycastle was not loaded by the JVM.

The explanation is that JCE (Java Cryptography Extension) providers are loaded in a special way by the JVM. First, they must be signed. And it seems not any certificate can be used (it must be approved by the JVM vendors). Bouncycastle is signed. But if you wrap it into another JAR, you will lose the signature. And then, these providers must be loaded at startup. If you apply this to an OSGi context, it means you cannot deploy Bouncycastle as a bundle whenever you want.

Finally, I solved my issue by…

  • … following Karaf’s documentation and making Bouncycastle a part of my Karaf distribution (copy it in lib/ext and updating some configuration files). See this POM to automate such a thing for you custom Karaf distribution.
  • … not importing org.bouncycastle.* in my packages. That’s because putting these libraries under lib/ext means these packages are considered as root classes (just like java.*). No need to import them then.
  • … making sure all the bundles that depend on Bouncycastle would use the same version. I achieved it by updating the dependencyManagement section in my parent POM.

And although it was not a real issue, I decided to put SSHj as Karaf feature. This way, no need to make an uber-jar. And I can reuse it as much as I want. See this file for a description of this feature (ssh4j-for-roboconf). The dependencies that are not already OSGi bundles are deployed through the wrap protocol.

I spent about a day to solve these issues. That’s why I thought an article might help.

Register MBeans in Apache Karaf

Just a quick note about how your OSGi application can register MBeans in Karaf’s platform.
Apache Karaf comes with its own JMX server and a global configuration you can find under its etc directory. Configuration goes from the server’s URL and remote access security, to a fine-grained permission system (e.g. who can access this or that MBean). For more details about what this, you can refer to the official documentation of Karaf.

Now, let’s assume you want to expose custom MBeans in Karaf’s platform.
How to proceed? Well, Karaf’s management feature comes with Apache Aries. The JMX component of this project is the Reference Implementation of the OSGi JMX Management Model Specification. This specification indicates how one can easily register MBeans within OSGi thanks to the white board pattern.

The only thing you have to do is to register a service that respects JMX naming conventions and that has the jmx.objectname property. Every time such a service is registered, Aries is notified and registers a MBean from it. If you remove the service, the MBean will be unregistered too. So, you only have to register a service! And that’s great. Then, you are plugged with Karaf’s JMX configuration, meaning you can easily integrate with other tools like Apache Decanter and its JMX collector.

How to register such a service?
You can do this programmatically…

BundleContext bundleCtx = osgiHelper.findBundleContext();
if( bundleCtx != null ) {

	this.logger.fine( "Running in an OSGi environment. Trying to register a MBean for the messaging." );
	Dictionary<String,String> properties = new Hashtable<> ();
	properties.put( "jmx.objectname", "net.roboconf:type=messaging" );
	try {
		this.serviceReg = bundleCtx.registerService( MessagingApiMBean.class, this, properties );
		this.logger.fine( "A MBean was successfully registered for the messaging." );

	} catch( Exception e ) {
		this.logger.severe( "A MBean could not be registered for the messaging." );
		Utils.logException( this.logger, e );
	}
}

… or with a framework, like iPojo (here with a metadata.xml file).

<ipojo 
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="org.apache.felix.ipojo http://felix.apache.org/ipojo/schemas/CURRENT/core.xsd"
		xmlns="org.apache.felix.ipojo">

	<!-- Servlets registration -->
	<component classname="net.roboconf.dm.rest.services.internal.ServletRegistrationComponent" name="roboconf-dm-rest-component" immediate="true" public="false">

		<!-- Mandatory -->
		<requires field="manager" optional="false" />
		<requires field="httpService" optional="false" />

		<!-- Optional: Maven resolver -->
		<requires field="mavenResolver" optional="true">
			<callback type="bind" method="mavenResolverAppears" />
			<callback type="unbind" method="mavenResolverDisappears" />
		</requires>

		<!-- Optional: scheduler -->
		<requires field="scheduler" optional="true">
			<callback type="bind" method="schedulerAppears" />
			<callback type="unbind" method="schedulerDisappears" />
		</requires>

		<callback transition="validate" method="starting" />
		<callback transition="invalidate" method="stopping" />

		<properties pid="net.roboconf.dm.rest.services.configuration">
			<property name="enable-cors" method="setEnableCors" value="false" />
			<property name="enable-authentication" method="setEnableAuthentication" value="false" />
			<property name="authentication-realm" method="setAuthenticationRealm" value="karaf" />
			<property name="session-period" method="setSessionPeriod" value="-1" />
		</properties>

		<!-- Register OSGi services -->
		<!-- The implementation class also registers additional services... -->
		<provides specifications="net.roboconf.dm.rest.services.jmx.RestServicesMBean">
			<property name="jmx.objectname" value="net.roboconf:type=web" type="java.lang.String" />
		</provides>
	</component>

	<instance component="roboconf-dm-rest-component" name="Roboconf - DM REST services" />
</ipojo>

My example may look a little bit complex.
The interesting part is the provides section, which declares the service to register. Notice the service property. You can obviously use any framework (Blueprint, Declarative Services), no matter how you register your service, provided you actually do register it.

Here are some pointers to real implementations.
The code approach is used here to create MBeans on the fly for objects that are not managed as OSGi services. The framework approach is perfect when you want to associate MBeans with predefined OSGi services.

As usual, the interface of your service must be exported outside your bundle. This is fundamental! You must also respect JMX conventions. All my service interfaces for JMX have their name that end with MBean (and it is case sensitive). My implentation classes can use whatever names they want. You will find additional information on Apache Aries’s web site.

Registering a servlet filter in Apache Karaf

I have already written several articles about deploying web applications in OSGi, and in particular in Apache Karaf. This blog post is a small addition to indicate how to deploy a filter in such an environment.

My first attempt was trying to register my filter directly in the HTTP service. Unfortunately, there is no method for this. After searching the web, I finally found an example here. Apache Karaf comes with Pax-Web. And Pax-Web has extender modules. Basically, all you have to do is registering your filter as an OSGi service. Pax-Web extender modules will be notified about this service (white board pattern) and automatically register it as a servlet filter in the web server (which is Jetty, by the way).

Personnaly, I use this approach to add authentication to REST services. They are implemented with Jersey 1.x and served by Karaf’s web server. The filter intercepts requests and verifies they have the identity token. Obviously, it aims at being used with HTTPS.

So, here are some code snippets.
Let’s start with the filter itself.

public class AuthenticationFilter implements Filter {

	private final Logger logger = Logger.getLogger( getClass().getName());

	private AuthenticationManager authenticationMngr;
	private boolean authenticationEnabled;
	private long sessionPeriod;


	@Override
	public void doFilter( ServletRequest req, ServletResponse resp, FilterChain chain )
	throws IOException, ServletException {

		if( ! this.authenticationEnabled ) {
			chain.doFilter( req, resp );

		} else {
			HttpServletRequest request = (HttpServletRequest) req;
			HttpServletResponse response = (HttpServletResponse) resp;
			String requestedPath = request.getRequestURI();
			this.logger.info( "Path for auth: " + requestedPath );

			// Find the session ID in the cookies
			String sessionId = null;
			Cookie[] cookies = request.getCookies();
			if( cookies != null ) {
				for( Cookie cookie : cookies ) {
					if( UrlConstants.SESSION_ID.equals( cookie.getName())) {
						sessionId = cookie.getValue();
						break;
					}
				}
			}

			// Is there a valid session?
			boolean loggedIn = false;
			if( ! Utils.isEmptyOrWhitespaces( sessionId )) {
				loggedIn = this.authenticationMngr.isSessionValid( sessionId, this.sessionPeriod );
				this.logger.finest( "Session " + sessionId + (loggedIn ? " was successfully " : " failed to be ") + "validated." );
			} else {
				this.logger.finest( "No session ID was found in the cookie. Authentication cannot be performed." );
			}

			// Valid session, go on. Send an error otherwise.
			// No redirection, we mainly deal with our web socket and REST API.
			boolean loginRequest = requestedPath.endsWith( IAuthenticationResource.PATH + IAuthenticationResource.LOGIN_PATH );
			if( loggedIn || loginRequest ) {
				chain.doFilter( request, response );
			} else {
				response.sendError( 403, "Authentication is required." );
			}
		}
	}


	@Override
	public void destroy() {
		// nothing
	}


	@Override
	public void init( FilterConfig filterConfig ) throws ServletException {
		// nothing
	}

	public void setAuthenticationEnabled( boolean authenticationEnabled ) {
		this.authenticationEnabled = authenticationEnabled;
	}

	public void setAuthenticationManager( AuthenticationManager authenticationMngr ) {
		this.authenticationMngr = authenticationMngr;
	}

	public void setSessionPeriod( long sessionPeriod ) {
		this.sessionPeriod = sessionPeriod;
	}
}

And here is the section in my class that registers my filter.
It is the same method that registers my servlets in the HTTP service.

this.authenticationFilter = new AuthenticationFilter();
this.authenticationFilter.setAuthenticationEnabled( this.enableAuthentication );
this.authenticationFilter.setAuthenticationManager( this.authenticationMngr );
this.authenticationFilter.setSessionPeriod( this.sessionPeriod );

initParams = new Hashtable<> ();
initParams.put( "urlPatterns", "*" );

// Consider the bundle context can be null (e.g. when used outside of OSGi)
if( this.bundleContext != null )
	this.filterServiceRegistration = this.bundleContext.registerService( Filter.class, this.authenticationFilter, initParams );
else
	this.logger.warning( "No bundle context was available, the authentication filter was not registered." );

In Karaf 4.x, this is enough to register a servlet filter.
There are other alternatives, but this one requires less code. Read Achim’s answer here for more details.

Use Karaf’s JAAS implementation in your own Bundle

This article acts as both a help for people and as a reminder for myself (just in case).

So, I have a REST API based on Jersey 1.x that runs inside Apache Karaf. And I would like to add authentication for this REST API. The most important part is that the authentication should be delegated to Karaf. Indeed, Karaf provides an extensible and easy-to- use solution to authenticate with various backends: properties files, LDAP, databases, etc.

Personally, I was not an expert with JAAS (JAAS is the framework used by Karaf to manage users, roles, and in a more general way, authentication and authorizations). I spent a couple of hours to find how to easily plug with Karaf’s JAAS implementation. And believe or not, but the solution is very easy.

Here is a code snippet to put in your bundle, and that’s it!

public void authenticate( String user, String pwd, String realm ) throws LoginException {

	// If the authentication fails, a LoginException will be thrown
	LoginContext loginCtx = new LoginContext( realm, new RoboconfCallbackHandler( user, pwd ));
	loginCtx.login();
}

/**
* A callback handler for JAAS.
*/
static final class RoboconfCallbackHandler implements CallbackHandler {
	private final String username, password;

	/**
	 * Constructor.
	 * @param username
	 * @param password
	 */
	public RoboconfCallbackHandler( String username, String password ) {
		this.username = username;
		this.password = password;
	}

	@Override
	public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {

		for( Callback callback : callbacks ) {
			if (callback instanceof NameCallback )
				((NameCallback) callback).setName( this.username );
			else if( callback instanceof PasswordCallback )
				((PasswordCallback) callback).setPassword( this.password.toCharArray());
			else
				throw new UnsupportedCallbackException( callback );
		}
	}
}

By default, the configured REALM in Karaf is named “karaf”. It is based on properties files. So, you can test the code above with authenticate( “karaf”, “karaf”, “karaf” ). REALMs configuration is then documented on Karaf’s web site.