How to use pemCertificate method in Cypress

Best JavaScript code snippet using cypress

Run Cypress automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

certificates.js

Source: certificates.js Github

copy
1const childProcess    = require('child_process');
2const spawn           = require('child_process').spawn;
3const S               = require('string');
4const os              = require('os');
5const path            = require('path');
6const pem             = require('pem');
7const crypto          = require('crypto');
8const async           = require('async');
9const _               = require('underscore');
10const fs              = require('fs');
11const request         = require('request');
12const Utils           = require('./utils');
13
14// keep track of root certificates
15if(!global.PASSMARKED_ROOT_CERTS) global.PASSMARKED_ROOT_CERTS = [];
16
17module.exports = exports = function(payload) {
18
19  /**
20  * Get our payload data
21  **/
22  var data = payload.getData();
23
24  /**
25  * Object to return
26  **/
27  var SSL = {};
28
29  /**
30  * Merge the libraries
31  **/
32  SSL = _.extend(SSL, Utils(payload));
33
34  /** 
35  * Cache of root certificates
36  **/ 
37  var roots = [];
38
39  /**
40  * Returns a presentable form of our internal certificate layout
41  **/
42  SSL.buildPresentableCertificate = function(cert) {
43
44    // build the output
45    var output = {
46
47      pem:          cert.pem,
48      source:       cert.collection,
49      type:         cert.type,
50      commonName:   cert.commonName,
51      alt:          ((cert.san || {}).dns || []),
52      title:        cert.commonName,
53      description:  cert.commonName,
54      index:        cert.index,
55      verified:     cert.verified   === true,
56      revoked:      cert.revoked    === true,
57      signature:    cert.signature  || null,
58      bits:         cert.strength   || null,
59      crl:          cert.crl        || null,
60      ocsp:         cert.ocsp       || null,
61      url:          cert.url        || null
62
63    };
64
65    // returns the output
66    return output;
67
68  };
69
70  /**
71  * Builds the presentable chain
72  **/
73  SSL.buildPresentableChain = function(expectedPath, suppliedPath) {
74
75    // the final chain to return 
76    var chain               = [];
77
78    // sort the chain
79    var sortedChain = _.sortBy(expectedPath, 'index');
80
81    // loop the expected certificate
82    for(var i = 0; i < sortedChain.length; i++) {
83
84      // use this as the certificate
85      var cert = _.find(suppliedPath || [], function(item) {
86
87        return item.commonName === sortedChain[i].commonName;
88
89      });
90
91      // did we find this cert ?
92      if(cert) {
93
94        // add our found certificate
95        chain.push(SSL.buildPresentableCertificate(cert));
96
97      } else {
98
99        // add the certificate from the chain as it was not present
100        chain.push(SSL.buildPresentableCertificate(sortedChain[i]));
101
102      }
103
104    }
105
106    // returns the chain we generated
107    return chain;
108
109  };
110
111  /**
112  * Returns true if the given certificate fingerprint is present
113  **/
114  SSL.findByFingerprint = function(certificates, fingerprints, fn) {
115
116    // create a slug we can use to check
117    var slug    = _.pluck(fingerprints || {}, 'hash').join('-');
118
119    // loop the certificates
120    for(var i = 0; i < certificates.length; i++) {
121
122      // loop the fingerprints
123      for(var a = 0; a < (certificates[i].fingerprints || []).length; a++) {
124
125        // check if any of the fingerprints match
126        if(slug.indexOf(certificates[i].fingerprints[a].hash) !== -1) {
127
128          // return the certificate
129          return certificates[i];
130
131        }
132
133      }
134
135    }
136
137    // the default to return is null
138    return null;
139
140  };
141
142  /**
143  * Returns the possible SSL fingerprints
144  **/
145  SSL.getFingerPrintsFromPEM = function(cert, fn) {
146
147    // list of hashes to add
148    var fingerprints = [];
149
150    // general all the fingerprints
151    async.each([
152
153      'md5', 'sha1', 'sha256'
154
155    ], function (algorithm, cb) {
156
157      // set the fingerprint
158      pem.getFingerprint(cert, algorithm, function(err, prints) {
159
160        // add to list
161        if(prints && 
162            S(prints.fingerprint || '').isEmpty() === false)
163              fingerprints.push({
164
165                algorithm:  algorithm,
166                hash:       prints.fingerprint
167
168              });
169
170        // done
171        cb(err);
172
173      });
174
175    }, function(err) {
176
177      // finish
178      fn(err, fingerprints);
179
180    });
181
182  };
183
184  /**
185  * Returns the timeout to use
186  **/
187  SSL.getTimeoutCommand = function() {
188
189    // the path for timeout
190    var platform          = os.platform();
191    var timeoutCommand    = 'timeout';
192
193    // fallback to gtimeout on osx
194    if(platform === 'linux') {
195
196      // set to gtimeout
197      timeoutCommand = 'timeout ';
198
199    } else if(platform === 'darwin') {
200
201      // set to gtimeout
202      timeoutCommand = '/usr/local/bin/gtimeout ';
203
204    }
205
206    // done
207    return timeoutCommand;
208
209  };
210
211  /**
212  * Returns the exec from bin
213  **/
214  SSL.getExecutable = function(version) {
215
216    // return the full command
217    return SSL.getTimeoutCommand() + ' 10 ' + SSL.getSSLExecutable(version);
218
219  };
220
221  /**
222  * Execs the OPENSSL and first check the first testing stdout
223  **/
224  SSL.exec = function(cmd, fn) {
225
226    // load out
227    payload.debug('Running command: ' + cmd);
228
229    // check if stdout and stderror was defined
230    if(data.testingStdout || data.testingStderr) {
231
232      // return the callback
233      return fn(
234
235        null, 
236        (data.testingStdout || '').toString(), 
237        (data.testingStderr || '').toString()
238
239      );
240
241    }
242
243    // execute the actual process
244    childProcess.exec(cmd, {}, function(err, stdout, stderr) {
245
246      // check the error
247      if(err) {
248
249        // done
250        return fn(err);
251
252      }
253
254      // to string just in case
255      stdout = (stdout || '').toString();
256      stderr = (stderr || '').toString();
257
258      // done
259      fn(err, stdout, stderr);
260
261    });
262
263  };
264
265  /**
266  * Returns all the certificates from the peer certificates
267  **/
268  SSL.getPeerCertificates = function(cert, fn) {
269
270    // get the certificates
271    var certs         = [];
272    var cnames        = [];
273
274    // the current item we are looking at
275    var currentCertificate = cert;
276
277    // keep count
278    var count = 0;
279    var run   = 0;
280
281    // loop it
282    while(currentCertificate !== null) {
283
284      // check if not in list already
285      if(cnames.indexOf(currentCertificate.subject.CN) === -1) {
286
287        // add our mod
288        cnames.push(currentCertificate.subject.CN);
289
290        // add the certificate
291        certs.push(_.extend({}, currentCertificate, {
292
293          index: count
294
295        }));
296
297        // increment
298        count++;
299
300      }
301
302      // internal run count
303      run++;
304
305      // update the current certificate
306      currentCertificate = currentCertificate.issuerCertificate || null;
307
308      // stop it
309      if(currentCertificate && 
310          (currentCertificate.issuer || {}).CN === currentCertificate.subject.CN) {
311
312        // done
313        break;
314
315      }
316
317      // break if more than 30, just in case this is a "forever" loop :\
318      if(run > 30) break;
319
320    }
321
322    // done
323    fn(null, certs);
324
325  };
326
327  /**
328  * Parses the PEM and returns valid information
329  **/
330  SSL.readCertificateInfo = function(pemCertificate, fn) {
331
332    // get the details from pem
333    pem.readCertificateInfo(pemCertificate, function(err, info) {
334
335      // check for a error
336      if(err) {
337
338        // report
339        payload.error('Problem parsing details out of Pem Format', err);
340
341        // done
342        return fn(err);
343
344      }
345
346      // right so we got the info
347      info = (info || {});
348
349      // extract more variables
350      SSL.extractPEMVariables(pemCertificate, function(err, meta) {
351
352        // set our properties
353        info = _.extend(info, meta);
354
355        // done
356        fn(null, info);
357
358      });
359
360    });
361
362  };
363
364  /**
365  *
366  **/
367  SSL.verifyChain = function(certs, info, fn) {
368
369    // create the chain
370    var userCert      = certs[0];
371    var middleCerts   = certs.slice(1, certs.length).join('\n');
372
373    // get certificates by the same company
374    fn(null);
375
376  };
377
378  /**
379  * Downloads all the known certificate paths
380  **/
381  SSL.downloadPath = function(cert, index, fn) {
382
383    // the list for the path
384    var paths = [];
385
386    // sanity check
387    if(!cert) return fn(null, paths);
388
389    // limit the stack if more than 20 already
390    if(index > 20) return fn(null, paths);
391
392    // check if this is a peer certificate
393    if(index === 0) {
394
395      // convert the certificate
396      return SSL.convertDERtoPEM(cert.raw, function(err, pemCertificate) {
397
398        // check for a error
399        if(err) {
400
401          // debug
402          payload.error('Something went wrong while converting the DER to PEM', err);
403
404          // done
405          return fn(err, paths);
406
407        }
408
409        // get the finger prints of the passed certificates
410        SSL.getFingerPrintsFromPEM(pemCertificate, function(err, prints) {
411
412          // handle error
413          if(err) {
414
415            // output to stderr
416            payload.error('Problem generating fingerprints of PEM file', err);
417
418            // done
419            return fn(err, paths);
420
421          }
422
423          // get the details from pem
424          SSL.readCertificateInfo(pemCertificate, function(err, info) {
425
426            // handle error
427            if(err) {
428
429              // output to stderr
430              payload.error('Problem generating certificate info of PEM file', err);
431
432              // done
433              return fn(err, paths);
434
435            }
436
437            // add to list
438            paths.push(_.extend({}, info, {
439
440              pem:            pemCertificate,
441              der:            cert.raw.toString('base64'),
442              fingerprints:   prints,
443              collection:     'supplied',
444              index:          index,
445              type:           'user'
446
447            }));
448
449            // trigger the next certificate walk
450            SSL.downloadPath(info, index + 1, function(err, returnedPaths) {
451
452              // find any other legacy roots
453              SSL.verifyChain(_.pluck(returnedPaths, 'pem'), info, function() {
454
455                // done
456                fn(err, paths.concat(returnedPaths || []));
457
458              });
459
460            });
461
462          });
463
464        });
465
466      });
467
468    } else if(S(cert.parent || '').isEmpty() === false) {
469
470      // downloads the certificate
471      SSL.downloadCertificate(cert.parent, function(err, downloadedCert) {
472
473        // check for a error
474        if(err) {
475
476          // output
477          payload.error('Problem downloading the certificate from ' + path, err);
478
479          // done
480          return fn(err);
481
482        }
483
484        // convert the certificate
485        SSL.convertDERtoPEM(downloadedCert, function(err, pemCertificate) {
486
487          // check for a error
488          if(err) {
489
490            // debug
491            payload.error('Something went wrong while converting the DER to PEM', err);
492
493            // done
494            return fn(err);
495
496          }
497
498          // get the finger prints of the passed certificates
499          SSL.getFingerPrintsFromPEM(pemCertificate, function(err, prints) {
500
501            // get the details from pem
502            SSL.readCertificateInfo(pemCertificate, function(err, info) {
503
504              // check for a error
505              if(err) {
506
507                // debug
508                payload.error('Something went wrong checking the certificate info', err);
509
510                // done
511                return cb(err);
512
513              }
514
515              SSL.getRootCertificateByIssuer(info, function(err, rootCertificate) {
516
517                // get the type
518                var certType = 'user';
519                if(info.commonName === ((info || {}).issuer || {}).commonName)
520                  infoType = 'root';
521                else
522                  infoType = 'intermediate';
523
524                // add to list
525                paths.push(_.extend({}, info, {
526
527                  url:            cert.parent,
528                  pem:            pemCertificate.split('\n'),
529                  der:            downloadedCert.toString('base64'),
530                  fingerprints:   prints,
531                  collection:     'expected',
532                  index:          index,
533                  type:           infoType
534
535                }));
536
537                // if not the root already ?
538                // if(info.commonName == (info.issuer || {}).commonName) return fn(null, paths);
539
540                // check the path
541                if(S(info.parent || '').isEmpty() === true) {
542
543                  SSL.walkTrustedChain(info.issuer || {}, index + 1, function(err, returnedPaths) {
544
545                    // done
546                    fn(err, paths.concat(returnedPaths || []));
547
548                  });
549
550                } else {
551
552                  // trigger the next certificate walk
553                  SSL.downloadPath(info, index + 1, function(err, returnedPaths) {
554
555                    // done
556                    fn(err, paths.concat(returnedPaths || []));
557
558                  });
559
560                }
561
562              });
563
564            });
565
566          });
567
568        });
569
570      });
571
572    } else {
573
574      // done
575      fn(null, paths);
576
577    }
578
579  };
580
581  /**
582  * Checks down the chain
583  **/
584  SSL.walkTrustedChain = function(info, index, fn) {
585
586    // the list of paths to return
587    var paths = [];
588
589    // check if not over limit
590    if(index > 20) {
591
592      // stop at the limit, 20
593      return fn(null, paths);
594
595    }
596
597    // return the info if not defined
598    if(!info) {
599
600      // finish returning the path
601      return fn(null, paths);
602
603    }
604
605    // check if registed as a trusted certificate
606    SSL.getRootCertificateByIssuer(info, function(err, cert) {
607
608      // sanity check
609      if(!cert) return fn(null, paths);
610
611      // create fingerprints
612      SSL.getFingerPrintsFromPEM(cert.pem, function(err, prints) {
613
614        // get the type
615        var certType = 'user';
616        if(cert.index === 0)
617          certType = 'user';
618        else if(cert.commonName === ((cert || {}).issuer || {}).commonName)
619          certType = 'root';
620        else
621          certType = 'intermediate';
622
623        // add to list
624        paths.push(_.extend({}, info, {
625
626          pem:            cert.pem.split('\n'),
627          der:            cert.der,
628          fingerprints:   prints,
629          collection:     'expected',
630          index:          index,
631          type:           certType
632
633        }));
634
635        // check if not the root
636        if(cert.issuer && 
637            cert.issuer.commonName !== cert.commonName) {
638
639          // start to walk the actual tree
640          return SSL.walkTrustedChain(cert.issuer, index + 1, function(err, returnedCert) {
641
642            // return the paths
643            fn(null, paths.concat(returnedCert || []));
644
645          });
646
647        }
648
649        // return the paths
650        fn(null, paths);
651
652      });
653
654    });
655
656  };
657
658  /**
659  * Returns all the certificates from the peer certificates
660  **/
661  SSL.parseCertificates = function(peers, fn) {
662
663    // get the certificates
664    var certs         = [];
665
666    // loop the certificates
667    async.each(peers, function(cert, cb) {
668
669      // do te request and get all the certificates
670      SSL.convertDERtoPEM(cert.raw, function(err, pemCertificate) {
671
672        // check for a error
673        if(err) {
674
675          // debug
676          payload.error('Something went wrong while converting the DER to PEM', err);
677
678          // done
679          return cb(err);
680
681        }
682
683        // get the details from pem
684        SSL.readCertificateInfo(pemCertificate, function(err, info) {
685
686          // check for a error
687          if(err) {
688
689            // debug
690            payload.error('Something went wrong checking the certificate info', err);
691
692            // done
693            return cb(err);
694
695          }
696
697          // create fingerprints
698          SSL.getFingerPrintsFromPEM(pemCertificate, function(err, prints) {
699
700            SSL.getRootCertificateByIssuer(info, function(err, rootCertificate) {
701
702              // get the type
703              var certType = 'user';
704              if(cert.index === 0)
705                certType = 'user';
706              else if(rootCertificate && 
707                  cert.commonName === ((cert || {}).issuer || {}).commonName)
708                certType = 'root';
709              else
710                certType = 'intermediate';
711
712              // add to list
713              certs.push(_.extend({}, info, {
714
715                pem:            pemCertificate.split('\n'),
716                der:            cert.raw.toString('base64'),
717                fingerprints:   prints,
718                collection:     'supplied',
719                index:          cert.index,
720                type:           certType
721
722              }));
723
724              // done
725              cb(null);
726
727            });
728
729          });
730
731        });
732
733      });
734
735    }, function() {
736
737      // done
738      fn(null, certs);
739
740    });
741
742  };
743
744  /**
745  * Returns the CRT for the path given, but first check the cache
746  **/
747  SSL.downloadCertificate = function(path, fn) {
748
749    // create the sha1 hash
750    var shasum  = crypto.createHash('sha1');
751    shasum.update(path);
752    var hash    = shasum.digest('hex');
753
754    // build the cache key
755    var cacheKey = [
756
757      'passmarked',
758      'certificates',
759      'der',
760      hash
761
762    ].join(':');
763
764    // first check the cache for the certificate
765    payload.get(cacheKey, function(err, cachedBody) {
766
767      // did we get a error ?
768      if(!err && cachedBody) {
769
770        // return the cached version
771        return fn(null, new Buffer(cachedBody, 'base64'));
772
773      }
774
775      // do the actual request and processing
776      request({
777
778        url:      path,
779        timeout:  10 * 1000,
780        encoding: null
781
782      }, function(err, response, body) {
783
784        // check if we have a error
785        if(err) {
786
787          // output to stderr
788          payload.error('Problem downloading the given certificate from ' + path, err);
789
790          // done
791          return fn(err);
792
793        }
794
795        // check if we got the certificate
796        if((response || {}).statusCode === 200) {
797
798          // all good set in the cache and return
799          return payload.set(cacheKey, body.toString('base64'), function(err) {
800
801            // just output the error setting this in the cache
802            if(err) payload.error('Problem settings the certificate from ' + path + ' in the cache', err);
803
804            // just move on
805            fn(null, body);
806
807          });
808
809        }
810
811        // nope was not able to get the certificate
812        fn(null);
813
814      });
815
816    });
817
818  };
819
820  // return object instance
821  return SSL;
822
823};
Full Screen

