spsdk-el2go 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+
2
+
3
+ BSD 3-Clause "New" or "Revised" License
4
+
5
+ Copyright (c) 2026, NXP
6
+ All rights reserved.
7
+
8
+ Redistribution and use in source and binary forms, with or without modification,
9
+ are permitted provided that the following conditions are met:
10
+
11
+ * Redistributions of source code must retain the above copyright notice, this
12
+ list of conditions and the following disclaimer.
13
+
14
+ * Redistributions in binary form must reproduce the above copyright notice, this
15
+ list of conditions and the following disclaimer in the documentation and/or
16
+ other materials provided with the distribution.
17
+
18
+ * Neither the name of the copyright holder nor the names of its
19
+ contributors may be used to endorse or promote products derived from this
20
+ software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
26
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
29
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
30
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31
+ OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
@@ -0,0 +1,7 @@
1
+ include LICENSE
2
+ include README.md
3
+
4
+ recursive-include tests *
5
+ recursive-exclude * __pycache__
6
+ recursive-exclude * *.py[co]
7
+ recursive-exclude .github *
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: spsdk-el2go
3
+ Version: 0.2.0
4
+ Summary: SPSDK Service providers powered by EdgeLock2GO
5
+ Author: NXP
6
+ Maintainer-email: NXP <spsdk@nxp.com>
7
+ License-Expression: BSD-3-Clause
8
+ Project-URL: Homepage, https://github.com/nxp-mcuxpresso/spsdk_plugins/tree/master/el2go
9
+ Project-URL: Issues, https://github.com/nxp-mcuxpresso/spsdk_plugins/issues
10
+ Keywords: NXP,SPSDK,el2go
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: spsdk>2.1
24
+ Requires-Dist: platformdirs
25
+ Dynamic: license-file
26
+
27
+
28
+ # EdgeLock 2 GO service providers for SPSDK
29
+
30
+ Package contains multiple SPSDK service providers powered by EL2GO
31
+
32
+ * Free software: BSD-3-Clause
33
+ * Documentation: https://spsdk-el2go.readthedocs.io.
34
+
35
+
36
+ Installation
37
+ ------------
38
+
39
+ * Activate virtual env, where you have SPSDK
40
+ - if SPSDK is not installed, the latest one will be installed automatically from [PyPI](https://pypi.org/project/spsdk/)
41
+ * `pip install spsdk-el2go`
42
+
43
+
44
+ ## Signature Provider
45
+
46
+ Everywhere in SPSDK where a signature (private key) is required, a signature provider can be used instead.
47
+ Using this plugin, all signing operations can be delegated to EL2GO. Private keys stay securely stored in EL2GO servers.
48
+
49
+
50
+ ### Configuration:
51
+
52
+ SPSDK Service providers are configured via a Service configuration string.
53
+ Format: `type=<service_id>[;param1=value1][;param2=value2]`
54
+ In this case the service identifier is `el2go` thus the config string starts with `type=el2go`
55
+
56
+ EL2GO signature provider uses the following configuration options:
57
+ * `url`: Base EL2GO API URL (default: https://api.edgelock2go.com)
58
+ * `api_key`: EL2GO API key. Can be placed in env variable (staring with $) or a file.
59
+ * E.g.: `~/.config/el2go/token.txt`, `$EL2GO_TOKEN`, `$PATH_TO_FILE_WITH_API_KEY`
60
+ * make sure your API token has Code Signing permissions set (both View and Manage are required)
61
+ * `key_set_id` (number): ID of the Key Set in EL2GO server
62
+ * `key_id` (number): ID of the key used for signing operation
63
+ * `config_path`: path to a YAML file containing some or all the config options above
64
+
65
+ These configuration options can be specified in service configuration string
66
+
67
+ type=el2go;url=https://api.edgelock2go.com;api_key=~/.config/el2go/token.txt;key_set_id=4;key_id=1
68
+
69
+ or can be passed via a config file
70
+
71
+ type=el2go;config_path=el2go_config.yaml
72
+
73
+ or a combination of both, overriding/adding options from config file in config string:
74
+
75
+ type=el2go;config_path=el2go_config.yaml;key_id=2
76
+
77
+ To help with setting up the config file, you may use `el2go-helper` application which is bundled with this python package.
78
+ Run `el2go-helper --help` to see more info.
79
+
80
+ ### Usage
81
+
82
+
83
+ * Use the configuration string in any SPSDK YAML config file, where a private key is normally provided
84
+ * e.g.: in MBI config file: `signer: type=el2go;config_path=el2go_config.yaml`
85
+
86
+ * Use SPSDK application as you would normally do
87
+ * e.g.: `nxpimage mbi export -c mbi_config.yaml`
88
+
89
+
90
+ ## SB Key derivation provider
91
+
92
+ Derive encryption key used for SB3 and SB4 files from key stored in EdgeLock 2 GO server
93
+
94
+
95
+ ### Configuration
96
+
97
+ Exactly the same as for Signature Provider
98
+
99
+
100
+ ### Usage
101
+
102
+ * Use the configuration string in SB3 or SB4 YAML config file, where the CUST_MK_SK/SBKEK is provided
103
+ * e.g.: `containerKeyBlobEncryptionKey: type=el2go;config_path=el2go_config.yaml;key_id=20`
104
+
105
+ * Use SPSDK's nxpimage application as you would normally do
106
+ * e.g.: `nxpimage sb31 export -c sb_config.yaml`
107
+
108
+ ## Public key provider
109
+
110
+ Allow to specify any public key in YAML config file as a reference to public key stored in EdgeLock 2 GO
111
+
112
+ **Coming soon**
113
+
@@ -0,0 +1,87 @@
1
+
2
+ # EdgeLock 2 GO service providers for SPSDK
3
+
4
+ Package contains multiple SPSDK service providers powered by EL2GO
5
+
6
+ * Free software: BSD-3-Clause
7
+ * Documentation: https://spsdk-el2go.readthedocs.io.
8
+
9
+
10
+ Installation
11
+ ------------
12
+
13
+ * Activate virtual env, where you have SPSDK
14
+ - if SPSDK is not installed, the latest one will be installed automatically from [PyPI](https://pypi.org/project/spsdk/)
15
+ * `pip install spsdk-el2go`
16
+
17
+
18
+ ## Signature Provider
19
+
20
+ Everywhere in SPSDK where a signature (private key) is required, a signature provider can be used instead.
21
+ Using this plugin, all signing operations can be delegated to EL2GO. Private keys stay securely stored in EL2GO servers.
22
+
23
+
24
+ ### Configuration:
25
+
26
+ SPSDK Service providers are configured via a Service configuration string.
27
+ Format: `type=<service_id>[;param1=value1][;param2=value2]`
28
+ In this case the service identifier is `el2go` thus the config string starts with `type=el2go`
29
+
30
+ EL2GO signature provider uses the following configuration options:
31
+ * `url`: Base EL2GO API URL (default: https://api.edgelock2go.com)
32
+ * `api_key`: EL2GO API key. Can be placed in env variable (staring with $) or a file.
33
+ * E.g.: `~/.config/el2go/token.txt`, `$EL2GO_TOKEN`, `$PATH_TO_FILE_WITH_API_KEY`
34
+ * make sure your API token has Code Signing permissions set (both View and Manage are required)
35
+ * `key_set_id` (number): ID of the Key Set in EL2GO server
36
+ * `key_id` (number): ID of the key used for signing operation
37
+ * `config_path`: path to a YAML file containing some or all the config options above
38
+
39
+ These configuration options can be specified in service configuration string
40
+
41
+ type=el2go;url=https://api.edgelock2go.com;api_key=~/.config/el2go/token.txt;key_set_id=4;key_id=1
42
+
43
+ or can be passed via a config file
44
+
45
+ type=el2go;config_path=el2go_config.yaml
46
+
47
+ or a combination of both, overriding/adding options from config file in config string:
48
+
49
+ type=el2go;config_path=el2go_config.yaml;key_id=2
50
+
51
+ To help with setting up the config file, you may use `el2go-helper` application which is bundled with this python package.
52
+ Run `el2go-helper --help` to see more info.
53
+
54
+ ### Usage
55
+
56
+
57
+ * Use the configuration string in any SPSDK YAML config file, where a private key is normally provided
58
+ * e.g.: in MBI config file: `signer: type=el2go;config_path=el2go_config.yaml`
59
+
60
+ * Use SPSDK application as you would normally do
61
+ * e.g.: `nxpimage mbi export -c mbi_config.yaml`
62
+
63
+
64
+ ## SB Key derivation provider
65
+
66
+ Derive encryption key used for SB3 and SB4 files from key stored in EdgeLock 2 GO server
67
+
68
+
69
+ ### Configuration
70
+
71
+ Exactly the same as for Signature Provider
72
+
73
+
74
+ ### Usage
75
+
76
+ * Use the configuration string in SB3 or SB4 YAML config file, where the CUST_MK_SK/SBKEK is provided
77
+ * e.g.: `containerKeyBlobEncryptionKey: type=el2go;config_path=el2go_config.yaml;key_id=20`
78
+
79
+ * Use SPSDK's nxpimage application as you would normally do
80
+ * e.g.: `nxpimage sb31 export -c sb_config.yaml`
81
+
82
+ ## Public key provider
83
+
84
+ Allow to specify any public key in YAML config file as a reference to public key stored in EdgeLock 2 GO
85
+
86
+ **Coming soon**
87
+
@@ -0,0 +1,125 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools]
6
+ packages = ["spsdk_el2go"]
7
+
8
+ [tool.setuptools.dynamic]
9
+ version = { attr = "spsdk_el2go.__version__" }
10
+
11
+
12
+ [project]
13
+ dynamic = ["version"]
14
+ name = "spsdk-el2go"
15
+ description = "SPSDK Service providers powered by EdgeLock2GO"
16
+
17
+ requires-python = ">= 3.10"
18
+ dependencies = ["spsdk>2.1", "platformdirs"]
19
+ authors = [{ name = "NXP" }]
20
+ maintainers = [{ name = "NXP", email = "spsdk@nxp.com" }]
21
+ readme = { file = "README.md", content-type = "text/markdown" }
22
+ license = "BSD-3-Clause"
23
+ license-files = ["LICENSE"]
24
+
25
+ keywords = ["NXP", "SPSDK", "el2go"]
26
+ classifiers = [
27
+ "Development Status :: 2 - Pre-Alpha",
28
+ "Intended Audience :: Developers",
29
+ "Natural Language :: English",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Programming Language :: Python :: 3.14",
36
+ ]
37
+
38
+ [project.scripts]
39
+ "el2go-helper" = "spsdk_el2go.cli:main"
40
+
41
+ [project.entry-points]
42
+ "spsdk.sp" = { el2go = "spsdk_el2go.signature_provider" }
43
+ "spsdk.sb31kdp" = { el2go = "spsdk_el2go.key_derivation_provider" }
44
+ pyinstaller40 = { hook-dirs = "spsdk_el2go.__pyinstaller:get_hook_dirs" }
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/nxp-mcuxpresso/spsdk_plugins/tree/master/el2go"
48
+ Issues = "https://github.com/nxp-mcuxpresso/spsdk_plugins/issues"
49
+
50
+ [tool.bumpversion]
51
+ current_version = "0.2.0"
52
+
53
+ [[tool.bumpversion.files]]
54
+ filename = "spsdk_el2go/__init__.py"
55
+ search = '__version__ = "{current_version}"'
56
+ replace = '__version__ = "{new_version}"'
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ junit_family = "xunit1"
61
+
62
+ [tool.black]
63
+ line-length = 100
64
+ target-version = ["py310", "py311", "py312", "py313", "py314"]
65
+ include = '\.pyi?$'
66
+
67
+ [tool.isort]
68
+ multi_line_output = 3
69
+ include_trailing_comma = true
70
+ force_grid_wrap = 0
71
+ use_parentheses = true
72
+ ensure_newline_before_comments = true
73
+ line_length = 100
74
+
75
+ [tool.mypy]
76
+ disallow_untyped_defs = true
77
+ ignore_missing_imports = true
78
+ warn_unused_ignores = true
79
+
80
+ [tool.pydocstyle]
81
+ convention = "google"
82
+ add_ignore = "D105,D301"
83
+ match = '(?!test_|__version__).*\.py'
84
+
85
+ [tool.pylint]
86
+ format = { max-line-length = 120 }
87
+ "messages control" = { disable = ["logging-fstring-interpolation"] }
88
+
89
+ [tool.bandit]
90
+ skips = ["B101"]
91
+
92
+ [tool.ruff]
93
+ line-length = 100
94
+
95
+ [tool.ruff.lint]
96
+ ignore = ["EXE001", "I001", "N999", "UP009"]
97
+
98
+ [tool.nxp_codecheck]
99
+ git_parent_branch = "origin/master"
100
+ output_directory = "reports"
101
+ default_check_paths = ["spsdk_el2go"]
102
+ checkers = [
103
+ { "pytest" = { kwargs = { disable_xdist = true } } },
104
+ { "gitcov" = { info_only = true } },
105
+ "pylint",
106
+ "mypy",
107
+ # "dependencies",
108
+ "pydocstyle",
109
+ "ruff",
110
+ { "radon_c" = { info_only = true } },
111
+ "radon_d",
112
+ "black",
113
+ "isort",
114
+ "copyright",
115
+ "py_headers",
116
+ { "bandit" = { info_only = true } },
117
+ ]
118
+
119
+ [tool.release-tools]
120
+ disabled = ['docs', 'scr']
121
+
122
+ [tool.release-tools.blackduck_sca]
123
+ project_name = "spsdk_el2go"
124
+ include_detector_types = ["pip"]
125
+ sbom_location = "SBOM-spsdk_el2go.spdx.json"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2026 NXP
5
+ #
6
+ # SPDX-License-Identifier: BSD-3-Clause
7
+ """Top-level package for el2go_signature_provider."""
8
+
9
+ __author__ = """NXP"""
10
+ __email__ = "spsdk@nxp.com"
11
+ __version__ = "0.2.0"
12
+
13
+ from .key_derivation_provider import EL2GoKDP
14
+ from .signature_provider import EL2GoSP
15
+
16
+ __all__ = ["EL2GoKDP", "EL2GoSP"]
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2026 NXP
5
+ #
6
+ # SPDX-License-Identifier: BSD-3-Clause
7
+ """Helper application for EL2GO Service Providers."""
8
+
9
+ import os
10
+ import sys
11
+
12
+ import click
13
+
14
+ from spsdk_el2go.client import EL2GOServiceClient, KeyInfo
15
+ from spsdk_el2go.utils import load_config
16
+
17
+
18
+ @click.group()
19
+ def main() -> None:
20
+ """EL2GO CLI tool."""
21
+
22
+
23
+ @main.command(name="download-public-key", no_args_is_help=True)
24
+ @click.option(
25
+ "-c",
26
+ "--config",
27
+ type=click.Path(exists=True),
28
+ required=True,
29
+ help="Path to the configuration file.",
30
+ )
31
+ @click.option(
32
+ "-ks",
33
+ "--key-set-id",
34
+ type=int,
35
+ help="Key set ID for the public key to download. (override config file value)",
36
+ )
37
+ @click.option(
38
+ "-k",
39
+ "--key-id",
40
+ type=int,
41
+ help="Key ID for the public key to download. (override config file value)",
42
+ )
43
+ @click.option(
44
+ "-o",
45
+ "--output",
46
+ type=click.Path(),
47
+ required=True,
48
+ help="Path to save the downloaded public key.",
49
+ )
50
+ @click.option(
51
+ "-fd",
52
+ "--force-download",
53
+ is_flag=True,
54
+ default=False,
55
+ help="Force download even if cached key exists.",
56
+ )
57
+ def download_public_key(
58
+ config: str, output: str, force_download: bool, key_id: int | None, key_set_id: int | None
59
+ ) -> None:
60
+ """Download public key from EL2GO service."""
61
+ click.echo("Downloading public key from EL2GO service...")
62
+
63
+ config_data = load_config(config)
64
+ client = EL2GOServiceClient.from_config(config_data)
65
+ key_set_id = key_set_id or config_data.get_int("key_set_id")
66
+ key_id = key_id or config_data.get_int("key_id")
67
+ key_info = KeyInfo(key_set_id=key_set_id, key_id=key_id)
68
+ public_key = client.download_public_key(key_info=key_info, force_download=force_download)
69
+
70
+ os.makedirs(os.path.dirname(output) or ".", exist_ok=True)
71
+
72
+ with open(output, "wb") as f:
73
+ f.write(public_key)
74
+ click.echo(f"Public key downloaded: {output}")
75
+
76
+
77
+ @main.command(name="get-template", no_args_is_help=True)
78
+ @click.option(
79
+ "-o",
80
+ "--output",
81
+ type=click.Path(),
82
+ required=True,
83
+ help="Path to save the configuration file template.",
84
+ )
85
+ def get_template(output: str) -> None:
86
+ """Generate a configuration file template for EL2GO service."""
87
+ click.echo("Generating configuration file template...")
88
+
89
+ template = """
90
+ # Configuration file for EL2GO Code Signing service
91
+
92
+ # Base URL of the EL2GO service
93
+ url: https://api.edgelock2go.com
94
+
95
+ # API key for authenticating with the EL2GO service
96
+ # Options how to provide the API key:
97
+ # 1. Directly in the configuration file (not recommended for security reasons)
98
+ # 2. Environment variable (starting with $ e.g.: $MY_EL2GO_API_KEY)
99
+ # 3. Path to a file containing the API key (e.g.: ~/.el2go/api_key)
100
+ # 4. Environment variable pointing to a file containing the API key (e.g.: $MY_EL2GO_API_KEY_FILE)
101
+ api_key: ~/.el2go/api_key
102
+
103
+ # Key set ID and key ID for sign/encryption operations
104
+ # IDs are shown in the EL2GO web interface under "Code Signing" section
105
+ key_set_id: 12
106
+ key_id: 123
107
+ """
108
+
109
+ os.makedirs(os.path.dirname(output) or ".", exist_ok=True)
110
+
111
+ with open(output, "w", encoding="utf-8") as f:
112
+ f.write(template)
113
+ click.echo(f"Configuration file template generated: {output}")
114
+
115
+
116
+ if __name__ == "__main__":
117
+ sys.exit(main())
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright 2026 NXP
5
+ #
6
+ # SPDX-License-Identifier: BSD-3-Clause
7
+ """Module for EL2GO Service Client."""
8
+
9
+ import base64
10
+ import logging
11
+ import uuid
12
+
13
+ from spsdk.crypto.hash import EnumHashAlgorithm, get_hash
14
+ from spsdk.crypto.keys import PublicKey
15
+ from spsdk.el2go.client import EL2GOClient
16
+ from spsdk.utils.config import Config
17
+
18
+ from .utils import KeyInfo, get_public_key_cache_path, load_config
19
+
20
+ logger = logging.getLogger("spsdk.el2go_service_client")
21
+
22
+
23
+ KEY_TYPES = ["ecc_p256", "ecc_p384", "ecc_p512", "rsa_2048", "rsa_3072", "rsa_4096"]
24
+ KEY_TO_HASH_ALGORITHM = {
25
+ "ecc_p256": EnumHashAlgorithm.SHA256,
26
+ "ecc_p384": EnumHashAlgorithm.SHA384,
27
+ "ecc_p512": EnumHashAlgorithm.SHA512,
28
+ "rsa_2048": EnumHashAlgorithm.SHA256,
29
+ "rsa_3072": EnumHashAlgorithm.SHA384,
30
+ "rsa_4096": EnumHashAlgorithm.SHA512,
31
+ }
32
+ KEY_TO_EL2GO_SIGNATURE_ALGORITHM = {
33
+ "ecc_p256": "ECDSA",
34
+ "ecc_p384": "ECDSA",
35
+ "ecc_p512": "ECDSA",
36
+ "rsa_2048": "RSASSA_PKCS_v1_5",
37
+ "rsa_3072": "RSASSA_PKCS_v1_5",
38
+ "rsa_4096": "RSASSA_PKCS_v1_5",
39
+ }
40
+ KEY_TO_SIGNATURE_LENGTH = {
41
+ "ecc_p256": 64,
42
+ "ecc_p384": 96,
43
+ "ecc_p512": 132,
44
+ "rsa_2048": 256,
45
+ "rsa_3072": 384,
46
+ "rsa_4096": 512,
47
+ }
48
+
49
+
50
+ class EL2GOServiceClient(EL2GOClient):
51
+ """Extended Client for interacting with the EL2GO services."""
52
+
53
+ def __init__(
54
+ self,
55
+ api_key: str,
56
+ url: str = "https://api.edgelock2go.com",
57
+ timeout: int = 60,
58
+ ) -> None:
59
+ """Initialize EL2GO Service Client.
60
+
61
+ :param api_key: API key for authentication with the EL2GO service.
62
+ :param url: Base URL for the EL2GO API endpoints.
63
+ :param timeout: Timeout in seconds for HTTP requests to the EL2GO service.
64
+ """
65
+ super().__init__(api_key=api_key, url=url, timeout=timeout)
66
+
67
+ @classmethod
68
+ def from_config(cls, config: Config) -> "EL2GOServiceClient":
69
+ """Create an instance of EL2GOServiceClient from a configuration object.
70
+
71
+ :param config: Configuration object containing API credentials and settings.
72
+ :return: An instance of EL2GOServiceClient initialized with the provided configuration.
73
+ """
74
+ api_key = config.load_secret("api_key")
75
+ url = config.get_str("url", "https://api.edgelock2go.com")
76
+ timeout = config.get_int("timeout", 60)
77
+ return cls(
78
+ api_key=api_key,
79
+ url=url,
80
+ timeout=timeout,
81
+ )
82
+
83
+ def sign(self, data: bytes, key_info: KeyInfo) -> bytes:
84
+ """Sign the provided data using the EL2GO Service Client.
85
+
86
+ :param data: Data to be signed, provided as bytes.
87
+ :param key_info: KeyInfo object containing key details for signing.
88
+
89
+ :return: The signature as bytes.
90
+ """
91
+ logger.info(
92
+ f"Signing data using key_set_id={key_info.key_set_id}, key_id={key_info.key_id}"
93
+ )
94
+ endpoint = f"/api/v1/keysets/{key_info.key_set_id}/keys/{key_info.key_id}/sign-hash"
95
+ logger.debug(f"Signing endpoint: {endpoint}")
96
+
97
+ # at this point, hash should be set already
98
+ assert key_info.hash_algorithm is not None, "Hash algorithm must be specified in KeyInfo."
99
+
100
+ hashed_data = get_hash(data, key_info.hash_algorithm)
101
+
102
+ logger.debug(f"Using hash algorithm: {key_info.hash_algorithm.name}")
103
+ logger.debug(f"Length of pre-hashed data: {len(hashed_data)} bytes")
104
+ logger.debug(f"Data to be signed (pre-hashed): {hashed_data.hex()}")
105
+
106
+ data_to_sign = base64.b64encode(hashed_data).decode()
107
+ json_data = {
108
+ "payload": data_to_sign,
109
+ "signatureAlgorithm": key_info.signature_algorithm,
110
+ }
111
+ logger.debug(f"JSON data to be sent for signing: {json_data}")
112
+ response = self._handle_el2go_request(
113
+ method=self.Method.POST,
114
+ url=endpoint,
115
+ json_data=json_data,
116
+ )
117
+ logger.debug(f"Received response from signing request: {response.json_body}")
118
+ if response.status_code != 200:
119
+ logger.error(f"Failed to sign data: {response.json_body}")
120
+ raise RuntimeError(
121
+ f"Failed to sign data: {response.status_code} - {response.json_body}"
122
+ )
123
+ signature = base64.b64decode(response.json_body["signature"])
124
+ logger.debug(f"Received signature (raw): {signature.hex()}")
125
+ return signature
126
+
127
+ def download_public_key(self, key_info: KeyInfo, force_download: bool = False) -> bytes:
128
+ """Download the public key associated with the specified key set and key ID.
129
+
130
+ :param key_info: KeyInfo object containing key details for downloading the public key.
131
+
132
+ :param force_download: If True, force download even if cached key exists.
133
+ :return: The public key as bytes.
134
+ """
135
+ if not force_download:
136
+ key_file_path = get_public_key_cache_path(key_info=key_info)
137
+ if key_file_path.exists():
138
+ logger.info(f"Using cached public key from: {key_file_path}")
139
+ return key_file_path.read_bytes()
140
+
141
+ endpoint = (
142
+ f"/api/v1/keysets/{key_info.key_set_id}/keys/{key_info.key_id}/download-public-key"
143
+ )
144
+ logger.info(
145
+ f"Downloading public key: key_set_id={key_info.key_set_id}, key_id={key_info.key_id}"
146
+ )
147
+ logger.debug(f"Public key download endpoint: {endpoint}")
148
+
149
+ accept_header_backup = self.headers.get("Accept")
150
+ try:
151
+ self.headers["Accept"] = "application/octet-stream"
152
+
153
+ self.correlation_id = str(uuid.uuid4())
154
+ logger.info(f"EL2G-Correlation-ID: {self.correlation_id}")
155
+ self.headers["EL2G-Correlation-ID"] = self.correlation_id
156
+ api_response = super()._handle_request(
157
+ method=self.Method.GET, url=endpoint, param_data=None, json_data=None
158
+ )
159
+ api_response.raise_for_status()
160
+
161
+ # Assuming the public key is returned as a string
162
+ public_key_str = api_response.text
163
+ public_key_bytes = public_key_str.encode()
164
+
165
+ logger.debug(f"Received response from public key request:\n{api_response.text}")
166
+
167
+ key_file_path = get_public_key_cache_path(key_info=key_info)
168
+ key_file_path.write_bytes(public_key_bytes)
169
+ logger.info(f"Public key saved to: {key_file_path}")
170
+
171
+ return public_key_bytes
172
+
173
+ finally:
174
+ if accept_header_backup is not None:
175
+ self.headers["Accept"] = accept_header_backup
176
+ else:
177
+ del self.headers["Accept"]
178
+
179
+ def cmac(self, data: bytes, key_info: KeyInfo) -> bytes:
180
+ """Compute CMAC for the provided data using the EL2GO Service Client.
181
+
182
+ :param data: Data for which to compute the CMAC, provided as bytes.
183
+ :param key_info: KeyInfo object containing key details for CMAC computation.
184
+
185
+ :return: The computed CMAC as bytes.
186
+ """
187
+ logger.info(
188
+ f"Computing CMAC for data using key_set_id={key_info.key_set_id}, key_id={key_info.key_id}"
189
+ )
190
+ endpoint = f"/api/v1/keysets/{key_info.key_set_id}/keys/{key_info.key_id}/cmac"
191
+ logger.debug(f"CMAC endpoint: {endpoint}")
192
+
193
+ json_data = {"payload": base64.b64encode(data).decode()}
194
+ logger.debug(f"JSON data to be sent for CMAC computation: {json_data}")
195
+ response = self._handle_el2go_request(
196
+ method=self.Method.POST,
197
+ url=endpoint,
198
+ json_data=json_data,
199
+ )
200
+ logger.debug(f"Received response from CMAC request: {response.json_body}")
201
+ if response.status_code != 200:
202
+ logger.error(f"Failed to compute CMAC: {response.json_body}")
203
+ raise RuntimeError(
204
+ f"Failed to compute CMAC: {response.status_code} - {response.json_body}"
205
+ )
206
+ cmac_value = base64.b64decode(response.json_body["cmacPayload"])
207
+ logger.debug(f"Received CMAC (raw): {cmac_value.hex()}")
208
+ return cmac_value
209
+
210
+ def get_key_info(self, key_set_id: int, key_id: int) -> KeyInfo:
211
+ """Retrieve the public key type from the EL2GO service.
212
+
213
+ :param key_set_id: ID of the key set.
214
+ :param key_id: ID of the key.
215
+ :return: The public key type as a string.
216
+ """
217
+ logger.info(f"Retrieving public key type for key_set_id={key_set_id}, key_id={key_id}")
218
+ key_info = KeyInfo(key_set_id=key_set_id, key_id=key_id)
219
+ key_data = self.download_public_key(key_info=key_info)
220
+ public_key = PublicKey.parse(key_data)
221
+ key_type = ""
222
+
223
+ # the mypy issues below shall be fixed in SPSDK
224
+ if "RSA" in repr(public_key):
225
+ key_type = f"rsa_{public_key.key_size}" # type: ignore[attr-defined]
226
+ elif "EC" in repr(public_key):
227
+ key_type = f"ecc_p{public_key.key_size}" # type: ignore[attr-defined]
228
+ if key_type not in KEY_TYPES:
229
+ raise ValueError(f"Invalid key type retrieved: {key_type}. Must be one of {KEY_TYPES}.")
230
+
231
+ hash_algorithm = KEY_TO_HASH_ALGORITHM[key_type]
232
+ signature_algorithm = KEY_TO_EL2GO_SIGNATURE_ALGORITHM[key_type]
233
+ signature_length = KEY_TO_SIGNATURE_LENGTH[key_type]
234
+
235
+ return KeyInfo(
236
+ key_set_id=key_set_id,
237
+ key_id=key_id,
238
+ key_type=key_type,
239
+ hash_algorithm=hash_algorithm,
240
+ signature_algorithm=signature_algorithm,
241
+ signature_length=signature_length,
242
+ )
243
+
244
+
245
+ def get_client_key_info(
246
+ config_path: str | None = None, full_key_info: bool = True, **kwargs: str
247
+ ) -> tuple[EL2GOServiceClient, KeyInfo]:
248
+ """Get the key information from the EL2GO service based on the provided configuration.
249
+
250
+ :param config_path: Path to the configuration file. If None, an empty configuration is created.
251
+ :param full_key_info: Whether to retrieve full key information.
252
+ :param kwargs: Additional configuration parameters to update the loaded configuration.
253
+ :return: A tuple containing the EL2GOServiceClient and a KeyInfo object.
254
+ """
255
+ config = load_config(config_path, **kwargs)
256
+ client = EL2GOServiceClient.from_config(config)
257
+ key_set_id = config.get_int("key_set_id")
258
+ key_id = config.get_int("key_id")
259
+ if full_key_info:
260
+ key_info = client.get_key_info(key_set_id=key_set_id, key_id=key_id)
261
+ else:
262
+ key_info = KeyInfo(key_set_id=key_set_id, key_id=key_id)
263
+ return client, key_info
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2026 NXP
5
+ #
6
+ # SPDX-License-Identifier: BSD-3-Clause
7
+ """Main module for EL2GO SB Key Derivation Provider."""
8
+
9
+ from spsdk.sbfile.utils.key_derivator import SB31KeyDerivator
10
+
11
+ from .client import get_client_key_info
12
+
13
+
14
+ class EL2GoKDP(SB31KeyDerivator):
15
+ """Key Derivation Provider based on a remote signing service."""
16
+
17
+ # identifier of this key derivation provider; used in yaml configuration file
18
+ identifier = "el2go"
19
+
20
+ def __init__(self, config_path: str | None = None, **kwargs: str) -> None:
21
+ """Initialize the EL2GoSP."""
22
+ super().__init__()
23
+ self.client, self.key_info = get_client_key_info(
24
+ config_path=config_path, full_key_info=False, **kwargs
25
+ )
26
+
27
+ def remote_cmac(self, data: bytes) -> bytes:
28
+ """Calculate CMAC using the implementation-specific method."""
29
+ return self.client.cmac(data=data, key_info=self.key_info)
30
+
31
+ def info(self) -> str:
32
+ """Provide information about the Key Derivation provider."""
33
+ return f"EL2GO SB key derivator: {self.client.base_url}, {self.key_info}"
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2026 NXP
5
+ #
6
+ # SPDX-License-Identifier: BSD-3-Clause
7
+ """Main module for EL2GO Signature Provider."""
8
+
9
+ from spsdk.crypto.keys import PublicKey
10
+ from spsdk.crypto.signature_provider import SignatureProvider
11
+
12
+ from .client import get_client_key_info
13
+
14
+
15
+ class EL2GoSP(SignatureProvider):
16
+ """Signature Provider based on a remote signing service."""
17
+
18
+ # identifier of this signature provider; used in yaml configuration file
19
+ identifier = "el2go"
20
+
21
+ def __init__(self, config_path: str | None = None, **kwargs: str) -> None:
22
+ """Initialize the EL2GoSP."""
23
+ super().__init__()
24
+ self.client, self.key_info = get_client_key_info(
25
+ config_path=config_path, full_key_info=True, **kwargs
26
+ )
27
+
28
+ def sign(self, data: bytes) -> bytes:
29
+ """Return signature for data."""
30
+ signature = self.client.sign(data=data, key_info=self.key_info)
31
+ return signature
32
+
33
+ @property
34
+ def signature_length(self) -> int:
35
+ """Return length of the signature."""
36
+ # at this point, signature_length should be set already
37
+ assert self.key_info.signature_length is not None
38
+ return self.key_info.signature_length
39
+
40
+ def verify_public_key(self, public_key: PublicKey) -> bool:
41
+ """Verify if given public key matches private key."""
42
+ puk = PublicKey.parse(self.client.download_public_key(key_info=self.key_info))
43
+ return puk == public_key
44
+
45
+ def info(self) -> str:
46
+ """Provide information about the Signature provider."""
47
+ return f"EL2GO SP: {self.client.base_url}, {self.key_info}"
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright 2026 NXP
5
+ #
6
+ # SPDX-License-Identifier: BSD-3-Clause
7
+ """Module for EL2GO Service Client utilities."""
8
+
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+
12
+ from platformdirs import PlatformDirs
13
+ from spsdk.crypto.hash import EnumHashAlgorithm
14
+ from spsdk.utils.config import Config
15
+
16
+ PLATFORM_DIRS = PlatformDirs("spsdk-el2go", "nxp", ensure_exists=True)
17
+ PUBLIC_KEY_CACHE_DIR = PLATFORM_DIRS.user_cache_path
18
+ PUBLIC_KEY_FILE_TEMPLATE = "public_key_{key_set_id}_{key_id}.pem"
19
+
20
+
21
+ def load_config(config_path: str | None = None, **kwargs: int | str) -> Config:
22
+ """Load configuration from a file and update it with additional keyword arguments.
23
+
24
+ :param config_path: Path to the configuration file. If None, an empty configuration is created.
25
+ :param kwargs: Additional configuration parameters to update the loaded configuration.
26
+ :return: A Config object containing the loaded and updated configuration.
27
+ """
28
+ if config_path is None:
29
+ config = Config({})
30
+ else:
31
+ config = Config.create_from_file(config_path)
32
+ config.update(kwargs)
33
+ return config
34
+
35
+
36
+ @dataclass
37
+ class KeyInfo:
38
+ """Data class to hold key information."""
39
+
40
+ key_set_id: int
41
+ key_id: int
42
+ key_type: str | None = None
43
+ hash_algorithm: EnumHashAlgorithm | None = None
44
+ signature_algorithm: str | None = None
45
+ signature_length: int | None = None
46
+
47
+
48
+ def get_public_key_cache_path(key_info: KeyInfo) -> Path:
49
+ """Get the path to the cached public key file.
50
+
51
+ :param key_info: KeyInfo object containing key details.
52
+ :return: Path to the cached public key file as a Path object.
53
+ """
54
+ key_file_name = PUBLIC_KEY_FILE_TEMPLATE.format(
55
+ key_set_id=key_info.key_set_id, key_id=key_info.key_id
56
+ )
57
+ return PUBLIC_KEY_CACHE_DIR / key_file_name
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: spsdk-el2go
3
+ Version: 0.2.0
4
+ Summary: SPSDK Service providers powered by EdgeLock2GO
5
+ Author: NXP
6
+ Maintainer-email: NXP <spsdk@nxp.com>
7
+ License-Expression: BSD-3-Clause
8
+ Project-URL: Homepage, https://github.com/nxp-mcuxpresso/spsdk_plugins/tree/master/el2go
9
+ Project-URL: Issues, https://github.com/nxp-mcuxpresso/spsdk_plugins/issues
10
+ Keywords: NXP,SPSDK,el2go
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: spsdk>2.1
24
+ Requires-Dist: platformdirs
25
+ Dynamic: license-file
26
+
27
+
28
+ # EdgeLock 2 GO service providers for SPSDK
29
+
30
+ Package contains multiple SPSDK service providers powered by EL2GO
31
+
32
+ * Free software: BSD-3-Clause
33
+ * Documentation: https://spsdk-el2go.readthedocs.io.
34
+
35
+
36
+ Installation
37
+ ------------
38
+
39
+ * Activate virtual env, where you have SPSDK
40
+ - if SPSDK is not installed, the latest one will be installed automatically from [PyPI](https://pypi.org/project/spsdk/)
41
+ * `pip install spsdk-el2go`
42
+
43
+
44
+ ## Signature Provider
45
+
46
+ Everywhere in SPSDK where a signature (private key) is required, a signature provider can be used instead.
47
+ Using this plugin, all signing operations can be delegated to EL2GO. Private keys stay securely stored in EL2GO servers.
48
+
49
+
50
+ ### Configuration:
51
+
52
+ SPSDK Service providers are configured via a Service configuration string.
53
+ Format: `type=<service_id>[;param1=value1][;param2=value2]`
54
+ In this case the service identifier is `el2go` thus the config string starts with `type=el2go`
55
+
56
+ EL2GO signature provider uses the following configuration options:
57
+ * `url`: Base EL2GO API URL (default: https://api.edgelock2go.com)
58
+ * `api_key`: EL2GO API key. Can be placed in env variable (staring with $) or a file.
59
+ * E.g.: `~/.config/el2go/token.txt`, `$EL2GO_TOKEN`, `$PATH_TO_FILE_WITH_API_KEY`
60
+ * make sure your API token has Code Signing permissions set (both View and Manage are required)
61
+ * `key_set_id` (number): ID of the Key Set in EL2GO server
62
+ * `key_id` (number): ID of the key used for signing operation
63
+ * `config_path`: path to a YAML file containing some or all the config options above
64
+
65
+ These configuration options can be specified in service configuration string
66
+
67
+ type=el2go;url=https://api.edgelock2go.com;api_key=~/.config/el2go/token.txt;key_set_id=4;key_id=1
68
+
69
+ or can be passed via a config file
70
+
71
+ type=el2go;config_path=el2go_config.yaml
72
+
73
+ or a combination of both, overriding/adding options from config file in config string:
74
+
75
+ type=el2go;config_path=el2go_config.yaml;key_id=2
76
+
77
+ To help with setting up the config file, you may use `el2go-helper` application which is bundled with this python package.
78
+ Run `el2go-helper --help` to see more info.
79
+
80
+ ### Usage
81
+
82
+
83
+ * Use the configuration string in any SPSDK YAML config file, where a private key is normally provided
84
+ * e.g.: in MBI config file: `signer: type=el2go;config_path=el2go_config.yaml`
85
+
86
+ * Use SPSDK application as you would normally do
87
+ * e.g.: `nxpimage mbi export -c mbi_config.yaml`
88
+
89
+
90
+ ## SB Key derivation provider
91
+
92
+ Derive encryption key used for SB3 and SB4 files from key stored in EdgeLock 2 GO server
93
+
94
+
95
+ ### Configuration
96
+
97
+ Exactly the same as for Signature Provider
98
+
99
+
100
+ ### Usage
101
+
102
+ * Use the configuration string in SB3 or SB4 YAML config file, where the CUST_MK_SK/SBKEK is provided
103
+ * e.g.: `containerKeyBlobEncryptionKey: type=el2go;config_path=el2go_config.yaml;key_id=20`
104
+
105
+ * Use SPSDK's nxpimage application as you would normally do
106
+ * e.g.: `nxpimage sb31 export -c sb_config.yaml`
107
+
108
+ ## Public key provider
109
+
110
+ Allow to specify any public key in YAML config file as a reference to public key stored in EdgeLock 2 GO
111
+
112
+ **Coming soon**
113
+
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ spsdk_el2go/__init__.py
6
+ spsdk_el2go/cli.py
7
+ spsdk_el2go/client.py
8
+ spsdk_el2go/key_derivation_provider.py
9
+ spsdk_el2go/signature_provider.py
10
+ spsdk_el2go/utils.py
11
+ spsdk_el2go.egg-info/PKG-INFO
12
+ spsdk_el2go.egg-info/SOURCES.txt
13
+ spsdk_el2go.egg-info/dependency_links.txt
14
+ spsdk_el2go.egg-info/entry_points.txt
15
+ spsdk_el2go.egg-info/requires.txt
16
+ spsdk_el2go.egg-info/top_level.txt
17
+ tests/__init__.py
18
+ tests/test_integration.py
@@ -0,0 +1,11 @@
1
+ [console_scripts]
2
+ el2go-helper = spsdk_el2go.cli:main
3
+
4
+ [pyinstaller40]
5
+ hook-dirs = spsdk_el2go.__pyinstaller:get_hook_dirs
6
+
7
+ [spsdk.sb31kdp]
8
+ el2go = spsdk_el2go.key_derivation_provider
9
+
10
+ [spsdk.sp]
11
+ el2go = spsdk_el2go.signature_provider
@@ -0,0 +1,2 @@
1
+ spsdk>2.1
2
+ platformdirs
@@ -0,0 +1 @@
1
+ spsdk_el2go
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # -*- coding: UTF-8 -*-
5
+ #
6
+ # Copyright 2026 NXP
7
+ #
8
+ # SPDX-License-Identifier: BSD-3-Clause
9
+ """Unit test package for el2go_signature_provider."""
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2026 NXP
5
+ #
6
+ # SPDX-License-Identifier: BSD-3-Clause
7
+ """Tests for `spsdk_el2go` package."""
8
+
9
+ from spsdk.crypto.signature_provider import SignatureProvider
10
+ from spsdk.sbfile.utils.key_derivator import SB31KeyDerivator
11
+
12
+ from spsdk_el2go import EL2GoKDP, EL2GoSP
13
+
14
+
15
+ def test_sp_registration() -> None:
16
+ """Test whether EL2GoSP got picked up by SPSDK."""
17
+ assert EL2GoSP.identifier in SignatureProvider.get_types()
18
+
19
+
20
+ def test_kdp_registration() -> None:
21
+ """Test whether EL2GoKDP got picked up by SPSDK."""
22
+ assert EL2GoKDP.identifier in SB31KeyDerivator.get_types()