Creating TLS Certificates for Home Use

I used to create self-signed certificates, but they have the problem that I have to accept them the first time when using a browser, and when openssl library connects, I have to disable the certificate verification in curl, Node.js etc.

The proper fix is to create your own CA which your computers trust. Then signing certificates with this CA makes your computer trust those certificates. Luckily creating a CA is simple.

Note that all this is very insecure and should only be used for home use. For the real Internet, use Let’s Encrypt . I wish I could use the same for local certificates, alas this is not supported as Let’s Encrypt verifies DNS ownership.

Here the steps. See the original instructions by mrkiril which this is based on. And OpenSSL’s ca command (check the Warning section!). Also this includes plenty examples including something about the Java keytool.

1. Create your CA

You need 3 config files. First one: config_ssl_ca.cnf is for the CA.

[ req ]
default_bits = 2048

prompt = no
distinguished_name=req_distinguished_name
req_extensions = v3_req

[ req_distinguished_name ]
countryName=JP
stateOrProvinceName=Tokyo
localityName=Tama
organizationName=root organisation
organizationalUnitName=root department
commonName=Harald Kubota
emailAddress=harald.kubota@gmail.com

[ alternate_names ]
DNS.1 = t620.lan

[ v3_req ]
keyUsage=digitalSignature
basicConstraints=CA:true
subjectKeyIdentifier = hash
subjectAltName = @alternate_names

And this is how to create your CA certificate (self-signed):

mkdir CA
openssl genrsa -out ./CA/CA.key 4096
openssl req -new -x509 -key ./CA/CA.key -out ./CA/CA.crt -days 3650 -config config_ssl_ca.cnf

2. Create a CSR

Next one config_ssl.cnf is for generating a certificate sign request. The alternate_names should match the URL you use:

[ req ]
default_bits = 2048

prompt = no
distinguished_name=req_distinguished_name
req_extensions = v3_req

[ req_distinguished_name ]
countryName=JP
stateOrProvinceName=Tokyo
localityName=Home
organizationName=MyOrg
organizationalUnitName=Tech Department
commonName=Harald Kubota
emailAddress=harald.kubota@gmail.com

[ alternate_names ]
DNS.1 = web.lan
DNS.2 = web.net
IP.1  = 192.168.2.84

[ v3_req ]
keyUsage=digitalSignature
basicConstraints=CA:false
subjectAltName = @alternate_names
subjectKeyIdentifier = hash

Below is how to create a CSR. Note that “$x” is just a filename. The DNS name is in the previously mentioned config_ssl.cnf.

export x=web.lan
openssl genrsa -out "$x.key" 2048
openssl req -new -sha256 -key "$x.key" -config ./config_ssl.cnf -out "$x.csr"

3. Sign the CSR

And the last one config_ca.cnf to sign the CSR:

# we use 'ca' as the default section because we're using the ca command
[ ca ]
default_ca = my_ca

[ my_ca ]
#  a text file containing the next serial number to use in hex. Mandatory.
#  This file must be present and contain a valid serial number.
serial = ./CA/CA.srl

# the text database file to use. Mandatory. This file must be present though
# initially it will be empty.
database = ./CA/index.txt

# specifies the directory where new certificates will be placed. Mandatory.
new_certs_dir = ./

# the file containing the CA certificate. Mandatory
certificate = ./CA/CA.crt

# the file contaning the CA private key. Mandatory
private_key = ./CA/CA.key

# the message digest algorithm. Remember to not use MD5
default_md = sha256

# for how many days will the signed certificate be valid
default_days = 365

# a section with a set of variables corresponding to DN fields
policy = my_policy

# MOST IMPORTANT PART OF THIS CONFIG
copy_extensions = copy

[ my_policy ]
# if the value is "match" then the field value must match the same field in the
# CA certificate. If the value is "supplied" then it must be present.
# Optional means it may be present. Any fields not mentioned are silently
# deleted.
countryName = match
stateOrProvinceName = supplied
organizationName = supplied
commonName = supplied
organizationalUnitName = optional
commonName = supplied

This is how to sign:

openssl ca -config ./config_ca.cnf -out "$x.crt" -in "$x.csr" -batch

Should you try to sign 2 certificates with the same DN, you’ll get this error:

ERROR:There is already a certificate for /C=JP/ST=Tokyo/O=MyOrg/OU=Tech Department/CN=Harald Kubota

The fix is to change ./CA/index.txt.attr to be

unique subject = no

4. Import CA to Ubuntu

Your machine should not yet trust your new CA certificate. Use above created certificate and present a web page or similar. Confirm via

openssl s_client -connect HOST:PORT

to connect to that process using your previously created host certificate. You should see something along those lines. Note line 4 and 7 with a “verify error” part.

❯ openssl s_client -connect web.lan:8443
CONNECTED(00000003)
depth=0 C = JP, ST = Tokyo, O = MyOrg, OU = Tech Department, CN = Harald Kubota
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = JP, ST = Tokyo, O = MyOrg, OU = Tech Department, CN = Harald Kubota
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
[...]

To make OpenSSL trust the certificate, you have to make it trust your CA. This is how:

if [[ ! -d /usr/share/ca-certificates/extras ]] ; then
  mkdir /usr/share/ca-certificates/extras
fi
cp CA.crt /usr/share/ca-certificates/extras/HaraldCA.crt
echo "extras/HaraldCA.crt" >>/etc/ca-certificates.conf
update-ca-certificates -v

and after updating the root certificates on your computer:

❯ openssl s_client -connect web.lan:8443
CONNECTED(00000003)
depth=1 C = JP, ST = Tokyo, L = Tama, O = root organisation, OU = root department, CN = Harald Kubota, emailAddress = harald.kubota@gmail.com
verify return:1
depth=0 C = JP, ST = Tokyo, O = MyOrg, OU = Tech Department, CN = Harald Kubota
verify return:1
---
Certificate chain
[...]

5. Import CA into your Browser

Browsers bring their own CA store, so you have to update those. Copy CA.crt into a directory you can access from the browser, and import it.

Firefox: Preferences → Privacy & Security → Certificates View Certificates → In the Authorities pane click on Import… and import your CA.crt

Chrome: Settings → Privacy and security → Click on More → Manage certificates → Authorities → Import and import your CA.crt

From now on if you go to any web page which is signed with your CA, you browser will not show a warning anymore.

Caveats

This has no password/passphrase on any private key. Also the CA should be on a secured server.

Impact of Changing 5GHz WiFi Channels

My routers do DFS really well. So well, that they detect radar once in a while (about once per week) where I am pretty sure there is nothing to detect. I found a map of weather radar stations in my area (100km around me) and their frequencies. I should not be able to detect weather radar. I am not anywhere close to their frequencies.

Anyway, I thought changing to channels which do not require DFS might be helpful. But look at this:

Ping latency of 2 WiFi connected PCs, all 5GHz. From 20:00 to 10:00 I used different channels.

So on 4th at about 20:00 I changed from channel 52 which needs DFS, to channel 44 which does not need DFS. I expected nothing to happen minus no false weather radar detection. But what I found was a severe increase of latency. Changing to channel 60 on 5th at about 10:00 made a huge difference.

While I’m at risk of DFS hitting me again,I’d say it’s worth it.

Channels 44, 52 and 60 showed no activity from neighbors. Only channel 36-40 are used as well as some more channels above 100 (see here for lists of available channels and frequencies).

Lesson learned:

  • Not all channels are equal.
  • To find the best channels, run a proper real-world test.
  • Be aware of weather radar. Or DFS. Or both.

Creating self-signed Certs in 2020

It’s simpler than it used to be:

openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout example.key -out example.crt \
-subj /CN=example.com \
-addext subjectAltName=DNS:example.com,DNS:example.net,IP:10.0.0.1

# To create a PEM file:
cat example.crt example.key >example.pem

Source: https://dev.to/suntong/how-to-create-a-self-signed-certificate-1aib

From bash to zsh

Updating to the latest Kubuntu gave me a chance to revisit all previous decisions related to the OS. The login shell is one of them…

As a long term bash user migrating off is hard: everything is set up to work perfectly. And it works on every computer I use, be it at home, in the cloud or at work. Changing to another shell would need to give me significant benefits. Powerlevel10k on zsh was that benefit for me: Works out of the box and has a fancy prompt which shows contains important and useful information, especially when you use git (which I do):

Powerlevel10k typical prompt

There’s a lot of useful information: I’m on a GitHub repo, in the “experimental_compact_vars” branch with 2 changes not yet committed. The last command returned with an ok status and took 4s (time is not displayed if less than 3s which shows the attention to details).

In the words of Oh My Zsh:

Oh My Zsh will not make you a 10x developer…but you may feel like one.

Update

Small minor problems with zsh which I have not seen with bash.

  • TZ is not set correctly. Set it in /etc/zsh/zprofile:
TZ=$(cat /etc/timezone)
export TZ

AKG Lyra – A (Big) Microphone

All this work-from-home puts more focus on sound quality of audio and video conferences. Some people are very hard to understand on a conference call. Speakerphone with echo, noisy background, windy area or they simply don’t have the microphone in the right place. In all cases having a discussion is hard when someone who has to say something is inaudible.

So good audio quality is important. And listening to YouTube, Twitch and Podcasts gives you an idea of what is good and what is bad.

So I wanted to get myself a decent microphone. Turns out that I’m not the only one who thought so: Amazon (.co.jp) is pretty much sold out on USB connected “good” microphones. Literally all the recommended brand name microphones were sold out. What’s left is either way too expensive (30000 Yen or more) or of very questionable quality with matching bad ratings.

Moving off the mainstream helps: Where Amazon is sold out, Sound House is not. While they sell mainly music things, they also sell USB connected microphones. And while most were sold out here too, not all were. One in particular was supposed to arrive on April 30th: the AKG Lyra. It’s USB-C connected, and seems to be generally good according to reviews. It was marked as “in stock” on 30th, so I ordered one. And it arrived today.

And it’s…huge.

But it sounds really nice: very natural. Picks up sound from far away too. Not overly sensitive to plosives. Can do stereo. Has a headphone connector to listen to yourself and it acts as a sound output for your computer too.

I am mightily impressed with the whole design and functionality.

It works on Phones too!

But the best part, which was unconfirmed until I tested it: It works as a microphone and sound output using my Sony Xperia XZ2: plug it in via USB-C and programs like Audio Recorder use the Lyra as input (and output).

Will it work during a normal phone call? We’ll see.

MicroSD Cards Performance

App Performance Class Spec Table

I read about this “A1” specification on microSD cards: it’s supposed to be “App Performance” and is technically a minimum read and write IOPS.

So I got one to find out how much of a difference it makes in an OrangePi Zero running Armbian.

Simple test:

  1. Prepare an existing microSD card (I-O Data, Class 4, 8GB)
    • apt clear
    • apt update
    • shutdown
  2. Copy that card on another PC
    • dd bs=128k if=/dev/sdb of=8g.img
  3. Copy that image to the A1 card (Kingston Canvas Select Plus 16GB)
    • dd bs=128k if=8g.img of=/dev/sdb
  4. Now do the following commands after booting each card

This was the result:

CommandOld cardNew card
ls -Rl /usr >/dev/null5.6″4.9″
apt update2’22”1’57”
apt upgrade -y24’10”10’2″
Linux commands using different microSD cards

I thought the “ls” will make a bigger difference, but I guess the filesystem and its caching does a good job. The “apt update” is mainly pulling data from the Internet. But the “apt upgrade” was impressive: 97 packets were updated in both cases and while it includes a lot of data pulling from the Internet, the main time is spend on unpacking and installing all those Debian packets. That’s reading and writing plenty files. And it shows.

I did not expect the difference to be that much. I’m sold and will replace any future microSD cards with A1 models (note A2 needs drivers for queuing and without driver support, they can be slower than A1 models).

BME280 and Node.js on OrangePi

I got some more sensors, like an Bosch BME280 which uses I²C bus. Neat as it can measure temperature, air pressure and humidity.

Using it on an OrangePi Zero is simple:

  • Enable the I2C-0 module: in /boot/armbianEnv.txt add “i2c0” (or use i2c1 or i2c2) to the overlay.
  • Create /etc/udev/rules.d/60-i2c-tools.rules with the following content:
KERNEL=="i2c-0" , GROUP="i2c", MODE="0660"
KERNEL=="i2c-[1-9]*", GROUP="i2c", MODE="0666"
  • Add yourself to the i2c group:
useradd -a -G i2c harald
  • Reboot

You now have an accessible /dev/i2c-0 (or i2c-1 or i2c-2). You can check you have a BME280 visible on the I2C bus:

harald@opz2:~$ i2cdetect 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
I will probe address range 0x03-0x77.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- 76 --                         

0x76 is the default I2C address of a BME280 (0x77 being the alternative address).

Now read it via Node.js (package from here):

const BME280 = require('bme280-sensor');

// The BME280 constructor options are optional.
// 
const options = {
  i2cBusNo   : 0, // defaults to 1
  i2cAddress : 0x76 //BME280.BME280_DEFAULT_I2C_ADDRESS() // defaults to 0x77
};

const bme280 = new BME280(options);

// Read BME280 sensor data, repeat
//
const readSensorData = () => {
  bme280.readSensorData()
    .then((data) => {
      // temperature_C, pressure_hPa, and humidity are returned by default.
      // I'll also calculate some unit conversions for display purposes.
      //
      data.temperature_F = BME280.convertCelciusToFahrenheit(data.temperature_C);
      data.pressure_inHg = BME280.convertHectopascalToInchesOfMercury(data.pressure_hPa);
 
      console.log(`data = ${JSON.stringify(data, null, 2)}`);
      setTimeout(readSensorData, 2000);
    })
    .catch((err) => {
      console.log(`BME280 read error: ${err}`);
      setTimeout(readSensorData, 2000);
    });
};

