diff options
| author | adamjanovsky | 2024-02-20 10:49:57 +0100 |
|---|---|---|
| committer | GitHub | 2024-02-20 10:49:57 +0100 |
| commit | 78d2cfd8711b56c4d06b7c3bc24cf94b3743cd91 (patch) | |
| tree | 17284715b74fb082ae81b5848e0d7e8e97a2c361 | |
| parent | 609ee028fad536c7a7e3d83b004b0617ebaaf460 (diff) | |
| parent | cdb54b71d53e8378457822b4a95f1b3fa78b1add (diff) | |
| download | sec-certs-78d2cfd8711b56c4d06b7c3bc24cf94b3743cd91.tar.gz sec-certs-78d2cfd8711b56c4d06b7c3bc24cf94b3743cd91.tar.zst sec-certs-78d2cfd8711b56c4d06b7c3bc24cf94b3743cd91.zip | |
Merge pull request #392 from crocs-muni/reference-notebook
Reference notebook
| -rw-r--r-- | docs/api/model.md | 8 | ||||
| -rw-r--r-- | docs/configuration.md | 7 | ||||
| -rw-r--r-- | notebooks/cc/paper2_plots.ipynb | 403 | ||||
| -rw-r--r-- | notebooks/cc/reference_annotations/prediction.ipynb | 53 | ||||
| -rw-r--r-- | notebooks/cc/references.ipynb | 1301 | ||||
| -rw-r--r-- | src/sec_certs/data/reference_annotations/readme.md | 2 |
6 files changed, 1579 insertions, 195 deletions
diff --git a/docs/api/model.md b/docs/api/model.md index 329e92cf..ba8ca387 100644 --- a/docs/api/model.md +++ b/docs/api/model.md @@ -9,12 +9,6 @@ The examples related to this package can be found at [model notebook](./../notebooks/examples/model.ipynb). ``` -```{warning} -Transforming `CPE` records to existing vulnerabilities is handled by [Dataset](https://github.com/crocs-muni/sec-certs/blob/main/sec_certs/dataset/dataset.py) class, `compute_related_cves()` method. - -However, come CVEs are missed due to omitted vulnerable configurations in [CVEDataset](https://github.com/crocs-muni/sec-certs/blob/main/sec_certs/dataset/cve.py) class. We omit configurations that comprise of two components joined with `AND` operator. For closer description, see [issue #252](https://github.com/crocs-muni/sec-certs/issues/252) at GitHub. -``` - ## CPEClassifier ```{eval-rst} @@ -45,4 +39,4 @@ However, come CVEs are missed due to omitted vulnerable configurations in [CVEDa .. currentmodule:: sec_certs.model .. autoclass:: TransitiveVulnerabilityFinder :members: -```
\ No newline at end of file +``` diff --git a/docs/configuration.md b/docs/configuration.md index dcf2b8d8..898bb0e9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -44,11 +44,12 @@ import typing type_hints = typing.get_type_hints(Configuration) text = "" -for field, value in config.__fields__.items(): +for field, value in config.model_fields.items(): text += f"`{field}`\n\n" text += f"- type: `{type_hints[field]}`\n" text += f"- default: `{value.default}`\n" - text += f"- description: {value.field_info.description}\n" - text += f"- env name: `{list(value.field_info.extra['env_names'])[0]}`\n\n" + text += f"- description: {value.description}\n" + env_var = "SECCERTS_" + field.upper() + text += f"- env name: `{env_var}`\n\n" glue("text", Markdown(text)) ``` diff --git a/notebooks/cc/paper2_plots.ipynb b/notebooks/cc/paper2_plots.ipynb new file mode 100644 index 00000000..d6b17e09 --- /dev/null +++ b/notebooks/cc/paper2_plots.ipynb @@ -0,0 +1,403 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "from pathlib import Path\n", + "\n", + "import matplotlib\n", + "import matplotlib.dates as mdates\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import seaborn as sns\n", + "from matplotlib import lines\n", + "from notebooks.fixed_sankey_plot import sankey\n", + "from sklearn import metrics\n", + "\n", + "# Surpress user warnings\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "\n", + "%matplotlib inline\n", + "\n", + "# LaTeX plotting\n", + "# matplotlib.use(\"pgf\")\n", + "# plt.rcParams[\"pgf.texsystem\"] = \"pdflatex\"\n", + "# plt.rcParams[\"font.family\"] = \"serif\"\n", + "# plt.rcParams[\"text.usetex\"] = True\n", + "# plt.rcParams[\"pgf.rcfonts\"] = False\n", + "\n", + "\n", + "sns.set_theme(style=\"white\")\n", + "plt.rcParams[\"axes.linewidth\"] = 0.5\n", + "plt.rcParams[\"legend.fontsize\"] = 6.5\n", + "plt.rcParams[\"xtick.labelsize\"] = 8\n", + "plt.rcParams[\"ytick.labelsize\"] = 8\n", + "plt.rcParams[\"ytick.left\"] = True\n", + "plt.rcParams[\"ytick.major.size\"] = 5\n", + "plt.rcParams[\"ytick.major.width\"] = 0.5\n", + "plt.rcParams[\"ytick.major.pad\"] = 0\n", + "plt.rcParams[\"xtick.bottom\"] = True\n", + "plt.rcParams[\"xtick.major.size\"] = 5\n", + "plt.rcParams[\"xtick.major.width\"] = 0.5\n", + "plt.rcParams[\"xtick.major.pad\"] = 0\n", + "\n", + "plt.rcParams[\"axes.titlesize\"] = 8\n", + "plt.rcParams[\"legend.handletextpad\"] = 0.3\n", + "plt.rcParams[\"lines.markersize\"] = 0.5\n", + "plt.rcParams[\"savefig.pad_inches\"] = 0.01\n", + "sns.set_palette(\"deep\")\n", + "\n", + "plt.style.use(\"default\")\n", + "sns.set_palette(\"Set2\")\n", + "sns.set_context(\"paper\") # Set to \"paper\" for use in paper :)\n", + "\n", + "# plt.rcParams['figure.figsize'] = (10, 6)\n", + "\n", + "REPO_ROOT = Path().resolve()\n", + "RESULTS_DIR = Path(\"./results/references\")\n", + "RESULTS_DIR.mkdir(exist_ok=True, parents=True)\n", + "\n", + "DATASET_PATH = REPO_ROOT / \"dataset/cc_november_23/dataset.json\"\n", + "PREDICTIONS_PATH = REPO_ROOT / \"dataset/reference_prediction/predictions.csv\"\n", + "\n", + "SMARTCARD_CATEGORY = \"ICs, Smart Cards and Smart Card-Related Devices and Systems\"\n", + "CARD_RELATED_CAT = {\"Other Devices and Systems\", \"Products for Digital Signatures\", \"Trusted Computing\"}\n", + "OTHERS_CAR = {\n", + " \"Access Control Devices and Systems\",\n", + " \"Biometric Systems and Devices\",\n", + " \"Boundary Protection Devices and Systems\",\n", + " \"Data Protection\",\n", + " \"Databases\",\n", + " \"Detection Devices and Systems\",\n", + " \"Key Management Systems\",\n", + " \"Mobility\",\n", + " \"Multi-Function Devices\",\n", + " \"Network and Network-Related Devices and Systems\",\n", + " \"Operating Systems\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Top-reach certificates in time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.rcParams[\"legend.fontsize\"] = 9\n", + "plt.rcParams[\"xtick.labelsize\"] = 12\n", + "plt.rcParams[\"ytick.labelsize\"] = 12\n", + "\n", + "df_to_plot = pd.read_csv(RESULTS_DIR / \"average_reach_over_time.csv\", parse_dates=[\"date\"])\n", + "\n", + "plt.figure()\n", + "g = sns.lineplot(data=df_to_plot, x=\"date\", y=\"reach\", hue=\"certificate\", errorbar=None)\n", + "\n", + "plt.legend(frameon=False, handlelength=2)\n", + "g.set_xlabel(\"\")\n", + "g.set_ylabel(\"Certificate reach\", fontsize=12)\n", + "\n", + "\n", + "dtFmt = mdates.DateFormatter(\"%Y\") # define the formatting\n", + "g.xaxis.set_major_formatter(dtFmt)\n", + "g.set_xticks(\n", + " [\n", + " pd.to_datetime(\"1998-01-01\"),\n", + " pd.to_datetime(\"2003-01-01\"),\n", + " pd.to_datetime(\"2008-01-01\"),\n", + " pd.to_datetime(\"2013-01-01\"),\n", + " pd.to_datetime(\"2018-01-01\"),\n", + " pd.to_datetime(\"2023-01-01\"),\n", + " ]\n", + ")\n", + "g.figure.set_size_inches(3.9, 3)\n", + "plt.tight_layout(pad=0.1)\n", + "g.figure.savefig(RESULTS_DIR / \"lineplot_top_reach.pdf\")\n", + "g.figure.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Average number of transitive references over time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.rcParams[\"legend.fontsize\"] = 9\n", + "plt.rcParams[\"xtick.labelsize\"] = 12\n", + "plt.rcParams[\"ytick.labelsize\"] = 12\n", + "sns.set_palette(\"Set2\")\n", + "df_to_plot = pd.read_csv(RESULTS_DIR / \"avg_refs_over_time.csv\", parse_dates=[\"date\"])\n", + "df_to_plot[\"category\"] = df_to_plot[\"category\"].map(lambda x: \"others\" if x == \"others categories\" else x)\n", + "plt.figure()\n", + "g = sns.lineplot(data=df_to_plot, x=\"date\", y=\"n_references\", hue=\"category\", errorbar=None)\n", + "\n", + "plt.legend(frameon=True, handlelength=2, title=\"Product category\")\n", + "g.set_xlabel(\"\")\n", + "g.set_ylabel(\"Avg. # transitive refs.\", fontsize=12)\n", + "\n", + "dtFmt = mdates.DateFormatter(\"%Y\") # define the formatting\n", + "g.xaxis.set_major_formatter(dtFmt)\n", + "g.set_xticks(\n", + " [\n", + " pd.to_datetime(\"1998-01-01\"),\n", + " pd.to_datetime(\"2003-01-01\"),\n", + " pd.to_datetime(\"2008-01-01\"),\n", + " pd.to_datetime(\"2013-01-01\"),\n", + " pd.to_datetime(\"2018-01-01\"),\n", + " pd.to_datetime(\"2023-01-01\"),\n", + " ]\n", + ")\n", + "g.figure.set_size_inches(3.9, 3)\n", + "plt.tight_layout(pad=0.1)\n", + "g.figure.savefig(\n", + " RESULTS_DIR / \"lineplot_avg_refs.pdf\",\n", + ")\n", + "g.figure.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Average reach over time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.rcParams[\"legend.fontsize\"] = 9\n", + "plt.rcParams[\"xtick.labelsize\"] = 12\n", + "plt.rcParams[\"ytick.labelsize\"] = 12\n", + "sns.set_palette(\"Set2\")\n", + "df_to_plot = pd.read_csv(RESULTS_DIR / \"avg_reach_over_time.csv\", parse_dates=[\"date\"])\n", + "plt.figure()\n", + "g = sns.lineplot(data=df_to_plot, x=\"date\", y=\"n_references\", hue=\"category\", errorbar=None)\n", + "\n", + "plt.legend(frameon=True, handlelength=2, title=\"Product category\")\n", + "g.set_xlabel(\"\")\n", + "g.set_ylabel(\"Average certificate reach\", fontsize=12)\n", + "\n", + "dtFmt = mdates.DateFormatter(\"%Y\") # define the formatting\n", + "g.xaxis.set_major_formatter(dtFmt)\n", + "g.set_xticks(\n", + " [\n", + " pd.to_datetime(\"1998-01-01\"),\n", + " pd.to_datetime(\"2003-01-01\"),\n", + " pd.to_datetime(\"2008-01-01\"),\n", + " pd.to_datetime(\"2013-01-01\"),\n", + " pd.to_datetime(\"2018-01-01\"),\n", + " pd.to_datetime(\"2023-01-01\"),\n", + " ]\n", + ")\n", + "g.figure.set_size_inches(3.9, 3)\n", + "plt.tight_layout(pad=0.1)\n", + "g.figure.savefig(RESULTS_DIR / \"lineplot_avg_reach.pdf\")\n", + "g.figure.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Area under curve" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure(figsize=(2.8, 1.8))\n", + "sns.set_palette(\"Set2\")\n", + "colors = plt.cm.Dark2(np.linspace(0, 1, 8))\n", + "\n", + "df_sent = pd.read_csv(RESULTS_DIR / \"df_pred_sentence_transformers.csv\")\n", + "df_tf_idf = pd.read_csv(RESULTS_DIR / \"df_pred_tf_idf.csv\")\n", + "df_baseline = pd.read_csv(RESULTS_DIR / \"df_pred_baseline.csv\")\n", + "\n", + "fpr, tpr, thresholds = metrics.roc_curve(df_sent.y_true, df_sent.y_pred)\n", + "auc = metrics.roc_auc_score(df_sent.y_true, df_sent.y_pred)\n", + "plt.plot(fpr, tpr, label=f\"Sent. trans. (AUC={auc:.2f})\", color=colors[0])\n", + "\n", + "fpr, tpr, thresholds = metrics.roc_curve(df_tf_idf.y_true, df_tf_idf.y_pred)\n", + "auc = metrics.roc_auc_score(df_tf_idf.y_true, df_tf_idf.y_pred)\n", + "plt.plot(fpr, tpr, label=f\"TF-IDF (AUC={auc:.2f})\", color=colors[1])\n", + "\n", + "fpr, tpr, thresholds = metrics.roc_curve(df_baseline.y_true, df_baseline.y_pred)\n", + "auc = metrics.roc_auc_score(df_baseline.y_true, df_baseline.y_pred)\n", + "plt.plot(fpr, tpr, label=f\"Random guess (AUC={auc:.2f})\", color=colors[2])\n", + "\n", + "plt.legend(loc=\"lower right\")\n", + "plt.tight_layout(pad=0.1)\n", + "plt.savefig(RESULTS_DIR / \"roc_auc.pdf\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stack-bar plot of annotations in categories" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv(RESULTS_DIR / \"ref_categories_stackplot.csv\")\n", + "\n", + "ax = df.plot.barh(stacked=True, rot=0, width=0.95)\n", + "ax.set_ylim(-0.6, 2.6)\n", + "ax.set_xlabel(\"# references\", fontsize=10)\n", + "ax.set_yticklabels([\"Others\", \"Smartcard-related\", \"Smartcards\"], fontsize=10)\n", + "ax.legend(title=\"Reference context\", loc=\"lower right\", frameon=True)\n", + "\n", + "plt.text(0.4, 0.8, df.iloc[2][\"Component reuse\"], transform=ax.transAxes, color=\"white\", fontsize=10)\n", + "plt.text(0.83, 0.8, df.iloc[2][\"Predecessor\"], transform=ax.transAxes, color=\"white\", fontsize=10)\n", + "\n", + "plt.axhline(y=1.21, xmin=0.05, xmax=0.18, color=\"black\", linewidth=0.75)\n", + "plt.axhline(y=0.9, xmin=0.12, xmax=0.18, color=\"black\", linewidth=0.75)\n", + "plt.text(0.2, 0.55, df.iloc[1][\"Component reuse\"], transform=ax.transAxes, color=\"black\", fontsize=10)\n", + "plt.text(0.2, 0.45, df.iloc[1][\"Predecessor\"], transform=ax.transAxes, color=\"black\", fontsize=10)\n", + "\n", + "plt.axhline(y=0.17, xmin=0.02, xmax=0.1, color=\"black\", linewidth=0.75)\n", + "plt.axhline(y=-0.1, xmin=0.05, xmax=0.1, color=\"black\", linewidth=0.75)\n", + "plt.text(0.12, 0.22, df.iloc[0][\"Component reuse\"], transform=ax.transAxes, color=\"black\", fontsize=10)\n", + "plt.text(0.12, 0.13, df.iloc[0][\"Predecessor\"], transform=ax.transAxes, color=\"black\", fontsize=10)\n", + "\n", + "ax.figure.set_size_inches(4, 3)\n", + "plt.tight_layout(pad=0.1)\n", + "plt.savefig(RESULTS_DIR / \"stacked_barplot.pdf\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Archived certificate half-life" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure()\n", + "\n", + "df = pd.read_csv(RESULTS_DIR / \"archived_half_life.csv\")\n", + "g = sns.ecdfplot(data=df.n_days, complementary=True)\n", + "\n", + "plt.axvline(x=365, color=\"r\", linestyle=\"--\", linewidth=0.75)\n", + "vertical_line = lines.Line2D(\n", + " [], [], color=\"r\", marker=\"\", linestyle=\"--\", markersize=10, markeredgewidth=1.5, label=\"One year\"\n", + ")\n", + "plt.legend(handles=[vertical_line])\n", + "\n", + "g.figure.set_size_inches(3, 2)\n", + "g.set_xlim(0, 2000)\n", + "\n", + "g.set_xlabel(\"Number of days\")\n", + "g.set_ylabel(\"Proportion\")\n", + "\n", + "g.yaxis.set_major_formatter(matplotlib.ticker.PercentFormatter(xmax=1))\n", + "g.set_yticks([0, 0.25, 0.5, 0.75, 1])\n", + "\n", + "plt.tight_layout(pad=0.05)\n", + "g.figure.savefig(RESULTS_DIR / \"cdf_half_life.pdf\")\n", + "g.figure.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Age of referenced certificate in composite-evaluation products" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure()\n", + "\n", + "df = pd.read_csv(RESULTS_DIR / \"ecdf_archival_data.csv\")\n", + "df = df.loc[df.scheme.isin({\"FR\", \"DE\", \"NL\"})]\n", + "\n", + "g = sns.ecdfplot(data=df, x=\"date_diff\", hue=\"scheme\", legend=True)\n", + "plt.axvline(x=540, color=\"r\", linestyle=\"--\", linewidth=0.75)\n", + "\n", + "vertical_line = lines.Line2D([], [], color=\"r\", linestyle=\"--\", markersize=10, label=\"18 months\")\n", + "unique_hues = df[\"scheme\"].unique()\n", + "handles = [\n", + " plt.Line2D([], [], color=g.lines[color_idx].get_color(), label=label) for color_idx, label in enumerate(unique_hues)\n", + "]\n", + "\n", + "handles.append(vertical_line)\n", + "labels = list(unique_hues) + [\"18 months\"]\n", + "\n", + "g.legend(handles=handles, labels=labels)\n", + "\n", + "# Your code to finalize and save the plot\n", + "g.figure.set_size_inches(3, 2)\n", + "\n", + "# plt.tight_layout(pad=1.17)\n", + "g.yaxis.set_major_formatter(matplotlib.ticker.PercentFormatter(xmax=1))\n", + "g.set_yticks([0, 0.25, 0.5, 0.75, 1])\n", + "g.set_xlim(0, 2000)\n", + "g.set_xlabel(\"Number of days\")\n", + "g.set_ylabel(\"Proportion\")\n", + "plt.tight_layout(pad=0.05)\n", + "g.figure.savefig(RESULTS_DIR / \"ref_comp_age.pdf\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/cc/reference_annotations/prediction.ipynb b/notebooks/cc/reference_annotations/prediction.ipynb index 4eec5da8..8aae82f0 100644 --- a/notebooks/cc/reference_annotations/prediction.ipynb +++ b/notebooks/cc/reference_annotations/prediction.ipynb @@ -42,7 +42,7 @@ "logging.getLogger(\"sentence_transformers\").setLevel(logging.CRITICAL)\n", "file_handler = logging.StreamHandler(sys.stderr)\n", "file_handler.setFormatter(logging.Formatter(\"%(asctime)s - %(name)s - %(levelname)s - %(message)s\"))\n", - "logging.basicConfig(level=logging.INFO, handlers=[file_handler])\n" + "logging.basicConfig(level=logging.INFO, handlers=[file_handler])" ] }, { @@ -51,11 +51,11 @@ "metadata": {}, "outputs": [], "source": [ - "mode = \"production\"\n", + "mode = \"evaluation\"\n", "cc_dset = CCDataset.from_json(DATASET_PATH)\n", "\n", - "# df = extract_segments(cc_dset, mode=mode)\n", - "# df.to_csv(REPO_ROOT / \"dataset/reference_prediction/dataset.csv\", index=False)\n", + "df = extract_segments(cc_dset, mode=mode)\n", + "df.to_csv(REPO_ROOT / \"dataset/reference_prediction/dataset.csv\", index=False)\n", "\n", "df = (\n", " pd.read_csv(REPO_ROOT / \"dataset/reference_prediction/dataset.csv\")\n", @@ -79,7 +79,7 @@ " df,\n", " mode=mode,\n", " method=\"transformer\",\n", - " model_path=\"/var/tmp/xjanovsk/certs/sec-certs/dataset/reference_prediction/final_model\",\n", + " model_path=REPO_ROOT / \"/dataset/reference_prediction/final_model\",\n", ")\n", "df = perform_dimensionality_reduction(\n", " df,\n", @@ -123,7 +123,6 @@ " \"pred_4\",\n", "]\n", "df_ = df[features_to_use + [\"label\", \"split\"]]\n", - "# df_ = df.copy()\n", "\n", "x_train, y_train, x_valid, y_valid, features = dataframe_to_training_arrays(\n", " df_, mode=mode, use_pca=True, use_umap=True, use_pred=True, use_lang=True\n", @@ -150,8 +149,8 @@ "df[\"y_pred\"] = clf.predict(x_all)\n", "df[\"reference_label\"] = df.label.fillna(df.y_pred)\n", "df[[\"dgst\", \"canonical_reference_keyword\", \"reference_label\"]].to_csv(\n", - " \"/var/tmp/xjanovsk/certs/sec-certs/dataset/reference_prediction/predictions.csv\"\n", - ")\n" + " REPO_ROOT / \"/dataset/reference_prediction/predictions.csv\"\n", + ")" ] }, { @@ -191,8 +190,8 @@ " x_valid,\n", " y_valid,\n", " features,\n", - " output_path=Path(\"/var/tmp/xjanovsk/certs/sec-certs/dataset/cc_ref_annotator_evaluation/embeddings\"),\n", - ")\n" + " output_path=REPO_ROOT / \"dataset/cc_ref_annotator_evaluation/embeddings\",\n", + ")" ] }, { @@ -208,8 +207,10 @@ "metadata": {}, "outputs": [], "source": [ - "misclassified_instances = df.loc[df.y_pred != df.label]\n", - "misclassified_instances = misclassified_instances[\n", + "scheme_mapping = {x.dgst: x.scheme for x in cc_dset}\n", + "all_classified_instances = df.loc[(df.label.notnull())].assign(scheme=lambda df_: df_.dgst.map(scheme_mapping))\n", + "misclassified_instances = df.loc[\n", + " (df.y_pred != df.label) & (df.label.notnull()),\n", " [\n", " \"dgst\",\n", " \"canonical_reference_keyword\",\n", @@ -223,20 +224,30 @@ " \"referenced_cert_versions\",\n", " \"lang_partial_ratio\",\n", " \"lang_token_sort_ratio\",\n", - " ]\n", - "]\n", - "misclassified_instances[\"report_link\"] = misclassified_instances.dgst.map(\n", - " lambda x: f\"https://seccerts.org/cc/{x}/report.pdf\"\n", - ")\n", - "misclassified_instances[\"st_link\"] = misclassified_instances.dgst.map(\n", - " lambda x: f\"https://seccerts.org/cc/{x}/target.pdf\"\n", + " ],\n", + "].assign(\n", + " report_link=lambda df_: df_.dgst.map(lambda x: f\"https://seccerts.org/cc/{x}/report.pdf\"),\n", + " st_link=lambda df_: df_.dgst.map(lambda x: f\"https://seccerts.org/cc/{x}/target.pdf\"),\n", + " scheme=lambda df_: df_.dgst.map(scheme_mapping),\n", ")\n", + "\n", "# Then replace all \\\\/ with / in the corresponding json, as the pandas to_json method escapes the slashes.\n", "misclassified_instances.to_json(\n", - " \"/var/tmp/xjanovsk/certs/sec-certs/dataset/misclassified_references_validation_set.json\",\n", + " REPO_ROOT / \"dataset/misclassified_references_validation_set.json\",\n", " orient=\"records\",\n", " indent=4,\n", - ")\n" + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Display proportion of misclassifications per scheme. Only DE and FR have sufficient support to make any conclusions.\n", + "# FR 4times more likely to be misclassified than DE\n", + "misclassified_instances.scheme.value_counts() * 100 / all_classified_instances.scheme.value_counts()" ] } ], diff --git a/notebooks/cc/references.ipynb b/notebooks/cc/references.ipynb index 6ce7d137..cb57f537 100644 --- a/notebooks/cc/references.ipynb +++ b/notebooks/cc/references.ipynb @@ -25,27 +25,47 @@ }, "outputs": [], "source": [ + "import functools\n", + "import itertools\n", + "import json\n", + "import random\n", + "import re\n", "import warnings\n", + "from collections.abc import Iterable\n", + "from datetime import datetime\n", "from pathlib import Path\n", - "from typing import Iterable\n", + "from typing import Literal\n", "\n", "import matplotlib.pyplot as plt\n", "import networkx as nx\n", "import networkx.algorithms.community as nx_comm\n", "import numpy as np\n", "import pandas as pd\n", + "import plotly.express as px\n", + "import plotly.graph_objects as go\n", "import seaborn as sns\n", "from notebooks.fixed_sankey_plot import sankey\n", + "from scipy import stats\n", "from tqdm import tqdm\n", "\n", "from sec_certs.dataset.cc import CCDataset\n", + "from sec_certs.utils.parallel_processing import process_parallel\n", + "\n", + "random.seed(42)\n", "\n", "# Surpress user warnings\n", "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", "\n", "%matplotlib inline\n", "\n", + "# LaTeX plotting\n", "# matplotlib.use(\"pgf\")\n", + "# plt.rcParams[\"pgf.texsystem\"] = \"pdflatex\"\n", + "# plt.rcParams[\"font.family\"] = \"serif\"\n", + "# plt.rcParams[\"text.usetex\"] = True\n", + "# plt.rcParams[\"pgf.rcfonts\"] = False\n", + "\n", + "\n", "sns.set_theme(style=\"white\")\n", "plt.rcParams[\"axes.linewidth\"] = 0.5\n", "plt.rcParams[\"legend.fontsize\"] = 6.5\n", @@ -59,25 +79,39 @@ "plt.rcParams[\"xtick.major.size\"] = 5\n", "plt.rcParams[\"xtick.major.width\"] = 0.5\n", "plt.rcParams[\"xtick.major.pad\"] = 0\n", - "# plt.rcParams[\"pgf.texsystem\"] = \"pdflatex\"\n", - "# plt.rcParams[\"font.family\"] = \"serif\"\n", - "# plt.rcParams[\"text.usetex\"] = True\n", - "# plt.rcParams[\"pgf.rcfonts\"] = False\n", + "\n", "plt.rcParams[\"axes.titlesize\"] = 8\n", "plt.rcParams[\"legend.handletextpad\"] = 0.3\n", - "plt.rcParams[\"lines.markersize\"] = 4\n", + "plt.rcParams[\"lines.markersize\"] = 0.5\n", "plt.rcParams[\"savefig.pad_inches\"] = 0.01\n", "sns.set_palette(\"deep\")\n", "\n", - "# plt.style.use(\"seaborn-whitegrid\")\n", - "# sns.set_palette(\"deep\")\n", - "# sns.set_context(\"notebook\") # Set to \"paper\" for use in paper :)\n", - "\n", - "# plt.rcParams['figure.figsize'] = (10, 6)\n", + "plt.style.use(\"default\")\n", + "sns.set_palette(\"deep\")\n", + "sns.set_context(\"notebook\")\n", "\n", + "REPO_ROOT = Path().resolve()\n", "RESULTS_DIR = Path(\"./results/references\")\n", "RESULTS_DIR.mkdir(exist_ok=True, parents=True)\n", - "SMARTCARD_CATEGORY = \"ICs, Smart Cards and Smart Card-Related Devices and Systems\"\n" + "\n", + "DATASET_PATH = REPO_ROOT / \"dataset/cc_november_23/dataset.json\"\n", + "PREDICTIONS_PATH = REPO_ROOT / \"dataset/reference_prediction/predictions.csv\"\n", + "\n", + "SMARTCARD_CATEGORY = \"ICs, Smart Cards and Smart Card-Related Devices and Systems\"\n", + "CARD_RELATED_CAT = {\"Other Devices and Systems\", \"Products for Digital Signatures\", \"Trusted Computing\"}\n", + "OTHERS_CAT = {\n", + " \"Access Control Devices and Systems\",\n", + " \"Biometric Systems and Devices\",\n", + " \"Boundary Protection Devices and Systems\",\n", + " \"Data Protection\",\n", + " \"Databases\",\n", + " \"Detection Devices and Systems\",\n", + " \"Key Management Systems\",\n", + " \"Mobility\",\n", + " \"Multi-Function Devices\",\n", + " \"Network and Network-Related Devices and Systems\",\n", + " \"Operating Systems\",\n", + "}" ] }, { @@ -97,6 +131,12 @@ " return len(x) if pd.notnull(x) else 0\n", "\n", "\n", + "def get_label_subgraph(graph: nx.DiGraph, label: str | Iterable[str]) -> nx.DiGraph:\n", + " label = label if isinstance(label, Iterable) else [label]\n", + " sub_edges = [(u, v) for u, v, d in graph.edges(data=True) if d.get(\"reference_label\") in label]\n", + " return graph.edge_subgraph(sub_edges)\n", + "\n", + "\n", "def compute_reference_numbers(df__: pd.DataFrame) -> pd.DataFrame:\n", " \"\"\"\n", " Creates new columns with number of references for each certificate.\n", @@ -113,6 +153,10 @@ " \"\"\"\n", " Pre-processing run on the CC dataset for the sake of this notebook.\n", " \"\"\"\n", + "\n", + " def choose_archival_date(not_valid_before):\n", + " return max(datetime(2019, 6, 1), not_valid_before + pd.Timedelta(days=(5 * 365) + 2))\n", + "\n", " return (\n", " cc_df.loc[cc_df.cert_id.notnull()]\n", " .copy()\n", @@ -125,12 +169,13 @@ " }\n", " )\n", " .assign(\n", - " longer_than_5_years=lambda df_: df_.not_valid_after - df_.not_valid_before > pd.Timedelta(days=5 * 365),\n", + " longer_than_5_years=lambda df_: df_.not_valid_after - df_.not_valid_before\n", + " > pd.Timedelta(days=(5 * 365) + 2),\n", " not_valid_after=lambda df_: df_.not_valid_after.where(\n", - " ~df_.longer_than_5_years, df_.not_valid_before + pd.Timedelta(days=5 * 365)\n", + " ~df_.longer_than_5_years, df_.not_valid_before.apply(choose_archival_date)\n", " ),\n", " )\n", - " .drop_duplicates(subset=[\"cert_id\"], keep=\"first\") # TODO: Investigate high number of duplicates and resolve\n", + " .drop_duplicates(subset=[\"cert_id\"], keep=\"first\")\n", " )\n", "\n", "\n", @@ -138,9 +183,7 @@ " \"\"\"\n", " Limits the columns with references to a given label.\n", " \"\"\"\n", - " label = label if isinstance(label, Iterable) else [label]\n", - " sub_edges = [(u, v) for u, v, d in graph.edges(data=True) if d.get(\"reference_label\") in label]\n", - " subgraph = graph.edge_subgraph(sub_edges)\n", + " subgraph = get_label_subgraph(graph, label)\n", "\n", " return cc_df.assign(\n", " refs=lambda df_: df_.apply(\n", @@ -175,7 +218,102 @@ " target=\"reference\",\n", " create_using=nx.DiGraph,\n", " edge_attr=[\"reference_label\"],\n", - " )\n" + " )\n", + "\n", + "\n", + "def get_plotly_node_edge_traces(G: nx.DiGraph, cc_dset: CCDataset) -> tuple[go.Scatter, go.Scatter]:\n", + " pos = nx.spring_layout(G)\n", + "\n", + " edge_x = []\n", + " edge_y = []\n", + " for edge in G.edges():\n", + " x0, y0 = pos[edge[0]]\n", + " x1, y1 = pos[edge[1]]\n", + " edge_x.append(x0)\n", + " edge_x.append(x1)\n", + " edge_x.append(None)\n", + " edge_y.append(y0)\n", + " edge_y.append(y1)\n", + " edge_y.append(None)\n", + "\n", + " edge_trace = go.Scatter(x=edge_x, y=edge_y, line={\"width\": 0.5, \"color\": \"#888\"}, hoverinfo=\"none\", mode=\"lines\")\n", + "\n", + " node_x = []\n", + " node_y = []\n", + " for node in G.nodes():\n", + " x, y = pos[node]\n", + " node_x.append(x)\n", + " node_y.append(y)\n", + "\n", + " node_trace = go.Scatter(\n", + " x=node_x,\n", + " y=node_y,\n", + " mode=\"markers\",\n", + " hoverinfo=\"text\",\n", + " marker={\n", + " \"showscale\": True,\n", + " \"colorscale\": \"Viridis\",\n", + " \"reversescale\": True,\n", + " \"color\": [],\n", + " \"size\": 10,\n", + " \"colorbar\": {\"thickness\": 15, \"title\": \"Node Connections\", \"xanchor\": \"left\", \"titleside\": \"right\"},\n", + " \"line_width\": 2,\n", + " },\n", + " )\n", + "\n", + " node_adjacencies = []\n", + " node_text = []\n", + " node_name_mapping = dict(enumerate(G.nodes()))\n", + " cert_id_to_dgst = {x.heuristics.cert_id: x.dgst for x in cc_dset}\n", + " for node, adjacencies in enumerate(nx.reverse_view(G).adjacency()):\n", + " node_adjacencies.append(len(adjacencies[1]))\n", + " node_text.append(\n", + " f\"{node_name_mapping[node]} ({cert_id_to_dgst[node_name_mapping[node]]}) (# conns: {len(adjacencies[1])})\"\n", + " )\n", + "\n", + " node_trace.marker.color = node_adjacencies\n", + " node_trace.text = node_text\n", + "\n", + " return node_trace, edge_trace\n", + "\n", + "\n", + "def plotly_draw(graph: nx.DiGraph, cc_dset: CCDataset):\n", + " plt.figure(figsize=(7, 7))\n", + " node_trace, edge_trace = get_plotly_node_edge_traces(graph, cc_dset)\n", + " fig = go.Figure(\n", + " data=[edge_trace, node_trace],\n", + " layout=go.Layout(\n", + " title=\"<br>Network graph made with Python\",\n", + " titlefont_size=16,\n", + " showlegend=False,\n", + " hovermode=\"closest\",\n", + " margin={\"b\": 20, \"l\": 5, \"r\": 5, \"t\": 40},\n", + " annotations=[\n", + " {\n", + " \"text\": \"Python code: <a href='https://plotly.com/ipython-notebooks/network-graphs/'> https://plotly.com/ipython-notebooks/network-graphs/</a>\",\n", + " \"showarrow\": False,\n", + " \"xref\": \"paper\",\n", + " \"yref\": \"paper\",\n", + " \"x\": 0.005,\n", + " \"y\": -0.002,\n", + " }\n", + " ],\n", + " xaxis={\"showgrid\": False, \"zeroline\": False, \"showticklabels\": False},\n", + " yaxis={\"showgrid\": False, \"zeroline\": False, \"showticklabels\": False},\n", + " ),\n", + " )\n", + " fig.update_layout(\n", + " autosize=False,\n", + " width=800,\n", + " height=500,\n", + " )\n", + " fig.show()\n", + "\n", + "\n", + "def find_reach_over_time(df_: pd.DataFrame, cert_id: str, date_range: pd.DatetimeIndex) -> pd.Series:\n", + " df = df_.copy().loc[lambda df_: df_.trans_refs.apply(lambda x: pd.notnull(x) and cert_id in x)]\n", + " dct = {date: df.loc[(date >= df.not_valid_before) & (date <= df.not_valid_after)].shape[0] for date in date_range}\n", + " return pd.Series(dct, name=cert_id)" ] }, { @@ -191,9 +329,9 @@ "metadata": {}, "outputs": [], "source": [ - "dset = CCDataset.from_json(\"/Users/adam/phd/projects/certificates/sec-certs/dataset/cc_final_run_may_23/dataset.json\")\n", + "dset = CCDataset.from_json(DATASET_PATH)\n", "cc_df = preprocess_cc_df(dset.to_pandas())\n", - "refs_df = preprocess_refs_df(\"/Users/adam/Downloads/predictions.csv\", cc_df)\n", + "refs_df = preprocess_refs_df(PREDICTIONS_PATH, cc_df)\n", "unique_labels = refs_df.reference_label.unique().tolist()\n", "\n", "# Load labeled reference graph as networkx directed graph\n", @@ -204,15 +342,36 @@ " edge_attr=\"reference_label\",\n", " create_using=nx.DiGraph,\n", ")\n", + "comp_subgraph = get_label_subgraph(graph, \"COMPONENT_USED\")\n", + "prev_subgraph = get_label_subgraph(graph, \"PREVIOUS_VERSION\")\n", "\n", - "cc_df = compute_reference_numbers(compute_references(cc_df, graph, unique_labels))\n" + "cc_df = compute_reference_numbers(compute_references(cc_df, graph, unique_labels))\n", + "cc_df_comp = compute_reference_numbers(compute_references(cc_df, graph, \"COMPONENT_USED\"))\n", + "cc_df_prev = compute_reference_numbers(compute_references(cc_df, graph, \"PREVIOUS_VERSION\"))\n", + "assert cc_df.n_refs.sum() == cc_df_comp.n_refs.sum() + cc_df_prev.n_refs.sum()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Display EAL in time\n", + "sec_levels = cc_df.loc[cc_df.year_from < 2023].groupby([\"year_from\", \"eal\"], as_index=False).size()\n", + "plt.figure()\n", + "g = sns.relplot(data=sec_levels, x=\"year_from\", y=\"size\", col=\"eal\", kind=\"line\", col_wrap=7)\n", + "g.set(xlabel=\"Year of certification\", ylabel=\"Number of issued certificates\")\n", + "g.fig.suptitle(\"Security level prevalence in time\", y=1.03)\n", + "g.fig.savefig(RESULTS_DIR / \"sec_levels_in_time.pdf\", bbox_inches=\"tight\")\n", + "g.fig.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Common processing functions" + "## Compute category summary" ] }, { @@ -221,12 +380,26 @@ "metadata": {}, "outputs": [], "source": [ - "# Understand which columns I need and limit myself to those columns\n", - "# Every analytical cell should be isolated in a function that takes a single input: The dataframe of certificates to work on.\n", - "# - The number of those references is computed in the function itself\n", - "# - Each analytical method should have some tests at the end\n", - "# - If some LaTeX output accompanies the computaiton, the function should return it as a string\n", - "# - Those are stored in a dictionary that keeps expanding\n" + "def simplified_category_mapping(cat) -> str:\n", + " if cat in CARD_RELATED_CAT:\n", + " return \"Smartcard-related\"\n", + " elif cat in OTHERS_CAT:\n", + " return \"Others\"\n", + " elif cat == SMARTCARD_CATEGORY:\n", + " return \"Smartcards\"\n", + " else:\n", + " raise ValueError(f\"Unknown category {cat}\")\n", + "\n", + "\n", + "cc_df_comp[\"category_simplified\"] = cc_df_comp.category.map(simplified_category_mapping)\n", + "cc_df_prev[\"category_simplified\"] = cc_df_prev.category.map(simplified_category_mapping)\n", + "\n", + "component_reuse = cc_df_comp.groupby(\"category_simplified\").n_refs.sum().rename(\"Component reuse\")\n", + "previous_version = cc_df_prev.groupby(\"category_simplified\").n_refs.sum().rename(\"Predecessor\")\n", + "\n", + "pd.DataFrame([component_reuse, previous_version]).transpose().rename_axis(\"Product category\").to_csv(\n", + " RESULTS_DIR / \"ref_categories_stackplot.csv\"\n", + ")" ] }, { @@ -243,33 +416,50 @@ "metadata": {}, "outputs": [], "source": [ - "def compute_basic_reference_graph_stats(df__: pd.DataFrame, graph: nx.DiGraph) -> dict[str, str]:\n", - " df = df__.copy().assign(has_refs=lambda df_: df_.refs.notnull()).pipe(compute_reference_numbers)\n", + "def compute_basic_reference_graph_stats(\n", + " df__: pd.DataFrame, df_comp__: pd.DataFrame, df_prev__: pd.DataFrame\n", + ") -> dict[str, str]:\n", + " df = df__.copy().assign(has_refs=lambda df_: df_.refs.notnull())\n", + " df_comp = df_comp__.copy().assign(has_refs=lambda df_: df_.refs.notnull())\n", + " df_prev = df_prev__.copy().assign(has_refs=lambda df_: df_.refs.notnull())\n", "\n", " n_ref_smartcards = df.loc[(df.has_refs) & (df.category == SMARTCARD_CATEGORY)].shape[0]\n", " n_ref_others = df.loc[(df.has_refs) & (df.category != SMARTCARD_CATEGORY)].shape[0]\n", "\n", + " n_comp_smartcards = df_comp.loc[(df_comp.category == SMARTCARD_CATEGORY) & (df_comp.has_refs)].shape[0]\n", + " n_comp_others = df_comp.loc[(df_comp.category != SMARTCARD_CATEGORY) & (df_comp.has_refs)].shape[0]\n", + "\n", + " n_prev_smartcards = df_prev.loc[(df_prev.category == SMARTCARD_CATEGORY) & (df_prev.has_refs)].shape[0]\n", + " n_prev_others = df_prev.loc[(df_prev.category != SMARTCARD_CATEGORY) & (df_prev.has_refs)].shape[0]\n", + "\n", + " print(\n", + " f\"Total number of referencing certificates: {n_ref_smartcards + n_ref_others} ({100 * (n_ref_smartcards + n_ref_others) / df.shape[0]:.2f}%)\"\n", + " )\n", " print(\n", " f\"Number of smartcard certificates that reference some other certificate: {n_ref_smartcards} ({100 * n_ref_smartcards / df.loc[df.category == SMARTCARD_CATEGORY].shape[0]:.2f}%)\"\n", " )\n", + " print(f\"\\t- Out of that, {n_comp_smartcards} do reference a sub-component.\")\n", + " print(f\"\\t- Out of that, {n_prev_smartcards} do reference a previous version.\")\n", " print(\n", " f\"Number of non-smartcard certificates that reference some other certificate: {n_ref_others} ({100 * n_ref_others / df.loc[df.category != SMARTCARD_CATEGORY].shape[0]:.2f}%)\"\n", " )\n", + " print(f\"\\t- Out of that, {n_comp_others} do reference a sub-component.\")\n", + " print(f\"\\t- Out of that, {n_prev_others} do reference a previous version.\")\n", " print(\n", - " f\"Total number of referencing certificates: {n_ref_smartcards + n_ref_others} ({100 * (n_ref_smartcards + n_ref_others) / df.shape[0]:.2f}%)\"\n", + " \"Note that the numbers don't sum to 100, as a certificate can reference both a sub-component and a previous version.\"\n", " )\n", "\n", " df_melted = df[[\"n_refs\", \"n_trans_refs\", \"n_in_refs\", \"n_in_trans_refs\"]].melt()\n", " df_melted[\"incoming\"] = df_melted.variable.map(lambda x: bool(x.endswith(\"by\")))\n", - " sns.catplot(data=df_melted, kind=\"boxen\", x=\"variable\", y=\"value\", col=\"variable\", sharex=False, sharey=False)\n", - " plt.savefig(RESULTS_DIR / \"boxen_plot_references.pdf\", bbox_inches=\"tight\")\n", - "\n", - " plt.show()\n", + " plt.figure()\n", + " g = sns.catplot(data=df_melted, kind=\"boxen\", x=\"variable\", y=\"value\", col=\"variable\", sharex=False, sharey=False)\n", + " g.fig.savefig(RESULTS_DIR / \"boxen_plot_references.pdf\", bbox_inches=\"tight\")\n", + " g.fig.show()\n", "\n", " return {}\n", "\n", "\n", - "compute_basic_reference_graph_stats(cc_df, graph)\n" + "compute_basic_reference_graph_stats(cc_df, cc_df_comp, cc_df_prev)" ] }, { @@ -286,19 +476,12 @@ "metadata": {}, "outputs": [], "source": [ - "# TODO: Check that it actually works, the data on small subset was fairly weird\n", - "# TODO: Work only on sub-component references?\n", - "def compute_certs_top_reach(df__: pd.DataFrame) -> dict:\n", - " def find_reach_over_time(df_: pd.DataFrame, cert_id: str, date_range: pd.DatetimeIndex) -> pd.Series:\n", - " df = df_.copy().loc[lambda df_: df_.in_trans_refs.apply(lambda x: pd.notnull(x) and cert_id in x)]\n", - " dct = {\n", - " date: df.loc[(date >= df.not_valid_before) & (date <= df.not_valid_after)].shape[0] for date in date_range\n", - " }\n", - " return pd.Series(dct, name=cert_id)\n", - "\n", + "def compute_certs_top_reach(df__: pd.DataFrame):\n", " df = df__.copy()\n", " top_10_certs = df.sort_values(by=\"n_in_trans_refs\", ascending=False).head(10)\n", " print(top_10_certs[[\"cert_id\", \"n_in_trans_refs\"]])\n", + " for dgst in top_10_certs.index.tolist():\n", + " print(dset[dgst].name)\n", "\n", " date_range = pd.date_range(df.not_valid_before.min(), df.not_valid_before.max())\n", " data = [find_reach_over_time(df, x, date_range) for x in tqdm(top_10_certs.cert_id.tolist())]\n", @@ -309,15 +492,54 @@ " .melt(id_vars=\"date\", var_name=\"certificate\", value_name=\"reach\")\n", " )\n", "\n", + " plt.figure()\n", " g = sns.lineplot(data=df_reach_evolution_melted, x=\"date\", y=\"reach\", hue=\"certificate\")\n", " g.set(title=\"Reach of top-10 certificates in time\", xlabel=\"Time\", ylabel=\"Certificate reach\")\n", - " plt.savefig(RESULTS_DIR / \"lineplot_top_certificate_reach.pdf\", bbox_inches=\"tight\")\n", - " plt.show()\n", + " g.figure.savefig(RESULTS_DIR / \"lineplot_top_certificate_reach.pdf\", bbox_inches=\"tight\")\n", + " g.figure.show()\n", "\n", - " return {}\n", + " return top_10_certs.index.tolist(), df_reach_evolution_melted\n", + "\n", + "\n", + "top_10_digests, df_to_plot = compute_certs_top_reach(cc_df_comp)\n", + "df_to_plot.to_csv(RESULTS_DIR / \"average_reach_over_time.csv\", header=True, index=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Combined reach evolution in time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = cc_df_comp.loc[cc_df_comp.category == SMARTCARD_CATEGORY]\n", + "date_range = pd.date_range(df.not_valid_before.min(), df.not_valid_before.max())\n", + "cert_ids_with_reach = df.loc[df.n_in_trans_refs > 0]\n", "\n", + "dct = {}\n", + "for date in tqdm(date_range):\n", + " active_certs = df.loc[(date >= df.not_valid_before) & (date <= df.not_valid_after)]\n", + " active_cert_ids = set(active_certs.cert_id)\n", + " daily_certs_with_reach = cert_ids_with_reach.assign(\n", + " in_trans_refs=lambda df_: df_.in_trans_refs.apply(lambda x: x.intersection(active_cert_ids)),\n", + " n_in_trans_refs=lambda df_: df_.in_trans_refs.map(len_if_exists),\n", + " )\n", + " daily_top_10 = daily_certs_with_reach.sort_values(by=\"n_in_trans_refs\", ascending=False).head(10)\n", + " dct[date] = len(set(itertools.chain.from_iterable(daily_top_10.in_trans_refs.tolist()))) / len(active_cert_ids)\n", + "\n", + "reach_df = pd.DataFrame(pd.Series(dct, name=\"combined reach\")).rename_axis(\"date\")\n", "\n", - "compute_certs_top_reach(cc_df)\n" + "plt.figure()\n", + "g = sns.lineplot(data=reach_df, x=\"date\", y=\"combined reach\")\n", + "g.set(title=\"Combined reach of top-10 certificates in time\", xlabel=\"Time\", ylabel=\"Combined reach\")\n", + "g.figure.savefig(RESULTS_DIR / \"combined_reach_evolution.pdf\", bbox_inches=\"tight\")\n", + "g.figure.show()" ] }, { @@ -342,6 +564,25 @@ " }\n", "\n", "\n", + "def compute_avg_reach(df__: pd.DataFrame, categories: set[str], date_range: pd.DatetimeIndex) -> dict:\n", + " certs_from_category = df__.copy().loc[df__.category.isin(categories)]\n", + " certs_with_reach = certs_from_category.loc[certs_from_category.n_in_trans_refs > 0].cert_id.tolist()\n", + "\n", + " print(\"Preparing inputs for average reach computation...\")\n", + " parallel_processing_inputs = [(df__.copy(), x, date_range) for x in certs_with_reach]\n", + " print(\"Computing average reach in parallel...\")\n", + " outputs = process_parallel(\n", + " find_reach_over_time, parallel_processing_inputs, max_workers=200, use_threading=False, unpack=True\n", + " )\n", + " reach_dict = dict(zip(certs_with_reach, outputs))\n", + "\n", + " averages = {}\n", + " for date in date_range:\n", + " averages[date] = np.mean([reach_dict[x][date] for x in reach_dict])\n", + "\n", + " return averages\n", + "\n", + "\n", "def compute_avg_references_over_time(df__: pd.DataFrame) -> dict:\n", " df = df__.copy()\n", " date_range = pd.date_range(df.not_valid_before.min(), df.not_valid_before.max())\n", @@ -367,10 +608,16 @@ " .melt(id_vars=[\"date\"], var_name=\"category\", value_name=\"n_references\")\n", " )\n", "\n", - " g = sns.lineplot(data=df_avg_num_refs_melted, x=\"date\", y=\"n_references\", hue=\"category\")\n", - " g.set(title=\"Average number of references in certificates\", xlabel=\"Time\", ylabel=\"Number of references\")\n", - " plt.savefig(RESULTS_DIR / \"lineplot_avg_n_references.pdf\", bbox_inches=\"tight\")\n", - " plt.show()\n", + " plt.figure()\n", + " fig = px.line(\n", + " df_avg_num_refs_melted,\n", + " x=\"date\",\n", + " y=\"n_references\",\n", + " color=\"category\",\n", + " hover_data={\"date\": \"|%Y-%m-%d\"},\n", + " title=\"Timestamp vs Value\",\n", + " )\n", + " fig.show()\n", "\n", " return {}\n", "\n", @@ -378,14 +625,16 @@ "def compute_avg_reach_over_time(df__: pd.DataFrame) -> dict:\n", " df = df__.copy()\n", " date_range = pd.date_range(df.not_valid_before.min(), df.not_valid_before.max())\n", - " reach_smartcards = compute_avg_references(df.loc[df.category == SMARTCARD_CATEGORY], \"n_in_trans_refs\", date_range)\n", - " reach_others = compute_avg_references(df.loc[df.category != SMARTCARD_CATEGORY], \"n_in_trans_refs\", date_range)\n", + " reach_smartcards = compute_avg_reach(df, {SMARTCARD_CATEGORY}, date_range)\n", + " reach_card_related = compute_avg_reach(df, CARD_RELATED_CAT, date_range)\n", + " reach_others = compute_avg_reach(df, OTHERS_CAT, date_range)\n", "\n", " df_avg_num_refs_melted = (\n", " pd.concat(\n", " [\n", - " pd.Series(reach_smartcards, name=\"smartcard reach\"),\n", - " pd.Series(reach_others, name=\"other reach\"),\n", + " pd.Series(reach_smartcards, name=\"smartcards\"),\n", + " pd.Series(reach_card_related, name=\"smartcard-related\"),\n", + " pd.Series(reach_others, name=\"others\"),\n", " ],\n", " axis=1,\n", " )\n", @@ -394,20 +643,101 @@ " .melt(id_vars=[\"date\"], var_name=\"category\", value_name=\"n_references\")\n", " )\n", "\n", - " g = sns.lineplot(data=df_avg_num_refs_melted, x=\"date\", y=\"n_references\", hue=\"category\")\n", - " g.set(\n", - " title=\"Average certificate reach over time\",\n", - " xlabel=\"Time\",\n", - " ylabel=\"Number of (transitively) referencing certificates\",\n", + " return df_avg_num_refs_melted\n", + "\n", + "\n", + "# compute_avg_references_over_time(cc_df_comp)\n", + "# df_avg_reach = compute_avg_reach_over_time(cc_df_comp)\n", + "# df_avg_reach.to_csv(RESULTS_DIR / \"avg_reach_over_time.csv\", header=True, index=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Histogram of n days to 0-reach for certificates given they archival date\n", + "\n", + "- For each certificate that has >0 reach on its archival date, we compute the number of days *after* the archival date such that its reach is >0\n", + "- We then plot the histogram of these numbers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = cc_df_comp.loc[cc_df_comp.not_valid_before != cc_df_comp.not_valid_after].copy()\n", + "date_range = pd.date_range(df.not_valid_before.min(), df.not_valid_before.max())\n", + "cert_ids_with_reach = list(set(df.loc[df.n_in_trans_refs > 0].cert_id.tolist()))\n", + "\n", + "\n", + "parallel_processing_inputs = [(df.copy(), x, date_range) for x in cert_ids_with_reach]\n", + "reach_list = process_parallel(\n", + " find_reach_over_time, parallel_processing_inputs, max_workers=200, use_threading=False, unpack=True\n", + ")\n", + "cert_id_to_archival = df[[\"cert_id\", \"not_valid_after\"]].set_index(\"cert_id\").to_dict()[\"not_valid_after\"]\n", + "\n", + "# Choose only rows that are post-archival and with >0 reach\n", + "reach_list = [x.loc[(x.index > cert_id_to_archival[x.name]) & (x > 0)] for x in reach_list]\n", + "# Pick the number of such rows\n", + "reach_list = [x.shape[0] for x in reach_list if x.shape[0] > 0]\n", + "\n", + "sns.ecdfplot(data=reach_list, complementary=True)\n", + "plt.show()\n", + "pd.DataFrame(reach_list).describe()\n", + "\n", + "pd.DataFrame(reach_list, columns=[\"n_days\"]).to_csv(RESULTS_DIR / \"archived_half_life.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_avg_references_over_time(df__: pd.DataFrame) -> dict:\n", + " df = df__.copy()\n", + " card_related_categories = {\"Other Devices and Systems\", \"Products for Digital Signatures\", \"Trusted Computing\"}\n", + " other_categories = {\n", + " \"Access Control Devices and Systems\",\n", + " \"Biometric Systems and Devices\",\n", + " \"Boundary Protection Devices and Systems\",\n", + " \"Data Protection\",\n", + " \"Databases\",\n", + " \"Detection Devices and Systems\",\n", + " \"Key Management Systems\",\n", + " \"Mobility\",\n", + " \"Multi-Function Devices\",\n", + " \"Network and Network-Related Devices and Systems\",\n", + " \"Operating Systems\",\n", + " }\n", + " date_range = pd.date_range(df.not_valid_before.min(), df.not_valid_before.max())\n", + " refs_smartcards = compute_avg_references(df.loc[df.category == SMARTCARD_CATEGORY], \"n_trans_refs\", date_range)\n", + " refs_card_related = compute_avg_references(\n", + " df.loc[df.category.isin(card_related_categories)], \"n_trans_refs\", date_range\n", " )\n", - " plt.savefig(RESULTS_DIR / \"lineplot_avg_n_references.pdf\", bbox_inches=\"tight\")\n", - " plt.show()\n", + " refs_others = compute_avg_references(df.loc[df.category.isin(other_categories)], \"n_trans_refs\", date_range)\n", "\n", - " return {}\n", + " df_avg_num_refs_melted = (\n", + " pd.concat(\n", + " [\n", + " pd.Series(refs_smartcards, name=\"smartcards\"),\n", + " pd.Series(refs_card_related, name=\"smartcard-related\"),\n", + " pd.Series(refs_others, name=\"others categories\"),\n", + " ],\n", + " axis=1,\n", + " )\n", + " .rename_axis(\"date\")\n", + " .reset_index()\n", + " .melt(id_vars=[\"date\"], var_name=\"category\", value_name=\"n_references\")\n", + " )\n", + "\n", + " return df_avg_num_refs_melted\n", "\n", "\n", - "compute_avg_references_over_time(cc_df)\n", - "compute_avg_reach_over_time(cc_df)\n" + "df_to_plot = plot_avg_references_over_time(cc_df_comp)\n", + "df_to_plot.to_csv(RESULTS_DIR / \"avg_refs_over_time.csv\", header=True, index=False)" ] }, { @@ -459,14 +789,15 @@ " .melt(id_vars=[\"date\"], var_name=\"category\", value_name=\"number of certificates\")\n", " )\n", "\n", + " plt.figure()\n", " g = sns.lineplot(data=df_active_vs_ref_rich_melted, x=\"date\", y=\"number of certificates\", hue=\"category\")\n", " g.set(\n", " title=\"Number of active certificates vs. reference-rich certificates in time\",\n", " xlabel=\"Time\",\n", " ylabel=\"Number of certificates\",\n", " )\n", - " plt.savefig(RESULTS_DIR / \"lienplot_n_active_certs_vs_n_references.pdf\", bbox_inches=\"tight\")\n", - " plt.show()\n", + " g.figure.savefig(RESULTS_DIR / \"lienplot_n_active_certs_vs_n_references.pdf\", bbox_inches=\"tight\")\n", + " g.figure.show()\n", " return {}\n", "\n", "\n", @@ -503,6 +834,8 @@ " df_summary_references_melted = df_summary_references.melt(\n", " id_vars=[\"date\"], var_name=\"category\", value_name=\"number of certificates\"\n", " )\n", + "\n", + " plt.figure()\n", " g = sns.lineplot(\n", " data=df_summary_references_melted, x=\"date\", y=\"number of certificates\", hue=\"category\", errorbar=None\n", " )\n", @@ -511,8 +844,8 @@ " xlabel=\"Time\",\n", " ylabel=\"Number of certificates\",\n", " )\n", - " plt.savefig(RESULTS_DIR / \"lineplot_references_summary.pdf\", bbox_inches=\"tight\")\n", - " plt.show()\n", + " g.figure.savefig(RESULTS_DIR / \"lineplot_references_summary.pdf\", bbox_inches=\"tight\")\n", + " g.figure.show()\n", "\n", " df_ratios = df_summary_references.copy()\n", " df_ratios[\"ref. rich certificates\"] = df_ratios[\"ref. rich certificates\"] / df_ratios[\"active certificates\"]\n", @@ -521,20 +854,21 @@ " df_ratios = df_ratios.drop(columns=[\"active certificates\"])\n", " df_ratios_melted = df_ratios.melt(id_vars=[\"date\"], var_name=\"category\", value_name=\"ratio of certificates\")\n", "\n", + " plt.figure()\n", " g = sns.lineplot(data=df_ratios_melted, x=\"date\", y=\"ratio of certificates\", hue=\"category\", errorbar=None)\n", " g.set(\n", " title=\"ratio of reference-rich vs. referenced vs. isolated certificates in time\",\n", " xlabel=\"Time\",\n", " ylabel=\"Number of certificates\",\n", " )\n", - " plt.savefig(RESULTS_DIR / \"lineplot_reference_ratio.pdf\", bbox_inches=\"tight\")\n", - " plt.show()\n", + " g.figure.savefig(RESULTS_DIR / \"lineplot_reference_ratio.pdf\", bbox_inches=\"tight\")\n", + " g.figure.show()\n", "\n", " return {}\n", "\n", "\n", - "compute_number_of_active_vs_ref_rich_certs_over_time(cc_df)\n", - "compute_summary_active_vs_ref_rich_over_time(cc_df)\n" + "compute_number_of_active_vs_ref_rich_certs_over_time(cc_df_comp)\n", + "compute_summary_active_vs_ref_rich_over_time(cc_df_comp)" ] }, { @@ -562,24 +896,26 @@ "\n", " for date in tqdm(date_range):\n", " active_certs = df.loc[(date >= df.not_valid_before) & (date <= df.not_valid_after)].copy()\n", - " active_certs_cert_ids = set(active_certs[\"cert_id\"].tolist())\n", - " active_certs[\"no_intersection\"] = active_certs.refs.map(\n", - " lambda x: False if pd.isnull(x) else not x.intersection(active_certs_cert_ids)\n", + " archived_certs = df.loc[(date < df.not_valid_before) | (date > df.not_valid_after)].copy()\n", + " archived_cert_ids = set(archived_certs[\"cert_id\"].tolist())\n", + " active_certs[\"ref_archived\"] = active_certs.refs.map(\n", + " lambda x: False if pd.isnull(x) else bool(x.intersection(archived_cert_ids))\n", " )\n", - " active_certs[\"no_transitive_intersection\"] = active_certs.trans_refs.map(\n", - " lambda x: False if pd.isnull(x) else not x.intersection(active_certs_cert_ids)\n", + " active_certs[\"trans_ref_archived\"] = active_certs.trans_refs.map(\n", + " lambda x: False if pd.isnull(x) else bool(x.intersection(archived_cert_ids))\n", " )\n", + "\n", " dct_direct_others[date] = active_certs.loc[\n", - " (active_certs.no_intersection) & (active_certs.category != SMARTCARD_CATEGORY)\n", + " (active_certs.ref_archived) & (active_certs.category != SMARTCARD_CATEGORY)\n", " ].shape[0]\n", " dct_transitive_others[date] = active_certs.loc[\n", - " (active_certs.no_transitive_intersection) & (active_certs.category != SMARTCARD_CATEGORY)\n", + " (active_certs.trans_ref_archived) & (active_certs.category != SMARTCARD_CATEGORY)\n", " ].shape[0]\n", " dct_direct_smartcards[date] = active_certs.loc[\n", - " (active_certs.no_intersection) & (active_certs.category == SMARTCARD_CATEGORY)\n", + " (active_certs.ref_archived) & (active_certs.category == SMARTCARD_CATEGORY)\n", " ].shape[0]\n", " dct_transitive_smartcards[date] = active_certs.loc[\n", - " (active_certs.no_transitive_intersection) & (active_certs.category == SMARTCARD_CATEGORY)\n", + " (active_certs.trans_ref_archived) & (active_certs.category == SMARTCARD_CATEGORY)\n", " ].shape[0]\n", "\n", " df_refs_to_archived_melted = (\n", @@ -597,19 +933,92 @@ " .melt(id_vars=[\"date\"], var_name=\"reference type\", value_name=\"number of certificates\")\n", " )\n", "\n", + " plt.figure()\n", " g = sns.lineplot(data=df_refs_to_archived_melted, x=\"date\", y=\"number of certificates\", hue=\"reference type\")\n", " g.set(\n", " title=\"Number of active certificates that reference some archived certificate\",\n", " xlabel=\"Time\",\n", " ylabel=\"Number of certificates\",\n", " )\n", - " plt.savefig(RESULTS_DIR / \"lienplot_active_certs_referencing_archived.pdf\", bbox_inches=\"tight\")\n", - " plt.show()\n", + " g.figure.savefig(RESULTS_DIR / \"lienplot_active_certs_referencing_archived.pdf\", bbox_inches=\"tight\")\n", + " g.figure.show()\n", "\n", " return {}\n", "\n", "\n", - "compute_certs_referencing_archived_ones(cc_df)\n" + "compute_certs_referencing_archived_ones(cc_df_comp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Certificates referencing an archived certificate on their issuance day" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def compute_refs_to_archived_on_issuance_day(cc_df_comp: pd.DataFrame, cc_dset: CCDataset) -> None:\n", + " cert_id_to_dgst = {x.heuristics.cert_id: x.dgst for x in cc_dset}\n", + "\n", + " def cert_id_to_url(cert_id):\n", + " return f\"https://seccerts.org/cc/{cert_id_to_dgst[cert_id]}/\"\n", + "\n", + " def cert_was_archived_on_date(date: pd.Timestamp, cert_id: str) -> bool:\n", + " if not date:\n", + " return False\n", + " if not dset[cert_id_to_dgst[cert_id]].not_valid_after:\n", + " return False\n", + " return date.date() > dset[cert_id_to_dgst[cert_id]].not_valid_after\n", + "\n", + " df__ = cc_df_comp.copy()\n", + " date_range = pd.date_range(df__.not_valid_before.min(), df__.not_valid_before.max())\n", + "\n", + " df_problematic = pd.DataFrame()\n", + " for date in tqdm(date_range):\n", + " certs_issued = df__.loc[(date == df__.not_valid_before) & (df__.n_refs > 0)].assign(\n", + " refs_to_archived_certs=lambda df_: df_.refs.map(\n", + " lambda refs: {x for x in refs if cert_was_archived_on_date(date, x)}\n", + " )\n", + " )\n", + " df_problematic = pd.concat(\n", + " [df_problematic, certs_issued[certs_issued.refs_to_archived_certs.map(lambda x: len(x) > 0)]]\n", + " )\n", + "\n", + " df_problematic = df_problematic.assign(\n", + " url=lambda df_: df_.cert_id.map(cert_id_to_url),\n", + " archived_refs_urls=lambda df_: df_.refs_to_archived_certs.map(lambda x: [cert_id_to_url(y) for y in x]),\n", + " )\n", + "\n", + " df_problematic = df_problematic[\n", + " [\n", + " \"cert_id\",\n", + " \"url\",\n", + " \"name\",\n", + " \"status\",\n", + " \"category\",\n", + " \"manufacturer\",\n", + " \"scheme\",\n", + " \"eal\",\n", + " \"not_valid_before\",\n", + " \"not_valid_after\",\n", + " \"cert_lab\",\n", + " \"refs\",\n", + " \"refs_to_archived_certs\",\n", + " \"archived_refs_urls\",\n", + " ]\n", + " ]\n", + " df_problematic.to_csv(RESULTS_DIR / \"certs_with_refs_to_archived_on_their_issuance_date.csv\", sep=\";\", index=False)\n", + "\n", + " return df_problematic\n", + "\n", + "\n", + "df_problematic = compute_refs_to_archived_on_issuance_day(cc_df_comp, dset)\n", + "n_refs_to_archived_when_issued = df_problematic.shape[0]" ] }, { @@ -671,18 +1080,77 @@ " .melt(id_vars=[\"date\"], var_name=\"reference type\", value_name=\"number of certificates\")\n", " )\n", "\n", + " plt.figure()\n", " g = sns.lineplot(data=df_references_vuln_melted, x=\"date\", y=\"number of certificates\", hue=\"reference type\")\n", " g.set(\n", " title=\"Number of active certificates that reference some vulnerable certificate\",\n", " xlabel=\"Time\",\n", " ylabel=\"Number of certificates\",\n", " )\n", - " plt.savefig(RESULTS_DIR / \"lienplot_active_certs_referencing_vulnerable.pdf\", bbox_inches=\"tight\")\n", - " plt.show()\n", + " g.figure.savefig(RESULTS_DIR / \"lienplot_active_certs_referencing_vulnerable.pdf\", bbox_inches=\"tight\")\n", + " g.figure.show()\n", " return {}\n", "\n", "\n", - "compute_certs_referencing_vulnerable_over_time(cc_df)\n" + "compute_certs_referencing_vulnerable_over_time(cc_df_comp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Certificates with sub-component reference to lower EAL cert" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def compute_references_to_weaker_eals(cc_df_comp: pd.DataFrame, cc_dset: CCDataset) -> None:\n", + " eal_to_rank = {category: index for index, category in enumerate(cc_df_comp.eal.dtype.categories)}\n", + " cert_id_to_dst_mapping = {x.heuristics.cert_id: x.dgst for x in dset}\n", + "\n", + " def ref_is_weaker(cert_id: str, eal: str, ref_cert_id: str) -> bool:\n", + " eal_rank = eal_to_rank[eal]\n", + " ref_eal = dset[cert_id_to_dst_mapping[ref_cert_id]].eal\n", + " ref_eal_rank = eal_to_rank[ref_eal]\n", + " return ref_eal_rank < eal_rank\n", + "\n", + " df_ = cc_df_comp.copy()\n", + " df_ = (\n", + " df_.loc[\n", + " (df_.n_refs > 0) & (df_.eal.notnull()),\n", + " [\n", + " \"cert_id\",\n", + " \"name\",\n", + " \"status\",\n", + " \"category\",\n", + " \"manufacturer\",\n", + " \"scheme\",\n", + " \"eal\",\n", + " \"not_valid_before\",\n", + " \"not_valid_after\",\n", + " \"refs\",\n", + " ],\n", + " ]\n", + " .assign(\n", + " refs=lambda df__: df__.apply(\n", + " lambda row: {x for x in row.refs if ref_is_weaker(row.cert_id, row.eal, x)}, axis=1\n", + " ),\n", + " n_refs=lambda df__: df__.refs.map(len_if_exists),\n", + " referenced_levels=lambda df__: df__.refs.map(\n", + " lambda x: {dset[cert_id_to_dst_mapping[y]].eal for y in x} if pd.notnull(x) else set()\n", + " ),\n", + " )\n", + " .loc[lambda df__: df__.n_refs > 0]\n", + " )\n", + "\n", + " df_.to_csv(RESULTS_DIR / \"certs_referencing_weaker_eals.csv\", index=False, sep=\";\")\n", + "\n", + "\n", + "compute_references_to_weaker_eals(cc_df_comp, dset)" ] }, { @@ -714,7 +1182,7 @@ " countplot = sns.countplot(data=df, x=\"category\", hue=col, ax=axes[index])\n", " countplot.set(\n", " xlabel=\"Category\",\n", - " ylabel=\"Outgoing direct references\",\n", + " ylabel=f\"{' '.join(col.split('_'))}\",\n", " title=f\"Countplot of {' '.join(col.split('_'))}\",\n", " )\n", " countplot.tick_params(axis=\"x\", rotation=90)\n", @@ -725,7 +1193,10 @@ " return {}\n", "\n", "\n", - "plot_direct_refs_per_category(cc_df)\n" + "print(\"Countplot of sub-component references\")\n", + "plot_direct_refs_per_category(cc_df_comp)\n", + "print(\"Countplot of previous version references\")\n", + "plot_direct_refs_per_category(cc_df_prev)" ] }, { @@ -743,25 +1214,33 @@ "outputs": [], "source": [ "def plot_sankey_refs_categories(df__: pd.DataFrame) -> dict:\n", - " df = df__.copy()\n", + " def map_category(cat):\n", + " if cat == SMARTCARD_CATEGORY:\n", + " return \"Smartcards\"\n", + " elif cat in CARD_RELATED_CAT:\n", + " return \"Card-related\"\n", + " else:\n", + " return \"Others\"\n", + "\n", + " df = df__.copy().assign(category=lambda df_: df_.category.map(map_category))\n", "\n", " cert_id_to_category_mapping = dict(zip(df.cert_id, df.category))\n", " cert_id_to_category_mapping[np.NaN] = \"No references\"\n", "\n", - " exploded = df.loc[:, [\"category\", \"refs\"]].explode(\"refs\")\n", - " exploded[\"ref_category\"] = exploded.refs.map(lambda x: cert_id_to_category_mapping[x] if pd.notnull(x) else np.nan)\n", + " exploded = df.loc[:, [\"category\", \"trans_refs\"]].explode(\"trans_refs\")\n", + " exploded[\"ref_category\"] = exploded.trans_refs.map(\n", + " lambda x: cert_id_to_category_mapping[x] if pd.notnull(x) else np.nan\n", + " )\n", " exploded = exploded.loc[exploded.ref_category.notnull()]\n", - "\n", - " exploded_with_refs = exploded.loc[exploded.ref_category != \"No references\"]\n", - "\n", " all_categories = set(exploded.category.unique()) | set(exploded.ref_category.unique())\n", " colors = list(sns.color_palette(\"hls\", len(all_categories), as_cmap=False).as_hex())\n", " color_dict = dict(zip(all_categories, colors))\n", "\n", " figure, axes = plt.subplots(1, 1)\n", - " figure.set_size_inches(24, 10)\n", + " figure.set_size_inches(6, 3)\n", " figure.set_tight_layout(True)\n", "\n", + " plt.figure()\n", " sankey(\n", " exploded.category,\n", " exploded.ref_category,\n", @@ -777,7 +1256,7 @@ " return {}\n", "\n", "\n", - "plot_sankey_refs_categories(cc_df)\n" + "plot_sankey_refs_categories(cc_df_comp)" ] }, { @@ -799,6 +1278,7 @@ " has_outgoing_direct_references=lambda df_: df_.n_refs > 0,\n", " has_incoming_direct_references=lambda df_: df_.n_in_refs > 0,\n", " )\n", + " plt.figure()\n", " figure, axes = plt.subplots(1, 2)\n", " figure.set_size_inches(14, 4)\n", " figure.set_tight_layout(True)\n", @@ -820,7 +1300,7 @@ " return {}\n", "\n", "\n", - "plot_refs_per_scheme(cc_df)\n" + "plot_refs_per_scheme(cc_df)" ] }, { @@ -834,32 +1314,32 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, + "metadata": {}, "outputs": [], "source": [ - "def countplot_certs_referencing_archived(df__: pd.DataFrame) -> dict:\n", - " def references_archived_cert(references):\n", - " if pd.isnull(references):\n", - " return False\n", - "\n", - " return any([x in cert_ids] for x in references)\n", - "\n", + "def compute_certs_referencing_archived_ones(df__: pd.DataFrame) -> dict:\n", " df = df__.copy()\n", + " date_range = pd.date_range(df.not_valid_before.min(), df.not_valid_before.max())\n", + " certs_that_reference_archived_one = set()\n", "\n", - " cert_ids = set(df.loc[((df.cert_id.notnull()) & (df.status == \"archived\")), \"cert_id\"].tolist())\n", - " df[\"references_archived_cert\"] = df.in_refs.map(references_archived_cert)\n", + " for date in tqdm(date_range):\n", + " active_certs = df.loc[(date >= df.not_valid_before) & (date <= df.not_valid_after)].copy()\n", + " archived_certs = df.loc[(date < df.not_valid_before) | (date > df.not_valid_after)].copy()\n", + " archived_cert_ids = set(archived_certs[\"cert_id\"].tolist())\n", + " active_certs[\"refs_archived\"] = active_certs.refs.map(\n", + " lambda x: False if pd.isnull(x) else bool(x.intersection(archived_cert_ids))\n", + " )\n", + " certs_that_reference_archived_one.update(active_certs.loc[(active_certs.refs_archived)].index.tolist())\n", + "\n", + " df[\"references_archived_cert\"] = df.index.map(lambda x: x in certs_that_reference_archived_one)\n", "\n", - " # TODO: We should limit on the number of certificates that referenced an archived certificate at some point where they were active as well.\n", " print(\n", " f\"Number of certificates that reference some archived certificate: {df.loc[df.references_archived_cert].shape[0]}\"\n", " )\n", "\n", " col_to_depict = [\"category\", \"scheme\"]\n", "\n", + " plt.figure()\n", " figure, axes = plt.subplots(1, 2)\n", " figure.set_size_inches(14, 8)\n", " figure.set_tight_layout(True)\n", @@ -879,7 +1359,7 @@ " return {}\n", "\n", "\n", - "countplot_certs_referencing_archived(cc_df)\n" + "compute_certs_referencing_archived_ones(cc_df_comp)" ] }, { @@ -910,6 +1390,7 @@ " colors = list(sns.color_palette(\"hls\", len(all_schemes), as_cmap=False).as_hex())\n", " color_dict = dict(zip(all_schemes, colors))\n", "\n", + " plt.figure()\n", " figure, axes = plt.subplots(1, 1)\n", " figure.set_size_inches(4, 4)\n", " figure.set_tight_layout(True)\n", @@ -931,15 +1412,18 @@ " return {}\n", "\n", "\n", - "plot_sankey_refs_schemes(cc_df)\n" + "plot_sankey_refs_schemes(cc_df)" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "## Reference network visualization" + "## Re-evaluation popularity\n", + "\n", + "- How many certificates do undergo re-evaluation?\n", + "- How many rounds of re-evaluation do certificates undergo?\n", + "- How often (timewise) do these certificates undergo re-evaluation?" ] }, { @@ -948,19 +1432,53 @@ "metadata": {}, "outputs": [], "source": [ - "# Print:\n", - "# - How many references in reports\n", - "# - How many references in targets\n", - "# - How many references in total\n", - "# -\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Combined references" + "def plot_ref_label_popularity_over_time(df__: pd.DataFrame, cc_df_comp__: pd.DataFrame, cc_df_prev__: pd.DataFrame):\n", + " df = df__.copy()\n", + " date_range = pd.date_range(df.not_valid_before.min(), df.not_valid_before.max())\n", + " sub_comp_refs = {}\n", + " prev_refs = {}\n", + " active_certs_dct = {}\n", + " for date in tqdm(date_range):\n", + " active_sub_comp_certs = cc_df_comp__.loc[\n", + " (date >= cc_df_comp__.not_valid_before) & (date <= cc_df_comp__.not_valid_after)\n", + " ].copy()\n", + " active_prev_certs = cc_df_prev__.loc[\n", + " (date >= cc_df_prev__.not_valid_before) & (date <= cc_df_prev__.not_valid_after)\n", + " ].copy()\n", + " sub_comp_refs[date] = active_sub_comp_certs.n_refs.sum()\n", + " prev_refs[date] = active_prev_certs.n_refs.sum()\n", + " active_certs_dct[date] = df.loc[(date >= df.not_valid_before) & (date <= df.not_valid_after)].shape[0]\n", + "\n", + " df_references_melted = (\n", + " pd.concat(\n", + " [\n", + " pd.Series(sub_comp_refs, name=\"sub-component\"),\n", + " pd.Series(prev_refs, name=\"previous_version\"),\n", + " pd.Series(active_certs_dct, name=\"active certificates\"),\n", + " ],\n", + " axis=1,\n", + " )\n", + " .rename_axis(\"date\")\n", + " .reset_index()\n", + " .melt(id_vars=[\"date\"], var_name=\"reference type\", value_name=\"number of certificates\")\n", + " )\n", + "\n", + " plt.figure()\n", + " g = sns.lineplot(data=df_references_melted, x=\"date\", y=\"number of certificates\", hue=\"reference type\")\n", + " g.set(\n", + " title=\"Sum of references in currently active certs. with different labels in time\",\n", + " xlabel=\"Time\",\n", + " ylabel=\"Number of references\",\n", + " )\n", + " g.figure.savefig(RESULTS_DIR / \"lineplot_different_labels.pdf\", bbox_inches=\"tight\")\n", + " g.figure.show()\n", + "\n", + "\n", + "plot_ref_label_popularity_over_time(cc_df, cc_df_comp, cc_df_prev)\n", + "\n", + "print(\n", + " f\"Number of certificates that did undergo re-evaluation (or previous version): {len(set(itertools.chain.from_iterable(cc_df_prev.loc[cc_df_prev.refs.notnull()].refs.tolist())))}\"\n", + ")" ] }, { @@ -980,7 +1498,7 @@ }, "outputs": [], "source": [ - "cert_id = \"ANSSI-CC-2014/07\"\n", + "cert_id = \"BSI-DSZ-CC-0753-2012\"\n", "\n", "for component in nx.weakly_connected_components(graph):\n", " if cert_id in component:\n", @@ -988,7 +1506,7 @@ "else:\n", " raise ValueError(f\"Certificate with id {cert_id} not found in graph.\")\n", "\n", - "view = nx.subgraph_view(graph, lambda node: node in component)\n", + "view = nx.subgraph_view(prev_subgraph, lambda node: node in component)\n", "print(f\"Certificate with id {cert_id}:\")\n", "print(f\" - is in a component with {len(view.nodes)} certificates and {len(view.edges)} references.\")\n", "print(f\" - references {list(view[cert_id].keys())}\")\n", @@ -998,16 +1516,7 @@ " break\n", "else:\n", " raise ValueError(f\"Certificate with id {cert_id} not found in dataset.\")\n", - "print(f\" - its page is at https://seccerts.org/cc/{cert.dgst}/\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nx.draw(view, pos=nx.planar_layout(view), with_labels=True)\n" + "print(f\" - its page is at https://seccerts.org/cc/{cert.dgst}/\")" ] }, { @@ -1071,11 +1580,10 @@ "\n", "for com in communities:\n", " for i in sorted(com):\n", - " print(f\"\\t{i}\")\n" + " print(f\"\\t{i}\")" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -1088,22 +1596,487 @@ "metadata": {}, "outputs": [], "source": [ - "# TODO: These are old commands that belonged to first paper. Replace them with code that can produce the similar numbers.\n", - "# print(f\"\\\\newcommand{{\\\\numCcAllDirectReferencing}}{{{df.has_outgoing_direct_references.sum()}}}\")\n", - "# print(f\"\\\\newcommand{{\\\\numCcAllNotDirectReferencing}}{{{len(df) - df.has_outgoing_direct_references.sum()}}}\")\n", - "# print(f\"\\\\newcommand{{\\\\numCcWithIdDirectReferencing}}{{{df_id_rich.has_outgoing_direct_references.sum()}}}\")\n", - "# print(f\"\\\\newcommand{{\\\\numCcWithIdNotDirectReferencing}}{{{len(df_id_rich) - df_id_rich.has_outgoing_direct_references.sum()}}}\")\n", - "# print(f\"\\\\newcommand{{\\\\numCCActiveDirectReferencing}}{{{df_id_rich.loc[df_id_rich.status == 'active'].has_outgoing_direct_references.sum()}}}\")\n", + "def print_category_commands(category: Literal[\"smartcard\", \"related\", \"others\"]) -> None:\n", + " if category == \"smartcard\":\n", + " categories = {SMARTCARD_CATEGORY}\n", + " elif category == \"related\":\n", + " categories = CARD_RELATED_CAT\n", + " elif category == \"others\":\n", + " categories = OTHERS_CAT\n", + " else:\n", + " raise ValueError(f\"Unknown category {category}\")\n", + "\n", + " cc_df_cat = cc_df.loc[cc_df.category.isin(categories)]\n", + " cc_df_cat_comp = cc_df_comp.loc[cc_df_comp.category.isin(categories)]\n", + " cc_df_cat_prev = cc_df_prev.loc[cc_df_prev.category.isin(categories)]\n", + "\n", + " ref_rich = cc_df_cat.loc[cc_df_cat.n_refs > 0].shape[0]\n", + " ref_rich_ratio = 100 * cc_df_cat.loc[cc_df_cat.n_refs > 0].shape[0] / cc_df_cat.shape[0]\n", + " ref_rich_comp = cc_df_cat_comp.loc[cc_df_cat_comp.n_refs > 0].shape[0]\n", + " ref_rich_comp_ratio = 100 * cc_df_cat_comp.loc[cc_df_cat_comp.n_refs > 0].shape[0] / cc_df_cat_comp.shape[0]\n", + " ref_rich_pred = cc_df_cat_prev.loc[cc_df_cat_prev.n_refs > 0].shape[0]\n", + " ref_rich_pred_ratio = 100 * cc_df_cat_prev.loc[cc_df_cat_prev.n_refs > 0].shape[0] / cc_df_cat_prev.shape[0]\n", + "\n", + " print(f\"\\\\newcommand{{\\\\refRich{category.capitalize()}}}{{{ref_rich}}}\")\n", + " print(f\"\\\\newcommand{{\\\\refRich{category.capitalize()}Percentage}}{{${ref_rich_ratio:.2f}\\%$}}\")\n", + "\n", + " print(f\"\\\\newcommand{{\\\\refRich{category.capitalize()}Comp}}{{{ref_rich_comp}}}\")\n", + " print(f\"\\\\newcommand{{\\\\refRich{category.capitalize()}CompPercentage}}{{${ref_rich_comp_ratio:.2f}\\%$}}\")\n", + "\n", + " print(f\"\\\\newcommand{{\\\\refRich{category.capitalize()}Pred}}{{{ref_rich_pred}}}\")\n", + " print(f\"\\\\newcommand{{\\\\refRich{category.capitalize()}PredPercentage}}{{${ref_rich_pred_ratio:.2f}\\%$}}\")\n", + " print(\"\")\n", + "\n", + "\n", + "annot_test = pd.read_csv(REPO_ROOT / \"src/sec_certs/data/reference_annotations/final/test.csv\")\n", + "annot_train = pd.read_csv(REPO_ROOT / \"src/sec_certs/data/reference_annotations/final/train.csv\")\n", + "annot_valid = pd.read_csv(REPO_ROOT / \"src/sec_certs/data/reference_annotations/final/valid.csv\")\n", + "annot_all = pd.concat([annot_test, annot_train, annot_valid])\n", + "n_irrelevant = len(annot_all[annot_all[\"label\"] == \"irrelevant\"])\n", + "irrelevant_percentage = n_irrelevant / len(annot_all) * 100\n", + "\n", + "print(f\"\\\\newcommand{{\\\\numAnnotIrrelevant}}{{{n_irrelevant}}}\")\n", + "print(f\"\\\\newcommand{{\\\\percentageAnnotIrrelevant}}{{${irrelevant_percentage:.1f}\\%$}}\")\n", + "\n", + "default_accuracy = 0.88\n", + "n_refs_to_predict = cc_df.n_refs.sum() - annot_all.shape[0]\n", + "n_refs_predicted_correct = n_refs_to_predict * default_accuracy\n", + "total_refs_correct = annot_all.shape[0] + n_refs_predicted_correct\n", + "total_refs_correct_ratio = 100 * total_refs_correct / cc_df.n_refs.sum()\n", + "print(f\"\\\\newcommand{{\\\\correctedPredPercentage}}{{${total_refs_correct_ratio:.0f}\\%$}}\")\n", + "\n", + "n_refs_pred = cc_df_prev.n_refs.sum()\n", + "n_refs_comp = cc_df_comp.n_refs.sum()\n", + "ratio_pred = 100 * n_refs_pred / (n_refs_pred + n_refs_comp)\n", + "ratio_comp = 100 * n_refs_comp / (n_refs_pred + n_refs_comp)\n", + "\n", + "smartcards_ratio = cc_df.loc[cc_df.category == SMARTCARD_CATEGORY].category.value_counts().sum() / cc_df.shape[0]\n", + "smartcard_related_ratio = (\n", + " cc_df.loc[cc_df.category.isin(CARD_RELATED_CAT)].category.value_counts().sum() / cc_df.shape[0]\n", + ")\n", + "others_ratio = cc_df.loc[cc_df.category.isin(OTHERS_CAT)].category.value_counts().sum() / cc_df.shape[0]\n", + "assert smartcards_ratio + smartcard_related_ratio + others_ratio == 1\n", + "\n", + "print(f\"\\\\newcommand{{\\\\smartcardsRatio}}{{${(100 * smartcards_ratio):.0f}\\%$}}\")\n", + "print(f\"\\\\newcommand{{\\\\smartcardRelatedRatio}}{{${(100 * smartcard_related_ratio):.0f}\\%$}}\")\n", + "print(f\"\\\\newcommand{{\\\\othersRatio}}{{${(100 * others_ratio):.0f}\\%$}}\")\n", + "\n", + "\n", + "print(f\"\\\\newcommand{{\\\\numcccerts}}{{{cc_df.shape[0]}}}\")\n", + "print(f\"\\\\newcommand{{\\\\refRichTotal}}{{{cc_df.loc[cc_df.n_refs > 0].shape[0]}}}\")\n", + "print(f\"\\\\newcommand{{\\\\refRichPercentage}}{{${cc_df.loc[cc_df.n_refs > 0].shape[0] * 100 / cc_df.shape[0]:.2f}\\%$}}\")\n", + "print(f\"\\\\newcommand{{\\\\numRefsTotal}}{{{cc_df.n_refs.sum()}}}\")\n", + "print(f\"\\\\newcommand{{\\\\compReuseRatio}}{{${ratio_comp:.2f}\\%$}}\")\n", + "print(f\"\\\\newcommand{{\\\\predRatio}}{{${ratio_pred:.2f}\\%$}}\")\n", + "if \"reach_list\" in locals():\n", + " print(f\"\\\\newcommand{{\\\\NumPositiveReachWhenArchived}}{{${len(reach_list)}$}}\")\n", + "else:\n", + " print(\n", + " \"Warning: `reach_list` variable not found. Cannot compute number of certificates with >0 reach on their archival date\"\n", + " )\n", + "\n", + "if \"df_problematic\" in locals():\n", + " print(f\"\\\\newcommand{{\\\\NumRefsToArchivedOnIssuanceDate}}{{${df_problematic.shape[0]}$}}\")\n", + "else:\n", + " print(\n", + " f\"Warning: `df_problematic` variable not found. Cannot compute number of certs referencing an archived certificate on their issuance date.\"\n", + " )\n", + "\n", + "\n", + "if \"aging_df\" in locals():\n", + " stale_nl = aging_df.loc[aging_df.scheme == \"NL\"].date_diff.gt(547).mean()\n", + " stale_de = aging_df.loc[aging_df.scheme == \"DE\"].date_diff.gt(547).mean()\n", + " stale_fr = aging_df.loc[aging_df.scheme == \"FR\"].date_diff.gt(547).mean()\n", + " print(f\"\\\\newcommand{{\\\\staleNL}}{{${(100 * stale_nl):.0f}\\%$}}\")\n", + " print(f\"\\\\newcommand{{\\\\staleDE}}{{${(100 * stale_de):.0f}\\%$}}\")\n", + " print(f\"\\\\newcommand{{\\\\staleFR}}{{${(100 * stale_fr):.0f}\\%$}}\")\n", + "else:\n", + " print(\"Warning: `aging_df` variable not found. Cannot compute aging statistics.\")\n", + "\n", + "print(\"\")\n", + "\n", + "print_category_commands(\"smartcard\")\n", + "print_category_commands(\"related\")\n", + "print_category_commands(\"others\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Correlation between EAL and number of references / reach" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eal_mapping = {cat: i for i, cat in enumerate(cc_df_comp.eal.dtype.categories)}\n", + "cc_df_comp_eal = cc_df_comp.loc[cc_df_comp.eal.notnull()]\n", + "cc_df_comp_eal[\"eal_int\"] = cc_df_comp_eal.eal.map(lambda x: eal_mapping[x])\n", + "\n", + "smartcard_trans_refs = stats.spearmanr(\n", + " cc_df_comp_eal.loc[cc_df_comp_eal.category == SMARTCARD_CATEGORY].eal_int,\n", + " cc_df_comp_eal.loc[cc_df_comp_eal.category == SMARTCARD_CATEGORY].n_trans_refs,\n", + " nan_policy=\"omit\",\n", + " alternative=\"less\",\n", + ")\n", + "smartcard_reach = stats.spearmanr(\n", + " cc_df_comp_eal.loc[cc_df_comp_eal.category == SMARTCARD_CATEGORY].eal_int,\n", + " cc_df_comp_eal.loc[cc_df_comp_eal.category == SMARTCARD_CATEGORY].n_in_trans_refs,\n", + " nan_policy=\"omit\",\n", + " alternative=\"greater\",\n", + ")\n", + "others_trans_refs = stats.spearmanr(\n", + " cc_df_comp_eal.loc[cc_df_comp_eal.category != SMARTCARD_CATEGORY].eal_int,\n", + " cc_df_comp_eal.loc[cc_df_comp_eal.category != SMARTCARD_CATEGORY].n_trans_refs,\n", + " nan_policy=\"omit\",\n", + " alternative=\"greater\",\n", + ")\n", + "others_reach = stats.spearmanr(\n", + " cc_df_comp_eal.loc[cc_df_comp_eal.category != SMARTCARD_CATEGORY].eal_int,\n", + " cc_df_comp_eal.loc[cc_df_comp_eal.category != SMARTCARD_CATEGORY].n_in_trans_refs,\n", + " nan_policy=\"omit\",\n", + " alternative=\"greater\",\n", + ")\n", + "\n", + "df_corr = pd.DataFrame(\n", + " {\n", + " \"transitive references corr\": [smartcard_trans_refs.statistic, others_trans_refs.statistic],\n", + " \"transitive references p-value\": [smartcard_trans_refs.pvalue, others_trans_refs.pvalue],\n", + " \"reach corr\": [smartcard_reach.statistic, others_reach.statistic],\n", + " \"reach p-value\": [smartcard_reach.pvalue, others_reach.pvalue],\n", + " },\n", + " index=[\"smartcards\", \"other categories\"],\n", + ")\n", + "\n", + "df_corr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examine re-evaluation graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nodes_with_long_paths = set()\n", + "min_path_len = 12\n", + "\n", + "for source in tqdm(prev_subgraph.nodes()):\n", + " ancestors = nx.ancestors(prev_subgraph, source)\n", + " if len(ancestors) > min_path_len - 1:\n", + " nodes_with_long_paths.add(source)\n", + " continue\n", + "\n", + "print(f\"Found: {len(nodes_with_long_paths)} nodes with path of len >={min_path_len}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examine sub-component graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plotly_draw(prev_subgraph, dset)\n", + "\n", + "cycles = list(nx.simple_cycles(comp_subgraph))\n", + "nodes_in_cycle = set(itertools.chain.from_iterable(cycles))\n", + "print(\n", + " f\"Found {len(cycles)} cycles with {len(nodes_in_cycle)} nodes. {100 * len(nodes_in_cycle) / len(comp_subgraph)}% of the nodes are in a cycle.\"\n", + ")\n", + "cycle_subg = comp_subgraph.subgraph(nodes_in_cycle)\n", + "plotly_draw(cycle_subg, dset)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Vulnerability propagation experiment\n", + "\n", + "- List all weakly connected components with >=10 nodes\n", + "- Choose the node with the highest reach inside the component\n", + "- See its reach" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "VULN_PROPAG_PATH = RESULTS_DIR / \"vuln_propagation_experiment\"\n", + "VULN_PROPAG_PATH.mkdir(exist_ok=True)\n", + "\n", + "\n", + "def get_component_graph(component: dict) -> str:\n", + " \"\"\"\n", + " Gets graph definition for graphviz-defined graph\n", + " Can be plotted interactively, e.g., with https://edotor.net/\n", + " \"\"\"\n", + "\n", + " def cert_id_to_numeric_name(cert_id: str) -> str:\n", + " return \"\".join([ch for ch in cert_id if ch.isdigit()])\n", + "\n", + " def cert_id_to_node_name(cert_id: str) -> str:\n", + " return cert_id_to_numeric_name(cert_id) + f' [label=\"{cert_id}\"]'\n", + "\n", + " def get_node_names_from_indirect_path(path: list) -> set[str]:\n", + " return {cert_id_to_node_name(x[\"cert_id\"]) for x in path}\n", + "\n", + " def get_indirect_edges_from_path(path: list) -> set[str]:\n", + " node_list = [cert_id_to_numeric_name(x[\"cert_id\"]) for x in path]\n", + " return {f\"{node_list[i]} -> {node_list[i+1]}\" for i in range(len(node_list) - 1)}\n", + "\n", + " # Nodes\n", + " root = cert_id_to_node_name(component[\"starting_node\"][\"cert_id\"])\n", + " direct_refs = [cert_id_to_node_name(x) for x in component[\"direct_refs\"]]\n", + " indirect_refs = set(\n", + " itertools.chain.from_iterable(\n", + " [get_node_names_from_indirect_path(x) for x in component[\"indirect_paths\"].values()]\n", + " )\n", + " )\n", + " # Edges\n", + " direct_edges = [\n", + " f\"{cert_id_to_numeric_name(x)} -> {cert_id_to_numeric_name(component['starting_node']['cert_id'])}\"\n", + " for x in component[\"direct_refs\"]\n", + " ]\n", + " indirect_edges = set(\n", + " itertools.chain.from_iterable([get_indirect_edges_from_path(x) for x in component[\"indirect_paths\"].values()])\n", + " )\n", + "\n", + " graph_string = \"digraph {\\n\\t# nodes\\n\"\n", + " graph_string += f\"\\t{root} [shape=box]\\n\"\n", + " graph_string += \"\\n\".join([f\"\\t{x}\" for x in direct_refs])\n", + " graph_string += \"\\n\"\n", + " graph_string += \"\\n\".join([f\"\\t{x}\" for x in indirect_refs])\n", + " graph_string += \"\\n\\n\\t# TODO: Add color to each edge, e.g., [color=red]\\n\"\n", + " graph_string += \"\\t# Color meaning: Red: vulnerable path, blue: predecessor path, green: non-vulnerable path\\n\"\n", + " graph_string += \"\\n\\t# edges\\n\"\n", + " graph_string += \"\\n\".join([f\"\\t{x}\" for x in direct_edges])\n", + " graph_string += \"\\n\\n\"\n", + " graph_string += \"\\n\".join([f\"\\t{x}\" for x in indirect_edges])\n", + " graph_string += \"\\n}\\n\"\n", + " return graph_string\n", + "\n", + "\n", + "components = list(nx.weakly_connected_components(comp_subgraph))\n", + "large_components = [list(x) for x in components if len(x) >= 10][1:] # Disable select huge component\n", + "cert_to_reach = cc_df_comp[[\"cert_id\", \"n_in_trans_refs\"]].set_index(\"cert_id\").to_dict()[\"n_in_trans_refs\"]\n", + "cert_to_in_refs = cc_df_comp[[\"cert_id\", \"n_in_refs\"]].set_index(\"cert_id\").to_dict()[\"n_in_refs\"]\n", + "\n", + "\n", + "def get_component_dict(df_comp: pd.DataFrame, component_nodes: list[str], comp_subgraph: nx.DiGraph) -> dict:\n", + " def get_url(cert_id: str) -> str:\n", + " try:\n", + " return \"https://seccerts.org/cc/\" + df_comp.loc[df_comp.cert_id == cert_id].index.values[0]\n", + " except IndexError:\n", + " print(f\"Cert id: {cert_id} not in df_comp\")\n", + "\n", + " nodes_sorted = sorted(component_nodes, key=lambda cert_id: cert_to_reach[cert_id], reverse=True)\n", + " starting_node = nodes_sorted[0]\n", + " starting_node_url = get_url(starting_node)\n", + " component_subgraph = comp_subgraph.subgraph(component_nodes)\n", + "\n", + " direct_refs = component_subgraph.predecessors(starting_node)\n", + " paths = nx.shortest_path(component_subgraph, target=starting_node).values()\n", + " indirect_paths = [x[:-1] for x in paths if len(x) > 2]\n", + " indirect_paths = [[{\"cert_id\": x, \"url\": get_url(x), \"security_func\": \"\"} for x in path] for path in indirect_paths]\n", + "\n", + " dct = {\n", + " \"starting_node\": {\"cert_id\": starting_node, \"dgst\": starting_node_url},\n", + " \"direct_refs\": {x: {\"url\": get_url(x), \"security_func\": \"\"} for x in direct_refs},\n", + " \"indirect_paths\": {x[0][\"cert_id\"]: x for x in indirect_paths},\n", + " }\n", + "\n", + " for cert_id in dct[\"direct_refs\"]:\n", + " assert cert_id in df_comp.loc[df_comp.cert_id == starting_node].in_refs.values[0]\n", + "\n", + " for path in dct[\"indirect_paths\"].values():\n", + " for cert in path:\n", + " assert cert[\"cert_id\"] in df_comp.loc[df_comp.cert_id == starting_node].in_trans_refs.values[0]\n", + "\n", + " return dct\n", + "\n", + "\n", + "def component_to_summary_row(component: dict, comp_subgraph: nx.DiGraph) -> list:\n", + " \"vector: [starting_node, starting_node_url, starting_node_description, n_direct_refs, n_indirect_refs, n_total_refs]\"\n", + " starting_node = component[\"starting_node\"][\"cert_id\"]\n", + " starting_node_url = component[\"starting_node\"][\"dgst\"]\n", + " starting_node_description = None\n", + "\n", + " direct_refs = set(comp_subgraph.predecessors(starting_node))\n", + " all_refs = set(\n", + " itertools.chain.from_iterable(list(nx.shortest_path(comp_subgraph, target=starting_node).values()))\n", + " ) - {starting_node}\n", + " indirect_refs = all_refs - direct_refs\n", + "\n", + " return [\n", + " starting_node,\n", + " starting_node_url,\n", + " starting_node_description,\n", + " len(direct_refs),\n", + " len(indirect_refs),\n", + " len(all_refs),\n", + " ]\n", + "\n", + "\n", + "def data_to_summary_table(data: list[dict], comp_subgraph: nx.DiGraph) -> pd.DataFrame:\n", + " rows = [[index] + component_to_summary_row(x, comp_subgraph) for index, x in enumerate(data)]\n", + " return pd.DataFrame(\n", + " rows,\n", + " columns=[\n", + " \"component_index\",\n", + " \"starting_node\",\n", + " \"starting_node_url\",\n", + " \"starting_node_description\",\n", + " \"n_direct_refs\",\n", + " \"n_indirect_refs\",\n", + " \"n_total_refs\",\n", + " ],\n", + " )\n", + "\n", + "\n", + "data_to_serialize = [get_component_dict(cc_df_comp, x, comp_subgraph) for x in large_components]\n", + "\n", + "comp_indices = list(range(len(large_components)))\n", + "random.shuffle(comp_indices)\n", + "adam_indices = comp_indices[::2]\n", + "lukasz_indices = comp_indices[1::2]\n", + "assert set(adam_indices) | set(lukasz_indices) == set(comp_indices)\n", + "\n", + "with Path(VULN_PROPAG_PATH / \"vuln_probability_experiment_lukasz.json\").open(\"w\") as handle:\n", + " json.dump([data_to_serialize[x] for x in lukasz_indices], handle, indent=2)\n", + "\n", + "with Path(VULN_PROPAG_PATH / \"vuln_probability_experiment_adam.json\").open(\"w\") as handle:\n", + " json.dump([data_to_serialize[x] for x in adam_indices], handle, indent=2)\n", + "\n", + "GRAPHS_ADAM_PATH = VULN_PROPAG_PATH / \"graphs_adam\"\n", + "GRAPHS_ADAM_PATH.mkdir(exist_ok=True)\n", + "for i in adam_indices:\n", + " comp = data_to_serialize[i]\n", + " graph_string = get_component_graph(comp)\n", + " with (GRAPHS_ADAM_PATH / f\"component_{i}.dot\").open(\"w\") as handle:\n", + " handle.write(graph_string)\n", + "\n", + "GRAPHS_LUKASZ_PATH = VULN_PROPAG_PATH / \"graphs_lukasz\"\n", + "GRAPHS_LUKASZ_PATH.mkdir(exist_ok=True)\n", + "for i in lukasz_indices:\n", + " comp = data_to_serialize[i]\n", + " graph_string = get_component_graph(comp)\n", + " with (GRAPHS_LUKASZ_PATH / f\"component_{i}.dot\").open(\"w\") as handle:\n", + " handle.write(graph_string)\n", + "\n", + "df_summary_table = data_to_summary_table(data_to_serialize, comp_subgraph)\n", + "df_summary_table.to_csv(VULN_PROPAG_PATH / \"summary_table.csv\", index=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute time delta between certification dates in component reuse relationship\n", + "\n", + "For each pair `(referencing_certificate, referenced_certificate)`, compute the difference in the certification dates.\n", + "Group by scheme" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def compute_composite_evaluation_aging(cc_df_comp: pd.DataFrame, dset: CCDataset) -> pd.DataFrame:\n", + " def find_comp_in_document(document_path: Path) -> bool:\n", + " phrases = [\n", + " \"composite product evaluation\",\n", + " \"L’évaluation en composition\",\n", + " \"Composite product evaluation for Smart Cards and similar devices\",\n", + " \"\\[COMP\\]\",\n", + " \"is the composite product\",\n", + " \"valuation en composition\",\n", + " ]\n", + "\n", + " with document_path.open(\"r\") as handle:\n", + " data = handle.read()\n", + "\n", + " return any(re.search(phrase, data, re.IGNORECASE) for phrase in phrases)\n", + "\n", + " def find_comp_in_cert(cert_dgst: str, cc_dset: CCDataset) -> bool:\n", + " cert = cc_dset[cert_dgst]\n", + " if cert.state.report_txt_path.exists() and find_comp_in_document(cert.state.report_txt_path):\n", + " return True\n", + "\n", + " if cert.state.st_txt_path.exists() and find_comp_in_document(cert.state.st_txt_path):\n", + " return True\n", + "\n", + " return False\n", + "\n", + " def compute_date_diff(date_a: pd.Timestamp, date_b: pd.Timestamp) -> int | float:\n", + " if pd.isnull(date_a) or pd.isnull(date_b):\n", + " return np.nan\n", + " return (date_a - date_b).days\n", + "\n", + " def compute_referenced_cert_date(row) -> pd.Timestamp:\n", + " \"\"\"\n", + " This computes the most plausible date of referenced certificate.\n", + " All maintenance updates that are older than the not_valid_before of the referencing certificates are considered\n", + " \"\"\"\n", + " candidate_dates = [cert_id_to_date[row.refs]] + cert_id_to_main_dates.get(row.refs, [])\n", + " candidate_dates = [pd.Timestamp(x) for x in candidate_dates]\n", + " filtered_dates = [x for x in candidate_dates if x <= row.not_valid_before]\n", + " if not filtered_dates:\n", + " return np.nan\n", + " return max(filtered_dates)\n", + "\n", + " cert_id_to_date = {x.heuristics.cert_id: x.not_valid_before for x in dset}\n", + " cc_dgst_to_cert_id = {x.dgst: x.heuristics.cert_id for x in dset}\n", + " dgst_to_main_dates = {x.dgst: [] for x in dset}\n", + " for cert in dset:\n", + " dgst_to_main_dates[cert.dgst] = (\n", + " [x.maintenance_date for x in cert.maintenance_updates] if cert.maintenance_updates else []\n", + " )\n", + " cert_id_to_main_dates = {cc_dgst_to_cert_id[dgst]: dates for dgst, dates in dgst_to_main_dates.items()}\n", + " schemes_to_consider = {\"FR\", \"DE\", \"NL\", \"ES\"}\n", + " is_composite_partial = functools.partial(find_comp_in_cert, cc_dset=dset)\n", + " df = (\n", + " (\n", + " cc_df_comp.loc[(cc_df_comp.n_refs > 0) & (cc_df_comp.category == SMARTCARD_CATEGORY)]\n", + " .copy()\n", + " .explode(column=\"refs\")\n", + " )\n", + " .assign(\n", + " ref_cert_date=lambda df_: df_.apply(compute_referenced_cert_date, axis=1),\n", + " is_composite=lambda df_: df_.index.map(is_composite_partial),\n", + " date_diff=lambda df_: df_.apply(lambda x: compute_date_diff(x.not_valid_before, x.ref_cert_date), axis=1),\n", + " )\n", + " .loc[lambda df_: (df_.is_composite & df_.scheme.isin(schemes_to_consider))]\n", + " .assign(scheme=lambda df_: df_.scheme.cat.set_categories(schemes_to_consider))\n", + " )\n", + "\n", + " df.to_csv(RESULTS_DIR / \"ecdf_archival_data.csv\")\n", + "\n", + " return df\n", "\n", - "# print(\"\")\n", - "# print(f\"\\\\newcommand{{\\\\numCCDirectRefsSameCategory}}{{{(exploded_with_refs.category == exploded_with_refs.ref_category).sum()}}}\")\n", - "# print(f\"\\\\newcommand{{\\\\numCCDirectRefsOtherCategory}}{{{(exploded_with_refs.category != exploded_with_refs.ref_category).sum()}}}\")\n", - "# print(f\"\\\\newcommand{{\\\\numCCDirectRefs}}{{{len(exploded_with_refs)}}}\")\n", - "# print(f\"\\\\newcommand{{\\\\numCCDirectRefsFromSmartcards}}{{{(exploded_with_refs.category == 'ICs, Smart Cards and Smart Card-Related Devices and Systems').sum()}}}\")\n", "\n", - "# print(\"\")\n", - "# print(f\"\\\\newcommand{{\\\\numCCUSReferencing}}{{{len(df_id_rich.loc[(df_id_rich.scheme == 'US') & (df_id_rich.directly_referencing.notnull())])}}}\")\n", - "# print(f\"\\\\newcommand{{\\\\numCCUS}}{{{len(df_id_rich.loc[(df_id_rich.scheme == 'US')])}}}\")\n" + "aging_df = compute_composite_evaluation_aging(cc_df_comp, dset)" ] } ], @@ -1123,7 +2096,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.11.6" }, "vscode": { "interpreter": { diff --git a/src/sec_certs/data/reference_annotations/readme.md b/src/sec_certs/data/reference_annotations/readme.md index 8521eead..8db71345 100644 --- a/src/sec_certs/data/reference_annotations/readme.md +++ b/src/sec_certs/data/reference_annotations/readme.md @@ -49,6 +49,8 @@ These can be further merged into the following super-categories: - **Previous version**: `previous_version` and `re-evaluation` - **None**: `None` or `irrelevant` +The complete formal codebook can be found at [Overleaf](https://www.overleaf.com/read/dmcpzcfpgrdg#2d938a). + ### Inter-annotator agreement The inter-annotator agreement is measured both with Cohen's Kappa and with percentage. The results are as follows: |
