Back to index

nordugrid-arc-nox  1.1.0~rc6
SAMLTokenSH.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/XMLNode.h>
00009 #include <arc/GUID.h>
00010 #include <arc/URL.h>
00011 #include <arc/StringConv.h>
00012 #include <arc/message/MCC.h>
00013 #include <arc/ws-security/SAMLToken.h>
00014 #include <arc/xmlsec/XmlSecUtils.h>
00015 #include <arc/xmlsec/XMLSecNode.h>
00016 #include <arc/credential/Credential.h>
00017 #include <arc/client/ClientInterface.h>
00018 #include <arc/message/PayloadSOAP.h>
00019 
00020 #include "SAMLTokenSH.h"
00021 
00022 static Arc::Logger logger(Arc::Logger::rootLogger, "SAMLTokenSH");
00023 
00024 Arc::Plugin* ArcSec::SAMLTokenSH::get_sechandler(Arc::PluginArgument* arg) {
00025     ArcSec::SecHandlerPluginArgument* shcarg =
00026             arg?dynamic_cast<ArcSec::SecHandlerPluginArgument*>(arg):NULL;
00027     if(!shcarg) return NULL;
00028     return new ArcSec::SAMLTokenSH((Arc::Config*)(*shcarg),(Arc::ChainContext*)(*shcarg));
00029 }
00030 
00031 /*
00032 sechandler_descriptors ARC_SECHANDLER_LOADER = {
00033     { "samltoken.creator", 0, &get_sechandler},
00034     { NULL, 0, NULL }
00035 };
00036 */
00037 
00038 namespace ArcSec {
00039 using namespace Arc;
00040 
00041 class SAMLAssertionSecAttr: public Arc::SecAttr {
00042  public:
00043   SAMLAssertionSecAttr(XMLNode& node);
00044   SAMLAssertionSecAttr(std::string& str);
00045   virtual ~SAMLAssertionSecAttr(void);
00046   virtual operator bool(void) const;
00047   virtual bool Export(SecAttrFormat format,XMLNode &val) const;
00048   virtual bool Import(SecAttrFormat format, const XMLNode& val);
00049  protected:
00050   virtual bool equal(const SecAttr &b) const;
00051  private:
00052   XMLNode saml_assertion_node_;
00053 };
00054 
00055 SAMLAssertionSecAttr::SAMLAssertionSecAttr(XMLNode& node) {
00056   Import(SAML, node);
00057 }
00058 
00059 SAMLAssertionSecAttr::SAMLAssertionSecAttr(std::string& node_str) {
00060   Import(SAML, node_str);
00061 }
00062 
00063 SAMLAssertionSecAttr::~SAMLAssertionSecAttr(){}
00064 
00065 bool SAMLAssertionSecAttr::equal(const SecAttr& b) const {
00066   try {
00067     const SAMLAssertionSecAttr& a = dynamic_cast<const SAMLAssertionSecAttr&>(b);
00068     if (!a) return false;
00069     // ...
00070     return false;
00071   } catch(std::exception&) { };
00072   return false;
00073 }
00074 
00075 SAMLAssertionSecAttr::operator bool() const {
00076   return true;
00077 }
00078 
00079 static void add_subject_attribute(XMLNode item,const std::string& subject,const char* id) {
00080    XMLNode attr = item.NewChild("ra:SubjectAttribute");
00081    attr=subject; attr.NewAttribute("Type")="string";
00082    attr.NewAttribute("AttributeId")=id;
00083 }
00084 
00085 bool SAMLAssertionSecAttr::Export(Arc::SecAttrFormat format, XMLNode& val) const {
00086   if(format == UNDEFINED) {
00087   } else if(format == SAML) {
00088     saml_assertion_node_.New(val);
00089     return true;
00090   } else if(format == ARCAuth) { 
00091     //Parse the attributes inside saml assertion, 
00092     //and compose it into Arc request
00093     NS ns;
00094     ns["ra"]="http://www.nordugrid.org/schemas/request-arc";
00095     val.Namespaces(ns); val.Name("ra:Request");
00096     XMLNode item = val.NewChild("ra:RequestItem");
00097     XMLNode subj = item.NewChild("ra:Subject");
00098 
00099     Arc::XMLNode subject_nd = saml_assertion_node_["Subject"]["NameID"];
00100     add_subject_attribute(subj,subject_nd,"http://www.nordugrid.org/schemas/policy-arc/types/wss-saml/subject");
00101 
00102     Arc::XMLNode issuer_nd = saml_assertion_node_["Issuer"];
00103     add_subject_attribute(subj,issuer_nd,"http://www.nordugrid.org/schemas/policy-arc/types/wss-saml/issuer");
00104 
00105     Arc::XMLNode attr_statement = saml_assertion_node_["AttributeStatement"];
00106     Arc::XMLNode attr_nd;
00107     for(int i=0;;i++) {
00108       attr_nd = attr_statement["Attribute"][i];
00109       if(!attr_nd) break;
00110       std::string attr_name = attr_nd.Attribute("Name");
00111       //std::string attr_nameformat = attr_nd.Attribute("NameFormat");
00112       //std::string attr_friendname = attribute.Attribute("FriendlyName");
00113       Arc::XMLNode attrval_nd;
00114       for(int j=0;;j++) {
00115         attrval_nd = attr_nd["AttributeValue"][j];
00116         if(!attrval_nd) break;
00117         std::string tmp = "http://www.nordugrid.org/schemas/policy-arc/types/wss-saml/"+attr_name;
00118         add_subject_attribute(subj,attrval_nd,tmp.c_str());
00119       }
00120     }
00121   }
00122   else {};
00123   return true;
00124 }
00125 
00126 bool SAMLAssertionSecAttr::Import(Arc::SecAttrFormat format, const XMLNode& val) {
00127   if(format == UNDEFINED) {
00128   } else if(format == SAML) {
00129     val.New(saml_assertion_node_);
00130     return true;
00131   }
00132   else {};
00133   return false;
00134 }
00135 
00136 SAMLTokenSH::SAMLTokenSH(Config *cfg,ChainContext*):SecHandler(cfg){
00137   if(!init_xmlsec()) return;
00138   process_type_=process_none;
00139   std::string process_type = (std::string)((*cfg)["Process"]);
00140   if(process_type == "generate") { 
00141     cert_file_=(std::string)((*cfg)["CertificatePath"]);
00142     if(cert_file_.empty()) {
00143       logger.msg(ERROR,"Missing or empty CertificatePath element");
00144       return;
00145     };
00146     key_file_=(std::string)((*cfg)["KeyPath"]);
00147     if(key_file_.empty()) {
00148       logger.msg(ERROR,"Missing or empty KeyPath element");
00149       return;
00150     };
00151     ca_file_=(std::string)((*cfg)["CACertificatePath"]);
00152     ca_dir_=(std::string)((*cfg)["CACertificatesDir"]);
00153     if(ca_file_.empty() && ca_dir_.empty()) {
00154       logger.msg(WARNING,"Both of CACertificatePath and CACertificatesDir elements missing or empty");
00155     };
00156     aa_service_ = (std::string)((*cfg)["AAService"]);
00157     process_type_=process_generate;
00158   } else if(process_type == "extract") {
00159     //If ca file does not exist, we can only verify the signature by
00160     //using the certificate in the incoming wssecurity; we can not authenticate
00161     //the the message because we can not check the certificate chain without 
00162     //trusted ca.
00163     ca_file_=(std::string)((*cfg)["CACertificatePath"]);
00164     ca_dir_=(std::string)((*cfg)["CACertificatesDir"]);
00165     if(ca_file_.empty() && ca_dir_.empty()) {
00166       logger.msg(INFO,"Missing or empty CertificatePath or CACertificatesDir element; will only check the signature, will not do message authentication");
00167     };
00168     process_type_=process_extract;
00169   } else {
00170     logger.msg(ERROR,"Processing type not supported: %s",process_type);
00171     return;
00172   };
00173   if(!cert_file_.empty()) {
00174     Arc::Credential cred(cert_file_, key_file_, ca_dir_, ca_file_);
00175     local_dn_ = convert_to_rdn(cred.GetDN());
00176   }
00177 }
00178 
00179 SAMLTokenSH::~SAMLTokenSH() {
00180   final_xmlsec();
00181 }
00182 
00183 bool SAMLTokenSH::Handle(Arc::Message* msg) const {
00184   if(process_type_ == process_extract) {
00185     try {
00186       PayloadSOAP* soap = dynamic_cast<PayloadSOAP*>(msg->Payload());
00187       SAMLToken st(*soap);
00188       if(!st) {
00189         logger.msg(ERROR,"Failed to parse SAML Token from incoming SOAP");
00190         return false;
00191       };
00192 /*
00193       if(!st.Authenticate()) {
00194         logger.msg(ERROR, "Failed to verify SAML Token inside the incoming SOAP");
00195         return false;
00196       };
00197 */
00198       if((!ca_file_.empty() || !ca_dir_.empty()) && !st.Authenticate(ca_file_, ca_dir_)) {
00199         logger.msg(ERROR, "Failed to authenticate SAML Token inside the incoming SOAP");
00200         return false;
00201       };
00202       logger.msg(INFO, "Succeeded to authenticate SAMLToken");
00203 
00204       //Store the saml assertion into message context
00205       Arc::XMLNode assertion_nd = st["Assertion"];
00206       SAMLAssertionSecAttr* sattr = new SAMLAssertionSecAttr(assertion_nd);
00207       msg->Auth()->set("SAMLAssertion", sattr);
00208 
00209     } catch(std::exception) {
00210       logger.msg(ERROR,"Incoming Message is not SOAP");
00211       return false;
00212     } 
00213   } else if(process_type_ == process_generate) {
00214     try {
00215       if(!saml_assertion_) {
00216         //Contact the AA service to get the saml assertion
00217 
00218         //Compose <samlp:AttributeQuery/>
00219         Arc::NS ns;
00220         ns["saml"] = "urn:oasis:names:tc:SAML:2.0:assertion";
00221         ns["samlp"] = "urn:oasis:names:tc:SAML:2.0:protocol";
00222         Arc::XMLNode attr_query(ns, "samlp:AttributeQuery");
00223         std::string query_id = Arc::UUID();
00224         attr_query.NewAttribute("ID") = query_id;
00225         Arc::Time t;
00226         std::string current_time = t.str(Arc::UTCTime);
00227         attr_query.NewAttribute("IssueInstant") = current_time;
00228         attr_query.NewAttribute("Version") = std::string("2.0");
00229         attr_query.NewChild("saml:Issuer") = local_dn_;
00230 
00231         Arc::XMLNode subject = attr_query.NewChild("saml:Subject");
00232         Arc::XMLNode name_id = subject.NewChild("saml:NameID");
00233         name_id.NewAttribute("Format")=std::string("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName");
00234         name_id = local_dn_;
00235 
00236         Arc::XMLNode attribute = attr_query.NewChild("saml:Attribute");
00237         attribute.NewAttribute("Name")=std::string("urn:oid:1.3.6.1.4.1.5923.1.1.1.6");
00238         attribute.NewAttribute("NameFormat")=std::string("urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
00239         attribute.NewAttribute("FriendlyName")=std::string("eduPersonPrincipalName");
00240 
00241         Arc::XMLSecNode attr_query_secnd(attr_query);
00242         std::string attr_query_idname("ID");
00243         attr_query_secnd.AddSignatureTemplate(attr_query_idname, Arc::XMLSecNode::RSA_SHA1);
00244         if(attr_query_secnd.SignNode(key_file_, cert_file_)) {
00245           std::cout<<"Succeeded to sign the signature under <samlp:AttributeQuery/>"<<std::endl;
00246         }
00247 
00248         Arc::NS soap_ns;
00249         Arc::SOAPEnvelope envelope(soap_ns);
00250         envelope.NewChild(attr_query);
00251         Arc::PayloadSOAP request(envelope);
00252 
00253         // Send request
00254         Arc::URL aa_service_url(aa_service_);
00255         Arc::MCCConfig cfg;
00256         if (!cert_file_.empty()) cfg.AddCertificate(cert_file_);
00257         if (!key_file_.empty()) cfg.AddPrivateKey(key_file_);
00258         if (!ca_file_.empty()) cfg.AddCAFile(ca_file_);
00259         if (!ca_dir_.empty()) cfg.AddCADir(ca_dir_);
00260 
00261         Arc::ClientSOAP client(cfg, aa_service_url);
00262         Arc::PayloadSOAP *response = NULL;
00263         Arc::MCC_Status status = client.process(&request,&response);
00264         if (!response) {
00265           logger.msg(Arc::ERROR, "No response from AA service %s failed", aa_service_.c_str());
00266           return false;
00267         }
00268         if (!status) {
00269           logger.msg(Arc::ERROR, "SOAP Request to AA service %s failed", aa_service_.c_str());
00270           delete response; return false;
00271         }
00272 
00273         //Consume the response from AA
00274         Arc::XMLNode attr_resp;
00275         attr_resp = (*response).Body().Child(0);
00276         if(!attr_resp) {
00277           logger.msg(Arc::ERROR, "Cannot find content under response soap message");
00278           return false;
00279         }
00280         if((attr_resp.Name() != "Response") || (attr_resp.Prefix() != "samlp")) {
00281           logger.msg(Arc::ERROR, "Cannot find <samlp:Response/> under response soap message:");
00282           std::string tmp; attr_resp.GetXML(tmp);
00283           logger.msg(Arc::ERROR, "%s", tmp.c_str());
00284           return false;
00285         }
00286 
00287         std::string resp_idname = "ID";
00288         Arc::XMLSecNode attr_resp_secnode(attr_resp);
00289         if(attr_resp_secnode.VerifyNode(resp_idname, ca_file_, ca_dir_)) {
00290           logger.msg(Arc::INFO, "Succeeded to verify the signature under <samlp:Response/>");
00291         }
00292         else {
00293           logger.msg(Arc::ERROR, "Failed to verify the signature under <samlp:Response/>");
00294           delete response; return false;
00295         }
00296         std::string responseto_id = (std::string)(attr_resp.Attribute("InResponseTo"));
00297         if(query_id != responseto_id) {
00298           logger.msg(Arc::INFO, "The Response is not going to this end");
00299           delete response; return false;
00300         }
00301 
00302         std::string resp_time = attr_resp.Attribute("IssueInstant");
00303         std::string statuscode_value = attr_resp["samlp:Status"]["samlp:StatusCode"];
00304         if(statuscode_value == "urn:oasis:names:tc:SAML:2.0:status:Success")
00305           logger.msg(Arc::INFO, "The StatusCode is Success");
00306 
00307         Arc::XMLNode assertion = attr_resp["saml:Assertion"];
00308         std::string assertion_idname = "ID";
00309         Arc::XMLSecNode assertion_secnode(assertion);
00310         if(assertion_secnode.VerifyNode(assertion_idname, ca_file_, ca_dir_)) {
00311           logger.msg(Arc::INFO, "Succeeded to verify the signature under <saml:Assertion/>");
00312         }
00313         else {
00314           logger.msg(Arc::ERROR, "Failed to verify the signature under <saml:Assertion/>");
00315           delete response;  return false;
00316         }
00317         assertion.New(saml_assertion_);
00318         delete response;
00319       }
00320 
00321       //Protect the SOAP message with SAML assertion
00322       PayloadSOAP* soap = dynamic_cast<PayloadSOAP*>(msg->Payload());
00323       SAMLToken st(*soap, cert_file_, key_file_, SAMLToken::SAML2, saml_assertion_);
00324       if(!st) {
00325         logger.msg(ERROR,"Failed to generate SAML Token for outgoing SOAP");
00326         return false;
00327       };
00328       //Reset the soap message
00329       (*soap) = st;
00330     } catch(std::exception) {
00331       logger.msg(ERROR,"Outgoing Message is not SOAP");
00332       return false;
00333     }
00334   } else {
00335     logger.msg(ERROR,"SAML Token handler is not configured");
00336     return false;
00337   } 
00338   return true;
00339 }
00340 
00341 }
00342 
00343