Skip to content

Commit

Permalink
#54 - Adding functionality to show the SHA-1 fingerprint in the key d…
Browse files Browse the repository at this point in the history
…etails to public keys and key pairs.
  • Loading branch information
piratax007 committed Jul 26, 2022
1 parent f19a56a commit e2c65a0
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 81 deletions.
5 changes: 5 additions & 0 deletions api/key_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ type KeyEntry interface {
PrivateKeyLocations() []string
KeyType() KeyType
}

type PublicKeyEntry interface {
KeyEntry
WithDigestContent(func([]byte) []byte) []byte
}
47 changes: 47 additions & 0 deletions gui/definitions/interface/KeyDetails.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,53 @@
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="keyFingerprintRow">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="fingerprintLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Fingerprint:</property>
<style>
<class name="fingerprintLabel"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="fingerprint">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="ellipsize">end</property>
<property name="width-chars">20</property>
<property name="selectable">True</property>
<style>
<class name="fingerprint"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="fingerprint"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<style>
<class name="keyDetail"/>
</style>
Expand Down
4 changes: 4 additions & 0 deletions gui/definitions/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,8 @@ window {

.keyEntry.pair {
background-color: @keylist-entry-pair-bg;
}

.fingerprint .fingerprintLabel {
padding-right: 10px;
}
68 changes: 57 additions & 11 deletions gui/key_details.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package gui

import (
"crypto/sha1"
"fmt"
"github.com/coyim/gotk3adapter/gtki"
"github.com/digitalautonomy/keymirror/api"
"strings"
)

type clearable[T any] interface {
Expand All @@ -16,17 +19,6 @@ func clearAllChildrenOf[T any](b clearable[T]) {
}
}

func (kd *keyDetails) displayLocations(keyLocations []string, pathLabelName, rowName string) {
if keyLocations != nil {
label := kd.builder.get(pathLabelName).(gtki.Label)
label.SetLabel(keyLocations[0])
label.SetTooltipText(keyLocations[0])
} else {
row := kd.builder.get(rowName).(gtki.Box)
row.Hide()
}
}

type keyDetails struct {
builder *builder
key api.KeyEntry
Expand Down Expand Up @@ -67,9 +59,63 @@ func (kd *keyDetails) setClassForKeyDetails() {
addClass(kd.box, className)
}

func (kd *keyDetails) displayLocations(keyLocations []string, pathLabelName, rowName string) {
if keyLocations != nil {
label := kd.builder.get(pathLabelName).(gtki.Label)
label.SetLabel(keyLocations[0])
label.SetTooltipText(keyLocations[0])
} else {
row := kd.builder.get(rowName).(gtki.Box)
row.Hide()
}
}

func returningSlice20(f func([]byte) [20]byte) func([]byte) []byte {
return func(v []byte) []byte {
res := f(v)
return res[:]
}
}

func returningSlice32(f func([]byte) [32]byte) func([]byte) []byte {
return func(v []byte) []byte {
res := f(v)
return res[:]
}
}

func formatByteForFingerprint(b byte) string {
return fmt.Sprintf("%02X", b)
}

func formatFingerprint(f []byte) string {
result := []string{}

for _, v := range f {
result = append(result, formatByteForFingerprint(v))
}

return strings.Join(result, ":")
}

const fingerprintRow string = "keyFingerprintRow"

func (kd *keyDetails) displayFingerprint(rowName string) {
if pk, ok := kd.key.(api.PublicKeyEntry); ok {
f := formatFingerprint(pk.WithDigestContent(returningSlice20(sha1.Sum)))
label := kd.builder.get("fingerprint").(gtki.Label)
label.SetLabel(f)
label.SetTooltipText(f)
} else {
row := kd.builder.get(rowName).(gtki.Box)
row.Hide()
}
}

func (kd *keyDetails) display() {
kd.displayLocations(kd.key.PublicKeyLocations(), publicKeyPathLabel, publicKeyRowName)
kd.displayLocations(kd.key.PrivateKeyLocations(), privateKeyPathLabel, privateKeyRowName)
kd.displayFingerprint(fingerprintRow)
kd.setClassForKeyDetails()
}

Expand Down
88 changes: 88 additions & 0 deletions gui/key_details_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,17 @@ func (s *guiSuite) Test_populateKeyDetails_createsTheKeyDetailsBoxAndDisplaysThe
privateKeyRow := &gtk.MockBox{}
builder.On("GetObject", "keyDetailsPrivateKeyRow").Return(privateKeyRow, nil).Once()

fingerprintRowMock := &gtk.MockBox{}
builder.On("GetObject", "keyFingerprintRow").Return(fingerprintRowMock, nil).Once()

keMock := &keyEntryMock{}
keMock.On("PublicKeyLocations").Return([]string{"/a/path/to/a/public/key"}).Once()
keMock.On("PrivateKeyLocations").Return(nil).Once()
keMock.On("KeyType").Return(api.PublicKeyType).Once()
publicKeyPathLabel.On("SetLabel", "/a/path/to/a/public/key").Return().Once()
publicKeyPathLabel.On("SetTooltipText", "/a/path/to/a/public/key").Return().Once()
privateKeyRow.On("Hide").Return().Once()
fingerprintRowMock.On("Hide").Return().Once()

scMock := expectClassToBeAdded(keyDetailsBoxMock, "publicKey")

Expand All @@ -49,6 +53,7 @@ func (s *guiSuite) Test_populateKeyDetails_createsTheKeyDetailsBoxAndDisplaysThe
keMock.AssertExpectations(s.T())
publicKeyPathLabel.AssertExpectations(s.T())
privateKeyRow.AssertExpectations(s.T())
fingerprintRowMock.AssertExpectations(s.T())
scMock.AssertExpectations(s.T())
}

Expand All @@ -66,13 +71,17 @@ func (s *guiSuite) Test_populateKeyDetails_createsTheKeyDetailsBoxAndDisplaysThe
publicKeyRow := &gtk.MockBox{}
builder.On("GetObject", "keyDetailsPublicKeyRow").Return(publicKeyRow, nil).Once()

fingerprintRowMock := &gtk.MockBox{}
builder.On("GetObject", "keyFingerprintRow").Return(fingerprintRowMock, nil).Once()

keMock := &keyEntryMock{}
keMock.On("PublicKeyLocations").Return(nil).Once()
keMock.On("PrivateKeyLocations").Return([]string{"/a/path/to/a/private/key"}).Once()
keMock.On("KeyType").Return(api.PrivateKeyType).Once()
privateKeyPathLabel.On("SetLabel", "/a/path/to/a/private/key").Return().Once()
privateKeyPathLabel.On("SetTooltipText", "/a/path/to/a/private/key").Return().Once()
publicKeyRow.On("Hide").Return().Once()
fingerprintRowMock.On("Hide").Return().Once()

scMock := expectClassToBeAdded(keyDetailsBoxMock, "privateKey")

Expand All @@ -83,6 +92,7 @@ func (s *guiSuite) Test_populateKeyDetails_createsTheKeyDetailsBoxAndDisplaysThe
keMock.AssertExpectations(s.T())
privateKeyPathLabel.AssertExpectations(s.T())
publicKeyRow.AssertExpectations(s.T())
fingerprintRowMock.AssertExpectations(s.T())
scMock.AssertExpectations(s.T())
}

