Adding a secret password to your device

Perhaps the simple, default MIRTOS Manager or Administrative mode logins are not enough security for your applcation or liking. Presented here is a way to add an additional secret password to the login sequence before your device accepts commands.

This example introduces the concept of a header, or an introductory description of the routine. It allows you to convey to someone else what you were thinking when you created the routine. Headers will also help you remember when you look back on your own code a year or two later. It can be omitted, but providing good documentation is really good practice.

All of the code is presented before the instructions on how to build the and assembled the source into an iHEX image to upload it to your device.

/*****************************************************************************
*
*                      "SecretPassword" Command Extension
*
*  Purpose: Prevent any command from executing until "werty" is input.  The
*           string should be changed to be as complex as desired, even with
*           fewer or more characters.
*
*           This routine could have used a string embedded in FLASH memory
*           then used the strcmpPGM() system call to compare that string to
*           the one input.  That method would allow the password string to
*           be revealed if the user dumps that area of FLASH memory.  Of
*
*  Assumes: The original Command Extensions were in place and are located at
*           0x3600 in the 328P and 0x0EE00 in the 1284P or 2560.
*
*  Passed:  R31:R30 *CommandExtensions (this subroutine's address via ICALL)
*           R29:R28 pointer to uVars.B[0]
*           R27:R26 pointer to the bBuffer being parsed (format notes below):
*           R23     third  buffered character3, uppercase (but may be 0)
*           R22     second buffered character, uppercase (but may be 0)
*           R21     first  buffered character, uppercase; the command byte
*           R20     string length, or buffered byte count
*           R19:R18 the WORD address of the jump table handler
*
*  Returns: ZERO FLAG TRUE (or set) if NOT handled, FALSE if it was handled
*              (Note that the first command upon return is a BRNE past the
*               default Operating System command handler to the EXIT.)

Because this header is long, it is broken into two parts, each of which can simply be copied into the clipboard (Control-C in Windows.) The next header section contains some important coding rules so this routine can work with the original Command Extensions and pass on the command when the password has been accepted and the command is to be executed.


*  Alters:  If desiring the default command processor to handle it in the
*           usual way (i.e., not intercepted), the only changes that can be
*           safely made are to the FLAGs, R0, R18, R19, R24, R25, R30, and
*           R31.  When no further system command processing is to be done,
*           all of those plus R20 to R23 may be changed without consequence.
*           Save and restore all the state of other Registers used.  These
*           are the customary register usage rules for subroutines.
*
*  Notes:   The expected buffer format on entry:
*                   +-------+-------+------+------+-/ /-+----+-/ /-+---+---+
*       R27:R26 --> | Byte  |Command| Byte | Byte |     |NULL|     |   |   |
*       pointer     | Count |  Char |  #2  |  #3  | ... |(=0)| ... | 0 | 0 |
*                   +-------+-------+------+------+-/ /-+----+-/ /-+---+---+
*                 in:  R20     R21     R22    R23         ^ string terminator
*
*           To activate, enter the command "EW 70 = 0x29C0" for this example
*           in the 328P MCU or "EW 70 = 0xEE00" in the 1284P or 2560 MCUs.
*           This initializes the WORD pointer at EEPROM address 0x46 to point
*           to this routine (but any non-system address above 0x100 may be
*           use as the entry point.)
*
*           If using R21, R22, and/or R23, remember that they have been
*           converted to uppercase.  To do a full case sensitive comparison,
*           obtain them directly from the command buffer, as 't' and 'y' are
*           handled in the code below.
*
******************************************************************************/

This next section of code handles any of the MCUs which MIRTOS supports. The only thing needed to select the 1284 or the 2560 rather than the default 328 processor is to modify the first line. If the label address file is not read, the warning reminder is printed. Another way not to need this comment is to generate a .PTR file that has no Assembler instructions, such as a blank or comment line. The Assemble.Bat script rebuilds SecretPassword.Ptr after every successful Assembly. Also note that the two commented .ORG addresses suggest some other possible code locations.


