From d75b571b9f5f4fc2d49fa4cd7f67c4abf2bac6e5 Mon Sep 17 00:00:00 2001 From: Felicitas Pojtinger Date: Fri, 28 Oct 2022 23:52:10 +0200 Subject: [PATCH] feat: Add option to base64-encode keys --- pkg/components/export_key_modal.go | 106 +++++++++++++++++++++++++---- pkg/components/home.go | 93 +++++++++++++++++++++---- pkg/stories/export_key_modal.go | 16 ++--- 3 files changed, 181 insertions(+), 34 deletions(-) diff --git a/pkg/components/export_key_modal.go b/pkg/components/export_key_modal.go index 1e61394..e3c4e0c 100644 --- a/pkg/components/export_key_modal.go +++ b/pkg/components/export_key_modal.go @@ -13,18 +13,21 @@ const ( type ExportKeyModal struct { app.Compo - PublicKey bool // Whether to display the options for a public key - OnDownloadPublicKey func(armor bool) // Handler to call to download the public key - OnViewPublicKey func() // Handler to call to view the public key + PublicKey bool // Whether to display the options for a public key + OnDownloadPublicKey func(armor, base64encode bool) // Handler to call to download the public key + OnViewPublicKey func(armor, base64encode bool) // Handler to call to view the public key - PrivateKey bool // Whether to display the options for a private key - OnDownloadPrivateKey func(armor bool) // Handler to call to download the private key - OnViewPrivateKey func() // Handler to call to view the private key + PrivateKey bool // Whether to display the options for a private key + OnDownloadPrivateKey func(armor, base64encode bool) // Handler to call to download the private key + OnViewPrivateKey func(armor, base64encode bool) // Handler to call to view the private key OnOK func() // Handler to call when dismissing the modal - skipPublicKeyArmor bool - skipPrivateKeyArmor bool + skipPublicKeyArmor bool + publicKeyBase64Encode bool + + skipPrivateKeyArmor bool + privateKeyBase64Encode bool } func (c *ExportKeyModal) Render() app.UI { @@ -91,6 +94,42 @@ func (c *ExportKeyModal) Render() app.UI { ), ), ), + app.Div(). + Aria("role", "group"). + Class("pf-c-form__group"). + Body( + app.Div(). + Class("pf-c-form__group-control"). + Body( + app.Div(). + Class("pf-c-check"). + Body( + &Controlled{ + Component: app.Input(). + Class("pf-c-check__input"). + Type("checkbox"). + ID("public-base64-checkbox"). + OnInput(func(ctx app.Context, e app.Event) { + c.publicKeyBase64Encode = !c.publicKeyBase64Encode + }), + Properties: map[string]interface{}{ + "checked": c.publicKeyBase64Encode, + }, + }, + app.Label(). + Class("pf-c-check__label"). + For("public-base64-checkbox"). + Body( + app.I(). + Class("fas fa-shield-alt pf-u-mr-sm"), + app.Text("Base64 encode"), + ), + app.Span(). + Class("pf-c-check__description"). + Text("Use a reduced alphabet for better portability."), + ), + ), + ), ), ), app.Div(). @@ -108,7 +147,7 @@ func (c *ExportKeyModal) Render() app.UI { Type("submit"). Form(exportPublicKeyForm). OnClick(func(ctx app.Context, e app.Event) { - c.OnDownloadPublicKey(!c.skipPublicKeyArmor) + c.OnDownloadPublicKey(!c.skipPublicKeyArmor, c.publicKeyBase64Encode) }). Body( app.Span(). @@ -121,13 +160,13 @@ func (c *ExportKeyModal) Render() app.UI { app.Text("Download public key"), ), app.If( - !c.skipPublicKeyArmor, + !c.skipPublicKeyArmor || c.publicKeyBase64Encode, app.Button(). Class("pf-c-button pf-m-control pf-u-mr-sm pf-u-display-block pf-u-display-inline-block-on-md pf-u-w-100 pf-u-w-initial-on-md"). Type("submit"). Form(exportPublicKeyForm). OnClick(func(ctx app.Context, e app.Event) { - c.OnViewPublicKey() + c.OnViewPublicKey(!c.skipPublicKeyArmor, c.publicKeyBase64Encode) }). Body( app.Span(). @@ -202,6 +241,42 @@ func (c *ExportKeyModal) Render() app.UI { ), ), ), + app.Div(). + Aria("role", "group"). + Class("pf-c-form__group"). + Body( + app.Div(). + Class("pf-c-form__group-control"). + Body( + app.Div(). + Class("pf-c-check"). + Body( + &Controlled{ + Component: app.Input(). + Class("pf-c-check__input"). + Type("checkbox"). + ID("private-base64-checkbox"). + OnInput(func(ctx app.Context, e app.Event) { + c.privateKeyBase64Encode = !c.privateKeyBase64Encode + }), + Properties: map[string]interface{}{ + "checked": c.privateKeyBase64Encode, + }, + }, + app.Label(). + Class("pf-c-check__label"). + For("private-base64-checkbox"). + Body( + app.I(). + Class("fas fa-shield-alt pf-u-mr-sm"), + app.Text("Base64 encode"), + ), + app.Span(). + Class("pf-c-check__description"). + Text("Use a reduced alphabet for better portability."), + ), + ), + ), ), ), app.Div(). @@ -219,7 +294,7 @@ func (c *ExportKeyModal) Render() app.UI { Type("submit"). Form(exportPrivateKeyForm). OnClick(func(ctx app.Context, e app.Event) { - c.OnDownloadPrivateKey(!c.skipPrivateKeyArmor) + c.OnDownloadPrivateKey(!c.skipPrivateKeyArmor, c.privateKeyBase64Encode) }). Body( app.Span(). @@ -232,13 +307,13 @@ func (c *ExportKeyModal) Render() app.UI { app.Text("Download private key"), ), app.If( - !c.skipPrivateKeyArmor, + !c.skipPrivateKeyArmor || c.privateKeyBase64Encode, app.Button(). Class("pf-c-button pf-m-control pf-u-mr-sm pf-u-display-block pf-u-display-inline-block-on-md pf-u-w-100 pf-u-w-initial-on-md"). Type("submit"). Form(exportPrivateKeyForm). OnClick(func(ctx app.Context, e app.Event) { - c.OnViewPrivateKey() + c.OnViewPrivateKey(!c.skipPrivateKeyArmor, c.privateKeyBase64Encode) }). Body( app.Span(). @@ -274,5 +349,8 @@ func (c *ExportKeyModal) Render() app.UI { func (c *ExportKeyModal) clear() { c.skipPublicKeyArmor = false + c.publicKeyBase64Encode = false + c.skipPrivateKeyArmor = false + c.privateKeyBase64Encode = false } diff --git a/pkg/components/home.go b/pkg/components/home.go index c511035..9475aa2 100644 --- a/pkg/components/home.go +++ b/pkg/components/home.go @@ -1,6 +1,7 @@ package components import ( + "encoding/base64" "encoding/json" "errors" "log" @@ -55,6 +56,9 @@ type Home struct { viewPrivateKey bool + viewArmor bool + viewBase64 bool + err error onRecover func() @@ -88,7 +92,9 @@ func (c *Home) Render() app.UI { privateKey := PGPKey{} privateKeyExport := []byte{} + privateKeyExportBase64 := "" privateKeyExportArmored := "" + privateKeyExportArmoredBase64 := "" for _, candidate := range c.keys { if candidate.ID == c.privateKeyID { privateKey = candidate @@ -100,6 +106,8 @@ func (c *Home) Render() app.UI { break } + privateKeyExportBase64 = base64.StdEncoding.EncodeToString(rawKey) + parsedKey, err := crypto.NewKey(rawKey) if err != nil { c.panic(err, func() {}) @@ -121,13 +129,17 @@ func (c *Home) Render() app.UI { break } + privateKeyExportArmoredBase64 = base64.StdEncoding.EncodeToString(privateKeyExport) + break } } publicKey := PGPKey{} publicKeyExport := []byte{} + publicKeyExportBase64 := "" publicKeyExportArmored := "" + publicKeyExportArmoredBase64 := "" for _, candidate := range c.keys { if candidate.ID == c.publicKeyID { publicKey = candidate @@ -139,6 +151,8 @@ func (c *Home) Render() app.UI { break } + publicKeyExportBase64 = base64.StdEncoding.EncodeToString(rawKey) + parsedKey, err := crypto.NewKey(rawKey) if err != nil { c.panic(err, func() {}) @@ -160,6 +174,8 @@ func (c *Home) Render() app.UI { break } + publicKeyExportArmoredBase64 = base64.StdEncoding.EncodeToString(publicKeyExport) + break } } @@ -882,31 +898,51 @@ func (c *Home) Render() app.UI { c.exportKeyModalOpen, &ExportKeyModal{ PublicKey: c.publicKeyID != "", - OnDownloadPublicKey: func(armor bool) { + OnDownloadPublicKey: func(armor, base64encode bool) { if armor { - c.download([]byte(publicKeyExportArmored), publicKey.Label+".asc", "text/plain") + if base64encode { + c.download([]byte(publicKeyExportArmoredBase64), publicKey.Label+".asc.txt", "text/plain") + } else { + c.download([]byte(publicKeyExportArmored), publicKey.Label+".asc", "text/plain") + } } else { - c.download(publicKeyExport, publicKey.Label+".pgp", "application/octet-stream") + if base64encode { + c.download([]byte(publicKeyExportBase64), publicKey.Label+".pgp.txt", "application/octet-stream") + } else { + c.download(publicKeyExport, publicKey.Label+".pgp", "application/octet-stream") + } } }, - OnViewPublicKey: func() { + OnViewPublicKey: func(armor, base64encode bool) { c.exportKeyModalOpen = false c.viewPrivateKey = false c.viewKeyModalOpen = true + c.viewArmor = armor + c.viewBase64 = base64encode }, PrivateKey: c.privateKeyID != "", - OnDownloadPrivateKey: func(armor bool) { + OnDownloadPrivateKey: func(armor, base64encode bool) { if armor { - c.download([]byte(privateKeyExportArmored), privateKey.Label+".asc", "text/plain") + if base64encode { + c.download([]byte(privateKeyExportArmoredBase64), privateKey.Label+".asc.txt", "text/plain") + } else { + c.download([]byte(privateKeyExportArmored), privateKey.Label+".asc", "text/plain") + } } else { - c.download(privateKeyExport, privateKey.Label+".pgp", "application/octet-stream") + if base64encode { + c.download([]byte(privateKeyExportBase64), privateKey.Label+".pgp.txt", "application/octet-stream") + } else { + c.download(privateKeyExport, privateKey.Label+".pgp", "application/octet-stream") + } } }, - OnViewPrivateKey: func() { + OnViewPrivateKey: func(armor, base64encode bool) { c.exportKeyModalOpen = false c.viewPrivateKey = true c.viewKeyModalOpen = true + c.viewArmor = armor + c.viewBase64 = base64encode }, OnOK: func() { @@ -922,8 +958,24 @@ func (c *Home) Render() app.UI { tabs := []TextOutputModalTab{ { Language: "text/plain", - Title: publicKey.Label + ".pub", - Body: publicKeyExportArmored, + Title: publicKey.Label + func() string { + if c.viewArmor { + if c.viewBase64 { + return ".asc.txt" + } + + return ".asc" + } + + return ".txt" + }(), + Body: func() string { + if c.viewBase64 { + return publicKeyExportArmoredBase64 + } + + return publicKeyExportArmored + }(), }, } title := `View Public Key "` + publicKey.Label + `"` @@ -932,8 +984,24 @@ func (c *Home) Render() app.UI { tabs = []TextOutputModalTab{ { Language: "text/plain", - Title: privateKey.Label, - Body: privateKeyExportArmored, + Title: privateKey.Label + ".asc" + func() string { + if c.viewArmor { + if c.viewBase64 { + return ".asc.txt" + } + + return ".asc" + } + + return ".txt" + }(), + Body: func() string { + if c.viewBase64 { + return privateKeyExportArmoredBase64 + } + + return privateKeyExportArmored + }(), }, } title = `View Private Key "` + privateKey.Label + `"` @@ -945,6 +1013,7 @@ func (c *Home) Render() app.UI { OnClose: func() { c.viewKeyModalOpen = false c.exportKeyModalOpen = true + c.viewBase64 = false c.Update() }, diff --git a/pkg/stories/export_key_modal.go b/pkg/stories/export_key_modal.go index e6fb639..387a5ab 100644 --- a/pkg/stories/export_key_modal.go +++ b/pkg/stories/export_key_modal.go @@ -27,19 +27,19 @@ func (c *ExportKeyModalStory) Render() app.UI { c.WithRoot( &components.ExportKeyModal{ PublicKey: true, - OnDownloadPublicKey: func(armor bool) { - app.Window().Call("alert", fmt.Sprintf("Downloaded public key with armor set to %v", armor)) + OnDownloadPublicKey: func(armor, base64encode bool) { + app.Window().Call("alert", fmt.Sprintf("Downloaded public key with armor set to %v with base64encode %v", armor, base64encode)) }, - OnViewPublicKey: func() { - app.Window().Call("alert", "Viewed public key") + OnViewPublicKey: func(armor, base64encode bool) { + app.Window().Call("alert", fmt.Sprintf("Viewed public key with armor set to %v with base64encode %v", armor, base64encode)) }, PrivateKey: true, - OnDownloadPrivateKey: func(armor bool) { - app.Window().Call("alert", fmt.Sprintf("Downloaded private key with armor set to %v", armor)) + OnDownloadPrivateKey: func(armor, base64encode bool) { + app.Window().Call("alert", fmt.Sprintf("Downloaded private key with armor set to %v with base64encode %v", armor, base64encode)) }, - OnViewPrivateKey: func() { - app.Window().Call("alert", "Viewed private key") + OnViewPrivateKey: func(armor, base64encode bool) { + app.Window().Call("alert", fmt.Sprintf("Viewed private key with armor set to %v with base64encode %v", armor, base64encode)) }, OnOK: func() {