nl.alders.openam.YubikeyModule.java Source code

Java tutorial

Introduction

Here is the source code for nl.alders.openam.YubikeyModule.java

Source

/**
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010 ForgeRock AS. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://forgerock.org/license/CDDLv1.0.html
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at http://forgerock.org/license/CDDLv1.0.html
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted 2013 Ron Alders"
 */

package nl.alders.openam;

import com.iplanet.dpro.session.service.InternalSession;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.iplanet.sso.SSOTokenManager;
import com.sun.identity.authentication.spi.AMLoginModule;
import com.sun.identity.authentication.spi.AuthLoginException;
import com.sun.identity.authentication.spi.InvalidPasswordException;
import com.sun.identity.authentication.util.ISAuthConstants;
import com.sun.identity.idm.*;
import com.sun.identity.shared.datastruct.CollectionHelper;
import com.sun.identity.shared.debug.Debug;
import com.yubico.client.v2.YubicoClient;
import com.yubico.client.v2.VerificationResponse;
import com.yubico.client.v2.ResponseStatus;
import com.yubico.client.v2.exceptions.YubicoVerificationException;
import com.yubico.client.v2.exceptions.YubicoValidationFailure;
import org.apache.commons.lang.StringUtils;

import java.security.Principal;
import java.util.*;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;

public class YubikeyModule extends AMLoginModule {
    public static final String MODULE_NAME = "YubikeyModule";
    public static final String BUNDLE_NAME = "amAuthYubikeyModule";
    private static final String AUTHLEVEL = "sunAMAuthYubikeyModuleAuthLevel";

    private static Debug debug = Debug.getInstance(MODULE_NAME);
    private Map options = null;
    private Map sharedState = null;
    private String UUID = null;
    private String userName = null;

    // Module setting parameters
    private static final String CLIENTID = ISAuthConstants.AUTH_ATTR_PREFIX_NEW + "YubikeyModuleClientID";
    private static final String SECRETKEY = ISAuthConstants.AUTH_ATTR_PREFIX_NEW + "YubikeyModuleSecretKey";
    private static final String YUBIKEY_VAL_SERVERS = ISAuthConstants.AUTH_ATTR_PREFIX_NEW
            + "YubikeyModuleWSApiUrls";
    private static final String YUBIKEY_ATTR = ISAuthConstants.AUTH_ATTR_PREFIX_NEW
            + "YubikeyModuleYubiKeyAttributeName";

    private int clientId = 0;
    private String secretKey = null;
    private String yubikeyAttrName = null;
    private String wsapiUrls[] = {};

