diff --git a/warden3/contrib/warden_ra/warden_ra.py b/warden3/contrib/warden_ra/warden_ra.py
index f524153d720d515ad3de0c63620bc4e4f61a0703..bb340842aa667f2fc0facf80592d205fcf4ffa0c 100755
--- a/warden3/contrib/warden_ra/warden_ra.py
+++ b/warden3/contrib/warden_ra/warden_ra.py
@@ -6,13 +6,22 @@
 
 import sys
 import os
+import time
+import fcntl
+import errno
 import string
 import random
 import struct
+import operator
 import argparse
-import subprocess
 import json
 import logging
+import os.path as pth
+import subprocess
+import shlex
+import tempfile
+import M2Crypto
+import ConfigParser
 # *ph* server vulnerable to logjam, local openssl too new, use hammer to disable Diffie-Helmann
 import ssl
 ssl._DEFAULT_CIPHERS += ":!DH"
@@ -20,105 +29,55 @@ ssl._DEFAULT_CIPHERS += ":!DH"
 import ejbcaws
 
 # usual path to warden server
-sys.path.append(os.path.join(os.path.dirname(__file__), "..", "warden-server"))
+sys.path.append(pth.join(pth.dirname(__file__), "..", "warden-server"))
 import warden_server
 from warden_server import Request, ObjectBase, FileLogger, SysLogger, Server, expose, read_cfg
 
 
-class ClientDisabledError(Exception):
-    pass
+class ClientDisabledError(Exception): pass
+class ClientNotIssuableError(Exception): pass
+class AuthenticationError(Exception): pass
+class PopenError(Exception): pass
 
 
-class EjbcaClient(object):
+class Client(object):
 
-    def __init__(self, registry, ejbca_data=None):
-        self.registry = registry
-        self.ejbca_data = ejbca_data or {}
-
-    @property
-    def admins(self):
-        return [u if not u.startswith("RFC822NAME") else u[11:] for u in self.ejbca_data["subjectAltName"].split(",")]
-
-    @admins.setter
-    def admins(self, emails):
-        self.ejbca_data["subjectAltName"] = ",".join(("RFC822NAME=%s" % e for e in emails))
-
-    @property
-    def name(self):
-        username = self.ejbca_data["username"]
-        if not username.endswith(self.registry.username_suffix):
-            raise ValueError(("Ejbca user username does not conform to config", self.ejbca_data))
-        return username[:-len(self.registry.username_suffix)]
-
-    @name.setter
-    def name(self, new):
-        self.ejbca_data["username"] = new + self.registry.username_suffix
-        self.ejbca_data["subjectDN"] = self.registry.subject_dn_template % new
-
-    @property
-    def enabled(self):
-        return self.ejbca_data["status"] != ejbcaws.STATUS_HISTORICAL
-
-    @enabled.setter
-    def enabled(self, new):
-        if self.enabled:
-            if not new:
-                self.ejbca_data["status"] = ejbcaws.STATUS_HISTORICAL
-        else:
-            if new:
-                self.ejbca_data["status"] = ejbcaws.STATUS_GENERATED
-
-    @property
-    def status(self):
-        s = self.ejbca_data["status"]
-        if s == ejbcaws.STATUS_NEW:
-            return "Issuable"
-        elif s == ejbcaws.STATUS_GENERATED:
-            return "Passive"
-        elif s == ejbcaws.STATUS_INITIALIZED:
-            return "New"
-        elif s == ejbcaws.STATUS_HISTORICAL:
-            return "Disabled"
-        else:
-            return "EJBCA status %d" % s
-
-    def get_certs(self):
-        return self.registry.ejbca.find_certs(self.ejbca_data["username"], validOnly=False)
-
-    def allow_new_cert(self, pwd=None):
-        if not self.enabled:
-            raise ClientDisabledError("This client is disabled")
-        self.ejbca_data["status"] = ejbcaws.STATUS_NEW
-        if pwd is not None:
-            self.ejbca_data["password"] = pwd
-            self.ejbca_data["clearPwd"] = True
-
-    def new_cert(self, csr, pwd):
-        cert = self.registry.ejbca.pkcs10_request(
-            self.ejbca_data["username"],
-            pwd, csr, 0, ejbcaws.RESPONSETYPE_CERTIFICATE)
-        return cert
+    def __init__(self, name, admins=None, status=None, pwd=None, opaque=None):
+        self.name = name
+        self.admins = admins or []
+        self.status = status or "New"
+        self.pwd = pwd
+        self.opaque = opaque or {}
+
+    def update(self, admins=None, status=None, pwd=None):
+        if admins is not None:
+            self.admins = admins
+        if status:
+            if self.status == "Disabled" and status not in ("Passive", "Disabled"):
+                raise ClientDisabledError("This client is disabled")
+            self.status = status
+        self.pwd = pwd if status=="Issuable" and pwd else None
 
     def __str__(self):
         return (
-            "Client:   %s (%s)\n"
+            "Client:   %s\n"
             "Admins:   %s\n"
             "Status:   %s\n"
-        ) % (
-            self.name,
-            self.ejbca_data["subjectDN"],
-            ", ".join(self.admins),
-            self.status
-        )
+        ) % (self.name, ", ".join(self.admins), self.status)
 
