diff --git a/modules/InfoCard/config-template/config-login-infocard.php b/modules/InfoCard/config-template/config-login-infocard.php new file mode 100644 index 0000000000000000000000000000000000000000..055fee0a71ea5dc0253e414f59f675aac0734836 --- /dev/null +++ b/modules/InfoCard/config-template/config-login-infocard.php @@ -0,0 +1,97 @@ +<?php + +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 1-DEC-08 +* DESCRIPTION: 'login-infocard' module configuration. + + +-server_key: +-server_crt: +-IClogo: InfoCard logo (template's button) + + +Definitions taken from: +A Guide to Using the Identity Selector +Interoperability Profile V1.5 within Web +Applications and Browsers. +Copyright Microsoft +" +-issuer (optional) + This parameter specifies the URL of the STS from which to obtain a token. If omitted, no + specific STS is requested. The special value + “http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self” specifies that the + token should come from a Self-issued Identity Provider. + +-issuerPolicy (optional) + This parameter specifies the URL of an endpoint from which the STS’s WS-SecurityPolicy + can be retrieved using WS-MetadataExchange. This endpoint must use HTTPS. + +-tokenType (optional) + This parameter specifies the type of the token to be requested from the STS as a URI. Th + parameter can be omitted if the STS and the Web site front-end have a mutual + understanding about what token type will be provided or if the Web site is willing to accep + any token type. + +-requiredClaims (optional) + This parameter specifies the types of claims that must be supplied by the identity. If + omitted, there are no required claims. The value of requiredClaims is a space-separate + list of URIs, each specifying a required claim type. + +-optionalClaims (optional) + This parameter specifies the types of optional claims that may be supplied by the identity + If omitted, there are no optional claims. The value of optionalClaims is a space-separat + list of URIs, each specifying a claim type that can be optionally submitted. + +-privacyUrl (optional) + This parameter specifies the URL of the human-readable Privacy Policy of the site, if + provided. +" + + +-Claims supported by the current schema + givenname + surname + emailaddress + streetaddress + locality + stateorprovince + postalcode + country + primaryphone + dateofbirth + privatepersonalid + gender + webpage + +*/ + + +$config = array ( + + 'server_key' => '/etc/apache2/ssl/idp.key', + 'server_crt' => '/etc/apache2/ssl/idp.crt', + 'IClogo' => 'resources/infocard_114x80.png', + + + 'InfoCard' => array( + 'schema' => 'http://schemas.xmlsoap.org/ws/2005/05/identity', + 'issuer' => 'https://sts/tokenservice.php', + 'issuerPolicy' => '', + 'privacyURL' => '', + 'tokenType' => 'urn:oasis:names:tc:SAML:1.0:assertion', + 'requiredClaims' => array( + 'privatepersonalidentifier' => array('displayTag'=>"Id", 'description'=>"id"), + 'givenname' => array('displayTag'=>"Given Name", 'description'=>"etc"), + 'surname' => array('displayTag'=>"Surname", 'description'=>"apellidos"), + 'emailaddress' => array('displayTag'=>"e-mail", 'description'=>"E-mail address") + ), + 'optionalClaims' => array( + 'country' => array('displayTag'=>"country", 'description'=>"PaĂs"), + 'webpage' => array('displayTag'=>"webpage", 'description'=>"Página web") + ), + ), +); + +?> \ No newline at end of file diff --git a/modules/InfoCard/default-disable b/modules/InfoCard/default-disable new file mode 100644 index 0000000000000000000000000000000000000000..25615cb47c350d23033eb9801627ed8330bcc3e9 --- /dev/null +++ b/modules/InfoCard/default-disable @@ -0,0 +1,3 @@ +This file indicates that the default state of this module +is enabled. To disable, create a file named disable in the +same directory as this file. diff --git a/modules/InfoCard/dictionaries/logininfocard.php b/modules/InfoCard/dictionaries/logininfocard.php new file mode 100644 index 0000000000000000000000000000000000000000..8d8dfa65c0f942bfdcda52795f5a3c7adaeeb0e2 --- /dev/null +++ b/modules/InfoCard/dictionaries/logininfocard.php @@ -0,0 +1,186 @@ +<?php + +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 1-DEC-08 +* DESCRIPTION: 'login-infocard' module dictionary. +*/ + +$lang = array( + 'wrong_IC' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'Invalid InfoCard', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => 'InfoCard errĂłnea', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), + 'error_header' => array ( + 'no' => 'Feil', + 'nn' => 'Feil', + 'da' => 'Fejl', + 'en' => 'Error', + 'de' => 'Fehler', + 'sv' => 'Fel', + 'fi' => 'Virhe', + 'es' => 'Error', + 'fr' => 'Erreur', + 'nl' => 'Fout', + 'lb' => 'Fehler', + 'sl' => 'Napaka', + 'hr' => 'Greška', + 'hu' => 'Hiba', + 'pt' => 'Erro', + 'pt-BR' => 'Erro', + ), + 'user_IC_header' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'Select an InfoCard', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => 'Seleccione una InfoCard', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), + 'user_IC_text' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'A service has requested you to authenticate yourself. Please click on the image below to start a session with an InfoCard.', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => 'Un servicio solicita que se autentique. Pinche en la imagen inferior para iniciar una sesiĂłn con una InfoCard.', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), + 'login_button' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => ' ', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => ' ', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), + 'help_header' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'HELP! What is an InfoCard?', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => '¡Ayuda! ÂżQuĂ© es una InfoCard?', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), + 'help_text' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'Information Cards (aka InfoCard) is a web authentication technology. Contact with your services provider in order to configure your computer and gives you and Information Card (identification virtual card).', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => 'Information Cards (alias Infocard) es una tecnologĂa de autenticaciĂłn web. Consulte con su proveedor de servicios para que le ayude a configurar su máquina y le expida una Information Card (tarjeta virtual de identificaciĂłn).', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), + 'help_desk_link' => array ( + 'no' => 'Hjemmesiden til brukerstøtte', + 'nn' => 'Heimeside for brukarstøtte', + 'da' => 'Servicedesk', + 'en' => 'Help desk homepage', + 'de' => 'Seite des Helpdesk', + 'sv' => 'Hemsida för helpdesk', + 'es' => 'Página de soporte tĂ©cnico', + 'nl' => 'Helpdesk homepage', + 'sl' => 'Spletna stran tehniÄŤne podpore uporabnikom.', + 'hr' => 'Helpdesk stranice', + 'hu' => 'ĂśgyfĂ©lszolgálat weboldala', + 'pt' => 'Página do serviço de apoio ao utilizador', + 'pt-BR' => 'Central de Ajuda', + ), + 'help_desk_email' => array ( + 'no' => 'Send e-post til brukerstøtte', + 'nn' => 'Send epost til brukarstøtte', + 'da' => 'Send en e-mail servicedesk', + 'en' => 'Send e-mail to help desk', + 'de' => 'Email an den Helpdesk senden', + 'sv' => 'Skicka e-post till helpdesk', + 'es' => 'Enviar correo electrĂłnico al soporte tĂ©cnico', + 'nl' => 'Stuur een e-mail naar de helpdesk', + 'sl' => 'Pošlji e-poštno sporoÄŤilo tehniÄŤni podpori.', + 'hr' => 'Pošaljite e-mail helpdesk sluĹľbi', + 'hu' => 'KĂĽldjön e-mailt az ĂĽgyfĂ©lszolgálatnak', + 'pt' => 'Enviar um e-mail para o serviço de apoio ao utilizador', + 'pt-BR' => 'Envie um e-mail para a Central de Ajuda.', + ), + 'contact_info' => array ( + 'no' => 'Kontaktinformasjon:', + 'nn' => 'Kontaktinformasjon:', + 'da' => 'Kontaktoplysninger', + 'en' => 'Contact information:', + 'de' => 'Kontakt', + 'sv' => 'Kontaktinformation:', + 'es' => 'InformaciĂłn de contacto:', + 'nl' => 'Contact informatie', + 'sl' => 'Kontakt', + 'hr' => 'Kontakt podaci', + 'hu' => 'ElĂ©rĂ©si informáciĂłk', + 'pt' => 'Contactos:', + 'pt-BR' => 'Informações de Contato', + ), + +); + + +?> \ No newline at end of file diff --git a/modules/InfoCard/lib/Auth/Source/ICAuth.php b/modules/InfoCard/lib/Auth/Source/ICAuth.php new file mode 100644 index 0000000000000000000000000000000000000000..b16167bd63abffa78b801e40dccf78e8b2b04019 --- /dev/null +++ b/modules/InfoCard/lib/Auth/Source/ICAuth.php @@ -0,0 +1,80 @@ +<?php +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 1-DEC-08 +* DESCRIPTION: +* 'login-infocard' module. +* Auth class +*/ + +class sspmod_InfoCard_Auth_Source_ICAuth extends SimpleSAML_Auth_Source { + + //The string used to identify our states. + const STAGEID = 'sspmod_core_Auth_UserPassBase.state'; + + + //The key of the AuthId field in the state. + const AUTHID = 'sspmod_core_Auth_UserPassBase.AuthId'; + + + public function __construct($info, $config) { + assert('is_array($info)'); + assert('is_array($config)'); + + /* Call the parent constructor first, as required by the interface. */ + parent::__construct($info, $config); + } + + + public function authenticate(&$state) { + assert('is_array($state)'); + + /* We are going to need the authId in order to retrieve this authentication source later. */ + $state[self::AUTHID] = $this->authId; + $id = SimpleSAML_Auth_State::saveState($state, self::STAGEID); + $url = SimpleSAML_Module::getModuleURL('InfoCard/login-infocard.php'); + SimpleSAML_Utilities::redirect($url, array('AuthState' => $id)); + } + + + public static function handleLogin($authStateId, $xmlToken) { + assert('is_string($authStateId)'); + + /* Retrieve the authentication state. */ + $state = SimpleSAML_Auth_State::loadState($authStateId, self::STAGEID); + + /* Find authentication source. */ + assert('array_key_exists(self::AUTHID, $state)'); + $source = SimpleSAML_Auth_Source::getById($state[self::AUTHID]); + if ($source === NULL) { + throw new Exception('Could not find authentication source with id ' . $state[self::AUTHID]); + } + + $config = SimpleSAML_Configuration::getInstance(); + $autoconfig = $config->copyFromBase('logininfocard', 'config-login-infocard.php'); + $server_key = $autoconfig->getValue('server_key'); + $server_crt = $autoconfig->getValue('server_crt'); + $Infocard = $autoconfig->getValue('InfoCard'); + + $infocard = new sspmod_InfoCard_RP_InfoCard(); + $infocard->addCertificatePair($server_key,$server_crt); + $claims = $infocard->process($xmlToken); + if($claims->isValid()) { + $attributes = array(); + foreach ($Infocard['requiredClaims'] as $claim => $data){ + $attributes[$claim] = array($claims->$claim); + } + foreach ($Infocard['optionalClaims'] as $claim => $data){ + $attributes[$claim] = array($claims->$claim); + } + $state['Attributes'] = $attributes; + SimpleSAML_Auth_Source::completeAuth($state); + } else { + return 'wrong_IC'; + } + } + +} + +?> \ No newline at end of file diff --git a/modules/InfoCard/lib/RP/InfoCard.php b/modules/InfoCard/lib/RP/InfoCard.php new file mode 100644 index 0000000000000000000000000000000000000000..442629195efdb12b9cae9737c539691bd09960fc --- /dev/null +++ b/modules/InfoCard/lib/RP/InfoCard.php @@ -0,0 +1,311 @@ +<?php + +require_once 'Zend_InfoCard_Claims.php'; + +class sspmod_InfoCard_RP_InfoCard +{ + + const XENC_NS = "http://www.w3.org/2001/04/xmlenc#"; + const XENC_ELEMENT_TYPE = "http://www.w3.org/2001/04/xmlenc#Element"; + const XENC_ENC_ALGO = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"; + const XENC_KEYINFO_ENC_ALGO = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"; + + const DSIG_NS = "http://www.w3.org/2000/09/xmldsig#"; + const DSIG_RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + const DSIG_ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"; + const DSIG_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"; + + const CANON_EXCLUSIVE = "http://www.w3.org/2001/10/xml-exc-c14n#"; + + const WSSE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; + const WSSE_KEYID_VALUE_TYPE = "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1"; + + const XMLSOAP_SELF_ISSUED = "http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self"; + + const XMLSOAP_CLAIMS_NS = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims'; + + const SAML_ASSERTION_1_0_NS = "urn:oasis:names:tc:SAML:1.0:assertion"; + const SAML_ASSERTION_1_1_NS = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1"; + + protected $_private_key_file; + protected $_public_key_file; + protected $_password; + protected $_sxml; + + public function __construct() { + if(!extension_loaded('mcrypt')) { + SimpleSAML_Logger::debug("Use of the InfoCard component requires the mcrypt extension to be enabled in PHP"); + } + + if(!extension_loaded('openssl')) { + SimpleSAML_Logger::debug("Use of the InfoCard component requires the openssl extension to be enabled in PHP"); + } + } + + public function addCertificatePair($private_key_file, $public_key_file, $password = null) { + $this->_private_key_file = $private_key_file; + $this->_public_key_file = $public_key_file; + $this->_password = $password; + + if(!file_exists($this->_private_key_file)) { + SimpleSAML_Logger::debug("Private key file does not exists"); + } + + if(!file_exists($this->_public_key_file)) { + SimpleSAML_Logger::debug("Public key file does not exists"); + } + + if(!is_readable($this->_private_key_file)) { + SimpleSAML_Logger::debug("Private key file is not readable"); + } + + if(!is_readable($this->_public_key_file)) { + SimpleSAML_Logger::debug("Public key file is not readable"); + } + } + + public function process($xmlToken) { + if(strpos($xmlToken, "EncryptedData") === false ) { + return self::processUnSecureToken($xmlToken); + } + else { + return self::processSecureToken($xmlToken); + } + } + + private function processSecureToken($xmlToken) { + $retval = new Zend_InfoCard_Claims(); + + try { + $this->_sxml = simplexml_load_string($xmlToken); + $decryptedToken = self::decryptToken($xmlToken); + } + catch(Exception $e) { + $retval->setError('Failed to extract assertion document'); + SimpleSAML_Logger::debug('Failed to extract assertion document'); + $retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE); + return $retval; + } + + try { + $assertions = self::getAssertions($decryptedToken); + } + catch(Exception $e) { + $retval->setError('Failure processing assertion document'); + SimpleSAML_Logger::debug('Failure processing assertion document'); + $retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE); + return $retval; + } + + try { + $reference_id = self::ValidateSignature($assertions); + self::checkConditions($reference_id, $assertions); + } + catch(Exception $e) { + $retval->setError($e->getMessage()); + $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE); + return $retval; + } + + return self::getClaims($retval, $assertions); + } + + private function processUnsecureToken($xmlToken) { + $retval = new Zend_InfoCard_Claims(); + + try { + $assertions = self::getAssertions($xmlToken); + } + catch(Exception $e) { + $retval->setError('Failure processing assertion document'); + SimpleSAML_Logger::debug('Failure processing assertion document'); + $retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE); + return $retval; + } + + return self::getClaims($retval, $assertions); + } + + private function ValidateSignature($assertions) { + include_once 'Zend_InfoCard_Xml_Security.php'; + $reference_id = Zend_InfoCard_Xml_Security::validateXMLSignature($assertions->asXML()); + if(!$reference_id) { + SimpleSAML_Logger::debug("Failure Validating the Signature of the assertion document"); + } + + return $reference_id; + } + + private function checkConditions($reference_id, $assertions) { + if($reference_id[0] == '#') { + $reference_id = substr($reference_id, 1); + } else { + SimpleSAML_Logger::debug("Reference of document signature does not reference the local document"); + } + + if($reference_id != $assertions->getAssertionID()) { + SimpleSAML_Logger::debug("Reference of document signature does not reference the local document"); + } + + $conditions = $assertions->getConditions(); + if(is_array($condition_error = $assertions->validateConditions($conditions))) { + SimpleSAML_Logger::debug("Conditions of assertion document are not met: {$condition_error[1]} ({$condition_error[0]})"); + } + } + + private function getClaims($retval, $assertions) { + $attributes = $assertions->getAttributes(); + $retval->setClaims($attributes); + if($retval->getCode() == 0) { + $retval->setCode(Zend_InfoCard_Claims::RESULT_SUCCESS); + } + + return $retval; + } + + private function getAssertions($strXmlData) { + $sxe = simplexml_load_string($strXmlData); + $namespaces = $sxe->getDocNameSpaces(); + foreach($namespaces as $namespace) { + switch($namespace) { + case self::SAML_ASSERTION_1_0_NS: + include_once 'Zend_InfoCard_Xml_Assertion_Saml.php'; + return simplexml_load_string($strXmlData, 'Zend_InfoCard_Xml_Assertion_Saml', null); + } + } + + SimpleSAML_Logger::debug("Unable to determine Assertion type by Namespace"); + } + + private function decryptToken($xmlToken) { + if($this->_sxml['Type'] != self::XENC_ELEMENT_TYPE) { + SimpleSAML_Logger::debug("Unknown EncryptedData type found"); + } + + $this->_sxml->registerXPathNamespace('enc', self::XENC_NS); + list($encryptionMethod) = $this->_sxml->xpath("//enc:EncryptionMethod"); + if(!$encryptionMethod instanceof SimpleXMLElement) { + SimpleSAML_Logger::debug("EncryptionMethod node not found"); + } + + $encMethodDom = dom_import_simplexml($encryptionMethod); + if(!$encMethodDom instanceof DOMElement) { + SimpleSAML_Logger::debug("Failed to create DOM from EncryptionMethod node"); + } + + if(!$encMethodDom->hasAttribute("Algorithm")) { + SimpleSAML_Logger::debug("Unable to determine the encryption algorithm in the Symmetric enc:EncryptionMethod XML block"); + } + + $algo = $encMethodDom->getAttribute("Algorithm"); + if($algo != self::XENC_ENC_ALGO) { + SimpleSAML_Logger::debug("Unsupported encryption algorithm"); + } + + $this->_sxml->registerXPathNamespace('ds', self::DSIG_NS); + list($keyInfo) = $this->_sxml->xpath("ds:KeyInfo"); + if(!$keyInfo instanceof SimpleXMLElement) { + SimpleSAML_Logger::debug("KeyInfo node not found"); + } + + $keyInfo->registerXPathNamespace('enc', self::XENC_NS); + list($encryptedKey) = $keyInfo->xpath("enc:EncryptedKey"); + if(!$encryptedKey instanceof SimpleXMLElement) { + SimpleSAML_Logger::debug("EncryptedKey element not found in KeyInfo"); + } + + $encryptedKey->registerXPathNamespace('enc', self::XENC_NS); + list($keyInfoEncryptionMethod) = $encryptedKey->xpath("enc:EncryptionMethod"); + if(!$keyInfoEncryptionMethod instanceof SimpleXMLElement) { + SimpleSAML_Logger::debug("EncryptionMethod element not found in EncryptedKey"); + } + + $keyInfoEncMethodDom = dom_import_simplexml($keyInfoEncryptionMethod); + if(!$keyInfoEncMethodDom instanceof DOMElement) { + SimpleSAML_Logger::debug("Failed to create DOM from EncryptionMethod node"); + } + + if(!$keyInfoEncMethodDom->hasAttribute("Algorithm")) { + SimpleSAML_Logger::debug("Unable to determine the encryption algorithm in the Symmetric enc:EncryptionMethod XML block"); + } + + $keyInfoEncMethodAlgo = $keyInfoEncMethodDom->getAttribute("Algorithm"); + if($keyInfoEncMethodAlgo != self::XENC_KEYINFO_ENC_ALGO) { + SimpleSAML_Logger::debug("Unsupported encryption algorithm"); + } + + $encryptedKey->registerXPathNamespace('ds', self::DSIG_NS); + $encryptedKey->registerXPathNamespace('wsse', self::WSSE_NS); + list($keyIdentifier) = $encryptedKey->xpath("ds:KeyInfo/wsse:SecurityTokenReference/wsse:KeyIdentifier"); + if(!$keyIdentifier instanceof SimpleXMLElement) { + SimpleSAML_Logger::debug("KeyInfo/SecurityTokenReference/KeyIdentifier node not found in KeyInfo"); + } + + $keyIdDom = dom_import_simplexml($keyIdentifier); + if(!$keyIdDom instanceof DOMElement) { + SimpleSAML_Logger::debug("Failed to create DOM from KeyIdentifier node"); + } + + if(!$keyIdDom->hasAttribute("ValueType")) { + SimpleSAML_Logger::debug("Unable to determine ValueType of KeyIdentifier"); + } + + $valueType = $keyIdDom->getAttribute("ValueType"); + if($valueType != self::WSSE_KEYID_VALUE_TYPE) { + SimpleSAML_Logger::debug("Unsupported KeyIdentifier ValueType"); + } + + list($cipherValue) = $encryptedKey->xpath("enc:CipherData/enc:CipherValue"); + if(!$cipherValue instanceof SimpleXMLElement) { + SimpleSAML_Logger::debug("CipherValue node found in EncryptedKey"); + } + + $base64DecodeSupportsStrictParam = version_compare(PHP_VERSION, '5.2.0', '>='); + + if ($base64DecodeSupportsStrictParam) { + $keyCipherValueBase64Decoded = base64_decode($cipherValue, true); + } else { + $keyCipherValueBase64Decoded = base64_decode($cipherValue); + } + + $private_key = openssl_pkey_get_private(array(file_get_contents($this->_private_key_file), $this->_password)); + if(!$private_key) { + SimpleSAML_Logger::debug("Unable to load private key"); + } + + $result = openssl_private_decrypt($keyCipherValueBase64Decoded, $symmetricKey, $private_key, OPENSSL_PKCS1_OAEP_PADDING); + openssl_free_key($private_key); + + if(!$result) { + SimpleSAML_Logger::debug("Unable to decrypt symmetric key"); + } + + list($cipherValue) = $this->_sxml->xpath("enc:CipherData/enc:CipherValue"); + if(!$cipherValue instanceof SimpleXMLElement) { + SimpleSAML_Logger::debug("CipherValue node found in EncryptedData"); + } + + if ($base64DecodeSupportsStrictParam) { + $keyCipherValueBase64Decoded = base64_decode($cipherValue, true); + } else { + $keyCipherValueBase64Decoded = base64_decode($cipherValue); + } + + $mcrypt_iv = substr($keyCipherValueBase64Decoded, 0, 16); + $keyCipherValueBase64Decoded = substr($keyCipherValueBase64Decoded, 16); + $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $symmetricKey, $keyCipherValueBase64Decoded, MCRYPT_MODE_CBC, $mcrypt_iv); + + if(!$decrypted) { + SimpleSAML_Logger::debug("Unable to decrypt token"); + } + + $decryptedLength = strlen($decrypted); + $paddingLength = substr($decrypted, $decryptedLength -1, 1); + $decrypted = substr($decrypted, 0, $decryptedLength - ord($paddingLength)); + $decrypted = rtrim($decrypted, "\0"); + + return $decrypted; + } +} + +?> diff --git a/modules/InfoCard/lib/RP/LICENSE.txt b/modules/InfoCard/lib/RP/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..77a910f6aca7901d30410ee523685e1f75cc1f0d --- /dev/null +++ b/modules/InfoCard/lib/RP/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2005-2008, Zend Technologies USA, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Zend Technologies USA, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/modules/InfoCard/lib/RP/Zend_InfoCard_Claims.php b/modules/InfoCard/lib/RP/Zend_InfoCard_Claims.php new file mode 100644 index 0000000000000000000000000000000000000000..321d1b14976a7fb9202ca02f8789f7228531bf89 --- /dev/null +++ b/modules/InfoCard/lib/RP/Zend_InfoCard_Claims.php @@ -0,0 +1,304 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_InfoCard + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Claims.php 9094 2008-03-30 18:36:55Z thomas $ + */ + +/** + * Result value of the InfoCard component, contains any error messages and claims + * from the processing of an information card. + * + * @category Zend + * @package Zend_InfoCard + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_InfoCard_Claims +{ + /** + * Successful validation and extraion of claims + */ + const RESULT_SUCCESS = 1; + + /** + * Indicates there was an error processing the XML document + */ + const RESULT_PROCESSING_FAILURE = 2; + + /** + * Indicates that the signature values within the XML document failed verification + */ + const RESULT_VALIDATION_FAILURE = 3; + + /** + * The default namespace to assume in these claims + * + * @var string + */ + protected $_defaultNamespace = null; + + /** + * A boolean indicating if the claims should be consider "valid" or not based on processing + * + * @var bool + */ + protected $_isValid = true; + + /** + * The error message if any + * + * @var string + */ + protected $_error = ""; + + /** + * An array of claims taken from the information card + * + * @var array + */ + protected $_claims; + + /** + * The result code of processing the information card as defined by the constants of this class + * + * @var integer + */ + protected $_code; + + /** + * Override for the safeguard which ensures that you don't use claims which failed validation. + * Used in situations when there was a validation error you'd like to ignore + * + * @return Zend_InfoCard_Claims + */ + public function forceValid() + { + trigger_error("Forcing Claims to be valid although it is a security risk", E_USER_WARNING); + $this->_isValid = true; + return $this; + } + + /** + * Retrieve the PPI (Private Personal Identifier) associated with the information card + * + * @return string the private personal identifier + */ + public function getCardID() + { + return $this->getClaim('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier'); + } + + /** + * Retrieves the default namespace used in this information card. If a default namespace was not + * set, it figures out which one to consider 'default' by taking the first namespace sorted by use-count + * in claims + * + * @throws Exception + * @return string The default namespace + */ + public function getDefaultNamespace() + { + + if(is_null($this->_defaultNamespace)) { + + $namespaces = array(); + $leader = ''; + foreach($this->_claims as $claim) { + + if(!isset($namespaces[$claim['namespace']])) { + $namespaces[$claim['namespace']] = 1; + } else { + $namespaces[$claim['namespace']]++; + } + + if(empty($leader) || ($namespaces[$claim['namespace']] > $leader)) { + $leader = $claim['namespace']; + } + } + + if(empty($leader)) { + throw new Exception("Failed to determine default namespace"); + } + + $this->setDefaultNamespace($leader); + } + + return $this->_defaultNamespace; + } + + /** + * Set the default namespace, overriding any existing default + * + * @throws Exception + * @param string $namespace The default namespace to use + * @return Zend_InfoCard_Claims + */ + public function setDefaultNamespace($namespace) + { + + foreach($this->_claims as $claim) { + if($namespace == $claim['namespace']) { + $this->_defaultNamespace = $namespace; + return $this; + } + } + + throw new Exception("At least one claim must exist in specified namespace to make it the default namespace"); + } + + /** + * Indicates if this claim object contains validated claims or not + * + * @return bool + */ + public function isValid() + { + return $this->_isValid; + } + + /** + * Set the error message contained within the claims object + * + * @param string $error The error message + * @return Zend_InfoCard_Claims + */ + public function setError($error) + { + $this->_error = $error; + $this->_isValid = false; + return $this; + } + + /** + * Retrieve the error message contained within the claims object + * + * @return string The error message + */ + public function getErrorMsg() + { + return $this->_error; + } + + /** + * Set the claims for the claims object. Can only be set once and is done + * by the component itself. Internal use only. + * + * @throws Exception + * @param array $claims + * @return Zend_InfoCard_Claims + */ + public function setClaims(Array $claims) + { + if(!is_null($this->_claims)) { + throw new Exception("Claim objects are read-only"); + } + + $this->_claims = $claims; + return $this; + } + + /** + * Set the result code of the claims object. + * + * @throws Exception + * @param int $code The result code + * @return Zend_InfoCard_Claims + */ + public function setCode($code) + { + switch($code) { + case self::RESULT_PROCESSING_FAILURE: + case self::RESULT_SUCCESS: + case self::RESULT_VALIDATION_FAILURE: + $this->_code = $code; + return $this; + } + + throw new Exception("Attempted to set unknown error code"); + } + + /** + * Gets the result code of the claims object + * + * @return integer The result code + */ + public function getCode() + { + return $this->_code; + } + + /** + * Get a claim by providing its complete claim URI + * + * @param string $claimURI The complete claim URI to retrieve + * @return mixed The claim matching that specific URI or null if not found + */ + public function getClaim($claimURI) + { + if($this->claimExists($claimURI)) { + return $this->_claims[$claimURI]['value']; + } + + return null; + } + + /** + * Indicates if a specific claim URI exists or not within the object + * + * @param string $claimURI The complete claim URI to check + * @return bool true if the claim exists, false if not found + */ + public function claimExists($claimURI) + { + return isset($this->_claims[$claimURI]); + } + + /** + * Magic helper function + * @throws Exception + */ + public function __unset($k) + { + throw new Exception("Claim objects are read-only"); + } + + /** + * Magic helper function + */ + public function __isset($k) + { + return $this->claimExists("{$this->getDefaultNamespace()}/$k"); + } + + /** + * Magic helper function + */ + public function __get($k) + { + return $this->getClaim("{$this->getDefaultNamespace()}/$k"); + } + + /** + * Magic helper function + * @throws Exception + */ + public function __set($k, $v) + { + throw new Exception("Claim objects are read-only"); + } +} diff --git a/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Assertion_Saml.php b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Assertion_Saml.php new file mode 100644 index 0000000000000000000000000000000000000000..b82d63603045ce051de856de655fd44c132b7da4 --- /dev/null +++ b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Assertion_Saml.php @@ -0,0 +1,272 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Saml.php 9094 2008-03-30 18:36:55Z thomas $ + */ + +/** + * A Xml Assertion Document in SAML Token format + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_InfoCard_Xml_Assertion_Saml extends SimpleXMLElement +{ + + /** + * Audience Restriction Condition + */ + const CONDITION_AUDIENCE = 'AudienceRestrictionCondition'; + + /** + * The URI for a 'bearer' confirmation + */ + const CONFIRMATION_BEARER = 'urn:oasis:names:tc:SAML:1.0:cm:bearer'; + + /** + * The amount of time in seconds to buffer when checking conditions to ensure + * that differences between client/server clocks don't interfer too much + */ + const CONDITION_TIME_ADJ = 3600; // +- 5 minutes + + protected function _getServerName() { + return $_SERVER['SERVER_NAME']; + } + + protected function _getServerPort() { + return $_SERVER['SERVER_PORT']; + } + + /** + * Validate the conditions array returned from the getConditions() call + * + * @param array $conditions An array of condtions for the assertion taken from getConditions() + * @return mixed Boolean true on success, an array of condition, error message on failure + */ + public function validateConditions(Array $conditions) + { + + $currentTime = time(); + + if(!empty($conditions)) { + + foreach($conditions as $condition => $conditionValue) { + switch(strtolower($condition)) { + case 'audiencerestrictioncondition': + + $serverName = $this->_getServerName(); + $serverPort = $this->_getServerPort(); + + $self_aliases[] = $serverName; + $self_aliases[] = "{{$serverName}:{$serverPort}"; + + $found = false; + if(is_array($conditionValue)) { + foreach($conditionValue as $audience) { + + list(,,$audience) = explode('/', $audience); + if(in_array($audience, $self_aliases)) { + $found = true; + break; + } + } + } + + if(!$found) { + return array($condition, 'Could not find self in allowed audience list'); + } + + break; + case 'notbefore': + $notbeforetime = strtotime($conditionValue); + + if($currentTime < $notbeforetime) { + if($currentTime + self::CONDITION_TIME_ADJ < $notbeforetime) { + return array($condition, 'Current time is before specified window'); + } + } + + break; + case 'notonorafter': + $notonoraftertime = strtotime($conditionValue); + + if($currentTime >= $notonoraftertime) { + if($currentTime - self::CONDITION_TIME_ADJ >= $notonoraftertime) { + return array($condition, 'Current time is after specified window'); + } + } + + break; + + } + } + } + return true; + } + + /** + * Get the Assertion URI for this type of Assertion + * + * @return string the Assertion URI + */ + public function getAssertionURI() + { + return Zend_InfoCard_Xml_Assertion::TYPE_SAML; + } + + /** + * Get the Major Version of the SAML Assertion + * + * @return integer The major version number + */ + public function getMajorVersion() + { + return (int)(string)$this['MajorVersion']; + } + + /** + * The Minor Version of the SAML Assertion + * + * @return integer The minor version number + */ + public function getMinorVersion() + { + return (int)(string)$this['MinorVersion']; + } + + /** + * Get the Assertion ID of the assertion + * + * @return string The Assertion ID + */ + public function getAssertionID() + { + return (string)$this['AssertionID']; + } + + /** + * Get the Issuer URI of the assertion + * + * @return string the URI of the assertion Issuer + */ + public function getIssuer() + { + return (string)$this['Issuer']; + } + + /** + * Get the Timestamp of when the assertion was issued + * + * @return integer a UNIX timestamp representing when the assertion was issued + */ + public function getIssuedTimestamp() + { + return strtotime((string)$this['IssueInstant']); + } + + /** + * Return an array of conditions which the assertions are predicated on + * + * @throws Exception + * @return array an array of conditions + */ + public function getConditions() + { + + list($conditions) = $this->xpath("//saml:Conditions"); + + if(!($conditions instanceof SimpleXMLElement)) { + throw new Exception("Unable to find the saml:Conditions block"); + } + + $retval = array(); + + foreach($conditions->children('urn:oasis:names:tc:SAML:1.0:assertion') as $key => $value) { + switch($key) { + case self::CONDITION_AUDIENCE: + foreach($value->children('urn:oasis:names:tc:SAML:1.0:assertion') as $audience_key => $audience_value) { + if($audience_key == 'Audience') { + $retval[$key][] = (string)$audience_value; + } + } + break; + } + } + + $retval['NotBefore'] = (string)$conditions['NotBefore']; + $retval['NotOnOrAfter'] = (string)$conditions['NotOnOrAfter']; + + return $retval; + } + + /** + * Get they KeyInfo element for the Subject KeyInfo block + * + * @todo Not Yet Implemented + * @ignore + */ + public function getSubjectKeyInfo() + { + /** + * @todo Not sure if this is part of the scope for now.. + */ + + if($this->getConfirmationMethod() == self::CONFIRMATION_BEARER) { + throw new Exception("Cannot get Subject Key Info when Confirmation Method was Bearer"); + } + } + + /** + * Return the Confirmation Method URI used in the Assertion + * + * @return string The confirmation method URI + */ + public function getConfirmationMethod() + { + list($confirmation) = $this->xPath("//saml:ConfirmationMethod"); + return (string)$confirmation; + } + + /** + * Return an array of attributes (claims) contained within the assertion + * + * @return array An array of attributes / claims within the assertion + */ + public function getAttributes() + { + $attributes = $this->xPath('//saml:Attribute'); + + $retval = array(); + foreach($attributes as $key => $value) { + + $retkey = (string)$value['AttributeNamespace'].'/'.(string)$value['AttributeName']; + + $retval[$retkey]['name'] = (string)$value['AttributeName']; + $retval[$retkey]['namespace'] = (string)$value['AttributeNamespace']; + + list($aValue) = $value->children('urn:oasis:names:tc:SAML:1.0:assertion'); + $retval[$retkey]['value'] = (string)$aValue; + } + + return $retval; + } +} diff --git a/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security.php b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security.php new file mode 100644 index 0000000000000000000000000000000000000000..da4ef9f7905a5b145f9631d07640edcce88dba40 --- /dev/null +++ b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security.php @@ -0,0 +1,302 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml_Security + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Security.php 9094 2008-03-30 18:36:55Z thomas $ + */ + +/** + * Zend_InfoCard_Xml_Security_Transform + */ +require_once 'Zend_InfoCard_Xml_Security_Transform.php'; + +/** + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml_Security + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_InfoCard_Xml_Security +{ + /** + * ASN.1 type INTEGER class + */ + const ASN_TYPE_INTEGER = 0x02; + + /** + * ASN.1 type BIT STRING class + */ + const ASN_TYPE_BITSTRING = 0x03; + + /** + * ASN.1 type SEQUENCE class + */ + const ASN_TYPE_SEQUENCE = 0x30; + + /** + * The URI for Canonical Method C14N Exclusive + */ + const CANONICAL_METHOD_C14N_EXC = 'http://www.w3.org/2001/10/xml-exc-c14n#'; + + /** + * The URI for Signature Method SHA1 + */ + const SIGNATURE_METHOD_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + + /** + * The URI for Digest Method SHA1 + */ + const DIGEST_METHOD_SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'; + + /** + * The Identifier for RSA Keys + */ + const RSA_KEY_IDENTIFIER = '300D06092A864886F70D0101010500'; + + /** + * Constructor (disabled) + * + * @return void + */ + private function __construct() + { + } + + /** + * Validates the signature of a provided XML block + * + * @param string $strXMLInput An XML block containing a Signature + * @return bool True if the signature validated, false otherwise + * @throws Exception + */ + static public function validateXMLSignature($strXMLInput) + { + if(!extension_loaded('openssl')) { + SimpleSAML_Logger::debug("You must have the openssl extension installed to use this class"); + } + + $sxe = simplexml_load_string($strXMLInput); + + $sxe->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); + + list($canonMethod) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:CanonicalizationMethod"); + switch((string)$canonMethod['Algorithm']) { + case self::CANONICAL_METHOD_C14N_EXC: + $cMethod = (string)$canonMethod['Algorithm']; + break; + default: + SimpleSAML_Logger::debug("Unknown or unsupported CanonicalizationMethod Requested"); + } + + list($signatureMethod) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:SignatureMethod"); + switch((string)$signatureMethod['Algorithm']) { + case self::SIGNATURE_METHOD_SHA1: + $sMethod = (string)$signatureMethod['Algorithm']; + break; + default: + SimpleSAML_Logger::debug("Unknown or unsupported SignatureMethod Requested"); + } + + list($digestMethod) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod"); + switch((string)$digestMethod['Algorithm']) { + case self::DIGEST_METHOD_SHA1: + $dMethod = (string)$digestMethod['Algorithm']; + break; + default: + SimpleSAML_Logger::debug("Unknown or unsupported DigestMethod Requested"); + } + + $base64DecodeSupportsStrictParam = version_compare(PHP_VERSION, '5.2.0', '>='); + + list($digestValue) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestValue"); + if ($base64DecodeSupportsStrictParam) { + $dValue = base64_decode((string)$digestValue, true); + } else { + $dValue = base64_decode((string)$digestValue); + } + + list($signatureValueElem) = $sxe->xpath("//ds:Signature/ds:SignatureValue"); + if ($base64DecodeSupportsStrictParam) { + $signatureValue = base64_decode((string)$signatureValueElem, true); + } else { + $signatureValue = base64_decode((string)$signatureValueElem); + } + + $transformer = new Zend_InfoCard_Xml_Security_Transform(); + + //need to fix this later + $transforms = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:Reference/ds:Transforms/ds:Transform"); + while(list( , $transform) = each($transforms)) { + $transformer->addTransform((string)$transform['Algorithm']); + } + + $transformed_xml = $transformer->applyTransforms($strXMLInput); + + //$transformed_xml_binhash = pack("H*", sha1($transformed_xml)); + $transformed_xml_binhash = pack("H*", sha1($transformed_xml)); + + if($transformed_xml_binhash != $dValue) { + SimpleSAML_Logger::debug("Locally Transformed XML (".$transformed_xml_binhash.") does not match XML Document (".$dValue."). Cannot Verify Signature"); + } + + $public_key = null; + + + $sxe->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); + list($keyValue) = $sxe->xpath("//ds:Signature/ds:KeyInfo"); + + $keyValue->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); + + list($x509cert) = $keyValue->xpath("ds:X509Data/ds:X509Certificate"); + list($rsaKeyValue) = $keyValue->xpath("ds:KeyValue/ds:RSAKeyValue"); + + switch(true) { + case isset($x509cert): + + $certificate = (string)$x509cert; + + + $pem = "-----BEGIN CERTIFICATE-----\n" . + wordwrap($certificate, 64, "\n", true) . + "\n-----END CERTIFICATE-----"; + + $public_key = openssl_pkey_get_public($pem); + + if(!$public_key) { + SimpleSAML_Logger::debug("Unable to extract and prcoess X509 Certificate from KeyValue"); + } + + break; + case isset($rsaKeyValue): + + $rsaKeyValue->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); + list($modulus) = $rsaKeyValue->xpath("ds:Modulus"); + list($exponent) = $rsaKeyValue->xpath("ds:Exponent"); + if(!isset($modulus) || + !isset($exponent)) { + SimpleSAML_Logger::debug("RSA Key Value not in Modulus/Exponent form"); + } + + $modulus = base64_decode((string)$modulus); + $exponent = base64_decode((string)$exponent); + + $pem_public_key = self::_getPublicKeyFromModExp($modulus, $exponent); + + $public_key = openssl_pkey_get_public ($pem_public_key); + + break; + default: + SimpleSAML_Logger::debug("Unable to determine or unsupported representation of the KeyValue block"); + } + + $transformer = new Zend_InfoCard_Xml_Security_Transform(); + $transformer->addTransform((string)$canonMethod['Algorithm']); + + list($signedInfo) = $sxe->xpath("//ds:Signature/ds:SignedInfo"); + $signedInfoXML = self::addNamespace($signedInfo, "http://www.w3.org/2000/09/xmldsig#"); + + $canonical_signedinfo = $transformer->applyTransforms($signedInfoXML); + + if(openssl_verify($canonical_signedinfo, $signatureValue, $public_key)) { + list($reference) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:Reference"); + return (string)$reference['URI']; + } + + return false; + } + + private function addNamespace($xmlElem, $ns) { + $xmlElem->addAttribute('DS_NS', $ns); + $xml = $xmlElem->asXML(); + if(preg_match("/<(\w+)\:\w+/", $xml, $matches)) { + $prefix = $matches[1]; + $xml = str_replace("DS_NS", "xmlns:" . $prefix, $xml); + } + else { + $xml = str_replace("DS_NS", "xmlns", $xml); + } + + return $xml; + } + + /** + * Transform an RSA Key in Modulus/Exponent format into a PEM encoding and + * return an openssl resource for it + * + * @param string $modulus The RSA Modulus in binary format + * @param string $exponent The RSA exponent in binary format + * @return string The PEM encoded version of the key + */ + static protected function _getPublicKeyFromModExp($modulus, $exponent) + { + $modulusInteger = self::_encodeValue($modulus, self::ASN_TYPE_INTEGER); + $exponentInteger = self::_encodeValue($exponent, self::ASN_TYPE_INTEGER); + $modExpSequence = self::_encodeValue($modulusInteger . $exponentInteger, self::ASN_TYPE_SEQUENCE); + $modExpBitString = self::_encodeValue($modExpSequence, self::ASN_TYPE_BITSTRING); + + $binRsaKeyIdentifier = pack( "H*", self::RSA_KEY_IDENTIFIER ); + + $publicKeySequence = self::_encodeValue($binRsaKeyIdentifier . $modExpBitString, self::ASN_TYPE_SEQUENCE); + + $publicKeyInfoBase64 = base64_encode( $publicKeySequence ); + + $publicKeyString = "-----BEGIN PUBLIC KEY-----\n"; + $publicKeyString .= wordwrap($publicKeyInfoBase64, 64, "\n", true); + $publicKeyString .= "\n-----END PUBLIC KEY-----\n"; + + return $publicKeyString; + } + + /** + * Encode a limited set of data types into ASN.1 encoding format + * which is used in X.509 certificates + * + * @param string $data The data to encode + * @param const $type The encoding format constant + * @return string The encoded value + * @throws Exception + */ + static protected function _encodeValue($data, $type) + { + // Null pad some data when we get it (integer values > 128 and bitstrings) + if( (($type == self::ASN_TYPE_INTEGER) && (ord($data) > 0x7f)) || + ($type == self::ASN_TYPE_BITSTRING)) { + $data = "\0$data"; + } + + $len = strlen($data); + + // encode the value based on length of the string + // I'm fairly confident that this is by no means a complete implementation + // but it is enough for our purposes + switch(true) { + case ($len < 128): + return sprintf("%c%c%s", $type, $len, $data); + case ($len < 0x0100): + return sprintf("%c%c%c%s", $type, 0x81, $len, $data); + case ($len < 0x010000): + return sprintf("%c%c%c%c%s", $type, 0x82, $len / 0x0100, $len % 0x0100, $data); + default: + SimpleSAML_Logger::debug("Could not encode value"); + } + + SimpleSAML_Logger::debug("Invalid code path"); + } +} diff --git a/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform.php b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform.php new file mode 100644 index 0000000000000000000000000000000000000000..d96bd216d9d722deafe8e8439ad3ddd3c1734f24 --- /dev/null +++ b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform.php @@ -0,0 +1,113 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml_Security + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Transform.php 9094 2008-03-30 18:36:55Z thomas $ + */ + +require_once 'Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature.php'; +require_once 'Zend_InfoCard_Xml_Security_Transform_XmlExcC14N.php'; + +/** + * A class to create a transform rule set based on XML URIs and then apply those rules + * in the correct order to a given XML input + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml_Security + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_InfoCard_Xml_Security_Transform +{ + /** + * A list of transforms to apply + * + * @var array + */ + protected $_transformList = array(); + + /** + * Returns the name of the transform class based on a given URI + * + * @throws Exception + * @param string $uri The transform URI + * @return string The transform implementation class name + */ + protected function _findClassbyURI($uri) + { + switch($uri) { + case 'http://www.w3.org/2000/09/xmldsig#enveloped-signature': + return 'Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature'; + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + return 'Zend_InfoCard_Xml_Security_Transform_XmlExcC14N'; + default: + SimpleSAML_Logger::debug("Unknown or Unsupported Transformation Requested"); + } + } + + /** + * Add a Transform URI to the list of transforms to perform + * + * @param string $uri The Transform URI + * @return Zend_InfoCard_Xml_Security_Transform + */ + public function addTransform($uri) + { + $class = $this->_findClassbyURI($uri); + + $this->_transformList[] = array('uri' => $uri, + 'class' => $class); + return $this; + } + + /** + * Return the list of transforms to perform + * + * @return array The list of transforms + */ + public function getTransformList() + { + return $this->_transformList; + } + + /** + * Apply the transforms in the transform list to the input XML document + * + * @param string $strXmlDocument The input XML + * @return string The XML after the transformations have been applied + */ + public function applyTransforms($strXmlDocument) + { + $transformer = null; + foreach($this->_transformList as $transform) { + switch($transform['class']) { + case 'Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature': + $transformer = new Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature(); + break; + case 'Zend_InfoCard_Xml_Security_Transform_XmlExcC14N': + $transformer = new Zend_InfoCard_Xml_Security_Transform_XmlExcC14N(); + break; + } + + $strXmlDocument = $transformer->transform($strXmlDocument); + } + + return $strXmlDocument; + } +} diff --git a/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature.php b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature.php new file mode 100644 index 0000000000000000000000000000000000000000..42d2f126bfadc94bbf6a5e3b5f7d1acc2b0eba7f --- /dev/null +++ b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature.php @@ -0,0 +1,55 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml_Security + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: EnvelopedSignature.php 9094 2008-03-30 18:36:55Z thomas $ + */ + +/** + * A object implementing the EnvelopedSignature XML Transform + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml_Security + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature +{ + /** + * Transforms the XML Document according to the EnvelopedSignature Transform + * + * @throws Exception + * @param string $strXMLData The input XML data + * @return string the transformed XML data + */ + public function transform($strXMLData) + { + $sxe = simplexml_load_string($strXMLData); + $sxe->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); + + list($signature) = $sxe->xpath("//ds:Signature"); + if(!isset($signature)) { + SimpleSAML_Logger::debug("Unable to locate Signature Block for EnvelopedSignature Transform"); + } + + $transformed_xml = str_replace($signature->asXML(), "", $sxe->asXML()); + + return $transformed_xml; + } +} diff --git a/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform_XmlExcC14N.php b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform_XmlExcC14N.php new file mode 100644 index 0000000000000000000000000000000000000000..c680dd8b7ec0ecbf28b9d93d6e120d9ddb260de4 --- /dev/null +++ b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform_XmlExcC14N.php @@ -0,0 +1,52 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml_Security + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: XmlExcC14N.php 9094 2008-03-30 18:36:55Z thomas $ + */ + +/** + * A Transform to perform C14n XML Exclusive Canonicalization + * + * @category Zend + * @package Zend_InfoCard + * @subpackage Zend_InfoCard_Xml_Security + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_InfoCard_Xml_Security_Transform_XmlExcC14N +{ + /** + * Transform the input XML based on C14n XML Exclusive Canonicalization rules + * + * @throws Exception + * @param string $strXMLData The input XML + * @return string The output XML + */ + public function transform($strXMLData) + { + $dom = new DOMDocument(); + $dom->loadXML($strXMLData); + + if(method_exists($dom, 'C14N')) { + return $dom->C14N(true, false); + } + + SimpleSAML_Logger::debug("This transform requires the C14N() method to exist in the DOM extension"); + } +} diff --git a/modules/InfoCard/templates/default/login-infocard.php b/modules/InfoCard/templates/default/login-infocard.php new file mode 100644 index 0000000000000000000000000000000000000000..1b439071a7bc405ccd036597c4654e8183265d33 --- /dev/null +++ b/modules/InfoCard/templates/default/login-infocard.php @@ -0,0 +1,58 @@ +<?php +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 1-DEC-08 +* DESCRIPTION: 'login-infocard' module template. +*/ + $this->includeAtTemplateBase('includes/header.php'); + if (!array_key_exists('icon', $this->data)) $this->data['icon'] = 'lock.png'; + if (isset($this->data['error'])) { ?> + <div style="border-left: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8; background: #f5f5f5"> + <img src="/<?php echo $this->data['baseurlpath']; ?>resources/icons/bomb.png" style="float: left; margin: 15px " /> + <h2><?php echo $this->t('error_header'); ?></h2> + + <p><?php echo $this->t($this->data['error']); ?> </p> + </div> + <?php } ?> + + <h2 style="break: both"><?php echo $this->t('user_IC_header'); ?></h2> + + <p><?php echo $this->t('user_IC_text'); ?></p> + + <form name="ctl00" id="ctl00" method="post" action="?"> + <?php foreach ($this->data['stateparams'] as $name => $value) { + echo('<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value) . '" />'); + }?> + <ic:informationCard xmlns:ic="<?php echo $this->data['InfoCard']['schema'] ?>" name='xmlToken' + issuer="<?php echo $this->data['InfoCard']['issuer']; ?>" + issuerPolicy="<?php echo $this->data['InfoCard']['issuerPolicy']; ?>" + tokenType="<?php echo $this->data['InfoCard']['tokenType']; ?>" + privacyUrl="<?php echo $this->data['InfoCard']['privacyURL']; ?>" + privacyVersion="<?php echo $this->data['InfoCard']['privacyVersion']; ?>"> + <?php + $schema = $this->data['InfoCard']['schema']."/claims/"; + foreach ($this->data['InfoCard']['requiredClaims'] as $claim=>$data) { + echo "<ic:add claimType = \"$schema".$claim."\" optional=\"false\" />\n"; + } + foreach ($this->data['InfoCard']['optionalClaims'] as $claim=>$data) { + echo "<ic:add claimType = \"$schema".$claim."\" optional=\"true\" />\n"; + } + unset($value);?> + </ic:informationCard> + <input type='image' src="<?php echo $this->data['IClogo']; ?>" align='center' style='cursor:pointer' /> + </form> + + <?php if (strcmp($this->data['CardGenerator'],'')>0) { + echo '<h2>Or get one</h2>'; + echo '<table border="0">'; + echo "<form action=\"". $this->data['CardGenerator'] ."\" method='post'>"; + echo "<tr><td>Username: </td><td><input type='text' name='username' value='usuario' /></tr></td>"; + echo "<tr><td>Password: </td><td><input type='password' name='password' value='clave' /></tr></td>"; + echo "<tr><td></td><td><input type='submit' name='Get_card' value='Get InfoCard' /></tr></td>"; + echo '</form>'; + echo '</table>'; + } ?> + <h2><?php echo $this->t('help_header'); ?></h2> + <p><?php echo $this->t('help_text'); ?></p> +<?php $this->includeAtTemplateBase('includes/footer.php'); ?> \ No newline at end of file diff --git a/modules/InfoCard/www/login-infocard.php b/modules/InfoCard/www/login-infocard.php new file mode 100644 index 0000000000000000000000000000000000000000..577c533cfcbd22357fb036638e88ebb320f9d95a --- /dev/null +++ b/modules/InfoCard/www/login-infocard.php @@ -0,0 +1,54 @@ +<?php + +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 1-DEC-08 +* DESCRIPTION: +* 'login-infocard' module. +* Allows an user to authenticate to the system with an Information Card. +* Infocard's claims are extracted passed as attributes. +*/ + + +/* Load the configuration. */ +$config = SimpleSAML_Configuration::getInstance(); +$autoconfig = $config->copyFromBase('logininfocard', 'config-login-infocard.php'); + +$server_key = $autoconfig->getValue('server_key'); +$server_crt = $autoconfig->getValue('server_crt'); +$IClogo = $autoconfig->getValue('IClogo'); +$Infocard = $autoconfig->getValue('InfoCard'); +$cardGenerator = $autoconfig->getValue('CardGenerator'); + + +/* Load the session of the current user. */ +$session = SimpleSAML_Session::getInstance(); +if($session == NULL) { + SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NOSESSION'); +} + + +if (!array_key_exists('AuthState', $_REQUEST)) { + throw new SimpleSAML_Error_BadRequest('Missing AuthState parameter.'); +} +$authStateId = $_REQUEST['AuthState']; + + +if(array_key_exists('xmlToken', $_POST) && ($_POST['xmlToken']!=NULL) ) { + $error = sspmod_InfoCard_Auth_Source_ICAuth::handleLogin($authStateId, $_POST['xmlToken']); +}else { + $error = NULL; +} + +//Login Page +$t = new SimpleSAML_XHTML_Template($config, 'InfoCard:login-infocard.php', 'logininfocard'); //(configuracion, template, diccionario) +$t->data['header'] = 'simpleSAMLphp: Infocard login'; +$t->data['stateparams'] = array('AuthState' => $authStateId); +$t->data['IClogo'] = $IClogo; +$t->data['InfoCard'] = $Infocard; +$t->data['CardGenerator'] = $cardGenerator; +$t->data['error'] = $error; +$t->show(); +exit(); +?> diff --git a/modules/InfoCard/www/resources/infocard_114x80.png b/modules/InfoCard/www/resources/infocard_114x80.png new file mode 100644 index 0000000000000000000000000000000000000000..6dba25fbd5b54d68bdf1be245e5c8807bfd06e69 Binary files /dev/null and b/modules/InfoCard/www/resources/infocard_114x80.png differ