diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-01-04 12:21:33 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-01-04 12:21:33 +0200 |
commit | 4ab1e49bcd1c90a47ddccab0afa37dc0eb5e2244 (patch) | |
tree | 1c40dc5d4736304520dc6c638ce7cf107a4fed6d /src/ui/certimportwidget.c | |
parent | 473558b969e2521c79dc9cd0df1e04749c6185ea (diff) |
Added a client certificate import dialog
Todo: Drag-and-drop; the actual identity creation step.
Diffstat (limited to 'src/ui/certimportwidget.c')
-rw-r--r-- | src/ui/certimportwidget.c | 225 |
1 files changed, 225 insertions, 0 deletions
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 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #include "certimportwidget.h" | ||
24 | #include "labelwidget.h" | ||
25 | #include "inputwidget.h" | ||
26 | #include "color.h" | ||
27 | #include "text.h" | ||
28 | #include "ui/util.h" | ||
29 | #include "app.h" | ||
30 | |||
31 | #include <the_Foundation/tlsrequest.h> | ||
32 | #include <SDL_clipboard.h> | ||
33 | |||
34 | iDefineObjectConstruction(CertImportWidget) | ||
35 | |||
36 | struct Impl_CertImportWidget { | ||
37 | iWidget widget; | ||
38 | iLabelWidget *info; | ||
39 | iLabelWidget *crtLabel; | ||
40 | iLabelWidget *keyLabel; | ||
41 | iInputWidget *filename; | ||
42 | iInputWidget *notes; | ||
43 | iTlsCertificate *cert; | ||
44 | }; | ||
45 | |||
46 | static const char *infoText_ = "Paste a PEM-encoded certificate and/or private key,\n" | ||
47 | "or drop a .crt/.key file on the window."; | ||
48 | |||
49 | static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *data) { | ||
50 | iBool ok = iFalse; | ||
51 | iString pem; | ||
52 | initBlock_String(&pem, data); | ||
53 | iTlsCertificate *newCert = newPemKey_TlsCertificate(&pem, &pem); | ||
54 | const iBool gotNewCrt = !isEmpty_TlsCertificate(newCert); | ||
55 | const iBool gotNewKey = hasPrivateKey_TlsCertificate(newCert); | ||
56 | if (d->cert && (gotNewCrt ^ gotNewKey)) { /* One new part? Merge with existing. */ | ||
57 | const iString *crt = collect_String(pem_TlsCertificate(gotNewCrt ? newCert : d->cert)); | ||
58 | const iString *key = collect_String(privateKeyPem_TlsCertificate(gotNewKey ? newCert : d->cert)); | ||
59 | delete_TlsCertificate(d->cert); | ||
60 | delete_TlsCertificate(newCert); | ||
61 | d->cert = newPemKey_TlsCertificate(crt, key); | ||
62 | ok = iTrue; | ||
63 | } | ||
64 | else if (gotNewCrt || gotNewKey) { | ||
65 | delete_TlsCertificate(d->cert); | ||
66 | d->cert = newCert; | ||
67 | ok = iTrue; | ||
68 | } | ||
69 | else { | ||
70 | delete_TlsCertificate(newCert); | ||
71 | } | ||
72 | deinit_String(&pem); | ||
73 | /* Update the labels. */ { | ||
74 | if (d->cert && !isEmpty_TlsCertificate(d->cert)) { | ||
75 | setTextCStr_LabelWidget( | ||
76 | d->crtLabel, | ||
77 | format_CStr("%s%s", | ||
78 | uiTextAction_ColorEscape, | ||
79 | cstrCollect_String(subject_TlsCertificate(d->cert)))); | ||
80 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextAction_ColorId); | ||
81 | } | ||
82 | else { | ||
83 | setTextCStr_LabelWidget(d->crtLabel, uiTextCaution_ColorEscape "No Certificate"); | ||
84 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); | ||
85 | } | ||
86 | if (d->cert && hasPrivateKey_TlsCertificate(d->cert)) { | ||
87 | iString *fng = collect_String( | ||
88 | hexEncode_Block(collect_Block(privateKeyFingerprint_TlsCertificate(d->cert)))); | ||
89 | insertData_Block(&fng->chars, size_String(fng) / 2, "\n", 1); | ||
90 | setTextCStr_LabelWidget( | ||
91 | d->keyLabel, format_CStr("%s%s", uiTextAction_ColorEscape, cstr_String(fng))); | ||
92 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextAction_ColorId); | ||
93 | } | ||
94 | else { | ||
95 | setTextCStr_LabelWidget(d->keyLabel, uiTextCaution_ColorEscape "No Private Key"); | ||
96 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); | ||
97 | } | ||
98 | } | ||
99 | return ok; | ||
100 | } | ||
101 | |||
102 | void init_CertImportWidget(iCertImportWidget *d) { | ||
103 | iWidget *w = as_Widget(d); | ||
104 | init_Widget(w); | ||
105 | setId_Widget(w, "certimport"); | ||
106 | d->cert = NULL; | ||
107 | /* This should behave similar to sheets. */ { | ||
108 | setPadding1_Widget(w, 3 * gap_UI); | ||
109 | setFrameColor_Widget(w, uiSeparator_ColorId); | ||
110 | setBackgroundColor_Widget(w, uiBackground_ColorId); | ||
111 | setFlags_Widget(w, | ||
112 | mouseModal_WidgetFlag | keepOnTop_WidgetFlag | arrangeVertical_WidgetFlag | | ||
113 | arrangeSize_WidgetFlag | centerHorizontal_WidgetFlag | | ||
114 | overflowScrollable_WidgetFlag, | ||
115 | iTrue); | ||
116 | } | ||
117 | addChildFlags_Widget(w, | ||
118 | iClob(new_LabelWidget(uiHeading_ColorEscape "IMPORT IDENTITY", NULL)), | ||
119 | frameless_WidgetFlag); | ||
120 | d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); | ||
121 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
122 | d->crtLabel = new_LabelWidget("", NULL); { | ||
123 | setFont_LabelWidget(d->crtLabel, uiContent_FontId); | ||
124 | addChildFlags_Widget(w, iClob(d->crtLabel), 0); | ||
125 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); | ||
126 | } | ||
127 | d->keyLabel = new_LabelWidget("", NULL); { | ||
128 | setFont_LabelWidget(d->keyLabel, uiContent_FontId); | ||
129 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
130 | addChildFlags_Widget(w, iClob(d->keyLabel), 0); | ||
131 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); | ||
132 | } | ||
133 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
134 | iWidget *page = new_Widget(); { | ||
135 | setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | ||
136 | iWidget *headings = addChildFlags_Widget( | ||
137 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | ||
138 | iWidget *values = addChildFlags_Widget( | ||
139 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | ||
140 | addChild_Widget(headings, iClob(makeHeading_Widget("Save as:"))); | ||
141 | addChild_Widget(values, iClob(d->filename = new_InputWidget(0))); | ||
142 | setHint_InputWidget(d->filename, "filename (no extension)"); | ||
143 | addChild_Widget(headings, iClob(makeHeading_Widget("Notes:"))); | ||
144 | addChild_Widget(values, iClob(d->notes = new_InputWidget(0))); | ||
145 | setHint_InputWidget(d->notes, "e.g., site name"); | ||
146 | as_Widget(d->filename)->rect.size.x = gap_UI * 70; | ||
147 | as_Widget(d->notes)->rect.size.x = gap_UI * 70; | ||
148 | } | ||
149 | addChild_Widget(w, iClob(page)); | ||
150 | arrange_Widget(w); | ||
151 | setSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); | ||
152 | setSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); | ||
153 | /* Buttons. */ | ||
154 | iWidget *div = new_Widget(); { | ||
155 | setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | ||
156 | addChild_Widget(div, iClob(newKeyMods_LabelWidget("Cancel", SDLK_ESCAPE, 0, "cancel"))); | ||
157 | iLabelWidget *accept = addChild_Widget( | ||
158 | div, | ||
159 | iClob(newKeyMods_LabelWidget( | ||
160 | uiTextAction_ColorEscape "Import", SDLK_RETURN, KMOD_PRIMARY, "certimport.accept"))); | ||
161 | setFont_LabelWidget(accept, uiLabelBold_FontId); | ||
162 | } | ||
163 | addChild_Widget(w, iClob(div)); | ||
164 | arrange_Widget(w); | ||
165 | } | ||
166 | |||
167 | void deinit_CertImportWidget(iCertImportWidget *d) { | ||
168 | delete_TlsCertificate(d->cert); | ||
169 | } | ||
170 | |||
171 | static iBool isComplete_CertImportWidget_(const iCertImportWidget *d) { | ||
172 | return d->cert && !isEmpty_TlsCertificate(d->cert) && hasPrivateKey_TlsCertificate(d->cert); | ||
173 | } | ||
174 | |||
175 | void setPageContent_CertImportWidget(iCertImportWidget *d, const iBlock *content) { | ||
176 | if (tryImport_CertImportWidget_(d, content)) { | ||
177 | setTextCStr_LabelWidget(d->info, infoText_); | ||
178 | if (isComplete_CertImportWidget_(d)) { | ||
179 | setFocus_Widget(as_Widget(d->filename)); | ||
180 | } | ||
181 | } | ||
182 | else { | ||
183 | setTextCStr_LabelWidget( | ||
184 | d->info, format_CStr("No certificate/key found on the current page.\n%s", infoText_)); | ||
185 | } | ||
186 | arrange_Widget(as_Widget(d)); | ||
187 | } | ||
188 | |||
189 | static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Event *ev) { | ||
190 | iWidget *w = as_Widget(d); | ||
191 | if (ev->type == SDL_KEYDOWN) { | ||
192 | const int key = ev->key.keysym.sym; | ||
193 | const int mods = keyMods_Sym(ev->key.keysym.mod); | ||
194 | if (key == SDLK_v && mods == KMOD_PRIMARY) { | ||
195 | if (!tryImport_CertImportWidget_( | ||
196 | d, collect_Block(newCStr_Block(SDL_GetClipboardText())))) { | ||
197 | makeMessage_Widget(uiTextCaution_ColorEscape "PASTED FROM CLIPBOARD", | ||
198 | "No certificate or private key was found."); | ||
199 | } | ||
200 | postRefresh_App(); | ||
201 | return iTrue; | ||
202 | } | ||
203 | } | ||
204 | if (isCommand_Widget(w, ev, "cancel")) { | ||
205 | destroy_Widget(w); | ||
206 | return iTrue; | ||
207 | } | ||
208 | if (isCommand_Widget(w, ev, "certimport.accept")) { | ||
209 | if (d->cert) { | ||
210 | destroy_Widget(w); | ||
211 | } | ||
212 | return iTrue; | ||
213 | } | ||
214 | return processEvent_Widget(w, ev); | ||
215 | } | ||
216 | |||
217 | static void draw_CertImportWidget_(const iCertImportWidget *d) { | ||
218 | const iWidget *w = constAs_Widget(d); | ||
219 | draw_Widget(w); | ||
220 | } | ||
221 | |||
222 | iBeginDefineSubclass(CertImportWidget, Widget) | ||
223 | .processEvent = (iAny *) processEvent_CertImportWidget_, | ||
224 | .draw = (iAny *) draw_CertImportWidget_, | ||
225 | iEndDefineSubclass(CertImportWidget) | ||