PROCESSOR = 328

; Next line commented out on the first assembly to squelch "Not found" error.
;.INCLUDE "SecretPassword.Ptr"

.INCLUDE "BinDefs.Def"
.IFNDEF AVerify_SecretPassword
  .PRINT "Remember to reenable .INCLUDE \"SecretPassword.Ptr\""
.ENDIF

.IF PROCESSOR == 328
   .INCLUDE "IOM328P.Def"
   AOriginalExtension = 0x3600
   .ORG 0x29C0                       ; Just before "nRF Extensions:" in FLASH
;  .ORG 0x3728                       ; Command Extension slack area begins at
;  .ORG 0x3730                       ;  0x3728 and end at 0x3775; either work
   .INCLUDE "Addresses328.Def"
   bSysModeTimer = 0x11A
   bFlags_System = 0x129
.ENDIF
.IF PROCESSOR == 1284
   .INCLUDE "IOM1284P.Def"
   AOriginalExtension = 0xEE00
   .ORG 0xE200
   .INCLUDE "Addresses1284.Def"
   bSysModeTimer = 0x11A
   bFlags_System = 0x129
.ENDIF
.IF PROCESSOR == 2560
   .INCLUDE "IOM2560.Def"
   AOriginalExtension = 0xEE00
   .ORG 0xE200
   .INCLUDE "Addresses2560.Def"
   bSysModeTimer = 0x21A             ; Note: these two SRAM address differ from
   bFlags_System = 0x229             ;  the others; the 2560 has 512 Registers
.ENDIF                               ;  while the 328 and 1284 each have 256

; The bFlags_System bits:
 BIT_SYS_MGR_MODE     = 0 ;   1  In Manager configuration mode
 BIT_SYS_SCRIPT_FLASH = 1 ;   2  Read EEPROM (0) or FLASH (1) in USER logic
 BIT_SYS_VALID_SERNO  = 2 ;   4  Serial Number was valid when last checked
 BIT_SYS__spare3__    = 3 ;   8  \  These 2 BITs   \   All 4 BITs may be used
 BIT_SYS__spare4__    = 4 ;  16  /  don't timeout   \   for other functions,
 BIT_SYS_PRIV5        = 5 ;  32  \  These 2 BITs    /   if desired (timed BITS
 BIT_SYS_PRIV6        = 6 ;  64  /  use timeout    /    for privileged modes)
 BIT_SYS_ADMIN_MODE   = 7 ; 128  In Administrative configuration mode

;------------------------------------

That section was longer than the window in which it is displayed. Be sure to copy everything in that window. The BIT name constants beneath the bFlags_System BYTE show the uses for the BITs within it. The blue highlighted BIT_SYS_PRIV5 name indicates the one which we'll be using. The two with "PRIV" in their names will timeout if there is no input into COM1 for 255 seconds. They work in the same way that the Manager and Administrator privileged modes work, with one exception. There is a discrete output specified by EEbLED_LoginStatus (EEPROM address 32) which may optionally be configured to light an LED when in any of Manager, Admin, or BIT_SYS_PRIV6 privileged modes. The BIT_SYS_PRIV5 selected here is the only one purposely NOT reflected in that output. If you want to show when not password protected, just switch this routine to use BIT_SYS_PRIV6 instead.

Although they aren't a part of this example, the two BITs constants with "spare" in their names function in bFlags_System in the same way that BIT_SYS_SCRIPT_FLASH and BIT_SYS_VALID_SERNO do. Those four (4) BITs neither invoke the keypress timer, nor are they reset by it when it times out for the four (4) privileged mode bits.

The first two operations in the next section only change Register R24 and not even the FLAGS. They check to see if the password has recently been input and the keypress timeout period has not yet expired. If so, it just returns, allowing all the original routines to function normally. The last two lines allow the nRF24L01 commands to be passed through even when password protection is active.


SecretPassword:
   LDS     R24, bFlags_System        ; Obtain the MIRTOS System Flags BYTE
   SBRC    R24, BIT_SYS_PRIV5        ; If our flag is already set, ...