-    def verbose_str(self):
-        return "%s\n" % self.ejbca_data
+    def str(self, verbose=False):
+        return str(self) + (str(self.opaque) if self.opaque and verbose else "")
 
-    def save(self):
-        self.registry.ejbca.edit_user(self.ejbca_data)
 
+class EjbcaRegistry(OpenSSLRegistry):
 
-class EjbcaRegistry(object):
+    status_ejbca_to_str = {
+        ejbcaws.STATUS_NEW: "Issuable",
+        ejbcaws.STATUS_GENERATED: "Passive",
+        ejbcaws.STATUS_INITIALIZED: "New",
+        ejbcaws.STATUS_HISTORICAL: "Disabled"
+    }
+    status_str_to_ejbca = dict((v, k) for k, v in status_ejbca_to_str.items())
 
     def __init__(self, log, url, cert=None, key=None,
                  ca_name="", certificate_profile_name="", end_entity_profile_name="",
@@ -131,8 +90,15 @@ class EjbcaRegistry(object):
         self.subject_dn_template = subject_dn_template
         self.username_suffix = username_suffix
 
+    def client_data(self, ejbca_data):
+        ejbca_username = ejbca_data["username"]
+        username = ejbca_username[:-len(self.username_suffix)] if ejbca_username.endswith(self.username_suffix) else ejbca_username
+        admins = [u if not u.startswith("RFC822NAME") else u[11:] for u in ejbca_data["subjectAltName"].split(",")]
+        status = self.status_ejbca_to_str.get(ejbca_data["status"], "Other")
+        return username, admins, status, None, ejbca_data
+
     def get_clients(self):
-        return (EjbcaClient(registry=self, ejbca_data=data) for data in self.ejbca.get_users())
+        return [Client(*self.client_data(u)) for u in self.ejbca.get_users()]
 
     def get_client(self, name):
         users = self.ejbca.find_user(ejbcaws.MATCH_WITH_USERNAME, ejbcaws.MATCH_TYPE_EQUALS, name + self.username_suffix)
@@ -140,35 +106,151 @@ class EjbcaRegistry(object):
             raise LookupError("%d users %s found (more than one?!)" % (len(users), name))
         if not users:
             return None
-        return EjbcaClient(registry=self, ejbca_data=users[0])
+        return Client(*self.client_data(users[0]))
 
-    def new_client(self, name, admins):
-        user = self.get_client(name)
-        if user:
-            raise LookupError("Client %s already exists" % name)
-        new_ejbca_data = dict(
+    def save_client(self, client):
+        edata = client.opaque or dict(
             caName=self.ca_name,
             certificateProfileName=self.certificate_profile_name,
             endEntityProfileName=self.end_entity_profile_name,
             keyRecoverable=False,
             sendNotification=False,
-            status=ejbcaws.STATUS_INITIALIZED,
-            subjectAltName="",
-            subjectDN="",
             tokenType=ejbcaws.TOKEN_TYPE_USERGENERATED,
-            username="",
             password = "".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16))),
-            clearPwd = True
+            clearPwd = True,
+            username = client.name + self.username_suffix,
+            subjectDN = self.subject_dn_template % client.name
         )
