Skip to content

Commit

Permalink
Middleware SGX admin tooling (#199)
Browse files Browse the repository at this point in the history
* Added onboard method to HSM2DongleSGX

* Added Platform abstraction to manage common platform parameters

* Replaced protocol platform-specific configuration for text messages with Platform messages

* Implemented SGX admin tool

- Implemented main SGX admin tool entrypoint with onboard, unlock, pin change and public key gathering commands
- Misc get_hsm function now accounts for current Platform to acquire a connection
- Updated specific admin modules to account for difference between platforms for specific operations
- Now setting Platform within Ledger admin tool

* Updated build scripts to account for SGX manager and admin

* Added and updated unit tests
  • Loading branch information
amendelzon authored Sep 18, 2024
1 parent 2250e60 commit 0b951ed
Show file tree
Hide file tree
Showing 20 changed files with 508 additions and 54 deletions.
4 changes: 2 additions & 2 deletions middleware/adm_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from argparse import ArgumentParser
import logging
from ledger.hsm2dongle import HSM2DongleError
from comm.platform import Platform
from admin.misc import not_implemented, info, AdminError
from admin.unlock import do_unlock
from admin.onboard import do_onboard
Expand All @@ -33,8 +34,6 @@
from admin.verify_attestation import do_verify_attestation
from admin.authorize_signer import do_authorize_signer

DEFAULT_PIN_FILE = "pin.txt"
DEFAULT_PIN_CHANGE_FILE = "changePIN"
DEFAULT_ATT_UD_SOURCE = "https://public-node.rsk.co"


Expand Down Expand Up @@ -144,6 +143,7 @@ def main():

try:
options = parser.parse_args()
Platform.set(Platform.LEDGER)
actions.get(options.operation, not_implemented)(options)
sys.exit(0)
except AdminError as e:
Expand Down
128 changes: 128 additions & 0 deletions middleware/adm_sgx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# The MIT License (MIT)
#
# Copyright (c) 2021 RSK Labs Ltd
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sys
from argparse import ArgumentParser
import logging
from ledger.hsm2dongle import HSM2DongleError
from comm.platform import Platform
from admin.misc import not_implemented, info, AdminError
from admin.unlock import do_unlock
from admin.onboard import do_onboard
from admin.pubkeys import do_get_pubkeys
from admin.changepin import do_changepin


def main():
logging.disable(logging.CRITICAL)

actions = {
"unlock": do_unlock,
"onboard": do_onboard,
"pubkeys": do_get_pubkeys,
"changepin": do_changepin,
}

parser = ArgumentParser(description="SGX powHSM Administrative tool")
parser.add_argument("operation", choices=list(actions.keys()))
parser.add_argument(
"-r",
"--port",
dest="sgx_port",
help="SGX powHSM listening port (default 7777)",
type=int,
default=7777,
)
parser.add_argument(
"-s",
"--host",
dest="sgx_host",
help="SGX powHSM host. (default 'localhost')",
default="localhost",
)
parser.add_argument("-p", "--pin", dest="pin", help="PIN.")
parser.add_argument(
"-n",
"--newpin",
dest="new_pin",
help="New PIN (only valid for 'changepin' operation).",
)
parser.add_argument(
"-a",
"--anypin",
dest="any_pin",
action="store_const",
help="Allow any pin (only valid for 'changepin' operation).",
default=False,
const=True,
)
parser.add_argument(
"-o",
"--output",
dest="output_file_path",
help="Output file (only valid for 'onboard' and 'pubkeys' "
"operations).",
)
parser.add_argument(
"-u",
"--nounlock",
dest="no_unlock",
action="store_const",
help="Do not attempt to unlock (only valid for 'changepin' and 'pubkeys' "
"operations).",
default=False,
const=True,
)
parser.add_argument(
"-v",
"--verbose",
dest="verbose",
action="store_const",
help="Enable verbose mode",
default=False,
const=True,
)

try:
options = parser.parse_args()
Platform.set(Platform.SGX, {
"sgx_host": options.sgx_host,
"sgx_port": options.sgx_port,
})
actions.get(options.operation, not_implemented)(options)
sys.exit(0)
except AdminError as e:
info(str(e))
sys.exit(1)
except HSM2DongleError as e:
info(str(e))
sys.exit(2)
except KeyboardInterrupt:
info("Interrupted by user!")
sys.exit(3)
except Exception as e:
info(str(e))
sys.exit(4)


if __name__ == "__main__":
main()
14 changes: 9 additions & 5 deletions middleware/admin/changepin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
AdminError,
)
from .unlock import do_unlock
from comm.platform import Platform