safari-ssl-e2e-specs.js

Source: safari-ssl-e2e-specs.js Github

copy
1import chai from 'chai';
2import chaiAsPromised from 'chai-as-promised';
3import _ from 'lodash';
4import B from 'bluebird';
5import { MOCHA_TIMEOUT, initSession, deleteSession } from '../helpers/session';
6import { doesIncludeCookie, doesNotIncludeCookie,
7         newCookie, oldCookie1 } from './safari-basic-e2e-specs';
8import { SAFARI_CAPS } from '../desired';
9import https from 'https';
10
11
12const pem = B.promisifyAll(require('pem'));
13
14chai.should();
15chai.use(chaiAsPromised);
16
17const HTTPS_PORT = 9762;
18
19const LOCAL_HTTPS_URL = `https://localhost:${HTTPS_PORT}/`;
20
21const caps = _.defaults({
22  safariInitialUrl: LOCAL_HTTPS_URL,
23  noReset: true,
24}, SAFARI_CAPS);
25
26let pemCertificate;
27
28if (!process.env.REAL_DEVICE && !process.env.CLOUD) {
29  describe.skip('Safari SSL', function () {
30    this.timeout(MOCHA_TIMEOUT);
31
32    let sslServer, driver;
33    before(async function () {
34      // Create a random pem certificate
35      const privateKey = await pem.createPrivateKeyAsync();
36      const keys = await pem.createCertificateAsync({
37        days: 1,
38        selfSigned: true,
39        serviceKey: privateKey.key,
40        altNames: ['localhost'],
41      });
42      pemCertificate = keys.certificate;
43
44      // Host an SSL server that uses that certificate
45      const serverOpts = {key: keys.serviceKey, cert: pemCertificate};
46      sslServer = https.createServer(serverOpts, (req, res) => {
47        res.end('Arbitrary text');
48      }).listen(HTTPS_PORT);
49
50      caps.customSSLCert = pemCertificate;
51    });
52    after(async function () {
53      await deleteSession();
54      if (sslServer) {
55        await sslServer.close();
56      }
57    });
58
59    it('should open pages with untrusted certs if the cert was provided in desired capabilities', async function () {
60      try {
61        driver = await initSession(caps);
62        await driver.source().should.eventually.include('Arbitrary text');
63        await driver.quit();
64        await B.delay(1000);
65
66        // Now do another session using the same cert to verify that it still works
67        // (Don't do it on CLOUD. Restarting is too slow)
68        if (!process.env.CLOUD) {
69          await driver.init(caps);
70          await driver.get(LOCAL_HTTPS_URL);
71          await driver.source().should.eventually.include('Arbitrary text');
72        }
73      } finally {
74        await deleteSession();
75      }
76    });
77
78    describe('cookies', function () {
79      const secureCookie = Object.assign({}, newCookie, {
80        secure: true,
81        name: 'securecookie',
82        value: 'this is a secure cookie',
83      });
84
85      before(async function () {
86        driver = await initSession(caps);
87      });
88
89      beforeEach(async function () {
90        await driver.get(LOCAL_HTTPS_URL);
91        await driver.setCookie(oldCookie1);
92        await driver.deleteCookie(secureCookie.name);
93      });
94
95      it('should be able to set a secure cookie', async function () {
96        let cookies = await driver.allCookies();
97        doesNotIncludeCookie(cookies, secureCookie);
98
99        await driver.setCookie(secureCookie);
100        cookies = await driver.allCookies();
101
102        doesIncludeCookie(cookies, secureCookie);
103
104        // should not clobber old cookie
105        doesIncludeCookie(cookies, oldCookie1);
106      });
107      it('should be able to set a secure cookie', async function () {
108        await driver.setCookie(secureCookie);
109        let cookies = await driver.allCookies();
110
111        doesIncludeCookie(cookies, secureCookie);
112
113        // should not clobber old cookie
114        doesIncludeCookie(cookies, oldCookie1);
115
116        await driver.deleteCookie(secureCookie.name);
117
118        cookies = await driver.allCookies();
119        doesNotIncludeCookie(cookies, secureCookie);
120      });
121    });
122  });
123}
124
Full Screen