RJumpToOriginalExtension:
   RJMP    AOriginalExtension        ; Thers is no need to check any further

AlwaysAllow_N:
   CPI     R21, 'N'                  ; Allow all nRF24L01+ commands in order
   BREQ    ARJumpToOriginalExtension ;  not to disable communications

When the valid password is required to proceed, the next section is executed. It is looking for the character sequence 'werty' before the carriage return, although it doesn't check to see if there are more characters after those, which it could. If the password is invalid, it just returns to the calling routine telling it, in effect, "I've already processed that command." The caller then returns to its caller, thereby discarding the command.


Verify_SecretPassword:
   CPI     R21, 'W'                  ; First command character, uppercase
   BRNE    AIgnoreCommand            ; Exit if not 'W' (= 0x57 = ASCII 87)

Check_E:
   CPI     R22, 'E'                  ; Second command character, uppercase
   BRNE    AIgnoreCommand            ; Exit if not 'E' (= 0x45 = ASCII 69)

Check_R:
   CPI     R23, 'R'                  ; Third command character, uppercase
   BRNE    AIgnoreCommand            ; Exit if not 'R' (= 0x52 = ASCII 82)

Check_t:
   MOVW    R30, R26                  ; Transfer the command buffer pointer (in
   LDD     R25, Z+4                  ;  R27:R26 upon subroutine entry) to 'Z'
   CPI     R25, 't'                  ; Fourth command character, as entered
;# RCALL   ANear_DumpRegs_Begin      ; Optional debug output commented out
   BREQ    ACheck_y

IgnoreCommand:
   CLZ                               ; Clear the ZERO flag, which returns the
   RET                               ;  "already handled" reply to the caller

Check_y:
   LDD     R25, Z+5                  ; Retrieve the fifth command character,
   CPI     R25, 'y'                  ;  as entered
;# RCALL   ANear_DumpRegs_Begin
   BRNE    AIgnoreCommand

Note that there are a couple of commands which are commented out above and one below, as well as the local subroutine which they call. They cause the Registers and optionally, the stack, to be displayed (or "dumped") to the COM1 buffer. Any or all of them can certainly be enabled for debugging or demonstration purposes, if you like.

Finally, when the password has been successfully input, this is the code which is executed. It sets the BIT_SYS_PRIV5 bit in R24, which was read from bFlags_System in this routine's very first statement. It then updates that system variable back to SRAM. Notice that the ORI instruction clears the ZERO flag, returning "already handled" to the calling routine.


Update_MIRTOS_Flags:
   ORI     R24, 1 << BIT_SYS_PRIV5   ; This generates the bitmask B00100000
   STS     bFlags_System, R24
;# RCALL   ANear_DumpRegs_Begin      ; Note: the DumpRegs() routines preserve
   RJMP    AOriginalExtension        ;  all FLAGS as well as all Registers

;#Near_DumpRegs_Begin:
;# JMP     ADumpRegs_Begin

   .BALIGN 16, -1, 15

To create the file and Assemble this routine, open a command console and type the Copy command as shown. CON is a reserved Windows system device name for this command console's input device. The command means copy from the console input (i.e., the keyboard or paste buffer) to the file named. Go back to each code window above, copy (Ctrl-C) it into the clipboard, and paste (Ctrl-V) it into this command console window. The single blank line at the top of most of the windows above aren't essential, but helps to create a more readable source code file. Append each block to the target file, ending with a Carriage Return (or Enter) then Ctrl-Z on the new line when done. This lets the console application know that the file input has ended and that it should close the file.

C:\TestDir>Copy CON SecretPassword.Asm
   (copy each of the six (6) block above)
^Z
        1 file(s) copied.

C:\TestDir>Assemble SecretPassword
Remember to reenable .INCLUDE "SecretPassword.Ptr"

