org.wso2.carbon.identity.application.authenticator.totp.TOTPTokenGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.identity.application.authenticator.totp.TOTPTokenGenerator.java

Source

/*
 * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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.
 */

package org.wso2.carbon.identity.application.authenticator.totp;

import org.apache.axis2.context.ConfigurationContext;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.core.util.CryptoException;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.identity.application.authenticator.totp.exception.TOTPException;
import org.wso2.carbon.identity.application.authenticator.totp.internal.TOTPDataHolder;
import org.wso2.carbon.identity.application.authenticator.totp.util.TOTPUtil;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.mgt.IdentityMgtConfigException;
import org.wso2.carbon.identity.mgt.IdentityMgtServiceException;
import org.wso2.carbon.identity.mgt.NotificationSender;
import org.wso2.carbon.identity.mgt.NotificationSendingModule;
import org.wso2.carbon.identity.mgt.config.Config;
import org.wso2.carbon.identity.mgt.config.ConfigBuilder;
import org.wso2.carbon.identity.mgt.config.ConfigType;
import org.wso2.carbon.identity.mgt.config.StorageType;
import org.wso2.carbon.identity.mgt.dto.NotificationDataDTO;
import org.wso2.carbon.identity.mgt.mail.DefaultEmailSendingModule;
import org.wso2.carbon.identity.mgt.mail.Notification;
import org.wso2.carbon.identity.mgt.mail.NotificationBuilder;
import org.wso2.carbon.identity.mgt.mail.NotificationData;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

/**
 * TOTP Token generator class.
 *
 * @since 2.0.3
 */
public class TOTPTokenGenerator {

    private static final String FIRST_NAME = "firstname";
    private static final String TOTP_TOKEN = "totp-token";
    private static Log log = LogFactory.getLog(TOTPTokenGenerator.class);

    /**
     * Get Time steps from unix epoch time.
     *
     * @return Time index
     */
    private static long getTimeIndex(AuthenticationContext context) throws TOTPException {
        return System.currentTimeMillis() / 1000 / TOTPUtil.getTimeStepSize(context);
    }

    /**
     * Generate TOTP token for a locally stored user.
     *
     * @param username Username of the user
     * @param context  Authentication context
     * @return TOTP token as a String
     * @throws TOTPException When could not find user realm for the given tenant domain, invalid
     * secret key, decrypting invalid key and could not find the configured hashing algorithm
     */
    public static String generateTOTPTokenLocal(String username, AuthenticationContext context)
            throws TOTPException {
        long token = 0;
        String tenantAwareUsername = null;
        if (username != null) {
            try {
                String tenantDomain = MultitenantUtils.getTenantDomain(username);
                UserRealm userRealm = TOTPUtil.getUserRealm(username);
                tenantAwareUsername = MultitenantUtils.getTenantAwareUsername(username);
                if (userRealm != null) {
                    Map<String, String> userClaimValues = userRealm.getUserStoreManager().getUserClaimValues(
                            tenantAwareUsername, new String[] { TOTPAuthenticatorConstants.SECRET_KEY_CLAIM_URL },
                            null);
                    String secretKey = TOTPUtil
                            .decrypt(userClaimValues.get(TOTPAuthenticatorConstants.SECRET_KEY_CLAIM_URL));
                    String firstName = userRealm.getUserStoreManager().getUserClaimValue(tenantAwareUsername,
                            TOTPAuthenticatorConstants.FIRST_NAME_CLAIM_URL, null);
                    String email = userRealm.getUserStoreManager().getUserClaimValue(tenantAwareUsername,
                            TOTPAuthenticatorConstants.EMAIL_CLAIM_URL, null);
                    byte[] secretKeyByteArray;
                    String encoding = TOTPUtil.getEncodingMethod(tenantDomain, context);
                    if (TOTPAuthenticatorConstants.BASE32.equals(encoding)) {
                        Base32 codec32 = new Base32();
                        secretKeyByteArray = codec32.decode(secretKey);
                    } else {
                        Base64 codec64 = new Base64();
                        secretKeyByteArray = codec64.decode(secretKey);
                    }
                    token = getCode(secretKeyByteArray, getTimeIndex(context));
                    sendNotification(tenantAwareUsername, firstName, Long.toString(token), email);
                    if (log.isDebugEnabled()) {
                        log.debug("Token is sent to via email to the user : " + tenantAwareUsername);
                    }
                } else {
                    throw new TOTPException("Cannot find the user realm for the given tenant domain : "
                            + CarbonContext.getThreadLocalCarbonContext().getTenantDomain());
                }
            } catch (UserStoreException e) {
                throw new TOTPException("TOTPTokenGenerator failed while trying to access userRealm of the user : "
                        + tenantAwareUsername, e);
            } catch (NoSuchAlgorithmException e) {
                throw new TOTPException("TOTPTokenGenerator can't find the configured hashing algorithm", e);
            } catch (InvalidKeyException e) {
                throw new TOTPException("Secret key is not valid", e);
            } catch (CryptoException e) {
                throw new TOTPException("Error while decrypting the key", e);
            } catch (AuthenticationFailedException e) {
                throw new TOTPException("TOTPTokenVerifier cannot find the property value for encodingMethod");
            }
        }
        return Long.toString(token);
    }

