Skip to content

Commit 53e1764

Browse files
danimtbAbrilRBSfranramirez688czoido
authored
Add docs for package signing openssl example (#4376)
* Add docs for package signing openssl example * add examples section * Update examples/extensions/package_signing/package_signing_with_openssl.rst Co-authored-by: Abril Rincón Blanco <5364255+AbrilRBS@users.noreply.github.com> * Update examples/extensions/package_signing/package_signing_with_openssl.rst Co-authored-by: Francisco Ramírez <franchuti688@gmail.com> * Update examples/extensions/package_signing/package_signing_with_openssl.rst Co-authored-by: Francisco Ramírez <franchuti688@gmail.com> * Update examples/extensions/package_signing/package_signing_with_openssl.rst Co-authored-by: Francisco Ramírez <franchuti688@gmail.com> * Update reference/extensions/package_signing.rst Co-authored-by: Francisco Ramírez <franchuti688@gmail.com> * Update examples/extensions/package_signing/package_signing_with_openssl.rst Co-authored-by: Francisco Ramírez <franchuti688@gmail.com> * Update examples/extensions/package_signing/package_signing_with_openssl.rst Co-authored-by: Carlos Zoido <mrgalleta@gmail.com> * Update examples/extensions/package_signing/package_signing_with_openssl.rst Co-authored-by: Carlos Zoido <mrgalleta@gmail.com> * review * add code tour * remove line --------- Co-authored-by: Abril Rincón Blanco <5364255+AbrilRBS@users.noreply.github.com> Co-authored-by: Francisco Ramírez <franchuti688@gmail.com> Co-authored-by: Carlos Zoido <mrgalleta@gmail.com>
1 parent 587d064 commit 53e1764

File tree

4 files changed

+273
-0
lines changed

4 files changed

+273
-0
lines changed

examples/extensions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ Conan extensions examples
1414
extensions/commands/custom_commands
1515
extensions/deployers/builtin_deployers
1616
extensions/deployers/custom_deployers
17+
extensions/package_signing/package_signing
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.. _examples_extensions_package_signing:
2+
3+
4+
Package Signing Plugin
5+
======================
6+
7+
8+
.. toctree::
9+
:maxdepth: 2
10+
11+
12+
package_signing_with_openssl
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
.. _examples_extensions_package_signing_openssl:
2+
3+
Signing packages with OpenSSL
4+
=============================
5+
6+
This is an example of a Package Signing Plugin implementation using the `OpensSSL digest tool <https://docs.openssl.org/3.1/man1/openssl-dgst/>`_.
7+
You will need to have ``openssl`` installed at the system level and available in your ``PATH``.
8+
9+
.. include:: ../../../common/experimental_warning.inc
10+
11+
This example is available in the examples2 repository: `examples/extensions/plugins/openssl_sign <https://github.com/conan-io/examples2/tree/main/examples/extensions/plugins/openssl_sign>`_.
12+
13+
.. note::
14+
15+
OpenSSL is used here for demonstration purposes only. The Package Signing
16+
plugin mechanism is backend-agnostic, and you could implement a similar
17+
plugin using other tools available in your system (for example, ``gpg``),
18+
with minimal changes to the signing and verification commands.
19+
20+
Generating the signing keys
21+
+++++++++++++++++++++++++++
22+
23+
To sign and verify the packages using the plugin, first, we will need a public and private key.
24+
25+
To generate the keys using the ``openssl`` executable, we can run:
26+
27+
.. code-block:: bash
28+
29+
$ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
30+
31+
This will generate the private key used to sign the packages.
32+
33+
Now, we can get the public key from it with this command:
34+
35+
.. code-block:: bash
36+
37+
$ openssl pkey -in private_key.pem -pubout -out public_key.pem
38+
39+
The plugin will use this public key to verify the packages.
40+
41+
42+
Configuring the plugin
43+
++++++++++++++++++++++
44+
45+
.. caution::
46+
47+
This example stores a private key next to the plugin for simplicity. **Do not do this in production**.
48+
Instead, load the signing key from environment variables or a secret manager, or delegate signing to a remote signing service.
49+
**Always keep the private key out of the Conan cache and out of source control**.
50+
51+
1. Copy the ``examples/extensions/plugins/openssl_sign/sign.py`` file to your Conan home at ``CONAN_HOME/extensions/plugins/sign/sign.py``.
52+
53+
1. Copy the ``sign.py`` file to your Conan home:
54+
55+
``CONAN_HOME/extensions/plugins/sign/sign.py``
56+
57+
2. Place the generated keys in a folder named after your provider
58+
(``my-organization`` in this example), next to ``sign.py``:
59+
60+
Your final folder structure should look like this:
61+
62+
.. code-block:: text
63+
64+
CONAN_HOME/
65+
└── extensions/
66+
└── plugins/
67+
└── sign/
68+
├── sign.py
69+
└── my-organization/
70+
├── private_key.pem
71+
└── public_key.pem
72+
73+
The ``my-organization`` folder serves as the **provider** in this example, and it is used by the plugin to identify the organization that owns the keys.
74+
75+
.. tip::
76+
77+
The Package Signing plugin is installed in the Conan configuration folder, so they can be easily distributed as part of the client
78+
configuration using the :ref:`conan config install<reference_commands_conan_config_install>` command.
79+
80+
81+
Implementation
82+
++++++++++++++
83+
84+
The plugin's implementation is very straightforward.
85+
86+
For signing packages, the `sign()` function is defined, where the packages are signed by the :command:`openssl dgst` command:
87+
88+
.. code-block:: python
89+
90+
def sign(ref, artifacts_folder, signature_folder, **kwargs)
91+
...
92+
openssl_sign_cmd = [
93+
"openssl",
94+
"dgst",
95+
"-sha256",
96+
"-sign", privkey_filepath,
97+
"-out", signature_filepath,
98+
manifest_filepath
99+
]
100+
try:
101+
_run_command(openssl_sign_cmd)
102+
ConanOutput().success(f"Package signed for reference {ref}")
103+
except Exception as exc:
104+
raise ConanException(f"Error signing artifact: {exc}")
105+
...
106+
107+
There, the manifest ``pkgsign-manifest.json`` (created right before ``sign()`` function is called) is used to sign the package,
108+
as it contains the filenames and checksums of the artifacts of the package.
109+
110+
The signature file is saved into the ``signature_filepath`` (the signature folder at ``<package_folder>/metadata/sign``), and finally, the
111+
metadata of the signature is returned as a dictionary in a list:
112+
113+
.. code-block:: python
114+
115+
def sign(ref, artifacts_folder, signature_folder, **kwargs)
116+
...
117+
return [{"method": "openssl-dgst",
118+
"provider": "my-organization",
119+
"sign_artifacts": {
120+
"manifest": "pkgsign-manifest.json",
121+
"signature": signature_filename}}]
122+
123+
This information saved in a file ``pkgsign-signatures.json`` placed in the signature folder, so it can be used in the `verify()` to
124+
verify the package signature against the correct provider keys, with the correct signing method (``openssl-dgst`` for this example)
125+
and using the signature files in ``sign_artifacts``.
126+
127+
For verifying packages, the `verify()` function is defined.
128+
129+
First, the ``pkgsign-signatures.json`` is loaded to read the metadata of the signatures (multiple signatures are supported):
130+
131+
.. code-block:: python
132+
133+
def verify(ref, artifacts_folder, signature_folder, files, **kwargs):
134+
...
135+
signatures = json.loads(f.read()).get("signatures")
136+
...
137+
for signature in signatures:
138+
signature_filename = signature.get("sign_artifacts").get("signature")
139+
signature_filepath = os.path.join(signature_folder, signature_filename)
140+
...
141+
provider = signature.get("provider")
142+
signature_method = signature.get("method")
143+
...
144+
145+
Then, the ``provider`` information is used to select the correct public key for verification that use the right signature verification
146+
``method`` (``openssl-dgst`` for this example) and run the :command:`openssl dgst -verify` command:
147+
148+
.. code-block:: python
149+
150+
def verify(ref, artifacts_folder, signature_folder, files, **kwargs):
151+
...
152+
openssl_verify_cmd = [
153+
"openssl",
154+
"dgst",
155+
"-sha256",
156+
"-verify", pubkey_filepath,
157+
"-signature", signature_filepath,
158+
manifest_filepath,
159+
]
160+
try:
161+
_run_command(openssl_verify_cmd)
162+
ConanOutput().success(f"Package verified for reference {ref}")
163+
except Exception as exc:
164+
raise ConanException(f"Error verifying signature {signature_filepath}: {exc}")
165+
166+
The ``verify()`` function does not return any value in case the package is correct. If the verification fails, then a ``ConanException()`` should be raised.
167+
168+
Signing packages
169+
++++++++++++++++
170+
171+
Now that the plugin is configured, we can create a package and sign it afterwards:
172+
173+
.. code-block:: bash
174+
175+
$ conan new cmake_lib -d name=hello -d version=1.0
176+
$ conan create
177+
178+
For signing the recipe and package, use the dedicated command:
179+
180+
.. code-block:: bash
181+
182+
$ conan cache sign hello/1.0
183+
184+
hello/1.0: Compressing conan_sources.tgz
185+
hello/1.0:dee9f7f985eb1c20e3c41afaa8c35e2a34b5ae0b: Compressing conan_package.tgz
186+
Running command: openssl dgst -sha256 -sign C:\Users\user\.conan2\extensions\plugins\sign\my-organization\private_key.pem -out C:\Users\user\.conan2\p\hello092ffa809a9a1\d\metadata\sign\pkgsign-manifest.json.sig C:\Users\user\.conan2\p\hello092ffa809a9a1\d\metadata\sign\pkgsign-manifest.json
187+
Package signed for reference hello/1.0
188+
Running command: openssl dgst -sha256 -sign C:\Users\user\.conan2\extensions\plugins\sign\my-organization\private_key.pem -out C:\Users\user\.conan2\p\b\hello5b13c694fef4a\d\metadata\sign\pkgsign-manifest.json.sig C:\Users\user\.conan2\p\b\hello5b13c694fef4a\d\metadata\sign\pkgsign-manifest.json
189+
Package signed for reference hello/1.0:dee9f7f985eb1c20e3c41afaa8c35e2a34b5ae0b
190+
[Package sign] Results:
191+
192+
hello/1.0
193+
revisions
194+
53321bba8793db6fea5ea1a98dd6f3d6
195+
packages
196+
dee9f7f985eb1c20e3c41afaa8c35e2a34b5ae0b
197+
revisions
198+
4b1eaf2e27996cb39cb3774f185fcd8e
199+
200+
[Package sign] Summary: OK=2, FAILED=0
201+
202+
As you see, the command is executing the ``sign()`` function of the plugin that uses the ``openssl`` executable to sign the recipe and the package with a command similar to:
203+
204+
.. code-block:: bash
205+
206+
$ openssl dgst -sha256 -sign private_key.pem -out pkgsign-manifest.json.sig pkgsign-manifest.json
207+
208+
And it is also using the conan-generated ``pkgsign-manifest.json`` file to create the signature.
209+
You can read more about this manifest file at :ref:`reference_extensions_package_signing`.
210+
211+
212+
Verifying packages
213+
++++++++++++++++++
214+
215+
For verifying the recipe and package, use the dedicated command:
216+
217+
.. code-block:: bash
218+
219+
$ conan cache verify hello/1.0
220+
221+
[Package sign] Checksum verified for file conan_sources.tgz (4ce077cbea9ce87a481b5d6dbb50bd791f4e37e931754cdeb40aeb017baed66c).
222+
[Package sign] Checksum verified for file conanfile.py (0ec44c268f0f255ab59a246c3d13ae6dbd487dea7635b584236b701047f92ba0).
223+
[Package sign] Checksum verified for file conanmanifest.txt (f7f00bb74ed8469a367ed02faded3c763130da9b63dae23916b2a4f099625b15).
224+
Running command: openssl dgst -sha256 -verify C:\Users\user\.conan2\extensions\plugins\sign\my-organization\public_key.pem -signature C:\Users\user\.conan2\p\hello092ffa809a9a1\d\metadata\sign\pkgsign-manifest.json.sig C:\Users\user\.conan2\p\hello092ffa809a9a1\d\metadata\sign\pkgsign-manifest.json
225+
Package verified for reference hello/1.0
226+
[Package sign] Checksum verified for file conan_package.tgz (5cc1b9e330fe5bb6ad5904db45d78ecd6bdc71bcc18eff8d19a1ed126ba5a5aa).
227+
[Package sign] Checksum verified for file conaninfo.txt (f80367b17176346e10640ed813d6d2f1c45ed526822ff71066696179d16e2f2f).
228+
[Package sign] Checksum verified for file conanmanifest.txt (91429ce32c2d0a99de6459a589ac9c35933ed65165ee5c564b6534da57fdfa65).
229+
Running command: openssl dgst -sha256 -verify C:\Users\user\.conan2\extensions\plugins\sign\my-organization\public_key.pem -signature C:\Users\user\.conan2\p\b\hello5b13c694fef4a\d\metadata\sign\pkgsign-manifest.json.sig C:\Users\user\.conan2\p\b\hello5b13c694fef4a\d\metadata\sign\pkgsign-manifest.json
230+
Package verified for reference hello/1.0:dee9f7f985eb1c20e3c41afaa8c35e2a34b5ae0b
231+
[Package sign] Results:
232+
233+
hello/1.0
234+
revisions
235+
53321bba8793db6fea5ea1a98dd6f3d6
236+
packages
237+
dee9f7f985eb1c20e3c41afaa8c35e2a34b5ae0b
238+
revisions
239+
4b1eaf2e27996cb39cb3774f185fcd8e
240+
241+
[Package sign] Summary: OK=2, FAILED=0
242+
243+
As you see, Conan is performing an internal checksum verification for the files and calling the ``verify()`` function of the plugin that uses
244+
the ``openssl`` executable to verify the recipe and the package with a command similar to:
245+
246+
.. code-block:: bash
247+
248+
$ openssl dgst -sha256 -verify public_key.pem -signature pkgsign-manifest.json.sig pkgsign-manifest.json
249+
250+
.. seealso::
251+
252+
If you want to create your own package signing plugin, check the reference documentation at
253+
:ref:`reference_extensions_package_signing`.

reference/extensions/package_signing.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,10 @@ Here is a usual flow for signing and verifying packages:
211211
The :command:`conan upload` command **will not automatically sign** the packages since Conan 2.26.0.
212212
Please make sure to use the :command:`conan cache sign` command to **sign the packages before uploading them**,
213213
and **update your plugin** to conform to the new implementation.
214+
215+
Plugin implementation examples
216+
==============================
217+
218+
Here you can find some implementation examples of the plugin so they can serve as guidance to develop your own:
219+
220+
- :ref:`examples_extensions_package_signing_openssl`.

0 commit comments

Comments
 (0)