Keycard initial integration (#63)

* add nim-keycard-go to makefile

* fix keycard-go build

* add keycard.nim

* remove test make task

* remove debug line from makefile

* fix import keycard_go

* add keycard-go to .PHONY

* add keycard start/stop/select methods

* use https url for submodule

* add KeycardApplication info and return it from select

* update nim-keycard-go version

* fix select return type

* actually return the result

* update nim-keycard-go

* add keycard methods to backend

* add base/mock keycard backends

* imports

* export keycard methods in backend

* update start/stop keycard implementation

* add KeycardStarted signal

* fix keycard started signal

* rename to KeycardConnected signal

* fix keycard signal renamed

* add keycardgo in makefile tasks

* add back build/.gitignore

* fix .gitignore

* fix .gitignore

* Makefile: export keycard vars

* add keycard lib to nimble file

* use spaces instead of tabs in non-recipe sections of Makefile

* use install_name_tool on libkeycard on macOS

* in GHA ubuntu environment install libpcsclite-dev with apt at start of workflow

* add Keycard exceptions

* remove useless test

* remove useless return

* move keycard types to /types

* reraise exception from status/keycard.nim

* remove unused import

* add keycard commands: opensecure channel, pair, verify pin, export key

* fix last keycard commands

* add exportKey params

* update nim-keycard-go

Co-authored-by: Michele Balistreri <michele@bitgamma.com>
Co-authored-by: Michael Bradley, Jr <michaelsbradleyjr@gmail.com>
This commit is contained in:
Andrea Franz 2021-10-04 23:21:07 +02:00 committed by GitHub
parent c7722cda00
commit 45887b1724
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 333 additions and 19 deletions

View File

@ -37,6 +37,11 @@ jobs:
steps:
- name: Install tools and libraries via APT
if: matrix.platform.os == 'ubuntu'
run: |
sudo apt install -y libpcsclite-dev
- name: Install tools and libraries via Homebrew
if: matrix.platform.os == 'macos'
run: |

3
.gitmodules vendored
View File

@ -79,3 +79,6 @@
[submodule "vendor/edn.nim"]
path = vendor/edn.nim
url = https://github.com/status-im/edn.nim
[submodule "vendor/nim-keycard-go"]
path = vendor/nim-keycard-go
url = https://github.com/status-im/nim-keycard-go

View File

@ -20,6 +20,7 @@ BUILD_SYSTEM_DIR := vendor/nimbus-build-system
deps \
libstatuslib \
status-go \
keycard-go \
update \
build_ctest \
ctest \
@ -57,12 +58,15 @@ ifeq ($(detected_OS),Darwin)
CGO_CFLAGS := -mmacosx-version-min=10.14
export CGO_CFLAGS
LIBSTATUS_EXT := dylib
LIBKEYCARD_EXT := dylib
MACOSX_DEPLOYMENT_TARGET := 10.14
export MACOSX_DEPLOYMENT_TARGET
else ifeq ($(detected_OS),Windows)
LIBSTATUS_EXT := dll
LIBKEYCARD_EXT := dll
else
LIBSTATUS_EXT := so
LIBKEYCARD_EXT := so
endif
# ifeq ($(detected_OS),Darwin)
@ -89,7 +93,6 @@ deps: | deps-common bottles
update: | update-common
RELEASE ?= false
ifeq ($(RELEASE),false)
# We need `-d:debug` to get Nim's default stack traces
@ -111,23 +114,35 @@ export STATUSGO
export STATUSGO_LIBDIR
export LIBSTATUS_EXT
KEYCARDGO := vendor/nim-keycard-go/go/keycard/build/libkeycard/libkeycard.$(LIBKEYCARD_EXT)
KEYCARDGO_LIBDIR := $(shell pwd)/$(shell dirname "$(KEYCARDGO)")
export KEYCARDGO
export KEYCARDGO_LIBDIR
export LIBKEYCARD_EXT
status-go: $(STATUSGO)
$(STATUSGO): | deps
echo -e $(BUILD_MSG) "status-go"
+ cd vendor/status-go && \
$(MAKE) statusgo-shared-library $(HANDLE_OUTPUT)
keycard-go: $(KEYCARDGO)
$(KEYCARDGO): | deps
echo -e $(BUILD_MSG) "keycard-go"
+ cd vendor/nim-keycard-go && \
$(MAKE) build-keycard-go $(HANDLE_OUTPUT)
LIBSTATUSLIB := build/$@.$(LIBSTATUS_EXT).0
libstatuslib: | $(STATUSGO)
libstatuslib: | $(STATUSGO) $(KEYCARDGO)
echo -e $(BUILD_MSG) "$@" && \
$(ENV_SCRIPT) nim c $(NIM_PARAMS) $(NIM_EXTRA_PARAMS) --passL:"-L$(STATUSGO_LIBDIR)" --passL:"-lstatus" -o:build/$@.$(LIBSTATUS_EXT).0 -d:ssl --app:lib --noMain --header --nimcache:nimcache/libstatuslib statuslib.nim && \
$(ENV_SCRIPT) nim c $(NIM_PARAMS) $(NIM_EXTRA_PARAMS) --passL:"-L$(STATUSGO_LIBDIR)" --passL:"-lstatus" --passL:"-L$(KEYCARDGO_LIBDIR)" --passL="-lkeycard" -o:build/$@.$(LIBSTATUS_EXT).0 -d:ssl --app:lib --noMain --header --nimcache:nimcache/libstatuslib statuslib.nim && \
rm -f build/$@.$(LIBSTATUS_EXT) && \
ln -s $@.$(LIBSTATUS_EXT).0 build/$@.so && \
cp nimcache/libstatuslib/*.h build/. && \
[[ $$? = 0 ]]
# libraries for dynamic linking of non-Nim objects
EXTRA_LIBS_DYNAMIC := -L"$(CURDIR)/build" -lstatuslib -lm -L"$(STATUSGO_LIBDIR)" -lstatus
EXTRA_LIBS_DYNAMIC := -L"$(CURDIR)/build" -lstatuslib -lm -L"$(STATUSGO_LIBDIR)" -lstatus -L"$(KEYCARDGO_LIBDIR)" -lkeycard
build_ctest: | $(LIBSTATUSLIB) build deps
echo -e $(BUILD_MSG) "build/ctest" && \
$(CC) test/main.c -Wl,-rpath,'$$ORIGIN' -I./vendor/nimbus-build-system/vendor/Nim/lib $(EXTRA_LIBS_DYNAMIC) -g -o build/ctest
@ -135,17 +150,20 @@ build_ctest: | $(LIBSTATUSLIB) build deps
LD_LIBRARY_PATH_NIMBLE := $${LD_LIBRARY_PATH}
ifneq ($(detected_OS),Windows)
ifneq ($(detected_OS),Darwin)
LD_LIBRARY_PATH_NIMBLE := $(STATUSGO_LIBDIR):$(LD_LIBRARY_PATH_NIMBLE)
LD_LIBRARY_PATH_NIMBLE := $(STATUSGO_LIBDIR):$(KEYCARDGO_LIBDIR):$(LD_LIBRARY_PATH_NIMBLE)
endif
endif
PATH_NIMBLE := $${PATH}
ifeq ($(detected_OS),Windows)
PATH_NIMBLE := $(STATUSGO_LIBDIR):$(PATH_NIMBLE)
PATH_NIMBLE := $(STATUSGO_LIBDIR):$(KEYCARDGO_LIBDIR):$(PATH_NIMBLE)
endif
RUN_AFTER_BUILD ?= true
NIMBLE_ENV = \
RELEASE=$(RELEASE)
RELEASE=$(RELEASE) \
RUN_AFTER_BUILD=$(RUN_AFTER_BUILD)
ifeq ($(detected_OS),Windows)
NIMBLE_ENV += PATH="$(PATH_NIMBLE)"
PCRE_LDFLAGS := -L$(shell cygpath -m /ucrt64/lib) -lpcre
@ -156,13 +174,13 @@ endif
ctest: | build_ctest
echo -e "Running ctest:" && \
LD_LIBRARY_PATH="$(STATUSGO_LIBDIR)" \
LD_LIBRARY_PATH="$(STATUSGO_LIBDIR):$(KEYCARDGO_LIBDIR)" \
./build/ctest
test: | $(STATUSGO)
test: | $(STATUSGO) $(KEYCARDGO)
$(NIMBLE_ENV) $(ENV_SCRIPT) nimble tests
clean: | clean-common
rm -rf bin/* node_modules bottles/* pkg/* tmp/* $(STATUSGO)
rm -rf bin/* node_modules bottles/* pkg/* tmp/* $(STATUSGO) $(KEYCARDGO)
endif # "variables.mk" was not included

View File

@ -4,13 +4,20 @@ export Backend, StatusGoBackend, MockBackend
from base/bookmarks as bookmarks_methods import storeBookmark, updateBookmark, getBookmarks, deleteBookmark
export storeBookmark, updateBookmark, getBookmarks, deleteBookmark
from base/keycard as keycard_methods import keycardStart, keycardStop, keycardSelect, keycardPair,
keycardOpenSecureChannel, keycardVerifyPin, keycardExportKey
export keycardStart, keycardStop, keycardSelect, keycardPair,
keycardOpenSecureChannel, keycardVerifyPin, keycardExportKey
import statusgo/bookmarks as statusgo_bookmarks
import mock/bookmarks as mock_bookmarks
import statusgo/keycard as statusgo_keycard
import mock/keycard as mock_keycard
proc newBackend*(name: string): Backend =
proc newBackend*(name: string): Backend =
if name == "statusgo":
result = StatusGoBackend()
elif name == "mock":
result = MockBackend()
else:
raise newException(ValueError, "unknown backend")
raise newException(ValueError, "unknown backend")

23
backends/base/keycard.nim Normal file
View File

@ -0,0 +1,23 @@
import ../../types/[keycard]
import ../types
method keycardStart*(self: Backend) =
raise newException(ValueError, "No implementation available")
method keycardStop*(self: Backend) =
raise newException(ValueError, "No implementation available")
method keycardSelect*(self: Backend): KeycardApplicationInfo =
raise newException(ValueError, "No implementation available")
method keycardPair*(self: Backend, pairingPassword: string): KeycardPairingInfo =
raise newException(ValueError, "No implementation available")
method keycardOpenSecureChannel*(self: Backend, index: int, key: string) =
raise newException(ValueError, "No implementation available")
method keycardVerifyPin*(self: Backend, pin: string) =
raise newException(ValueError, "No implementation available")
method keycardExportKey*(self: Backend, derive: bool, makeCurrent: bool, onlyPublic: bool, path: string): string =
raise newException(ValueError, "No implementation available")

19
backends/mock/keycard.nim Normal file
View File

@ -0,0 +1,19 @@
import ../types
import ../../types/[keycard]
method keycardStart*(self: MockBackend) = discard
method keycardStop*(self: MockBackend) = discard
method keycardSelect*(self: MockBackend): KeycardApplicationInfo =
result = KeycardApplicationInfo(installed: true)
method keycardPair*(self: MockBackend, pairingPassword: string): KeycardPairingInfo =
result = KeycardPairingInfo()
method keycardOpenSecureChannel*(self: MockBackend, index: int, key: string) = discard
method keycardVerifyPin*(self: MockBackend, pin: string) = discard
method keycardExportKey*(self: MockBackend, derive: bool, makeCurrent: bool, onlyPublic: bool, path: string): string =
result = "0x00"

View File

@ -0,0 +1,82 @@
import json
import keycard_go
import ../../types/[keycard]
import ../types
import ./core
method keycardStart*(self: StatusGoBackend) =
let response = keycard_go.start()
let parsedResponse = parseJson(response)
if not parsedResponse{"ok"}.getBool():
raise KeycardStartException(error: parsedResponse{"error"}.getStr())
method keycardStop*(self: StatusGoBackend) =
let response = keycard_go.stop()
let parsedResponse = parseJson(response)
if not parsedResponse{"ok"}.getBool():
raise KeycardStopException(error: parsedResponse{"error"}.getStr())
method keycardSelect*(self: StatusGoBackend): KeycardApplicationInfo =
let response = keycard_go.select()
let parsedResponse = parseJson(response)
if not parsedResponse{"ok"}.getBool():
raise KeycardSelectException(error: parsedResponse{"error"}.getStr())
return KeycardApplicationInfo(
installed: parsedResponse["applicationInfo"]["installed"].getBool(),
initialized: parsedResponse["applicationInfo"]["initialized"].getBool(),
instanceUID: parsedResponse["applicationInfo"]["instanceUID"].getStr(),
secureChannelPublicKey: parsedResponse["applicationInfo"]["secureChannelPublicKey"].getStr(),
version: parsedResponse["applicationInfo"]["version"].getInt(),
availableSlots: parsedResponse["applicationInfo"]["availableSlots"].getInt(),
keyUID: parsedResponse["applicationInfo"]["keyUID"].getStr(),
capabilities: parsedResponse["applicationInfo"]["capabilities"].getInt()
)
method keycardPair*(self: StatusGoBackend, pairingPassword: string): KeycardPairingInfo =
let inputJSON = %* {
"pairingPassword": pairingPassword
}
let response = keycard_go.pair($inputJSON)
let parsedResponse = parseJson(response)
if not parsedResponse{"ok"}.getBool():
raise KeycardPairException(error: parsedResponse{"error"}.getStr())
result = KeycardPairingInfo(
key: parsedResponse["pairingInfo"]["key"].getStr(),
index: parsedResponse["pairingInfo"]["index"].getInt(),
)
method keycardOpenSecureChannel*(self: StatusGoBackend, index: int, key: string) =
let inputJSON = %* {
"key": key,
"index": index
}
let response = keycard_go.openSecureChannel($inputJSON)
let parsedResponse = parseJson(response)
if not parsedResponse{"ok"}.getBool():
raise KeycardOpenSecureChannelException(error: parsedResponse{"error"}.getStr())
method keycardVerifyPin*(self: StatusGoBackend, pin: string) =
let inputJSON = %* {
"pin": pin
}
let response = keycard_go.verifyPin($inputJSON)
let parsedResponse = parseJson(response)
if not parsedResponse{"ok"}.getBool():
raise KeycardVerifyPINException(error: parsedResponse{"error"}.getStr())
method keycardExportKey*(self: StatusGoBackend, derive: bool, makeCurrent: bool, onlyPublic: bool, path: string): string =
let inputJSON = %* {
"derive": derive,
"makeCurrent": makeCurrent,
"onlyPublic": onlyPublic,
"path": path
}
let response = keycard_go.exportKey($inputJSON)
let parsedResponse = parseJson(response)
if not parsedResponse{"ok"}.getBool():
raise KeycardSelectException(error: parsedResponse{"error"}.getStr())
result = parsedResponse["key"].getStr()

View File

@ -40,12 +40,15 @@ proc buildAndRun(name: string,
" --nimcache:nimcache/" & (if getEnv("RELEASE").strip != "false": "release/" else: "debug/") & name &
(if getEnv("PCRE_LDFLAGS").strip != "": " --passL:\"" & getEnv("PCRE_LDFLAGS") & "\"" else: "") &
" --passL:\"-L" & getEnv("STATUSGO_LIBDIR") & " -lstatus \"" &
" --passL:\"-L" & getEnv("KEYCARDGO_LIBDIR") & " -lkeycard \"" &
" --out:" & outDir & name &
" " &
srcDir & name & ".nim"
if defined(macosx):
exec "install_name_tool -add_rpath " & getEnv("STATUSGO_LIBDIR") & " " & outDir & name
exec "install_name_tool -change " & "libstatus." & getEnv("LIBSTATUS_EXT") & " @rpath/libstatus." & getEnv("LIBSTATUS_EXT") & " " & outDir & name
exec "install_name_tool -add_rpath " & getEnv("KEYCARDGO_LIBDIR") & " " & outDir & name
exec "install_name_tool -change " & "libkeycard." & getEnv("LIBKEYCARD_EXT") & " @rpath/libkeycard." & getEnv("LIBKEYCARD_EXT") & " " & outDir & name
if getEnv("RUN_AFTER_BUILD").strip != "false":
exec outDir & name

View File

@ -1,7 +1,7 @@
# import statusgo_backend/browser as status_browser
import ../eventemitter
import ../types/[bookmark]
import ../../types/[bookmark]
import ../backends/backend

54
status/keycard.nim Normal file
View File

@ -0,0 +1,54 @@
import keycard_go
import ../types/keycard
import ../backends/backend
include utils/json_utils
type KeycardModel* = ref object
backend*: Backend
proc newKeycardModel*(backend: Backend): KeycardModel =
result = KeycardModel()
result.backend = backend
proc start*(self: KeycardModel) =
try:
self.backend.keycardStart()
except:
raise
proc stop*(self: KeycardModel) =
try:
self.backend.keycardStop()
except:
raise
proc select*(self: KeycardModel): KeycardApplicationInfo =
try:
result = self.backend.keycardSelect()
except:
raise
proc pair*(self: KeycardModel, pairingPassword: string): KeycardPairingInfo =
try:
result = self.backend.keycardPair(pairingPassword)
except:
raise
proc openSecureChannel*(self: KeycardModel, index: int, key: string) =
try:
self.backend.keycardOpenSecureChannel(index, key)
except:
raise
proc verifyPin*(self: KeycardModel, pin: string) =
try:
self.backend.keycardVerifyPin(pin)
except:
raise
proc exportKey*(self: KeycardModel, derive: bool, makeCurrent: bool, onlyPublic: bool, path: string): string =
try:
result = self.backend.keycardExportKey(derive, makeCurrent, onlyPublic, path)
except:
raise

View File

@ -1,5 +1,5 @@
import json, json_serialization, strutils
import signals/[base, chronicles_logs, community, discovery_summary, envelope, expired, mailserver, messages, peerstats, signal_type, stats, wallet, whisper_filter]
import signals/[base, chronicles_logs, community, discovery_summary, envelope, expired, mailserver, messages, peerstats, signal_type, stats, wallet, whisper_filter, keycard]
export base, chronicles_logs, community, discovery_summary, envelope, expired, mailserver, messages, peerstats, signal_type, stats, wallet, whisper_filter
@ -28,6 +28,7 @@ proc decode*(jsonSignal: JsonNode): Signal =
of SignalType.HistoryRequestCompleted: HistoryRequestCompletedSignal.fromEvent(jsonSignal)
of SignalType.HistoryRequestStarted: HistoryRequestStartedSignal.fromEvent(jsonSignal)
of SignalType.HistoryRequestFailed: HistoryRequestFailedSignal.fromEvent(jsonSignal)
of SignalType.KeycardConnected: KeycardConnectedSignal.fromEvent(jsonSignal)
else: Signal()
result.signalType = signalType

View File

@ -0,0 +1,11 @@
import json
import base
import signal_type
type KeycardConnectedSignal* = ref object of Signal
started*: string
proc fromEvent*(T: type KeycardConnectedSignal, event: JsonNode): KeycardConnectedSignal =
result = KeycardConnectedSignal()
result.started = event["event"].getStr()

View File

@ -25,7 +25,8 @@ type SignalType* {.pure.} = enum
HistoryRequestStarted = "history.request.started"
HistoryRequestCompleted = "history.request.completed"
HistoryRequestFailed = "history.request.failed"
KeycardConnected = "keycard.connected"
Unknown
proc event*(self:SignalType):string =
result = "signal:" & $self
result = "signal:" & $self

View File

@ -6,12 +6,13 @@ import notifications/os_notifications
import ../eventemitter
import bitops, stew/byteutils, chronicles
import ./types/[setting]
import ./keycard
import ../backends/backend
export chat, accounts, node, messages, contacts, profile, network, permissions, fleet, eventemitter
type Status* = ref object
type Status* = ref object
backend*: Backend
events*: EventEmitter
fleet*: FleetModel
@ -32,6 +33,7 @@ type Status* = ref object
tokens*: TokensModel
provider*: ProviderModel
osnotifications*: OsNotifications
keycard*: KeycardModel
proc newStatusInstance*(fleetConfig: string, backendName: string = "statusgo"): Status =
result = Status()
@ -56,6 +58,7 @@ proc newStatusInstance*(fleetConfig: string, backendName: string = "statusgo"):
result.tokens = tokens.newTokensModel(result.events)
result.provider = provider.newProviderModel(result.events, result.permissions, result.wallet)
result.osnotifications = newOsNotifications(result.events)
result.keycard = newKeycardModel(result.backend)
proc initNode*(self: Status, statusGoDir, keystoreDir: string) =
statusgo_backend_accounts.initNode(statusGoDir, keystoreDir)
@ -110,4 +113,4 @@ proc saveStringSetting*(self: Status, setting: Setting, value: cstring) {.export
self.saveSetting(setting, $value)
proc saveBoolSetting*(self: Status, setting: Setting, value: bool) {.exportc, dynlib.} =
self.saveSetting(setting, value)
self.saveSetting(setting, value)

51
test_nim/keycard.nim Normal file
View File

@ -0,0 +1,51 @@
import # std libs
std/[json, options, os, unittest]
import ../status/status
import ../types/[keycard]
import ../status/keycard as keycard_module
suite "#Keycard":
setup:
let fleetConfig = "{\"fleets\":{\"eth.prod\":{\"boot\":{\"boot-01.ac-cn-hongkong-c.eth.prod\":\"enode://6e6554fb3034b211398fcd0f0082cbb6bd13619e1a7e76ba66e1809aaa0c5f1ac53c9ae79cf2fd4a7bacb10d12010899b370c75fed19b991d9c0cdd02891abad@47.75.99.169:443\"},\"mail\":{\"mail-01.ac-cn-hongkong-c.eth.prod\":\"enode://606ae04a71e5db868a722c77a21c8244ae38f1bd6e81687cc6cfe88a3063fa1c245692232f64f45bd5408fed5133eab8ed78049332b04f9c110eac7f71c1b429@47.75.247.214:443\"},\"rendezvous\":{\"boot-01.ac-cn-hongkong-c.eth.prod\":\"/ip4/47.75.99.169/tcp/30703/ethv4/16Uiu2HAmV8Hq9e3zm9TMVP4zrVHo3BjqW5D6bDVV6VQntQd687e4\"},\"whisper\":{\"node-01.ac-cn-hongkong-c.eth.prod\":\"enode://b957e51f41e4abab8382e1ea7229e88c6e18f34672694c6eae389eac22dab8655622bbd4a08192c321416b9becffaab11c8e2b7a5d0813b922aa128b82990dab@47.75.222.178:443\"}}},\"meta\":{\"hostname\":\"node-01.do-ams3.proxy.misc\",\"timestamp\":\"2021-09-09T00:00:14.760436\"}}";
let statuslib_instance = newStatusInstance(fleetConfig, "mock")
test "start: should start listening to keycards":
try:
statuslib_instance.keycard.start()
except KeycardStartException as ex:
echo "keycard start exception"
echo repr(ex)
except KeycardException as ex:
echo "keycard exception"
echo repr(ex)
test "select: should return KeycardApplicationInfo":
try:
statuslib_instance.keycard.start()
let info = statuslib_instance.keycard.select()
statuslib_instance.keycard.stop()
check(info.installed)
except KeycardStartException as ex:
echo "keycard start exception"
echo repr(ex)
except KeycardStopException as ex:
echo "keycard stop exception"
echo repr(ex)
except KeycardException as ex:
echo "keycard exception"
echo repr(ex)
test "stop: should stop listening to keycards":
try:
statuslib_instance.keycard.start()
statuslib_instance.keycard.stop()
except KeycardStartException as ex:
echo "keycard start exception"
echo repr(ex)
except KeycardStopException as ex:
echo "keycard stop exception"
echo repr(ex)
except KeycardException as ex:
echo "keycard exception"
echo repr(ex)

View File

@ -1,2 +1,3 @@
import
./bookmarks
./bookmarks,
./keycard

31
types/keycard.nim Normal file
View File

@ -0,0 +1,31 @@
type KeycardException* = ref object of Exception
error*: string
type KeycardStartException* = ref object of KeycardException
type KeycardStopException* = ref object of KeycardException
type KeycardSelectException* = ref object of KeycardException
type KeycardPairException* = ref object of KeycardException
type KeycardOpenSecureChannelException* = ref object of KeycardException
type KeycardVerifyPINException* = ref object of KeycardException
pinRetry*: int64
type KeycardExportKeyException* = ref object of KeycardException
type KeycardApplicationInfo* = ref object
installed*: bool
initialized*: bool
instanceUID*: string
secureChannelPublicKey*: string
version*: int64
availableSlots*: int64
keyUID*: string
capabilities*: int64
type KeycardPairingInfo* = ref object
key*: string
index*: int64

1
vendor/nim-keycard-go vendored Submodule

@ -0,0 +1 @@
Subproject commit e959cec55edfc06ffe1567be36fdef63c761dae3