Signature Provider Plugin

Signature Provider Plugin#

This notebook describes how to setup a Signature Provider plugin

When signing the data with SPSDK, there are two options:

  • Using the local private key (not recommended)

  • Using the remote signing service(HSM)

Let’s look at the second option and setup Signature Provider

%run ../../init_notebook.ipynb

import pprint

pp = pprint.PrettyPrinter(indent=4)

PLUGINS_DIR = "plugins/" # change this to path to your workspace
VERBOSITY = "-v" # verbosity of commands, might be -v or -vv for debug or blank for no additional info
env: JUPYTER_SPSDK=1
Created `%!` as an alias for `%execute`.

HSM setup#

First, we need to start the custom HSM. In order to do that, open this notebook and follow the instructions there. Once you are done, come back and continue here.

Now the HSM should be up and running. In order to test the functionality of HSM from previous step, run a simple test:

import requests
#rsa2048 sign
response = requests.get("http://127.0.0.1:5000/signer/rsa2048/0?data=b'ABC'")
print(f"RSA2048: {response.json()}")

response = requests.get("http://127.0.0.1:5000/signer/secp384r1/0?data=b'ABC'")
print(f"SECP384R1: {response.json()}")
RSA2048: {'signature': 'QWjBWnbG7QtninaD6R9dQZqGiMZZskdVLCV1peXZEp43SJx3PATOoXTIQhvLhOZ5Q0f1683dtGAkEzb1aHKY05fIw2iPAGNHsL7IAe5nH0t3dOaCvemlodzAbb8GDpdahUHBURpnJOsgqYccZZOR6E3GSuIwD8qKBlZ7sGomtwzrBGuNHU5AG8U0J+8hLhExpEttd953mtnyMnC5aq3W30SbwU+7lZDAc2jIJn1PltVUetdHOVyGSPi4yAGZIlnzgYD8vpse2xlPP+3Ifdfuu3ckkNSZ0xzmK8adehKGTqD5hlpnP9iWPd7lio+82SovjmQ552RwwtRGbFmqC2qEkg=='}
SECP384R1: {'signature': 'b63Hysi7FGgy86+iI0Fdl2orzhKqK6UTUj5s309z7/iIUD5a+bqK6zGtKlHjzNi+6cFtetTc/yofbdZx0Au0KNdA69Zx3oIkwK69RuKlQtKnPUFYnyt6MDNiOOl5INk6'}

Signature Provider plugin#

Plugins extend the existing SPSDK functionality with additional features.

In order to use remote signing, a Signature Provider plugin used for communication with HSM must be implemented.

Explore the file plugins\sasp.py. It will be used later on.

import os

plugins_dir = 'plugins/'
# The content of plugin will be printed here
SASP_PLUGIN = os.path.join(plugins_dir, 'sasp.py')
with open(SASP_PLUGIN, 'r') as f:
    print(f.read())
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Copyright 2020-2023 NXP
#
# SPDX-License-Identifier: BSD-3-Clause

"""Customer-specific Signature Provider."""

import base64

import requests

from spsdk.crypto.signature_provider import SignatureProvider


class SuperAwesomeSP(SignatureProvider):
    """Signature Provider based on a remote signing service."""

    # identifier of this signature provider; used in yaml configuration file
    sp_type = "sasp"

    def __init__(self, key_number: int, key_type: str) -> None:
        """Initialize the Super Awesome SignatureProvider.

        :param key_number: index of the key to use (rot_id from yaml config)
        """
        self.url = "http://127.0.0.1:5000"
        self.key_number = key_number
        self.key_type = key_type

    def sign(self, data: bytes) -> bytes:
        """Perform the signing.

        :param data: Data to sign
        :return: Signature
        """
        endpoint = f"{self.url}/signer/{self.key_type}/{self.key_number}"
        params = {"data": base64.b64encode(data)}
        response = requests.get(endpoint, params=params)
        self.check_response(response)
        signature = response.json()["signature"]
        data = base64.b64decode(signature)
        return data

    def verify_public_key(self, public_key: bytes) -> bool:
        """Verify if given public key matches private key.

        :param data: Public key to verify
        :return: True if public_key is matching private_key, False otherwise
        """
        endpoint = f"{self.url}/verifier/{self.key_type}/{self.key_number}"
        params = {"public_key": base64.b64encode(public_key)}
        response = requests.get(endpoint, params=params)
        self.check_response(response)
        is_matching = response.json()["is_matching"]
        return is_matching

    @property
    def signature_length(self) -> int:
        """Return length of the signature."""
        return {"rsa2048": 256, "secp256r1": 64, "secp384r1": 96, "secp521r1": 132}[self.key_type]

    @staticmethod
    def check_response(response: requests.Response) -> None:
        """Raise if response is not 2xx."""
        try:
            response.raise_for_status()
        except requests.HTTPError as e:
            if response.text:
                raise requests.HTTPError(
                    f"{str(e)}; Error Message: {response.text}",
                    request=e.request,
                    response=e.response,
                )
            else:
                raise e

The only plugin requirement is that it contains a class derived from spsdk.crypto.SignatureProvider base class.

  • The derived class has to implement:

    • sp_type: str: class attribute that identifies the concrete implementation of SignatureProvider

    • sign(bytes) -> bytes: method which performs the actual signing

    • signature_length -> str: property which returns a length of a signature

  • The derived class can also optionally implement:

    • info() -> str: method which returns information about the signature provider (for debugging purposes). The default implementation returns a class name as a string

    • verify_public_key(bytes) -> bool: method which verifies if a given public key matches a private key.

Omitting the implementation of optional methods such as info() does not break the functionality of application.

Signature Formats#

The signature must meet following formats:

  • RSA: Standard format

  • ECC: Extracted public numbers r+s stored in big endian or DER-formatted signature