    /**
     * Constructor
     */
    public YubikeyModule() {
        debug.message("In YubikeyModule.YubikeyModule()");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void init(Subject subject, Map sharedState, Map options) {
        debug.message("In YubikeyModule.init()");
        this.sharedState = sharedState;
        this.options = options;

        amCache.getResBundle(BUNDLE_NAME, getLoginLocale());
        String authLevel = CollectionHelper.getMapAttr(options, AUTHLEVEL);
        if (authLevel != null) {
            try {
                setAuthLevel(Integer.parseInt(authLevel));
            } catch (Exception e) {
                debug.error("Unable to set auth level " + authLevel, e);
            }
        }
        if (options != null) {
            try {
                clientId = Integer.parseInt(CollectionHelper.getMapAttr(options, CLIENTID));
            } catch (NumberFormatException e) {
                debug.error("Unable to set Yubikey Client ID " + clientId, e);
            }
            secretKey = CollectionHelper.getMapAttr(options, SECRETKEY);
            yubikeyAttrName = CollectionHelper.getMapAttr(options, YUBIKEY_ATTR);
            // Validation servers
            Set<String> wsapiUrlsSet = (Set<String>) options.get(YUBIKEY_VAL_SERVERS);
            if (wsapiUrlsSet != null) {
                wsapiUrls = wsapiUrlsSet.toArray(new String[wsapiUrlsSet.size()]);
            }
        }
        //get username from previous authentication
        try {
            userName = (String) sharedState.get(getUserKey());
        } catch (Exception e) {
            debug.error("YubiKey" + ".init() : " + "Unable to get username : ", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int process(Callback[] callbacks, int state) throws LoginException {
        debug.message("In YubikeyModule.process()");

        try {
            //check for session and get username and UUID
            if (userName == null || userName.length() == 0) {
                // session upgrade case. Need to find the user ID from the old
                // session
                SSOTokenManager mgr = SSOTokenManager.getInstance();
                InternalSession isess = getLoginState("Yubikey").getOldSession();
                if (isess == null) {
                    throw new AuthLoginException("amAuth", "noInternalSession", null);
                }
                SSOToken token = mgr.createSSOToken(isess.getID().toString());
                UUID = token.getPrincipal().getName();
                userName = token.getProperty("UserToken");
                if (debug.messageEnabled()) {
                    debug.message("OATH" + ".process() : " + "Username from SSOToken : " + userName);
                }

                if (userName == null || userName.length() == 0) {
                    throw new AuthLoginException("amAuth", "noUserName", null);
                }
            }
            switch (state) {
            case ISAuthConstants.LOGIN_START:
                if (callbacks == null || callbacks.length != 2) {
                    throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
                }
                //get OTP and check format
                String OTP = String.valueOf(((PasswordCallback) callbacks[0]).getPassword());
                if (!YubicoClient.isValidOTPFormat(OTP)) {
                    debug.error("Yubikey.process() : invalid OTP code");
                    setFailureID(userName);
                    throw new InvalidPasswordException(BUNDLE_NAME, "invalidOTP", null);
                }

                //Yubikey OTP validation
                if (checkOTP(OTP)) {
                    return ISAuthConstants.LOGIN_SUCCEED;
                } else {
                    setFailureID(userName);
                    throw new InvalidPasswordException("amAuth", "invalidPasswd", null);
                }
            }
        } catch (SSOException e) {
            debug.error("Yubikey" + ".process() : " + "SSOException", e);
            throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
        }
        return ISAuthConstants.LOGIN_IGNORE;
    }

    private boolean checkOTP(String otp) throws AuthLoginException {
        debug.message("In YubikeyModule.checkOTP()");

        AMIdentity id = getIdentity(userName);
        if (id == null) {
            throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
        }
        String yubiKeyId = getYubiKeyId(id);
        debug.message("In YubikeyModule.checkOTP() 1");

        if (StringUtils.isEmpty(yubiKeyId)) {
            debug.error("Yubikey.checkOTP() : yubikeyID of user : " + userName + " is not a valid value");
            throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
        }
        try {
            debug.message("In YubikeyModule.checkOTP() 2");

            if (StringUtils.isEmpty(secretKey)) {
                //      client.setKey(this.secretKey);
                debug.error("Yubikey.checkOTP() : yubii secretKey : " + secretKey + " is not a valid value");
                throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
            }
            debug.message("In YubikeyModule.checkOTP() 3");

            YubicoClient client = YubicoClient.getClient(clientId, secretKey);
            debug.message("In YubikeyModule.checkOTP() 4");

            //YubicoClient client = YubicoClient.getClient(this.clientId);
            //  if (StringUtils.isNotEmpty(secretKey)) {
            //      client.setKey(this.secretKey);
            //  }
            //  if (wsapiUrls != null && wsapiUrls.length > 0) {
            //      client.setWsapiUrls(this.wsapiUrls);
            //  }
            VerificationResponse yubicoResponse = client.verify(otp);

            debug.message("In YubikeyModule.checkOTP() 5");

            debug.message("In YubikeyModule response: " + yubicoResponse.getStatus());
            debug.message("In YubikeyModule response: " + yubicoResponse.getPublicId());
            debug.message("yubiKeyId: " + yubiKeyId);

            return yubicoResponse.getStatus().equals(ResponseStatus.OK)
                    && yubicoResponse.getPublicId().equals(yubiKeyId);
        } catch (YubicoVerificationException e) {
            throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
        } catch (YubicoValidationFailure yubicoValidationFailure) {
            throw new AuthLoginException(BUNDLE_NAME, "falidationFailed",
                    new String[] { yubicoValidationFailure.getMessage() });
        } catch (Exception e) {
            throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
        }
    }

    /**
     * get YubiKey identification from user profile
     *
     * @param id AMIdentity of user
     * @return yubikey identification for OTP validation
     * @throws AuthLoginException
     */
    private String getYubiKeyId(AMIdentity id) throws AuthLoginException {
        Set<String> yubiKeySet;
        try {
            if (StringUtils.isEmpty(yubikeyAttrName)) {
                debug.error("Yubikey" + ".checkOTP() : " + "invalid secret key attribute name : ");
                throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
            }
            yubiKeySet = id.getAttribute(yubikeyAttrName);
        } catch (IdRepoException e) {
            debug.error("Yubikey" + ".checkOTP() : " + "error getting secret key attribute : ", e);
            throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
        } catch (SSOException e) {
            debug.error("Yubikey" + ".checkOTP() : " + "error invalid repo id : " + id, e);
            throw new AuthLoginException(BUNDLE_NAME, "authFailed", null);
        }
        String yubiKey = yubiKeySet.iterator().next();
        //get rid of white space in string (messes with data converter)
        yubiKey = yubiKey.replaceAll("\\s+", "");
        yubiKey = yubiKey.toLowerCase();
        return yubiKey;
    }

    /**
     * Gets the AMIdentity of a user with username equal to uName.
     *
     * @param uName username of the user to get.
     * @return The AMIdentity of user with username equal to uName.
     */
    private AMIdentity getIdentity(String uName) {
        AMIdentity theID = null;
        AMIdentityRepository amIdRepo = getAMIdentityRepository(getRequestOrg());
        IdSearchControl idsc = new IdSearchControl();
        idsc.setAllReturnAttributes(true);
        // search for the identity
        Set<AMIdentity> results = Collections.EMPTY_SET;
        try {
            idsc.setMaxResults(0);
            IdSearchResults searchResults = amIdRepo.searchIdentities(IdType.USER, uName, idsc);
            if (searchResults != null) {
                results = searchResults.getSearchResults();
            }
            if (results == null || results.size() != 1) {
                throw new IdRepoException("Yubikey" + ".getIdentity : " + "More than one user found");

            }
            theID = results.iterator().next();
        } catch (IdRepoException e) {
            debug.error("Yubikey.getIdentity : error searching Identities with username : " + userName, e);
        } catch (SSOException e) {
            debug.error("Yubikey.getIdentity : AuthOATH module exception : ", e);
        }
        return theID;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Principal getPrincipal() {
        if (UUID != null) {
            return new YubikeyPrincipal(UUID);
        }
        if (userName != null) {
            return new YubikeyPrincipal(userName);
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void destroyModuleState() {
        userName = null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void nullifyUsedVars() {
        options = null;
        sharedState = null;
        userName = null;
        clientId = 0;
        secretKey = null;
        yubikeyAttrName = null;

    }

}