1313from typing import Tuple , Dict
1414from types import FunctionType
1515
16- VERSION = "1.1.4a "
16+ VERSION = "1.1.5 "
1717
1818
1919def decode_n0 (response_to_decode : bytes , head_len : int ):
@@ -162,74 +162,6 @@ def decode_j8(response_to_decode: bytes, head_len: int):
162162 print ("Pin attacks: " , response_to_decode [str_pointer :str_pointer + 8 ])
163163
164164
165- def decode_nc (response_to_decode : bytes , head_len : int ):
166- """
167- It decodes the result of the command NC an prints the meaning of the returned output
168- The message trailer is not considered
169-
170- Parameters
171- ___________
172- response_to_decode: bytes
173- The response returned by the payShield
174- head_len: int
175- The length of the header
176-
177- Returns
178- ___________
179- nothing
180- """
181- response_to_decode , msg_len , str_pointer = common_parser (response_to_decode , head_len )
182- if response_to_decode [str_pointer :str_pointer + 2 ] == '00' :
183- str_pointer = str_pointer + 2
184- print ("LMK CRC:" , response_to_decode [str_pointer :str_pointer + 16 ])
185- str_pointer = str_pointer + 16
186- print ("Firmware number:" , response_to_decode [str_pointer :str_pointer + 9 ])
187-
188-
189- def decode_j8 (response_to_decode : bytes , head_len : int ):
190- """
191- It decodes the result of the command J8 an prints the meaning of the returned output
192- The message trailer is not considered
193-
194- Parameters
195- ___________
196- response_to_decode: bytes
197- The response returned by the payShield
198- head_len: int
199- The length of the header
200-
201- Returns
202- ___________
203- nothing
204- """
205- response_to_decode , msg_len , str_pointer = common_parser (response_to_decode , head_len )
206- if response_to_decode [str_pointer :str_pointer + 2 ] == '00' :
207- str_pointer = str_pointer + 2
208- print ("Serial Number: " , response_to_decode [str_pointer :str_pointer + 12 ])
209- str_pointer = str_pointer + 12
210- print ("Start Date: " , response_to_decode [str_pointer :str_pointer + 6 ])
211- str_pointer = str_pointer + 6
212- print ("Start Time: " , response_to_decode [str_pointer :str_pointer + 6 ])
213- str_pointer = str_pointer + 6
214- print ("End Date: " , response_to_decode [str_pointer :str_pointer + 6 ])
215- str_pointer = str_pointer + 6
216- print ("End Time: " , response_to_decode [str_pointer :str_pointer + 6 ])
217- str_pointer = str_pointer + 6
218- print ("Current Date: " , response_to_decode [str_pointer :str_pointer + 6 ])
219- str_pointer = str_pointer + 6
220- print ("Current Time: " , response_to_decode [str_pointer :str_pointer + 6 ])
221- str_pointer = str_pointer + 6
222- print ("Reboots: " , response_to_decode [str_pointer :str_pointer + 10 ])
223- str_pointer = str_pointer + 10
224- print ("Tampers: " , response_to_decode [str_pointer :str_pointer + 10 ])
225- str_pointer = str_pointer + 10
226- print ("Pin verifies/minute: " , response_to_decode [str_pointer :str_pointer + 7 ])
227- str_pointer = str_pointer + 7
228- print ("Pin verifies/hour: " , response_to_decode [str_pointer :str_pointer + 5 ])
229- str_pointer = str_pointer + 5
230- print ("Pin attacks: " , response_to_decode [str_pointer :str_pointer + 8 ])
231-
232-
233165def decode_b2 (response_to_decode : bytes , head_len : int ):
234166 """
235167 It decodes the result of the command B2 an prints the meaning of the returned output
@@ -360,7 +292,7 @@ def decode_jk(response_to_decode: bytes, head_len: int):
360292 nothing
361293 """
362294 # structures to decode the result
363- # We cam use CONSOLE_STATUS_CODE to check the status of the payShield Manager as well.
295+ # We can use CONSOLE_STATUS_CODE to check the status of the payShield Manager as well.
364296
365297 CONSOLE_STATUS_CODE = {
366298 '0' : 'unknown' ,
@@ -487,6 +419,36 @@ def decode_jk(response_to_decode: bytes, head_len: int):
487419 print ("" )
488420
489421
422+ def decode_ecc (response_to_decode : bytes , head_len : int ):
423+ """
424+ It decodes the result of the command FY an prints the meaning of the returned output
425+
426+ Parameters
427+ ___________
428+ response_to_decode: bytes
429+ The response returned by the payShield
430+ head_len: int
431+ The length of the header
432+
433+ Returns
434+ ___________
435+ nothing
436+ """
437+ response_to_decode , msg_len , str_pointer = common_parser (response_to_decode , head_len )
438+ if response_to_decode [str_pointer :str_pointer + 2 ] == '00' :
439+ str_pointer = str_pointer + 2
440+ key_len = int (response_to_decode [str_pointer :str_pointer + 4 ])
441+ print ("ECC Public Key Length: " , key_len )
442+ str_pointer = str_pointer + 4
443+ print ("ECC Public Key" ,
444+ binascii .hexlify ((response_to_decode [str_pointer :str_pointer + key_len ]).encode ())
445+ .decode ('ascii' , 'ignore' ))
446+ print ("Public/private separator: " , response_to_decode [str_pointer + key_len :str_pointer + key_len + 1 ])
447+ str_pointer = str_pointer + key_len + 1
448+ print ("ECC Private Key under LMK" ,
449+ response_to_decode [str_pointer :])
450+
451+
490452def payshield_error_codes (error_code : str ) -> str :
491453 """This function maps the result code with the error message.
492454 I derived the list of errors and messages from the following manual:
@@ -595,7 +557,11 @@ def payshield_error_codes(error_code: str) -> str:
595557 'BB' : 'Invalid wrapping key' ,
596558 'BC' : 'Repeated optional block' ,
597559 'BD' : 'Incompatible key types' ,
598- 'BE' : 'Invalid key block header ID' }
560+ 'BE' : 'Invalid key block header ID' ,
561+ 'D2' : 'Invalid curve reference' ,
562+ 'D3' : 'Invalid Key Encoding' ,
563+ 'E0' : 'Invalid command version number'
564+ }
599565
600566 return PAYSHIELD_ERROR_CODE .get (error_code , "Unknown error" )
601567
@@ -827,7 +793,8 @@ def common_parser(response_to_decode: bytes, head_len: int) -> Tuple[str, int, i
827793 'J2' : decode_j2 ,
828794 'J4' : decode_j4 ,
829795 'JK' : decode_jk ,
830- 'B2' : decode_b2
796+ 'B2' : decode_b2 ,
797+ 'FY' : decode_ecc
831798 }
832799
833800 parser = argparse .ArgumentParser (
@@ -837,8 +804,7 @@ def common_parser(response_to_decode: bytes, head_len: int) -> Tuple[str, int, i
837804 parser .add_argument ("host" , help = "Ip address or hostname of the payShield" )
838805 group = parser .add_mutually_exclusive_group ()
839806 parser .add_argument ("--port" , "-p" , help = "The host port" , default = 1500 , type = int )
840- group .add_argument ("--key" , help = "RSA key length. Accepted values are 2048 and 4096." ,
841- default = 2048 , choices = [2048 , 4096 ], type = int )
807+ group .add_argument ("--key" , help = "RSA key length. Accepted values are between 320 and 4096." , type = int )
842808 group .add_argument ("--nc" , help = "Just perform a NC test. " ,
843809 action = "store_true" )
844810 group .add_argument ("--no" , help = "Retrieves HSM status information using NO command. " ,
@@ -860,6 +826,15 @@ def common_parser(response_to_decode: bytes, head_len: int) -> Tuple[str, int, i
860826 help = "Echo received data back to the user." , action = "store_true" )
861827 group .add_argument ("--randgen" ,
862828 help = "Generate a random value 8 bytes long." , action = "store_true" )
829+ group .add_argument ("--ecc" ,
830+ help = "Generate an ECC public/private key pair using the Elliptic Curve algorithm curve NIST "
831+ "P-521." ,
832+ action = "store_true" )
833+ parser .add_argument ("--ecc-curve" , help = "select the ECC curve." , default = '0' , type = str , choices = ['0' , '1' , '2' ])
834+ parser .add_argument ("--key-use" , help = "select the key mode of use." , default = 'S' , type = str .upper ,
835+ choices = ['S' , 'X' , 'N' ])
836+ parser .add_argument ("--key-exportability" , help = "select the key exportability." , default = 'S' , type = str .upper ,
837+ choices = ['N' , 'E' , 'S' ])
863838 parser .add_argument ("--header" ,
864839 help = "the header string to prepend to the host command. If not specified the default is HEAD." ,
865840 default = "HEAD" , type = str )
@@ -881,10 +856,16 @@ def common_parser(response_to_decode: bytes, head_len: int) -> Tuple[str, int, i
881856 args = parser .parse_args ()
882857 # the order of the IF here is important due to the default arguments.
883858 # All the mutually exclusive options need to be in this block where ELIF statements are used.
884- if args .key == 2048 :
885- command = args .header + 'EI2204801#0000'
886- elif args .key == 4096 :
887- command = args .header + 'EI2409601#0000'
859+ command = ''
860+ if args .key is not None :
861+ if 320 <= args .key <= 4096 :
862+ k_len_str = str (args .key )
863+ if len (k_len_str ) <= 3 :
864+ k_len_str = '0' + k_len_str
865+ command = args .header + 'EI2' + k_len_str + '01#0000'
866+ elif args .key < 320 or args .key > 4096 :
867+ print ("The key length value needs to be between 320 and 4096" )
868+ exit ()
888869 elif args .nc :
889870 command = args .header + 'NC'
890871 elif args .no :
@@ -901,6 +882,8 @@ def common_parser(response_to_decode: bytes, head_len: int) -> Tuple[str, int, i
901882 command = args .header + 'JK'
902883 elif args .randgen :
903884 command = args .header + 'N0008'
885+ elif args .ecc :
886+ command = args .header + 'FY010' + args .ecc_curve + '03#' + args .key_use + '00' + args .key_exportability + '00'
904887 if args .b2 :
905888 # we need to calculate the hexadecimal representation of the length of the payload string
906889 # the length of the string field is 4 char long so we need to format it accordingly
@@ -910,8 +893,17 @@ def common_parser(response_to_decode: bytes, head_len: int) -> Tuple[str, int, i
910893 h_padding = '0000'
911894 len_echo_message = len (args .echo )
912895 hex_string_len = hex (len_echo_message ).lstrip ('0x' ).upper ()
896+ # using lstrip() to strip the '0x' prefix is acceptable due to the expected pattern
897+ # Ideally you should use removeprefix() but it was introduced in python 3.9 and I want to keep compatibility
913898 hex_string_len = h_padding [:4 - len (hex_string_len )] + hex_string_len
914899 command = args .header + 'B2' + hex_string_len + args .echo
900+
901+ # IMPORTANT: At this point the 'command' need to contain something.
902+ # If you want to add to the tool command link arguments about commands do it before this comment block
903+ # Now we verify if the command variable is empty. In this case we thrown an error.
904+ if len (command ) == 0 :
905+ print ("You forgot to specify the action you want to to perform on the payShield" )
906+ exit ()
915907 if args .proto == 'tls' :
916908 # check that the cert and key files are accessible
917909 if not (args .keyfile .exists () and args .crtfile .exists ()):
0 commit comments