def do_changepin(options):
Expand Down Expand Up @@ -62,10 +63,10 @@ def do_changepin(options):
mode = hsm.get_current_mode()
info(f"Mode: {mode.name.capitalize()}")

# We can only change the pin while in bootloader mode
if mode != HSM2Dongle.MODE.BOOTLOADER:
raise AdminError("Device not in bootloader mode. Disconnect and re-connect the "
"ledger and try again")
# In Ledger, we can only change the pin while in bootloader mode
if Platform.is_ledger() and mode != HSM2Dongle.MODE.BOOTLOADER:
raise AdminError("Device not in bootloader mode. "
f"{Platform.message("restart").capitalize()} and try again")

# Ask the user for a new pin if one has not been given
if new_pin is None:
Expand All @@ -76,6 +77,9 @@ def do_changepin(options):
info("Changing pin... ", options.verbose)
if not hsm.new_pin(new_pin):
raise AdminError("Failed to change pin")
info("Pin changed. Please disconnect and re-connect the ledger.")
info("Pin changed.", nl=Platform.is_sgx())
# We require a restart in Ledger only
if Platform.is_ledger():
info(f" Please {Platform.message("restart")}.")

dispose_hsm(hsm)
10 changes: 9 additions & 1 deletion middleware/admin/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
import time
from getpass import getpass
from ledger.hsm2dongle import HSM2Dongle
from sgx.hsm2dongle import HSM2DongleSGX
from ledger.pin import BasePin
from .dongle_admin import DongleAdmin
from .dongle_eth import DongleEth
from comm.platform import Platform

PIN_ERROR_MESSAGE = ("Invalid pin given. It must be exactly 8 alphanumeric "
"characters with at least one alphabetic character.")
Expand Down Expand Up @@ -68,7 +70,13 @@ def not_implemented(options):

def get_hsm(debug):
info("Connecting to HSM... ", False)
hsm = HSM2Dongle(debug)
if Platform.is_ledger():
hsm = HSM2Dongle(debug)
elif Platform.is_sgx():
hsm = HSM2DongleSGX(Platform.options("sgx_host"),
Platform.options("sgx_port"), debug)
else:
raise AdminError("Platform not set or unknown platform")
hsm.connect()
info("OK")
return hsm
Expand Down
17 changes: 13 additions & 4 deletions middleware/admin/onboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from .dongle_admin import DongleAdmin
from .unlock import do_unlock
from .certificate import HSMCertificate, HSMCertificateElement
from comm.platform import Platform

# TODO: this could perhaps be done with a different value.
# Currently unused but necessary for the attestation setup process.
Expand All @@ -50,8 +51,8 @@ def do_onboard(options):
head("### -> Onboarding and attestation setup", fill="#")
hsm = None

# Require an output file
if options.output_file_path is None:
# Ledger-only: require an output file
if Platform.is_ledger() and options.output_file_path is None:
raise AdminError("No output file path given")

# Validate pin (if given)
Expand All @@ -71,8 +72,8 @@ def do_onboard(options):

# Require bootloader mode for onboarding
if mode != HSM2Dongle.MODE.BOOTLOADER:
raise AdminError("Device not in bootloader mode. Disconnect and re-connect the "
"ledger and try again")
raise AdminError("Device not in bootloader mode. "
f"{Platform.message("restart").capitalize()} and try again")

# Echo check
info("Sending echo... ", options.verbose)
Expand Down Expand Up @@ -120,6 +121,14 @@ def do_onboard(options):

dispose_hsm(hsm)

if Platform.is_sgx():
head(["Onboarding done"])
return

if not Platform.is_ledger():
raise AdminError("Unsupported platform")