webauthn-utils.js

Source: webauthn-utils.js Github

copy
1const crypto    = require('crypto');
2const base64url = require('base64url');
3const cbor      = require('cbor');
4const fs        = require('fs');
5const { Certificate } = require('@fidm/x509');
6
7
8let userVerificationDefault = "preferred"; //userVerification - can be set to "required", "preferred", "discouraged". More in WebAuthn specification. Default set to "preferred"
9
10
11/**
12 * U2F Presence constant
13 */
14let U2F_USER_PRESENTED = 0x01;
15
16/**
17 * Takes signature, data and PEM public key and tries to verify signature
18 * @param  {Buffer} signature
19 * @param  {Buffer} data
20 * @param  {String} publicKey - PEM encoded public key
21 * @return {Boolean}
22 */
23let verifySignature = (signature, data, publicKey) => {
24    return crypto.createVerify('SHA256')
25        .update(data)
26        .verify(publicKey, signature);
27}
28
29
30/**
31 * Returns base64url encoded buffer of the given length
32 * @param  {Number} len - length of the buffer
33 * @return {String}     - base64url random buffer
34 */
35let randomBase64URLBuffer = (len) => {
36    len = len || 32;
37    let buff = crypto.randomBytes(len);
38    return base64url(buff);
39}
40
41/**
42 * Generates makeCredentials request
43 * @param  {String} username       - username
44 * @param  {String} displayName    - user's personal display name
45 * @param  {String} id             - user's base64url encoded id
46 * @return {MakePublicKeyCredentialOptions} - server encoded make credentials request
47 */
48let generateServerMakeCredRequest = (userVerification, requireResidentKey, id, username, displayName) => {
49    if( userVerification == null) userVerification = userVerificationDefault;
50    return {
51        attestation: 'direct',
52        authenticatorSelection : {
53            requireResidentKey: requireResidentKey,
54            userVerification: userVerification
55        },
56        challenge: randomBase64URLBuffer(32),
57        pubKeyCredParams: [
58            {
59                type: "public-key", alg: -7 // "ES256" IANA COSE Algorithms registry
60            }
61        ],
62        rp: {
63            //id: "fido.demo.gemalto.com",
64            name: "Thales FIDO Demo"
65        },
66        timeout: 90000,
67        user: {
68            id: id,
69            name: username,
70            displayName: displayName
71        }
72    }
73}
74
75/**
76 * Generates getAssertion request
77 * @param  {Array} authenticators              - list of registered authenticators
78 * @return {PublicKeyCredentialRequestOptions} - server encoded get assertion request
79 */
80let generateServerGetAssertion = (userVerification, authenticators) => {
81    if( userVerification == null) userVerification = userVerificationDefault;
82
83    let allowCredentials = [];
84    for(let authr of authenticators) {
85        allowCredentials.push({
86              type: 'public-key',
87              id: authr.credID
88              //,transports: ['usb', 'nfc', 'ble']
89        })
90    }
91    return {
92        challenge: randomBase64URLBuffer(32)
93        ,timeout: 60000
94        ,allowCredentials: allowCredentials
95        ,userVerification: userVerification
96    }
97}
98
99
100/**
101 * Returns SHA-256 digest of the given data.
102 * @param  {Buffer} data - data to hash
103 * @return {Buffer}      - the hash
104 */
105let hash = (data) => {
106    return crypto.createHash('SHA256').update(data).digest();
107}
108
109/**
110 * Takes COSE encoded public key and converts it to RAW PKCS ECDHA key
111 * @param  {Buffer} COSEPublicKey - COSE encoded public key
112 * @return {Buffer}               - RAW PKCS encoded public key
113 */
114let COSEECDHAtoPKCS = (COSEPublicKey) => {
115    /* 
116       +------+-------+-------+---------+----------------------------------+
117       | name | key   | label | type    | description                      |
118       |      | type  |       |         |                                  |
119       +------+-------+-------+---------+----------------------------------+
120       | crv  | 2     | -1    | int /   | EC Curve identifier - Taken from |
121       |      |       |       | tstr    | the COSE Curves registry         |
122       |      |       |       |         |                                  |
123       | x    | 2     | -2    | bstr    | X Coordinate                     |
124       |      |       |       |         |                                  |
125       | y    | 2     | -3    | bstr /  | Y Coordinate                     |
126       |      |       |       | bool    |                                  |
127       |      |       |       |         |                                  |
128       | d    | 2     | -4    | bstr    | Private key                      |
129       +------+-------+-------+---------+----------------------------------+
130    */
131
132    let coseStruct = cbor.decodeAllSync(COSEPublicKey)[0];
133    let tag = Buffer.from([0x04]);
134    let x   = coseStruct.get(-2);
135    let y   = coseStruct.get(-3);
136
137    return Buffer.concat([tag, x, y])
138}
139
140/**
141 * Convert binary certificate or public key to an OpenSSL-compatible PEM text format.
142 * @param  {Buffer} buffer - Cert or PubKey buffer
143 * @return {String}             - PEM
144 */
145let ASN1toPEM = (pkBuffer) => {
146    if (!Buffer.isBuffer(pkBuffer))
147        throw new Error("ASN1toPEM: pkBuffer must be Buffer.")
148
149    let type;
150    if (pkBuffer.length == 65 && pkBuffer[0] == 0x04) {
151        /*
152            If needed, we encode rawpublic key to ASN structure, adding metadata:
153            SEQUENCE {
154              SEQUENCE {
155                 OBJECTIDENTIFIER 1.2.840.10045.2.1 (ecPublicKey)
156                 OBJECTIDENTIFIER 1.2.840.10045.3.1.7 (P-256)
157              }
158              BITSTRING <raw public key>
159            }
160            Luckily, to do that, we just need to prefix it with constant 26 bytes (metadata is constant).
161        */
162        
163        pkBuffer = Buffer.concat([
164            new Buffer.from("3059301306072a8648ce3d020106082a8648ce3d030107034200", "hex"),
165            pkBuffer
166        ]);
167
168        type = 'PUBLIC KEY';
169    } else {
170        type = 'CERTIFICATE';
171    }
172
173    let b64cert = pkBuffer.toString('base64');
174    return formatPEM(b64cert, type);
175
176    /*
177    let PEMKey = '';
178    for(let i = 0; i < Math.ceil(b64cert.length / 64); i++) {
179        let start = 64 * i;
180
181        PEMKey += b64cert.substr(start, 64) + '\n';
182    }
183
184    PEMKey = `-----BEGIN ${type}-----\n` + PEMKey + `-----END ${type}-----\n`;
185    
186    return PEMKey
187    */
188}
189
190let formatPEM = (b64cert, type = "CERTIFICATE") => {
191    let PEMKey = '';
192    for(let i = 0; i < Math.ceil(b64cert.length / 64); i++) {
193        let start = 64 * i;
194        PEMKey += b64cert.substr(start, 64) + '\n';
195    }
196    PEMKey = `-----BEGIN ${type}-----\n` + PEMKey + `-----END ${type}-----\n`;
197    return PEMKey;
198}
199
200/**
201 * Parses authenticatorData buffer.
202 * @param  {Buffer} buffer - authenticatorData buffer
203 * @return {Object}        - parsed authenticatorData struct
204 */
205let parseMakeCredAuthData = (buffer) => {
206    let rpIdHash      = buffer.slice(0, 32);          buffer = buffer.slice(32);
207    let flagsBuf      = buffer.slice(0, 1);           buffer = buffer.slice(1);
208    let flags         = flagsBuf[0];
209    let counterBuf    = buffer.slice(0, 4);           buffer = buffer.slice(4);
210    let counter       = counterBuf.readUInt32BE(0);
211    let aaguid        = buffer.slice(0, 16);          buffer = buffer.slice(16);
212    let credIDLenBuf  = buffer.slice(0, 2);           buffer = buffer.slice(2);
213    let credIDLen     = credIDLenBuf.readUInt16BE(0);
214    let credID        = buffer.slice(0, credIDLen);   buffer = buffer.slice(credIDLen);
215    let COSEPublicKey = buffer;
216
217    return {rpIdHash, flagsBuf, flags, counter, counterBuf, aaguid, credID, COSEPublicKey}
218}
219
220let verifyAuthenticatorAttestationResponse = (webAuthnResponse) => {
221    console.log("verifyAuthenticatorAttestationResponse");
222    let attestationBuffer    = base64url.toBuffer(webAuthnResponse.response.attestationObject);
223    let attestationObject    = cbor.decodeAllSync(attestationBuffer)[0];
224
225    /*
226    let details = " type: self";
227    let attestationType = "self";
228    if( ctapMakeCredResp.attStmt !== undefined && ctapMakeCredResp.attStmt.x5c !== undefined) {
229        details += " type: AttCA with X5C length: " + ctapMakeCredResp.attStmt.x5c.length;
230        attestationType = "AttCA";
231    }
232    if( ctapMakeCredResp.attStmt !== undefined && ctapMakeCredResp.attStmt.ecdaaKeyId !== undefined) {
233        details += " type: ECDAA";
234        attestationType = "ECDAA";
235    }
236    console.log("Attestation received: " + ctapMakeCredResp.fmt + " " + details);
237    */
238
239    console.log("Attestation received: " + attestationObject.fmt);
240
241    let authDataBuffer = attestationObject.authData;
242    attestationObject.authData = parseMakeCredAuthData(authDataBuffer);
243    let clientDataHash  = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON))
244
245    let response = {'verified': false, 'fmt': attestationObject.fmt, 'message': "" , 'log' : '', attestationObject: attestationObject };
246
247    if(attestationObject.fmt === 'fido-u2f') {
248
249        response = u2fAttestation(attestationObject, clientDataHash);
250
251        /*
252        if(!(attestationObject.authData.flags & U2F_USER_PRESENTED))
253            throw new Error('User was NOT presented durring authentication!');
254
255        let reservedByte    = Buffer.from([0x00]);
256        let publicKey       = COSEECDHAtoPKCS(attestationObject.authData.COSEPublicKey)
257        let signatureBase   = Buffer.concat([reservedByte, attestationObject.authData.rpIdHash, clientDataHash, attestationObject.authData.credID, publicKey]);
258
259        let PEMCertificate = ASN1toPEM(attestationObject.attStmt.x5c[0]);
260        let signature      = attestationObject.attStmt.sig;
261
262        
263        // console.log(PEMCertificate);
264        // console.log(ctapMakeCredResp.attStmt.sig.length);
265        // console.log(signature.toString("hex"));
266        // console.log(hash(signature).toString("hex"));   
267        // console.log(ctapMakeCredResp.attStmt.sig.length - 70);
268        // let signature = Buffer.allocUnsafe(70);
269        // ctapMakeCredResp.attStmt.sig.copy(signature,0, ctapMakeCredResp.attStmt.sig.length - 70)
270        
271
272        
273        let maxTrailingZero = 0;
274        for(let i = 0; i < signature.length ; i++) {
275            if( signature.readInt8(i) === 0)
276                maxTrailingZero = i + 1;
277            else 
278                break;
279        }
280
281        if( maxTrailingZero > 0) {
282            console.log("WARNING - I modifiy the length");
283            signature = signature.slice(maxTrailingZero);
284        }
285
286
287        console.log(signature.length);
288        console.log(signature);
289
290        
291        // console.log(signature);
292        // console.log(signatureBase);
293        // console.log(base64url.decode(webAuthnResponse.response.clientDataJSON));
294        // console.log(hash(signatureBase).toString("hex"));
295
296        // Save cert
297        let filename = saveCertificate(PEMCertificate);
298
299
300        response.verified = verifySignature(signature, signatureBase, PEMCertificate);
301        if( !response.verified ) {
302            console.log("Invalid Signature");
303        }
304        
305        // Try to detect device
306        let cert = Certificate.fromPEM(PEMCertificate);
307        let aaguid = '';
308
309        // The certificate should contain the product ID in this OID
310        for(let i = 0 ; i < cert.extensions.length ; i ++) {
311            if( cert.extensions[i].oid === "1.3.6.1.4.1.45724.1.1.4") {
312                aaguid = cert.extensions[i].value.slice(2);
313                break;
314            }   
315        }
316
317        // Try to get the first OID
318        if( aaguid.length <= 0 ) {
319            for(let i = 0 ; i < cert.extensions.length ; i ++) {
320                // Transport OID "1.3.6.1.4.1.45724.2.1.1"
321                if( cert.extensions[i].oid.indexOf("1.3.6.1.4.1.") == 0 && cert.extensions[i].oid.indexOf("1.3.6.1.4.1.45724.2.1.1") !== 0 ) {
322                    aaguid = cert.extensions[i].value;
323                    break;
324                }    
325            }
326        }
327
328
329        if(response.verified) {
330            console.log("Attestation Verified");
331            response.attestationObject.attStmt.sig = response.attestationObject.attStmt.sig.toString('base64');
332            response.authrInfo = {
333                fmt: 'fido-u2f',
334                aaguid: convertAAGUID(aaguid),
335                publicKey: base64url.encode(publicKey),
336                counter: attestationObject.authData.counter,
337                credID: base64url.encode(attestationObject.authData.credID),
338                cert: filename
339            }
340        }
341        else
342            console.log("Attestation NOT Verified");
343            */
344    } 
345    else if (attestationObject.fmt === 'packed' && attestationObject.attStmt.x5c !== undefined) {        
346        response = packedAttestation(attestationObject, clientDataHash, authDataBuffer);
347
348/*
349        // https://www.w3.org/TR/webauthn/#packed-attestation
350        
351        let authrDataStruct = parseMakeCredAuthData(ctapMakeCredResp.authData);
352
353        let clientDataHash  = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON))
354        let publicKey       = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey)
355
356        // Step 1 - Verify that sig is a valid signature
357        let signatureBase   = Buffer.concat([ctapMakeCredResp.authData, clientDataHash]);
358        let PEMCertificate = ASN1toPEM(ctapMakeCredResp.attStmt.x5c[0]);
359        let signature      = ctapMakeCredResp.attStmt.sig;
360        response.verified = verifySignature(signature, signatureBase, PEMCertificate);
361        if( !response.verified ) response.message = "Invalid Signature";
362
363        // Save cert
364        let filename = "cert/" + crypto.createHash('md5').update(authrDataStruct.credID).digest("hex") + ".crt";
365        fs.writeFileSync( "static/" + filename, PEMCertificate);
366
367        // Step 2 - ???
368
369        // Step 3 - if OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) 
370        //          verify that the value of this extension matches the aaguid in authenticatorData.
371        
372        // DEBUG: PEMCertificate = fs.readFileSync("cert/card.crt");
373        //PEMCertificate = fs.readFileSync("cert/ctap_fpvcFk2ZxU8RsZDihqbO23ARBn4sKK_Y0akjna8tAhIt2vfCWgw29_F5KrWFaAb4-PEjYjW_lqPMgccDmwrEVu6CaEQsEaektvPLiig.crt");
374
375        let checkAAGUID = true;
376        if( checkAAGUID )
377        {
378            let cert = Certificate.fromPEM(PEMCertificate);
379            for(let i = 0 ; i < cert.extensions.length ; i ++) {
380                if( cert.extensions[i].oid === "1.3.6.1.4.1.45724.1.1.4") {
381
382                    let certValue = Buffer.from(cert.extensions[i].value, 2);
383                    response.verified = certValue.equals(authrDataStruct.aaguid);
384                    if( !response.verified ) {   
385                        response.message    = "Invalid AAGUID";
386                        response.log        = "Invalid AAGUID [" + JSON.stringify(authrDataStruct.aaguid) + "] [" + JSON.stringify(certValue) + "]";
387                        console.log(response.log);
388                    }
389                    break;
390                }
391            }
392        }
393
394
395        if(response.verified) {
396            response.authrInfo = {
397                fmt: 'packed',
398                aaguid: authrDataStruct.aaguid.toString('hex'),
399                publicKey: base64url.encode(publicKey),
400                counter: authrDataStruct.counter,
401                credID: base64url.encode(authrDataStruct.credID),
402                cert: filename
403            }
404        }
405        */
406    }
407    /*
408    else if (ctapMakeCredResp.fmt === "android-key")
409    {
410        let authrDataStruct = parseMakeCredAuthData(ctapMakeCredResp.authData);
411
412        let clientDataHash  = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON))
413        let publicKey       = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey)        
414
415        // Step 1 - Verify that sig is a valid signature
416        let signatureBase   = Buffer.concat([ctapMakeCredResp.authData, clientDataHash]);
417        let PEMCertificate = ASN1toPEM(ctapMakeCredResp.attStmt.x5c[0]);
418        let signature      = ctapMakeCredResp.attStmt.sig;
419        response.verified = verifySignature(signature, signatureBase, PEMCertificate);
420        if( !response.verified ) response.message = "Invalid Signature";
421
422        // Save cert
423        let filename = "cert/" + crypto.createHash('md5').update(authrDataStruct.credID).digest("hex") + ".crt";
424        fs.writeFileSync( "static/" + filename, PEMCertificate);
425
426        if(response.verified) {
427            response.authrInfo = {
428                fmt: ctapMakeCredResp.fmt,
429                aaguid: authrDataStruct.aaguid.toString('hex'),
430                publicKey: base64url.encode(publicKey),
431                counter: authrDataStruct.counter,
432                credID: base64url.encode(authrDataStruct.credID),
433                cert: filename
434            }
435        }
436    }
437    */
438    else if (ctapMakeCredResp.fmt === "android-safetynet")
439    {
440        response = androidSafetynetAttestation(attestationObject, clientDataHash);
441
442        /*
443        let authrDataStruct = parseMakeCredAuthData(ctapMakeCredResp.authData);
444
445        let clientDataHash  = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON))
446        let publicKey       = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey);
447
448        let ver = ctapMakeCredResp.attStmt.ver;
449        let response = ctapMakeCredResp.attStmt.response.toString("utf-8");
450        
451        console.log(ver);
452
453        let jwsArray = response.split(".");
454        if( jwsArray.length )
455
456        let nonce   = Buffer.concat([ctapMakeCredResp.authData, clientDataHash]).toString('base64');
457        */
458
459
460        /*
461        // Step 1 - Verify that sig is a valid signature
462        let signatureBase   = Buffer.concat([ctapMakeCredResp.authData, clientDataHash]);
463        let PEMCertificate  = ASN1toPEM(ctapMakeCredResp.attStmt.x5c[0]);
464        let signature       = ctapMakeCredResp.attStmt.sig;
465        response.verified   = verifySignature(signature, signatureBase, PEMCertificate);
466        if( !response.verified ) response.message = "Invalid Signature";
467
468        // Save cert
469        let filename = "cert/" + crypto.createHash('md5').update(authrDataStruct.credID).digest("hex") + ".crt";
470        fs.writeFileSync( "static/" + filename, PEMCertificate);
471
472
473        if(response.verified) {
474            response.authrInfo = {
475                fmt: ctapMakeCredResp.fmt,
476                aaguid: authrDataStruct.aaguid.toString('hex'),
477                publicKey: base64url.encode(publicKey),
478                counter: authrDataStruct.counter,
479                credID: base64url.encode(authrDataStruct.credID),
480                cert: filename
481            }
482        }
483        */
484    }
485    else
486    {                
487        response.authrInfo = {
488            fmt: attestationObject.fmt,            
489        }
490        response.message = 'Unsupported attestation [' + attestationObject.fmt + "]"; 
491    }
492    
493    // Format attestationObject for logs
494    if( response.verified == true)
495    {
496        attestationLog = {attStmt: {x5c: []}, authData: {credentialData: {}}};
497
498        // Prepare "attStmt"
499        attestationLog.attStmt.sig = attestationObject.attStmt.sig.toString('base64');
500        attestationObject.attStmt.x5c.forEach(x5c => attestationLog.attStmt.x5c.push(x5c.toString('base64')));
501
502        // Prepare "credentialData"
503        attestationLog.authData.credentialData.aaguid           = attestationObject.authData.aaguid.toString('base64');
504        attestationLog.authData.credentialData.credentialId     = attestationObject.authData.credID.toString('base64');
505        attestationLog.authData.credentialData.rpIdHash         = attestationObject.authData.rpIdHash.toString('base64');
506        attestationLog.authData.credentialData.signatureCounter = attestationObject.authData.signatureCounter;
507
508        // Prepare "fmt"
509        attestationLog.fmt = attestationObject.fmt;
510        
511        // Assign the log object
512        response.attestationObject = attestationLog;
513
514        /*
515              "credentialData": {
516        "aaguid": "AAAAAAAAAAAAAAAAAAAAAA==",
517        "credentialId": "aRaHCZ8z63X946K6WFwE5+Naqcc0P3mw46/23s4dHMc5xmjuAmVav4wiAl1LjHlimW2ABKYFl4govGNffdNrktNiU6xr8qNSCh+mqP5MJI6DRq4Z65o5QkABkG1ElcZXsO83ACCFL9JAeRj9X9ufqG4qC31v91Sjk9v2gunfnrcda6fRtNBA9yn9/ONoxbzuXg5LeGV7MRM6NNUwrCREYQ==",
518        "publicKey": {
519          "1": 2,
520          "3": -7,
521          "-1": 1,
522          "-2": "ia56X+doqE2bb+rmkT5gM6jQZe2zfb6xAHR55uM6Lyc=",
523          "-3": "fWnMFdHhu9An/oguPJLHTarRHdFCjGTEiO9lOW8hJmE="
524        }
525      },
526      "flags": {
527        "AT": true,
528        "ED": false,
529        "UP": true,
530        "UV": false,
531        "value": 65
532      },
533      "rpIdHash": "xGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7c=",
534      "signatureCounter": 0   },
535    "fmt": "fido-u2f"
536      */
537
538    }
539
540    return response
541}
542
543/**
544 * save cert
545 * @param  {Buffer} data    - certificate Data
546 * @return {String}         - filename
547 */
548
549let saveCertificate = (data) => {
550    let filename = "cert/" + crypto.createHash('md5').update(data).digest("hex") + ".crt";
551    fs.writeFileSync( "static/" + filename, data);
552    return filename;
553}
554
555
556function u2fAttestation(attestationObject, clientDataHash) {
557
558    if(!(attestationObject.authData.flags & U2F_USER_PRESENTED)) {        
559        console.log("User presented FLAG");
560        return {verified: false, message: "User presented FLAG"};
561    }
562
563    let reservedByte    = Buffer.from([0x00]);
564    let publicKey       = COSEECDHAtoPKCS(attestationObject.authData.COSEPublicKey)
565    let signatureBase   = Buffer.concat([reservedByte, attestationObject.authData.rpIdHash, clientDataHash, attestationObject.authData.credID, publicKey]);
566
567    let PEMCertificate = ASN1toPEM(attestationObject.attStmt.x5c[0]);
568    let signature      = attestationObject.attStmt.sig;
569
570    let maxTrailingZero = 0;
571    for(let i = 0; i < signature.length ; i++) {
572        if( signature.readInt8(i) === 0)
573            maxTrailingZero = i + 1;
574        else 
575            break;
576    }
577    if( maxTrailingZero > 0) {
578        console.log("WARNING - I modify the length");
579        signature = signature.slice(maxTrailingZero);
580    }
581
582    // Save cert
583    let filename = saveCertificate(PEMCertificate);
584
585    if( !verifySignature(signature, signatureBase, PEMCertificate) ) {
586        console.log("Invalid Signature");
587        return {verified: false, message: "Invalid Signature"};
588    }
589
590    // Try to detect device
591    let cert    = Certificate.fromPEM(PEMCertificate);
592    let aaguid  = '';
593
594    // The certificate should contain the product ID in this OID
595    for(let i = 0 ; i < cert.extensions.length ; i ++) {
596        if( cert.extensions[i].oid === "1.3.6.1.4.1.45724.1.1.4") {
597            aaguid = cert.extensions[i].value.slice(2);
598            break;
599        }   
600    }
601
602    // Try to get the first OID
603    if( aaguid.length <= 0 ) {
604        for(let i = 0 ; i < cert.extensions.length ; i ++) {
605            // Transport OID "1.3.6.1.4.1.45724.2.1.1"
606            if( cert.extensions[i].oid.indexOf("1.3.6.1.4.1.") == 0 && cert.extensions[i].oid.indexOf("1.3.6.1.4.1.45724.2.1.1") !== 0 ) {
607                aaguid = cert.extensions[i].value;
608                break;
609            }    
610        }
611    }
612
613
614    console.log("U2F Attestation Verified");
615
616    let authrInfo = {
617        fmt: 'fido-u2f',
618        aaguid: convertAAGUID(aaguid),
619        publicKey: base64url.encode(publicKey),
620        counter: attestationObject.authData.counter,
621        credID: base64url.encode(attestationObject.authData.credID),
622        cert: filename };
623
624
625    return {verified: true, authrInfo: authrInfo, message: "OK", attestationObject: attestationObject};
626}
627
628
629
630
631
632/**
633 * Validate packed attestation
634 * @param  {Buffer} attestationObject   - ctapMakeCred buffer
635 * @param  {String} clientDataHash      - hash
636 * @param  {String} authDataBuffer      - Original buffer
637 * @return {Object}                     - parsed authenticatorData struct
638 */
639let packedAttestation = (attestationObject, clientDataHash, authDataBuffer) => {
640
641    // https://www.w3.org/TR/webauthn/#packed-attestation
642    
643    let publicKey       = COSEECDHAtoPKCS(attestationObject.authData.COSEPublicKey);
644
645    // Step 1 - Verify that sig is a valid signature
646    let signatureBase  = Buffer.concat([authDataBuffer, clientDataHash]);
647    let PEMCertificate = ASN1toPEM(attestationObject.attStmt.x5c[0]);
648    let signature      = attestationObject.attStmt.sig;
649    if( !verifySignature(signature, signatureBase, PEMCertificate) ) 
650    {
651        console.log("invalid Signature");
652        return {verified: false, message: "invalid Signature"};
653    }
654
655    // Save cert
656    let filename = saveCertificate(PEMCertificate);
657
658    // Step 2 - ???
659
660    // Step 3 - if OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) 
661    //          verify that the value of this extension matches the aaguid in authenticatorData.
662   
663    // DEBUG: PEMCertificate = fs.readFileSync("cert/card.crt");
664    //PEMCertificate = fs.readFileSync("cert/ctap_fpvcFk2ZxU8RsZDihqbO23ARBn4sKK_Y0akjna8tAhIt2vfCWgw29_F5KrWFaAb4-PEjYjW_lqPMgccDmwrEVu6CaEQsEaektvPLiig.crt");
665
666    let checkAAGUID = true;
667    if( checkAAGUID )
668    {
669        let cert = Certificate.fromPEM(PEMCertificate);
670        for(let i = 0 ; i < cert.extensions.length ; i ++) {
671            if( cert.extensions[i].oid === "1.3.6.1.4.1.45724.1.1.4") {
672
673                let certValue = cert.extensions[i].value.slice(2);
674                if( !certValue.equals(attestationObject.authData.aaguid)) {
675                    let log = "Invalid AAGUID [" + JSON.stringify(attestationObject.authData.aaguid) + "] [" + JSON.stringify(certValue) + "]";
676                    console.log(log);
677                    return {verified: false, message: "invalid AAGUID", log: log};
678                }
679                break;
680            }
681        }
682    }
683
684    let authrInfo = {
685            fmt: attestationObject.fmt,
686            aaguid: convertAAGUID(attestationObject.authData.aaguid),
687            publicKey: base64url.encode(publicKey),
688            counter: attestationObject.authData.counter,
689            credID: base64url.encode(attestationObject.authData.credID),
690            cert: filename
691        };    
692        
693    console.log("Attestation is valid");
694    return {verified: true, authrInfo: authrInfo, message: "OK", attestationObject: attestationObject};
695}
696
697
698let convertAAGUID  = (byteArray) => {
699    if( byteArray.length == 18 )
700        byteArray = byteArray.slice(2);
701    let aaguid = byteArray.toString('hex').toLowerCase();
702    aaguid = aaguid.slice(0,8) + '-' + aaguid.slice(8);
703    aaguid = aaguid.slice(0,13) + '-' + aaguid.slice(13);
704    aaguid = aaguid.slice(0,18) + '-' + aaguid.slice(18);
705    aaguid = aaguid.slice(0,23) + '-' + aaguid.slice(23);
706    return aaguid;
707}
708
709
710
711/**
712 * Validate android-safetynet attestation
713 * @param  {Buffer} attestationObject    - ctapMakeCred buffer
714 * @param  {String} clientDataHash      - hash
715 * @return {Object}                     - parsed authenticatorData struct
716 */
717let androidSafetynetAttestation = (attestationObject, clientDataHash) => {
718
719    //let authrDataStruct = parseMakeCredAuthData(attestationObject.authData);
720    let publicKey       = COSEECDHAtoPKCS(attestationObject.authData.COSEPublicKey);
721
722    let ver      = attestationObject.attStmt.ver;
723    let response = attestationObject.attStmt.response.toString("utf-8");
724    
725    let jwsArray = response.split(".");
726    if( jwsArray.length <= 2 )
727        return {verified: false, message: "invalid JWS attestation"}
728
729    // STEP 1 - Get certificate
730    let attestation = JSON.parse(base64url.decode(jwsArray[0]));
731    let PEMCertificate  = formatPEM(attestation.x5c[0]);
732
733    // Save cert    
734    let filename = saveCertificate(PEMCertificate);
735
736    // Compare hostname
737    let cert = Certificate.fromPEM(PEMCertificate);
738    let hostnameIsValid = false;
739    for(let i = 0 ; i < cert.subject.attributes.length ; i++) {
740        if(( cert.subject.attributes[i].shortName === 'CN' ) && (cert.subject.attributes[i].value === 'attest.android.com'))
741            hostnameIsValid = true;
742    }
743    if( !hostnameIsValid ) 
744        return {verified: false, message: "invalid Hostname"}
745    
746
747    // STEP 2 - Verify nonce
748    let jws     = JSON.parse(base64url.decode(jwsArray[1]));
749    let nonce   = crypto.createHash('sha256').update(Buffer.concat([attestationObject.authData, clientDataHash])).digest().toString('base64'); 
750
751    if( nonce != jws.nonce)
752        return {verified: false, message: "invalid nonce"}
753
754    // STEP 2 - Verify ctsProfileMatch = true
755    if( jws.ctsProfileMatch !== true)
756        return {verified: false, message: "invalid ctsProfileMatch"}
757
758
759    let authrInfo = {
760            fmt: attestationObject.fmt,
761            aaguid: attestationObject.authData.aaguid.toString('hex'),
762            publicKey: base64url.encode(publicKey),
763            counter: attestationObject.authData.counter,
764            credID: base64url.encode(attestationObject.authData.credID),
765            cert: filename
766        };
767    
768        
769    console.log("Attestation is valid");
770    return {verified: true, authrInfo: authrInfo, message: "OK", attestationObject: attestationObject};
771}
772
773
774
775
776
777
778
779/**
780 * Takes an array of registered authenticators and find one specified by credID
781 * @param  {String} credID        - base64url encoded credential
782 * @param  {Array} authenticators - list of authenticators
783 * @return {Object}               - found authenticator
784 */
785let findAuthr = (credID, authenticators) => {
786    for(let authr of authenticators) {
787        if(authr.credID === credID)
788            return authr
789    }
790    throw new Error(`Unknown authenticator with credID ${credID}!`)
791}
792
793/**
794 * Parses AuthenticatorData from GetAssertion response
795 * @param  {Buffer} buffer - Auth data buffer
796 * @return {Object}        - parsed authenticatorData struct
797 */
798let parseGetAssertAuthData = (buffer) => {
799    let rpIdHash      = buffer.slice(0, 32);          buffer = buffer.slice(32);
800    let flagsBuf      = buffer.slice(0, 1);           buffer = buffer.slice(1);
801    let flags         = flagsBuf[0];
802    let counterBuf    = buffer.slice(0, 4);           buffer = buffer.slice(4);
803    let counter       = counterBuf.readUInt32BE(0);
804
805    return {rpIdHash, flagsBuf, flags, counter, counterBuf}
806}
807
808let verifyAuthenticatorAssertionResponse = (webAuthnResponse, authenticators) => {
809    let authr = findAuthr(webAuthnResponse.id, authenticators);
810    let authenticatorData = base64url.toBuffer(webAuthnResponse.response.authenticatorData);
811
812    let response = {'verified': false};
813
814    //*********************************************** */
815    // Generic check
816
817    let authrDataStruct = parseGetAssertAuthData(authenticatorData);
818    let clientDataHash  = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON));
819
820    if(!(authrDataStruct.flags & U2F_USER_PRESENTED))
821    throw new Error('User was NOT presented durring authentication!');
822
823    let publicKey = ASN1toPEM(base64url.toBuffer(authr.publicKey));
824    let signature = base64url.toBuffer(webAuthnResponse.response.signature);
825    let signatureBase    = Buffer.concat([authrDataStruct.rpIdHash, authrDataStruct.flagsBuf, authrDataStruct.counterBuf, clientDataHash]);
826
827    response.verified = verifySignature(signature, signatureBase, publicKey);
828    if( !response.verified ) response.message = "Invalid Signature";
829
830    if(response.verified) {
831        if(response.counter <= authr.counter)
832            throw new Error('Authr counter did not increase!');
833        authr.counter = authrDataStruct.counter
834    }
835
836
837
838
839
840
841    /*
842    if(authr.fmt === 'fido-u2f') {
843        let authrDataStruct  = parseGetAssertAuthData(authenticatorData);
844
845        if(!(authrDataStruct.flags & U2F_USER_PRESENTED))
846            throw new Error('User was NOT presented durring authentication!');
847
848        let clientDataHash   = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON))
849        let signatureBase    = Buffer.concat([authrDataStruct.rpIdHash, authrDataStruct.flagsBuf, authrDataStruct.counterBuf, clientDataHash]);
850
851        let publicKey = ASN1toPEM(base64url.toBuffer(authr.publicKey));
852        let signature = base64url.toBuffer(webAuthnResponse.response.signature);
853
854        response.verified = verifySignature(signature, signatureBase, publicKey)
855
856        if(response.verified) {
857            if(response.counter <= authr.counter)
858                throw new Error('Authr counter did not increase!');
859
860            authr.counter = authrDataStruct.counter
861        }
862    }
863    else if(authr.fmt === 'packed') {    
864        let authrDataStruct = parseGetAssertAuthData(authenticatorData);
865
866        let clientDataHash  = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON))
867        let publicKey = ASN1toPEM(base64url.toBuffer(authr.publicKey));
868        let signature = base64url.toBuffer(webAuthnResponse.response.signature);
869        let signatureBase    = Buffer.concat([authrDataStruct.rpIdHash, authrDataStruct.flagsBuf, authrDataStruct.counterBuf, clientDataHash]);
870
871        // Step 1 - Verify that sig is a valid signature
872        response.verified = verifySignature(signature, signatureBase, publicKey);
873        if( !response.verified ) response.message = "Invalid Signature";
874
875        if(response.verified) {
876            if(response.counter <= authr.counter)
877                throw new Error('Authr counter did not increase!');
878            authr.counter = authrDataStruct.counter
879        }
880    }
881    else if(authr.fmt === 'android-safetynet') {    
882        console.log("login with: " + authr.fmt); 
883        let authrDataStruct = parseGetAssertAuthData(authenticatorData);
884        let clientDataHash  = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON));
885
886        
887        let publicKey = ASN1toPEM(base64url.toBuffer(authr.publicKey));
888        let signature = base64url.toBuffer(webAuthnResponse.response.signature);
889        let signatureBase    = Buffer.concat([authrDataStruct.rpIdHash, authrDataStruct.flagsBuf, authrDataStruct.counterBuf, clientDataHash]);
890
891        console.log(publicKey);
892        console.log(signature);
893        console.log(signatureBase);
894
895        // Step 1 - Verify that sig is a valid signature
896        response.verified = verifySignature(signature, signatureBase, publicKey);
897        if( !response.verified ) response.message = "Invalid Signature";
898
899        console.log(authr);
900        console.log(authrDataStruct);
901        console.log(clientDataHash);
902
903        //response.verified = true;
904        if(response.verified) {
905            if(response.counter <= authr.counter)
906                throw new Error('Authr counter did not increase!');
907            authr.counter = authrDataStruct.counter
908        }
909    }*/
910    
911
912    return response;
913}
914
915module.exports = {
916    randomBase64URLBuffer,
917    generateServerMakeCredRequest,
918    generateServerGetAssertion,
919    verifyAuthenticatorAttestationResponse,
920    verifyAuthenticatorAssertionResponse
921}
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Cypress on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)