C:\TestDir>Edit SecretPassword.Asm
   (remove the semicolon before ".INCLUDE" on about line 60)
   (save and close SecretPassword.Asm

C:\TestDir>Assemble SecretPassword

C:\TestDir>Dir                                                           

The last assembly should not generate any errors but should have created seven (7) new files. Open the SecretPassword.Dmp file. Verify that it contains no ".+0" string. That would indicate unresolved jump or call target line references or unresolved subroutine references. If it does, they will need to be resolved before proceeding.

Open the SecretPassword.Trm file and copy all five (5) of the lines which start with a colon. Once in Manager mode (reminder: use the '$' "first character"), paste them into your device's input buffer.

m>:1029C0008091290185FD1CC65E34E9F3573541F439
m>:1029D000653431F4723521F4FD019481943711F09E
m>:1029E0009894089595819937D9F780628093290149
m>:1029F00007C6FFFFFFFFFFFFFFFFFFFFFFFFFFFF18
m>:00000001FF  ; The first EOF updates the block to FLASH
m>:00000001FF  ; The second EOF releases the iHex buffer
m>DFH+ 0x29C0 80
FLASH (program memory) contents:
:1029C0008091290185FD1CC65E34E9F3573541F439
:1029D000653431F4723521F4FD019481943711F09E
:1029E0009894089595819937D9F780628093290149
:1029F00007C6FFFFFFFFFFFFFFFFFFFFFFFFFFFF18
:102A00006E524620457874656E73696F6E733A0036
:00000001FF
m>             ; Exit Manager mode
>                                                                               

The next step is to test the routine. To do that, we'll first need to change the pointer to (or "address of") the Command Extension to the one just loaded. It will immediately activate the password requirement. Follow along and enter the commands shown. The comments can either be included or omitted. The underlined values are added to draw your attention to the addresses being changed. They will not be underlined in your console output.

>DSE
EEPROM contents:
0000:  10 21 00 9F 01 00 84 FF-04 00 FF FF 28 01 50 FF  .!..........(.P.
0010:  C0 00 3F FF 60 08 03 67-FF 15 74 FF FF FF FF FF  ..?.`..g..t.....
0020:  22 28 05 00 FF FF 38 FF-FF FF 14 20 00 08 FF FF  "(....8.... ....
0030:  80 16 FF FF FF FF FF FF-90 00 B0 00 80 01 FF FF  ................
0040:  FF FF FF FF F0 01 00 36-FF FF FF FF FF FF FF FF  .......6........
>EW 0x46 = 0x29C0   ; That's our new password subroutine's FLASH address
>DSE            ; Password code activated, so there should be no response
>werty          ; Enter the programmed password
>DE+ 0x40 16
EEPROM contents:
0040:  FF FF FF FF F0 01 C0 29-FF FF FF FF FF FF FF FF  .......)........
>ES 0x129 ^= 32 ; This command will lock the input buffer again
>DES            ; Generates no response since password is required again
>werty
>ES 0x11A = 3   ; Change the timeout timer to 3 seconds (wait 4 sec.)
>DES            ; Again, there will be no response
>WERty
>EW 0x46 = 0x3600   ; Disable password checking; restore prior extensions
>DES
EEPROM contents:
0000:  10 21 00 9F 01 00 84 FF-04 00 FF FF 28 01 50 FF  .!..........(.P.
0010:  C0 00 3F FF 60 08 03 67-FF 15 74 FF FF FF FF FF  ..?.`..g..t.....
0020:  22 28 05 00 FF FF 38 FF-FF FF 14 20 00 08 FF FF  "(....8.... ....
0030:  80 16 FF FF FF FF FF FF-90 00 B0 00 80 01 FF FF  ................
0040:  FF FF FF FF F0 01 00 36-FF FF FF FF FF FF FF FF  .......6........
>                                                                               

That should be a good starting point for creating and testing your own password routine. One reminder: The SRAM addresses used here are those for the 328P and 1284P Microcontrollers. SRAM addresses in the 2560 are 256 (= 0x100) BYTEs higher, since it reserves 512 BYTEs for its register bank instead of 256 BYTEs.