@@ -46,7 +46,7 @@ public function __construct($rpName, $rpId, $allowedFormats=null, $useBase64UrlE
4646 $ supportedFormats = array ('android-key ' , 'android-safetynet ' , 'apple ' , 'fido-u2f ' , 'none ' , 'packed ' , 'tpm ' );
4747
4848 if (!\function_exists ('\openssl_open ' )) {
49- throw new WebAuthnException ('OpenSSL-Module not installed ' );;
49+ throw new WebAuthnException ('OpenSSL-Module not installed ' );
5050 }
5151
5252 if (!\in_array ('SHA256 ' , \array_map ('\strtoupper ' , \openssl_get_md_methods ()))) {
@@ -73,7 +73,7 @@ public function __construct($rpName, $rpId, $allowedFormats=null, $useBase64UrlE
7373 */
7474 public function addRootCertificates ($ path , $ certFileExtensions =null ) {
7575 if (!\is_array ($ this ->_caFiles )) {
76- $ this ->_caFiles = array () ;
76+ $ this ->_caFiles = [] ;
7777 }
7878 if ($ certFileExtensions === null ) {
7979 $ certFileExtensions = array ('pem ' , 'crt ' , 'cer ' , 'der ' );
@@ -122,7 +122,7 @@ public function getChallenge() {
122122 * @param array $excludeCredentialIds a array of ids, which are already registered, to prevent re-registration
123123 * @return \stdClass
124124 */
125- public function getCreateArgs ($ userId , $ userName , $ userDisplayName , $ timeout =20 , $ requireResidentKey =false , $ requireUserVerification =false , $ crossPlatformAttachment =null , $ excludeCredentialIds =array () ) {
125+ public function getCreateArgs ($ userId , $ userName , $ userDisplayName , $ timeout =20 , $ requireResidentKey =false , $ requireUserVerification =false , $ crossPlatformAttachment =null , $ excludeCredentialIds =[] ) {
126126
127127 $ args = new \stdClass ();
128128 $ args ->publicKey = new \stdClass ();
@@ -166,12 +166,23 @@ public function getCreateArgs($userId, $userName, $userDisplayName, $timeout=20,
166166 $ args ->publicKey ->user ->displayName = $ userDisplayName ;
167167
168168 // supported algorithms
169- $ args ->publicKey ->pubKeyCredParams = array ();
170- $ tmp = new \stdClass ();
171- $ tmp ->type = 'public-key ' ;
172- $ tmp ->alg = -7 ; // ES256
173- $ args ->publicKey ->pubKeyCredParams [] = $ tmp ;
174- unset ($ tmp );
169+ $ args ->publicKey ->pubKeyCredParams = [];
170+
171+ if (function_exists ('sodium_crypto_sign_verify_detached ' ) || \in_array ('ed25519 ' , \openssl_get_curve_names (), true )) {
172+ $ tmp = new \stdClass ();
173+ $ tmp ->type = 'public-key ' ;
174+ $ tmp ->alg = -8 ; // EdDSA
175+ $ args ->publicKey ->pubKeyCredParams [] = $ tmp ;
176+ unset ($ tmp );
177+ }
178+
179+ if (\in_array ('prime256v1 ' , \openssl_get_curve_names (), true )) {
180+ $ tmp = new \stdClass ();
181+ $ tmp ->type = 'public-key ' ;
182+ $ tmp ->alg = -7 ; // ES256
183+ $ args ->publicKey ->pubKeyCredParams [] = $ tmp ;
184+ unset ($ tmp );
185+ }
175186
176187 $ tmp = new \stdClass ();
177188 $ tmp ->type = 'public-key ' ;
@@ -194,7 +205,7 @@ public function getCreateArgs($userId, $userName, $userDisplayName, $timeout=20,
194205 $ args ->publicKey ->challenge = $ this ->_createChallenge (); // binary
195206
196207 //prevent re-registration by specifying existing credentials
197- $ args ->publicKey ->excludeCredentials = array () ;
208+ $ args ->publicKey ->excludeCredentials = [] ;
198209
199210 if (is_array ($ excludeCredentialIds )) {
200211 foreach ($ excludeCredentialIds as $ id ) {
@@ -228,7 +239,7 @@ public function getCreateArgs($userId, $userName, $userDisplayName, $timeout=20,
228239 * string 'required' 'preferred' 'discouraged'
229240 * @return \stdClass
230241 */
231- public function getGetArgs ($ credentialIds =array () , $ timeout =20 , $ allowUsb =true , $ allowNfc =true , $ allowBle =true , $ allowHybrid =true , $ allowInternal =true , $ requireUserVerification =false ) {
242+ public function getGetArgs ($ credentialIds =[] , $ timeout =20 , $ allowUsb =true , $ allowNfc =true , $ allowBle =true , $ allowHybrid =true , $ allowInternal =true , $ requireUserVerification =false ) {
232243
233244 // validate User Verification Requirement
234245 if (\is_bool ($ requireUserVerification )) {
@@ -247,12 +258,12 @@ public function getGetArgs($credentialIds=array(), $timeout=20, $allowUsb=true,
247258 $ args ->publicKey ->rpId = $ this ->_rpId ;
248259
249260 if (\is_array ($ credentialIds ) && \count ($ credentialIds ) > 0 ) {
250- $ args ->publicKey ->allowCredentials = array () ;
261+ $ args ->publicKey ->allowCredentials = [] ;
251262
252263 foreach ($ credentialIds as $ id ) {
253264 $ tmp = new \stdClass ();
254265 $ tmp ->id = $ id instanceof ByteBuffer ? $ id : new ByteBuffer ($ id ); // binary
255- $ tmp ->transports = array () ;
266+ $ tmp ->transports = [] ;
256267
257268 if ($ allowUsb ) {
258269 $ tmp ->transports [] = 'usb ' ;
@@ -468,12 +479,7 @@ public function processGet($clientDataJSON, $authenticatorData, $signature, $cre
468479 $ dataToVerify .= $ authenticatorData ;
469480 $ dataToVerify .= $ clientDataHash ;
470481
471- $ publicKey = \openssl_pkey_get_public ($ credentialPublicKey );
472- if ($ publicKey === false ) {
473- throw new WebAuthnException ('public key invalid ' , WebAuthnException::INVALID_PUBLIC_KEY );
474- }
475-
476- if (\openssl_verify ($ dataToVerify , $ signature , $ publicKey , OPENSSL_ALGO_SHA256 ) !== 1 ) {
482+ if (!$ this ->_verifySignature ($ dataToVerify , $ signature , $ credentialPublicKey )) {
477483 throw new WebAuthnException ('invalid signature ' , WebAuthnException::INVALID_SIGNATURE );
478484 }
479485
@@ -623,4 +629,49 @@ private function _createChallenge($length = 32) {
623629 }
624630 return $ this ->_challenge ;
625631 }
632+
633+ /**
634+ * check if the signature is valid.
635+ * @param string $dataToVerify
636+ * @param string $signature
637+ * @param string $credentialPublicKey PEM format
638+ * @return bool
639+ */
640+ private function _verifySignature ($ dataToVerify , $ signature , $ credentialPublicKey ) {
641+
642+ // Use Sodium to verify EdDSA 25519 as its not yet supported by openssl
643+ if (\function_exists ('sodium_crypto_sign_verify_detached ' ) && !\in_array ('ed25519 ' , \openssl_get_curve_names (), true )) {
644+ $ pkParts = [];
645+ if (\preg_match ('/BEGIN PUBLIC KEY\-+(?:\s|\n|\r)+([^\-]+)(?:\s|\n|\r)*\-+END PUBLIC KEY/i ' , $ credentialPublicKey , $ pkParts )) {
646+ $ rawPk = \base64_decode ($ pkParts [1 ]);
647+
648+ // 30 = der sequence
649+ // 2a = length 42 byte
650+ // 30 = der sequence
651+ // 05 = lenght 5 byte
652+ // 06 = der OID
653+ // 03 = OID length 3 byte
654+ // 2b 65 70 = OID 1.3.101.112 curveEd25519 (EdDSA 25519 signature algorithm)
655+ // 03 = der bit string
656+ // 21 = length 33 byte
657+ // 00 = null padding
658+ // [...] = 32 byte x-curve
659+ $ okpPrefix = "\x30\x2a\x30\x05\x06\x03\x2b\x65\x70\x03\x21\x00" ;
660+
661+ if ($ rawPk && \strlen ($ rawPk ) === 44 && \substr ($ rawPk ,0 , \strlen ($ okpPrefix )) === $ okpPrefix ) {
662+ $ publicKeyXCurve = \substr ($ rawPk , \strlen ($ okpPrefix ));
663+
664+ return \sodium_crypto_sign_verify_detached ($ signature , $ dataToVerify , $ publicKeyXCurve );
665+ }
666+ }
667+ }
668+
669+ // verify with openSSL
670+ $ publicKey = \openssl_pkey_get_public ($ credentialPublicKey );
671+ if ($ publicKey === false ) {
672+ throw new WebAuthnException ('public key invalid ' , WebAuthnException::INVALID_PUBLIC_KEY );
673+ }
674+
675+ return \openssl_verify ($ dataToVerify , $ signature , $ publicKey , OPENSSL_ALGO_SHA256 ) === 1 ;
676+ }
626677}
0 commit comments