2019-09-20
Modernizing (a bit) some of our HTML form <input> elements
We have a Django web app for handling requests for Unix accounts, which has some HTML forms (in fact it's basically half HTML form filling). These forms (and all of the app's HTML) were put together years ago and only looked at on the desktop at the time. Recently, I poked around the app's forms on the work iPad to see how it would go. Even after I fixed the traditional viewport issue (see the comments), there were little irritations; for example, when you entered your desired Unix login, the iPad wanted to capitalize the first letter as part of its general auto-capitalization. Our Unix logins have to be all lower case, so this was a point of friction. Naturally I wondered if it was possible to improve the experience.
(Our Django web app was written in 2011, and we first got a work iPad in mid-2014. In 2011, not checking phone and table browser behavior was not crazy; today, it probably is and we likely should pay more attention to how all of our sites look on them.)
Unsurprisingly, there are ways to improve the situation by simple
changes to our HTML, although not all of them were completely
successful. You can find a variety of people writing about this
online, and also the MDN page on <input type="text">
form fields,
although it doesn't mention the autocapitalize
attribute.
The short version is that we apparently want to set all of the
following attributes for text field that is supposed to be a login:
<input type="text" autocapitalize="none" autocorrect="off" spellcheck="false" ...>
Spellchecking is obviously not applicable; very few logins are
dictionary words. Autocorrection is similarly probably not desirable,
and autocapitalization is what we started out not wanting. The
autocorrect
attribute is a Safari extension, but apparently
Android may want you to use autocomplete
instead (which is a nominally standard attribute with all sorts
of possible values).
The form also has a field for people's names. I set this to
'autocapitalize="words"
', and should probably also set it
'autocomplete="name"
' now that I've read about it. On my iOS
devices, some combination of the attributes we're using (possibly
including a 'name="name"
' attribute from Django) causes Safari
to be willing to autocomplete it from your contacts, which is handy
if your contacts include yourself.
My less successful experiment was setting a 'pattern=...
'
and a 'title=...
' attribute on the field for your login. What I
wanted was for the browser to automatically react with a helpful
error message when you entered an invalid character, but my flailing
around so far hasn't produced this. We have some additional client
side validation, but they involve a
server check and so only trigger when the field is de-focused;
faster feedback would be nice. However, my initial reading left me
with the impression that doing a good job of this across both desktop
and mobile browsers would require more JavaScript than I wanted to
write.
(MDN has a useful page on general form validation, which I haven't read all of.)
All of this (including writing this entry) has done a good job of showing me how ignorant I am about modern HTML. Things have definitely changed here over the last decade or so, which is good to see even if it leaves me well behind the times.
PS: Django is already setting appropriate 'type=...
' values on things
like the field for your email address, or that would be another obvious
and necessary change to make here.
TLS server certificate verification has two parts (and some consequences)
One of the unusual and sometimes troublesome parts of TLS is that verifying a TLS server's certificate actually has two separate parts, each critical. The first part is verifying that you have a valid certificate, one that is signed by a certificate chain that runs up to a known CA, hasn't expired, hasn't been revoked (or is asserted as valid), perhaps appears in a CT log, and so on. The second, equally critical part is making sure that this valid certificate is actually for the server you are talking to, because there are a lot of valid certificates out there and more or less anyone can get one for some name. Failing to do this opens you up to an obvious and often trivial set of impersonation attacks.
However, there is an important consequence of this for using TLS outside of the web, which is that you must know the name of the server you're supposed to be talking to in order to verify a server's TLS certificate. On the web, the server name is burned into the URL; you cannot make a request without knowing it (even if you know it as an IP address). In other protocols that also use TLS, this may not be true or it may not be clear what name for the server you should use (if there are levels of aliases, redirections, and so on going on, possibly including DNS CNAMEs).
The corollary of this is that it's much harder to use TLS with a
protocol that doesn't give you start with a server
name somehow. If the protocol is 'I broadcast a status packet and
something responds' or 'someone gives me a list of IP addresses of
resources', you sort of have a problem. Sometimes you can resolve
this problem by fiat, for example by saying 'we will do a DNS PTR
query to resolve this IP address to a name and then use the name',
and sometimes you can't even get that far.
(You can also say 'we will not verify the server name', but then you only have part of TLS.)
That's all very abstract, so let's go with two real examples. The first one is 801.2X network authentication, which I tangled with recently. When I dealt with this on my phone, I was puzzled why various instructions said to make sure that the TLS certificate was for a specific name (and I even wondered if this meant that the TLS certificate wasn't being verified at all). But the reason you have to check the name is that the 801.2X protocol doesn't have any trustworthy way of asserting what authentication server should be called; almost by definition, you can't trust anything the 801.2X server itself claims about what it should be called, and the only other information you have is (perhaps) the free-form name of the network (as, for example, the wireless SSID). The server name and the trust has to be established out of band, and on phones that's through websites with instructions.
(On Linux you have to explicitly configure the expected server name in advance if you want security.)
The second example is wanting to use DNS over TLS or DNS over HTTPS to talk to the DNS servers you find through DHCP or have in a normal resolv.conf. In both of these cases, the protocol and the configuration file only specify the DNS servers by IP address, with no names associated with them (and the IPs may well be RFC 1918 private addresses). It's possible to turn this into a server name if you want to through DNS, but you wind up basically having to trust what the DNS server is telling you about what its TLS server name should be.
(You can augment DHCP and resolv.conf with additional information about the server names you should look for, but then you need to define the format and protocol for this information, and you need more moving parts in order to get your TLS protected DNS queries.)
PS: Sometimes the first part of TLS is sufficiently important by itself, because blocking passive eavesdropping can be a significant win. But it's at least questionable, and you need to consider your threat models carefully.