皇家邮政航运API - SOAP连接& pem /证书查询

时间:2015-10-13 11:11:32

标签: php ssl soap-client digital-certificate pem

我正在尝试设置皇家邮件运输API(如果有任何人有这方面的经验,我将非常感激,如果你能提供协助)。

在他们提供的文档中,我需要下载证书(.p12文件)&将其导入我的Windows机器 - 使用“证书导入向导”这非常简单。一旦进入"设置安全级别'我必须选择&这将在每次使用时使用密码请求权限。

Internet Explorer中的Internet Explorer'在“内容”选项卡中,我可以查看“证书”,并且可以清楚地看到此证书已导入且尚未过期。

下一步是提取证书组件,这里我必须使用OpenSSL运行以下三个命令来生成.pem文件。

$ openssl pkcs12 -in mycert.p12 -cacerts -nokeys -out cacert.pem
$ openssl pkcs12 -in mycert.p12 -clcerts -nokeys -out mycert.pem
$ openssl pkcs12 -in mycert.p12 -nocerts -nodes -out mykey.pem 

文档说明 cacert.pem文件可以由使用文件本身的应用程序直接引用,我相信我已经在我的PHP脚本中完成了,但是我不清楚我在哪里应该把另一个 mycert& mykey pem文件

文档说明了以下内容: -

  

在建立SSL网络连接时应用程序如何通过已发布的客户端SSL证书依赖于应用程序和环境,但它基本上需要同时访问" mycert.pem"和" mykey.pem"文件,或在某些情况下,包含证书和密钥的单个组合文件。

所以它没有说明应用程序如何使用这两个文件,目前我只是把它们放在与cacert.pem文件相同的目录中。

如果我尝试直接从浏览器访问网址https://api.royalmail.com/shipping/onboarding,它会要求我选择一个证书,我选择这个&然后在要求“授予或拒绝使用此密钥”的权限时输入正确的密码。一旦我输入正确的密码,就会出现以下页面 - 任何人都可以确认这是否意味着问题是在我的最后还是Royal Mail没有在他们的最终配置正确的。

enter image description here

除此之外,我用来将SOAP请求发送到Shipping API的实际PHP脚本不起作用(可能与上面的所有内容有关)。

在我的PHP脚本中,soapclient选项设置如下:

$soapclient_options['cache_wsdl'] = 'WSDL_CACHE_NONE'; 
$soapclient_options['local_cert'] = 'certs/cacert.pem';
$soapclient_options['passphrase'] = $api_certificate_passphrase;
$soapclient_options['trace'] = true;
$soapclient_options['ssl_method'] = 'SOAP_SSL_METHOD_SSLv3';
$soapclient_options['location'] = 'https://api.royalmail.com/shipping/onboarding';

$client = new SoapClient('SAPI/ShippingAPI_V2_0_8.wsdl', $soapclient_options);
$client->__setLocation($soapclient_options['location']);

当我运行PHP脚本时(这与Royal Mail使用我自己的个人API登录详细信息提供的代码基本相同)我在浏览器中收到以下消息:

Could not connect to host 
REQUEST: email@yoursite.co.ukAPI rngfJ+4dt4Gt855a5pr6u38i3B4= ODcwMTE5Nzc3 2015-10-13T11:02:20Z 2015-10-13T11:02:201.00526348001DeliveryDSD12015-10-13bobSS23, Some AvenueLondonE10g1000000

显然,由于某些未知原因,这无法连接到主机,后者只是发送的请求。 PHP脚本的其余部分与他们发送给我的皇家邮政脚本完全相同。已确认已被他人使用且工作正常。

我在WAMP环境中工作,尽管最终的代码将在Linux环境中。 任何人都可以提供帮助我真的很困惑&皇家邮政本身尚未能提供任何可靠的技术支持。

更新

这是浏览器中显示的完整错误消息(出于安全目的,我已更改了电子邮件地址)

