/* MyVerifyDecryptCallback.java
 *
 * Copyright (C) 2006-2025 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

import java.io.*;
import java.net.*;
import java.nio.*;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.Cipher;
import java.nio.ByteBuffer;
import com.wolfssl.*;

/*
 * Example Verify/Decrypt callback implementation. For use when
 * HAVE_ENCRYPT_THEN_MAC is defined, which can be tested from Java using
 * WolfSSL.encryptThenMacEnabled().
 *
 * This example callback has been modeled directly after the native wolfSSL
 * example callback (myVerifyDecryptCb()) in wolfssl/test.h.
 *
 * NOTE: if native HAVE_ENCRYPT_THEN_MAC is not defined, the DecryptVerify
 * callback needs to be set and used.
 */
class MyVerifyDecryptCallback implements WolfSSLVerifyDecryptCallback
{
    public int verifyDecryptCallback(WolfSSLSession ssl, ByteBuffer decOut,
            byte[] decIn, long decSz, int macContent, int macVerify,
            long[] padSz, Object ctx) {

        int hmacType = ssl.getHmacType();
        int digestSz = ssl.getHmacSize();
        byte[] myInner = new byte[WolfSSL.WOLFSSL_TLS_HMAC_INNER_SZ];
        byte[] verify = null;
        byte[] keyBytes = null;
        byte[] ivBytes  = null;
        String hmacString;
        String tlsStr   = "TLS";

        Cipher cipher = null;
        MyAtomicDecCtx decCtx = (MyAtomicDecCtx) ctx;

        /* example supports (d)tls AES */
        if (ssl.getBulkCipher() != WolfSSL.wolfssl_aes) {
            System.out.println("MyVerifyDecryptCallback not using AES");
            return -1;
        }

        try {
            if (!ssl.getVersion().contains(tlsStr)) {
                System.out.println("MyVerifyDecryptCallback not using (D)TLS");
                return -1;
            }

            ssl.setTlsHmacInner(myInner, decSz, macContent, macVerify);

            if (hmacType == WolfSSL.SHA) {
                hmacString = "HmacSHA1";
            } else if (hmacType == WolfSSL.SHA256) {
                hmacString = "HmacSHA256";
            } else if (hmacType == WolfSSL.SHA384) {
                hmacString = "HmacSHA384";
            } else if (hmacType == WolfSSL.SHA512) {
                hmacString = "HmacSHA512";
            } else {
                System.out.println("Unsupported HMAC hash type in " +
                        "MyVerifyDecryptCallback: " + hmacType);
                return -1;
            }

            /* construct HMAC key */
            SecretKeySpec hmacKey = new SecretKeySpec(
                ssl.getMacSecret(macVerify), hmacString);

            /* get Mac instance, initialize with key, compute */
            Mac mac = Mac.getInstance(hmacString);
            mac.init(hmacKey);
            mac.update(myInner, 0, myInner.length);
            mac.update(decIn, 0, (int)decSz);
            verify = mac.doFinal();

            /* Get MAC (digestSz bytes) off end of decOut for comparison */
            byte[] verifyMac = new byte[digestSz];
            int tmpPos = decOut.position();
            decOut.position(decOut.limit() - digestSz);
            decOut.get(verifyMac);
            decOut.position(tmpPos);

            if (verifyMac.length != verify.length) {
                System.out.println("MyVerifyDecryptCallback verifyMac length " +
                        "different than calculated MAC length");
                return -1;
            }

            if (!Arrays.equals(verify, verifyMac)) {
                System.out.println("MyVerifyDecryptCallback MAC " +
                    "comparison failed");
                return -1;
            }

            /* Setup AES for decrypt */
            if(!decCtx.isCipherSetup()) {
                int keyLen = ssl.getKeySize();
                SecretKeySpec key = null;
                cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");

                /* Decrypt is from other side (peer) */
                if (ssl.getSide() == WolfSSL.WOLFSSL_SERVER_END) {
                    keyBytes = ssl.getClientWriteKey();
                    ivBytes  = ssl.getClientWriteIV();
                } else {
                    keyBytes = ssl.getServerWriteKey();
                    ivBytes  = ssl.getServerWriteIV();
                }

                key = new SecretKeySpec(keyBytes, "AES");
                cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
                decCtx.setCipher(cipher);
                decCtx.isCipherSetup(true);
            } else {
                cipher = decCtx.getCipher();

                if (cipher == null) {
                    System.out.println("Cipher was not previously set up");
                    return -1;
                }
            }

            /* Decrypt */
            decOut.position(0);
            decOut.put(cipher.doFinal(decIn, 0, (int)decSz));
            decOut.flip();

            byte padVal = decOut.get((int)decSz - 1);
            padSz[0] = (long)padVal + 1;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return 0;
    }
}

