TubitakAuthenticationHelper.java

package io.mersel.dss.signer.api.services.timestamp.tubitak;

import io.mersel.dss.signer.api.util.CryptoUtils;
import org.bouncycastle.asn1.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.security.spec.KeySpec;

/**
 * TÜBİTAK ESYA Zaman Damgası servisi için kimlik doğrulama yardımcı sınıfı.
 * <p>
 * TÜBİTAK zaman damgası sunucusunun gerektirdiği özel kimlik doğrulama
 * mekanizmasını uygular. Müşteri kimlik bilgileri ve timestamp verisi
 * kullanılarak güvenli bir authentication token üretir.
 */
public class TubitakAuthenticationHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(TubitakAuthenticationHelper.class);

    private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
    private static final String AES_ALGORITHM = "AES";
    private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
    
    private static final int KEY_LENGTH = 256;
    private static final int DEFAULT_ITERATION_COUNT = 100;
    
    private static final int SALT_SIZE = 16;
    private static final int IV_SIZE = 16;

    /**
     * TÜBİTAK kimlik doğrulama token'ı oluşturur.
     *
     * @param customerId       Müşteri numarası
     * @param customerPassword Müşteri parolası
     * @param dataHash        Timestamp alınacak verinin hash değeri
     * @return Hex string formatında authentication token
     * @throws TubitakAuthenticationException Şifreleme hatası durumunda
     */
    public static String encryptIdentity(int customerId, String customerPassword, byte[] dataHash) {
        return encryptIdentity(customerId, customerPassword, dataHash, null, DEFAULT_ITERATION_COUNT);
    }

    /**
     * TÜBİTAK kimlik doğrulama token'ı oluşturur (gelişmiş parametreler ile).
     *
     * @param customerId       Müşteri numarası
     * @param customerPassword Müşteri parolası
     * @param dataHash        Timestamp alınacak verinin hash değeri
     * @param salt            Kriptografik salt (null ise otomatik üretilir)
     * @param iterationCount  Anahtar türetme iterasyon sayısı
     * @return Hex string formatında authentication token
     * @throws TubitakAuthenticationException Şifreleme hatası durumunda
     */
    public static String encryptIdentity(
            int customerId,
            String customerPassword,
            byte[] dataHash,
            byte[] salt,
            int iterationCount) {

        try {
            if (salt == null) {
                SecureRandom random = new SecureRandom();
                salt = new byte[SALT_SIZE];
                random.nextBytes(salt);
            }

            SecureRandom random = new SecureRandom();
            byte[] iv = new byte[IV_SIZE];
            random.nextBytes(iv);

            SecretKey key = deriveKey(customerPassword, salt, iterationCount);
            byte[] encryptedData = encrypt(dataHash, key, iv);
            byte[] authToken = buildAuthenticationToken(customerId, salt, iterationCount, iv, encryptedData);
            String hexString = CryptoUtils.bytesToHex(authToken);

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("TÜBİTAK authentication token created:");
                LOGGER.debug("  Customer ID: {}", customerId);
                LOGGER.debug("  Token length: {} bytes", authToken.length);
                LOGGER.debug("  Token (hex): {}...", hexString.substring(0, Math.min(80, hexString.length())));
            }

            return hexString;

        } catch (Exception e) {
            throw new TubitakAuthenticationException("Kimlik şifreleme başarısız", e);
        }
    }

    /**
     * PBKDF2 ile anahtar türetir.
     */
    private static SecretKey deriveKey(String password, byte[] salt, int iterationCount) throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
        KeySpec spec = new PBEKeySpec(
                password.toCharArray(),
                salt,
                iterationCount,
                KEY_LENGTH
        );
        SecretKey tmp = factory.generateSecret(spec);
        return new SecretKeySpec(tmp.getEncoded(), AES_ALGORITHM);
    }

    /**
     * AES-256-CBC ile veriyi şifreler.
     */
    private static byte[] encrypt(byte[] data, SecretKey key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        return cipher.doFinal(data);
    }

    /**
     * Authentication token'ı ASN.1 yapısında oluşturur ve DER encode eder.
     */
    private static byte[] buildAuthenticationToken(
            int customerId,
            byte[] salt,
            int iterationCount,
            byte[] iv,
            byte[] encryptedData) throws IOException {

        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(BigInteger.valueOf(customerId)));
        v.add(new DEROctetString(salt));
        v.add(new ASN1Integer(BigInteger.valueOf(iterationCount)));
        v.add(new DEROctetString(iv));
        v.add(new DEROctetString(encryptedData));

        DERSequence sequence = new DERSequence(v);

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        ASN1OutputStream aOut = ASN1OutputStream.create(bOut, ASN1Encoding.DER);
        aOut.writeObject(sequence);
        aOut.close();

        return bOut.toByteArray();
    }

    /**
     * TÜBİTAK kimlik doğrulama hatası.
     */
    public static class TubitakAuthenticationException extends RuntimeException {
        public TubitakAuthenticationException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}