From 4ab1e49bcd1c90a47ddccab0afa37dc0eb5e2244 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 4 Jan 2021 12:21:33 +0200 Subject: Added a client certificate import dialog Todo: Drag-and-drop; the actual identity creation step. --- src/ui/certimportwidget.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 src/ui/certimportwidget.c (limited to 'src/ui/certimportwidget.c') diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c new file mode 100644 index 00000000..51743e89 --- /dev/null +++ b/src/ui/certimportwidget.c @@ -0,0 +1,225 @@ +/* Copyright 2021 Jaakko Keränen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#include "certimportwidget.h" +#include "labelwidget.h" +#include "inputwidget.h" +#include "color.h" +#include "text.h" +#include "ui/util.h" +#include "app.h" + +#include +#include + +iDefineObjectConstruction(CertImportWidget) + +struct Impl_CertImportWidget { + iWidget widget; + iLabelWidget *info; + iLabelWidget *crtLabel; + iLabelWidget *keyLabel; + iInputWidget *filename; + iInputWidget *notes; + iTlsCertificate *cert; +}; + +static const char *infoText_ = "Paste a PEM-encoded certificate and/or private key,\n" + "or drop a .crt/.key file on the window."; + +static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *data) { + iBool ok = iFalse; + iString pem; + initBlock_String(&pem, data); + iTlsCertificate *newCert = newPemKey_TlsCertificate(&pem, &pem); + const iBool gotNewCrt = !isEmpty_TlsCertificate(newCert); + const iBool gotNewKey = hasPrivateKey_TlsCertificate(newCert); + if (d->cert && (gotNewCrt ^ gotNewKey)) { /* One new part? Merge with existing. */ + const iString *crt = collect_String(pem_TlsCertificate(gotNewCrt ? newCert : d->cert)); + const iString *key = collect_String(privateKeyPem_TlsCertificate(gotNewKey ? newCert : d->cert)); + delete_TlsCertificate(d->cert); + delete_TlsCertificate(newCert); + d->cert = newPemKey_TlsCertificate(crt, key); + ok = iTrue; + } + else if (gotNewCrt || gotNewKey) { + delete_TlsCertificate(d->cert); + d->cert = newCert; + ok = iTrue; + } + else { + delete_TlsCertificate(newCert); + } + deinit_String(&pem); + /* Update the labels. */ { + if (d->cert && !isEmpty_TlsCertificate(d->cert)) { + setTextCStr_LabelWidget( + d->crtLabel, + format_CStr("%s%s", + uiTextAction_ColorEscape, + cstrCollect_String(subject_TlsCertificate(d->cert)))); + setFrameColor_Widget(as_Widget(d->crtLabel), uiTextAction_ColorId); + } + else { + setTextCStr_LabelWidget(d->crtLabel, uiTextCaution_ColorEscape "No Certificate"); + setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); + } + if (d->cert && hasPrivateKey_TlsCertificate(d->cert)) { + iString *fng = collect_String( + hexEncode_Block(collect_Block(privateKeyFingerprint_TlsCertificate(d->cert)))); + insertData_Block(&fng->chars, size_String(fng) / 2, "\n", 1); + setTextCStr_LabelWidget( + d->keyLabel, format_CStr("%s%s", uiTextAction_ColorEscape, cstr_String(fng))); + setFrameColor_Widget(as_Widget(d->keyLabel), uiTextAction_ColorId); + } + else { + setTextCStr_LabelWidget(d->keyLabel, uiTextCaution_ColorEscape "No Private Key"); + setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); + } + } + return ok; +} + +void init_CertImportWidget(iCertImportWidget *d) { + iWidget *w = as_Widget(d); + init_Widget(w); + setId_Widget(w, "certimport"); + d->cert = NULL; + /* This should behave similar to sheets. */ { + setPadding1_Widget(w, 3 * gap_UI); + setFrameColor_Widget(w, uiSeparator_ColorId); + setBackgroundColor_Widget(w, uiBackground_ColorId); + setFlags_Widget(w, + mouseModal_WidgetFlag | keepOnTop_WidgetFlag | arrangeVertical_WidgetFlag | + arrangeSize_WidgetFlag | centerHorizontal_WidgetFlag | + overflowScrollable_WidgetFlag, + iTrue); + } + addChildFlags_Widget(w, + iClob(new_LabelWidget(uiHeading_ColorEscape "IMPORT IDENTITY", NULL)), + frameless_WidgetFlag); + d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + d->crtLabel = new_LabelWidget("", NULL); { + setFont_LabelWidget(d->crtLabel, uiContent_FontId); + addChildFlags_Widget(w, iClob(d->crtLabel), 0); + setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); + } + d->keyLabel = new_LabelWidget("", NULL); { + setFont_LabelWidget(d->keyLabel, uiContent_FontId); + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + addChildFlags_Widget(w, iClob(d->keyLabel), 0); + setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); + } + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + iWidget *page = new_Widget(); { + setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); + iWidget *headings = addChildFlags_Widget( + page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); + iWidget *values = addChildFlags_Widget( + page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); + addChild_Widget(headings, iClob(makeHeading_Widget("Save as:"))); + addChild_Widget(values, iClob(d->filename = new_InputWidget(0))); + setHint_InputWidget(d->filename, "filename (no extension)"); + addChild_Widget(headings, iClob(makeHeading_Widget("Notes:"))); + addChild_Widget(values, iClob(d->notes = new_InputWidget(0))); + setHint_InputWidget(d->notes, "e.g., site name"); + as_Widget(d->filename)->rect.size.x = gap_UI * 70; + as_Widget(d->notes)->rect.size.x = gap_UI * 70; + } + addChild_Widget(w, iClob(page)); + arrange_Widget(w); + setSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); + setSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); + /* Buttons. */ + iWidget *div = new_Widget(); { + setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); + addChild_Widget(div, iClob(newKeyMods_LabelWidget("Cancel", SDLK_ESCAPE, 0, "cancel"))); + iLabelWidget *accept = addChild_Widget( + div, + iClob(newKeyMods_LabelWidget( + uiTextAction_ColorEscape "Import", SDLK_RETURN, KMOD_PRIMARY, "certimport.accept"))); + setFont_LabelWidget(accept, uiLabelBold_FontId); + } + addChild_Widget(w, iClob(div)); + arrange_Widget(w); +} + +void deinit_CertImportWidget(iCertImportWidget *d) { + delete_TlsCertificate(d->cert); +} + +static iBool isComplete_CertImportWidget_(const iCertImportWidget *d) { + return d->cert && !isEmpty_TlsCertificate(d->cert) && hasPrivateKey_TlsCertificate(d->cert); +} + +void setPageContent_CertImportWidget(iCertImportWidget *d, const iBlock *content) { + if (tryImport_CertImportWidget_(d, content)) { + setTextCStr_LabelWidget(d->info, infoText_); + if (isComplete_CertImportWidget_(d)) { + setFocus_Widget(as_Widget(d->filename)); + } + } + else { + setTextCStr_LabelWidget( + d->info, format_CStr("No certificate/key found on the current page.\n%s", infoText_)); + } + arrange_Widget(as_Widget(d)); +} + +static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); + if (ev->type == SDL_KEYDOWN) { + const int key = ev->key.keysym.sym; + const int mods = keyMods_Sym(ev->key.keysym.mod); + if (key == SDLK_v && mods == KMOD_PRIMARY) { + if (!tryImport_CertImportWidget_( + d, collect_Block(newCStr_Block(SDL_GetClipboardText())))) { + makeMessage_Widget(uiTextCaution_ColorEscape "PASTED FROM CLIPBOARD", + "No certificate or private key was found."); + } + postRefresh_App(); + return iTrue; + } + } + if (isCommand_Widget(w, ev, "cancel")) { + destroy_Widget(w); + return iTrue; + } + if (isCommand_Widget(w, ev, "certimport.accept")) { + if (d->cert) { + destroy_Widget(w); + } + return iTrue; + } + return processEvent_Widget(w, ev); +} + +static void draw_CertImportWidget_(const iCertImportWidget *d) { + const iWidget *w = constAs_Widget(d); + draw_Widget(w); +} + +iBeginDefineSubclass(CertImportWidget, Widget) + .processEvent = (iAny *) processEvent_CertImportWidget_, + .draw = (iAny *) draw_CertImportWidget_, +iEndDefineSubclass(CertImportWidget) -- cgit v1.2.3