Building and Using the Secret Service Java API

The Secret Service API was jointly designed by the Gnome keyring and the KDE KWallet developers in order to have a standard way for desktop applications to access and manage stored passwords in both Gnome and KDE environments.

In this article I will show you how to generate java code for the Secret Service API and then use it in your Java application.

A Bit of Terminology

If you are a Gnome user, passwords and keys are stored and managed by the GNOME Keyring Manager. The GUI to access passwords is a program called Seahorse. In gnome-keyring, passwords and keys are stored in keyrings. For example the keyring that contains all passwords and keys that you will be able to use after logging into your Gnome environment is called 'login'.

In the KDE environment the application is called KWallet. Passwords and keys are stored in a so-called 'Folder'. The Secret Service API is implemented from KDE-4.8 in the KSecretService module used by KWallet.

In the Secret Service API terminology, passwords and keys are called 'Items'. Keyrings and folders are called 'Collections'

Mapping between SecretService API and desktop terminology
Secret Service APIcollectionitem
Gnomekeyringuser/password;key
KDEfolderuser/password;key

Use cases in this article were designed for the Gnome environment. They are of course aimed to work under the KDE 4.8 environment.

Building and Using the Secret Service Java API

Requirements

For this tutorial, I use Java 1.5.

The Secret Service API is based upon the DBus architecture. Using DBus in Java will be possible with the dbus-java library.

Installing Required Libraries

The first thing to do is to install the libmatthew library, which will allow the dbus library to use native code.

Download at libmatthew library and then install it with appropriate paths (ignore the JAVA, JAVAC and JAR variables if your default JVM is 1.5).

tar zvxf libmatthew-java-0.8.tar.gz
cd libmatthew-java-0.8/
JAVA=/your/path/to/java JAVAC=/your/path/to/javac JAR=/your/path/to/jar PREFIX=/your/path/to/libmatthew make install

Then download the dbus-java library and install it with appropriate paths (ignore the JAVA, JAVAC and JAR variables if your default JVM is 1.5).

tar zxvf dbus-java-2.7.tar.gz
cd dbus-java-2.7/
JAVA=/your/path/to/java JAVAC=/your/path/to/javac JAR=/your/path/to/jar JAVAUNIXLIBDIR=/your/path/to/libmatthew/lib/jni/ JAVAUNIXJARDIR=/your/path/to/libmatthew/share/java make

You might have some LaTeX errors when generating the documentation, just skip them by pressing RETURN.

Building and Using the Secret Service Java API

Building the Secret Service Java Library

As an alternative, you can simply download my own version and skip to the next page.

Generating Secret Service API Java Sources

Sources are generated with the 'CreateInterface' tool described at: http://dbus.freedesktop.org/doc/dbus-java/dbus-java/dbus-javase10.html

Java source code can be generated from the DBus service XML description files or by accessing the DBus service itself.

Generating Sources from XML Files

In the Gnome environment, XML files are stored in /usr/share/gnome-keyring/introspect. KDE users need to find XML file location themselves.

You have to generate classes for each file:

java -Djava.library.path=/your/path/to/libmatthew/lib/jni/ -cp /your/path/to/libmatthew/share/java/unix.jar:/your/path/to/libmatthew/share/java/debug-disable.jar:/your/path/to/libmatthew/share/java/hexdump.jar:./libdbus-java-2.7.jar:./dbus-java-bin-2.7.jar org.freedesktop.dbus.bin.CreateInterface --create-files /usr/share/gnome-keyring/introspect/introspect-service.xml

This will give you a source file called 'Service.java'. Do the same with other files.

If you have no read access to XML files, you can download a copy with the dbus-send client:

dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.secrets /org/freedesktop/secrets org.freedesktop.DBus.Introspectable.Introspect
dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.secrets /org/freedesktop/secrets/collection/xxxx org.freedesktop.DBus.Introspectable.Introspect
dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.secrets /org/freedesktop/secrets/collection/xxxx/yyyy org.freedesktop.DBus.Introspectable.Introspect
...

xxxx is the name of a collection (keyring in Gnome and Folder in KDE).

yyyy is the ID of an item (password or key).

Generating Sources by Accessing the DBus Daemon

You can also use the CreateInterface directly by connecting to the DBus:

java -Djava.library.path=/your/path/to/libmatthew/lib/jni/ -cp /your/path/to/libmatthew/share/java/unix.jar:/your/path/to/libmatthew/share/java/debug-disable.jar:/your/path/to/libmatthew/share/java/hexdump.jar:./libdbus-java-2.7.jar:./dbus-java-bin-2.7.jar org.freedesktop.dbus.bin.CreateInterface --create-files --session "org.freedesktop.secrets" "/org/freedesktop/secrets"

You will also have to do this for each object path:

