karaf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ldywi...@apache.org
Subject [karaf] branch karaf-4.1.x updated: [KARAF-5418] Add LDAPPubkeyLoginModule JAAS module
Date Wed, 06 Dec 2017 14:19:51 GMT
This is an automated email from the ASF dual-hosted git repository.

ldywicki pushed a commit to branch karaf-4.1.x
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/karaf-4.1.x by this push:
     new fcff64f  [KARAF-5418] Add LDAPPubkeyLoginModule JAAS module
fcff64f is described below

commit fcff64f6e95dbe92582d0e4a8ba9db2125ad1086
Author: Ciprian Ciubotariu <cheepeero@gmx.net>
AuthorDate: Mon Oct 9 20:41:53 2017 +0300

    [KARAF-5418] Add LDAPPubkeyLoginModule JAAS module
    
    This commit contains two test public/private key pairs that are used to exercise the LDAPPubkeyLoginModule
---
 jaas/modules/pom.xml                               |  11 ++
 .../apache/karaf/jaas/modules/ldap/LDAPCache.java  |  42 ++++++
 .../karaf/jaas/modules/ldap/LDAPLoginModule.java   |  30 +---
 .../karaf/jaas/modules/ldap/LDAPOptions.java       |   5 +
 .../jaas/modules/ldap/LDAPPubkeyLoginModule.java   | 158 ++++++++++++++++++++
 .../org/apache/karaf/jaas/modules/ldap/Util.java   |  48 ++++++
 .../modules/publickey/PublickeyLoginModule.java    |   6 +-
 .../jaas/modules/NamePubkeyCallbackHandler.java    |  63 ++++++++
 .../modules/ldap/LDAPPubkeyLoginModuleTest.java    | 161 +++++++++++++++++++++
 .../jaas/modules/ldap/LdapLoginModuleTest.java     |   4 +
 .../jaas/modules/ldap/example.com_pubkey.ldif      |  68 +++++++++
 .../karaf/jaas/modules/ldap/ldap_pubkey.properties |  37 +++++
 .../karaf/jaas/modules/ldap/ldaptest.admin.id_rsa  |  15 ++
 .../jaas/modules/ldap/ldaptest.admin.id_rsa.pub    |   1 +
 .../karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa |  15 ++
 .../jaas/modules/ldap/ldaptest.cheese.id_rsa.pub   |   1 +
 16 files changed, 633 insertions(+), 32 deletions(-)

diff --git a/jaas/modules/pom.xml b/jaas/modules/pom.xml
index 0e20efd..09a2792 100644
--- a/jaas/modules/pom.xml
+++ b/jaas/modules/pom.xml
@@ -115,6 +115,17 @@
             <version>${derby-version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPCache.java
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPCache.java
index f80af8c..bf9b83b 100644
--- a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPCache.java
+++ b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPCache.java
@@ -69,6 +69,7 @@ public class LDAPCache implements Closeable, NamespaceChangeListener, ObjectChan
 
     private final Map<String, String[]> userDnAndNamespace;
     private final Map<String, String[]> userRoles;
+    private final Map<String, String[]> userPubkeys;
     private final LDAPOptions options;
     private DirContext context;
 
@@ -76,6 +77,7 @@ public class LDAPCache implements Closeable, NamespaceChangeListener, ObjectChan
         this.options = options;
         userDnAndNamespace = new HashMap<>();
         userRoles = new HashMap<>();
+        userPubkeys = new HashMap<>();
     }
 
     @Override
@@ -212,6 +214,18 @@ public class LDAPCache implements Closeable, NamespaceChangeListener,
ObjectChan
         return result;
     }
 
+    public synchronized String[] getUserPubkeys(String userDn) throws NamingException {
+        String[] result = userPubkeys.get(userDn);
+        if (result == null) {
+            result = doGetUserPubkeys(userDn);
+            if (!options.getDisableCache()) {
+                userPubkeys.put(userDn, result);
+            }
+        }
+        return result;
+    }
+
+
     protected Set<String> tryMappingRole(String role) {
         Set<String> roles = new HashSet<>();
         if (options.getRoleMapping().isEmpty()) {
@@ -292,6 +306,33 @@ public class LDAPCache implements Closeable, NamespaceChangeListener,
ObjectChan
         }
     }
 
