Cypress: Send a PKI from the client to the server

Created on 31 Oct 2018  Ā·  36Comments  Ā·  Source: cypress-io/cypress

Need a way to send a PKI from the client to the server when it is requested by the server on login (two way SSL).

On connecting to our application, the server requests a PKI / personal certificate from the client. This PKI is unique to the user; it is installed in the browser so that they can authenticate to websites with it.

The browser prompts the user to ā€œSelect a certificate to authenticate yourselfā€, where the user can select OK to send it.

This prompt/dialog isn’t appearing in Test Runner. Instead, our custom error message indicating that the user did not send a PKI is displayed.

I attempted to use a group policy to force all instances of Chrome to auto select a certificate, but the PKI still isn’t sent from the client to the server. The group policy does work for all other instances of Chrome that aren’t created by Cypress.

4ļøāƒ£ proposal šŸ’”

Most helpful comment

This is a critical piece of functionality for our ability to use Cypress as our testing framework.

All 36 comments

This is a critical piece of functionality for our ability to use Cypress as our testing framework.

We also need to add a specific certificate to communicate with the server.
This feature is necessary if we want to use cypress.

I've reviewed the PR here #2740 and understand the implications of this and have some feedback.

I see three areas that need to be addressed:

  1. Would attaching the client certs only on cy.visit() actually fix the issue? Wouldn't the browser need to send the certs on all requests, not just the first?

  2. We'd also need to ensure this works on cy.request() too since that is made outside of the browser.

  3. I need to review how browsers normally accept the PKI certificates as command line arguments or through group policy to understand the various ways in which they can be configured. These would at the least need to be configurable at the cypress.json level, and likely need some way to control which domain(s) they are sent to - enabling you to have multiple certificates for multiple domains. We may also need to abstract this away and enable it to be configured at the CLI level too, and automatically make it work for multiple browser agents. Alternatively, if its configurable via command line flags, then right now you could just modify the launch arguments (to chrome, etc) and add them in.

Thoughts:
Although we could do everything at the network layer transparently - I believe it likely makes more sense to utilize the browser agent itself to handle automatically attaching these instead of at the proxy layer. That way problems would bubble up to the dev tools in the browser and would be more immediately apparent.

Update:
On second thought... after some brief research it may actually be quite a PITA / difficult to get all of the browsers to play nice with this, and it may actually be easiest to attach this at the network layer outside of the browser. The configuration would likely be easier, and we may be able to also support it dynamically via cy.visit() and cy.request() as well as options.

Even in a CI environment it is more difficult to add this to a browser instance.
Had a lot of issues there recently.

Thanks for reviewing :) Just to provide a bit of background in regards to your first point, our reasoning for focusing on cy.visit() was because it was impossible for us to perform any form of e2e testing on the page under test without some way of utilising certificates.

By allowing certs to be passed through via cy.visit() we could at least load the initial page, then use fixtures for subsequent requests the page made. While this doesn't give us a true e2e picture, it would allow us to have fairly comprehensive front-end tests and significantly enhances our ability to adopt Cypress while further support for PKI was being considered/worked on. We saw it very much as a potential first step, but one that would have immediate benefit to us at the BBC.

In regards to your third point, my understanding of how PKI typically works is that a certificate is unique to a single user/machine, but can be used to authenticate across multiple domains that a corporation manages. With this in mind, certainly for us, the ability to configure just a single certificate in cypress.json, or by another means, would be enough for us to make full use of what Cypress has to offer.

We'd more than happily work with whatever implementation you felt was best, but hopefully this sheds some more light on what we're trying to achieve.

@brian-mann Hi there. I worked with @matt-rhys-jones on this and we would like to keep this PR moving. Did you come to a conclusion on what you thought the best approach was? Thanks for the feedback btw. Cheers.

This PR would need to be updated to reflect / address the issue in a more comprehensive way. I would prefer coming up with a solution that:

  1. works across all browsers
  2. works for cy.visit, cy.request, and all other network traffic the browser makes

Updating just cy.visit() would not really address this issue - and my preference would be either adding command line arguments or cypress.json configuration to address this.

@matt-rhys-jones and @craigkj what would be most helpful is if you can find us examples of using other testing tools to accommodate this need - selenium/webdriver, puppeteer, etc.