-        client = EjbcaClient(registry=self, ejbca_data=new_ejbca_data)
-        client.name = name
-        client.admins = admins
-        return client
+        edata["subjectAltName"] = ",".join(("RFC822NAME=%s" % a for a in client.admins))
+        edata["status"] = self.status_str_to_ejbca.get(client.status, edata["status"])
+        if client.pwd:
+            edata["password"] = client.pwd
+            edata["clearPwd"] = True
+        self.ejbca.edit_user(edata)
+
+    def get_certs(self, client):
+        return self.ejbca.find_certs(client.opaque["username"], validOnly=False)
+
+    def new_cert(self, client, csr, pwd):
+        cert = self.ejbca.pkcs10_request(
+            client.opaque["username"],
+            pwd, csr, 0, ejbcaws.RESPONSETYPE_CERTIFICATE)
+        return cert
 
-    def verbose_str(self):
+    def __str__(self):
         return self.ejbca.get_version()
 
 
+class OpenSSLRegistry(object):
+
+    def __init__(self, log, base_dir,
+                 subject_dn_template, openssl_sign, lock_timeout):
+        self.base_dir = base_dir
+        self.cnf_file = pth.join(base_dir, "openssl.cnf")
+        self.client_dir = pth.join(base_dir, "clients")
+        self.serial_file = pth.join(base_dir, "serial")
+        self.newcerts_dir = pth.join(base_dir, "newcerts")
+        self.csr_dir = pth.join(base_dir, "csr")
+        self.lock_file = pth.join(base_dir, "lock")
+        self.lock_timeout = lock_timeout
+        self.log = log
+        self.subject_dn_template = subject_dn_template
+        self.openssl_sign = openssl_sign
+
+    def get_clients(self):
+        return [self.get_client(c) for c in os.listdir(self.client_dir) if pth.isdir(pth.join(self.client_dir, c))]
+
+    def get_client(self, name):
+        config = ConfigParser.RawConfigParser()
+        if not config.read(pth.join(self.client_dir, name, "state")):
+            return None
+        datum = dict(config.items("Client"))
+        return Client(name, admins=datum["admins"].split(","), status=datum["status"], pwd=datum.get("password"))
+
+    def new_client(self, name, admins=None):
+        user = self.get_client(name)
+        if user:
+            raise LookupError("Client %s already exists" % name)
+        return Client(name, admins)
+
+    def save_client(self, client):
+        config = ConfigParser.RawConfigParser()
+        config.add_section("Client")
+        config.set("Client", "admins", ",".join(client.admins))
+        config.set("Client", "status", client.status)
+        if client.pwd:
+            config.set("Client", "password", client.pwd)
+        client_path = pth.join(self.client_dir, client.name)
+        try:
+            os.makedirs(client_path)
+        except OSError as e:
+            if e.errno != errno.EEXIST:
+                raise
+        with tempfile.NamedTemporaryFile(dir=client_path, delete=False) as cf:
+            config.write(cf)
+        os.rename(cf.name, pth.join(client_path, "state")) # atomic + rewrite, so no need for locking
+
+    def get_certs(self, client):
+        files = [fname for fname in os.listdir(pth.join(self.client_dir, client.name)) if not fname.startswith(".") and fname.endswith(".pem")]
+        certs = [M2Crypto.X509.load_cert(pth.join(self.client_dir, client.name, fname)) for fname in files]
+        return certs
+
+    def __enter__(self):
+        self._lockfd = os.open(self.lock_file, os.O_CREAT)
+        start = time.time()
+        while True:
+            try:
+                fcntl.flock(self._lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+                return
+            except (OSError, IOError) as e:
+                if e.errno != errno.EAGAIN or time.time() > start + self.lock_timeout:
+                   raise
+            time.sleep(0.5)
+
+    def __exit__(self, type_, value, traceback):
+        fcntl.flock(self._lockfd, fcntl.LOCK_UN)
+        os.close(self._lockfd)
+        try:
+            os.unlink(self.lock_file)
+        except:
+            pass
+
+    def run_openssl(self, command, **kwargs):
+        cmdline = shlex.split(command % kwargs)
+        process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        res = process.communicate()
+        if process.returncode:
+            raise PopenError("Popen returned nonzero code", process.returncode, ' '.join(cmdline), res[0], res[1])
+        return res
+
+    def new_cert(self, client, csr, pwd):
+        if client.status != "Issuable" or not client.pwd:
+            raise ClientNotIssuableError("Client not allowed to issue request or password not set")
+        if client.pwd != pwd:
+            raise AuthenticationError("Wrong credentials")
+        dn = self.subject_dn_template.replace("/", "//").replace(",", "/") % client.name
+        if not dn.startswith("/"):
+            dn = "/" + dn
+        with tempfile.NamedTemporaryFile(dir=self.csr_dir, delete=False) as csr_file:
+            csr_file.write(csr)
+        with self:  # lock dance
+            with open(self.serial_file) as f:
+                serial = f.read().strip()
+            output = self.run_openssl(self.openssl_sign, cnf = self.cnf_file, csr = csr_file.name, dn = dn)
+        self.log.debug(output)
+        os.rename(csr_file.name, pth.join(self.csr_dir, serial + ".csr.pem"))
+        client_pem_name = pth.join(self.client_dir, client.name, serial + ".cert.pem")
+        os.symlink(pth.join(self.newcerts_dir, serial + ".pem"), client_pem_name)
+        with open(client_pem_name) as pem:
+            cert = M2Crypto.X509.load_cert_string(pem.read(), M2Crypto.X509.FORMAT_PEM)
+        client.update(status="Passive", pwd=None)
+        self.save_client(client)
+        return cert
+
+    def __str__(self):
+        return "%s<%s>" % (type(self).__name__, self.base_dir)
+
+
 def format_cert(cert):
     return (
         "Subject:     %s\n"
@@ -186,6 +268,7 @@ def format_cert(cert):
         cert.get_issuer().as_text()
     )
 
+
 # Server side
 
 class OptionalAuthenticator(ObjectBase):
@@ -242,21 +325,21 @@ class CertHandler(ObjectBase):
         client = self.registry.get_client(name[0])
         if not client:
             raise self.req.error(message="Unknown client", error=403, name=name, password=password)
-        self.log.info("Client %s" % client.name)
+        self.log.info("Client %s" % client)
         if self.req.client == "cert":
             # Correctly authenticated by cert, most probably not preactivated with password,
             # so generate oneshot password and allow now
             password = "".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16)))
             self.log.debug("Authorized by X509, enabling cert generation with password %s" % password)
             try:
