#!/usr/bin/perl

# Regression test for CVE-2026-8507:
# Integer overflow in print_attribute (V_ASN1_OCTET_STRING / V_ASN1_BIT_STRING)
# leading to heap out-of-bounds write.
#
# The fix bounds-checks ASN1_STRING.length before the *4 multiplication and
# promotes to size_t. This test exercises the fixed code path by calling
# info() and info_as_hash() on a PKCS12 that carries OCTET STRING bag
# attributes (localKeyID), confirming normal processing still works and that
# the returned hex strings are intact.
#
# Note: directly triggering the croak for lengths > INT_MAX/4 would require a
# specially crafted binary PKCS12 fixture with a ~512 MiB attribute field and
# is not included here.

use strict;
use warnings;

use Test::More tests => 8;
use File::Spec::Functions qw(:ALL);
use Crypt::OpenSSL::Guess qw(openssl_version);

BEGIN { use_ok('Crypt::OpenSSL::PKCS12') }

my ($major) = openssl_version();

# test.pfx contains localKeyID (OCTET STRING) bag attributes — the exact
# type whose length*4 multiplication overflowed before the fix.
my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file(catdir('certs', 'test.pfx'));
ok($pkcs12, 'PKCS12 object loaded from test.pfx');

my $pass = '1234';

# info() exercises the BIO/string output path of print_attribute
my $info = eval { $pkcs12->info($pass) };
is($@, '', 'info() does not croak on OCTET STRING bag attributes');
ok(defined $info && length $info, 'info() returns non-empty string');

# localKeyID is rendered as hex by get_hex() via the OCTET STRING branch —
# confirm the output is present and well-formed.
like($info, qr/localKeyID:?\s+[0-9A-F]{2}/i,
    'info() output contains hex-encoded localKeyID attribute');

# info_as_hash() exercises the hash-population path of the same code
my $hash = eval { $pkcs12->info_as_hash($pass) };
is($@, '', 'info_as_hash() does not croak on OCTET STRING bag attributes');
ok(defined $hash && ref $hash eq 'HASH', 'info_as_hash() returns a hash reference');

SKIP: {
    skip('pkcs7_data not present or empty', 1)
        unless ref $hash->{pkcs7_data} eq 'ARRAY' && @{$hash->{pkcs7_data}};

    my $found_localKeyID = 0;
    for my $section (@{$hash->{pkcs7_data}}) {
        for my $bag (@{$section->{bags} // []}) {
            if (exists $bag->{bag_attributes}{localKeyID}) {
                $found_localKeyID = 1;
                last;
            }
        }
        last if $found_localKeyID;
    }
    ok($found_localKeyID, 'info_as_hash() correctly decoded localKeyID OCTET STRING attribute');
}
