Skip to content

Commit 62094f1

Browse files
authored
Merge pull request #190 from CyberSource/feature/gp-mtls
pgp mtls
2 parents 7fb4752 + 5a30d63 commit 62094f1

File tree

7 files changed

+552
-3
lines changed

7 files changed

+552
-3
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"ext-mbstring": "*",
2121
"firebase/php-jwt": "^6.0.0",
2222
"monolog/monolog": ">=1.25.0",
23-
"web-token/jwt-framework": "^2.2.11|^3.3.5"
23+
"web-token/jwt-framework": "^2.2.11|^3.3.5",
24+
"singpolyma/openpgp-php": "0.7.0"
2425
},
2526
"require-dev": {
2627
"phpunit/phpunit": "9.6.15"

generator/cybersource_php_sdk_gen.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ git checkout ..\composer.json
3737
git checkout ..\lib\Api\OAuthApi.php
3838
git checkout ..\lib\Model\AccessTokenResponse.php
3939
git checkout ..\lib\Model\CreateAccessTokenRequest.php
40+
git checkout ..\lib\Api\BatchUploadApi.php
4041

4142
pause
4243

lib/Api/BatchUploadApi.php

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<?php
2+
3+
namespace CyberSource\Api;
4+
5+
use CyberSource\ApiException;
6+
use CyberSource\Utilities\PGP\BatchUpload\PgpEncryptionUtility;
7+
use CyberSource\Utilities\PGP\BatchUpload\MutualAuthUploadUtility;
8+
use CyberSource\Utilities\PGP\BatchUpload\BatchuploadUtility;
9+
use CyberSource\Logging\LogFactory;
10+
use CyberSource\Logging\LogConfiguration;
11+
12+
class BatchUploadApi
13+
{
14+
private static $logger = null;
15+
16+
/**
17+
* BatchUploadApi constructor.
18+
*
19+
* Example usage with custom log configuration:
20+
* ```php
21+
* $logConfig = new \CyberSource\Logging\LogConfiguration();
22+
* $logConfig->enableLogging(true);
23+
* $logConfig->setDebugLogFile(__DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "Log" . DIRECTORY_SEPARATOR . "debugTest.log");
24+
* $logConfig->setErrorLogFile(__DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "Log" . DIRECTORY_SEPARATOR . "errorTest.log");
25+
* $logConfig->setLogDateFormat("Y-m-d\TH:i:s");
26+
* $logConfig->setLogFormat("[%datetime%] [%level_name%] [%channel%] : %message%\n");
27+
* $logConfig->setLogMaxFiles(3);
28+
* $logConfig->setLogLevel("debug");
29+
* $logConfig->enableMasking(true);
30+
* $api = new \CyberSource\Api\BatchUploadApi($logConfig);
31+
* ```
32+
*
33+
* @param LogConfiguration|null $logConfig
34+
*/
35+
public function __construct($logConfig = null)
36+
{
37+
// If no log config provided, create one with logging disabled
38+
if ($logConfig === null) {
39+
$logConfig = new LogConfiguration();
40+
$logConfig->enableLogging(false);
41+
}
42+
// Use LogFactory to get a logger for this class
43+
self::$logger = (new LogFactory())->getLogger(
44+
\CyberSource\Utilities\Helpers\ClassHelper::getClassName(get_class($this)),
45+
$logConfig
46+
);
47+
}
48+
49+
/**
50+
* Uploads a batch file using mutual TLS authentication with a PKCS#12 (.p12/.pfx) client certificate file.
51+
*
52+
* @param string $inputFilePath Path to the file to be uploaded.
53+
* @param string $environmentHostname The environment hostname.
54+
* @param string $pgpEncryptionCertPath Path to the PGP encryption certificate.
55+
* @param string $clientCertP12FilePath Path to the PKCS#12 client certificate file (.p12 or .pfx).
56+
* @param string $clientCertP12Password Password for the PKCS#12 client certificate.
57+
* @param string|null $serverTrustCertPath Path to the server trust certificate(s) in PEM format. Optional.
58+
* @param bool $verify_ssl Whether to verify the server's SSL certificate. Optional. Set to false to disable verification (not recommended). Default is true.
59+
* @return array [responseBody, statusCode, headers]
60+
* @throws ApiException
61+
*/
62+
public function uploadBatchWithP12(
63+
$inputFilePath,
64+
$environmentHostname,
65+
$pgpEncryptionCertPath,
66+
$clientCertP12FilePath,
67+
$clientCertP12Password,
68+
$serverTrustCertPath = null,
69+
$verify_ssl = true
70+
) {
71+
if (self::$logger) {
72+
self::$logger->info("Starting batch upload with P12/PFX for file: $inputFilePath");
73+
if ($verify_ssl === false) {
74+
self::$logger->warning("SSL verification is DISABLED for this batch upload. This is insecure and should not be used in production.");
75+
}
76+
}
77+
BatchuploadUtility::validateBatchApiP12Inputs(
78+
$inputFilePath, $environmentHostname, $pgpEncryptionCertPath, $clientCertP12FilePath, $serverTrustCertPath
79+
);
80+
81+
$endpointUrl = $this->getEndpointUrl($environmentHostname, "/pts/v1/transaction-batch-upload");
82+
83+
$encryptedPgpBytes = PgpEncryptionUtility::encryptFileToBytes($inputFilePath, $pgpEncryptionCertPath);
84+
85+
$pgpFileName = basename($inputFilePath);
86+
if (empty($pgpFileName) || $pgpFileName === '.' || $pgpFileName === '..') {
87+
$pgpFileName = 'file.pgp';
88+
} else {
89+
$pgpFileName = pathinfo($pgpFileName, PATHINFO_FILENAME) . '.pgp';
90+
}
91+
92+
return MutualAuthUploadUtility::uploadWithP12(
93+
$encryptedPgpBytes,
94+
$endpointUrl,
95+
$pgpFileName,
96+
$clientCertP12FilePath,
97+
$clientCertP12Password,
98+
$serverTrustCertPath,
99+
$verify_ssl,
100+
self::$logger
101+
);
102+
}
103+
104+
/**
105+
* Uploads a batch file using mutual TLS authentication with client private key and certificate.
106+
*
107+
* @param string $inputFilePath Path to the file to be uploaded.
108+
* @param string $environmentHostname The environment hostname (e.g., api.cybersource.com).
109+
* @param string $pgpEncryptionCertPath Path to the PGP encryption certificate.
110+
* @param string $clientCertPath Path to the client certificate (PEM).
111+
* @param string $clientKeyPath Path to the client private key (PEM).
112+
* @param string|null $serverTrustCertPath Path to the server trust certificate(s) in PEM format. Optional.
113+
* @param string|null $clientKeyPassword Password for the client private key. Optional.
114+
* @param bool $verify_ssl Whether to verify the server's SSL certificate. Optional. Set to false to disable verification (not recommended). Default is true.
115+
* @return array [responseBody, statusCode, headers]
116+
* @throws ApiException
117+
*/
118+
public function uploadBatchWithKeyAndCert(
119+
$inputFilePath,
120+
$environmentHostname,
121+
$pgpEncryptionCertPath,
122+
$clientCertPath,
123+
$clientKeyPath,
124+
$serverTrustCertPath = null,
125+
$clientKeyPassword = null,
126+
$verify_ssl = true
127+
) {
128+
if (self::$logger) {
129+
self::$logger->info("Starting batch upload with client key/cert for file: $inputFilePath");
130+
if ($verify_ssl === false) {
131+
self::$logger->warning("SSL verification is DISABLED for this batch upload. This is insecure and should not be used in production.");
132+
}
133+
}
134+
135+
BatchuploadUtility::validateBatchApiKeysInputs($inputFilePath, $environmentHostname, $pgpEncryptionCertPath, $clientKeyPath, $clientCertPath, $serverTrustCertPath);
136+
137+
$endpointUrl = $this->getEndpointUrl($environmentHostname, "/pts/v1/transaction-batch-upload");
138+
139+
$encryptedPgpBytes = PgpEncryptionUtility::encryptFileToBytes($inputFilePath, $pgpEncryptionCertPath);
140+
141+
$pgpFileName = basename($inputFilePath);
142+
if (empty($pgpFileName) || $pgpFileName === '.' || $pgpFileName === '..') {
143+
$pgpFileName = 'file.pgp';
144+
} else {
145+
$pgpFileName = pathinfo($pgpFileName, PATHINFO_FILENAME) . '.pgp';
146+
}
147+
148+
return MutualAuthUploadUtility::uploadWithKeyAndCert(
149+
$encryptedPgpBytes,
150+
$endpointUrl,
151+
$pgpFileName,
152+
$clientCertPath,
153+
$clientKeyPath,
154+
$serverTrustCertPath,
155+
$clientKeyPassword,
156+
$verify_ssl,
157+
self::$logger
158+
);
159+
}
160+
161+
/**
162+
* Constructs the full endpoint URL for the given environment hostname and endpoint path.
163+
*
164+
* @param string $environmentHostname The environment hostname (with or without protocol prefix).
165+
* @param string $endpoint The endpoint path to append.
166+
* @return string The full endpoint URL.
167+
*/
168+
private function getEndpointUrl($environmentHostname, $endpoint)
169+
{
170+
$URL_PREFIX = 'https://';
171+
$baseUrl = (stripos(trim($environmentHostname), $URL_PREFIX) === 0)
172+
? trim($environmentHostname)
173+
: $URL_PREFIX . trim($environmentHostname);
174+
return $baseUrl . $endpoint;
175+
}
176+
177+
}

lib/Utilities/MultipartHelpers/MultipartHelper.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ public static function build_data_files($boundary, $formParams){
1010

1111
$delimiter = '-------------' . $boundary;
1212

13-
foreach ($formParams as $name => $content) {
13+
foreach ($formParams as $filename => $content) {
1414
$data .= "--" . $delimiter . $eol
15-
. 'Content-Disposition: form-data; name="' . $name . '"; filename="' . $name . '"' . $eol
15+
. 'Content-Disposition: form-data; name="' . $filename . '"; filename="' . $filename . '"' . $eol
1616
. 'Content-Transfer-Encoding: binary'.$eol
1717
;
1818

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace CyberSource\Utilities\PGP\BatchUpload;
4+
5+
class BatchuploadUtility
6+
{
7+
const MAX_FILE_SIZE_BYTES = 75 * 1024 * 1024; // 75MB
8+
9+
/**
10+
* Validates the input parameters for batch API using P12 client certificate.
11+
*
12+
* @param string $inputFile Path to the input CSV file for batch upload.
13+
* @param string $environmentHostname
14+
* @param string $pgpEncryptionCertPath Path to the PGP public key file (.asc).
15+
* @param string $clientCertP12FilePath Path to the client P12 certificate file.
16+
* @param string|null $serverTrustCertPath Path to the server trust certificate file (PEM, optional).
17+
* @throws \InvalidArgumentException
18+
*/
19+
public static function validateBatchApiP12Inputs($inputFile, $environmentHostname, $pgpEncryptionCertPath, $clientCertP12FilePath, $serverTrustCertPath = null)
20+
{
21+
self::validateInputFile($inputFile);
22+
if (empty(trim($environmentHostname))) {
23+
throw new \InvalidArgumentException('Environment Host Name for Batch Upload API cannot be null or empty.');
24+
}
25+
self::validatePathAndFile($pgpEncryptionCertPath, 'PGP Encryption Cert Path');
26+
self::validatePathAndFile($clientCertP12FilePath, 'Client Cert P12 File Path');
27+
if (!empty($serverTrustCertPath)) {
28+
self::validatePathAndFile($serverTrustCertPath, 'Server Trust Cert Path');
29+
}
30+
}
31+
32+
/**
33+
* Validates the input parameters for batch API using direct key and certificate file paths.
34+
*
35+
* @param string $inputFile Path to the input CSV file for batch upload.
36+
* @param string $environmentHostname
37+
* @param string $pgpPublicKeyPath Path to the PGP public key file (.asc).
38+
* @param string $clientPrivateKeyPath Path to the client private key file (PEM).
39+
* @param string $clientCertPath Path to the client certificate file (PEM).
40+
* @param string|null $serverTrustCertPath Path to the server trust certificate file (PEM, optional).
41+
* @throws \InvalidArgumentException
42+
*/
43+
public static function validateBatchApiKeysInputs($inputFile, $environmentHostname, $pgpPublicKeyPath, $clientPrivateKeyPath, $clientCertPath, $serverTrustCertPath = null)
44+
{
45+
self::validateInputFile($inputFile);
46+
if (empty(trim($environmentHostname))) {
47+
throw new \InvalidArgumentException('Environment Host Name for Batch Upload API cannot be null or empty.');
48+
}
49+
self::validatePathAndFile($pgpPublicKeyPath, 'PGP Public Key Path');
50+
self::validatePathAndFile($clientPrivateKeyPath, 'Client Private Key Path');
51+
self::validatePathAndFile($clientCertPath, 'Client Certificate Path');
52+
if (!empty($serverTrustCertPath)) {
53+
self::validatePathAndFile($serverTrustCertPath, 'Server Trust Certificate Path');
54+
}
55+
}
56+
57+
/**
58+
* Validates the input file for batch upload.
59+
* Checks for existence, file type (CSV), and maximum file size (75MB).
60+
*
61+
* @param string $inputFile Path to the input file to validate.
62+
* @throws \InvalidArgumentException
63+
*/
64+
public static function validateInputFile($inputFile)
65+
{
66+
if (empty($inputFile) || !file_exists($inputFile) || !is_file($inputFile)) {
67+
throw new \InvalidArgumentException("Input file is invalid or does not exist: $inputFile");
68+
}
69+
// Only CSV files are allowed for batch API
70+
if (strtolower(pathinfo($inputFile, PATHINFO_EXTENSION)) !== 'csv') {
71+
throw new \InvalidArgumentException("Only CSV file type is allowed: " . basename($inputFile));
72+
}
73+
// Max file size allowed is 75MB
74+
$fileSize = filesize($inputFile);
75+
if ($fileSize > self::MAX_FILE_SIZE_BYTES) {
76+
throw new \InvalidArgumentException("Input file size exceeds the maximum allowed size of 75MB: $fileSize");
77+
}
78+
}
79+
80+
/**
81+
* Validates that the given file path exists and is not empty.
82+
*
83+
* @param string $filePath The file path to validate.
84+
* @param string $pathType A description of the path type (e.g., "Input file").
85+
* @throws \InvalidArgumentException
86+
*/
87+
public static function validatePathAndFile($filePath, $pathType)
88+
{
89+
if (empty($filePath) || !is_string($filePath) || !trim($filePath)) {
90+
throw new \InvalidArgumentException("$pathType path cannot be null or empty");
91+
}
92+
93+
$normalizedPath = trim($filePath);
94+
95+
if (!file_exists($normalizedPath)) {
96+
throw new \InvalidArgumentException("$pathType does not exist: $normalizedPath");
97+
}
98+
if (!is_file($normalizedPath)) {
99+
throw new \InvalidArgumentException("$pathType does not have valid file: $normalizedPath");
100+
}
101+
if (!is_readable($normalizedPath)) {
102+
throw new \InvalidArgumentException("$pathType is not readable: $normalizedPath");
103+
}
104+
}
105+
106+
}

0 commit comments

Comments
 (0)