You need multiple SAML IDP signing keys

SAML is a widely deployed single sign-on protocol. If you run a SAML server and integrate with more than a few other sites, you’re almost certainly using an insecure setup. One of the biggest threats to SAML security isn’t weird XML edge cases or hackers stealing your signing keys – it’s low-quality third party implementations not validating your assertions, allowing your users to log in to apps you thought they couldn’t access. To ensure your SAML assertions only work with the right apps, use unique signing keys for each app or service provider.

This problem isn’t unique to SAML! I just wrote this post because I’m most familiar with SAML. Signed JWTs and other SSO uses, such as in OIDC, can suffer from a similar lack of token verification.

If you just want to see the which IDPs (identity providers, or servers) support this security feature, click here to jump to the list.

How does SAML work?

This overview of SAML is enough background for the rest of this post. If you’re looking for a more detailed background on SAML, Duo’s “Beer Drinker’s Guide to SAML” is a good starting point. I’ve also found reading the SAML spec to be surprisingly helpful.

At a high level, SAML is a way of logging users in that uses in-browser communication between two systems that can’t otherwise talk to each other. When a user wants to log in to their favorite cat picture SaaS, the SaaS app (the SP, or service provider) will send you to your IDP with a little bit of data about the login request. This includes things like a unique request ID and data such as the original page you were trying to visit. The authentication request can theoretically specify what kind of username and fields like names your IDP should return, but in practice this gets ignored. These requests can also be signed but in practice mostly breaks things when the SP rotates their key. There’s little security benefit since the SAML exchange is all happening over TLS in any modern system.

Once your IDP receives the authentication request, the IDP verifies you’re logged in (maybe password, maybe client certs, maybe smoke signals – it doesn’t matter) and then signs an assertion. The assertion is what the SP will verify and use to log you in. Your IDP will then send this assertion back to the SP. The SP verifies the cryptographic signature, verifies that the assertion was supposed to be sent to that particular SP, and extracts the relevant username and other fields. Now you can look at all the cat pictures you want!

That signing key sounds pretty scary! Your first instinct might be to protect that key at all costs. The key is worth protecting, but the biggest credible threat to your SAML IDP’s security is not an attacker owning up your SAML server.

Threat landscape

There are many different ways to attack SAML! While unique signing keys can solve some of them, it’s not a panacea.

Audience restriction issues

This is the issue I focus on in this post and I’ll cover it in more detail later. Unsurprisingly, unique signing keys solve this class of problem.

IDP signing key theft

It’s true that someone who gains access to your IDP can get a copy of the signing keys and log in as anybody to any website you’ve integrated with. People have written breathless articles about how this is a mind-blowing vulnerability. It’s not – it’s a logical consequence of how assertion signing works. If this is a threat you’re concerned about (and you probably should be!), hardware-backed keys that only provide a signing oracle are the correct defense.

Multiple signing keys don’t change the impact of a successful key theft so we’re not going to consider this further.

XML and XML Security libraries

I don’t trust libxmlsec1 and friends because they’re mostly un-audited C code. You should use a memory-safe library if you can! That said, this has no real impact on assertion validation by third parties and your security posture doesn’t change if you use unique signing keys.

XML processing issues

There’s a long history of weird XML canonicalization problems and strange interactions with XML Security (also known as XML DSIG), the inline signature format from the early 2000s nobody asked for and fewer people need. Example of these issues include canonicalization problems such as ignoring XML comments in usernames, the signature format itself ignoring comments that otherwise matter to your XML parser, and not checking that the signature you verified actually covers all the data you’re trusting.

You can reduce the blast radius of these issues by using unique signing keys. Instead of these bugs allowing users to log in to any service you integrate with (authorized or not), you only have to be concerned about internal permissions for an individual app.

What are we solving for?

We’re interested in the normal corporate SAML use case. You have ten, maybe twenty, maybe more applications that you’ve integrated your IDP with. You have some form of access control and don’t allow blanket access to these applications. It’d be a real bummer if an employee who can only access your cat pictures SaaS app got to log in to your payroll site and give themselves a nice bonus.

You have no control over the quality of these third party SSO implementations. Some implementations are going to be companies who spend all of their time thinking about SAML, or are direct integrations with the products these companies offer. Some are going to be an easily-integrated library that has a few tests. The rest are going to be the hackiest minimal attempt to get anything working so they can keep your security team (i.e. me) happy and sell your company an enterprise SSO solution.

You need to make SAML work now and your legal / finance / dev team isn’t going to take “in two months when these neat vulns get fixed!” for an answer.

What’s the problem?

The SAML summary above paints a rosy picture where SPs are competent and do a good job validating your assertions. A few years of performing SAML integrations (and getting an opportunity to do testing against them) has provided me with solid data proving this rosy picture wrong. Far too many SPs will accept assertions intended for other providers as long as they’re signed with the correct key.