When I looked I did not see a way to launch chrome with arguments that would add these certificates so I cannot find a comparable way in which this could be achieved. If there are existing solutions we can piggy-back off of their ideas.

To be clear, without an easy way to configure each browser means that we'd be looking at implementing this at the network layer behind the scenes in a way that the browser doesn't know that it's happening. That'll work across all browsers but may have unintended effects, since I'm not super experienced with PKI in general, although I understand how it works.

Additionally it seems that whatever way we come up with would have to be targeted on a domain basis, and possibly also expose specific sets of configuration.

Hi @brian-mann - thanks for your thoughts. I agree that updating cy.visit() only partially addresses the issue (and was only intended to do so initially).

In regards to existing solutions, I haven’t come across many in the past, and certainly not any elegant ones. Most seem to involve automating dialogue box clicks with device automation tools. Selenium does allow the web driver to be configured to custom Firefox profiles with the various certificates provided but as you say this isn't possible across all browsers.

These solutions are inherently flakey, you'd know more than me but personally I don’t think it would be easy to come up with a reliable cross browser solution that automated the various dialogue boxes (or configs for headless browsers) to support PKI. This would also require continued maintenance as new versions of browsers emerge, and documentation updates too.

I think that for this to work reliably it would need to be done at the network level.

My understanding is that SSL operates at the Session/Presentation layer of the OSI model rather than Application layer. While the browser can be configured to support PKI in it's entirety, I believe the Operating System would typically be responsible for housing and trusting Root Certificates / Certificate Authorities, and the browser would verify against these. Either way, how the browser ultimately handles the HTTPS response should remain unchanged as that’s at the Application layer.

This is probably an over-simplistic viewpoint and I"m sure the separation between OSI layers in the browser isn't as clear-cut as that, but that's my understanding of how it should work.

Therefore I think implementing this transparently at the network layer of Cypress makes more sense as the browser shouldn't care whether the original request was being made to a PKI domain or not providing it gets a valid HTTPS response from it's original request.

If we were worried about side effects it might be sensible to implement PKI support behind an opt-in feature flag or switch (e.g. cypress run —enablePKI). If the flag is enabled then we could check the configuration for certificate/domain combinations and pass the certificates on with the request to the relevant domains transparently.


I’d imagine the most challenging part would be maintaining the standard of Cypress’s error handling and messages. Errors returned from openssl are not particularly friendly and I’d imagine we’d need to wrap around these so that we can provide clear guidance to users on which part of the PKI authentication went wrong (e.g. badly formatted certs, missing CA, missing certificate/key, unable to validate certificate against CA). I’m not sure how easy that would be, and it would require more understanding of the TLS spec and openssl internals/errors which again would take time.

If you think the above approach is viable, and Cypress would be happy to provide support with PR reviews and some guidance (if the docs fail me) then I would be happy to have a go at implementing this in my spare time.

Alternatively, if you wanted to add PKI support to your roadmap and develop in house then I would be happy to test it out.

Either way, it's definitely a feature we feel would be valuable for us at the BBC, add value to Cypress as a product, and address the original issue that was raised.

Please do let me know how you'd like to proceed! :)

Seems that our problem is also not fixed by the PR.
We need to add the certificate to the XHR requests made by the browser.
So the requirement
"works for cy.visit, cy.request, and all other network traffic the browser makes"
is important for us.
Best thing would be to have something like:
{ "certificates": { "baseurl": { "key": "keypath", "cert": "certpath" } } }

Yes, it should definitely be implemented for all requests. However you can at least use fixtures for your subsequent XHR requests as a middle ground until we have a more rounded solution.

I can see it was added to Sprint 11, but never got fixed or added to the subsequent sprint milestones.
Is this still something you are looking into?

The scope of this issue has been expanded - so work on this has been postponed until the implementation details are decided first. See https://github.com/cypress-io/cypress/issues/2694#issuecomment-439970547

Realize this is closed, just wanted to drop for future reference how we did it. I'm a US government software engineer and ran into this problem today, decided to just leave Cypress as-is and implement a reverse proxy instead. Basically expose different proxy ports with different scenarios: Valid, invalid pass, revoked, tampered, etc. That way Cypress (or anything else) can just get the scenarios it needs via a port.

We generate the SSL certs dynamically and inject them into the test environment. Also note, this app uses web sockets so I had to add a handler for that, it would be even simpler without.

