xref: /plugin/pureldap/vendor/freedsx/asn1/src/FreeDSx/Asn1/Encoder/CerDerTrait.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
10b3fd2d3SAndreas Gohr<?php
20b3fd2d3SAndreas Gohr/**
30b3fd2d3SAndreas Gohr * This file is part of the FreeDSx ASN1 package.
40b3fd2d3SAndreas Gohr *
50b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
60b3fd2d3SAndreas Gohr *
70b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE
80b3fd2d3SAndreas Gohr * file that was distributed with this source code.
90b3fd2d3SAndreas Gohr */
100b3fd2d3SAndreas Gohr
110b3fd2d3SAndreas Gohrnamespace FreeDSx\Asn1\Encoder;
120b3fd2d3SAndreas Gohr
130b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Exception\EncoderException;
140b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\AbstractTimeType;
150b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\AbstractType;
160b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\SetOfType;
17*dad993c5SAndreas Gohruse function count;
18*dad993c5SAndreas Gohruse function end;
19*dad993c5SAndreas Gohruse function ord;
20*dad993c5SAndreas Gohruse function reset;
21*dad993c5SAndreas Gohruse function str_pad;
22*dad993c5SAndreas Gohruse function strcmp;
23*dad993c5SAndreas Gohruse function strlen;
24*dad993c5SAndreas Gohruse function usort;
250b3fd2d3SAndreas Gohr
260b3fd2d3SAndreas Gohr/**
270b3fd2d3SAndreas Gohr * Common restrictions on CER and DER encoding.
280b3fd2d3SAndreas Gohr *
290b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com>
300b3fd2d3SAndreas Gohr */
310b3fd2d3SAndreas Gohrtrait CerDerTrait
320b3fd2d3SAndreas Gohr{
330b3fd2d3SAndreas Gohr    /**
340b3fd2d3SAndreas Gohr     * @param int $length
350b3fd2d3SAndreas Gohr     * @param int $unused
360b3fd2d3SAndreas Gohr     * @return string
370b3fd2d3SAndreas Gohr     * @throws EncoderException
380b3fd2d3SAndreas Gohr     */
390b3fd2d3SAndreas Gohr    protected function binaryToBitString(int $length, int $unused): string
400b3fd2d3SAndreas Gohr    {
41*dad993c5SAndreas Gohr        if ($unused && $length && ord($this->binary[$this->pos + ($length - 1)]) !== 0 && ((8 - $length) << ord($this->binary[$this->pos + ($length - 1)])) !== 0) {
420b3fd2d3SAndreas Gohr            throw new EncoderException(sprintf(
430b3fd2d3SAndreas Gohr                'The last %s unused bits of the bit string must be 0, but they are not.',
440b3fd2d3SAndreas Gohr                $unused
450b3fd2d3SAndreas Gohr            ));
460b3fd2d3SAndreas Gohr        }
470b3fd2d3SAndreas Gohr
480b3fd2d3SAndreas Gohr        return parent::binaryToBitString($length, $unused);
490b3fd2d3SAndreas Gohr    }
500b3fd2d3SAndreas Gohr
510b3fd2d3SAndreas Gohr    /**
520b3fd2d3SAndreas Gohr     * @return bool
530b3fd2d3SAndreas Gohr     * @throws EncoderException
540b3fd2d3SAndreas Gohr     */
550b3fd2d3SAndreas Gohr    protected function decodeBoolean(): bool
560b3fd2d3SAndreas Gohr    {
570b3fd2d3SAndreas Gohr        if (!($this->binary[$this->pos] === self::BOOL_FALSE || $this->binary[$this->pos] === self::BOOL_TRUE)) {
580b3fd2d3SAndreas Gohr            throw new EncoderException(sprintf('The encoded boolean must be 0 or 255, received "%s".', ord($this->binary[$this->pos])));
590b3fd2d3SAndreas Gohr        }
600b3fd2d3SAndreas Gohr
610b3fd2d3SAndreas Gohr        return parent::decodeBoolean();
620b3fd2d3SAndreas Gohr    }
630b3fd2d3SAndreas Gohr
640b3fd2d3SAndreas Gohr    /**
650b3fd2d3SAndreas Gohr     * {@inheritdoc}
660b3fd2d3SAndreas Gohr     * @throws EncoderException
670b3fd2d3SAndreas Gohr     */
680b3fd2d3SAndreas Gohr    protected function encodeTime(AbstractTimeType $type, string $format)
690b3fd2d3SAndreas Gohr    {
700b3fd2d3SAndreas Gohr        $this->validateTimeType($type);
710b3fd2d3SAndreas Gohr
720b3fd2d3SAndreas Gohr        return parent::encodeTime($type, $format);
730b3fd2d3SAndreas Gohr    }
740b3fd2d3SAndreas Gohr
750b3fd2d3SAndreas Gohr    /**
760b3fd2d3SAndreas Gohr     * {@inheritdoc}
770b3fd2d3SAndreas Gohr     * @throws EncoderException
780b3fd2d3SAndreas Gohr     */
790b3fd2d3SAndreas Gohr    protected function validateDateFormat(array $matches, array $matchMap)
800b3fd2d3SAndreas Gohr    {
810b3fd2d3SAndreas Gohr        if (isset($matchMap['fractions']) && isset($matches[$matchMap['fractions']]) && $matches[$matchMap['fractions']] !== '') {
820b3fd2d3SAndreas Gohr            if ($matches[$matchMap['fractions']][-1] === '0') {
830b3fd2d3SAndreas Gohr                throw new EncoderException('Trailing zeros must be omitted from Generalized Time types, but it is not.');
840b3fd2d3SAndreas Gohr            }
850b3fd2d3SAndreas Gohr        }
860b3fd2d3SAndreas Gohr    }
870b3fd2d3SAndreas Gohr
880b3fd2d3SAndreas Gohr    /**
890b3fd2d3SAndreas Gohr     * @param AbstractTimeType $type
900b3fd2d3SAndreas Gohr     * @throws EncoderException
910b3fd2d3SAndreas Gohr     */
920b3fd2d3SAndreas Gohr    protected function validateTimeType(AbstractTimeType $type)
930b3fd2d3SAndreas Gohr    {
940b3fd2d3SAndreas Gohr        if ($type->getTimeZoneFormat() !== AbstractTimeType::TZ_UTC) {
950b3fd2d3SAndreas Gohr            throw new EncoderException(sprintf(
960b3fd2d3SAndreas Gohr                'Time must end in a Z, but it does not. It is set to "%s".',
970b3fd2d3SAndreas Gohr                $type->getTimeZoneFormat()
980b3fd2d3SAndreas Gohr            ));
990b3fd2d3SAndreas Gohr        }
1000b3fd2d3SAndreas Gohr        $dtFormat = $type->getDateTimeFormat();
1010b3fd2d3SAndreas Gohr        if (!($dtFormat === AbstractTimeType::FORMAT_SECONDS || $dtFormat === AbstractTimeType::FORMAT_FRACTIONS)) {
1020b3fd2d3SAndreas Gohr            throw new EncoderException(sprintf(
1030b3fd2d3SAndreas Gohr                'Time must be specified to the seconds, but it is specified to "%s".',
1040b3fd2d3SAndreas Gohr                $dtFormat
1050b3fd2d3SAndreas Gohr            ));
1060b3fd2d3SAndreas Gohr        }
1070b3fd2d3SAndreas Gohr    }
1080b3fd2d3SAndreas Gohr
1090b3fd2d3SAndreas Gohr    /**
1100b3fd2d3SAndreas Gohr     * X.690 Section 11.6
1110b3fd2d3SAndreas Gohr     *
1120b3fd2d3SAndreas Gohr     * The encodings of the component values of a set-of value shall appear in ascending order, the encodings being
1130b3fd2d3SAndreas Gohr     * compared as octet strings with the shorter components being padded at their trailing end with 0-octets.
1140b3fd2d3SAndreas Gohr     *
1150b3fd2d3SAndreas Gohr     *   NOTE – The padding octets are for comparison purposes only and do not appear in the encodings.
1160b3fd2d3SAndreas Gohr     *
1170b3fd2d3SAndreas Gohr     * ---------
1180b3fd2d3SAndreas Gohr     *
1190b3fd2d3SAndreas Gohr     * It's very hard to find examples, but it's not clear to me from the wording if I have this correct. The example I
1200b3fd2d3SAndreas Gohr     * did find in "ASN.1 Complete" (John Larmouth) contains seemingly several encoding errors:
1210b3fd2d3SAndreas Gohr     *
1220b3fd2d3SAndreas Gohr     *    - Length is not encoded correctly for the SET OF element.
1230b3fd2d3SAndreas Gohr     *    - The integer 10 is encoded incorrectly.
1240b3fd2d3SAndreas Gohr     *    - The sort is in descending order of the encoded value (in opposition to X.690 11.6), though in ascending
1250b3fd2d3SAndreas Gohr     *      order of the literal integer values.
1260b3fd2d3SAndreas Gohr     *
1270b3fd2d3SAndreas Gohr     * So I'm hesitant to trust that. Perhaps there's an example elsewhere to be used? Tests around this are hard to
1280b3fd2d3SAndreas Gohr     * come by in ASN.1 libraries for some reason.
1290b3fd2d3SAndreas Gohr     *
1300b3fd2d3SAndreas Gohr     * @todo Is this assumed ordering correct? Confirmation needed. This could probably be simplified too.
1310b3fd2d3SAndreas Gohr     * @param SetOfType $setOf
1320b3fd2d3SAndreas Gohr     * @return string
1330b3fd2d3SAndreas Gohr     */
1340b3fd2d3SAndreas Gohr    protected function encodeSetOf(SetOfType $setOf)
1350b3fd2d3SAndreas Gohr    {
136*dad993c5SAndreas Gohr        if (count($setOf->getChildren()) === 0) {
1370b3fd2d3SAndreas Gohr            return '';
1380b3fd2d3SAndreas Gohr        }
1390b3fd2d3SAndreas Gohr        $children = [];
1400b3fd2d3SAndreas Gohr
1410b3fd2d3SAndreas Gohr        # Encode each child and record the length, we need it later
1420b3fd2d3SAndreas Gohr        foreach ($setOf as $type) {
1430b3fd2d3SAndreas Gohr            $child = ['original' => $this->encode($type)];
144*dad993c5SAndreas Gohr            $child['length'] = strlen($child['original']);
1450b3fd2d3SAndreas Gohr            $children[] = $child;
1460b3fd2d3SAndreas Gohr        }
1470b3fd2d3SAndreas Gohr
1480b3fd2d3SAndreas Gohr        # Sort the encoded types by length first to determine the padding needed.
149*dad993c5SAndreas Gohr        usort($children, function ($a, $b) {
1500b3fd2d3SAndreas Gohr            /* @var AbstractType $a
1510b3fd2d3SAndreas Gohr             * @var AbstractType $b */
1520b3fd2d3SAndreas Gohr            return $a['length'] < $b['length'] ? -1 : 1;
1530b3fd2d3SAndreas Gohr        });
1540b3fd2d3SAndreas Gohr
1550b3fd2d3SAndreas Gohr        # Get the last child (ie. the longest), and put the array back to normal.
156*dad993c5SAndreas Gohr        $child = end($children);
1570b3fd2d3SAndreas Gohr        $padding = $child ['length'];
158*dad993c5SAndreas Gohr        reset($children);
1590b3fd2d3SAndreas Gohr
1600b3fd2d3SAndreas Gohr        # Sort by padding the items and comparing them.
161*dad993c5SAndreas Gohr        usort($children, function ($a, $b) use ($padding) {
162*dad993c5SAndreas Gohr            return strcmp(
163*dad993c5SAndreas Gohr                str_pad($a['original'], $padding, "\x00"),
164*dad993c5SAndreas Gohr                str_pad($b['original'], $padding, "\x00")
1650b3fd2d3SAndreas Gohr            );
1660b3fd2d3SAndreas Gohr        });
1670b3fd2d3SAndreas Gohr
1680b3fd2d3SAndreas Gohr        # Reconstruct the byte string from the order obtained.
1690b3fd2d3SAndreas Gohr        $bytes = '';
1700b3fd2d3SAndreas Gohr        foreach ($children as $child) {
1710b3fd2d3SAndreas Gohr            $bytes .= $child['original'];
1720b3fd2d3SAndreas Gohr        }
1730b3fd2d3SAndreas Gohr
1740b3fd2d3SAndreas Gohr        return $bytes;
1750b3fd2d3SAndreas Gohr    }
1760b3fd2d3SAndreas Gohr}
177