diff --git a/lib/SimpleSAML/XML/Validator.php b/lib/SimpleSAML/XML/Validator.php index d3fba00114dce38b0456db80bda5abe3828904cd..ddd404b28e905b45f847f1efe3ccf4a4a9368b24 100644 --- a/lib/SimpleSAML/XML/Validator.php +++ b/lib/SimpleSAML/XML/Validator.php @@ -24,14 +24,33 @@ class SimpleSAML_XML_Validator { /** * This function initializes the validator. * - * @param $xmlNode The XML node which contains the Signature element. - * @param $idAttribute The ID attribute which is used in node references. If this attribute is - * NULL (the default), then we will use whatever is the default ID. Can be eigther - * a string with one value, or an array with multiple ID attrbute names. + * This function accepts an optional parameter $publickey, which is the public key + * or certificate which should be used to validate the signature. This parameter can + * take the following values: + * - NULL/FALSE: No validation will be performed. This is the default. + * - A string: Assumed to be a PEM-encoded certificate / public key. + * - An array: Assumed to be an array returned by SimpleSAML_Utilities::loadPublicKey. + * + * @param DOMNode $xmlNode The XML node which contains the Signature element. + * @param string|array $idAttribute The ID attribute which is used in node references. If + * this attribute is NULL (the default), then we will use whatever is the default + * ID. Can be eigther a string with one value, or an array with multiple ID + * attrbute names. + * @param array $publickey The public key / certificate which should be used to validate the XML node. */ public function __construct($xmlNode, $idAttribute = NULL, $publickey = FALSE) { assert('$xmlNode instanceof DOMNode'); + if ($publickey === NULL) { + $publickey = FALSE; + } elseif(is_string($publickey)) { + $publickey = array( + 'PEM' => $publickey, + ); + } else { + assert('$publickey === FALSE || is_array($publickey)'); + } + /* Create an XML security object. */ $objXMLSecDSig = new XMLSecurityDSig(); @@ -67,13 +86,35 @@ class SimpleSAML_XML_Validator { } /* Load the key data. */ - if ($publickey) { - $objKey->loadKey($publickey); + if (array_key_exists('PEM', $publickey)) { + /* We have PEM data for the public key / certificate. */ + $objKey->loadKey($publickey['PEM']); } else { + /* No PEM data. Search for key in signature. */ + if (!XMLSecEnc::staticLocateKeyInfo($objKey, $signatureElement)) { throw new Exception('Error finding key data for XML signature validation.'); } + + if ($publickey !== FALSE) { + /* $publickey is set, and should therefore contain one or more fingerprints. + * Check that the response contains a certificate with a matching + * fingerprint. + */ + assert('is_array($publickey["certFingerprint"])'); + + $certificate = $objKey->getX509Certificate(); + if ($certificate === NULL) { + /* Wasn't signed with an X509 certificate. */ + throw new Exception('Message wasn\'t signed with an X509 certificate,' . + ' and no public key was provided in the metadata.'); + } + + self::validateCertificateFingerprint($certificate, $publickey['certFingerprint']); + /* Key OK. */ + } } + /* Check the signature. */ if (! $objXMLSecDSig->verify($objKey)) { throw new Exception("Unable to validate Signature"); @@ -140,6 +181,42 @@ class SimpleSAML_XML_Validator { } + /** + * Helper function for validating the fingerprint. + * + * Checks the fingerprint of a certificate against an array of valid fingerprints. + * Will throw an exception if none of the fingerprints matches. + * + * @param string $certificate The X509 certificate we should validate. + * @param array $fingerprints The valid fingerprints. + */ + private static function validateCertificateFingerprint($certificate, $fingerprints) { + assert('is_string($certificate)'); + assert('is_array($fingerprints)'); + + $certFingerprint = self::calculateX509Fingerprint($certificate); + if ($certFingerprint === NULL) { + /* Couldn't calculate fingerprint from X509 certificate. Should not happen. */ + throw new Exception('Unable to calculate fingerprint from X509' . + ' certificate. Maybe it isn\'t an X509 certificate?'); + } + + foreach ($fingerprints as $fp) { + assert('is_string($fp)'); + + if ($fp === $certFingerprint) { + /* The fingerprints matched. */ + return; + } + + } + + /* None of the fingerprints matched. Throw an exception describing the error. */ + throw new Exception('Invalid fingerprint of certificate. Expected one of [' . + implode('], [', $fingerprints) . '], but got [' . $certFingerprint . ']'); + } + + /** * Validate the fingerprint of the certificate which was used to sign this document. * @@ -156,28 +233,20 @@ class SimpleSAML_XML_Validator { if($this->x509Certificate === NULL) { throw new Exception('Key used to sign the message was not an X509 certificate.'); } - $certFingerprint = self::calculateX509Fingerprint($this->x509Certificate); if(!is_array($fingerprints)) { $fingerprints = array($fingerprints); } - foreach($fingerprints as $fp) { + /* Normalize the fingerprints. */ + foreach($fingerprints as &$fp) { assert('is_string($fp)'); /* Make sure that the fingerprint is in the correct format. */ $fp = strtolower(str_replace(":", "", $fp)); - - if($fp === $certFingerprint) { - /* The fingerprints matched. */ - return; - } - } - /* None of the fingerprints matched. Throw an exception describing the error. */ - throw new Exception('Invalid fingerprint of certificate. Expected one of [' . - implode('], [', $fingerprints) . '], but got [' . $certFingerprint . ']'); + self::validateCertificateFingerprint($this->x509Certificate, $fingerprints); }