const httpProxy = require('http-proxy');
const fs = require('fs');
const http = require('http');
const morgan = require('morgan')('dev');

proxyBuilder('Valid cert', 'client-valid.p12', 'test', 25000);
proxyBuilder('Invalid cert password', 'client-valid.p12', 'bad-password', 25001);
proxyBuilder('Untrusted cert', 'client-untrusted.p12', 'test', 25002);
proxyBuilder('Revoked cert', 'client-revoked.p12', 'test', 25003);
proxyBuilder('Expired cert', 'client-expired.p12', 'test', 25004);
proxyBuilder('Tampered cert', 'client-tampered.p12', 'test', 25005);

function proxyBuilder(testTitle, certPath, certPass, targetPort) {

  try {

    const proxy = new httpProxy.createProxyServer({
      target: {
        protocol: 'https:',
        host: 'localhost',
        port: process.env.SERVER_PORT,
        pfx: fs.readFileSync(`${__dirname}/../ssl/${certPath}`),
        passphrase: certPass,
      },
      secure: false,
      changeOrigin: true,
    });

    const proxyServer = http.createServer((req, res) => {
      console.log(testTitle);
      morgan(req, res, () => null);
      proxy.web(req, res);
    });

    proxyServer.on('upgrade', (req, socket, head) => {
      console.log(testTitle);
      morgan(req, socket, () => null);
      proxy.ws(req, socket, head);
    });

    proxyServer.listen(targetPort);

  } catch (e) {
    console.error(`Problem starting ${testTitle}`);
    console.error(e);
  }

}

@clevrtec This issue is not closed. It is part of our roadmap, but has not had any work done on it at this time.

In my use case, I would like to use cy.request to make API calls that require certificate-based authentication. Being able to use certificates at all would be a minimum need for me to be able to test our UI when we make changes via the API. However, I'd like to be able to use Cypress to test the API itself, and to do that I would want to test using different certificates, so just setting one in a config file wouldn't be ideal.

We also need this feature to be able to use Cypress at all. Any Updates? Thank you!

@Brett-Holmes Not currently being worked on, though we'd be happy to accept a PR that implements this functionality. The workaround that @clevrtec is the way to do this in the meantime.

Did you guys get an answer to the original question?

I did some experiments today to try to understand what a thorough solution to this would look like: https://gist.github.com/flotwig/12b2f070fd627f48fa7681802e6c36de

I noticed that we can't just send a bunch of PKI certs and hope that Node.js uses the correct one - we have to use the correct cert for each server: https://gist.github.com/flotwig/12b2f070fd627f48fa7681802e6c36de#file-pki-js-L84-L94


I can see why a user would want to configure this in cypress.json, if they aren't particularly interested in testing how PKI affects their website, they just want to be able to load it. That could look something like this:

{
    /** rest of the cypress.json... **/
    "clientCertificates": [
        {
            "url": "http://megacorp.net/secure/*",
            "cert": "./cypress/support/my-cert.crt",
            "key": "./cypress/support/my-private-key.key",
            "passphrase": "some optional passphrase for the key",
        },
        {
            // this is an example of a chained client certificate, which is apparently possible
            // see `cert` and `key` on https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options 
            "url": "http://megacorp.net/chained-secure/*",
            "cert": [
                "./cypress/support/my-first-cert.crt",
                "./cypress/support/my-second-cert.crt"
            ],
            "key": [
                "./cypress/support/my-first-private-key.key",
                "./cypress/support/my-second-private-key.key",
            ]   
        },
        {
            // an example of using a pfx/pkcs12 key and cert chain combined
            "url": "http://megacorp.net/pfx-secure/*",
            "pfx": "./cypress/support/my-pfx.pfx",
            "passphrase": "some optional passphrase for the key",
        },
        // additional configurations can go here, the first matching `url`'s `cert` 
        // and `key` will be used for any visits that match that `url`
    ]
}

I think we should also expose an option on cy.visit and cy.request that follows the same syntax, so users can test what happens when they use invalid client certificates on their website:

cy.visit({
    url: 'http://megacorp.net/secure/',
    cert: "./cypress/support/my-cert.crt",
    key: "./cypress/support/my-private-key.key"
})

Same sort of syntax for cy.request. This would override the clientCertificates from cypress.json, but only for that exact visit or request, not for any subresources.