Invalid Request REQUEST: myemail@company.co.ukAPI dgCW98Vqw3ladYgPPpNialODhvI= MTMzMjE1NjM4 2015-10-13T13:25:30Z 2015-10-13T13:25:302.00526348001DeliveryDSD12015-10-13Jon DoeSS23, Some RoadLondonE10g1000000
  

我已将两个pem文件合并到一个名为' bundle.pem'的文件中。 &安培;在' local_cert'中引用了这个SoapClient&的变量BINGO现在正在连接。现在更长时间会显示无法连接,但会显示“无效请求”'相反,所以至少现在这是连接并给我一个不同的错误。

我的整个PHP脚本如下:

<?php

ini_set('default_socket_timeout', 120);
ini_set('soap.wsdl_cache_enabled',1);
ini_set('soap.wsdl_cache_ttl',1);

$api_password = "xxxxxxxxxxxxxx!";
$api_username = "xxxxxxxxx@xxxxxxxxx.co.ukAPI";
$api_application_id = "xxxxxxxxxxxx";
$api_service_type = "D";
$api_service_code = "SD1";
$api_service_format = "";
$api_certificate_passphrase = 'xxxxxxxxxx';
$api_service_enhancements = "";

$data = new ArrayObject();
$data->order_tracking_id = "";
$data->shipping_name = "Jon Doe";
$data->shipping_company = "SS";
$data->shipping_address1 = "23, Some Road"; 
$data->shipping_address2 = "";
$data->shipping_town = "London";
$data->shipping_postcode = "E1";
$data->order_tracking_boxes = "0";
$data->order_tracking_weight = "1000";    

$time = gmdate('Y-m-d\TH:i:s');
$created = gmdate('Y-m-d\TH:i:s\Z');
$nonce = mt_rand();
$nonce_date_pwd = pack("A*",$nonce) . pack("A*",$created) . pack("H*", sha1($api_password));
$passwordDigest = base64_encode(pack('H*',sha1($nonce_date_pwd)));
$ENCODEDNONCE = base64_encode($nonce);


$soapclient_options = array(); 
$soapclient_options['cache_wsdl'] = 'WSDL_CACHE_NONE'; 
$soapclient_options['local_cert'] = 'royalmail/cert/bundle.pem';
$soapclient_options['passphrase'] = $api_certificate_passphrase;
$soapclient_options['trace'] = true;
$soapclient_options['ssl_method'] = 'SOAP_SSL_METHOD_SSLv3';
$soapclient_options['exceptions'] = true;
$soapclient_options['location'] = 'https://api.royalmail.com/shipping/onboarding';

//launch soap client
$client = new SoapClient('royalmail/ShippingAPI_V2_0_8.wsdl', $soapclient_options);
$client->__setLocation($soapclient_options['location']);

//headers needed for royal mail
$HeaderObjectXML  = '<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                      xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
           <wsse:UsernameToken wsu:Id="UsernameToken-000">
              <wsse:Username>'.$api_username.'</wsse:Username>
              <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$passwordDigest.'</wsse:Password>
              <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.$ENCODEDNONCE.'</wsse:Nonce>
              <wsu:Created>'.$created.'</wsu:Created>
           </wsse:UsernameToken>
       </wsse:Security>';

//push the header into soap
$HeaderObject = new SoapVar( $HeaderObjectXML, XSD_ANYXML );

