fi.vm.kapa.identification.service.PhaseIdService.java Source code

Java tutorial

Introduction

Here is the source code for fi.vm.kapa.identification.service.PhaseIdService.java

Source

/**
 * The MIT License
 * Copyright (c) 2015 Population Register Centre
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package fi.vm.kapa.identification.service;

import org.springframework.security.crypto.codec.Hex;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.security.SecureRandom;

/**
 * This class is intended as a general purpose tool for calculating
 * checksum based on HMAC algorithm. This is mainly used to verify
 * the validity of incoming requests but it can be used to all other
 * purposes where time limited checksum is needed.
 */
public class PhaseIdService {

    private final static String ALPHANUMBERIC = "^[a-z0-9]*$";
    private final static int TID_LENGTH = 26;
    private final static int PID_LENGTH = 64;

    private int timeInterval;
    private Mac hmacCalc;

    public PhaseIdService(String sharedSecret, int timeInterval, String hmacAlgorithm) throws Exception {
        this.timeInterval = timeInterval;
        hmacCalc = Mac.getInstance(hmacAlgorithm);
        SecretKeySpec keySpec = new SecretKeySpec(sharedSecret.getBytes(), hmacAlgorithm);
        hmacCalc.init(keySpec);
    }

    /**
     * Creates new standard length token ID that is used to calculate
     * the phase ID.
     *
     * @return New token ID as alphanumeric string
     */
    public String nextTokenId() {
        String token = new BigInteger(130, new SecureRandom()).toString(32);
        while (token.length() != TID_LENGTH) {
            token = new BigInteger(130, new SecureRandom()).toString(32);
        }
        return token;
    }

    /**
     * Validates token ID and phase ID that they must be alphanumeric hash strings
     * with correct string lengths and that they must not be in the used IDs list
     *
     * @param tid token ID to be verified
     * @param pid phase ID to be verified
     * @return Boolean value, true if both values are OK
     */
    public boolean validateTidAndPid(String tid, String pid) {
        return (tid.matches(ALPHANUMBERIC) && pid.matches(ALPHANUMBERIC) && tid.length() == TID_LENGTH
                && pid.length() == PID_LENGTH);
    }

    /**
     * Generates new phase ID from the given step counter and token ID.
     *
     * @param tokenId Simple string token which can be generated by random
     * @param stepCounter The step counter value to which the phase ID must match
     * @return New phase ID value as alphanumeric string
     * @throws Exception
     */
    public String newPhaseId(String tokenId, String stepCounter) throws Exception {
        long sysTime = System.currentTimeMillis();
        int time = (int) (sysTime / (timeInterval * 1000));

        return generateHmacString(tokenId, stepCounter, time);
    }

    /**
     * Verifies the given phase ID against the given token ID and step counter values.
     * Tries to match the current time frame and current +-1 time frame to the given phase ID
     * as the time slot might have just changed when it was generated.
     *
     * @param phaseId The value which should be generated from the other parameters
     * @param tokenId The token ID received in the request
     * @param stepCounter Step counter of the current step, this must be defined in the calling service
     * @return Boolean value if the verification succeeded or not
     * @throws Exception
     */
    synchronized public boolean verifyPhaseId(String phaseId, String tokenId, String stepCounter) throws Exception {
        long sysTime = System.currentTimeMillis();
        int time = (int) (sysTime / (timeInterval * 1000));
        /* This allows 3 x time interval for counting the HMAC checksum and if anyone of them succeeds,
         * then the next phase ID is generated from current time, this has to be quite strict to prevent
         * possible attacks! Note that this step is dependant on the phase number so each server has to
         * know its own phases!
         */
        return (generateHmacString(tokenId, stepCounter, time).equals(phaseId)
                || generateHmacString(tokenId, stepCounter, (time - 1)).equals(phaseId)
                || generateHmacString(tokenId, stepCounter, (time + 1)).equals(phaseId));
    }

    private String generateHmacString(String tokenId, String stepCounter, int time) {
        return String.valueOf(Hex
                .encode(hmacCalc.doFinal(("tid=" + tokenId + ";pid=" + stepCounter + ";time=" + time).getBytes())));
    }
}