Compare commits
1 Commits
fce31ebcd7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec97e1097c |
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
this.captureEditsIntoTemplate();
|
if (hadTemplate) {
|
||||||
|
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();
|
||||||
this.captureEditsIntoTemplate();
|
if (hadTemplate) {
|
||||||
|
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, " ");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'], '/');
|
||||||
$path = ($template_path !== '' ? $template_path . '/' : '') . $filename;
|
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;
|
||||||
|
}
|
||||||
$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, [
|
private function store_secure_pdf_file(string $source_file, string $filename, bool $uploaded)
|
||||||
'test_form' => false,
|
{
|
||||||
'mimes' => ['pdf' => 'application/pdf'],
|
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]);
|
||||||
|
|
||||||
if (!empty($upload['error'])) {
|
|
||||||
return new WP_Error('qa_pdf_upload_failed', (string) $upload['error'], ['status' => 500]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$title = 'QA Report';
|
$target = $this->secure_pdf_target($filename);
|
||||||
if (!empty($run['module'])) {
|
if (is_wp_error($target)) {
|
||||||
$title .= ' - ' . $this->plain($run['module']);
|
return $target;
|
||||||
}
|
}
|
||||||
|
|
||||||
$attachment_id = wp_insert_attachment(
|
if ($uploaded) {
|
||||||
[
|
$stored = move_uploaded_file($source_file, $target['path']);
|
||||||
'post_mime_type' => 'application/pdf',
|
} else {
|
||||||
'post_title' => $title,
|
$stored = copy($source_file, $target['path']);
|
||||||
'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 (!$stored) {
|
||||||
if (is_array($metadata)) {
|
return new WP_Error('qa_pdf_store_failed', 'PDF could not be stored securely.', ['status' => 500]);
|
||||||
wp_update_attachment_metadata($attachment_id, $metadata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 = [
|
||||||
|
|||||||
@@ -1,460 +0,0 @@
|
|||||||
# o‑Byte QA – Testprotokoll‑Tool (Full README)
|
|
||||||
|
|
||||||
**Stand:** 2025-09-04
|
|
||||||
**Stack:** PHP 8.3 ( Apache ), JS (Vanilla), MariaDB, Docker & Compose, Portainer
|
|
||||||
|
|
||||||
Dieses Repository enthält ein vollwertiges QA‑Tool mit Login (OIDC), GitLab‑Template‑Loader, DocBee‑Posting, PDF‑Erzeugung im Browser und persistenter Speicherung (DB + PDF‑Ablage). Diese README basiert ausschließlich auf dem mitgelieferten Projektstand.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Inhaltsverzeichnis
|
|
||||||
|
|
||||||
1. [Überblick](#überblick)
|
|
||||||
2. [Architektur & Verzeichnisstruktur](#architektur--verzeichnisstruktur)
|
|
||||||
3. [Funktionen](#funktionen)
|
|
||||||
4. [UI‑Bedienung (Kurzüberblick)](#ui-bedienung-kurzüberblick)
|
|
||||||
5. [Datenmodell (DB)](#datenmodell-db)
|
|
||||||
6. [API‑Endpunkte](#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. [GitLab‑Vorlagen](#gitlab-vorlagen)
|
|
||||||
12. [DocBee‑Integration](#docbee-integration)
|
|
||||||
13. [PDF‑Export](#pdf-export)
|
|
||||||
14. [Troubleshooting](#troubleshooting)
|
|
||||||
15. [Sicherheitshinweise](#sicherheitshinweise)
|
|
||||||
16. [Lizenz / Nutzung](#lizenz--nutzung)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Überblick
|
|
||||||
|
|
||||||
- **Ziel:** Manuelle QA‑Testläufe effizient vorbereiten, durchführen und revisionssicher exportieren.
|
|
||||||
- **Workflow:** Vorlage laden → Metadaten eintragen (inkl. **OLM‑Nummer**) → 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 (YAML‑Vorlagen), DocBee (Login + Notiz‑Erzeugung 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 # Health‑Check (DB/PDF‑Pfad)
|
|
||||||
├─ config/config.php # ENV‑Konfiguration (OIDC, DocBee, DB, APP_BASE_URL, …)
|
|
||||||
├─ login.php # OIDC‑Start
|
|
||||||
├─ callback.php # OIDC‑Callback, Session‑Bootstrap
|
|
||||||
├─ 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 Drag‑Handle verschiebbar (innerhalb und zwischen Gruppen).
|
|
||||||
- **Automatische Neu‑Nummerierung** 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**, **Modul‑Version**, **PBX‑Version**, **Tester** (aus Session), **DocBee‑Ticket‑URL**, **OLM‑Nummer** (`olm_nummer`).
|
|
||||||
|
|
||||||
### Exporte
|
|
||||||
- **Exportieren (DocBee, DB, PDF)** per **einem** Button:
|
|
||||||
1. DocBee‑Notiz erstellen (falls Token vorhanden) → Ticket‑URL ggf. aktualisiert.
|
|
||||||
2. **PDF** im Browser generieren (jsPDF+AutoTable).
|
|
||||||
3. **/api/export.php**: JSON (Run) + PDF (multipart) an Server → DB‑Speicherung + PDF‑Ablage.
|
|
||||||
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/MD‑Funktionen existieren noch im Code, UI‑seitig aber nicht exponiert.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UI‑Bedienung (Kurzüberblick)
|
|
||||||
|
|
||||||
- **GitLab Vorlage**: Dropdown befüllen (`GitLab: …` Tag zeigt Status), Datei wählen → Vorlage wird geladen.
|
|
||||||
- **Metadaten**: Felder ausfüllen (**OLM‑Nummer** nicht vergessen).
|
|
||||||
- **Steps**: `+ Step` / `+ Gruppe`, per Griff `⋮⋮` ziehen, Status setzen, Kommentare/Evidenzen pflegen.
|
|
||||||
- **Exportieren (DocBee, DB, PDF)**: Startet kompletten Lauf‑Export. Ergebnisdialog zeigt Ticket/PDF‑Pfad/Report‑ID.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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 **schema‑loader** Service in `docker-compose.yml` erstellt die Tabellen automatisch beim ersten Start.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API‑Endpunkte
|
|
||||||
|
|
||||||
- `GET /api/health.php`
|
|
||||||
- Prüft PDO / MariaDB‑Erreichbarkeit, Schreibrechte des PDF‑Ordners.
|
|
||||||
- `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 PDF‑Datei (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 Portainer‑UI/Secrets setzen.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Lokale Entwicklung
|
|
||||||
|
|
||||||
1. **Composer‑Abhängigkeiten** sind bereits im Repo (`vendor/`). Falls nötig:
|
|
||||||
```bash
|
|
||||||
cd htdocs && composer install
|
|
||||||
```
|
|
||||||
2. **PHP‑Built‑in** (nur für schnellen Test, ohne DB/PDF‑Ablage):
|
|
||||||
```bash
|
|
||||||
php -S 127.0.0.1:8009 -t htdocs
|
|
||||||
```
|
|
||||||
3. **Login‑Bypass** 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), bind‑mountet `htdocs` und nutzt `PDF_STORAGE_DIR` Volume
|
|
||||||
- **mariadb** (11.x)
|
|
||||||
- **schema-loader** (initialisiert Tabellen)
|
|
||||||
- **phpmyadmin** (optional)
|
|
||||||
- **fix-pdf-perms** (setzt 0777 auf PDF‑Volume)
|
|
||||||
|
|
||||||
**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}`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GitLab‑Vorlagen
|
|
||||||
|
|
||||||
- Konfiguration via ENV (`GITLAB_HOST`, `GITLAB_PROJECT_ID`, `GITLAB_REF`, `GITLAB_PATH`, `GITLAB_TOKEN`).
|
|
||||||
- UI lädt per GitLab API die Liste der YAML‑Dateien aus `GITLAB_PATH` (nur `.yaml`/`.yml`).
|
|
||||||
- Parser akzeptiert **YAML** und **JSON** (Fallback).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## DocBee‑Integration
|
|
||||||
|
|
||||||
- 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 Ticket‑URL`).
|
|
||||||
2. Bei Erfolg wird die URL im Lauf aktualisiert.
|
|
||||||
3. Danach erfolgt Server‑Export (DB + optional PDF).
|
|
||||||
|
|
||||||
**Erforderliche ENV:** `DOCBEE_BASEURL`, `DOCBEE_USER`, `DOCBEE_PASS`, `DOCBEE_TIME` (Minuten).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PDF‑Export
|
|
||||||
|
|
||||||
- Erzeugung **im Browser** via **jsPDF** + **AutoTable** (CDN‑Fallbacks vorhanden).
|
|
||||||
- **Logo** `logo_light.png` wird proportional (max 26×12 mm) **rechtsbündig** im Kopf platziert.
|
|
||||||
- Fallback: Falls Libraries fehlen, wird ein Text‑PDF erzeugt.
|
|
||||||
- Server speichert PDFs in `PDF_STORAGE_DIR` (Default `/var/reports`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## YAML‑Vorlage (Beispiel inkl. OLM)
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: "Modul XYZ – Basis‑Tests"
|
|
||||||
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
|
|
||||||
|
|
||||||
- **Login‑Loop / 401:** OIDC‑Konfiguration prüfen (`OIDC_PROVIDER`, Redirect‑URL `/callback.php`). Lokal ggf. `AUTH_DISABLED=true`.
|
|
||||||
- **DocBee 401/403:** `DOCBEE_USER/DOCBEE_PASS/DOCBEE_BASEURL/DOCBEE_TIME` prüfen.
|
|
||||||
- **DB‑Fehler (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.
|
|
||||||
- **GitLab‑Liste 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.
|
|
||||||
- DocBee‑Token wird **clientseitig** verwendet – nur in internen Netzen nutzen oder alternative Server‑Proxy‑Variante vorsehen.
|
|
||||||
- Secrets per Portainer‑Secrets/ENV setzen, nicht im Repo.
|
|
||||||
- DB‑Backups/Retention für `reports`/`steps` einplanen; PDF‑Ablage regelmäßig bereinigen/archivieren.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Lizenz / Nutzung
|
|
||||||
|
|
||||||
Interne Nutzung innerhalb o‑Byte / Kundenprojekten. Anpassungen willkommen.
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
@@ -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:
|
|
||||||
@@ -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.
|
|
||||||
@@ -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 ?? [])
|
|
||||||
]);
|
|
||||||
@@ -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()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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
@@ -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();
|
|
||||||
?>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"require": {
|
|
||||||
"litesaml/lightsaml": "^4.2",
|
|
||||||
"jumbojett/openid-connect-php": "^1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
681
qa-tool/htdocs/composer.lock
generated
681
qa-tool/htdocs/composer.lock
generated
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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 |
@@ -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();
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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 =====================
|
|
||||||
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
||||||
// ===== 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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'));
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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>`;
|
|
||||||
}
|
|
||||||
@@ -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('"', '"');
|
|
||||||
|
|
||||||
|
|
||||||
const escHTML = (s) => String(s ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
||||||
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
@@ -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 |
@@ -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();
|
|
||||||
?>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -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]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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-----";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -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
Reference in New Issue
Block a user