Expand All @@ -100,6 +110,9 @@ func (s *guiSuite) Test_populateKeyDetails_createsTheKeyDetailsBoxAndDisplaysBot
privateKeyPathLabel := &gtk.MockLabel{}
builder.On("GetObject", "privateKeyPath").Return(privateKeyPathLabel, nil).Once()

fingerprintRowMock := &gtk.MockBox{}
builder.On("GetObject", "keyFingerprintRow").Return(fingerprintRowMock, nil).Once()

keMock := &keyEntryMock{}
keMock.On("PublicKeyLocations").Return([]string{"/a/path/to/a/public/key"}).Once()
keMock.On("PrivateKeyLocations").Return([]string{"/a/path/to/a/private/key"}).Once()
Expand All @@ -108,6 +121,7 @@ func (s *guiSuite) Test_populateKeyDetails_createsTheKeyDetailsBoxAndDisplaysBot
publicKeyPathLabel.On("SetTooltipText", "/a/path/to/a/public/key").Return().Once()
privateKeyPathLabel.On("SetLabel", "/a/path/to/a/private/key").Return().Once()
privateKeyPathLabel.On("SetTooltipText", "/a/path/to/a/private/key").Return().Once()
fingerprintRowMock.On("Hide").Return().Once()

scMock := expectClassToBeAdded(keyDetailsBoxMock, "keyPair")

