aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons_uncategorized/Lesson_Password/README.md
blob: 02bc6d6bbccc5d2c78922256af57bf4ca03c1e4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
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:

![password1](password1.png?raw=true "password1")

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:

# `EFI_IFR_PASSWORD`

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.

# Callback code

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:

![flowchart](flowchart.png?raw=true "flowchart")

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 the `Action=EFI_BROWSER_ACTION_FORM_OPEN` argument and one with the `Action=EFI_BROWSER_ACTION_RETRIEVE` argument, in both cases the passed agument `Value` 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 the `Action=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:
```cpp
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:

![password2](password2.png?raw=true "password2")

If the input is valid the Form Browser prompts again for password confirmation:

![password3](password3.png?raw=true "password3")

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:
```cpp
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.

![password4](password4.png?raw=true "password4")

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:

![password5](password5.png?raw=true "password5")

And if everything correct to confirm it:

![password6](password6.png?raw=true "password6")

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:
```cpp
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.