//push soap header
$header = new SoapHeader( 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', 'Security', $HeaderObject );
$client->__setSoapHeaders($header);

//build the request
$request = array(
    'integrationHeader' => array(
        'dateTime' => $time,
        'version' => '1.0',
        'identification' => array(
            'applicationId' => $api_application_id,
            'transactionId' => $data->order_tracking_id
        )
    ),
    'requestedShipment' => array(
                                'shipmentType' => array('code' => 'Delivery'),
                                'serviceOccurence' => '1',
                                'serviceType' => array('code' => $api_service_type),
                                'serviceOffering' => array('serviceOfferingCode' => array('code' => $api_service_code)),
                                'serviceFormat' => array('serviceFormatCode' => array('code' => $api_service_format)),
                                'shippingDate' => date('Y-m-d'),
                                'recipientContact' => array('name' => $data->shipping_name, 'complementaryName' => $data->shipping_company),
                                'recipientAddress' => array('addressLine1' => $data->shipping_address1,  'addressLine2' => $data->shipping_address2, 'postTown' => $data->shipping_town, 'postcode' => $data->shipping_postcode),
                                'items' => array('item' => array(
                                            'numberOfItems' => $data->order_tracking_boxes,
                                            'weight' => array( 'unitOfMeasure' => array('unitOfMeasureCode' => array('code' => 'g')), 'value' => ($data->order_tracking_weight*1000) //weight of each individual item
                                                             )
                                                                )
                                                )
                                )               
);


//if any enhancements, add it into the array
if($api_service_enhancements != "") {
    $request['requestedShipment']['serviceEnhancements'] = array('enhancementType' => array('serviceEnhancementCode' => array('code' => $api_service_enhancements)));
}

//try make the call
try { 
    $response = $client->__soapCall( 'createShipment', array($request), array('soapaction' => 'https://api.royalmail.com/shipping/onboarding') );
} catch (Exception $e) {
    //catch the error message and echo the last request for debug
    echo $e->getMessage(); 
    echo " REQUEST:\n" . $client->__getLastRequest() . "\n";
    die;
}

//check for any errors
if(isset($response->integrationFooter->errors)) { 
    $build = "";

    //check it wasn't a single error message
    if(isset($response->integrationFooter->errors->error->errorCode)) { 
        $build .= $output_error->errorCode.": ".$output_error->errorDescription."<br/>"; 
    } else {
        //loop out each error message, throw exception will be added ehre
        foreach($response->integrationFooter->errors->error as $output_error) { 
            $build .= $output_error->errorCode.": ".$output_error->errorDescription."<br/>";
        }
    }

    echo $build; die;

}

print_r($response);

echo "REQUEST:\n" . $client->__getLastRequest() . "\n";
die;            
?>

为了更加清晰,我在$ demand变量到达try / catch块之前添加了$ request变量的转储(注意这有点长)。

Array
(
    [integrationHeader] => Array
        (
        [dateTime] => 2015-10-13T13:34:44
        [version] => 1.0
        [identification] => Array
            (
                [applicationId] => 0526348001
                [transactionId] => 
            )

    )

[requestedShipment] => Array
    (
        [shipmentType] => Array
            (
                [code] => Delivery
            )

        [serviceOccurence] => 1
        [serviceType] => Array
            (
                [code] => D
            )

        [serviceOffering] => Array
            (
                [serviceOfferingCode] => Array
                    (
                        [code] => SD1
                    )

            )

        [serviceFormat] => Array
            (
                [serviceFormatCode] => Array
                    (
                        [code] => 
                    )

            )

        [shippingDate] => 2015-10-13
        [recipientContact] => Array
            (
                [name] => Jon Doe
                [complementaryName] => SS
            )

        [recipientAddress] => Array
            (
                [addressLine1] => 23, Some Road
                [addressLine2] => 
                [postTown] => London
                [postcode] => E1
            )

        [items] => Array
            (
                [item] => Array
                    (
                        [numberOfItems] => 0
                        [weight] => Array
                            (
                                [unitOfMeasure] => Array
                                    (
                                        [unitOfMeasureCode] => Array
                                            (
                                                [code] => g
                                            )

                                    )

                                [value] => 1000000
                            )

                    )

            )

    )

2 个答案:

答案 0 :(得分:5)

虽然您已经提到过您已经完成了大部分步骤,但无论如何我都会详细介绍它们以完成说明

将CA安装到服务器

  • 将p12文件上传到服务器(例如/root/Desktop
  • 运行以下3个命令进行安装
openssl pkcs12 -in mycert.p12 -cacerts -nokeys -out cacert.pem
openssl pkcs12 -in mycert.p12 -clcerts -nokeys -out mycert.pem
openssl pkcs12 -in mycert.p12 -nocerts -nodes -out mykey.pem 
  • 现在将*.pem个文件复制到/etc/ssl/certs
    • 我喜欢在这里创建一个子目录(mkdir royalmail
    • 并移动*.pem个文件(mv *.pem /etc/ssl/certs/certificates
  • 现在创建一个新文件并将mycert.pemmykey.pem的内容复制到其中(仅从-----BEGIN .... -------到EOF)

您应该拥有以下文件;

enter image description here

使用SOAPClient

现在证书已安装,我们现在可以测试连接(假设您的证书在/etc/pki/tls/certs/certificates/royalmail/shippingv2

如果你跑

wget https://api.royalmail.com/shipping/onboarding --private-key=/etc/ssl/certs/certificates/royalmail/shippingv2/rm_bundle.pem --private-key-type=PEM

您应该能够连接到端口443(虽然OpenSSL中可能存在握手失败 - 至少我刚刚在我们的暂存环境中进行测试。)

我们现在可以使用WSDL的本地副本实例化SoapClient并在$options参数中指定本地证书。

$objSoapClient = new \SoapClient('lib/wsdl/royalmail/shipping/ShippingAPI_V2_0_8.wsdl', array(
    'soap_version' => SOAP_1_1,
    'trace' => 1,
    'uri' => 'http://www.royalmailgroup.com/api/ship/V2',
    'location' => 'https://api.royalmail.com/shipping/onboarding',
    'local_cert' => '/etc/ssl/certs/certificates/royalmail/shippingv2/rm_bundle.pem',
    'passphrase' => '', //Your passphrase when doing step 1
    'ssl_method' => 'SOAP_SSL_METHOD_TLS',
    'exceptions' => 1,
    'trace' => 1
));

验证

在您的文档中,您应该找到一个名为rm_password_digest.php的文件或类似文件,其中详细说明了如何创建身份验证标头。

/* The value below should be changed to your password.  If you store the password  */  
/* as hashed in your database, you will need to change the code below to remove hashing */

$password = 'just_my_royalmail_api_password';

/* CREATIONDATE - The timestamp. The computer must be on correct time or the server you are
* connecting may reject the password digest for security.
*/
$CREATIONDATE = gmdate('Y-m-d\TH:i:s\Z');

/* NONCE - A random word. The use of rand() may repeat the word if the server is
* very loaded.
*/
$nonce = mt_rand();

/* PASSWORDDIGEST This is the way to create the password digest. As per OASIS standard
*  digest = base64_encode(Sha1(nonce + creationdate + password)
*  however note that we use a SHA1(password) instead of the password above
*/
$nonce_date_pwd = pack("A*",$nonce) . pack("A*",$CREATIONDATE) . pack("H*", sha1($password));
$PASSWORDDIGEST = base64_encode(
pack('H*', sha1($nonce_date_pwd)));

/* ENCODEDNONCE - Now encode the nonce for security header */

$ENCODEDNONCE = base64_encode($nonce);

/* Now Print all the values - so we can use it for testing with tools like soapui */

print "WS Security Header elements \n";
print "--------------------------- \n";
print 'Nonce = ' . $nonce;
print "\n";
print 'PASSWORDDIGEST= ' . $PASSWORDDIGEST;
print "\n";
print 'ENCODEDNONCE= ' . $ENCODEDNONCE;
print "\n";
print "CREATIONDATE= " . $CREATIONDATE;

这将帮助您在SOAPHeader

中构建以下内容
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <wsse:UsernameToken wsu:Id="UsernameToken-0000">
        <wsse:Username>[...]</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">[...]</wsse:Password>
        <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">[...]</wsse:Nonce>
        <wsu:Created>[...]</wsu:Created>
    </wsse:UsernameToken>
</wsse:Security>

答案 1 :(得分:4)

首先,直接访问https://api.royalmail.com/shipping/onboarding将无效,因为它只能通过API访问。

使用Royal Mail,您是否拥有所有CDM文件和WSDL文件?确保CDM文件与WSDL文件位于同一目录中。

这是我在使用API​​时所做的事情;

$client = new SoapClient("/royalmail/ShippingAPI_V2_0_8.wsdl", array(
                                                    'trace' => 1,
                                                    'location'   => $location, //https://api.royalmail.com/shipping
                                                    'soap_version' => SOAP_1_1,
                                                    'local_cert' => '/royalmail/cert/cert.pem',
                                                    'passphrase' => 'xxx',
                                                    'exceptions' => true
                                                ));

然后,当实际建立连接时,我做了类似的事情:

    $password = 'xxx';

    $date = gmdate('Y-m-d\TH:i:s\Z');
    $nonce = mt_rand();
    $nonce_date_pwd = pack("A*",$nonce) . pack("A*",$date) . pack("H*", sha1($password));
    $encoded_password = base64_encode(pack('H*',sha1($nonce_date_pwd)));
    $encoded_nonce = base64_encode($nonce);

    $HeaderObjectXML  = '<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                              xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                   <wsse:UsernameToken wsu:Id="UsernameToken-0000">
                      <wsse:Username>Username</wsse:Username>
                      <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $encoded_password . '</wsse:Password>
                      <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' . $encoded_nonce . '</wsse:Nonce>
                      <wsu:Created>'.$date.'</wsu:Created>
                   </wsse:UsernameToken>
               </wsse:Security>';

    $HeaderObject = new SoapVar( $HeaderObjectXML, XSD_ANYXML );

    $header = new SoapHeader( 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', 'Security', $HeaderObject );
    $client->__setSoapHeaders( $header );

    $request = array('the shipment request');

    try {
        $client->__soapCall( 'createShipment', array($request) );
    }
    catch (SoapFault $soapFault) {
       print_r($soapFault);
    }

我希望这会有所帮助。

修改

尝试此请求

如果我没记错的话,我认为你需要使用版本2进行该服务发生。另外,只是...格式化。使调试更容易。

$request = Array(

    'integrationHeader' => array(
        'dateTime' => date('Y-m-d\TH:i:s'),
        'version' => '2',
        'identification' => array(
            'applicationId' => $api_application_id,
            'transactionId' => $data->order_tracking_id
        ),

    ),

    'requestedShipment' => array(   
        'shipmentType' => array(
            'code' => 'Delivery'
        ),
    'serviceOccurrence' => 1,
        'serviceType' => array(
            'code' => $api_service_type
        ),
        'serviceOffering' => array(
            'serviceOfferingCode' => array(
                'code' => $api_service_code
            )
        ),
        'serviceFormat' => array(
            'serviceFormatCode' => array(
                'code' => $api_service_format
            )
        ),
        'shippingDate' => gmdate('Y-m-d'),
        'recipientContact' => array(
            'name' => $data->shipping_name,
            'complementaryName' => $data->shipping_company
        ),
        'recipientAddress' => array(
            'addressLine1' => $data->shipping_address1,
            'addressLine2' => $data->shipping_address2,
            'postTown' => $data->shipping_town,
            'postcode' => $data->shipping_postcode
        ),
        'items' => array(
            'item' => array(
                'numberOfItems' => $data->order_tracking_boxes,
                'weight' => array(
                    'unitOfMeasure' => array(
                        'unitOfMeasureCode' => array(
                            'code' => 'g'
                        )
                    ),
                    'value' => ($data->order_tracking_weight*1000)
                )
            )
        )

    )
);