<?php

/**
 * @author    Andreas Fischer <bantu@phpbb.com>
 * @copyright 2013 Andreas Fischer
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 */

declare(strict_types=1);

namespace phpseclib3\Tests\Unit\Crypt;

use phpseclib3\Crypt\AES;
use phpseclib3\Tests\PhpseclibTestCase;

class GCMTest extends PhpseclibTestCase
{
    /**
     * Produces all combinations of test values.
     */
    public function engine128Vectors(): array
    {
        $engines = [
            'PHP',
            'Eval',
            'OpenSSL',
            'OpenSSL (GCM)',
        ];

        // test vectors come from the following URL:
        // http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf

        $p1 = '00000000000000000000000000000000';
        $p2 = 'd9313225f88406e5a55909c5aff5269a' .
              '86a7a9531534f7da2e4c303d8a318a72' .
              '1c3c0c95956809532fcf0e2449a6b525' .
              'b16aedf5aa0de657ba637b391aafd255';
        $p3 = 'd9313225f88406e5a55909c5aff5269a' .
              '86a7a9531534f7da2e4c303d8a318a72' .
              '1c3c0c95956809532fcf0e2449a6b525' .
              'b16aedf5aa0de657ba637b39';

        $n1 = '000000000000000000000000';
        $n2 = 'cafebabefacedbaddecaf888';
        $n3 = 'cafebabefacedbad';
        $n4 = '9313225df88406e555909c5aff5269aa' .
              '6a7a9538534f7da1e4c303d2a318a728' .
              'c3c0c95156809539fcf0e2429a6b5254' .
              '16aedbf5a0de6a57a637b39b';

        $k1 = '00000000000000000000000000000000';
        $k2 = 'feffe9928665731c6d6a8f9467308308';

        $c1 = '0388dace60b6a392f328c2b971b2fe78';
        $c2 = '42831ec2217774244b7221b784d0d49c' .
              'e3aa212f2c02a4e035c17e2329aca12e' .
              '21d514b25466931c7d8f6a5aac84aa05' .
              '1ba30b396a0aac973d58e091473f5985';
        $c3 = '42831ec2217774244b7221b784d0d49c' .
              'e3aa212f2c02a4e035c17e2329aca12e' .
              '21d514b25466931c7d8f6a5aac84aa05' .
              '1ba30b396a0aac973d58e091';
        $c4 = '61353b4c2806934a777ff51fa22a4755' .
              '699b2a714fcdc6f83766e5f97b6c7423' .
              '73806900e49f24b22b097544d4896b42' .
              '4989b5e1ebac0f07c23f4598';
        $c5 = '8ce24998625615b603a033aca13fb894' .
              'be9112a5c3a211a8ba262a3cca7e2ca7' .
              '01e4a9a4fba43c90ccdcb281d48c7c6f' .
              'd62875d2aca417034c34aee5';

        $a1 = 'feedfacedeadbeeffeedfacedeadbeef' .
              'abaddad2';

        $subvectors = [
            // key, plaintext, nonce, aad, ciphertext, tag
            // Test Case 1
            [$k1, '', $n1, '', '', '58e2fccefa7e3061367f1d57a4e7455a'],
            // Test Case 2
            [$k1, $p1, $n1, '', $c1, 'ab6e47d42cec13bdf53a67b21257bddf'],
            // Test Case 3
            [$k2, $p2, $n2, '', $c2, '4d5c2af327cd64a62cf35abd2ba6fab4'],
            // Test Case 4
            [$k2, $p3, $n2, $a1, $c3, '5bc94fbc3221a5db94fae95ae7121a47'],
            // Test Case 5
            [$k2, $p3, $n3, $a1, $c4, '3612d2e79e3b0785561be14aaca2fccb'],
            // Test Case 6
            [$k2, $p3, $n4, $a1, $c5, '619cc5aefffe0bfa462af43c1699d050'],
        ];

        $vectors = [];
        for ($i = 0; $i < count($subvectors); $i++) {
            for ($j = 0; $j < count($subvectors[$i]); $j++) {
                $subvectors[$i][$j] = pack('H*', $subvectors[$i][$j]);
            }
            foreach ($engines as $engine) {
                $temp = $subvectors[$i];
                array_unshift($temp, $engine);
                $vectors[] = $temp;
            }
        }

        return $vectors;
    }

    /**
     * @dataProvider engine128Vectors
     */
    public function test128Vectors($engine, $key, $plaintext, $nonce, $aad, $ciphertext, $tag): void
    {
        $aes = new AES('gcm');
        $aes->setKey($key);
        $aes->setNonce($nonce);
        $aes->setAAD($aad);

        if (!$aes->isValidEngine($engine)) {
            self::markTestSkipped("Unable to initialize $engine engine");
        }
        $aes->setPreferredEngine($engine);

        $ciphertext2 = $aes->encrypt($plaintext);
        $this->assertEquals($ciphertext, $ciphertext2);
        $this->assertEquals($tag, $aes->getTag());
        $aes->setTag($tag);
        $this->assertEquals($plaintext, $aes->decrypt($ciphertext));
    }

