Skip to content

Conversation

mmauv
Copy link
Contributor

@mmauv mmauv commented Jan 13, 2025

Allow ASN.1 DER encoding of tags with values greater than 30.
From X.690-0207.pdf#page=12 section 8.1.2.4:

For tags with a number greater than or equal to 31, the identifier shall comprise a leading octet followed by
one or more subsequent octets.
...
c) bits 5 to 1 shall be encoded as 111112 .
...
a) bit 8 of each octet shall be set to one unless it is the last octet of the identifier octets;
b) bits 7 to 1 of the first subsequent octet, followed by bits 7 to 1 of the second subsequent octet, followed in
turn by bits 7 to 1 of each further octet, up to and including the last subsequent octet in the identifier
octets shall be the encoding of an unsigned binary integer equal to the tag number, with bit 7 of the first
subsequent octet as the most significant bit

Copy link
Member

@terrafrost terrafrost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using BigInteger? $child['constant'] is an integer and converting that to a BigInteger at the point where you are isn't going to all of a sudden enable you to use bigger integers. And although there's absolutely a use case for being able to do constants > 30 is there one for being able to do constants > PHP_INT_MAX?

Other than that I didn't realize that this wasn't supported. Like phpseclib can decode ASN.1 stuff with > 30 but it can't encode them. So nice catch there!

A unit test would be good, as well.

If it helps (and partially for my own reference lol) this is what I used to test it:

$map = [
    'type'     => ASN1::TYPE_SEQUENCE,
    'children' => [
        'demo' => [
            'constant' => 0xFFFFFFFF,
            'optional' => true,
            'explicit' => true,
            'default'  => 'v1',
            'type'     => ASN1::TYPE_INTEGER,
            'mapping' => ['v1', 'v2', 'v3']
        ]
    ]
];

$data = ['demo' => 'v3']; // should be 300abf8fffffff7f03020102

$str = ASN1::encodeDER($data, $map);

$decoded = ASN1::decodeBER($str);
$arr = ASN1::asn1map($decoded[0], $map);

$subtagvalue = $constant->bitwise_and($mask);
$subtagvalue->setPrecision(8);
$subtag = (chr(0x80) | $subtagvalue->toBytes()) . $subtag;
$constant = $constant->bitwise_rightShift(shift: 7);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using named arguments for a function that only has one argument seems unnecessary. And inconsistent. Like if you're going to do it for this method then why not do it for $subtagvalue->setPrecision(8) as well? Not that I'm saying you ought to.

 - Fix inconsistent named arguments usage
 - Add Unit test of ASN1 DER encoding with constants > 30
@mmauv
Copy link
Contributor Author

mmauv commented Jan 20, 2025

Why are you using BigInteger? $child['constant'] is an integer and converting that to a BigInteger at the point where you are isn't going to all of a sudden enable you to use bigger integers. And although there's absolutely a use case for being able to do constants > 30 is there one for being able to do constants > PHP_INT_MAX?

BigInteger was used for coherence with the implementation of object identifier values File/ASN1.php line 1232. If it's not relevant, the implementation can be done without using BigInteger.

I'm not sure if there's a use case with constants > PHP_INT_MAX, since PHP_INT_MAX is quite big.

Other requested changes were done in the last commit.

@terrafrost
Copy link
Member

BigInteger was used for coherence with the implementation of object identifier values File/ASN1.php line 1232. If it's not relevant, the implementation can be done without using BigInteger.

Good catch - that's prob something that ought to be changed. I can do that.

That said, altho encoding with tags using BigInteger is consistent with how phpseclib handles OIDs, it's inconsistent with how phpsecib decodes tags:

$tag = $type & 0x1F;
if ($tag == 0x1F) {
$tag = 0;
// process septets (since the eighth bit is ignored, it's not an octet)
do {
if (!isset($encoded[$encoded_pos])) {
return false;
}
$temp = ord($encoded[$encoded_pos++]);
$startOffset++;
$loop = $temp >> 7;
$tag <<= 7;
$temp &= 0x7F;
// "bits 7 to 1 of the first subsequent octet shall not all be zero"
if ($startOffset == 2 && $temp == 0) {
return false;
}
$tag |= $temp;
} while ($loop);
}

So if you could change it to use ints that'd be great. Meanwhile, I'll update how OIDs are handled.

Thanks!!

@terrafrost
Copy link
Member

BigInteger was used for coherence with the implementation of object identifier values File/ASN1.php line 1232. If it's not relevant, the implementation can be done without using BigInteger.

Good catch - that's prob something that ought to be changed. I can do that.

So I modified ASN1.php to make it stop using BigIntegers for OIDs and one of the unit tests then failed. Turns out phpseclib started using BigInteger's because of #1367.

So I guess there is a valid use-case for using BigInteger's for OIDs but I am aware of no valid use case for using them with tags. And plus, as previously stated, BigInteger's are not used for decoding tags.

Anyway, imma try to squash your commits into one and then cherry-pick that to the 3.0 branch. Maybe this evening or tomorrow or some such.

Thanks!

@terrafrost
Copy link
Member

This has been squashed into one commit and cherry-picked to the 3.0 branch:

a02712f

Thanks!

@terrafrost terrafrost closed this Jan 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants