Skip to content

Commit

Permalink
Cleanup, improve crypto and hash functions
Browse files Browse the repository at this point in the history
  • Loading branch information
olssonm committed Aug 9, 2024
1 parent 14effbb commit 377071f
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 53 deletions.
3 changes: 2 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ parameters:
level: 8
paths:
- src
checkGenericClassInNonGenericObjectType: false
ignoreErrors:
- identifier: missingType.generics
7 changes: 5 additions & 2 deletions src/Api/AbstractResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ abstract class AbstractResource
{
protected ClientInterface $client;

protected ?Client $swish;
protected Client $swish;

public function __construct(ClientInterface $client, ?Client $swish = null)
{
$this->client = $client;
$this->swish = $swish;

if ($swish) {
$this->swish = $swish;
}
}

/**
Expand Down
12 changes: 6 additions & 6 deletions src/Api/Payouts.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Olssonm\Swish\Payout;
use Olssonm\Swish\PayoutResult;
use Olssonm\Swish\Util\Crypto;
use Olssonm\Swish\Util\Hash;
use Olssonm\Swish\Util\Id;

Expand All @@ -29,19 +30,18 @@ public function get($payment): Payout
/**
* Create a payment.
*
* @param Payout $payment
* @return PaymentResult
* @param Payout $payout
* @return PayoutResult
*/
public function create($payout): PayoutResult
{
$hash = Hash::make(json_encode($payout));
$signature = Hash::sign($hash, $this->swish->getCertificate()->getSigningCertificate());
$signature = Crypto::hashAndSign($payout, $this->swish->getCertificate()->getSigningCertificate());

$response = $this->request('POST', 'v1/payouts', [], (string) json_encode(
[
'payload' => $payout,
'callbackUrl' => 'https://webhook.site/9b1854f3-afec-4e1c-9f31-0df81d2423d3',
'signature' => base64_encode($signature),
'callbackUrl' => $payout->callbackUrl,
'signature' => $signature,
]
));

Expand Down
20 changes: 12 additions & 8 deletions src/Certificate.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ public function __construct(
?string $passphrase = null,
bool|string $rootPath = true,
?string $signingPath = null
)
{
) {
$this->client = $clientPath;
$this->passphrase = $passphrase;
$this->root = $rootPath;
Expand All @@ -54,7 +53,7 @@ public function getRootCertificate(): bool|string
}

/**
* @return bool|string
* @return null|string
*/
public function getSigningCertificate(): null|string
{
Expand All @@ -67,13 +66,18 @@ public function getSigningCertificate(): null|string
public function getSerial(): string
{
try {
$content = file_get_contents($this->client);
$details = openssl_x509_read($content);
$results = openssl_x509_parse($details)['serialNumberHex'];
$content = file_get_contents($this->client); // @phpstan-ignore argument.type
$details = openssl_x509_read($content); // @phpstan-ignore argument.type
$results = openssl_x509_parse($details); // @phpstan-ignore argument.type
$serial = $results['serialNumberHex']; // @phpstan-ignore offsetAccess.nonOffsetAccessible
} catch (\Throwable $th) {
throw new CertificateDecodingException('Could not parse and retrieve the serial number for the certificate. Please check your path and passphrase.', 0, $th);
throw new CertificateDecodingException(
'Could notretrieve the serial number for the certificate. Please check your path and passphrase.',
0,
$th
);
}

return $results;
return $serial;
}
}
16 changes: 11 additions & 5 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Client
{
protected string $endpoint;

protected Certificate $certificate;
protected ?Certificate $certificate;

/**
* @var array<mixed>
Expand Down Expand Up @@ -80,9 +80,9 @@ public function getHistory(): array
/**
* Return the clients call-history
*
* @return Certificate
* @return ?Certificate
*/
public function getCertificate(): Certificate
public function getCertificate(): ?Certificate
{
return $this->certificate;
}
Expand All @@ -94,9 +94,15 @@ public function __call(string $method, array $args): mixed
{
if (
!is_object($args[0]) ||
((get_class($args[0]) != Payment::class) && (get_class($args[0]) != Refund::class)&& (get_class($args[0]) != Payout::class))
(
(get_class($args[0]) != Payment::class) &&
(get_class($args[0]) != Refund::class) &&
(get_class($args[0]) != Payout::class)
)
) {
throw new InvalidArgumentException('Only Payment-, Payous- and Refund-objects are allowed as first argument');
throw new InvalidArgumentException(
'Only Payment-, Payous- and Refund-objects are allowed as first argument'
);
}

switch (get_class($args[0])) {
Expand Down
1 change: 0 additions & 1 deletion src/Exceptions/CertificateDecodingException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@

class CertificateDecodingException extends \Exception
{

}
29 changes: 27 additions & 2 deletions src/Payout.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use Olssonm\Swish\Util\Uuid;

use function PHPUnit\Framework\objectHasAttribute;

/**
* @property string $payoutInstructionUUID
* @property string $payerPaymentReference
Expand All @@ -24,16 +26,39 @@
*/
class Payout extends Resource
{
public string $callbackUrl = '';

/**
* @param array<string, mixed> $attributes
*/
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->payoutInstructionUUID = $this->payoutInstructionUUID ?? Uuid::make();
foreach ($attributes as $key => $value) {
$this->{$key} = $value;
}

// Assume some default details
$this->payoutInstructionUUID = $this->payoutInstructionUUID ?? Uuid::make();
$this->currency = $this->currency ?? 'SEK';
$this->payoutType = $this->payoutType ?? 'PAYOUT';
}

public function __get(string $key): mixed
{
if (property_exists($this, $key)) {
return $this->{$key};
}

return parent::__get($key);
}

public function __set(string $key, mixed $value)
{
if (property_exists($this, $key)) {
$this->{$key} = $value;
return;
}

parent::__set($key, $value);
}
}
70 changes: 70 additions & 0 deletions src/Util/Crypto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Olssonm\Swish\Util;

use ArrayAccess;
use Olssonm\Swish\Exceptions\CertificateDecodingException;

class Crypto
{
/**
* Create a sha512 hash of the payload.
*
* @param string $payload
* @return string
*/
public static function hash(string $payload): string
{
$bytes = mb_convert_encoding($payload, 'UTF-8');
$hash = hash('sha512', $bytes, true);
return $hash;
}

/**
* Sign a hash with a certificate.
*
* @param string $hash
* @param string $certificate
* @return string
* @throws CertificateDecodingException
*/
public static function sign(string $hash, string $certificate): string
{
$signature = null;

// Load your private key
$key = file_get_contents($certificate);

if (!$key) {
throw new CertificateDecodingException('Failed to load certificate');
}

$id = openssl_get_privatekey($key);

// Sign the hash
openssl_sign($hash, $signature, $id, OPENSSL_ALGO_SHA512); // @phpstan-ignore argument.type

return $signature;
}

/**
* Hash and sign a payload.
*
* @param ArrayAccess $payload
* @param ?string $certificate
* @return string
*/
public static function hashAndSign(ArrayAccess $payload, ?string $certificate): string
{
$data = json_encode($payload);

if (!$data || !$certificate) {
throw new CertificateDecodingException('Failed to encode payload');
}

$hash = self::hash($data);
$signature = self::sign($hash, $certificate);

return base64_encode($signature);
}
}
27 changes: 0 additions & 27 deletions src/Util/Hash.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/Util/Time.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class Time
{
public static function make($timezone = 'Europe/Stockholm'): string
public static function make(string $timezone = 'Europe/Stockholm'): string
{
return Carbon::now($timezone)->toIso8601String();
}
Expand Down
2 changes: 2 additions & 0 deletions tests/Payout.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@
'currency' => 'SEK',
'payoutType' => 'PAYOUT',
'message' => 'Test',
'callbackUrl' => 'https://example.com/callback',
'instructionDate' => Time::make(),
]);

$client = get_real_client($certificate);

$client->create($payout);
Expand Down

0 comments on commit 377071f

Please sign in to comment.