The core issue is a lack of audience restriction validation – in other words, the SP not checking that the assertion was meant for it. SAML was designed with the idea that managing more than one secret is untenably hard and that your IDP will have exactly one signing key that you distribute to everybody you integrate with. Given SAML’s academic background, it was meant to be a happy federated protocol with lambs frolicking between IDPs and close collaboration between organizations. Modern corporate SAML ignores all of these fun features because they’re a giant security and configuration nightmare.

When your IDP signs an assertion, it includes two fields for the SP to validate: the SP’s entity ID and the URL that the assertion is getting sent to. The SP can silently ignore these fields and there’s nothing you (or really, your IDP) can do about it. If your payroll site has a bad SAML implementation, a cat picture assertion will be considered as valid as a real assertion destined for your payroll site.

I can definitely fix this with more due diligence!

You can try and audit these providers to ensure they’re up to snuff. You can yell about how they’re not compliant with the specification until you’re blue in the face. You can try and convince your company that it’s worth waiting a few more months after going through vendor review before the first users can log in.

It’s thankless hard work. You can test for issues and the third party will work on fixing them in the next month or two. Sometimes the fixes regress and there’s no way for you to know without recurring audits! Meanwhile, people from the business side of your company (you know, the people who just want to get stuff done and not nerd out about SAML hilarity) are asking why the integration isn’t finished yet.

As an IDP who takes care of their signing keys, how do you protect yourself against the inevitable weak SP?

Juggling a pile of signing keys

Rather than relying on fiddly and optional (not according to the spec, but we know how much that matters) parts of the protocol like audience validation, the only scalable way to enforce that your assertions are only valid on one SP is by having unique signing keys for every single SP.

This works because there are three parts that almost every SAML SP implementation gets right. They’ll extract the username from the request, validate the signature on an assertion, and (most importantly) reject invalid assertion signatures. Everything else should be considered optional.

Yes, the SP could just ignore your signatures, but it’s super simple to test for and the kind of thing that a bug bounty reporter will find easily. There’s no complexity involved here unlike more esoteric audience restriction tests.

How do you manage so many keys?

Most IDPs unfortunately make this difficult. Some even include statements in their documentation to dissuade you from this golden path! Shibboleth’s documentation explicitly tells you not to try it:

The most complex (and ill-advised) case is if you’re trying to selectively apply different keys or certificates when interacting with different relying parties. The primary advice on this is “don’t”.

I’ve managed multiple keys for Shibboleth in the past by writing a small pile of Ruby to automatically generate the relevant XML to handle a unique signing key per SP. Every time I encounter yet another SP who mishandles assertions, I appreciate the effort put in to making this less of a problem we have to worry about.

What can I do?

In a perfect world, we wouldn’t use SAML. SAML is a crufty protocol that lets you create a mesh network of identity providers with bonkers inline signatures where whitespace in your XML determines whether the signature is valid. But SAML is here to stay, along with OAuth 2.0 and the less-absurd-but-still-not-perfect OIDC. Given that SAML is the de facto enterprise single sign-on protocol, we’d be remiss in ignoring it.

If your IDP doesn’t support this feature (see below), you should open a feature request with them! This is an important security control that your IDP should support.

If your IDP does support this feature, issue per-SP signing keys for new apps you onboard. Migrating applications using the older certificate is a lot of work but can be worth doing if you have particularly sensitive applications. Payroll app? Worth migrating. Cat pictures? Probably not.

What can the industry do?

All SaaS IDPs should generate per-application signing keys without any user intervention. Defaulting to per-SP keys is extremely high leverage, quietly upgrading the security of every business signing up with these providers. As of March 2020, the only major provider that gets this right is Azure AD.

Self-hosted IDPs should ensure they support per-SP signing keys and have documentation for enabling this feature. Ideally, shared signing keys would be less obvious to configure so that administrators choose per-SP signing keys by default.

While it’s ultimately up to SSO administrators to make good SSO choices, it’s our responsibility as the security industry to make it easy to choose correctly.

IDP support for multiple signing keys

Best practices aren’t helpful without an implementation guide. This is a list of various major IDPs I’ve tested (including SaaS and self-hosted options) as of March 2020. If your preferred IDP isn’t on this list or an entry is inaccurate, contact me and I can take a look.

Current as of April 23, 2020.

Azure AD – SaaS

Yes!

Azure AD automatically generates a new key for each “enterprise application” and there’s no option to share certificates between applications in the console. You can upload your own cert + private key manually, but it’s not particularly easy or encouraged. Azure AD should be a model for all other SaaS IDPs.

Keycloak – Java (self-hosted)

Yes!

The admin UI for Keycloak makes this super straightforward. When you add a new SAML “client”, Keycloak automatically generates a new key and a self-signed certificate for your client. There’s no mucking around with APIs or config files, and it’s self-hosted!

I have a lot more experience with Shibboleth, but from what I’ve seen of Keycloak, it looks like a good option.

Shibboleth – Java (self-hosted)

Yes, but it’s not pretty.

