aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamjanovsky2024-02-20 10:49:57 +0100
committerGitHub2024-02-20 10:49:57 +0100
commit78d2cfd8711b56c4d06b7c3bc24cf94b3743cd91 (patch)
tree17284715b74fb082ae81b5848e0d7e8e97a2c361
parent609ee028fad536c7a7e3d83b004b0617ebaaf460 (diff)
parentcdb54b71d53e8378457822b4a95f1b3fa78b1add (diff)
downloadsec-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.md8
-rw-r--r--docs/configuration.md7
-rw-r--r--notebooks/cc/paper2_plots.ipynb403
-rw-r--r--notebooks/cc/reference_annotations/prediction.ipynb53
-rw-r--r--notebooks/cc/references.ipynb1301
-rw-r--r--src/sec_certs/data/reference_annotations/readme.md2
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: