In this lesson we would investigate the password
VFR element.
For that let's create a driver PasswordForm
with the following VFR code (UefiLessonsPkg/PasswordForm/Form.vfr
):
#include <Uefi/UefiMultiPhase.h>
#include "Data.h"
formset
guid = FORMSET_GUID,
title = STRING_TOKEN(FORMSET_TITLE),
help = STRING_TOKEN(FORMSET_HELP),
varstore VARIABLE_STRUCTURE,
name = FormData,
guid = STORAGE_GUID;
defaultstore StandardDefault,
prompt = STRING_TOKEN(STANDARD_DEFAULT_PROMPT),
attribute = 0x0000;
form
formid = 1,
title = STRING_TOKEN(FORMID1_TITLE);
password
varid = FormData.Password,
prompt = STRING_TOKEN(PASSWORD_PROMPT),
help = STRING_TOKEN(PASSWORD_HELP),
minsize = PASSWORD_MIN_LEN,
maxsize = PASSWORD_MAX_LEN,
endpassword;
endform;
endformset;
As you can see we can set minimal and maximum size for the password via its minsize
/maxsize
fields.
The values for these defines and the overall varstore
structure as usually we would place to the header file (UefiLessonsPkg/PasswordForm/Data.h
):
#ifndef _DATA_H_
#define _DATA_H_
#define FORMSET_GUID {0xe54b953d, 0x7ddc, 0x455c, {0x8c, 0x1a, 0x59, 0x92, 0xbb, 0xc7, 0x72, 0xc4}}
#define DATAPATH_GUID {0xb289cf9f, 0xf911, 0x41fd, {0x9b, 0xad, 0x2c, 0x92, 0x57, 0x68, 0x86, 0x64}}
#define STORAGE_GUID {0xe7f2d73c, 0x699a, 0x4606, {0x92, 0xb6, 0xa3, 0x5e, 0x49, 0x27, 0xc4, 0xd4}}
#define PASSWORD_MIN_LEN 6
#define PASSWORD_MAX_LEN 8
#define PASSWORD_STORAGE_SIZE 9
#pragma pack(1)
typedef struct {
CHAR16 Password[PASSWORD_STORAGE_SIZE];
} VARIABLE_STRUCTURE;
#pragma pack()
#endif
Here you can see that internally the password
element is a simple string. Looking ahead we set the size of the array PASSWORD_STORAGE_SIZE = (PASSWORD_MAX_LEN + 1)
. This way the password string would be stored along with the null-terminator symbol. This way it would be easier to use standard string handling function on the password data.
Now let's compile our application and try to use it.
Unfortunately when we try to access the password
element we would get the following error:
So let's add flags = INTERACTIVE
to our element. As you know with that flag our element would call EFI_HII_CONFIG_ACCESS_PROTOCOL.Callback()
function when we would access the element (open/close its form, try to change value of the element).
In our case the password
element is the only element on the form, so all the generated callbacks would be the callbacks generated by it. But as a good tone let's also add key = KEY_PASSWORD
to the VFR code:
password
varid = FormData.Password,
prompt = STRING_TOKEN(PASSWORD_PROMPT),
help = STRING_TOKEN(PASSWORD_HELP),
flags = INTERACTIVE,
key = KEY_PASSWORD,
minsize = PASSWORD_MIN_LEN,
maxsize = PASSWORD_MAX_LEN,
endpassword;
Don't forget to add #define KEY_PASSWORD 0x1234
to the UefiLessonsPkg/PasswordForm/Data.h
.
Before diving into the actual callback code let's make a short stop exploring the IFR code of the element:
The password element produces the EFI_IFR_PASSWORD
and EFI_IFR_END
opcodes: Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/PasswordForm/PasswordForm/DEBUG/Form.lst
password
>00000058: 08 91 06 00 07 00 34 12 01 00 00 00 04 06 00 08 00
varid = FormData.Password,
prompt = STRING_TOKEN(0x0006),
help = STRING_TOKEN(0x0007),
flags = INTERACTIVE,
key = 0x1234,
minsize = 6,
maxsize = 8,
endpassword;
>00000069: 29 02
Here is a definition for the EFI_IFR_PASSWORD
:
EFI_IFR_PASSWORD
Summary:
Creates a password question
Prototype:
#define EFI_IFR_PASSWORD_OP 0x08
typedef struct _EFI_IFR_PASSWORD {
EFI_IFR_OP_HEADER Header;
EFI_IFR_QUESTION_HEADER Question;
UINT16 MinSize;
UINT16 MaxSize;
} EFI_IFR_PASSWORD;
Members:
Header The sequence that defines the type of opcode as well as the length of the opcode being defined.
Header.OpCode = EFI_IFR_PASSWORD_OP
Question The standard question header
MinSize The minimum number of characters that can be accepted for this opcode
MaxSize The maximum number of characters that can be accepted for this opcode
Description:
Creates a password question in the current form.
We've covered EFI_IFR_OP_HEADER
and EFI_IFR_QUESTION_HEADER
many times, and MinSize
/MaxSize
are pretty trivial. So there is nothing new for us here. But it is good to know that we understand the underlying IFR code now.
Now we need to write an implementation for the EFI_HII_CONFIG_ACCESS_PROTOCOL.CallBack()
to correctly handle our password element.
This element is kind of special and UEFI specification even defines a dedicated flowchart about how the browser should communicate with the driver having the password
element:
The specification also has part two
of the flowchart, but currently it seems like is not supported in the edk2 code. The part two
is for password
elements without the flags = INTERACTIVE
, but as you saw earlier the Form Browser simply outputs an error for such cases.
Before we would write the necessary callback code we need to understand when and how the Form Browser will call Callback()
.
Here is couple of observations regarding the Form Browser:
- When we open the form
Callback()
function is called 2 times for the password element, one with theAction=EFI_BROWSER_ACTION_FORM_OPEN
argument and one with theAction=EFI_BROWSER_ACTION_RETRIEVE
argument, in both cases the passed agumentValue
would be the string token pointing to the empty string "":
EFI_BROWSER_ACTION_FORM_OPEN (Value="")
EFI_BROWSER_ACTION_RETRIEVE (Value="")
This is even true when the password has some value, the Form Browser still woudn't pass it to Callback()
. This is a security measure.
- When we close the form
Callback()
function is called 1 time, also with an empty string:
EFI_BROWSER_ACTION_FORM_CLOSE (Value="")
- All the rest calls of the
Callback()
function would happen with theAction=EFI_BROWSER_ACTION_CHANGING
argument.
We don't need to do anything special in the EFI_BROWSER_ACTION_FORM_OPEN
/EFI_BROWSER_ACTION_RETRIEVE
callbacks, so all our handling code can be placed inside the EFI_BROWSER_ACTION_CHANGING
action handling:
STATIC
EFI_STATUS
EFIAPI
Callback (
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN EFI_BROWSER_ACTION Action,
IN EFI_QUESTION_ID QuestionId,
IN UINT8 Type,
IN OUT EFI_IFR_TYPE_VALUE *Value,
OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest
)
{
if ((QuestionId == KEY_PASSWORD) && (Action == EFI_BROWSER_ACTION_CHANGING)) {
<...>
}
return EFI_UNSUPPORTED;
}
Now to the actual logic. According to the UEFI specification password flowchart when you access the password element (i.e. try to change it) first the browser asks if the password is already set. For that the form browser calls the Callback()
function with a Value
string token pointing to an empty string. In the edk2 code the Form Browser uses Action=EFI_BROWSER_ACTION_CHANGING
for that action.
Let's start with a case when the password is not yet set. Then the Callback()
code should return EFI_SUCCESS
for the above call.
After that the Form Browser prompts user to type new password:
If the input is valid the Form Browser prompts again for password confirmation:
If the user input is incorrect (user has typed password with a length less than password minsize
, or user has typed the second time different password) the Form Broswer calls the Callback()
function with the Value
equal to 0. Keep in mind that since in this case Value
is a string token, you shouldn't even try to get an actual string from that with the HiiGetString
call.
Here is a flowchart describing possible form browser calls:
EFI_BROWSER_ACTION_CHANGING (Value="")
[Callback returns EFI_SUCCESS to say that password is not set yet]
|
"Please type in your new password"
----------------------------------------------------------------------------
| |
"Please confirm your new password" "Please enter enough characters"
| EFI_BROWSER_ACTION_CHANGING (Token=ZERO)
|
----------------------------------------------------------------------------------------------------------------------------------
| | |
EFI_BROWSER_ACTION_CHANGING (Value="<USER INPUT>") "Please enter enough characters" "Passwords are not the same"
[Callback sets password and returns EFI_SUCCESS] EFI_BROWSER_ACTION_CHANGING (Token=ZERO) EFI_BROWSER_ACTION_CHANGING (Token=ZERO)
Here is a minimal code to support that logic:
EFI_STATUS HandlePasswordInput(EFI_STRING Password)
{
if (Password[0] == 0) {
// Form Browser checks if password exists
if (StrLen(FormStorage.Password) != 0) {
//TODO
}
return EFI_SUCCESS;
}
// Form Browser sends 'new passwd' to set
if (FormStorage.Password[0] == 0) {
StrnCpyS(FormStorage.Password, PASSWORD_STORAGE_SIZE, Password, StrLen(Password));
return EFI_SUCCESS;
}
}
STATIC
EFI_STATUS
EFIAPI
Callback (
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN EFI_BROWSER_ACTION Action,
IN EFI_QUESTION_ID QuestionId,
IN UINT8 Type,
IN OUT EFI_IFR_TYPE_VALUE *Value,
OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest
)
{
EFI_STATUS Status;
if ((QuestionId == KEY_PASSWORD) && (Action == EFI_BROWSER_ACTION_CHANGING)) {
if (Value->string == 0) {
return EFI_UNSUPPORTED;
}
EFI_STRING Password = HiiGetString(mHiiHandle, Value->string, "en-US");
Status = HandlePasswordInput(Password);
FreePool(Password);
return Status;
}
return EFI_UNSUPPORTED;
}
Now we can successfully set initial value for the password from the browser. So it it time to investigate the Form Browser Callback()
calls for the case when password is already set.
As before, when we try to access the password element, first the browser would ask if the password is already set. But this time we should return an error. We would return EFI_ALREADY_STARTED
(other errors are possible, but let's stick with that).
After that the Form Browser will ask user to input the current password.
If it has enough characters the browser will pass the input to the Callback()
. The Callback()
code should check if the input is equal to the old password. If everything is ok, the code should return EFI_SUCCESS
.
After that the Form Browser will ask user to type new password:
And if everything correct to confirm it:
The logic is the same as earlier. If the user input is incorrect (user has typed password with a length less than password minsize
, or user typed the second time different password) the Form Broswer calls the Callback()
function with the Value
equal to 0.
Once again here is a flowchart for better understanding:
EFI_BROWSER_ACTION_CHANGING (Value="")
[Callback should return EFI_ALREADY_STARTED to say the password is set]
|
"Please type in your password"
|
----------------------------------------------------------------------------
| |
EFI_BROWSER_ACTION_CHANGING (Value="<USER INPUT>") "Please enter enough characters"
[Callback checks if <USER INPUT> equal to current password]
[and returns EFI_SUCCESS on match, EFI_NOT_READY on fail]
|
---------------------------------------------------------------------------
| |
"Please type in your new password" "Incorrect password"
|
----------------------------------------------------------------------------
| |
"Please confirm your new password" "Please enter enough characters"
| EFI_BROWSER_ACTION_CHANGING (Token=ZERO)
|
----------------------------------------------------------------------------------------------------------------------------------
| | |
EFI_BROWSER_ACTION_CHANGING (Value="<USER INPUT>") "Please enter enough characters" "Passwords are not the same"
[Callback sets password and returns EFI_SUCCESS] EFI_BROWSER_ACTION_CHANGING (Token=ZERO) EFI_BROWSER_ACTION_CHANGING (Token=ZERO)
Since now the Form Browser can send non-empty string not only to set the new password, but also to verify the old one, we need to understand our place in the flowchart for the correct input handling. For that let's create a global variable BOOLEAN OldPasswordVerified
:
- initially we would set this variable to
FALSE
, - every time the user resets the Form Browser logic typing incorrect data, we would set it to
FALSE
, - when the old password is verified we would set it to
TRUE
, - when the password is updated correctly, we would set it back to
FALSE
again.
With everything in mind let's update our HandlePasswordInput()
code:
BOOLEAN OldPasswordVerified = FALSE;
EFI_STATUS HandlePasswordInput(EFI_STRING Password)
{
if (Password[0] == 0) {
// Form Browser checks if password exists
if (FormStorage.Password[0] != 0) {
return EFI_ALREADY_STARTED;
} else {
return EFI_SUCCESS;
}
} else {
// Form Browser sends password value
// It can be old password to check or initial/updated password to set
if (FormStorage.Password[0] == 0) {
// Set initial password
StrnCpyS(FormStorage.Password, PASSWORD_STORAGE_SIZE, Password, StrLen(Password));
return EFI_SUCCESS;
}
if (!OldPasswordVerified) {
// Check old password
if (StrCmp(Password, FormStorage.Password))
return EFI_NOT_READY;
OldPasswordVerified = TRUE;
return EFI_SUCCESS;
}
// Update password
StrnCpyS(FormStorage.Password, PASSWORD_STORAGE_SIZE, Password, StrLen(Password));
OldPasswordVerified = FALSE;
return EFI_SUCCESS;
}
}
With the following code you can set initial password and also have a possibility update it via the Form Browser.
In the next lesson we would investigate why we shouldn't store password as a plain text in the storage and how we can make our password storage more secure.