# **** Attestation setup is Ledger only (for now) ****
head(
[
"Onboarding done",
Expand Down
3 changes: 2 additions & 1 deletion middleware/admin/pubkeys.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .misc import info, get_hsm, dispose_hsm, AdminError, wait_for_reconnection
from .unlock import do_unlock
from comm.bip32 import BIP32Path
from comm.platform import Platform

SIGNER_WAIT_TIME = 1 # second

Expand Down Expand Up @@ -65,7 +66,7 @@ def do_get_pubkeys(options):
# Modes for which we can't get the public keys
if mode in [HSM2Dongle.MODE.UNKNOWN, HSM2Dongle.MODE.BOOTLOADER]:
raise AdminError(
"Device not in app mode. Disconnect and re-connect the ledger and try again")
f"Device not in app mode. {Platform.message("restart").capitalize()}")

# Gather public keys
pubkeys = {}
Expand Down
8 changes: 5 additions & 3 deletions middleware/admin/unlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
AdminError,
ask_for_pin,
)
from comm.platform import Platform


def do_unlock(options, exit=True, no_exec=False, label=True):
Expand Down Expand Up @@ -65,8 +66,8 @@ def do_unlock(options, exit=True, no_exec=False, label=True):

# Modes for which we can't unlock
if mode == HSM2Dongle.MODE.UNKNOWN:
raise AdminError("Device mode unknown. Already unlocked? Otherwise disconnect "
"and re-connect the ledger and try again")
raise AdminError("Device mode unknown. Already unlocked? Otherwise "
f"{Platform.message("restart")} and try again")
if mode == HSM2Dongle.MODE.SIGNER or mode == HSM2Dongle.MODE.UI_HEARTBEAT:
raise AdminError("Device already unlocked")

Expand All @@ -87,9 +88,10 @@ def do_unlock(options, exit=True, no_exec=False, label=True):
raise AdminError("Unable to unlock: PIN mismatch")
info("PIN accepted")

# **** Ledger only ****
# Exit the bootloader, go into menu (or, if app is properly signed, into
# the app)
if exit:
if Platform.is_ledger() and exit:
autoexec = not (options.no_exec or no_exec)
info(f"Exiting to menu/app (execute signer: {bls(autoexec)})... ",
options.verbose)
Expand Down
2 changes: 2 additions & 0 deletions middleware/build/adm_sgx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
source $(dirname $0)/bld-docker adm_sgx
2 changes: 2 additions & 0 deletions middleware/build/all
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ BINDIR=$(realpath $BUILDDIR/../bin/)
echo "Building all..."

QUIET=1 $BUILDDIR/manager_ledger && \
QUIET=1 $BUILDDIR/manager_sgx && \
QUIET=1 $BUILDDIR/manager_tcp && \
QUIET=1 $BUILDDIR/adm_ledger && \
QUIET=1 $BUILDDIR/adm_sgx && \
QUIET=1 $BUILDDIR/lbutils && \
QUIET=1 $BUILDDIR/signapp && \
echo "" && sha256sum $BINDIR/*.tgz
65 changes: 65 additions & 0 deletions middleware/comm/platform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# The MIT License (MIT)
#
# Copyright (c) 2021 RSK Labs Ltd
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

class Platform:
LEDGER = "Ledger"
SGX = "SGX"
X86 = "X86"
VALID_PLATFORMS = [LEDGER, SGX, X86]

MESSAGES = {
LEDGER: {
"restart": "disconnect and re-connect the ledger nano",
},
SGX: {
"restart": "restart the SGX powHSM",
},
X86: {
"restart": "restart the TCPSigner",
}
}

_platform = None
_options = None

@classmethod
def set(klass, plf, options={}):
if plf not in klass.VALID_PLATFORMS:
raise RuntimeError("Invalid platform given")
klass._platform = plf
klass._options = options

@classmethod
def is_ledger(klass):
return klass._platform == Platform.LEDGER

@classmethod
def is_sgx(klass):
return klass._platform == Platform.SGX

@classmethod
def options(klass, key):
return klass._options[key]

@classmethod
def message(klass, key):
return klass.MESSAGES[klass._platform][key]
Loading

0 comments on commit 0b951ed

Please sign in to comment.