Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling verifyPoint on EC curves throws TypeError #1997

Open
orose-assetgo opened this issue May 9, 2024 · 6 comments
Open

Calling verifyPoint on EC curves throws TypeError #1997

orose-assetgo opened this issue May 9, 2024 · 6 comments

Comments

@orose-assetgo
Copy link

I'm trying to load a public key from a weird storage method, and I've come across the following when trying to use it to verify a signature. In the full code, the curve is being loaded based on an ASN.1 object identifier in DER format along with the x/y coordinates, but I've stripped that out to see what's happening.

The $x parameter appears to be EC\BaseCurves\Prime::$a.

Error Message:
PHP Fatal error: Uncaught TypeError: phpseclib3\Math\BigInteger::multiply(): Argument #1 ($x) must be of type phpseclib3\Math\BigInteger, phpseclib3\Math\PrimeField\Integer given, called in vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php on line 476 and defined in vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php:274

Minimal test case

$curve = new \phpseclib3\Crypt\EC\Curves\brainpoolP256r1();
$x = new \phpseclib3\Math\BigInteger(0);
$y = new \phpseclib3\Math\BigInteger(0);

$curve->verifyPoint([$x, $y]);

Expected outcome: true/false depending on whether 0,0 is on the curve or not.

I'm not ruling out that I'm doing something dumb to get to this point, but this seems like a bug.

@terrafrost
Copy link
Member

Try this:

$curve = new \phpseclib3\Crypt\EC\Curves\brainpoolP256r1();
$x = new \phpseclib3\Math\BigInteger(0);
$y = new \phpseclib3\Math\BigInteger(0);

$x = $curve->convertInteger($x);
$y = $curve->convertInteger($y);

$curve->verifyPoint([$x, $y]);

The thing is... brainpoolP256r1 is a prime field elliptic curve (as opposed to a binary field elliptic curve). What this means is that you can't have numbers greater than or equal to the modulo. Like if the modulo is 5 then the only valid numbers that you're going to be able to use are 0-4. \phpseclib3\Math\BigInteger doesn't enforce this, hence why you need to use \phpseclib3\Math\PrimeField. Like with \phpseclib3\Math\BigInteger if you do 4+4 you'll get 8 but with \phpseclib3\Math\PrimeField you'd get (4+4)%5 = 8%5 = 3.

From that one might conclude that this would work:

$curve = new \phpseclib3\Crypt\EC\Curves\brainpoolP256r1();
$x = new \phpseclib3\Math\BigInteger(0);
$y = new \phpseclib3\Math\BigInteger(0);

$modulo = new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16);
$factory = new \phpseclib3\Math\PrimeField($modulo);
$x = $factory->newInteger($x);
$y = $factory->newInteger($y);

$curve->verifyPoint([$x, $y]);

But if you do that you'll get this:

Fatal error: Uncaught UnexpectedValueException: The instances of the two PrimeField\Integer objects do not match

That's because doing modular reductions over and over again is costly so what phpseclib does is that it create a hyper optimized modular reduction method that's bespoke to the modulo and for performance purposes it just checks to see that both \phpseclib3\Math\PrimeField\Integer instances were created by the same factory. So like whereas this works:

$modulo = new BigInteger(5);
$factory = new \phpseclib3\Math\PrimeField($modulo);
$x = $factory->newInteger(new BigInteger(4));
$y = $factory->newInteger(new BigInteger(4));

echo $x->add($y);

This doesn't:

$modulo = new BigInteger(5);
$factory = new \phpseclib3\Math\PrimeField($modulo);
$x = $factory->newInteger(new BigInteger(4));
$factory = new \phpseclib3\Math\PrimeField($modulo);
$y = $factory->newInteger(new BigInteger(4));

echo $x->add($y);

@orose-assetgo
Copy link
Author

Thanks for the detailed explanation.

Is there a mechanism for creating key objects (for me, public keys) from the curve name and coordinates, or am I looking at things from the wrong direction?

@terrafrost
Copy link
Member

Is there a mechanism for creating key objects (for me, public keys) from the curve name and coordinates, or am I looking at things from the wrong direction?

Yah - I'll try to get that to you this evening. Need to get ready to head off to work!

@terrafrost
Copy link
Member

So prob the easiest way to create a key with specific coordinate would be to do something like this (based on the RFC4050 format):

$key = PublicKeyLoader::load('<ECDSAKeyValue xmlns="http://www.w3.org/2001/04/xmldsig-more#">
<DomainParameters><NamedCurve URN="urn:oid:1.3.36.3.3.2.8.1.1.7" /></DomainParameters>
<PublicKey>
<X Value="72379364585787862211816843651173825953534775149312366423617662317441035882390" />
<Y Value="60964272061100602708590358249320835245921448174521229979647778785859175709132" />
</PublicKey>
</ECDSAKeyValue>');

The curve name is loaded with <NamedCurve URN="urn:oid:1.3.36.3.3.2.8.1.1.7" />. The curve name / OID mappings can be found at https://raw.githubusercontent.com/phpseclib/phpseclib/3.0/phpseclib/Crypt/EC/Formats/Keys/Common.php

If it weren't for you wanting to use less common curves like the brainpool curves the JWK format could work, too, but that format only supports a few very specific curves.

It might be easier to pass to PublicKeyLoader::load() an array, much like you can do with RSA keys. eg.

$key = PublicKeyLoader::load([
    'e' => new BigInteger($e, 16),
    'n' => new BigInteger($n, 16)
]);

Unfortunately, there's not (currently) a Raw plugin for EC curves. I could add one this weekend but there's not currently one.

Alternatively, you could write your own plugin. https://phpseclib.com/docs/publickeys#custom-key-formats talks about how to do that. Or if I wrote a Raw plugin you could get that plugin and use it with the current version of phpseclib 3 without having to make any changes to your vendor/ directory contents. If you wanted a Raw plugin I should be able to whip one up this weekend.

Also, sorry for not responding yesterday after work - I got home and fell asleep very shortly thereafter. Not sure why I was so tired but I was!

@orose-assetgo
Copy link
Author

I was wondering whether a plugin might be a useful addition - this format is supposedly a standardised format from ISO 7816 (self-descriptive card-verifiable certificates), although without access to the standard itself I don't know if it's explicitly described there.

The best reference I have for it is the EU tachograph equipment regulations, which is probably why it seems like I'm approaching it from different directions to most.

I'll try and get myself familiar with how phpseclib does things, and see if I can't put something useful together myself first - what you've already sent across has helped massively though, as I can at least see something similar to OpenSSLs certificate detail text view now.

@terrafrost
Copy link
Member

I was wondering whether a plugin might be a useful addition - this format is supposedly a standardised format from ISO 7816 (self-descriptive card-verifiable certificates), although without access to the standard itself I don't know if it's explicitly described there.

Yah - ISO standards tend not to be free. If I had to guess I'd guess https://www.iso.org/standard/77180.html houses it but I'm also not prepared to pay $200+ USD for it.

Anyway, if you'd be willing to share it I'd probably be down to include it in phpseclib!

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

No branches or pull requests

2 participants