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
25 #include "app.h"
26 #include "color.h"
27 #include "command.h"
28 #include "gmcerts.h"
29 #include "inputwidget.h"
30 #include "labelwidget.h"
31 #include "text.h"
32 #include "ui/util.h"
33
34 #if defined (iPlatformAppleMobile)
35 # include "ios.h"
36 #endif
37
38 #include <the_Foundation/file.h>
39 #include <the_Foundation/tlsrequest.h>
40 #include <the_Foundation/path.h>
41 #include <SDL_clipboard.h>
42
43 iDefineObjectConstruction(CertImportWidget)
44
45 struct Impl_CertImportWidget {
46 iWidget widget;
47 iLabelWidget *info;
48 iLabelWidget *crtLabel;
49 iLabelWidget *keyLabel;
50 iInputWidget *notes;
51 iTlsCertificate *cert;
52 };
53
54 static const char *infoText_ = "${dlg.certimport.help}";
55
tryImport_CertImportWidget_(iCertImportWidget * d,const iBlock * data)56 static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *data) {
57 iBool ok = iFalse;
58 iString pem;
59 initBlock_String(&pem, data);
60 iTlsCertificate *newCert = newPemKey_TlsCertificate(&pem, &pem);
61 const iBool gotNewCrt = !isEmpty_TlsCertificate(newCert);
62 const iBool gotNewKey = hasPrivateKey_TlsCertificate(newCert);
63 if (d->cert && (gotNewCrt ^ gotNewKey)) { /* One new part? Merge with existing. */
64 const iString *crt = collect_String(pem_TlsCertificate(gotNewCrt ? newCert : d->cert));
65 const iString *key = collect_String(privateKeyPem_TlsCertificate(gotNewKey ? newCert : d->cert));
66 delete_TlsCertificate(d->cert);
67 delete_TlsCertificate(newCert);
68 d->cert = newPemKey_TlsCertificate(crt, key);
69 ok = iTrue;
70 }
71 else if (gotNewCrt || gotNewKey) {
72 delete_TlsCertificate(d->cert);
73 d->cert = newCert;
74 ok = iTrue;
75 }
76 else {
77 delete_TlsCertificate(newCert);
78 }
79 deinit_String(&pem);
80 /* Update the labels. */ {
81 if (d->cert && !isEmpty_TlsCertificate(d->cert)) {
82 updateTextCStr_LabelWidget(
83 d->crtLabel,
84 format_CStr("%s%s",
85 uiTextAction_ColorEscape,
86 cstrCollect_String(subject_TlsCertificate(d->cert))));
87 setFrameColor_Widget(as_Widget(d->crtLabel), uiTextAction_ColorId);
88 }
89 else {
90 updateTextCStr_LabelWidget(d->crtLabel, uiTextCaution_ColorEscape "${dlg.certimport.nocert}");
91 setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId);
92 }
93 if (d->cert && hasPrivateKey_TlsCertificate(d->cert)) {
94 iString *fng = collect_String(
95 hexEncode_Block(collect_Block(privateKeyFingerprint_TlsCertificate(d->cert))));
96 insertData_Block(&fng->chars, size_String(fng) / 2, "\n", 1);
97 updateTextCStr_LabelWidget(
98 d->keyLabel, format_CStr("%s%s", uiTextAction_ColorEscape, cstr_String(fng)));
99 setFrameColor_Widget(as_Widget(d->keyLabel), uiTextAction_ColorId);
100 }
101 else {
102 updateTextCStr_LabelWidget(d->keyLabel, uiTextCaution_ColorEscape "${dlg.certimport.nokey}");
103 setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId);
104 }
105 }
106 return ok;
107 }
108
init_CertImportWidget(iCertImportWidget * d)109 void init_CertImportWidget(iCertImportWidget *d) {
110 iWidget *w = as_Widget(d);
111 const iMenuItem actions[] = {
112 #if defined (iPlatformAppleMobile)
113 { "${dlg.certimport.pickfile}", 0, 0, "certimport.pickfile" },
114 { "---" },
115 #endif
116 { "${cancel}" },
117 { uiTextAction_ColorEscape "${dlg.certimport.import}",
118 SDLK_RETURN, KMOD_PRIMARY,
119 "certimport.accept" }
120 };
121 init_Widget(w);
122 setId_Widget(w, "certimport");
123 d->cert = NULL;
124 if (isUsingPanelLayout_Mobile()) {
125 initPanels_Mobile(w, NULL, (iMenuItem[]){
126 { "title id:heading.certimport" },
127 { format_CStr("label id:certimport.info text:%s", infoText_) },
128 //{ "padding" },
129 { "label id:certimport.crt nowrap:1 frame:1" },
130 { "padding arg:0.25" },
131 { "label id:certimport.key nowrap:1 frame:1" },
132 { "heading text:${dlg.certimport.notes}" },
133 { "input id:certimport.notes hint:hint.certimport.description noheading:1" },
134 { NULL }
135 }, actions, iElemCount(actions));
136 d->info = findChild_Widget(w, "certimport.info");
137 d->crtLabel = findChild_Widget(w, "certimport.crt");
138 d->keyLabel = findChild_Widget(w, "certimport.key");
139 d->notes = findChild_Widget(w, "certimport.notes");
140 setFont_LabelWidget(d->crtLabel, uiContent_FontId);
141 setFont_LabelWidget(d->keyLabel, uiContent_FontId);
142 setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(-1, gap_UI * 12));
143 setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(-1, gap_UI * 12));
144 }
145 else {
146 /* This should behave similar to sheets. */
147 useSheetStyle_Widget(w);
148 addChildFlags_Widget(
149 w,
150 iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.certimport}", NULL)),
151 frameless_WidgetFlag);
152 d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag);
153 addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
154 d->crtLabel = new_LabelWidget("", NULL); {
155 setFont_LabelWidget(d->crtLabel, uiContent_FontId);
156 addChildFlags_Widget(w, iClob(d->crtLabel), 0);
157 }
158 d->keyLabel = new_LabelWidget("", NULL); {
159 setFont_LabelWidget(d->keyLabel, uiContent_FontId);
160 addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
161 addChildFlags_Widget(w, iClob(d->keyLabel), 0);
162 }
163 addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
164 /* TODO: Use makeTwoColumnWidget_() */
165 iWidget *page = new_Widget(); {
166 setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
167 iWidget *headings = addChildFlags_Widget(
168 page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
169 iWidget *values = addChildFlags_Widget(
170 page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
171 addTwoColumnDialogInputField_Widget(
172 headings,
173 values,
174 "${dlg.certimport.notes}",
175 "",
176 iClob(d->notes = newHint_InputWidget(0, "${hint.certimport.description}")));
177 as_Widget(d->notes)->rect.size.x = gap_UI * 70;
178 }
179 addChild_Widget(w, iClob(page));
180 arrange_Widget(w);
181 setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12));
182 setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12));
183 /* Buttons. */
184 addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
185 iWidget *buttons = makeDialogButtons_Widget(actions, iElemCount(actions));
186 addChild_Widget(w, iClob(buttons));
187 }
188 setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId);
189 setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId);
190 if (deviceType_App() != desktop_AppDeviceType) {
191 /* Try auto-pasting. */
192 postCommand_App("certimport.paste");
193 }
194 }
195
deinit_CertImportWidget(iCertImportWidget * d)196 void deinit_CertImportWidget(iCertImportWidget *d) {
197 delete_TlsCertificate(d->cert);
198 }
199
isComplete_CertImportWidget_(const iCertImportWidget * d)200 static iBool isComplete_CertImportWidget_(const iCertImportWidget *d) {
201 return d->cert && !isEmpty_TlsCertificate(d->cert) && hasPrivateKey_TlsCertificate(d->cert);
202 }
203
setPageContent_CertImportWidget(iCertImportWidget * d,const iBlock * content)204 void setPageContent_CertImportWidget(iCertImportWidget *d, const iBlock *content) {
205 if (tryImport_CertImportWidget_(d, content)) {
206 setTextCStr_LabelWidget(d->info, infoText_);
207 if (isComplete_CertImportWidget_(d)) {
208 setFocus_Widget(as_Widget(d->notes));
209 }
210 }
211 else {
212 setTextCStr_LabelWidget(
213 d->info, format_CStr("${dlg.certimport.notfound.page}\n%s", infoText_));
214 }
215 arrange_Widget(as_Widget(d));
216 }
217
tryImportFromClipboard_CertImportWidget_(iCertImportWidget * d)218 static iBool tryImportFromClipboard_CertImportWidget_(iCertImportWidget *d) {
219 return tryImport_CertImportWidget_(d, collect_Block(newCStr_Block(SDL_GetClipboardText())));
220 }
221
tryImportFromFile_CertImportWidget_(iCertImportWidget * d,const iString * path)222 static iBool tryImportFromFile_CertImportWidget_(iCertImportWidget *d, const iString *path) {
223 iBool success = iFalse;
224 iFile *f = new_File(path);
225 if (open_File(f, readOnly_FileMode | text_FileMode)) {
226 if (tryImport_CertImportWidget_(d, collect_Block(readAll_File(f)))) {
227 success = iTrue;
228 if (isComplete_CertImportWidget_(d)) {
229 setFocus_Widget(as_Widget(d->notes));
230 }
231 }
232 else {
233 makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.certimport.dropped}",
234 "${dlg.certimport.notfound}");
235 }
236 }
237 iRelease(f);
238 return success;
239 }
240
processEvent_CertImportWidget_(iCertImportWidget * d,const SDL_Event * ev)241 static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Event *ev) {
242 iWidget *w = as_Widget(d);
243 if (ev->type == SDL_KEYDOWN) {
244 const int key = ev->key.keysym.sym;
245 const int mods = keyMods_Sym(ev->key.keysym.mod);
246 if (key == SDLK_v && mods == KMOD_PRIMARY) {
247 if (!tryImportFromClipboard_CertImportWidget_(d)) {
248 makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.certimport.pasted}",
249 "${dlg.certimport.notfound}");
250 }
251 postRefresh_App();
252 return iTrue;
253 }
254 }
255 if (isCommand_UserEvent(ev, "input.paste")) {
256 if (!tryImportFromClipboard_CertImportWidget_(d)) {
257 makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.certimport.pasted}",
258 "${dlg.certimport.notfound}");
259 }
260 postRefresh_App();
261 return iTrue;
262 }
263 if (isCommand_UserEvent(ev, "certimport.paste")) {
264 tryImportFromClipboard_CertImportWidget_(d);
265 return iTrue;
266 }
267 if (isCommand_Widget(w, ev, "cancel")) {
268 setupSheetTransition_Mobile(w, iFalse);
269 destroy_Widget(w);
270 return iTrue;
271 }
272 if (isCommand_Widget(w, ev, "certimport.accept")) {
273 if (d->cert && !isEmpty_TlsCertificate(d->cert) && hasPrivateKey_TlsCertificate(d->cert)) {
274 importIdentity_GmCerts(certs_App(), d->cert, text_InputWidget(d->notes));
275 d->cert = NULL; /* taken */
276 setupSheetTransition_Mobile(w, iFalse);
277 destroy_Widget(w);
278 postCommand_App("idents.changed");
279 }
280 return iTrue;
281 }
282 #if defined (iPlatformAppleMobile)
283 if (isCommand_UserEvent(ev, "certimport.pickfile")) {
284 const char *cmd = command_UserEvent(ev);
285 if (hasLabel_Command(cmd, "path")) {
286 const iString *path = collect_String(suffix_Command(cmd, "path"));
287 tryImportFromFile_CertImportWidget_(d, path);
288 remove(cstr_String(path)); /* it is a temporary copy */
289 }
290 else {
291 pickFile_iOS("certimport.pickfile");
292 }
293 return iTrue;
294 }
295 #endif
296 if (ev->type == SDL_DROPFILE) {
297 tryImportFromFile_CertImportWidget_(d, collectNewCStr_String(ev->drop.file));
298 return iTrue;
299 }
300 return processEvent_Widget(w, ev);
301 }
302
draw_CertImportWidget_(const iCertImportWidget * d)303 static void draw_CertImportWidget_(const iCertImportWidget *d) {
304 draw_Widget(constAs_Widget(d));
305 }
306
307 iBeginDefineSubclass(CertImportWidget, Widget)
308 .processEvent = (iAny *) processEvent_CertImportWidget_,
309 .draw = (iAny *) draw_CertImportWidget_,
310 iEndDefineSubclass(CertImportWidget)
311