I wanted to play with MQTT and TLS and that’s about it… I decided to go with golang. Mainly because I never tried the language before and because I wanted a single binary with no dependencies and C was not even an option (I’m too old for this shit).
While client authentication using public key is nice, I didn’t feel it brings enough added security in order for me to go through the hassle of having to manage a full PKI (as well as delivering client certificates) for a pet project. Authenticating the server using public key and the client using a login and password is plenty enough for a simple “I’m alive” mqtt publisher. However, there is a hmac function in order to ensure that a rogue client cannot impersonate another one (this needs to be set before each go build, obviously…). This requires some provisionning on server side obviously so both parties use the same psk for the hmac.
The paho library is pretty straightforward in go… Because I wanted a standalone binary, I decided to hardcode the Certificate Autority public key directly into the source code instead of using io.ReadFile…
/*
* Yokai, a simple MQTT "I'm alive" publisher for Dodomeki"
* (c) jme@opium.io for crashdump.net
* BSD LICENCE
*/
package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
MQTT "github.com/eclipse/paho.mqtt.golang"
"time"
)
const mqtt_protocol string = "tls://" // mqtt protocol to use (tls:// is prefered)
const mqtt_host string = "mqtt_server_ip" // mqtt server fqdn or ip
const mqtt_port string = "8883" // mqtt server port
const mqtt_login string = "yokai" // mqtt server login
const mqtt_passwd string = "password" // mqtt server password
const mqtt_topic string = "dodomeki/alive" //topic to publish
const mqtt_id string = "yokai-arkham" //mqtt iD and topic trailing id.
const hmac_secret string = "my_secret_hmac" //hmac secret to sign msg
const broker = mqtt_protocol + mqtt_host + ":" + mqtt_port
const mqtt_subscribe = mqtt_topic + "/" + mqtt_id
var message_string string = ""
//yes that is a self signed certificate but nothing wrong with them
//yes, I am aware that the fqdn is in the Subject…
const mqtt_tls_ca = `-----BEGIN CERTIFICATE-----
MIIDozCCAougAwIBAgIJAKdfNTvWh5tmMA0GCSqGSIb3DQEBDQUAMGgxFjAUBgNV
BAMMDUEgTVFUVCBicm9rZXIxFjAUBgNVBAoMDWNyYXNoZHVtcC5uZXQxFDASBgNV
BAsMC2dlbmVyYXRlLUNBMSAwHgYJKoZIhvcNAQkBFhFub2NAY3Jhc2hkdW1wLm5l
dDAeFw0xNzAyMTYyMDUwMjJaFw0zMjAyMTMyMDUwMjJaMGgxFjAUBgNVBAMMDUEg
TVFUVCBicm9rZXIxFjAUBgNVBAoMDWNyYXNoZHVtcC5uZXQxFDASBgNVBAsMC2dl
bmVyYXRlLUNBMSAwHgYJKoZIhvcNAQkBFhFub2NAY3Jhc2hkdW1wLm5ldDCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPVaQ8Ut20IpEEnBETJEzDBe51n
X3UMZe/2ZFmJBWai2mpvnG3Tcqfd8JdEFsqHBlUZ2F5DuKOsZjpiRgFyRhh4tW2Q
qPKIp+rMJOJdTvSU/ct/gD4STVAQFBQSWdrHm+qWmzztTQTpGuTjoG0gQBGj/n/8
CAL6Er3cMnncwzVScTRdjbU8Al4eio4zRRNo0bg9tj8zf9uXxViLKCbOXVenDY0v
cAfj9eSbLwh5b3wGlSo1amOBb8E/xgoq86RcduT71vare8puSSPCOwSjEnRfuco/
tIuEaERTrYPp8czGBo2pA5z0Lp0jvJ3d5DK2jF/g2LZyvMsZdg/yI0OTrOkCAwEA
AaNQME4wHQYDVR0OBBYEFFhKhZO96OYLn3mjBwdANnEwykYmMB8GA1UdIwQYMBaA
FFhKhZO96OYLn3mjBwdANnEwykYmMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEN
BQADggEBABYdNzUcvTX891sE6lrHAxw2NwgiMh45ERYZD12WyrDCJIAp+h5DP1sI
BvCakNTEbqXTEX2R7/5gQSMmgfnbGMDdf/qkFfB3qzx9VsJ1SYXR+FqUZ19ykD6Y
CJW+KrN5+hGtVv6HiNQrs5o8eaW4sD94HD1nV3Lt0/UFeL2hbbxC2HPAmRG3813/
fSSGg7MkQRAD2Wjt1hgfEG09wQJY9U9iCmgr9nAQpzN+5kQLZLjGksAG53HWV0sf
6UQ33NcofpL6njX+3mLNYOaKI808n4yorX/ffPBsZ+SJYYCdXyJmws2sCpQHymGg
F0NxUmaPTAifrf7Ia9JOg02Hh4cJlTo=
-----END CERTIFICATE-----`
func build_message() string {
// return the I'm alive message in the form of [current_epoch_time]:[hmac_verification]
// the [hmac_verification] is a sha256 hmac of [mqtt_id]:[the alive message itself]
// the hmac is to prevent a rogue device to send alive messages in place of another one
// the current time in epoch format provide the nonce against replay attacks
now := time.Now()
var my_msg string = fmt.Sprintf("%d", now.Unix())
var my_verification string = mqtt_id + ":" + my_msg
message_hmac := hmac.New(sha256.New, []byte(hmac_secret))
message_hmac.Write([]byte(my_verification))
myHmac := fmt.Sprintf("%s", base64.StdEncoding.EncodeToString(message_hmac.Sum(nil)))
my_msg += ":" + myHmac
return my_msg
} //message
func main() {
root_ca := x509.NewCertPool()
load_ca := root_ca.AppendCertsFromPEM([]byte(mqtt_tls_ca))
if !load_ca {
panic("failed to parse root certificate")
}
tlsConfig := &tls.Config{RootCAs: root_ca}
opts := MQTT.NewClientOptions()
opts.SetTLSConfig(tlsConfig) //we set the tls configuration
opts.AddBroker(broker) //we add the broker
opts.SetClientID(mqtt_id) //we set the mqtt id
opts.SetCleanSession(true) // Sets true to client and server should remember state across restarts and reconnect
opts.SetUsername(mqtt_login) // Set the mqtt server login
opts.SetPassword(mqtt_passwd) // Set the mqtt server password
c := MQTT.NewClient(opts) // Launch the client using the set options
if token := c.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
var message string = build_message()
text := fmt.Sprintf("%s", message)
token := c.Publish(mqtt_subscribe, 0, false, text)
token.Wait()
c.Disconnect(250)
} //main
Because I’m coding on my Mac, I need to cross compile the final client for Linux. This can be easily achieved with the following command: env GOOS=linux GOARCH=amd64 go build ./yokai.go
Then we can launch the client (I’m using a simple crontab). Then if we subscribe to the mqtt server, we can see the following output:
$root@mordor:/etc/mosquitto# mosquitto_sub -h 45.63.115.108 --cafile /etc/CA/ca.crt -t "#" -p 8883 -u login -P password -v
dodomeki/alive/yokai-arkham 1487375041:sadIYNsoyVHr28uLegpwn5YScpm9x+xA3PV73ygVzVw=
dodomeki/alive/yokai-arkham 1487375101:Q2fSHQ61Uf7UGR94PKecHqONBKBFUPB3Z83Tfi/PIPA=
Next step will be to have a cookbook to use templating in order to customise the client before compiling it and then uploading it to any host I want to monitor.