-                client.allow_new_cert(pwd=password)
+                client.update(status="Issuable", pwd=password)
+                self.registry.save_client(client)
             except ClientDisabledError as e:
                 raise self.req.error(message="Error enabling cert generation", error=403, exc=sys.exc_info())
-            client.save()
         if not password:
             raise self.req.error(message="Missing password and certificate validation failed", error=403, name=name, password=password)
         try:
-            newcert = client.new_cert(csr_data, password)
+            newcert = self.registry.new_cert(client, csr_data, password)
         except Exception as e:
             raise self.req.error(message="Processing error", error=403, exc=sys.exc_info())
         self.log.info("Generated.")
@@ -272,7 +355,7 @@ section_order = ("log", "auth", "registry", "handler", "server")
 section_def = {
     "log": [FileLogger, SysLogger],
     "auth": [OptionalAuthenticator],
-    "registry": [EjbcaRegistry],
+    "registry": [OpenSSLRegistry, EjbcaRegistry],
     "handler": [CertHandler],
     "server": [Server]
 }
@@ -286,11 +369,18 @@ param_def = {
         "req": {"type": "obj", "default": "req"},
         "log": {"type": "obj", "default": "log"}
     },
+    OpenSSLRegistry: {
+        "log":  {"type": "obj", "default": "log"},
+        "base_dir": {"type": "str", "default": pth.join(pth.dirname(__file__), "ca")},
+        "subject_dn_template": {"type": "str", "default": "DC=cz,DC=example-ca,DC=warden,CN=%s"},
+        "openssl_sign": {"type": "str", "default": "openssl ca -config %(cnf)s -batch -extensions server_cert -days 375 -notext -md sha256 -in %(csr)s -subj '%(dn)s'"},
+        "lock_timeout": {"type": "natural", "default": "3"}
+    },
     EjbcaRegistry: {
         "log":  {"type": "obj", "default": "log"},
         "url": {"type": "str", "default": "https://ejbca.example.org/ejbca/ejbcaws/ejbcaws?wsdl"},
-        "cert": {"type": "filepath", "default": os.path.join(os.path.dirname(__file__), "warden_ra.cert.pem")},
-        "key": {"type": "filepath", "default": os.path.join(os.path.dirname(__file__), "warden_ra.key.pem")},
+        "cert": {"type": "filepath", "default": pth.join(pth.dirname(__file__), "warden_ra.cert.pem")},
+        "key": {"type": "filepath", "default": pth.join(pth.dirname(__file__), "warden_ra.key.pem")},
         "ca_name": {"type": "str", "default": "Example CA"},
         "certificate_profile_name": {"type": "str", "default": "Example"},
         "end_entity_profile_name": {"type": "str", "default": "Example EE"},
@@ -304,7 +394,7 @@ param_def = {
     }
 }
 
