Compare commits

..

1 Commits

Author SHA1 Message Date
Sven Steinert
ec97e1097c Update 2026-05-04 19:20:22 +02:00
1254 changed files with 421 additions and 174285 deletions

View File

@@ -7,30 +7,30 @@ WordPress plugin version of the legacy `qa-tool` app.
- Frontend shortcode: `[obyte_qa_tool]` - Frontend shortcode: `[obyte_qa_tool]`
- WordPress backend settings for GitLab and DocBee configuration - WordPress backend settings for GitLab and DocBee configuration
- GitLab template loading through a WordPress REST proxy - GitLab template loading through a WordPress REST proxy
- GitLab template writeback through WordPress REST, using the backend token - GitLab template writeback through WordPress REST, using only the token saved in the backend
- Local YAML/JSON template loading - Local YAML/JSON template loading
- Full YAML parsing through `js-yaml` with a small built-in fallback parser - Full YAML parsing through `js-yaml` with a small built-in fallback parser
- Editable QA steps and groups with drag and drop - Editable QA steps and groups with drag and drop
- Required-step validation - Required-step validation
- Run save/load as JSON - Run save/load as JSON
- Markdown, CSV, printable PDF, and YAML template export - Markdown, CSV, printable PDF, and YAML template export
- Combined export: DocBee post, WordPress database storage, and Media Library PDF upload - Combined export: DocBee post, WordPress database storage, and protected PDF storage
- DocBee ticket posting through a server-side REST endpoint with optional ticket-status restoration - DocBee ticket posting through a server-side REST endpoint with optional ticket-status restoration
## Setup ## Setup
1. Copy or keep the `obyte-qa-tool` folder in `wp-content/plugins/`. 1. Copy or keep the `obyte-qa-tool` folder in `wp-content/plugins/`.
2. Activate **o-Byte QA Tool** in WordPress. 2. Activate **o-Byte QA Tool** in WordPress.
3. Open **Settings > o-Byte QA Tool**. 3. Open **QA Tool > Settings**.
4. Enter GitLab and DocBee settings. Secrets are stored as WordPress options and are not exposed to frontend JavaScript. 4. Enter GitLab and DocBee settings. Secrets are stored as WordPress options and are not exposed to frontend JavaScript.
5. Add `[obyte_qa_tool]` to the page where the QA runner should appear. 5. Add `[obyte_qa_tool]` to the page where the QA runner should appear.
6. Saved exports can be reviewed under **Tools > o-Byte QA Reports**. 6. Saved exports can be reviewed under **QA Tool > Reports**.
## Notes ## Notes
- The legacy standalone OIDC login is replaced by WordPress login and capability checks. - Access control is expected to be handled by the site/OAuth tag layer before the shortcode is shown.
- The REST endpoints require the configured WordPress capability. - The REST endpoints require a logged-in WordPress session, but no plugin-owned capability setting.
- GitLab and DocBee credentials from the old PHP files are intentionally not hardcoded into the plugin. - GitLab and DocBee credentials are never hardcoded; secrets must be entered and stored through the backend settings.
- Reports are stored in WordPress-owned custom tables: `wp_obyte_qa_reports` and `wp_obyte_qa_steps` using the active site prefix. - Reports are stored in WordPress-owned custom tables: `wp_obyte_qa_reports` and `wp_obyte_qa_steps` using the active site prefix.
- Exported PDFs are stored as normal Media Library attachments when enabled. - Exported PDFs are stored in protected plugin storage when enabled. Backend report links are short-lived one-time links.
- Client-side PDF generation uses jsPDF/AutoTable CDNs, matching the standalone tool's browser-based export model. - Client-side PDF generation uses jsPDF/AutoTable CDNs, matching the standalone tool's browser-based export model.

View File

@@ -14,11 +14,22 @@
font-display: swap; font-display: swap;
} }
.obyte-qa-tool-shell,
.obyte-qa-tool, .obyte-qa-tool,
.obyte-qa-tool * { .obyte-qa-tool * {
box-sizing: border-box; box-sizing: border-box;
} }
.obyte-qa-tool-shell {
width: 100vw;
max-width: 100vw;
margin-left: calc(50% - 50vw);
margin-right: calc(50% - 50vw);
padding: 0 clamp(16px, 3vw, 34px);
overflow-x: clip;
background: #f5fbfe;
}
.obyte-qa-tool { .obyte-qa-tool {
--oqt-blue: #00a7e6; --oqt-blue: #00a7e6;
--oqt-blue-soft: #33b9eb; --oqt-blue-soft: #33b9eb;
@@ -36,8 +47,9 @@
--oqt-danger: #c43b3b; --oqt-danger: #c43b3b;
--oqt-muted: #67717a; --oqt-muted: #67717a;
--oqt-focus: 0 0 0 3px rgba(0, 167, 230, 0.28); --oqt-focus: 0 0 0 3px rgba(0, 167, 230, 0.28);
width: 100%; width: min(1240px, 100%);
margin: 0; max-width: 100%;
margin: 0 auto;
overflow: visible; overflow: visible;
border: 1px solid var(--oqt-line); border: 1px solid var(--oqt-line);
border-radius: 8px; border-radius: 8px;

View File

@@ -25,6 +25,8 @@
this.root = root; this.root = root;
this.template = null; this.template = null;
this.dragIndex = -1; this.dragIndex = -1;
this.gitlabTemplatePath = "";
this.gitlabTemplateModule = "";
this.storageKey = "obyteQaToolState:" + window.location.pathname; this.storageKey = "obyteQaToolState:" + window.location.pathname;
this.els = this.collectElements(); this.els = this.collectElements();
this.init(); this.init();
@@ -128,11 +130,15 @@
self.loadGitlabTemplate(event.target.value); self.loadGitlabTemplate(event.target.value);
}); });
bind(this.els.addStep, "click", function () { bind(this.els.addStep, "click", function (event) {
event.preventDefault();
event.stopImmediatePropagation();
self.addStep(); self.addStep();
}); });
bind(this.els.addGroup, "click", function () { bind(this.els.addGroup, "click", function (event) {
event.preventDefault();
event.stopImmediatePropagation();
self.addGroup(); self.addGroup();
}); });
@@ -274,13 +280,15 @@
QaTool.prototype.loadGitlabTemplate = async function (path) { QaTool.prototype.loadGitlabTemplate = async function (path) {
if (!path) { if (!path) {
this.gitlabTemplatePath = "";
this.gitlabTemplateModule = "";
return; return;
} }
try { try {
this.setTag(this.els.gitlabTplStatus, "GitLab: lade Datei", ""); this.setTag(this.els.gitlabTplStatus, "GitLab: lade Datei", "");
var data = await this.api("template?path=" + encodeURIComponent(path)); var data = await this.api("template?path=" + encodeURIComponent(path));
this.loadTemplateText(data.content || "", path.split("/").pop()); this.loadTemplateText(data.content || "", path.split("/").pop(), data.path || path);
this.setTag(this.els.gitlabTplStatus, "GitLab: geladen", "ok"); this.setTag(this.els.gitlabTplStatus, "GitLab: geladen", "ok");
} catch (error) { } catch (error) {
this.setTag(this.els.gitlabTplStatus, "GitLab: Fehler", "bad"); this.setTag(this.els.gitlabTplStatus, "GitLab: Fehler", "bad");
@@ -305,10 +313,12 @@
} }
}; };
QaTool.prototype.loadTemplateText = function (text, fallbackName) { QaTool.prototype.loadTemplateText = function (text, fallbackName, sourcePath) {
var parsed = parseTemplateText(text); var parsed = parseTemplateText(text);
var normalized = normalizeTemplate(parsed, fallbackName || "Template"); var normalized = normalizeTemplate(parsed, fallbackName || "Template");
this.template = normalized; this.template = normalized;
this.gitlabTemplatePath = sourcePath || "";
this.gitlabTemplateModule = sourcePath ? (normalized.module || "") : "";
if (this.els.tplName) { if (this.els.tplName) {
this.els.tplName.textContent = normalized.name || fallbackName || "Template"; this.els.tplName.textContent = normalized.name || fallbackName || "Template";
@@ -440,7 +450,9 @@
return; return;
} }
var rows = Array.prototype.slice.call(this.els.stepsTableBody.querySelectorAll("tr")); var rows = Array.prototype.slice.call(this.els.stepsTableBody.querySelectorAll("tr")).filter(function (row) {
return !row.classList.contains("oqt-empty-row");
});
this.template.steps = rows.map(function (row, index) { this.template.steps = rows.map(function (row, index) {
var kind = row.dataset.kind || "step"; var kind = row.dataset.kind || "step";
if (kind === "group") { if (kind === "group") {
@@ -474,6 +486,8 @@
pbx_version: config.defaultPbxVersion || "", pbx_version: config.defaultPbxVersion || "",
steps: [] steps: []
}; };
this.gitlabTemplatePath = "";
this.gitlabTemplateModule = "";
if (this.els.tplName) { if (this.els.tplName) {
this.els.tplName.textContent = this.template.name; this.els.tplName.textContent = this.template.name;
} }
@@ -481,8 +495,11 @@
}; };
QaTool.prototype.addStep = function () { QaTool.prototype.addStep = function () {
var hadTemplate = !!this.template;
this.ensureTemplate(); this.ensureTemplate();
if (hadTemplate) {
this.captureEditsIntoTemplate(); this.captureEditsIntoTemplate();
}
this.template.steps.push({ this.template.steps.push({
kind: "step", kind: "step",
id: "", id: "",
@@ -499,8 +516,11 @@
}; };
QaTool.prototype.addGroup = function () { QaTool.prototype.addGroup = function () {
var hadTemplate = !!this.template;
this.ensureTemplate(); this.ensureTemplate();
if (hadTemplate) {
this.captureEditsIntoTemplate(); this.captureEditsIntoTemplate();
}
this.template.steps.push({ this.template.steps.push({
kind: "group", kind: "group",
title: "Neue Gruppe", title: "Neue Gruppe",
@@ -882,6 +902,8 @@
pbx_version: run.pbx_version || "", pbx_version: run.pbx_version || "",
steps: run.steps steps: run.steps
}, file.name); }, file.name);
this.gitlabTemplatePath = "";
this.gitlabTemplateModule = "";
if (this.els.tplName) { if (this.els.tplName) {
this.els.tplName.textContent = run.name || file.name; this.els.tplName.textContent = run.name || file.name;
@@ -1251,8 +1273,8 @@
if (data.report_id) { if (data.report_id) {
parts.push("Report-ID: " + data.report_id); parts.push("Report-ID: " + data.report_id);
} }
if (data.pdf_url) { if (data.pdf_stored) {
parts.push('<a href="' + escAttr(data.pdf_url) + '" target="_blank" rel="noopener">PDF in Mediathek öffnen</a>'); parts.push("PDF geschuetzt gespeichert");
} }
if (data.docbee && data.docbee.ok) { if (data.docbee && data.docbee.ok) {
var docbeeLink = data.docbee.url ? ' <a href="' + escAttr(data.docbee.url) + '" target="_blank" rel="noopener">DocBee öffnen</a>' : ""; var docbeeLink = data.docbee.url ? ' <a href="' + escAttr(data.docbee.url) + '" target="_blank" rel="noopener">DocBee öffnen</a>' : "";
@@ -1295,11 +1317,16 @@
button.classList.add("is-busy"); button.classList.add("is-busy");
} }
this.showMessage("GitLab: Template wird geschrieben...", ""); this.showMessage("GitLab: Template wird geschrieben...", "");
var sourcePath = this.gitlabTemplatePath && sameModuleName(template.module, this.gitlabTemplateModule)
? this.gitlabTemplatePath
: "";
var data = await this.api("gitlab/template", { var data = await this.api("gitlab/template", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ template: template, yaml: yaml }) body: JSON.stringify({ template: template, yaml: yaml, source_path: sourcePath })
}); });
this.gitlabTemplatePath = data.path || sourcePath || "";
this.gitlabTemplateModule = template.module || "";
this.showMessage("GitLab: Template gespeichert: " + (data.path || ""), "ok"); this.showMessage("GitLab: Template gespeichert: " + (data.path || ""), "ok");
} catch (error) { } catch (error) {
this.showMessage("GitLab-Push fehlgeschlagen: " + error.message, "bad"); this.showMessage("GitLab-Push fehlgeschlagen: " + error.message, "bad");
@@ -1800,6 +1827,10 @@
.replace(/[^\w.-]/g, "") || "qa"; .replace(/[^\w.-]/g, "") || "qa";
} }
function sameModuleName(left, right) {
return String(left || "").trim() === String(right || "").trim();
}
function mdCell(valueToEscape) { function mdCell(valueToEscape) {
return String(valueToEscape || "").replace(/\|/g, "\\|").replace(/\n/g, " "); return String(valueToEscape || "").replace(/\|/g, "\\|").replace(/\n/g, " ");
} }

View File

@@ -2,7 +2,7 @@
/** /**
* Plugin Name: o-Byte QA Tool * Plugin Name: o-Byte QA Tool
* Description: WordPress version of the o-Byte manual QA runner with GitLab templates, exports, and DocBee posting. * Description: WordPress version of the o-Byte manual QA runner with GitLab templates, exports, and DocBee posting.
* Version: 1.0.1 * Version: 1.0.8
* Author: o-byte.com * Author: o-byte.com
* Text Domain: obyte-qa-tool * Text Domain: obyte-qa-tool
*/ */
@@ -13,7 +13,7 @@ if (!defined('ABSPATH')) {
final class Obyte_QA_Tool final class Obyte_QA_Tool
{ {
private const VERSION = '1.0.1'; private const VERSION = '1.0.8';
private const OPTION_NAME = 'obyte_qa_tool_settings'; private const OPTION_NAME = 'obyte_qa_tool_settings';
private const REST_NAMESPACE = 'obyte-qa-tool/v1'; private const REST_NAMESPACE = 'obyte-qa-tool/v1';
@@ -44,13 +44,13 @@ final class Obyte_QA_Tool
add_action('admin_enqueue_scripts', [$this, 'register_assets']); add_action('admin_enqueue_scripts', [$this, 'register_assets']);
add_action('admin_menu', [$this, 'register_admin_page']); add_action('admin_menu', [$this, 'register_admin_page']);
add_action('admin_init', [$this, 'register_settings']); add_action('admin_init', [$this, 'register_settings']);
add_action('admin_post_obyte_qa_pdf', [$this, 'download_secure_pdf']);
add_action('rest_api_init', [$this, 'register_rest_routes']); add_action('rest_api_init', [$this, 'register_rest_routes']);
} }
private static function defaults(): array private static function defaults(): array
{ {
return [ return [
'required_capability' => 'read',
'default_pbx_version' => '', 'default_pbx_version' => '',
'gitlab_enabled' => 1, 'gitlab_enabled' => 1,
'gitlab_base_url' => 'https://git.steinert.cc', 'gitlab_base_url' => 'https://git.steinert.cc',
@@ -118,6 +118,7 @@ final class Obyte_QA_Tool
summary VARCHAR(255) NULL, summary VARCHAR(255) NULL,
pdf_attachment_id BIGINT UNSIGNED NOT NULL DEFAULT 0, pdf_attachment_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
pdf_url TEXT NULL, pdf_url TEXT NULL,
pdf_file TEXT NULL,
run_json LONGTEXT NULL, run_json LONGTEXT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
KEY module (module(120)), KEY module (module(120)),
@@ -213,9 +214,6 @@ final class Obyte_QA_Tool
public function render_shortcode(array $atts = []): string public function render_shortcode(array $atts = []): string
{ {
$settings = $this->settings();
$capability = $this->required_capability($settings);
if (!is_user_logged_in()) { if (!is_user_logged_in()) {
$login_url = wp_login_url(get_permalink()); $login_url = wp_login_url(get_permalink());
return '<div class="obyte-qa-tool-notice">' . return '<div class="obyte-qa-tool-notice">' .
@@ -223,17 +221,12 @@ final class Obyte_QA_Tool
' <a href="' . esc_url($login_url) . '">' . esc_html__('Zum Login', 'obyte-qa-tool') . '</a></div>'; ' <a href="' . esc_url($login_url) . '">' . esc_html__('Zum Login', 'obyte-qa-tool') . '</a></div>';
} }
if (!current_user_can($capability)) {
return '<div class="obyte-qa-tool-notice">' .
esc_html__('Dein WordPress Benutzer hat keine Berechtigung für das QA Tool.', 'obyte-qa-tool') .
'</div>';
}
$root_id = wp_unique_id('obyte-qa-tool-'); $root_id = wp_unique_id('obyte-qa-tool-');
$this->enqueue_frontend_assets($root_id); $this->enqueue_frontend_assets($root_id);
ob_start(); ob_start();
?> ?>
<div class="obyte-qa-tool-shell">
<div id="<?php echo esc_attr($root_id); ?>" class="obyte-qa-tool" data-obyte-qa-tool> <div id="<?php echo esc_attr($root_id); ?>" class="obyte-qa-tool" data-obyte-qa-tool>
<header class="oqt-header"> <header class="oqt-header">
<div class="oqt-title-block"> <div class="oqt-title-block">
@@ -378,6 +371,7 @@ final class Obyte_QA_Tool
<div class="oqt-message" data-field="message" aria-live="polite"></div> <div class="oqt-message" data-field="message" aria-live="polite"></div>
</section> </section>
</div> </div>
</div>
<?php <?php
return (string) ob_get_clean(); return (string) ob_get_clean();
@@ -385,17 +379,29 @@ final class Obyte_QA_Tool
public function register_admin_page(): void public function register_admin_page(): void
{ {
add_options_page( add_menu_page(
'o-Byte QA Tool',
'o-Byte QA Tool', 'o-Byte QA Tool',
'QA Tool',
'manage_options',
'obyte-qa-tool',
[$this, 'render_admin_page'],
'dashicons-clipboard',
58
);
add_submenu_page(
'obyte-qa-tool',
'o-Byte QA Tool Settings',
'Settings',
'manage_options', 'manage_options',
'obyte-qa-tool', 'obyte-qa-tool',
[$this, 'render_admin_page'] [$this, 'render_admin_page']
); );
add_management_page( add_submenu_page(
'o-Byte QA Reports', 'obyte-qa-tool',
'o-Byte QA Reports', 'o-Byte QA Reports',
'Reports',
'manage_options', 'manage_options',
'obyte-qa-reports', 'obyte-qa-reports',
[$this, 'render_reports_page'] [$this, 'render_reports_page']
@@ -421,7 +427,6 @@ final class Obyte_QA_Tool
$old = $this->settings(); $old = $this->settings();
$out = self::defaults(); $out = self::defaults();
$out['required_capability'] = sanitize_text_field((string) ($input['required_capability'] ?? $old['required_capability']));
$out['default_pbx_version'] = sanitize_text_field((string) ($input['default_pbx_version'] ?? '')); $out['default_pbx_version'] = sanitize_text_field((string) ($input['default_pbx_version'] ?? ''));
$out['gitlab_enabled'] = empty($input['gitlab_enabled']) ? 0 : 1; $out['gitlab_enabled'] = empty($input['gitlab_enabled']) ? 0 : 1;
$out['gitlab_base_url'] = $this->base_url((string) ($input['gitlab_base_url'] ?? '')); $out['gitlab_base_url'] = $this->base_url((string) ($input['gitlab_base_url'] ?? ''));
@@ -467,13 +472,6 @@ final class Obyte_QA_Tool
<h2>Allgemein</h2> <h2>Allgemein</h2>
<table class="form-table" role="presentation"> <table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="oqt-required-capability">Berechtigung</label></th>
<td>
<input id="oqt-required-capability" class="regular-text" type="text" name="<?php echo esc_attr($option); ?>[required_capability]" value="<?php echo esc_attr($settings['required_capability']); ?>">
<p class="description">WordPress Capability für Benutzer, die das Tool nutzen dürfen. Standard: <code>read</code>.</p>
</td>
</tr>
<tr> <tr>
<th scope="row"><label for="oqt-default-pbx-version">Standard PBX-Version</label></th> <th scope="row"><label for="oqt-default-pbx-version">Standard PBX-Version</label></th>
<td> <td>
@@ -535,7 +533,10 @@ final class Obyte_QA_Tool
</tr> </tr>
<tr> <tr>
<th scope="row">PDFs</th> <th scope="row">PDFs</th>
<td><label><input type="checkbox" name="<?php echo esc_attr($option); ?>[media_pdf_enabled]" value="1" <?php checked($settings['media_pdf_enabled']); ?>> Export-PDFs in der WordPress Mediathek speichern</label></td> <td>
<label><input type="checkbox" name="<?php echo esc_attr($option); ?>[media_pdf_enabled]" value="1" <?php checked($settings['media_pdf_enabled']); ?>> Export-PDFs geschützt speichern</label>
<p class="description">PDFs werden nicht als öffentliche Media-Library-Dateien abgelegt. Der Zugriff erfolgt über einmalige Backend-Links.</p>
</td>
</tr> </tr>
</table> </table>
@@ -597,6 +598,7 @@ final class Obyte_QA_Tool
global $wpdb; global $wpdb;
self::create_tables(); self::create_tables();
$reports_table = self::reports_table(); $reports_table = self::reports_table();
$this->migrate_legacy_pdf_attachments(500);
$rows = $wpdb->get_results("SELECT * FROM {$reports_table} ORDER BY created_at DESC LIMIT 100", ARRAY_A); $rows = $wpdb->get_results("SELECT * FROM {$reports_table} ORDER BY created_at DESC LIMIT 100", ARRAY_A);
?> ?>
<div class="wrap obyte-qa-reports"> <div class="wrap obyte-qa-reports">
@@ -621,6 +623,10 @@ final class Obyte_QA_Tool
<tr><td colspan="9">Noch keine QA Reports gespeichert.</td></tr> <tr><td colspan="9">Noch keine QA Reports gespeichert.</td></tr>
<?php else : ?> <?php else : ?>
<?php foreach ($rows as $row) : ?> <?php foreach ($rows as $row) : ?>
<?php
$row = $this->ensure_secure_report_pdf($row);
$pdf_download_url = $this->create_pdf_download_url($row);
?>
<tr> <tr>
<td><?php echo esc_html((string) $row['id']); ?></td> <td><?php echo esc_html((string) $row['id']); ?></td>
<td><?php echo esc_html((string) $row['created_at']); ?></td> <td><?php echo esc_html((string) $row['created_at']); ?></td>
@@ -630,8 +636,8 @@ final class Obyte_QA_Tool
<td><?php echo esc_html((string) $row['tester']); ?></td> <td><?php echo esc_html((string) $row['tester']); ?></td>
<td><?php echo esc_html((string) $row['summary']); ?></td> <td><?php echo esc_html((string) $row['summary']); ?></td>
<td> <td>
<?php if (!empty($row['pdf_url'])) : ?> <?php if ($pdf_download_url !== '') : ?>
<a href="<?php echo esc_url((string) $row['pdf_url']); ?>" target="_blank" rel="noopener">PDF</a> <a href="<?php echo esc_url($pdf_download_url); ?>" target="_blank" rel="noopener">PDF</a>
<?php else : ?> <?php else : ?>
- -
<?php endif; ?> <?php endif; ?>
@@ -654,6 +660,159 @@ final class Obyte_QA_Tool
<?php <?php
} }
public function download_secure_pdf(): void
{
if (!is_user_logged_in() || !current_user_can('manage_options')) {
wp_die(esc_html__('You do not have permission to download this PDF.', 'obyte-qa-tool'), '', ['response' => 403]);
}
$token = isset($_GET['token']) ? sanitize_text_field(wp_unslash((string) $_GET['token'])) : '';
if ($token === '') {
wp_die(esc_html__('PDF download link is invalid.', 'obyte-qa-tool'), '', ['response' => 403]);
}
$transient_key = 'oqt_pdf_' . hash('sha256', $token);
$payload = get_transient($transient_key);
delete_transient($transient_key);
if (!is_array($payload) || (int) ($payload['user_id'] ?? 0) !== get_current_user_id()) {
wp_die(esc_html__('PDF download link has expired.', 'obyte-qa-tool'), '', ['response' => 403]);
}
global $wpdb;
$reports_table = self::reports_table();
$report_id = (int) ($payload['report_id'] ?? 0);
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$reports_table} WHERE id = %d", $report_id), ARRAY_A);
if (!is_array($row) || empty($row['pdf_file'])) {
wp_die(esc_html__('PDF was not found.', 'obyte-qa-tool'), '', ['response' => 404]);
}
$file = $this->secure_pdf_absolute_path((string) $row['pdf_file']);
if ($file === '' || !is_readable($file)) {
wp_die(esc_html__('PDF was not found.', 'obyte-qa-tool'), '', ['response' => 404]);
}
$download_name = sanitize_file_name(sprintf(
'QA-%s-%s-%s.pdf',
$row['module'] ?: 'report',
$row['module_version'] ?: 'version',
$row['pbx_version'] ?: 'pbx'
));
while (ob_get_level()) {
ob_end_clean();
}
nocache_headers();
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $download_name . '"');
$size = filesize($file);
if ($size !== false) {
header('Content-Length: ' . $size);
}
header('X-Content-Type-Options: nosniff');
readfile($file);
exit;
}
private function ensure_secure_report_pdf(array $row): array
{
if (!empty($row['pdf_file'])) {
return $row;
}
$attachment_id = (int) ($row['pdf_attachment_id'] ?? 0);
if ($attachment_id <= 0) {
return $row;
}
$source_file = get_attached_file($attachment_id);
if (!$source_file || !is_readable($source_file)) {
return $row;
}
$stored = $this->store_secure_pdf_file((string) $source_file, basename((string) $source_file), false);
if (is_wp_error($stored)) {
return $row;
}
global $wpdb;
$reports_table = self::reports_table();
$wpdb->update(
$reports_table,
[
'pdf_attachment_id' => 0,
'pdf_url' => '',
'pdf_file' => (string) $stored['file'],
],
['id' => (int) $row['id']],
['%d', '%s', '%s'],
['%d']
);
wp_delete_attachment($attachment_id, true);
$row['pdf_attachment_id'] = 0;
$row['pdf_url'] = '';
$row['pdf_file'] = (string) $stored['file'];
return $row;
}
private function migrate_legacy_pdf_attachments(int $limit): void
{
global $wpdb;
$reports_table = self::reports_table();
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$reports_table} WHERE pdf_attachment_id > 0 AND (pdf_file IS NULL OR pdf_file = '') ORDER BY id DESC LIMIT %d",
max(1, $limit)
),
ARRAY_A
);
if (!is_array($rows)) {
return;
}
foreach ($rows as $row) {
if (is_array($row)) {
$this->ensure_secure_report_pdf($row);
}
}
}
private function create_pdf_download_url(array $row): string
{
if (empty($row['pdf_file'])) {
return '';
}
$file = $this->secure_pdf_absolute_path((string) $row['pdf_file']);
if ($file === '' || !is_readable($file)) {
return '';
}
$token = wp_generate_password(40, false, false);
set_transient(
'oqt_pdf_' . hash('sha256', $token),
[
'report_id' => (int) $row['id'],
'user_id' => get_current_user_id(),
],
10 * MINUTE_IN_SECONDS
);
return add_query_arg(
[
'action' => 'obyte_qa_pdf',
'token' => $token,
],
admin_url('admin-post.php')
);
}
public function register_rest_routes(): void public function register_rest_routes(): void
{ {
register_rest_route(self::REST_NAMESPACE, '/templates', [ register_rest_route(self::REST_NAMESPACE, '/templates', [
@@ -701,8 +860,7 @@ final class Obyte_QA_Tool
public function rest_can_use(): bool public function rest_can_use(): bool
{ {
$settings = $this->settings(); return is_user_logged_in();
return is_user_logged_in() && current_user_can($this->required_capability($settings));
} }
public function rest_list_templates(WP_REST_Request $request) public function rest_list_templates(WP_REST_Request $request)
@@ -832,22 +990,20 @@ final class Obyte_QA_Tool
} }
} }
$attachment_id = 0; $pdf_file = '';
$pdf_url = '';
$files = $request->get_file_params(); $files = $request->get_file_params();
if (!empty($settings['media_pdf_enabled']) && isset($files['pdf']) && is_array($files['pdf']) && (int) ($files['pdf']['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK) { if (!empty($settings['storage_enabled']) && !empty($settings['media_pdf_enabled']) && isset($files['pdf']) && is_array($files['pdf']) && (int) ($files['pdf']['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK) {
$uploaded = $this->store_pdf_attachment($files['pdf'], $run); $uploaded = $this->store_secure_pdf_upload($files['pdf'], $run);
if (is_wp_error($uploaded)) { if (is_wp_error($uploaded)) {
return $uploaded; return $uploaded;
} }
$attachment_id = (int) $uploaded['attachment_id']; $pdf_file = (string) $uploaded['file'];
$pdf_url = (string) $uploaded['url'];
} }
$report_id = 0; $report_id = 0;
$summary = $this->summary_text($run); $summary = $this->summary_text($run);
if (!empty($settings['storage_enabled'])) { if (!empty($settings['storage_enabled'])) {
$stored = $this->store_report($run, $summary, $attachment_id, $pdf_url, $docbee); $stored = $this->store_report($run, $summary, $pdf_file, $docbee);
if (is_wp_error($stored)) { if (is_wp_error($stored)) {
return $stored; return $stored;
} }
@@ -858,8 +1014,7 @@ final class Obyte_QA_Tool
'ok' => true, 'ok' => true,
'report_id' => $report_id, 'report_id' => $report_id,
'summary' => $summary, 'summary' => $summary,
'pdf_attachment_id' => $attachment_id, 'pdf_stored' => $pdf_file !== '',
'pdf_url' => $pdf_url,
'docbee' => $docbee, 'docbee' => $docbee,
]); ]);
} }
@@ -877,16 +1032,25 @@ final class Obyte_QA_Tool
$params = $request->get_json_params(); $params = $request->get_json_params();
$template = is_array($params) && isset($params['template']) && is_array($params['template']) ? $params['template'] : []; $template = is_array($params) && isset($params['template']) && is_array($params['template']) ? $params['template'] : [];
$yaml = is_array($params) ? (string) ($params['yaml'] ?? '') : ''; $yaml = is_array($params) ? (string) ($params['yaml'] ?? '') : '';
$source_path_input = is_array($params) ? (string) ($params['source_path'] ?? '') : '';
$source_path = $this->clean_gitlab_path($source_path_input);
if (empty($template['steps']) || !is_array($template['steps']) || $yaml === '') { if (empty($template['steps']) || !is_array($template['steps']) || $yaml === '') {
return new WP_Error('gitlab_bad_template', 'Template payload is invalid.', ['status' => 400]); return new WP_Error('gitlab_bad_template', 'Template payload is invalid.', ['status' => 400]);
} }
if (empty($template['module']) || empty($template['module_version']) || empty($template['pbx_version'])) { if (empty($template['module']) || empty($template['module_version']) || empty($template['pbx_version'])) {
return new WP_Error('gitlab_missing_template_meta', 'Module, module version, and PBX version are required for GitLab template export.', ['status' => 400]); return new WP_Error('gitlab_missing_template_meta', 'Module, module version, and PBX version are required for GitLab template export.', ['status' => 400]);
} }
if (trim($source_path_input) !== '' && !$this->is_gitlab_template_file_path($source_path, $settings)) {
return new WP_Error('gitlab_bad_path', 'GitLab source path is invalid.', ['status' => 400]);
}
$filename = $this->safe_filename((string) ($template['module'] ?? $template['name'] ?? 'qa-template')) . '.yaml';
$template_path = trim((string) $settings['gitlab_template_path'], '/'); $template_path = trim((string) $settings['gitlab_template_path'], '/');
if ($source_path !== '') {
$path = $source_path;
} else {
$filename = $this->safe_filename((string) ($template['module'] ?? $template['name'] ?? 'qa-template')) . '.yaml';
$path = ($template_path !== '' ? $template_path . '/' : '') . $filename; $path = ($template_path !== '' ? $template_path . '/' : '') . $filename;
}
$file_url = $settings['gitlab_base_url'] . '/api/v4/projects/' . rawurlencode($settings['gitlab_project']) . $file_url = $settings['gitlab_base_url'] . '/api/v4/projects/' . rawurlencode($settings['gitlab_project']) .
'/repository/files/' . rawurlencode($path); '/repository/files/' . rawurlencode($path);
@@ -929,12 +1093,18 @@ final class Obyte_QA_Tool
public function rest_list_reports(WP_REST_Request $request) public function rest_list_reports(WP_REST_Request $request)
{ {
global $wpdb; global $wpdb;
self::create_tables();
$reports_table = self::reports_table(); $reports_table = self::reports_table();
$limit = min(100, max(1, (int) ($request->get_param('limit') ?: 20))); $limit = min(100, max(1, (int) ($request->get_param('limit') ?: 20)));
$rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$reports_table} ORDER BY created_at DESC LIMIT %d", $limit), ARRAY_A); $rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$reports_table} ORDER BY created_at DESC LIMIT %d", $limit), ARRAY_A);
$rows = is_array($rows) ? array_map(function ($row) {
$row['has_pdf'] = !empty($row['pdf_file']) || !empty($row['pdf_attachment_id']);
unset($row['pdf_attachment_id'], $row['pdf_file'], $row['pdf_url']);
return $row;
}, $rows) : [];
return rest_ensure_response([ return rest_ensure_response([
'reports' => is_array($rows) ? $rows : [], 'reports' => $rows,
]); ]);
} }
@@ -951,12 +1121,6 @@ final class Obyte_QA_Tool
return ''; return '';
} }
private function required_capability(array $settings): string
{
$capability = trim((string) ($settings['required_capability'] ?? 'read'));
return $capability !== '' ? $capability : 'read';
}
private function docbee_configured(array $settings): bool private function docbee_configured(array $settings): bool
{ {
return !empty($settings['docbee_base_url']) return !empty($settings['docbee_base_url'])
@@ -1050,7 +1214,7 @@ final class Obyte_QA_Tool
]; ];
} }
private function store_report(array $run, string $summary, int $attachment_id, string $pdf_url, ?array $docbee) private function store_report(array $run, string $summary, string $pdf_file, ?array $docbee)
{ {
global $wpdb; global $wpdb;
@@ -1073,11 +1237,12 @@ final class Obyte_QA_Tool
'docbee_url' => esc_url_raw((string) ($run['docbee_url'] ?? '')), 'docbee_url' => esc_url_raw((string) ($run['docbee_url'] ?? '')),
'docbee_result_url' => esc_url_raw($docbee_result_url), 'docbee_result_url' => esc_url_raw($docbee_result_url),
'summary' => $summary, 'summary' => $summary,
'pdf_attachment_id' => $attachment_id, 'pdf_attachment_id' => 0,
'pdf_url' => esc_url_raw($pdf_url), 'pdf_url' => '',
'pdf_file' => sanitize_text_field($pdf_file),
'run_json' => wp_json_encode($run), 'run_json' => wp_json_encode($run),
], ],
['%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s'] ['%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s']
); );
if (!$inserted) { if (!$inserted) {
@@ -1124,15 +1289,15 @@ final class Obyte_QA_Tool
return $report_id; return $report_id;
} }
private function store_pdf_attachment(array $file, array $run) private function store_secure_pdf_upload(array $file, array $run)
{ {
if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) { if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
return new WP_Error('qa_pdf_upload_failed', 'PDF upload is invalid.', ['status' => 400]); return new WP_Error('qa_pdf_upload_failed', 'PDF upload is invalid.', ['status' => 400]);
} }
require_once ABSPATH . 'wp-admin/includes/file.php'; if ((int) ($file['error'] ?? 0) !== UPLOAD_ERR_OK) {
require_once ABSPATH . 'wp-admin/includes/media.php'; return new WP_Error('qa_pdf_upload_failed', 'PDF upload failed.', ['status' => 400]);
require_once ABSPATH . 'wp-admin/includes/image.php'; }
$filename = sprintf( $filename = sprintf(
'QA-%s-%s-%s-%s.pdf', 'QA-%s-%s-%s-%s.pdf',
@@ -1142,53 +1307,143 @@ final class Obyte_QA_Tool
wp_date('Ymd_His') wp_date('Ymd_His')
); );
$file_array = [ return $this->store_secure_pdf_file((string) $file['tmp_name'], $filename, true);
'name' => $filename,
'type' => 'application/pdf',
'tmp_name' => $file['tmp_name'],
'error' => (int) ($file['error'] ?? 0),
'size' => (int) ($file['size'] ?? 0),
];
$upload = wp_handle_sideload($file_array, [
'test_form' => false,
'mimes' => ['pdf' => 'application/pdf'],
]);
if (!empty($upload['error'])) {
return new WP_Error('qa_pdf_upload_failed', (string) $upload['error'], ['status' => 500]);
} }
$title = 'QA Report'; private function store_secure_pdf_file(string $source_file, string $filename, bool $uploaded)
if (!empty($run['module'])) { {
$title .= ' - ' . $this->plain($run['module']); if (!is_readable($source_file) || !$this->looks_like_pdf($source_file)) {
return new WP_Error('qa_pdf_upload_failed', 'PDF upload is invalid.', ['status' => 400]);
} }
$attachment_id = wp_insert_attachment( $target = $this->secure_pdf_target($filename);
[ if (is_wp_error($target)) {
'post_mime_type' => 'application/pdf', return $target;
'post_title' => $title,
'post_content' => '',
'post_status' => 'inherit',
],
$upload['file']
);
if (is_wp_error($attachment_id) || !$attachment_id) {
return new WP_Error('qa_pdf_attachment_failed', 'PDF could not be added to the Media Library.', ['status' => 500]);
} }
$metadata = wp_generate_attachment_metadata($attachment_id, $upload['file']); if ($uploaded) {
if (is_array($metadata)) { $stored = move_uploaded_file($source_file, $target['path']);
wp_update_attachment_metadata($attachment_id, $metadata); } else {
$stored = copy($source_file, $target['path']);
} }
if (!$stored) {
return new WP_Error('qa_pdf_store_failed', 'PDF could not be stored securely.', ['status' => 500]);
}
@chmod($target['path'], 0640);
return [ return [
'attachment_id' => (int) $attachment_id, 'file' => $target['relative'],
'url' => wp_get_attachment_url($attachment_id) ?: '',
]; ];
} }
private function secure_pdf_base_dir(): string
{
$uploads = wp_upload_dir(null, false);
if (!empty($uploads['error']) || empty($uploads['basedir'])) {
return '';
}
return trailingslashit((string) $uploads['basedir']) . 'obyte-qa-tool-secure';
}
private function secure_pdf_absolute_path(string $relative): string
{
$relative = str_replace('\\', '/', trim(wp_unslash($relative)));
$relative = ltrim($relative, '/');
if (strpos($relative, 'obyte-qa-tool-secure/') !== 0) {
return '';
}
$uploads = wp_upload_dir(null, false);
if (!empty($uploads['error']) || empty($uploads['basedir'])) {
return '';
}
$path = trailingslashit((string) $uploads['basedir']) . $relative;
$real_base = realpath($this->secure_pdf_base_dir());
$real_path = realpath($path);
if (!$real_base || !$real_path) {
return '';
}
$real_base = trailingslashit(wp_normalize_path($real_base));
$real_path = wp_normalize_path($real_path);
return strpos($real_path, $real_base) === 0 ? $real_path : '';
}
private function secure_pdf_target(string $filename)
{
$base_dir = $this->secure_pdf_base_dir();
if ($base_dir === '' || !$this->ensure_secure_pdf_storage($base_dir)) {
return new WP_Error('qa_pdf_store_failed', 'Secure PDF storage could not be created.', ['status' => 500]);
}
$subdir = wp_date('Y/m');
$target_dir = trailingslashit($base_dir) . $subdir;
if (!wp_mkdir_p($target_dir)) {
return new WP_Error('qa_pdf_store_failed', 'Secure PDF storage could not be created.', ['status' => 500]);
}
$name = sanitize_file_name($filename);
if ($name === '' || !preg_match('/\.pdf$/i', $name)) {
$name = 'qa-report.pdf';
}
$name = preg_replace('/\.pdf$/i', '-' . wp_generate_password(16, false, false) . '.pdf', $name);
$name = wp_unique_filename($target_dir, $name);
return [
'path' => trailingslashit($target_dir) . $name,
'relative' => 'obyte-qa-tool-secure/' . $subdir . '/' . $name,
];
}
private function ensure_secure_pdf_storage(string $base_dir): bool
{
if (!wp_mkdir_p($base_dir)) {
return false;
}
$index = trailingslashit($base_dir) . 'index.php';
if (!file_exists($index)) {
if (file_put_contents($index, "<?php\n// Silence is golden.\n") === false) {
return false;
}
}
$htaccess = trailingslashit($base_dir) . '.htaccess';
if (!file_exists($htaccess)) {
if (file_put_contents($htaccess, "Options -Indexes\n<IfModule mod_authz_core.c>\nRequire all denied\n</IfModule>\n<IfModule !mod_authz_core.c>\nDeny from all\n</IfModule>\n") === false) {
return false;
}
}
$web_config = trailingslashit($base_dir) . 'web.config';
if (!file_exists($web_config)) {
if (file_put_contents($web_config, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n <system.webServer>\n <security>\n <authorization>\n <remove users=\"*\" roles=\"\" verbs=\"\" />\n <add accessType=\"Deny\" users=\"*\" />\n </authorization>\n </security>\n </system.webServer>\n</configuration>\n") === false) {
return false;
}
}
return true;
}
private function looks_like_pdf(string $file): bool
{
$handle = fopen($file, 'rb');
if (!$handle) {
return false;
}
$header = fread($handle, 5);
fclose($handle);
return $header === '%PDF-';
}
private function summary_text(array $run): string private function summary_text(array $run): string
{ {
$counts = $this->status_counts((array) ($run['steps'] ?? [])); $counts = $this->status_counts((array) ($run['steps'] ?? []));
@@ -1318,6 +1573,20 @@ final class Obyte_QA_Tool
return $path; return $path;
} }
private function is_gitlab_template_file_path(string $path, array $settings): bool
{
if ($path === '' || !preg_match('/\.ya?ml$/i', $path)) {
return false;
}
$template_path = trim((string) ($settings['gitlab_template_path'] ?? ''), '/');
if ($template_path === '') {
return true;
}
return strpos($path, $template_path . '/') === 0;
}
private function docbee_token(array $settings) private function docbee_token(array $settings)
{ {
$payload = [ $payload = [

View File

@@ -1,460 +0,0 @@
# oByte QA TestprotokollTool (Full README)
**Stand:** 2025-09-04
**Stack:** PHP 8.3 ( Apache ), JS (Vanilla), MariaDB, Docker & Compose, Portainer
Dieses Repository enthält ein vollwertiges QATool mit Login (OIDC), GitLabTemplateLoader, DocBeePosting, PDFErzeugung im Browser und persistenter Speicherung (DB + PDFAblage). Diese README basiert ausschließlich auf dem mitgelieferten Projektstand.
---
## Inhaltsverzeichnis
1. [Überblick](#überblick)
2. [Architektur & Verzeichnisstruktur](#architektur--verzeichnisstruktur)
3. [Funktionen](#funktionen)
4. [UIBedienung (Kurzüberblick)](#ui-bedienung-kurzüberblick)
5. [Datenmodell (DB)](#datenmodell-db)
6. [APIEndpunkte](#api-endpunkte)
7. [Konfiguration per Umgebungsvariablen (.env)](#konfiguration-per-umgebungsvariablen-env)
8. [Lokale Entwicklung](#lokale-entwicklung)
9. [Deployment mit Docker Compose](#deployment-mit-docker-compose)
10. [Deployment mit Portainer (Stack)](#deployment-mit-portainer-stack)
11. [GitLabVorlagen](#gitlab-vorlagen)
12. [DocBeeIntegration](#docbee-integration)
13. [PDFExport](#pdf-export)
14. [Troubleshooting](#troubleshooting)
15. [Sicherheitshinweise](#sicherheitshinweise)
16. [Lizenz / Nutzung](#lizenz--nutzung)
---
## Überblick
- **Ziel:** Manuelle QATestläufe effizient vorbereiten, durchführen und revisionssicher exportieren.
- **Workflow:** Vorlage laden → Metadaten eintragen (inkl. **OLMNummer**) → Schritte bearbeiten (Gruppen, Status, Evidenzen) → **Exportieren (DocBee, DB, PDF)**.
- **Login:** OpenID Connect (konfigurierbar), optional **bypassbar** über `AUTH_DISABLED=true` für lokale Tests.
- **Persistenz:** Reports (Metadaten & Steps) in MariaDB, PDFs serverseitig abgelegt.
- **Integrationen:** GitLab (YAMLVorlagen), DocBee (Login + NotizErzeugung via REST).
---
## Architektur & Verzeichnisstruktur
```
o-byte-qa-tool/
├─ compose/
│ ├─ Dockerfile # PHP 8.3 + Apache, Composer
│ └─ docker-compose.yml # app, mariadb, schema-loader, phpmyadmin, fix-pdf-perms
└─ htdocs/ # Webroot
├─ index.php # App-Entry, Token-Bootstrap für DocBee, UI
├─ app.js # UI-Logik, Exporte, DocBee-Push, GitLab-Loader
├─ style.css # Styles
├─ api/
│ ├─ export.php # Report+Steps in DB speichern, PDF entgegennehmen
│ ├─ echo.php # einfache Echo-Route (Debug)
│ └─ health.php # HealthCheck (DB/PDFPfad)
├─ config/config.php # ENVKonfiguration (OIDC, DocBee, DB, APP_BASE_URL, …)
├─ login.php # OIDCStart
├─ callback.php # OIDCCallback, SessionBootstrap
├─ logout.php # Session reset (OLM beibehalten)
├─ logo.png / logo_light.png
├─ favicon.ico
├─ vendor/ # Composer (z.B. jumbojett/openid-connect-php, phpseclib, …)
└─ oidc/ # phpseclib Assets (lokal eingebunden)
```
---
## Funktionen
### Vorlagen & Schritte
- **Vorlagen laden**: YAML/JSON (lokal per Datei **oder** aus **GitLab**Repo/Pfad/Ref).
- **Gruppen**: Gruppenzeilen, ein-/ausklappbar; Schritte via DragHandle verschiebbar (innerhalb und zwischen Gruppen).
- **Automatische NeuNummerierung** der Steps bei Änderungen/Verschieben.
- **Pflichtschritte** (📌) werden geprüft Export blockiert, wenn offen.
- **Gruppenstatus setzen** auf *pass/fail/skip/blocked*, inkl. **Warnung** bei Überschreiben bestehender Stati.
- **Kommentare & Evidenzen** (Freitext + URL).
### Metadaten
- **Module**, **ModulVersion**, **PBXVersion**, **Tester** (aus Session), **DocBeeTicketURL**, **OLMNummer** (`olm_nummer`).
### Exporte
- **Exportieren (DocBee, DB, PDF)** per **einem** Button:
1. DocBeeNotiz erstellen (falls Token vorhanden) → TicketURL ggf. aktualisiert.
2. **PDF** im Browser generieren (jsPDF+AutoTable).
3. **/api/export.php**: JSON (Run) + PDF (multipart) an Server → DBSpeicherung + PDFAblage.
4. Server sendet `report_id` und `pdf_path` zurück.
- **Lauf speichern / laden** als JSON (Zwischenspeichern/Weiterarbeiten).
- **Template als YAML exportieren** (inkl. `olm_nummer`, Gruppen & Steps).
> Hinweis: Dedizierte Buttons für CSV/MD/PDF wurden in dieser Version **konsolidiert** CSV/MDFunktionen existieren noch im Code, UIseitig aber nicht exponiert.
---
## UIBedienung (Kurzüberblick)
- **GitLab Vorlage**: Dropdown befüllen (`GitLab: …` Tag zeigt Status), Datei wählen → Vorlage wird geladen.
- **Metadaten**: Felder ausfüllen (**OLMNummer** nicht vergessen).
- **Steps**: `+ Step` / `+ Gruppe`, per Griff `⋮⋮` ziehen, Status setzen, Kommentare/Evidenzen pflegen.
- **Exportieren (DocBee, DB, PDF)**: Startet kompletten LaufExport. Ergebnisdialog zeigt Ticket/PDFPfad/ReportID.
---
## Datenmodell (DB)
Beim Export werden folgende Tabellen (idempotent) angelegt/aktualisiert:
**reports**
- `id` BIGINT (PK)
- `created_at` TIMESTAMP (Default `CURRENT_TIMESTAMP`)
- `module` VARCHAR(255)
- `module_version` VARCHAR(100)
- `pbx_version` VARCHAR(100)
- `olm_nummer` VARCHAR(100)
- `tester` VARCHAR(255)
- `docbee_url` TEXT
- `summary` VARCHAR(255)  → z.B. `12/15 pass, 2 fail, 1 skip`
- `pdf_path` TEXT
**steps**
- `id` BIGINT (PK)
- `report_id` BIGINT (FK → reports.id, ON DELETE CASCADE)
- `step_index` INT
- `step_id` VARCHAR(50)
- `title` TEXT
- `expected` TEXT
- `status` ENUM('pass','fail','skip','na','')
- `comment` TEXT
- `evidence` TEXT
- `group_title` VARCHAR(255)
- `group_index` INT
Der **schemaloader** Service in `docker-compose.yml` erstellt die Tabellen automatisch beim ersten Start.
---
## APIEndpunkte
- `GET /api/health.php`
- Prüft PDO / MariaDBErreichbarkeit, Schreibrechte des PDFOrdners.
- `POST /api/export.php` (multipart/form-data)
- **Felder:**
- `run` JSON mit Metadaten (`module`, `module_version`, `pbx_version`, `olm_nummer`, `tester`, `docbee_url`, `ts`, `steps`[…])
- `pdf` erzeugte PDFDatei (optional; Export läuft auch ohne PDF durch)
- **Antwort:** `{{"ok":true,"report_id":<int>,"pdf_path":"…","summary":"…"}}`
- `POST /api/echo.php`
- Debug/Echo.
---
## Konfiguration per Umgebungsvariablen (.env)
Beispiel **.env** (für Compose/Portainer):
```env
# App
APP_PORT=8009
APP_BASE_URL=http://localhost:8009
AUTH_DISABLED=false # true = Login-BYPASS (nur lokal verwenden)
# OpenID Connect (Login)
OIDC_PROVIDER=https://auth.o-byte.com/realms/o-byte.com
OIDC_CLIENT=qa-tool
OIDC_SECRET=<client-secret>
# MariaDB
DB_HOST=mariadb
DB_PORT=3306
DB_NAME=qa_tool
DB_USER=qa
DB_PASS=change_me
DB_ROOT_PASS=change_me_root
# Persistenz
PDF_STORAGE_DIR=/var/reports
# DocBee
DOCBEE_BASEURL=https://obyte.docbee.com
DOCBEE_USER=OBYTE/service
DOCBEE_PASS=<password>
DOCBEE_TIME=1440 # Minuten Token-Gültigkeit
# GitLab Templates
GITLAB_HOST=https://git.steinert.cc
GITLAB_PROJECT_ID=qa/templates # oder numerische ID
GITLAB_REF=main
GITLAB_PATH=templates
GITLAB_TOKEN= # optional (PRIVATE-TOKEN)
# Container-User (Dateirechte)
APP_UID=0
APP_GID=0
# phpMyAdmin Port (optional)
PHPMYADMIN_PORT=8010
```
> **Wichtig:** Keine Secrets in Repos commiten. Für Produktion Token/Passwörter über PortainerUI/Secrets setzen.
---
## Lokale Entwicklung
1. **ComposerAbhängigkeiten** sind bereits im Repo (`vendor/`). Falls nötig:
```bash
cd htdocs && composer install
```
2. **PHPBuiltin** (nur für schnellen Test, ohne DB/PDFAblage):
```bash
php -S 127.0.0.1:8009 -t htdocs
```
3. **LoginBypass** lokal aktivieren:
```bash
export AUTH_DISABLED=true
```
4. Browser: `http://127.0.0.1:8009`
---
## Deployment mit Docker Compose
Im Ordner `compose/` befindet sich ein funktionsfähiges Setup mit:
- **app** (Apache+PHP 8.3), bindmountet `htdocs` und nutzt `PDF_STORAGE_DIR` Volume
- **mariadb** (11.x)
- **schema-loader** (initialisiert Tabellen)
- **phpmyadmin** (optional)
- **fix-pdf-perms** (setzt 0777 auf PDFVolume)
**Starten:**
```bash
cd compose
cp ../.env .env # eigene Werte setzen
docker compose up -d
```
**Zugriff:** `http://<host>:${APP_PORT:-8009}`
**phpMyAdmin:** `http://<host>:${PHPMYADMIN_PORT:-8010}`
---
## Deployment mit Portainer (Stack)
1. **Stack → Add Stack**
2. **Environment Vars** nach obiger `.env` setzen (oder Datei hochladen).
3. **Compose** verwenden (aus `compose/docker-compose.yml`), z.B.:
```yaml
version: "3.8"
services:
app:
image: thecodingmachine/php:8.3-v4-apache
user: "${APP_UID:-0}:${APP_GID:-0}"
ports:
- "${APP_PORT:-8009}:80"
environment:
PHP_EXTENSION_PDO_MYSQL: "1"
APP_BASE_URL: ${APP_BASE_URL}
AUTH_DISABLED: ${AUTH_DISABLED}
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
PDF_STORAGE_DIR: ${PDF_STORAGE_DIR}
OIDC_PROVIDER: ${OIDC_PROVIDER}
OIDC_CLIENT: ${OIDC_CLIENT}
OIDC_SECRET: ${OIDC_SECRET}
DOCBEE_BASEURL: ${DOCBEE_BASEURL}
DOCBEE_USER: ${DOCBEE_USER}
DOCBEE_PASS: ${DOCBEE_PASS}
DOCBEE_TIME: ${DOCBEE_TIME}
GITLAB_HOST: ${GITLAB_HOST}
GITLAB_PROJECT_ID: ${GITLAB_PROJECT_ID}
GITLAB_REF: ${GITLAB_REF}
GITLAB_PATH: ${GITLAB_PATH}
GITLAB_TOKEN: ${GITLAB_TOKEN}
volumes:
- ${APP_HTDOCS_HOST:-/opt/qa-tool/htdocs}:/var/www/html
- pdf_storage:/var/reports
depends_on:
mariadb:
condition: service_healthy
fix-pdf-perms:
condition: service_completed_successfully
restart: unless-stopped
mariadb:
image: mariadb:11
environment:
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASS}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "127.0.0.1", "-p${DB_PASS}"]
interval: 10s
timeout: 5s
retries: 30
start_period: 20s
restart: unless-stopped
schema-loader:
image: mariadb:11
depends_on:
mariadb:
condition: service_healthy
environment:
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
command: >
sh -lc "
echo 'Waiting for DB…'; sleep 3;
mysql -h $DB_HOST -P $DB_PORT -u$DB_USER -p$DB_PASS $DB_NAME <<SQL
CREATE TABLE IF NOT EXISTS reports (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
module VARCHAR(255),
module_version VARCHAR(100),
pbx_version VARCHAR(100),
olm_nummer VARCHAR(100),
tester VARCHAR(255),
docbee_url TEXT,
summary VARCHAR(255),
pdf_path TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS steps (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
report_id BIGINT NOT NULL,
step_index INT,
step_id VARCHAR(50),
title TEXT,
expected TEXT,
status ENUM('pass','fail','skip','na','') DEFAULT '',
comment TEXT,
evidence TEXT,
group_title VARCHAR(255),
group_index INT,
CONSTRAINT fk_steps_report FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SQL
"
restart: "no"
fix-pdf-perms:
image: busybox:1.36
volumes:
- pdf_storage:/target
command: |
sh -lc '
set +e
mkdir -p /target
chmod -R 0777 /target 2>/dev/null || true
chown -R 0:0 /target 2>/dev/null || true
exit 0
'
restart: "no"
phpmyadmin:
image: phpmyadmin:5
ports:
- "${PHPMYADMIN_PORT:-8010}:80"
environment:
PMA_HOST: mariadb
PMA_USER: ${DB_USER}
PMA_PASSWORD: ${DB_PASS}
depends_on:
mariadb:
condition: service_healthy
restart: unless-stopped
volumes:
db_data:
pdf_storage:
```
4. **Deploy Stack** → öffnen: `http://<host>:${APP_PORT}`.
---
## GitLabVorlagen
- Konfiguration via ENV (`GITLAB_HOST`, `GITLAB_PROJECT_ID`, `GITLAB_REF`, `GITLAB_PATH`, `GITLAB_TOKEN`).
- UI lädt per GitLab API die Liste der YAMLDateien aus `GITLAB_PATH` (nur `.yaml`/`.yml`).
- Parser akzeptiert **YAML** und **JSON** (Fallback).
---
## DocBeeIntegration
- Beim Laden von `index.php` wird via `DOCBEE_BASEURL/restApi/login` ein Token geholt und **clientseitig** als `window.DOCBEE_TOKEN` bereitgestellt.
- Exportablauf:
1. **postToDocBee** erstellt eine Notiz im angegebenen **Ticket** (aus `DocBee TicketURL`).
2. Bei Erfolg wird die URL im Lauf aktualisiert.
3. Danach erfolgt ServerExport (DB + optional PDF).
**Erforderliche ENV:** `DOCBEE_BASEURL`, `DOCBEE_USER`, `DOCBEE_PASS`, `DOCBEE_TIME` (Minuten).
---
## PDFExport
- Erzeugung **im Browser** via **jsPDF** + **AutoTable** (CDNFallbacks vorhanden).
- **Logo** `logo_light.png` wird proportional (max 26×12 mm) **rechtsbündig** im Kopf platziert.
- Fallback: Falls Libraries fehlen, wird ein TextPDF erzeugt.
- Server speichert PDFs in `PDF_STORAGE_DIR` (Default `/var/reports`).
---
## YAMLVorlage (Beispiel inkl. OLM)
```yaml
name: "Modul XYZ BasisTests"
module: "Modul XYZ"
module_version: "1.2.3"
pbx_version: "8.1"
olm_nummer: "OLM-12345"
steps:
- type: "group"
title: "Telefonie Basis"
- type: "step"
id: "s1"
title: "Anruf initiieren"
expected: "Ruf wird erfolgreich aufgebaut"
required: true
- type: "step"
id: "s2"
title: "Rufumleitung"
expected: "Ruf wird korrekt umgeleitet"
required: false
```
---
## Troubleshooting
- **LoginLoop / 401:** OIDCKonfiguration prüfen (`OIDC_PROVIDER`, RedirectURL `/callback.php`). Lokal ggf. `AUTH_DISABLED=true`.
- **DocBee 401/403:** `DOCBEE_USER/DOCBEE_PASS/DOCBEE_BASEURL/DOCBEE_TIME` prüfen.
- **DBFehler (PDO not loaded):** Image/Extension prüfen (`PHP_EXTENSION_PDO_MYSQL=1` gesetzt? Compose).
- **Keine Schreibrechte PDF:** Volume/`fix-pdf-perms` prüfen; `PDF_STORAGE_DIR` existiert/schreibbar? `/api/health.php` ansehen.
- **GitLabListe leer:** Pfad/Ref/Token prüfen; nur `.yaml/.yml` werden angezeigt.
- **Export blockiert:** Pflichtschritte haben keinen Status (📌).
---
## Sicherheitshinweise
- **AUTH_DISABLED** ausschließlich in Dev/Stage verwenden.
- DocBeeToken wird **clientseitig** verwendet nur in internen Netzen nutzen oder alternative ServerProxyVariante vorsehen.
- Secrets per PortainerSecrets/ENV setzen, nicht im Repo.
- DBBackups/Retention für `reports`/`steps` einplanen; PDFAblage regelmäßig bereinigen/archivieren.
---
## Lizenz / Nutzung
Interne Nutzung innerhalb oByte / Kundenprojekten. Anpassungen willkommen.

View File

@@ -1,45 +0,0 @@
# ====== QA-Tool .env (Beispiel) ======
# Port-Mapping (Host:Container)
APP_PORT=8009
# Host-Pfad zu htdocs (Code); in PROD auf readonly Mount achten
APP_HTDOCS_HOST=/opt/qa-tool/htdocs
# Basis-URL der App (ohne trailing slash)
APP_BASE_URL=http://192.168.1.112:8009
# Authentifizierung umgehen (nur lokal/test): true/false
AUTH_DISABLED=false
# --- OpenID Connect ---
OIDC_PROVIDER=https://auth.o-byte.com/realms/o-byte.com
OIDC_CLIENT=qa-tool
OIDC_SECRET=PLEASE_SET_ME
# --- DocBee ---
DOCBEE_BASEURL=https://obyte.docbee.com
DOCBEE_USER=OBYTE/service
DOCBEE_PASS=PLEASE_SET_ME
DOCBEE_TIME=1440
# --- GitLab (Templates) ---
GITLAB_HOST=https://git.steinert.cc
GITLAB_PROJECT_ID=qa/templates
GITLAB_REF=main
GITLAB_PATH=templates
# optional, leer lassen wenn Repo public ist
GITLAB_TOKEN=
# --- Database ---
DB_HOST=mariadb
DB_PORT=3306
DB_NAME=qa_tool
DB_USER=qa
DB_PASS=PLEASE_SET_ME
DB_ROOT_PASS=PLEASE_SET_ME_ROOT
# --- phpMyAdmin ---
PHPMYADMIN_PORT=8010

View File

@@ -1,24 +0,0 @@
# Verwende das offizielle PHP-Image mit Apache und PHP 8.3
FROM php:8.3-apache
# Enable Apache modules
RUN a2enmod rewrite
# Beispiel für Debian/Ubuntu-basierte Images
RUN apt-get update && apt-get install -y && apt install -y nano mc libssl-dev \
zip \
unzip \
git && docker-php-ext-install mysqli
# Installiere Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Setze das Arbeitsverzeichnis
WORKDIR /var/www/html
# Installiere die benötigte Bibliothek (leeres Verzeichnis, um Abhängigkeiten zu verwalten)
RUN mkdir -p /var/www/html
#RUN composer require jumbojett/openid-connect-php
# Exponiere Port 8009
EXPOSE 8009

View File

@@ -1,29 +0,0 @@
-- qa-tool schema
CREATE TABLE IF NOT EXISTS reports (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
module VARCHAR(255),
module_version VARCHAR(100),
pbx_version VARCHAR(100),
olm_nummer VARCHAR(100),
tester VARCHAR(255),
docbee_url TEXT,
summary VARCHAR(255),
pdf_path TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS steps (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
report_id BIGINT NOT NULL,
step_index INT,
step_id VARCHAR(50),
title TEXT,
expected TEXT,
status ENUM('pass','fail','skip','na','') DEFAULT '',
comment TEXT,
evidence TEXT,
group_title VARCHAR(255),
group_index INT,
CONSTRAINT fk_steps_report FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@@ -1,128 +0,0 @@
version: "3.8"
services:
app:
image: thecodingmachine/php:8.3-v4-apache
user: "${APP_UID:-0}:${APP_GID:-0}" # root zum Stabilisieren; später gern 33:33
ports:
- "${APP_PORT:-8009}:80"
environment:
PHP_EXTENSION_PDO_MYSQL: "1"
APP_BASE_URL: ${APP_BASE_URL:-http://localhost:8009}
AUTH_DISABLED: ${AUTH_DISABLED:-false}
# DB
DB_HOST: ${DB_HOST:-mariadb}
DB_PORT: ${DB_PORT:-3306}
DB_NAME: ${DB_NAME:-qa_tool}
DB_USER: ${DB_USER:-qa}
DB_PASS: ${DB_PASS:-change_me}
# STORAGE außerhalb des Code-Mounts
PDF_STORAGE_DIR: ${PDF_STORAGE_DIR:-/var/reports}
volumes:
# CODE (Bind-Mount) passe den Hostpfad an
- ${APP_HTDOCS_HOST:-/opt/qa-tool/htdocs}:/var/www/html
# STORAGE (Named Volume, **RW**!)
- pdf_storage:/var/reports
depends_on:
mariadb:
condition: service_healthy
fix-pdf-perms:
condition: service_completed_successfully
restart: unless-stopped
mariadb:
image: mariadb:11
environment:
MYSQL_DATABASE: ${DB_NAME:-qa_tool}
MYSQL_USER: ${DB_USER:-qa}
MYSQL_PASSWORD: ${DB_PASS:-change_me}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS:-change_me_root}
volumes:
- db_data:/var/lib/mysql
restart: unless-stopped
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "127.0.0.1", "-p${DB_PASS:-change_me}"]
interval: 10s
timeout: 5s
retries: 30
start_period: 20s
schema-loader:
image: mariadb:11
depends_on:
mariadb:
condition: service_healthy
environment:
DB_HOST: ${DB_HOST:-mariadb}
DB_PORT: ${DB_PORT:-3306}
DB_NAME: ${DB_NAME:-qa_tool}
DB_USER: ${DB_USER:-qa}
DB_PASS: ${DB_PASS:-change_me}
command:
- bash
- -lc
- |
until mariadb-admin -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" ping --silent; do sleep 2; done
mariadb -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" <<'SQL'
CREATE TABLE IF NOT EXISTS reports (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
module VARCHAR(255),
module_version VARCHAR(100),
pbx_version VARCHAR(100),
olm_nummer VARCHAR(100),
tester VARCHAR(255),
docbee_url TEXT,
summary VARCHAR(255),
pdf_path TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS steps (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
report_id BIGINT NOT NULL,
step_index INT,
step_id VARCHAR(50),
title TEXT,
expected TEXT,
status ENUM('pass','fail','skip','na','') DEFAULT '',
comment TEXT,
evidence TEXT,
group_title VARCHAR(255),
group_index INT,
CONSTRAINT fk_steps_report FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SQL
restart: "no"
# Rechte-Fix fürs Storage-Volume nie failen, maximal permissiv
fix-pdf-perms:
image: busybox:1.36
volumes:
- pdf_storage:/target
command: |
sh -lc '
set +e
mkdir -p /target
chmod -R 0777 /target 2>/dev/null || true
chown -R 0:0 /target 2>/dev/null || true
exit 0
'
restart: "no"
phpmyadmin:
image: phpmyadmin:5
ports:
- "${PHPMYADMIN_PORT:-8010}:80"
environment:
PMA_HOST: mariadb
PMA_USER: ${DB_USER:-qa}
PMA_PASSWORD: ${DB_PASS:-change_me}
depends_on:
mariadb:
condition: service_healthy
restart: unless-stopped
volumes:
db_data:
pdf_storage:

View File

@@ -1,85 +0,0 @@
# o-Byte QA Testprotokoll-Tool
Ein leichtgewichtiges Web-Tool für manuelle QA-Tests, entwickelt für kleine Teams.
Das Tool läuft **lokal im Browser** und benötigt keinen Server, keine Logins und keine Datenbank.
---
## Features
- **Vorlagen laden** (YAML oder JSON)
- **Steps dynamisch hinzufügen / löschen**
- Pflichtschritte 📌 markieren (mit Check, ob alle required Steps bearbeitet wurden)
- **Metadaten erfassen:** Modul, Modul-Version, PBX-Version, Tester, DocBee-URL
- **Ergebnis eintragen:** pass ✅, fail ❌, skip ⏭️, blocked ⛔
- **Evidenz-Links** hinterlegen (z. B. Screenshots, Tickets, Nextcloud-Dateien)
- **Exporte:**
- Markdown (`.md`)
- CSV (`.csv`)
- PDF (mit Logo, Farben, Emojis, klickbaren Links)
- YAML-Template (nur Vorlage ohne Testergebnisse)
- JSON (Lauf speichern und wieder laden)
- DocBee (Report als DocBee Nachricht posten)
---
## Installation & Nutzung
1. Repository klonen oder ZIP entpacken:
```bash
git clone <repo-url>
cd o-byte-qa-tool
```
2. Dateien liegen im Projektordner:
- `index.html`
- `style.css`
- `app.js`
- `logo.png` (für UI)
- `logo_light.png` (für PDF-Export)
- `favicon.ico`
3. Öffne `index.html` direkt im Browser (Doppelklick oder via `file://`).
4. Vorlage (`.yaml`) laden → Tests ausführen → Ergebnisse exportieren.
---
## YAML-Vorlage (Beispiel)
```yaml
name: "Modul XYZ Basis-Tests"
module: "Modul XYZ"
module_version: "1.2.3"
pbx_version: "8.1"
steps:
- id: "s1"
title: "Anruf initiieren"
expected: "Ruf wird erfolgreich aufgebaut"
required: true
- id: "s2"
title: "Rufumleitung"
expected: "Ruf wird korrekt umgeleitet"
required: false
```
---
## Exportformate
- **Markdown:** Übersicht für Doku oder Git-Repos
- **CSV:** Für Excel oder Datenanalyse
- **PDF:** Fertiges Protokoll (inkl. Logo, Farben, Emojis)
- **YAML:** Neue Testvorlage (nur Struktur, keine Ergebnisse)
- **JSON:** Gesamter Testlauf (inkl. Ergebnisse) → kann wieder eingelesen werden
---
## Hinweise
- Pflichtschritte sind mit 📌 markiert und müssen einen Status haben, sonst wird kein Export zugelassen.
- Evidenz-URLs können beliebige Links sein (http/https, Tickets, Dateien im Intranet).
- Alle Daten bleiben **lokal** im Browser; es gibt keinen Server und keine externe Speicherung.
- Getestet mit **Chrome** und **Edge**. Andere Browser funktionieren meist, sind aber nicht primär getestet.
---
## Lizenz
Interne Nutzung. Erweiterungen und Anpassungen nach Bedarf.

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'ok'=>true,
'method'=>$_SERVER['REQUEST_METHOD'] ?? null,
'post'=>$_POST,
'files'=>array_map(fn($f)=>['name'=>$f['name']??null,'size'=>$f['size']??null,'type'=>$f['type']??null,'error'=>$f['error']??null], $_FILES ?? [])
]);

View File

@@ -1,162 +0,0 @@
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__ . '/../config/config.php';
try {
if (getenv('APP_DEBUG') === 'true') { ini_set('display_errors', '1'); error_reporting(E_ALL); }
if (!class_exists('PDO')) { throw new Exception('PDO extension not loaded'); }
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['ok'=>false, 'error'=>'Method not allowed']);
exit;
}
if (!isset($_POST['run'])) {
http_response_code(400);
echo json_encode(['ok'=>false, 'error'=>'Missing run']);
exit;
}
// Eingabe parsen
$run = json_decode($_POST['run'], true, 512, JSON_THROW_ON_ERROR);
// Optional: PDF speichern
$savedPdf = null;
if (isset($_FILES['pdf']) && $_FILES['pdf']['error'] === UPLOAD_ERR_OK) {
$ext = '.pdf';
$fname = sprintf(
'QA-%s-%s-%s-%s%s',
preg_replace('~[^a-zA-Z0-9_-]+~','_', $run['module'] ?? 'mod'),
preg_replace('~[^a-zA-Z0-9_-]+~','_', $run['module_version'] ?? 'v'),
preg_replace('~[^a-zA-Z0-9_-]+~','_', $run['pbx_version'] ?? 'pbx'),
date('Ymd_His'),
$ext
);
$destDir = rtrim($pdf_storage_dir ?? (__DIR__ . '/../storage/reports'), '/');
if (!is_dir($destDir)) { @mkdir($destDir, 0775, true); }
$dest = $destDir . '/' . $fname;
if (!@move_uploaded_file($_FILES['pdf']['tmp_name'], $dest)) {
$savedPdf = null; // PDF optional; DB-Export läuft trotzdem weiter
}
$savedPdf = $dest;
}
// DB verbinden
$dsn = "mysql:host={$db_host};port={$db_port};dbname={$db_name};charset=utf8mb4";
$pdo = new PDO($dsn, $db_user, $db_pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
// Schema sicherstellen (idempotent)
$pdo->exec("
CREATE TABLE IF NOT EXISTS reports (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
module VARCHAR(255),
module_version VARCHAR(100),
pbx_version VARCHAR(100),
olm_nummer VARCHAR(100),
tester VARCHAR(255),
docbee_url TEXT,
summary VARCHAR(255),
pdf_path TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS steps (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
report_id BIGINT NOT NULL,
step_index INT,
step_id VARCHAR(50),
title TEXT,
expected TEXT,
status ENUM('pass','fail','skip','na','') DEFAULT '',
comment TEXT,
evidence TEXT,
group_title VARCHAR(255),
group_index INT,
CONSTRAINT fk_steps_report FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
// Summary berechnen
$total=0;$pass=0;$fail=0;$skip=0;
foreach (($run['steps'] ?? []) as $s) {
if (($s['kind'] ?? 'step') === 'step') {
$total++;
$st = $s['status'] ?? '';
if ($st==='pass') $pass++;
elseif ($st==='fail') $fail++;
elseif ($st==='skip') $skip++;
}
}
$summary = sprintf('%d/%d pass, %d fail, %d skip', $pass, $total, $fail, $skip);
// Report speichern
$stmt = $pdo->prepare("INSERT INTO reports
(module, module_version, pbx_version, olm_nummer, tester, docbee_url, summary, pdf_path)
VALUES (:module, :module_version, :pbx_version, :olm_nummer, :tester, :docbee_url, :summary, :pdf_path)");
$stmt->execute([
':module' => $run['module'] ?? null,
':module_version' => $run['module_version'] ?? null,
':pbx_version' => $run['pbx_version'] ?? null,
':olm_nummer' => $run['olm_nummer'] ?? null,
':tester' => $run['tester'] ?? null,
':docbee_url' => $run['docbee_url'] ?? null,
':summary' => $summary,
':pdf_path' => $savedPdf
]);
$reportId = (int)$pdo->lastInsertId();
// Steps speichern
$idx = 0; $gidx = -1; $currentGroup = null;
$ins = $pdo->prepare("INSERT INTO steps
(report_id, step_index, step_id, title, expected, status, comment, evidence, group_title, group_index)
VALUES (:report_id,:step_index,:step_id,:title,:expected,:status,:comment,:evidence,:group_title,:group_index)");
foreach (($run['steps'] ?? []) as $s) {
if (($s['kind'] ?? 'step') === 'group') {
$currentGroup = $s['title'] ?? null;
$gidx++;
continue;
}
$idx++;
// status normalisieren → ENUM-kompatibel
$rawStatus = strtolower(trim((string)($s['status'] ?? '')));
$map = [
'ok'=>'pass','passed'=>'pass','success'=>'pass','true'=>'pass','yes'=>'pass','✔'=>'pass','✓'=>'pass',
'ko'=>'fail','failed'=>'fail','error'=>'fail','✗'=>'fail','x'=>'fail',
'skipped'=>'skip',
'n/a'=>'na','not applicable'=>'na',
'block'=>'blocked','blocked'=>'blocked','⛔'=>'blocked'
];
$st = $map[$rawStatus] ?? (in_array($rawStatus, ['pass','fail','skip','na','blocked',''], true) ? $rawStatus : '');
// und beim Insert:
// ':status' => $st,
$st = $map[$rawStatus] ?? (in_array($rawStatus, ['pass','fail','skip','na',''], true) ? $rawStatus : '');
try {
$ins->execute([
':report_id' => $reportId,
':step_index' => $idx,
':step_id' => $s['id'] ?? null,
':title' => $s['title'] ?? null,
':expected' => $s['expected'] ?? null,
':status' => $st,
':comment' => $s['comment'] ?? null,
':evidence' => $s['evidence'] ?? null,
':group_title' => $currentGroup,
':group_index' => $gidx >= 0 ? $gidx : null,
]);
} catch (PDOException $e) {
if ($e->getCode() !== '01000') { throw $e; } // ENUM-Truncation-Warning ignorieren
}
}
echo json_encode(['ok'=>true, 'report_id'=>$reportId, 'pdf_path'=>$savedPdf, 'summary'=>$summary]);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode(['ok'=>false, 'error'=>$e->getMessage()]);
}

View File

@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
$status = ['ok'=>true, 'checks'=>[]];
try {
require_once __DIR__ . '/../config/config.php';
$status['checks']['php'] = ['pdo'=>extension_loaded('pdo'), 'pdo_mysql'=>extension_loaded('pdo_mysql')];
// DB-Ping
$dsn = "mysql:host={$db_host};port={$db_port};dbname={$db_name};charset=utf8mb4";
$pdo = new PDO($dsn, $db_user, $db_pass, [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$pdo->query('SELECT 1');
$status['checks']['db'] = 'ok';
// Schreibtest PDF-Pfad (ohne Datei anzulegen)
$dir = rtrim($pdf_storage_dir ?? (__DIR__.'/../storage/reports'), '/');
$status['checks']['pdf_dir'] = ['path'=>$dir, 'exists'=>is_dir($dir), 'writable'=>is_writable($dir)];
$probe = @file_put_contents($dir.'/.__probe__.tmp', 'x');
if ($probe !== false) { @unlink($dir.'/.__probe__.tmp'); }
$status['checks']['pdf_dir']['probe_write'] = ($probe !== false);
} catch (Throwable $e) {
$status['ok'] = false;
$status['error'] = $e->getMessage();
}
echo json_encode($status);

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +0,0 @@
<?php
require_once 'vendor/autoload.php';
include 'config/config.php';
use Jumbojett\OpenIDConnectClient;
session_start();
// OpenID Connect Client konfigurieren
$oidc = new OpenIDConnectClient(
$oidc_provider, // OpenID Provider URL
$oidc_client, // Client ID
$oidc_secret // Client Secret
);
// Weiterleitungen konfigurieren
$oidc->setRedirectURL($OIDC_REDIRECT_URL);
// Scopes als Array hinzufügen
$oidc->addScope(['openid', 'profile', 'email', 'groups']);
// Benutzer authentifizieren
$oidc->authenticate();
// Benutzerinformationen abrufen
$_SESSION['user_authenticated'] = true;
$_SESSION['user_name'] = $oidc->requestUserInfo('name');
$_SESSION['user_email'] = $oidc->requestUserInfo('email');
$_SESSION['groups'] = $oidc->requestUserInfo('groups');
// Zurück zur Hauptseite
header('Location: index.php');
exit();
?>

View File

@@ -1,6 +0,0 @@
{
"require": {
"litesaml/lightsaml": "^4.2",
"jumbojett/openid-connect-php": "^1.0"
}
}

View File

@@ -1,681 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e5120dce5ca198fc0f926511014570d9",
"packages": [
{
"name": "jumbojett/openid-connect-php",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/jumbojett/OpenID-Connect-PHP.git",
"reference": "4af1d11497ec765dccddf928c14c04535ca96017"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/jumbojett/OpenID-Connect-PHP/zipball/4af1d11497ec765dccddf928c14c04535ca96017",
"reference": "4af1d11497ec765dccddf928c14c04535ca96017",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"php": ">=7.0",
"phpseclib/phpseclib": "~3.0"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
"yoast/phpunit-polyfills": "^1.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"description": "Bare-bones OpenID Connect client",
"support": {
"issues": "https://github.com/jumbojett/OpenID-Connect-PHP/issues",
"source": "https://github.com/jumbojett/OpenID-Connect-PHP/tree/v1.0.0"
},
"time": "2023-12-13T09:52:12+00:00"
},
{
"name": "litesaml/lightsaml",
"version": "v4.2.0",
"source": {
"type": "git",
"url": "https://github.com/litesaml/lightsaml.git",
"reference": "da88de24e699418918a128f0142dd09e2c41dd38"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/litesaml/lightsaml/zipball/da88de24e699418918a128f0142dd09e2c41dd38",
"reference": "da88de24e699418918a128f0142dd09e2c41dd38",
"shasum": ""
},
"require": {
"php": ">=7.4",
"psr/event-dispatcher": "^1.0",
"robrichards/xmlseclibs": "~2.0|~3.0|~4.0",
"symfony/http-foundation": "~5.0|~6.0|~7.0"
},
"require-dev": {
"litesaml/schemas": "~1.0.0",
"marcocesarato/php-conventional-changelog": "^1.15",
"monolog/monolog": "^2.0|^3.0",
"phpstan/phpstan": "^1.8",
"phpunit/phpunit": "~8.4|~9.5",
"pimple/pimple": "~3.0",
"squizlabs/php_codesniffer": "^3.6",
"symfony/css-selector": "~5.0|~6.0|~7.0",
"symfony/dom-crawler": "~5.0|~6.0|~7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"LightSaml\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "William",
"email": "work@suppo.fr"
},
{
"name": "Milos Tomic",
"email": "tmilos@gmail.com",
"homepage": "https://github.com/tmilos/",
"role": "Developer"
}
],
"description": "SAML 2.0 PHP library",
"keywords": [
"SAML 2.0",
"Single Logout",
"Single SignOn",
"library",
"lightSAML",
"php"
],
"support": {
"docs": "https://docs.litesaml.com",
"issues": "https://github.com/litesaml/lightsaml/issues",
"source": "https://github.com/litesaml/lightsaml"
},
"time": "2024-02-08T11:59:00+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "df1e7fde177501eee2037dd159cf04f5f301a512"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512",
"reference": "df1e7fde177501eee2037dd159cf04f5f301a512",
"shasum": ""
},
"require": {
"php": "^8"
},
"require-dev": {
"phpunit/phpunit": "^9",
"vimeo/psalm": "^4|^5"
},
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"time": "2024-05-08T12:36:18+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.41",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
"reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
"shasum": ""
},
"require": {
"paragonie/constant_time_encoding": "^1|^2|^3",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": ">=5.6.1"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.41"
},
"funding": [
{
"url": "https://github.com/terrafrost",
"type": "github"
},
{
"url": "https://www.patreon.com/phpseclib",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
"type": "tidelift"
}
],
"time": "2024-08-12T00:13:54+00:00"
},
{
"name": "psr/event-dispatcher",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/event-dispatcher.git",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Standard interfaces for event handling.",
"keywords": [
"events",
"psr",
"psr-14"
],
"support": {
"issues": "https://github.com/php-fig/event-dispatcher/issues",
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
},
"time": "2019-01-08T18:20:26+00:00"
},
{
"name": "robrichards/xmlseclibs",
"version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/robrichards/xmlseclibs.git",
"reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/f8f19e58f26cdb42c54b214ff8a820760292f8df",
"reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"php": ">= 5.4"
},
"type": "library",
"autoload": {
"psr-4": {
"RobRichards\\XMLSecLibs\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "A PHP library for XML Security",
"homepage": "https://github.com/robrichards/xmlseclibs",
"keywords": [
"security",
"signature",
"xml",
"xmldsig"
],
"support": {
"issues": "https://github.com/robrichards/xmlseclibs/issues",
"source": "https://github.com/robrichards/xmlseclibs/tree/3.1.1"
},
"time": "2020-09-05T13:00:25+00:00"
},
{
"name": "symfony/http-foundation",
"version": "v7.1.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
"reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-mbstring": "~1.1",
"symfony/polyfill-php83": "^1.27"
},
"conflict": {
"doctrine/dbal": "<3.6",
"symfony/cache": "<6.4"
},
"require-dev": {
"doctrine/dbal": "^3.6|^4",
"predis/predis": "^1.1|^2.0",
"symfony/cache": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/expression-language": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/mime": "^6.4|^7.0",
"symfony/rate-limiter": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\HttpFoundation\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.1.3"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-07-26T12:41:01+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
},
{
"name": "symfony/polyfill-php83",
"version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
"reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
"reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php83\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-06-19T12:35:24+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.6.0"
}

View File

@@ -1,35 +0,0 @@
<?php
//OpenID (aus ENV, mit Fallbacks auf bisherige Werte)
$oidc_provider = getenv('OIDC_PROVIDER') ?: 'https://auth.o-byte.com/realms/o-byte.com';
$oidc_client = getenv('OIDC_CLIENT') ?: 'qa-tool';
$oidc_secret = getenv('OIDC_SECRET') ?: 'vuTWMfJybjg9yCn9awxBUUIxqqHTj35O';
// DocBee (aus ENV, mit Fallbacks auf bisherige Werte)
$docbee_baseurl = getenv('DOCBEE_BASEURL') ?: 'https://obyte.docbee.com';
$docbee_user = getenv('DOCBEE_USER') ?: 'OBYTE/service';
$docbee_pass = getenv('DOCBEE_PASS') ?: 'mBG9ZrC*fh@*co';
$docbee_time = getenv('DOCBEE_TIME') ?: '1440';
/**
* App-Basis-URL aus ENV (Fallback auf lokale IP:8009)
* Beispiel: http://192.168.1.112:8009 oder https://qa.o-byte.team
*/
$APP_BASE_URL = getenv('APP_BASE_URL') ?: 'http://192.168.1.112:8009';
$APP_BASE_URL = rtrim($APP_BASE_URL, '/'); // sauber ohne trailing slash
/** Ableiten der Redirect-URL für OIDC (login.php / callback.php) */
$OIDC_REDIRECT_URL = $APP_BASE_URL . '/callback.php';
// Database (from ENV)
$db_host = getenv('DB_HOST') ?: 'mariadb';
$db_port = getenv('DB_PORT') ?: '3306';
$db_name = getenv('DB_NAME') ?: 'qa_tool';
$db_user = getenv('DB_USER') ?: 'qa';
$db_pass = getenv('DB_PASS') ?: '';
$pdf_storage_dir = rtrim(getenv('PDF_STORAGE_DIR') ?: '/var/reports', '/');
if (!is_dir($pdf_storage_dir)) { @mkdir($pdf_storage_dir, 0775, true); }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,176 +0,0 @@
<?php
require_once 'vendor/autoload.php';
use Jumbojett\OpenIDConnectClient;
session_start();
include_once("config/config.php");
// === AUTH BYPASS FOR LOCAL TESTING ===
if (getenv('AUTH_DISABLED') === 'true') {
$_SESSION['user_authenticated'] = true;
if (empty($_SESSION['user_name'])) {
$_SESSION['user_name'] = 'localtest';
}
}
// === END AUTH BYPASS ===
if (isset($_SESSION['user_authenticated']) && $_SESSION['user_authenticated'] === true) {
// #######################
//DocBee Token genrieren
// #######################
$loginEndpoint = $docbee_baseurl . '/restApi/login';
$payload = [
'username' => $docbee_user, // Benutzername
'password' => $docbee_pass, // Passwort
'lifeTime' => $docbee_time, // Gültigkeit in Minuten
'lifeTimeRefresh' => true // Token-Lebenszeit bei jedem Call verlängern
];
$ch = curl_init($loginEndpoint);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE),
CURLOPT_TIMEOUT => 20,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) {
throw new RuntimeException('cURL error: ' . curl_error($ch));
}
curl_close($ch);
if ($httpCode !== 200) {
// 401 bei falschen Credentials usw.
throw new RuntimeException("Login fehlgeschlagen, HTTP $httpCode: $response");
}
$data = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
$docbee_token = $data['access_token'] ?? null;
if (!$docbee_token) {
throw new RuntimeException('Kein access_token in der Antwort gefunden.');
}
?>
<!--
index.html — aufgeräumt & ausführlich kommentiert
Projekt: QA System (Test-Template-Lader, Runner & Exporter)
Firma: o-byte.com telekommunikation
Stand: 2025-09-01T17:35:27
Hinweis:
- Funktionen sind mit JSDoc kommentiert (Parameter/Return, Nebenwirkungen).
- Größere Codeabschnitte haben Abschnittsbanner.
- Keine funktionalen Änderungen, nur Kommentare & leichte Struktur.
-->
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>QA System</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="style.css">
<link rel="icon" href="favicon.ico" type="image/x-icon">
</head>
<body>
<!-- Kopfbereich mit Branding und Status -->
<header>
<div class="brand">
<img src="logo.png" alt="Logo" class="logo"
onerror="this.onerror=null;this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22160%22 height=%2236%22 viewBox=%220 0 160 36%22%3E%3Crect width=%22160%22 height=%2236%22 rx=%226%22 fill=%22%23141722%22/%3E%3Ctext x=%229%22 y=%2224%22 font-family=%22Arial,Helvetica,sans-serif%22 font-size=%2216%22 fill=%22%23e7e7ea%22%3EQA Lite%3C/text%3E%3C/svg%3E';">
<h1>QA System</h1>
</div>
<div class="tags">
<span id="tplName" class="tag"></span>
<span id="statusTag" class="tag">Keine Vorlage geladen</span>
</div>
</header>
<!-- Metadaten des Testlaufs (Name/Modul/Versionen/Tester/Status) -->
<section class="meta">
<div class="row">
<label>GitLab Vorlage
<select id="gitlabTplSelect" style="min-width:320px;">
<option value="">— GitLab-Templates laden —</option>
</select>
</label>
<button class="btn" id="btnGitlabReload" title="Liste neu laden">Neu laden</button>
<span id="gitlabTplStatus" class="tag">GitLab: nicht verbunden</span>
<span id="docbeeTokenStatus" class="tag">DocBee: unbekannt</span>
</div>
<label>Modul <input id="module" placeholder="z. B. CallRouting"></label>
<label>Modul-Version <input id="moduleVersion" placeholder="z. B. 1.2.3"></label>
<label>OLM-Nummer <input id="olmNummer" placeholder="z. B. OLM-12345"></label>
<label>PBX-Version <input id="pbxVersion" placeholder="z. B. 8.1.2"></label>
<label>Tester <input id="tester" value="<?php echo $_SESSION['user_name']; ?>" readonly></label>
<label>DocBee Ticket-URL <input id="docbeeUrl" placeholder="https://docbee…"></label>
</section>
<section>
<table id="stepsTable">
<thead>
<tr>
<th>Step</th>
<th>Expected</th>
<th>Status</th>
<th>Kommentar/Evidenz</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="steps-actions">
<button class="btn" id="btnAddStep">+ Step hinzufügen</button>
<button class="btn" id="btnAddGroup">+ Gruppe hinzufügen</button>
</div>
</section>
<!-- Aktionen/Exporte & DocBee-Push -->
<section class="actions">
<button class="btn" id="btnSaveJSON">Als JSON speichern</button>
<label class="btn like-label" for="loadJSON">Lauf laden</label>
<input type="file" id="loadJSON" accept=".json,.yaml,.yml" />
<button class="btn primary" id="btnExportAll">Exportieren (DocBee, DB, PDF)</button>
<button class="btn" id="btnExportTemplateYAML">Template als YAML</button>
</section>
<!-- YAML Parser (js-yaml) für Template-Import -->
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<!-- App-Logik: UI-Steuerung, Parser, Exporte, DocBee-Integration -->
<script>
// Token in globales JS-Objekt setzen
window.DOCBEE_TOKEN = "<?php echo $docbee_token; ?>";
</script>
<script>
window.GITLAB = {
host: <?php echo json_encode(getenv('GITLAB_HOST') ?: 'https://git.steinert.cc'); ?>,
projectId: <?php echo json_encode(getenv('GITLAB_PROJECT_ID') ?: 'qa/templates'); ?>,
ref: <?php echo json_encode(getenv('GITLAB_REF') ?: 'main'); ?>,
path: <?php echo json_encode(getenv('GITLAB_PATH') ?: 'templates'); ?>,
token: <?php echo json_encode(getenv('GITLAB_TOKEN') ?: ''); ?>
};
window.DOCBEE_BASEURL = <?php echo json_encode(getenv('DOCBEE_BASEURL') ?: 'https://obyte.docbee.com'); ?>;
</script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf-autotable@3.8.2/dist/jspdf.plugin.autotable.min.js"></script>
<script src="app.js?v=<?=filemtime(__DIR__.('/app.js'))?>"></script>
</body>
</html>
<?php
}
else {
header("Location: login.php");
exit();
}
?>

View File

@@ -1,151 +0,0 @@
// auto-split module
function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
async function getJSON(url) {
const r = await fetch(url, {
method: "GET",
headers: {
"Authorization": "Bearer " + DOCBEE_TOKEN,
"Accept": "application/json"
}
});
const text = await r.text().catch(() => "-");
let json = null;
try {
json = JSON.parse(text);
} catch {}
console.log("[DocBee][GET]", url);
console.log("[DocBee][RES]", r.status, text.slice(0, 700));
return {
ok: r.ok,
status: r.status,
json,
text
};
}
async function putJSON(url, body) {
const r = await fetch(url, {
method: "PUT",
headers: {
"Authorization": "Bearer " + DOCBEE_TOKEN,
"Content-Type": "application/json; charset=utf-8",
"Accept": "application/json"
},
body: JSON.stringify(body)
});
const text = await r.text().catch(() => "-");
console.log("[DocBee][PUT]", url, body);
console.log("[DocBee][RES]", r.status, text.slice(0, 700));
let json = null;
try {
json = JSON.parse(text);
} catch {}
return {
ok: r.ok,
status: r.status,
json,
text
};
}
async function postJSON(url, body) {
const r = await fetch(url, {
method: "POST",
headers: {
"Authorization": "Bearer " + DOCBEE_TOKEN,
"Content-Type": "application/json; charset=utf-8",
"Accept": "application/json"
},
body: JSON.stringify(body)
});
const text = await r.text().catch(() => "-");
console.log("[DocBee][POST]", url, body);
console.log("[DocBee][RES]", r.status, text.slice(0, 700));
if (r.status === 401) {
try {
updateTokenBadge("bad");
} catch (_) {}
}
return {
ok: r.ok,
status: r.status,
text
};
}
async function restoreTicketStatus(ticketId, prevStatusId) {
try {
const maxLoops = 5; // ~30s
let stable = 0;
for (let i = 0; i < maxLoops; i++) {
await sleep(100);
const tCur = await getJSON(`${DOCBEE_BASEURL}/restApi/v1/ticket/${ticketId}?fields=ticketStatus`);
const curId = tCur?.json?.ticketStatus ?? null;
if (curId == null) break;
if (curId !== prevStatusId) {
let r = await putJSON(`${DOCBEE_BASEURL}/restApi/v1/ticket/${ticketId}`, {
ticketStatus: {
id: prevStatusId
}
});
if (!r.ok) {
await putJSON(`${DOCBEE_BASEURL}/restApi/v1/ticket/${ticketId}`, {
status: {
id: prevStatusId
}
});
}
stable = 0;
} else {
stable++;
if (stable >= 2) break;
}
}
} catch (e) {
console.warn("[DocBee] restoreTicketStatus failed:", e);
}
}
async function setTicketStatus(ticketId, statusId) {
const base = `${DOCBEE_BASEURL}/restApi/v1`;
const tries = [
() => putJSON(`${base}/ticket/${ticketId}`, {
ticketStatus: statusId
}),
];
let last = null;
for (let i = 0; i < tries.length; i++) {
try {
const r = await tries[i]();
console.log(`[DocBee][STATUS][try ${i}]`, r?.status, r?.ok);
if (r && r.ok) return {
ok: true,
variant: i,
status: r.status,
text: r.text
};
last = r;
} catch (e) {
console.warn(`[DocBee][STATUS][try ${i}] exception`, e);
last = {
ok: false,
status: 0,
text: String(e)
};
}
}
return last || {
ok: false
};
}

View File

@@ -1,93 +0,0 @@
// auto-split module
async function exportAll() {
console.log('exportAll START');
const run = collectRun();
console.log('collectRun()', run);
if (!run) {
alert('Kein Template/keine Steps: bitte Template laden oder Steps anlegen.');
return;
}
if (!checkRequired(run)) {
console.log('checkRequired() failed');
return;
}
renumberSteps();
// 1) Push to DocBee using existing function (if token available)
let pushedDocBee = null;
try {
if (window.DOCBEE_TOKEN && window.DOCBEE_TOKEN.length > 10) {
pushedDocBee = await postToDocBee(true); // variant returning URL
if (pushedDocBee && typeof pushedDocBee === 'string') {
run.docbee_url = pushedDocBee;
}
}
} catch (e) {
console.warn('DocBee push failed:', e);
}
// 2) Generate PDF client-side
const pdfBlob = await generatePdfBlob(run).catch(() => null);
// 3) Upload to server (DB + PDF)
const fd = new FormData();
fd.append('run', JSON.stringify(run));
if (pdfBlob) fd.append('pdf', pdfBlob, 'report.pdf');
const res = await fetch('/api/export.php', {
method: 'POST',
body: fd
});
const raw = await res.text();
let json;
try {
json = JSON.parse(raw);
} catch {
json = null;
}
console.log('Server response:', raw);
if (!res.ok || !json || json.ok === false) {
alert('Serverfehler beim Export:\n' + (json?.error || raw || ('HTTP ' + res.status)));
return;
}
alert('Export fertig:\n' +
(run.docbee_url ? ('DocBee: ' + run.docbee_url + '\n') : '') +
(json.pdf_path ? ('PDF gespeichert: ' + json.pdf_path + '\n') : '') +
('Report-ID: ' + json.report_id));
}
function exportTemplateYAML() {
const tpl = collectTemplateFromDOM();
let yml = '';
if (window.jsyaml && jsyaml.dump) {
yml = jsyaml.dump(tpl, {
lineWidth: 100
});
} else {
yml += `name: "${tpl.name||''}"\n`;
yml += `module: "${tpl.module||''}"\n`;
yml += `module_version: "${tpl.module_version||''}"\n`;
yml += `pbx_version: "${tpl.pbx_version||''}"\n`;
yml += `olm_nummer: "${tpl.olm_nummer||''}"\n`;
yml += `steps:\n`;
tpl.steps.forEach(s => {
if (s.type === 'group') {
yml += ` - type: "group"\n`;
yml += ` title: "${(s.title||'').replace(/"/g,'\\"')}"\n`;
} else {
yml += ` - type: "step"\n`;
yml += ` id: "${s.id||''}"\n`;
yml += ` title: "${(s.title||'').replace(/"/g,'\\"')}"\n`;
yml += ` expected: "${(s.expected||'').replace(/"/g,'\\"')}"\n`;
yml += ` required: ${s.required ? 'true':'false'}\n`;
}
});
}
const base = `qa-template-${safeName(tpl.module)}-${safeName(tpl.module_version)}-${safeName(tpl.pbx_version)}`;
download(yml, `${base}.yaml`, 'text/yaml');
}

View File

@@ -1,314 +0,0 @@
// auto-split module
async function tryLoad(urls) {
for (const u of urls) {
try {
await loadScript(u);
} catch (_) {}
if (window.jspdf && window.jspdf.jsPDF) return true;
}
return !!(window.jspdf && window.jspdf.jsPDF);
}
function go(){
try{ window.focus(); }catch(e){}
setTimeout(function(){ window.print(); }, 120);
}
function applyRun(run) {
template = {
name: run.name || '',
steps: (run.steps || []).map(s => {
const k = s.kind || s.type || 'step';
if (k === 'group') return {
kind: 'group',
title: s.title || ''
};
return {
kind: 'step',
id: s.id,
title: s.title,
expected: s.expected,
required: !!s.required,
status: s.status || '',
comment: s.comment || '',
evidence: s.evidence || ''
};
})
};
if (els.tplName) els.tplName.textContent = run.name || '—';
if (els.module) els.module.value = run.module || '';
if (els.moduleVersion) els.moduleVersion.value = run.module_version || '';
if (els.pbxVersion) els.pbxVersion.value = run.pbx_version || '';
if (els.tester) els.tester.value = run.tester || '';
if (els.docbeeUrl) els.docbeeUrl.value = run.docbee_url || '';
renderSteps(template.steps);
recomputeGroupStyles();
if (els.statusTag) els.statusTag.textContent = 'Lauf geladen';
updateAutosave();
}
function applyTemplateOnly(tplObj) {
template = {
name: tplObj.name || '',
steps: (tplObj.steps || []).map(s => {
const k = s.kind || s.type || 'step';
if (k === 'group') return {
kind: 'group',
title: s.title || ''
};
return {
kind: 'step',
id: s.id || '',
title: s.title || '',
expected: s.expected || '',
required: !!s.required,
status: '',
comment: '',
evidence: ''
};
})
};
if (els.tplName) els.tplName.textContent = template.name || '—';
if (els.module) els.module.value = tplObj.module || '';
if (els.moduleVersion) els.moduleVersion.value = tplObj.module_version || '';
if (els.pbxVersion) els.pbxVersion.value = tplObj.pbx_version || '';
renderSteps(template.steps);
recomputeGroupStyles();
if (els.statusTag) els.statusTag.textContent = 'Template geladen';
updateAutosave();
}
function collectTemplateFromDOM() {
if (!template) return {
name: '',
module: '',
module_version: '',
pbx_version: '',
olm_nummer: '',
steps: []
};
captureEditsIntoTemplate();
renumberSteps();
return {
name: template?.name || els.tplName?.textContent || '',
module: els.module.value,
module_version: els.moduleVersion.value,
pbx_version: els.pbxVersion.value,
olm_nummer: els.olmNummer ? els.olmNummer.value : '',
steps: template.steps.map(s => {
if ((s.kind || 'step') === 'group') return {
type: 'group',
title: s.title
};
return {
type: 'step',
id: s.id,
title: s.title,
expected: s.expected,
required: !!s.required
};
})
};
}
function collectRun() {
if (!template) return null;
captureEditsIntoTemplate();
return {
name: template?.name || '',
module: els.module.value,
module_version: els.moduleVersion.value,
pbx_version: els.pbxVersion.value,
olm_nummer: els.olmNummer ? els.olmNummer.value : '',
tester: els.tester.value,
docbee_url: els.docbeeUrl.value,
ts: new Date().toISOString(),
steps: [...template.steps]
};
}
function appendResultLink(createdJSON) {
let link = null;
try {
const j = JSON.parse(createdJSON || "{}");
link = j?.link || null;
} catch {}
// Ticket-ID aus dem Eingabefeld (falls vorhanden) extrahieren
const ticketId = extractTicketId(els.docbeeUrl?.value || '');
// Ziel-URL bestimmen:
// 1) Falls API bereits einen Pfad liefert (z. B. "ticket/show/123"), normieren.
// 2) Sonst UI-Link über bekannte Struktur /ticket/show/%ID% bauen.
let url = null;
if (link) {
if (/^https?:\/\//i.test(link)) {
url = link; // bereits absolute URL
} else {
url = DOCBEE_UI_BASE.replace(/\/+$/, '') + '/' + String(link).replace(/^\/+/, '');
}
} else if (ticketId) {
url = `${DOCBEE_UI_BASE.replace(/\/+$/,'')}/ticket/show/${ticketId}`;
}
if (!url) return;
const hint = document.createElement('div');
hint.className = 'docbee-hint';
hint.innerHTML = `✅ Angelegt: <a href="${url}" target="_blank" rel="noopener">${url}</a>`;
document.querySelector('.actions')?.appendChild(hint);
}
function formatDocBeeMessage(run) {
const pad = (s, n) => (s || '').length > n ? (s || '').slice(0, n - 1) + '…' : (s || '').padEnd(n, ' ');
const fmtDate = new Date(run.ts).toLocaleString('de-DE');
// Kurz-Summary (nur echte Steps zählen)
const counts = {
pass: 0,
fail: 0,
skip: 0,
blocked: 0
};
(run.steps || []).forEach(s => {
const k = s.kind || s.type || 'step';
if (k !== 'step') return;
if (counts[s.status] !== undefined) counts[s.status]++;
});
const summary = `${counts.pass} | ❌ ${counts.fail} | ⏭️ ${counts.skip} | ⛔ ${counts.blocked}`;
// Kopf + Metadaten (out VOR jeglicher Nutzung initialisieren)
let out = '';
out += `QA REPORT\n`;
out += `===========\n`;
out += `Modul: ${run.module || ''}\n`;
out += `Modul-Version:${run.module_version || ''}\n`;
out += `PBX-Version: ${run.pbx_version || ''}\n`;
out += `Tester: ${run.tester || ''}\n`;
if (run.docbee_url) out += `Ticket: ${run.docbee_url}\n`;
out += `Datum: ${fmtDate}\n\n`;
out += `Übersicht: ${summary}\n\n`;
// Tabelle (monospace-geeignet)
const SEP = '────────────────────────────────────────────────────────────────────────';
out += `${SEP}\n`;
out += `${pad('Schritt', 12)} ${pad('Status', 7)} ${pad('Titel', 52)}\n`;
out += `${SEP}\n`;
(run.steps || []).forEach(s => {
const k = s.kind || s.type || 'step';
if (k === 'group') {
out += `\n## ${s.title || ''}\n\n`;
return;
}
const st = (s.status || '').toUpperCase(); // PASS/FAIL/SKIP/BLOCKED/…
const stShort = st === 'BLOCKED' ? 'BLOCK' : st;
const SMAP = {
pass: '✅',
fail: '❌',
skip: '⏭️',
blocked: '⛔'
};
const stLabel = (SMAP[(s.status || '').toLowerCase()] ?
SMAP[(s.status || '').toLowerCase()] + ' ' :
'') + stShort;
const req = s.required ? '📌 ' : '';
out += `${pad(s.id || '', 12)} ${pad(stLabel, 9)} ${pad(req + (s.title || ''), 50)}\n`;
if (s.comment) out += ` • Kommentar: ${s.comment}\n`;
if (s.evidence) out += ` • Evidenz: ${s.evidence}\n`;
});
out += `${SEP}\n`;
out += `Legende: PASS=✅, FAIL=❌, SKIP=⏭️, BLOCK=⛔\n`;
return out;
}
async function postToDocBee() {
const run = collectRun();
if (!run) return;
if (!checkRequired(run)) return;
const ticketId = extractTicketId(els.docbeeUrl?.value || '');
if (!ticketId) {
alert("Keine Ticket-ID in der DocBee-URL gefunden.");
return;
}
if (!hasToken()) {
alert("Kein API-Token konfiguriert.");
return;
}
// Kommentarinhalt (Markdown)
const content = formatDocBeeMessage(run);
// alten Vorgangs-Status holen (Vorgangs-Status = ticketStatus)
const tGet = await getJSON(`${DOCBEE_BASEURL}/restApi/v1/ticket/${ticketId}?fields=ticketStatus`);
const prevStatusId = tGet?.json?.ticketStatus ?? null;
// Busy-State
els.btnPushDocBee?.setAttribute('disabled', '');
els.btnPushDocBee?.classList.add('is-busy');
try {
// *** Message posten (muss Message sein) ***
const url = `${DOCBEE_BASEURL}/restApi/v1/ticket/${ticketId}/message`;
const rMsg = await postJSON(url, {
content: content,
subject: `QA Report ${ticketId}`,
internal: true,
hidden: false
});
if (rMsg.ok) {
// kurze Wartezeit, dann Status ggf. zurücksetzen (gegen Auto-"Antwort erhalten")
await sleep(800);
if (prevStatusId != null) {
await restoreTicketStatus(ticketId, prevStatusId);
}
appendResultLink(rMsg.text);
alert("Nachricht im Ticket angelegt (Status beibehalten).");
return;
}
if (ENABLE_FALLBACK_NOTE) {
const rNote = await postJSON(`${DOCBEE_BASEURL}/restApi/v1/note`, {
note: {
ticket: {
id: Number(ticketId)
},
subject: `QA Report ${ticketId}`,
text: content,
internal: false
}
});
if (rNote.ok) {
appendResultLink(rNote.text);
alert("Notiz angelegt (Fallback).");
return;
}
alert(`Fehler: MSG ${rMsg.status}, NOTE ${rNote.status}`);
} else {
alert(`Nachricht fehlgeschlagen (Status ${rMsg.status}). Details siehe Konsole.`);
}
} catch (e) {
alert("DocBee-Request fehlgeschlagen: " + String(e));
} finally {
els.btnPushDocBee?.removeAttribute('disabled');
els.btnPushDocBee?.classList.remove('is-busy');
}
}

View File

@@ -1,102 +0,0 @@
// auto-split module
const GITLAB = {
host: (window.GITLAB && window.GITLAB.host) || "https://git.steinert.cc",
projectId: (window.GITLAB && window.GITLAB.projectId) || "qa/templates",
ref: (window.GITLAB && window.GITLAB.ref) || "main",
path: (window.GITLAB && window.GITLAB.path) || "templates",
token: (window.GITLAB && window.GITLAB.token) || ""
};
// auto-split module
function glHeaders() {
const h = {
"Accept": "application/json"
};
if (GITLAB.token && GITLAB.token.trim()) h["PRIVATE-TOKEN"] = GITLAB.token.trim();
return h;
}
async function listGitlabTemplates() {
const url = `${GITLAB.host}/api/v4/projects/${encodeURIComponent(GITLAB.projectId)}/repository/tree?path=${encodeURIComponent(GITLAB.path)}&ref=${encodeURIComponent(GITLAB.ref)}&per_page=100`;
const r = await fetch(url, {
headers: glHeaders()
});
if (!r.ok) throw new Error(`GitLab Tree ${r.status}`);
const items = await r.json();
return items.filter(it => it.type === "blob" && /\.ya?ml$/i.test(it.name)).map(it => ({
name: it.name,
path: it.path
}));
}
async function fetchGitlabFileRaw(filePath) {
const url = `${GITLAB.host}/api/v4/projects/${encodeURIComponent(GITLAB.projectId)}/repository/files/${encodeURIComponent(filePath)}/raw?ref=${encodeURIComponent(GITLAB.ref)}`;
const r = await fetch(url, {
headers: glHeaders(),
cache: "no-store"
});
if (!r.ok) throw new Error(`GitLab Raw ${r.status}`);
return await r.text();
}
async function populateGitlabDropdown() {
const sel = els.gitlabTplSelect,
tag = els.gitlabTplStatus;
if (!sel) return;
sel.innerHTML = `<option value="">— GitLab-Templates laden —</option>`;
try {
tag && (tag.textContent = "GitLab: lade Liste…");
const files = await listGitlabTemplates();
files.forEach(f => {
const opt = document.createElement('option');
opt.value = f.path;
opt.textContent = f.name;
sel.appendChild(opt);
});
tag && (tag.textContent = files.length ? `GitLab: ${files.length} Vorlage(n)` : "GitLab: keine YAMLs gefunden");
} catch (e) {
tag && (tag.textContent = "GitLab: Fehler");
console.error("[GitLab] list failed:", e);
alert("GitLab-Liste konnte nicht geladen werden: " + e.message);
}
}
function loadYAMLText(yamlText) {
const obj = parseTemplate(yamlText);
if (!obj || !Array.isArray(obj.steps)) throw new Error('Ungültige Vorlage: "steps" fehlt.');
// Normalisieren: type→kind, default "step"
obj.steps = obj.steps.map(s => {
const k = s.kind || s.type || 'step';
if (k === 'group') return {
kind: 'group',
title: s.title || s.name || '',
collapsed: !!s.collapsed
};
return {
kind: 'step',
id: s.id,
title: s.title,
expected: s.expected,
required: !!s.required,
status: s.status || '',
comment: s.comment || '',
evidence: s.evidence || ''
};
});
template = obj;
if (els.tplName) els.tplName.textContent = obj.name || '—';
if (obj.module) els.module.value = obj.module;
if (obj.module_version) els.moduleVersion.value = obj.module_version;
if (obj.pbx_version) els.pbxVersion.value = obj.pbx_version;
renderSteps(obj.steps);
if (els.statusTag) els.statusTag.textContent = 'Vorlage geladen';
updateAutosave();
}

View File

@@ -1,253 +0,0 @@
// ===================== QA App komplette app.js =====================
// === DocBee: Token HIER eintragen ===
const DOCBEE_TOKEN = window.DOCBEE_TOKEN || ""; // DocBee Token eintragen"
const ENABLE_FALLBACK_NOTE = false; // auf true setzen, falls bei Fehler automatisch Notiz angelegt werden soll
window.addEventListener('error', e => {
try {
console.error(e.error || e.message || e);
} catch (_) {}
alert('JS-Fehler: ' + (e.message || e));
});
let template = null; // {name,module,module_version,pbx_version,steps:[...]}
// === DOM-Refs ===
const els = {
yamlFile: document.getElementById('yamlFile'),
stepsTableBody: document.querySelector('#stepsTable tbody'),
tplName: document.getElementById('tplName'),
statusTag: document.getElementById('statusTag'),
module: document.getElementById('module'),
moduleVersion: document.getElementById('moduleVersion'),
pbxVersion: document.getElementById('pbxVersion'),
tester: document.getElementById('tester'),
olmNummer: document.getElementById('olmNummer'),
docbeeUrl: document.getElementById('docbeeUrl'),
btnAddStep: document.getElementById('btnAddStep'),
btnSaveJSON: document.getElementById('btnSaveJSON'),
loadJSON: document.getElementById('loadJSON'),
btnAddGroup: document.getElementById('btnAddGroup'),
btnExportTemplateYAML: document.getElementById('btnExportTemplateYAML'),
btnExportAll: document.getElementById('btnExportAll'),
gitlabTplSelect: document.getElementById('gitlabTplSelect'),
gitlabTplStatus: document.getElementById('gitlabTplStatus'),
docbeeTokenStatus: document.getElementById('docbeeTokenStatus'),
btnGitlabReload: document.getElementById('btnGitlabReload'),
};
// === Drag Guard + Observer: Nur Drag über expliziten Griff zulassen ===
function enforceDragHandleOnly() {
if (!els.stepsTableBody) return;
els.stepsTableBody.querySelectorAll('tr[draggable="true"]').forEach(tr => tr.setAttribute('draggable', 'false'));
els.stepsTableBody.querySelectorAll('.drag-handle').forEach(h => h.setAttribute('draggable', 'true'));
}
document.addEventListener('DOMContentLoaded', () => {
if (els.stepsTableBody) {
const mo = new MutationObserver(() => enforceDragHandleOnly());
mo.observe(els.stepsTableBody, {
childList: true,
subtree: true
});
enforceDragHandleOnly();
}
});
document.addEventListener('dragstart', (e) => {
if (!e.target.closest('.drag-handle')) {
e.preventDefault();
}
}, {
capture: true
});
// === GitLab Events ===
if (els.gitlabTplSelect) els.gitlabTplSelect.addEventListener('change', async (e) => {
const p = e.target.value;
if (!p) return;
try {
els.gitlabTplStatus && (els.gitlabTplStatus.textContent = "GitLab: lade Datei…");
const y = await fetchGitlabFileRaw(p);
loadYAMLText(y);
els.gitlabTplStatus && (els.gitlabTplStatus.textContent = `GitLab: geladen ${p.split('/').pop()}`);
} catch (err) {
els.gitlabTplStatus && (els.gitlabTplStatus.textContent = "GitLab: Fehler");
console.error("[GitLab] raw failed:", err);
alert("Vorlage aus GitLab konnte nicht geladen werden: " + err.message);
}
});
if (els.btnGitlabReload) els.btnGitlabReload.addEventListener('click', (e) => {
e.preventDefault();
populateGitlabDropdown();
});
// === Drag & Drop (Rows verschieben, inkl. Gruppen) ===
let dragIndex = -1;
// ===== Funktion: rowIndexOf =====
// Zweck: siehe Inline-Kommentare im Funktionskörper.
function rowIndexOf(target) {
const tr = target.closest('tr');
if (!tr) return -1;
return [...els.stepsTableBody.children].indexOf(tr);
}
els.stepsTableBody && els.stepsTableBody.addEventListener('dragstart', (e) => {
if (!e.target.closest('.drag-handle')) {
e.preventDefault();
return;
}
const i = rowIndexOf(e.target);
if (i < 0) return;
dragIndex = i;
e.dataTransfer?.setData('text/plain', String(i));
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move';
const row = e.target.closest('tr') || e.target;
e.dataTransfer?.setDragImage?.(row, 16, 16);
row.classList.add('dragging');
});
els.stepsTableBody && els.stepsTableBody.addEventListener('dragend', (e) => {
const tr = e.target.closest('tr');
if (tr) tr.classList.remove('dragging');
[...els.stepsTableBody.children].forEach(r => {
r.classList.remove('drag-over');
r.removeAttribute('data-pos');
});
dragIndex = -1;
});
els.stepsTableBody && els.stepsTableBody.addEventListener('dragover', (e) => {
e.preventDefault();
const tr = e.target.closest('tr');
if (!tr) return;
[...els.stepsTableBody.children].forEach(r => {
r.classList.remove('drag-over');
r.removeAttribute('data-pos');
});
tr.classList.add('drag-over');
const rect = tr.getBoundingClientRect();
const pos = (e.clientY - rect.top) < rect.height / 2 ? 'before' : 'after';
tr.setAttribute('data-pos', pos);
});
els.stepsTableBody && els.stepsTableBody.addEventListener('drop', (e) => {
e.preventDefault();
captureEditsIntoTemplate();
const from = dragIndex >= 0 ? dragIndex : parseInt(e.dataTransfer?.getData('text/plain') || '-1', 10);
const tr = e.target.closest('tr');
if (from < 0 || !tr) return;
const toBase = rowIndexOf(tr);
const pos = tr.getAttribute('data-pos') || 'after';
let to = toBase + (pos === 'after' ? 1 : 0);
if (to === from || to - 1 === from) {
renderSteps(template.steps);
recomputeGroupStyles();
return;
}
// Element verschieben
const item = template.steps.splice(from, 1)[0];
if (to > from) to--; // nach dem Entfernen verschiebt sich Index
template.steps.splice(to, 0, item);
ensureRenumberAndRender();
});
document.getElementById('stepsTable')?.addEventListener('change', (e) => {
if (e.target && e.target.matches('select.status')) {
updateStatusClass(e.target);
recomputeGroupStyles();
}
});
els.btnExportMD && els.btnExportMD.addEventListener('click', exportMD);
els.btnExportCSV && els.btnExportCSV.addEventListener('click', exportCSV);
els.btnPrintPDF && els.btnPrintPDF.addEventListener('click', printPDF);
els.btnExportTemplateYAML && els.btnExportTemplateYAML.addEventListener('click', exportTemplateYAML);
els.btnSaveJSON && els.btnSaveJSON.addEventListener('click', () => {
const run = collectRun();
if (!run) return;
if (!checkRequired(run)) return;
const base = `qa-run-${safeName(run.module)}-${safeName(run.module_version)}-${safeName(run.pbx_version)}`;
download(JSON.stringify(run, null, 2), `${base}.json`, 'application/json');
});
els.loadJSON && els.loadJSON.addEventListener('change', async (e) => {
const f = e.target.files?.[0];
if (!f) return;
const txt = await f.text();
try {
const run = JSON.parse(txt);
template = {
name: run.name || '',
steps: (run.steps || []).map(s => ({
id: s.id,
title: s.title,
expected: s.expected,
required: !!s.required,
status: s.status || '',
comment: s.comment || '',
evidence: s.evidence || ''
}))
};
if (els.tplName) els.tplName.textContent = run.name || '—';
els.module.value = run.module || '';
els.moduleVersion.value = run.module_version || '';
els.pbxVersion.value = run.pbx_version || '';
els.tester.value = run.tester || '';
els.docbeeUrl.value = run.docbee_url || '';
renderSteps(template.steps);
recomputeGroupStyles();
if (els.statusTag) els.statusTag.textContent = 'Lauf geladen';
updateAutosave();
} catch (err) {
alert('Ungültiges JSON: ' + err.message);
}
});
// === Bindings absichern + Busy-Fix + Mini-CSS ===
(function ensureBindings() {
// ===== Funktion: bind =====
// Zweck: siehe Inline-Kommentare im Funktionskörper.
function bind() {
const b = document.getElementById('btnPushDocBee');
if (b) {
b.removeAttribute('disabled');
b.classList.remove('is-busy');
b.style.pointerEvents = 'auto';
b.addEventListener('click', postToDocBee, {
once: false
});
}
const p = document.getElementById('btnPrintPDF');
const btn = document.getElementById('btnExportAll');
if (btn) {
btn.addEventListener('click', e => {
e.preventDefault();
exportAll().catch(err => alert('Export error: ' + err.message));
});
console.log('Export-Button gebunden');
} else {
alert('Export-Button nicht gefunden (id=btnExportAll).');
}
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind);
else bind();
window.addEventListener('pageshow', () => {
const b = document.getElementById('btnPushDocBee');
if (b) {
b.removeAttribute('disabled');
b.classList.remove('is-busy');
b.style.pointerEvents = 'auto';
}
});
})();
(function injectAuxCss() {
const s = document.createElement('style');
s.textContent = `
.is-busy{opacity:.6;pointer-events:none}
.docbee-hint{margin-top:8px;font-size:14px}
.docbee-hint a{text-decoration:underline}
`;
document.head.appendChild(s);
})();
// ===================== Ende app.js =====================

View File

@@ -1,515 +0,0 @@
// auto-split module
async function ensureJsPdf() {
function loadScript(url) {
return new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = url;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}
async function tryLoad(urls) {
for (const u of urls) {
try {
await loadScript(u);
} catch (_) {}
if (window.jspdf && window.jspdf.jsPDF) return true;
}
return !!(window.jspdf && window.jspdf.jsPDF);
}
// 1) jsPDF laden (lokal bevorzugt, dann CDN/Unpkg)
if (!(window.jspdf && window.jspdf.jsPDF)) {
await tryLoad([
'/vendor/jspdf/jspdf.umd.min.js',
'https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js',
'https://unpkg.com/jspdf@2.5.1/dist/jspdf.umd.min.js'
]);
}
if (!(window.jspdf && window.jspdf.jsPDF)) return false;
// 2) autotable nachladen, falls nicht vorhanden
try {
const test = new window.jspdf.jsPDF();
if (typeof test.autoTable !== 'function') {
await tryLoad([
'/vendor/jspdf-autotable/jspdf.plugin.autotable.min.js',
'https://cdn.jsdelivr.net/npm/jspdf-autotable@3.8.2/dist/jspdf.plugin.autotable.min.js',
'https://unpkg.com/jspdf-autotable@3.8.2/dist/jspdf.plugin.autotable.min.js'
]);
}
} catch (_) {
/* egal PDF geht auch ohne autotable-Funktion (Fallback) */
}
return true;
}
async function generatePdfBlob(run) {
// Lade jsPDF + AutoTable falls nötig (lokal → CDN/Unpkg). Fällt ansonsten sauber auf Text-Blob zurück.
const pdfReady = await ensureJsPdf();
if (!pdfReady) {
const txt = 'QA Report\n' + JSON.stringify(run, null, 2);
return new Blob([txt], {
type: 'application/pdf'
});
}
const {
jsPDF
} = window.jspdf;
const doc = new jsPDF({
orientation: 'landscape',
unit: 'mm',
format: 'a4',
compress: true
});
// dynamic layout metrics for landscape A4
const pageW = doc.internal.pageSize.getWidth();
const pageH = doc.internal.pageSize.getHeight();
const marginL = 14,
marginR = 14;
const xRight = pageW - marginR;
const footerY = pageH - 13;
// Farben (RGB, NICHT Hex!): "muted" war zuvor unsichtbar jetzt korrekt
const muted = [102, 102, 102];
const headFill = [242, 242, 242];
const ok = [0, 140, 0],
fail = [200, 0, 0],
skip = [160, 160, 0];
// Optionales Logo oben rechts proportional in 26×12 mm Box, rechtsbündig
let logo = null;
try {
logo = await imgToDataURL('logo_light.png');
} catch {}
if (logo) {
// 1) Bildabmessungen ermitteln (erst jsPDF, dann Fallback über <img>)
let iw = 0,
ih = 0;
try {
const props = doc.getImageProperties ? doc.getImageProperties(logo) : null;
if (props) {
iw = props.width || 0;
ih = props.height || 0;
}
} catch {}
if (!(iw && ih)) {
await new Promise((resolve) => {
const img = new Image();
img.onload = () => {
iw = img.naturalWidth;
ih = img.naturalHeight;
resolve();
};
img.onerror = () => resolve();
img.src = logo;
});
}
// 2) Proportional in die Box einpassen
const maxW = 26,
maxH = 12;
const ratio = (ih && iw) ? (ih / iw) : (10 / 26);
let w = maxW,
h = w * ratio;
if (h > maxH) {
h = maxH;
w = h / ratio;
}
// 3) Rechtsbündig relativ zur Nutzkante (xRight)
const x = xRight - w;
const y = 7.8;
try {
doc.addImage(logo, 'PNG', x, y, w, h, undefined, 'FAST');
} catch {}
}
// Titelzeile
doc.setFont('helvetica', 'bold');
doc.setFontSize(18);
doc.setTextColor(0, 0, 0);
doc.text('QA Report', 14, 16);
if (logo) {
try {
// Optionales Logo oben rechts proportional in 26×12 mm Box, rechtsbündig
let logo = null;
try {
logo = await imgToDataURL('logo_light.png');
} catch {}
if (logo) {
// Bildabmessungen: zuerst aus jsPDF, sonst via <img>-Fallback
let iw = 0,
ih = 0;
try {
const props = doc.getImageProperties ? doc.getImageProperties(logo) : null;
if (props) {
iw = props.width || 0;
ih = props.height || 0;
}
} catch {}
if (!(iw && ih)) {
await new Promise((resolve) => {
const img = new Image();
img.onload = () => {
iw = img.naturalWidth;
ih = img.naturalHeight;
resolve();
};
img.onerror = () => resolve();
img.src = logo;
});
}
// proportional in die Box einpassen
const maxW = 26,
maxH = 12;
const ratio = (ih && iw) ? (ih / iw) : (10 / 26);
let w = maxW,
h = w * ratio;
if (h > maxH) {
h = maxH;
w = h / ratio;
}
// rechtsbündig zur Nutzkante
const x = xRight - w;
const y = 7.8;
try {
doc.addImage(logo, 'PNG', x, y, w, h, undefined, 'FAST');
} catch {}
}
} catch {}
}
doc.setDrawColor(0, 0, 0);
doc.setLineWidth(0.2);
doc.line(marginL, 18, xRight, 18);
// Metadaten mit Labels (sichtbar!)
const meta = [
['Modul', sanitizeText(run.module)],
['Modul-Version', sanitizeText(run.module_version)],
['PBX-Version', sanitizeText(run.pbx_version)],
['OLM-Nummer', sanitizeText(run.olm_nummer)],
['Tester', sanitizeText(run.tester)],
['DocBee', sanitizeText(run.docbee_url || '-')],
];
let y = 24;
doc.setFontSize(10);
meta.forEach(([k, v]) => {
doc.setFont('helvetica', 'bold');
doc.setTextColor(...muted);
doc.text(k + ':', 14, y);
doc.setFont('helvetica', 'normal');
doc.setTextColor(0, 0, 0);
doc.text(v || '—', 50, y);
y += 6;
});
// Zeilen aus Steps bauen
const rows = [];
let i = 0,
group = null;
(run.steps || []).forEach(s => {
if ((s.kind || 'step') === 'group') {
group = sanitizeText(s.title);
return;
}
i++;
rows.push({
nr: i,
group: sanitizeText(group || ''),
id: sanitizeText(s.id),
title: sanitizeText(s.title),
expected: sanitizeText(s.expected),
status: sanitizeText((s.status || '').toLowerCase()),
comment: sanitizeText(s.comment),
});
});
const body = rows.map(r => [r.nr, r.group, r.id, r.title, r.expected, r.status, r.comment]);
const startY = Math.max(y + 4, 24);
// Tabelle (autoTable)
// @ts-ignore
doc.autoTable({
startY,
tableWidth: pageW - (marginL + marginR),
tableWidth: pageW - (marginL + marginR),
head: [
['#', 'Gruppe', 'Step-ID', 'Titel', 'Erwartet', 'Status', 'Kommentar']
],
body,
styles: {
font: 'helvetica',
fontSize: 9,
cellPadding: 2,
overflow: 'linebreak',
valign: 'top'
},
headStyles: {
fillColor: headFill,
textColor: [0, 0, 0],
halign: 'left'
},
columnStyles: {
0: {
halign: 'right',
cellWidth: 8
},
1: {
cellWidth: 36
},
2: {
cellWidth: 24
},
3: {
cellWidth: 62
},
4: {
cellWidth: 76
},
5: {
cellWidth: 20,
halign: 'left'
},
6: {
cellWidth: 36
}
},
didParseCell: (d) => {
// Normalize every cell fragment to PDF-safe ASCII/Latin-1 and map arrows
if (d && d.cell) {
const arr = Array.isArray(d.cell.text) ? d.cell.text : [String(d.cell.text ?? '')];
d.cell.text = arr.map(t => toPdfSafe(replaceSymbols(String(t))));
}
if (d.section === 'body') {
// Status einfärben
if (d.column.index === 5) {
const val = String(d.cell.raw || '').toLowerCase();
if (val === 'pass') d.cell.styles.textColor = ok;
else if (val === 'fail') d.cell.styles.textColor = fail;
else if (val === 'skip' || val === 'na') d.cell.styles.textColor = skip;
}
// "Erwartet" als Monospace + leicht grauer Hintergrund
if (d.column.index === 4) {
d.cell.styles.font = 'courier';
d.cell.styles.fillColor = [250, 250, 250];
}
}
},
margin: {
left: marginL,
right: marginR
},
pageBreak: 'auto'
});
// Footer: Seitenzahlen & Zeitstempel
const pageCount = doc.getNumberOfPages();
const ts = new Date().toLocaleString();
for (let p = 1; p <= pageCount; p++) {
doc.setPage(p);
doc.setFont('helvetica', 'normal');
doc.setFontSize(8);
doc.setTextColor(...muted);
doc.text(`Seite ${p} / ${pageCount}`, xRight, footerY, {
align: 'right'
});
doc.text(ts, marginL, footerY);
}
return doc.output('blob');
}
async function printPDF() {
const run = collectRun();
if (!run) return;
if (!checkRequired(run)) return;
renumberSteps();
const logoDataUrl = await imgToDataURL('logo_light.png').catch(() => null);
const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
// ===== Funktion: badge (Arrow) =====
// Zweck: siehe Inline-Kommentare im Funktionskörper.
const badge = (status) => {
const map = {
pass: {
txt: '✅ PASS',
bg: '#DCFCE7',
bd: '#22c55e'
},
fail: {
txt: '❌ FAIL',
bg: '#FEE2E2',
bd: '#ef4444'
},
skip: {
txt: '⏭️ SKIP',
bg: '#E5E7EB',
bd: '#6b7280'
},
blocked: {
txt: '⛔ BLOCK',
bg: '#FEF3C7',
bd: '#f59e0b'
},
'': {
txt: '—',
bg: '#F3F4F6',
bd: '#9ca3af'
}
};
const m = map[status || ''] || map[''];
return `<span style="display:inline-block;padding:2px 8px;border:1px solid ${m.bd};border-radius:999px;background:${m.bg};font-weight:600;font-size:12px">${m.txt}</span>`;
};
const counts = {
pass: 0,
fail: 0,
skip: 0,
blocked: 0
};
run.steps.forEach(s => {
if ((s.kind || 'step') !== 'step') return;
if (counts[s.status] !== undefined) counts[s.status]++;
});
const summary = `${counts.pass}${counts.fail} ⏭️ ${counts.skip}${counts.blocked}`;
let rows = '';
let seenGroup = false;
for (const s of run.steps) {
if ((s.kind || 'step') === 'group') {
const t = esc(s.title || '');
const cls = `group${seenGroup ? ' pagebreak' : ''}`;
rows += `<tr class="${cls}"><td colspan="5"><strong>${t}</strong></td></tr>`;
seenGroup = true;
continue;
}
rows += `
<tr>
<td><strong>${esc(s.id)}</strong><div class="stitle">${esc(s.title)}${s.required?' 📌':''}</div></td>
<td>${esc(s.expected)}</td>
<td class="status">${badge(s.status||'')}</td>
<td>${esc(s.comment)}</td>
<td>${linkify(s.evidence)}</td>
</tr>`;
}
const html = `<!doctype html>
<html><head><meta charset="utf-8"><title>QA Report</title>
<style>
:root{ --text:#111827; --muted:#4b5563; --line:#d1d5db; --brandLight:#f3f4f6; }
*{box-sizing:border-box}
body{font-family:system-ui,Segoe UI,Roboto,Arial,sans-serif;margin:24px;color:var(--text); background:#fff}
header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.brand{display:flex;gap:10px;align-items:center}
.brand img{height:28px;width:auto;object-fit:contain}
h1{margin:0;font-size:20px}
.summary{margin:6px 0 0 0;font-weight:600}
.meta{border:1px solid var(--line);border-radius:10px;padding:10px;background:#fff;margin:8px 0 14px 0}
.meta ul{margin:0;padding-left:16px;color:var(--muted)}
table{width:100%;border-collapse:collapse;margin-top:4px}
thead th{background:var(--brandLight);text-align:left;border-bottom:1px solid var(--line);padding:8px}
td{border-bottom:1px solid var(--line);padding:8px;vertical-align:top}
td.status{white-space:nowrap}
.stitle{color:var(--muted);font-size:12px;margin-top:2px}
.legend{font-size:12px;color:var(--muted);margin-top:4px}
tr.group td{ background:#e8f6fd; border-top:2px solid #bfe7fb; font-weight:700 }
/* Seitenumbruch vor jeder Gruppe (außer der ersten) */
@media print{
tr.pagebreak{ break-before: page; page-break-before: always; }
}
a{color:#1d4ed8;text-decoration:underline}
@media print{ body{margin:10mm} thead th, td{border-color:#000} }
</style>
</head>
<body>
<header>
<div class="brand">
${logoDataUrl ? `<img src="${logoDataUrl}" alt="Logo">` : ``}
<h1>Testprotokoll</h1>
</div>
<div class="summary">${summary}</div>
</header>
<section class="meta">
<ul>
<li><strong>Modul:</strong> ${esc(run.module||'')}</li>
<li><strong>Modul-Version:</strong> ${esc(run.module_version||'')}</li>
<li><strong>PBX-Version:</strong> ${esc(run.pbx_version||'')}</li>
<li><strong>DocBee:</strong> ${esc(run.docbee_url||'')}</li>
<li><strong>Tester:</strong> ${esc(run.tester||'')}</li>
<li><strong>Datum:</strong> ${new Date(run.ts).toLocaleString('de-DE')}</li>
</ul>
<div class="legend">Legende: ✅ Pass, ❌ Fail, ⏭️ Skip, ⛔ Blocked, 📌 Pflicht</div>
</section>
<table>
<thead><tr><th>Schritt</th><th>Erwartung</th><th>Status</th><th>Kommentar</th><th>Evidenz</th></tr></thead>
<tbody>${rows}</tbody>
</table>
<script>
(function(){
function go(){
try{ window.focus(); }catch(e){}
setTimeout(function(){ window.print(); }, 120);
}
if (document.readyState === 'complete') go();
else window.addEventListener('load', go);
window.onafterprint = function(){
setTimeout(function(){ try{ window.close(); }catch(e){} }, 50);
};
setTimeout(function(){ try{ window.close(); }catch(e){} }, 3000);
})();
</script>
</body></html>`;
const blob = new Blob([html], {
type: 'text/html'
});
const url = URL.createObjectURL(blob);
const w = window.open(url, '_blank', 'noopener');
if (!w) {
alert('Pop-up blockiert. Bitte Pop-ups erlauben.');
URL.revokeObjectURL(url);
return;
}
setTimeout(() => URL.revokeObjectURL(url), 10000);
}
function toPdfSafe(s) {
return String(s).replace(/[^\x09\x0A\x0D\x20-\x7E\u00A0-\u00FF]/g, '?');
}
async function imgToDataURL(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
const c = document.createElement('canvas');
c.width = img.naturalWidth;
c.height = img.naturalHeight;
const ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0);
resolve(c.toDataURL('image/png'));
};
img.onerror = reject;
img.src = src;
});
}

View File

@@ -1,195 +0,0 @@
// auto-split module
function parseTemplate(text) {
if (window.jsyaml && typeof jsyaml.load === 'function') {
try {
return jsyaml.load(text);
} catch (e) {}
}
try {
return JSON.parse(text);
} catch (e) {}
throw new Error('Vorlage konnte weder als YAML noch JSON gelesen werden.');
}
function captureEditsIntoTemplate() {
if (!template) return;
const rows = [...(els.stepsTableBody?.querySelectorAll('tr') || [])];
template.steps = rows.map((row, i) => {
const kind = row.getAttribute('data-kind') || 'step';
if (kind === 'group') {
return {
kind: 'group',
title: (row.querySelector('.tpl-group-title')?.value || '').trim(),
collapsed: row.getAttribute('data-collapsed') === '1'
};
}
return {
kind: 'step',
id: (row.querySelector('.tpl-id')?.value || '').trim() || `step-${String(i+1).padStart(3,'0')}`,
title: (row.querySelector('.tpl-title')?.value || row.querySelector('.tpl-title')?.textContent || '').trim(),
expected: (row.querySelector('.tpl-expected')?.value || '').trim(),
required: !!(row.querySelector('.tpl-required')?.checked),
status: row.querySelector('select.status')?.value || '',
comment: row.querySelector('.run-comment')?.value || '',
evidence: row.querySelector('.run-evidence')?.value || ''
};
});
}
function nextStepNumber() {
const stepsOnly = (template?.steps || []).filter(s => (s.kind || s.type || 'step') === 'step');
// Versuche numerischen Suffix aus "step-XYZ" zu lesen, sonst zähle Steps
const nums = stepsOnly
.map(s => String(s.id || ''))
.map(id => {
const m = id.match(/step-(\d+)/i);
return m ? parseInt(m[1], 10) : null;
})
.filter(n => Number.isFinite(n));
const base = nums.length ? Math.max(...nums) : stepsOnly.length;
return base + 1;
}
function makeStepId(n) {
const num = String(Math.max(1, n)).padStart(3, '0');
return `step-${num}`;
}
function renumberSteps() {
if (!template || !Array.isArray(template.steps)) return;
let n = 1;
template.steps.forEach(s => {
const k = s.kind || s.type || 'step';
if (k !== 'step') return;
const id = `step-${String(n).padStart(3,'0')}`;
s.id = id;
n++;
});
}
function ensureRenumberAndRender() {
renumberSteps();
renderSteps(template.steps);
recomputeGroupStyles();
}
function renderSteps(steps) {
if (!els.stepsTableBody) return;
els.stepsTableBody.innerHTML = '';
let groupCollapsed = false;
steps.forEach((s, idx) => {
if ((s.kind || 'step') === 'group') {
const trG = document.createElement('tr');
trG.setAttribute('data-kind', 'group');
trG.className = 'group-row';
if (s.collapsed) trG.setAttribute('data-collapsed', '1');
/* drag via handle only */
// trG.setAttribute('draggable','true');
trG.innerHTML = `
<td colspan="4">
<div class="step-head" style="grid-template-columns: 1fr auto auto;">
<input type="text" class="tpl-group-title group-title" placeholder="Gruppen-Titel (z. B. Einrichtung des Moduls)" value="${escAttr(s.title||'')}">
<div class="group-actions">
<button type="button" class="btn btn-group-status" data-status="pass" title="Alle in Gruppe: PASS">✅</button>
<button type="button" class="btn btn-group-status" data-status="fail" title="Alle in Gruppe: FAIL">❌</button>
<button type="button" class="btn btn-group-status" data-status="skip" title="Alle in Gruppe: SKIP">⏭️</button>
<button type="button" class="btn btn-group-status" data-status="blocked" title="Alle in Gruppe: BLOCK">⛔</button>
<button type="button" class="btn btn-toggle-group" title="Gruppe ein-/ausklappen"><span class="chev">${s.collapsed?'▸':'▾'}</span></button>
<button type="button" class="btn btn-delete-group" title="Diese Gruppe löschen">🗑️</button>
</div>
<div class="drag-handle" draggable="true" title="Ziehen zum Verschieben">⋮⋮</div>
</div>
</td>`;
els.stepsTableBody.appendChild(trG);
groupCollapsed = !!s.collapsed;
return;
}
const id = s.id || '';
const tr = document.createElement('tr');
tr.setAttribute('data-kind', 'step');
/* drag via handle only */
// tr.setAttribute('draggable','true');
if (groupCollapsed) tr.setAttribute('data-hidden', '1');
tr.innerHTML = `
<td class="cell-step">
<div class="step-head">
<input type="text" class="tpl-id" value="${escAttr(id)}" />
<textarea class="tpl-title" placeholder="Titel">${escHTML(s.title||'')}</textarea>
<span class="req-pin" title="Pflichtschritt">${s.required ? '📌' : ''}</span>
<button type="button" class="btn btn-delete-step" title="Diesen Step löschen">🗑️</button>
<div class="drag-handle" draggable="true" title="Ziehen zum Verschieben">⋮⋮</div>
</div>
<label class="req-row"><input type="checkbox" class="tpl-required"${s.required?' checked':''}> required</label>
</td>
<td><textarea class="tpl-expected" placeholder="Erwartetes Verhalten">${escHTML(s.expected||'')}</textarea></td>
<td>
<select class="status" data-step="${escAttr(id)}">
<option value="" ${!s.status ? 'selected':''}></option>
<option value="pass" ${s.status==='pass'?'selected':''}>pass</option>
<option value="fail" ${s.status==='fail'?'selected':''}>fail</option>
<option value="skip" ${s.status==='skip'?'selected':''}>skip</option>
<option value="blocked" ${s.status==='blocked'?'selected':''}>blocked</option>
</select>
</td>
<td>
<textarea class="run-comment" placeholder="Kommentar">${escHTML(s.comment||'')}</textarea>
<input class="run-evidence" placeholder="Evidenz-URL" value="${escAttr(s.evidence||'')}">
</td>`;
els.stepsTableBody.appendChild(tr);
updateStatusClass(tr.querySelector('select.status'));
// nach jedem Render Schritt neu bewerten
recomputeGroupStyles();
});
}
function recomputeGroupStyles() {
try {
const body = els.stepsTableBody;
if (!body) return;
const rows = [...body.querySelectorAll('tr')];
// Collect groups with following step rows until next group
let groups = [];
let current = null;
rows.forEach(r => {
const kind = r.getAttribute('data-kind') || 'step';
if (kind === 'group') {
current = {
row: r,
steps: []
};
groups.push(current);
} else if (current) {
current.steps.push(r);
}
});
groups.forEach(g => {
g.row.classList.remove('group-ok', 'group-fail');
const statuses = g.steps.map(tr => (tr.querySelector('select.status')?.value || '').toUpperCase());
if (!statuses.length) return;
const allPass = statuses.every(s => s === 'PASS');
const anyFail = statuses.some(s => s === 'FAIL' || s === 'BLOCKED');
if (allPass) g.row.classList.add('group-ok');
else if (anyFail) g.row.classList.add('group-fail');
});
} catch (e) {
console.warn('recomputeGroupStyles failed', e);
}
}
function enforceDragHandleOnly() {
if (!els.stepsTableBody) return;
els.stepsTableBody.querySelectorAll('tr[draggable="true"]').forEach(tr => tr.setAttribute('draggable', 'false'));
els.stepsTableBody.querySelectorAll('.drag-handle').forEach(h => h.setAttribute('draggable', 'true'));
}

View File

@@ -1,101 +0,0 @@
// auto-split module
function updateStatusClass(selectEl) {
if (!selectEl) return;
const tr = selectEl.closest('tr');
['st-pass', 'st-fail', 'st-skip', 'st-blocked'].forEach(c => selectEl.classList.remove(c));
['row-pass', 'row-fail', 'row-skip', 'row-blocked'].forEach(c => tr.classList.remove(c));
const v = selectEl.value || '';
if (!v) return;
selectEl.classList.add('st-' + v);
tr.classList.add('row-' + v);
}
function bind() {
const b = document.getElementById('btnPushDocBee');
if (b) {
b.removeAttribute('disabled');
b.classList.remove('is-busy');
b.style.pointerEvents = 'auto';
b.addEventListener('click', postToDocBee, {
once: false
});
}
const p = document.getElementById('btnPrintPDF');
const btn = document.getElementById('btnExportAll');
if (btn) {
btn.addEventListener('click', e => {
e.preventDefault();
exportAll().catch(err => alert('Export error: ' + err.message));
});
console.log('Export-Button gebunden');
} else {
alert('Export-Button nicht gefunden (id=btnExportAll).');
}
}
function updateTokenBadge(state) {
const el = els.docbeeTokenStatus;
if (!el) return;
const has = !!(typeof DOCBEE_TOKEN !== 'undefined' && String(DOCBEE_TOKEN || '').trim().length > 10);
el.classList.remove('ok', 'bad', 'warn');
if (state === 'bad') {
el.textContent = 'DocBee: ungültig/401';
el.classList.add('bad');
return;
}
if (state === 'ok' || (state === undefined && has)) {
el.textContent = 'DocBee: Token gesetzt';
el.classList.add('ok');
return;
}
el.textContent = 'DocBee: fehlt';
el.classList.add('warn');
}
function checkRequired(run) {
const missing = run.steps.filter(s => (s.kind || 'step') === 'step' && s.required && !s.status);
if (missing.length > 0) {
alert("Folgende Pflichtschritte haben keinen Status:\n" +
missing.map(s => `${s.id} ${s.title}`).join("\n"));
return false;
}
return true;
}
const badge = (status) => {
const map = {
pass: {
txt: '✅ PASS',
bg: '#DCFCE7',
bd: '#22c55e'
},
fail: {
txt: '❌ FAIL',
bg: '#FEE2E2',
bd: '#ef4444'
},
skip: {
txt: '⏭️ SKIP',
bg: '#E5E7EB',
bd: '#6b7280'
},
blocked: {
txt: '⛔ BLOCK',
bg: '#FEF3C7',
bd: '#f59e0b'
},
'': {
txt: '—',
bg: '#F3F4F6',
bd: '#9ca3af'
}
};
const m = map[status || ''] || map[''];
return `<span style="display:inline-block;padding:2px 8px;border:1px solid ${m.bd};border-radius:999px;background:${m.bg};font-weight:600;font-size:12px">${m.txt}</span>`;
}

View File

@@ -1,96 +0,0 @@
// auto-split module
const DOCBEE_UI_BASE = DOCBEE_BASEURL; // feste Instanzbasis für UI-Links
const SEP = '────────────────────────────────────────────────────────────────────────';
const SMAP = {
pass: '✅',
fail: '❌',
skip: '⏭️',
blocked: '⛔'
};
// auto-split module
const escAttr = (s) => String(s ?? '').replaceAll('"', '&quot;');
const escHTML = (s) => String(s ?? '').replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
const safeName = (s) => String(s || '').replace(/\s+/g, '-').toLowerCase().replace(/[^\w.-]/g, '');
const linkify = (s) => /^https?:\/\//i.test(String(s || '')) ? `<a href="${escHTML(s)}" target="_blank" rel="noopener">${escHTML(s)}</a>` : escHTML(s);
const hasToken = () => typeof DOCBEE_TOKEN === 'string' && DOCBEE_TOKEN.trim().length > 0;
// auto-split module
function download(content, filename, type) {
const blob = new Blob([content], {
type
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
setTimeout(() => URL.revokeObjectURL(url), 1500);
}
function loadScript(url) {
return new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = url;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}
function extractTicketId(u) {
const m = String(u || "").match(/(?:\/ticket\/show\/|\/tickets\/)(\d+)/i);
return m ? m[1] : null;
}
function rowIndexOf(target) {
const tr = target.closest('tr');
if (!tr) return -1;
return [...els.stepsTableBody.children].indexOf(tr);
}
function replaceSymbols(s) {
return String(s)
// right arrows (→ ⇒ ⟶ ➔ ➜ …) → "->"
.replace(/[\u2192\u21A6\u21E8\u27A1\u2794\u27F6\u27F7\u27F9\u279D\u279E\u279F\u27A0]/g, '->')
// left arrows (← ⇐ ⟵ …) → "<-"
.replace(/[\u2190\u21A4\u21E6\u2B05\u27F5]/g, '<-')
// both directions (↔ ⇔ ⟷ …) → "<->"
.replace(/[\u2194\u21D4\u27F7\u27F8\u2B04]/g, '<->')
// normalize dashes
.replace(/[–—‑]/g, '-')
// ellipsis/multiply
.replace(/…/g, '...')
.replace(/×/g, 'x');
}
function sanitizeText(s) {
// Inline text normalization for labels/titles/comments
const out = String(s ?? '')
.replace(/\u00A0/g, ' ') // NBSP -> space
.replace(/[“”]/g, '"') // smart quotes -> ASCII
.replace(/[]/g, "'")
.replace(/\s+/g, ' ')
.trim();
// Map arrows/dashes and limit to PDF-safe charset
return toPdfSafe(replaceSymbols(out));
}

View File

@@ -1,32 +0,0 @@
<?php
require_once 'vendor/autoload.php';
include 'config/config.php';
use Jumbojett\OpenIDConnectClient;
session_start();
if (getenv('AUTH_DISABLED') === 'true') {
$_SESSION['user_authenticated'] = true;
if (empty($_SESSION['user_name'])) { $_SESSION['user_name'] = 'localtest'; }
header('Location: index.php');
exit();
}
// OpenID Connect Client konfigurieren
$oidc = new OpenIDConnectClient(
$oidc_provider, // OpenID Provider URL
$oidc_client, // Client ID
$oidc_secret // Client Secret
);
// Weiterleitungen konfigurieren
$oidc->setRedirectURL($OIDC_REDIRECT_URL);
// Scopes als Array hinzufügen
$oidc->addScope(['openid', 'profile', 'email', 'groups']);
// Benutzer zur Authentifizierungsseite weiterleiten
$oidc->authenticate();
?>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -1,10 +0,0 @@
<?php
session_start();
$olm = $_SESSION['olm'];
session_destroy();
session_start();
$_SESSION['olm'] = $olm;
$_SESSION['show'] = "50";
header('Location: index.php');
exit();
?>

View File

@@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
namespace phpseclib3\Common;
use phpseclib3\Exception\InvalidArgumentException;
/**
* @internal
*/
trait ConstantUtilityTrait
{
/** @var string[]|null */
private static $valueToConstantNameMap = null;
/**
* @param string|int $value
*/
public static function findConstantNameByValue($value): ?string
{
if (!self::$valueToConstantNameMap) {
$reflectionClass = new \ReflectionClass(static::class);
$constantNameToValueMap = $reflectionClass->getConstants();
self::$valueToConstantNameMap = array_flip($constantNameToValueMap);
}
if (isset(self::$valueToConstantNameMap[$value])) {
return self::$valueToConstantNameMap[$value];
}
return null;
}
/**
* @param string|int $value
*/
public static function getConstantNameByValue($value): string
{
$constantName = static::findConstantNameByValue($value);
if ($constantName === null) {
throw new InvalidArgumentException(sprintf('"%s" does not have constant with value "%s".', static::class, $value));
}
return $constantName;
}
}

View File

@@ -1,461 +0,0 @@
<?php
/**
* Common String Functions
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Common\Functions;
use ParagonIE\ConstantTime\Base64;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\ConstantTime\Hex;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Exception\LengthException;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\Common\FiniteField;
/**
* Common String Functions
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Strings
{
/**
* String Shift
*
* Inspired by array_shift
*/
public static function shift(string &$string, int $index = 1): string
{
$substr = substr($string, 0, $index);
$string = substr($string, $index);
return $substr;
}
/**
* String Pop
*
* Inspired by array_pop
*/
public static function pop(string &$string, int $index = 1): string
{
$substr = substr($string, -$index);
$string = substr($string, 0, -$index);
return $substr;
}
/**
* Parse SSH2-style string
*
* Returns either an array or a boolean if $data is malformed.
*
* Valid characters for $format are as follows:
*
* C = byte
* b = boolean (true/false)
* N = uint32
* Q = uint64
* s = string
* i = mpint
* L = name-list
*
* uint64 is not supported.
*/
public static function unpackSSH2(string $format, string &$data): array
{
$format = self::formatPack($format);
$result = [];
for ($i = 0; $i < strlen($format); $i++) {
switch ($format[$i]) {
case 'C':
case 'b':
if (!strlen($data)) {
throw new LengthException('At least one byte needs to be present for successful C / b decodes');
}
break;
case 'N':
case 'i':
case 's':
case 'L':
if (strlen($data) < 4) {
throw new LengthException('At least four byte needs to be present for successful N / i / s / L decodes');
}
break;
case 'Q':
if (strlen($data) < 8) {
throw new LengthException('At least eight byte needs to be present for successful N / i / s / L decodes');
}
break;
default:
throw new InvalidArgumentException('$format contains an invalid character');
}
switch ($format[$i]) {
case 'C':
$result[] = ord(self::shift($data));
continue 2;
case 'b':
$result[] = ord(self::shift($data)) != 0;
continue 2;
case 'N':
[, $temp] = unpack('N', self::shift($data, 4));
$result[] = $temp;
continue 2;
case 'Q':
// pack() added support for Q in PHP 5.6.3 and PHP 5.6 is phpseclib 3's minimum version
// so in theory we could support this BUT, "64-bit format codes are not available for
// 32-bit versions" and phpseclib works on 32-bit installs. on 32-bit installs
// 64-bit floats can be used to get larger numbers then 32-bit signed ints would allow
// for. sure, you're not gonna get the full precision of 64-bit numbers but just because
// you need > 32-bit precision doesn't mean you need the full 64-bit precision
extract(unpack('Nupper/Nlower', self::shift($data, 8)));
$temp = $upper ? 4294967296 * $upper : 0;
$temp += $lower < 0 ? ($lower & 0x7FFFFFFFF) + 0x80000000 : $lower;
// $temp = hexdec(bin2hex(self::shift($data, 8)));
$result[] = $temp;
continue 2;
}
[, $length] = unpack('N', self::shift($data, 4));
if (strlen($data) < $length) {
throw new LengthException("$length bytes needed; " . strlen($data) . ' bytes available');
}
$temp = self::shift($data, $length);
switch ($format[$i]) {
case 'i':
$result[] = new BigInteger($temp, -256);
break;
case 's':
$result[] = $temp;
break;
case 'L':
$result[] = explode(',', $temp);
}
}
return $result;
}
/**
* Create SSH2-style string
*
* @param string|int|float|array|bool ...$elements
*/
public static function packSSH2(string $format, ...$elements): string
{
$format = self::formatPack($format);
if (strlen($format) != count($elements)) {
throw new InvalidArgumentException('There must be as many arguments as there are characters in the $format string');
}
$result = '';
for ($i = 0; $i < strlen($format); $i++) {
$element = $elements[$i];
switch ($format[$i]) {
case 'C':
if (!is_int($element)) {
throw new InvalidArgumentException('Bytes must be represented as an integer between 0 and 255, inclusive.');
}
$result .= pack('C', $element);
break;
case 'b':
if (!is_bool($element)) {
throw new InvalidArgumentException('A boolean parameter was expected.');
}
$result .= $element ? "\1" : "\0";
break;
case 'Q':
if (!is_int($element) && !is_float($element)) {
throw new InvalidArgumentException('An integer was expected.');
}
// 4294967296 == 1 << 32
$result .= pack('NN', $element / 4294967296, $element);
break;
case 'N':
if (is_float($element)) {
$element = (int) $element;
}
if (!is_int($element)) {
throw new InvalidArgumentException('An integer was expected.');
}
$result .= pack('N', $element);
break;
case 's':
if (!self::is_stringable($element)) {
throw new InvalidArgumentException('A string was expected.');
}
$result .= pack('Na*', strlen($element), $element);
break;
case 'i':
if (!$element instanceof BigInteger && !$element instanceof FiniteField\Integer) {
throw new InvalidArgumentException('A phpseclib3\Math\BigInteger or phpseclib3\Math\Common\FiniteField\Integer object was expected.');
}
$element = $element->toBytes(true);
$result .= pack('Na*', strlen($element), $element);
break;
case 'L':
if (!is_array($element)) {
throw new InvalidArgumentException('An array was expected.');
}
$element = implode(',', $element);
$result .= pack('Na*', strlen($element), $element);
break;
default:
throw new InvalidArgumentException('$format contains an invalid character');
}
}
return $result;
}
/**
* Expand a pack string
*
* Converts C5 to CCCCC, for example.
*/
private static function formatPack(string $format): string
{
$parts = preg_split('#(\d+)#', $format, -1, PREG_SPLIT_DELIM_CAPTURE);
$format = '';
for ($i = 1; $i < count($parts); $i += 2) {
$format .= substr($parts[$i - 1], 0, -1) . str_repeat($parts[$i - 1][-1], (int) $parts[$i]);
}
$format .= $parts[$i - 1];
return $format;
}
/**
* Convert binary data into bits
*
* bin2hex / hex2bin refer to base-256 encoded data as binary, whilst
* decbin / bindec refer to base-2 encoded data as binary. For the purposes
* of this function, bin refers to base-256 encoded data whilst bits refers
* to base-2 encoded data
*/
public static function bits2bin(string $x): string
{
/*
// the pure-PHP approach is faster than the GMP approach
if (function_exists('gmp_export')) {
return strlen($x) ? gmp_export(gmp_init($x, 2)) : gmp_init(0);
}
*/
if (preg_match('#[^01]#', $x)) {
throw new RuntimeException('The only valid characters are 0 and 1');
}
if (!defined('PHP_INT_MIN')) {
define('PHP_INT_MIN', ~PHP_INT_MAX);
}
$length = strlen($x);
if (!$length) {
return '';
}
$block_size = PHP_INT_SIZE << 3;
$pad = $block_size - ($length % $block_size);
if ($pad != $block_size) {
$x = str_repeat('0', $pad) . $x;
}
$parts = str_split($x, $block_size);
$str = '';
foreach ($parts as $part) {
$xor = $part[0] == '1' ? PHP_INT_MIN : 0;
$part[0] = '0';
$str .= pack(
PHP_INT_SIZE == 4 ? 'N' : 'J',
$xor ^ eval('return 0b' . $part . ';')
);
}
return ltrim($str, "\0");
}
/**
* Convert bits to binary data
*/
public static function bin2bits(string $x, bool $trim = true): string
{
/*
// the pure-PHP approach is slower than the GMP approach BUT
// i want to the pure-PHP version to be easily unit tested as well
if (function_exists('gmp_import')) {
return gmp_strval(gmp_import($x), 2);
}
*/
$len = strlen($x);
$mod = $len % PHP_INT_SIZE;
if ($mod) {
$x = str_pad($x, $len + PHP_INT_SIZE - $mod, "\0", STR_PAD_LEFT);
}
$bits = '';
if (PHP_INT_SIZE == 4) {
$digits = unpack('N*', $x);
foreach ($digits as $digit) {
$bits .= sprintf('%032b', $digit);
}
} else {
$digits = unpack('J*', $x);
foreach ($digits as $digit) {
$bits .= sprintf('%064b', $digit);
}
}
return $trim ? ltrim($bits, '0') : $bits;
}
/**
* Switch Endianness Bit Order
*/
public static function switchEndianness(string $x): string
{
$r = '';
for ($i = strlen($x) - 1; $i >= 0; $i--) {
$b = ord($x[$i]);
if (PHP_INT_SIZE === 8) {
// 3 operations
// from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv
$r .= chr((($b * 0x0202020202) & 0x010884422010) % 1023);
} else {
// 7 operations
// from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits
$p1 = ($b * 0x0802) & 0x22110;
$p2 = ($b * 0x8020) & 0x88440;
$r .= chr(
(($p1 | $p2) * 0x10101) >> 16
);
}
}
return $r;
}
/**
* Increment the current string
*/
public static function increment_str(string &$var): string
{
if (function_exists('sodium_increment')) {
$var = strrev($var);
sodium_increment($var);
$var = strrev($var);
return $var;
}
for ($i = 4; $i <= strlen($var); $i += 4) {
$temp = substr($var, -$i, 4);
switch ($temp) {
case "\xFF\xFF\xFF\xFF":
$var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
break;
case "\x7F\xFF\xFF\xFF":
$var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
return $var;
default:
$temp = unpack('Nnum', $temp);
$var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
return $var;
}
}
$remainder = strlen($var) % 4;
if ($remainder == 0) {
return $var;
}
$temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
$temp = substr(pack('N', $temp['num'] + 1), -$remainder);
$var = substr_replace($var, $temp, 0, $remainder);
return $var;
}
/**
* Find whether the type of a variable is string (or could be converted to one)
*
* @psalm-assert-if-true string|\Stringable $var
*/
public static function is_stringable($var): bool
{
return is_string($var) || (is_object($var) && method_exists($var, '__toString'));
}
/**
* Constant Time Base64-decoding
*
* ParagoneIE\ConstantTime doesn't use libsodium if it's available so we'll do so
* ourselves. see https://github.com/paragonie/constant_time_encoding/issues/39
*/
public static function base64_decode(string $data): string
{
return function_exists('sodium_base642bin') ?
sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, '=') :
Base64::decode($data);
}
/**
* Constant Time Base64-decoding (URL safe)
*/
public static function base64url_decode(string $data): string
{
// return self::base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
return function_exists('sodium_base642bin') ?
sodium_base642bin($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, '=') :
Base64UrlSafe::decode($data);
}
/**
* Constant Time Base64-encoding
*/
public static function base64_encode(string $data): string
{
return function_exists('sodium_bin2base64') ?
sodium_bin2base64($data, SODIUM_BASE64_VARIANT_ORIGINAL) :
Base64::encode($data);
}
/**
* Constant Time Base64-encoding (URL safe)
*/
public static function base64url_encode(string $data): string
{
// return str_replace(['+', '/'], ['-', '_'], self::base64_encode($data));
return function_exists('sodium_bin2base64') ?
sodium_bin2base64($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING) :
Base64UrlSafe::encode($data);
}
/**
* Constant Time Hex Decoder
*/
public static function hex2bin(string $data): string
{
return function_exists('sodium_hex2bin') ?
sodium_hex2bin($data) :
Hex::decode($data);
}
/**
* Constant Time Hex Encoder
*/
public static function bin2hex(string $data): string
{
return function_exists('sodium_bin2hex') ?
sodium_bin2hex($data) :
Hex::encode($data);
}
}

View File

@@ -1,118 +0,0 @@
<?php
/**
* Pure-PHP implementation of AES.
*
* Uses OpenSSL, if available/possible, and an internal implementation, otherwise
*
* PHP version 5
*
* NOTE: Since AES.php is (for compatibility and phpseclib-historical reasons) virtually
* just a wrapper to Rijndael.php you may consider using Rijndael.php instead of
* to save one include_once().
*
* If {@link self::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
* {@link self::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's 136-bits
* it'll be null-padded to 192-bits and 192 bits will be the key length until {@link self::setKey() setKey()}
* is called, again, at which point, it'll be recalculated.
*
* Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't
* make a whole lot of sense. {@link self::setBlockLength() setBlockLength()}, for instance. Calling that function,
* however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one).
*
* Here's a short example of how to use this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $aes = new \phpseclib3\Crypt\AES('ctr');
*
* $aes->setKey('abcdefghijklmnop');
*
* $size = 10 * 1024;
* $plaintext = '';
* for ($i = 0; $i < $size; $i++) {
* $plaintext.= 'a';
* }
*
* echo $aes->decrypt($aes->encrypt($plaintext));
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2008 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt;
use phpseclib3\Exception\BadMethodCallException;
use phpseclib3\Exception\LengthException;
/**
* Pure-PHP implementation of AES.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class AES extends Rijndael
{
/**
* Dummy function
*
* Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything.
*
* @throws BadMethodCallException anytime it's called
* @see \phpseclib3\Crypt\Rijndael::setBlockLength()
*/
public function setBlockLength(int $length): void
{
throw new BadMethodCallException('The block length cannot be set for AES.');
}
/**
* Sets the key length
*
* Valid key lengths are 128, 192, and 256. Set the link to bool(false) to disable a fixed key length
*
* @throws LengthException if the key length isn't supported
* @see \phpseclib3\Crypt\Rijndael:setKeyLength()
*/
public function setKeyLength(int $length): void
{
switch ($length) {
case 128:
case 192:
case 256:
break;
default:
throw new LengthException('Key of size ' . $length . ' not supported by this algorithm. Only keys of sizes 128, 192 or 256 supported');
}
parent::setKeyLength($length);
}
/**
* Sets the key.
*
* Rijndael supports five different key lengths, AES only supports three.
*
* @throws LengthException if the key length isn't supported
* @see \phpseclib3\Crypt\Rijndael:setKey()
* @see setKeyLength()
*/
public function setKey(string $key): void
{
switch (strlen($key)) {
case 16:
case 24:
case 32:
break;
default:
throw new LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported');
}
parent::setKey($key);
}
}

View File

@@ -1,790 +0,0 @@
<?php
/**
* Pure-PHP implementation of Blowfish.
*
* Uses an internal implementation.
*
* PHP version 5
*
* Useful resources are as follows:
*
* - {@link http://en.wikipedia.org/wiki/Blowfish_(cipher) Wikipedia description of Blowfish}
*
* # An overview of bcrypt vs Blowfish
*
* OpenSSH private keys use a customized version of bcrypt. Specifically, instead of
* encrypting OrpheanBeholderScryDoubt 64 times OpenSSH's bcrypt variant encrypts
* OxychromaticBlowfishSwatDynamite 64 times. so we can't use crypt().
*
* bcrypt is basically Blowfish but instead of performing the key expansion once it performs
* the expansion 129 times for each round, with the first key expansion interleaving the salt
* and password. This renders OpenSSL unusable and forces us to use a pure-PHP implementation
* of blowfish.
*
* # phpseclib's three different _encryptBlock() implementations
*
* When using Blowfish as an encryption algorithm, _encryptBlock() is called 9 + 512 +
* (the number of blocks in the plaintext) times.
*
* Each of the first 9 calls to _encryptBlock() modify the P-array. Each of the next 512
* calls modify the S-boxes. The remaining _encryptBlock() calls operate on the plaintext to
* produce the ciphertext. In the pure-PHP implementation of Blowfish these remaining
* _encryptBlock() calls are highly optimized through the use of eval(). Among other things,
* P-array lookups are eliminated by hard-coding the key-dependent P-array values, and thus we
* have explained 2 of the 4 different _encryptBlock() implementations.
*
* With bcrypt things are a bit different. _encryptBlock() is called 1,079,296 times,
* assuming 16 rounds (which is what OpenSSH's bcrypt defaults to). The eval()-optimized
* _encryptBlock() isn't as beneficial because the P-array values are not constant. Well, they
* are constant, but only for, at most, 777 _encryptBlock() calls, which is equivalent to ~6KB
* of data. The average length of back to back _encryptBlock() calls with a fixed P-array is
* 514.12, which is ~4KB of data. Creating an eval()-optimized _encryptBlock() has an upfront
* cost, which is CPU dependent and is probably not going to be worth it for just ~4KB of
* data. Conseqeuently, bcrypt does not benefit from the eval()-optimized _encryptBlock().
*
* The regular _encryptBlock() does unpack() and pack() on every call, as well, and that can
* begin to add up after one million function calls.
*
* In theory, one might think that it might be beneficial to rewrite all block ciphers so
* that, instead of passing strings to _encryptBlock(), you convert the string to an array of
* integers and then pass successive subarrays of that array to _encryptBlock. This, however,
* kills PHP's memory use. Like let's say you have a 1MB long string. After doing
* $in = str_repeat('a', 1024 * 1024); PHP's memory utilization jumps up by ~1MB. After doing
* $blocks = str_split($in, 4); it jumps up by an additional ~16MB. After
* $blocks = array_map(fn($x) => unpack('N*', $x), $blocks); it jumps up by an additional
* ~90MB, yielding a 106x increase in memory usage. Consequently, it bcrypt calls a different
* _encryptBlock() then the regular Blowfish does. That said, the Blowfish _encryptBlock() is
* basically just a thin wrapper around the bcrypt _encryptBlock(), so there's that.
*
* # phpseclib's three different _setupKey() implementations
*
* Every bcrypt round is the equivalent of encrypting 512KB of data. Since OpenSSH uses 16
* rounds by default that's ~8MB of data that's essentially being encrypted whenever
* you use bcrypt. That's a lot of data, however, bcrypt operates within tighter constraints
* than regular Blowfish, so we can use that to our advantage. In particular, whereas Blowfish
* supports variable length keys, in bcrypt, the initial "key" is the sha512 hash of the
* password. sha512 hashes are 512 bits or 64 bytes long and thus the bcrypt keys are of a
* fixed length whereas Blowfish keys are not of a fixed length.
*
* bcrypt actually has two different key expansion steps. The first one (expandstate) is
* constantly XOR'ing every _encryptBlock() parameter against the salt prior _encryptBlock()'s
* being called. The second one (expand0state) is more similar to Blowfish's _setupKey()
* but it can still use the fixed length key optimization discussed above and can do away with
* the pack() / unpack() calls.
*
* I suppose _setupKey() could be made to be a thin wrapper around expandstate() but idk it's
* just a lot of work for very marginal benefits as _setupKey() is only called once for
* regular Blowfish vs the 128 times it's called --per round-- with bcrypt.
*
* # blowfish + bcrypt in the same class
*
* Altho there's a lot of Blowfish code that bcrypt doesn't re-use, bcrypt does re-use the
* initial S-boxes, the initial P-array and the int-only _encryptBlock() implementation.
*
* # Credit
*
* phpseclib's bcrypt implementation is based losely off of OpenSSH's implementation:
*
* https://github.com/openssh/openssh-portable/blob/master/openbsd-compat/bcrypt_pbkdf.c
*
* Here's a short example of how to use this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $blowfish = new \phpseclib3\Crypt\Blowfish('ctr');
*
* $blowfish->setKey('12345678901234567890123456789012');
*
* $plaintext = str_repeat('a', 1024);
*
* echo $blowfish->decrypt($blowfish->encrypt($plaintext));
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\BlockCipher;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Exception\LengthException;
/**
* Pure-PHP implementation of Blowfish.
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
*/
class Blowfish extends BlockCipher
{
/**
* Block Length of the cipher
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::block_size
* @var int
*/
protected $block_size = 8;
/**
* The fixed subkeys boxes ($sbox0 - $sbox3) with 256 entries each
*
* S-Box
*
* @var array
*/
private static $sbox = [
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
];
/**
* P-Array consists of 18 32-bit subkeys
*
* @var array
*/
private static $parray = [
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b,
];
/**
* The BCTX-working Array
*
* Holds the expanded key [p] and the key-depended s-boxes [sb]
*
* @var array
*/
private $bctx;
/**
* Holds the last used key
*
* @var array
*/
private $kl;
/**
* The Key Length (in bytes)
* {@internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk
* because the encryption / decryption / key schedule creation requires this number and not $key_length. We could
* derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
* of that, we'll just precompute it once.}
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::setKeyLength()
* @var int
*/
protected $key_length = 16;
/**
* Default Constructor.
*
* @throws InvalidArgumentException if an invalid / unsupported mode is provided
*/
public function __construct(string $mode)
{
parent::__construct($mode);
if ($this->mode == self::MODE_STREAM) {
throw new InvalidArgumentException('Block ciphers cannot be ran in stream mode');
}
}
/**
* Sets the key length.
*
* Key lengths can be between 32 and 448 bits.
*/
public function setKeyLength(int $length): void
{
if ($length < 32 || $length > 448) {
throw new LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes between 32 and 448 bits are supported');
}
$this->key_length = $length >> 3;
parent::setKeyLength($length);
}
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*/
protected function isValidEngineHelper(int $engine): bool
{
if ($engine == self::ENGINE_OPENSSL) {
if ($this->key_length < 16) {
return false;
}
// quoting https://www.openssl.org/news/openssl-3.0-notes.html, OpenSSL 3.0.1
// "Moved all variations of the EVP ciphers CAST5, BF, IDEA, SEED, RC2, RC4, RC5, and DES to the legacy provider"
// in theory openssl_get_cipher_methods() should catch this but, on GitHub Actions, at least, it does not
if (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
return false;
}
$this->cipher_name_openssl_ecb = 'bf-ecb';
$this->cipher_name_openssl = 'bf-' . $this->openssl_translate_mode();
}
return parent::isValidEngineHelper($engine);
}
/**
* Setup the key (expansion)
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::_setupKey()
*/
protected function setupKey(): void
{
if (isset($this->kl['key']) && $this->key === $this->kl['key']) {
// already expanded
return;
}
$this->kl = ['key' => $this->key];
/* key-expanding p[] and S-Box building sb[] */
$this->bctx = [
'p' => [],
'sb' => self::$sbox,
];
// unpack binary string in unsigned chars
$key = array_values(unpack('C*', $this->key));
$keyl = count($key);
// with bcrypt $keyl will always be 16 (because the key is the sha512 of the key you provide)
for ($j = 0, $i = 0; $i < 18; ++$i) {
// xor P1 with the first 32-bits of the key, xor P2 with the second 32-bits ...
for ($data = 0, $k = 0; $k < 4; ++$k) {
$data = ($data << 8) | $key[$j];
if (++$j >= $keyl) {
$j = 0;
}
}
$this->bctx['p'][] = self::$parray[$i] ^ intval($data);
}
// encrypt the zero-string, replace P1 and P2 with the encrypted data,
// encrypt P3 and P4 with the new P1 and P2, do it with all P-array and subkeys
$data = "\0\0\0\0\0\0\0\0";
for ($i = 0; $i < 18; $i += 2) {
[$l, $r] = array_values(unpack('N*', $data = $this->encryptBlock($data)));
$this->bctx['p'][$i ] = $l;
$this->bctx['p'][$i + 1] = $r;
}
for ($i = 0; $i < 0x400; $i += 0x100) {
for ($j = 0; $j < 256; $j += 2) {
[$l, $r] = array_values(unpack('N*', $data = $this->encryptBlock($data)));
$this->bctx['sb'][$i | $j] = $l;
$this->bctx['sb'][$i | ($j + 1)] = $r;
}
}
}
/**
* Initialize Static Variables
*/
protected static function initialize_static_variables(): void
{
if (is_float(self::$sbox[0x200])) {
self::$sbox = array_map('intval', self::$sbox);
self::$parray = array_map('intval', self::$parray);
}
parent::initialize_static_variables();
}
/**
* bcrypt
*/
private static function bcrypt_hash(string $sha2pass, string $sha2salt): string
{
$p = self::$parray;
$sbox = self::$sbox;
$cdata = array_values(unpack('N*', 'OxychromaticBlowfishSwatDynamite'));
$sha2pass = array_values(unpack('N*', $sha2pass));
$sha2salt = array_values(unpack('N*', $sha2salt));
self::expandstate($sha2salt, $sha2pass, $sbox, $p);
for ($i = 0; $i < 64; $i++) {
self::expand0state($sha2salt, $sbox, $p);
self::expand0state($sha2pass, $sbox, $p);
}
for ($i = 0; $i < 64; $i++) {
for ($j = 0; $j < 8; $j += 2) { // count($cdata) == 8
[$cdata[$j], $cdata[$j + 1]] = self::encryptBlockHelperFast($cdata[$j], $cdata[$j + 1], $sbox, $p);
}
}
return pack('V*', ...$cdata);
}
/**
* Performs OpenSSH-style bcrypt
*/
public static function bcrypt_pbkdf(string $pass, string $salt, int $keylen, int $rounds): string
{
self::initialize_static_variables();
if (PHP_INT_SIZE == 4) {
throw new \RuntimeException('bcrypt is far too slow to be practical on 32-bit versions of PHP');
}
$sha2pass = hash('sha512', $pass, true);
$results = [];
$count = 1;
while (32 * count($results) < $keylen) {
$countsalt = $salt . pack('N', $count++);
$sha2salt = hash('sha512', $countsalt, true);
$out = $tmpout = self::bcrypt_hash($sha2pass, $sha2salt);
for ($i = 1; $i < $rounds; $i++) {
$sha2salt = hash('sha512', $tmpout, true);
$tmpout = self::bcrypt_hash($sha2pass, $sha2salt);
$out ^= $tmpout;
}
$results[] = $out;
}
$output = '';
for ($i = 0; $i < 32; $i++) {
foreach ($results as $result) {
$output .= $result[$i];
}
}
return substr($output, 0, $keylen);
}
/**
* Key expansion without salt
*
* @access private
* @param int[] $key
* @param int[] $sbox
* @param int[] $p
* @see self::_bcrypt_hash()
*/
private static function expand0state(array $key, array &$sbox, array &$p): void
{
// expand0state is basically the same thing as this:
//return self::expandstate(array_fill(0, 16, 0), $key);
// but this separate function eliminates a bunch of XORs and array lookups
$p = [
$p[0] ^ $key[0],
$p[1] ^ $key[1],
$p[2] ^ $key[2],
$p[3] ^ $key[3],
$p[4] ^ $key[4],
$p[5] ^ $key[5],
$p[6] ^ $key[6],
$p[7] ^ $key[7],
$p[8] ^ $key[8],
$p[9] ^ $key[9],
$p[10] ^ $key[10],
$p[11] ^ $key[11],
$p[12] ^ $key[12],
$p[13] ^ $key[13],
$p[14] ^ $key[14],
$p[15] ^ $key[15],
$p[16] ^ $key[0],
$p[17] ^ $key[1],
];
// @codingStandardsIgnoreStart
[ $p[0], $p[1]] = self::encryptBlockHelperFast( 0, 0, $sbox, $p);
[ $p[2], $p[3]] = self::encryptBlockHelperFast($p[ 0], $p[ 1], $sbox, $p);
[ $p[4], $p[5]] = self::encryptBlockHelperFast($p[ 2], $p[ 3], $sbox, $p);
[ $p[6], $p[7]] = self::encryptBlockHelperFast($p[ 4], $p[ 5], $sbox, $p);
[ $p[8], $p[9]] = self::encryptBlockHelperFast($p[ 6], $p[ 7], $sbox, $p);
[$p[10], $p[11]] = self::encryptBlockHelperFast($p[ 8], $p[ 9], $sbox, $p);
[$p[12], $p[13]] = self::encryptBlockHelperFast($p[10], $p[11], $sbox, $p);
[$p[14], $p[15]] = self::encryptBlockHelperFast($p[12], $p[13], $sbox, $p);
[$p[16], $p[17]] = self::encryptBlockHelperFast($p[14], $p[15], $sbox, $p);
// @codingStandardsIgnoreEnd
[$sbox[0], $sbox[1]] = self::encryptBlockHelperFast($p[16], $p[17], $sbox, $p);
for ($i = 2; $i < 1024; $i += 2) {
[$sbox[$i], $sbox[$i + 1]] = self::encryptBlockHelperFast($sbox[$i - 2], $sbox[$i - 1], $sbox, $p);
}
}
/**
* Key expansion with salt
*
* @access private
* @param int[] $data
* @param int[] $key
* @param int[] $sbox
* @param int[] $p
* @see self::_bcrypt_hash()
*/
private static function expandstate(array $data, array $key, array &$sbox, array &$p): void
{
$p = [
$p[0] ^ $key[0],
$p[1] ^ $key[1],
$p[2] ^ $key[2],
$p[3] ^ $key[3],
$p[4] ^ $key[4],
$p[5] ^ $key[5],
$p[6] ^ $key[6],
$p[7] ^ $key[7],
$p[8] ^ $key[8],
$p[9] ^ $key[9],
$p[10] ^ $key[10],
$p[11] ^ $key[11],
$p[12] ^ $key[12],
$p[13] ^ $key[13],
$p[14] ^ $key[14],
$p[15] ^ $key[15],
$p[16] ^ $key[0],
$p[17] ^ $key[1],
];
// @codingStandardsIgnoreStart
[ $p[0], $p[1]] = self::encryptBlockHelperFast($data[ 0] , $data[ 1] , $sbox, $p);
[ $p[2], $p[3]] = self::encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox, $p);
[ $p[4], $p[5]] = self::encryptBlockHelperFast($data[ 4] ^ $p[ 2], $data[ 5] ^ $p[ 3], $sbox, $p);
[ $p[6], $p[7]] = self::encryptBlockHelperFast($data[ 6] ^ $p[ 4], $data[ 7] ^ $p[ 5], $sbox, $p);
[ $p[8], $p[9]] = self::encryptBlockHelperFast($data[ 8] ^ $p[ 6], $data[ 9] ^ $p[ 7], $sbox, $p);
[$p[10], $p[11]] = self::encryptBlockHelperFast($data[10] ^ $p[ 8], $data[11] ^ $p[ 9], $sbox, $p);
[$p[12], $p[13]] = self::encryptBlockHelperFast($data[12] ^ $p[10], $data[13] ^ $p[11], $sbox, $p);
[$p[14], $p[15]] = self::encryptBlockHelperFast($data[14] ^ $p[12], $data[15] ^ $p[13], $sbox, $p);
[$p[16], $p[17]] = self::encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox, $p);
// @codingStandardsIgnoreEnd
[$sbox[0], $sbox[1]] = self::encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox, $p);
for ($i = 2, $j = 4; $i < 1024; $i += 2, $j = ($j + 2) % 16) { // instead of 16 maybe count($data) would be better?
[$sbox[$i], $sbox[$i + 1]] = self::encryptBlockHelperFast($data[$j] ^ $sbox[$i - 2], $data[$j + 1] ^ $sbox[$i - 1], $sbox, $p);
}
}
/**
* Encrypts a block
*/
protected function encryptBlock(string $in): string
{
$p = $this->bctx['p'];
// extract($this->bctx['sb'], EXTR_PREFIX_ALL, 'sb'); // slower
$sb = $this->bctx['sb'];
$in = unpack('N*', $in);
$l = $in[1];
$r = $in[2];
[$r, $l] = PHP_INT_SIZE == 4 ?
self::encryptBlockHelperSlow($l, $r, $sb, $p) :
self::encryptBlockHelperFast($l, $r, $sb, $p);
return pack("N*", $r, $l);
}
/**
* Fast helper function for block encryption
*
* @access private
* @param int[] $sbox
* @param int[] $p
* @return int[]
*/
private static function encryptBlockHelperFast(int $x0, int $x1, array $sbox, array $p): array
{
$x0 ^= $p[0];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[1];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[2];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[3];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[4];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[5];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[6];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[7];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[8];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[9];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[10];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[11];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[12];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[13];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[14];
$x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[15];
$x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[16];
return [$x1 & 0xFFFFFFFF ^ $p[17], $x0 & 0xFFFFFFFF];
}
/**
* Slow helper function for block encryption
*
* @param int[] $sbox
* @param int[] $p
* @return int[]
*/
private static function encryptBlockHelperSlow(int $x0, int $x1, array $sbox0, array $sbox, array $p): array
{
// -16777216 == intval(0xFF000000) on 32-bit PHP installs
$x0 ^= $p[0];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[1];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[2];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[3];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[4];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[5];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[6];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[7];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[8];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[9];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[10];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[11];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[12];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[13];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[14];
$x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[15];
$x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[16];
return [$x1 ^ $p[17], $x0];
}
/**
* Decrypts a block
*/
protected function decryptBlock(string $in): string
{
$p = $this->bctx['p'];
$sb = $this->bctx['sb'];
$in = unpack('N*', $in);
$l = $in[1];
$r = $in[2];
for ($i = 17; $i > 2; $i -= 2) {
$l ^= $p[$i];
$r ^= intval((intval($sb[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]) ^
$sb[0x200 + ($l >> 8 & 0xff)]) +
$sb[0x300 + ($l & 0xff)]);
$r ^= $p[$i - 1];
$l ^= intval((intval($sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]) ^
$sb[0x200 + ($r >> 8 & 0xff)]) +
$sb[0x300 + ($r & 0xff)]);
}
return pack('N*', $r ^ $p[0], $l ^ $p[1]);
}
/**
* Setup the performance-optimized function for de/encrypt()
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::_setupInlineCrypt()
*/
protected function setupInlineCrypt(): void
{
$p = $this->bctx['p'];
$init_crypt = '
static $sb;
if (!$sb) {
$sb = $this->bctx["sb"];
}
';
// Generating encrypt code:
$encrypt_block = '
$in = unpack("N*", $in);
$l = $in[1];
$r = $in[2];
';
for ($i = 0; $i < 16; $i += 2) {
$encrypt_block .= '
$l^= ' . $p[$i] . ';
$r^= intval((intval($sb[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]) ^
$sb[0x200 + ($l >> 8 & 0xff)]) +
$sb[0x300 + ($l & 0xff)]);
$r^= ' . $p[$i + 1] . ';
$l^= intval((intval($sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]) ^
$sb[0x200 + ($r >> 8 & 0xff)]) +
$sb[0x300 + ($r & 0xff)]);
';
}
$encrypt_block .= '
$in = pack("N*",
$r ^ ' . $p[17] . ',
$l ^ ' . $p[16] . '
);
';
// Generating decrypt code:
$decrypt_block = '
$in = unpack("N*", $in);
$l = $in[1];
$r = $in[2];
';
for ($i = 17; $i > 2; $i -= 2) {
$decrypt_block .= '
$l^= ' . $p[$i] . ';
$r^= intval((intval($sb[$l >> 24 & 0xff] + $sb[$l >> 16 & 0xff]) ^
$sb[0x200 + ($l >> 8 & 0xff)]) +
$sb[0x300 + ($l & 0xff)]);
$r^= ' . $p[$i - 1] . ';
$l^= intval((intval($sb[$r >> 24 & 0xff] + $sb[$r >> 16 & 0xff]) ^
$sb[0x200 + ($r >> 8 & 0xff)]) +
$sb[0x300 + ($r & 0xff)]);
';
}
$decrypt_block .= '
$in = pack("N*",
$r ^ ' . $p[0] . ',
$l ^ ' . $p[1] . '
);
';
$this->inline_crypt = $this->createInlineCryptFunction(
[
'init_crypt' => $init_crypt,
'init_encrypt' => '',
'init_decrypt' => '',
'encrypt_block' => $encrypt_block,
'decrypt_block' => $decrypt_block,
]
);
}
}

View File

@@ -1,788 +0,0 @@
<?php
/**
* Pure-PHP implementation of ChaCha20.
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt;
use phpseclib3\Exception\BadDecryptionException;
use phpseclib3\Exception\InsufficientSetupException;
use phpseclib3\Exception\LengthException;
use phpseclib3\Exception\UnexpectedValueException;
/**
* Pure-PHP implementation of ChaCha20.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class ChaCha20 extends Salsa20
{
/**
* The OpenSSL specific name of the cipher
*
* @var string
*/
protected $cipher_name_openssl = 'chacha20';
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
*/
protected function isValidEngineHelper(int $engine): bool
{
switch ($engine) {
case self::ENGINE_LIBSODIUM:
// PHP 7.2.0 (30 Nov 2017) added support for libsodium
// we could probably make it so that if $this->counter == 0 then the first block would be done with either OpenSSL
// or PHP and then subsequent blocks would then be done with libsodium but idk - it's not a high priority atm
// we could also make it so that if $this->counter == 0 and $this->continuousBuffer then do the first string
// with libsodium and subsequent strings with openssl or pure-PHP but again not a high priority
return function_exists('sodium_crypto_aead_chacha20poly1305_ietf_encrypt') &&
$this->key_length == 32 &&
(($this->usePoly1305 && !isset($this->poly1305Key) && $this->counter == 0) || $this->counter == 1) &&
!$this->continuousBuffer;
case self::ENGINE_OPENSSL:
// OpenSSL 1.1.0 (released 25 Aug 2016) added support for chacha20.
// PHP didn't support OpenSSL 1.1.0 until 7.0.19 (11 May 2017)
// if you attempt to provide openssl with a 128 bit key (as opposed to a 256 bit key) openssl will null
// pad the key to 256 bits and still use the expansion constant for 256-bit keys. the fact that
// openssl treats the IV as both the counter and nonce, however, let's us use openssl in continuous mode
// whereas libsodium does not
if ($this->key_length != 32) {
return false;
}
}
return parent::isValidEngineHelper($engine);
}
/**
* Encrypts a message.
*
* @return string $ciphertext
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
* @see self::crypt()
*/
public function encrypt(string $plaintext): string
{
$this->setup();
if ($this->engine == self::ENGINE_LIBSODIUM) {
return $this->encrypt_with_libsodium($plaintext);
}
return parent::encrypt($plaintext);
}
/**
* Decrypts a message.
*
* $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
* At least if the continuous buffer is disabled.
*
* @return string $plaintext
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see self::crypt()
*/
public function decrypt(string $ciphertext): string
{
$this->setup();
if ($this->engine == self::ENGINE_LIBSODIUM) {
return $this->decrypt_with_libsodium($ciphertext);
}
return parent::decrypt($ciphertext);
}
/**
* Encrypts a message with libsodium
*
* @return string $text
* @see self::encrypt()
*/
private function encrypt_with_libsodium(string $plaintext): string
{
$params = [$plaintext, $this->aad, $this->nonce, $this->key];
$ciphertext = strlen($this->nonce) == 8 ?
sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
if (!$this->usePoly1305) {
return substr($ciphertext, 0, strlen($plaintext));
}
$newciphertext = substr($ciphertext, 0, strlen($plaintext));
$this->newtag = $this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12 ?
substr($ciphertext, strlen($plaintext)) :
$this->poly1305($newciphertext);
return $newciphertext;
}
/**
* Decrypts a message with libsodium
*
* @return string $text
* @see self::decrypt()
*/
private function decrypt_with_libsodium(string $ciphertext): string
{
$params = [$ciphertext, $this->aad, $this->nonce, $this->key];
if (isset($this->poly1305Key)) {
if ($this->oldtag === false) {
throw new InsufficientSetupException('Authentication Tag has not been set');
}
if ($this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12) {
$plaintext = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(...$params);
$this->oldtag = false;
if ($plaintext === false) {
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
}
return $plaintext;
}
$newtag = $this->poly1305($ciphertext);
if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
$this->oldtag = false;
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
}
$this->oldtag = false;
}
$plaintext = strlen($this->nonce) == 8 ?
sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
return substr($plaintext, 0, strlen($ciphertext));
}
/**
* Sets the nonce.
*/
public function setNonce(string $nonce): void
{
if (!is_string($nonce)) {
throw new UnexpectedValueException('The nonce should be a string');
}
/*
from https://tools.ietf.org/html/rfc7539#page-7
"Note also that the original ChaCha had a 64-bit nonce and 64-bit
block count. We have modified this here to be more consistent with
recommendations in Section 3.2 of [RFC5116]."
*/
switch (strlen($nonce)) {
case 8: // 64 bits
case 12: // 96 bits
break;
default:
throw new LengthException('Nonce of size ' . strlen($nonce) . ' not supported by this algorithm. Only 64-bit nonces or 96-bit nonces are supported');
}
$this->nonce = $nonce;
$this->changed = true;
$this->setEngine();
}
/**
* Setup the self::ENGINE_INTERNAL $engine
*
* (re)init, if necessary, the internal cipher $engine
*
* _setup() will be called each time if $changed === true
* typically this happens when using one or more of following public methods:
*
* - setKey()
*
* - setNonce()
*
* - First run of encrypt() / decrypt() with no init-settings
*
* @see self::setKey()
* @see self::setNonce()
* @see self::disableContinuousBuffer()
*/
protected function setup(): void
{
if (!$this->changed) {
return;
}
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
$this->changed = $this->nonIVChanged = false;
if ($this->nonce === false) {
throw new InsufficientSetupException('No nonce has been defined');
}
if ($this->key === false) {
throw new InsufficientSetupException('No key has been defined');
}
if ($this->usePoly1305 && !isset($this->poly1305Key)) {
$this->usingGeneratedPoly1305Key = true;
if ($this->engine == self::ENGINE_LIBSODIUM) {
return;
}
$this->createPoly1305Key();
}
$key = $this->key;
if (strlen($key) == 16) {
$constant = 'expand 16-byte k';
$key .= $key;
} else {
$constant = 'expand 32-byte k';
}
$this->p1 = $constant . $key;
$this->p2 = $this->nonce;
if (strlen($this->nonce) == 8) {
$this->p2 = "\0\0\0\0" . $this->p2;
}
}
/**
* The quarterround function
*/
protected static function quarterRound(int &$a, int &$b, int &$c, int &$d): void
{
// in https://datatracker.ietf.org/doc/html/rfc7539#section-2.1 the addition,
// xor'ing and rotation are all on the same line so i'm keeping it on the same
// line here as well
// @codingStandardsIgnoreStart
$a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 16);
$c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 12);
$a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 8);
$c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 7);
// @codingStandardsIgnoreEnd
}
/**
* The doubleround function
*
* @param int $x0 (by reference)
* @param int $x1 (by reference)
* @param int $x2 (by reference)
* @param int $x3 (by reference)
* @param int $x4 (by reference)
* @param int $x5 (by reference)
* @param int $x6 (by reference)
* @param int $x7 (by reference)
* @param int $x8 (by reference)
* @param int $x9 (by reference)
* @param int $x10 (by reference)
* @param int $x11 (by reference)
* @param int $x12 (by reference)
* @param int $x13 (by reference)
* @param int $x14 (by reference)
* @param int $x15 (by reference)
*/
protected static function doubleRound(int &$x0, int &$x1, int &$x2, int &$x3, int &$x4, int &$x5, int &$x6, int &$x7, int &$x8, int &$x9, int &$x10, int &$x11, int &$x12, int &$x13, int &$x14, int &$x15): void
{
// columnRound
static::quarterRound($x0, $x4, $x8, $x12);
static::quarterRound($x1, $x5, $x9, $x13);
static::quarterRound($x2, $x6, $x10, $x14);
static::quarterRound($x3, $x7, $x11, $x15);
// rowRound
static::quarterRound($x0, $x5, $x10, $x15);
static::quarterRound($x1, $x6, $x11, $x12);
static::quarterRound($x2, $x7, $x8, $x13);
static::quarterRound($x3, $x4, $x9, $x14);
}
/**
* The Salsa20 hash function function
*
* On my laptop this loop unrolled / function dereferenced version of parent::salsa20 encrypts 1mb of text in
* 0.65s vs the 0.85s that it takes with the parent method.
*
* If we were free to assume that the host OS would always be 64-bits then the if condition in leftRotate could
* be eliminated and we could knock this done to 0.60s.
*
* For comparison purposes, RC4 takes 0.16s and AES in CTR mode with the Eval engine takes 0.48s.
* AES in CTR mode with the PHP engine takes 1.19s. Salsa20 / ChaCha20 do not benefit as much from the Eval
* approach due to the fact that there are a lot less variables to de-reference, fewer loops to unroll, etc
*/
protected static function salsa20(string $x)
{
[, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15] = unpack('V*', $x);
$z0 = $x0;
$z1 = $x1;
$z2 = $x2;
$z3 = $x3;
$z4 = $x4;
$z5 = $x5;
$z6 = $x6;
$z7 = $x7;
$z8 = $x8;
$z9 = $x9;
$z10 = $x10;
$z11 = $x11;
$z12 = $x12;
$z13 = $x13;
$z14 = $x14;
$z15 = $x15;
// @codingStandardsIgnoreStart
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// @codingStandardsIgnoreEnd
$x0 += $z0;
$x1 += $z1;
$x2 += $z2;
$x3 += $z3;
$x4 += $z4;
$x5 += $z5;
$x6 += $z6;
$x7 += $z7;
$x8 += $z8;
$x9 += $z9;
$x10 += $z10;
$x11 += $z11;
$x12 += $z12;
$x13 += $z13;
$x14 += $z14;
$x15 += $z15;
return pack('V*', $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15);
}
}

View File

@@ -1,532 +0,0 @@
<?php
/**
* Base Class for all asymmetric key ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common;
use phpseclib3\Crypt\Hash;
use phpseclib3\Exception\NoKeyLoadedException;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\Math\BigInteger;
/**
* Base Class for all asymmetric cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class AsymmetricKey
{
/**
* Precomputed Zero
*
* @var BigInteger
*/
protected static $zero;
/**
* Precomputed One
*
* @var BigInteger
*/
protected static $one;
/**
* Format of the loaded key
*
* @var string
*/
protected $format;
/**
* Hash function
*
* @var Hash
*/
protected $hash;
/**
* HMAC function
*
* @var Hash
*/
private $hmac;
/**
* Supported plugins (lower case)
*
* @see self::initialize_static_variables()
* @var array
*/
private static $plugins = [];
/**
* Invisible plugins
*
* @see self::initialize_static_variables()
* @var array
*/
private static $invisiblePlugins = [];
/**
* Available Engines
*
* @var boolean[]
*/
protected static $engines = [];
/**
* Key Comment
*
* @var null|string
*/
private $comment;
abstract public function toString(string $type, array $options = []): array|string;
/**
* The constructor
*/
protected function __construct()
{
self::initialize_static_variables();
$this->hash = new Hash('sha256');
$this->hmac = new Hash('sha256');
}
/**
* Initialize static variables
*/
protected static function initialize_static_variables(): void
{
if (!isset(self::$zero)) {
self::$zero = new BigInteger(0);
self::$one = new BigInteger(1);
}
self::loadPlugins('Keys');
if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') {
self::loadPlugins('Signature');
}
}
/**
* Load the key
*
* @param string|array $key
* @return PublicKey|PrivateKey
*/
public static function load($key, ?string $password = null): AsymmetricKey
{
self::initialize_static_variables();
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('load() should not be called from final classes (' . static::class . ')');
}
$components = false;
foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) {
if (isset(self::$invisiblePlugins[static::ALGORITHM]) && in_array($format, self::$invisiblePlugins[static::ALGORITHM])) {
continue;
}
try {
$components = $format::load($key, $password);
} catch (\Exception $e) {
$components = false;
}
if ($components !== false) {
break;
}
}
if ($components === false) {
throw new NoKeyLoadedException('Unable to read key');
}
$components['format'] = $format;
$components['secret'] ??= '';
$comment = $components['comment'] ?? null;
$new = static::onLoad($components);
$new->format = $format;
$new->comment = $comment;
return $new instanceof PrivateKey ?
$new->withPassword($password) :
$new;
}
/**
* Loads a private key
*
* @param string|array $key
* @param string $password optional
*/
public static function loadPrivateKey($key, string $password = ''): PrivateKey
{
$key = self::load($key, $password);
if (!$key instanceof PrivateKey) {
throw new NoKeyLoadedException('The key that was loaded was not a private key');
}
return $key;
}
/**
* Loads a public key
*
* @param string|array $key
*/
public static function loadPublicKey($key): PublicKey
{
$key = self::load($key);
if (!$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a public key');
}
return $key;
}
/**
* Loads parameters
*
* @param string|array $key
*/
public static function loadParameters($key): AsymmetricKey
{
$key = self::load($key);
if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a parameter');
}
return $key;
}
/**
* Load the key, assuming a specific format
*
* @return static
*/
public static function loadFormat(string $type, string $key, ?string $password = null): AsymmetricKey
{
self::initialize_static_variables();
$components = false;
$format = strtolower($type);
if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) {
$format = self::$plugins[static::ALGORITHM]['Keys'][$format];
$components = $format::load($key, $password);
}
if ($components === false) {
throw new NoKeyLoadedException('Unable to read key');
}
$components['format'] = $format;
$components['secret'] ??= '';
$new = static::onLoad($components);
$new->format = $format;
return $new instanceof PrivateKey ?
$new->withPassword($password) :
$new;
}
/**
* Loads a private key
*/
public static function loadPrivateKeyFormat(string $type, string $key, ?string $password = null): PrivateKey
{
$key = self::loadFormat($type, $key, $password);
if (!$key instanceof PrivateKey) {
throw new NoKeyLoadedException('The key that was loaded was not a private key');
}
return $key;
}
/**
* Loads a public key
*/
public static function loadPublicKeyFormat(string $type, string $key): PublicKey
{
$key = self::loadFormat($type, $key);
if (!$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a public key');
}
return $key;
}
/**
* Loads parameters
*
* @param string|array $key
*/
public static function loadParametersFormat(string $type, $key): AsymmetricKey
{
$key = self::loadFormat($type, $key);
if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a parameter');
}
return $key;
}
/**
* Validate Plugin
*
* @param string|null $method optional
*/
protected static function validatePlugin(string $format, string $type, ?string $method = null)
{
$type = strtolower($type);
if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) {
throw new UnsupportedFormatException("$type is not a supported format");
}
$type = self::$plugins[static::ALGORITHM][$format][$type];
if (isset($method) && !method_exists($type, $method)) {
throw new UnsupportedFormatException("$type does not implement $method");
}
return $type;
}
/**
* Load Plugins
*/
private static function loadPlugins(string $format): void
{
if (!isset(self::$plugins[static::ALGORITHM][$format])) {
self::$plugins[static::ALGORITHM][$format] = [];
foreach (new \DirectoryIterator(__DIR__ . '/../' . static::ALGORITHM . '/Formats/' . $format . '/') as $file) {
if ($file->getExtension() != 'php') {
continue;
}
$name = $file->getBasename('.php');
if ($name[0] == '.') {
continue;
}
$type = 'phpseclib3\Crypt\\' . static::ALGORITHM . '\\Formats\\' . $format . '\\' . $name;
$reflect = new \ReflectionClass($type);
if ($reflect->isTrait()) {
continue;
}
self::$plugins[static::ALGORITHM][$format][strtolower($name)] = $type;
if ($reflect->hasConstant('IS_INVISIBLE')) {
self::$invisiblePlugins[static::ALGORITHM][] = $type;
}
}
}
}
/**
* Returns a list of supported formats.
*/
public static function getSupportedKeyFormats(): array
{
self::initialize_static_variables();
return self::$plugins[static::ALGORITHM]['Keys'];
}
/**
* Add a fileformat plugin
*
* The plugin needs to either already be loaded or be auto-loadable.
* Loading a plugin whose shortname overwrite an existing shortname will overwrite the old plugin.
*
* @see self::load()
*/
public static function addFileFormat(string $fullname): void
{
self::initialize_static_variables();
if (class_exists($fullname)) {
$meta = new \ReflectionClass($fullname);
$shortname = $meta->getShortName();
self::$plugins[static::ALGORITHM]['Keys'][strtolower($shortname)] = $fullname;
if ($meta->hasConstant('IS_INVISIBLE')) {
self::$invisiblePlugins[static::ALGORITHM][] = strtolower($shortname);
}
}
}
/**
* Returns the format of the loaded key.
*
* If the key that was loaded wasn't in a valid or if the key was auto-generated
* with RSA::createKey() then this will throw an exception.
*
* @see self::load()
*/
public function getLoadedFormat(): string
{
if (empty($this->format)) {
throw new NoKeyLoadedException('This key was created with createKey - it was not loaded with load. Therefore there is no "loaded format"');
}
$meta = new \ReflectionClass($this->format);
return $meta->getShortName();
}
/**
* Returns the key's comment
*
* Not all key formats support comments. If you want to set a comment use toString()
*/
public function getComment(): ?string
{
return $this->comment;
}
/**
* Tests engine validity
*/
public static function useBestEngine(): array
{
static::$engines = [
'PHP' => true,
'OpenSSL' => extension_loaded('openssl'),
// this test can be satisfied by either of the following:
// http://php.net/manual/en/book.sodium.php
// https://github.com/paragonie/sodium_compat
'libsodium' => function_exists('sodium_crypto_sign_keypair'),
];
return static::$engines;
}
/**
* Flag to use internal engine only (useful for unit testing)
*/
public static function useInternalEngine(): void
{
static::$engines = [
'PHP' => true,
'OpenSSL' => false,
'libsodium' => false,
];
}
/**
* __toString() magic method
*
* @return string
*/
public function __toString()
{
return $this->toString('PKCS8');
}
/**
* Determines which hashing function should be used
*/
public function withHash(string $hash): AsymmetricKey
{
$new = clone $this;
$new->hash = new Hash($hash);
$new->hmac = new Hash($hash);
return $new;
}
/**
* Returns the hash algorithm currently being used
*/
public function getHash(): Hash
{
return clone $this->hash;
}
/**
* Compute the pseudorandom k for signature generation,
* using the process specified for deterministic DSA.
*
* @return string
*/
protected function computek(string $h1)
{
$v = str_repeat("\1", strlen($h1));
$k = str_repeat("\0", strlen($h1));
$x = $this->int2octets($this->x);
$h1 = $this->bits2octets($h1);
$this->hmac->setKey($k);
$k = $this->hmac->hash($v . "\0" . $x . $h1);
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
$k = $this->hmac->hash($v . "\1" . $x . $h1);
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
$qlen = $this->q->getLengthInBytes();
while (true) {
$t = '';
while (strlen($t) < $qlen) {
$v = $this->hmac->hash($v);
$t = $t . $v;
}
$k = $this->bits2int($t);
if (!$k->equals(self::$zero) && $k->compare($this->q) < 0) {
break;
}
$k = $this->hmac->hash($v . "\0");
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
}
return $k;
}
/**
* Integer to Octet String
*/
private function int2octets(BigInteger $v): string
{
$out = $v->toBytes();
$rolen = $this->q->getLengthInBytes();
if (strlen($out) < $rolen) {
return str_pad($out, $rolen, "\0", STR_PAD_LEFT);
} elseif (strlen($out) > $rolen) {
return substr($out, -$rolen);
} else {
return $out;
}
}
/**
* Bit String to Integer
*/
protected function bits2int(string $in): BigInteger
{
$v = new BigInteger($in, 256);
$vlen = strlen($in) << 3;
$qlen = $this->q->getLength();
if ($vlen > $qlen) {
return $v->bitwise_rightShift($vlen - $qlen);
}
return $v;
}
/**
* Bit String to Octet String
*/
private function bits2octets(string $in): string
{
$z1 = $this->bits2int($in);
$z2 = $z1->subtract($this->q);
return $z2->compare(self::$zero) < 0 ?
$this->int2octets($z1) :
$this->int2octets($z2);
}
}

View File

@@ -1,26 +0,0 @@
<?php
/**
* Base Class for all block ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common;
/**
* Base Class for all block cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class BlockCipher extends SymmetricKey
{
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* JSON Web Key (RFC7517) Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
/**
* JSON Web Key Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class JWK
{
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
protected static function loadHelper($key): \stdClass
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$key = preg_replace('#\s#', '', $key); // remove whitespace
$key = json_decode($key, null, 512, JSON_THROW_ON_ERROR);
if (isset($key->kty)) {
return $key;
}
if (count($key->keys) != 1) {
throw new \RuntimeException('Although the JWK key format supports multiple keys phpseclib does not');
}
return $key->keys[0];
}
/**
* Wrap a key appropriately
*/
protected static function wrapKey(array $key, array $options): string
{
return json_encode(['keys' => [$key + $options]]);
}
}

View File

@@ -1,217 +0,0 @@
<?php
/**
* OpenSSH Key Handler
*
* PHP version 5
*
* Place in $HOME/.ssh/authorized_keys
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Random;
use phpseclib3\Exception\BadDecryptionException;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnexpectedValueException;
/**
* OpenSSH Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class OpenSSH
{
/**
* Default comment
*
* @var string
*/
protected static $comment = 'phpseclib-generated-key';
/**
* Binary key flag
*
* @var bool
*/
protected static $binary = false;
/**
* Sets the default comment
*/
public static function setComment(string $comment): void
{
self::$comment = str_replace(["\r", "\n"], '', $comment);
}
/**
* Break a public or private key down into its constituent components
*
* $type can be either ssh-dss or ssh-rsa
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
// key format is described here:
// https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
if (str_contains($key, 'BEGIN OPENSSH PRIVATE KEY')) {
$key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key);
$key = Strings::base64_decode($key);
$magic = Strings::shift($key, 15);
if ($magic != "openssh-key-v1\0") {
throw new RuntimeException('Expected openssh-key-v1');
}
[$ciphername, $kdfname, $kdfoptions, $numKeys] = Strings::unpackSSH2('sssN', $key);
if ($numKeys != 1) {
// if we wanted to support multiple keys we could update PublicKeyLoader to preview what the # of keys
// would be; it'd then call Common\Keys\OpenSSH.php::load() and get the paddedKey. it'd then pass
// that to the appropriate key loading parser $numKey times or something
throw new RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not');
}
switch ($ciphername) {
case 'none':
break;
case 'aes256-ctr':
if ($kdfname != 'bcrypt') {
throw new RuntimeException('Only the bcrypt kdf is supported (' . $kdfname . ' encountered)');
}
[$salt, $rounds] = Strings::unpackSSH2('sN', $kdfoptions);
$crypto = new AES('ctr');
//$crypto->setKeyLength(256);
//$crypto->disablePadding();
$crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
break;
default:
throw new RuntimeException('The only supported ciphers are: none, aes256-ctr (' . $ciphername . ' is being used)');
}
[$publicKey, $paddedKey] = Strings::unpackSSH2('ss', $key);
[$type] = Strings::unpackSSH2('s', $publicKey);
if (isset($crypto)) {
$paddedKey = $crypto->decrypt($paddedKey);
}
[$checkint1, $checkint2] = Strings::unpackSSH2('NN', $paddedKey);
// any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc.
if ($checkint1 != $checkint2) {
if (isset($crypto)) {
throw new BadDecryptionException('Unable to decrypt key - please verify the password you are using');
}
throw new RuntimeException("The two checkints do not match ($checkint1 vs. $checkint2)");
}
self::checkType($type);
return compact('type', 'publicKey', 'paddedKey');
}
$parts = explode(' ', $key, 3);
if (!isset($parts[1])) {
$key = base64_decode($parts[0]);
$comment = false;
} else {
$asciiType = $parts[0];
self::checkType($parts[0]);
$key = base64_decode($parts[1]);
$comment = $parts[2] ?? false;
}
if ($key === false) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
[$type] = Strings::unpackSSH2('s', $key);
self::checkType($type);
if (isset($asciiType) && $asciiType != $type) {
throw new RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type);
}
if (strlen($key) <= 4) {
throw new UnexpectedValueException('Key appears to be malformed');
}
$publicKey = $key;
return compact('type', 'publicKey', 'comment');
}
/**
* Toggle between binary and printable keys
*
* Printable keys are what are generated by default. These are the ones that go in
* $HOME/.ssh/authorized_key.
*/
public static function setBinaryOutput(bool $enabled): void
{
self::$binary = $enabled;
}
/**
* Checks to see if the type is valid
*/
private static function checkType(string $candidate): void
{
if (!in_array($candidate, static::$types)) {
throw new RuntimeException("The key type ($candidate) is not equal to: " . implode(',', static::$types));
}
}
/**
* Wrap a private key appropriately
*
* @param string|false $password
*/
protected static function wrapPrivateKey(string $publicKey, string $privateKey, $password, array $options): string
{
[, $checkint] = unpack('N', Random::string(4));
$comment = $options['comment'] ?? self::$comment;
$paddedKey = Strings::packSSH2('NN', $checkint, $checkint) .
$privateKey .
Strings::packSSH2('s', $comment);
$usesEncryption = !empty($password) && is_string($password);
/*
from http://tools.ietf.org/html/rfc4253#section-6 :
Note that the length of the concatenation of 'packet_length',
'padding_length', 'payload', and 'random padding' MUST be a multiple
of the cipher block size or 8, whichever is larger.
*/
$blockSize = $usesEncryption ? 16 : 8;
$paddingLength = (($blockSize - 1) * strlen($paddedKey)) % $blockSize;
for ($i = 1; $i <= $paddingLength; $i++) {
$paddedKey .= chr($i);
}
if (!$usesEncryption) {
$key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey);
} else {
$rounds = $options['rounds'] ?? 16;
$salt = Random::string(16);
$kdfoptions = Strings::packSSH2('sN', $salt, $rounds);
$crypto = new AES('ctr');
$crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
$paddedKey = $crypto->encrypt($paddedKey);
$key = Strings::packSSH2('sssNss', 'aes256-ctr', 'bcrypt', $kdfoptions, 1, $publicKey, $paddedKey);
}
$key = "openssh-key-v1\0$key";
return "-----BEGIN OPENSSH PRIVATE KEY-----\n" .
chunk_split(Strings::base64_encode($key), 70, "\n") .
"-----END OPENSSH PRIVATE KEY-----\n";
}
}

View File

@@ -1,71 +0,0 @@
<?php
/**
* PKCS Formatted Key Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Formats\Keys;
/**
* PKCS1 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS
{
/**
* Auto-detect the format
*/
public const MODE_ANY = 0;
/**
* Require base64-encoded PEM's be supplied
*/
public const MODE_PEM = 1;
/**
* Require raw DER's be supplied
*/
public const MODE_DER = 2;
/**#@-*/
/**
* Is the key a base-64 encoded PEM, DER or should it be auto-detected?
*
* @var int
*/
protected static $format = self::MODE_ANY;
/**
* Require base64-encoded PEM's be supplied
*/
public static function requirePEM(): void
{
self::$format = self::MODE_PEM;
}
/**
* Require raw DER's be supplied
*/
public static function requireDER(): void
{
self::$format = self::MODE_DER;
}
/**
* Accept any format and auto detect the format
*
* This is the default setting
*/
public static function requireAny(): void
{
self::$format = self::MODE_ANY;
}
}

View File

@@ -1,195 +0,0 @@
<?php
/**
* PKCS1 Formatted Key Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\DES;
use phpseclib3\Crypt\Random;
use phpseclib3\Crypt\TripleDES;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\File\ASN1;
/**
* PKCS1 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS1 extends PKCS
{
/**
* Default encryption algorithm
*
* @var string
*/
private static $defaultEncryptionAlgorithm = 'AES-128-CBC';
/**
* Sets the default encryption algorithm
*/
public static function setEncryptionAlgorithm(string $algo): void
{
self::$defaultEncryptionAlgorithm = $algo;
}
/**
* Returns the mode constant corresponding to the mode string
*
* @return int
* @throws UnexpectedValueException if the block cipher mode is unsupported
*/
private static function getEncryptionMode(string $mode)
{
switch ($mode) {
case 'CBC':
case 'ECB':
case 'CFB':
case 'OFB':
case 'CTR':
return $mode;
}
throw new UnexpectedValueException('Unsupported block cipher mode of operation');
}
/**
* Returns a cipher object corresponding to a string
*
* @return AES|DES|TripleDES
* @throws UnexpectedValueException if the encryption algorithm is unsupported
*/
private static function getEncryptionObject(string $algo)
{
$modes = '(CBC|ECB|CFB|OFB|CTR)';
switch (true) {
case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches):
$cipher = new AES(self::getEncryptionMode($matches[2]));
$cipher->setKeyLength((int) $matches[1]);
return $cipher;
case preg_match("#^DES-EDE3-$modes$#", $algo, $matches):
return new TripleDES(self::getEncryptionMode($matches[1]));
case preg_match("#^DES-$modes$#", $algo, $matches):
return new DES(self::getEncryptionMode($matches[1]));
default:
throw new UnsupportedAlgorithmException($algo . ' is not a supported algorithm');
}
}
/**
* Generate a symmetric key for PKCS#1 keys
*/
private static function generateSymmetricKey(string $password, string $iv, int $length): string
{
$symkey = '';
$iv = substr($iv, 0, 8);
while (strlen($symkey) < $length) {
$symkey .= md5($symkey . $password . $iv, true);
}
return substr($symkey, 0, $length);
}
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
* @return array|string
*/
protected static function load($key, ?string $password = null)
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
/* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
"outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding
two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here:
http://tools.ietf.org/html/rfc1421#section-4.6.1.1
http://tools.ietf.org/html/rfc1421#section-4.6.1.3
DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
own implementation. ie. the implementation *is* the standard and any bugs that may exist in that
implementation are part of the standard, as well.
* OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */
if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
$iv = Strings::hex2bin(trim($matches[2]));
// remove the Proc-Type / DEK-Info sections as they're no longer needed
$key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
$ciphertext = ASN1::extractBER($key);
if ($ciphertext === false) {
$ciphertext = $key;
}
$crypto = self::getEncryptionObject($matches[1]);
$crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3));
$crypto->setIV($iv);
$key = $crypto->decrypt($ciphertext);
} else {
if (self::$format != self::MODE_DER) {
$decoded = ASN1::extractBER($key);
if ($decoded !== false) {
$key = $decoded;
} elseif (self::$format == self::MODE_PEM) {
throw new UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
}
}
}
return $key;
}
/**
* Wrap a private key appropriately
*
* @param string|false $password
* @param array $options optional
*/
protected static function wrapPrivateKey(string $key, string $type, $password, array $options = []): string
{
if (empty($password) || !is_string($password)) {
return "-----BEGIN $type PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END $type PRIVATE KEY-----";
}
$encryptionAlgorithm = $options['encryptionAlgorithm'] ?? self::$defaultEncryptionAlgorithm;
$cipher = self::getEncryptionObject($encryptionAlgorithm);
$iv = Random::string($cipher->getBlockLength() >> 3);
$cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3));
$cipher->setIV($iv);
$iv = strtoupper(Strings::bin2hex($iv));
return "-----BEGIN $type PRIVATE KEY-----\r\n" .
"Proc-Type: 4,ENCRYPTED\r\n" .
"DEK-Info: " . $encryptionAlgorithm . ",$iv\r\n" .
"\r\n" .
chunk_split(Strings::base64_encode($cipher->encrypt($key)), 64) .
"-----END $type PRIVATE KEY-----";
}
/**
* Wrap a public key appropriately
*/
protected static function wrapPublicKey(string $key, string $type): string
{
return "-----BEGIN $type PUBLIC KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END $type PUBLIC KEY-----";
}
}

View File

@@ -1,697 +0,0 @@
<?php
/**
* PKCS#8 Formatted Key Handler
*
* PHP version 5
*
* Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
* is specific to private keys it's basically creating a DER-encoded wrapper
* for keys. This just extends that same concept to public keys (much like ssh-keygen)
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Common\SymmetricKey;
use phpseclib3\Crypt\DES;
use phpseclib3\Crypt\Random;
use phpseclib3\Crypt\RC2;
use phpseclib3\Crypt\RC4;
use phpseclib3\Crypt\TripleDES;
use phpseclib3\Exception\InsufficientSetupException;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
/**
* PKCS#8 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS8 extends PKCS
{
/**
* Default encryption algorithm
*
* @var string
*/
private static $defaultEncryptionAlgorithm = 'id-PBES2';
/**
* Default encryption scheme
*
* Only used when defaultEncryptionAlgorithm is id-PBES2
*
* @var string
*/
private static $defaultEncryptionScheme = 'aes128-CBC-PAD';
/**
* Default PRF
*
* Only used when defaultEncryptionAlgorithm is id-PBES2
*
* @var string
*/
private static $defaultPRF = 'id-hmacWithSHA256';
/**
* Default Iteration Count
*
* @var int
*/
private static $defaultIterationCount = 2048;
/**
* OIDs loaded
*
* @var bool
*/
private static $oidsLoaded = false;
/**
* Sets the default encryption algorithm
*/
public static function setEncryptionAlgorithm(string $algo): void
{
self::$defaultEncryptionAlgorithm = $algo;
}
/**
* Sets the default encryption algorithm for PBES2
*/
public static function setEncryptionScheme(string $algo): void
{
self::$defaultEncryptionScheme = $algo;
}
/**
* Sets the iteration count
*/
public static function setIterationCount(int $count): void
{
self::$defaultIterationCount = $count;
}
/**
* Sets the PRF for PBES2
*/
public static function setPRF(string $algo): void
{
self::$defaultPRF = $algo;
}
/**
* Returns a SymmetricKey object based on a PBES1 $algo
*
* @return SymmetricKey
*/
private static function getPBES1EncryptionObject(string $algo)
{
$algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ?
$matches[1] :
substr($algo, 13); // strlen('pbeWithSHAAnd') == 13
switch ($algo) {
case 'DES':
$cipher = new DES('cbc');
break;
case 'RC2':
$cipher = new RC2('cbc');
$cipher->setKeyLength(64);
break;
case '3-KeyTripleDES':
$cipher = new TripleDES('cbc');
break;
case '2-KeyTripleDES':
$cipher = new TripleDES('cbc');
$cipher->setKeyLength(128);
break;
case '128BitRC2':
$cipher = new RC2('cbc');
$cipher->setKeyLength(128);
break;
case '40BitRC2':
$cipher = new RC2('cbc');
$cipher->setKeyLength(40);
break;
case '128BitRC4':
$cipher = new RC4();
$cipher->setKeyLength(128);
break;
case '40BitRC4':
$cipher = new RC4();
$cipher->setKeyLength(40);
break;
default:
throw new UnsupportedAlgorithmException("$algo is not a supported algorithm");
}
return $cipher;
}
/**
* Returns a hash based on a PBES1 $algo
*/
private static function getPBES1Hash(string $algo): string
{
if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) {
return $matches[1] == 'SHA' ? 'sha1' : $matches[1];
}
return 'sha1';
}
/**
* Returns a KDF baesd on a PBES1 $algo
*/
private static function getPBES1KDF(string $algo): string
{
switch ($algo) {
case 'pbeWithMD2AndDES-CBC':
case 'pbeWithMD2AndRC2-CBC':
case 'pbeWithMD5AndDES-CBC':
case 'pbeWithMD5AndRC2-CBC':
case 'pbeWithSHA1AndDES-CBC':
case 'pbeWithSHA1AndRC2-CBC':
return 'pbkdf1';
}
return 'pkcs12';
}
/**
* Returns a SymmetricKey object baesd on a PBES2 $algo
*/
private static function getPBES2EncryptionObject(string $algo): SymmetricKey
{
switch ($algo) {
case 'desCBC':
$cipher = new DES('cbc');
break;
case 'des-EDE3-CBC':
$cipher = new TripleDES('cbc');
break;
case 'rc2CBC':
$cipher = new RC2('cbc');
// in theory this can be changed
$cipher->setKeyLength(128);
break;
case 'rc5-CBC-PAD':
throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys');
case 'aes128-CBC-PAD':
case 'aes192-CBC-PAD':
case 'aes256-CBC-PAD':
$cipher = new AES('cbc');
$cipher->setKeyLength((int) substr($algo, 3, 3));
break;
default:
throw new UnsupportedAlgorithmException("$algo is not supported");
}
return $cipher;
}
/**
* Initialize static variables
*/
private static function initialize_static_variables(): void
{
if (!isset(static::$childOIDsLoaded)) {
throw new InsufficientSetupException('This class should not be called directly');
}
if (!static::$childOIDsLoaded) {
ASN1::loadOIDs(is_array(static::OID_NAME) ?
array_combine(static::OID_NAME, static::OID_VALUE) :
[static::OID_NAME => static::OID_VALUE]);
static::$childOIDsLoaded = true;
}
if (!self::$oidsLoaded) {
// from https://tools.ietf.org/html/rfc2898
ASN1::loadOIDs([
// PBES1 encryption schemes
'pbeWithMD2AndDES-CBC' => '1.2.840.113549.1.5.1',
'pbeWithMD2AndRC2-CBC' => '1.2.840.113549.1.5.4',
'pbeWithMD5AndDES-CBC' => '1.2.840.113549.1.5.3',
'pbeWithMD5AndRC2-CBC' => '1.2.840.113549.1.5.6',
'pbeWithSHA1AndDES-CBC' => '1.2.840.113549.1.5.10',
'pbeWithSHA1AndRC2-CBC' => '1.2.840.113549.1.5.11',
// from PKCS#12:
// https://tools.ietf.org/html/rfc7292
'pbeWithSHAAnd128BitRC4' => '1.2.840.113549.1.12.1.1',
'pbeWithSHAAnd40BitRC4' => '1.2.840.113549.1.12.1.2',
'pbeWithSHAAnd3-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.3',
'pbeWithSHAAnd2-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.4',
'pbeWithSHAAnd128BitRC2-CBC' => '1.2.840.113549.1.12.1.5',
'pbeWithSHAAnd40BitRC2-CBC' => '1.2.840.113549.1.12.1.6',
'id-PBKDF2' => '1.2.840.113549.1.5.12',
'id-PBES2' => '1.2.840.113549.1.5.13',
'id-PBMAC1' => '1.2.840.113549.1.5.14',
// from PKCS#5 v2.1:
// http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf
'id-hmacWithSHA1' => '1.2.840.113549.2.7',
'id-hmacWithSHA224' => '1.2.840.113549.2.8',
'id-hmacWithSHA256' => '1.2.840.113549.2.9',
'id-hmacWithSHA384' => '1.2.840.113549.2.10',
'id-hmacWithSHA512' => '1.2.840.113549.2.11',
'id-hmacWithSHA512-224' => '1.2.840.113549.2.12',
'id-hmacWithSHA512-256' => '1.2.840.113549.2.13',
'desCBC' => '1.3.14.3.2.7',
'des-EDE3-CBC' => '1.2.840.113549.3.7',
'rc2CBC' => '1.2.840.113549.3.2',
'rc5-CBC-PAD' => '1.2.840.113549.3.9',
'aes128-CBC-PAD' => '2.16.840.1.101.3.4.1.2',
'aes192-CBC-PAD' => '2.16.840.1.101.3.4.1.22',
'aes256-CBC-PAD' => '2.16.840.1.101.3.4.1.42',
]);
self::$oidsLoaded = true;
}
}
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
protected static function load($key, ?string $password = null): array
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$isPublic = str_contains($key, 'PUBLIC');
$isPrivate = str_contains($key, 'PRIVATE');
$decoded = self::preParse($key);
$meta = [];
$decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
if ($password !== null && strlen($password) && is_array($decrypted)) {
$algorithm = $decrypted['encryptionAlgorithm']['algorithm'];
switch ($algorithm) {
// PBES1
case 'pbeWithMD2AndDES-CBC':
case 'pbeWithMD2AndRC2-CBC':
case 'pbeWithMD5AndDES-CBC':
case 'pbeWithMD5AndRC2-CBC':
case 'pbeWithSHA1AndDES-CBC':
case 'pbeWithSHA1AndRC2-CBC':
case 'pbeWithSHAAnd3-KeyTripleDES-CBC':
case 'pbeWithSHAAnd2-KeyTripleDES-CBC':
case 'pbeWithSHAAnd128BitRC2-CBC':
case 'pbeWithSHAAnd40BitRC2-CBC':
case 'pbeWithSHAAnd128BitRC4':
case 'pbeWithSHAAnd40BitRC4':
$cipher = self::getPBES1EncryptionObject($algorithm);
$hash = self::getPBES1Hash($algorithm);
$kdf = self::getPBES1KDF($algorithm);
$meta['meta']['algorithm'] = $algorithm;
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new RuntimeException('Unable to decode BER');
}
extract(ASN1::asn1map($temp[0], Maps\PBEParameter::MAP));
$iterationCount = (int) $iterationCount->toString();
$cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount);
$key = $cipher->decrypt($decrypted['encryptedData']);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER 2');
}
break;
case 'id-PBES2':
$meta['meta']['algorithm'] = $algorithm;
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new RuntimeException('Unable to decode BER');
}
$temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
extract($temp);
$cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']);
$meta['meta']['cipher'] = $encryptionScheme['algorithm'];
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new RuntimeException('Unable to decode BER');
}
$temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
extract($temp);
if (!$cipher instanceof RC2) {
$cipher->setIV($encryptionScheme['parameters']['octetString']);
} else {
$temp = ASN1::decodeBER($encryptionScheme['parameters']);
if (!$temp) {
throw new RuntimeException('Unable to decode BER');
}
extract(ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP));
$effectiveKeyLength = (int) $rc2ParametersVersion->toString();
switch ($effectiveKeyLength) {
case 160:
$effectiveKeyLength = 40;
break;
case 120:
$effectiveKeyLength = 64;
break;
case 58:
$effectiveKeyLength = 128;
break;
//default: // should be >= 256
}
$cipher->setIV($iv);
$cipher->setKeyLength($effectiveKeyLength);
}
$meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm'];
switch ($keyDerivationFunc['algorithm']) {
case 'id-PBKDF2':
$temp = ASN1::decodeBER($keyDerivationFunc['parameters']);
if (!$temp) {
throw new RuntimeException('Unable to decode BER');
}
$prf = ['algorithm' => 'id-hmacWithSHA1'];
$params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP);
extract($params);
$meta['meta']['prf'] = $prf['algorithm'];
$hash = str_replace('-', '/', substr($prf['algorithm'], 11));
$params = [
$password,
'pbkdf2',
$hash,
$salt,
(int) $iterationCount->toString(),
];
if (isset($keyLength)) {
$params[] = (int) $keyLength->toString();
}
$cipher->setPassword(...$params);
$key = $cipher->decrypt($decrypted['encryptedData']);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER 3');
}
break;
default:
throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys');
}
break;
case 'id-PBMAC1':
//$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
//$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP);
// since i can't find any implementation that does PBMAC1 it is unsupported
throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.');
// at this point we'll assume that the key conforms to PublicKeyInfo
}
}
$private = ASN1::asn1map($decoded[0], Maps\OneAsymmetricKey::MAP);
if (is_array($private)) {
if ($isPublic) {
throw new UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key');
}
if (isset($private['privateKeyAlgorithm']['parameters']) && !$private['privateKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][1]['content'][1])) {
$temp = $decoded[0]['content'][1]['content'][1];
$private['privateKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
}
if (is_array(static::OID_NAME)) {
if (!in_array($private['privateKeyAlgorithm']['algorithm'], static::OID_NAME)) {
throw new UnsupportedAlgorithmException($private['privateKeyAlgorithm']['algorithm'] . ' is not a supported key type');
}
} else {
if ($private['privateKeyAlgorithm']['algorithm'] != static::OID_NAME) {
throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $private['privateKeyAlgorithm']['algorithm'] . ' key');
}
}
if (isset($private['publicKey'])) {
if ($private['publicKey'][0] != "\0") {
throw new UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0]));
}
$private['publicKey'] = substr($private['publicKey'], 1);
}
return $private + $meta;
}
// EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference
// is that the former has an octet string and the later has a bit string. the first byte of a bit
// string represents the number of bits in the last byte that are to be ignored but, currently,
// bit strings wanting a non-zero amount of bits trimmed are not supported
$public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP);
if (is_array($public)) {
if ($isPrivate) {
throw new UnexpectedValueException('Human readable string claims private key but DER encoded string claims public key');
}
if ($public['publicKey'][0] != "\0") {
throw new UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0]));
}
if (is_array(static::OID_NAME)) {
if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) {
throw new UnsupportedAlgorithmException($public['publicKeyAlgorithm']['algorithm'] . ' is not a supported key type');
}
} else {
if ($public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) {
throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $public['publicKeyAlgorithm']['algorithm'] . ' key');
}
}
if (isset($public['publicKeyAlgorithm']['parameters']) && !$public['publicKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][0]['content'][1])) {
$temp = $decoded[0]['content'][0]['content'][1];
$public['publicKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
}
$public['publicKey'] = substr($public['publicKey'], 1);
return $public;
}
throw new RuntimeException('Unable to parse using either OneAsymmetricKey or PublicKeyInfo ASN1 maps');
}
/**
* Wrap a private key appropriately
*
* @param array|string $attr
* @param string|false $password
* @param string|null $oid optional
* @param string $publicKey optional
* @param array $options optional
*/
protected static function wrapPrivateKey(string $key, $attr, $params, $password, ?string $oid = null, string $publicKey = '', array $options = []): string
{
self::initialize_static_variables();
$key = [
'version' => 'v1',
'privateKeyAlgorithm' => [
'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid,
],
'privateKey' => $key,
];
if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
$key['privateKeyAlgorithm']['parameters'] = $params;
}
if (!empty($attr)) {
$key['attributes'] = $attr;
}
if (!empty($publicKey)) {
$key['version'] = 'v2';
$key['publicKey'] = $publicKey;
}
$key = ASN1::encodeDER($key, Maps\OneAsymmetricKey::MAP);
if (!empty($password) && is_string($password)) {
$salt = Random::string(8);
$iterationCount = $options['iterationCount'] ?? self::$defaultIterationCount;
$encryptionAlgorithm = $options['encryptionAlgorithm'] ?? self::$defaultEncryptionAlgorithm;
$encryptionScheme = $options['encryptionScheme'] ?? self::$defaultEncryptionScheme;
$prf = $options['PRF'] ?? self::$defaultPRF;
if ($encryptionAlgorithm == 'id-PBES2') {
$crypto = self::getPBES2EncryptionObject($encryptionScheme);
$hash = str_replace('-', '/', substr($prf, 11));
$kdf = 'pbkdf2';
$iv = Random::string($crypto->getBlockLength() >> 3);
$PBKDF2params = [
'salt' => $salt,
'iterationCount' => $iterationCount,
'prf' => ['algorithm' => $prf, 'parameters' => null],
];
$PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP);
if (!$crypto instanceof RC2) {
$params = ['octetString' => $iv];
} else {
$params = [
'rc2ParametersVersion' => 58,
'iv' => $iv,
];
$params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP);
$params = new ASN1\Element($params);
}
$params = [
'keyDerivationFunc' => [
'algorithm' => 'id-PBKDF2',
'parameters' => new ASN1\Element($PBKDF2params),
],
'encryptionScheme' => [
'algorithm' => $encryptionScheme,
'parameters' => $params,
],
];
$params = ASN1::encodeDER($params, Maps\PBES2params::MAP);
$crypto->setIV($iv);
} else {
$crypto = self::getPBES1EncryptionObject($encryptionAlgorithm);
$hash = self::getPBES1Hash($encryptionAlgorithm);
$kdf = self::getPBES1KDF($encryptionAlgorithm);
$params = [
'salt' => $salt,
'iterationCount' => $iterationCount,
];
$params = ASN1::encodeDER($params, Maps\PBEParameter::MAP);
}
$crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount);
$key = $crypto->encrypt($key);
$key = [
'encryptionAlgorithm' => [
'algorithm' => $encryptionAlgorithm,
'parameters' => new ASN1\Element($params),
],
'encryptedData' => $key,
];
$key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP);
return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END ENCRYPTED PRIVATE KEY-----";
}
return "-----BEGIN PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END PRIVATE KEY-----";
}
/**
* Wrap a public key appropriately
*/
protected static function wrapPublicKey(string $key, $params, ?string $oid = null): string
{
self::initialize_static_variables();
$key = [
'publicKeyAlgorithm' => [
'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid,
],
'publicKey' => "\0" . $key,
];
if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
$key['publicKeyAlgorithm']['parameters'] = $params;
}
$key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP);
return "-----BEGIN PUBLIC KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END PUBLIC KEY-----";
}
/**
* Perform some preliminary parsing of the key
*
* @param string|array $key
*/
private static function preParse(&$key): array
{
self::initialize_static_variables();
if (self::$format != self::MODE_DER) {
$decoded = ASN1::extractBER($key);
if ($decoded !== false) {
$key = $decoded;
} elseif (self::$format == self::MODE_PEM) {
throw new UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
}
}
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER');
}
return $decoded;
}
/**
* Returns the encryption parameters used by the key
*/
public static function extractEncryptionAlgorithm(string $key): array
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$decoded = self::preParse($key);
$r = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
if (!is_array($r)) {
throw new RuntimeException('Unable to parse using EncryptedPrivateKeyInfo map');
}
if ($r['encryptionAlgorithm']['algorithm'] == 'id-PBES2') {
$decoded = ASN1::decodeBER($r['encryptionAlgorithm']['parameters']->element);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER');
}
$r['encryptionAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\PBES2params::MAP);
$kdf = &$r['encryptionAlgorithm']['parameters']['keyDerivationFunc'];
switch ($kdf['algorithm']) {
case 'id-PBKDF2':
$decoded = ASN1::decodeBER($kdf['parameters']->element);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER');
}
$kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP);
}
}
return $r['encryptionAlgorithm'];
}
}

View File

@@ -1,353 +0,0 @@
<?php
/**
* PuTTY Formatted Key Handler
*
* See PuTTY's SSHPUBK.C and https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixC.html
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\Random;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Exception\UnsupportedAlgorithmException;
/**
* PuTTY Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PuTTY
{
/**
* Default comment
*
* @var string
*/
private static $comment = 'phpseclib-generated-key';
/**
* Default version
*
* @var int
*/
private static $version = 2;
/**
* Sets the default comment
*/
public static function setComment(string $comment): void
{
self::$comment = str_replace(["\r", "\n"], '', $comment);
}
/**
* Sets the default version
*/
public static function setVersion(int $version): void
{
if ($version != 2 && $version != 3) {
throw new RuntimeException('Only supported versions are 2 and 3');
}
self::$version = $version;
}
/**
* Generate a symmetric key for PuTTY v2 keys
*/
private static function generateV2Key(string $password, int $length): string
{
$symkey = '';
$sequence = 0;
while (strlen($symkey) < $length) {
$temp = pack('Na*', $sequence++, $password);
$symkey .= Strings::hex2bin(sha1($temp));
}
return substr($symkey, 0, $length);
}
/**
* Generate a symmetric key for PuTTY v3 keys
*/
private static function generateV3Key(string $password, string $flavour, int $memory, int $passes, string $salt): array
{
if (!function_exists('sodium_crypto_pwhash')) {
throw new RuntimeException('sodium_crypto_pwhash needs to exist for Argon2 password hasing');
}
switch ($flavour) {
case 'Argon2i':
$flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13;
break;
case 'Argon2id':
$flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
break;
default:
throw new UnsupportedAlgorithmException('Only Argon2i and Argon2id are supported');
}
$length = 80; // keylen + ivlen + mac_keylen
$temp = sodium_crypto_pwhash($length, $password, $salt, $passes, $memory << 10, $flavour);
$symkey = substr($temp, 0, 32);
$symiv = substr($temp, 32, 16);
$hashkey = substr($temp, -32);
return compact('symkey', 'symiv', 'hashkey');
}
/**
* Break a public or private key down into its constituent components
*
* @param array|string $key
* @param string|false $password
* @return array|false
*/
public static function load($key, $password)
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
if (str_contains($key, 'BEGIN SSH2 PUBLIC KEY')) {
$lines = preg_split('#[\r\n]+#', $key);
switch (true) {
case $lines[0] != '---- BEGIN SSH2 PUBLIC KEY ----':
throw new UnexpectedValueException('Key doesn\'t start with ---- BEGIN SSH2 PUBLIC KEY ----');
case $lines[count($lines) - 1] != '---- END SSH2 PUBLIC KEY ----':
throw new UnexpectedValueException('Key doesn\'t end with ---- END SSH2 PUBLIC KEY ----');
}
$lines = array_splice($lines, 1, -1);
$lines = array_map(fn ($line) => rtrim($line, "\r\n"), $lines);
$data = $current = '';
$values = [];
$in_value = false;
foreach ($lines as $line) {
switch (true) {
case preg_match('#^(.*?): (.*)#', $line, $match):
$in_value = $line[-1] == '\\';
$current = strtolower($match[1]);
$values[$current] = $in_value ? substr($match[2], 0, -1) : $match[2];
break;
case $in_value:
$in_value = $line[-1] == '\\';
$values[$current] .= $in_value ? substr($line, 0, -1) : $line;
break;
default:
$data .= $line;
}
}
$components = call_user_func([static::PUBLIC_HANDLER, 'load'], $data);
if ($components === false) {
throw new UnexpectedValueException('Unable to decode public key');
}
$components += $values;
$components['comment'] = str_replace(['\\\\', '\"'], ['\\', '"'], $values['comment']);
return $components;
}
$components = [];
$key = preg_split('#\r\n|\r|\n#', trim($key));
if (Strings::shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') {
return false;
}
$version = (int) Strings::shift($key[0], 3); // should be either "2: " or "3: 0" prior to int casting
if ($version != 2 && $version != 3) {
throw new RuntimeException('Only v2 and v3 PuTTY private keys are supported');
}
$components['type'] = $type = rtrim($key[0]);
if (!in_array($type, static::$types)) {
$error = count(static::$types) == 1 ?
'Only ' . static::$types[0] . ' keys are supported. ' :
'';
throw new UnsupportedAlgorithmException($error . 'This is an unsupported ' . $type . ' key');
}
$encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
$components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
$publicLength = (int) trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
$public = Strings::base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
$source = Strings::packSSH2('ssss', $type, $encryption, $components['comment'], $public);
extract(unpack('Nlength', Strings::shift($public, 4)));
$newtype = Strings::shift($public, $length);
if ($newtype != $type) {
throw new RuntimeException('The binary type does not match the human readable type field');
}
$components['public'] = $public;
switch ($version) {
case 3:
$hashkey = '';
break;
case 2:
$hashkey = 'putty-private-key-file-mac-key';
}
$offset = $publicLength + 4;
switch ($encryption) {
case 'aes256-cbc':
$crypto = new AES('cbc');
switch ($version) {
case 3:
$flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++]));
$memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++]));
$passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++]));
$parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++]));
$salt = Strings::hex2bin(trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++])));
extract(self::generateV3Key($password, $flavour, (int)$memory, (int)$passes, $salt));
break;
case 2:
$symkey = self::generateV2Key($password, 32);
$symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
$hashkey .= $password;
}
}
switch ($version) {
case 3:
$hash = new Hash('sha256');
$hash->setKey($hashkey);
break;
case 2:
$hash = new Hash('sha1');
$hash->setKey(sha1($hashkey, true));
}
$privateLength = (int) trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++]));
$private = Strings::base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength))));
if ($encryption != 'none') {
$crypto->setKey($symkey);
$crypto->setIV($symiv);
$crypto->disablePadding();
$private = $crypto->decrypt($private);
}
$source .= Strings::packSSH2('s', $private);
$hmac = trim(preg_replace('#Private-MAC: (.+)#', '$1', $key[$offset + $privateLength]));
$hmac = Strings::hex2bin($hmac);
if (!hash_equals($hash->hash($source), $hmac)) {
throw new UnexpectedValueException('MAC validation error');
}
$components['private'] = $private;
return $components;
}
/**
* Wrap a private key appropriately
*
* @param string|false $password
* @param array $options optional
*/
protected static function wrapPrivateKey(string $public, string $private, string $type, $password, array $options = []): string
{
$encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none';
$comment = $options['comment'] ?? self::$comment;
$version = $options['version'] ?? self::$version;
$key = "PuTTY-User-Key-File-$version: $type\r\n";
$key .= "Encryption: $encryption\r\n";
$key .= "Comment: $comment\r\n";
$public = Strings::packSSH2('s', $type) . $public;
$source = Strings::packSSH2('ssss', $type, $encryption, $comment, $public);
$public = Strings::base64_encode($public);
$key .= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
$key .= chunk_split($public, 64);
if (empty($password) && !is_string($password)) {
$source .= Strings::packSSH2('s', $private);
switch ($version) {
case 3:
$hash = new Hash('sha256');
$hash->setKey('');
break;
case 2:
$hash = new Hash('sha1');
$hash->setKey(sha1('putty-private-key-file-mac-key', true));
}
} else {
$private .= Random::string(16 - (strlen($private) & 15));
$source .= Strings::packSSH2('s', $private);
$crypto = new AES('cbc');
switch ($version) {
case 3:
$salt = Random::string(16);
$key .= "Key-Derivation: Argon2id\r\n";
$key .= "Argon2-Memory: 8192\r\n";
$key .= "Argon2-Passes: 13\r\n";
$key .= "Argon2-Parallelism: 1\r\n";
$key .= "Argon2-Salt: " . Strings::bin2hex($salt) . "\r\n";
extract(self::generateV3Key($password, 'Argon2id', 8192, 13, $salt));
$hash = new Hash('sha256');
$hash->setKey($hashkey);
break;
case 2:
$symkey = self::generateV2Key($password, 32);
$symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
$hashkey = 'putty-private-key-file-mac-key' . $password;
$hash = new Hash('sha1');
$hash->setKey(sha1($hashkey, true));
}
$crypto->setKey($symkey);
$crypto->setIV($symiv);
$crypto->disablePadding();
$private = $crypto->encrypt($private);
$mac = $hash->hash($source);
}
$private = Strings::base64_encode($private);
$key .= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
$key .= chunk_split($private, 64);
$key .= 'Private-MAC: ' . Strings::bin2hex($hash->hash($source)) . "\r\n";
return $key;
}
/**
* Wrap a public key appropriately
*
* This is basically the format described in RFC 4716 (https://tools.ietf.org/html/rfc4716)
*/
protected static function wrapPublicKey(string $key, string $type): string
{
$key = pack('Na*a*', strlen($type), $type, $key);
$key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" .
'Comment: "' . str_replace(['\\', '"'], ['\\\\', '\"'], self::$comment) . "\"\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
'---- END SSH2 PUBLIC KEY ----';
return $key;
}
}

View File

@@ -1,57 +0,0 @@
<?php
/**
* Raw Signature Handler
*
* PHP version 5
*
* Handles signatures as arrays
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Formats\Signature;
use phpseclib3\Math\BigInteger;
/**
* Raw Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Raw
{
/**
* Loads a signature
*
* @return array|bool
*/
public static function load(array $sig)
{
switch (true) {
case !is_array($sig):
case !isset($sig['r']) || !isset($sig['s']):
case !$sig['r'] instanceof BigInteger:
case !$sig['s'] instanceof BigInteger:
return false;
}
return [
'r' => $sig['r'],
's' => $sig['s'],
];
}
/**
* Returns a signature in the appropriate format
*/
public static function save(BigInteger $r, BigInteger $s): string
{
return compact('r', 's');
}
}

View File

@@ -1,32 +0,0 @@
<?php
/**
* PrivateKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common;
/**
* PrivateKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
*/
interface PrivateKey
{
public function sign($message);
//public function decrypt($ciphertext);
public function getPublicKey();
public function toString(string $type, array $options = []): string;
/**
* @return static
*/
public function withPassword(?string $password = null): PrivateKey;
}

View File

@@ -1,27 +0,0 @@
<?php
/**
* PublicKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common;
/**
* PublicKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
*/
interface PublicKey
{
public function verify($message, $signature);
//public function encrypt($plaintext);
public function toString(string $type, array $options = []): string;
public function getFingerprint($algorithm);
}

View File

@@ -1,54 +0,0 @@
<?php
/**
* Base Class for all stream ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common;
/**
* Base Class for all stream cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class StreamCipher extends SymmetricKey
{
/**
* Block Length of the cipher
*
* Stream ciphers do not have a block size
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::block_size
* @var int
*/
protected $block_size = 0;
/**
* Default Constructor.
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
* @return StreamCipher
*/
public function __construct()
{
parent::__construct('stream');
}
/**
* Stream ciphers not use an IV
*/
public function usesIV(): bool
{
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +0,0 @@
<?php
/**
* Fingerprint Trait for Public Keys
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Traits;
use phpseclib3\Crypt\Hash;
/**
* Fingerprint Trait for Private Keys
*
* @author Jim Wigginton <terrafrost@php.net>
*/
trait Fingerprint
{
/**
* Returns the public key's fingerprint
*
* The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is
* no public key currently loaded, false is returned.
* Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716)
*
* @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned
* for invalid values.
*/
public function getFingerprint($algorithm = 'md5')
{
$type = self::validatePlugin('Keys', 'OpenSSH', 'savePublicKey');
if ($type === false) {
return false;
}
$key = $this->toString('OpenSSH', ['binary' => true]);
if ($key === false) {
return false;
}
switch ($algorithm) {
case 'sha256':
$hash = new Hash('sha256');
$base = base64_encode($hash->hash($key));
return substr($base, 0, strlen($base) - 1);
case 'md5':
return substr(chunk_split(md5($key), 2, ':'), 0, -1);
default:
return false;
}
}
}

View File

@@ -1,47 +0,0 @@
<?php
/**
* Password Protected Trait for Private Keys
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\Common\Traits;
/**
* Password Protected Trait for Private Keys
*
* @author Jim Wigginton <terrafrost@php.net>
*/
trait PasswordProtected
{
/**
* @var string|null
*/
private $password = null;
/**
* Sets the password
*
* Private keys can be encrypted with a password. To unset the password, pass in the empty string or false.
* Or rather, pass in $password such that empty($password) && !is_string($password) is true.
*
* @see self::createKey()
* @see self::load()
*
* @return static
*/
public function withPassword(?string $password = null): self
{
$new = clone $this;
$new->password = $password;
return $new;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,397 +0,0 @@
<?php
/**
* Pure-PHP (EC)DH implementation
*
* PHP version 5
*
* Here's an example of how to compute a shared secret with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $ourPrivate = \phpseclib3\Crypt\DH::createKey();
* $secret = DH::computeSecret($ourPrivate, $theirPublic);
*
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\DH\Parameters;
use phpseclib3\Crypt\DH\PrivateKey;
use phpseclib3\Crypt\DH\PublicKey;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Exception\NoKeyLoadedException;
use phpseclib3\Exception\UnsupportedOperationException;
use phpseclib3\Math\BigInteger;
/**
* Pure-PHP (EC)DH implementation
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class DH extends AsymmetricKey
{
/**
* Algorithm Name
*
* @var string
*/
public const ALGORITHM = 'DH';
/**
* DH prime
*
* @var BigInteger
*/
protected $prime;
/**
* DH Base
*
* Prime divisor of p-1
*
* @var BigInteger
*/
protected $base;
/**
* Public Key
*
* @var BigInteger
*/
protected $publicKey;
/**
* Create DH parameters
*
* This method is a bit polymorphic. It can take any of the following:
* - two BigInteger's (prime and base)
* - an integer representing the size of the prime in bits (the base is assumed to be 2)
* - a string (eg. diffie-hellman-group14-sha1)
*/
public static function createParameters(...$args): Parameters
{
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
}
$params = new Parameters();
if (count($args) == 2 && $args[0] instanceof BigInteger && $args[1] instanceof BigInteger) {
//if (!$args[0]->isPrime()) {
// throw new \phpseclib3\Exception\InvalidArgumentException('The first parameter should be a prime number');
//}
$params->prime = $args[0];
$params->base = $args[1];
return $params;
} elseif (count($args) == 1 && is_numeric($args[0])) {
$params->prime = BigInteger::randomPrime($args[0]);
$params->base = new BigInteger(2);
return $params;
} elseif (count($args) != 1 || !is_string($args[0])) {
throw new InvalidArgumentException('Valid parameters are either: two BigInteger\'s (prime and base), a single integer (the length of the prime; base is assumed to be 2) or a string');
}
switch ($args[0]) {
// see http://tools.ietf.org/html/rfc2409#section-6.2 and
// http://tools.ietf.org/html/rfc2412, appendex E
case 'diffie-hellman-group1-sha1':
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
break;
// see http://tools.ietf.org/html/rfc3526#section-3
case 'diffie-hellman-group14-sha1': // 2048-bit MODP Group
case 'diffie-hellman-group14-sha256':
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
break;
// see https://tools.ietf.org/html/rfc3526#section-4
case 'diffie-hellman-group15-sha512': // 3072-bit MODP Group
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
'08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
break;
// see https://tools.ietf.org/html/rfc3526#section-5
case 'diffie-hellman-group16-sha512': // 4096-bit MODP Group
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
'08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
'88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
'233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
'93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF';
break;
// see https://tools.ietf.org/html/rfc3526#section-6
case 'diffie-hellman-group17-sha512': // 6144-bit MODP Group
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
'08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
'88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
'233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
'93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
'59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
'043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF';
break;
// see https://tools.ietf.org/html/rfc3526#section-7
case 'diffie-hellman-group18-sha512': // 8192-bit MODP Group
$prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
'020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
'4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
'98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
'9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
'3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
'08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
'88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
'233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
'93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
'59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
'043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4' .
'38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED' .
'2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D' .
'E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B' .
'4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6' .
'6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D' .
'F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92' .
'4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA' .
'9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF';
break;
default:
throw new InvalidArgumentException('Invalid named prime provided');
}
$params->prime = new BigInteger($prime, 16);
$params->base = new BigInteger(2);
return $params;
}
/**
* Create public / private key pair.
*
* The rationale for the second parameter is described in http://tools.ietf.org/html/rfc4419#section-6.2 :
*
* "To increase the speed of the key exchange, both client and server may
* reduce the size of their private exponents. It should be at least
* twice as long as the key material that is generated from the shared
* secret. For more details, see the paper by van Oorschot and Wiener
* [VAN-OORSCHOT]."
*
* $length is in bits
*
* @param int $length optional
*/
public static function createKey(Parameters $params, int $length = 0): PrivateKey
{
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
}
$one = new BigInteger(1);
if ($length) {
$max = $one->bitwise_leftShift($length);
$max = $max->subtract($one);
} else {
$max = $params->prime->subtract($one);
}
$key = new PrivateKey();
$key->prime = $params->prime;
$key->base = $params->base;
$key->privateKey = BigInteger::randomRange($one, $max);
$key->publicKey = $key->base->powMod($key->privateKey, $key->prime);
return $key;
}
/**
* Compute Shared Secret
*
* @param PrivateKey|EC $private
* @param PublicKey|BigInteger|string $public
*/
public static function computeSecret($private, $public)
{
if ($private instanceof PrivateKey) { // DH\PrivateKey
switch (true) {
case $public instanceof PublicKey:
if (!$private->prime->equals($public->prime) || !$private->base->equals($public->base)) {
throw new InvalidArgumentException('The public and private key do not share the same prime and / or base numbers');
}
return $public->publicKey->powMod($private->privateKey, $private->prime)->toBytes(true);
case is_string($public):
$public = new BigInteger($public, -256);
// fall-through
case $public instanceof BigInteger:
return $public->powMod($private->privateKey, $private->prime)->toBytes(true);
default:
throw new InvalidArgumentException('$public needs to be an instance of DH\PublicKey, a BigInteger or a string');
}
}
if ($private instanceof EC\PrivateKey) {
switch (true) {
case $public instanceof EC\PublicKey:
$public = $public->getEncodedCoordinates();
// fall-through
case is_string($public):
$point = $private->multiply($public);
switch ($private->getCurve()) {
case 'Curve25519':
case 'Curve448':
$secret = $point;
break;
default:
// according to https://www.secg.org/sec1-v2.pdf#page=33 only X is returned
$secret = substr($point, 1, (strlen($point) - 1) >> 1);
}
/*
if (($secret[0] & "\x80") === "\x80") {
$secret = "\0$secret";
}
*/
return $secret;
default:
throw new InvalidArgumentException('$public needs to be an instance of EC\PublicKey or a string (an encoded coordinate)');
}
}
}
/**
* Load the key
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): AsymmetricKey
{
try {
return EC::load($key, $password);
} catch (NoKeyLoadedException $e) {
}
return parent::load($key, $password);
}
/**
* OnLoad Handler
*
* @return Parameters|PrivateKey|PublicKey
*/
protected static function onLoad(array $components)
{
if (!isset($components['privateKey']) && !isset($components['publicKey'])) {
$new = new Parameters();
} else {
$new = isset($components['privateKey']) ?
new PrivateKey() :
new PublicKey();
}
$new->prime = $components['prime'];
$new->base = $components['base'];
if (isset($components['privateKey'])) {
$new->privateKey = $components['privateKey'];
}
if (isset($components['publicKey'])) {
$new->publicKey = $components['publicKey'];
}
return $new;
}
/**
* Determines which hashing function should be used
*/
public function withHash(string $hash): AsymmetricKey
{
throw new UnsupportedOperationException('DH does not use a hash algorithm');
}
/**
* Returns the hash algorithm currently being used
*/
public function getHash(): Hash
{
throw new UnsupportedOperationException('DH does not use a hash algorithm');
}
/**
* Returns the parameters
*
* A public / private key is only returned if the currently loaded "key" contains an x or y
* value.
*
* @see self::getPublicKey()
*/
public function getParameters(): AsymmetricKey
{
$type = DH::validatePlugin('Keys', 'PKCS1', 'saveParameters');
$key = $type::saveParameters($this->prime, $this->base);
return DH::load($key, 'PKCS1');
}
}

View File

@@ -1,76 +0,0 @@
<?php
/**
* "PKCS1" Formatted EC Key Handler
*
* PHP version 5
*
* Processes keys with the following headers:
*
* -----BEGIN DH PARAMETERS-----
*
* Technically, PKCS1 is for RSA keys, only, but we're using PKCS1 to describe
* DSA, whose format isn't really formally described anywhere, so might as well
* use it to describe this, too.
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DH\Formats\Keys;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* "PKCS1" Formatted DH Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS1 extends Progenitor
{
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
$key = parent::load($key, $password);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER');
}
$components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP);
if (!is_array($components)) {
throw new RuntimeException('Unable to perform ASN1 mapping on parameters');
}
return $components;
}
/**
* Convert EC parameters to the appropriate format
*/
public static function saveParameters(BigInteger $prime, BigInteger $base, array $options = []): string
{
$params = [
'prime' => $prime,
'base' => $base,
];
$params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
return "-----BEGIN DH PARAMETERS-----\r\n" .
chunk_split(base64_encode($params), 64) .
"-----END DH PARAMETERS-----\r\n";
}
}

View File

@@ -1,121 +0,0 @@
<?php
/**
* PKCS#8 Formatted DH Key Handler
*
* PHP version 5
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DH\Formats\Keys;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* PKCS#8 Formatted DH Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS8 extends Progenitor
{
/**
* OID Name
*
* @var string
*/
public const OID_NAME = 'dhKeyAgreement';
/**
* OID Value
*
* @var string
*/
public const OID_VALUE = '1.2.840.113549.1.3.1';
/**
* Child OIDs loaded
*
* @var bool
*/
protected static $childOIDsLoaded = false;
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
$key = parent::load($key, $password);
$type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
$decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
if (empty($decoded)) {
throw new RuntimeException('Unable to decode BER of parameters');
}
$components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP);
if (!is_array($components)) {
throw new RuntimeException('Unable to perform ASN1 mapping on parameters');
}
$decoded = ASN1::decodeBER($key[$type]);
switch (true) {
case !isset($decoded):
case !isset($decoded[0]['content']):
case !$decoded[0]['content'] instanceof BigInteger:
throw new RuntimeException('Unable to decode BER of parameters');
}
$components[$type] = $decoded[0]['content'];
return $components;
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $prime, BigInteger $base, BigInteger $privateKey, BigInteger $publicKey, ?string $password = null, array $options = []): string
{
$params = [
'prime' => $prime,
'base' => $base,
];
$params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
$params = new ASN1\Element($params);
$key = ASN1::encodeDER($privateKey, ['type' => ASN1::TYPE_INTEGER]);
return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
}
/**
* Convert a public key to the appropriate format
*
* @param array $options optional
*/
public static function savePublicKey(BigInteger $prime, BigInteger $base, BigInteger $publicKey, array $options = []): string
{
$params = [
'prime' => $prime,
'base' => $base,
];
$params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
$params = new ASN1\Element($params);
$key = ASN1::encodeDER($publicKey, ['type' => ASN1::TYPE_INTEGER]);
return self::wrapPublicKey($key, $params);
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* DH Parameters
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DH;
use phpseclib3\Crypt\DH;
/**
* DH Parameters
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class Parameters extends DH
{
/**
* Returns the parameters
*
* @param array $options optional
*/
public function toString(string $type = 'PKCS1', array $options = []): string
{
$type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
return $type::saveParameters($this->prime, $this->base, $options);
}
}

View File

@@ -1,74 +0,0 @@
<?php
/**
* DH Private Key
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DH;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DH;
use phpseclib3\Math\BigInteger;
/**
* DH Private Key
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PrivateKey extends DH
{
use Common\Traits\PasswordProtected;
/**
* Private Key
*
* @var BigInteger
*/
protected $privateKey;
/**
* Public Key
*
* @var BigInteger
*/
protected $publicKey;
/**
* Returns the public key
*/
public function getPublicKey(): PublicKey
{
$type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
if (!isset($this->publicKey)) {
$this->publicKey = $this->base->powMod($this->privateKey, $this->prime);
}
$key = $type::savePublicKey($this->prime, $this->base, $this->publicKey);
return DH::loadFormat('PKCS8', $key);
}
/**
* Returns the private key
*
* @param array $options optional
*/
public function toString(string $type, array $options = []): string
{
$type = self::validatePlugin('Keys', $type, 'savePrivateKey');
if (!isset($this->publicKey)) {
$this->publicKey = $this->base->powMod($this->privateKey, $this->prime);
}
return $type::savePrivateKey($this->prime, $this->base, $this->privateKey, $this->publicKey, $this->password, $options);
}
}

View File

@@ -1,48 +0,0 @@
<?php
/**
* DH Public Key
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DH;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DH;
use phpseclib3\Math\BigInteger;
/**
* DH Public Key
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PublicKey extends DH
{
use Common\Traits\Fingerprint;
/**
* Returns the public key
*
* @param array $options optional
*/
public function toString(string $type, array $options = []): string
{
$type = self::validatePlugin('Keys', $type, 'savePublicKey');
return $type::savePublicKey($this->prime, $this->base, $this->publicKey, $options);
}
/**
* Returns the public key as a BigInteger
*/
public function toBigInteger(): BigInteger
{
return $this->publicKey;
}
}

View File

@@ -1,330 +0,0 @@
<?php
/**
* Pure-PHP FIPS 186-4 compliant implementation of DSA.
*
* PHP version 5
*
* Here's an example of how to create signatures and verify signatures with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $private = \phpseclib3\Crypt\DSA::createKey();
* $public = $private->getPublicKey();
*
* $plaintext = 'terrafrost';
*
* $signature = $private->sign($plaintext);
*
* echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\DSA\Parameters;
use phpseclib3\Crypt\DSA\PrivateKey;
use phpseclib3\Crypt\DSA\PublicKey;
use phpseclib3\Exception\InsufficientSetupException;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Math\BigInteger;
/**
* Pure-PHP FIPS 186-4 compliant implementation of DSA.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class DSA extends AsymmetricKey
{
/**
* Algorithm Name
*
* @var string
*/
public const ALGORITHM = 'DSA';
/**
* DSA Prime P
*
* @var BigInteger
*/
protected $p;
/**
* DSA Group Order q
*
* Prime divisor of p-1
*
* @var BigInteger
*/
protected $q;
/**
* DSA Group Generator G
*
* @var BigInteger
*/
protected $g;
/**
* DSA public key value y
*
* @var BigInteger
*/
protected $y;
/**
* Signature Format
*
* @var string
*/
protected $sigFormat;
/**
* Signature Format (Short)
*
* @var string
*/
protected $shortFormat;
/**
* Create DSA parameters
*
* @return DSA|bool
*/
public static function createParameters(int $L = 2048, int $N = 224)
{
self::initialize_static_variables();
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
}
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
switch (true) {
case $N == 160:
/*
in FIPS 186-1 and 186-2 N was fixed at 160 whereas K had an upper bound of 1024.
RFC 4253 (SSH Transport Layer Protocol) references FIPS 186-2 and as such most
SSH DSA implementations only support keys with an N of 160.
puttygen let's you set the size of L (but not the size of N) and uses 2048 as the
default L value. that's not really compliant with any of the FIPS standards, however,
for the purposes of maintaining compatibility with puttygen, we'll support it
*/
//case ($L >= 512 || $L <= 1024) && (($L & 0x3F) == 0) && $N == 160:
// FIPS 186-3 changed this as follows:
//case $L == 1024 && $N == 160:
case $L == 2048 && $N == 224:
case $L == 2048 && $N == 256:
case $L == 3072 && $N == 256:
break;
default:
throw new InvalidArgumentException('Invalid values for N and L');
}
$two = new BigInteger(2);
$q = BigInteger::randomPrime($N);
$divisor = $q->multiply($two);
do {
$x = BigInteger::random($L);
[, $c] = $x->divide($divisor);
$p = $x->subtract($c->subtract(self::$one));
} while ($p->getLength() != $L || !$p->isPrime());
$p_1 = $p->subtract(self::$one);
[$e] = $p_1->divide($q);
// quoting http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#page=50 ,
// "h could be obtained from a random number generator or from a counter that
// changes after each use". PuTTY (sshdssg.c) starts h off at 1 and increments
// it on each loop. wikipedia says "commonly h = 2 is used" so we'll just do that
$h = clone $two;
while (true) {
$g = $h->powMod($e, $p);
if (!$g->equals(self::$one)) {
break;
}
$h = $h->add(self::$one);
}
$dsa = new Parameters();
$dsa->p = $p;
$dsa->q = $q;
$dsa->g = $g;
return $dsa;
}
/**
* Create public / private key pair.
*
* This method is a bit polymorphic. It can take a DSA/Parameters object, L / N as two distinct parameters or
* no parameters (at which point L and N will be generated with this method)
*
* Returns the private key, from which the publickey can be extracted
*
* @param int[] ...$args
*/
public static function createKey(...$args): PrivateKey
{
self::initialize_static_variables();
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
}
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) {
$params = self::createParameters($args[0], $args[1]);
} elseif (count($args) == 1 && $args[0] instanceof Parameters) {
$params = $args[0];
} elseif (!count($args)) {
$params = self::createParameters();
} else {
throw new InsufficientSetupException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.');
}
$private = new PrivateKey();
$private->p = $params->p;
$private->q = $params->q;
$private->g = $params->g;
$private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one));
$private->y = $private->g->powMod($private->x, $private->p);
//$public = clone $private;
//unset($public->x);
return $private
->withHash($params->hash->getHash())
->withSignatureFormat($params->shortFormat);
}
/**
* OnLoad Handler
*
* @return Parameters|PrivateKey|PublicKey
*/
protected static function onLoad(array $components)
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if (!isset($components['x']) && !isset($components['y'])) {
$new = new Parameters();
} elseif (isset($components['x'])) {
$new = new PrivateKey();
$new->x = $components['x'];
} else {
$new = new PublicKey();
}
$new->p = $components['p'];
$new->q = $components['q'];
$new->g = $components['g'];
if (isset($components['y'])) {
$new->y = $components['y'];
}
return $new;
}
/**
* Constructor
*
* PublicKey and PrivateKey objects can only be created from abstract RSA class
*/
protected function __construct()
{
$this->sigFormat = self::validatePlugin('Signature', 'ASN1');
$this->shortFormat = 'ASN1';
parent::__construct();
}
/**
* Returns the key size
*
* More specifically, this L (the length of DSA Prime P) and N (the length of DSA Group Order q)
*/
public function getLength(): array
{
return ['L' => $this->p->getLength(), 'N' => $this->q->getLength()];
}
/**
* Returns the current engine being used
*
* @see self::useInternalEngine()
* @see self::useBestEngine()
*/
public function getEngine(): string
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
'OpenSSL' : 'PHP';
}
/**
* Returns the parameters
*
* A public / private key is only returned if the currently loaded "key" contains an x or y
* value.
*
* @see self::getPublicKey()
*/
public function getParameters()
{
$type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
$key = $type::saveParameters($this->p, $this->q, $this->g);
return DSA::load($key, 'PKCS1')
->withHash($this->hash->getHash())
->withSignatureFormat($this->shortFormat);
}
/**
* Determines the signature padding mode
*
* Valid values are: ASN1, SSH2, Raw
*/
public function withSignatureFormat(string $format): DSA
{
$new = clone $this;
$new->shortFormat = $format;
$new->sigFormat = self::validatePlugin('Signature', $format);
return $new;
}
/**
* Returns the signature format currently being used
*/
public function getSignatureFormat(): string
{
return $this->shortFormat;
}
}

View File

@@ -1,106 +0,0 @@
<?php
/**
* OpenSSH Formatted DSA Key Handler
*
* PHP version 5
*
* Place in $HOME/.ssh/authorized_keys
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Math\BigInteger;
/**
* OpenSSH Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class OpenSSH extends Progenitor
{
/**
* Supported Key Types
*
* @var array
*/
protected static $types = ['ssh-dss'];
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
$parsed = parent::load($key, $password);
if (isset($parsed['paddedKey'])) {
[$type] = Strings::unpackSSH2('s', $parsed['paddedKey']);
if ($type != $parsed['type']) {
throw new RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
}
[$p, $q, $g, $y, $x, $comment] = Strings::unpackSSH2('i5s', $parsed['paddedKey']);
return compact('p', 'q', 'g', 'y', 'x', 'comment');
}
[$p, $q, $g, $y] = Strings::unpackSSH2('iiii', $parsed['publicKey']);
$comment = $parsed['comment'];
return compact('p', 'q', 'g', 'y', 'comment');
}
/**
* Convert a public key to the appropriate format
*
* @param array $options optional
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = []): string
{
if ($q->getLength() != 160) {
throw new InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
}
// from <http://tools.ietf.org/html/rfc4253#page-15>:
// string "ssh-dss"
// mpint p
// mpint q
// mpint g
// mpint y
$DSAPublicKey = Strings::packSSH2('siiii', 'ssh-dss', $p, $q, $g, $y);
if ($options['binary'] ?? self::$binary) {
return $DSAPublicKey;
}
$comment = $options['comment'] ?? self::$comment;
$DSAPublicKey = 'ssh-dss ' . base64_encode($DSAPublicKey) . ' ' . $comment;
return $DSAPublicKey;
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, ?string $password = null, array $options = []): string
{
$publicKey = self::savePublicKey($p, $q, $g, $y, ['binary' => true]);
$privateKey = Strings::packSSH2('si5', 'ssh-dss', $p, $q, $g, $y, $x);
return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
}
}

View File

@@ -1,127 +0,0 @@
<?php
/**
* PKCS#1 Formatted DSA Key Handler
*
* PHP version 5
*
* Used by File/X509.php
*
* Processes keys with the following headers:
*
* -----BEGIN DSA PRIVATE KEY-----
* -----BEGIN DSA PUBLIC KEY-----
* -----BEGIN DSA PARAMETERS-----
*
* Analogous to ssh-keygen's pem format (as specified by -m)
*
* Also, technically, PKCS1 decribes RSA but I am not aware of a formal specification for DSA.
* The DSA private key format seems to have been adapted from the RSA private key format so
* we're just re-using that as the name.
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* PKCS#1 Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS1 extends Progenitor
{
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
$key = parent::load($key, $password);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER');
}
$key = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
if (is_array($key)) {
return $key;
}
$key = ASN1::asn1map($decoded[0], Maps\DSAPrivateKey::MAP);
if (is_array($key)) {
return $key;
}
$key = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
if (is_array($key)) {
return $key;
}
throw new RuntimeException('Unable to perform ASN1 mapping');
}
/**
* Convert DSA parameters to the appropriate format
*/
public static function saveParameters(BigInteger $p, BigInteger $q, BigInteger $g): string
{
$key = [
'p' => $p,
'q' => $q,
'g' => $g,
];
$key = ASN1::encodeDER($key, Maps\DSAParams::MAP);
return "-----BEGIN DSA PARAMETERS-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END DSA PARAMETERS-----\r\n";
}
/**
* Convert a private key to the appropriate format.
*
* @param string $password optional
* @param array $options optional
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, string $password = '', array $options = []): string
{
$key = [
'version' => 0,
'p' => $p,
'q' => $q,
'g' => $g,
'y' => $y,
'x' => $x,
];
$key = ASN1::encodeDER($key, Maps\DSAPrivateKey::MAP);
return self::wrapPrivateKey($key, 'DSA', $password, $options);
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y): string
{
$key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
return self::wrapPublicKey($key, 'DSA');
}
}

View File

@@ -1,133 +0,0 @@
<?php
/**
* PKCS#8 Formatted DSA Key Handler
*
* PHP version 5
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
* is specific to private keys it's basically creating a DER-encoded wrapper
* for keys. This just extends that same concept to public keys (much like ssh-keygen)
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* PKCS#8 Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS8 extends Progenitor
{
/**
* OID Name
*
* @var string
*/
public const OID_NAME = 'id-dsa';
/**
* OID Value
*
* @var string
*/
public const OID_VALUE = '1.2.840.10040.4.1';
/**
* Child OIDs loaded
*
* @var bool
*/
protected static $childOIDsLoaded = false;
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
$key = parent::load($key, $password);
$type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
$decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
if (!$decoded) {
throw new RuntimeException('Unable to decode BER of parameters');
}
$components = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
if (!is_array($components)) {
throw new RuntimeException('Unable to perform ASN1 mapping on parameters');
}
$decoded = ASN1::decodeBER($key[$type]);
if (empty($decoded)) {
throw new RuntimeException('Unable to decode BER');
}
$var = $type == 'privateKey' ? 'x' : 'y';
$components[$var] = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
if (!$components[$var] instanceof BigInteger) {
throw new RuntimeException('Unable to perform ASN1 mapping');
}
if (isset($key['meta'])) {
$components['meta'] = $key['meta'];
}
return $components;
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, ?string $password = null, array $options = []): string
{
$params = [
'p' => $p,
'q' => $q,
'g' => $g,
];
$params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
$params = new ASN1\Element($params);
$key = ASN1::encodeDER($x, Maps\DSAPublicKey::MAP);
return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
}
/**
* Convert a public key to the appropriate format
*
* @param array $options optional
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = []): string
{
$params = [
'p' => $p,
'q' => $q,
'g' => $g,
];
$params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
$params = new ASN1\Element($params);
$key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
return self::wrapPublicKey($key, $params);
}
}

View File

@@ -1,97 +0,0 @@
<?php
/**
* PuTTY Formatted DSA Key Handler
*
* puttygen does not generate DSA keys with an N of anything other than 160, however,
* it can still load them and convert them. PuTTY will load them, too, but SSH servers
* won't accept them. Since PuTTY formatted keys are primarily used with SSH this makes
* keys with N > 160 kinda useless, hence this handlers not supporting such keys.
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Math\BigInteger;
/**
* PuTTY Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PuTTY extends Progenitor
{
/**
* Public Handler
*
* @var string
*/
public const PUBLIC_HANDLER = 'phpseclib3\Crypt\DSA\Formats\Keys\OpenSSH';
/**
* Algorithm Identifier
*
* @var array
*/
protected static $types = ['ssh-dss'];
/**
* Break a public or private key down into its constituent components
*
* @param array|string $key
* @param string|false $password
* @return array|false
*/
public static function load($key, $password)
{
$components = parent::load($key, $password);
if (!isset($components['private'])) {
return $components;
}
extract($components);
unset($components['public'], $components['private']);
[$p, $q, $g, $y] = Strings::unpackSSH2('iiii', $public);
[$x] = Strings::unpackSSH2('i', $private);
return compact('p', 'q', 'g', 'y', 'x', 'comment');
}
/**
* Convert a private key to the appropriate format.
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, ?string $password = null, array $options = []): string
{
if ($q->getLength() != 160) {
throw new InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
}
$public = Strings::packSSH2('iiii', $p, $q, $g, $y);
$private = Strings::packSSH2('i', $x);
return self::wrapPrivateKey($public, $private, 'ssh-dss', $password, $options);
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y): string
{
if ($q->getLength() != 160) {
throw new InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
}
return self::wrapPublicKey(Strings::packSSH2('iiii', $p, $q, $g, $y), 'ssh-dss');
}
}

View File

@@ -1,74 +0,0 @@
<?php
/**
* Raw DSA Key Handler
*
* PHP version 5
*
* Reads and creates arrays as DSA keys
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Math\BigInteger;
/**
* Raw DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Raw
{
/**
* Break a public or private key down into its constituent components
*
* @param string|array $key
*/
public static function load($key, ?string $password = null): array
{
if (!is_array($key)) {
throw new UnexpectedValueException('Key should be a array - not a ' . gettype($key));
}
switch (true) {
case !isset($key['p']) || !isset($key['q']) || !isset($key['g']):
case !$key['p'] instanceof BigInteger:
case !$key['q'] instanceof BigInteger:
case !$key['g'] instanceof BigInteger:
case !isset($key['x']) && !isset($key['y']):
case isset($key['x']) && !$key['x'] instanceof BigInteger:
case isset($key['y']) && !$key['y'] instanceof BigInteger:
throw new UnexpectedValueException('Key appears to be malformed');
}
$options = ['p' => 1, 'q' => 1, 'g' => 1, 'x' => 1, 'y' => 1];
return array_intersect_key($key, $options);
}
/**
* Convert a private key to the appropriate format.
*
* @param string $password optional
*/
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, string $password = ''): string
{
return compact('p', 'q', 'g', 'y', 'x');
}
/**
* Convert a public key to the appropriate format
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y): string
{
return compact('p', 'q', 'g', 'y');
}
}

View File

@@ -1,125 +0,0 @@
<?php
/**
* XML Formatted DSA Key Handler
*
* While XKMS defines a private key format for RSA it does not do so for DSA. Quoting that standard:
*
* "[XKMS] does not specify private key parameters for the DSA signature algorithm since the algorithm only
* supports signature modes and so the application of server generated keys and key recovery is of limited
* value"
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Exception\BadConfigurationException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Math\BigInteger;
/**
* XML Formatted DSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class XML
{
/**
* Break a public or private key down into its constituent components
*/
public static function load(string $key, ?string $password = null): array
{
if (!Strings::is_stringable($key)) {
throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
if (!class_exists('DOMDocument')) {
throw new BadConfigurationException('The dom extension is not setup correctly on this system');
}
$use_errors = libxml_use_internal_errors(true);
$dom = new \DOMDocument();
if (substr($key, 0, 5) != '<?xml') {
$key = '<xml>' . $key . '</xml>';
}
if (!$dom->loadXML($key)) {
libxml_use_internal_errors($use_errors);
throw new UnexpectedValueException('Key does not appear to contain XML');
}
$xpath = new \DOMXPath($dom);
$keys = ['p', 'q', 'g', 'y', 'j', 'seed', 'pgencounter'];
foreach ($keys as $key) {
// $dom->getElementsByTagName($key) is case-sensitive
$temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']");
if (!$temp->length) {
continue;
}
$value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256);
switch ($key) {
case 'p': // a prime modulus meeting the [DSS] requirements
// Parameters P, Q, and G can be public and common to a group of users. They might be known
// from application context. As such, they are optional but P and Q must either both appear
// or both be absent
$components['p'] = $value;
break;
case 'q': // an integer in the range 2**159 < Q < 2**160 which is a prime divisor of P-1
$components['q'] = $value;
break;
case 'g': // an integer with certain properties with respect to P and Q
$components['g'] = $value;
break;
case 'y': // G**X mod P (where X is part of the private key and not made public)
$components['y'] = $value;
// the remaining options do not do anything
case 'j': // (P - 1) / Q
// Parameter J is available for inclusion solely for efficiency as it is calculatable from
// P and Q
case 'seed': // a DSA prime generation seed
// Parameters seed and pgenCounter are used in the DSA prime number generation algorithm
// specified in [DSS]. As such, they are optional but must either both be present or both
// be absent
case 'pgencounter': // a DSA prime generation counter
}
}
libxml_use_internal_errors($use_errors);
if (!isset($components['y'])) {
throw new UnexpectedValueException('Key is missing y component');
}
switch (true) {
case !isset($components['p']):
case !isset($components['q']):
case !isset($components['g']):
return ['y' => $components['y']];
}
return $components;
}
/**
* Convert a public key to the appropriate format
*
* See https://www.w3.org/TR/xmldsig-core/#sec-DSAKeyValue
*/
public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y): string
{
return "<DSAKeyValue>\r\n" .
' <P>' . Strings::base64_encode($p->toBytes()) . "</P>\r\n" .
' <Q>' . Strings::base64_encode($q->toBytes()) . "</Q>\r\n" .
' <G>' . Strings::base64_encode($g->toBytes()) . "</G>\r\n" .
' <Y>' . Strings::base64_encode($y->toBytes()) . "</Y>\r\n" .
'</DSAKeyValue>';
}
}

View File

@@ -1,59 +0,0 @@
<?php
/**
* ASN1 Signature Handler
*
* PHP version 5
*
* Handles signatures in the format described in
* https://tools.ietf.org/html/rfc3279#section-2.2.2
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Signature;
use phpseclib3\File\ASN1 as Encoder;
use phpseclib3\File\ASN1\Maps;
use phpseclib3\Math\BigInteger;
/**
* ASN1 Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class ASN1
{
/**
* Loads a signature
*
* @return array|bool
*/
public static function load(string $sig)
{
if (!is_string($sig)) {
return false;
}
$decoded = Encoder::decodeBER($sig);
if (empty($decoded)) {
return false;
}
$components = Encoder::asn1map($decoded[0], Maps\DssSigValue::MAP);
return $components;
}
/**
* Returns a signature in the appropriate format
*/
public static function save(BigInteger $r, BigInteger $s): string
{
return Encoder::encodeDER(compact('r', 's'), Maps\DssSigValue::MAP);
}
}

View File

@@ -1,27 +0,0 @@
<?php
/**
* Raw DSA Signature Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Signature;
use phpseclib3\Crypt\Common\Formats\Signature\Raw as Progenitor;
/**
* Raw DSA Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Raw extends Progenitor
{
}

View File

@@ -1,71 +0,0 @@
<?php
/**
* SSH2 Signature Handler
*
* PHP version 5
*
* Handles signatures in the format used by SSH2
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA\Formats\Signature;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Math\BigInteger;
/**
* SSH2 Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class SSH2
{
/**
* Loads a signature
*/
public static function load(string $sig)
{
if (!is_string($sig)) {
return false;
}
$result = Strings::unpackSSH2('ss', $sig);
if ($result === false) {
return false;
}
[$type, $blob] = $result;
if ($type != 'ssh-dss' || strlen($blob) != 40) {
return false;
}
return [
'r' => new BigInteger(substr($blob, 0, 20), 256),
's' => new BigInteger(substr($blob, 20), 256),
];
}
/**
* Returns a signature in the appropriate format
*
* @return string
*/
public static function save(BigInteger $r, BigInteger $s)
{
if ($r->getLength() > 160 || $s->getLength() > 160) {
return false;
}
return Strings::packSSH2(
'ss',
'ssh-dss',
str_pad($r->toBytes(), 20, "\0", STR_PAD_LEFT) .
str_pad($s->toBytes(), 20, "\0", STR_PAD_LEFT)
);
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* DSA Parameters
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\DSA;
/**
* DSA Parameters
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class Parameters extends DSA
{
/**
* Returns the parameters
*
* @param array $options optional
*/
public function toString(string $type = 'PKCS1', array $options = []): string
{
$type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
return $type::saveParameters($this->p, $this->q, $this->g, $options);
}
}

View File

@@ -1,150 +0,0 @@
<?php
/**
* DSA Private Key
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
use phpseclib3\Math\BigInteger;
/**
* DSA Private Key
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PrivateKey extends DSA implements Common\PrivateKey
{
use Common\Traits\PasswordProtected;
/**
* DSA secret exponent x
*
* @var BigInteger
*/
protected $x;
/**
* Returns the public key
*
* If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key
* that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING.
* An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this
* parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g
* variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified
* by getting a DSA PKCS8 public key:
*
* "openssl dsa -in private.dsa -pubout -outform PEM"
*
* ie. just swap out rsa with dsa in the rsa command above.
*
* A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA
* the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature
* without the parameters and the PKCS1 DSA public key format does not include the parameters.
*
* @see self::getPrivateKey()
*/
public function getPublicKey()
{
$type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
if (!isset($this->y)) {
$this->y = $this->g->powMod($this->x, $this->p);
}
$key = $type::savePublicKey($this->p, $this->q, $this->g, $this->y);
return DSA::loadFormat('PKCS8', $key)
->withHash($this->hash->getHash())
->withSignatureFormat($this->shortFormat);
}
/**
* Create a signature
*
* @see self::verify()
* @param string $message
*/
public function sign($message): string
{
$format = $this->sigFormat;
if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
$signature = '';
$result = openssl_sign($message, $signature, $this->toString('PKCS8'), $this->hash->getHash());
if ($result) {
if ($this->shortFormat == 'ASN1') {
return $signature;
}
extract(ASN1Signature::load($signature));
return $format::save($r, $s);
}
}
$h = $this->hash->hash($message);
$h = $this->bits2int($h);
while (true) {
$k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one));
$r = $this->g->powMod($k, $this->p);
[, $r] = $r->divide($this->q);
if ($r->equals(self::$zero)) {
continue;
}
$kinv = $k->modInverse($this->q);
$temp = $h->add($this->x->multiply($r));
$temp = $kinv->multiply($temp);
[, $s] = $temp->divide($this->q);
if (!$s->equals(self::$zero)) {
break;
}
}
// the following is an RFC6979 compliant implementation of deterministic DSA
// it's unused because it's mainly intended for use when a good CSPRNG isn't
// available. if phpseclib's CSPRNG isn't good then even key generation is
// suspect
/*
$h1 = $this->hash->hash($message);
$k = $this->computek($h1);
$r = $this->g->powMod($k, $this->p);
list(, $r) = $r->divide($this->q);
$kinv = $k->modInverse($this->q);
$h1 = $this->bits2int($h1);
$temp = $h1->add($this->x->multiply($r));
$temp = $kinv->multiply($temp);
list(, $s) = $temp->divide($this->q);
*/
return $format::save($r, $s);
}
/**
* Returns the private key
*
* @param array $options optional
*/
public function toString(string $type, array $options = []): string
{
$type = self::validatePlugin('Keys', $type, 'savePrivateKey');
if (!isset($this->y)) {
$this->y = $this->g->powMod($this->x, $this->p);
}
return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password, $options);
}
}

View File

@@ -1,85 +0,0 @@
<?php
/**
* DSA Public Key
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
/**
* DSA Public Key
*
* @author Jim Wigginton <terrafrost@php.net>
*/
final class PublicKey extends DSA implements Common\PublicKey
{
use Common\Traits\Fingerprint;
/**
* Verify a signature
*
* @see self::verify()
* @param string $message
* @param string $signature
*/
public function verify($message, $signature): bool
{
$format = $this->sigFormat;
$params = $format::load($signature);
if ($params === false || count($params) != 2) {
return false;
}
extract($params);
if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
$sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature;
$result = openssl_verify($message, $sig, $this->toString('PKCS8'), $this->hash->getHash());
if ($result != -1) {
return (bool) $result;
}
}
$q_1 = $this->q->subtract(self::$one);
if (!$r->between(self::$one, $q_1) || !$s->between(self::$one, $q_1)) {
return false;
}
$w = $s->modInverse($this->q);
$h = $this->hash->hash($message);
$h = $this->bits2int($h);
[, $u1] = $h->multiply($w)->divide($this->q);
[, $u2] = $r->multiply($w)->divide($this->q);
$v1 = $this->g->powMod($u1, $this->p);
$v2 = $this->y->powMod($u2, $this->p);
[, $v] = $v1->multiply($v2)->divide($this->p);
[, $v] = $v->divide($this->q);
return $v->equals($r);
}
/**
* Returns the public key
*
* @param array $options optional
*/
public function toString(string $type, array $options = []): string
{
$type = self::validatePlugin('Keys', $type, 'savePublicKey');
return $type::savePublicKey($this->p, $this->q, $this->g, $this->y, $options);
}
}

View File

@@ -1,470 +0,0 @@
<?php
/**
* Pure-PHP implementation of EC.
*
* PHP version 5
*
* Here's an example of how to create signatures and verify signatures with this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $private = \phpseclib3\Crypt\EC::createKey('secp256k1');
* $public = $private->getPublicKey();
*
* $plaintext = 'terrafrost';
*
* $signature = $private->sign($plaintext);
*
* echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
declare(strict_types=1);
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\EC\BaseCurves\Base;
use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
use phpseclib3\Crypt\EC\Curves\Curve25519;
use phpseclib3\Crypt\EC\Curves\Ed25519;
use phpseclib3\Crypt\EC\Curves\Ed448;
use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
use phpseclib3\Crypt\EC\Parameters;
use phpseclib3\Crypt\EC\PrivateKey;
use phpseclib3\Crypt\EC\PublicKey;
use phpseclib3\Exception\InvalidArgumentException;
use phpseclib3\Exception\LengthException;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\Exception\UnsupportedCurveException;
use phpseclib3\Exception\UnsupportedOperationException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps\ECParameters;
use phpseclib3\Math\BigInteger;
/**
* Pure-PHP implementation of EC.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class EC extends AsymmetricKey
{
/**
* Algorithm Name
*
* @var string
*/
public const ALGORITHM = 'EC';
/**
* Public Key QA
*
* @var object[]
*/
protected $QA;
/**
* Curve
*
* @var Base
*/
protected $curve;
/**
* Signature Format
*
* @var string
*/
protected $format;
/**
* Signature Format (Short)
*
* @var string
*/
protected $shortFormat;
/**
* Curve Name
*
* @var string
*/
private $curveName;
/**
* Curve Order
*
* Used for deterministic ECDSA
*
* @var BigInteger
*/
protected $q;
/**
* Alias for the private key
*
* Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because
* with x you have x * the base point yielding an (x, y)-coordinate that is the
* public key. But the x is different depending on which side of the equal sign
* you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate.
*
* @var BigInteger
*/
protected $x;
/**
* Context
*
* @var string
*/
protected $context;
/**
* Signature Format
*
* @var string
*/
protected $sigFormat;
/**
* Create public / private key pair.
*/
public static function createKey(string $curve): PrivateKey
{
self::initialize_static_variables();
$class = new \ReflectionClass(static::class);
if ($class->isFinal()) {
throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
}
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
$curve = strtolower($curve);
if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) {
$kp = sodium_crypto_sign_keypair();
$privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp));
//$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp));
$privatekey->curveName = 'Ed25519';
//$publickey->curveName = $curve;
return $privatekey;
}
$privatekey = new PrivateKey();
$curveName = $curve;
if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) {
$curveName = ucfirst($curveName);
} elseif (substr($curveName, 0, 10) == 'brainpoolp') {
$curveName = 'brainpoolP' . substr($curveName, 10);
}
$curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
if (!class_exists($curve)) {
throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported');
}
$reflect = new \ReflectionClass($curve);
$curveName = $reflect->isFinal() ?
$reflect->getParentClass()->getShortName() :
$reflect->getShortName();
$curve = new $curve();
if ($curve instanceof TwistedEdwardsCurve) {
$arr = $curve->extractSecret(Random::string($curve instanceof Ed448 ? 57 : 32));
$privatekey->dA = $dA = $arr['dA'];
$privatekey->secret = $arr['secret'];
} else {
$privatekey->dA = $dA = $curve->createRandomMultiplier();
}
if ($curve instanceof Curve25519 && self::$engines['libsodium']) {
//$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000');
//$QA = sodium_crypto_scalarmult($dA->toBytes(), $r);
$QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes());
$privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))];
} else {
$privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA);
}
$privatekey->curve = $curve;
//$publickey = clone $privatekey;
//unset($publickey->dA);
//unset($publickey->x);
$privatekey->curveName = $curveName;
//$publickey->curveName = $curveName;
if ($privatekey->curve instanceof TwistedEdwardsCurve) {
return $privatekey->withHash($curve::HASH);
}
return $privatekey;
}
/**
* OnLoad Handler
*
* @return AsymmetricKey|Parameters|PrivateKey|PublicKey
*/
protected static function onLoad(array $components)
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if (!isset($components['dA']) && !isset($components['QA'])) {
$new = new Parameters();
$new->curve = $components['curve'];
return $new;
}
$new = isset($components['dA']) ?
new PrivateKey() :
new PublicKey();
$new->curve = $components['curve'];
$new->QA = $components['QA'];
if (isset($components['dA'])) {
$new->dA = $components['dA'];
$new->secret = $components['secret'];
}
if ($new->curve instanceof TwistedEdwardsCurve) {
return $new->withHash($components['curve']::HASH);
}
return $new;
}
/**
* Constructor
*
* PublicKey and PrivateKey objects can only be created from abstract RSA class
*/
protected function __construct()
{
$this->sigFormat = self::validatePlugin('Signature', 'ASN1');
$this->shortFormat = 'ASN1';
parent::__construct();
}
/**
* Returns the curve
*
* Returns a string if it's a named curve, an array if not
*
* @return string|array
*/
public function getCurve()
{
if ($this->curveName) {
return $this->curveName;
}
if ($this->curve instanceof MontgomeryCurve) {
$this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448';
return $this->curveName;
}
if ($this->curve instanceof TwistedEdwardsCurve) {
$this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448';
return $this->curveName;
}
$params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]);
$decoded = ASN1::extractBER($params);
$decoded = ASN1::decodeBER($decoded);
$decoded = ASN1::asn1map($decoded[0], ECParameters::MAP);
if (isset($decoded['namedCurve'])) {
$this->curveName = $decoded['namedCurve'];
return $decoded['namedCurve'];
}
if (!$namedCurves) {
PKCS1::useSpecifiedCurve();
}
return $decoded;
}
/**
* Returns the key size
*
* Quoting https://tools.ietf.org/html/rfc5656#section-2,
*
* "The size of a set of elliptic curve domain parameters on a prime
* curve is defined as the number of bits in the binary representation
* of the field order, commonly denoted by p. Size on a
* characteristic-2 curve is defined as the number of bits in the binary
* representation of the field, commonly denoted by m. A set of
* elliptic curve domain parameters defines a group of order n generated
* by a base point P"
*/
public function getLength(): int
{
return $this->curve->getLength();
}
/**
* Returns the current engine being used
*
* @see self::useInternalEngine()
* @see self::useBestEngine()
*/
public function getEngine(): string
{
if (!isset(self::$engines['PHP'])) {
self::useBestEngine();
}
if ($this->curve instanceof TwistedEdwardsCurve) {
return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ?
'libsodium' : 'PHP';
}
return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
'OpenSSL' : 'PHP';
}
/**
* Returns the public key coordinates as a string
*
* Used by ECDH
*/
public function getEncodedCoordinates(): string
{
if ($this->curve instanceof MontgomeryCurve) {
return strrev($this->QA[0]->toBytes(true));
}
if ($this->curve instanceof TwistedEdwardsCurve) {
return $this->curve->encodePoint($this->QA);
}
return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true);
}
/**
* Returns the parameters
*
* @param string $type optional
* @see self::getPublicKey()
*/
public function getParameters(string $type = 'PKCS1')
{
$type = self::validatePlugin('Keys', $type, 'saveParameters');
$key = $type::saveParameters($this->curve);
return EC::load($key, 'PKCS1')
->withHash($this->hash->getHash())
->withSignatureFormat($this->shortFormat);
}
/**
* Determines the signature padding mode
*
* Valid values are: ASN1, SSH2, Raw
*/
public function withSignatureFormat(string $format): EC
{
if ($this->curve instanceof MontgomeryCurve) {
throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
}
$new = clone $this;
$new->shortFormat = $format;
$new->sigFormat = self::validatePlugin('Signature', $format);
return $new;
}
/**
* Returns the signature format currently being used
*/
public function getSignatureFormat(): string
{
return $this->shortFormat;
}
/**
* Sets the context
*
* Used by Ed25519 / Ed448.
*
* @param string|null $context optional
* @see self::verify()
* @see self::sign()
*/
public function withContext(?string $context = null): EC
{
if (!$this->curve instanceof TwistedEdwardsCurve) {
throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts');
}
$new = clone $this;
if (!isset($context)) {
$new->context = null;
return $new;
}
if (!is_string($context)) {
throw new InvalidArgumentException('setContext expects a string');
}
if (strlen($context) > 255) {
throw new LengthException('The context is supposed to be, at most, 255 bytes long');
}
$new->context = $context;
return $new;
}
/**
* Returns the signature format currently being used
*/
public function getContext(): string
{
return $this->context;
}
/**
* Determines which hashing function should be used
*/
public function withHash(string $hash): AsymmetricKey
{
if ($this->curve instanceof MontgomeryCurve) {
throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
}
if ($this->curve instanceof Ed25519 && $hash != 'sha512') {
throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash');
}
if ($this->curve instanceof Ed448 && $hash != 'shake256-912') {
throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes');
}
return parent::withHash($hash);
}
/**
* __toString() magic method
*
* @return string
*/
public function __toString()
{
if ($this->curve instanceof MontgomeryCurve) {
return '';
}
return parent::__toString();
}
}

View File

@@ -1,217 +0,0 @@
<?php
/**
* Curve methods common to all curves
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Exception\RangeException;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\FiniteField\Integer;
/**
* Base
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Base
{
/**
* The Order
*
* @var BigInteger
*/
protected $order;
/**
* Finite Field Integer factory
*
* @var Integer
*/
protected $factory;
/**
* Returns a random integer
*
* @return object
*/
public function randomInteger()
{
return $this->factory->randomInteger();
}
/**
* Converts a BigInteger to a \phpseclib3\Math\FiniteField\Integer integer
*
* @return object
*/
public function convertInteger(BigInteger $x)
{
return $this->factory->newInteger($x);
}
/**
* Returns the length, in bytes, of the modulo
*
* @return Integer
*/
public function getLengthInBytes(): int
{
return $this->factory->getLengthInBytes();
}
/**
* Returns the length, in bits, of the modulo
*
* @return Integer
*/
public function getLength(): int
{
return $this->factory->getLength();
}
/**
* Multiply a point on the curve by a scalar
*
* Uses the montgomery ladder technique as described here:
*
* https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
* https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772
*/
public function multiplyPoint(array $p, BigInteger $d): array
{
$alreadyInternal = isset($p[2]);
$r = $alreadyInternal ?
[[], $p] :
[[], $this->convertToInternal($p)];
$d = $d->toBits();
for ($i = 0; $i < strlen($d); $i++) {
$d_i = (int) $d[$i];
$r[1 - $d_i] = $this->addPoint($r[0], $r[1]);
$r[$d_i] = $this->doublePoint($r[$d_i]);
}
return $alreadyInternal ? $r[0] : $this->convertToAffine($r[0]);
}
/**
* Creates a random scalar multiplier
*/
public function createRandomMultiplier(): BigInteger
{
static $one;
if (!isset($one)) {
$one = new BigInteger(1);
}
return BigInteger::randomRange($one, $this->order->subtract($one));
}
/**
* Performs range check
*/
public function rangeCheck(BigInteger $x): void
{
static $zero;
if (!isset($zero)) {
$zero = new BigInteger();
}
if (!isset($this->order)) {
throw new RuntimeException('setOrder needs to be called before this method');
}
if ($x->compare($this->order) > 0 || $x->compare($zero) <= 0) {
throw new RangeException('x must be between 1 and the order of the curve');
}
}
/**
* Sets the Order
*/
public function setOrder(BigInteger $order): void
{
$this->order = $order;
}
/**
* Returns the Order
*/
public function getOrder(): BigInteger
{
return $this->order;
}
/**
* Use a custom defined modular reduction function
*
* @return object
*/
public function setReduction(callable $func)
{
$this->factory->setReduction($func);
}
/**
* Returns the affine point
*
* @return object[]
*/
public function convertToAffine(array $p): array
{
return $p;
}
/**
* Converts an affine point to a jacobian coordinate
*
* @return object[]
*/
public function convertToInternal(array $p): array
{
return $p;
}
/**
* Negates a point
*
* @return object[]
*/
public function negatePoint(array $p): array
{
$temp = [
$p[0],
$p[1]->negate(),
];
if (isset($p[2])) {
$temp[] = $p[2];
}
return $temp;
}
/**
* Multiply and Add Points
*
* @return int[]
*/
public function multiplyAddPoints(array $points, array $scalars): array
{
$p1 = $this->convertToInternal($points[0]);
$p2 = $this->convertToInternal($points[1]);
$p1 = $this->multiplyPoint($p1, $scalars[0]);
$p2 = $this->multiplyPoint($p2, $scalars[1]);
$r = $this->addPoint($p1, $p2);
return $this->convertToAffine($r);
}
}

View File

@@ -1,371 +0,0 @@
<?php
/**
* Curves over y^2 + x*y = x^3 + a*x^2 + b
*
* These are curves used in SEC 2 over prime fields: http://www.secg.org/SEC2-Ver-1.0.pdf
* The curve is a weierstrass curve with a[3] and a[2] set to 0.
*
* Uses Jacobian Coordinates for speed if able:
*
* https://en.wikipedia.org/wiki/Jacobian_curve
* https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\BinaryField;
use phpseclib3\Math\BinaryField\Integer as BinaryInteger;
use phpseclib3\Math\PrimeField\Integer;
/**
* Curves over y^2 + x*y = x^3 + a*x^2 + b
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class Binary extends Base
{
/**
* Binary Field Integer factory
*
* @var BinaryField
*/
protected $factory;
/**
* Cofficient for x^1
*
* @var object
*/
protected $a;
/**
* Cofficient for x^0
*
* @var object
*/
protected $b;
/**
* Base Point
*
* @var object
*/
protected $p;
/**
* The number one over the specified finite field
*
* @var object
*/
protected $one;
/**
* The modulo
*
* @var array
*/
protected $modulo;
/**
* The Order
*
* @var BigInteger
*/
protected $order;
/**
* Sets the modulo
*/
public function setModulo(int ...$modulo): void
{
$this->modulo = $modulo;
$this->factory = new BinaryField(...$modulo);
$this->one = $this->factory->newInteger("\1");
}
/**
* Set coefficients a and b
*/
public function setCoefficients(string $a, string $b): void
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
$this->a = $this->factory->newInteger(pack('H*', $a));
$this->b = $this->factory->newInteger(pack('H*', $b));
}
/**
* Set x and y coordinates for the base point
*
* @param string|BinaryInteger $x
* @param string|BinaryInteger $y
*/
public function setBasePoint($x, $y): void
{
switch (true) {
case !is_string($x) && !$x instanceof BinaryInteger:
throw new UnexpectedValueException('Argument 1 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer');
case !is_string($y) && !$y instanceof BinaryInteger:
throw new UnexpectedValueException('Argument 2 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer');
}
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
$this->p = [
is_string($x) ? $this->factory->newInteger(pack('H*', $x)) : $x,
is_string($y) ? $this->factory->newInteger(pack('H*', $y)) : $y,
];
}
/**
* Retrieve the base point as an array
*
* @return array
*/
public function getBasePoint()
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
/*
if (!isset($this->p)) {
throw new \phpseclib3\Exception\RuntimeException('setBasePoint needs to be called before this method');
}
*/
return $this->p;
}
/**
* Adds two points on the curve
*
* @return FiniteField[]
*/
public function addPoint(array $p, array $q): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
if (count($q)) {
return $q;
}
if (count($p)) {
return $p;
}
return [];
}
if (!isset($p[2]) || !isset($q[2])) {
throw new RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
if ($p[0]->equals($q[0])) {
return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
}
// formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html
[$x1, $y1, $z1] = $p;
[$x2, $y2, $z2] = $q;
$o1 = $z1->multiply($z1);
$b = $x2->multiply($o1);
if ($z2->equals($this->one)) {
$d = $y2->multiply($o1)->multiply($z1);
$e = $x1->add($b);
$f = $y1->add($d);
$z3 = $e->multiply($z1);
$h = $f->multiply($x2)->add($z3->multiply($y2));
$i = $f->add($z3);
$g = $z3->multiply($z3);
$p1 = $this->a->multiply($g);
$p2 = $f->multiply($i);
$p3 = $e->multiply($e)->multiply($e);
$x3 = $p1->add($p2)->add($p3);
$y3 = $i->multiply($x3)->add($g->multiply($h));
return [$x3, $y3, $z3];
}
$o2 = $z2->multiply($z2);
$a = $x1->multiply($o2);
$c = $y1->multiply($o2)->multiply($z2);
$d = $y2->multiply($o1)->multiply($z1);
$e = $a->add($b);
$f = $c->add($d);
$g = $e->multiply($z1);
$h = $f->multiply($x2)->add($g->multiply($y2));
$z3 = $g->multiply($z2);
$i = $f->add($z3);
$p1 = $this->a->multiply($z3->multiply($z3));
$p2 = $f->multiply($i);
$p3 = $e->multiply($e)->multiply($e);
$x3 = $p1->add($p2)->add($p3);
$y3 = $i->multiply($x3)->add($g->multiply($g)->multiply($h));
return [$x3, $y3, $z3];
}
/**
* Doubles a point on a curve
*
* @return FiniteField[]
*/
public function doublePoint(array $p): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p)) {
return [];
}
if (!isset($p[2])) {
throw new RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
// formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html
[$x1, $y1, $z1] = $p;
$a = $x1->multiply($x1);
$b = $a->multiply($a);
if ($z1->equals($this->one)) {
$x3 = $b->add($this->b);
$z3 = clone $x1;
$p1 = $a->add($y1)->add($z3)->multiply($this->b);
$p2 = $a->add($y1)->multiply($b);
$y3 = $p1->add($p2);
return [$x3, $y3, $z3];
}
$c = $z1->multiply($z1);
$d = $c->multiply($c);
$x3 = $b->add($this->b->multiply($d->multiply($d)));
$z3 = $x1->multiply($c);
$p1 = $b->multiply($z3);
$p2 = $a->add($y1->multiply($z1))->add($z3)->multiply($x3);
$y3 = $p1->add($p2);
return [$x3, $y3, $z3];
}
/**
* Returns the X coordinate and the derived Y coordinate
*
* Not supported because it is covered by patents.
* Quoting https://www.openssl.org/docs/man1.1.0/apps/ecparam.html ,
*
* "Due to patent issues the compressed option is disabled by default for binary curves
* and can be enabled by defining the preprocessor macro OPENSSL_EC_BIN_PT_COMP at
* compile time."
*/
public function derivePoint($m): array
{
throw new RuntimeException('Point compression on binary finite field elliptic curves is not supported');
}
/**
* Tests whether or not the x / y values satisfy the equation
*
* @return boolean
*/
public function verifyPoint(array $p): bool
{
[$x, $y] = $p;
$lhs = $y->multiply($y);
$lhs = $lhs->add($x->multiply($y));
$x2 = $x->multiply($x);
$x3 = $x2->multiply($x);
$rhs = $x3->add($this->a->multiply($x2))->add($this->b);
return $lhs->equals($rhs);
}
/**
* Returns the modulo
*/
public function getModulo(): array
{
return $this->modulo;
}
/**
* Returns the a coefficient
*
* @return Integer
*/
public function getA()
{
return $this->a;
}
/**
* Returns the a coefficient
*
* @return Integer
*/
public function getB()
{
return $this->b;
}
/**
* Returns the affine point
*
* A Jacobian Coordinate is of the form (x, y, z).
* To convert a Jacobian Coordinate to an Affine Point
* you do (x / z^2, y / z^3)
*
* @return Integer[]
*/
public function convertToAffine(array $p): array
{
if (!isset($p[2])) {
return $p;
}
[$x, $y, $z] = $p;
$z = $this->one->divide($z);
$z2 = $z->multiply($z);
return [
$x->multiply($z2),
$y->multiply($z2)->multiply($z),
];
}
/**
* Converts an affine point to a jacobian coordinate
*
* @return Integer[]
*/
public function convertToInternal(array $p): array
{
if (isset($p[2])) {
return $p;
}
$p[2] = clone $this->one;
$p['fresh'] = true;
return $p;
}
}

View File

@@ -1,335 +0,0 @@
<?php
/**
* Generalized Koblitz Curves over y^2 = x^3 + b.
*
* According to http://www.secg.org/SEC2-Ver-1.0.pdf Koblitz curves are over the GF(2**m)
* finite field. Both the $a$ and $b$ coefficients are either 0 or 1. However, SEC2
* generalizes the definition to include curves over GF(P) "which possess an efficiently
* computable endomorphism".
*
* For these generalized Koblitz curves $b$ doesn't have to be 0 or 1. Whether or not $a$
* has any restrictions on it is unclear, however, for all the GF(P) Koblitz curves defined
* in SEC2 v1.0 $a$ is $0$ so all of the methods defined herein will assume that it is.
*
* I suppose we could rename the $b$ coefficient to $a$, however, the documentation refers
* to $b$ so we'll just keep it.
*
* If a later version of SEC2 comes out wherein some $a$ values are non-zero we can create a
* new method for those. eg. KoblitzA1Prime.php or something.
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\PrimeField;
/**
* Curves over y^2 = x^3 + b
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class KoblitzPrime extends Prime
{
/**
* Basis
*
* @var list<array{a: BigInteger, b: BigInteger}>
*/
public $basis;
/**
* Beta
*
* @var PrimeField\Integer
*/
public $beta;
// don't overwrite setCoefficients() with one that only accepts one parameter so that
// one might be able to switch between KoblitzPrime and Prime more easily (for benchmarking
// purposes).
/**
* Multiply and Add Points
*
* Uses a efficiently computable endomorphism to achieve a slight speedup
*
* Adapted from:
* https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/short.js#L219
*
* @return int[]
*/
public function multiplyAddPoints(array $points, array $scalars): array
{
static $zero, $one, $two;
if (!isset($two)) {
$two = new BigInteger(2);
$one = new BigInteger(1);
}
if (!isset($this->beta)) {
// get roots
$inv = $this->one->divide($this->two)->negate();
$s = $this->three->negate()->squareRoot()->multiply($inv);
$betas = [
$inv->add($s),
$inv->subtract($s),
];
$this->beta = $betas[0]->compare($betas[1]) < 0 ? $betas[0] : $betas[1];
//echo strtoupper($this->beta->toHex(true)) . "\n"; exit;
}
if (!isset($this->basis)) {
$factory = new PrimeField($this->order);
$tempOne = $factory->newInteger($one);
$tempTwo = $factory->newInteger($two);
$tempThree = $factory->newInteger(new BigInteger(3));
$inv = $tempOne->divide($tempTwo)->negate();
$s = $tempThree->negate()->squareRoot()->multiply($inv);
$lambdas = [
$inv->add($s),
$inv->subtract($s),
];
$lhs = $this->multiplyPoint($this->p, $lambdas[0])[0];
$rhs = $this->p[0]->multiply($this->beta);
$lambda = $lhs->equals($rhs) ? $lambdas[0] : $lambdas[1];
$this->basis = static::extendedGCD($lambda->toBigInteger(), $this->order);
///*
foreach ($this->basis as $basis) {
echo strtoupper($basis['a']->toHex(true)) . "\n";
echo strtoupper($basis['b']->toHex(true)) . "\n\n";
}
exit;
//*/
}
$npoints = $nscalars = [];
for ($i = 0; $i < count($points); $i++) {
$p = $points[$i];
$k = $scalars[$i]->toBigInteger();
// begin split
[$v1, $v2] = $this->basis;
$c1 = $v2['b']->multiply($k);
[$c1, $r] = $c1->divide($this->order);
if ($this->order->compare($r->multiply($two)) <= 0) {
$c1 = $c1->add($one);
}
$c2 = $v1['b']->negate()->multiply($k);
[$c2, $r] = $c2->divide($this->order);
if ($this->order->compare($r->multiply($two)) <= 0) {
$c2 = $c2->add($one);
}
$p1 = $c1->multiply($v1['a']);
$p2 = $c2->multiply($v2['a']);
$q1 = $c1->multiply($v1['b']);
$q2 = $c2->multiply($v2['b']);
$k1 = $k->subtract($p1)->subtract($p2);
$k2 = $q1->add($q2)->negate();
// end split
$beta = [
$p[0]->multiply($this->beta),
$p[1],
clone $this->one,
];
if (isset($p['naf'])) {
$beta['naf'] = array_map(function ($p) {
return [
$p[0]->multiply($this->beta),
$p[1],
clone $this->one,
];
}, $p['naf']);
$beta['nafwidth'] = $p['nafwidth'];
}
if ($k1->isNegative()) {
$k1 = $k1->negate();
$p = $this->negatePoint($p);
}
if ($k2->isNegative()) {
$k2 = $k2->negate();
$beta = $this->negatePoint($beta);
}
$pos = 2 * $i;
$npoints[$pos] = $p;
$nscalars[$pos] = $this->factory->newInteger($k1);
$pos++;
$npoints[$pos] = $beta;
$nscalars[$pos] = $this->factory->newInteger($k2);
}
return parent::multiplyAddPoints($npoints, $nscalars);
}
/**
* Returns the numerator and denominator of the slope
*
* @return FiniteField[]
*/
protected function doublePointHelper(array $p): array
{
$numerator = $this->three->multiply($p[0])->multiply($p[0]);
$denominator = $this->two->multiply($p[1]);
return [$numerator, $denominator];
}
/**
* Doubles a jacobian coordinate on the curve
*
* See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
*
* @return FiniteField[]
*/
protected function jacobianDoublePoint(array $p): array
{
[$x1, $y1, $z1] = $p;
$a = $x1->multiply($x1);
$b = $y1->multiply($y1);
$c = $b->multiply($b);
$d = $x1->add($b);
$d = $d->multiply($d)->subtract($a)->subtract($c)->multiply($this->two);
$e = $this->three->multiply($a);
$f = $e->multiply($e);
$x3 = $f->subtract($this->two->multiply($d));
$y3 = $e->multiply($d->subtract($x3))->subtract(
$this->eight->multiply($c)
);
$z3 = $this->two->multiply($y1)->multiply($z1);
return [$x3, $y3, $z3];
}
/**
* Doubles a "fresh" jacobian coordinate on the curve
*
* See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-mdbl-2007-bl
*
* @return FiniteField[]
*/
protected function jacobianDoublePointMixed(array $p): array
{
[$x1, $y1] = $p;
$xx = $x1->multiply($x1);
$yy = $y1->multiply($y1);
$yyyy = $yy->multiply($yy);
$s = $x1->add($yy);
$s = $s->multiply($s)->subtract($xx)->subtract($yyyy)->multiply($this->two);
$m = $this->three->multiply($xx);
$t = $m->multiply($m)->subtract($this->two->multiply($s));
$x3 = $t;
$y3 = $s->subtract($t);
$y3 = $m->multiply($y3)->subtract($this->eight->multiply($yyyy));
$z3 = $this->two->multiply($y1);
return [$x3, $y3, $z3];
}
/**
* Tests whether or not the x / y values satisfy the equation
*
* @return boolean
*/
public function verifyPoint(array $p): bool
{
[$x, $y] = $p;
$lhs = $y->multiply($y);
$temp = $x->multiply($x)->multiply($x);
$rhs = $temp->add($this->b);
return $lhs->equals($rhs);
}
/**
* Calculates the parameters needed from the Euclidean algorithm as discussed at
* http://diamond.boisestate.edu/~liljanab/MATH308/GuideToECC.pdf#page=148
*
* @return BigInteger[]
*/
protected static function extendedGCD(BigInteger $u, BigInteger $v): array
{
$one = new BigInteger(1);
$zero = new BigInteger();
$a = clone $one;
$b = clone $zero;
$c = clone $zero;
$d = clone $one;
$stop = $v->bitwise_rightShift($v->getLength() >> 1);
$a1 = clone $zero;
$b1 = clone $zero;
$a2 = clone $zero;
$b2 = clone $zero;
$postGreatestIndex = 0;
while (!$v->equals($zero)) {
[$q] = $u->divide($v);
$temp = $u;
$u = $v;
$v = $temp->subtract($v->multiply($q));
$temp = $a;
$a = $c;
$c = $temp->subtract($a->multiply($q));
$temp = $b;
$b = $d;
$d = $temp->subtract($b->multiply($q));
if ($v->compare($stop) > 0) {
$a0 = $v;
$b0 = $c;
} else {
$postGreatestIndex++;
}
if ($postGreatestIndex == 1) {
$a1 = $v;
$b1 = $c->negate();
}
if ($postGreatestIndex == 2) {
$rhs = $a0->multiply($a0)->add($b0->multiply($b0));
$lhs = $v->multiply($v)->add($b->multiply($b));
if ($lhs->compare($rhs) <= 0) {
$a2 = $a0;
$b2 = $b0->negate();
} else {
$a2 = $v;
$b2 = $c->negate();
}
break;
}
}
return [
['a' => $a1, 'b' => $b1],
['a' => $a2, 'b' => $b2],
];
}
}

View File

@@ -1,281 +0,0 @@
<?php
/**
* Curves over y^2 = x^3 + a*x + x
*
* Technically, a Montgomery curve has a coefficient for y^2 but for Curve25519 and Curve448 that
* coefficient is 1.
*
* Curve25519 and Curve448 do not make use of the y coordinate, which makes it unsuitable for use
* with ECDSA / EdDSA. A few other differences between Curve25519 and Ed25519 are discussed at
* https://crypto.stackexchange.com/a/43058/4520
*
* More info:
*
* https://en.wikipedia.org/wiki/Montgomery_curve
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Crypt\EC\Curves\Curve25519;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\PrimeField;
use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
/**
* Curves over y^2 = x^3 + a*x + x
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class Montgomery extends Base
{
/**
* Prime Field Integer factory
*
* @var PrimeField
*/
protected $factory;
/**
* Cofficient for x
*
* @var object
*/
protected $a;
/**
* Constant used for point doubling
*
* @var object
*/
protected $a24;
/**
* The Number Zero
*
* @var object
*/
protected $zero;
/**
* The Number One
*
* @var object
*/
protected $one;
/**
* Base Point
*
* @var object
*/
protected $p;
/**
* The modulo
*
* @var BigInteger
*/
protected $modulo;
/**
* The Order
*
* @var BigInteger
*/
protected $order;
/**
* Sets the modulo
*/
public function setModulo(BigInteger $modulo): void
{
$this->modulo = $modulo;
$this->factory = new PrimeField($modulo);
$this->zero = $this->factory->newInteger(new BigInteger());
$this->one = $this->factory->newInteger(new BigInteger(1));
}
/**
* Set coefficients a
*/
public function setCoefficients(BigInteger $a): void
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
$this->a = $this->factory->newInteger($a);
$two = $this->factory->newInteger(new BigInteger(2));
$four = $this->factory->newInteger(new BigInteger(4));
$this->a24 = $this->a->subtract($two)->divide($four);
}
/**
* Set x and y coordinates for the base point
*
* @param BigInteger|PrimeInteger $x
* @param BigInteger|PrimeInteger $y
* @return PrimeInteger[]
*/
public function setBasePoint($x, $y): array
{
switch (true) {
case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
throw new UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
throw new UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
}
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
$this->p = [
$x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
$y instanceof BigInteger ? $this->factory->newInteger($y) : $y,
];
}
/**
* Retrieve the base point as an array
*
* @return array
*/
public function getBasePoint()
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
/*
if (!isset($this->p)) {
throw new \phpseclib3\Exception\RuntimeException('setBasePoint needs to be called before this method');
}
*/
return $this->p;
}
/**
* Doubles and adds a point on a curve
*
* See https://tools.ietf.org/html/draft-ietf-tls-curve25519-01#appendix-A.1.3
*
* @return FiniteField[][]
*/
private function doubleAndAddPoint(array $p, array $q, PrimeInteger $x1): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
return [];
}
if (!isset($p[1])) {
throw new RuntimeException('Affine coordinates need to be manually converted to XZ coordinates');
}
[$x2, $z2] = $p;
[$x3, $z3] = $q;
$a = $x2->add($z2);
$aa = $a->multiply($a);
$b = $x2->subtract($z2);
$bb = $b->multiply($b);
$e = $aa->subtract($bb);
$c = $x3->add($z3);
$d = $x3->subtract($z3);
$da = $d->multiply($a);
$cb = $c->multiply($b);
$temp = $da->add($cb);
$x5 = $temp->multiply($temp);
$temp = $da->subtract($cb);
$z5 = $x1->multiply($temp->multiply($temp));
$x4 = $aa->multiply($bb);
$temp = static::class == Curve25519::class ? $bb : $aa;
$z4 = $e->multiply($temp->add($this->a24->multiply($e)));
return [
[$x4, $z4],
[$x5, $z5],
];
}
/**
* Multiply a point on the curve by a scalar
*
* Uses the montgomery ladder technique as described here:
*
* https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
* https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772
*/
public function multiplyPoint(array $p, BigInteger $d): array
{
$p1 = [$this->one, $this->zero];
$alreadyInternal = isset($x[1]);
$p2 = $this->convertToInternal($p);
$x = $p[0];
$b = $d->toBits();
$b = str_pad($b, 256, '0', STR_PAD_LEFT);
for ($i = 0; $i < strlen($b); $i++) {
$b_i = (int) $b[$i];
if ($b_i) {
[$p2, $p1] = $this->doubleAndAddPoint($p2, $p1, $x);
} else {
[$p1, $p2] = $this->doubleAndAddPoint($p1, $p2, $x);
}
}
return $alreadyInternal ? $p1 : $this->convertToAffine($p1);
}
/**
* Converts an affine point to an XZ coordinate
*
* From https://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html
*
* XZ coordinates represent x y as X Z satsfying the following equations:
*
* x=X/Z
*
* @return PrimeInteger[]
*/
public function convertToInternal(array $p): array
{
if (empty($p)) {
return [clone $this->zero, clone $this->one];
}
if (isset($p[1])) {
return $p;
}
$p[1] = clone $this->one;
return $p;
}
/**
* Returns the affine point
*
* @return PrimeInteger[]
*/
public function convertToAffine(array $p): array
{
if (!isset($p[1])) {
return $p;
}
[$x, $z] = $p;
return [$x->divide($z)];
}
}

View File

@@ -1,785 +0,0 @@
<?php
/**
* Curves over y^2 = x^3 + a*x + b
*
* These are curves used in SEC 2 over prime fields: http://www.secg.org/SEC2-Ver-1.0.pdf
* The curve is a weierstrass curve with a[1], a[3] and a[2] set to 0.
*
* Uses Jacobian Coordinates for speed if able:
*
* https://en.wikipedia.org/wiki/Jacobian_curve
* https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\Common\FiniteField\Integer;
use phpseclib3\Math\PrimeField;
use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
use phpseclib3\Math\PrimeFields;
/**
* Curves over y^2 = x^3 + a*x + b
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class Prime extends Base
{
/**
* Prime Field Integer factory
*
* @var PrimeFields
*/
protected $factory;
/**
* Cofficient for x^1
*
* @var object
*/
protected $a;
/**
* Cofficient for x^0
*
* @var object
*/
protected $b;
/**
* Base Point
*
* @var object
*/
protected $p;
/**
* The number one over the specified finite field
*
* @var object
*/
protected $one;
/**
* The number two over the specified finite field
*
* @var object
*/
protected $two;
/**
* The number three over the specified finite field
*
* @var object
*/
protected $three;
/**
* The number four over the specified finite field
*
* @var object
*/
protected $four;
/**
* The number eight over the specified finite field
*
* @var object
*/
protected $eight;
/**
* The modulo
*
* @var BigInteger
*/
protected $modulo;
/**
* The Order
*
* @var BigInteger
*/
protected $order;
/**
* Sets the modulo
*/
public function setModulo(BigInteger $modulo): void
{
$this->modulo = $modulo;
$this->factory = new PrimeField($modulo);
$this->two = $this->factory->newInteger(new BigInteger(2));
$this->three = $this->factory->newInteger(new BigInteger(3));
// used by jacobian coordinates
$this->one = $this->factory->newInteger(new BigInteger(1));
$this->four = $this->factory->newInteger(new BigInteger(4));
$this->eight = $this->factory->newInteger(new BigInteger(8));
}
/**
* Set coefficients a and b
*/
public function setCoefficients(BigInteger $a, BigInteger $b): void
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
$this->a = $this->factory->newInteger($a);
$this->b = $this->factory->newInteger($b);
}
/**
* Set x and y coordinates for the base point
*
* @param BigInteger|PrimeInteger $x
* @param BigInteger|PrimeInteger $y
*/
public function setBasePoint($x, $y): void
{
switch (true) {
case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
throw new UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
throw new UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
}
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
$this->p = [
$x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
$y instanceof BigInteger ? $this->factory->newInteger($y) : $y,
];
}
/**
* Retrieve the base point as an array
*
* @return array
*/
public function getBasePoint()
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
/*
if (!isset($this->p)) {
throw new \phpseclib3\Exception\RuntimeException('setBasePoint needs to be called before this method');
}
*/
return $this->p;
}
/**
* Adds two "fresh" jacobian form on the curve
*
* @return FiniteField[]
*/
protected function jacobianAddPointMixedXY(array $p, array $q): array
{
[$u1, $s1] = $p;
[$u2, $s2] = $q;
if ($u1->equals($u2)) {
if (!$s1->equals($s2)) {
return [];
} else {
return $this->doublePoint($p);
}
}
$h = $u2->subtract($u1);
$r = $s2->subtract($s1);
$h2 = $h->multiply($h);
$h3 = $h2->multiply($h);
$v = $u1->multiply($h2);
$x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
$y3 = $r->multiply(
$v->subtract($x3)
)->subtract(
$s1->multiply($h3)
);
return [$x3, $y3, $h];
}
/**
* Adds one "fresh" jacobian form on the curve
*
* The second parameter should be the "fresh" one
*
* @return FiniteField[]
*/
protected function jacobianAddPointMixedX(array $p, array $q): array
{
[$u1, $s1, $z1] = $p;
[$x2, $y2] = $q;
$z12 = $z1->multiply($z1);
$u2 = $x2->multiply($z12);
$s2 = $y2->multiply($z12->multiply($z1));
if ($u1->equals($u2)) {
if (!$s1->equals($s2)) {
return [];
} else {
return $this->doublePoint($p);
}
}
$h = $u2->subtract($u1);
$r = $s2->subtract($s1);
$h2 = $h->multiply($h);
$h3 = $h2->multiply($h);
$v = $u1->multiply($h2);
$x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
$y3 = $r->multiply(
$v->subtract($x3)
)->subtract(
$s1->multiply($h3)
);
$z3 = $h->multiply($z1);
return [$x3, $y3, $z3];
}
/**
* Adds two jacobian coordinates on the curve
*
* @return FiniteField[]
*/
protected function jacobianAddPoint(array $p, array $q): array
{
[$x1, $y1, $z1] = $p;
[$x2, $y2, $z2] = $q;
$z12 = $z1->multiply($z1);
$z22 = $z2->multiply($z2);
$u1 = $x1->multiply($z22);
$u2 = $x2->multiply($z12);
$s1 = $y1->multiply($z22->multiply($z2));
$s2 = $y2->multiply($z12->multiply($z1));
if ($u1->equals($u2)) {
if (!$s1->equals($s2)) {
return [];
} else {
return $this->doublePoint($p);
}
}
$h = $u2->subtract($u1);
$r = $s2->subtract($s1);
$h2 = $h->multiply($h);
$h3 = $h2->multiply($h);
$v = $u1->multiply($h2);
$x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
$y3 = $r->multiply(
$v->subtract($x3)
)->subtract(
$s1->multiply($h3)
);
$z3 = $h->multiply($z1)->multiply($z2);
return [$x3, $y3, $z3];
}
/**
* Adds two points on the curve
*
* @return FiniteField[]
*/
public function addPoint(array $p, array $q): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
if (count($q)) {
return $q;
}
if (count($p)) {
return $p;
}
return [];
}
// use jacobian coordinates
if (isset($p[2]) && isset($q[2])) {
if (isset($p['fresh']) && isset($q['fresh'])) {
return $this->jacobianAddPointMixedXY($p, $q);
}
if (isset($p['fresh'])) {
return $this->jacobianAddPointMixedX($q, $p);
}
if (isset($q['fresh'])) {
return $this->jacobianAddPointMixedX($p, $q);
}
return $this->jacobianAddPoint($p, $q);
}
if (isset($p[2]) || isset($q[2])) {
throw new RuntimeException('Affine coordinates need to be manually converted to Jacobi coordinates or vice versa');
}
if ($p[0]->equals($q[0])) {
if (!$p[1]->equals($q[1])) {
return [];
} else { // eg. doublePoint
[$numerator, $denominator] = $this->doublePointHelper($p);
}
} else {
$numerator = $q[1]->subtract($p[1]);
$denominator = $q[0]->subtract($p[0]);
}
$slope = $numerator->divide($denominator);
$x = $slope->multiply($slope)->subtract($p[0])->subtract($q[0]);
$y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]);
return [$x, $y];
}
/**
* Returns the numerator and denominator of the slope
*
* @return FiniteField[]
*/
protected function doublePointHelper(array $p): array
{
$numerator = $this->three->multiply($p[0])->multiply($p[0])->add($this->a);
$denominator = $this->two->multiply($p[1]);
return [$numerator, $denominator];
}
/**
* Doubles a jacobian coordinate on the curve
*
* @return FiniteField[]
*/
protected function jacobianDoublePoint(array $p): array
{
[$x, $y, $z] = $p;
$x2 = $x->multiply($x);
$y2 = $y->multiply($y);
$z2 = $z->multiply($z);
$s = $this->four->multiply($x)->multiply($y2);
$m1 = $this->three->multiply($x2);
$m2 = $this->a->multiply($z2->multiply($z2));
$m = $m1->add($m2);
$x1 = $m->multiply($m)->subtract($this->two->multiply($s));
$y1 = $m->multiply($s->subtract($x1))->subtract(
$this->eight->multiply($y2->multiply($y2))
);
$z1 = $this->two->multiply($y)->multiply($z);
return [$x1, $y1, $z1];
}
/**
* Doubles a "fresh" jacobian coordinate on the curve
*
* @return FiniteField[]
*/
protected function jacobianDoublePointMixed(array $p): array
{
[$x, $y] = $p;
$x2 = $x->multiply($x);
$y2 = $y->multiply($y);
$s = $this->four->multiply($x)->multiply($y2);
$m1 = $this->three->multiply($x2);
$m = $m1->add($this->a);
$x1 = $m->multiply($m)->subtract($this->two->multiply($s));
$y1 = $m->multiply($s->subtract($x1))->subtract(
$this->eight->multiply($y2->multiply($y2))
);
$z1 = $this->two->multiply($y);
return [$x1, $y1, $z1];
}
/**
* Doubles a point on a curve
*
* @return FiniteField[]
*/
public function doublePoint(array $p): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p)) {
return [];
}
// use jacobian coordinates
if (isset($p[2])) {
if (isset($p['fresh'])) {
return $this->jacobianDoublePointMixed($p);
}
return $this->jacobianDoublePoint($p);
}
[$numerator, $denominator] = $this->doublePointHelper($p);
$slope = $numerator->divide($denominator);
$x = $slope->multiply($slope)->subtract($p[0])->subtract($p[0]);
$y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]);
return [$x, $y];
}
/**
* Returns the X coordinate and the derived Y coordinate
*/
public function derivePoint($m): array
{
$y = ord(Strings::shift($m));
$x = new BigInteger($m, 256);
$xp = $this->convertInteger($x);
switch ($y) {
case 2:
$ypn = false;
break;
case 3:
$ypn = true;
break;
default:
throw new RuntimeException('Coordinate not in recognized format');
}
$temp = $xp->multiply($this->a);
$temp = $xp->multiply($xp)->multiply($xp)->add($temp);
$temp = $temp->add($this->b);
$b = $temp->squareRoot();
if (!$b) {
throw new RuntimeException('Unable to derive Y coordinate');
}
$bn = $b->isOdd();
$yp = $ypn == $bn ? $b : $b->negate();
return [$xp, $yp];
}
/**
* Tests whether or not the x / y values satisfy the equation
*
* @return boolean
*/
public function verifyPoint(array $p): bool
{
[$x, $y] = $p;
$lhs = $y->multiply($y);
$temp = $x->multiply($this->a);
$temp = $x->multiply($x)->multiply($x)->add($temp);
$rhs = $temp->add($this->b);
return $lhs->equals($rhs);
}
/**
* Returns the modulo
*/
public function getModulo(): BigInteger
{
return $this->modulo;
}
/**
* Returns the a coefficient
*
* @return PrimeInteger
*/
public function getA()
{
return $this->a;
}
/**
* Returns the a coefficient
*
* @return PrimeInteger
*/
public function getB()
{
return $this->b;
}
/**
* Multiply and Add Points
*
* Adapted from:
* https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L125
*
* @return int[]
*/
public function multiplyAddPoints(array $points, array $scalars): array
{
$length = count($points);
foreach ($points as &$point) {
$point = $this->convertToInternal($point);
}
$wnd = [$this->getNAFPoints($points[0], 7)];
$wndWidth = [$points[0]['nafwidth'] ?? 7];
for ($i = 1; $i < $length; $i++) {
$wnd[] = $this->getNAFPoints($points[$i], 1);
$wndWidth[] = $points[$i]['nafwidth'] ?? 1;
}
$naf = [];
// comb all window NAFs
$max = 0;
for ($i = $length - 1; $i >= 1; $i -= 2) {
$a = $i - 1;
$b = $i;
if ($wndWidth[$a] != 1 || $wndWidth[$b] != 1) {
$naf[$a] = $scalars[$a]->getNAF($wndWidth[$a]);
$naf[$b] = $scalars[$b]->getNAF($wndWidth[$b]);
$max = max(count($naf[$a]), count($naf[$b]), $max);
continue;
}
$comb = [
$points[$a], // 1
null, // 3
null, // 5
$points[$b], // 7
];
$comb[1] = $this->addPoint($points[$a], $points[$b]);
$comb[2] = $this->addPoint($points[$a], $this->negatePoint($points[$b]));
$index = [
-3, /* -1 -1 */
-1, /* -1 0 */
-5, /* -1 1 */
-7, /* 0 -1 */
0, /* 0 -1 */
7, /* 0 1 */
5, /* 1 -1 */
1, /* 1 0 */
3, /* 1 1 */
];
$jsf = self::getJSFPoints($scalars[$a], $scalars[$b]);
$max = max(count($jsf[0]), $max);
if ($max > 0) {
$naf[$a] = array_fill(0, $max, 0);
$naf[$b] = array_fill(0, $max, 0);
} else {
$naf[$a] = [];
$naf[$b] = [];
}
for ($j = 0; $j < $max; $j++) {
$ja = $jsf[0][$j] ?? 0;
$jb = $jsf[1][$j] ?? 0;
$naf[$a][$j] = $index[3 * ($ja + 1) + $jb + 1];
$naf[$b][$j] = 0;
$wnd[$a] = $comb;
}
}
$acc = [];
$temp = [0, 0, 0, 0];
for ($i = $max; $i >= 0; $i--) {
$k = 0;
while ($i >= 0) {
$zero = true;
for ($j = 0; $j < $length; $j++) {
$temp[$j] = $naf[$j][$i] ?? 0;
if ($temp[$j] != 0) {
$zero = false;
}
}
if (!$zero) {
break;
}
$k++;
$i--;
}
if ($i >= 0) {
$k++;
}
while ($k--) {
$acc = $this->doublePoint($acc);
}
if ($i < 0) {
break;
}
for ($j = 0; $j < $length; $j++) {
$z = $temp[$j];
$p = null;
if ($z == 0) {
continue;
}
$p = $z > 0 ?
$wnd[$j][($z - 1) >> 1] :
$this->negatePoint($wnd[$j][(-$z - 1) >> 1]);
$acc = $this->addPoint($acc, $p);
}
}
return $this->convertToAffine($acc);
}
/**
* Precomputes NAF points
*
* Adapted from:
* https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L351
*
* @return list<array>
*/
private function getNAFPoints(array $point, int $wnd): array
{
if (isset($point['naf'])) {
return $point['naf'];
}
$res = [$point];
$max = (1 << $wnd) - 1;
$dbl = $max == 1 ? null : $this->doublePoint($point);
for ($i = 1; $i < $max; $i++) {
$res[] = $this->addPoint($res[$i - 1], $dbl);
}
$point['naf'] = $res;
/*
$str = '';
foreach ($res as $re) {
$re[0] = bin2hex($re[0]->toBytes());
$re[1] = bin2hex($re[1]->toBytes());
$str.= " ['$re[0]', '$re[1]'],\r\n";
}
file_put_contents('temp.txt', $str);
exit;
*/
return $res;
}
/**
* Precomputes points in Joint Sparse Form
*
* Adapted from:
* https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/utils.js#L96
*
* @return int[]
*/
private static function getJSFPoints(Integer $k1, Integer $k2): array
{
static $three;
if (!isset($three)) {
$three = new BigInteger(3);
}
$jsf = [[], []];
$k1 = $k1->toBigInteger();
$k2 = $k2->toBigInteger();
$d1 = 0;
$d2 = 0;
while ($k1->compare(new BigInteger(-$d1)) > 0 || $k2->compare(new BigInteger(-$d2)) > 0) {
// first phase
$m14 = $k1->testBit(0) + 2 * $k1->testBit(1);
$m14 += $d1;
$m14 &= 3;
$m24 = $k2->testBit(0) + 2 * $k2->testBit(1);
$m24 += $d2;
$m24 &= 3;
if ($m14 == 3) {
$m14 = -1;
}
if ($m24 == 3) {
$m24 = -1;
}
$u1 = 0;
if ($m14 & 1) { // if $m14 is odd
$m8 = $k1->testBit(0) + 2 * $k1->testBit(1) + 4 * $k1->testBit(2);
$m8 += $d1;
$m8 &= 7;
$u1 = ($m8 == 3 || $m8 == 5) && $m24 == 2 ? -$m14 : $m14;
}
$jsf[0][] = $u1;
$u2 = 0;
if ($m24 & 1) { // if $m24 is odd
$m8 = $k2->testBit(0) + 2 * $k2->testBit(1) + 4 * $k2->testBit(2);
$m8 += $d2;
$m8 &= 7;
$u2 = ($m8 == 3 || $m8 == 5) && $m14 == 2 ? -$m24 : $m24;
}
$jsf[1][] = $u2;
// second phase
if (2 * $d1 == $u1 + 1) {
$d1 = 1 - $d1;
}
if (2 * $d2 == $u2 + 1) {
$d2 = 1 - $d2;
}
$k1 = $k1->bitwise_rightShift(1);
$k2 = $k2->bitwise_rightShift(1);
}
return $jsf;
}
/**
* Returns the affine point
*
* A Jacobian Coordinate is of the form (x, y, z).
* To convert a Jacobian Coordinate to an Affine Point
* you do (x / z^2, y / z^3)
*
* @return PrimeInteger[]
*/
public function convertToAffine(array $p): array
{
if (!isset($p[2])) {
return $p;
}
[$x, $y, $z] = $p;
$z = $this->one->divide($z);
$z2 = $z->multiply($z);
return [
$x->multiply($z2),
$y->multiply($z2)->multiply($z),
];
}
/**
* Converts an affine point to a jacobian coordinate
*
* @return PrimeInteger[]
*/
public function convertToInternal(array $p): array
{
if (isset($p[2])) {
return $p;
}
$p[2] = clone $this->one;
$p['fresh'] = true;
return $p;
}
}

View File

@@ -1,215 +0,0 @@
<?php
/**
* Curves over a*x^2 + y^2 = 1 + d*x^2*y^2
*
* http://www.secg.org/SEC2-Ver-1.0.pdf provides for curves with custom parameters.
* ie. the coefficients can be arbitrary set through specially formatted keys, etc.
* As such, Prime.php is built very generically and it's not able to take full
* advantage of curves with 0 coefficients to produce simplified point doubling,
* point addition. Twisted Edwards curves, in contrast, do not have a way, currently,
* to customize them. As such, we can omit the super generic stuff from this class
* and let the named curves (Ed25519 and Ed448) define their own custom tailored
* point addition and point doubling methods.
*
* More info:
*
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\BaseCurves;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Exception\UnexpectedValueException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\PrimeField;
use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
/**
* Curves over a*x^2 + y^2 = 1 + d*x^2*y^2
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class TwistedEdwards extends Base
{
/**
* The modulo
*
* @var BigInteger
*/
protected $modulo;
/**
* Cofficient for x^2
*
* @var object
*/
protected $a;
/**
* Cofficient for x^2*y^2
*
* @var object
*/
protected $d;
/**
* Base Point
*
* @var object[]
*/
protected $p;
/**
* The number zero over the specified finite field
*
* @var object
*/
protected $zero;
/**
* The number one over the specified finite field
*
* @var object
*/
protected $one;
/**
* The number two over the specified finite field
*
* @var object
*/
protected $two;
/**
* Sets the modulo
*/
public function setModulo(BigInteger $modulo): void
{
$this->modulo = $modulo;
$this->factory = new PrimeField($modulo);
$this->zero = $this->factory->newInteger(new BigInteger(0));
$this->one = $this->factory->newInteger(new BigInteger(1));
$this->two = $this->factory->newInteger(new BigInteger(2));
}
/**
* Set coefficients a and b
*/
public function setCoefficients(BigInteger $a, BigInteger $d): void
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
$this->a = $this->factory->newInteger($a);
$this->d = $this->factory->newInteger($d);
}
/**
* Set x and y coordinates for the base point
*/
public function setBasePoint($x, $y): void
{
switch (true) {
case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
throw new UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
throw new UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
}
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
$this->p = [
$x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
$y instanceof BigInteger ? $this->factory->newInteger($y) : $y,
];
}
/**
* Returns the a coefficient
*
* @return PrimeInteger
*/
public function getA()
{
return $this->a;
}
/**
* Returns the a coefficient
*
* @return PrimeInteger
*/
public function getD()
{
return $this->d;
}
/**
* Retrieve the base point as an array
*/
public function getBasePoint(): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
/*
if (!isset($this->p)) {
throw new \phpseclib3\Exception\RuntimeException('setBasePoint needs to be called before this method');
}
*/
return $this->p;
}
/**
* Returns the affine point
*
* @return PrimeInteger[]
*/
public function convertToAffine(array $p): array
{
if (!isset($p[2])) {
return $p;
}
[$x, $y, $z] = $p;
$z = $this->one->divide($z);
return [
$x->multiply($z),
$y->multiply($z),
];
}
/**
* Returns the modulo
*/
public function getModulo(): BigInteger
{
return $this->modulo;
}
/**
* Tests whether or not the x / y values satisfy the equation
*
* @return boolean
*/
public function verifyPoint(array $p): bool
{
[$x, $y] = $p;
$x2 = $x->multiply($x);
$y2 = $y->multiply($y);
$lhs = $this->a->multiply($x2)->add($y2);
$rhs = $this->d->multiply($x2)->multiply($y2)->add($this->one);
return $lhs->equals($rhs);
}
}

View File

@@ -1,80 +0,0 @@
<?php
/**
* Curve25519
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
use phpseclib3\Exception\RangeException;
use phpseclib3\Math\BigInteger;
class Curve25519 extends Montgomery
{
public function __construct()
{
// 2^255 - 19
$this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16));
$this->a24 = $this->factory->newInteger(new BigInteger('121666'));
$this->p = [$this->factory->newInteger(new BigInteger(9))];
// 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed
$this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16));
/*
$this->setCoefficients(
new BigInteger('486662'), // a
);
$this->setBasePoint(
new BigInteger(9),
new BigInteger('14781619447589544791020593568409986887264606134616475288964881837755586237401')
);
*/
}
/**
* Multiply a point on the curve by a scalar
*
* Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8
*/
public function multiplyPoint(array $p, BigInteger $d): array
{
//$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes())));
//return [$this->factory->newInteger(new BigInteger($r, 256))];
$d = $d->toBytes();
$d &= "\xF8" . str_repeat("\xFF", 30) . "\x7F";
$d = strrev($d);
$d |= "\x40";
$d = new BigInteger($d, -256);
return parent::multiplyPoint($p, $d);
}
/**
* Creates a random scalar multiplier
*/
public function createRandomMultiplier(): BigInteger
{
return BigInteger::random(256);
}
/**
* Performs range check
*/
public function rangeCheck(BigInteger $x): void
{
if ($x->getLength() > 256 || $x->isNegative()) {
throw new RangeException('x must be a positive integer less than 256 bytes in length');
}
}
}

View File

@@ -1,91 +0,0 @@
<?php
/**
* Curve448
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
use phpseclib3\Exception\RangeException;
use phpseclib3\Math\BigInteger;
class Curve448 extends Montgomery
{
public function __construct()
{
// 2^448 - 2^224 - 1
$this->setModulo(new BigInteger(
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
16
));
$this->a24 = $this->factory->newInteger(new BigInteger('39081'));
$this->p = [$this->factory->newInteger(new BigInteger(5))];
// 2^446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
$this->setOrder(new BigInteger(
'3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
'7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3',
16
));
/*
$this->setCoefficients(
new BigInteger('156326'), // a
);
$this->setBasePoint(
new BigInteger(5),
new BigInteger(
'355293926785568175264127502063783334808976399387714271831880898' .
'435169088786967410002932673765864550910142774147268105838985595290' .
'606362')
);
*/
}
/**
* Multiply a point on the curve by a scalar
*
* Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8
*/
public function multiplyPoint(array $p, BigInteger $d): array
{
//$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes())));
//return [$this->factory->newInteger(new BigInteger($r, 256))];
$d = $d->toBytes();
$d[0] = $d[0] & "\xFC";
$d = strrev($d);
$d |= "\x80";
$d = new BigInteger($d, 256);
return parent::multiplyPoint($p, $d);
}
/**
* Creates a random scalar multiplier
*/
public function createRandomMultiplier(): BigInteger
{
return BigInteger::random(446);
}
/**
* Performs range check
*/
public function rangeCheck(BigInteger $x): void
{
if ($x->getLength() > 448 || $x->isNegative()) {
throw new RangeException('x must be a positive integer less than 446 bytes in length');
}
}
}

View File

@@ -1,330 +0,0 @@
<?php
/**
* Ed25519
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\Random;
use phpseclib3\Exception\LengthException;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Math\BigInteger;
class Ed25519 extends TwistedEdwards
{
public const HASH = 'sha512';
/*
Per https://tools.ietf.org/html/rfc8032#page-6 EdDSA has several parameters, one of which is b:
2. An integer b with 2^(b-1) > p. EdDSA public keys have exactly b
bits, and EdDSA signatures have exactly 2*b bits. b is
recommended to be a multiple of 8, so public key and signature
lengths are an integral number of octets.
SIZE corresponds to b
*/
public const SIZE = 32;
public function __construct()
{
// 2^255 - 19
$this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16));
$this->setCoefficients(
// -1
new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC', 16), // a
// -121665/121666
new BigInteger('52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3', 16) // d
);
$this->setBasePoint(
new BigInteger('216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A', 16),
new BigInteger('6666666666666666666666666666666666666666666666666666666666666658', 16)
);
$this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16));
// algorithm 14.47 from http://cacr.uwaterloo.ca/hac/about/chap14.pdf#page=16
/*
$this->setReduction(function($x) {
$parts = $x->bitwise_split(255);
$className = $this->className;
if (count($parts) > 2) {
list(, $r) = $x->divide($className::$modulo);
return $r;
}
$zero = new BigInteger();
$c = new BigInteger(19);
switch (count($parts)) {
case 2:
list($qi, $ri) = $parts;
break;
case 1:
$qi = $zero;
list($ri) = $parts;
break;
case 0:
return $zero;
}
$r = $ri;
while ($qi->compare($zero) > 0) {
$temp = $qi->multiply($c)->bitwise_split(255);
if (count($temp) == 2) {
list($qi, $ri) = $temp;
} else {
$qi = $zero;
list($ri) = $temp;
}
$r = $r->add($ri);
}
while ($r->compare($className::$modulo) > 0) {
$r = $r->subtract($className::$modulo);
}
return $r;
});
*/
}
/**
* Recover X from Y
*
* Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.1.3
*
* Used by EC\Keys\Common.php
*
* @param boolean $sign
* @return object[]
*/
public function recoverX(BigInteger $y, bool $sign): array
{
$y = $this->factory->newInteger($y);
$y2 = $y->multiply($y);
$u = $y2->subtract($this->one);
$v = $this->d->multiply($y2)->add($this->one);
$x2 = $u->divide($v);
if ($x2->equals($this->zero)) {
if ($sign) {
throw new RuntimeException('Unable to recover X coordinate (x2 = 0)');
}
return clone $this->zero;
}
// find the square root
/* we don't do $x2->squareRoot() because, quoting from
https://tools.ietf.org/html/rfc8032#section-5.1.1:
"For point decoding or "decompression", square roots modulo p are
needed. They can be computed using the Tonelli-Shanks algorithm or
the special case for p = 5 (mod 8). To find a square root of a,
first compute the candidate root x = a^((p+3)/8) (mod p)."
*/
$exp = $this->getModulo()->add(new BigInteger(3));
$exp = $exp->bitwise_rightShift(3);
$x = $x2->pow($exp);
// If v x^2 = -u (mod p), set x <-- x * 2^((p-1)/4), which is a square root.
if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
$temp = $this->getModulo()->subtract(new BigInteger(1));
$temp = $temp->bitwise_rightShift(2);
$temp = $this->two->pow($temp);
$x = $x->multiply($temp);
if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
throw new RuntimeException('Unable to recover X coordinate');
}
}
if ($x->isOdd() != $sign) {
$x = $x->negate();
}
return [$x, $y];
}
/**
* Extract Secret Scalar
*
* Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.1.5
*
* Used by the various key handlers
*
* @return array
*/
public function extractSecret(string $str)
{
if (strlen($str) != 32) {
throw new LengthException('Private Key should be 32-bytes long');
}
// 1. Hash the 32-byte private key using SHA-512, storing the digest in
// a 64-octet large buffer, denoted h. Only the lower 32 bytes are
// used for generating the public key.
$hash = new Hash('sha512');
$h = $hash->hash($str);
$h = substr($h, 0, 32);
// 2. Prune the buffer: The lowest three bits of the first octet are
// cleared, the highest bit of the last octet is cleared, and the
// second highest bit of the last octet is set.
$h[0] = $h[0] & chr(0xF8);
$h = strrev($h);
$h[0] = ($h[0] & chr(0x3F)) | chr(0x40);
// 3. Interpret the buffer as the little-endian integer, forming a
// secret scalar s.
$dA = new BigInteger($h, 256);
return [
'dA' => $dA,
'secret' => $str,
];
}
/**
* Encode a point as a string
*/
public function encodePoint(array $point): string
{
[$x, $y] = $point;
$y = $y->toBytes();
$y[0] = $y[0] & chr(0x7F);
if ($x->isOdd()) {
$y[0] = $y[0] | chr(0x80);
}
$y = strrev($y);
return $y;
}
/**
* Creates a random scalar multiplier
*/
public function createRandomMultiplier(): BigInteger
{
return $this->extractSecret(Random::string(32))['dA'];
}
/**
* Converts an affine point to an extended homogeneous coordinate
*
* From https://tools.ietf.org/html/rfc8032#section-5.1.4 :
*
* A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T),
* with x = X/Z, y = Y/Z, x * y = T/Z.
*
* @return Integer[]
*/
public function convertToInternal(array $p): array
{
if (empty($p)) {
return [clone $this->zero, clone $this->one, clone $this->one, clone $this->zero];
}
if (isset($p[2])) {
return $p;
}
$p[2] = clone $this->one;
$p[3] = $p[0]->multiply($p[1]);
return $p;
}
/**
* Doubles a point on a curve
*
* @return FiniteField[]
*/
public function doublePoint(array $p): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p)) {
return [];
}
if (!isset($p[2])) {
throw new RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
// from https://tools.ietf.org/html/rfc8032#page-12
[$x1, $y1, $z1, $t1] = $p;
$a = $x1->multiply($x1);
$b = $y1->multiply($y1);
$c = $this->two->multiply($z1)->multiply($z1);
$h = $a->add($b);
$temp = $x1->add($y1);
$e = $h->subtract($temp->multiply($temp));
$g = $a->subtract($b);
$f = $c->add($g);
$x3 = $e->multiply($f);
$y3 = $g->multiply($h);
$t3 = $e->multiply($h);
$z3 = $f->multiply($g);
return [$x3, $y3, $z3, $t3];
}
/**
* Adds two points on the curve
*
* @return FiniteField[]
*/
public function addPoint(array $p, array $q): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
if (count($q)) {
return $q;
}
if (count($p)) {
return $p;
}
return [];
}
if (!isset($p[2]) || !isset($q[2])) {
throw new RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
if ($p[0]->equals($q[0])) {
return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
}
// from https://tools.ietf.org/html/rfc8032#page-12
[$x1, $y1, $z1, $t1] = $p;
[$x2, $y2, $z2, $t2] = $q;
$a = $y1->subtract($x1)->multiply($y2->subtract($x2));
$b = $y1->add($x1)->multiply($y2->add($x2));
$c = $t1->multiply($this->two)->multiply($this->d)->multiply($t2);
$d = $z1->multiply($this->two)->multiply($z2);
$e = $b->subtract($a);
$f = $d->subtract($c);
$g = $d->add($c);
$h = $b->add($a);
$x3 = $e->multiply($f);
$y3 = $g->multiply($h);
$t3 = $e->multiply($h);
$z3 = $f->multiply($g);
return [$x3, $y3, $z3, $t3];
}
}

View File

@@ -1,268 +0,0 @@
<?php
/**
* Ed448
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\Random;
use phpseclib3\Exception\LengthException;
use phpseclib3\Exception\RuntimeException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\PrimeField\Integer;
class Ed448 extends TwistedEdwards
{
public const HASH = 'shake256-912';
public const SIZE = 57;
public function __construct()
{
// 2^448 - 2^224 - 1
$this->setModulo(new BigInteger(
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
16
));
$this->setCoefficients(
new BigInteger(1),
// -39081
new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6756', 16)
);
$this->setBasePoint(
new BigInteger('4F1970C66BED0DED221D15A622BF36DA9E146570470F1767EA6DE324' .
'A3D3A46412AE1AF72AB66511433B80E18B00938E2626A82BC70CC05E', 16),
new BigInteger('693F46716EB6BC248876203756C9C7624BEA73736CA3984087789C1E' .
'05A0C2D73AD3FF1CE67C39C4FDBD132C4ED7C8AD9808795BF230FA14', 16)
);
$this->setOrder(new BigInteger(
'3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
'7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3',
16
));
}
/**
* Recover X from Y
*
* Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.2.3
*
* Used by EC\Keys\Common.php
*
* @param boolean $sign
* @return object[]
*/
public function recoverX(BigInteger $y, bool $sign): array
{
$y = $this->factory->newInteger($y);
$y2 = $y->multiply($y);
$u = $y2->subtract($this->one);
$v = $this->d->multiply($y2)->subtract($this->one);
$x2 = $u->divide($v);
if ($x2->equals($this->zero)) {
if ($sign) {
throw new RuntimeException('Unable to recover X coordinate (x2 = 0)');
}
return clone $this->zero;
}
// find the square root
$exp = $this->getModulo()->add(new BigInteger(1));
$exp = $exp->bitwise_rightShift(2);
$x = $x2->pow($exp);
if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
throw new RuntimeException('Unable to recover X coordinate');
}
if ($x->isOdd() != $sign) {
$x = $x->negate();
}
return [$x, $y];
}
/**
* Extract Secret Scalar
*
* Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.2.5
*
* Used by the various key handlers
*
* @return array
*/
public function extractSecret(string $str)
{
if (strlen($str) != 57) {
throw new LengthException('Private Key should be 57-bytes long');
}
// 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the
// digest in a 114-octet large buffer, denoted h. Only the lower 57
// bytes are used for generating the public key.
$hash = new Hash('shake256-912');
$h = $hash->hash($str);
$h = substr($h, 0, 57);
// 2. Prune the buffer: The two least significant bits of the first
// octet are cleared, all eight bits the last octet are cleared, and
// the highest bit of the second to last octet is set.
$h[0] = $h[0] & chr(0xFC);
$h = strrev($h);
$h[0] = "\0";
$h[1] = $h[1] | chr(0x80);
// 3. Interpret the buffer as the little-endian integer, forming a
// secret scalar s.
$dA = new BigInteger($h, 256);
return [
'dA' => $dA,
'secret' => $str,
];
}
/**
* Encode a point as a string
*/
public function encodePoint(array $point): string
{
[$x, $y] = $point;
$y = "\0" . $y->toBytes();
if ($x->isOdd()) {
$y[0] = $y[0] | chr(0x80);
}
$y = strrev($y);
return $y;
}
/**
* Creates a random scalar multiplier
*/
public function createRandomMultiplier(): BigInteger
{
return $this->extractSecret(Random::string(57))['dA'];
}
/**
* Converts an affine point to an extended homogeneous coordinate
*
* From https://tools.ietf.org/html/rfc8032#section-5.2.4 :
*
* A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T),
* with x = X/Z, y = Y/Z, x * y = T/Z.
*
* @return Integer[]
*/
public function convertToInternal(array $p): array
{
if (empty($p)) {
return [clone $this->zero, clone $this->one, clone $this->one];
}
if (isset($p[2])) {
return $p;
}
$p[2] = clone $this->one;
return $p;
}
/**
* Doubles a point on a curve
*
* @return FiniteField[]
*/
public function doublePoint(array $p): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p)) {
return [];
}
if (!isset($p[2])) {
throw new RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
// from https://tools.ietf.org/html/rfc8032#page-18
[$x1, $y1, $z1] = $p;
$b = $x1->add($y1);
$b = $b->multiply($b);
$c = $x1->multiply($x1);
$d = $y1->multiply($y1);
$e = $c->add($d);
$h = $z1->multiply($z1);
$j = $e->subtract($this->two->multiply($h));
$x3 = $b->subtract($e)->multiply($j);
$y3 = $c->subtract($d)->multiply($e);
$z3 = $e->multiply($j);
return [$x3, $y3, $z3];
}
/**
* Adds two points on the curve
*
* @return FiniteField[]
*/
public function addPoint(array $p, array $q): array
{
if (!isset($this->factory)) {
throw new RuntimeException('setModulo needs to be called before this method');
}
if (!count($p) || !count($q)) {
if (count($q)) {
return $q;
}
if (count($p)) {
return $p;
}
return [];
}
if (!isset($p[2]) || !isset($q[2])) {
throw new RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
}
if ($p[0]->equals($q[0])) {
return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
}
// from https://tools.ietf.org/html/rfc8032#page-17
[$x1, $y1, $z1] = $p;
[$x2, $y2, $z2] = $q;
$a = $z1->multiply($z2);
$b = $a->multiply($a);
$c = $x1->multiply($x2);
$d = $y1->multiply($y2);
$e = $this->d->multiply($c)->multiply($d);
$f = $b->subtract($e);
$g = $b->add($e);
$h = $x1->add($y1)->multiply($x2->add($y2));
$x3 = $a->multiply($f)->multiply($h->subtract($c)->subtract($d));
$y3 = $a->multiply($g)->multiply($d->subtract($c));
$z3 = $f->multiply($g);
return [$x3, $y3, $z3];
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* brainpoolP160r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP160r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16));
$this->setCoefficients(
new BigInteger('340E7BE2A280EB74E2BE61BADA745D97E8F7C300', 16),
new BigInteger('1E589A8595423412134FAA2DBDEC95C8D8675E58', 16)
);
$this->setBasePoint(
new BigInteger('BED5AF16EA3F6A4F62938C4631EB5AF7BDBCDBC3', 16),
new BigInteger('1667CB477A1A8EC338F94741669C976316DA6321', 16)
);
$this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16));
}
}

View File

@@ -1,49 +0,0 @@
<?php
/**
* brainpoolP160t1
*
* This curve is a twisted version of brainpoolP160r1 with A = -3. With brainpool,
* the curves ending in r1 are the "regular" curves and the curves ending in "t1"
* are the twisted version of the r1 curves. Per https://tools.ietf.org/html/rfc5639#page-7
* you can convert a point on an r1 curve to a point on a t1 curve thusly:
*
* F(x,y) := (x*Z^2, y*Z^3)
*
* The advantage of A = -3 is that some of the point doubling and point addition can be
* slightly optimized. See http://hyperelliptic.org/EFD/g1p/auto-shortw-projective-3.html
* vs http://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html for example.
*
* phpseclib does not currently take advantage of this optimization opportunity
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP160t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16));
$this->setCoefficients(
new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620C', 16), // eg. -3
new BigInteger('7A556B6DAE535B7B51ED2C4D7DAA7A0B5C55F380', 16)
);
$this->setBasePoint(
new BigInteger('B199B13B9B34EFC1397E64BAEB05ACC265FF2378', 16),
new BigInteger('ADD6718B7C7C1961F0991B842443772152C9E0AD', 16)
);
$this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16));
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* brainpoolP192r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP192r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16));
$this->setCoefficients(
new BigInteger('6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF', 16),
new BigInteger('469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9', 16)
);
$this->setBasePoint(
new BigInteger('C0A0647EAAB6A48753B033C56CB0F0900A2F5C4853375FD6', 16),
new BigInteger('14B690866ABD5BB88B5F4828C1490002E6773FA2FA299B8F', 16)
);
$this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16));
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* brainpoolP192t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP192t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16));
$this->setCoefficients(
new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86294', 16), // eg. -3
new BigInteger('13D56FFAEC78681E68F9DEB43B35BEC2FB68542E27897B79', 16)
);
$this->setBasePoint(
new BigInteger('3AE9E58C82F63C30282E1FE7BBF43FA72C446AF6F4618129', 16),
new BigInteger('097E2C5667C2223A902AB5CA449D0084B7E5B3DE7CCC01C9', 16)
);
$this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16));
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* brainpoolP224r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP224r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16));
$this->setCoefficients(
new BigInteger('68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43', 16),
new BigInteger('2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B', 16)
);
$this->setBasePoint(
new BigInteger('0D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D', 16),
new BigInteger('58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD', 16)
);
$this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16));
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* brainpoolP224t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP224t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16));
$this->setCoefficients(
new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FC', 16), // eg. -3
new BigInteger('4B337D934104CD7BEF271BF60CED1ED20DA14C08B3BB64F18A60888D', 16)
);
$this->setBasePoint(
new BigInteger('6AB1E344CE25FF3896424E7FFE14762ECB49F8928AC0C76029B4D580', 16),
new BigInteger('0374E9F5143E568CD23F3F4D7C0D4B1E41C8CC0D1C6ABD5F1A46DB4C', 16)
);
$this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16));
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* brainpoolP256r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP256r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16));
$this->setCoefficients(
new BigInteger('7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9', 16),
new BigInteger('26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6', 16)
);
$this->setBasePoint(
new BigInteger('8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262', 16),
new BigInteger('547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997', 16)
);
$this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16));
}
}

View File

@@ -1,36 +0,0 @@
<?php
/**
* brainpoolP256t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP256t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16));
$this->setCoefficients(
new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5374', 16), // eg. -3
new BigInteger('662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04', 16)
);
$this->setBasePoint(
new BigInteger('A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F4', 16),
new BigInteger('2D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE', 16)
);
$this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16));
}
}

View File

@@ -1,42 +0,0 @@
<?php
/**
* brainpoolP320r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP320r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F9' .
'2B9EC7893EC28FCD412B1F1B32E27', 16));
$this->setCoefficients(
new BigInteger('3EE30B568FBAB0F883CCEBD46D3F3BB8A2A73513F5EB79DA66190EB085FFA9F4' .
'92F375A97D860EB4', 16),
new BigInteger('520883949DFDBC42D3AD198640688A6FE13F41349554B49ACC31DCCD88453981' .
'6F5EB4AC8FB1F1A6', 16)
);
$this->setBasePoint(
new BigInteger('43BD7E9AFB53D8B85289BCC48EE5BFE6F20137D10A087EB6E7871E2A10A599C7' .
'10AF8D0D39E20611', 16),
new BigInteger('14FDD05545EC1CC8AB4093247F77275E0743FFED117182EAA9C77877AAAC6AC7' .
'D35245D1692E8EE1', 16)
);
$this->setOrder(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D4' .
'82EC7EE8658E98691555B44C59311', 16));
}
}

View File

@@ -1,42 +0,0 @@
<?php
/**
* brainpoolP320t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP320t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F9' .
'2B9EC7893EC28FCD412B1F1B32E27', 16));
$this->setCoefficients(
new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28' .
'FCD412B1F1B32E24', 16), // eg. -3
new BigInteger('A7F561E038EB1ED560B3D147DB782013064C19F27ED27C6780AAF77FB8A547CE' .
'B5B4FEF422340353', 16)
);
$this->setBasePoint(
new BigInteger('925BE9FB01AFC6FB4D3E7D4990010F813408AB106C4F09CB7EE07868CC136FFF' .
'3357F624A21BED52', 16),
new BigInteger('63BA3A7A27483EBF6671DBEF7ABB30EBEE084E58A0B077AD42A5A0989D1EE71B' .
'1B9BC0455FB0D2C3', 16)
);
$this->setOrder(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D4' .
'82EC7EE8658E98691555B44C59311', 16));
}
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* brainpoolP384r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP384r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger(
'8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A7' .
'1874700133107EC53',
16
));
$this->setCoefficients(
new BigInteger(
'7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503' .
'AD4EB04A8C7DD22CE2826',
16
),
new BigInteger(
'4A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DB' .
'C9943AB78696FA504C11',
16
)
);
$this->setBasePoint(
new BigInteger(
'1D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D' .
'646AAEF87B2E247D4AF1E',
16
),
new BigInteger(
'8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E464621779' .
'1811142820341263C5315',
16
)
);
$this->setOrder(new BigInteger(
'8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC31' .
'03B883202E9046565',
16
));
}
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* brainpoolP384t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP384t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger(
'8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A7' .
'1874700133107EC53',
16
));
$this->setCoefficients(
new BigInteger(
'8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901' .
'D1A71874700133107EC50',
16
), // eg. -3
new BigInteger(
'7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE1D2074AA263B8' .
'8805CED70355A33B471EE',
16
)
);
$this->setBasePoint(
new BigInteger(
'18DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AABFFC4FF191B946' .
'A5F54D8D0AA2F418808CC',
16
),
new BigInteger(
'25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CCFE469408584DC' .
'2B2912675BF5B9E582928',
16
)
);
$this->setOrder(new BigInteger(
'8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC31' .
'03B883202E9046565',
16
));
}
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* brainpoolP512r1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP512r1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger(
'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
'66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3',
16
));
$this->setCoefficients(
new BigInteger(
'7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA82' .
'53AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA',
16
),
new BigInteger(
'3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C' .
'1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723',
16
)
);
$this->setBasePoint(
new BigInteger(
'81AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D' .
'0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F822',
16
),
new BigInteger(
'7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5' .
'F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892',
16
)
);
$this->setOrder(new BigInteger(
'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA' .
'92619418661197FAC10471DB1D381085DDADDB58796829CA90069',
16
));
}
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* brainpoolP512t1
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
use phpseclib3\Crypt\EC\BaseCurves\Prime;
use phpseclib3\Math\BigInteger;
class brainpoolP512t1 extends Prime
{
public function __construct()
{
$this->setModulo(new BigInteger(
'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
'66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3',
16
));
$this->setCoefficients(
new BigInteger(
'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
'66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F0',
16
), // eg. -3
new BigInteger(
'7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36A62BCDFA23049' .
'76540F6450085F2DAE145C22553B465763689180EA2571867423E',
16
)
);
$this->setBasePoint(
new BigInteger(
'640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C0313D82BA51735CD' .
'B3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA',
16
),
new BigInteger(
'5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CEE9D9932184BEE' .
'F216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332',
16
)
);
$this->setOrder(new BigInteger(
'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA' .
'92619418661197FAC10471DB1D381085DDADDB58796829CA90069',
16
));
}
}

View File

@@ -1,20 +0,0 @@
<?php
/**
* nistb233
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
final class nistb233 extends sect233r1
{
}

View File

@@ -1,20 +0,0 @@
<?php
/**
* nistb409
*
* PHP version 5 and 7
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
declare(strict_types=1);
namespace phpseclib3\Crypt\EC\Curves;
final class nistb409 extends sect409r1
{
}

Some files were not shown because too many files have changed in this diff Show More