    /**
     * Create the TOTP token for a given secret key and time index.
     *
     * @param secret    Secret key in binary format
     * @param timeIndex Number of Time elapse from the unix epoch time
     * @return TOTP token value as a long
     * @throws NoSuchAlgorithmException Could not find the specific algorithm
     * @throws InvalidKeyException      invalid signKey
     */
    private static long getCode(byte[] secret, long timeIndex)
            throws NoSuchAlgorithmException, InvalidKeyException {
        // Building the secret key specification for the HmacSHA1 algorithm.
        SecretKeySpec signKey = new SecretKeySpec(secret, TOTPAuthenticatorConstants.HMAC_ALGORITHM);
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.putLong(timeIndex);
        byte[] timeBytes = buffer.array();
        // Getting an HmacSHA1 algorithm implementation from the Java Cryptography Extension (JCE).
        Mac mac = Mac.getInstance(TOTPAuthenticatorConstants.HMAC_ALGORITHM);
        // Initializing the MAC algorithm.
        mac.init(signKey);
        // Processing the instant of time and getting the encrypted data.
        byte[] hash = mac.doFinal(timeBytes);
        // Building the validation code performing dynamic truncation.
        int offset = hash[19] & 0xf;
        long truncatedHash = hash[offset] & 0x7f;
        for (int i = 1; i < 4; i++) {
            truncatedHash <<= 8;
            truncatedHash |= hash[offset + i] & 0xff;
        }
        truncatedHash %= 1000000;
        return truncatedHash;
    }

    /**
     * Send the TOTP token via MAILTO transport.
     *
     * @param tenantAwareUsername Tenant aware username of user
     * @param firstName           First name of user
     * @param token               Token which needs to be sent to user
     * @param email               Email address of the user
     * @throws TOTPException MAILTO transport sender is not defined
     */
    private static void sendNotification(String tenantAwareUsername, String firstName, String token, String email)
            throws TOTPException {
        ConfigurationContext configurationContext = TOTPDataHolder.getInstance().getConfigurationContextService()
                .getServerConfigContext();
        if (configurationContext.getAxisConfiguration().getTransportsOut()
                .containsKey(TOTPAuthenticatorConstants.TRANSPORT_MAILTO)) {
            NotificationSender notificationSender = new NotificationSender();
            NotificationDataDTO notificationData = new NotificationDataDTO();
            Notification emailNotification;
            NotificationData emailNotificationData = new NotificationData();
            ConfigBuilder configBuilder = ConfigBuilder.getInstance();
            String tenantDomain = MultitenantUtils.getTenantDomain(tenantAwareUsername);
            NotificationSendingModule module = new DefaultEmailSendingModule();
            int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
            String emailTemplate = null;
            Config config;
            try {
                config = configBuilder.loadConfiguration(ConfigType.EMAIL, StorageType.REGISTRY, tenantId);
            } catch (IdentityMgtConfigException e) {
                throw new TOTPException(
                        "Error occurred while loading email templates for user : " + tenantAwareUsername, e);
            }
            emailNotificationData.setTagData(FIRST_NAME, firstName);
            emailNotificationData.setTagData(TOTP_TOKEN, token);
            emailNotificationData.setSendTo(email);
            if (config.getProperties().containsKey(TOTPAuthenticatorConstants.EMAIL_TEMPLATE_NAME)) {
                emailTemplate = config.getProperty(TOTPAuthenticatorConstants.EMAIL_TEMPLATE_NAME);
                try {
                    emailNotification = NotificationBuilder.createNotification("EMAIL", emailTemplate,
                            emailNotificationData);
                } catch (IdentityMgtServiceException e) {
                    log.error("Error occurred while creating notification from email template : " + emailTemplate,
                            e);
                    throw new TOTPException(
                            "Error occurred while creating notification from email template : " + emailTemplate, e);
                }
                notificationData.setNotificationAddress(email);
                module.setNotificationData(notificationData);
                module.setNotification(emailNotification);
                notificationSender.sendNotification(module);
                notificationData.setNotificationSent(true);
            } else {
                throw new TOTPException("Unable to find the email template: " + emailTemplate);
            }
        } else {
            throw new TOTPException("MAILTO transport sender is not defined in axis2 configuration file");
        }
    }
}