Alternatively, using a cert and key with cy.visit could cause all requests that start with the same origin to use that client certificate for the duration of the current test, which would allow users to test subresources getting PKI as well. Could be a flag to switch to this behavior, something like usePkiForOrigin: 'http://megacorp.net/secure/*' to indicate what URLs should match


I've never had to work with an implementation of PKI in the wild, so I would appreciate any comments regarding this design, particularly concerning if sending a PKI with cy.visit should apply to all requests in the same origin or not.

@flotwig this looks great, what do we need to get a PR for this?

Apologies I haven't had time to keep up to date with this issue. @flotwig the config file looks great :)

I think we'd also need ca to be added to the config file as many large organisations have their own certificate authority which the certs need to be validated against (as opposed to a Root CA which the underlying OS would already know about).

Concur with @matt-rhys-jones on the ability to add certificate authorities.

Would love to use this on my team, but sadly this is a blocker for us.

I ended up creating a script to start a proxy server to workaround this limitation. Check out https://github.com/http-party/node-http-proxy

@jennifer-shehane Good morning! i'm working on API tests and have the same issue with certificates. Can you confirm if you will work in some solution for this? Like @jsalankey mentioned, this is critical to us.
I have two months working with cypress and love it.

I did some experiments today to try to understand what a thorough solution to this would look like: https://gist.github.com/flotwig/12b2f070fd627f48fa7681802e6c36de

I noticed that we can't just send a bunch of PKI certs and hope that Node.js uses the correct one - we have to use the correct cert for each server: https://gist.github.com/flotwig/12b2f070fd627f48fa7681802e6c36de#file-pki-js-L84-L94

I can see why a user would want to configure this in cypress.json, if they aren't particularly interested in testing how PKI affects their website, they just want to be able to load it. That could look something like this:

{
  /** rest of the cypress.json... **/
  "clientCertificates": [
      {
          "url": "http://megacorp.net/secure/*",
          "cert": "./cypress/support/my-cert.crt",
          "key": "./cypress/support/my-private-key.key",
          "passphrase": "some optional passphrase for the key",
      },
      {
          // this is an example of a chained client certificate, which is apparently possible
          // see `cert` and `key` on https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options 
          "url": "http://megacorp.net/chained-secure/*",
          "cert": [
              "./cypress/support/my-first-cert.crt",
              "./cypress/support/my-second-cert.crt"
          ],
          "key": [
              "./cypress/support/my-first-private-key.key",
              "./cypress/support/my-second-private-key.key",
          ]   
      },
      {
          // an example of using a pfx/pkcs12 key and cert chain combined
          "url": "http://megacorp.net/pfx-secure/*",
          "pfx": "./cypress/support/my-pfx.pfx",
          "passphrase": "some optional passphrase for the key",
      },
      // additional configurations can go here, the first matching `url`'s `cert` 
      // and `key` will be used for any visits that match that `url`
  ]
}

I think we should also expose an option on cy.visit and cy.request that follows the same syntax, so users can test what happens when they use invalid client certificates on their website:

cy.visit({
  url: 'http://megacorp.net/secure/',
  cert: "./cypress/support/my-cert.crt",
  key: "./cypress/support/my-private-key.key"
})

Same sort of syntax for cy.request. This would override the clientCertificates from cypress.json, but only for that exact visit or request, not for any subresources.

Alternatively, using a cert and key with cy.visit could cause all requests that start with the same origin to use that client certificate for the duration of the current test, which would allow users to test subresources getting PKI as well. Could be a flag to switch to this behavior, something like usePkiForOrigin: 'http://megacorp.net/secure/*' to indicate what URLs should match

I've never had to work with an implementation of PKI in the wild, so I would appreciate any comments regarding this design, particularly concerning if sending a PKI with cy.visit should apply to all requests in the same origin or not.

I have no experience with e2e testing but I've seen the twitter of folks I respect that say, "Write more integration tests then Unit Tests...and use Cypress. I need pki certificate support as well.

If there is some sort of work around it would be great if it was documented via tutorial the same way basic use of cypress is.

What pki help do you need? I can help

On Thu, Oct 31, 2019, 8:16 PM jdmairs notifications@github.com wrote:

I have no experience with e2e testing but I've seen the twitter of folks I
respect that say, "Write more integration tests then Unit Tests...and use
Cypress. I need pki certificate support as well.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/cypress-io/cypress/issues/2694?email_source=notifications&email_token=AGFNCXFR76XHQKBE5ZE2UNTQRNYNVA5CNFSM4GATRAE2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECZUBLQ#issuecomment-548618414,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AGFNCXHTYJX4L5QFPFF3KBDQRNYNVANCNFSM4GATRAEQ
.

I also have a need to support client side certificates.It will be great to Cypress support for client certs. @justin-bits can you provide some more info on how did you use proxy server to implement a work around? My web apps are written in Ruby on Rails or have Vue.js(Nuxt.js) front end calling APIs written in Rails. We use Nginx as frontend web server with Passenger as Ruby application server.

I used apache to handle proxy and certs. If you are using nginx check the
configuration file. You will need to research what ssl mods nginx has. It
routes the certs to your application by defined url.

On Wed, Apr 22, 2020, 1:27 PM Navjeet notifications@github.com wrote:

I also have a need to support client side certificates.It will be great to
Cypress support for client certs. @justin-bits
https://github.com/justin-bits can you provide some more info on how
did you use proxy server to implement a work around? My web apps are
written in Ruby on Rails or have Vue.js(Nuxt.js) front end calling APIs
written in Rails. We use Nginx as frontend web server with Passenger as
Ruby application server.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/cypress-io/cypress/issues/2694#issuecomment-617919804,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AGFNCXF65OEOS6Z5HWYTBCDRN4SHJANCNFSM4GATRAEQ
.

@kmaverick Nginx is configured to forward certs and it works in the web browser (for now printing cert CN in php script and it prints correct value). But when cypress runs the test, CN is blank.

Forgive me here folks, but am I missing something? Is there a solution to this now available? We're really keen to use Cypress, but are seeing SSL errors as seen in #4394 - apparently because our client cert isn't being passed in on test launch. As for many others, this basically blows Cypress out of the water as a non-starter.... Does cy.visit now provide a mechanism to specify the cert and key(that I've missed the docs for?)... Or is there another way of passing them in at test launch (eg, specify in cypress.json?). FWIW, we're currently on Cypress 4.10.0, with Node 12.18.2.

TIA!

Are you trying to connect to a remote server or web service or something?

On Wed, Jul 22, 2020, 6:51 AM GCHQDeveloper911 notifications@github.com
wrote:

Forgive me here folks, but am I missing something? Is there a solution to
this now available? We're really keen to use Cypress, but are seeing SSL
errors as seen in #4394
https://github.com/cypress-io/cypress/issues/4394 - apparently because
our client cert isn't being passed in on test launch. As for many others,
this basically blows Cypress out of the water as a non-starter.... Does
cy.visit now provide a mechanism to specify the cert and key(that I've
missed the docs for?)... Or is there another way of passing them in at test
launch (eg, specify in cypress.json?). FWIW, we're currently on Cypress
4.10.0, with Node 12.18.2.

TIA!

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/cypress-io/cypress/issues/2694#issuecomment-662434408,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AGFNCXFB7K77EAA57UV72YDR43OGBANCNFSM4GATRAEQ
.

Hello I am running into the same issue. We are using PKI and need a way to handle Certs in Cypress. Is there any work around available at the moment?

@vijar50 and @GCHQDeveloper911 the solution of creating a proxy to pass the client cert works fine for us for now. This solution shown earlier in the thread works just fine for us: https://github.com/cypress-io/cypress/issues/2694#issuecomment-464270224

It would be nice though if Cypress could support this though as adding a proxy is a bit clunky and confusing to some people.

Thanks @onetrickwolf - So if I'm understanding correctly.... I use the code above to create a standalone node app, but changing target.host and target.port to point at the site I'm testing, and then in my cypress test I'll have something like cy.visit('https://localhost:25000') ??

I've tried the above (with a dummy self created p12 for a locally running test app), but even ignoring cypress, and trying to browse to https://localhost:25000, I get 'the site can't provide a secure connection (err_ssl_protocol_error)'?

What am I not understanding? :) Thanks again!

@GCHQDeveloper911 yes that is correct but I believe you would hit http NOT https. The idea is that the proxy is setting up a completely unsecured connection for Cypress to use that proxies to the secured connection.

So cy.visit('http://localhost:25000')

Was this page helpful?
0 / 5 - 0 ratings