Close

Security, OpenWRT and storage

A project log for Prism

Prism is a smart charging station for your electric vehicle.

Mastro GippoMastro Gippo 10/08/2019 at 09:312 Comments

Tales of security woes over the years made me a bit paranoid about the various vulnerabilities I could expose my users to, so in the development of Prism I wanted to make everything as secure as possible without breaking the bank.

---------- more ----------

The first goal is to avoid remote attacks as they’re way harder to detect than ones requiring physical access. We want to make especially sure that we avoid fleet-wide hacks and that compromising a single device will not make it easier to compromise the others. Today we will look at the first step in this direction: default passwords must be different among all devices as users are still not prone to changing them. This is usually done in the industry by calculating a unique password based on the basic devices characteristics, like the MAC address. This usually makes users feel safer and even less motivated to change the password (how may times have you taken pictures of the label under a friends router?). But once we find the algorithm behind the password generator by reversing the routers firmware, we can just sniff the MAC address and use the SSID to calculate the password for any vulnerable router. Device makers usually use this technique because it costs nothing, is very easy and provides some basic security; issuing truly unique passwords requires extra storage that has to be written before the device ships and needs to be persistent across updates. Routers usually have a specific partition for storing various calibration data; I experimented a bit with OpenWRT and found it quite hard to use that for storage and it sometimes wouldn’t survive recovery or direct flashing, but I don’t have a valid reason for why big manufacturers with big money and big development teams are not using it.

Well, turns out I have a nice STM32 on board. The easiest thing would be to read its serial number and hash it in some way to generate the passwords, and that would already be better than just using the publicly discoverable MAC address; but I wanted to go one step further as we have plenty of flash for my firmware and some storage. BOOT0 and Reset pins are connected to the Linux module for firmware updates, so I can use stm32flash to read and write its memory! And I’m not even enabling read protection since this is an open source project, so I can just use the tool to read the chunk of memory I need without bothering writing a custom serial command to fetch the data for me. Let’s try this!

I can generate a RSA key pair, a bunch of passwords for WiFi, root, user/admin/installer for the GUI, an update key, and store them all in a bunch of files:

#gen private
openssl genrsa -out ./$folder/$serial/id_rsa 2048
#save public key
openssl rsa -in ./$folder/$serial/id_rsa -outform PEM -pubout -out ./pub/$serial.pem

echo "$i" > ./$folder/$serial/serial

#wifi pass
echo `pwgen -Bcn 8 -1` > ./$folder/$serial/pw

[...] 

I can then tar.gz it all, pad the file to 4k with dd and flash it at the end of the STM32 memory.

cd ./$folder/$serial/
tar -czvf ../$serial.tar.gz *
cd ../..

#check size
sz=`wc -c $folder/$serial.tar.gz | awk '{print $1}'`
if [ $sz -ge 4000 ]; then
	echo -e "\e[31mERROR!! $sz size for $serial\e[0m"
else 
	# First, create a 128K file of all zeros:
	dd if=/dev/zero bs=1024 count=4 of=./$folder/$serial.bin
	# Then, write the BIN to the file without truncating the result:
	dd if=./$folder/$serial.tar.gz of=./$folder/$serial.bin conv=notrunc
fi

This way I can read it up on first boot from a clean image and setup everything!

stm32flash -R -i $reset -r key.bin -S $address:0x1000 /dev/ttyS1 
tar -zxf key.bin 

[...] 

if [ -f "root_pass" ]; then 
 #set root password 
 pass=`cat root_pass` 
 echo -e "$pass\n$pass" | passwd root 
else 
 logger -t loadkey "Error! No root pass file" 
fi 

[...] 

And that is a great way to protect our users from “keygen” types of attack. But an attacker can still disassemble the device and read the STM32 flash! At this point we are only trying to protect the root password and the private key, as all the other passwords are printed on the manual and on a sticker. Protecting the root password (and other passwords if needed) is easy, as we can just store the pre-calculated hash:

#OpenWRT uses md5
mkpasswd -m md5 $password 

but the id-rsa file with the private key needs to be readable! So as far as I know, the only way to do this other than using a TPM would be to gpg encrypt the whole file to add a layer of security. Of course I would have to use the same key for all the products due to the aforementioned limitations, and that means that an attacker just have to read the contents of an update or the flash of a device to discover that key. I could use an encryption password that is based on the MAC address or some other parameter, but that would only make it a bit harder as the attacker just has to reverse that.

I tried to implement all the best practices here, and I think I did quite well compared to big corporations. The biggest threat is, as always, physical access, but I have a big deterrent on my side: the charging station works at very high voltages, so the chances of an attacker messing with it are a bit lower; and if they play safe by removing power before touching it, that shouldn’t go unnoticed by the owner.

So what do you think? Can I take other measures to improve the password storage security, or am I already overthinking it? Let the Hackaday commenters wisdom decide! :)


Bonus short post: I wanted to write a script to automate the printing of labels with passwords, serial numbers and MAC addresses so I purchased a Brother label printer instead of a Dymo because it was better compatible with Linux. I started with the best intentions, but it turns out the included software already allows importing data from a database, so I just had to throw all the data in a CSV  and I was good to go in 20 minutes. The end.  ¯\_(ツ)_/¯

Discussions

beikeland wrote 12/21/2019 at 16:47 point

Firstly thanks for sharing, enjoyed reading large parts of your journey.

With regards for the security I'm not sure why you need to be concerned with securing the private key in the charger. This is typically generated randomly at first boot, and the only thing that should leave the device after that is the public key. 

Maybe you can use your public key instead of the device private key, but should probably elaborate a little on the use case for the keys. If you send out a private key, I'd consider it a shared key at that stage.

As for the root password, "easy", don't use one. Use SSH keys, even better signed SSH keys with a certificate authority (CA). This does however make working networking a prerequisite, serial console would have to be replaced by SLIP or PPP and over that IP connection use SSH.

  Are you sure? yes | no

Mastro Gippo wrote 12/23/2019 at 12:57 point

Thanks for your comment! I'll try to better explain my reasoning behind those choices.

I'm not using the RSA key at all, right now. I just included a pair for future use, and saved the public keys on my server. I can't rely on the self-generated key, as it changes across factory resets, and I would have to implement an authentication mechanism anyway to make sure that that particular device (and not an attacker) is sending me its new updated public key. But that should be GPG's duty, right? Well, it's there if I need it :)

About the root password, I generated a very robust one and closed all the low hanging fruits, trying to make it not worth it to force it. I still wanted to include one so if developers want root access, they can just ask me for the password providing some proof of ownership and I'll just send it to them. I thought about implementing some backdoor requiring physical access, but I don't want software developers risking their life disassembling a high voltage device, so I'll take the burden of making sure that the person requiring the password is the rightful owner. I could have done the same with SSH keys, but that would not work with the serial console; I also firewalled the SSH port (and everything but HTTP), but it can be unlocked if needed.

  Are you sure? yes | no