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