Replies: 1 comment 1 reply
-
Signing XML document is really simple task. I.e. sign your authnresponses in your local env. This issue has an example #419 (see Keep in mind that code at aforementioned issue report was written against passport-saml's pre 1.3.0 version (against version ac7939fc to be exact) and dependencies has changed since then (e.g. Edit/Update
and which can be easily copy pasted to any (integration) test code to sign SAML authn messages before delivered to Actual reusable function ( // example written by using xml-crypto 2.1.5
const SignedXml = require("xml-crypto").SignedXml;
// actual signAuthnResponseMessage function (with message and assertion signing)
// is at the end of this example...following stuff is just to test
// example signAuthnResponseMessage
// Result(s) of signAuthnResponseMessage function was tested with samltool's
// SAML Response signature and xml schema validations
// --- Start dummy test material -----
// this is simulating IdP which signs AuthnResonse thus we need
// IdP's priavte key
// copied from:
// https://github.com/node-saml/passport-saml/blob/72effd8c9a694eb295a007b6900ba7e71bbe0a0d/test/static/key.pem
const IDP_PRIVATE_KEY = `
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXy
ptqB3ynXKsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9J
oIJzGhJyvO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1
Xb0ZSIE+DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYk
tn7DjdEslqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCR
vFlvAiMSaebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABAoIBAF/Hh/a1Q7wuWZH2
gjh8bLjis9hrTnpAzMpRNTOTxRvfrWf99vfNDA6ZBEXhlSxmvX6PronOjEB5vxcQ
oFiKqPzTUqf/MruguBykxc+VLAOL+5k1mt0dPqqz3CarMyL6lyWcou59FgudfR3c
DY1F8e2G0NP7oR1lioEKI4MszAwBEUt6oB4wDkGBv31uUbqtJZlxid9qOeMZxSCl
/uzd32wOwc/zWPCS2mtsE2PScq5Wzep6EGQEDRno0WbfkxHn+CZxvRYv5XVWtyUD
yCBrKuGQhRCCp6RO0x+39qxtXyT4m2ehwJb0wVmTPQ6e4ZayjRSlYHJI4+YPcdgc
WeT4HOECgYEA/SidmPth4jfQY7H6gv1wryNCNHsWssYiittsjsEv//McQR1Nsnoi
bU6RJTqpNFU0PPDgf9oh4W6Jxg8uHeuOcykacPhtlC9CotRYSJrxaOefW72G+ZWP
VlnErdJWPyb61olLKOH/lyXCbs7k3JltPqJwJGdt+9N9eu+N3D6+B80CgYEAxqJL
+J3UJVUSpLfkgnp03IglAeABEoCb1LzO8G63dHxucCP+Jt/enB1qSZ8ez+VwACzh
eCSFwzdL7Jqh0KJmcJf67c6YXZDfgT9oxWf6sY5KuljWvt6uvpvAATaHDM6Mvzyj
vlc/d+8uwed/Slbq2EfeTzPP6goK3ppfbBQFx9MCgYEA4JTWcm+X31J6WOb8AJaL
D6OsyNflRAVHgX206VNynJH0H8O6OLnmrqeDVc6baqSnqeRalLFTWyRvrreqxrpA
beMp5MxOkaX3bHIKO6bQwKqyEXWqNuG5/fW26CjvgCi5X/b+KS+MSW8i9KAwIY8u
feEmsPTcegmdiKDZbWhvtoUCgYBJooq1TJUDjekOZRlyAUFnK5VEf60GFeUu1RuF
6BVcOnCaY81ozd7xUro/Npyuhyig+AJRjoCD4PDlcmGAPWPqY3zjQY4cSYOBn2cQ
nz5BKjWrpRlewqBXCtf/2x3vcnacwjcVbbSamnFj8pSuk1AWA3Z3OAbghP6IjQPI
xhdqfwKBgBUG49pMk1yYldtrDWQnGg/73nKXcnxyRus8uus51eLFUUaTDyp2HMKY
DQer/NkQDDf26Ze1nBWURVPoMSU98JixBQjeKn3MX39PHtwFh6s3mxwLlzyxjAcu
MmFNzbu+qfEJ7pFmNaYR83s52qs6GyfclVPB8g6ICV5c98VoFfqB
-----END RSA PRIVATE KEY-----
`;
// not needed by this sample code. IdP's cert is added just to
// have all necessary information in one place to test that
// signing works (i.e. if you want to use certificate to test
// that signatures are valid you don't have to look all over the
// places).
// copied from:
// https://github.com/node-saml/passport-saml/blob/72effd8c9a694eb295a007b6900ba7e71bbe0a0d/test/static/cert.pem
const IDP_CERT = `
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTUwODEzMDE1NDIwWhcNMTUwOTEyMDE1NDIwWjBF
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXyptqB3ynX
KsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9JoIJzGhJy
vO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1Xb0ZSIE+
DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYktn7DjdEs
lqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCRvFlvAiMS
aebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABo4GnMIGkMB0GA1UdDgQWBBSVGgvo
W4MhMuzBGce29PY8vSzHFzB1BgNVHSMEbjBsgBSVGgvoW4MhMuzBGce29PY8vSzH
F6FJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKg4VeVcIDz1MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJu1rqs+anD74dbdwgd3CnqnQsQDJiEX
mBhG2leaGt3ve9b/9gKaJg2pyb2NyppDe1uLqh6nNXDuzg1oNZrPz5pJL/eCXPl7
FhxhMUi04TtLf8LeNTCIWYZiFuO4pmhohHcv8kRvYR1+6SkLTC8j/TZerm7qvesS
iTQFNapa1eNdVQ8nFwVkEtWl+JzKEM1BlRcn42sjJkijeFp7DpI7pU+PnYeiaXpR
v5pJo8ogM1iFxN+SnfEs0EuQ7fhKIG9aHKi7bKZ7L6SyX7MDIGLeulEU6lf5D9Bf
XNmcMambiS0pXhL2QXajt96UBq8FT2KNXY8XNtR4y6MyyCzhaiZZcc8=
-----END CERTIFICATE-----
`;
// just some example authn response for testing signing...
// copied (and modified) from https://github.com/node-saml/passport-saml/issues/419
const sampleSamlLoginResponse = (() => {
const nameId = "[email protected]";
const inResponseTo = "TBD_inresponse-value";
const sessionIndex = "TBD_session-index-value";
const notBefore = "1980-01-01T01:00:00Z"
const issueInstant = "1980-01-01T01:01:00Z";
const notOnOrAfter = "4980-01-01T01:01:00Z";
const issuer = "idp-issuer-value";
const audience = "audience-value";
const destination = "https://TBD-ACS-URL";
return `<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
Version="2.0"
IssueInstant="${issueInstant}"
Destination="${destination}"
InResponseTo="${inResponseTo}">
<saml:Issuer>${issuer}</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
ID="_bbbbbbbbbbbbbbbbbbbbbbbb"
Version="2.0" IssueInstant="${issueInstant}">
<saml:Issuer>${issuer}</saml:Issuer>
<saml:Subject>
<saml:NameID
SPNameQualifier="${audience}"
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">${nameId}</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="${notOnOrAfter}"
Recipient="${destination}"
InResponseTo="${inResponseTo}"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions
NotBefore="${notBefore}"
NotOnOrAfter="${notOnOrAfter}">
<saml:AudienceRestriction>
<saml:Audience>${audience}</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="${issueInstant}"
SessionNotOnOrAfter="${notOnOrAfter}"
SessionIndex="${sessionIndex}">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
</samlp:Response>`
})();
// --- End dummy test material -----
const signSamlAuthnResponse = (
idpPrivateKey,
inputSamlAuthnResponseMsg,
wantMessageSigned = true,
wantAssertionSigned = true
) => {
const signAssertion = () => {
const sig = new SignedXml();
sig.addReference(
// NOTE: assume exactly one (unencrypted) assertion
//
// if you want to use (signed) encrypted assertions modify this function to
// first sign unencrypted assertion with IdP's key and then encrypt
// Assertion with SP's certificate/public key and replace unencrypted
// assertion with EncryptedAssertion
"(/*[local-name()='Response']/*[local-name()='Assertion'])[1]",
[
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
"http://www.w3.org/2001/10/xml-exc-c14n#"
],
"http://www.w3.org/2001/04/xmlenc#sha256"
);
sig.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
sig.signingKey = idpPrivateKey;
sig.computeSignature(
inputSamlAuthnResponseMsg,
{
// according to:
// https://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd
// Assertion's ds:Signature must be after Assertion/Issuer
location: {
reference: "/*[local-name()='Response']/*[local-name()='Assertion']/*[local-name()='Issuer']",
action: "after"
}
}
);
return sig.getSignedXml();
};
const signMessage = (responseMessage) => {
const sig = new SignedXml();
sig.addReference(
"/*[local-name()='Response']",
[
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
"http://www.w3.org/2001/10/xml-exc-c14n#"
],
"http://www.w3.org/2001/04/xmlenc#sha256"
);
sig.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
sig.signingKey = idpPrivateKey;
sig.computeSignature(
responseMessage,
{
// according to:
// https://docs.oasis-open.org/security/saml/v2.0/saml-schema-protocol-2.0.xsd
// Response's ds:Signature must be after Response/Issuer
location: {
reference: "/*[local-name()='Response']/*[local-name()='Issuer']",
action: "after"
}
}
);
return sig.getSignedXml();
};
let authnResponse = (wantAssertionSigned ? signAssertion() : inputSamlAuthnResponseMsg);
authnResponse = (wantMessageSigned ? signMessage(authnResponse) : authnResponse);
return authnResponse;
};
console.log(
signSamlAuthnResponse(
IDP_PRIVATE_KEY,
sampleSamlLoginResponse,
true,
true
)
);
// use your favourite saml tool to verify signature / authnResponse
// use IDP_CERT from the beginning of this file for validation |
Beta Was this translation helpful? Give feedback.
-
Hello, we're recently upgrading from 2.x -> 4.x and I'm wondering what the current recommendations / strategies are for testing our callback endpoints now that Signature Verification cannot be disabled.
With 2.x, in the local and non-integrated environments we would disable Signature Verification, and create a test SAML Assertion so that we could test the logic in the callback endpoints without having to connect to an IDP or go through the login process to generate a properly Signed SAML Assertion. Then in the integrated environments we'd enable signature verification and test the full flow against our Dev SSO environment (also asserting that the forged Assertions are rejected in that environment).
In 3.x and 4.x we've lost the ability to disable Signature Verification, and so we've also lost our ability to test these endpoints locally.
I'm assuming that others have already come up with strategies to solve this issue, so please share :)
Beta Was this translation helpful? Give feedback.
All reactions