-param_def[FileLogger]["filename"] = {"type": "filepath", "default": os.path.join(os.path.dirname(__file__), os.path.splitext(os.path.split(__file__)[1])[0] + ".log")}
+param_def[FileLogger]["filename"] = {"type": "filepath", "default": pth.join(pth.dirname(__file__), pth.splitext(pth.split(__file__)[1])[0] + ".log")}
 
 
 def build_server(conf):
@@ -313,36 +403,33 @@ def build_server(conf):
 
 # Command line
 
-def list_clients(registry, name=None, verbose=False):
+def list_clients(registry, name=None, verbose=False, show_cert=True):
     if name is not None:
         client = registry.get_client(name)
         if client is None:
             print "No such client."
             return
         else:
-            print(client)
-            if verbose:
-                print(client.verbose_str())
-            for cert in sorted(client.get_certs(), key=lambda c: c.get_not_after().get_datetime()):
-                print(format_cert(cert))
-                if verbose:
-                    print(cert.as_text())
+            print(client.str(verbose))
+            if show_cert:
+                for cert in sorted(registry.get_certs(client), key=lambda c: c.get_not_after().get_datetime()):
+                    print(format_cert(cert))
+                    if verbose:
+                        print(cert.as_text())
     else:
         clients = registry.get_clients()
-        for client in sorted (clients, key=lambda c: c.name):
-            print(client)
-            if verbose:
-                print(client.verbose_str())
+        for client in sorted(clients, key=operator.attrgetter("name")):
+            print(client.str(verbose))
 
 
 def register_client(registry, name, admins=None, verbose=False):
     try:
-        client = registry.new_client(name, admins or [])
+        client = registry.new_client(name, admins)
     except LookupError as e:
         print(e)
         return
-    client.save()
-    list_clients(registry, name, verbose)
+    registry.save_client(client)
+    list_clients(registry, name, verbose, show_cert=False)
 
 
 def applicant(registry, name, password=None, verbose=False):
@@ -353,12 +440,12 @@ def applicant(registry, name, password=None, verbose=False):
     if password is None:
         password = "".join((random.choice(string.ascii_letters + string.digits) for dummy in range(16)))
     try:
-        client.allow_new_cert(pwd=password)
+        client.update(status="Issuable", pwd=password)
     except ClientDisabledError:
         print "This client is disabled. Use 'enable' first."
         return
-    client.save()
-    list_clients(registry, name, verbose)
+    registry.save_client(client)
+    list_clients(registry, name, verbose, show_cert=False)
     print("Application password is: %s\n" % password)
 
 
@@ -367,9 +454,9 @@ def enable(registry, name, verbose=False):
     if not client:
         print "No such client."
         return
-    client.enabled = True
-    client.save()
-    list_clients(registry, name, verbose)
+    client.update(status="Passive")
+    registry.save_client(client)
+    list_clients(registry, name, verbose, show_cert=False)
 
 
 def disable(registry, name, verbose=False):
@@ -377,9 +464,9 @@ def disable(registry, name, verbose=False):
     if not client:
         print "No such client."
         return
-    client.enabled = False
-    client.save()
-    list_clients(registry, name, verbose)
+    client.update(status="Disabled")
+    registry.save_client(client)
+    list_clients(registry, name, verbose, show_cert=False)
 
 
 def request(registry, key, csr, verbose=False):
@@ -409,7 +496,7 @@ def gen_cert(registry, name, csr, cert, password, verbose=False):
     with open(csr, "r") as f:
         csr_data = f.read()
     client = registry.get_client(name)
-    newcert = client.new_cert(csr_data, password)
+    newcert = registry.new_cert(client, csr_data, password)
     print(format_cert(newcert))
     if verbose:
         print(newcert.as_text())
@@ -510,7 +597,7 @@ def get_args():
 
 if __name__ == "__main__":
     args = get_args()
-    config = os.path.join(os.path.dirname(__file__), args.config or "warden_ra.cfg")
+    config = pth.join(pth.dirname(__file__), args.config or "warden_ra.cfg")
     server = build_server(read_cfg(config))
     registry = server.handler.registry
     if args.verbose: