1package gui 2 3import ( 4 "crypto/rand" 5 "fmt" 6 "log" 7 "math/big" 8 "regexp" 9 10 "github.com/coyim/coyim/i18n" 11 "github.com/coyim/coyim/session/access" 12 "github.com/coyim/coyim/xmpp/jid" 13 "github.com/coyim/gotk3adapter/gtki" 14) 15 16type verifier struct { 17 parentWindow gtki.Window 18 session access.Session 19 notifier *notifier 20 pinWindow *pinWindow 21 answerSMPWindow *answerSMPWindow 22 smpFailed *smpFailedNotification 23 waitingForPeer *waitingForPeerNotification 24 peerRequestsSMP *peerRequestsSMPNotification 25 unverifiedWarning *unverifiedWarning 26 verificationSuccess *verificationSuccessNotification 27 peerName func() string 28 peerToSendTo func() jid.WithResource 29} 30 31type notifier struct { 32 notificationArea gtki.Box 33} 34 35func (n *notifier) notify(i gtki.InfoBar) { 36 n.notificationArea.Add(i) 37} 38 39// TODO: unify repeated stuff 40func newVerifier(u *gtkUI, conv *conversationPane) *verifier { 41 v := &verifier{ 42 parentWindow: conv.transientParent, 43 session: conv.account.session, 44 notifier: ¬ifier{conv.notificationArea}, 45 peerName: func() string { 46 p, ok := conv.currentPeer() 47 if !ok { 48 return "" 49 } 50 return p.NameForPresentation() 51 }, 52 peerToSendTo: func() jid.WithResource { 53 return conv.currentPeerForSending().(jid.WithResource) 54 }, 55 } 56 57 v.buildPinWindow() 58 v.buildAnswerSMPDialog() 59 // A function is used below because we cannot check whether a contact is verified 60 // when newVerifier is called. 61 v.buildUnverifiedWarning(func() bool { 62 return conv.isEncrypted() && !conv.hasVerifiedKey() 63 }) 64 v.buildWaitingForPeerNotification() 65 v.buildPeerRequestsSMPNotification() 66 v.buildSMPFailedDialog() 67 68 return v 69} 70 71type pinWindow struct { 72 b *builder 73 dialog gtki.Dialog 74 prompt gtki.Label 75 pin gtki.Label 76 smpImage gtki.Image 77 padlockImage1 gtki.Image 78 padlockImage2 gtki.Image 79 alertImage gtki.Image 80} 81 82func (v *verifier) buildPinWindow() { 83 v.pinWindow = &pinWindow{ 84 b: newBuilder("GeneratePIN"), 85 } 86 87 v.pinWindow.b.getItems( 88 "dialog", &v.pinWindow.dialog, 89 "prompt", &v.pinWindow.prompt, 90 "pin", &v.pinWindow.pin, 91 "smp_image", &v.pinWindow.smpImage, 92 "padlock_image1", &v.pinWindow.padlockImage1, 93 "padlock_image2", &v.pinWindow.padlockImage2, 94 "alert_image", &v.pinWindow.alertImage, 95 ) 96 97 v.pinWindow.dialog.HideOnDelete() 98 v.pinWindow.dialog.SetTransientFor(v.parentWindow) 99 addBoldHeaderStyle(v.pinWindow.pin) 100 101 v.pinWindow.b.ConnectSignals(map[string]interface{}{ 102 "close_share_pin": func() { 103 v.showWaitingForPeerToCompleteSMPDialog() 104 v.pinWindow.dialog.Hide() 105 }, 106 }) 107 108 setImageFromFile(v.pinWindow.smpImage, "smp.svg") 109 setImageFromFile(v.pinWindow.padlockImage1, "padlock.svg") 110 setImageFromFile(v.pinWindow.padlockImage2, "padlock.svg") 111 setImageFromFile(v.pinWindow.alertImage, "alert.svg") 112} 113 114func (v *verifier) updateUnverifiedWarning() { 115 v.unverifiedWarning.update() 116} 117 118func (v *verifier) hideUnverifiedWarning() { 119 v.unverifiedWarning.infobar.Hide() 120} 121 122// TODO: check on linux 123type unverifiedWarning struct { 124 b *builder 125 infobar gtki.Box 126 closeInfobar gtki.Box 127 notification gtki.Box 128 label gtki.Label 129 image gtki.Image 130 button gtki.Button 131 shouldShowVerificationBar func() bool 132} 133 134// TODO: how will this work with i18n? 135var question = "Please enter the PIN that I shared with you." 136var coyIMQuestion = regexp.MustCompile("Please enter the PIN that I shared with you.") 137 138func (u *unverifiedWarning) update() { 139 if u.shouldShowVerificationBar() { 140 u.infobar.Show() 141 u.label.Show() 142 u.image.ShowAll() 143 } else { 144 u.infobar.Hide() 145 } 146} 147 148func (v *verifier) buildUnverifiedWarning(shouldShowVerificationBar func() bool) { 149 v.unverifiedWarning = &unverifiedWarning{ 150 b: newBuilder("UnverifiedWarning"), 151 } 152 153 v.unverifiedWarning.shouldShowVerificationBar = shouldShowVerificationBar 154 155 v.unverifiedWarning.b.getItems( 156 "verify-infobar", &v.unverifiedWarning.infobar, 157 "verify-close-infobar", &v.unverifiedWarning.closeInfobar, 158 "verify-notification", &v.unverifiedWarning.notification, 159 "verify-message", &v.unverifiedWarning.label, 160 "verify-image", &v.unverifiedWarning.image, 161 "verify-button", &v.unverifiedWarning.button, 162 ) 163 164 v.unverifiedWarning.b.ConnectSignals(map[string]interface{}{ 165 "on_press_image": v.hideUnverifiedWarning, 166 }) 167 168 prov := providerWithCSS("box { background-color: #fff3f3; color: #000000; border: 2px; }") 169 updateWithStyle(v.unverifiedWarning.infobar, prov) 170 171 prov = providerWithCSS("box { background-color: #e5d7d6; }") 172 updateWithStyle(v.unverifiedWarning.closeInfobar, prov) 173 174 setImageFromFile(v.unverifiedWarning.image, "warning.svg") 175 v.unverifiedWarning.label.SetLabel(i18n.Local("Make sure no one else\nis reading your messages")) 176 v.unverifiedWarning.button.Connect("clicked", v.showPINDialog) 177 178 v.notifier.notify(v.unverifiedWarning.infobar) 179} 180 181func (v *verifier) smpError(err error) { 182 v.hideUnverifiedWarning() 183 v.showCannotGeneratePINDialog(err) 184} 185 186func (v *verifier) showPINDialog() { 187 v.unverifiedWarning.infobar.Hide() 188 189 pin, err := createPIN() 190 if err != nil { 191 v.pinWindow.dialog.Hide() 192 v.smpError(err) 193 return 194 } 195 v.pinWindow.pin.SetText(pin) 196 v.pinWindow.prompt.SetMarkup(i18n.Localf("Share this one-time PIN with <b>%s</b>", v.peerName())) 197 198 v.session.StartSMP(v.peerToSendTo(), question, pin) 199 v.pinWindow.dialog.ShowAll() 200 201 v.waitingForPeer.label.SetLabel(i18n.Localf("Waiting for peer to finish \nsecuring the channel...")) 202 v.waitingForPeer.infobar.ShowAll() 203} 204 205func createPIN() (string, error) { 206 val, err := rand.Int(rand.Reader, big.NewInt(int64(1000000))) 207 if err != nil { 208 log.Printf("Error encountered when creating a new PIN: %v", err) 209 return "", err 210 } 211 212 return fmt.Sprintf("%06d", val), err 213} 214 215type waitingForPeerNotification struct { 216 b *builder 217 infobar gtki.InfoBar 218 label gtki.Label 219 image gtki.Image 220 button gtki.Button 221} 222 223func (v *verifier) buildWaitingForPeerNotification() { 224 v.waitingForPeer = &waitingForPeerNotification{ 225 b: newBuilder("WaitingSMPComplete"), 226 } 227 228 v.waitingForPeer.b.getItems( 229 "smp-waiting-infobar", &v.waitingForPeer.infobar, 230 "smp-waiting-label", &v.waitingForPeer.label, 231 "smp-waiting-image", &v.waitingForPeer.image, 232 "smp-waiting-button", &v.waitingForPeer.button, 233 ) 234 235 prov := providerWithCSS("box { background-color: #fff3f3; color: #000000; border: 2px; }") 236 updateWithStyle(v.waitingForPeer.infobar, prov) 237 238 v.waitingForPeer.label.SetText(i18n.Localf("Waiting for peer to finish \nsecuring the channel...")) 239 setImageFromFile(v.waitingForPeer.image, "waiting.svg") 240 241 v.waitingForPeer.button.Connect("clicked", func() { 242 v.cancelSMP() 243 }) 244 245 v.notifier.notify(v.waitingForPeer.infobar) 246} 247 248func (v *verifier) showWaitingForPeerToCompleteSMPDialog() { 249 v.waitingForPeer.label.SetLabel(i18n.Localf("Waiting for peer to finish \nsecuring the channel...")) 250 v.hideUnverifiedWarning() 251 v.waitingForPeer.infobar.ShowAll() 252} 253 254func (v *verifier) showCannotGeneratePINDialog(err error) { 255 b := newBuilder("CannotVerifyWithSMP") 256 257 infobar := b.getObj("smp-error-infobar").(gtki.InfoBar) 258 label := b.getObj("smp-error-label").(gtki.Label) 259 image := b.getObj("smp-error-image").(gtki.Image) 260 button := b.getObj("smp-error-button").(gtki.Button) 261 262 prov := providerWithCSS("box { background-color: #fff3f3; color: #000000; border: 2px; }") 263 updateWithStyle(infobar, prov) 264 265 label.SetText(i18n.Local("Unable to verify at this time.")) 266 log.Printf("Cannot recover from error: %v. Quitting SMP verification.", err) 267 setImageFromFile(image, "failure.svg") 268 button.Connect("clicked", infobar.Destroy) 269 270 infobar.ShowAll() 271 272 v.notifier.notify(infobar) 273} 274 275type answerSMPWindow struct { 276 b *builder 277 dialog gtki.Dialog 278 question gtki.Label 279 answer gtki.Entry 280 submitButton gtki.Button 281 smpImage gtki.Image 282 padlockImage1 gtki.Image 283 padlockImage2 gtki.Image 284 alertImage gtki.Image 285} 286 287func (v *verifier) buildAnswerSMPDialog() { 288 v.answerSMPWindow = &answerSMPWindow{ 289 b: newBuilder("AnswerSMPQuestion"), 290 } 291 292 v.answerSMPWindow.b.getItems( 293 "dialog", &v.answerSMPWindow.dialog, 294 "question_from_peer", &v.answerSMPWindow.question, 295 "button_submit", &v.answerSMPWindow.submitButton, 296 "answer", &v.answerSMPWindow.answer, 297 "smp_image", &v.answerSMPWindow.smpImage, 298 "padlock_image1", &v.answerSMPWindow.padlockImage1, 299 "padlock_image2", &v.answerSMPWindow.padlockImage2, 300 "alert_image", &v.answerSMPWindow.alertImage, 301 ) 302 303 v.answerSMPWindow.dialog.HideOnDelete() 304 v.answerSMPWindow.dialog.SetTransientFor(v.parentWindow) 305 v.answerSMPWindow.submitButton.SetSensitive(false) 306 307 setImageFromFile(v.answerSMPWindow.smpImage, "smp.svg") 308 setImageFromFile(v.answerSMPWindow.padlockImage1, "padlock.svg") 309 setImageFromFile(v.answerSMPWindow.padlockImage2, "padlock.svg") 310 setImageFromFile(v.answerSMPWindow.alertImage, "alert.svg") 311 312 v.answerSMPWindow.b.ConnectSignals(map[string]interface{}{ 313 "text_changing": func() { 314 answer, _ := v.answerSMPWindow.answer.GetText() 315 v.answerSMPWindow.submitButton.SetSensitive(len(answer) > 0) 316 }, 317 "close_share_pin": func() { 318 answer, _ := v.answerSMPWindow.answer.GetText() 319 v.removeInProgressDialogs() 320 v.session.FinishSMP(v.peerToSendTo(), answer) 321 v.showWaitingForPeerToCompleteSMPDialog() 322 }, 323 }) 324 325} 326 327func (v *verifier) showAnswerSMPDialog(question string) { 328 if "" == question { 329 v.answerSMPWindow.question.SetMarkup(i18n.Localf("Enter the secret that <b>%s</b> shared with you", v.peerName())) 330 } else if coyIMQuestion.MatchString(question) { 331 v.answerSMPWindow.question.SetMarkup(i18n.Localf("Type the PIN that <b>%s</b> sent you. It can be used only once.", v.peerName())) 332 } else { 333 v.answerSMPWindow.question.SetMarkup(i18n.Localf("Enter the answer to\n<b>%s</b>", question)) 334 } 335 336 v.answerSMPWindow.answer.SetText("") 337 v.answerSMPWindow.dialog.ShowAll() 338} 339 340type peerRequestsSMPNotification struct { 341 b *builder 342 infobar gtki.Box 343 closeInfobar gtki.Box 344 notification gtki.Box 345 label gtki.Label 346 image gtki.Image 347 button gtki.Button 348} 349 350func (p *peerRequestsSMPNotification) show() { 351 p.infobar.Show() 352 p.closeInfobar.Show() 353 p.label.Show() 354} 355 356func (v *verifier) buildPeerRequestsSMPNotification() { 357 v.peerRequestsSMP = &peerRequestsSMPNotification{ 358 b: newBuilder("PeerRequestsSMP"), 359 } 360 361 v.peerRequestsSMP.b.getItems( 362 "smp-requested-infobar", &v.peerRequestsSMP.infobar, 363 "smp-requested-close-infobar", &v.peerRequestsSMP.closeInfobar, 364 "smp-requested-notification", &v.peerRequestsSMP.notification, 365 "smp-requested-message", &v.peerRequestsSMP.label, 366 "smp-requested-image", &v.peerRequestsSMP.image, 367 "smp-requested-button", &v.peerRequestsSMP.button, 368 ) 369 370 prov := providerWithCSS("box { background-color: #fff3f3; color: #000000; border: 2px; }") 371 updateWithStyle(v.peerRequestsSMP.infobar, prov) 372 373 prov = providerWithCSS("box { background-color: #e5d7d6; }") 374 updateWithStyle(v.peerRequestsSMP.closeInfobar, prov) 375 376 v.peerRequestsSMP.b.ConnectSignals(map[string]interface{}{ 377 "on_press_close_image": v.cancelSMP, 378 }) 379 380 setImageFromFile(v.peerRequestsSMP.image, "waiting.svg") 381 382 v.notifier.notify(v.peerRequestsSMP.infobar) 383} 384 385func (v *verifier) displayRequestForSecret(question string) { 386 v.hideUnverifiedWarning() 387 388 v.peerRequestsSMP.label.SetLabel(i18n.Localf("Finish verifying the \nsecurity of this channel...")) 389 v.peerRequestsSMP.button.Connect("clicked", func() { 390 v.showAnswerSMPDialog(question) 391 }) 392 393 v.peerRequestsSMP.show() 394} 395 396type verificationSuccessNotification struct { 397 b *builder 398 dialog gtki.Dialog 399 label gtki.Label 400 image gtki.Image 401 button gtki.Button 402} 403 404func (v *verifier) displayVerificationSuccess() { 405 v.verificationSuccess = &verificationSuccessNotification{ 406 b: newBuilder("VerificationSucceeded"), 407 } 408 409 v.verificationSuccess.b.getItems( 410 "verif-success-dialog", &v.verificationSuccess.dialog, 411 "verif-success-label", &v.verificationSuccess.label, 412 "verif-success-image", &v.verificationSuccess.image, 413 "verif-success-button", &v.verificationSuccess.button, 414 ) 415 416 v.verificationSuccess.button.Connect("clicked", v.verificationSuccess.dialog.Destroy) 417 418 v.verificationSuccess.label.SetMarkup(i18n.Localf("Hooray! No one is listening to your conversations with <b>%s</b>", v.peerName())) 419 setImageFromFile(v.verificationSuccess.image, "smpsuccess.svg") 420 421 v.hideUnverifiedWarning() 422 v.verificationSuccess.dialog.SetTransientFor(v.parentWindow) 423 v.verificationSuccess.dialog.ShowAll() 424} 425 426type smpFailedNotification struct { 427 dialog gtki.Dialog 428 label gtki.Label 429 button gtki.Button 430} 431 432// TODO: make this consistent 433func (v *verifier) buildSMPFailedDialog() { 434 builder := newBuilder("VerificationFailed") 435 v.smpFailed = &smpFailedNotification{ 436 dialog: builder.getObj("verif-failure-dialog").(gtki.Dialog), 437 label: builder.getObj("verif-failure-label").(gtki.Label), 438 button: builder.getObj("verif-failure-button").(gtki.Button), 439 } 440 441 v.smpFailed.dialog.SetTransientFor(v.parentWindow) 442 v.smpFailed.dialog.HideOnDelete() 443 444 v.smpFailed.dialog.Connect("response", func() { 445 v.updateUnverifiedWarning() 446 v.smpFailed.dialog.Hide() 447 }) 448 v.smpFailed.button.Connect("clicked", func() { 449 v.updateUnverifiedWarning() 450 v.smpFailed.dialog.Hide() 451 }) 452} 453 454func (v *verifier) displayVerificationFailure() { 455 v.smpFailed.label.SetMarkup(i18n.Localf("We could not verify this channel with <b>%s</b>.", v.peerName())) 456 v.smpFailed.dialog.ShowAll() 457} 458 459func (v *verifier) updateInProgressDialogs(encrypted bool) { 460 if !encrypted { 461 v.removeInProgressDialogs() 462 } 463} 464 465func (v *verifier) removeInProgressDialogs() { 466 v.peerRequestsSMP.infobar.Hide() 467 v.waitingForPeer.infobar.Hide() 468 v.pinWindow.dialog.Hide() 469 v.answerSMPWindow.dialog.Hide() 470} 471 472func (v *verifier) cancelSMP() { 473 v.removeInProgressDialogs() 474 v.session.AbortSMP(v.peerToSendTo()) 475 v.updateUnverifiedWarning() 476} 477