diff --git a/lib/SimpleSAML/XML/Validator.php b/lib/SimpleSAML/XML/Validator.php index 103212cb54fc92b829a9dd9fc8d395c0ca7cefac..133136c86ba9cd1dec86e5f990b82dc77a54b8fc 100644 --- a/lib/SimpleSAML/XML/Validator.php +++ b/lib/SimpleSAML/XML/Validator.php @@ -199,6 +199,130 @@ class SimpleSAML_XML_Validator { */ return FALSE; } + + + /** + * Validate the certificate used to sign the XML against a CA file, by using the builtin + * openssl_x509_checkpurpose function + * + * This function throws an exception if unable to validate against the given CA file. + * + * @param $caFile File with trusted certificates, in PEM-format. + * @return TRUE on success, or a string with error messages if it failed. + */ + private function validateCABuiltIn($caFile) { + + /* Clear openssl errors. */ + while(openssl_error_string() !== FALSE); + + $res = openssl_x509_checkpurpose($this->x509Certificate, X509_PURPOSE_ANY, array($caFile)); + + $errors = ''; + /* Log errors. */ + while( ($error = openssl_error_string()) !== FALSE) { + $errors .= ' [' . $error . ']'; + } + + if($res === -1) { + return $errors; + } + + + if($res !== TRUE) { + return $errors; + } + + return TRUE; + } + + + /** + * Validate the certificate used to sign the XML against a CA file, by using the "openssl verify" command. + * + * This function uses the openssl verify command to verify a certificate, to work around limitations + * on the openssl_x509_checkpurpose function. That function will not work on certificates without a purpose + * set. + * + * @param $caFile File with trusted certificates, in PEM-format. + * @return TRUE on success, a string with error messages on failure. + */ + private function validateCAExec($caFile) { + + $command = array( + 'openssl', 'verify', + '-CAfile', $caFile, + '-purpose', 'any', + ); + + $cmdline = ''; + foreach($command as $c) { + $cmdline .= escapeshellarg($c) . ' '; + } + + $cmdline .= '2>&1'; + $descSpec = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + ); + $process = proc_open($cmdline, $descSpec, $pipes); + if(!is_resource($process)) { + throw new Exception('Failed to execute verification command: ' . $cmdline . "\n"); + } + + if(fwrite($pipes[0], $this->x509Certificate) === FALSE) { + throw new Exception('Failed to write certificate for verification.' . "\n"); + } + fclose($pipes[0]); + + $out = ''; + while(!feof($pipes[1])) { + $line = trim(fgets($pipes[1])); + if(strlen($line) > 0) { + $out .= ' [' . $line . ']'; + } + } + fclose($pipes[1]); + + $status = proc_close($process); + if($status !== 0 || $out !== ' [stdin: OK]') { + return $out; + } + + return TRUE; + } + + + /** + * Validate the certificate used to sign the XML against a CA file. + * + * This function throws an exception if unable to validate against the given CA file. + * + * @param $caFile File with trusted certificates, in PEM-format. + */ + public function validateCA($caFile) { + + assert('is_string($caFile)'); + + if(!file_exists($caFile)) { + throw new Exception('Could not load CA file: ' . $caFile); + } + + if($this->x509Certificate === NULL) { + throw new Exception('Key used to sign the message was not an X509 certificate.'); + } + + $resBuiltIn = $this->validateCABuiltIn($caFile); + if($resBuiltIn !== TRUE) { + $resExternal = $this->validateCAExec($caFile); + if($resExternal !== TRUE) { + $certFingerprint = self::calculateX509Fingerprint($this->x509Certificate); + throw new Exception('Could not verify certificate with fingerprint ' . $certFingerprint . + ' against CA file "' . $caFile . '". Internal result:' . $resBuiltIn . + ' External result:' . $resExternal); + } + } + } + } ?> \ No newline at end of file