Sunrise SMD updates

This commit is contained in:
Pinga 2025-05-07 10:12:42 +03:00
parent 27246ff77a
commit 72b0180f3f
5 changed files with 102 additions and 45 deletions

View file

@ -5,9 +5,6 @@ namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Selective\XmlDSig\PublicKeyStore;
use Selective\XmlDSig\CryptoVerifier;
use Selective\XmlDSig\XmlSignatureVerifier;
use League\ISO3166\ISO3166; use League\ISO3166\ISO3166;
class ApplicationsController extends Controller class ApplicationsController extends Controller
@ -65,7 +62,6 @@ class ApplicationsController extends Controller
$phaseType = $data['phaseType'] ?? null; $phaseType = $data['phaseType'] ?? null;
$phaseName = $data['phaseName'] ?? null; $phaseName = $data['phaseName'] ?? null;
$smd = $data['smd'] ?? null;
$nameservers = !empty($data['nameserver']) ? $data['nameserver'] : null; $nameservers = !empty($data['nameserver']) ? $data['nameserver'] : null;
$nameserver_ipv4 = !empty($data['nameserver_ipv4']) ? $data['nameserver_ipv4'] : null; $nameserver_ipv4 = !empty($data['nameserver_ipv4']) ? $data['nameserver_ipv4'] : null;
@ -74,6 +70,23 @@ class ApplicationsController extends Controller
$authInfo = $data['authInfo'] ?? null; $authInfo = $data['authInfo'] ?? null;
$invalid_domain = validate_label($domainName, $db); $invalid_domain = validate_label($domainName, $db);
$uploadedFiles = $request->getUploadedFiles();
$smdFile = $uploadedFiles['smd'] ?? null;
$smd = null;
if ($smdFile && $smdFile->getError() === UPLOAD_ERR_OK) {
$smd = $smdFile->getStream()->getContents();
$smd_filename = $smdFile->getClientFilename();
if (pathinfo($smd_filename, PATHINFO_EXTENSION) !== 'smd') {
$this->container->get('flash')->addMessage('error', 'Only .smd files are allowed');
return $response->withHeader('Location', '/application/create')->withStatus(302);
}
} else {
$this->container->get('flash')->addMessage('error', 'SMD file upload failed');
return $response->withHeader('Location', '/application/create')->withStatus(302);
}
if ($phaseType === 'custom' && empty($phaseName)) { if ($phaseType === 'custom' && empty($phaseName)) {
$this->container->get('flash')->addMessage('error', 'Please provide a phase name when selecting the "Custom" phase type.'); $this->container->get('flash')->addMessage('error', 'Please provide a phase name when selecting the "Custom" phase type.');
return $response->withHeader('Location', '/application/create')->withStatus(302); return $response->withHeader('Location', '/application/create')->withStatus(302);
@ -172,10 +185,17 @@ class ApplicationsController extends Controller
$xpath = new \DOMXPath($domDocument); $xpath = new \DOMXPath($domDocument);
$xpath->registerNamespace('smd', 'urn:ietf:params:xml:ns:signedMark-1.0'); $xpath->registerNamespace('smd', 'urn:ietf:params:xml:ns:signedMark-1.0');
$xpath->registerNamespace('mark', 'urn:ietf:params:xml:ns:mark-1.0'); $xpath->registerNamespace('mark', 'urn:ietf:params:xml:ns:mark-1.0');
$xpath->registerNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
$certNode = $xpath->evaluate('string(//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate)');
$certBase64 = preg_replace('/\s+/', '', $certNode);
$certPem = "-----BEGIN CERTIFICATE-----\n" .
chunk_split($certBase64, 64, "\n") .
"-----END CERTIFICATE-----\n";
$notBefore = new \DateTime($xpath->evaluate('string(//smd:notBefore)')); $notBefore = new \DateTime($xpath->evaluate('string(//smd:notBefore)'));
$notAfter = new \DateTime($xpath->evaluate('string(//smd:notAfter)')); $notafter = new \DateTime($xpath->evaluate('string(//smd:notAfter)'));
$markName = $xpath->evaluate('string(//mark:markName)'); $markName = $xpath->evaluate('string(//mark:markName)');
$markId = $xpath->evaluate('string(//mark:id)');
$labels = []; $labels = [];
foreach ($xpath->query('//mark:label') as $x_label) { foreach ($xpath->query('//mark:label') as $x_label) {
$labels[] = $x_label->nodeValue; $labels[] = $x_label->nodeValue;
@ -188,20 +208,28 @@ class ApplicationsController extends Controller
// Check if current date and time is between notBefore and notAfter // Check if current date and time is between notBefore and notAfter
$now = new \DateTime(); $now = new \DateTime();
if (!($now >= $notBefore && $now <= $notAfter)) { if (!($now >= $notBefore && $now <= $notafter)) {
$this->container->get('flash')->addMessage('error', 'Error creating application: Current time is outside the valid range in the SMD file'); $this->container->get('flash')->addMessage('error', 'Error creating application: Current time is outside the valid range in the SMD file');
return $response->withHeader('Location', '/application/create')->withStatus(302); return $response->withHeader('Location', '/application/create')->withStatus(302);
} }
// Verify the signature // Verify the signature
$publicKeyStore = new PublicKeyStore(); $dsig = new \RobRichards\XMLSecLibs\XMLSecurityDSig();
$publicKeyStore->loadFromDocument($domDocument); $signatureNode = $dsig->locateSignature($domDocument);
$cryptoVerifier = new CryptoVerifier($publicKeyStore); $dsig->canonicalizeSignedInfo();
$xmlSignatureVerifier = new XmlSignatureVerifier($cryptoVerifier); $dsig->idKeys = ['ID'];
$isValid = $xmlSignatureVerifier->verifyXml($xmlContent); $dsig->idNS = ['smd' => 'urn:ietf:params:xml:ns:signedMark-1.0'];
if (!$isValid) { $key = new \RobRichards\XMLSecLibs\XMLSecurityKey(
$this->container->get('flash')->addMessage('error', 'Error creating application: The XML signature of the SMD file is not valid'); \RobRichards\XMLSecLibs\XMLSecurityKey::RSA_SHA256,
['type' => 'public']
);
$key->loadKey($certPem, false, true);
$accepted = (new \DateTime())->format('Y-m-d H:i:s.v');
if (!$dsig->verify($key, $signatureNode)) {
$this->container->get('flash')->addMessage('error', 'Error creating application: The XML signature of the SMD file is not valid.');
return $response->withHeader('Location', '/application/create')->withStatus(302); return $response->withHeader('Location', '/application/create')->withStatus(302);
} }
} else { } else {
@ -374,12 +402,13 @@ class ApplicationsController extends Controller
'acdate' => null, 'acdate' => null,
'authtype' => 'pw', 'authtype' => 'pw',
'authinfo' => $authInfo, 'authinfo' => $authInfo,
'phase_name' => $phaseName, 'phase_name' => $phaseName ?? null,
'phase_type' => $phaseType, 'phase_type' => $phaseType ?? 'none',
'smd' => $smd, 'smd' => $smd ?? null,
'tm_notice_id' => $noticeid, 'tm_smd_id' => $markId ?? null,
'tm_notice_accepted' => $accepted, 'tm_notice_id' => $noticeid ?? null,
'tm_notice_expires' => $notafter 'tm_notice_accepted' => $accepted ?? null,
'tm_notice_expires' => isset($notafter) ? ($notafter instanceof \DateTime ? $notafter->format('Y-m-d H:i:s') : $notafter) : null
]); ]);
$domain_id = $db->getlastInsertId(); $domain_id = $db->getlastInsertId();

