Skip to content

Commit 7453815

Browse files
authored
Add files via upload
1 parent 5442fca commit 7453815

File tree

1 file changed

+365
-0
lines changed

1 file changed

+365
-0
lines changed

rsa-gen-csr-aes-wrap.py

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
# Import the required modules
2+
3+
import pkcs11
4+
import os
5+
import sys
6+
import time
7+
import re
8+
import subprocess
9+
10+
# Create banner.
11+
12+
def create_banner(text, width=150, border_char='*'):
13+
lines = text.strip().split('\n')
14+
border = border_char * width
15+
16+
print(border)
17+
for line in lines:
18+
print(f"{border_char} {line.ljust(width - 4)} {border_char}")
19+
print(border)
20+
21+
banner_text = """
22+
23+
24+
DISCLAIMER:
25+
This code is provided for example purposes only.
26+
It is not intended for production use. It has not been tested for security.
27+
nCipher disclaims all warranties, express or implied, including without
28+
limitation, any implied warranties of fitness of this code for any
29+
particular purpose. nCipher shall not be liable for any damages, however
30+
caused, arising out of any use of this code.
31+
32+
Description & Usage:
33+
34+
Creates or uses an exsisting an RSA Key Pair on an nShield HSM device and wraps the private key with an exisiting or newly generated AES key.
35+
File is exported in encrypted binary format.
36+
37+
* Enter the key label (e.g., mykey)
38+
* Enter the token label (e.g., loadshared accelerator)
39+
* Enter the token pin
40+
* Enter the key size (e.g., 2048)
41+
* Do you want the public key to be a wrapping key? (y/n)
42+
* Public key is generated
43+
* Private key is generated
44+
* Private key is wrapped
45+
* Wrapped key material can be saved to file (custom path can be given or default is current working directory)
46+
* CSR is generated using the public key
47+
* CSR is signed using the private key
48+
* CSR is saved as file (custom path can be given or default is current working directory)
49+
50+
"""
51+
# Create the banner
52+
create_banner(banner_text)
53+
54+
# Functions
55+
56+
def extract_appname_ident(key_label):
57+
"""
58+
Extracts a specific value (e.g., pkcs11_hashvalue####) from the command output
59+
based on the provided key label.
60+
61+
Args:
62+
key_label (str): The key label to search for in the command output.
63+
64+
Returns:
65+
str: The extracted value if found, or None if not found.
66+
"""
67+
try:
68+
# Get OS type to determine the command to run
69+
os_type = sys.platform
70+
71+
if os_type == 'win32' or os_type == 'cygwin':
72+
output = subprocess.check_output(
73+
'set PATH=%PATH%;C:\\Program Files\\nCipher\\nfast\\bin && set PATH=%PATH%;C:\\Program Files\\nCipher\\nfast\\openssl && nfkminfo -l',
74+
shell=True, text=True
75+
)
76+
print('Available keys:')
77+
print(output)
78+
elif os_type == 'linux' or os_type == 'linux2':
79+
output = subprocess.check_output(['/opt/nfast/bin/nfkminfo', '-l'], text=True)
80+
print('Available keys:')
81+
print(output)
82+
else:
83+
print("Unsupported OS type")
84+
return None
85+
except subprocess.CalledProcessError as e:
86+
print("Error executing nfkminfo -l:", e)
87+
return None
88+
89+
# Regex pattern to match and extract `pkcs11_*` for the given key label
90+
pattern = rf"key_(pkcs11_[a-zA-Z0-9]+).*`{re.escape(key_label)}'"
91+
92+
# Search for the key label in the command output
93+
match = re.search(pattern, output)
94+
if match:
95+
# Extract the `pkcs11_*` portion
96+
appname_ident = match.group(1)
97+
print(f"Extracted appname_ident for '{key_label}': {appname_ident}")
98+
return appname_ident
99+
else:
100+
print(f"No matching key found for label: {key_label}")
101+
return None
102+
103+
104+
def get_user_input(prompt, default=None):
105+
"""Helper function for user input with an optional default value."""
106+
value = input(f"{prompt} [{default}]: ").strip()
107+
return value if value else default
108+
109+
# Format the key output
110+
def format_key_output(key, key_type):
111+
'''Format the key output for display'''
112+
113+
key_info = f"{key_type} Key:\n"
114+
key_info += f" Label: {getattr(key, 'label', 'N/A')}\n"
115+
key_info += f" Key Size: {getattr(key, 'key_size', MODULUS_BITS)}-bit\n"
116+
key_info += f" Key Type: {key.__class__.__name__}\n"
117+
key_info += f" Exponent: {getattr(key, 'public_exponent', '65537')}\n"
118+
119+
return key_info
120+
121+
# If you're using a virtual environment, make sure pkcs11 is installed there
122+
# You can use pip list to see the installed packages
123+
# Install python-pkcs11 by executing pip install python-pkcs11.
124+
125+
# If there's a problem with your Python installation, you might need to reinstall Python or the python module.
126+
127+
# This script is used to generate a new RSA keypair on a nShield HSM Edge device.
128+
129+
# The following environment variables must be set:
130+
# - PKCS11_MODULE_PATH: the path to the pkcs11 module
131+
# - PKCS11_TOKEN_LABEL: the label of the token to use
132+
# - PKCS11_PIN: the pin of the token to use
133+
# - add %NFAS_HOME%\bin to your PATH environment variable.
134+
# (Windows) Go to Settings > Environment Variables > System Variables > Path > Edit > New > %NFAST_HOME%\bin = C:\Program Files\nCipher\nfast\bin
135+
# (Linux) export PATH=$PATH:/opt/nfast/bin
136+
137+
# Set the required environment variables.
138+
139+
os.environ['PKCS11_MODULE_PATH'] = 'C:\\Program Files\\nCipher\\nfast\\toolkits\\pkcs11\\cknfast.dll'
140+
os.environ['CKNFAST_LOADSHARING'] = '1'
141+
os.environ['CKNFAST_FAKE_ACCELERATOR_LOGIN'] = '1'
142+
os.environ['%NFAST_HOME%'] = 'C:\\Program Files\\nCipher\\nfast\\bin'
143+
144+
# Defining the pkcs11 module library
145+
lib = pkcs11.lib(os.environ['PKCS11_MODULE_PATH'])
146+
147+
148+
# The following environment variables are optional:
149+
# They can also be placed in the cknfastrc file in the nfast directory.
150+
151+
#os.environ['CKNFAST_OVERRIDE_SECURITY_ASSURANCE'] = 'unwrap_mech;tokenkeys'
152+
#os.environ['CKNFAST_DEBUG'] = '10'
153+
#os.environ['CKNFAST_DEBUGFILE'] = '/opt/nfast/pkcs11_debug.log'
154+
155+
156+
# Prompt for the key label
157+
PKCS11_KEY_LABEL = get_user_input("Enter the key label: ", default="rsa_key")
158+
159+
# Prompt for the wrapping key label
160+
PKCS11_WRAPPING_KEY_LABEL = get_user_input("Enter the wrapping key label: ", default="wrapping_key")
161+
162+
# Prompt for the token label
163+
PKCS11_TOKEN_LABEL = get_user_input("Enter the token label: ", default="loadshared accelerator")
164+
165+
# Prompt for the pin
166+
PKCS11_PIN = get_user_input("Enter the token pin: ", default="1234")
167+
168+
# Get the token using the provided label
169+
token = lib.get_token(token_label=PKCS11_TOKEN_LABEL)
170+
171+
# Prompt for the key size
172+
key_size = get_user_input("Enter the key size (e.g., 2048): ", default=2048)
173+
MODULUS_BITS = key_size
174+
175+
# Check if the key size is valid
176+
if key_size not in [2048, 4096]:
177+
sys.exit('Error: Key size must be 2048 or 4096')
178+
179+
# Prompt to ask if user wants pubkey to be wrapping key.
180+
WRAPPING_KEY = get_user_input("Do you want the public key to be a wrapping key? (y/n): ", default="n").lower() == 'y'
181+
182+
# The following templates are used to generate a keypair on the HSM. Modify as needed.
183+
184+
public_key_template = {pkcs11.Attribute.TOKEN: True,
185+
pkcs11.Attribute.PUBLIC_EXPONENT: 65537, # Public exponent is always 65537.
186+
pkcs11.Attribute.MODULUS_BITS: MODULUS_BITS,
187+
pkcs11.Attribute.WRAP: WRAPPING_KEY,
188+
pkcs11.Attribute.VERIFY: True,
189+
pkcs11.Attribute.MODIFIABLE: True,
190+
pkcs11.Attribute.ENCRYPT: True,
191+
192+
}
193+
194+
private_key_template = {pkcs11.Attribute.TOKEN: True,
195+
pkcs11.Attribute.PRIVATE: True,
196+
pkcs11.Attribute.SENSITIVE: True,
197+
pkcs11.Attribute.UNWRAP: True,
198+
pkcs11.Attribute.MODIFIABLE: True,
199+
pkcs11.Attribute.EXTRACTABLE: True,
200+
pkcs11.Attribute.DECRYPT: True,
201+
pkcs11.Attribute.WRAP_WITH_TRUSTED: False,
202+
pkcs11.Attribute.SIGN: True,
203+
204+
}
205+
206+
# Open a session with the token
207+
with token.open(rw=True, user_pin=PKCS11_PIN) as session:
208+
try:
209+
# Try to get the existing private key
210+
key = session.get_key(object_class=pkcs11.ObjectClass.PRIVATE_KEY, label=PKCS11_KEY_LABEL)
211+
212+
print(f"Key pair with label '{PKCS11_KEY_LABEL}' already exists, wrapping private key portion instead...")
213+
time.sleep(2)
214+
215+
private = key
216+
217+
except pkcs11.exceptions.NoSuchKey:
218+
print(f"Key with label '{PKCS11_KEY_LABEL}' does not exist. Generating a new key pair...")
219+
public, private = session.generate_keypair(pkcs11.KeyType.RSA, key_size, public_template=public_key_template,
220+
private_template=private_key_template,
221+
label=PKCS11_KEY_LABEL)
222+
223+
# Print the public key information
224+
key_info = format_key_output(public, "Public")
225+
print(key_info)
226+
227+
# Print the private key information
228+
key_info = format_key_output(private, "Private")
229+
print(key_info)
230+
231+
print("Key pair generated successfully")
232+
233+
print(f"Wrapping key label: '{PKCS11_KEY_LABEL}' key with wrapping key: '{PKCS11_WRAPPING_KEY_LABEL}'... ")
234+
time.sleep(2)
235+
236+
try:
237+
# Try to get the existing wrapping key
238+
wrapping_key = session.get_key(label=PKCS11_WRAPPING_KEY_LABEL)
239+
if wrapping_key:
240+
print(f"Wrapping key with label: '{wrapping_key.label}' already exists.")
241+
time.sleep(1)
242+
print(f'Proceeding with wrap operation in 2 seconds...')
243+
time.sleep(1)
244+
245+
# Wrap the RSA private key
246+
wrapped_key = wrapping_key.wrap_key(private, mechanism=pkcs11.Mechanism.AES_KEY_WRAP_PAD) # Required mech to wrap private RSA keys.
247+
248+
249+
250+
251+
252+
except pkcs11.exceptions.NoSuchKey:
253+
# Generate the wrapping key if it doesn't exist
254+
print('Wrapping key does not exist. Generating a new wrapping key...')
255+
wrapping_key = session.generate_key(pkcs11.KeyType.AES, 256, label=PKCS11_WRAPPING_KEY_LABEL, store=True) # store=True is required otherwise it creates ephemeral keys which we do not want in a wrapping/unwrapping scenario.
256+
257+
# Print the wrapping key information
258+
key_info = format_key_output(wrapping_key, "Wrapping")
259+
print(key_info)
260+
261+
# Wrap the RSA private key
262+
wrapped_key = wrapping_key.wrap_key(private, mechanism=pkcs11.Mechanism.AES_KEY_WRAP_PAD) # Required mech to wrap private RSA keys.
263+
264+
if wrapped_key:
265+
print(f"Private key label: '{PKCS11_KEY_LABEL}' wrapped successfully using wrapping key: '{PKCS11_WRAPPING_KEY_LABEL}' ")
266+
time.sleep(1)
267+
else:
268+
print('Error generating wrapping key')
269+
sys.exit(1)
270+
271+
272+
273+
# Prompt to save the wrapped key
274+
save_to_file = input('Save the wrapped key material to a file? (y/n): ').strip().lower()
275+
if save_to_file == 'y':
276+
file_path = input('Enter the file name or full path to save the wrapped key (default is current directory): ').strip()
277+
278+
# Default to the current working directory if no path is provided
279+
if not file_path:
280+
# Create timestamped file name
281+
current_time = time.strftime("%Y%m%d-%H%M%S")
282+
283+
# Save the file in cwd with a timestamped filename
284+
file_path = os.path.join(os.getcwd(), f"{PKCS11_KEY_LABEL}_wrapped_key_{current_time}.bin")
285+
else:
286+
# Check if the provided path is a directory
287+
if os.path.isdir(file_path):
288+
file_path = os.path.join(file_path, f"{PKCS11_KEY_LABEL}_wrapped_key_{current_time}.bin")
289+
290+
# Create any missing directories in the custom path
291+
dir_name = os.path.dirname(file_path)
292+
if dir_name and not os.path.exists(dir_name):
293+
os.makedirs(dir_name)
294+
295+
# Write the wrapped key to the specified file
296+
with open(file_path, 'wb') as f:
297+
f.write(wrapped_key)
298+
print(f'Wrapped key saved to {file_path}')
299+
300+
print("Private key wrapped successfully")
301+
302+
303+
# Generate a CSR using the public key
304+
305+
# Prompt if you want to create a CSR
306+
csr_bool = input("Do you want to create a CSR using the public key? (y/n): ").strip().lower()
307+
308+
if csr_bool != 'y':
309+
print('No CSR generated')
310+
sys.exit('Exiting...')
311+
312+
elif csr_bool == 'y':
313+
314+
key_label = input("Enter the key label (e.g., rsa_key): ").strip()
315+
appname_ident = extract_appname_ident(key_label)
316+
317+
if appname_ident:
318+
print(f"Extracted identifier: {appname_ident}")
319+
else:
320+
sys.exit("Error: appname_ident not found")
321+
322+
323+
# Use the extracted appname_ident in the OpenSSL command
324+
command = f'openssl req -new -keyform engine -engine nfkm -key "{appname_ident}" -out csr.pem'
325+
print("Running OpenSSL command...")
326+
327+
try:
328+
output = subprocess.check_output(command, shell=True, text=True)
329+
print("CSR generated successfully!")
330+
print(output)
331+
except subprocess.CalledProcessError as e:
332+
print(f"Error generating CSR: {e}")
333+
sys.exit(1)
334+
335+
# Prompt to save the CSR
336+
save_to_file = get_user_input('Save the CSR to a file? (y/n): ', default='y').strip().lower()
337+
if save_to_file == 'y':
338+
file_path = input('Enter the file name or full path to save the CSR (default is current directory): ').strip()
339+
340+
# Default to the current working directory if no path is provided
341+
if not file_path:
342+
file_path = os.path.join(os.getcwd(), f"{PKCS11_KEY_LABEL}_csr.pem")
343+
else:
344+
# Check if the provided path is a directory
345+
if os.path.isdir(file_path):
346+
file_path = os.path.join(file_path, f"{PKCS11_KEY_LABEL}_csr.pem")
347+
348+
# Create any missing directories in the custom path
349+
dir_name = os.path.dirname(file_path)
350+
if dir_name and not os.path.exists(dir_name):
351+
os.makedirs(dir_name)
352+
353+
# Move the CSR file to the specified path
354+
os.rename(f'{PKCS11_KEY_LABEL}_csr.pem', file_path)
355+
print(f'CSR saved to {file_path}')
356+
357+
elif save_to_file == 'n':
358+
print("CSR not saved. Exiting...")
359+
# Capture the output of the command and print it
360+
361+
362+
363+
# If SAN is required, add the -reqexts SAN option to the command and provide the SAN details in the config file
364+
# (C:\Program Files\nCipher\nfast\openssl\openssl.cnf)
365+
# -reqexts SAN \-config <(cat /opt/nfast/openssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:example.com,DNS:www.example.com"))

0 commit comments

Comments
 (0)