+    private String[] doGetUserPubkeys(String userDn) throws NamingException {
+        DirContext context = open();
+
+        String userPubkeyAttribute = options.getUserPubkeyAttribute();
+        if (userPubkeyAttribute != null) {
+            LOGGER.debug("Looking for public keys of user {} in attribute {}", userDn, userPubkeyAttribute);
+
+            Attributes attributes = context.getAttributes(userDn, new String[]{userPubkeyAttribute});
+            Attribute pubkeyAttribute = attributes.get(userPubkeyAttribute);
+
+            List<String> pubkeyList = new ArrayList<>();
+            if (pubkeyAttribute != null) {
+                for (int i = 0; i < pubkeyAttribute.size(); i++) {
+                    String pk = (String) pubkeyAttribute.get(i);
+                    if (pk != null) {
+                        pubkeyList.add(pk);
+                    }
+                }
+            }
+            return pubkeyList.toArray(new String[pubkeyList.size()]);
+        } else {
+            LOGGER.debug("The user public key attribute is null so no keys were retrieved");
+            return new String[] {};
+        }
+    }
+
+
     @Override
     public void objectAdded(NamingEvent evt) {
         clearCache();
@@ -320,5 +361,6 @@ public class LDAPCache implements Closeable, NamespaceChangeListener,
ObjectChan
     protected synchronized void clearCache() {
         userDnAndNamespace.clear();
         userRoles.clear();
+        userPubkeys.clear();
     }
 }