View file

@ -6,9 +6,6 @@ use App\Models\Domain;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Selective\XmlDSig\PublicKeyStore;
use Selective\XmlDSig\CryptoVerifier;
use Selective\XmlDSig\XmlSignatureVerifier;
use League\ISO3166\ISO3166; use League\ISO3166\ISO3166;
class DomainsController extends Controller class DomainsController extends Controller
@ -147,7 +144,6 @@ class DomainsController extends Controller
$contactBilling = $data['contactBilling'] ?? null; $contactBilling = $data['contactBilling'] ?? null;
$phaseType = $data['phaseType'] ?? 'none'; $phaseType = $data['phaseType'] ?? 'none';
$smd = $data['smd'] ?? null;
$phaseName = $data['phaseName'] ?? null; $phaseName = $data['phaseName'] ?? null;
$token = $data['token'] ?? null; $token = $data['token'] ?? null;
@ -169,6 +165,23 @@ class DomainsController extends Controller
$authInfo = $data['authInfo'] ?? null; $authInfo = $data['authInfo'] ?? null;
$invalid_domain = validate_label($domainName, $db); $invalid_domain = validate_label($domainName, $db);
$uploadedFiles = $request->getUploadedFiles();
$smdFile = $uploadedFiles['smd'] ?? null;
$smd = null;
if ($smdFile && $smdFile->getError() === UPLOAD_ERR_OK) {
$smd = $smdFile->getStream()->getContents();
$smd_filename = $smdFile->getClientFilename();
if (pathinfo($smd_filename, PATHINFO_EXTENSION) !== 'smd') {
$this->container->get('flash')->addMessage('error', 'Only .smd files are allowed');
return $response->withHeader('Location', '/domain/create')->withStatus(302);
}
} else {
$this->container->get('flash')->addMessage('error', 'SMD file upload failed');
return $response->withHeader('Location', '/domain/create')->withStatus(302);
}
if ($invalid_domain) { if ($invalid_domain) {
$this->container->get('flash')->addMessage('error', 'Error creating domain: Invalid domain name'); $this->container->get('flash')->addMessage('error', 'Error creating domain: Invalid domain name');
return $response->withHeader('Location', '/domain/create')->withStatus(302); return $response->withHeader('Location', '/domain/create')->withStatus(302);
@ -282,10 +295,17 @@ class DomainsController extends Controller
$xpath = new \DOMXPath($domDocument); $xpath = new \DOMXPath($domDocument);
$xpath->registerNamespace('smd', 'urn:ietf:params:xml:ns:signedMark-1.0'); $xpath->registerNamespace('smd', 'urn:ietf:params:xml:ns:signedMark-1.0');
$xpath->registerNamespace('mark', 'urn:ietf:params:xml:ns:mark-1.0'); $xpath->registerNamespace('mark', 'urn:ietf:params:xml:ns:mark-1.0');
$xpath->registerNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
$certNode = $xpath->evaluate('string(//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate)');
$certBase64 = preg_replace('/\s+/', '', $certNode);
$certPem = "-----BEGIN CERTIFICATE-----\n" .
chunk_split($certBase64, 64, "\n") .
"-----END CERTIFICATE-----\n";
$notBefore = new \DateTime($xpath->evaluate('string(//smd:notBefore)')); $notBefore = new \DateTime($xpath->evaluate('string(//smd:notBefore)'));
$notAfter = new \DateTime($xpath->evaluate('string(//smd:notAfter)')); $notafter = new \DateTime($xpath->evaluate('string(//smd:notAfter)'));
$markName = $xpath->evaluate('string(//mark:markName)'); $markName = $xpath->evaluate('string(//mark:markName)');
$markId = $xpath->evaluate('string(//mark:id)');
$labels = []; $labels = [];
foreach ($xpath->query('//mark:label') as $x_label) { foreach ($xpath->query('//mark:label') as $x_label) {
$labels[] = $x_label->nodeValue; $labels[] = $x_label->nodeValue;
@ -298,19 +318,27 @@ class DomainsController extends Controller
// Check if current date and time is between notBefore and notAfter // Check if current date and time is between notBefore and notAfter
$now = new \DateTime(); $now = new \DateTime();
if (!($now >= $notBefore && $now <= $notAfter)) { if (!($now >= $notBefore && $now <= $notafter)) {
$this->container->get('flash')->addMessage('error', 'Error creating domain: Current time is outside the valid range in the SMD.'); $this->container->get('flash')->addMessage('error', 'Error creating domain: Current time is outside the valid range in the SMD.');
return $response->withHeader('Location', '/domain/create')->withStatus(302); return $response->withHeader('Location', '/domain/create')->withStatus(302);
} }
// Verify the signature // Verify the signature
$publicKeyStore = new PublicKeyStore(); $dsig = new \RobRichards\XMLSecLibs\XMLSecurityDSig();
$publicKeyStore->loadFromDocument($domDocument); $signatureNode = $dsig->locateSignature($domDocument);
$cryptoVerifier = new CryptoVerifier($publicKeyStore); $dsig->canonicalizeSignedInfo();
$xmlSignatureVerifier = new XmlSignatureVerifier($cryptoVerifier); $dsig->idKeys = ['ID'];
$isValid = $xmlSignatureVerifier->verifyXml($xmlContent); $dsig->idNS = ['smd' => 'urn:ietf:params:xml:ns:signedMark-1.0'];
if (!$isValid) { $key = new \RobRichards\XMLSecLibs\XMLSecurityKey(
\RobRichards\XMLSecLibs\XMLSecurityKey::RSA_SHA256,
['type' => 'public']
);
$key->loadKey($certPem, false, true);
$accepted = (new \DateTime())->format('Y-m-d H:i:s.v');
if (!$dsig->verify($key, $signatureNode)) {
$this->container->get('flash')->addMessage('error', 'Error creating domain: The XML signature of the SMD file is not valid.'); $this->container->get('flash')->addMessage('error', 'Error creating domain: The XML signature of the SMD file is not valid.');
return $response->withHeader('Location', '/domain/create')->withStatus(302); return $response->withHeader('Location', '/domain/create')->withStatus(302);
} }
@ -513,12 +541,12 @@ class DomainsController extends Controller
'acdate' => null, 'acdate' => null,
'rgpstatus' => 'addPeriod', 'rgpstatus' => 'addPeriod',
'addPeriod' => $date_add, 'addPeriod' => $date_add,
'phase_name' => $phaseName, 'phase_name' => $phaseName ?? null,
'tm_phase' => $phaseType, 'tm_phase' => $phaseType ?? 'none',
'tm_smd_id' => $smd, 'tm_smd_id' => $markId ?? null,
'tm_notice_id' => $noticeid, 'tm_notice_id' => $noticeid ?? null,
'tm_notice_accepted' => $accepted, 'tm_notice_accepted' => $accepted ?? null,
'tm_notice_expires' => $notafter 'tm_notice_expires' => isset($notafter) ? ($notafter instanceof \DateTime ? $notafter->format('Y-m-d H:i:s') : $notafter) : null
]); ]);
$domain_id = $db->getlastInsertId(); $domain_id = $db->getlastInsertId();

