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. --- CMakeLists.txt | 2 + src/app.c | 15 ++-- src/ui/certimportwidget.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++ src/ui/certimportwidget.h | 30 +++++++ src/ui/documentwidget.c | 4 + src/ui/documentwidget.h | 1 + src/ui/util.c | 57 +----------- src/ui/util.h | 1 - src/ui/window.c | 8 +- 9 files changed, 272 insertions(+), 71 deletions(-) create mode 100644 src/ui/certimportwidget.c create mode 100644 src/ui/certimportwidget.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e29c3fa..b6456976 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,8 @@ set (SOURCES # User interface: src/ui/bindingswidget.c src/ui/bindingswidget.h + src/ui/certimportwidget.c + src/ui/certimportwidget.h src/ui/color.c src/ui/color.h src/ui/command.c diff --git a/src/app.c b/src/app.c index be968b36..82232afa 100644 --- a/src/app.c +++ b/src/app.c @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gmdocument.h" #include "gmutil.h" #include "history.h" +#include "ui/certimportwidget.h" #include "ui/color.h" #include "ui/command.h" #include "ui/documentwidget.h" @@ -994,14 +995,6 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { return iFalse; } -iBool handleCertificateImportCommands_(iWidget *dlg, const char *cmd) { - if (equal_Command(cmd, "certimport.accept") || equal_Command(cmd, "cancel")) { - destroy_Widget(dlg); - return iTrue; - } - return iFalse; -} - iBool willUseProxy_App(const iRangecc scheme) { return schemeProxy_App(scheme) != NULL; } @@ -1412,8 +1405,10 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } else if (equal_Command(cmd, "ident.import")) { - iWidget *dlg = makeCertificateImport_Widget(); - setCommandHandler_Widget(dlg, handleCertificateImportCommands_); + iCertImportWidget *imp = new_CertImportWidget(); + setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); + addChild_Widget(d->window->root, iClob(imp)); + postRefresh_App(); return iTrue; } else if (equal_Command(cmd, "ident.signin")) { 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) diff --git a/src/ui/certimportwidget.h b/src/ui/certimportwidget.h new file mode 100644 index 00000000..65548846 --- /dev/null +++ b/src/ui/certimportwidget.h @@ -0,0 +1,30 @@ +/* Copyright 2020 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. */ + +#pragma once + +#include "widget.h" + +iDeclareWidgetClass(CertImportWidget) +iDeclareObjectConstruction(CertImportWidget) + +void setPageContent_CertImportWidget (iCertImportWidget *, const iBlock *); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 1c95593b..8603f1fe 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -3219,6 +3219,10 @@ const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) { return d->doc; } +const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) { + return &d->sourceContent; +} + const iString *feedTitle_DocumentWidget(const iDocumentWidget *d) { if (!isEmpty_String(title_GmDocument(d->doc))) { return title_GmDocument(d->doc); diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h index 55c0a6ed..91337a24 100644 --- a/src/ui/documentwidget.h +++ b/src/ui/documentwidget.h @@ -39,6 +39,7 @@ iHistory * history_DocumentWidget (iDocumentWidget *); const iString * url_DocumentWidget (const iDocumentWidget *); iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); +const iBlock * sourceContent_DocumentWidget (const iDocumentWidget *); const iGmDocument * document_DocumentWidget (const iDocumentWidget *); const iString * bookmarkTitle_DocumentWidget (const iDocumentWidget *); const iString * feedTitle_DocumentWidget (const iDocumentWidget *); diff --git a/src/ui/util.c b/src/ui/util.c index d8a66bab..fd297ce2 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -744,8 +744,8 @@ iBool filePathHandler_(iWidget *dlg, const char *cmd) { iWidget *makeSheet_Widget(const char *id) { iWidget *sheet = new_Widget(); - setPadding1_Widget(sheet, 3 * gap_UI); setId_Widget(sheet, id); + setPadding1_Widget(sheet, 3 * gap_UI); setFrameColor_Widget(sheet, uiSeparator_ColorId); setBackgroundColor_Widget(sheet, uiBackground_ColorId); setFlags_Widget(sheet, @@ -1442,58 +1442,3 @@ iWidget *makeIdentityCreation_Widget(void) { centerSheet_Widget(dlg); return dlg; } - -iWidget *makeCertificateImport_Widget(void) { - iWidget *dlg = makeSheet_Widget("certimport"); - setId_Widget( - addChildFlags_Widget(dlg, - iClob(new_LabelWidget(uiHeading_ColorEscape "IMPORT CERTIFICATE", NULL)), - frameless_WidgetFlag), - "certimport.heading"); - addAction_Widget(dlg, SDLK_v, KMOD_PRIMARY, "certimport.paste"); - addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); - iLabelWidget *crt = new_LabelWidget("No Certificate", NULL); - setFont_LabelWidget(crt, uiContent_FontId); - addChildFlags_Widget(dlg, iClob(crt), 0); - setFrameColor_Widget(as_Widget(crt), uiTextDim_ColorId); - iLabelWidget *key = new_LabelWidget("No Private Key", NULL); - setFont_LabelWidget(key, uiContent_FontId); - addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); - addChildFlags_Widget(dlg, iClob(key), 0); - setFrameColor_Widget(as_Widget(key), uiTextDim_ColorId); - addChild_Widget(dlg, 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:"))); - void *inputs[2]; - addChild_Widget(values, iClob(inputs[0] = new_InputWidget(0))); - setHint_InputWidget(inputs[0], "filename (no extension)"); - addChild_Widget(headings, iClob(makeHeading_Widget("Notes:"))); - addChild_Widget(values, iClob(inputs[1] = new_InputWidget(0))); - setHint_InputWidget(inputs[1], "e.g., site name"); - as_Widget(inputs[0])->rect.size.x = gap_UI * 60; - as_Widget(inputs[1])->rect.size.x = gap_UI * 60; - } - addChild_Widget(dlg, iClob(page)); - arrange_Widget(dlg); - setSize_Widget(as_Widget(crt), init_I2(width_Widget(dlg), gap_UI * 15)); - setSize_Widget(as_Widget(key), init_I2(width_Widget(dlg), gap_UI * 15)); - /* 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(dlg, iClob(div)); - addChild_Widget(get_Window()->root, iClob(dlg)); - centerSheet_Widget(dlg); - return dlg; -} diff --git a/src/ui/util.h b/src/ui/util.h index 5b701e08..a280fedb 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -204,5 +204,4 @@ iWidget * makePreferences_Widget (void); iWidget * makeBookmarkEditor_Widget (void); iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon); iWidget * makeIdentityCreation_Widget (void); -iWidget * makeCertificateImport_Widget(void); iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); diff --git a/src/ui/window.c b/src/ui/window.c index a447340b..a4734675 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -163,23 +163,23 @@ static const iMenuItem viewMenuItems_[] = { static iMenuItem bookmarksMenuItems_[] = { { "Bookmark This Page...", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, + { "Subscribe to This Page...", subscribeToPage_KeyModifier, "feeds.subscribe" }, { "---", 0, 0, NULL }, { "Import All Links on Page...", 0, 0, "bookmark.links confirm:1" }, { "---", 0, 0, NULL }, { "List All", 0, 0, "open url:about:bookmarks" }, { "List by Tag", 0, 0, "open url:about:bookmarks?tags" }, { "List by Creation Time", 0, 0, "open url:about:bookmarks?created" }, - { "Refresh Remote Sources", 0, 0, "bookmarks.reload.remote" }, - { "---", 0, 0, NULL }, - { "Subscribe to This Page...", subscribeToPage_KeyModifier, "feeds.subscribe" }, { "Show Feed Entries", 0, 0, "open url:about:feeds" }, + { "---", 0, 0, NULL }, + { "Refresh Remote Bookmarks", 0, 0, "bookmarks.reload.remote" }, { "Refresh Feeds", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" }, }; static const iMenuItem identityMenuItems_[] = { { "New Identity...", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" }, { "---", 0, 0, NULL }, - { "Import...", 0, 0, "ident.import" }, + { "Import...", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, }; static const iMenuItem helpMenuItems_[] = { -- cgit v1.2.3