From e71e666d337c0a7e305a5dd027ee9fc07e4edb57 Mon Sep 17 00:00:00 2001 From: Enrique de la Hoz <enrique.delahoz@uah.es> Date: Mon, 22 Dec 2008 12:40:43 +0000 Subject: [PATCH] Added beta STS support to Infocard Module. Docs and README added git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@1072 44740490-163a-0410-bde0-09ae8108e29a --- modules/InfoCard/README.txt | 77 +++ .../InfoCard/dictionaries/logininfocard.php | 88 +++- .../InfoCard/extra/config-login-infocard.php | 174 +++++++ modules/InfoCard/extra/getinfocard.php | 198 ++++++++ modules/InfoCard/extra/mex.php | 441 ++++++++++++++++++ modules/InfoCard/extra/tokenservice.php | 296 ++++++++++++ modules/InfoCard/lib/Auth/Source/ICAuth.php | 56 ++- modules/InfoCard/lib/RP/InfoCard.php | 42 +- .../lib/RP/Zend_InfoCard_Xml_Security.php | 184 ++++---- ...Card_Xml_Security_Transform_XmlExcC14N.php | 5 +- modules/InfoCard/lib/UserFunctions.php | 74 +++ modules/InfoCard/lib/Utils.php | 108 +++++ .../templates/default/login-infocard.php | 70 ++- modules/InfoCard/www/login-infocard.php | 25 +- modules/InfoCard/www/resources/demoimage.png | Bin 0 -> 46843 bytes 15 files changed, 1695 insertions(+), 143 deletions(-) create mode 100644 modules/InfoCard/README.txt create mode 100644 modules/InfoCard/extra/config-login-infocard.php create mode 100644 modules/InfoCard/extra/getinfocard.php create mode 100644 modules/InfoCard/extra/mex.php create mode 100644 modules/InfoCard/extra/tokenservice.php create mode 100644 modules/InfoCard/lib/UserFunctions.php create mode 100644 modules/InfoCard/lib/Utils.php create mode 100644 modules/InfoCard/www/resources/demoimage.png diff --git a/modules/InfoCard/README.txt b/modules/InfoCard/README.txt new file mode 100644 index 000000000..43278eb77 --- /dev/null +++ b/modules/InfoCard/README.txt @@ -0,0 +1,77 @@ +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: What you should read before starting doing things. +*/ + +WARNING: THIS IS NOT mature software, it's released with testing, educational, developing purposes. It's on a very early version, so don't rely the life of anybody on it. + + + +------------ INFORMATION CARDS MODULE FOR SIMPLESAMLPHP ----------------------- + + +INTRODUCTION: + This is a simpleSAMLphp module that works with Information Cards techcnologies and provides two basic functionalities: + -RP + Acting as a Relying Party, you can accept user authentication through InfoCards comsumming tokens sent by aSTS. + + -STS + Acting as a Secure Token Service you can provide information to a RP generating tokens. Currently, only user-password authentication is supported. + + -InfoCard Generator + Your users could request their InfoCard filling a form with their username and password. + + +VERY IMPORTANT: + This document is not a strict guide, I mean it might have some errors or missed information. I've tried to comment almost every trick i've used to make the system work and make your life easier. So, if at any point of the installation you feel lost, breathe twice, think for ten minutes in what you are trying to do, read again the documentation and use your common sense. It'll be usefull when you'll face again the configuration file. + + +BASIC INSTALLATION: + 1. Copy the InfoCard folder in your modules directoy in your SimpleSAMLphp installation directory. + 2. Copy (or move) the file modules/InfoCard/extra/config-login-infocard.php to the config directory in your SimpleSAMLphp installation directory. + 3. Edit the config/config-login-infocard.php file, you should configure some values like: help_desk_email_URL, contact_info_URL, server_key, server_crt, sts_crt, requiredClaims and optionalClaims to feet your needs. + 4. Edit the config/authsources.php file, add this text before the last ); + 'InfoCard' => array( + 'InfoCard:ICAuth', + ), + 5. That's all. + + +ADDING AND INFOCARD GENERATOR: + 1. Go into the modules/InfoCard folder. + 2. Copy extra/getinfocard.php to www/getinfocard.php + 3. Edit the config/config-login-infocard.php file and uncomment this line +// 'CardGenerator' => 'getinfocard.php', (delete the two //). + 4. Following the previous example, uncomment this values:certificates, sts_key, tokenserviceurl and mexurl. + 6. Check the previous values andm modify them if you need. + 5. Read the USER FUNCTIONS section. + + +ADDING THE STS FUNCTIONALITY + 1. Go into the modules/InfoCard folder. + 2. Copy extra/mex.php and extra/tokenservice.php to the www folder. + 3. Edit the config/config-login-infocard.php file and uncomment the values: certificates and sts_key. + 4. Read the USER FUNCTIONS section. + + +USER FUNCTIONS + Because there are many authentication issues I cannot guess for you, you'll have to code a little bit to fit this module to your authentication system. + We we'll work with the file UserFunctions.php located in modules/InfoCard/lib/ + + validateUser, it receives two strings, username and password, you do the validation (against your database?) and return true if you want to validate the user or false instead. + + fillClaims, it's used by the tokenservice to give information about the user to the relying party. It receives the username, the configured required and optional claims and the claims requested by the RP. + It works filling the claimValues array and your job is the ensure the 'value' variable ($claimValues[$claim]['value']= ) of the array gets the value you want. Understand that requested values and your configured ones could not match. + + fillICdata, it's used by the card generator to retrieve needed information. It receives an authenticated username and returns an array containing information such as the card name, the card image, the expiring time, etc. + + +CERTIFICATES AND HOSTS + The architecture is composed by three independent elements: + -User: Identity Selector + -IDP: Relying Party + -STS: IC and token generation. + + That's because you should configure two hosts (with two x509 certificates) if you want two have the IDP and STS functionalities in the same machine. \ No newline at end of file diff --git a/modules/InfoCard/dictionaries/logininfocard.php b/modules/InfoCard/dictionaries/logininfocard.php index 8d8dfa65c..a8525b813 100644 --- a/modules/InfoCard/dictionaries/logininfocard.php +++ b/modules/InfoCard/dictionaries/logininfocard.php @@ -3,7 +3,7 @@ /* * AUTHOR: Samuel Muñoz Hidalgo * EMAIL: samuel.mh@gmail.com -* LAST REVISION: 1-DEC-08 +* LAST REVISION: 16-DEC-08 * DESCRIPTION: 'login-infocard' module dictionary. */ @@ -26,6 +26,60 @@ $lang = array( 'pt' => '', 'pt-BR' => '', ), + 'get_IC' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'Get your InfoCard', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => 'Consiga su InfoCard', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), + 'form_username' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'Username', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => 'Usuario', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), + 'form_password' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'Password', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => 'Contraseña', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), 'error_header' => array ( 'no' => 'Feil', 'nn' => 'Feil', @@ -98,6 +152,24 @@ $lang = array( 'pt' => '', 'pt-BR' => '', ), + 'get_button' => array ( + 'no' => '', + 'nn' => '', + 'da' => '', + 'en' => 'Get my Infocard', + 'de' => '', + 'sv' => '', + 'fi' => '', + 'es' => 'Conseguir mi Infocard', + 'fr' => '', + 'nl' => '', + 'lb' => '', + 'sl' => '', + 'hr' => '', + 'hu' => '', + 'pt' => '', + 'pt-BR' => '', + ), 'help_header' => array ( 'no' => '', 'nn' => '', @@ -120,7 +192,7 @@ $lang = 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).', + 'en' => 'Information Cards (aka InfoCard) is a web authentication technology. Contact with your services provider in order to configure your computer and give you and Information Card (identification virtual card).', 'de' => '', 'sv' => '', 'fi' => '', @@ -165,18 +237,18 @@ $lang = array( 'pt-BR' => 'Envie um e-mail para a Central de Ajuda.', ), 'contact_info' => array ( - 'no' => 'Kontaktinformasjon:', - 'nn' => 'Kontaktinformasjon:', + 'no' => 'Kontaktinformasjon', + 'nn' => 'Kontaktinformasjon', 'da' => 'Kontaktoplysninger', - 'en' => 'Contact information:', + 'en' => 'Contact information', 'de' => 'Kontakt', - 'sv' => 'Kontaktinformation:', - 'es' => 'InformaciĂłn de contacto:', + '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' => 'Contactos', 'pt-BR' => 'Informações de Contato', ), diff --git a/modules/InfoCard/extra/config-login-infocard.php b/modules/InfoCard/extra/config-login-infocard.php new file mode 100644 index 000000000..e94d75742 --- /dev/null +++ b/modules/InfoCard/extra/config-login-infocard.php @@ -0,0 +1,174 @@ +<?php + +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: 'InfoCard' module configuration for simpleSAMLphp. + + +Some definitions were taken from: +A Guide to Using the Identity Selector +Interoperability Profile V1.5 within Web +Applications and Browsers. +Copyright Microsoft + +*/ + + +$config = array ( + +//------------- TEMPLATE OPTIONS --------------- + 'IClogo' => 'resources/infocard_114x80.png', //Infocard logo button + 'help_desk_email_URL' => 'mailto:asd@asd.com', //Help desk e-mail + 'contact_info_URL' => 'http://google.es', //Contact information + + + + +//------------- CERTIFICATE OPTIONS --------------- + + /* + * USED IN: Relying Party + * DESCRIPTION: Key of the certificate used in the https connection with the idp, it'll be used + * for decrypting the received XML token, + */ + 'idp_key' => '/etc/apache2/ssl/idp.key', + + + /* + * USED IN: Relying Party + * DESCRIPTION: Only accept tokens signed with this certificate, + * if no certificate is set, it'll be assumed to accept + * a self isued token and accept any token. + */ + 'sts_crt' => '/etc/apache2/ssl/sts.crt', + + + /* + * USED IN: Infocard Generator, STS + * DESCRIPTION: STS certificate for signing Infocards and tokens. + */ + 'sts_key' => '/etc/apache2/ssl/sts.key', + + + /* + * USED IN: + * DESCRIPTION: Array of certificates forming a trust chain. The local signing + * certificate is [0], the one that signed that is [1], etc, chaining to a + * trust anchor. + * HINT: The first one, [0], should be the same as the sts_crt. + */ + 'certificates' => array( + 0 => '/etc/apache2/ssl/sts.crt', + 1 => '/etc/apache2/ssl/CA.crt' + ), + + + +//------------- DATA (InfoCard) OPTIONS --------------- + + /* + * USED IN: InfoCard Generator, Relying Party and STS + * DESCRIPTION: Infocard information + */ + 'InfoCard' => array( + /* + * -issuer (optional, taken from the sts_crt common name value, if no set, self issuer is assumed ) + * 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 + */ + /* + * Root of the current InfoCard schema + */ + 'schema' => 'http://schemas.xmlsoap.org/ws/2005/05/identity', + /* + * -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. + */ + 'issuerPolicy' => '', + /* + * -privacyUrl (optional) + * This parameter specifies the URL of the human-readable Privacy Policy of the site, if + * provided. + */ + 'privacyURL' => '', + /* + * -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. + */ + 'tokenType' => 'urn:oasis:names:tc:SAML:1.0:assertion', + + /*-Claims supported by the current schema + givenname + surname + emailaddress + streetaddress + locality + stateorprovince + postalcode + country + primaryphone + dateofbirth + privatepersonalid + gender + webpage + */ + + /* + * -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. + */ + '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 (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 + */ + 'optionalClaims' => array( + 'country' => array('displayTag'=>"country", 'description'=>"PaĂs"), + 'webpage' => array('displayTag'=>"webpage", 'description'=>"Página web") + ), + ), + + + + +//------------- WEB PAGES --------------- + + /* + * USED IN: InfoCard Generator, Relying Party (optional form) + * DESCRIPTION: Infocard generator URL, if set it'll appear a form with username-password authentication in the template + */ +// 'CardGenerator' => 'https://sts.aut.uah.es/simplesaml/module.php/InfoCard/getinfocard.php', + + + /* + * USED IN: InfoCard Generator, Relying Party (issuer), STS (Metadata-Exchange) + * DESCRIPTION: Token generator URL + */ + 'tokenserviceurl' => 'https://sts.aut.uah.es/simplesaml/module.php/InfoCard/tokenservice.php', + + + /* + * USED IN: InfoCard Generator + * DESCRIPTION: Metadata Exchange URL + */ + 'mexurl' => 'https://sts.aut.uah.es/simplesaml/module.php/InfoCard/mex.php', +); + +?> \ No newline at end of file diff --git a/modules/InfoCard/extra/getinfocard.php b/modules/InfoCard/extra/getinfocard.php new file mode 100644 index 000000000..d1fac6a9a --- /dev/null +++ b/modules/InfoCard/extra/getinfocard.php @@ -0,0 +1,198 @@ +<?php + + +/* +* COAUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: InfoCard module Infocard generator +*/ + +//Generate a raw InfoCard with the given data and the configuration +//NOTA: hay namespaces totalmente innecesarios desde un punto de vista práctico xml, están cubiertos por el nodo +// Signature, pero si no se ponen, la canonicalizaciĂłn de generaciĂłn de firma la de comprobaciĂłn son diferentes +// y no funciona. +//EJ: xmlns="http://www.w3.org/2000/09/xmldsig#" en los nodos Object y SignedInfo + +function create_card($ICdata,$ICconfig) { + + $infocardbuf = "<Object Id=\"IC01\" xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"; + $infocardbuf .= "<InformationCard xml:lang=\"en-us\" xmlns=\"http://schemas.xmlsoap.org/ws/2005/05/identity\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" xmlns:wst=\"http://schemas.xmlsoap.org/ws/2005/02/trust\" xmlns:wsx=\"http://schemas.xmlsoap.org/ws/2004/09/mex\">"; + + //cardId + $infocardbuf .= "<InformationCardReference>"; + $infocardbuf .= "<CardId>".$ICdata['CardId']."</CardId>"; //xs:anyURI cardId (="$cardurl/$ppid"; $ppid = "$uname-" . time();) + $infocardbuf .= "<CardVersion>1</CardVersion>"; //xs:unsignedInt + $infocardbuf .= "</InformationCardReference>"; + + //cardName + $infocardbuf .= "<CardName>".$ICdata['CardName']."</CardName>"; + + //image + $infocardbuf .= "<CardImage MimeType=\"".mime_content_type($ICdata['CardImage'])."\">"; + $infocardbuf .= base64_encode(file_get_contents($ICdata['CardImage'])); + $infocardbuf .= "</CardImage>"; + + //issuer - times + $infocardbuf .= "<Issuer>".$ICconfig['InfoCard']['issuer']."</Issuer>"; + $infocardbuf .= "<TimeIssued>".gmdate('Y-m-d').'T'.gmdate('H:i:s').'Z'."</TimeIssued>"; + $infocardbuf .= "<TimeExpires>".$ICdata['TimeExpires']."</TimeExpires>"; + + //Token Service List + $infocardbuf .= "<TokenServiceList>"; + $infocardbuf .= "<TokenService>"; + $infocardbuf .= "<wsa:EndpointReference>"; + $infocardbuf .= "<wsa:Address>".$ICconfig['tokenserviceurl']."</wsa:Address>"; + $infocardbuf .= "<wsa:Metadata>"; + $infocardbuf .= "<wsx:Metadata>"; + $infocardbuf .= "<wsx:MetadataSection>"; + $infocardbuf .= "<wsx:MetadataReference>"; + $infocardbuf .= "<wsa:Address>".$ICconfig['mexurl']."</wsa:Address>"; + $infocardbuf .= "</wsx:MetadataReference>"; + $infocardbuf .= "</wsx:MetadataSection>"; + $infocardbuf .= "</wsx:Metadata>"; + $infocardbuf .= "</wsa:Metadata>"; + $infocardbuf .= "</wsa:EndpointReference>"; + + + + /*Types of User Credentials + * UsernamePasswordCredential + * KerberosV5Credential + * X509V3Credential + * SelfIssuedCredential + */ + $infocardbuf .= "<UserCredential>"; + $infocardbuf .= "<DisplayCredentialHint>".$ICdata['DisplayCredentialHint']."</DisplayCredentialHint>"; + switch($ICdata['UserCredential']){ + case "UsernamePasswordCredential": + $infocardbuf .= "<UsernamePasswordCredential>"; + $infocardbuf .= "<Username>".$ICdata['UserName']."</Username>"; + $infocardbuf .= "</UsernamePasswordCredential>"; + break; + case "KerberosV5Credential": + $infocardbuf .= "<KerberosV5Credential/>"; + break; + case "X509V3Credential": + $infocardbuf .= "<X509V3Credential>"; + $infocardbuf .= "<ds:X509Data>"; + $infocardbuf .= "<wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/2004/xx/oasis-2004xx-wss-soap-message-security-1.1#ThumbprintSHA1\" EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis200401-wss-soap-message-security-1.0#Base64Binary"> + /*This element provides a key identifier for the X.509 certificate based on the SHA1 hash + of the entire certificate content expressed as a “thumbprint.” Note that the extensibility + point in the ds:X509Data element is used to add wsse:KeyIdentifier as a child + element.*/ + $infocardbuf .= $ICdata['KeyIdentifier']; //xs:base64binary; + $infocardbuf .= "</wsse:KeyIdentifier>"; + $infocardbuf .= "</ds:X509Data>"; + $infocardbuf .= "</X509V3Credential>"; + break; + default: //SelfIssuedCredential + $infocardbuf .= "<SelfIssuedCredential>"; + $infocardbuf .= "<PrivatePersonalIdentifier>"; + $infocardbuf .= $ICdata['PPID']; //xs:base64binary; + $infocardbuf .= "</PrivatePersonalIdentifier>"; + $infocardbuf .= "</SelfIssuedCredential> "; + break; + } + $infocardbuf .= "</UserCredential>"; + + $infocardbuf .= "</TokenService>"; + $infocardbuf .= "</TokenServiceList>"; + + + //Tokentype + $infocardbuf .= "<SupportedTokenTypeList>"; + $infocardbuf .= "<wst:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</wst:TokenType>"; + $infocardbuf .= "</SupportedTokenTypeList>"; + + //Claims + $infocardbuf .= "<SupportedClaimTypeList>"; + $url = $ICconfig['InfoCard']['schema']."/claims/"; + foreach ($ICconfig['InfoCard']['requiredClaims'] as $claim=>$data) { + $infocardbuf .= "<SupportedClaimType Uri=\"".$url.$claim."\">"; + $infocardbuf .= "<DisplayTag>".$data['displayTag']."</DisplayTag>"; + $infocardbuf .= "<Description>".$data['description']."</Description>"; + $infocardbuf .= "</SupportedClaimType>"; + } + foreach ($ICconfig['InfoCard']['optionalClaims'] as $claim=>$data) { + $infocardbuf .= "<SupportedClaimType Uri=\"".$url.$claim."\">"; + $infocardbuf .= "<DisplayTag>".$data['displayTag']."</DisplayTag>"; + $infocardbuf .= "<Description>".$data['description']."</Description>"; + $infocardbuf .= "</SupportedClaimType>"; + } + $infocardbuf .= "</SupportedClaimTypeList>"; + + //Privacy URL + $infocardbuf .= "<PrivacyNotice>".$ICconfig['InfoCard']['privacyURL']."</PrivacyNotice>"; + + $infocardbuf .= "</InformationCard>"; + $infocardbuf .= "</Object>"; + + $canonicalbuf = sspmod_InfoCard_Utils::canonicalize($infocardbuf); + + //construct a SignedInfo block + $signedinfo = "<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"; + $signedinfo .= "<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"; + $signedinfo .= "<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>"; + $signedinfo .= "<Reference URI=\"#IC01\">"; + $signedinfo .= "<Transforms>"; + $signedinfo .= "<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>"; + $signedinfo .= "</Transforms>"; + $signedinfo .= "<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>"; + $signedinfo .= "<DigestValue>".base64_encode(sha1($canonicalbuf, TRUE))."</DigestValue>"; + $signedinfo .= "</Reference>"; + $signedinfo .= "</SignedInfo>"; + + $canonicalbuf = sspmod_InfoCard_Utils::canonicalize($signedinfo); + + $signature = ''; + $privkey = openssl_pkey_get_private(file_get_contents($ICconfig['sts_key'])); + openssl_sign($canonicalbuf, &$signature, $privkey); + openssl_free_key($privkey); + $infocard_signature = base64_encode($signature); + + //Envelope + $buf = "<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"; + $buf .= $signedinfo; + $buf .= "<SignatureValue>".$infocard_signature."</SignatureValue>"; + $buf .= "<KeyInfo>"; + $buf .= "<X509Data>"; + // signing certificate(s) + foreach ($ICconfig['certificates'] as $idx=>$cert) + $buf .= "<X509Certificate>".sspmod_InfoCard_Utils::takeCert($cert)."</X509Certificate>"; + $buf .= "</X509Data>"; + $buf .= "</KeyInfo>"; + $buf .= $infocardbuf; + $buf .= "</Signature>"; + + return $buf; +} + + + +$username = $_POST['username']; +$password = $_POST['password']; + +if (sspmod_InfoCard_UserFunctions::validateUser($username,$password)){ + + $config = SimpleSAML_Configuration::getInstance(); + $autoconfig = $config->copyFromBase('logininfocard', 'config-login-infocard.php'); + $ICconfig['InfoCard'] = $autoconfig->getValue('InfoCard'); + $ICconfig['InfoCard']['issuer'] = $autoconfig->getValue('tokenserviceurl');//sspmod_InfoCard_Utils::getIssuer($sts_crt); + $ICconfig['tokenserviceurl'] = $autoconfig->getValue('tokenserviceurl'); + $ICconfig['mexurl'] = $autoconfig->getValue('mexurl'); + $ICconfig['sts_key'] = $autoconfig->getValue('sts_key'); + $ICconfig['certificates'] = $autoconfig->getValue('certificates'); + + $ICdata = sspmod_InfoCard_UserFunctions::fillICdata($username); + + $IC = create_card($ICdata,$ICconfig); + header("Content-Disposition: attachment; filename=\"".$ICdata['CardName'].".crd\""); + header('Content-Type: application/x-informationcard'); + header('Content-Length:'.strlen($IC)); +}else{ + $IC = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><head><title>ERROR!</title></head><body><h1>Wrong credentials!</h1> Could not authenticate you</body></html>"; +} + +echo $IC; +?> diff --git a/modules/InfoCard/extra/mex.php b/modules/InfoCard/extra/mex.php new file mode 100644 index 000000000..ba7f3908c --- /dev/null +++ b/modules/InfoCard/extra/mex.php @@ -0,0 +1,441 @@ +<?php +/* + * Copyright (C) 2007 Carillon Information Security Inc. + * + * WS-MetadataExchange responder for the Carillon STS. Everything is + * pretty much hard-coded -- the only things that get customized are the + * tokenservice URL and the certificate. + * + */ + +/* +* COAUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: InfoCard module metadata exchange +*/ + + +$method = $_SERVER["REQUEST_METHOD"]; +if ($method == "POST") + $use_soap = true; +else + $use_soap = false; + +if ($use_soap) + Header('Content-Type: application/soap+xml;charset=utf-8'); +else + Header('Content-Type: application/xml;charset=utf-8'); + +$config = SimpleSAML_Configuration::getInstance(); +$autoconfig = $config->copyFromBase('logininfocard', 'config-login-infocard.php'); +$ICconfig['tokenserviceurl'] = $autoconfig->getValue('tokenserviceurl'); +$ICconfig['certificates'] = $autoconfig->getValue('certificates'); + + +// Grab the important parts of the token request. That's pretty much just +// the request ID. +$request_id = ''; +if ($use_soap && strlen($HTTP_RAW_POST_DATA)) +{ + $token = new DOMDocument(); + $token->loadXML($HTTP_RAW_POST_DATA); + $doc = $token->documentElement; + $elements = $doc->getElementsByTagname('MessageID'); + $request_id = $elements->item(0)->nodeValue; +} + +$buf = '<?xml version="1.0"?>'; + +if ($use_soap) +{ + $buf .= '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">'; + $buf .= '<s:Header>'; + $buf .= '<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse</a:Action>'; + if ($request_id) + $buf .= "<a:RelatesTo>$request_id</a:RelatesTo>"; + $buf .= '</s:Header>'; + $buf .= '<s:Body>'; +} +$buf .= '<Metadata xmlns="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex">'; +$buf .= '<wsx:MetadataSection xmlns="" Dialect="http://schemas.xmlsoap.org/wsdl/" Identifier="http://schemas.xmlsoap.org/ws/2005/02/trust">'; +$buf .= '<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" targetNamespace="http://schemas.xmlsoap.org/ws/2005/02/trust">'; +$buf .= '<wsdl:types>'; +$buf .= '<xsd:schema targetNamespace="http://schemas.xmlsoap.org/ws/2005/02/trust/Imports">'; +$buf .= '<xsd:import namespace="http://schemas.microsoft.com/Message"/>'; +$buf .= '</xsd:schema>'; +$buf .= '</wsdl:types>'; +$buf .= '<wsdl:message name="IWSTrustContract_Cancel_InputMessage">'; +$buf .= '<wsdl:part xmlns:q1="http://schemas.microsoft.com/Message" name="request" type="q1:MessageBody"/>'; +$buf .= '</wsdl:message>'; +$buf .= '<wsdl:message name="IWSTrustContract_Cancel_OutputMessage">'; +$buf .= '<wsdl:part xmlns:q2="http://schemas.microsoft.com/Message" name="CancelResult" type="q2:MessageBody"/>'; +$buf .= '</wsdl:message>'; +$buf .= '<wsdl:message name="IWSTrustContract_Issue_InputMessage">'; +$buf .= '<wsdl:part xmlns:q3="http://schemas.microsoft.com/Message" name="request" type="q3:MessageBody"/>'; +$buf .= '</wsdl:message>'; +$buf .= '<wsdl:message name="IWSTrustContract_Issue_OutputMessage">'; +$buf .= '<wsdl:part xmlns:q4="http://schemas.microsoft.com/Message" name="IssueResult" type="q4:MessageBody"/>'; +$buf .= '</wsdl:message>'; +$buf .= '<wsdl:message name="IWSTrustContract_Renew_InputMessage">'; +$buf .= '<wsdl:part xmlns:q5="http://schemas.microsoft.com/Message" name="request" type="q5:MessageBody"/>'; +$buf .= '</wsdl:message>'; +$buf .= '<wsdl:message name="IWSTrustContract_Renew_OutputMessage">'; +$buf .= '<wsdl:part xmlns:q6="http://schemas.microsoft.com/Message" name="RenewResult" type="q6:MessageBody"/>'; +$buf .= '</wsdl:message>'; +$buf .= '<wsdl:message name="IWSTrustContract_Validate_InputMessage">'; +$buf .= '<wsdl:part xmlns:q7="http://schemas.microsoft.com/Message" name="request" type="q7:MessageBody"/>'; +$buf .= '</wsdl:message>'; +$buf .= '<wsdl:message name="IWSTrustContract_Validate_OutputMessage">'; +$buf .= '<wsdl:part xmlns:q8="http://schemas.microsoft.com/Message" name="ValidateResult" type="q8:MessageBody"/>'; +$buf .= '</wsdl:message>'; +$buf .= '<wsdl:portType name="IWSTrustContract">'; +$buf .= '<wsdl:operation name="Cancel">'; +$buf .= '<wsdl:input wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Cancel" message="tns:IWSTrustContract_Cancel_InputMessage"/>'; +$buf .= '<wsdl:output wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Cancel" message="tns:IWSTrustContract_Cancel_OutputMessage"/>'; +$buf .= '</wsdl:operation>'; +$buf .= '<wsdl:operation name="Issue">'; +$buf .= '<wsdl:input wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" message="tns:IWSTrustContract_Issue_InputMessage"/>'; +$buf .= '<wsdl:output wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue" message="tns:IWSTrustContract_Issue_OutputMessage"/>'; +$buf .= '</wsdl:operation>'; +$buf .= '<wsdl:operation name="Renew">'; +$buf .= '<wsdl:input wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Renew" message="tns:IWSTrustContract_Renew_InputMessage"/>'; +$buf .= '<wsdl:output wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Renew" message="tns:IWSTrustContract_Renew_OutputMessage"/>'; +$buf .= '</wsdl:operation>'; +$buf .= '<wsdl:operation name="Validate">'; +$buf .= '<wsdl:input wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Validate" message="tns:IWSTrustContract_Validate_InputMessage"/>'; +$buf .= '<wsdl:output wsaw:Action="http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Validate" message="tns:IWSTrustContract_Validate_OutputMessage"/>'; +$buf .= '</wsdl:operation>'; +$buf .= '</wsdl:portType>'; +$buf .= '</wsdl:definitions>'; +$buf .= '</wsx:MetadataSection>'; +$buf .= '<wsx:MetadataSection xmlns="" Dialect="http://schemas.xmlsoap.org/wsdl/" Identifier="http://tempuri.org/">'; +$buf .= '<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://tempuri.org/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:i0="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" name="STS" targetNamespace="http://tempuri.org/">'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<wsp:Policy>'; +$buf .= '<sp:TransportToken>'; +$buf .= '<wsp:Policy>'; +$buf .= '<sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never">'; +$buf .= '<wsp:Policy>'; +$buf .= '<sp:RequireThumbprintReference/>'; +$buf .= '<sp:WssX509V3Token10/>'; +$buf .= '</wsp:Policy>'; +$buf .= '</sp:X509Token>'; +$buf .= '</wsp:Policy>'; +$buf .= '</sp:TransportToken>'; +$buf .= '<sp:AlgorithmSuite>'; +$buf .= '<wsp:Policy>'; +$buf .= '<sp:Basic128/>'; +$buf .= '</wsp:Policy>'; +$buf .= '</sp:AlgorithmSuite>'; +$buf .= '<sp:Layout>'; +$buf .= '<wsp:Policy>'; +$buf .= '<sp:Strict/>'; +$buf .= '</wsp:Policy>'; +$buf .= '</sp:Layout>'; +if ($_GET['auth'] == 'x509') + $buf .= '<sp:IncludeTimestamp/>'; +$buf .= '</wsp:Policy>'; +$buf .= '</sp:TransportBinding>'; + +// is this metadata for an infocard that wants an x509-authenticated +// token, or a username/password token? +if ($_GET['auth'] == 'x509') +{ + $buf .= '<sp:EndorsingSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; + $buf .= '<wsp:Policy>'; + $buf .= '<sp:X509Token sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient">'; + $buf .= '<wsp:Policy>'; + $buf .= '<sp:RequireThumbprintReference/>'; + $buf .= '<sp:WssX509V3Token10/>'; + $buf .= '</wsp:Policy>'; + $buf .= '</sp:X509Token>'; + $buf .= '</wsp:Policy>'; + $buf .= '</sp:EndorsingSupportingTokens>'; +} +else +{ + $buf .= '<sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; + $buf .= '<wsp:Policy>'; + $buf .= '<sp:UsernameToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient">'; + $buf .= '<wsp:Policy>'; + $buf .= '<sp:WssUsernameToken10/>'; + $buf .= '</wsp:Policy>'; + $buf .= '</sp:UsernameToken>'; + $buf .= '</wsp:Policy>'; + $buf .= '</sp:SignedSupportingTokens>'; +} + +$buf .= '<sp:Wss11 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<wsp:Policy>'; +$buf .= '<sp:MustSupportRefKeyIdentifier/>'; +$buf .= '<sp:MustSupportRefIssuerSerial/>'; +$buf .= '<sp:MustSupportRefThumbprint/>'; +$buf .= '<sp:MustSupportRefEncryptedKey/>'; +$buf .= '</wsp:Policy>'; +$buf .= '</sp:Wss11>'; +$buf .= '<sp:Trust10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<wsp:Policy>'; +$buf .= '<sp:MustSupportIssuedTokens/>'; +$buf .= '<sp:RequireServerEntropy/>'; +$buf .= '</wsp:Policy>'; +$buf .= '</sp:Trust10>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Cancel_Input_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '<sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="From" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="FaultTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="ReplyTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="MessageID" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="RelatesTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="Action" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '</sp:SignedParts>'; +$buf .= '<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '</sp:EncryptedParts>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Cancel_output_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '<sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="From" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="FaultTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="ReplyTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="MessageID" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="RelatesTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="Action" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '</sp:SignedParts>'; +$buf .= '<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '</sp:EncryptedParts>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Issue_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:EndorsingSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<wsp:Policy>'; +$buf .= '<mssp:RsaToken xmlns:mssp="http://schemas.microsoft.com/ws/2005/07/securitypolicy" sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never" wsp:Optional="true"/>'; +$buf .= '</wsp:Policy>'; +$buf .= '</sp:EndorsingSupportingTokens>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Issue_Input_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '<sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="From" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="FaultTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="ReplyTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="MessageID" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="RelatesTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="Action" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '</sp:SignedParts>'; +$buf .= '<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '</sp:EncryptedParts>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Issue_output_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '<sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="From" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="FaultTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="ReplyTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="MessageID" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="RelatesTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="Action" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '</sp:SignedParts>'; +$buf .= '<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '</sp:EncryptedParts>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Renew_Input_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '<sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="From" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="FaultTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="ReplyTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="MessageID" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="RelatesTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="Action" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '</sp:SignedParts>'; +$buf .= '<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '</sp:EncryptedParts>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Renew_output_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '<sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="From" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="FaultTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="ReplyTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="MessageID" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="RelatesTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="Action" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '</sp:SignedParts>'; +$buf .= '<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '</sp:EncryptedParts>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Validate_Input_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '<sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="From" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="FaultTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="ReplyTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="MessageID" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="RelatesTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="Action" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '</sp:SignedParts>'; +$buf .= '<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '</sp:EncryptedParts>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsp:Policy wsu:Id="CustomBinding_IWSTrustContract_Validate_output_policy">'; +$buf .= '<wsp:ExactlyOne>'; +$buf .= '<wsp:All>'; +$buf .= '<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '<sp:Header Name="To" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="From" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="FaultTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="ReplyTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="MessageID" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="RelatesTo" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '<sp:Header Name="Action" Namespace="http://www.w3.org/2005/08/addressing"/>'; +$buf .= '</sp:SignedParts>'; +$buf .= '<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">'; +$buf .= '<sp:Body/>'; +$buf .= '</sp:EncryptedParts>'; +$buf .= '</wsp:All>'; +$buf .= '</wsp:ExactlyOne>'; +$buf .= '</wsp:Policy>'; +$buf .= '<wsdl:import namespace="http://schemas.xmlsoap.org/ws/2005/02/trust" location=""/>'; +$buf .= '<wsdl:types/>'; +$buf .= '<wsdl:binding name="CustomBinding_IWSTrustContract" type="i0:IWSTrustContract">'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_policy"/>'; +$buf .= '<soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/>'; +$buf .= '<wsdl:operation name="Cancel">'; +$buf .= '<soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Cancel" style="document"/>'; +$buf .= '<wsdl:input>'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Cancel_Input_policy"/>'; +$buf .= '<soap12:body use="literal"/>'; +$buf .= '</wsdl:input>'; +$buf .= '<wsdl:output>'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Cancel_output_policy"/>'; +$buf .= '<soap12:body use="literal"/>'; +$buf .= '</wsdl:output>'; +$buf .= '</wsdl:operation>'; +$buf .= '<wsdl:operation name="Issue">'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Issue_policy"/>'; +$buf .= '<soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" style="document"/>'; +$buf .= '<wsdl:input>'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Issue_Input_policy"/>'; +$buf .= '<soap12:body use="literal"/>'; +$buf .= '</wsdl:input>'; +$buf .= '<wsdl:output>'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Issue_output_policy"/>'; +$buf .= '<soap12:body use="literal"/>'; +$buf .= '</wsdl:output>'; +$buf .= '</wsdl:operation>'; +$buf .= '<wsdl:operation name="Renew">'; +$buf .= '<soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Renew" style="document"/>'; +$buf .= '<wsdl:input>'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Renew_Input_policy"/>'; +$buf .= '<soap12:body use="literal"/>'; +$buf .= '</wsdl:input>'; +$buf .= '<wsdl:output>'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Renew_output_policy"/>'; +$buf .= '<soap12:body use="literal"/>'; +$buf .= '</wsdl:output>'; +$buf .= '</wsdl:operation>'; +$buf .= '<wsdl:operation name="Validate">'; +$buf .= '<soap12:operation soapAction="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Validate" style="document"/>'; +$buf .= '<wsdl:input>'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Validate_Input_policy"/>'; +$buf .= '<soap12:body use="literal"/>'; +$buf .= '</wsdl:input>'; +$buf .= '<wsdl:output>'; +$buf .= '<wsp:PolicyReference URI="#CustomBinding_IWSTrustContract_Validate_output_policy"/>'; +$buf .= '<soap12:body use="literal"/>'; +$buf .= '</wsdl:output>'; +$buf .= '</wsdl:operation>'; +$buf .= '</wsdl:binding>'; +$buf .= '<wsdl:service name="STS">'; +$buf .= '<wsdl:port name="CustomBinding_IWSTrustContract" binding="tns:CustomBinding_IWSTrustContract">'; +$buf .= "<soap12:address location=\"".$ICconfig['tokenserviceurl']."\"/>"; +$buf .= '<wsa10:EndpointReference>'; +$buf .= "<wsa10:Address>".$ICconfig['tokenserviceurl']."</wsa10:Address>"; +$buf .= '<Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">'; +$buf .= '<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">'; +$buf .= '<X509Data>'; +$buf .= '<X509Certificate>'.sspmod_InfoCard_Utils::takeCert($ICconfig['certificates'][0]).'</X509Certificate>'; +$buf .= '</X509Data>'; +$buf .= '</KeyInfo>'; +$buf .= '</Identity>'; +$buf .= '</wsa10:EndpointReference>'; +$buf .= '</wsdl:port>'; +$buf .= '</wsdl:service>'; +$buf .= '</wsdl:definitions>'; +$buf .= '</wsx:MetadataSection>'; +$buf .= '<wsx:MetadataSection xmlns="" Dialect="http://www.w3.org/2001/XMLSchema" Identifier="http://schemas.microsoft.com/Message">'; +$buf .= '<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/Message" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/Message">'; +$buf .= '<xs:complexType name="MessageBody">'; +$buf .= '<xs:sequence>'; +$buf .= '<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##any"/>'; +$buf .= '</xs:sequence>'; +$buf .= '</xs:complexType>'; +$buf .= '</xs:schema>'; +$buf .= '</wsx:MetadataSection>'; +$buf .= '</Metadata>'; + +if ($use_soap) +{ + $buf .= '</s:Body>'; + $buf .= '</s:Envelope>'; +} + + + +print($buf); + +?> diff --git a/modules/InfoCard/extra/tokenservice.php b/modules/InfoCard/extra/tokenservice.php new file mode 100644 index 000000000..7b6267c97 --- /dev/null +++ b/modules/InfoCard/extra/tokenservice.php @@ -0,0 +1,296 @@ +<?php +/* + * Copyright (C) 2007 Carillon Information Security Inc. + * + * Token responder for the Carillon STS. Accepts a SOAP token request from + * a relying party (or an infocard client, more likely) and produces a + * token with the proper attributes, as stored in the database of issued + * infocards. + * + */ + +/* +* COAUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: InfoCard module token generator +*/ + + + +// Windows CardSpace doesn't support using the infocard's certificate as +// the SSL cert for transport binding... so we make it sign a timestamp in +// the token request, and validate the signature on that. +function validate_embedded_cert() +{ + global $doc, $row; + global $db_usertable; + global $uidnum, $uname, $fullname; + global $HTTP_RAW_POST_DATA; + + // FIXME: Add error checking to this! + + // get the signed part (the timestamp) in a horribly cheating way for + // now + // first grab the namespace for u + $begin = 'xmlns:u="'; + $end = 'xsd"'; + $xmlnsu = $HTTP_RAW_POST_DATA; + $xmlnsu = substr($xmlnsu, strpos($xmlnsu, $begin)); + $xmlnsu = substr($xmlnsu, 0, strpos($xmlnsu, $end)+strlen($end)); + $begin = '<u:Timestamp '; + $end = '</u:Timestamp>'; + $tmp = $HTTP_RAW_POST_DATA; + $tmp = substr($tmp, strpos($tmp, $begin)); + $tmp = substr($tmp, 0, strpos($tmp, $end)+strlen($end)); + $tmp1 = substr($tmp, 0, strpos($tmp, ' ')); + $tmp2 = substr($tmp, strpos($tmp, ' ')+1); + $timestamp = $tmp1." $xmlnsu ".$tmp2; + + // canonicalize the timestamp and digest it + $canonical_timestamp = sspmod_InfoCard_Utils::canonicalize($timestamp); + $myhash = sha1($canonical_timestamp,TRUE); + $mydigest = base64_encode($myhash); + + // grab the digest from the request + $elements = $doc->getElementsByTagname('DigestValue'); + $request_digest = $elements->item(0)->nodeValue; + + // if the digests don't match, we fail + if ($mydigest != $request_digest) + return false; + + // get the SignedInfo in a horribly cheating way for now + $begin = '<SignedInfo'; + $end = '</SignedInfo>'; + $sinfo = $HTTP_RAW_POST_DATA; + $sinfo = substr($sinfo, strpos($sinfo, $begin)); + $sinfo = substr($sinfo, 0, strpos($sinfo, $end)+strlen($end)); + + // grab the signing certificate and PEM-encode it to satisfy openssl + $elements = $doc->getElementsByTagname('BinarySecurityToken'); + $cert = $elements->item(0)->nodeValue; + $certpem = "-----BEGIN CERTIFICATE-----\n"; + $offset = 0; + while ($segment=substr($cert, $offset, 64)) + { + $certpem .= $segment."\n"; + $offset += 64; + } + $certpem .= "-----END CERTIFICATE-----\n"; + + $pubkey = openssl_pkey_get_public($certpem); + + // canonicalize the signed info + $canonical_sinfo = sspmod_InfoCard_Utils::canonicalize($sinfo); + + // grab the signature from the request + $elements = $doc->getElementsByTagname('SignatureValue'); + $request_sig = $elements->item(0)->nodeValue; + + $request_sig = base64_decode($request_sig); + + // try to verify the signature... if we can't, we fail. + if (openssl_verify($canonical_sinfo, $request_sig, $pubkey) == false) + return false; + + // so, the signature is OK. Was it the right cert? Check its + // thumbprint against the cert we recorded in the infocard... + $thumb = sspmod_InfoCard_Utils::thumbcert($cert); + if ($row['x509thumb'] != $thumb) + return false; + + // at this point we've succeeded, but we need to populate some fields + // based on the usertable to create a card... + $arr = openssl_x509_parse($certpem); + $who = $arr['subject']['CN']; + $query = "SELECT * FROM $db_usertable WHERE full_name='$who'"; + $userrow = pg_fetch_assoc(do_query($query)); + if ($userrow['status'] == "1") + { + $uidnum = $userrow['id']; + $uname = $userrow['userid']; + $fullname = $userrow['full_name']; + return true; + } + return false; +} + + + +/* +* claimValues ( 'claim'('value','displayTag'), 'claim'('value','displayTag'), ... ) +*/ +function create_token($claimValues,$config){ + // build a SAML assertion + $now = gmdate('Y-m-d').'T'.gmdate('H:i:s').'Z'; + $later = gmdate('Y-m-d', time()+3600).'T'.gmdate('H:i:s', time()+3600).'Z'; + $assertionid = uniqid('uuid-'); + + $saml = "<saml:Assertion MajorVersion=\"1\" MinorVersion=\"0\" AssertionID=\"$assertionid\" Issuer=\"".$config['issuer']."\" IssueInstant=\"$now\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\">"; + $saml .= "<saml:Conditions NotBefore=\"$now\" NotOnOrAfter=\"$later\" />"; + + $saml .= "<saml:AttributeStatement>"; + $saml .= "<saml:Subject>"; + $saml .= "<saml:SubjectConfirmation>"; + $saml .= "<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:holder-of-key</saml:ConfirmationMethod>"; + + // proof key + $saml .= "<dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">"; + $saml .= "<dsig:X509Data>"; + $saml .= "<dsig:X509Certificate>".sspmod_InfoCard_Utils::takeCert($config['sts_crt'])."</dsig:X509Certificate>"; + $saml .= "</dsig:X509Data>"; + $saml .= "</dsig:KeyInfo>"; + + $saml .= "</saml:SubjectConfirmation>"; + $saml .= "</saml:Subject>"; + + + foreach ($claimValues as $claim=>$data) { + $saml .= "<saml:Attribute AttributeName=\"$claim\" AttributeNamespace=\"".$config['InfoCard']['schema']."/claims\">"; + $saml .= "<saml:AttributeValue>".$data['value']."</saml:AttributeValue>"; + $saml .= "</saml:Attribute>"; + } + + $saml .= "</saml:AttributeStatement>"; + + + // calculate the digest for the signature... + $canonicalbuf = sspmod_InfoCard_Utils::canonicalize($saml."</saml:Assertion>"); + $myhash = sha1($canonicalbuf,TRUE); + $samldigest = base64_encode($myhash); + + + // construct a SignedInfo block + $signedinfo = "<dsig:SignedInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">"; + $signedinfo .= "<dsig:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\" />"; + $signedinfo .= "<dsig:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\" />"; + $signedinfo .= "<dsig:Reference URI=\"#$assertionid\">"; + $signedinfo .= "<dsig:Transforms>"; + $signedinfo .= "<dsig:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\" />"; + $signedinfo .= "<dsig:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\" />"; + $signedinfo .= "</dsig:Transforms>"; + $signedinfo .= "<dsig:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\" />"; + $signedinfo .= "<dsig:DigestValue>$samldigest</dsig:DigestValue>"; + $signedinfo .= "</dsig:Reference>"; + $signedinfo .= "</dsig:SignedInfo>"; + + // compute the signature of hte canonicalized digest + $canonicalbuf = sspmod_InfoCard_Utils::canonicalize($signedinfo); + $privkey = openssl_pkey_get_private(file_get_contents($config['sts_key'])); + $signature = ''; + openssl_sign($canonicalbuf, &$signature, $privkey); + openssl_free_key($privkey); + $samlsignature = base64_encode($signature); + + + // now put it all together + $saml .= "<dsig:Signature xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">"; + $saml .= $signedinfo; + $saml .= "<dsig:SignatureValue>$samlsignature</dsig:SignatureValue>"; + + $saml .= "<dsig:KeyInfo>"; + $saml .= "<dsig:X509Data>"; + $saml .= "<dsig:X509Certificate>".sspmod_InfoCard_Utils::takeCert($config['sts_crt'])."</dsig:X509Certificate>"; + $saml .= "</dsig:X509Data>"; + $saml .= "</dsig:KeyInfo>"; + $saml .= "</dsig:Signature>"; + + $saml .= "</saml:Assertion>"; + + + // cram the SAML assertion in a SOAP envelope + $buf = '<?xml version="1.0"?>'; + $buf .= "<soap:Envelope xmlns:ic=\"http://schemas.xmlsoap.org/ws/2005/05/identity\" xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wst=\"http://schemas.xmlsoap.org/ws/2005/02/trust\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"; + if ($include_timestamp) { + $buf .= "<soap:Header>"; + $buf .= "<wsse:Security>"; + $buf .= "<wsu:Timestamp>"; + $buf .= "<wsu:Created>$now</wsu:Created>"; + $buf .= "<wsu:Expires>$later</wsu:Expires>"; + $buf .= "</wsu:Timestamp>"; + $buf .= "</wsse:Security>"; + $buf .= "</soap:Header>"; + } else + $buf .= "<soap:Header />"; + + $buf .= "<soap:Body>"; + $buf .= "<wst:RequestSecurityTokenResponse Context=\"ProcessRequestSecurityToken\">"; + $buf .= "<wst:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</wst:TokenType>"; + $buf .= "<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"; + $buf .= "<wst:RequestedSecurityToken>"; + + $buf .= $saml; + + $buf .= "</wst:RequestedSecurityToken>"; + + // references + $buf .= "<wst:RequestedAttachedReference>"; + $buf .= "<wsse:SecurityTokenReference>"; + $buf .= "<wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">$assertionid</wsse:KeyIdentifier>"; + $buf .= "</wsse:SecurityTokenReference>"; + $buf .= "</wst:RequestedAttachedReference>"; + $buf .= "<wst:RequestedUnattachedReference>"; + $buf .= "<wsse:SecurityTokenReference>"; + $buf .= "<wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID\">$assertionid</wsse:KeyIdentifier>"; + $buf .= "</wsse:SecurityTokenReference>"; + $buf .= "</wst:RequestedUnattachedReference>"; + + // display token + $buf .= "<ic:RequestedDisplayToken>"; + $buf .= "<ic:DisplayToken xml:lang=\"en\">"; + + foreach ($claimValues as $claim=>$data) { + $buf .= "<ic:DisplayClaim Uri=\"".$config['InfoCard']['schema']."/claims/".$claim."\">"; + $buf .= "<ic:DisplayTag>".$data['displayTag']."</ic:DisplayTag>"; + $buf .= "<ic:DisplayValue>".$data['value']."</ic:DisplayValue>"; + $buf .= "</ic:DisplayClaim>"; + } + + $buf .= "</ic:DisplayToken>"; + $buf .= "</ic:RequestedDisplayToken>"; + + // the end + $buf .= "</wst:RequestSecurityTokenResponse>"; + $buf .= "</soap:Body>"; + $buf .= "</soap:Envelope>"; + + return $buf; +} + + + + +// grab the important parts of the token request. these are the username, +// password, and cardid. + +Header('Content-Type: application/soap+xml;charset=utf-8'); + + +$token = new DOMDocument(); +$token->loadXML($HTTP_RAW_POST_DATA); +$doc = $token->documentElement; +$username = $doc->getElementsByTagname('Username')->item(0)->nodeValue; +$password = $doc->getElementsByTagname('Password')->item(0)->nodeValue; +$cardId = $doc->getElementsByTagname('CardId')->item(0)->nodeValue; + + +if (sspmod_InfoCard_UserFunctions::validateUser($username,$password)){ + $config = SimpleSAML_Configuration::getInstance(); + $autoconfig = $config->copyFromBase('logininfocard', 'config-login-infocard.php'); + $ICconfig['InfoCard'] = $autoconfig->getValue('InfoCard'); + $ICconfig['issuer'] = $autoconfig->getValue('issuer'); + $ICconfig['sts_crt'] = $autoconfig->getValue('sts_crt'); + $ICconfig['sts_key'] = $autoconfig->getValue('sts_key'); + + $requiredClaims = sspmod_InfoCard_Utils::extractClaims($ICconfig['InfoCard']['schema'], $doc->getElementsByTagname('ClaimType')); + $claimValues = sspmod_InfoCard_UserFunctions::fillClaims($username, $ICconfig['InfoCard']['requiredClaims'], $ICconfig['InfoCard']['optionalClaims'],$requiredClaims); + $buf = create_token($claimValues,$ICconfig); + Header('Content-length: '.strlen($buf)+1); + print($buf); +}else{ + $bad = true; + print(""); +} + +?> \ No newline at end of file diff --git a/modules/InfoCard/lib/Auth/Source/ICAuth.php b/modules/InfoCard/lib/Auth/Source/ICAuth.php index b16167bd6..47d85f86c 100644 --- a/modules/InfoCard/lib/Auth/Source/ICAuth.php +++ b/modules/InfoCard/lib/Auth/Source/ICAuth.php @@ -1,13 +1,16 @@ <?php + /* * AUTHOR: Samuel Muñoz Hidalgo * EMAIL: samuel.mh@gmail.com -* LAST REVISION: 1-DEC-08 +* LAST REVISION: 22-DEC-08 * DESCRIPTION: -* 'login-infocard' module. -* Auth class +* Authentication module. +* Handles the login information +* Infocard's claims are extracted passed as attributes. */ + class sspmod_InfoCard_Auth_Source_ICAuth extends SimpleSAML_Auth_Source { //The string used to identify our states. @@ -39,38 +42,49 @@ class sspmod_InfoCard_Auth_Source_ICAuth extends SimpleSAML_Auth_Source { 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]); - } +SimpleSAML_Logger::debug('ENTRA en icauth'); + assert('is_string($authStateId)'); $config = SimpleSAML_Configuration::getInstance(); $autoconfig = $config->copyFromBase('logininfocard', 'config-login-infocard.php'); - $server_key = $autoconfig->getValue('server_key'); - $server_crt = $autoconfig->getValue('server_crt'); + $idp_key = $autoconfig->getValue('idp_key'); + $sts_crt = $autoconfig->getValue('sts_crt'); $Infocard = $autoconfig->getValue('InfoCard'); $infocard = new sspmod_InfoCard_RP_InfoCard(); - $infocard->addCertificatePair($server_key,$server_crt); + $infocard->addIDPKey($idp_key); + $infocard->addSTSCertificate($sts_crt); + if (!$xmlToken) + SimpleSAML_Logger::debug("XMLtoken: ".$xmlToken); + else + SimpleSAML_Logger::debug("NOXMLtoken: ".$xmlToken); $claims = $infocard->process($xmlToken); - if($claims->isValid()) { + if($claims->isValid()) { +// if(false) { $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); + } + /* 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]); + } + $state['Attributes'] = $attributes; +SimpleSAML_Logger::debug('VALIDA'); + unset($infocard); + unset($claims); + SimpleSAML_Auth_Source::completeAuth($state); } else { +SimpleSAML_Logger::debug('NO VALIDA ERROR:'.$claims->getErrorMsg()); + unset($infocard); + unset($claims); return 'wrong_IC'; } } diff --git a/modules/InfoCard/lib/RP/InfoCard.php b/modules/InfoCard/lib/RP/InfoCard.php index 20d70bd25..b43311045 100644 --- a/modules/InfoCard/lib/RP/InfoCard.php +++ b/modules/InfoCard/lib/RP/InfoCard.php @@ -1,5 +1,10 @@ <?php - +/* +* COAUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: Zend Infocard libraries added sts certificate check +*/ require_once 'Zend_InfoCard_Claims.php'; class sspmod_InfoCard_RP_InfoCard @@ -45,13 +50,33 @@ class sspmod_InfoCard_RP_InfoCard } + public function addSTSCertificate($sts_crt){ $this->_sts_crt = $sts_crt; - if(!file_exists($this->_sts_crt)) { + if(!file_exists($sts_crt) && ($sts_crt!=NULL) ) { throw new Exception("STS certificate does not exists"); } + if(!is_readable($sts_crt)) { + throw new Exception("STS certificate is not readable"); + } + } + + + + public function addIDPKey($private_key_file, $password = null){ + $this->_private_key_file = $private_key_file; + $this->_password = $password; + + if(!file_exists($this->_private_key_file)) { + throw new Exception("Private key file does not exists"); + } + + if(!is_readable($this->_private_key_file)) { + throw new Exception("Private key file is not readable"); + } } +/*Function not used $public_key_file is not used*/ 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; @@ -60,25 +85,28 @@ class sspmod_InfoCard_RP_InfoCard if(!file_exists($this->_private_key_file)) { throw new Exception("Private key file does not exists"); } - - if(!file_exists($this->_public_key_file)) { - throw new Exception("Public key file does not exists"); - } - + if(!is_readable($this->_private_key_file)) { throw new Exception("Private key file is not readable"); } + if(!file_exists($this->_public_key_file)) { + throw new Exception("Public key file does not exists"); + } + if(!is_readable($this->_public_key_file)) { throw new Exception("Public key file is not readable"); } } + public function process($xmlToken) { if(strpos($xmlToken, "EncryptedData") === false ) { +SimpleSAML_Logger::debug('IC: UNsecureToken'); return self::processUnSecureToken($xmlToken); } else { +SimpleSAML_Logger::debug('IC: secureToken'); return self::processSecureToken($xmlToken); } } diff --git a/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security.php b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security.php index 48ef389a6..23c1485af 100644 --- a/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security.php +++ b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security.php @@ -1,4 +1,11 @@ <?php +/* +* COAUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: modified validatexmlsignature +*/ + /** * Zend Framework * @@ -84,7 +91,6 @@ class Zend_InfoCard_Xml_Security { } - /** * Validates the signature of a provided XML block * @@ -92,12 +98,63 @@ class Zend_InfoCard_Xml_Security * @return bool True if the signature validated, false otherwise * @throws Exception */ - static public function validateXMLSignature($strXMLInput, $sts_crt=NULL){ - if(!extension_loaded('openssl')) { - throw new SimpleSAML_Error_Error("You must have the openssl extension installed to use this class"); + + +static public function validateXMLSignature($strXMLInput, $sts_crt=NULL){ + if(!extension_loaded('openssl')) { + throw new Exception("You must have the openssl extension installed to use this class"); + } + + $sxe = simplexml_load_string($strXMLInput); + + if ($sts_crt != 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"); + //Extract the XMLToken issuer public key + switch(true) { + case isset($x509cert): + SimpleSAML_Logger::debug("Public Key: x509cert"); + $certificate = (string)$x509cert; + $cert_issuer = "-----BEGIN CERTIFICATE-----\n".wordwrap($certificate, 64, "\n", true)."\n-----END CERTIFICATE-----"; + if (!$t_key = openssl_pkey_get_public($cert_issuer)) { + throw new Exception("Wrong token certificate"); + } + $t_det = openssl_pkey_get_details($t_key); + $pem_issuer = $t_det['key']; + 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)) { + throw new Exception("RSA Key Value not in Modulus/Exponent form"); + } + $modulus = base64_decode((string)$modulus); + $exponent = base64_decode((string)$exponent); + $pem_issuer = self::_getPublicKeyFromModExp($modulus, $exponent); + break; + default: + SimpleSAML_Logger::debug("Public Key: Unknown"); + throw new Exception("Unable to determine or unsupported representation of the KeyValue block"); } + + //Check isuer public key against configured one + $checkcert = file_get_contents($sts_crt); + $check_key = openssl_pkey_get_public($checkcert); + $checkData = openssl_pkey_get_details($check_key); + $pem_local = $checkData['key']; + + if ( strcmp($pem_issuer,$pem_local)!=0 ) { + SimpleSAML_Logger::debug("Configured STS cert and received STS cert mismatch"); + openssl_free_key($check_key); + throw new Exception("Configured STS cert and received STS cert mismatch"); + } - $sxe = simplexml_load_string($strXMLInput); + //Validate XML signature + $sxe->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); list($canonMethod) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:CanonicalizationMethod"); @@ -106,7 +163,7 @@ class Zend_InfoCard_Xml_Security $cMethod = (string)$canonMethod['Algorithm']; break; default: - throw new SimpleSAML_Error_Error("Unknown or unsupported CanonicalizationMethod Requested"); + throw new Exception("Unknown or unsupported CanonicalizationMethod Requested"); } list($signatureMethod) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:SignatureMethod"); @@ -115,7 +172,7 @@ class Zend_InfoCard_Xml_Security $sMethod = (string)$signatureMethod['Algorithm']; break; default: - throw new SimpleSAML_Error_Error("Unknown or unsupported SignatureMethod Requested"); + throw new Exception("Unknown or unsupported SignatureMethod Requested"); } list($digestMethod) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod"); @@ -124,7 +181,7 @@ class Zend_InfoCard_Xml_Security $dMethod = (string)$digestMethod['Algorithm']; break; default: - throw new SimpleSAML_Error_Error("Unknown or unsupported DigestMethod Requested"); + throw new Exception("Unknown or unsupported DigestMethod Requested"); } $base64DecodeSupportsStrictParam = version_compare(PHP_VERSION, '5.2.0', '>='); @@ -136,6 +193,7 @@ class Zend_InfoCard_Xml_Security $dValue = base64_decode((string)$digestValue); } + list($signatureValueElem) = $sxe->xpath("//ds:Signature/ds:SignatureValue"); if ($base64DecodeSupportsStrictParam) { $signatureValue = base64_decode((string)$signatureValueElem, true); @@ -144,8 +202,7 @@ class Zend_InfoCard_Xml_Security } $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']); @@ -153,84 +210,53 @@ class Zend_InfoCard_Xml_Security $transformed_xml = $transformer->applyTransforms($strXMLInput); $transformed_xml_binhash = pack("H*", sha1($transformed_xml)); if($transformed_xml_binhash != $dValue) { - throw new SimpleSAML_Error_Error("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) { - throw new SimpleSAML_Error_Error("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)) { - throw new SimpleSAML_Error_Error("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: - throw new SimpleSAML_Error_Error("Unable to determine or unsupported representation of the KeyValue block"); - } - + throw new Exception("Locally Transformed XML (".$transformed_xml_binhash.") does not match XML Document (".$dValue."). Cannot Verify Signature"); + } + $transformer = new Zend_InfoCard_Xml_Security_Transform(); $transformer->addTransform((string)$canonMethod['Algorithm']); list($signedInfo) = $sxe->xpath("//ds:Signature/ds:SignedInfo"); + //SimpleSAML_Logger::debug + //print ("signedinfo ".$sxe->saveXML()); $signedInfoXML = self::addNamespace($signedInfo, "http://www.w3.org/2000/09/xmldsig#"); + SimpleSAML_Logger::debug("canonicalizo ".$signedInfoXML); $canonical_signedinfo = $transformer->applyTransforms($signedInfoXML); - - - //Check received public_key against configured one - if ($sts_crt!=NULL){ - $checkcert = file_get_contents($sts_crt); - $check_key = openssl_pkey_get_public($checkcert); - $checkData = openssl_pkey_get_details($check_key); - $checkData2= openssl_pkey_get_details($public_key); - } - if (($checkData2 == $checkData) || ($sts_crt==NULL)) { - if (openssl_verify($canonical_signedinfo,$signatureValue,$public_key)) { - list($reference) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:Reference"); - return (string)$reference['URI']; - } else { - throw new SimpleSAML_Error_Error("Could not validate the XML signature"); - } + if (openssl_verify($canonical_signedinfo,$signatureValue,$check_key)) { + list($reference) = $sxe->xpath("//ds:Signature/ds:SignedInfo/ds:Reference"); + openssl_free_key($check_key); + return (string)$reference['URI']; } else { - SimpleSAML_Logger::debug("Configured STS cert and received STS cert mismatch"); - throw new SimpleSAML_Error_Error("Configured STS cert and received STS cert mismatch"); + openssl_free_key($check_key); + throw new Exception("Could not validate the XML signature"); } - return false; + } else { + $sxe->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); + 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); + private function addNamespace($xmlElem, $ns) { + $schema = '.*<[^<]*SignedInfo[^>]*'.$ns.'.*>.*'; + $pattern='/\//'; + $replacement='\/'; + $nspattern= '/'.preg_replace($pattern,$replacement,$schema).'/'; + if (preg_match($nspattern,$xmlElem->asXML())>0){ //M$ Cardspaces + $xml = $xmlElem->asXML(); + } + else { //Digitalme + $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); + } } - else { - $xml = str_replace("DS_NS", "xmlns", $xml); - } - return $xml; } @@ -292,9 +318,9 @@ class Zend_InfoCard_Xml_Security case ($len < 0x010000): return sprintf("%c%c%c%c%s", $type, 0x82, $len / 0x0100, $len % 0x0100, $data); default: - throw new SimpleSAML_Error_Error("Could not encode value"); + throw new Exception("Could not encode value"); } - throw new SimpleSAML_Error_Error("Invalid code path"); + throw new Exception("Invalid code path"); } } 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 index c680dd8b7..574907f18 100644 --- a/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform_XmlExcC14N.php +++ b/modules/InfoCard/lib/RP/Zend_InfoCard_Xml_Security_Transform_XmlExcC14N.php @@ -40,13 +40,16 @@ class Zend_InfoCard_Xml_Security_Transform_XmlExcC14N */ public function transform($strXMLData) { + $dom = new DOMDocument(); $dom->loadXML($strXMLData); + if ($strXMLData==NULL) SimpleSAML_Logger::debug("NOXML: ".$dom->saveXML()); + else SimpleSAML_Logger::debug("XMLcan: ".$dom->saveXML()); 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"); + throw new Exception('This transform requires the C14N() method to exist in the DOM extension'); } } diff --git a/modules/InfoCard/lib/UserFunctions.php b/modules/InfoCard/lib/UserFunctions.php new file mode 100644 index 000000000..4c49fff77 --- /dev/null +++ b/modules/InfoCard/lib/UserFunctions.php @@ -0,0 +1,74 @@ +<?php +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: edit this functions to fit your needs +*/ + +class sspmod_InfoCard_UserFunctions { + + /* Called by getinfocard.php and tokenservice.php + * INPUT: user and password + * OUTPUT: true if the data is correct or false in other case + */ + static public function validateUser($user,$pass){ + $status=false; + if( (strcmp($user,'usuario')==0) && (strcmp($pass,'clave')==0) ){ + $status=true; + } + return $status; + } + + + + /* Called by tokenservice.php + * INPUT: username, configured required claims, configured optional claims and requested claims + * OUTPUT: array of claims wiht value and display tag. + */ + static public function fillClaims($user, $configuredRequiredClaims, $configuredOptionalClaims, $requiredClaims){ + $claimValues = array(); + foreach ($requiredClaims as $claim){ + if (array_key_exists($claim,$configuredRequiredClaims) ){ + //The claim exists + $claimValues[$claim]['value']="value-".$claim; + $claimValues[$claim]['displayTag']=$configuredRequiredClaims[$claim]['displayTag']; + }else if (array_key_exists($claim,$configuredOptionalClaims) ){ + //The claim exists + $claimValues[$claim]['value']="value-".$claim; + $claimValues[$claim]['displayTag']=$configuredOptionalClaims[$claim]['displayTag']; + }else{ + //The claim DOES NOT exist + $claimValues[$claim]['value']="unknown-value"; + $claimValues[$claim]['displayTag']=$claim; + } + } + return $claimValues; + } + + + /* Called by getinfocard.php + * INPUT: valid username + * OUTPUT: array containing user data to create its InfoCard + */ + static public function fillICdata($user) { + $ICdata = array(); + $ICdata['CardId'] = 'urn:sts.uah.es:'.$user; + $ICdata['CardName'] = $user."-IC"; + $ICdata['CardImage'] = '/var/simplesaml/modules/InfoCard/www/resources/demoimage.png'; + $ICdata['TimeExpires'] = "9999-12-31T23:59:59Z"; + + //Credentials + $ICdata['DisplayCredentialHint'] = 'Enter your password'; + $ICdata['UserCredential'] = 'UsernamePasswordCredential'; //UsernamePasswordCredential, KerberosV5Credential, X509V3Credential, SelfIssuedCredential + $ICdata['UserName'] = 'usuario'; //UsernamePasswordCredential + $ICdata['KeyIdentifier'] = NULL; //X509V3Credential + $ICdata['PPID'] = NULL; //SelfIssuedCredential + +SimpleSAML_Logger::debug('ICDATA: '.$ICdata['CardImage']); + return $ICdata; + } + + +} +?> \ No newline at end of file diff --git a/modules/InfoCard/lib/Utils.php b/modules/InfoCard/lib/Utils.php new file mode 100644 index 000000000..0df026495 --- /dev/null +++ b/modules/InfoCard/lib/Utils.php @@ -0,0 +1,108 @@ +<?php +/* +* AUTHOR: Samuel Muñoz Hidalgo +* EMAIL: samuel.mh@gmail.com +* LAST REVISION: 16-DEC-08 +* DESCRIPTION: some useful functions. +*/ + +class sspmod_InfoCard_Utils { + + /* + *INPUT: a PEM-encoded certificate + *OUTPUT: a PEM-encoded certificate without the BEGIN and END headers + */ + static public function takeCert($cert) { + $begin = "CERTIFICATE-----"; + $end = "-----END"; + $pem = file_get_contents($cert); + $pem = substr($pem, strpos($pem, $begin)+strlen($begin)); + $pem = substr($pem, 0, strpos($pem, $end)); + return str_replace("\n", "", $pem); + } + + + /* + *INPUT: a XML document + *OUTPUT: a canonicalized XML document + */ + static public function canonicalize($XMLdoc){ + $dom = new DOMDocument(); + $dom->loadXML($XMLdoc); + return ($dom->C14N(true, false)); + } + + + static public function thumbcert($cert){ + return base64_encode(sha1(base64_decode($cert), true)); + } + + + /* + *INPUT: a x509 certificate + *OUTPUT: Common Name or a self issued value if no input is given + *EXTRA: The output is used as issuer + */ + static public function getIssuer($cert){ + if ($cert==NULL){ + return 'http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self'; + }else{ + $resource = file_get_contents($cert); + $check_cert = openssl_x509_read($resource); + $array = openssl_x509_parse($check_cert); + openssl_x509_free($check_cert); + $schema = $array['name']; + $pattern='/.*CN=/'; + $replacement=''; + $CN=preg_replace($pattern,$replacement,$schema); + return $CN; + } + } + + + + + + /* + * INPUT: claims schema (string) and a DOMNodelist with the requested claims in uri style + * OUTPUT: array of requested claims + * + */ + static public function extractClaims($ICschema, $nodeList){ + //Returns the Uri attribute from an attribute list + function getUri($attrList){ + $uri = null; + $end=false; + $i=0; + do{ + if ($i > $attrList->length){ + $end = true; + } else if (strcmp($attrList->item($i)->name,'Uri')==0){ + $end = true; + $uri = $attrList->item($i)->value; + } else { + $i++; + } + } while (!$end); + return $uri; + } + $requiredClaims = array(); + $schema = $ICschema."/claims/"; + SimpleSAML_Logger::debug("schema: ".$schema); + $pattern='/\//'; + $replacement='\/'; + $schema= '/'.preg_replace($pattern,$replacement,$schema).'/'; + for ($i=0;$i<($nodeList->length);$i++) { + $replacement=''; + $uri = getUri($nodeList->item($i)->attributes); + $claim = preg_replace($schema,$replacement,$uri); + $requiredClaims[$i]=$claim; + SimpleSAML_Logger::debug("uri: ".$uri); + SimpleSAML_Logger::debug("claim: ".$claim); + } + return $requiredClaims; +} + + +} +?> \ No newline at end of file diff --git a/modules/InfoCard/templates/default/login-infocard.php b/modules/InfoCard/templates/default/login-infocard.php index 1b439071a..48821e7e8 100644 --- a/modules/InfoCard/templates/default/login-infocard.php +++ b/modules/InfoCard/templates/default/login-infocard.php @@ -2,8 +2,8 @@ /* * AUTHOR: Samuel Muñoz Hidalgo * EMAIL: samuel.mh@gmail.com -* LAST REVISION: 1-DEC-08 -* DESCRIPTION: 'login-infocard' module template. +* LAST REVISION: 22-DEC-08 +* DESCRIPTION: InfoCard module template. */ $this->includeAtTemplateBase('includes/header.php'); if (!array_key_exists('icon', $this->data)) $this->data['icon'] = 'lock.png'; @@ -20,39 +20,67 @@ <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' + <form name="ctl00" id="ctl00" method="post" action="?AuthState=<?php echo $this->data['stateparams']['AuthState']?>"> +<!-- <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 + if ($this->data['InfoCard']['issuerPolicy']!='') echo 'issuerPolicy="'.$this->data['InfoCard']['issuerPolicy'].'"'; + if ($this->data['InfoCard']['tokenType']!='') echo 'tokenType="'.$this->data['InfoCard']['tokenType'].'"'; + if ($this->data['InfoCard']['privacyURL']!='') echo 'privacyUrl="'.$this->data['InfoCard']['privacyURL'].'"'; + if ($this->data['InfoCard']['privacyVersion']!='') echo 'privacyVersion="'.$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"; + 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"; + 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' /> + </ic:informationCard>--> + + <OBJECT type="application/x-informationCard" name="xmlToken"> + <?php + echo '<PARAM Name="issuer" Value="'.$this->data['InfoCard']['issuer']."\">\n"; + if ($this->data['InfoCard']['issuerPolicy']!='') echo '<PARAM Name="issuerPolicy" Value="'.$this->data['InfoCard']['issuerPolicy']."\">\n"; + if ($this->data['InfoCard']['tokenType']!='') echo '<PARAM Name="tokenType" Value="'.$this->data['InfoCard']['tokenType']."\">\n"; + if ($this->data['InfoCard']['privacyURL']!='') echo '<PARAM Name="privacyUrl" Value="'.$this->data['InfoCard']['privacyURL']."\">\n"; + if ($this->data['InfoCard']['privacyVersion']!='')echo '<PARAM Name="privacyVersion" Value="'.$this->data['InfoCard']['privacyVersion']."\">\n";?> + <PARAM Name="requiredClaims" Value="<?php + $schema = $this->data['InfoCard']['schema']."/claims/"; + foreach ($this->data['InfoCard']['requiredClaims'] as $claim=>$data) { + echo $schema.$claim." "; + }?>"> + <PARAM Name="optionalClaims" Value="<?php + $schema = $this->data['InfoCard']['schema']."/claims/"; + foreach ($this->data['InfoCard']['optionalClaims'] as $claim=>$data) { + echo $schema.$claim." "; + }?>"> + </OBJECT> + + <input type='image' src="<?php echo $this->data['IClogo']; ?>" style='cursor:pointer' /> </form> +<!-- GET INFOCARD SECTION --> <?php if (strcmp($this->data['CardGenerator'],'')>0) { - echo '<h2>Or get one</h2>'; - echo '<table border="0">'; + echo '<h2>'.$this->t('get_IC').'</h2>'; 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 border="0">'; + echo "<tr><td>".$this->t('form_username').": </td><td><input type='text' name='username' value='usuario' /></td></tr>"; + echo "<tr><td>".$this->t('form_password').": </td><td><input type='password' name='password' value='clave' /></td></tr>"; + echo "<tr><td></td><td><input type='submit' name='get_button' value='".$this->t('get_button')."' /></td></tr>"; echo '</table>'; + echo '</form>'; } ?> + +<!-- HELP SECTION --> <h2><?php echo $this->t('help_header'); ?></h2> <p><?php echo $this->t('help_text'); ?></p> + <?php + if ((array_key_exists('contact_info_URL',$this->data)) && ($this->data['contact_info_URL']!=null)) + echo "<p><a href='".$this->data['contact_info_URL']."'>".$this->t('contact_info')."</a><p/>"; + if ((array_key_exists('help_desk_email_URL',$this->data)) && ($this->data['help_desk_email_URL']!=null)) + echo "<p><a href='".$this->data['help_desk_email_URL']."'>".$this->t('help_desk_email')."</a></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 index 4989ddbbc..d33c7dbb7 100644 --- a/modules/InfoCard/www/login-infocard.php +++ b/modules/InfoCard/www/login-infocard.php @@ -3,14 +3,14 @@ /* * AUTHOR: Samuel Muñoz Hidalgo * EMAIL: samuel.mh@gmail.com -* LAST REVISION: 1-DEC-08 +* LAST REVISION: 22-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. +* User flow controller. +* Displays the template and request a non null xmlToken */ + /* Load the configuration. */ $config = SimpleSAML_Configuration::getInstance(); $autoconfig = $config->copyFromBase('logininfocard', 'config-login-infocard.php'); @@ -20,6 +20,9 @@ $server_crt = $autoconfig->getValue('server_crt'); $IClogo = $autoconfig->getValue('IClogo'); $Infocard = $autoconfig->getValue('InfoCard'); $cardGenerator = $autoconfig->getValue('CardGenerator'); +$sts_crt = $autoconfig->getValue('sts_crt'); +$help_desk_email_URL = $autoconfig->getValue('help_desk_email_URL'); +$contact_info_URL = $autoconfig->getValue('contact_info_URL'); /* Load the session of the current user. */ @@ -30,24 +33,34 @@ if($session == NULL) { if (!array_key_exists('AuthState', $_REQUEST)) { +SimpleSAML_Logger::debug('NO AUTH STATE'); +SimpleSAML_Logger::debug('ERROR: NO AUTH STATE'); throw new SimpleSAML_Error_BadRequest('Missing AuthState parameter.'); +} else { + $authStateId = $_REQUEST['AuthState']; +SimpleSAML_Logger::debug('AUTH STATE: '.$authStateId); } -$authStateId = $_REQUEST['AuthState']; - if(array_key_exists('xmlToken', $_POST) && ($_POST['xmlToken']!=NULL) ) { +SimpleSAML_Logger::debug('HAY XML TOKEN'); $error = sspmod_InfoCard_Auth_Source_ICAuth::handleLogin($authStateId, $_POST['xmlToken']); }else { +SimpleSAML_Logger::debug('NO HAY XML TOKEN'); $error = NULL; } +unset($_POST); //Show the languages bar if reloaded + //Login Page $t = new SimpleSAML_XHTML_Template($config, 'InfoCard:login-infocard.php', 'InfoCard: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['InfoCard']['issuer'] = $autoconfig->getValue('tokenserviceurl');//sspmod_InfoCard_Utils::getIssuer($sts_crt); $t->data['CardGenerator'] = $cardGenerator; +$t->data['help_desk_email_URL'] = $help_desk_email_URL; +$t->data['contact_info_URL'] = $contact_info_URL; $t->data['error'] = $error; $t->show(); exit(); diff --git a/modules/InfoCard/www/resources/demoimage.png b/modules/InfoCard/www/resources/demoimage.png new file mode 100644 index 0000000000000000000000000000000000000000..8242048599b7ccf3067657330ded38cec3f92f28 GIT binary patch literal 46843 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�wu_`7N`h`S*+%P&h;o{H!Pv-XyIC*?RmSyC| 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%0Aqp+a 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	||{l^=hZ!eU2Hnbsovh+Bd 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 LNkvXXu0mjf*^cmn literal 0 HcmV?d00001 -- GitLab