Back to index

nordugrid-arc-nox  1.1.0~rc6
test_client2shibboleth.cpp
Go to the documentation of this file.
00001 #ifdef HAVE_CONFIG_H
00002 #include <config.h>
00003 #endif
00004 
00005 #include <string>
00006 #include <iostream>
00007 #include <fstream>
00008 #include <signal.h>
00009 
00010 #include <arc/ArcConfig.h>
00011 #include <arc/Logger.h>
00012 #include <arc/XMLNode.h>
00013 #include <arc/loader/Loader.h>
00014 #include <arc/message/SOAPEnvelope.h>
00015 #include <arc/message/PayloadSOAP.h>
00016 #include <arc/StringConv.h>
00017 #include <arc/XMLNode.h>
00018 #include <arc/DateTime.h>
00019 #include <arc/GUID.h>
00020 #include <arc/URL.h>
00021 #include <arc/credential/Credential.h>
00022 
00023 #include <arc/message/MCC.h>
00024 #include <arc/message/PayloadRaw.h>
00025 #include <arc/client/ClientInterface.h>
00026 
00027 #ifdef WIN32
00028 #include <arc/win32.h>
00029 #endif
00030 
00031 #include "../../hed/libs/xmlsec/XmlSecUtils.h"
00032 #include "../../hed/libs/xmlsec/XMLSecNode.h"
00033 #include "../../hed/libs/xmlsec/saml_util.h"
00034 
00035 typedef enum {
00036   HTTP_METHOD_NONE = -1,
00037   HTTP_METHOD_ANY,
00038   HTTP_METHOD_IDP_INITIATED,
00039   HTTP_METHOD_GET,
00040   HTTP_METHOD_POST,
00041   HTTP_METHOD_REDIRECT,
00042   HTTP_METHOD_SOAP,
00043   HTTP_METHOD_ARTIFACT_GET,
00044   HTTP_METHOD_ARTIFACT_POST
00045 } HttpMethod;
00046 
00051 
00072 int main(void) {
00073   signal(SIGTTOU,SIG_IGN);
00074   signal(SIGTTIN,SIG_IGN);
00075   signal(SIGPIPE,SIG_IGN);
00076   Arc::Logger logger(Arc::Logger::rootLogger, "SAMLTest");
00077   Arc::LogStream logcerr(std::cerr);
00078   Arc::Logger::rootLogger.addDestination(logcerr);
00079 
00080   Arc::NS saml_ns;
00081   saml_ns["saml"] = SAML_NAMESPACE;
00082   saml_ns["samlp"] = SAMLP_NAMESPACE;
00083 
00084   //----------------------------------
00085   //Client-Agent and SP: Compose AuthnRequest
00086   //Client-Agent access SP, SP generate AuthnRequest and respond it to Client-Agent
00087   //----------------------------------
00088 
00089   //Compose <samlp:AuthnRequest/>
00090   Arc::XMLNode authn_request(saml_ns, "samlp:AuthnRequest");
00091   //std::string sp_name("https://sp.testshib.org/shibboleth-sp"); //TODO
00092   std::string sp_name("https://squark.uio.no/shibboleth-sp");
00093   std::string req_id = Arc::UUID();
00094   authn_request.NewAttribute("ID") = req_id;
00095   Arc::Time t1;
00096   std::string current_time1 = t1.str(Arc::UTCTime);
00097   authn_request.NewAttribute("IssueInstant") = current_time1;
00098   authn_request.NewAttribute("Version") = std::string("2.0");
00099   //authn_request.NewAttribute("AssertionConsumerServiceURL") = std::string("https://sp.testshib.org/Shibboleth.sso/SAML2/POST");
00100   authn_request.NewAttribute("AssertionConsumerServiceURL") = std::string("https://squark.uio.no/Shibboleth.sso/SAML2/POST");
00101 
00102 
00103   std::string destination("https://squark.uio.no:8443/idp/profile/SAML2/Redirect/SSO");
00104   //std::string destination("https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO");
00105 
00106   authn_request.NewAttribute("Destination") = destination;
00107   authn_request.NewAttribute("ProtocolBinding") = std::string("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
00108    
00109   authn_request.NewChild("saml:Issuer") = sp_name;
00110   
00111   Arc::XMLNode nameid_policy = authn_request.NewChild("samlp:NameIDPolicy");
00112   nameid_policy.NewAttribute("AllowCreate") = std::string("1");
00113 
00114   //nameid_policy.NewAttribute("Format") = std::string("urn:oasis:names:tc:SAML:2.0:nameid-format:transient");
00115   //nameid_policy.NewAttribute("SPNameQualifier") = sp_name;
00116 
00117   //authn_request.NewAttribute("IsPassive") = std::string("false");
00118   //authn_request.NewAttribute() =  
00119 
00120   bool must_signed = false; //TODO: get the information from metadata
00121   
00122   HttpMethod httpmethod = HTTP_METHOD_REDIRECT; //TODO
00123 
00124   std::string str1;
00125   authn_request.GetXML(str1);
00126   std::cout<<"AuthnRequest:  "<<str1<<std::endl;
00127 
00128   std::string cert_file;// = "testcert.pem";
00129   std::string privkey_file = "testkey-nopass.pem";
00130   //TestShib use the certificate for SAML2 SSO
00131   std::string ca_file = "cacert.pem"; // = "cacert_testshib.pem";
00132 
00133   std::string authnRequestQuery;
00134   if(httpmethod == HTTP_METHOD_REDIRECT) {
00135     std::string query = BuildDeflatedQuery(authn_request);
00136     std::cout<<"AuthnRequest after deflation: "<<query<<std::endl;
00137     if(must_signed) {
00138       authnRequestQuery = SignQuery(query, Arc::RSA_SHA1, privkey_file);
00139       std::cout<<"After signature: "<<authnRequestQuery<<std::endl;
00140     }
00141     else authnRequestQuery = query;
00142   }
00143   
00144   std::string authnRequestUrl;
00145   authnRequestUrl = destination + "?SAMLRequest=" + authnRequestQuery;
00146   std::cout<<"autnRequestUrl: "<<authnRequestUrl<<std::endl;
00147 
00148   // -------------------------------------------
00149   // Client-Agent: Send the AuthnRequest to IdP, 
00150   // (IP-based authentication, and Username/Password authentication) 
00151   // get back b64 encoded saml response, and 
00152   // send this saml response to SP
00153   // -------------------------------------------
00154   //
00155   //
00156 
00157   Arc::MCCConfig cfg;
00158   //if (!cert_file.empty())
00159   //  cfg.AddCertificate(cert_file);
00160   //if (!privkey_file.empty())
00161   //  cfg.AddPrivateKey(privkey_file);
00162   if (!ca_file.empty())
00163     cfg.AddCAFile(ca_file);
00164 
00165   Arc::URL url(authnRequestUrl);
00166   Arc::ClientHTTP client(cfg, url);
00167   Arc::PayloadRaw request;
00168   Arc::PayloadRawInterface *response = NULL;
00169   Arc::HTTPClientInfo info;
00170   Arc::MCC_Status status = client.process("GET", &request, &info, &response);
00171   if (!response) {
00172     logger.msg(Arc::ERROR, "Request failed: No response");
00173     return -1;
00174   }
00175   if (!status) {
00176     logger.msg(Arc::ERROR, "Request failed: Error");
00177     return -1;
00178   }
00179 
00180   //The following code is for authentication (username/password)
00181   std::string resp_html;
00182   Arc::HTTPClientInfo redirect_info = info;
00183   do {
00184     std::cout<<"Code: "<<redirect_info.code<<"Reason: "<<redirect_info.reason<<"Size: "<<
00185     redirect_info.size<<"Type: "<<redirect_info.type<<"Set-Cookie: "<<*(redirect_info.cookies.begin())<<
00186     "Location: "<<redirect_info.location<<std::endl;
00187     if(redirect_info.code != 302) break;
00188     
00189     Arc::URL redirect_url(redirect_info.location);
00190     Arc::ClientHTTP redirect_client(cfg, redirect_url);
00191 
00192     Arc::PayloadRaw redirect_request;
00193     Arc::PayloadRawInterface *redirect_response = NULL;
00194 
00195     std::multimap<std::string, std::string> http_attributes;
00196     if(!(redirect_info.cookies.empty())) http_attributes.insert(std::pair<std::string,std::string>("Cookie",*(redirect_info.cookies.begin())));
00197 
00198     Arc::MCC_Status redirect_status = redirect_client.process("GET", http_attributes, 
00199                               &redirect_request, &redirect_info, &redirect_response);
00200     if (!redirect_response) {
00201       logger.msg(Arc::ERROR, "Request failed: No response");
00202       return -1;
00203     }
00204     if (!redirect_status) {
00205       logger.msg(Arc::ERROR, "Request failed: Error");
00206       return -1;
00207     }
00208     char* content = redirect_response->Content();
00209     if(content!=NULL) {
00210       resp_html.assign(redirect_response->Content());
00211       size_t pos = resp_html.find("j_username"); 
00212       if(pos!=std::string::npos) break;
00213     }
00214   } while(1);
00215 
00216   //Arc::URL redirect_url_final("https://idp.testshib.org:443/idp/Authn/UserPassword");
00217   Arc::URL redirect_url_final(info.location);
00218   Arc::ClientHTTP redirect_client_final(cfg, redirect_url_final);
00219   Arc::PayloadRaw redirect_request_final;
00220   //std::string login_html("j_username=myself&j_password=myself");
00221   //std::string login_html("j_username=root&j_password=aa1122");
00222   std::string login_html("j_username=staff&j_password=123456");
00223   redirect_request_final.Insert(login_html.c_str(),0,login_html.size());
00224   std::multimap<std::string, std::string> http_attributes;
00225   http_attributes.insert(std::pair<std::string,std::string>("Content-Type","application/x-www-form-urlencoded"));
00226   //Use the first cookie
00227   if(!(info.cookies.empty()))            
00228     http_attributes.insert(std::pair<std::string,std::string>("Cookie",*(info.cookies.begin()))); 
00229   Arc::PayloadRawInterface *redirect_response_final = NULL;
00230   Arc::HTTPClientInfo redirect_info_final;
00231   Arc::MCC_Status redirect_status_final = redirect_client_final.process("POST", http_attributes, 
00232         &redirect_request_final, &redirect_info_final, &redirect_response_final);
00233   if (!redirect_response_final) {
00234     logger.msg(Arc::ERROR, "Request failed: No response");
00235     return -1;
00236   }
00237   if (!redirect_status_final) {
00238     logger.msg(Arc::ERROR, "Request failed: Error");
00239     return -1;
00240   }
00241 
00242   std::cout<<"http code: "<<redirect_info_final.code<<std::endl;
00243 
00244   std::string html_body;
00245   for(int i = 0;;i++) {
00246     char* buf = redirect_response_final->Buffer(i);
00247     if(buf == NULL) break;
00248     html_body.append(redirect_response_final->Buffer(i), redirect_response_final->BufferSize(i));
00249     std::cout<<"Buffer: "<<buf<<std::endl;
00250   }
00251 
00252   std::string type = redirect_info_final.type;
00253   size_t pos1 = type.find(';');
00254   if (pos1 != std::string::npos)
00255     type = type.substr(0, pos1);
00256   if(type != "text/html")
00257     std::cerr<<"The html response from IdP is with wrong format"<<std::endl;
00258 
00259   //The authentication is finished and the html response with saml response is returned
00260  
00261   //--------------------------------------
00262   //SP: Get the b64 encoded saml response from Client-Agent, and decode/verify/decrypt it
00263   //-------------------------------------
00264   Arc::XMLNode html_node(html_body);
00265   std::string saml_resp = html_node["body"]["form"]["div"]["input"].Attribute("value");
00266   std::cout<<"SAML Response: "<<saml_resp<<std::endl;
00267 
00268   Arc::XMLNode node;
00269   //Decode the saml response (b64 format)
00270   Arc::BuildNodefromMsg(saml_resp, node);  
00271   std::string decoded_saml_resp;
00272   node.GetXML(decoded_saml_resp);
00273   std::cout<<"Decoded SAML Response: "<<decoded_saml_resp<<std::endl;
00274 
00275   Arc::init_xmlsec();
00276   //Verify the signature of saml response
00277   std::string idname = "ID";
00278   Arc::XMLSecNode sec_samlresp_nd(node);
00279   //Since the certificate from idp.testshib.org which signs the saml response is self-signed 
00280   //certificate, only check the signature here.
00281   if(sec_samlresp_nd.VerifyNode(idname,"", "", false)) {
00282     logger.msg(Arc::INFO, "Succeeded to verify the signature under <samlp:Response/>");
00283   }
00284   else {
00285     logger.msg(Arc::ERROR, "Failed to verify the signature under <samlp:Response/>");
00286   }
00287   
00288   //Decrypte the encrypted saml assertion in this saml response
00289   Arc::XMLNode assertion_nd = node["saml:EncryptedAssertion"];
00290   std::string saml_assertion;
00291   assertion_nd.GetXML(saml_assertion);
00292   std::cout<<"Encrypted saml assertion: "<<saml_assertion<<std::endl;
00293 
00294   Arc::XMLSecNode sec_assertion_nd(assertion_nd);
00295   Arc::XMLNode decrypted_assertion_nd; 
00296   bool r = sec_assertion_nd.DecryptNode(privkey_file, decrypted_assertion_nd);
00297   if(!r) { std::cout<<"Can not decrypted the EncryptedAssertion from saml response"<<std::endl; return 0; }
00298 
00299   std::string decrypted_saml_assertion;
00300   decrypted_assertion_nd.GetXML(decrypted_saml_assertion);
00301   std::cout<<"Decrypted SAML Assertion: "<<decrypted_saml_assertion<<std::endl;
00302 
00303   //Decrypted the <saml:EncryptedID> in the above saml assertion
00304   Arc::XMLNode nameid_nd = decrypted_assertion_nd["saml:Subject"]["saml:EncryptedID"];
00305   std::string nameid;
00306   nameid_nd.GetXML(nameid);
00307   std::cout<<"Encrypted name id: "<<nameid<<std::endl;
00308 
00309   Arc::XMLSecNode sec_nameid_nd(nameid_nd);
00310   Arc::XMLNode decrypted_nameid_nd;
00311   r = sec_nameid_nd.DecryptNode(privkey_file, decrypted_nameid_nd);
00312   if(!r) { std::cout<<"Can not decrypted the EncryptedID from saml assertion"<<std::endl; return 0; }
00313 
00314   std::string decrypted_nameid;
00315   decrypted_nameid_nd.GetXML(decrypted_nameid);
00316   std::cout<<"Decrypted SAML NameID: "<<decrypted_nameid<<std::endl;
00317 
00318 #if 0
00319   //-----------------------
00320   //SP: AttributeQuery
00321   //-----------------------
00322   //Compose <samlp:AttributeQuery/>
00323   std::string cert = "testcert.pem";
00324   std::string key = "testkey-nopass.pem";
00325   std::string cafile = "cacert.pem";
00326 
00327   ArcLib::Credential cred(cert, key, "", cafile);
00328   std::string local_dn = cred.GetDN();
00329 
00330   Arc::XMLNode attr_query(saml_ns, "samlp:AttributeQuery");
00331   std::string query_id = Arc::UUID();
00332   attr_query.NewAttribute("ID") = query_id;
00333   Arc::Time t;
00334   std::string current_time = t.str(Arc::UTCTime);
00335   attr_query.NewAttribute("IssueInstant") = current_time;
00336   attr_query.NewAttribute("Version") = std::string("2.0");
00337 
00338   Arc::XMLNode issuer = attr_query.NewChild("saml:Issuer");
00339   issuer = sp_name;
00340 
00341   //<saml:Subject/>
00342   Arc::XMLNode subject = attr_query.NewChild("saml:Subject");
00343   Arc::XMLNode name_id = subject.NewChild("saml:NameID");
00344   name_id.NewAttribute("Format")=std::string("urn:oasis:names:tc:SAML:2.0:nameid-format:transient");
00345   name_id = (std::string)decrypted_nameid_nd;
00346 
00347   //Add one or more <Attribute>s into AttributeQuery
00348 /*
00349   Arc::XMLNode attribute = attr_query.NewChild("saml:Attribute");
00350   attribute.NewAttribute("Name")=std::string("urn:oid:1.3.6.1.4.1.5923.1.1.1.1");
00351   attribute.NewAttribute("NameFormat")=std::string("urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
00352   attribute.NewAttribute("FriendlyName")=std::string("eduPersonAffiliation");
00353 */
00354 
00355   std::string str;
00356   attr_query.GetXML(str);
00357   std::cout<<"Attribute Query: "<<str<<std::endl;
00358 
00359   Arc::NS soap_ns;
00360   Arc::SOAPEnvelope envelope(soap_ns);
00361   envelope.NewChild(attr_query);
00362   Arc::PayloadSOAP attrqry_request(envelope);
00363  
00364   // Send request
00365 std::cout<<"---------------------------------------------------------------------------"<<std::endl;
00366   Arc::MCCConfig attrqry_cfg;
00367   if (!cert_file.empty())
00368     attrqry_cfg.AddCertificate(cert_file);
00369   if (!privkey_file.empty())
00370     attrqry_cfg.AddPrivateKey(privkey_file);
00371   //TestShib use another certificate for AttributeQuery
00372   std::string ca_file1 = "cacert_testshib_idp.pem"; 
00373   if (!ca_file1.empty())
00374     attrqry_cfg.AddCAFile(ca_file1);
00375 
00376   std::string attrqry_path("/idp/profile/SAML2/SOAP/AttributeQuery");
00377   //std::string service_url_str("https://squark.uio.no:8443");
00378   std::string service_url_str("https://idp.testshib.org:8443");
00379 
00380   Arc::URL service_url(service_url_str + attrqry_path);
00381   Arc::ClientSOAP attrqry_client(attrqry_cfg, service_url);
00382 
00383   Arc::PayloadSOAP *attrqry_response = NULL;
00384   Arc::MCC_Status attrqry_status = attrqry_client.process(&attrqry_request,&attrqry_response);
00385   if (!attrqry_response) {
00386     logger.msg(Arc::ERROR, "Request failed: No response");
00387     return -1;
00388   }
00389   if (!attrqry_status) {
00390     std::string tmp;
00391     attrqry_response->GetXML(tmp);
00392     std::cout<<"Response: "<<tmp<<std::endl;
00393     logger.msg(Arc::ERROR, "Request failed: Error");
00394     return -1;
00395   }
00396   attrqry_response->GetXML(str);
00397   std::cout<<"Response for Attribute Query: "<<str<<std::endl;
00398 std::cout<<"---------------------------------------------------------------------------"<<std::endl;
00399 
00400 
00401   // -------------------------------------------------------
00402   //   Comsume the saml response
00403   // -------------------------------------------------------
00404   //Consume the response from AA
00405   Arc::XMLNode saml_response;
00406   saml_response = (*attrqry_response).Body().Child(0);
00407 
00408   std::string resp_time = saml_response.Attribute("IssueInstant");
00409 
00410   //<samlp:Status/>
00411   std::string statuscode_value = saml_response["samlp:Status"]["samlp:StatusCode"];
00412   if(statuscode_value == "urn:oasis:names:tc:SAML:2.0:status:Success")
00413     logger.msg(Arc::INFO, "The StatusCode is Success");
00414   
00415   //<saml:Assertion/>
00416   Arc::XMLNode assertion = saml_response["saml:Assertion"];
00417 
00418   //We don't need to verify the saml signature, because the idp.testshib.org does not return 
00419   //saml response with signature
00420  
00421   //<saml:Condition/>, TODO: Condition checking
00422   Arc::XMLNode cond_nd = assertion["saml:Conditions"];
00423 
00424   //<saml:AttributeStatement/>
00425   Arc::XMLNode attr_statement = assertion["saml:AttributeStatement"];
00426 
00427   //<saml:Attribue/>
00428   Arc::XMLNode attr_nd;
00429   std::vector<std::string> attributes_value;
00430   for(int i=0;;i++) {
00431     attr_nd = attr_statement["saml:Attribute"][i];
00432     if(!attr_nd) break;
00433 
00434     std::string name = attr_nd.Attribute("Name");
00435     std::string nameformat = attr_nd.Attribute("NameFormat");
00436 
00437     for(int j=0;;j++) {
00438       Arc::XMLNode attr_value = attr_nd["saml:AttributeValue"][j];
00439       if(!attr_value) break;
00440       std::string str;
00441       str.append("Name=").append(name).append(" Value=").append(attr_value);
00442       attributes_value.push_back(str);
00443     }
00444   }
00445 
00446   for(int i=0; i<attributes_value.size(); i++) {
00447     std::cout<<"Attribute Value: "<<attributes_value[i]<<std::endl;
00448   }
00449 
00450   delete attrqry_response;
00451 
00452 #endif
00453 
00454   delete response;
00455   Arc::final_xmlsec();
00456   return 0;
00457 }