// Initialize the BME280 sensor
//
bme280.init()
  .then(() => {
    console.log('BME280 initialization succeeded');
    readSensorData();
  })
  .catch((err) => console.error(`BME280 initialization failed: ${err} `));

And it works:

harald@opz2:~/js/bme280$ node bme280.js 
Found BMx280 chip ID 0x60 on bus i2c-0, address 0x76
BME280 initialization succeeded
data = {
  "temperature_C": 23.19,
  "humidity": 35.62696153399403,
  "pressure_hPa": 1003.0408627864141,
  "temperature_F": 73.742,
  "pressure_inHg": 29.619784150102433
}
[...]

Reading Temperatures from many DS18B20 via Node.js

The easiest way:

harald@opz2:~$ cat /sys/bus/w1/devices/28-*/hwmon/hwmon/temp1_input
21812
22375
23375

The problem is that each sensor takes about 800ms (750ms according to data sheet) conversion time. Making this better and reading them concurrently via Node.js:

const fs=require('fs');

const HWSENSORPREFIX='/sys/bus/w1/devices/';

async function getSensors() {
  let sensors=[];
  const dir = await fs.promises.opendir(`${HWSENSORPREFIX}`);
  for await (const dirent of dir) {
    if (dirent.name.slice(0,3)=='28-') {
        sensors.push(dirent.name);
    }
  }
  return sensors;
}

// Print temperature reading from a given 1-Wire sensor

async function printTemp(sensor) {
  const dir = await fs.promises.opendir(`${HWSENSORPREFIX}/${sensor}/hwmon`);
  for await (const dirent of dir) {
    if (dirent.name.slice(0, 5)=='hwmon') {
      let temp = parseFloat(await fs.promises.readFile(`${HWSENSORPREFIX}/${sensor}/hwmon/${dirent.name}/temp1_input`))/1000.0;
      console.log(`${dirent.name} (${sensor}): ${temp}`);
    }
  }
}

(async function () {
  try {
    let a=await getSensors();
    a.forEach(x => printTemp(x));
  } catch(e) {
    console.error(`Error: ${e}`);
  }
})();

That’s reading all in parallel (plus about 600ms startup time):

harald@opz2:~$ node read-temp.js
hwmon0 (28-80000004215e): 23.312
hwmon2 (28-0316884578ff): 21.812
hwmon1 (28-0316884fabff): 22.312

Adding sensors changes the time until all are read:

DS18B20 Countcatread-1w-temp.js
10.851.45
21.691.51
32.501.53
Time to read in [s] for 1 to 3 DS18B20 temperature sensors

OrangePi Zero + DS18B20

Making my OrangePi Zero useful (again) with updating it to the latest Armbian and connecting a 1-Wire DS18B20 temperature sensor to it.

OrangePi Zero on the left, DS18B20 on the right

Installing

Get a microSD card and install the latest Armbian image for your Zero. Boot and do the usual setup: set root password, create a new user, copy ssh keys etc.

Hardware Related Setup

Here the fun part starts. Define the features you want to use on the 26 pin header (see at the Sunxi wiki).

Edit /boot/armbianEnv.txt

Add the w1-gpio overlay and configure it to use PA07 (pin 12 on the 26 pin header) and enable pull-up (not really needed though as we need an external pull-up resistor anyway). Reboot when done. Other pins are ok too. PA07 works though.

overlay_prefix=sun8i-h3
overlays=usbhost2 usbhost3 w1-gpio
param_w1_pin=PA07
param_w1_pin_int_pullup=1

The name of the overlay are from /boot/dtb/overlay/.

Wire up everything

DS18B20 has 3 pins: GND, 3.3V and DATA. DATA goes to PA07. Don’t forget a 4.7k pull-up resistor between 3.3V and DATA.

Measure Temperature

If everything worked, you will have a temp1_input which has the temperature in °C * 1000:

harald@opz2:/boot$ modprobe w1_therm
harald@opz2:/boot$ lsmod | grep w1
w1_therm               16384  0
w1_gpio                16384  0
wire                   24576  2 w1_gpio,w1_therm
harald@opz2:/boot$ dmesg
[...]
[  142.370130] w1_master_driver w1_bus_master1: Attaching one wire slave 28.80000004215e crc 94
harald@opz2:/boot$ cat /sys/bus/w1/devices/28-80000004215e/hwmon/hwmon0/temp1_input
23812

2020-04-09 Quick update: Adding a 2nd DS18B20 was picked up automatically and now I have 2 sensors:

harald@opz2:~$ cat /sys/bus/w1/devices/28-80000004215e/hwmon/hwmon0/temp1_input
24187
harald@opz2:~$ cat /sys/bus/w1/devices/28-0316884fabff/hwmon/hwmon1/temp1_input 
24375