-
Notifications
You must be signed in to change notification settings - Fork 7.9k
SoapClient feature: Helper function to signed SOAP messages #8035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Apparently, this PR breaks the Windows build of ext/soap. Can you please have a look? |
The error seems to be:
I was able to fix this issue with my local Windows build by adding a following to config.w32 file:
Unfortunately I'm not that familiar with PHPs Windows build system, so I can't say was that the best possible solution. |
Ah, I see, thanks! I think instead of that, the missing functions should be declared in php_libxml2.def. |
Thanks! I added missing declarations to php_libxml2.def and removed CHECK_LIB from w32. |
Hi Jahyvari, +1 for WSS support. I think a) it creates SOAP header Imho I would create factory method which creates WSS SOAP header. Returned header could be used in $client = new SoapClient("my.wsdl");
$wssSoapHeader = $client->__createSoapWssHeader(array( // New function to SoapClient: reateSoapWssHeader
"digest_method" => SOAP_WSS_DIGEST_METHOD_SHA256, // New constants: SOAP_WSS_DIGEST_METHOD_SHA1, SOAP_WSS_DIGEST_METHOD_SHA256 & SOAP_WSS_DIGEST_METHOD_SHA512
"wss_version" => SOAP_WSS_VERSION_1_1, // New constants: SOAP_WSS_VERSION_1_0 & SOAP_WSS_VERSION_1_1
"add_timestamp" => true,
"timestamp_expires" => 300,
"x509_binsectoken" => file_get_contents("cert.der"),
"signfunc" => function($data) {
$signature = "";
openssl_sign(
$data,
$signature,
openssl_pkey_get_private("file://priv.key"),
OPENSSL_ALGO_SHA1
);
return $signature;
}
));
$client->__soapCall("SomeFunction", [$a, $b, $c], null, $wssSoapHeader); For direct soap method call $client->testFunction(array(
"Param1" => "foo",
"Param2" => "bar"
)); which require wss header existing __setSoapHeaders method could be used. If I am not wrong. |
Hi, thanks for feedback! __setSoapHeaders can be used for static WSS headers (plain username and password). But when there is a need for signed SOAP messages, then the signature varies between SOAP calls because it is calculated from SOAP Body (and timestamp if used). And therefore signature can be injected/calculated only after the SOAP Body has been constructed. |
Thanks for response and clarification. I am not familiar with WSS. |
If I may make a request: would it be possible for the final output to be canonicalized XML with no comments, to make life a bit easier on consumers? |
The example output above was manually pretty printed and commented for clarity purposes. Actual output format is the same as before = minified XML without comments. |
There has not been any recent activity in this PR. It will automatically be closed in 7 days if no further action is taken. |
Hi. Is there anything else what I should do for this PR, or could this go to be Reviewed? |
Apology for this not getting any review. I noticed this issue yesterday when going through 8.2 milestone and needed to look to some other things as well. I had a quick look but unfortunately it's too big to get properly reviewed for PHP 8.2 at this time (feature freeze is end of today). I will try to look more to the SOAP issues as the extension has been quite neglected. I will do my best to get this at least to 8.3. If you could rebase it in the meantime and get the pipeline green, that would be great. |
Modified files BAD_CAST & bailout Random id & tests +1 test +2 tests Newline +1 test Spaces -> tabs CRLF -> LF Added CHECK_LIB Missing declarations & sprintf -> slprintf (just in case) Fix test
Hi. Thanks for getting back to this. I rebased the PR. |
case SOAP_WSS_DIGEST_METHOD_SHA1: | ||
algo = zend_string_init("sha1", strlen("sha1"), 0); | ||
ops = php_hash_fetch_ops(algo); | ||
xmlSetProp(reference_dm, BAD_CAST("Algorithm"), BAD_CAST(SOAP_WSS_SHA1)); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
case SOAP_WSS_DIGEST_METHOD_SHA1: | |
algo = zend_string_init("sha1", strlen("sha1"), 0); | |
ops = php_hash_fetch_ops(algo); | |
xmlSetProp(reference_dm, BAD_CAST("Algorithm"), BAD_CAST(SOAP_WSS_SHA1)); | |
break; |
SHA1 ought to be dead. Let’s not add another use of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be needed for legacy servers so I would keep it - there's not such a big harm in it.
case SOAP_WSS_DIGEST_METHOD_SHA256: | ||
algo = zend_string_init("sha256", strlen("sha256"), 0); | ||
ops = php_hash_fetch_ops(algo); | ||
xmlSetProp(reference_dm, BAD_CAST("Algorithm"), BAD_CAST(SOAP_WSS_SHA256)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this fail?
case SOAP_WSS_DIGEST_METHOD_SHA512: | ||
algo = zend_string_init("sha512", strlen("sha512"), 0); | ||
ops = php_hash_fetch_ops(algo); | ||
xmlSetProp(reference_dm, BAD_CAST("Algorithm"), BAD_CAST(SOAP_WSS_SHA512)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
|
||
if (ops == NULL) { | ||
if (algo != NULL) { | ||
php_error_docref(NULL, E_WARNING, "invalid hashing algorithm, %s", ZSTR_VAL(algo)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should throw an exception.
php_error_docref(NULL, E_WARNING, "invalid hashing algorithm, %s", ZSTR_VAL(algo)); | ||
zend_string_release_ex(algo, 0); | ||
} else { | ||
php_error_docref(NULL, E_WARNING, "unknown hashing algorithm"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
ZSTR_VAL(digest)[ops->digest_size] = 0; | ||
|
||
zend_string *digest_base64 = php_base64_encode((unsigned char *)ZSTR_VAL(digest), ZSTR_LEN(digest)); | ||
xmlNewChild(reference, ns_ds, BAD_CAST("DigestValue"), BAD_CAST(ZSTR_VAL(digest_base64))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this fail (e.g. with out of memory)?
|
||
return 1; | ||
} else { | ||
php_error_docref(NULL, E_WARNING, "exclusive C14N failed, URI \"%s\"", ZSTR_VAL(source_uri)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should throw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to keep it consistent and the extension mainly uses warnings from the quick look so I would keep it that way. The algorithm above could be exception but this is failure so it might be better to be a warning for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that unless one sets an error handler, there is no way to know what happened.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SOAP extension seem to use E_ERROR's, E_WARNING's & SoapFault object to signal error events. So maybe some of these new warnings ("invalid hashing algorithm" & "unknown hashing algorithm") could be E_ERROR's instead and this one maybe a SoapFault?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E_ERROR and E_WARNING are bad. No idea about SoapFault.
ns_wsse = xmlNewNs(envelope, BAD_CAST(SOAP_WSS_SECEXT_1_1), BAD_CAST("wsse11")); | ||
} else { | ||
ns_wsse = xmlNewNs(envelope, BAD_CAST(SOAP_WSS_SECEXT_1_0), BAD_CAST("wsse")); | ||
} | ||
|
||
xmlNsPtr ns_ds = xmlNewNs(envelope, BAD_CAST(SOAP_WSS_XMLDSIG), BAD_CAST("ds")); | ||
xmlNsPtr ns_wsu = xmlNewNs(envelope, BAD_CAST(SOAP_WSS_UTILITY), BAD_CAST("wsu")); | ||
xmlNodePtr security = xmlNewChild(head, ns_wsse, BAD_CAST("Security"), NULL); | ||
xmlSetNsProp(body, ns_wsu, BAD_CAST("Id"), BAD_CAST(ZSTR_VAL(body_id))); | ||
|
||
if (soap_version == SOAP_1_1) { | ||
xmlSetProp(security, BAD_CAST(SOAP_1_1_ENV_NS_PREFIX":mustUnderstand"), BAD_CAST("1")); | ||
} else { | ||
xmlSetProp(security, BAD_CAST(SOAP_1_2_ENV_NS_PREFIX":mustUnderstand"), BAD_CAST("true")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can any of the xml*
calls fail due to out of memory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly if the libxml handles everything in memory, but then the same goes for the rest of the SOAP extension where xml*
calls are used.
@@ -522,6 +522,14 @@ PHP_MINIT_FUNCTION(soap) | |||
REGISTER_LONG_CONSTANT("SOAP_SSL_METHOD_SSLv3", SOAP_SSL_METHOD_SSLv3, CONST_CS | CONST_PERSISTENT); | |||
REGISTER_LONG_CONSTANT("SOAP_SSL_METHOD_SSLv23", SOAP_SSL_METHOD_SSLv23, CONST_CS | CONST_PERSISTENT); | |||
|
|||
/* SOAP WSS Constants */ | |||
REGISTER_LONG_CONSTANT("SOAP_WSS_DIGEST_METHOD_SHA1", SOAP_WSS_DIGEST_METHOD_SHA1, CONST_CS | CONST_PERSISTENT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SHA-1 should not be supported.
Hello Jahyvari, I have few suggestions. Your proposal is add __setWss method with plenty of params. What about create a class eg. class WssSigner
{
//...
//...
public function __construct(
string $digestMethod,
string $wssVersion,
bool $addTimestamp,
int $expires,
string x509BinSecToken,
callable $callback
) {
//...
}
// I am not sure about in/out typehints
public function sign(string $request): string
{
//...
}
}
and then pass the instance into soapClient:
$signer = new WssSigner(
SOAP_WSS_DIGEST_METHOD_SHA256,
SOAP_WSS_VERSION_1_1,
true,
300,
file_get_contents("cert.der"),
function($data) {
$signature = "";
openssl_sign(
$data,
$signature,
openssl_pkey_get_private("file://priv.key"),
OPENSSL_ALGO_SHA1
);
return $signature;
}
);
$client = new SoapClient("my.wsdl");
$client->__setWSS($signer); If I think my proposal has much better type hinting and as bonus it could be used standalone. |
Yep, I agree. That could be better for the type hinting. I think that I based the current function signature to SoapClient's constructor which takes the options as an array. |
Usage:
Generated SOAP message:
WSS specifications:
https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0.pdf
https://www.oasis-open.org/committees/download.php/16790/wss-v1.1-spec-os-SOAPMessageSecurity.pdf