SAML.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <?php
  2. /**
  3. ** PHP versions 4 and 5
  4. **
  5. ** LICENSE: See the COPYING file included in this distribution.
  6. **
  7. ** @package OpenID
  8. ** @author Santosh Subramanian <subrasan@cs.sunysb.edu>
  9. ** @author Shishir Randive <srandive@cs.sunysb.edu>
  10. ** Stony Brook University.
  11. ** largely derived from
  12. **
  13. * Copyright (C) 2007 Google Inc.
  14. *
  15. * Licensed under the Apache License, Version 2.0 (the "License");
  16. * you may not use this file except in compliance with the License.
  17. * You may obtain a copy of the License at
  18. *
  19. * http://www.apache.org/licenses/LICENSE-2.0
  20. *
  21. * Unless required by applicable law or agreed to in writing, software
  22. * distributed under the License is distributed on an "AS IS" BASIS,
  23. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24. * See the License for the specific language governing permissions and
  25. * limitations under the License.
  26. **/
  27. class SAML{
  28. private $assertionTemplate=null;
  29. /**
  30. * Returns a SAML response with various elements filled in.
  31. * @param string $authenticatedUser The OpenId of the user
  32. * @param string $notBefore The ISO 8601 formatted date before which the
  33. response is invalid
  34. * @param string $notOnOrAfter The ISO 8601 formatted data after which the
  35. response is invalid
  36. * @param string $rsadsa 'rsa' if the response will be signed with RSA keys,
  37. 'dsa' for DSA keys
  38. * @param string $requestID The ID of the request we're responding to
  39. * @param string $destination The ACS URL that the response is submitted to
  40. * @return string XML SAML response.
  41. */
  42. function createSamlAssertion($authenticatedUser, $notBefore, $notOnOrAfter, $rsadsa, $acsURI,$attribute,$value,$assertionTemplate)
  43. {
  44. $samlResponse = $assertionTemplate;
  45. $samlResponse = str_replace('USERNAME_STRING', $authenticatedUser, $samlResponse);
  46. $samlResponse = str_replace('RESPONSE_ID', $this->samlCreateId(), $samlResponse);
  47. $samlResponse = str_replace('ISSUE_INSTANT', $this->samlGetDateTime(time()), $samlResponse);
  48. $samlResponse = str_replace('NOT_BEFORE', $this->samlGetDateTime(strtotime($notBefore)), $samlResponse);
  49. $samlResponse = str_replace('NOT_ON_OR_AFTER', $this->samlGetDateTime(strtotime($notOnOrAfter)),$samlResponse);
  50. $samlResponse = str_replace('ASSERTION_ID',$this->samlCreateId(), $samlResponse);
  51. $samlResponse = str_replace('RSADSA', strtolower($rsadsa), $samlResponse);
  52. $samlResponse = str_replace('ISSUER_DOMAIN', $acsURI, $samlResponse);
  53. $samlResponse = str_replace('ATTRIBUTE_NAME', $attribute, $samlResponse);
  54. $samlResponse = str_replace('ATTRIBUTE_VALUE', $value, $samlResponse);
  55. return $samlResponse;
  56. }
  57. /**
  58. * Signs a SAML response with the given private key, and embeds the public key.
  59. * @param string $responseXmlString The unsigned Assertion which will be signed
  60. * @param string $priKey Private key to sign the certificate
  61. * @param string $cert Public key certificate of signee
  62. * @return string Signed Assertion
  63. */
  64. function signAssertion($responseXmlString,$privKey,$cert)
  65. {
  66. if (file_exists("/tmp/xml")) {
  67. $tempFileDir="/tmp/xml/";
  68. } else {
  69. mkdir("/tmp/xml",0777);
  70. $tempFileDir="/tmp/xml/";
  71. }
  72. $tempName = 'saml-response-' . $this->samlCreateId() . '.xml';
  73. $tempFileName=$tempFileDir.$tempName;
  74. while (file_exists($tempFileName))
  75. $tempFileName = 'saml-response-' . $this->samlCreateId() . '.xml';
  76. if (!$handle = fopen($tempFileName, 'w')) {
  77. return null;
  78. }
  79. if (fwrite($handle, $responseXmlString) === false) {
  80. return null;
  81. }
  82. fclose($handle);
  83. $cmd = 'xmlsec1 --sign --privkey-pem ' . $privKey .
  84. ',' . $cert . ' --output ' . $tempFileName .
  85. '.out ' . $tempFileName;
  86. exec($cmd, $resp);
  87. unlink($tempFileName);
  88. $xmlResult = @file_get_contents($tempFileName . '.out');
  89. if (!$xmlResult) {
  90. return null;
  91. } else {
  92. unlink($tempFileName . '.out');
  93. return $xmlResult;
  94. }
  95. }
  96. /**
  97. * Verify a saml response with the given public key.
  98. * @param string $responseXmlString Response to sign
  99. * @param string $rootcert trusted public key certificate
  100. * @return string Signed SAML response
  101. */
  102. function verifyAssertion($responseXmlString,$rootcert)
  103. {
  104. date_default_timezone_set("UTC");
  105. if (file_exists("/tmp/xml")) {
  106. $tempFileDir="/tmp/xml/";
  107. } else {
  108. mkdir("/tmp/xml",0777);
  109. $tempFileDir="/tmp/xml/";
  110. }
  111. $tempName = 'saml-response-' . $this->samlCreateId() . '.xml';
  112. $tempFileName=$tempFileDir.$tempName;
  113. while (file_exists($tempFileName))
  114. $tempFileName = 'saml-response-' . $this->samlCreateId() . '.xml';
  115. if (!$handle = fopen($tempFileName, 'w')) {
  116. return false;
  117. }
  118. if (fwrite($handle, $responseXmlString) === false) {
  119. return false;
  120. }
  121. $p=xml_parser_create();
  122. $result=xml_parse_into_struct($p,$responseXmlString,$vals,$index);
  123. xml_parser_free($p);
  124. $cert_info=$index["X509CERTIFICATE"];
  125. $conditions=$index["CONDITIONS"];
  126. foreach($cert_info as $key=>$value){
  127. file_put_contents($tempFileName.'.cert',$vals[$value]['value']);
  128. }
  129. $cert=$tempFileName.'.cert';
  130. $before=0;
  131. $after=0;
  132. foreach($conditions as $key=>$value){
  133. $before=$vals[$value]['attributes']['NOTBEFORE'];
  134. $after=$vals[$value]['attributes']['NOTONORAFTER'];
  135. }
  136. $before=$this->validSamlDateFormat($before);
  137. $after=$this->validSamlDateFormat($after);
  138. if(strtotime("now") < $before || strtotime("now") >= $after){
  139. unlink($tempFileName);
  140. unlink($cert);
  141. return false;
  142. }
  143. fclose($handle);
  144. $cmd = 'xmlsec1 --verify --pubkey-cert ' . $cert .'--trusted '.$rootcert. ' '.$tempFileName.'* 2>&1 1>/dev/null';
  145. exec($cmd,$resp);
  146. if(strcmp($resp[0],"FAIL") == 0){
  147. $value = false;
  148. }elseif(strcmp($resp[0],"ERROR") == 0){
  149. $value = false;
  150. }elseif(strcmp($resp[0],"OK") == 0){
  151. $value = TRUE;
  152. }
  153. unlink($tempFileName);
  154. unlink($cert);
  155. return $value;
  156. }
  157. /**
  158. * Creates a 40-character string containing 160-bits of pseudorandomness.
  159. * @return string Containing pseudorandomness of 160 bits
  160. */
  161. function samlCreateId()
  162. {
  163. $rndChars = 'abcdefghijklmnop';
  164. $rndId = '';
  165. for ($i = 0; $i < 40; $i++ ) {
  166. $rndId .= $rndChars[rand(0,strlen($rndChars)-1)];
  167. }
  168. return $rndId;
  169. }
  170. /**
  171. * Returns a unix timestamp in xsd:dateTime format.
  172. * @param timestamp int UNIX Timestamp to convert to xsd:dateTime
  173. * ISO 8601 format.
  174. * @return string
  175. */
  176. function samlGetDateTime($timestamp)
  177. {
  178. return gmdate('Y-m-d\TH:i:s\Z', $timestamp);
  179. }
  180. /**
  181. * Attempts to check whether a SAML date is valid. Returns true or false.
  182. * @param string $samlDate
  183. * @return bool
  184. */
  185. function validSamlDateFormat($samlDate)
  186. {
  187. if ($samlDate == "") return false;
  188. $indexT = strpos($samlDate, 'T');
  189. $indexZ = strpos($samlDate, 'Z');
  190. if (($indexT != 10) || ($indexZ != 19)) {
  191. return false;
  192. }
  193. $dateString = substr($samlDate, 0, 10);
  194. $timeString = substr($samlDate, $indexT + 1, 8);
  195. list($year, $month, $day) = explode('-', $dateString);
  196. list($hour, $minute, $second) = explode(':', $timeString);
  197. $parsedDate = gmmktime($hour, $minute, $second, $month, $day, $year);
  198. if (($parsedDate === false) || ($parsedDate == -1)) return false;
  199. if (!checkdate($month, $day, $year)) return false;
  200. return $parsedDate;
  201. }
  202. }
  203. ?>