From 037a6c56d3f7ce93a2e2a5f73a00f6ac4f7fbf5f Mon Sep 17 00:00:00 2001 From: Maxim Kochurov <maxim.v.kochurov@gmail.com> Date: Sun, 13 Mar 2022 19:33:37 +0300 Subject: [PATCH] Implement Optional Private Keys (#161) --- custom/js/helper.js | 2 +- handler/routes.go | 73 +++++++++++++++++++++++++++++++----------- store/jsondb/jsondb.go | 4 +-- templates/base.html | 22 ++++++++++++- 4 files changed, 78 insertions(+), 23 deletions(-) diff --git a/custom/js/helper.js b/custom/js/helper.js index 6fe8847..dd4b497 100644 --- a/custom/js/helper.js +++ b/custom/js/helper.js @@ -31,7 +31,7 @@ function renderClientList(data) { <div class="btn-group"> <button type="button" class="btn btn-outline-secondary btn-sm" data-toggle="modal" data-target="#modal_qr_client" data-clientid="${obj.Client.id}" - data-clientname="${obj.Client.name}">Scan</button> + data-clientname="${obj.Client.name}" ${obj.QRCode != "" ? '' : ' disabled'}>Scan</button> </div> <div class="btn-group"> <button type="button" class="btn btn-outline-secondary btn-sm" data-toggle="modal" diff --git a/handler/routes.go b/handler/routes.go index 9c18fc4..da7ecd2 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -171,23 +171,51 @@ func NewClient(db store.IStore) echo.HandlerFunc { client.ID = guid.String() // gen Wireguard key pair - key, err := wgtypes.GeneratePrivateKey() - if err != nil { - log.Error("Cannot generate wireguard key pair: ", err) - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) + if client.PublicKey == "" { + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + log.Error("Cannot generate wireguard key pair: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) + } + client.PrivateKey = key.String() + client.PublicKey = key.PublicKey().String() + } else { + _, err := wgtypes.ParseKey(client.PublicKey) + if err != nil { + log.Error("Cannot verify wireguard public key: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify Wireguard public key"}) + } + // check for duplicates + clients, err := db.GetClients(false) + if err != nil { + log.Error("Cannot get clients for duplicate check") + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get clients for duplicate check"}) + } + for _, other := range clients { + if other.Client.PublicKey == client.PublicKey { + log.Error("Duplicate Public Key") + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Duplicate Public Key"}) + } + } + } - presharedKey, err := wgtypes.GenerateKey() - if err != nil { - log.Error("Cannot generated preshared key: ", err) - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ - false, "Cannot generate Wireguard preshared key", - }) + if client.PresharedKey == "" { + presharedKey, err := wgtypes.GenerateKey() + if err != nil { + log.Error("Cannot generated preshared key: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ + false, "Cannot generate Wireguard preshared key", + }) + } + client.PresharedKey = presharedKey.String() + } else { + _, err := wgtypes.ParseKey(client.PresharedKey) + if err != nil { + log.Error("Cannot verify wireguard preshared key: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify Wireguard preshared key"}) + } } - - client.PrivateKey = key.String() - client.PublicKey = key.PublicKey().String() - client.PresharedKey = presharedKey.String() client.CreatedAt = time.Now().UTC() client.UpdatedAt = client.CreatedAt @@ -227,18 +255,25 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon config := util.BuildClientConfig(*clientData.Client, server, globalSettings) cfg_att := emailer.Attachment{"wg0.conf", []byte(config)} - qrdata, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(clientData.QRCode, "data:image/png;base64,")) - if err != nil { - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "decoding: " + err.Error()}) + var attachments []emailer.Attachment + if clientData.Client.PrivateKey != "" { + qrdata, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(clientData.QRCode, "data:image/png;base64,")) + if err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "decoding: " + err.Error()}) + } + qr_att := emailer.Attachment{"wg.png", qrdata} + attachments = []emailer.Attachment{cfg_att, qr_att} + } else { + attachments = []emailer.Attachment{cfg_att} } - qr_att := emailer.Attachment{"wg.png", qrdata} err = mailer.Send( clientData.Client.Name, payload.Email, emailSubject, emailContent, - []emailer.Attachment{cfg_att, qr_att}, + attachments, ) + if err != nil { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) } diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index 763756e..4ee35ca 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -155,7 +155,7 @@ func (o *JsonDB) GetClients(hasQRCode bool) ([]model.ClientData, error) { } // generate client qrcode image in base64 - if hasQRCode { + if hasQRCode && client.PrivateKey != "" { server, _ := o.GetServer() globalSettings, _ := o.GetGlobalSettings() @@ -185,7 +185,7 @@ func (o *JsonDB) GetClientByID(clientID string, hasQRCode bool) (model.ClientDat } // generate client qrcode image in base64 - if hasQRCode { + if hasQRCode && client.PrivateKey != "" { server, _ := o.GetServer() globalSettings, _ := o.GetGlobalSettings() diff --git a/templates/base.html b/templates/base.html index b3c254c..acb0fe3 100644 --- a/templates/base.html +++ b/templates/base.html @@ -184,6 +184,21 @@ </label> </div> </div> + <details> + <summary>Public and Preshared Keys</summary> + <div class="form-group" style="margin-top: 1rem"> + <label for="client_public_key" class="control-label"> + Public Key + </label> + <input type="text" class="form-control" id="client_public_key" name="client_public_key" placeholder="Autogenerated (insecure)" aria-invalid="false"> + </div> + <div class="form-group"> + <label for="client_preshared_key" class="control-label"> + Preshared Key + </label> + <input type="text" class="form-control" id="client_preshared_key" name="client_preshared_key" placeholder="Autogenerated"> + </div> + </details> </div> <div class="modal-footer justify-content-between"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> @@ -314,9 +329,12 @@ if ($("#enabled").is(':checked')){ enabled = true; } + const public_key = $("#client_public_key").val(); + const preshared_key = $("#client_preshared_key").val(); const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips, - "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled}; + "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, + "public_key": public_key, "preshared_key": preshared_key}; $.ajax({ cache: false, @@ -434,6 +452,8 @@ $("#modal_new_client").on('shown.bs.modal', function (e) { $("#client_name").val(""); $("#client_email").val(""); + $("#client_public_key").val(""); + $("#client_preshared_key").val(""); $("#client_allocated_ips").importTags(''); $("#client_extra_allowed_ips").importTags(''); updateIPAllocationSuggestion();