该服务的文档说我需要使用WS-Security。 从他们的支持,我得到了一个p12文件,我应该使用它。
什么有用
我运行了SoapUI应用程序,配置它,添加了wsdl等,并获得了消息
<faultstring>These policy alternatives can not be satisfied: (...)</faultstring>
所以我发现我需要在请求中添加基本的Auth。我得到了正确的答案。
到目前为止我做了什么
找到一个生成与SoapUI几乎相同的xml的函数,所以我可能会使用它
我用它来称呼$x = callSecRequest('https://int.pz.gov.pl/pz-services/tpSigning?wsdl', 'addDocumentToSigning', $xmltosign, $pub, $priv, $msgback);
function callSecRequest($serviceUrl,$serviceMethod,$requestContent,$pubKey,$privKey,&$msg,$style='document',$use='literal')
{
require_once('libs/xmlseclibs-master/xmlseclibs.php');
require_once('libs/nuSoap/nusoap.php');
$client=new nusoap_client($serviceUrl);
$client->soap_defencoding = 'UTF-8';
$client->decode_utf8 = true;
$client->http_encoding = 'UTF-8';
$client->xml_encoding = 'UTF-8';
$client->setCurlOption(CURLOPT_SSLVERSION, 3);
$securityStart="<wsse:Security \n"
."xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"\n"
."xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">\n";
$securityEnd="</wsse:Security>\n";
$BinarySecurityToken="<wsse:BinarySecurityToken \n"
."wsu:Id=\"binarytoken\" \n"
."ValueType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3\" \n"
."EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">\n"
.implode("\n",array_slice(explode("\n",$pubKey),1,-1)) //a tu wycinamy napisy BEGIN CERTYFICATE i END CERTYFICATE ponieważ EPUAP oczekuje BinarySecurityToken jako base64 bez tych nagłówków
."</wsse:BinarySecurityToken>\n";
$timestamp = "<wsu:Timestamp>"
."<wsu:Created>".getTimestamp()."</wsu:Created>"
."<wsu:Expires>".getTimestamp(60*60)."</wsu:Expires>" //przesuniecie o godzinę
."</wsu:Timestamp>";
$sheaders = $securityStart.$BinarySecurityToken.$timestamp.$securityEnd; //komponujemy dodatek "security" który zostanie umieszczony w nagłówku SOAP:Header
$soapEnvelope = $client->serializeEnvelope($requestContent, $sheaders, array(), $style, $use); //tworzymy całą kopertę SOAP z zawartym tam nagłówkiem (jeszcze nie podpisanym i nie są przydzielone również referencje
//w tym momecie mamy już wstępnie utworzoną kopertę SOAP z zawartością SOAP:BODY jednak nie jest jeszcze w pełni utworzony nagłówek security ani nie jest tam wstawiony podpis które będzie wstawiony tam w elemencie "signature" brakuje również referecji wskazujących które elementy koperty SOAp są podpisane
//dlatego teraz zajmiemy się dalszym wstawianiem odpowiednich danych do elementu security.
$doc = new DOMDocument();
$doc->loadXML($soapEnvelope); //całą kopertę soap wczytujemy do drzewa dom i na tym drzewie będziemy już rzeźbić
//znajdujemy element body aby dodac do niego referencje poniewaz tylko body bedziemy podpisywali
$xp = new DOMXPath($doc);
$wsseNamespace = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
$wsuNamespace = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
//musimy zarejestrować właściwe namespaces z prefixami aby zapytania xpath umiały znaleść właściwe węzły
$xp->registerNamespace('soapenv', 'http://schemas.xmlsoap.org/soap/envelope/'); //namespace sekcji body
$xp->registerNamespace('wsse',$wsseNamespace); //namespace node security
$xp->registerNamespace('wsu',$wsuNamespace);
$xp->registerNamespace('ds',XMLSecurityDSig::XMLDSIGNS);
$bodyNode = $xp->query('//soapenv:Body')->item(0);
if($bodyNode) //znaleźliśmy element soap:body mozemy przypisać mu id i dodać do niego referencje w elemencie "Signature"->"SignedInfo"->"Reference[URI]"
{
//echo "jest BODY";
$objDSig = new XMLSecurityDSig(); //tworzymy obiekt za pomocą którego będziemy podpisywali kopertę SOAP
$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
$objDSig->addReference($bodyNode, XMLSecurityDSig::SHA1,NULL,array('prefix'=>'wsu','prefix_ns'=>$wsuNamespace)); //dodajemy referencje do body możemy tu użyć gotowej funkcji z biblioteki "xmlseclibs"
//teraz dodamy referencję do elementu Timestamp opisującego moment czasowy podpisu
$timestampNode = $xp->query('//wsu:Timestamp')->item(0);
if($timestampNode)
$objDSig->addReference($timestampNode, XMLSecurityDSig::SHA1,NULL,array('prefix'=>'wsu','prefix_ns'=>$wsuNamespace));
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'private'));
$objKey->loadKey($privKey); //tworzymy obiekt klucza prywatnego, w tym wypadku odczytujemy go z konfiguracji
$objDSig->sign($objKey); //podpisujemy kopertę SOAP kluczem prywatnym
//$objDSig->add509Cert($pub_key);\
//znajdujemy element do którego wstawimy sekcję podpisu w tym wypadku jest to Security
$securityNode = $xp->query('//wsse:Security')->item(0);
if($securityNode)
{
//echo "jest Security node";
//teraz musimy dodać właśnie wygenerowany podpis (na podstawie referecji do zewnętrzego obiektu) do całośći koperty SOAP
$timestampNode = $xp->query('//wsu:Timestamp')->item(0);
if($timestampNode)
$objDSig->insertSignature($securityNode,$timestampNode); //wstawiamy podpis do węzła "Security" ale ma być umieszczony przed węzłem "TimeStamp" (to ważne aby był przed nim)
//$objDSig->appendSignature($securityNode);
//Teraz ustawimy referencje do BinarySecurityToken niestety ponieważ funkcja addRefInternal jest prywatna nie możemy jej stąd wywołać i musimy dodanie atrybutu reference zrobić na piechotę
$binarySecurityTokenNode = $xp->query('//wsse:BinarySecurityToken')->item(0);
if($binarySecurityTokenNode)
{
$uri = XMLSecurityDSig::generate_GUID();
$binarySecurityTokenNode->setAttributeNS($wsuNamespace,'Id', $uri); //dodajemy atrybut Id do BinarySecurityToken aby potem użyć tego samego identyfikatora przy dodawaniu referencji w elemencie securityTokenReference
$keyInfoNode = $xp->query('//ds:KeyInfo')->item(0);
if(!$keyInfoNode)
{
$signatureNode = $xp->query('//ds:Signature')->item(0);
$keyInfoNode = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS,'ds:KeyInfo');
if($signatureNode)
$signatureNode->appendChild($keyInfoNode);
}
$securityTokenReferenceNode = $xp->query('//wsse:SecurityTokenReference')->item(0);
if($keyInfoNode && ! $securityTokenReferenceNode)
{
$securityTokenReferenceNode = $doc->createElementNS($wsseNamespace, 'wsse:SecurityTokenReference');
$referenceNode = $doc->createElementNS($wsseNamespace,'wsse:Reference');
$referenceNode->setAttribute("URI", '#'.$uri); //dodajemy teraz do referencji ten sam identyfikator co jest w BinarySecurityToken z elementu Security który właściwie to nie wchodzi do podpisu
$securityTokenReferenceNode->appendChild($referenceNode);
$keyInfoNode->appendChild($securityTokenReferenceNode);
}
}
$soapEnvelope = $doc->saveXML();
}//if($securityNode)
else
{
$msg .="Nie odnaleziono elementu wsse:Security";
}
}//if($bodyNode)
else
{
$msg .="Nie odnaleziono elementu soapenv:Body";
}
$result2 = $client->send($soapEnvelope, $serviceMethod); //wysyłamy kopertę SOAP
if($client->fault)
{
$msg .= "ERROR przy wysyłaniu metody ".$serviceMethod." :".$client->fault." ".var_export($result2,true);
}
else
{
if ($client->getError())
{
$msg .= "ERROR przy wysyłaniu metody ".$serviceMethod." :".$client->getError()." ".var_export($result2,true);
}
else
{
if(isset($result2['faultcode']))
{
$msg .= 'faultcode='.$result2['faultcode'].' faultstring='.$result2['faultstring'].' detail='.$result2['detail'];
return false;
}
return $result2;
}
}
return false;
}
问题
我收到错误:
SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure
。并且不知道可能是什么原因。我从他们那里得到了证书和私钥,所以不应该成为问题。
我尝试过使用SSL v 2,或者根本没有设置它,但这也没有成功。
我实际上尝试过现在在网络上找到的8-10个解决方案,而且我的想法已经不多了。这是我得到的最接近的,它至少会产生它应该的xml(或者至少非常接近它)