From 39e5cefd794da839c7cb9765fd17c7eae12e6c63 Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Wed, 26 Mar 2025 16:20:13 +0200 Subject: [PATCH 1/7] Allow id-card authentication when Extended Key Usage is not present in certificate WE2-1028 Signed-off-by: Sven Mitt --- .../SubjectCertificatePurposeValidator.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/validator/certvalidators/SubjectCertificatePurposeValidator.php b/src/validator/certvalidators/SubjectCertificatePurposeValidator.php index 14ce96b..17670a2 100644 --- a/src/validator/certvalidators/SubjectCertificatePurposeValidator.php +++ b/src/validator/certvalidators/SubjectCertificatePurposeValidator.php @@ -32,6 +32,9 @@ final class SubjectCertificatePurposeValidator implements SubjectCertificateValidator { + private const KEY_USAGE = 'id-ce-keyUsage'; + private const KEY_USAGE_DIGITAL_SIGNATURE = 0; + private const EXTENDED_KEY_USAGE = 'id-ce-extKeyUsage'; // oid 1.3.6.1.5.5.7.3.2 private const EXTENDED_KEY_USAGE_CLIENT_AUTHENTICATION = "id-kp-clientAuth"; private $logger; @@ -51,10 +54,19 @@ public function __construct(LoggerInterface $logger = null) */ public function validate(X509 $subjectCertificate): void { - $usages = $subjectCertificate->getExtension('id-ce-extKeyUsage'); - if (!$usages || empty($usages)) { + $keyUsage = $subjectCertificate->getExtension(self::KEY_USAGE); + if (!$keyUsage || empty($keyUsage)) { throw new UserCertificateMissingPurposeException(); } + if (!$keyUsage[self::KEY_USAGE_DIGITAL_SIGNATURE]) { + throw new UserCertificateWrongPurposeException(); + } + $usages = $subjectCertificate->getExtension(self::EXTENDED_KEY_USAGE); + if (!$usages || empty($usages)) { + // Digital Signature extension present, but Extended Key Usage extension not present, + // assume it is an authentication certificate (e.g. Luxembourg eID). + return; + } // Extended usages must contain TLS Web Client Authentication if (!in_array(self::EXTENDED_KEY_USAGE_CLIENT_AUTHENTICATION, $usages)) { throw new UserCertificateWrongPurposeException(); @@ -62,4 +74,5 @@ public function validate(X509 $subjectCertificate): void $this->logger?->debug("User certificate can be used for client authentication."); } + } From 6fe12fbd3bc13d48525051386e3977b6fa954770 Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Wed, 26 Mar 2025 16:22:20 +0200 Subject: [PATCH 2/7] Test should fail because key usage is missing WE2-1028 Signed-off-by: Sven Mitt --- tests/validator/AuthTokenCertificateTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/validator/AuthTokenCertificateTest.php b/tests/validator/AuthTokenCertificateTest.php index 4ae445e..2750390 100644 --- a/tests/validator/AuthTokenCertificateTest.php +++ b/tests/validator/AuthTokenCertificateTest.php @@ -49,7 +49,7 @@ class AuthTokenCertificateTest extends AbstractTestWithValidator '"signature":"arx164xRiwhIQDINe0J+ZxJWZFOQTx0PBtOaWaxAe7gofEIHRIbV1w0sOCYBJnvmvMem9hU4nc2+iJx2x8poYck4Z6eI3GwtiksIec3XQ9ZIk1n/XchXnmPn3GYV+HzJ",' . '"format":"web-eid:1"}'; - private const MISSING_PURPOSE_CERT = 'MIICxjCCAa6gAwIBAgIJANTbd26vS6fmMA0GCSqGSIb3DQEBBQUAMBUxEzARBgNVBAMTCndlYi1laWQuZXUwHhcNMjAwOTI0MTIyNDMzWhcNMzAwOTIyMTIyNDMzWjAVMRMwEQYDVQQDEwp3ZWItZWlkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAza5qBFu5fvs47rx3o9yzBVfIxHjMotID8ppkwWVen/uFxlqsRVi+XnWkggW+K8X45inAnBAVi1rIw7GQNdacSHglyvQfwM64AallmD0+K+QgbqxcO9fvRvlAeISENBc2bGgqTIytPEON5ZmazzbOZjqY3M1QcPlPZOeUm6M9ZcZFhsxpiB4gwZUic9tnCz9eujd6k6DzNVfSRaJcpGA5hJ9aKH4vXS3x7anewna+USEXkRb4Il5zSlZR0i1yrVA1YNOxCG/+GgWvXfvXwdQ0z9BpGwNEyc0mRDNx+umaTukz9t+7/qTcB2JLTuiwM9Gqg5sDDnzPlcZSa7GnIU0MLQIDAQABoxkwFzAVBgNVHREEDjAMggp3ZWItZWlkLmV1MA0GCSqGSIb3DQEBBQUAA4IBAQAYGkBhTlet47uw3JYunYo6dj4nGWSGV4x6LYjCp5QlAmGd28HpC1RFB3ba+inwW8SP69kEOcB0sJQAZ/tV90oCATNsy/Whg/TtiHISL2pr1dyBoKDRWbgTp8jjzcp2Bj9nL14aqpj1t4K1lcoYETX41yVmyyJu6VFs80M5T3yikm2giAhszjChnjyoT2kaEKoua9EUK9SS27pVltgbbvtmeTp3ZPHtBfiDOATL6E03RZ5WfMLRefI796a+RcznnudzQHhMSwcjLpMDgIWpUU4OU7RiwrU+S3MrvgzCjkWh2MGu/OGLB+d3JZoW+eCvigoshmAsbJCMLbh4N78BCPqk'; + private const MISSING_KEY_USAGE_CERT = 'MIICxjCCAa6gAwIBAgIJANTbd26vS6fmMA0GCSqGSIb3DQEBBQUAMBUxEzARBgNVBAMTCndlYi1laWQuZXUwHhcNMjAwOTI0MTIyNDMzWhcNMzAwOTIyMTIyNDMzWjAVMRMwEQYDVQQDEwp3ZWItZWlkLmV1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAza5qBFu5fvs47rx3o9yzBVfIxHjMotID8ppkwWVen/uFxlqsRVi+XnWkggW+K8X45inAnBAVi1rIw7GQNdacSHglyvQfwM64AallmD0+K+QgbqxcO9fvRvlAeISENBc2bGgqTIytPEON5ZmazzbOZjqY3M1QcPlPZOeUm6M9ZcZFhsxpiB4gwZUic9tnCz9eujd6k6DzNVfSRaJcpGA5hJ9aKH4vXS3x7anewna+USEXkRb4Il5zSlZR0i1yrVA1YNOxCG/+GgWvXfvXwdQ0z9BpGwNEyc0mRDNx+umaTukz9t+7/qTcB2JLTuiwM9Gqg5sDDnzPlcZSa7GnIU0MLQIDAQABoxkwFzAVBgNVHREEDjAMggp3ZWItZWlkLmV1MA0GCSqGSIb3DQEBBQUAA4IBAQAYGkBhTlet47uw3JYunYo6dj4nGWSGV4x6LYjCp5QlAmGd28HpC1RFB3ba+inwW8SP69kEOcB0sJQAZ/tV90oCATNsy/Whg/TtiHISL2pr1dyBoKDRWbgTp8jjzcp2Bj9nL14aqpj1t4K1lcoYETX41yVmyyJu6VFs80M5T3yikm2giAhszjChnjyoT2kaEKoua9EUK9SS27pVltgbbvtmeTp3ZPHtBfiDOATL6E03RZ5WfMLRefI796a+RcznnudzQHhMSwcjLpMDgIWpUU4OU7RiwrU+S3MrvgzCjkWh2MGu/OGLB+d3JZoW+eCvigoshmAsbJCMLbh4N78BCPqk'; private const WRONG_PURPOSE_CERT = 'MIIEBDCCA2WgAwIBAgIQGIgoZxFL7VZbyFH7MAVEkTAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTE4MTAxODA5MjcyM1oXDTIzMTAxNzIxNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT3SZB34CUGYhQyLsLd9b2ihv35q7NT47Id9ugLIdgg3NSFDccH6rV16D2m8DKfuD2mn3V6QdaaZnbWF4YdDK1W0C9kLNsB70ob//y39pugMftepmQpJcBGPqug81tf5jujggHDMIIBvzAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIDiDBHBgNVHSAEQDA+MDIGCysGAQQBg5EhAQIBMCMwIQYIKwYBBQUHAgEWFWh0dHBzOi8vd3d3LnNrLmVlL0NQUzAIBgYEAI96AQIwHwYDVR0RBBgwFoEUMzgwMDEwODU3MThAZWVzdGkuZWUwHQYDVR0OBBYEFPhJx7ro54+N8r2ByiZXzZyWBbjFMGEGCCsGAQUFBwEDBFUwUzBRBgYEAI5GAQUwRzBFFj9odHRwczovL3NrLmVlL2VuL3JlcG9zaXRvcnkvY29uZGl0aW9ucy1mb3ItdXNlLW9mLWNlcnRpZmljYXRlcy8TAkVOMCAGA1UdJQEB/wQWMBQGCCsG/wUFBwMCBggrBgEFBQcDBDAfBgNVHSMEGDAWgBTAhJkpxE6fOwI09pnhClYACCk+ezBzBggrBgEFBQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0dHA6Ly9haWEuZGVtby5zay5lZS9lc3RlaWQyMDE4MDUGCCsGAQUFBzAChilodHRwOi8vYy5zay5lZS9UZXN0X29mX0VTVEVJRDIwMTguZGVyLmNydDAKBggqhkjOPQQDBAOBjAAwgYgCQgFi5XSCFGgsc8SKLWwMBWS0nu/20FjEqh6OGvsI4iPctNDkinsxcYgARdfqPsNnDX+KjALKPEKZCLKRixGL2kPLMgJCAQFXP9gstThxlj/1Q5YFb7KWhPWFiKgQEi9JdvxJQNXLkWV9onEh96mRFgv4IJJpGazuoSMZtzNpyBxmM0dwnxOf'; private const WRONG_POLICY_CERT = 'MIIEATCCA2OgAwIBAgIQOWkBWXNDJm1byFd3XsWkvjAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTE4MTAxODA5NTA0N1oXDTIzMTAxNzIxNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR5k1lXzvSeI9O/1s1pZvjhEW8nItJoG0EBFxmLEY6S7ki1vF2Q3TEDx6dNztI1Xtx96cs8r4zYTwdiQoDg7k3diUuR9nTWGxQEMO1FDo4Y9fAmiPGWT++GuOVoZQY3XxijggHBMIIBvTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIDiDBFBgNVHSAEPjA8MDAGCSsGAQQBzh8BAzAjMCEGCCsGAQUFBwIBFhVodHRwczovL3d3dy5zay5lZS9DUFMwCAYGBACPegECMB8GA1UdEQQYMBaBFDM4MDAxMDg1NzE4QGVlc3RpLmVlMB0GA1UdDgQWBBTkLL00CRAVTDEpocmV+W4m2CbmwDBhBggrBgEFBQcBAwRVMFMwUQYGBACORgEFMEcwRRY/aHR0cHM6Ly9zay5lZS9lbi9yZXBvc2l0b3J5L2NvbmRpdGlvbnMtZm9yLXVzZS1vZi1jZXJ0aWZpY2F0ZXMvEwJFTjAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwHwYDVR0jBBgwFoAUwISZKcROnzsCNPaZ4QpWAAgpPnswcwYIKwYBBQUHAQEEZzBlMCwGCCsGAQUFBzABhiBodHRwOi8vYWlhLmRlbW8uc2suZWUvZXN0ZWlkMjAxODA1BggrBgEFBQcwAoYpaHR0cDovL2Muc2suZWUvVGVzdF9vZl9FU1RFSUQyMDE4LmRlci5jcnQwCgYIKoZIzj0EAwQDgYsAMIGHAkIB9VLJjHbS2bYudRatkEeMFJAMKbJ4bAVdh0KlFxWASexF5ywpGl43WSpB6QAXzNEBMe1FIWiOIud44iexNWO1jgACQQ1+M+taZ4hyWqSNW5DCIiUP7Yu4WvH3SUjEqQHbOQshyMh5EM1pVcvOn/ZgOxLt6ETv9avnhVMw2zTd1b8u4EFk'; @@ -125,9 +125,9 @@ public function testWhenCertificateFieldIsNotCertificateThenParsingFails(): void $this->validator->validate($token, self::VALID_CHALLENGE_NONCE); } - public function testWhenCertificatePurposeIsMissingThenValidationFails(): void + public function testWhenCertificateKeyUsageIsMissingThenValidationFails(): void { - $token = $this->replaceTokenField(self::AUTH_TOKEN, "unverifiedCertificate", self::MISSING_PURPOSE_CERT); + $token = $this->replaceTokenField(self::AUTH_TOKEN, "unverifiedCertificate", self::MISSING_KEY_USAGE_CERT); $this->expectException(UserCertificateMissingPurposeException::class); $this->validator->validate($token, self::VALID_CHALLENGE_NONCE); @@ -171,7 +171,7 @@ public function testWhenUsingNewMobileIdCertificateThenValidationFails(): void { $token = $this->replaceTokenField(self::AUTH_TOKEN, "unverifiedCertificate", self::NEW_MOBILE_ID_CERT); - $this->expectException(UserCertificateMissingPurposeException::class); + $this->expectException(UserCertificateDisallowedPolicyException::class); $this->validator->validate($token, self::VALID_CHALLENGE_NONCE); } From 5be662c2d6eb9b0a48d0f026ebae3fa15edc8032 Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Mon, 31 Mar 2025 11:08:43 +0300 Subject: [PATCH 3/7] Fix documentation and formatting WE2-1028 Signed-off-by: Sven Mitt --- .../certvalidators/SubjectCertificatePurposeValidator.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/validator/certvalidators/SubjectCertificatePurposeValidator.php b/src/validator/certvalidators/SubjectCertificatePurposeValidator.php index 17670a2..bd1125e 100644 --- a/src/validator/certvalidators/SubjectCertificatePurposeValidator.php +++ b/src/validator/certvalidators/SubjectCertificatePurposeValidator.php @@ -47,10 +47,11 @@ public function __construct(LoggerInterface $logger = null) /** * Validates that the purpose of the user certificate from the authentication token contains client authentication. * - * @copyright 2022 Petr Muzikant pmuzikant@email.cz - * - * @param subjectCertificate user certificate to be validated + * @param X509 $subjectCertificate user certificate to be validated * @throws UserCertificateMissingPurposeException + * @throws UserCertificateWrongPurposeException + * @copyright 2022 Petr Muzikant pmuzikant@email.cz + * */ public function validate(X509 $subjectCertificate): void { From c9ddc194476260c95af874baeae5bc214a7d0a4f Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Thu, 17 Apr 2025 14:57:38 +0300 Subject: [PATCH 4/7] Add tests for certificates from Belgian and Finnish ID-cards WE2-1028 Signed-off-by: Sven Mitt --- .../DVV TEST Certificates - G5E.crt | Bin 0 -> 1095 bytes .../VRK TEST CA for Test Purposes - G4.crt | Bin 0 -> 1919 bytes tests/_resources/eID TEST EC Citizen CA.cer | 19 ++++ tests/testutil/AuthTokenValidators.php | 21 ++++ .../AuthTokenCertificateBelgianIdCardTest.php | 102 ++++++++++++++++++ .../AuthTokenCertificateFinnishIdCardTest.php | 92 ++++++++++++++++ 6 files changed, 234 insertions(+) create mode 100644 tests/_resources/DVV TEST Certificates - G5E.crt create mode 100644 tests/_resources/VRK TEST CA for Test Purposes - G4.crt create mode 100644 tests/_resources/eID TEST EC Citizen CA.cer create mode 100644 tests/validator/AuthTokenCertificateBelgianIdCardTest.php create mode 100644 tests/validator/AuthTokenCertificateFinnishIdCardTest.php diff --git a/tests/_resources/DVV TEST Certificates - G5E.crt b/tests/_resources/DVV TEST Certificates - G5E.crt new file mode 100644 index 0000000000000000000000000000000000000000..a01b659b1fb3a6fc393213f7cf53dccc32e7feee GIT binary patch literal 1095 zcmXqLVsSQTVm`HinTe5!iJ!4@(X=m7oeph_8M!Wn1sHI#acH%9oU>(NW;SSCWXNs6 z$;KSY!Y0h*=4q&Dpbp}2@hG@trf2FZWF;z;C8idaQp`M(E@5F{gB61E^Gg(*9TjvH+>I4not+Kj#CeSj4NQ$p4a`l9 z42`42c}+lE0|N^~5Z9oBY(GdCh(R329|H7bW*OLZC8?<;hAIY15O+z#-G%UGYB9)h zrmhBM1_EsC;J{>JWMkECWMNQZPGVrGKU#>(GGi<=nV8Z`|nQC67+=#2&u``|x{8FmttvVqym z);n$nP7m$xHjo7=;A0VE5fPfV{gKA;&r`Cx_xc|Da>#6YkQ0XiA4r;?k?}tZ3oxy) z88n^)2`I2M9y4e>Y|wasjZ3Sstc5k1t0|t*pmC!?V=EhnHX9==D?1aDL8VkVJUa7p zGD|Wuv$GQwfGMCjF|j1EEHfu35g1I+P|Hp%DF7-;u2e|N%*z9XA5w5C1VE%Rixm>f z5;JoWlX6lOGD=Dctn~HE%ggnET2eDpfRui6L9wN!J}{BOoWUq2jZdKv$Unj$4{{i= z0VyU%1_LXQm^_QIfuVu^0^J4LZJJOM3X1Y8Dv>QK%F$0LD=Pse4dc{gz2u@CgEE-K zjEpQ<1{nqhFunm}n+nvJ{N&;SkRBwH5T+QIz;!USY2dOr5!K!jH6fyeVG^enG7N( z1x9fk77v_!YTqU8Je833I&v{72155P9eLs17w5M!qH2%ZGFv5Hndhs+9_aj12LOw3 BS>ONw literal 0 HcmV?d00001 diff --git a/tests/_resources/VRK TEST CA for Test Purposes - G4.crt b/tests/_resources/VRK TEST CA for Test Purposes - G4.crt new file mode 100644 index 0000000000000000000000000000000000000000..3159602176fd17bb4055dc601391fdf99eae437b GIT binary patch literal 1919 zcmbVMX;@R|63#hUWDyVphTRjP5Y~JNRKV5+DWIYkL>3Dc3CRIY60)8IsUjo_RDDpd zP#!F@x{FxZE8mG!?Tf1s7#4* zQ8~*V(B427#B*ess7QoLI6@^VRNyQZ;A9LD=iA1|M7}PJ#E8W>5*mW|A`xuhLbG56 z0X7I`0kD)EkVv!e4`Ko1_u2&6{|9qBu;>jPt60NynDR9z97S=KH}HJJX30A?1|n%< z8Dc~t(F&PFEJx)=Y5_nEFM0n*LSzC_4O7GGe``3N$d+2TIV7+5mAQuBFJjwx5NG03cj{-q0Jkmd6Q}f5 z(g*wb1?|qfg04cuBj*O=2RovreEs>-n|)7uJYC8`L!v%HTNm#m)*-t!*~VmI&ia>2ljN2`{x|KJx+s}j5JR^-f{JM!^RN%My% ze@c2f+zx3kjxE_Rfd(yYzy6U8aWWynQ_s6HVfGAD#U1G#?%)m0Jk44?@Y}GUxpnAl zOYQoWTGN*?*3bj5uHJReBuC4(NN*8>3j^Yj1x37tHpy^o@90!|#gCMfz!g?cIAK?c zFApyLOnWZI{89OrZIwNj9(aDkI%U%7KXlof=94osFr4pSx*3&C?rZwik^f>P!qPbOegb!~Htz(cyIg(wtm>fc+pb5*5TeZqGgFK|8$CPD=0s2Z98YDfnb8y3sim1Nkx zJe&OG)uVqkn0oxIy|lPs-zti1wV4Vy8v&N2MI@W$Umb0EZfpI-y64gLEgOpS8n3VX z7FZg?RuD8xA{lf{0jlqe4uquY1}Y6uU8R_Lsg%3PTr<^L2&fu>DxYHFO@YYdc`yvJ z9kSjjPb|c63=;@Ah(WA!4hQEbF`++UmT`hgl?tVtsDI5(LG#N{4X4^+iUXIo*Kh*9X z!UzqTI8yR8ua2kDD@AtFl-vt2r$5wv*44Q`CR3Xm?h*RZt+VpBN4azztS#}rp`_Ej zF7R?X6kFGtUH<;n_TH|lBbtnj4cBxR^@H3CbpxkI;O@~T&5k_AuJot_AEf`|+`Xq+ ze0uSR(v-Ms{>`Q%ry@M6?j7ulR_fM;lB2wYvZZhn^bAYrT+q?u=AY& literal 0 HcmV?d00001 diff --git a/tests/_resources/eID TEST EC Citizen CA.cer b/tests/_resources/eID TEST EC Citizen CA.cer new file mode 100644 index 0000000..06456b7 --- /dev/null +++ b/tests/_resources/eID TEST EC Citizen CA.cer @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKTCCAq+gAwIBAgIIcND8I1qptLUwCgYIKoZIzj0EAwMwKzELMAkGA1UEBhMC +QkUxHDAaBgNVBAMME2VJRCBURVNUIEVDIFJvb3QgQ0EwIBcNMDcwNDMwMjIwMDIw +WhgPMjA4NzA0MTAyMjAwMjBaMC4xCzAJBgNVBAYTAkJFMR8wHQYDVQQDDBZlSUQg +VEVTVCBFQyBDaXRpemVuIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJAiNoOQf +Y0r8N6JVPMLedXyRZ7MwppGwQ9ZxFzLjVsbeKuUvqEFR0yKKyEidXc875m4UF5lR +pf/FSWagg2IXGWrypnRZkgnNVP6s5W2LzKdV09hd6v7O8j/8knfHOj+No4IBmTCC +AZUwHQYDVR0OBBYEFN2zf+OaGY5ZyRFWAi31+p1v3oRLMB8GA1UdIwQYMBaAFCHA +clfKHAQEGR3ZjH4+tYPrrBwCMA4GA1UdDwEB/wQEAwIBBjBIBgNVHSAEQTA/MD0G +BmA4DAEBAjAzMDEGCCsGAQUFBwIBFiVodHRwOi8vZWlkZGV2Y2FyZHMuemV0ZXNj +YXJkcy5iZS9jZXJ0MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDBCBgNV +HR8EOzA5MDegNaAzhjFodHRwOi8vZWlkZGV2Y2FyZHMuemV0ZXNjYXJkcy5iZS9j +cmwvcm9vdGNhRUMuY3JsMIGBBggrBgEFBQcBAQR1MHMwPgYIKwYBBQUHMAKGMmh0 +dHA6Ly9laWRkZXZjYXJkcy56ZXRlc2NhcmRzLmJlL2NlcnQvcm9vdGNhRUMuY3J0 +MDEGCCsGAQUFBzABhiVodHRwOi8vZWlkZGV2Y2FyZHMuemV0ZXNjYXJkcy5iZTo4 +ODg4MBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0EAwMDaAAwZQIxAOMiiByF +0aLEA6zUrobMw7aSH5o2u1hGVMe0AL4ezYztRdfxvXVU+m1JosBVBDDjeAIwYJJN +7bLWw8BVi/lkxRjKL/+zAJP6djGywXI1pVh4HKb0D+tipq5StO+QnM8cnPmg +-----END CERTIFICATE----- diff --git a/tests/testutil/AuthTokenValidators.php b/tests/testutil/AuthTokenValidators.php index 1bcd0b2..893ab88 100644 --- a/tests/testutil/AuthTokenValidators.php +++ b/tests/testutil/AuthTokenValidators.php @@ -89,6 +89,27 @@ public static function getAuthTokenValidatorWithDisallowedESTEIDPolicy(): AuthTo ->build(); } + public static function getAuthTokenValidatorForBelgianIdCard(): AuthTokenValidator + { + return self::getAuthTokenValidator( + "https://47f0-46-131-86-189.ngrok-free.app", + ...CertificateLoader::loadCertificatesFromResources( + __DIR__ . "/../_resources/eID TEST EC Citizen CA.cer" + ) + ); + } + + public static function getAuthTokenValidatorForFinnishIdCard(): AuthTokenValidator + { + return self::getAuthTokenValidator( + "https://47f0-46-131-86-189.ngrok-free.app", + ...CertificateLoader::loadCertificatesFromResources( + __DIR__ . "/../_resources/DVV TEST Certificates - G5E.crt", + __DIR__ . "/../_resources/VRK TEST CA for Test Purposes - G4.crt" + ) + ); + } + public static function getAuthTokenValidatorWithWrongTrustedCertificate(): AuthTokenValidator { return self::getAuthTokenValidator( diff --git a/tests/validator/AuthTokenCertificateBelgianIdCardTest.php b/tests/validator/AuthTokenCertificateBelgianIdCardTest.php new file mode 100644 index 0000000..ec00940 --- /dev/null +++ b/tests/validator/AuthTokenCertificateBelgianIdCardTest.php @@ -0,0 +1,102 @@ +mockDate("2024-12-24"); + } + + protected function tearDown(): void + { + Dates::resetMockedCertificateValidatorDate(); + } + + public function testWhenIdCardWithECCSignatureCertificateIsValidatedThenValidationSucceeds(): void + { + $this->expectNotToPerformAssertions(); + $validator = AuthTokenValidators::getAuthTokenValidatorForBelgianIdCard(); + $token = $validator->parse(self::BELGIAN_TEST_ID_CARD_AUTH_TOKEN_ECC); + + $validator->validate($token, 'iMeEwP2cgUINY2XoO/lqEpOUn7z/ysHRqGXkGKC4VXE='); + } + + public function testWhenIdCardWithRSASignatureCertificateIsValidatedThenValidationSucceeds(): void + { + $this->expectNotToPerformAssertions(); + $validator = AuthTokenValidators::getAuthTokenValidatorForBelgianIdCard(); + $token = $validator->parse(self::BELGIAN_TEST_ID_CARD_AUTH_TOKEN_RSA); + + $validator->validate($token, 'YPVgYc7Qds0qmK/RilPLffnsIg7IIovM4BAWqGZWwiY='); + } + + private function mockDate(string $date) + { + Dates::setMockedCertificateValidatorDate(new DateTime($date)); + } + +} diff --git a/tests/validator/AuthTokenCertificateFinnishIdCardTest.php b/tests/validator/AuthTokenCertificateFinnishIdCardTest.php new file mode 100644 index 0000000..8465755 --- /dev/null +++ b/tests/validator/AuthTokenCertificateFinnishIdCardTest.php @@ -0,0 +1,92 @@ +mockDate("2024-12-24"); + } + + protected function tearDown(): void + { + Dates::resetMockedCertificateValidatorDate(); + } + + public function testWhenIdCardSignatureCertificateWithG5ERootCertificateIsValidatedThenValidationSucceeds(): void + { + $this->expectNotToPerformAssertions(); + $validator = AuthTokenValidators::getAuthTokenValidatorForFinnishIdCard(); + $token = $validator->parse(self::FINNISH_TEST_ID_CARD_BACKMAN_JUHANI_AUTH_TOKEN); + + $validator->validate($token, 'x9qZDRO/ao2zprt3Z0bkW4CvvE/gALFtUIf3tcC0XxY='); + } + + public function testWhenIdCardSignatureCertificateWithG4RootCertificateIsValidatedThenValidationSucceeds(): void + { + $this->expectNotToPerformAssertions(); + $validator = AuthTokenValidators::getAuthTokenValidatorForFinnishIdCard(); + $token = $validator->parse(self::FINNISH_TEST_ID_CARD_BABAFO_VELI_AUTH_TOKEN); + + $validator->validate($token, 'ZqlDATkQRqh7LkqEbspBc2qDjot29oiNLlITdLgiVIo='); + } + + private function mockDate(string $date) + { + Dates::setMockedCertificateValidatorDate(new DateTime($date)); + } + +} From 99f33616ba2c2d7a17b3bf0b126553160f43cd80 Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Wed, 23 Apr 2025 11:42:57 +0300 Subject: [PATCH 5/7] Force PSS padding to be sent to openssl_verify when algorithm is PSXXX WE2-1028 Signed-off-by: Sven Mitt --- src/validator/AuthTokenSignatureValidator.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/validator/AuthTokenSignatureValidator.php b/src/validator/AuthTokenSignatureValidator.php index b3fa1ba..094816c 100644 --- a/src/validator/AuthTokenSignatureValidator.php +++ b/src/validator/AuthTokenSignatureValidator.php @@ -27,6 +27,7 @@ namespace web_eid\web_eid_authtoken_validation_php\validator; use GuzzleHttp\Psr7\Uri; +use phpseclib3\Crypt\RSA; use web_eid\web_eid_authtoken_validation_php\exceptions\AuthTokenParseException; use web_eid\web_eid_authtoken_validation_php\exceptions\ChallengeNullOrEmptyException; use InvalidArgumentException; @@ -36,6 +37,10 @@ class AuthTokenSignatureValidator { + private const ECDSA_ALGORITHMS = ['ES256', 'ES384', 'ES512']; + + private const RSASSA_PSS_ALGORITHMS = ['PS256', 'PS384', 'PS512']; + /** Supported subset of JSON Web Signature algorithms as defined in RFC 7518, sections 3.3, 3.4, 3.5. * See https://github.com/web-eid/libelectronic-id/blob/main/include/electronic-id/enums.hpp#L176. */ @@ -72,10 +77,17 @@ public function validate(string $algorithm, string $signature, $publicKey, strin $decodedSignature = base64_decode($signature); // Note that in case of ECDSA, some eID cards output raw R||S, so we need to trascode it to DER - if (in_array($algorithm, ["ES256", "ES384", "ES512"]) && !AsnUtil::isSignatureInAsn1Format($decodedSignature)) { + if (in_array($algorithm, self::ECDSA_ALGORITHMS) && !AsnUtil::isSignatureInAsn1Format($decodedSignature)) { $decodedSignature = AsnUtil::transcodeSignatureToDER($decodedSignature); } + if (in_array($algorithm, self::RSASSA_PSS_ALGORITHMS)) { + $publicKey = openssl_get_publickey($publicKey->withPadding(RSA::SIGNATURE_PSS)->toString('PSS')); + if (!$publicKey) { + throw new AuthTokenParseException(); + } + } + $hashAlgorithm = $this->hashAlgorithmForName($algorithm); $originHash = openssl_digest($this->siteOrigin->jsonSerialize(), $hashAlgorithm, true); From 57714646f0b1bf27f592fa0e08da84ab0c429ea7 Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Tue, 20 May 2025 13:14:16 +0300 Subject: [PATCH 6/7] Add exception message WE2-1028 Signed-off-by: Sven Mitt --- src/validator/AuthTokenSignatureValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validator/AuthTokenSignatureValidator.php b/src/validator/AuthTokenSignatureValidator.php index 094816c..0d0b1aa 100644 --- a/src/validator/AuthTokenSignatureValidator.php +++ b/src/validator/AuthTokenSignatureValidator.php @@ -84,7 +84,7 @@ public function validate(string $algorithm, string $signature, $publicKey, strin if (in_array($algorithm, self::RSASSA_PSS_ALGORITHMS)) { $publicKey = openssl_get_publickey($publicKey->withPadding(RSA::SIGNATURE_PSS)->toString('PSS')); if (!$publicKey) { - throw new AuthTokenParseException(); + throw new AuthTokenParseException('Could not use PSS padding for RSASSA-PSS algorithm'); } } From aaa4fcfb38da3a72f8afccd456f9016e0243cba3 Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Tue, 20 May 2025 13:14:39 +0300 Subject: [PATCH 7/7] Add debug statement when returning early WE2-1028 Signed-off-by: Sven Mitt --- .../certvalidators/SubjectCertificatePurposeValidator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/validator/certvalidators/SubjectCertificatePurposeValidator.php b/src/validator/certvalidators/SubjectCertificatePurposeValidator.php index bd1125e..9a94364 100644 --- a/src/validator/certvalidators/SubjectCertificatePurposeValidator.php +++ b/src/validator/certvalidators/SubjectCertificatePurposeValidator.php @@ -66,6 +66,7 @@ public function validate(X509 $subjectCertificate): void if (!$usages || empty($usages)) { // Digital Signature extension present, but Extended Key Usage extension not present, // assume it is an authentication certificate (e.g. Luxembourg eID). + $this->logger?->debug("User certificate has Digital Signature key usage and no Extended Key Usage extension, this means that it can be used for client authentication."); return; } // Extended usages must contain TLS Web Client Authentication