    /**
     * Produces all combinations of test values.
     */
    public function engine256Vectors(): array
    {
        $engines = [
            'PHP',
            'Eval',
            'OpenSSL',
            'OpenSSL (GCM)',
            'libsodium',
        ];

        $p1 = '00000000000000000000000000000000';
        $p2 = 'd9313225f88406e5a55909c5aff5269a' .
              '86a7a9531534f7da2e4c303d8a318a72' .
              '1c3c0c95956809532fcf0e2449a6b525' .
              'b16aedf5aa0de657ba637b391aafd255';
        $p3 = 'd9313225f88406e5a55909c5aff5269a' .
              '86a7a9531534f7da2e4c303d8a318a72' .
              '1c3c0c95956809532fcf0e2449a6b525' .
              'b16aedf5aa0de657ba637b39';

        $n1 = '000000000000000000000000';
        $n2 = 'cafebabefacedbaddecaf888';
        $n3 = 'cafebabefacedbad';
        $n4 = '9313225df88406e555909c5aff5269aa' .
              '6a7a9538534f7da1e4c303d2a318a728' .
              'c3c0c95156809539fcf0e2429a6b5254' .
              '16aedbf5a0de6a57a637b39b';

        $k1 = '00000000000000000000000000000000' .
              '00000000000000000000000000000000';
        $k2 = 'feffe9928665731c6d6a8f9467308308' .
              'feffe9928665731c6d6a8f9467308308';

        $c1 = 'cea7403d4d606b6e074ec5d3baf39d18';
        $c2 = '522dc1f099567d07f47f37a32a84427d' .
              '643a8cdcbfe5c0c97598a2bd2555d1aa' .
              '8cb08e48590dbb3da7b08b1056828838' .
              'c5f61e6393ba7a0abcc9f662898015ad';
        $c3 = '522dc1f099567d07f47f37a32a84427d' .
              '643a8cdcbfe5c0c97598a2bd2555d1aa' .
              '8cb08e48590dbb3da7b08b1056828838' .
              'c5f61e6393ba7a0abcc9f662';
        $c4 = 'c3762df1ca787d32ae47c13bf19844cb' .
              'af1ae14d0b976afac52ff7d79bba9de0' .
              'feb582d33934a4f0954cc2363bc73f78' .
              '62ac430e64abe499f47c9b1f';
        $c5 = '5a8def2f0c9e53f1f75d7853659e2a20' .
              'eeb2b22aafde6419a058ab4f6f746bf4' .
              '0fc0c3b780f244452da3ebf1c5d82cde' .
              'a2418997200ef82e44ae7e3f';

        $a1 = 'feedfacedeadbeeffeedfacedeadbeef' .
              'abaddad2';

        $subvectors = [
            // key, plaintext, nonce, aad, ciphertext, tag
            // Test Case 13
            [$k1, '', $n1, '', '', '530f8afbc74536b9a963b4f1c4cb738b'],
            // Test Case 14
            [$k1, $p1, $n1, '', $c1, 'd0d1c8a799996bf0265b98b5d48ab919'],
            // Test Case 15
            [$k2, $p2, $n2, '', $c2, 'b094dac5d93471bdec1a502270e3cc6c'],
            // Test Case 16
            [$k2, $p3, $n2, $a1, $c3, '76fc6ece0f4e1768cddf8853bb2d551b'],
            // Test Case 17
            [$k2, $p3, $n3, $a1, $c4, '3a337dbf46a792c45e454913fe2ea8f2'],
            // Test Case 18
            [$k2, $p3, $n4, $a1, $c5, 'a44a8266ee1c8eb0c8b5d4cf5ae9f19a'],
        ];

        $vectors = [];
        for ($i = 0; $i < count($subvectors); $i++) {
            for ($j = 0; $j < count($subvectors[$i]); $j++) {
                $subvectors[$i][$j] = pack('H*', $subvectors[$i][$j]);
            }
            foreach ($engines as $engine) {
                $temp = $subvectors[$i];
                array_unshift($temp, $engine);
                $vectors[] = $temp;
            }
        }

        return $vectors;
    }

    /**
     * @dataProvider engine256Vectors
     */
    public function test256Vectors($engine, $key, $plaintext, $nonce, $aad, $ciphertext, $tag): void
    {
        $aes = new AES('gcm');
        $aes->setKey($key);
        $aes->setNonce($nonce);
        $aes->setAAD($aad);

        if (!$aes->isValidEngine($engine)) {
            self::markTestSkipped("Unable to initialize $engine engine");
        }
        $aes->setPreferredEngine($engine);

        $ciphertext2 = $aes->encrypt($plaintext);
        $this->assertEquals($ciphertext, $ciphertext2);
        $this->assertEquals($tag, $aes->getTag());
        $aes->setTag($tag);
        $this->assertEquals($plaintext, $aes->decrypt($ciphertext));
    }
}