java -Djava.library.path=/your/path/to/libmatthew/lib/jni/ -cp /your/path/to/libmatthew/share/java/unix.jar:/your/path/to/libmatthew/share/java/debug-disable.jar:/your/path/to/libmatthew/share/java/hexdump.jar:./libdbus-java-2.7.jar:./dbus-java-bin-2.7.jar org.freedesktop.dbus.bin.CreateInterface --create-files --session "org.freedesktop.secrets" "/org/freedesktop/secrets/collection/login"
java -Djava.library.path=/your/path/to/libmatthew/lib/jni/ -cp /your/path/to/libmatthew/share/java/unix.jar:/your/path/to/libmatthew/share/java/debug-disable.jar:/your/path/to/libmatthew/share/java/hexdump.jar:./libdbus-java-2.7.jar:./dbus-java-bin-2.7.jar org.freedesktop.dbus.bin.CreateInterface --create-files --session "org.freedesktop.secrets" "/org/freedesktop/secrets/collection/xxxx/yyyy"
...

Refactoring, Compiling and Building

A couple of little bugs might make generated classes unusable. Some refactoring was necessary in my environment (Gnome-keyring 2.92.92.is.2.31.91-0ubuntu4 and libgnome-keyring0 2.31.92-0ubuntu1). I'll let you check if this is also the case for you.

Package Name

Classes should be located in a package named 'org.freedesktop.Secret'. If this is not the case, please change the generated name to this one in all Java source files.

Compiling Prompt.java

If the Prompt class compiling fails on method:

  public void Prompt(String window-id);

you have to change the name of the string argument to remove the '-'.

Using Collection.SearchItems

When using the 'Collection.SearchItems' method to get all items in a particular collection, my code failed with an error "Wrong return type". The number of values returned by this method did not match the number of values defined in my 'Collection' interface. Strange, since the 'Collection' code was generated from the introspect-collection.xml file!

XML definition:

<method name="SearchItems">
	<arg name="attributes" type="a{ss}" direction="in" />
	<arg name="results" type="ao" direction="out" />
</method>

Generated Java code:

public interface Collection extends DBusInterface
{
  public List<DBusInterface> SearchItems(Map<String,String> attributes);
  ...
}

After comparing with the method 'SearchItems' in the Service interface, I discovered that the XML file was bugged. The method returns a pair of List, one for unlocked keys, one for locked keys. The XML definition should be:

<method name="SearchItems">
	<arg name="attributes" type="a{ss}" direction="in" />
	<arg name="unlocked" type="ao" direction="out" />
	<arg name="locked" type="ao" direction="out" />
</method>

Then the Java code will be:

public interface Collection extends DBusInterface
{
  public Pair<List<DBusInterface>, List<DBusInterface>> SearchItems(Map<String,String> attributes);
  ...
}

Beautify the Code

This step is not necessary but provides cleaner code in your Java application.

The 'CreateInterface' generates two interfaces named 'Struct1' and 'Struct2' (that represent a secret). In fact both interfaces are the same, so you can delete 'Struct2'. I advise you to rename 'Struct1' to 'Secret'.

The class Secret is a Struct with three members 'a', 'b' and 'c'. Let's change the members name as follows:

  • change 'a' to 'session'
  • change 'b' to 'parameter'
  • change 'c' to 'value'

Then compile and build the Service Secret API jar (let's say secret-service-api-java-0.1.jar) using your favorite tool.

Building and Using the Secret Service Java API

Using the Secret Service API in Your Code

Once you have set appropriate classpaths in your project, you will have access to dbus-java-2.7.jar classes and secret-service-api-java-0.1.jar classes.

Do not forget to set the java.library.path property to use the libmatthew native java-unix.so library.

A Simple (Useless?) Use-case

Let's build a class that connects to the Gnome 'login' keyring and get the password for indentifier='21' (use an existing ID in your 'login' keyring). If you want to test on KDE, change 'login' for an existing folder (probably 'passwords').

First we have to open a connection to the DBus daemon:

DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION);

A Dbus connection can be opened at the System level or at the Session level. Here we want to get access to our personal keyrings so use the Session level.

Then we have to open a session, which will give us a Session object that will be used to exchange secrets (passwords):

String objectPath = "/org/freedesktop/secrets";
String busName = "org.freedesktop.secrets";
// get a proxy remote object that implements the Service interface
Service serv = (Service) conn.getRemoteObject(busName, objectPath, Service.class);
// call remote OpenSession on it (no encryption)
Pair<Variant,DBusInterface> osr = serv.OpenSession("plain", new org.freedesktop.dbus.Variant(""));
// get the session in the return object
DBusInterface dbusSession = osr.b;

The next step will provide the password:

// build the object path to the Item '21' in collection 'login'
objectPath = "/org/freedesktop/secrets/collection/login/21";
// get the remote object that implements the Item interface
Item item = (Item) conn.getRemoteObject(busName, objectPath, Item.class);
// call the remote method GetSecret and get a Secret object as the result
Secret secret = item.GetSecret(dbusSession);
// Access the password (stored in the 'value' member
Byte[] passwordAsByteArray = secret.value;

You will likely also get the name of the Item (called Label in the Secret Service API). The label is part of the information that you can get using the standard DBus 'Properties' interface:

