Android 13, GrapheneOS, freeradius, Wi-Fi authentication, and Let's Encrypt

I am using freeradius to authenticate users to a particular SSID, to steer them onto the right VLAN.

When I set up my new phone - a Pixel 6 running GrapheneOS - I could connect to Wi-Fi, entering my username and password, accept the proffered certificate, and it worked.

But if I switched off Wi-Fi, or even went out of range of the WAP, it would not reconnect. I needed to “forget” the Wi-Fi settings, and re-enter them.

Which was a pain.

Since the same set-up worked just fine on iOS, macOS, tvOS, Linux, and my other Android (10) phone, I had assumed it was an issue with GrapheneOS. But I was wrong.

Alert read:fatal:certificate expired

The freeradius server radius.log gave me a (strong) clue:

ERROR: (152) eap_peap: ERROR: (TLS) Alert read:fatal:certificate expired

And, sure enough, when I checked my cert with openssl x509 -in ca.pem -text, I got:

        Validity
            Not Before: Sep 25 16:21:57 2021 GMT
            Not After : Nov 24 16:21:57 2021 GMT

I set up freeradius’s default X.509 infrastructure using the supplied bootstrap and, since it worked (and continued to work, and continued to work), I never bothered to change it.

But it seems that, while pretty much every other device I own does not care about the expiry of the certificate, Android 13 does.

So I needed to build a valid X.509 infrastructure, with a new, not-expired, certificate.

I kind of wanted it to “just work” until I had a chance to do that, so I did what I wasn’t supposed to do, and ran bootstrap again. And it did what it was supposed to do and failed, since I already had a certificate of the same name.

freeradius with Let’s Encrypt

Rather than setting up and managing my own CA, I had wondered about using Let’s Encrypt. And it seemed that others had done it, successfully, so I gave it a try.

It’s a bit of a pain, in that my DNS provider doesn’t support DNS-based challenges, so I need to generate the certs via a web server. Not the end of the world, and I’ve automated copying the certificates from where they land with Let’s Encrypt into `/etc/freeradius/certs/letsencrypt’ and setting ownership to ‘freerad’.

With that in place, I just needed to modify /etc/freeradius/mods-enabled/eap, to reference the new certificates. The only change I made was:

	tls-config tls-common {
		private_key_file = /etc/freeradius/certs/letsencrypt/privkey.pem
		certificate_file = /etc/freeradius/certs/letsencrypt/fullchain.pem

and then I restarted freeradius (service freeradius restart).

Configuring Android 13 to use Let’s Encrypt for RADIUS authentication

While tailing radius.log, I set up the Android client.

And it authenticated and connected.

Excellent. So far, so good - the Let’s Encrypt-based CA seemed to be working.

So I turned off Wi-Fi, turned it back on again, and … it failed.

But with a different error in radius.log:

ERROR: (60) eap_peap: ERROR: (TLS) Alert read:fatal:unknown CA

Weird, I thought, as since I’m using Let’s Encrypt, which is handled by the system’s own certificates, so the device should be coping with just fine.

But I hadn’t told the device to rely on the system certificates. I revisited the Wi-Fi settings on the phone, and changed:

and hit “Save”.

And it authenticated and connected.

So I turned off Wi-Fi, turned it back on again, and … it worked!

What will happen after three months?

Let’s Encrypt certificates are valid for three months.

That’s fine, since I’ve automated renewal and re-provisioning into freeradius. So it should be seamless on the server side.

But what about clients? Will I be forced to accept a new certificate on each client every three months, or will it be handled invisibly?

I forced a renewal of the (still valid) Let’s Encrypt cert:

certbot --force-renewal -d [domain name] --preferred-chain="ISRG Root X1"

(I’m not sure if I really need --preferred-chain="ISRG Root X1"; one day, I will test it, but since it works for now…)

And turned off Wi-Fi on my phone, and turned it back on again.

It connected seamlessly.

So, all good with Android 13.

Same with Linux - it was invisible.

For Apple devices (iOS, iPadOS, tvOS), Wi-Fi refused to connected. I had to click into the Wi-Fi Settings, and trust the certificate again. That’s a bit of a pain, but it will only be for every three months, so it might be tolerable…

Impact on other devices