Compare commits

...

2 Commits

Author SHA1 Message Date
Yann Autissier c84b59e2cf new input/output format
* add JWK input/output format
* bump version v0.0.5
2022-09-27 16:34:30 +02:00
Yann Autissier 2c6f5a924c new input/output formats
* add b58mh and b64mh text output
* add protobuf2 input file
2022-09-24 00:12:58 +02:00
5 changed files with 549 additions and 272 deletions

View File

@ -1,28 +1,35 @@
.PHONY: all default install shellcheck-% shellspec-% tests uninstall
BINDIR ?= $(PREFIX)/bin BINDIR ?= $(PREFIX)/bin
PREFIX ?= /usr/local PREFIX ?= /usr/local
.PHONY: all default install shellcheck-% shellspec-% tests uninstall
-include $(MYOS)/make/include.mk
MYOS ?= ../myos MYOS ?= ../myos
MYOS_REPOSITORY ?= $(patsubst %/dpgpid,%/myos,$(shell git config --get remote.origin.url 2>/dev/null)) MYOS_REPOSITORY ?= $(patsubst %/$(THIS),%/myos,$(THIS_REPOSITORY))
THIS ?= $(lastword $(subst /, ,$(THIS_REPOSITORY)))
THIS_REPOSITORY ?= $(shell git config --get remote.origin.url 2>/dev/null)
$(MYOS): $(MYOS):
-@git clone $(MYOS_REPOSITORY) $(MYOS) -@git clone $(MYOS_REPOSITORY) $(MYOS)
-include $(MYOS)/make/include.mk
default: tests default: tests
all: install tests all: install tests
install: install: $(if $(shell which pip3),pip3-install,pip3-not-found)
mkdir -p "$(BINDIR)" mkdir -p "$(BINDIR)"
install dpgpid "$(BINDIR)/dpgpid" install dpgpid "$(BINDIR)/dpgpid"
install keygen "$(BINDIR)/keygen" install keygen "$(BINDIR)/keygen"
pip install -r requirements.txt
pip3-install:
pip3 install -r requirements.txt
pip3-not-found:
printf "WARNING: pip3 not found, please manually install python modules from requirements.txt\n"
shellcheck-%: shellcheck-%:
shellcheck $*/*.sh @shellcheck $*/*.sh
shellspec-%: shellspec-%:
shellspec -f tap $* @shellspec -f tap $*
tests: shellcheck-specs shellspec-specs tests: shellcheck-specs shellspec-specs

View File

@ -8,9 +8,20 @@ ARG PROCESSOR_ARCHITECTURE=$(uname -m)
ARG PYTHON_RELEASE=3.10 ARG PYTHON_RELEASE=3.10
WORKDIR /opt/dpgpid WORKDIR /opt/dpgpid
COPY requirements.txt ./
RUN apk upgrade --no-cache \ RUN apk upgrade --no-cache \
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ --virtual .build-deps \ && apk add --no-cache \
bash \
ca-certificates \
gettext \
gpg \
gpg-agent \
libc6-compat \
libsodium \
make
COPY requirements.txt ./
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ --virtual .build-deps \
build-base \ build-base \
libffi-dev \ libffi-dev \
py3-gpgme \ py3-gpgme \
@ -27,16 +38,7 @@ RUN apk upgrade --no-cache \
|awk 'system("[ -e /lib/"$1" -o -e /usr/lib/"$1" -o -e ./lib/python'"${PYTHON_RELEASE}"'/site-packages/*/"$1" ]") == 0 { next } { print "so:" $1 }' \ |awk 'system("[ -e /lib/"$1" -o -e /usr/lib/"$1" -o -e ./lib/python'"${PYTHON_RELEASE}"'/site-packages/*/"$1" ]") == 0 { next } { print "so:" $1 }' \
|xargs -rt apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ |xargs -rt apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/
RUN apk add --no-cache \ RUN OS="$(echo ${OPERATING_SYSTEM} |awk '{print tolower($0)}')"; \
bash \
ca-certificates \
gettext \
gpg \
gpg-agent \
libc6-compat \
libsodium \
make \
&& OS="$(echo ${OPERATING_SYSTEM} |awk '{print tolower($0)}')"; \
ARCH="$(echo ${PROCESSOR_ARCHITECTURE})"; \ ARCH="$(echo ${PROCESSOR_ARCHITECTURE})"; \
wget -qO - https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.${OS}.${ARCH}.tar.xz \ wget -qO - https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.${OS}.${ARCH}.tar.xz \
|tar --strip-components 1 -C ./bin -xJf - \ |tar --strip-components 1 -C ./bin -xJf - \

680
keygen
View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# link: https://git.p2p.legal/aya/dpgpid/ # link: https://git.p2p.legal/aya/dpgpid/
# desc: generate ed25519 keys suitables for duniter or ipfs # desc: generate ed25519 keys for duniter and ipfs from gpg
# Copyleft 2022 Yann Autissier <aya@asycn.io> # Copyleft 2022 Yann Autissier <aya@asycn.io>
# all crypto science belongs to Pascal Engélibert <tuxmain@zettascript.org> # all crypto science belongs to Pascal Engélibert <tuxmain@zettascript.org>
@ -31,6 +31,7 @@ from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
import duniterpy.key import duniterpy.key
import gpg import gpg
from jwcrypto import jwk
import logging as log import logging as log
import nacl.bindings import nacl.bindings
import nacl.encoding import nacl.encoding
@ -44,27 +45,27 @@ import sys
import time import time
import warnings import warnings
__version__='0.0.4' __version__='0.0.5'
class keygen: class keygen:
def __init__(self): def __init__(self):
# desc: generate ed25519 keys suitables for duniter or ipfs
self.parser = argparse.ArgumentParser(description=""" self.parser = argparse.ArgumentParser(description="""
Generate ed25519 keys suitables for duniter and ipfs. Generate ed25519 keys for duniter and ipfs from gpg.
It converts an ed25519 key, duniter username/password, or a GPG key, to It converts a gpg key, a duniter username/password, or any ed25519 key to
a duniter wallet or an IPFS PeerID/PrivateKEY.""") a duniter wallet or an IPFS key.""")
self.parser.add_argument( self.parser.add_argument(
"-d", "-d",
"--debug", "--debug",
action="store_true", action="store_true",
help="show debug informations (WARNING: including SECRET KEY value)", help="show debug informations (WARNING: including SECRET KEY)",
) )
self.parser.add_argument( self.parser.add_argument(
"-f", "-f",
"--format", "--format",
dest="format", choices=['ewif', 'jwk', 'nacl','pb2','pem','pubsec','seed','wif'],
default=None, default=None,
help="output file format: [ewif|nacl|pb2|pem|pubsec|seed|wif], default: pem (pkcs8)", dest="format",
help="output file format, default: pem (pkcs8)",
) )
self.parser.add_argument( self.parser.add_argument(
"-g", "-g",
@ -76,8 +77,8 @@ class keygen:
"-i", "-i",
"--input", "--input",
dest="input", dest="input",
default=None, help="read ed25519 key from file FILE, autodetect format: {credentials,ewif,jwk,nacl,mnemonic,pb2,pubsec,seed,wif}",
help="read ed25519 key from file INPUT, autodetect format: [credentials|ewif|libnacl|mnemonic|pubsec|seedhex|wif]", metavar='FILE',
) )
self.parser.add_argument( self.parser.add_argument(
"-k", "-k",
@ -96,7 +97,8 @@ class keygen:
"--output", "--output",
dest="output", dest="output",
default=None, default=None,
help="write ed25519 key to file OUTPUT", help="write ed25519 key to file FILE",
metavar='FILE',
) )
self.parser.add_argument( self.parser.add_argument(
"-p", "-p",
@ -114,14 +116,15 @@ class keygen:
"-s", "-s",
"--secret", "--secret",
action="store_true", action="store_true",
help="show secret key", help="show only secret key",
) )
self.parser.add_argument( self.parser.add_argument(
"-t", "-t",
"--type", "--type",
dest="type", choices=['b58mh','b64mh','base58','base64','duniter','ipfs','jwk'],
default="base58", default="base58",
help="output text format: [base58|base64|duniter|ipfs], default: base58", dest="type",
help="output text format, default: base58",
) )
self.parser.add_argument( self.parser.add_argument(
"-v", "-v",
@ -145,12 +148,8 @@ class keygen:
def _check_args(self, args): def _check_args(self, args):
log.debug("keygen._check_args(%s)" % args) log.debug("keygen._check_args(%s)" % args)
if self.input is None: if self.input is None and self.username is None:
if self.password is None: self.parser.error('keygen requires an input file or a username')
if self.username is None:
self.parser.error(f"keygen requires an input file or username args")
if self.format not in [None, 'ewif', 'nacl', 'pb2', 'pem', 'pubsec', 'seed', 'wif']:
self.parser.error(f"format not valid")
def _cleanup(self): def _cleanup(self):
log.debug("keygen._cleanup()") log.debug("keygen._cleanup()")
@ -173,21 +172,25 @@ class keygen:
if hasattr(self, 'ed25519_secret_pem_pkcs8') and self.ed25519_secret_pem_pkcs8: if hasattr(self, 'ed25519_secret_pem_pkcs8') and self.ed25519_secret_pem_pkcs8:
clearmem(self.ed25519_secret_pem_pkcs8) clearmem(self.ed25519_secret_pem_pkcs8)
log.debug("cleared: keygen.ed25515_secret_pem_pkcs8") log.debug("cleared: keygen.ed25515_secret_pem_pkcs8")
if hasattr(self, 'ed25519_secret_protobuf2') and self.ed25519_secret_protobuf2: if hasattr(self, 'ed25519_secret_protobuf') and self.ed25519_secret_protobuf:
clearmem(self.ed25519_secret_protobuf2) clearmem(self.ed25519_secret_protobuf)
log.debug("cleared: keygen.ed25515_secret_protobuf2") log.debug("cleared: keygen.ed25515_secret_protobuf")
if hasattr(self, 'ed25519_seed_bytes') and self.ed25519_seed_bytes: if hasattr(self, 'ed25519_seed_bytes') and self.ed25519_seed_bytes:
clearmem(self.ed25519_seed_bytes) clearmem(self.ed25519_seed_bytes)
log.debug("cleared: keygen.ed25519_seed_bytes") log.debug("cleared: keygen.ed25519_seed_bytes")
if hasattr(self, 'ipfs_privkey') and self.ipfs_privkey: if hasattr(self, 'ipfs_privkey') and self.ipfs_privkey:
clearmem(self.ipfs_privkey) clearmem(self.ipfs_privkey)
log.debug("cleared: keygen.ipfs_privkey") log.debug("cleared: keygen.ipfs_privkey")
if hasattr(self, 'jwk'):
if hasattr(self.jwk, 'd') and self.jwk.d:
clearmem(self.jwk.d)
log.debug("cleared: keygen.jwk.d")
if hasattr(self, 'password') and self.password: if hasattr(self, 'password') and self.password:
clearmem(self.password) clearmem(self.password)
log.debug("cleared: keygen.password") log.debug("cleared: keygen.password")
if hasattr(self, 'pgp_secret_armored') and self.pgp_secret_armored: if hasattr(self, 'pgp_secret_armor') and self.pgp_secret_armor:
clearmem(self.pgp_secret_armored) clearmem(self.pgp_secret_armor)
log.debug("cleared: keygen.pgp_secret_armored") log.debug("cleared: keygen.pgp_secret_armor")
if hasattr(self, 'pgpy'): if hasattr(self, 'pgpy'):
if hasattr(self.pgpy._key.keymaterial, 'p') and self.pgpy._key.keymaterial.p and not isinstance(self.pgpy._key.keymaterial.p, pgpy.packet.fields.ECPoint): if hasattr(self.pgpy._key.keymaterial, 'p') and self.pgpy._key.keymaterial.p and not isinstance(self.pgpy._key.keymaterial.p, pgpy.packet.fields.ECPoint):
clearmem(self.pgpy._key.keymaterial.p) clearmem(self.pgpy._key.keymaterial.p)
@ -204,7 +207,7 @@ class keygen:
def _invalid_type(self): def _invalid_type(self):
log.debug("keygen._invalid_type()") log.debug("keygen._invalid_type()")
self.parser.error(f"type: {self.type} is not valid.") self.parser.error(f"type {self.type} is not valid.")
def _load_config(self): def _load_config(self):
log.debug("keygen._load_config()") log.debug("keygen._load_config()")
@ -224,48 +227,74 @@ class keygen:
def _output_file(self): def _output_file(self):
log.debug("keygen._output_file()") log.debug("keygen._output_file()")
if self.format == 'ewif': try:
if not hasattr(self, 'duniterpy'): if self.format == 'dewif':
self.duniterpy_from_ed25519() if not hasattr(self, 'duniterpy'):
if not self.password: self.duniterpy_from_ed25519_seed_bytes()
with pynentry.PynEntry() as p: if not self.password:
p.description = f"""Data in EWIF file needs to be encrypted. with pynentry.PynEntry() as p:
Please enter a password to encrypt seed. p.description = f"""Data in DEWIF file needs to be encrypted.
""" Please enter a password to encrypt seed.
p.prompt = 'Passphrase:' """
try: p.prompt = 'Passphrase:'
self.password = p.get_pin() try:
except pynentry.PinEntryCancelled: self.password = p.get_pin()
log.warning('Cancelled! Goodbye.') except pynentry.PinEntryCancelled:
self._cleanup() log.warning('Cancelled! Goodbye.')
exit(2) self._cleanup()
self.duniterpy.save_ewif_file(self.output, self.password) exit(1)
elif self.format == 'nacl': self.duniterpy.save_dewif_v1_file(self.output, self.password)
if not hasattr(self, 'duniterpy'): elif self.format == 'ewif':
self.duniterpy_from_ed25519() if not hasattr(self, 'duniterpy'):
self.duniterpy.save_private_key(self.output) self.duniterpy_from_ed25519_seed_bytes()
elif self.format == 'pb2': if not self.password:
if not hasattr(self, 'ed25519_secret_protobuf2'): with pynentry.PynEntry() as p:
self.protobuf2_from_ed25519() p.description = f"""Data in EWIF file needs to be encrypted.
with open(self.output, "wb") as fh: Please enter a password to encrypt seed.
fh.write(self.ed25519_secret_protobuf2) """
elif self.format == 'pubsec': p.prompt = 'Passphrase:'
if not hasattr(self, 'duniterpy'): try:
self.duniterpy_from_ed25519() self.password = p.get_pin()
self.duniterpy.save_pubsec_file(self.output) except pynentry.PinEntryCancelled:
elif self.format == 'seed': log.warning('Cancelled! Goodbye.')
if not hasattr(self, 'duniterpy'): self._cleanup()
self.duniterpy_from_ed25519() exit(1)
self.duniterpy.save_seedhex_file(self.output) self.duniterpy.save_ewif_file(self.output, self.password)
elif self.format == 'wif': elif self.format == 'jwk':
if not hasattr(self, 'duniterpy'): if not hasattr(self, 'jwk'):
self.duniterpy_from_ed25519() self.jwk_from_ed25519()
self.duniterpy.save_wif_file(self.output) with open(self.output, "w") as file:
else: file.write(self.jwk.export())
if not hasattr(self, 'ed25519_secret_pem_pkcs8'): elif self.format == 'nacl':
self.pem_pkcs8_from_ed25519() if not hasattr(self, 'duniterpy'):
with open(self.output, "w") as fh: self.duniterpy_from_ed25519_seed_bytes()
fh.write(self.ed25519_secret_pem_pkcs8) self.duniterpy.save_private_key(self.output)
elif self.format == 'pb2':
if not hasattr(self, 'ed25519_secret_protobuf'):
self.protobuf_from_ed25519()
with open(self.output, "wb") as file:
file.write(self.ed25519_secret_protobuf)
elif self.format == 'pubsec':
if not hasattr(self, 'duniterpy'):
self.duniterpy_from_ed25519_seed_bytes()
self.duniterpy.save_pubsec_file(self.output)
elif self.format == 'seed':
if not hasattr(self, 'duniterpy'):
self.duniterpy_from_ed25519_seed_bytes()
self.duniterpy.save_seedhex_file(self.output)
elif self.format == 'wif':
if not hasattr(self, 'duniterpy'):
self.duniterpy_from_ed25519_seed_bytes()
self.duniterpy.save_wif_file(self.output)
else:
if not hasattr(self, 'ed25519_secret_pem_pkcs8'):
self.pem_pkcs8_from_ed25519()
with open(self.output, "w") as file:
file.write(self.ed25519_secret_pem_pkcs8)
except Exception as e:
log.error(f'Unable to output file {self.output}: {e}')
self._cleanup()
exit(2)
def _output_text(self, public_key, secret_key, public_key_prefix, secret_key_prefix): def _output_text(self, public_key, secret_key, public_key_prefix, secret_key_prefix):
log.debug("keygen._output_text()") log.debug("keygen._output_text()")
@ -305,27 +334,65 @@ class keygen:
method = getattr(self, f'do_{self.type}', self._invalid_type) method = getattr(self, f'do_{self.type}', self._invalid_type)
return method() return method()
def b58mh_from_protobuf(self):
log.debug("keygen.b58mh_from_protobuf()")
try:
self.ed25519_public_b58mh = base58.b58encode(self.ed25519_public_protobuf).decode('ascii')
self.ed25519_secret_b58mh = base58.b58encode(self.ed25519_secret_protobuf).decode('ascii')
except Exception as e:
log.error(f'Unable to get b58mh from protobuf: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_public_b58mh=%s" % self.ed25519_public_b58mh)
log.debug("keygen.ed25519_secret_b58mh=%s" % self.ed25519_secret_b58mh)
def b64mh_from_protobuf(self):
log.debug("keygen.b64mh_from_protobuf()")
try:
self.ed25519_public_b64mh = base64.b64encode(self.ed25519_public_protobuf).decode('ascii')
self.ed25519_secret_b64mh = base64.b64encode(self.ed25519_secret_protobuf).decode('ascii')
except Exception as e:
log.error(f'Unable to get b64mh from protobuf: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_public_b64mh=%s" % self.ed25519_public_b64mh)
log.debug("keygen.ed25519_secret_b64mh=%s" % self.ed25519_secret_b64mh)
def base58_from_ed25519(self): def base58_from_ed25519(self):
log.debug("keygen.base58_from_ed25519()") log.debug("keygen.base58_from_ed25519()")
self.ed25519_public_base58 = base58.b58encode(self.ed25519_public_bytes).decode('ascii') try:
self.ed25519_public_base58 = base58.b58encode(self.ed25519_public_bytes).decode('ascii')
self.ed25519_secret_base58 = base58.b58encode(self.ed25519_secret_bytes).decode('ascii')
except Exception as e:
log.error(f'Unable to get base58 from ed25519: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_public_base58=%s" % self.ed25519_public_base58) log.debug("keygen.ed25519_public_base58=%s" % self.ed25519_public_base58)
self.ed25519_secret_base58 = base58.b58encode(self.ed25519_secret_bytes).decode('ascii')
log.debug("keygen.ed25519_secret_base58=%s" % self.ed25519_secret_base58) log.debug("keygen.ed25519_secret_base58=%s" % self.ed25519_secret_base58)
def base64_from_ed25519(self): def base64_from_ed25519(self):
log.debug("keygen.base64_from_ed25519()") log.debug("keygen.base64_from_ed25519()")
self.ed25519_public_base64 = base64.b64encode(self.ed25519_public_bytes).decode('ascii') try:
self.ed25519_public_base64 = base64.b64encode(self.ed25519_public_bytes).decode('ascii')
self.ed25519_secret_base64 = base64.b64encode(self.ed25519_secret_bytes).decode('ascii')
except Exception as e:
log.error(f'Unable to get base64 from ed25519: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_public_base64=%s" % self.ed25519_public_base64) log.debug("keygen.ed25519_public_base64=%s" % self.ed25519_public_base64)
self.ed25519_secret_base64 = base64.b64encode(self.ed25519_secret_bytes).decode('ascii')
log.debug("keygen.ed25519_secret_base64=%s" % self.ed25519_secret_base64) log.debug("keygen.ed25519_secret_base64=%s" % self.ed25519_secret_base64)
def base58_from_pubsec(self): def do_b58mh(self):
log.debug("keygen.base58_from_pubsec()") log.debug("keygen.do_b58mh()")
for line in open(self.input, "r"): self.protobuf_from_ed25519()
if re.search("pub", line): self.b58mh_from_protobuf()
self.ed25519_public_base58 = line.replace('\n','').split(': ')[1] self._output(self.ed25519_public_b58mh, self.ed25519_secret_b58mh, 'pub: ', 'sec: ')
elif re.search("sec", line):
self.ed25519_secret_base58 = line.replace('\n','').split(': ')[1] def do_b64mh(self):
log.debug("keygen.do_b64mh()")
self.protobuf_from_ed25519()
self.b64mh_from_protobuf()
self._output(self.ed25519_public_b64mh, self.ed25519_secret_b64mh, 'pub: ', 'sec: ')
def do_base58(self): def do_base58(self):
log.debug("keygen.do_base58()") log.debug("keygen.do_base58()")
@ -338,44 +405,62 @@ class keygen:
self._output(self.ed25519_public_base64, self.ed25519_secret_base64, 'pub: ', 'sec: ') self._output(self.ed25519_public_base64, self.ed25519_secret_base64, 'pub: ', 'sec: ')
def do_duniter(self): def do_duniter(self):
log.debug("keygen.do_duniter()")
if not self.format: if not self.format:
self.format = 'pubsec' self.format = 'pubsec'
self.do_base58() self.base58_from_ed25519()
self._output(self.ed25519_public_base58, self.ed25519_secret_base58, 'pub: ', 'sec: ')
def do_ipfs(self): def do_ipfs(self):
log.debug("keygen.do_ipfs()") log.debug("keygen.do_ipfs()")
self.protobuf2_from_ed25519() self.protobuf_from_ed25519()
self.ipfs_from_protobuf2() self.b58mh_from_protobuf()
self._output(self.ipfs_peerid, self.ipfs_privkey, 'PeerID: ', 'PrivKEY: ') self.b64mh_from_protobuf()
self._output(self.ed25519_public_b58mh, self.ed25519_secret_b64mh, 'PeerID: ', 'PrivKEY: ')
def do_jwk(self):
log.debug("keygen.do_jwk()")
self.jwk_from_ed25519()
self._output(self.jwk.export_public(), self.jwk.export_private(), 'pub: ', 'sec: ')
def duniterpy_from_credentials(self): def duniterpy_from_credentials(self):
log.debug("keygen.duniterpy_from_credentials()") log.debug("keygen.duniterpy_from_credentials()")
scrypt_params = duniterpy.key.scrypt_params.ScryptParams( try:
int(self.config.get('scrypt', 'n')) if self.config.has_option('scrypt', 'n') else 4096, scrypt_params = duniterpy.key.scrypt_params.ScryptParams(
int(self.config.get('scrypt', 'r')) if self.config.has_option('scrypt', 'r') else 16, int(self.config.get('scrypt', 'n')) if self.config.has_option('scrypt', 'n') else 4096,
int(self.config.get('scrypt', 'p')) if self.config.has_option('scrypt', 'p') else 1, int(self.config.get('scrypt', 'r')) if self.config.has_option('scrypt', 'r') else 16,
int(self.config.get('scrypt', 'sl')) if self.config.has_option('scrypt', 'sl') else 32, int(self.config.get('scrypt', 'p')) if self.config.has_option('scrypt', 'p') else 1,
) int(self.config.get('scrypt', 'sl')) if self.config.has_option('scrypt', 'sl') else 32,
if not self.password: )
with pynentry.PynEntry() as p: if not self.password:
p.description = f"""Please enter the passord for username "{self.username}".""" with pynentry.PynEntry() as p:
p.prompt = 'Passsord:' p.description = f"""Please enter the passord for username "{self.username}"."""
try: p.prompt = 'Passsord:'
self.password = p.get_pin() try:
except pynentry.PinEntryCancelled: self.password = p.get_pin()
log.warning('Cancelled! Goodbye.') except pynentry.PinEntryCancelled:
self._cleanup() log.warning('Cancelled! Goodbye.')
exit(2) self._cleanup()
self.duniterpy = duniterpy.key.SigningKey.from_credentials( exit(1)
self.username, self.duniterpy = duniterpy.key.SigningKey.from_credentials(
self.password, self.username,
scrypt_params self.password,
) scrypt_params
)
except Exception as e:
log.error(f'Unable to get duniter from credentials: {e}')
self._cleanup()
exit(2)
log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed) log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed)
def duniterpy_from_ed25519(self): def duniterpy_from_ed25519_seed_bytes(self):
log.debug("keygen.duniterpy_from_ed25519()") log.debug("keygen.duniterpy_from_ed25519_seed_bytes()")
self.duniterpy = duniterpy.key.SigningKey(self.ed25519_secret_bytes[:32]) try:
self.duniterpy = duniterpy.key.SigningKey(self.ed25519_seed_bytes)
except Exception as e:
log.error(f'Unable to get duniterpy from ed25519 seed bytes: {e}')
self._cleanup()
exit(2)
log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed) log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed)
def duniterpy_from_file(self): def duniterpy_from_file(self):
@ -386,17 +471,18 @@ class keygen:
if len(lines) > 0: if len(lines) > 0:
line = lines[0].strip() line = lines[0].strip()
regex_ewif = re.compile('^Type: EWIF$') regex_ewif = re.compile('^Type: EWIF$')
regex_nacl = re.compile('^\\s*{\\s*"priv":\\s*"[0-9a-fA-F]+",\\s*"verify":\\s*"[0-9a-fA-F]+",\\s*"sign":\\s*"[0-9a-fA-F]+"\\s*}$') regex_jwk = re.compile('^\\s*{\\s*"crv":\\s*"Ed25519",\\s*"d":\\s*"(.)+",\\s*"kty":\\s*"OKP",\\s*"x":\\s*"(.)+"\\s*}')
regex_nacl = re.compile('^\\s*{\\s*"priv":\\s*"[0-9a-fA-F]+",\\s*"verify":\\s*"[0-9a-fA-F]+",\\s*"sign":\\s*"[0-9a-fA-F]+"\\s*}')
regex_pem = re.compile('^-----BEGIN PRIVATE KEY-----$') regex_pem = re.compile('^-----BEGIN PRIVATE KEY-----$')
regex_pubsec = re.compile('^Type: PubSec$') regex_pubsec = re.compile('^Type: PubSec$')
regex_seed = re.compile('^[0-9a-fA-F]{64}$') regex_seed = re.compile('^[0-9a-fA-F]{64}$')
regex_ssb = re.compile('\\s*{\\s*"curve": "ed25519",\\s*"public": "(.+)\\.ed25519",\\s*"private":\\s*"(.+)\\.ed25519",\\s*"id":\\s*"@(.+).ed25519"\\s*}') regex_ssb = re.compile('\\s*{\\s*"curve":\\s*"ed25519",\\s*"public":\\s*"(.+)\\.ed25519",\\s*"private":\\s*"(.+)\\.ed25519",\\s*"id":\\s*"@(.+).ed25519"\\s*}')
regex_wif = re.compile('^Type: WIF$') regex_wif = re.compile('^Type: WIF$')
if re.search(regex_ewif, line): if re.search(regex_ewif, line):
log.info("input file format detected: ewif") log.info("input file format detected: ewif")
if not self.password: if not self.password:
with pynentry.PynEntry() as p: with pynentry.PynEntry() as p:
p.description = f"""Data in EWIF file needs to be decrypted. p.description = f"""Data in EWIF file is encrypted.
Please enter a password to decrypt seed. Please enter a password to decrypt seed.
""" """
p.prompt = 'Passphrase:' p.prompt = 'Passphrase:'
@ -405,17 +491,20 @@ class keygen:
except pynentry.PinEntryCancelled: except pynentry.PinEntryCancelled:
log.warning('Cancelled! Goodbye.') log.warning('Cancelled! Goodbye.')
self._cleanup() self._cleanup()
exit(2) exit(1)
self.duniterpy = duniterpy.key.SigningKey.from_ewif_file(self.input, self.password) self.duniterpy = duniterpy.key.SigningKey.from_ewif_file(self.input, self.password)
elif re.search(regex_jwk, line):
log.info("input file format detected: jwk")
self.jwk_from_json(line)
self.ed25519_seed_bytes_from_jwk()
self.duniterpy_from_ed25519_seed_bytes()
elif re.search(regex_nacl, line): elif re.search(regex_nacl, line):
log.info("input file format detected: nacl") log.info("input file format detected: nacl")
self.duniterpy = duniterpy.key.SigningKey.from_private_key(self.input) self.duniterpy = duniterpy.key.SigningKey.from_private_key(self.input)
elif re.search(regex_pem, line): elif re.search(regex_pem, line):
log.info("input file format detected: pem") log.info("input file format detected: pem")
self.ed25519_secret_bytes = serialization.load_pem_private_key(''.join(lines).encode(), password=None).private_bytes(encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption()) self.ed25519_seed_bytes_from_pem(''.join(lines).encode())
self.duniterpy_from_ed25519() self.duniterpy_from_ed25519_seed_bytes()
## at this stage, self.ed25519_secret_bytes contains only the 32 seed bytes and not the 32 seed bytes + 32 public bytes as it should
# we need to call self.ed25519_from_duniterpy() later to correctly build the self.ed25519_secret_bytes
elif re.search(regex_pubsec, line): elif re.search(regex_pubsec, line):
log.info("input file format detected: pubsec") log.info("input file format detected: pubsec")
self.duniterpy = duniterpy.key.SigningKey.from_pubsec_file(self.input) self.duniterpy = duniterpy.key.SigningKey.from_pubsec_file(self.input)
@ -438,25 +527,68 @@ class keygen:
self.password = lines[1].strip() self.password = lines[1].strip()
self.duniterpy_from_credentials() self.duniterpy_from_credentials()
else: else:
raise NotImplementedError(f"""unable to detect input file format.""") raise NotImplementedError('unknown input file format.')
else:
raise NotImplementedError('empty file.')
except UnicodeDecodeError as e:
try:
with open(self.input, 'rb') as file:
lines = file.readlines()
if len(lines) > 0:
line = lines[0].strip()
regex_dewif = re.compile(b'^\x00\x00\x00\x01\x00\x00\x00\x01')
regex_pb2 = re.compile(b'^\x08\x01\x12@')
if re.search(regex_dewif, line):
log.info("input file format detected: dewif")
if not self.password:
with pynentry.PynEntry() as p:
p.description = f"""Data in DEWIF file is encrypted.
Please enter a password to decrypt seed.
"""
p.prompt = 'Passphrase:'
try:
self.password = p.get_pin()
except pynentry.PinEntryCancelled:
log.warning('Cancelled! Goodbye.')
self._cleanup()
exit(1)
self.duniterpy = duniterpy.key.SigningKey.from_dewif_file(self.input, self.password)
if re.search(regex_pb2, line):
log.info("input file format detected: pb2")
self.ed25519_secret_protobuf = line
self.ed25519_seed_bytes_from_protobuf()
self.duniterpy_from_ed25519_seed_bytes()
else:
raise NotImplementedError('unknown input file format.')
else:
raise NotImplementedError('empty file.')
except Exception as e:
log.error(f'Unable to get duniterpy from file {self.input}: {e}')
self._cleanup()
exit(2)
except Exception as e: except Exception as e:
log.error(f"""Unable to open file {self.input}: {e}""") log.error(f'Unable to get duniterpy from file {self.input}: {e}')
self._cleanup() self._cleanup()
exit(1) exit(2)
log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed) log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed)
def duniterpy_from_mnemonic(self): def duniterpy_from_mnemonic(self):
log.debug("keygen.duniterpy_from_mnemonic()") log.debug("keygen.duniterpy_from_mnemonic()")
scrypt_params = duniterpy.key.scrypt_params.ScryptParams( try:
int(self.config.get('scrypt', 'n')) if self.config.has_option('scrypt', 'n') else 4096, scrypt_params = duniterpy.key.scrypt_params.ScryptParams(
int(self.config.get('scrypt', 'r')) if self.config.has_option('scrypt', 'r') else 16, int(self.config.get('scrypt', 'n')) if self.config.has_option('scrypt', 'n') else 4096,
int(self.config.get('scrypt', 'p')) if self.config.has_option('scrypt', 'p') else 1, int(self.config.get('scrypt', 'r')) if self.config.has_option('scrypt', 'r') else 16,
int(self.config.get('scrypt', 'sl')) if self.config.has_option('scrypt', 'sl') else 32, int(self.config.get('scrypt', 'p')) if self.config.has_option('scrypt', 'p') else 1,
) int(self.config.get('scrypt', 'sl')) if self.config.has_option('scrypt', 'sl') else 32,
self.duniterpy = duniterpy.key.SigningKey.from_dubp_mnemonic( )
self.username, self.duniterpy = duniterpy.key.SigningKey.from_dubp_mnemonic(
scrypt_params self.username,
) scrypt_params
)
except Exception as e:
log.error(f'Unable to get duniterpy from mnemonic: {e}')
self._cleanup()
exit(2)
log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed) log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed)
def ed25519(self, args): def ed25519(self, args):
@ -473,131 +605,204 @@ class keygen:
self.duniterpy_from_credentials() self.duniterpy_from_credentials()
self.ed25519_from_duniterpy() self.ed25519_from_duniterpy()
def ed25519_from_base58(self):
log.debug("keygen.ed25519_from_base58()")
self.ed25519_public_bytes = base58.b58decode(self.ed25519_public_base58)
self.ed25519_secret_bytes = base58.b58decode(self.ed25519_secret_base58)
log.debug("keygen.ed25519_public_bytes=%s" % self.ed25519_public_bytes)
log.debug("keygen.ed25519_secret_bytes=%s" % self.ed25519_secret_bytes)
def ed25519_from_duniterpy(self): def ed25519_from_duniterpy(self):
log.debug("keygen.ed25519_from_duniterpy()") log.debug("keygen.ed25519_from_duniterpy()")
self.ed25519_public_bytes = base58.b58decode(self.duniterpy.pubkey) try:
self.ed25519_secret_bytes = self.duniterpy.sk self.ed25519_seed_bytes_from_duniterpy()
log.debug("keygen.ed25519_public_bytes=%s" % self.ed25519_public_bytes) self.ed25519_from_seed_bytes()
log.debug("keygen.ed25519_secret_bytes=%s" % self.ed25519_secret_bytes) except:
log.error(f'Unable to get ed25519 from duniterpy: {e}')
self._cleanup()
exit(2)
def ed25519_from_gpg(self): def ed25519_from_gpg(self):
log.debug("keygen.ed25519_from_gpg()") log.debug("keygen.ed25519_from_gpg()")
self.pgpy_from_gpg() try:
self.ed25519_from_pgpy() self.pgpy_from_gpg()
self.ed25519_from_pgpy()
def pgpy_from_gpg(self): except Exception as e:
log.debug("keygen.pgpy_from_gpg()") log.error(f'Unable to get ed25519 from pgp: {e}')
self.gpg_seckeys = list(self.gpg.keylist(pattern=self.username, secret=True))
log.debug("keygen.gpg_seckeys=%s" % self.gpg_seckeys)
if not self.gpg_seckeys:
log.error(f"""Unable to find any key matching username "{self.username}".""")
self._cleanup()
exit(1)
else:
self.gpg_seckey = self.gpg_seckeys[0]
log.info(f"""Found key id "{self.gpg_seckey.fpr}" matching username "{self.username}".""")
log.debug("keygen.gpg_seckey.expired=%s" % self.gpg_seckey.expired)
log.debug("keygen.gpg_seckey.fpr=%s" % self.gpg_seckey.fpr)
log.debug("keygen.gpg_seckey.revoked=%s" % self.gpg_seckey.revoked)
log.debug("keygen.gpg_seckey.uids=%s" % self.gpg_seckey.uids)
log.debug("keygen.gpg_seckey.owner_trust=%s" % self.gpg_seckey.owner_trust)
log.debug("keygen.gpg_seckey.last_update=%s" % self.gpg_seckey.last_update)
if self.password:
self.gpg.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
self.pgp_secret_armored = self.gpg.key_export_secret(self.gpg_seckey.fpr)
log.debug("keygen.pgp_secret_armored=%s" % self.pgp_secret_armored)
if not self.pgp_secret_armored:
log.error(f"""Unable to export gpg secret key id "{self.gpg_seckey.fpr}" of user "{self.username}". Please check your password!""")
self._cleanup() self._cleanup()
exit(2) exit(2)
with warnings.catch_warnings():
# remove CryptographyDeprecationWarning about deprecated
# SymmetricKeyAlgorithm IDEA, CAST5 and Blowfish (PGPy v0.5.4)
warnings.simplefilter('ignore')
self.pgpy, _ = pgpy.PGPKey.from_blob(self.pgp_secret_armored)
def ed25519_from_pgpy(self): def ed25519_from_pgpy(self):
log.debug("keygen.ed25519_from_pgpy()") log.debug("keygen.ed25519_from_pgpy()")
log.debug("keygen.pgpy.fingerprint.keyid=%s" % self.pgpy.fingerprint.keyid) try:
log.debug("keygen.pgpy.is_protected=%s" % self.pgpy.is_protected) log.debug("keygen.pgpy.fingerprint.keyid=%s" % self.pgpy.fingerprint.keyid)
if self.pgpy.is_protected: log.debug("keygen.pgpy.is_protected=%s" % self.pgpy.is_protected)
if not self.password: if self.pgpy.is_protected:
with pynentry.PynEntry() as p: if not self.password:
p.description = f"""The exported pgp key id "{self.pgpy.fingerprint.keyid}" of user "{self.username}" is password protected. with pynentry.PynEntry() as p:
Please enter the passphrase again to unlock it. p.description = f"""The exported pgp key id "{self.pgpy.fingerprint.keyid}" of user "{self.username}" is password protected.
""" Please enter the passphrase again to unlock it.
p.prompt = 'Passphrase:' """
try: p.prompt = 'Passphrase:'
self.password = p.get_pin() try:
except pynentry.PinEntryCancelled: self.password = p.get_pin()
log.warning('Cancelled! Goodbye.') except pynentry.PinEntryCancelled:
self._cleanup() log.warning('Cancelled! Goodbye.')
exit(2) self._cleanup()
try: exit(1)
with warnings.catch_warnings(): try:
# remove CryptographyDeprecationWarning about deprecated with warnings.catch_warnings():
# SymmetricKeyAlgorithm IDEA, CAST5 and Blowfish (PGPy v0.5.4) # remove CryptographyDeprecationWarning about deprecated
warnings.simplefilter('ignore') # SymmetricKeyAlgorithm IDEA, CAST5 and Blowfish (PGPy v0.5.4)
with self.pgpy.unlock(self.password): warnings.simplefilter('ignore')
assert self.pgpy.is_unlocked with self.pgpy.unlock(self.password):
log.debug("keygen.pgpy.is_unlocked=%s" % self.pgpy.is_unlocked) assert self.pgpy.is_unlocked
self.ed25519_seed_bytes_from_pgpy() log.debug("keygen.pgpy.is_unlocked=%s" % self.pgpy.is_unlocked)
except Exception as e: self.ed25519_seed_bytes_from_pgpy()
log.error(f"""Unable to unlock pgp secret key id "{self.pgpy.fingerprint.keyid}" of user "{self.username}". Please check your password!""") except Exception as e:
self._cleanup() log.error(f"""Unable to unlock pgp secret key id "{self.pgpy.fingerprint.keyid}" of user "{self.username}": {e}""")
exit(2) self._cleanup()
else: exit(2)
self.ed25519_seed_bytes_from_pgpy() else:
self.ed25519_public_bytes, self.ed25519_secret_bytes = nacl.bindings.crypto_sign_seed_keypair(self.ed25519_seed_bytes) self.ed25519_seed_bytes_from_pgpy()
self.ed25519_from_seed_bytes()
except Exception as e:
log.error(f'Unable to get ed25519 seed bytes from pgpy: {e}')
self._cleanup()
exit(2)
def ed25519_from_seed_bytes(self):
log.debug("keygen.ed25519_from_seed_bytes()")
try:
self.ed25519_public_bytes, self.ed25519_secret_bytes = nacl.bindings.crypto_sign_seed_keypair(self.ed25519_seed_bytes)
self.ed25519 = ed25519.Ed25519PrivateKey.from_private_bytes(self.ed25519_seed_bytes)
except Exception as e:
log.error(f'Unable to get ed25519 from seed bytes: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_public_bytes=%s" % self.ed25519_public_bytes) log.debug("keygen.ed25519_public_bytes=%s" % self.ed25519_public_bytes)
log.debug("keygen.ed25519_secret_bytes=%s" % self.ed25519_secret_bytes) log.debug("keygen.ed25519_secret_bytes=%s" % self.ed25519_secret_bytes)
def ed25519_seed_bytes_from_duniterpy(self):
log.debug("keygen.ed25519_seed_bytes_from_duniterpy()")
try:
self.ed25519_seed_bytes = self.duniterpy.sk[:32]
except Exception as e:
log.error(f'Unable to get ed25519 seed bytes from duniterpy: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_seed_bytes=%s" % self.ed25519_seed_bytes)
def ed25519_seed_bytes_from_jwk(self):
log.debug("keygen.ed25519_seed_bytes_from_jwk()")
try:
self.ed25519_seed_bytes = self.jwk._okp_pri().private_bytes(encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption())
except Exception as e:
log.error(f'Unable to get ed25519 seed bytes from jwk: {e}')
self._cleanup()
exit(2)
def ed25519_seed_bytes_from_pem(self, pem):
log.debug("keygen.ed25519_seed_bytes_from_pem()")
try:
self.ed25519_seed_bytes = serialization.load_pem_private_key(pem, password=None).private_bytes(encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption())
except Exception as e:
log.error(f'Unable to get ed25519 seed bytes from pem: {e}')
self._cleanup()
exit(2)
def ed25519_seed_bytes_from_pgpy(self): def ed25519_seed_bytes_from_pgpy(self):
log.debug("keygen.ed25519_seed_bytes_from_pgpy()") log.debug("keygen.ed25519_seed_bytes_from_pgpy()")
self.pgpy_key_type() try:
if self.pgpy_key_type == 'RSA': self.pgpy_key_type()
log.debug("keygen.pgpy._key.keymaterial.p=%s" % self.pgpy._key.keymaterial.p) if self.pgpy_key_type == 'RSA':
log.debug("keygen.pgpy._key.keymaterial.q=%s" % self.pgpy._key.keymaterial.q) log.debug("keygen.pgpy._key.keymaterial.p=%s" % self.pgpy._key.keymaterial.p)
# custom seed: use sha256 hash of (p + q) log.debug("keygen.pgpy._key.keymaterial.q=%s" % self.pgpy._key.keymaterial.q)
self.ed25519_seed_bytes = nacl.bindings.crypto_hash_sha256(long_to_bytes(self.pgpy._key.keymaterial.p + self.pgpy._key.keymaterial.q)) # custom seed: use sha256 hash of (p + q)
p = long_to_bytes(self.pgpy._key.keymaterial.p) self.ed25519_seed_bytes = nacl.bindings.crypto_hash_sha256(long_to_bytes(self.pgpy._key.keymaterial.p + self.pgpy._key.keymaterial.q))
q = long_to_bytes(self.pgpy._key.keymaterial.q) elif self.pgpy_key_type in ('ECDSA', 'EdDSA', 'ECDH'):
log.debug("keygen.ed25519_seed_bytes=%s" % self.ed25519_seed_bytes) log.debug("keygen.pgpy._key.keymaterial.s=%s" % self.pgpy._key.keymaterial.s)
elif self.pgpy_key_type in ('ECDSA', 'EdDSA', 'ECDH'): self.ed25519_seed_bytes = long_to_bytes(self.pgpy._key.keymaterial.s)
log.debug("keygen.pgpy._key.keymaterial.s=%s" % self.pgpy._key.keymaterial.s) else:
self.ed25519_seed_bytes = long_to_bytes(self.pgpy._key.keymaterial.s) raise NotImplementedError(f"getting seed from {self.pgpy_key_type} key is not implemented")
log.debug("keygen.ed25519_seed_bytes=%s" % self.ed25519_seed_bytes) except Exception as e:
else: log.error(f'Unable to get ed25519 seed bytes from pgpy: {e}')
raise NotImplementedError(f"Getting seed from {self.pgpy_key_type} key is not implemented") self._cleanup()
exit(2)
log.debug("keygen.ed25519_seed_bytes=%s" % self.ed25519_seed_bytes)
def ed25519_seed_bytes_from_protobuf(self):
log.debug("keygen.ed25519_seed_bytes_from_protobuf()")
try:
self.ed25519_seed_bytes = self.ed25519_secret_protobuf.lstrip(b'\x08\x01\x12@')[:32]
except Exception as e:
log.error(f'Unable to get ed25519 seed bytes from protobuf: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_seed_bytes=%s" % self.ed25519_seed_bytes)
def gpg_passphrase_cb(self, uid_hint, passphrase_info, prev_was_bad): def gpg_passphrase_cb(self, uid_hint, passphrase_info, prev_was_bad):
log.debug("keygen.gpg_passphrase_cb(%s, %s, %s)" % (uid_hint, passphrase_info, prev_was_bad)) log.debug("keygen.gpg_passphrase_cb(%s, %s, %s)" % (uid_hint, passphrase_info, prev_was_bad))
return self.password return self.password
def ipfs_from_protobuf2(self): def jwk_from_ed25519(self):
log.debug("keygen.ipfs_from_protobuf2()") log.debug("keygen.jwk_from_ed25519()")
try:
self.jwk = jwk.JWK.from_pyca(self.ed25519)
except Exception as e:
log.error(f'Unable to get jwk from ed25519: {e}')
self._cleanup()
exit(2)
# PeerID def jwk_from_json(self, json):
self.ipfs_peerid = base58.b58encode(self.ed25519_public_protobuf2).decode('ascii') log.debug("keygen.jwk_from_json()")
log.debug("keygen.ipfs_peerid=%s" % self.ipfs_peerid) try:
self.jwk = jwk.JWK.from_json(json)
# PrivKey except Exception as e:
self.ipfs_privkey = base64.b64encode(self.ed25519_secret_protobuf2).decode('ascii') log.error(f'Unable to get jwk from json: {e}')
log.debug("keygen.ipfs_privkey=%s" % self.ipfs_privkey) self._cleanup()
exit(2)
def pem_pkcs8_from_ed25519(self): def pem_pkcs8_from_ed25519(self):
log.debug("keygen.pem_pkcs8_from_ed25519()") log.debug("keygen.pem_pkcs8_from_ed25519()")
try:
self.ed25519_secret_pem_pkcs8 = ed25519.Ed25519PrivateKey.from_private_bytes(self.ed25519_secret_bytes[:32]).private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()).decode('ascii') self.ed25519_secret_pem_pkcs8 = self.ed25519.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()).decode('ascii')
except Exception as e:
log.error(f'Unable to get pem pkcs8 from ed25519: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_secret_pem_pkcs8=%s" % self.ed25519_secret_pem_pkcs8) log.debug("keygen.ed25519_secret_pem_pkcs8=%s" % self.ed25519_secret_pem_pkcs8)
def pgpy_from_gpg(self):
log.debug("keygen.pgpy_from_gpg()")
try:
self.gpg_seckeys = list(self.gpg.keylist(pattern=self.username, secret=True))
log.debug("keygen.gpg_seckeys=%s" % self.gpg_seckeys)
if not self.gpg_seckeys:
log.warning(f"""Unable to find any key matching username "{self.username}".""")
self._cleanup()
exit(1)
else:
self.gpg_seckey = self.gpg_seckeys[0]
log.info(f"""Found key id "{self.gpg_seckey.fpr}" matching username "{self.username}".""")
log.debug("keygen.gpg_seckey.expired=%s" % self.gpg_seckey.expired)
log.debug("keygen.gpg_seckey.fpr=%s" % self.gpg_seckey.fpr)
log.debug("keygen.gpg_seckey.revoked=%s" % self.gpg_seckey.revoked)
log.debug("keygen.gpg_seckey.uids=%s" % self.gpg_seckey.uids)
log.debug("keygen.gpg_seckey.owner_trust=%s" % self.gpg_seckey.owner_trust)
log.debug("keygen.gpg_seckey.last_update=%s" % self.gpg_seckey.last_update)
if self.password:
self.gpg.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
self.pgp_public_armor = self.gpg.key_export(self.gpg_seckey.fpr)
self.pgp_secret_armor = self.gpg.key_export_secret(self.gpg_seckey.fpr)
log.debug("keygen.pgp_secret_armor=%s" % self.pgp_secret_armor)
if not self.pgp_secret_armor:
log.error(f"""Unable to export gpg secret key id "{self.gpg_seckey.fpr}" of user "{self.username}". Please check your password!""")
self._cleanup()
exit(2)
with warnings.catch_warnings():
# remove CryptographyDeprecationWarning about deprecated
# SymmetricKeyAlgorithm IDEA, CAST5 and Blowfish (PGPy v0.5.4)
warnings.simplefilter('ignore')
self.pgpy, _ = pgpy.PGPKey.from_blob(self.pgp_secret_armor)
except Exception as e:
log.error(f'Unable to get pgpy from gpg: {e}')
self._cleanup()
exit(2)
def pgpy_key_type(self): def pgpy_key_type(self):
log.debug("keygen.pgpy_key_type()") log.debug("keygen.pgpy_key_type()")
if isinstance(self.pgpy._key.keymaterial, pgpy.packet.fields.RSAPriv): if isinstance(self.pgpy._key.keymaterial, pgpy.packet.fields.RSAPriv):
@ -616,13 +821,18 @@ class keygen:
self.pgpy_key_type = 'undefined' self.pgpy_key_type = 'undefined'
log.debug("keygen.pgpy_key_type=%s" % self.pgpy_key_type) log.debug("keygen.pgpy_key_type=%s" % self.pgpy_key_type)
def protobuf2_from_ed25519(self): def protobuf_from_ed25519(self):
# libp2p protobuf version 2 # libp2p protobuf version 2
log.debug("keygen.protobuf2_from_ed25519()") log.debug("keygen.protobuf_from_ed25519()")
self.ed25519_public_protobuf2 = b'\x00$\x08\x01\x12 ' + self.ed25519_public_bytes try:
self.ed25519_secret_protobuf2 = b'\x08\x01\x12@' + self.ed25519_secret_bytes self.ed25519_public_protobuf = b'\x00$\x08\x01\x12 ' + self.ed25519_public_bytes
log.debug("keygen.ed25519_public_protobuf2=%s" % self.ed25519_public_protobuf2) self.ed25519_secret_protobuf = b'\x08\x01\x12@' + self.ed25519_secret_bytes
log.debug("keygen.ed25519_secret_protobuf2=%s" % self.ed25519_secret_protobuf2) except Exception as e:
log.error(f'Unable to get protobuf from ed25519: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_public_protobuf=%s" % self.ed25519_public_protobuf)
log.debug("keygen.ed25519_secret_protobuf=%s" % self.ed25519_secret_protobuf)
## ##
# long_to_bytes comes from PyCrypto, which is released into Public Domain # long_to_bytes comes from PyCrypto, which is released into Public Domain

View File

@ -1,6 +1,7 @@
base58==2.1.1 base58==2.1.1
cryptography==3.4.8 cryptography==3.4.8
duniterpy==1.1.0 duniterpy==1.1.0
jwcrypto==1.4.2
pgpy==0.5.4 pgpy==0.5.4
pynentry==0.1.6 pynentry==0.1.6
pynacl==1.5.0 pynacl==1.5.0

View File

@ -3,14 +3,15 @@ set -eu
CRED_FILE="${SHELLSPEC_TMPBASE}/credentials" CRED_FILE="${SHELLSPEC_TMPBASE}/credentials"
DUBP_FILE="${SHELLSPEC_TMPBASE}/mnemonic" DUBP_FILE="${SHELLSPEC_TMPBASE}/mnemonic"
EWIF_FILE="${SHELLSPEC_TMPBASE}/ed25519.ewif" EWIF_FILE="${SHELLSPEC_TMPBASE}/username.ewif"
NACL_FILE="${SHELLSPEC_TMPBASE}/ed25519.nacl" JWK_FILE="${SHELLSPEC_TMPBASE}/username.jwk"
PB2_FILE="${SHELLSPEC_TMPBASE}/ed25519.pb2" NACL_FILE="${SHELLSPEC_TMPBASE}/username.nacl"
PEM_FILE="${SHELLSPEC_TMPBASE}/ed25519.pem" PB2_FILE="${SHELLSPEC_TMPBASE}/username.pb2"
PUBSEC_FILE="${SHELLSPEC_TMPBASE}/ed25519.pubsec" PEM_FILE="${SHELLSPEC_TMPBASE}/username.pem"
SEED_FILE="${SHELLSPEC_TMPBASE}/ed25519.seed" PUBSEC_FILE="${SHELLSPEC_TMPBASE}/username.pubsec"
SSB_FILE="${SHELLSPEC_TMPBASE}/ed25519.ssb" SEED_FILE="${SHELLSPEC_TMPBASE}/username.seed"
WIF_FILE="${SHELLSPEC_TMPBASE}/ed25519.wif" SSB_FILE="${SHELLSPEC_TMPBASE}/username.ssb"
WIF_FILE="${SHELLSPEC_TMPBASE}/username.wif"
gpg() { gpg() {
GNUPGHOME="${SHELLSPEC_TMPBASE}" command gpg "$@" GNUPGHOME="${SHELLSPEC_TMPBASE}" command gpg "$@"
@ -57,7 +58,7 @@ Describe 'keygen'
Describe '--version:' Describe '--version:'
It 'prints version' It 'prints version'
When run keygen --version When run keygen --version
The output should include 'v0.0.4' The output should include 'v0.0.5'
The status should be success The status should be success
The stderr should equal "" The stderr should equal ""
End End
@ -112,42 +113,69 @@ Describe 'keygen'
The stderr should equal "" The stderr should equal ""
End End
End End
Describe '-t base58 -pk username password:' Describe '-pkt b58mh username password:'
It 'prints prefixed base58 multihash public and secret keys for user "username" and password "password"'
When run keygen -pkt b58mh username password
The output should include 'pub: 12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC'
The output should include 'sec: 23jhTarm17VAHUwPkHD2Kv5sPfuQrsXSZUzKUrRkP2oP8bgnLjVExhG4AVoayCLxbXN4g2pjVG5qiJRucUtogbj7zGapz'
The status should be success
The stderr should equal ""
End
End
Describe '-pkt b64mh username password:'
It 'prints prefixed base64 multihash public and secret keys for user "username" and password "password"'
When run keygen -pkt b64mh username password
The output should include 'pub: ACQIARIgNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas='
The output should include 'sec: CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas='
The status should be success
The stderr should equal ""
End
End
Describe '-pkt base58 username password:'
It 'prints prefixed base58 public and secret keys for user "username" and password "password"' It 'prints prefixed base58 public and secret keys for user "username" and password "password"'
When run keygen -t base58 -pk username password When run keygen -pkt base58 username password
The output should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG' The output should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG'
The output should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC' The output should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC'
The status should be success The status should be success
The stderr should equal "" The stderr should equal ""
End End
End End
Describe '-t base64 -pk username password:' Describe '-pkt base64 username password:'
It 'prints prefixed base64 public and secret keys for user "username" and password "password"' It 'prints prefixed base64 public and secret keys for user "username" and password "password"'
When run keygen -t base64 -pk username password When run keygen -pkt base64 username password
The output should include 'pub: NJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas=' The output should include 'pub: NJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas='
The output should include 'sec: D5eoJaNGoKM172hTdADv3psQf5P6vGDI9D8SRe8TYy80mhNu9w/6bnX5fDGuZaodo6kjVkPRRLAuOoCZXwGhqw==' The output should include 'sec: D5eoJaNGoKM172hTdADv3psQf5P6vGDI9D8SRe8TYy80mhNu9w/6bnX5fDGuZaodo6kjVkPRRLAuOoCZXwGhqw=='
The status should be success The status should be success
The stderr should equal "" The stderr should equal ""
End End
End End
Describe '-t duniter -pk username password:' Describe '-pkt duniter username password:'
It 'prints prefixed duniter public and secret keys for user "username" and password "password"' It 'prints prefixed duniter public and secret keys for user "username" and password "password"'
When run keygen -t duniter -pk username password When run keygen -pkt duniter username password
The output should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG' The output should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG'
The output should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC' The output should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC'
The status should be success The status should be success
The stderr should equal "" The stderr should equal ""
End End
End End
Describe '-t ipfs -pk username password:' Describe '-pkt ipfs username password:'
It 'prints prefixed ipfs public and secret keys for user "username" and password "password"' It 'prints prefixed ipfs public and secret keys for user "username" and password "password"'
When run keygen -t ipfs -pk username password When run keygen -pkt ipfs username password
The output should include 'PeerID: 12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC' The output should include 'PeerID: 12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC'
The output should include 'PrivKEY: CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas=' The output should include 'PrivKEY: CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas='
The status should be success The status should be success
The stderr should equal "" The stderr should equal ""
End End
End End
Describe '-pkt jwk username password:'
It 'prints prefixed jwk public and secret keys for user "username" and password "password"'
When run keygen -pkt jwk username password
The output should include 'pub: {"crv":"Ed25519","kty":"OKP","x":"NJoTbvcP-m51-XwxrmWqHaOpI1ZD0USwLjqAmV8Boas"}'
The output should include 'sec: {"crv":"Ed25519","d":"D5eoJaNGoKM172hTdADv3psQf5P6vGDI9D8SRe8TYy8","kty":"OKP","x":"NJoTbvcP-m51-XwxrmWqHaOpI1ZD0USwLjqAmV8Boas"}'
The status should be success
The stderr should equal ""
End
End
Describe '-pkm "tongue cute mail ...":' Describe '-pkm "tongue cute mail ...":'
It 'prints prefixed base58 public and secret keys for mnemonic "tongue cute mail ..."' It 'prints prefixed base58 public and secret keys for mnemonic "tongue cute mail ..."'
When run keygen -pkm "tongue cute mail fossil great frozen same social weasel impact brush kind" When run keygen -pkm "tongue cute mail fossil great frozen same social weasel impact brush kind"
@ -179,6 +207,26 @@ Describe 'keygen'
End End
rm -f "${DUBP_FILE}" rm -f "${DUBP_FILE}"
End End
Describe "-f jwk -o ${JWK_FILE} username password:"
rm -f "${JWK_FILE}"
It 'writes secret key to a JWK file for user "username" and password "password"'
When run keygen -f jwk -o "${JWK_FILE}" username password
The path "${JWK_FILE}" should exist
The contents of file "${JWK_FILE}" should include '{"crv":"Ed25519","d":"D5eoJaNGoKM172hTdADv3psQf5P6vGDI9D8SRe8TYy8","kty":"OKP","x":"NJoTbvcP-m51-XwxrmWqHaOpI1ZD0USwLjqAmV8Boas"}'
The status should be success
The stderr should equal ""
End
End
Describe "-pki ${JWK_FILE}:"
It 'prints prefixed base58 public and secret keys for ed25519 key read from JWK file"'
When run keygen -pki "${JWK_FILE}" -v
The output should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG'
The output should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC'
The status should be success
The stderr should include 'input file format detected: jwk'
End
rm -f "${JWK_FILE}"
End
Describe "-f nacl -o ${NACL_FILE} username password:" Describe "-f nacl -o ${NACL_FILE} username password:"
rm -f "${NACL_FILE}" rm -f "${NACL_FILE}"
It 'writes secret key to a libnacl file for user "username" and password "password"' It 'writes secret key to a libnacl file for user "username" and password "password"'
@ -259,7 +307,7 @@ Describe 'keygen'
rm -f "${PB2_FILE}" rm -f "${PB2_FILE}"
It 'writes protobuf2 secret key to a pb2 file for user "username" and password "password"' It 'writes protobuf2 secret key to a pb2 file for user "username" and password "password"'
decode_pb2() { decode_pb2() {
xxd -ps "${PB2_FILE}" xxd -p "${PB2_FILE}"
} }
not_xxd() { not_xxd() {
! which xxd >/dev/null 2>&1 ! which xxd >/dev/null 2>&1
@ -273,6 +321,15 @@ Describe 'keygen'
The status should be success The status should be success
The stderr should equal "" The stderr should equal ""
End End
End
Describe "-pki ${PB2_FILE}:"
It 'prints prefixed base58 public and secret keys for ed25519 key read from pb2 file"'
When run keygen -pki "${PB2_FILE}" -v
The output should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG'
The output should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC'
The status should be success
The stderr should include 'input file format detected: pb2'
End
rm -f "${PB2_FILE}" rm -f "${PB2_FILE}"
End End
Describe "-f pubsec -o ${PUBSEC_FILE} username password:" Describe "-f pubsec -o ${PUBSEC_FILE} username password:"