From a1492873ddf5ba3c3b21955650a2392b5e11ebec Mon Sep 17 00:00:00 2001 From: Pinga <121483313+getpinga@users.noreply.github.com> Date: Sun, 28 Jul 2024 11:28:05 +0300 Subject: [PATCH] Fixed #44 , added full support for RFC8334 --- epp/composer.json | 1 + epp/src/epp-create.php | 163 +++++++++++++++++++++++++++++++++++++---- epp/src/epp-update.php | 10 +-- epp/start_epp.php | 3 + 4 files changed, 156 insertions(+), 21 deletions(-) diff --git a/epp/composer.json b/epp/composer.json index 2e7a99f..2a0aa07 100644 --- a/epp/composer.json +++ b/epp/composer.json @@ -5,6 +5,7 @@ "matthiasmullie/scrapbook": "^1.5", "guzzlehttp/guzzle": "^7.9.2", "league/flysystem": "^3.28", + "selective/xmldsig": "^3.1", "namingo/rately": "^0.1.0" } } diff --git a/epp/src/epp-create.php b/epp/src/epp-create.php index 7ef39ca..47b2d3a 100644 --- a/epp/src/epp-create.php +++ b/epp/src/epp-create.php @@ -580,10 +580,17 @@ function processDomainCreate($conn, $db, $xml, $clid, $database_type, $trans, $m $fee_create = $xml->xpath('//fee:create')[0] ?? null; $launch_create = $xml->xpath('//launch:create')[0] ?? null; $allocation_token = $xml->xpath('//allocationToken:allocationToken')[0] ?? null; + + // Check if launch extension is enabled in database settings + $stmt = $db->prepare("SELECT value FROM settings WHERE name = 'launch_phases' LIMIT 1"); + $stmt->execute(); + $launch_extension_enabled = $stmt->fetchColumn(); } - - if ($launch_create) { - $launch_phase = (string) $launch_create->xpath('launch:phase')[0] ?? null; + + if ($launch_extension_enabled && isset($launch_create)) { + $launch_phase_node = $launch_create->xpath('launch:phase')[0] ?? null; + $launch_phase = $launch_phase_node ? (string)$launch_phase_node : null; + $launch_phase_name = $launch_phase_node ? (string)$launch_phase_node['name'] : null; $smd_encodedSignedMark = $launch_create->xpath('smd:encodedSignedMark')[0] ?? null; $launch_notice = $launch_create->xpath('launch:notice')[0] ?? null; $launch_noticeID = $launch_notice ? (string) $launch_notice->xpath('launch:noticeID')[0] ?? null : null; @@ -592,9 +599,7 @@ function processDomainCreate($conn, $db, $xml, $clid, $database_type, $trans, $m // Validate and handle each specific case if ($launch_phase === 'sunrise' && $smd_encodedSignedMark) { - // Parse and validate SMD encoded signed mark, then return unimplemented error - sendEppError($conn, $db, 2101, 'sunrise with encodedSignedMark', $clTRID, $trans); - return; + // Parse and validate SMD encoded signed mark later } elseif ($launch_phase === 'claims') { // Check for missing notice elements and validate dates if (!$launch_notice || !$launch_noticeID || !$launch_notAfter || !$launch_acceptedDate) { @@ -621,16 +626,12 @@ function processDomainCreate($conn, $db, $xml, $clid, $database_type, $trans, $m return; } - // If all validations pass, return unimplemented error - sendEppError($conn, $db, 2101, 'claims with notice', $clTRID, $trans); - return; + // If all validations pass, continue } elseif ($launch_phase === 'landrush') { - // Parse phase and type attributes, then return unimplemented error - sendEppError($conn, $db, 2101, 'landrush', $clTRID, $trans); - return; + // Continue } else { // Mixed or unsupported form - sendEppError($conn, $db, 2101, 'unsupported or mixed form', $clTRID, $trans); + sendEppError($conn, $db, 2101, 'unsupported launch phase or mixed form', $clTRID, $trans); return; } } @@ -671,6 +672,124 @@ function processDomainCreate($conn, $db, $xml, $clid, $database_type, $trans, $m return; } + if ($launch_extension_enabled && isset($launch_create)) { + $stmt = $db->prepare("SELECT value FROM settings WHERE name = 'launch_phases' LIMIT 1"); + $stmt->execute(); + $launch_phases = $stmt->fetchColumn(); + + $currentDateTime = new \DateTime(); + $currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp + + $stmt = $db->prepare(" + SELECT phase_category + FROM launch_phases + WHERE tld_id = ? + AND phase_type = ? + AND start_date <= ? + AND (end_date >= ? OR end_date IS NULL OR end_date = '') + LIMIT 1 + "); + $stmt->execute([$tld_id, $launch_phase, $currentDate, $currentDate]); + $phase_details = $stmt->fetchColumn(); + + if ($phase_details !== 'First-Come-First-Serve') { + if ($launch_phase !== 'none') { + if ($launch_phase == null && $launch_phase == '') { + sendEppError($conn, $db, 2306, 'Error creating domain: The launch phase ' . $launch_phase . ' is improperly configured. Please check the settings or contact support.', $clTRID, $trans); + return; + } else if ($phase_details == null) { + sendEppError($conn, $db, 2306, 'Error creating domain: The launch phase ' . $launch_phase . ' is currently not active.', $clTRID, $trans); + return; + } + } + } else if ($launch_phase !== 'none') { + if ($launch_phase == null && $launch_phase == '') { + sendEppError($conn, $db, 2306, 'Error creating domain: The launch phase ' . $launch_phase . ' is improperly configured. Please check the settings or contact support.', $clTRID, $trans); + return; + } else if ($phase_details == null) { + sendEppError($conn, $db, 2306, 'Error creating domain: The launch phase ' . $launch_phase . ' is currently not active.', $clTRID, $trans); + return; + } + } + + if ($launch_phase === 'claims') { + if (!isset($launch_noticeID) || $launch_noticeID === '' || + !isset($launch_notAfter) || $launch_notAfter === '' || + !isset($launch_acceptedDate) || $launch_acceptedDate === '') { + sendEppError($conn, $db, 2306, "Error creating domain: 'noticeid', 'notafter', or 'accepted' cannot be empty when phaseType is 'claims'", $clTRID, $trans); + return; + } + + $noticeid = $launch_noticeID; + $notafter = $launch_notAfter; + $accepted = $launch_acceptedDate; + } else { + $noticeid = null; + $notafter = null; + $accepted = null; + } + + if ($launch_phase === 'sunrise') { + if ($smd_encodedSignedMark !== null && $smd_encodedSignedMark !== '') { + // Extract the BASE64 encoded part + $beginMarker = "-----BEGIN ENCODED SMD-----"; + $endMarker = "-----END ENCODED SMD-----"; + $beginPos = strpos($smd_encodedSignedMark, $beginMarker) + strlen($beginMarker); + $endPos = strpos($smd_encodedSignedMark, $endMarker); + $encodedSMD = trim(substr($smd_encodedSignedMark, $beginPos, $endPos - $beginPos)); + + // Decode the BASE64 content + $xmlContent = base64_decode($encodedSMD); + + // Load the XML content using DOMDocument + $domDocument = new \DOMDocument(); + $domDocument->preserveWhiteSpace = false; + $domDocument->formatOutput = true; + $domDocument->loadXML($xmlContent); + + // Parse data + $xpath = new \DOMXPath($domDocument); + $xpath->registerNamespace('smd', 'urn:ietf:params:xml:ns:signedMark-1.0'); + $xpath->registerNamespace('mark', 'urn:ietf:params:xml:ns:mark-1.0'); + + $notBefore = new \DateTime($xpath->evaluate('string(//smd:notBefore)')); + $notafter = new \DateTime($xpath->evaluate('string(//smd:notAfter)')); + $markName = $xpath->evaluate('string(//mark:markName)'); + $labels = []; + foreach ($xpath->query('//mark:label') as $x_label) { + $labels[] = $x_label->nodeValue; + } + + if (!in_array($label, $labels)) { + sendEppError($conn, $db, 2306, 'Error creating domain: SMD file is not valid for the domain name being registered.', $clTRID, $trans); + return; + } + + // Check if current date and time is between notBefore and notAfter + $now = new \DateTime(); + if (!($now >= $notBefore && $now <= $notafter)) { + sendEppError($conn, $db, 2306, 'Error creating domain: Current time is outside the valid range in the SMD.', $clTRID, $trans); + return; + } + + // Verify the signature + $publicKeyStore = new PublicKeyStore(); + $publicKeyStore->loadFromDocument($domDocument); + $cryptoVerifier = new CryptoVerifier($publicKeyStore); + $xmlSignatureVerifier = new XmlSignatureVerifier($cryptoVerifier); + $isValid = $xmlSignatureVerifier->verifyXml($xmlContent); + + if (!$isValid) { + sendEppError($conn, $db, 2306, 'Error creating domain: The XML signature of the SMD file is not valid.', $clTRID, $trans); + return; + } + } else { + sendEppError($conn, $db, 2306, "Error creating domain: SMD upload is required in the 'sunrise' phase.", $clTRID, $trans); + return; + } + } + } + $stmt = $db->prepare("SELECT id FROM reserved_domain_names WHERE name = ? LIMIT 1"); $stmt->execute([$label]); $domain_already_reserved = $stmt->fetchColumn(); @@ -1159,8 +1278,13 @@ function processDomainCreate($conn, $db, $xml, $clid, $database_type, $trans, $m } } - $domainSql = "INSERT INTO domain (name,tldid,registrant,crdate,exdate,lastupdate,clid,crid,upid,trdate,trstatus,reid,redate,acid,acdate,rgpstatus,addPeriod) - VALUES(:name, :tld_id, :registrant_id, CURRENT_TIMESTAMP(3), DATE_ADD(CURRENT_TIMESTAMP(3), INTERVAL :date_add MONTH), NULL, :registrar_id, :registrar_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'addPeriod', :date_add2)"; + $domainSql = "INSERT INTO domain ( + name, tldid, registrant, crdate, exdate, lastupdate, clid, crid, upid, trdate, trstatus, reid, redate, acid, acdate, rgpstatus, addPeriod, + phase_name, tm_phase, tm_smd_id, tm_notice_id, tm_notice_accepted, tm_notice_expires + ) VALUES ( + :name, :tld_id, :registrant_id, CURRENT_TIMESTAMP(3), DATE_ADD(CURRENT_TIMESTAMP(3), INTERVAL :date_add MONTH), NULL, :registrar_id, :registrar_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'addPeriod', :date_add2, + :phase_name, :tm_phase, :tm_smd_id, :tm_notice_id, :tm_notice_accepted, :tm_notice_expires + )"; $domainStmt = $db->prepare($domainSql); $domainStmt->execute([ @@ -1169,8 +1293,15 @@ function processDomainCreate($conn, $db, $xml, $clid, $database_type, $trans, $m ':registrant_id' => $registrant_id, ':date_add' => $date_add, ':date_add2' => $date_add, - ':registrar_id' => $clid + ':registrar_id' => $clid, + ':phase_name' => $launch_phase_name ?? null, + ':tm_phase' => $launch_phase ?? null, + ':tm_smd_id' => $smd_encodedSignedMark ?? null, + ':tm_notice_id' => $noticeid ?? null, + ':tm_notice_accepted' => $accepted ?? null, + ':tm_notice_expires' => $notafter ?? null ]); + $domain_id = $db->lastInsertId(); $authInfoStmt = $db->prepare("INSERT INTO domain_authInfo (domain_id,authtype,authinfo) VALUES(:domain_id,'pw',:authInfo_pw)"); diff --git a/epp/src/epp-update.php b/epp/src/epp-update.php index 851afbc..e11321e 100644 --- a/epp/src/epp-update.php +++ b/epp/src/epp-update.php @@ -934,6 +934,11 @@ function processDomainUpdate($conn, $db, $xml, $clid, $database_type, $trans) { $rgp_update = $xml->xpath('//rgp:update')[0] ?? null; $secdns_update = $xml->xpath('//secDNS:update')[0] ?? null; $launch_update = $xml->xpath('//launch:update')[0] ?? null; + + // Check if launch extension is enabled in database settings + $stmt = $db->prepare("SELECT value FROM settings WHERE name = 'launch_phases' LIMIT 1"); + $stmt->execute(); + $launch_extension_enabled = $stmt->fetchColumn(); } if ($domainRem === null && $domainAdd === null && $domainChg === null && $extensionNode === null) { @@ -968,11 +973,6 @@ function processDomainUpdate($conn, $db, $xml, $clid, $database_type, $trans) { $domain_id = $row['id']; - // Check if launch extension is enabled in database settings - $stmt = $db->prepare("SELECT value FROM settings WHERE name = 'launch_phases' LIMIT 1"); - $stmt->execute(); - $launch_extension_enabled = $stmt->fetchColumn(); - if ($launch_extension_enabled && isset($launch_update)) { $phase = (string) $launch_update->xpath('launch:phase')[0]; $applicationID = (string) $launch_update->xpath('launch:applicationID')[0]; diff --git a/epp/start_epp.php b/epp/start_epp.php index dfaec32..06882e3 100644 --- a/epp/start_epp.php +++ b/epp/start_epp.php @@ -21,6 +21,9 @@ use Swoole\Timer; use Swoole\Coroutine\Server; use Swoole\Coroutine\Server\Connection; use Namingo\Rately\Rately; +use Selective\XmlDSig\PublicKeyStore; +use Selective\XmlDSig\CryptoVerifier; +use Selective\XmlDSig\XmlSignatureVerifier; $table = new Table(1024); $table->column('clid', Table::TYPE_STRING, 64);