diff --git a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java
index 3266c90..4a6eec0 100644
--- a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java
+++ b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java
@@ -73,7 +73,7 @@ public class LDAPLoginModule extends AbstractKarafLoginModule {
             throw new LoginException(unsupportedCallbackException.getMessage() + " not available
to obtain information from user.");
         }
 
-        user = doRFC2254Encoding(((NameCallback) callbacks[0]).getName());
+        user = Util.doRFC2254Encoding(((NameCallback) callbacks[0]).getName());
 
         char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
 
@@ -161,34 +161,6 @@ public class LDAPLoginModule extends AbstractKarafLoginModule {
         return true;
     }
 
-    protected String doRFC2254Encoding(String inputString) {
-        StringBuffer buf = new StringBuffer(inputString.length());
-        for (int i = 0; i < inputString.length(); i++) {
-            char c = inputString.charAt(i);
-            switch (c) {
-                case '\\':
-                    buf.append("\\5c");
-                    break;
-                case '*':
-                    buf.append("\\2a");
-                    break;
-                case '(':
-                    buf.append("\\28");
-                    break;
-                case ')':
-                    buf.append("\\29");
-                    break;
-                case '\0':
-                    buf.append("\\00");
-                    break;
-                default:
-                    buf.append(c);
-                    break;
-            }
-        }
-        return buf.toString();
-    }
-
     public boolean abort() throws LoginException {
         return true;
     }
diff --git a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPOptions.java
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPOptions.java
index 3f2ba44..67a201f 100644
--- a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPOptions.java
+++ b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPOptions.java
@@ -38,6 +38,7 @@ public class LDAPOptions {
     public static final String USER_BASE_DN = "user.base.dn";
     public static final String USER_FILTER = "user.filter";
     public static final String USER_SEARCH_SUBTREE = "user.search.subtree";
+    public static final String USER_PUBKEY_ATTRIBUTE = "user.pubkey.attribute";
     public static final String ROLE_BASE_DN = "role.base.dn";
     public static final String ROLE_FILTER = "role.filter";
     public static final String ROLE_NAME_ATTRIBUTE = "role.name.attribute";
@@ -99,6 +100,10 @@ public class LDAPOptions {
         return Boolean.parseBoolean((String) options.get(USER_SEARCH_SUBTREE));
     }
 
+    public String getUserPubkeyAttribute() {
+        return (String) options.get(USER_PUBKEY_ATTRIBUTE);
+    }
+
     public String getRoleFilter() {
         return (String) options.get(ROLE_FILTER);
     }
diff --git a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModule.java
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModule.java
new file mode 100644
index 0000000..50e8729
--- /dev/null
+++ b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModule.java
@@ -0,0 +1,158 @@
+/*
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *  under the License.
+ */
+package org.apache.karaf.jaas.modules.ldap;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import java.io.IOException;
+import java.security.PublicKey;
+import java.util.HashSet;
+import java.util.Map;
+import javax.naming.NamingException;
+import javax.security.auth.login.FailedLoginException;
+
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.apache.karaf.jaas.boot.principal.UserPrincipal;
+import org.apache.karaf.jaas.modules.AbstractKarafLoginModule;
+import org.apache.karaf.jaas.modules.publickey.PublickeyCallback;
+import org.apache.karaf.jaas.modules.publickey.PublickeyLoginModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Karaf JAAS login module which uses a LDAP backend.
+ */
+public class LDAPPubkeyLoginModule extends AbstractKarafLoginModule {
+
+    private static Logger logger = LoggerFactory.getLogger(LDAPPubkeyLoginModule.class);
+
+    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String,
?> sharedState, Map<String, ?> options) {
+        super.initialize(subject, callbackHandler, options);
+        LDAPCache.clear();
+    }
+
+    public boolean login() throws LoginException {
+        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+        try {
+            return doLogin();
+        } finally {
+            ManagedSSLSocketFactory.setSocketFactory(null);
+            Thread.currentThread().setContextClassLoader(tccl);
+        }
+    }
+
+    protected boolean doLogin() throws LoginException {
+        Callback[] callbacks = new Callback[2];
+        callbacks[0] = new NameCallback("Username: ");
+        callbacks[1] = new PublickeyCallback();
+
+        try {
+            callbackHandler.handle(callbacks);
+        } catch (IOException ioException) {
+            throw new LoginException(ioException.getMessage());
+        } catch (UnsupportedCallbackException unsupportedCallbackException) {
+            throw new LoginException(unsupportedCallbackException.getMessage() + " not available
to obtain information from user.");
+        }
+
+        user = Util.doRFC2254Encoding(((NameCallback) callbacks[0]).getName());
+
+        PublicKey remotePubkey = ((PublickeyCallback) callbacks[1]).getPublicKey();
+
+        LDAPOptions options = new LDAPOptions(this.options);
+        if (options.isUsernameTrim()) {
+            if (user != null) {
+                user = user.trim();
+            }
+        }
+
+        principals = new HashSet<>();
+        LDAPCache cache = LDAPCache.getCache(options);
+
+        // step 1: get the user DN
+        final String[] userDnAndNamespace;
+        try {
+            logger.debug("Get the user DN.");
+            userDnAndNamespace = cache.getUserDnAndNamespace(user);
+            if (userDnAndNamespace == null) {
+                return false;
+            }
+        } catch (Exception e) {
+            logger.warn("Can't connect to the LDAP server: {}", e.getMessage(), e);
+            throw new LoginException("Can't connect to the LDAP server: " + e.getMessage());
+        }
+
+        String userFullDn = userDnAndNamespace[0] + "," + options.getUserBaseDn();
+
+        // step 2: pubkey authentication
+        try {
+            authenticatePubkey(userFullDn, remotePubkey, cache);
+        } catch (NamingException e) {
+            logger.warn("Can't connect to the LDAP server: {}", e.getMessage(), e);
+            throw new LoginException("Can't connect to the LDAP server: " + e.getMessage());
+        } catch (FailedLoginException e) {
+            if (!this.detailedLoginExcepion) {
+                throw new LoginException("Authentication failed");
+            } else {
+                logger.warn("Public key authentication failed for user {}: {}", user, e.getMessage(),
e);
+                throw new LoginException("Public key authentication failed for user " + user
+ ": " + e.getMessage());
+            }
+
+        }
+
+        principals.add(new UserPrincipal(user));
+
+        // step 3: retrieving user roles
+        try {
+            String[] roles = cache.getUserRoles(user, userDnAndNamespace[0], userDnAndNamespace[1]);
+            for (String role : roles) {
+                principals.add(new RolePrincipal(role));
+            }
+        } catch (Exception e) {
+            throw new LoginException("Can't get user " + user + " roles: " + e.getMessage());
+        }
+
+        return true;
+    }
+
+    private void authenticatePubkey(String userDn, PublicKey key, LDAPCache cache) throws
FailedLoginException, NamingException {
+        if (key == null)
+            throw new FailedLoginException("no public key supplied by the client");
+        String[] storedKeys = cache.getUserPubkeys(userDn);
+        if (storedKeys.length > 0) {
+            String keyString = PublickeyLoginModule.getString(key);
+            for (String storedKey : storedKeys) {
+                if (keyString.equals(storedKey)) {
+                    return;
+                }
+            }
+        }
+        throw new FailedLoginException("no matching public key found");
+    }
+
+    public boolean abort() throws LoginException {
+        return true;
+    }
+
+    public boolean logout() throws LoginException {
+        subject.getPrincipals().removeAll(principals);
+        principals.clear();
+        return true;
+    }
+
+}
diff --git a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/Util.java b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/Util.java
new file mode 100644
index 0000000..7ad2dd5
--- /dev/null
+++ b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/Util.java
@@ -0,0 +1,48 @@
+/*
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *  under the License.
+ */
+package org.apache.karaf.jaas.modules.ldap;
+
+class Util {
+
+    static String doRFC2254Encoding(String inputString) {
+        StringBuilder buf = new StringBuilder(inputString.length());
+        for (int i = 0; i < inputString.length(); i++) {
+            char c = inputString.charAt(i);
+            switch (c) {
+                case '\\':
+                    buf.append("\\5c");
+                    break;
+                case '*':
+                    buf.append("\\2a");
+                    break;
+                case '(':
+                    buf.append("\\28");
+                    break;
+                case ')':
+                    buf.append("\\29");
+                    break;
+                case '\u0000':
+                    buf.append("\\00");
+                    break;
+                default:
+                    buf.append(c);
+                    break;
+            }
+        }
+        return buf.toString();
+    }
+
+}
diff --git a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/publickey/PublickeyLoginModule.java
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/publickey/PublickeyLoginModule.java
index 310e39b..3394fc4 100644
--- a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/publickey/PublickeyLoginModule.java
+++ b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/publickey/PublickeyLoginModule.java
@@ -146,7 +146,7 @@ public class PublickeyLoginModule extends AbstractKarafLoginModule {
         return true;
     }
 
-    private String getString(PublicKey key) throws FailedLoginException {
+    public static String getString(PublicKey key) throws FailedLoginException {
         try {
             if (key instanceof DSAPublicKey) {
                 DSAPublicKey dsa = (DSAPublicKey) key;
@@ -176,13 +176,13 @@ public class PublickeyLoginModule extends AbstractKarafLoginModule {
         }
     }
 
-    private void write(DataOutputStream dos, BigInteger integer) throws IOException {
+    private static void write(DataOutputStream dos, BigInteger integer) throws IOException
{
         byte[] data = integer.toByteArray();
         dos.writeInt(data.length);
         dos.write(data, 0, data.length);
     }
 
-    private void write(DataOutputStream dos, String str) throws IOException {
+    private static void write(DataOutputStream dos, String str) throws IOException {
         byte[] data = str.getBytes();
         dos.writeInt(data.length);
         dos.write(data);
diff --git a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/NamePubkeyCallbackHandler.java
b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/NamePubkeyCallbackHandler.java
new file mode 100644
index 0000000..f85b271
--- /dev/null
+++ b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/NamePubkeyCallbackHandler.java
@@ -0,0 +1,63 @@
+/*
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *  under the License.
+ */
+package org.apache.karaf.jaas.modules;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Objects;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import org.apache.karaf.jaas.modules.publickey.PublickeyCallback;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+
+/**
+ * {@link CallbackHandler} implementation handling a name and public key.
+ */
+public class NamePubkeyCallbackHandler implements CallbackHandler {
+
+    private final String name;
+    private final PublicKey publicKey;
+
+    public NamePubkeyCallbackHandler(String name, PublicKey publicKey) {
+        this.name = Objects.requireNonNull(name);
+        this.publicKey = Objects.requireNonNull(publicKey);
+    }
+
+    public NamePubkeyCallbackHandler(String name, Path publicKeyFile) throws IOException
{
+        this.name = Objects.requireNonNull(name);
+
+        FileKeyPairProvider provider = new FileKeyPairProvider(publicKeyFile);
+        Iterable<KeyPair> keys = provider.loadKeys();
+        if (!keys.iterator().hasNext()) {
+            throw new IOException("no public keys loaded");
+        }
+        this.publicKey = keys.iterator().next().getPublic();
+    }
+
+    @Override
+    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
{
+        for (Callback cb : callbacks) {
+            if (cb instanceof NameCallback) {
+                ((NameCallback) cb).setName(name);
+            } else if (cb instanceof PublickeyCallback) {
+                ((PublickeyCallback) cb).setPublicKey(publicKey);
+            }
+        }
+    }
+}
diff --git a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModuleTest.java
b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModuleTest.java
new file mode 100644
index 0000000..b5a1d40
--- /dev/null
+++ b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModuleTest.java
@@ -0,0 +1,161 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.apache.karaf.jaas.modules.ldap;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+import org.apache.directory.server.annotations.CreateLdapServer;
+import org.apache.directory.server.annotations.CreateTransport;
+import org.apache.directory.server.core.annotations.ApplyLdifFiles;
+import org.apache.directory.server.core.annotations.CreateDS;
+import org.apache.directory.server.core.annotations.CreatePartition;
+import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
+import static org.apache.directory.server.core.integ.AbstractLdapTestUnit.getLdapServer;
+import org.apache.directory.server.core.integ.FrameworkRunner;
+import org.apache.felix.utils.properties.Properties;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.apache.karaf.jaas.boot.principal.UserPrincipal;
+import org.apache.karaf.jaas.modules.NamePubkeyCallbackHandler;
+import static org.apache.karaf.jaas.modules.PrincipalHelper.names;
+import static org.apache.karaf.jaas.modules.ldap.LdapPropsUpdater.ldapProps;
+import org.apache.log4j.Level;
+import org.hamcrest.Matchers;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import org.junit.After;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(FrameworkRunner.class)
+@CreateLdapServer(transports = {
+    @CreateTransport(protocol = "LDAP")})
+@CreateDS(name = "LdapPubkeyLoginModuleTest-class",
+        partitions = {
+            @CreatePartition(name = "example", suffix = "dc=example,dc=com")})
+@ApplyLdifFiles(
+        "org/apache/karaf/jaas/modules/ldap/example.com_pubkey.ldif"
+)
+public class LDAPPubkeyLoginModuleTest extends AbstractLdapTestUnit {
+
+    private static final String LDAP_PROPERTIES_FILE = "org/apache/karaf/jaas/modules/ldap/ldap_pubkey.properties";
+
+    @Before
+    public void updatePort() throws Exception {
+        ldapProps(LDAP_PROPERTIES_FILE, LdapLoginModuleTest::replacePort);
+    }
+
+    public static String replacePort(String line) {
+        return line.replaceAll("portno", "" + getLdapServer().getPort());
+    }
+
+    @After
+    public void tearDown() {
+        LDAPCache.clear();
+    }
+
+    @Test
+    public void testAdminLogin() throws Exception {
+        Properties options = ldapLoginModuleOptions();
+        LDAPPubkeyLoginModule module = new LDAPPubkeyLoginModule();
+        Subject subject = new Subject();
+        Path pubkeyFile = srcTestResourcePath("org/apache/karaf/jaas/modules/ldap/ldaptest.admin.id_rsa");
+        module.initialize(subject, new NamePubkeyCallbackHandler("admin", pubkeyFile), null,
options);
+
+        assertEquals("Precondition", 0, subject.getPrincipals().size());
+        assertTrue(module.login());
+        assertTrue(module.commit());
+
+        assertEquals(2, subject.getPrincipals().size());
+        assertThat(names(subject.getPrincipals(UserPrincipal.class)), containsInAnyOrder("admin"));
+        assertThat(names(subject.getPrincipals(RolePrincipal.class)), containsInAnyOrder("admin"));
+
+        assertTrue(module.logout());
+        assertEquals("Principals should be gone as the user has logged out", 0, subject.getPrincipals().size());
+    }
+
+    @Test
+    public void testNonAdminLogin() throws Exception {
+        Properties options = ldapLoginModuleOptions();
+        LDAPPubkeyLoginModule module = new LDAPPubkeyLoginModule();
+        Subject subject = new Subject();
+        Path pubkeyFile = srcTestResourcePath("org/apache/karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa");
+        module.initialize(subject, new NamePubkeyCallbackHandler("cheese", pubkeyFile), null,
options);
+
+        assertEquals("Precondition", 0, subject.getPrincipals().size());
+        assertTrue(module.login());
+        assertTrue(module.commit());
+
+        assertEquals(1, subject.getPrincipals().size());
+        assertThat(names(subject.getPrincipals(UserPrincipal.class)), containsInAnyOrder("cheese"));
+        assertThat(names(subject.getPrincipals(RolePrincipal.class)), Matchers.empty());
+
+        assertTrue(module.logout());
+        assertEquals("Principals should be gone as the user has logged out", 0, subject.getPrincipals().size());
+    }
+
+    @Test
+    public void testBadPrivateKey() throws Exception {
+        Properties options = ldapLoginModuleOptions();
+        LDAPPubkeyLoginModule module = new LDAPPubkeyLoginModule();
+        Subject subject = new Subject();
+        Path pubkeyFile = srcTestResourcePath("org/apache/karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa");
+        module.initialize(subject, new NamePubkeyCallbackHandler("admin", pubkeyFile), null,
options);
+
+        assertEquals("Precondition", 0, subject.getPrincipals().size());
+        org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(LDAPLoginModule.class);
+        Level oldLevel = logger.getLevel();
+        logger.setLevel(Level.OFF);
+        try {
+            module.login();
+            fail("Should have thrown LoginException");
+        } catch (LoginException e) {
+            assertTrue(e.getMessage().startsWith("Authentication failed"));
+        } finally {
+            logger.setLevel(oldLevel);
+        }
+
+    }
+
+    @Test
+    public void testUserNotFound() throws Exception {
+        Properties options = ldapLoginModuleOptions();
+        LDAPPubkeyLoginModule module = new LDAPPubkeyLoginModule();
+        Subject subject = new Subject();
+        Path pubkeyFile = srcTestResourcePath("org/apache/karaf/jaas/modules/ldap/ldaptest.admin.id_rsa");
+        module.initialize(subject, new NamePubkeyCallbackHandler("imnothere", pubkeyFile),
null, options);
+
+        assertEquals("Precondition", 0, subject.getPrincipals().size());
+        assertFalse(module.login());
+    }
+
+    private Path srcTestResourcePath(String relativePath) throws IOException {
+        String basedir = System.getProperty("basedir");
+        if (basedir == null) {
+            basedir = new File(".").getCanonicalPath();
+        }
+        Path pubkeyFile = Paths.get(basedir, "/src/test/resources/", relativePath);
+        return pubkeyFile;
+    }
+
+    protected Properties ldapLoginModuleOptions() throws IOException {
+        String basedir = System.getProperty("basedir");
+        if (basedir == null) {
+            basedir = new File(".").getCanonicalPath();
+        }
+        File file = new File(basedir + "/target/test-classes/" + LDAP_PROPERTIES_FILE);
+        return new Properties(file);
+    }
+
+}
diff --git a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java
b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java
index e93c0dd..137552c 100644
--- a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java
+++ b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java
@@ -60,6 +60,10 @@ public class LdapLoginModuleTest extends AbstractLdapTestUnit {
     
     private static boolean portUpdated;
 
+    public static String replacePort(String line) {
+        return line.replaceAll("portno", "" + getLdapServer().getPort());
+    }
+
     @Before
     public void updatePort() throws Exception {
         if (!portUpdated) {
diff --git a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/example.com_pubkey.ldif
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/example.com_pubkey.ldif
new file mode 100644
index 0000000..0cb87f5
--- /dev/null
+++ b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/example.com_pubkey.ldif
@@ -0,0 +1,68 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+
+dn: dc=example,dc=com
+objectclass: top
+objectclass: domain
+dc: example
+
+dn: ou=people,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: people
+
+dn: ou=groups,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: groups
+
+dn: cn=admin,ou=groups,dc=example,dc=com
+objectClass: top
+objectClass: groupOfNames
+cn: admin
+description: cn=admin,ou=groups,dc=example,dc=com
+member: cn=admin,ou=people,dc=example,dc=com
+
+dn: cn=admin,ou=people,dc=example,dc=com
+objectClass: top
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: organizationalPerson
+objectClass: extensibleObject
+cn: admin
+sn: admin
+uid: admin
+userPassword: admin123
+publicKey:: QUFBQUIzTnphQzF5YzJFQUFBQURBUUFCQUFBQWdRQzdRVi9MRFJSd29IL0xuV1NY
+ eDkxa0RZMEdvTW5uYUNpR3BGOTh6Y0JUSDY5dGxNdlBFRDNDYzd0eGRXVlp2UjFKSTIyeERwYmU
+ 1QTA1MzFaOFd0MTArSFZBZWorc2QwU0haVmJNYThrQ0NlYzBVQWlBZERuakd6NWNWTG5USCtwVl
+ FPVWk1cFUxalMva3N5dkdGcG44M29hVlgzRC9FLzg2TkN1aXRTRDkzUT09
+
+dn: cn=cheese,ou=people,dc=example,dc=com
+objectClass: top
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: organizationalPerson
+objectClass: extensibleObject
+cn: cheese
+sn: cheese
+uid: cheese
+userPassword: foodie
+publicKey:: QUFBQUIzTnphQzF5YzJFQUFBQURBUUFCQUFBQWdRQ3pFbjhETUF3ZzJodXdhSDc3
+ RVExZGdaMlQ5b2ltU0p5Lzc3Y1E0OFRkOVh1UGtTV3BUN0RZNWhJTDYreDVrVnRHSWhjdGxQZlc
+ xQTRmM3A4M216bjl0d1hkVFplSXMvUFk2UXdwWTQ3WDdzRDZHRElZZ21kMXh0ZjNwbms3Tkpna3
+ poallZTXhQVEdVWTFDQTVLMWhMMVlQMmMyS2JPTnVkTjFhZmhuSW01dz09
diff --git a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap_pubkey.properties
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap_pubkey.properties
new file mode 100644
index 0000000..5e1a707
--- /dev/null
+++ b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap_pubkey.properties
@@ -0,0 +1,37 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+debug=true
+connection.url=ldap://127.0.0.1:portno
+connection.username=uid=admin,ou=system
+connection.password=secret
+connection.protocol=
+authentication=simple
+
+user.base.dn=ou=people,dc=example,dc=com
+user.filter=(uid=%u)
+user.search.subtree=true
+user.pubkey.attribute=publicKey
+
+role.base.dn=ou=groups,dc=example,dc=com
+role.name.attribute=cn
+role.filter=(member=%fqdn)
+role.search.subtree=true
+
+initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
diff --git a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.admin.id_rsa
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.admin.id_rsa
new file mode 100644
index 0000000..dc79845
--- /dev/null
+++ b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.admin.id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC7QV/LDRRwoH/LnWSXx91kDY0GoMnnaCiGpF98zcBTH69tlMvP
+ED3Cc7txdWVZvR1JI22xDpbe5A0531Z8Wt10+HVAej+sd0SHZVbMa8kCCec0UAiA
+dDnjGz5cVLnTH+pVQOUi5pU1jS/ksyvGFpn83oaVX3D/E/86NCuitSD93QIDAQAB
+AoGBAKye3G7z1NbqrkSHCJd/ENFOSKZGjTn84/cTCk+j4NsAB5lOJP/yKezbAX3b
+Sh4K3zdwKIujNmOs+aBTCYhDv4eiZxnIV5Vej1d9zbzP7pVCUmd6qiHcW90sZsLt
+zVKkkv0OdlKEOTA5r9Ar5xx+Y61Vf/fNxxABACrCzfgsuyZFAkEA8b9+09GYKJp8
+6XY4wuguDiICODYPbFd7gqYm6p83zBTJXy/OfuX2LNfUyUja/XumsdPhPAHbLdc5
+H8vGHpnAfwJBAMZLdzxp+pGnYkgGEyaF0CLocEnoxSprhw6tY8o7SIrqyjPsltTu
+8iR9u53ntp7cjG7nzycULpkduslCjDcOE6MCQEi2UG9lm16bGPcfl/MH4tJdaE1/
+9SOhLIUfdJUdTqsTlX4L4xBIGsNiJ55jS3rytjDGifiClmozUfs+T1jk5gECQAe0
+2R508/MDMVOhQM9Hdg5VQD/vFvKOGUKdxHoQkcIsW81mzXnbC3gVltwNLFDCO4b3
+5Vocc68ps5+swWxGVMcCQCLukMpOgK8HGnMTs3F5+R+7AnhtnX6EAKuEzxJoXZYM
++QegJZvWii7GwJDuSvU0IQKqKZhdfp/TaxztDPEmMMg=
+-----END RSA PRIVATE KEY-----
diff --git a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.admin.id_rsa.pub
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.admin.id_rsa.pub
new file mode 100644
index 0000000..be1665e
--- /dev/null
+++ b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.admin.id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7QV/LDRRwoH/LnWSXx91kDY0GoMnnaCiGpF98zcBTH69tlMvPED3Cc7txdWVZvR1JI22xDpbe5A0531Z8Wt10+HVAej+sd0SHZVbMa8kCCec0UAiAdDnjGz5cVLnTH+pVQOUi5pU1jS/ksyvGFpn83oaVX3D/E/86NCuitSD93Q==
LdapUnitTestKey
diff --git a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa
new file mode 100644
index 0000000..1650c37
--- /dev/null
+++ b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQCzEn8DMAwg2huwaH77EQ1dgZ2T9oimSJy/77cQ48Td9XuPkSWp
+T7DY5hIL6+x5kVtGIhctlPfW1A4f3p83mzn9twXdTZeIs/PY6QwpY47X7sD6GDIY
+gmd1xtf3pnk7NJgkzhjYYMxPTGUY1CA5K1hL1YP2c2KbONudN1afhnIm5wIDAQAB
+AoGAC7xPlJ7mfJSusd33TG7uqE0hTZwfkn45v55vKe0zbrRy15LUnAb7+QsC7cMV
+aVYsXClJyZP0tiCJmG8XkiZbI3h1qGkD4Z8h4M9GoxVZvrYV6UnEX8C7oeaPe0ao
+xe1hk7RzGNFREfM9yHt++Zq0geAZDSwN6XrZDL+qpWLap7kCQQDXjZdleBiNxB6q
+dq7dJEgYUjc6fjs2x7BhaeCBgcRZWIpnqe4VtNGmT3wJHOKpxCj3w8AppRck4Ju6
+LS69b0c1AkEA1Kx+fZe9sYLg+l69lJy+slXDx32Ite9dUNgETRPNCO9Pp6btXjs8
+x+VZe7ZSxSnEiUvJ+x4K+gVmp3UTPIWNKwJBALiWPgHcuFoeiow7mj8x5LM/JKBo
+nNiqZHbnLiR5NeW1FsDzGjloYOhkxLkhDVGH8/VIonSHNayU04a5Tn9WnckCQQCc
+4YGNc9nikAEVr715Wwbw1oNNLTUjwCa99Bt4IBsndCD2MxT2Zgw4CN8xexUji+QG
+w3mDXG4McN+At8Qw6PNxAkBapr5ojSjeoaWS4+nmMLfxlLuCpo4WbjCr9aFVSCy1
+PpQtHVREUgoq4Vo61BNleQ/kSrQXBIgZwQtwGXifUWNM
+-----END RSA PRIVATE KEY-----
diff --git a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa.pub
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa.pub
new file mode 100644
index 0000000..17ae7b7
--- /dev/null
+++ b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldaptest.cheese.id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCzEn8DMAwg2huwaH77EQ1dgZ2T9oimSJy/77cQ48Td9XuPkSWpT7DY5hIL6+x5kVtGIhctlPfW1A4f3p83mzn9twXdTZeIs/PY6QwpY47X7sD6GDIYgmd1xtf3pnk7NJgkzhjYYMxPTGUY1CA5K1hL1YP2c2KbONudN1afhnIm5w==
LdapUnitTestKey

-- 
To stop receiving notification emails like this one, please contact
['"commits@karaf.apache.org" <commits@karaf.apache.org>'].

Mime
View raw message