The documentation warns you against this and you have to write a lot of XML to make it work. There are no issues once you’ve spent several hours beating your head against the Spring XML configuration required to wire it all up. I’ve included the basics needed. The gist is that you need to make individual signing credentials, include those signing credentials in a security configuration, and then reference that security configuration from the individual SP.

As an aside, I do like that Shibboleth is all-Java (no memory corruption!), can run locally on your own servers, and takes a very standards-compliant approach, reducing the likelihood of being affected by weird XML issues.

Example config for conf/relying-party.xml (Shibboleth docs):

<util:list id= "shibboleth.RelyingPartyOverrides" > <bean parent= "RelyingPartyByName" c:relyingPartyIds= "https://sp.example.org" > <property name= "profileConfigurations" > <list> <bean parent= "SAML2.SSO" p:securityConfiguration-ref= "local.ExampleSecConf" /> </list> </property> </bean> ... </util:list> <bean parent= "shibboleth.DefaultSecurityConfiguration" id= "local.ExampleSecConf" > <property name= "signatureSigningConfiguration" > <bean parent= "%{idp.signing.config}" p:signingCredentials-ref= "local.ExampleSignCred" /> </property> </bean> ...

Example config for conf/credentials.xml (Shibboleth docs):

<util:list id= "shibboleth.SigningCredentials" > <ref bean= "shibboleth.DefaultSigningCredential" /> <ref bean= "local.ExampleSignCred" /> </util:list> ... <bean id= "local.DefaultSigningCredential" class= "net.shibboleth.idp.profile.spring.factory.BasicX509CredentialFactoryBean" p:privateKeyResource= "%{idp.home}/credentials/example.key" p:certificateResource= "%{idp.home}/credentials/example.pem" p:entityId-ref= "entityID" /> ...

PingOne – SaaS

Yes, but not by default.

By default, PingOne uses a single shared signing key. There is a separate Certificates page where you can create new certificates. Once you’ve added some, you can configure each SAML “application” to use a unique signing key.

OneLogin – SaaS

Yes, but not by default.

By default, OneLogin uses a single shared signing key. You can attempt to change this key in the settings for an individual SAML configuration, but you can’t add new ones. You have to navigate to a separate Certificates page to create new certificates, but once you do, you can make unique signing keys per SP.

Once you’ve added a “certificate” (really a signing key), you can assign it to an arbitrary SP.

Okta – SaaS

Yes, but it requires mucking around in the API.

Okta uses different entity IDs for each SP, but by default uses the exact same credential to sign the assertions. There’s no ability to upload custom private keys or rotate the signing credential in the Okta console.

However, you can create a new signing key and them associate the key with an app via two API calls. It’s not the most ergonomic but it gets the job done!

GSuite SAML – SaaS

No.

GSuite’s SAML configuration allows you to have exactly two signing certificates at a given time so you can rotate expired signing certificates. It’s clear that GSuite could support additional certificates, but it does not.

Auth0 – SaaS

No.

Auth0 shares a single signing credential between all of your SAML “applications” despite supporting unique OAuth 2.0 client secrets! There’s no option to rotate your SAML signing credential either. Given that you’re dynamically configuring each SP, there’s no reason to not generate a per-SP signing credential.

ADFS– Microsoft Windows Server (self-hosted)

No.

ADFS as of Windows Server 2019 does not support unique “signing tokens” per “relying party” (what we call an SP). You can manually add a secondary certificate for rotation purposes after running some PowerShell to disable automatic rotation, but it’s not useful for anything else.

It is technically possible to run one ADFS server per SP and use a separate signing token on each server. This would be painful to manage, not to mention the Windows licensing costs, so I’m not considering this as a serious suggestion.

No.

Gluu’s UI doesn’t expose any way to associate specific signing identities with SAML SPs, nor can you create new signing identities.

No.

Based on the documentation for generic SP configuration, there’s only one certificate for the entire DAG server. You can recreate the certificate but that appears to affect every SP with no option for unique keys.

Yes, with a little extra config.

SimpleSAMLphp’s IDP supports unique keys for individual service providers. The documentation isn’t super clear, but once you know what to look for, it’s straightforward. In the “SP remote metadata” reference, signature.certificate and signature.privatekey will let you specify an individual key for each SP.

While it does support unique keys nicely, you might not want to use this software if you can. The project has a consistent history of classic PHP webapp vulnerabilities plus the additional lurking horror of libxmlsec1 .

Reported as working but I haven’t tested it.

People who work at Ping Identity have said on Twitter that per-SP keys are supported. I haven’t tested this or been able to find anything in the documentation, so I’m not sure how to set this up or if it’s enabled by default. If you have links or screenshots, please let me know!

miniOrange – self-hosted or SaaS

Yes!

miniOrange’s SAML implementation seems to be the same for both their cloud and on-premises option. The IDP automatically generates a new key for each “application” and there’s no option to share certificates between applications in the console. The IDP is a large Java application that uses OpenSAML as the backing implementation. I haven’t seen much (good or bad) about this provider.