Expand All @@ -118,6 +132,7 @@ func (s *guiSuite) Test_populateKeyDetails_createsTheKeyDetailsBoxAndDisplaysBot
keMock.AssertExpectations(s.T())
publicKeyPathLabel.AssertExpectations(s.T())
privateKeyPathLabel.AssertExpectations(s.T())
fingerprintRowMock.AssertExpectations(s.T())
scMock.AssertExpectations(s.T())
}

Expand All @@ -134,3 +149,76 @@ func (s *guiSuite) Test_clearAllChildrenOf_removeEachOneOfTheChildrenOfTheBox()

boxMock.AssertExpectations(s.T())
}

func (s *guiSuite) Test_formatFingerprint_returnsAnUpperCaseHexadecimalStringWithColons() {
f := []byte{}
expected := ""
s.Equal(expected, formatFingerprint(f))

f = []byte{0}
expected = "00"
s.Equal(expected, formatFingerprint(f))

f = []byte{8}
expected = "08"
s.Equal(expected, formatFingerprint(f))

f = []byte{0xfe}
expected = "FE"
s.Equal(expected, formatFingerprint(f))

f = []byte{0, 1, 32, 0x67, 0, 7, 0xfc, 0}
expected = "00:01:20:67:00:07:FC:00"
s.Equal(expected, formatFingerprint(f))
}

func (s *guiSuite) Test_keyDetails_displayFingerprint_calculateTheFingerprintAndDisplaysIt() {
keyMock := &publicKeyEntryMock{}
var calledWithFunc *func([]byte) []byte
keyMock.On("WithDigestContent", mock.AnythingOfType("func([]uint8) []uint8")).Return(
[]byte("something")).Run(func(a mock.Arguments) {
ff := a.Get(0).(func([]byte) []byte)
calledWithFunc = &ff
})

builderMock := &gtk.MockBuilder{}

kd := &keyDetails{
builder: &builder{builderMock},
key: keyMock,
}

labelMock := &gtk.MockLabel{}
builderMock.On("GetObject", "fingerprint").Return(labelMock, nil).Once()
labelMock.On("SetLabel", "73:6F:6D:65:74:68:69:6E:67").Return().Once()
labelMock.On("SetTooltipText", "73:6F:6D:65:74:68:69:6E:67").Return().Once()

kd.displayFingerprint("a row")

labelMock.AssertExpectations(s.T())
builderMock.AssertExpectations(s.T())
keyMock.AssertExpectations(s.T())

s.NotNil(calledWithFunc)
s.Equal([]byte{0x2a, 0xae, 0x6c, 0x35, 0xc9, 0x4f, 0xcf, 0xb4, 0x15, 0xdb, 0xe9, 0x5f, 0x40, 0x8b, 0x9c, 0xe9, 0x1e, 0xe8, 0x46, 0xed}, (*calledWithFunc)([]byte("hello world")))
s.Equal([]byte{0x0, 0x78, 0xbb, 0x8e, 0x5c, 0x9d, 0x8a, 0xbf, 0x7f, 0x1e, 0x4e, 0x14, 0xc8, 0x7d, 0x90, 0x23, 0x23, 0x5b, 0x62, 0x30}, (*calledWithFunc)([]byte("goodbye world")))
}