// get the remote object that implements the Properties interface
prop = (Properties) conn.getRemoteObject(busName, objectPath, Properties.class);
// call the remote method 'Get' to get the 'Label' property
String label = (String) prop.Get("org.freedesktop.Secret.Item", "Label");
You can get each property ('Get') or all properties ('GetAll'). To know which property is available on an interface, use the introspection:
// get the remote object that implements the Introspectable interface
Introspectable in = (Introspectable) conn.getRemoteObject(busName, objectPath, Introspectable.class);
// call the remote method Introspect and get the XML definition as a String
String xmlDef = in.Introspect();

Right, I know, you have recognized the content of the XML files described in page 3.

A More Realistic Use-case

OK, I see. What you really want to do is to find a password from its name (user,service...). Let's try to get the password of your XMPP account ("XMPP myaccount@myjabber.mydomain.org"):

The first step is the same as above. Once you have the dbusSession object, you get the list of passwords stored in the 'login' collection and iterate in this list, to get what you are looking for.

// build the object path for the 'login' collection
objectPath = "/org/freedesktop/secrets/collection/login";
// get the remote object that implements the Collection interface
Collection collection = (Collection) conn.getRemoteObject(busName, objectPath, Collection.class);
// call the remote method with an empty Map as search attributes and get all the items in the collection
// as a pair of List: one for unlocked items, one for locked items
Pair<List<DBusInterface>,List<DBusInterface>> itemList = collection.SearchItems(new HashMap());
List<DBusInterface> unlockedItems = itemList.a;
List<DBusInterface> lockedItems = itemList.b;
// scan the unlocked list to find the item with Label="XMPP myaccount@myjabber.mydomain.org"
for (int i=0; i<unlockedItems.size(); i++) {
	// get the label of the item
	prop = (Properties)unlockedItems.get(i);
	String foundLabel = (String) prop.Get("org.freedesktop.Secret.Item", "Label");
	if (foundLabel.equals("XMPP myaccount@myjabber.mydomain.org")) {
		// This is a bit tricky here:
		// proxy objects sent by SearchItems do not implement Item, but Properties and Introspectable only
		// so we cannot use directly the GetSecret method
		// instead we get the objectPath as String and get the remote object that implements Item
		// get the Object path from toString() !!! because there is not getObjectPath() method...
		// toString = ":busadress+":"+objectpath+":"+iface"
		objectPath = prop.toString().split(":")[2];
		Item item = (Item) conn.getRemoteObject(busName, objectPath, Item.class);
		secret = item.GetSecret(dbusSession);
		// Access the password (stored in the 'value' member
		Byte[] passwordAsByteArray = secret.value;
		break;
	}
}

Of course, you can also scan the locked list, but if you find your item in this list, you will not be able to get the corresponding secret. The item has to be unlocked first.

Unlocking an item can be done by this API (org.freedesktop.Secret.Service.Unlock) or using your desktop environment.

Note that a more elegant way to get your chat password would have been to use the attributes instead of the hard-coded Label "XMPP myaccount@myjabber.mydomain.org":

// call the remote method with a Map of attributes and get all matching items in the collection
// as a pair of List: one for unlocked items, one for locked items
Map attrs = new HashMap();
attrs.put("user", "myaccount");
attrs.put("protocol", "xmpp");
attrs.put("server", "myjabber.mydomain.org");
Pair<List<DBusInterface>,List<DBusInterface>> itemList = collection.SearchItems(attrs);

Other Use-cases

Using the Secret Service API, your application will be able to store passwords, create collections, lock and unlock items...

Building and Using the Secret Service Java API

To Go Further

Of course I let you organize your code in a smarter way and your building process in a more automatic way, using standard tools like Ant or Maven. For example, it would be better to fix XML source files and use a Maven plugin to refactor your code, so that this process can be automated.

Last but not least, as you can get the XML definition at run-time (using the Introspectable interface) and as the 'CreateInterface' can be also used at run-time, you might want to dynamically generate SecretService classes in your Java application using the Java reflection library. With this solution, your code will always be compatible with the DBus definition whenever this definition changes.

Conclusion

Defining and using a standard protocol or API allows you to write a single code to access different and heterogeneous systems that implement such standards. This is the case then for Gnome-keyring and KDEWallet, even if at the time of writing, the software is still unstable. So do not be surprised if things do not work smoothly, you might have to make some adjustments...

Java source code provided in this article was roughly extracted from my Java project, but not tested, please do not hesitate to contact me if you find any bugs in it.

Resources



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • VMware vCloud® Government Service provided by Carpathia® is an enterprise-class hybrid cloud service that delivers the tried and tested VMware capabilities widely used by government organizations today, with the added security and compliance assurance of FedRAMP authorization. The hybrid cloud is becoming more and more prevalent – in fact, nearly three-fourths of large enterprises expect to have hybrid deployments by 2015, according to a recent Gartner analyst report. Learn about the benefits of …

Most Popular Programming Stories

More for Developers

RSS Feeds