New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Key Rollover management #206

Merged
merged 6 commits into from May 17, 2017

Be able to register more than 1 Identity Provider x509cert, linked wi…

…th an specific use (signing or encryption).
  • Loading branch information...
pitbulk committed Apr 12, 2017
commit 84aa522b27a8d44cfc2f0229aa8963b9de438eec
View
@@ -389,6 +389,22 @@ $settings = array (
*/
// 'certFingerprint' => '',
// 'certFingerprintAlgorithm' => 'sha1',
/* In some scenarios the IdP uses different certificates for
* signing/encryption, or is under key rollover phase and
* more than one certificate is published on IdP metadata.
* In order to handle that the toolkit offers that parameter.
* (when used, 'x509cert' and 'certFingerprint' values are
* ignored).
*/
// 'x509certMulti' => array(
// 'signing' => array(
// 0 => '<cert1-string>',
// ),
// 'encryption' => array(
// 0 => '<cert2-string>',
// )
// ),
),
);
```
@@ -1095,6 +1111,26 @@ You should be able to workaround this by configuring your server so that it is a
Or by using the method described on the previous section.
### SP Key rollover ###
If you plan to update the SP x509cert and privateKey you can define the new x509cert as $settings['sp']['x509certNew'] and it will be
published on the SP metadata so Identity Providers can read them and get ready for rollover.
### IdP with multiple certificates ###
In some scenarios the IdP uses different certificates for
signing/encryption, or is under key rollover phase and more than one certificate is published on IdP metadata.
In order to handle that the toolkit offers the $settings['idp']['x509certMulti'] parameter.
When that parameter is used, 'x509cert' and 'certFingerprint' values will be ignored by the toolkit.
The 'x509certMulti' is an array with 2 keys:
- 'signing'. An array of certs that will be used to validate IdP signature
- 'encryption' An array with one unique cert that will be used to encrypt data to be sent to the IdP
### Replay attacks ###
In order to avoid replay attacks, you can store the ID of the SAML messages already processed, to avoid processing them twice. Since the Messages expires and will be invalidated due that fact, you don't need to store those IDs longer than the time frame that you currently accepting.
@@ -61,7 +61,13 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null,
$cert = null;
if (isset($security['nameIdEncrypted']) && $security['nameIdEncrypted']) {
$cert = $idpData['x509cert'];
$existsMultiX509Enc = isset($idpData['x509certMulti']) && isset($idpData['x509certMulti']['encryption']) && !empty($idpData['x509certMulti']['encryption']);
if ($existsMultiX509Enc) {
$cert = $idpData['x509certMulti']['encryption'][0];
} else {
$cert = $idpData['x509cert'];
}
}
if (!empty($nameId)) {
@@ -357,49 +363,8 @@ public function isValid($retrieveParametersFromServer = false)
}
if (isset($_GET['Signature'])) {
if (!isset($_GET['SigAlg'])) {
$signAlg = XMLSecurityKey::RSA_SHA1;
} else {
$signAlg = $_GET['SigAlg'];
}
if ($retrieveParametersFromServer) {
$signedQuery = 'SAMLRequest='.OneLogin_Saml2_Utils::extractOriginalQueryParam('SAMLRequest');
if (isset($_GET['RelayState'])) {
$signedQuery .= '&RelayState='.OneLogin_Saml2_Utils::extractOriginalQueryParam('RelayState');
}
$signedQuery .= '&SigAlg='.OneLogin_Saml2_Utils::extractOriginalQueryParam('SigAlg');
} else {
$signedQuery = 'SAMLRequest='.urlencode($_GET['SAMLRequest']);
if (isset($_GET['RelayState'])) {
$signedQuery .= '&RelayState='.urlencode($_GET['RelayState']);
}
$signedQuery .= '&SigAlg='.urlencode($signAlg);
}
if (!isset($idpData['x509cert']) || empty($idpData['x509cert'])) {
throw new OneLogin_Saml2_Error(
"In order to validate the sign on the Logout Request, the x509cert of the IdP is required",
OneLogin_Saml2_Error::CERT_NOT_FOUND
);
}
$cert = $idpData['x509cert'];
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public'));
$objKey->loadKey($cert, false, true);
if ($signAlg != XMLSecurityKey::RSA_SHA1) {
try {
$objKey = OneLogin_Saml2_Utils::castKey($objKey, $signAlg, 'public');
} catch (Exception $e) {
throw new OneLogin_Saml2_ValidationError(
"Invalid signAlg in the recieved Logout Request",
OneLogin_Saml2_ValidationError::INVALID_SIGNATURE
);
}
}
if ($objKey->verifySignature($signedQuery, base64_decode($_GET['Signature'])) !== 1) {
$signatureValid = OneLogin_Saml2_Utils::validateBinarySign("SAMLRequest", $_GET, $idpData, $retrieveParametersFromServer);
if (!$signatureValid) {
throw new OneLogin_Saml2_ValidationError(
"Signature validation failed. Logout Request rejected",
OneLogin_Saml2_ValidationError::INVALID_SIGNATURE
@@ -175,49 +175,8 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false
}
if (isset($_GET['Signature'])) {
if (!isset($_GET['SigAlg'])) {
$signAlg = XMLSecurityKey::RSA_SHA1;
} else {
$signAlg = $_GET['SigAlg'];
}
if ($retrieveParametersFromServer) {
$signedQuery = 'SAMLResponse='.OneLogin_Saml2_Utils::extractOriginalQueryParam('SAMLResponse');
if (isset($_GET['RelayState'])) {
$signedQuery .= '&RelayState='.OneLogin_Saml2_Utils::extractOriginalQueryParam('RelayState');
}
$signedQuery .= '&SigAlg='.OneLogin_Saml2_Utils::extractOriginalQueryParam('SigAlg');
} else {
$signedQuery = 'SAMLResponse='.urlencode($_GET['SAMLResponse']);
if (isset($_GET['RelayState'])) {
$signedQuery .= '&RelayState='.urlencode($_GET['RelayState']);
}
$signedQuery .= '&SigAlg='.urlencode($signAlg);
}
if (!isset($idpData['x509cert']) || empty($idpData['x509cert'])) {
throw new OneLogin_Saml2_Error(
"In order to validate the sign on the Logout Response, the x509cert of the IdP is required",
OneLogin_Saml2_Error::CERT_NOT_FOUND
);
}
$cert = $idpData['x509cert'];
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public'));
$objKey->loadKey($cert, false, true);
if ($signAlg != XMLSecurityKey::RSA_SHA1) {
try {
$objKey = OneLogin_Saml2_Utils::castKey($objKey, $signAlg, 'public');
} catch (Exception $e) {
throw new OneLogin_Saml2_ValidationError(
"Invalid signAlg in the recieved Logout Response",
OneLogin_Saml2_ValidationError::INVALID_SIGNATURE
);
}
}
if ($objKey->verifySignature($signedQuery, base64_decode($_GET['Signature'])) !== 1) {
$signatureValid = OneLogin_Saml2_Utils::validateBinarySign("SAMLResponse", $_GET, $idpData, $retrieveParametersFromServer);
if (!$signatureValid) {
throw new OneLogin_Saml2_ValidationError(
"Signature validation failed. Logout Response rejected",
OneLogin_Saml2_ValidationError::INVALID_SIGNATURE
View
@@ -373,8 +373,15 @@ public function isValid($requestId = null)
$fingerprint = $idpData['certFingerprint'];
$fingerprintalg = $idpData['certFingerprintAlgorithm'];
$multiCerts = null;
$existsMultiX509Sign = isset($idpData['x509certMulti']) && isset($idpData['x509certMulti']['signing']) && !empty($idpData['x509certMulti']['signing']);
if ($existsMultiX509Sign) {
$multiCerts = $idpData['x509certMulti']['signing'];
}
# If find a Signature on the Response, validates it checking the original response
if ($hasSignedResponse && !OneLogin_Saml2_Utils::validateSign($this->document, $cert, $fingerprint, $fingerprintalg, OneLogin_Saml2_Utils::RESPONSE_SIGNATURE_XPATH)) {
if ($hasSignedResponse && !OneLogin_Saml2_Utils::validateSign($this->document, $cert, $fingerprint, $fingerprintalg, OneLogin_Saml2_Utils::RESPONSE_SIGNATURE_XPATH, $multiCerts)) {
throw new OneLogin_Saml2_ValidationError(
"Signature validation failed. SAML Response rejected",
OneLogin_Saml2_ValidationError::INVALID_SIGNATURE
@@ -383,7 +390,7 @@ public function isValid($requestId = null)
# If find a Signature on the Assertion (decrypted assertion if was encrypted)
$documentToCheckAssertion = $this->encrypted ? $this->decryptedDocument : $this->document;
if ($hasSignedAssertion && !OneLogin_Saml2_Utils::validateSign($documentToCheckAssertion, $cert, $fingerprint, $fingerprintalg, OneLogin_Saml2_Utils::ASSERTION_SIGNATURE_XPATH)) {
if ($hasSignedAssertion && !OneLogin_Saml2_Utils::validateSign($documentToCheckAssertion, $cert, $fingerprint, $fingerprintalg, OneLogin_Saml2_Utils::ASSERTION_SIGNATURE_XPATH, $multiCerts)) {
throw new OneLogin_Saml2_ValidationError(
"Signature validation failed. SAML Response rejected",
OneLogin_Saml2_ValidationError::INVALID_SIGNATURE
@@ -1004,7 +1011,7 @@ protected function _decryptAssertion($dom)
OneLogin_Saml2_Error::PRIVATE_KEY_NOT_FOUND
);
}
$objenc = new XMLSecEnc();
$encData = $objenc->locateEncryptedData($dom);
if (!$encData) {
@@ -1013,7 +1020,7 @@ protected function _decryptAssertion($dom)
OneLogin_Saml2_ValidationError::MISSING_ENCRYPTED_ELEMENT
);
}
$objenc->setNode($encData);
$objenc->type = $encData->getAttribute("Type");
if (!$objKey = $objenc->locateKey()) {
View
@@ -17,7 +17,7 @@ class OneLogin_Saml2_Settings
/**
* @var string
*/
private $_baseurl;
private $_baseurl;
/**
* Strict. If active, PHP Toolkit will reject unsigned or unencrypted messages
@@ -143,6 +143,8 @@ public function __construct($settings = null, $spValidationOnly = false)
$this->formatIdPCert();
$this->formatSPCert();
$this->formatSPKey();
$this->formatSPCertNew();
$this->formatIdPCertMulti();
}
/**
@@ -465,14 +467,12 @@ public function checkCompressionSettings($settings)
if (isset($settings['compress'])) {
if (!is_array($settings['compress'])) {
$errors[] = "invalid_syntax";
} else if (
isset($settings['compress']['requests'])
} else if (isset($settings['compress']['requests'])
&& $settings['compress']['requests'] !== true
&& $settings['compress']['requests'] !== false
) {
$errors[] = "'compress'=>'requests' values must be true or false.";
} else if (
isset($settings['compress']['responses'])
} else if (isset($settings['compress']['responses'])
&& $settings['compress']['responses'] !== true
&& $settings['compress']['responses'] !== false
) {
@@ -528,15 +528,16 @@ public function checkIdPSettings($settings)
$security = $settings['security'];
$existsX509 = isset($idp['x509cert']) && !empty($idp['x509cert']);
$existsMultiX509Sign = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['signing']) && !empty($idp['x509certMulti']['signing']);
$existsMultiX509Enc = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['encryption']) && !empty($idp['x509certMulti']['encryption']);
$existsFingerprint = isset($idp['certFingerprint']) && !empty($idp['certFingerprint']);
if (((isset($security['wantAssertionsSigned']) && $security['wantAssertionsSigned'] == true)
|| (isset($security['wantMessagesSigned']) && $security['wantMessagesSigned'] == true))
&& !($existsX509 || $existsFingerprint)
if (!($existsX509 || $existsFingerprint || $existsMultiX509Sign)
) {
$errors[] = 'idp_cert_or_fingerprint_not_found_and_required';
}
if ((isset($security['nameIdEncrypted']) && $security['nameIdEncrypted'] == true)
&& !($existsX509)
&& !($existsX509 || $existsMultiX509Enc)
) {
$errors[] = 'idp_cert_not_found_and_required';
}
@@ -934,6 +935,25 @@ public function formatIdPCert()
}
}
/**
* Formats the Multple IdP certs.
*/
public function formatIdPCertMulti()
{
if (isset($this->_idp['x509certMulti'])) {
if (isset($this->_idp['x509certMulti']['signing'])) {
for ($i=0; $i < count($this->_idp['x509certMulti']['signing']); $i++) {
$this->_idp['x509certMulti']['signing'][$i] = OneLogin_Saml2_Utils::formatCert($this->_idp['x509certMulti']['signing'][$i]);
}
}
if (isset($this->_idp['x509certMulti']['encryption'])) {
for ($i=0; $i < count($this->_idp['x509certMulti']['encryption']); $i++) {
$this->_idp['x509certMulti']['encryption'][$i] = OneLogin_Saml2_Utils::formatCert($this->_idp['x509certMulti']['encryption'][$i]);
}
}
}
}
/**
* Formats the SP cert.
*/
@@ -944,6 +964,16 @@ public function formatSPCert()
}
}
/**
* Formats the SP cert.
*/
public function formatSPCertNew()
{
if (isset($this->_sp['x509certNew'])) {
$this->_sp['x509certNew'] = OneLogin_Saml2_Utils::formatCert($this->_sp['x509certNew']);
}
}
/**
* Formats the SP private key.
*/
@@ -1023,7 +1053,7 @@ public function getBaseURL()
*/
public function setIdPCert($cert)
{
$this->_idp['x509cert'] = $cert;
$this->formatIdPCert();
$this->_idp['x509cert'] = $cert;
$this->formatIdPCert();
}
}
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.