Back to index

nordugrid-arc-nox  1.1.0~rc6
SPService.cpp
Go to the documentation of this file.
00001 #ifdef HAVE_CONFIG_H
00002 #include <config.h>
00003 #endif
00004 
00005 #include <iostream>
00006 #include <fstream>
00007 
00008 #include <arc/DateTime.h>
00009 #include <arc/GUID.h>
00010 #include <arc/URL.h>
00011 #include <arc/XMLNode.h>
00012 #include <arc/message/PayloadRaw.h>
00013 #include <arc/credential/Credential.h>
00014 #include <arc/client/ClientInterface.h>
00015 
00016 #include <arc/xmlsec/XmlSecUtils.h>
00017 #include <arc/xmlsec/XMLSecNode.h>
00018 #include <arc/xmlsec/saml_util.h>
00019 
00020 #include "SPService.h"
00021 
00022 static Arc::Plugin* get_service(Arc::PluginArgument* arg) {
00023     Arc::ServicePluginArgument* srvarg =
00024             arg?dynamic_cast<Arc::ServicePluginArgument*>(arg):NULL;
00025     if(!srvarg) return NULL;
00026     return new SPService::Service_SP((Arc::Config*)(*srvarg));
00027 }
00028 
00029 Arc::PluginDescriptor PLUGINS_TABLE_NAME[] = {
00030     { "saml.sp", "HED:SERVICE", 0, &get_service },
00031     { NULL, NULL, 0, NULL }
00032 };
00033 
00034 namespace SPService {
00035 
00036 using namespace Arc;
00037 
00038 class SAMLAssertionSecAttr: public Arc::SecAttr {
00039  public:
00040   SAMLAssertionSecAttr(XMLNode& node);
00041   SAMLAssertionSecAttr(std::string& str);
00042   virtual ~SAMLAssertionSecAttr(void);
00043   virtual operator bool(void) const;
00044   virtual bool Export(SecAttrFormat format,XMLNode &val) const;
00045   virtual bool Import(SecAttrFormat format, const XMLNode& val);
00046  protected:
00047   virtual bool equal(const SecAttr &b) const;
00048  private:
00049   XMLNode saml_assertion_node_;
00050 };
00051 
00052 SAMLAssertionSecAttr::SAMLAssertionSecAttr(XMLNode& node) {
00053   Import(SAML, node);
00054 }
00055 
00056 SAMLAssertionSecAttr::SAMLAssertionSecAttr(std::string& node_str) {
00057   Import(SAML, node_str);
00058 }
00059 
00060 SAMLAssertionSecAttr::~SAMLAssertionSecAttr(){}
00061 
00062 bool SAMLAssertionSecAttr::equal(const SecAttr& b) const {
00063   try {
00064     const SAMLAssertionSecAttr& a = dynamic_cast<const SAMLAssertionSecAttr&>(b);
00065     if (!a) return false;
00066     // ...
00067     return false;
00068   } catch(std::exception&) { };
00069   return false;
00070 }
00071 
00072 SAMLAssertionSecAttr::operator bool() const {
00073   return true;
00074 }
00075 
00076 static void add_arc_subject_attribute(XMLNode item,const std::string& subject,const std::string& id) {
00077    XMLNode attr = item.NewChild("ra:SubjectAttribute");
00078    attr=subject; attr.NewAttribute("Type")="string";
00079    attr.NewAttribute("AttributeId")=id;
00080 }
00081 
00082 static void add_xacml_subject_attribute(XMLNode item,const std::string& subject,const std::string& id) {
00083    XMLNode attr = item.NewChild("ra:Attribute");
00084    attr.NewAttribute("DataType")="xs:string";
00085    attr.NewAttribute("AttributeId")=id;
00086    attr.NewChild("ra:AttributeValue") = subject;
00087 }
00088 
00089 bool SAMLAssertionSecAttr::Export(Arc::SecAttrFormat format, XMLNode& val) const {
00090   if(format == UNDEFINED) {
00091   } else if(format == SAML) {
00092     saml_assertion_node_.New(val);
00093     return true;
00094   } else if(format == ARCAuth) {
00095     NS ns;
00096     ns["ra"]="http://www.nordugrid.org/schemas/request-arc";
00097     val.Namespaces(ns); val.Name("ra:Request");
00098     XMLNode item = val.NewChild("ra:RequestItem");
00099     XMLNode subj = item.NewChild("ra:Subject");
00100 
00101     for(int i=0;;i++) {
00102       XMLNode attr_statement = const_cast<XMLNode&>(saml_assertion_node_)["AttributeStatement"][i];
00103       if(!attr_statement) break;
00104       for(int j=0;;j++) {
00105         XMLNode attr = attr_statement["Attribute"][j];
00106         if(!attr) break;
00107         std::string friendlyname = (std::string)(attr.Attribute("FriendlyName"));
00108         std::string attr_val = (std::string)(attr["AttributeValue"]);
00109         //Use the "FriendlyName" as the "AttributeId"
00110         add_arc_subject_attribute(subj, attr_val, friendlyname);
00111       };
00112     };
00113   } else if(format == XACML) {
00114     NS ns;
00115     ns["ra"]="urn:oasis:names:tc:xacml:2.0:context:schema:os";
00116     val.Namespaces(ns); val.Name("ra:Request");
00117     XMLNode subj = val.NewChild("ra:Subject");
00118 
00119     for(int i=0;;i++) {
00120       XMLNode attr_statement = const_cast<XMLNode&>(saml_assertion_node_)["AttributeStatement"][i];
00121       if(!attr_statement) break;
00122       for(int j=0;;j++) {
00123         XMLNode attr = attr_statement["Attribute"][j];
00124         if(!attr) break;
00125         std::string friendlyname = (std::string)(attr.Attribute("FriendlyName"));
00126         std::string attr_val = (std::string)(attr["AttributeValue"]);
00127         //Use the "FriendlyName" as the "AttributeId"
00128         add_xacml_subject_attribute(subj, attr_val, friendlyname);
00129       };
00130     };
00131   }
00132   else {};
00133   return true;
00134 }
00135 
00136 bool SAMLAssertionSecAttr::Import(Arc::SecAttrFormat format, const XMLNode& val) {
00137   if(format == UNDEFINED) {
00138   } else if(format == SAML) {
00139     val.New(saml_assertion_node_);
00140     return true;
00141   }
00142   else {};
00143   return false;
00144 }
00145 
00146 Service_SP::Service_SP(Arc::Config *cfg):RegisteredService(cfg),logger(Arc::Logger::rootLogger, "SAML2SP") {
00147   Arc::XMLNode chain_node = (*cfg).Parent();
00148   Arc::XMLNode tls_node;
00149   for(int i = 0; ;i++) {
00150     tls_node = chain_node.Child(i);
00151     if(!tls_node) break;
00152     std::string tls_name = (std::string)(tls_node.Attribute("name"));
00153     if(tls_name == "tls.service") break;
00154   }
00155   //Use the private key file of the main chain 
00156   //for signing samlp:AuthnRequest
00157   cert_file_ = (std::string)(tls_node["CertificatePath"]);
00158   privkey_file_ = (std::string)(tls_node["KeyPath"]);
00159 /*
00160   cert_file_ = (std::string)((*cfg)["CertificatePath"]);
00161   privkey_file_ = (std::string)((*cfg)["KeyPath"]);
00162 */
00163   sp_name_ = (std::string)((*cfg)["ServiceProviderName"]);
00164   logger.msg(Arc::INFO, "SP Service name is %s", sp_name_);
00165   std::string metadata_file = (std::string)((*cfg)["MetaDataLocation"]);
00166   logger.msg(Arc::INFO, "SAML Metadata is from %s", metadata_file);
00167   metadata_node_.ReadFromFile(metadata_file);
00168 
00169   if(!(Arc::init_xmlsec())) return;
00170 }
00171 
00172 Service_SP::~Service_SP(void) {
00173   Arc::final_xmlsec();
00174 }
00175 
00176 Arc::MCC_Status Service_SP::process(Arc::Message& inmsg,Arc::Message& outmsg) {
00177 
00178   // Check authentication and authorization
00179   if(!ProcessSecHandlers(inmsg, "incoming")) {
00180     logger.msg(Arc::ERROR, "saml2SP: Unauthorized");
00181     return Arc::MCC_Status(Arc::GENERIC_ERROR);
00182   };
00183   // Both input and output are supposed to be HTTP 
00184   // Extracting payload
00185   Arc::PayloadRawInterface* inpayload = dynamic_cast<Arc::PayloadRawInterface*>(inmsg.Payload());
00186   if(!inpayload) {
00187     logger.msg(Arc::WARNING, "empty input payload");
00188   };
00189 
00190   //Analyzing http request from user agent
00191 
00192   std::string msg_content(inpayload->Content());
00193   //SP service is supposed to get two types of http content from user agent:
00194   //1. The IdP name, which user agent sends to SP, and then SP uses to generate AuthnRequest
00195   //2. The saml assertion, which user agent gets from IdP, and then sends to SP 
00196   
00197   if(msg_content.substr(0,4) == "http") { //If IdP name is given from client/useragent
00198     //Get the IdP name from the request
00199     //Here we require the user agent to provide the idp name instead of the 
00200     //WAYF(where are you from) or Discovery Service in some other implementation of SP
00201     //like Shibboleth
00202 
00203     std::string idp_name(msg_content);
00204 
00205     //Compose <samlp:AuthnRequest/>
00206     Arc::NS saml_ns;
00207     saml_ns["saml"] = SAML_NAMESPACE;
00208     saml_ns["samlp"] = SAMLP_NAMESPACE;
00209     Arc::XMLNode authn_request(saml_ns, "samlp:AuthnRequest");
00210     //std::string sp_name("https://squark.uio.no/shibboleth-sp");
00211     std::string sp_name = sp_name_;
00212     std::string req_id = Arc::UUID();
00213     authn_request.NewAttribute("ID") = req_id;
00214     Arc::Time t1;
00215     std::string current_time1 = t1.str(Arc::UTCTime);
00216     authn_request.NewAttribute("IssueInstant") = current_time1;
00217     authn_request.NewAttribute("Version") = std::string("2.0");
00218 
00219     //Get url of assertion consumer service from metadata
00220     std::string assertion_consumer_url;
00221     for(int i = 0;;i++) {
00222       Arc::XMLNode nd = metadata_node_.Child(i);
00223       if(!nd) break;
00224       if(sp_name == (std::string)(nd.Attribute("entityID"))) {
00225         for(int j = 0;; j++) {
00226           Arc::XMLNode sp_nd = nd.Child(j);
00227           if(!sp_nd) break;
00228           if(MatchXMLName(sp_nd,"SPSSODescriptor")) {
00229             for(int k = 0;;k++) {
00230               Arc::XMLNode assertionconsumer_nd = sp_nd.Child(k);
00231               if(!assertionconsumer_nd) break;        
00232               if(MatchXMLName(assertionconsumer_nd, "AssertionConsumerService")) {
00233                 if((std::string)(assertionconsumer_nd.Attribute("Binding")) == "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
00234                   assertion_consumer_url = (std::string)(assertionconsumer_nd.Attribute("Location"));
00235               }
00236               if(!assertion_consumer_url.empty()) break;
00237             }
00238           }
00239           if(!assertion_consumer_url.empty()) break;
00240         }
00241       }
00242     }
00243     authn_request.NewAttribute("AssertionConsumerServiceURL") = assertion_consumer_url;
00244 
00245     //Get url of sso service from metadata
00246     std::string sso_url;
00247     for(int i = 0;;i++) {
00248       Arc::XMLNode nd = metadata_node_.Child(i);
00249       if(!nd) break;
00250       if(idp_name == (std::string)(nd.Attribute("entityID"))) {
00251         for(int j = 0;; j++) {
00252           Arc::XMLNode idp_nd = nd.Child(j);
00253           if(!idp_nd) break;
00254           if(MatchXMLName(idp_nd,"IDPSSODescriptor")) {
00255             for(int k = 0;;k++) {
00256               Arc::XMLNode sso_nd = idp_nd.Child(k);
00257               if(!sso_nd) break;
00258               if(MatchXMLName(sso_nd, "SingleSignOnService")) {
00259                 if((std::string)(sso_nd.Attribute("Binding")) == "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
00260                   sso_url = (std::string)(sso_nd.Attribute("Location"));
00261               }
00262               if(!sso_url.empty()) break;
00263             }
00264           }
00265           if(!sso_url.empty()) break;
00266         }
00267       }
00268     }
00269     authn_request.NewAttribute("Destination") = sso_url;
00270     authn_request.NewAttribute("ProtocolBinding") = std::string("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
00271     authn_request.NewChild("saml:Issuer") = sp_name;
00272 
00273     Arc::XMLNode nameid_policy = authn_request.NewChild("samlp:NameIDPolicy");
00274     nameid_policy.NewAttribute("AllowCreate") = std::string("1");
00275 
00276     bool must_signed = true; //TODO: get the information from metadata
00277     std::string authnRequestQuery;
00278     std::string query = "SAMLRequest=" + BuildDeflatedQuery(authn_request);
00279     logger.msg(Arc::DEBUG,"AuthnRequest after deflation: %s", query.c_str());
00280     if(must_signed) {
00281       //SP service uses it's private key to sign the AuthnRequest,
00282       //then after User Agent redirecting AuthnRequest to IdP, 
00283       //IdP will verify the signature by picking up SP's certificate
00284       //from IdP's metadata.
00285       logger.msg(Arc::DEBUG,"Using private key file to sign: %s", privkey_file_.c_str());
00286       authnRequestQuery = SignQuery(query, Arc::RSA_SHA1, privkey_file_);
00287       logger.msg(Arc::DEBUG,"After signature: %s", authnRequestQuery.c_str());
00288     }
00289     else authnRequestQuery = query;
00290 
00291 #if 0
00292     //Verify the signature
00293     std::string cert_str = get_cert_str(cert_file_.c_str());
00294     std::cout<<"Cert to sign AuthnRequest: "<<cert_str<<std::endl;
00295     if(VerifyQuery(authnRequestQuery, cert_str)) {
00296       std::cout<<"Succeeded to verify the signature on AuthnRequest"<<std::endl;
00297     }
00298     else { std::cout<<"Failed to verify the signature on AuthnRequest"<<std::endl; }
00299 #endif
00300 
00301     std::string authnRequestUrl;
00302     authnRequestUrl = sso_url + "?" + authnRequestQuery;
00303 
00304     //Return the composed url back to user agent through http
00305     Arc::PayloadRaw* outpayload = NULL;
00306     outpayload = new Arc::PayloadRaw;
00307     outpayload->Insert(authnRequestUrl.c_str(),0, authnRequestUrl.size());
00308     //outmsg.Attributes()->set("HTTP:CODE","302");
00309     //outmsg.Attributes()->set("HTTP:REASON","Moved Temporarily");
00310     delete outmsg.Payload(outpayload);
00311   }
00312   else {  
00313     //The http content should be <saml:EncryptedAssertion/> or <saml:Assertion/>
00314     //Decrypt the assertion (if it is encrypted) by using SP's private key (the key is the 
00315     //same as the one for the main message chain)
00316     //std::cout<<"saml assertion from peer side: "<<msg_content<<std::endl;
00317     Arc::XMLNode assertion_nd(msg_content);
00318     if(MatchXMLName(assertion_nd, "EncryptedAssertion")) {
00319       //Decrypt the encrypted saml assertion
00320       std::string saml_assertion;
00321       assertion_nd.GetXML(saml_assertion);
00322       logger.msg(Arc::DEBUG,"Encrypted saml assertion: %s", saml_assertion.c_str());
00323 
00324       Arc::XMLSecNode sec_assertion_nd(assertion_nd);
00325       Arc::XMLNode decrypted_assertion_nd;
00326 
00327       bool r = sec_assertion_nd.DecryptNode(privkey_file_, decrypted_assertion_nd);
00328       if(!r) { 
00329         logger.msg(Arc::ERROR,"Can not decrypt the EncryptedAssertion from saml response"); 
00330         return Arc::MCC_Status(); 
00331       }
00332 
00333       std::string decrypted_saml_assertion;
00334       decrypted_assertion_nd.GetXML(decrypted_saml_assertion);
00335       logger.msg(Arc::DEBUG,"Decrypted SAML Assertion: %s", decrypted_saml_assertion.c_str());
00336      
00337       //Decrypt the <saml:EncryptedID/> if it exists in the above saml assertion
00338       Arc::XMLNode nameid_nd = decrypted_assertion_nd["saml:Subject"]["saml:EncryptedID"];
00339       std::string nameid;
00340       nameid_nd.GetXML(nameid);
00341       logger.msg(Arc::DEBUG,"Encrypted name id: %s", nameid.c_str());
00342 
00343       Arc::XMLSecNode sec_nameid_nd(nameid_nd);
00344       Arc::XMLNode decrypted_nameid_nd;
00345       r = sec_nameid_nd.DecryptNode(privkey_file_, decrypted_nameid_nd);
00346       if(!r) { logger.msg(Arc::ERROR,"Can not decrypt the EncryptedID from saml assertion"); return Arc::MCC_Status(); }
00347 
00348       std::string decrypted_nameid;
00349       decrypted_nameid_nd.GetXML(decrypted_nameid);
00350       logger.msg(Arc::DEBUG,"Decrypted SAML NameID: %s", decrypted_nameid.c_str());
00351 
00352       //Replace the <saml:EncryptedID/> with <saml:NameID/>
00353       nameid_nd.Replace(decrypted_nameid_nd);
00354 
00355       //Check the <saml:Condition/> <saml:AuthnStatement/> 
00356       //and <saml:/Subject> part of saml assertion
00357       XMLNode subject   = decrypted_assertion_nd["saml:Subject"];
00358       XMLNode conditions = decrypted_assertion_nd["saml:Conditions"];
00359       XMLNode authnstatement = decrypted_assertion_nd["saml:AuthnStatement"];
00360 
00361       std::string notbefore_str = (std::string)(conditions.Attribute("NotBefore"));
00362       Time notbefore = notbefore_str;
00363       std::string notonorafter_str = (std::string)(conditions.Attribute("NotOnOrAfter"));
00364       Time notonorafter = notonorafter_str;
00365       Time now = Time();
00366       if(!notbefore_str.empty() && notbefore >= now) {
00367         logger.msg(Arc::ERROR,"saml:Conditions, current time is before the start time"); 
00368         return Arc::MCC_Status(); 
00369       } else if (!notonorafter_str.empty() && notonorafter < now) {
00370         logger.msg(Arc::ERROR,"saml:Conditions, current time is after the end time"); 
00371         return Arc::MCC_Status(); 
00372       }     
00373 
00374       XMLNode subject_confirmation = subject["saml:SubjectConfirmation"];
00375       std::string confirm_method = (std::string)(subject_confirmation.Attribute("Method"));
00376       if(confirm_method == "urn:oasis:names:tc:SAML:2.0:cm:bearer") {
00377         //TODO
00378       } else if (confirm_method == "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key") {
00379         //TODO
00380       }
00381       notbefore_str = (std::string)(subject_confirmation.Attribute("NotBefore"));
00382       notbefore = notbefore_str;
00383       notonorafter_str = (std::string)(subject_confirmation.Attribute("NotOnOrAfter"));
00384       notonorafter = notonorafter_str;
00385       now = Time();
00386       if(!notbefore_str.empty() && notbefore >= now) {
00387         logger.msg(Arc::ERROR,"saml:Subject, current time is before the start time");
00388         return Arc::MCC_Status();
00389       } else if (!notonorafter_str.empty() && notonorafter < now) {
00390         logger.msg(Arc::ERROR,"saml:Subject, current time is after the end time");
00391         return Arc::MCC_Status();
00392       }
00393       //TODO: "InResponseTo", need to recorde the ID?
00394 
00395       
00396   
00397       //Record the saml assertion into message context, this information
00398       //will be checked by saml2sso_serviceprovider handler later to decide
00399       //whether authorize the incoming message which is from the same session
00400       //as this saml2sso process
00401 
00402       SAMLAssertionSecAttr* sattr = new SAMLAssertionSecAttr(decrypted_assertion_nd);
00403       inmsg.Auth()->set("SAMLAssertion", sattr);
00404 
00405       Arc::PayloadRaw* outpayload = NULL;
00406       outpayload = new Arc::PayloadRaw;
00407       //std::string authorization_info("SAML2SSO process succeeded");
00408       //outpayload->Insert(authorization_info.c_str(), 0, authorization_info.size());
00409       delete outmsg.Payload(outpayload);
00410     }
00411     else if(MatchXMLName(assertion_nd, "Assertion")) {
00412 
00413     }
00414     else {
00415       logger.msg(Arc::ERROR,"Can not get saml:Assertion or saml:EncryptedAssertion from IdP");
00416       Arc::MCC_Status();
00417     }
00418   }
00419 
00420   return Arc::MCC_Status(Arc::STATUS_OK);
00421 }
00422 
00423 
00424 bool Service_SP::RegistrationCollector(Arc::XMLNode &doc) {
00425   Arc::NS isis_ns; isis_ns["isis"] = "http://www.nordugrid.org/schemas/isis/2008/08";
00426   Arc::XMLNode regentry(isis_ns, "RegEntry");
00427   regentry.NewChild("SrcAdv").NewChild("Type") = "org.nordugrid.security.saml";
00428   regentry.New(doc);
00429   return true;
00430 }
00431 
00432 
00433 } // namespace SPService
00434