View file

@ -40,7 +40,6 @@
"league/flysystem": "^3.28", "league/flysystem": "^3.28",
"mpociot/vat-calculator": "^3.11.0", "mpociot/vat-calculator": "^3.11.0",
"ramsey/uuid": "^4.7", "ramsey/uuid": "^4.7",
"selective/xmldsig": "^3.1",
"adyen/php-api-library": "^19.1", "adyen/php-api-library": "^19.1",
"giggsey/libphonenumber-for-php-lite": "^8.13", "giggsey/libphonenumber-for-php-lite": "^8.13",
"egulias/email-validator": "^4.0", "egulias/email-validator": "^4.0",
@ -50,7 +49,8 @@
"bjeavons/zxcvbn-php": "^1.4", "bjeavons/zxcvbn-php": "^1.4",
"moneyphp/money": "^4.6", "moneyphp/money": "^4.6",
"phpmailer/phpmailer": "^6.9", "phpmailer/phpmailer": "^6.9",
"filips123/monolog-phpmailer": "^2.0" "filips123/monolog-phpmailer": "^2.0",
"robrichards/xmlseclibs": "^3.1"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View file

@ -36,7 +36,7 @@
{% include 'partials/flash.twig' %} {% include 'partials/flash.twig' %}
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form id="domainCreateForm" action="/application/create" method="post"> <form id="domainCreateForm" action="/application/create" method="post" enctype="multipart/form-data">
{{ csrf.field | raw }} {{ csrf.field | raw }}
<div class="row mb-2"> <div class="row mb-2">
@ -195,7 +195,7 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">{{ __('Signed Mark Information') }}</label> <label class="form-label">{{ __('Signed Mark Information') }}</label>
<textarea class="form-control" data-bs-toggle="autosize" name="smd" placeholder="{{ __('Paste SMD contents') }}…"></textarea> <input type="file" class="form-control" name="smd" accept=".smd" />
</div> </div>
<div class="mb-3"> <div class="mb-3">

View file

@ -36,7 +36,7 @@
{% include 'partials/flash.twig' %} {% include 'partials/flash.twig' %}
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form id="domainCreateForm" action="/domain/create" method="post"> <form id="domainCreateForm" action="/domain/create" method="post" enctype="multipart/form-data">
{{ csrf.field | raw }} {{ csrf.field | raw }}
<div class="row"> <div class="row">
@ -327,7 +327,7 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label">{{ __('Signed Mark Information') }}</label> <label class="form-label">{{ __('Signed Mark Information') }}</label>
<textarea class="form-control" data-bs-toggle="autosize" name="smd" placeholder="{{ __('Paste SMD contents') }}…"></textarea> <input type="file" class="form-control" name="smd" accept=".smd" />
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">