func (s *guiSuite) Test_keyDetails_displayFingerprint_hideTheFingerprintRow_ifDisplayAPrivateKey() {
keyMock := &keyEntryMock{}

builderMock := &gtk.MockBuilder{}
rowMock := &gtk.MockBox{}

kd := &keyDetails{
builder: &builder{builderMock},
key: keyMock,
}

rowMock.On("Hide").Return().Once()

builderMock.On("GetObject", "label").Return(rowMock, nil).Once()
kd.displayFingerprint("label")

rowMock.AssertExpectations(s.T())
}
13 changes: 13 additions & 0 deletions gui/key_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ func (ke *keyEntryMock) KeyType() api.KeyType {
return ret[api.KeyType](returns, 0)
}

type publicKeyEntryMock struct {
keyEntryMock
}

func (pk *publicKeyEntryMock) WithDigestContent(f func([]byte) []byte) []byte {
returns := pk.Called(f)
return ret[[]byte](returns, 0)
}

func (s *guiSuite) Test_createKeyEntryBoxFrom_CreatesAGTKIBoxWithTheGivenASSHKeyEntry() {
box := s.setupBuildingOfKeyEntry("/home/amnesia/id_rsa.pub")

Expand Down Expand Up @@ -86,6 +95,9 @@ func (s *guiSuite) Test_createKeyEntryBoxFrom_CreatesAGTKIBoxWithTheGivenASSHKey
detailsRevMock.On("Show").Return().Once()
detailsRevMock.On("SetRevealChild", true).Return().Once()
privateKeyRow.On("Hide").Return().Once()
fingerprintRowMock := &gtk.MockBox{}
builder.On("GetObject", "keyFingerprintRow").Return(fingerprintRowMock, nil).Once()
fingerprintRowMock.On("Hide").Return().Once()

scMock1 := expectClassToBeAdded(box, "current")
scMock2 := expectClassToBeAdded(keyDetailsBoxMock, "publicKey")
Expand All @@ -100,6 +112,7 @@ func (s *guiSuite) Test_createKeyEntryBoxFrom_CreatesAGTKIBoxWithTheGivenASSHKey
scMock1.AssertExpectations(s.T())
scMock2.AssertExpectations(s.T())
privateKeyRow.AssertExpectations(s.T())
fingerprintRowMock.AssertExpectations(s.T())
}

type keyAccessMock struct {
Expand Down
25 changes: 0 additions & 25 deletions gui/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,6 @@ import (
"io/fs"
)

//
//// #cgo pkg-config: gdk-3.0 gio-2.0 glib-2.0 gobject-2.0 gtk+-3.0
//// #include <gio/gio.h>
//// #include <gtk/gtk.h>
//import "C"
//
//func withDefinitionInTempFile(def string, f func(string)) {
// content, _ := fs.ReadFile(getDefinitions(), def)
// td, _ := ioutil.TempDir("", "")
// defer func() {
// _ = os.RemoveAll(td)
// }()
// file := path.Join(td, path.Base(def))
// _ = ioutil.WriteFile(file, content, 0755)
// f(file)
//}
//
//func testBla() {
// withDefinitionInTempFile("definitions/icons.gresource", func(path string) {
// gr, _ := gio.LoadGResource(path)
// gio.RegisterGResource(gr)
// C.gtk_icon_theme_add_resource_path(C.gtk_icon_theme_get_default(), C.CString("/digital/autonomia/KeyMirror"))
// })
//}

func (u *ui) createStyleProviderFrom(filename string) gtki.CssProvider {
cssProvider, _ := u.gtk.CssProviderNew()
pathOfFile := styleDefinitionPath(filename)
Expand Down

0 comments on commit e2c65a0

Please sign in to comment.