new input/output format

* add JWK input/output format
* bump version v0.0.5
This commit is contained in:
Yann Autissier 2022-09-27 16:33:07 +02:00
parent 2c6f5a924c
commit c84b59e2cf
5 changed files with 254 additions and 117 deletions

View File

@ -3,7 +3,9 @@ BINDIR ?= $(PREFIX)/bin
PREFIX ?= /usr/local PREFIX ?= /usr/local
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 -include $(MYOS)/make/include.mk
@ -12,11 +14,16 @@ 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

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 - \

297
keygen
View File

@ -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,7 +45,7 @@ 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):
@ -56,14 +57,15 @@ class keygen:
"-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",
@ -75,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|nacl|mnemonic|pb2|pubsec|seed|wif]", metavar='FILE',
) )
self.parser.add_argument( self.parser.add_argument(
"-k", "-k",
@ -95,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",
@ -113,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: [b58mh|b64mh|base58|base64|duniter|ipfs], default: base58", dest="type",
help="output text format, default: base58",
) )
self.parser.add_argument( self.parser.add_argument(
"-v", "-v",
@ -144,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()")
@ -181,12 +181,16 @@ class keygen:
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)
@ -203,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()")
@ -223,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(1) 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_protobuf'): with pynentry.PynEntry() as p:
self.protobuf_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_protobuf) """
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()")
@ -388,6 +418,11 @@ class keygen:
self.b64mh_from_protobuf() self.b64mh_from_protobuf()
self._output(self.ed25519_public_b58mh, self.ed25519_secret_b64mh, 'PeerID: ', 'PrivKEY: ') 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()")
try: try:
@ -418,12 +453,12 @@ class keygen:
exit(2) 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()")
try: try:
self.duniterpy = duniterpy.key.SigningKey(self.ed25519_seed_bytes) self.duniterpy = duniterpy.key.SigningKey(self.ed25519_seed_bytes)
except Exception as e: except Exception as e:
log.error(f'Unable to get duniterpy from ed25519: {e}') log.error(f'Unable to get duniterpy from ed25519 seed bytes: {e}')
self._cleanup() self._cleanup()
exit(2) exit(2)
log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed) log.debug("keygen.duniterpy.seed: %s" % self.duniterpy.seed)
@ -436,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:'
@ -457,13 +493,18 @@ class keygen:
self._cleanup() self._cleanup()
exit(1) 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_seed_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()
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)
@ -495,12 +536,28 @@ class keygen:
lines = file.readlines() lines = file.readlines()
if len(lines) > 0: if len(lines) > 0:
line = lines[0].strip() 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@') 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): if re.search(regex_pb2, line):
log.info("input file format detected: pb2") log.info("input file format detected: pb2")
self.ed25519_secret_protobuf = line self.ed25519_secret_protobuf = line
self.ed25519_from_protobuf() self.ed25519_seed_bytes_from_protobuf()
self.duniterpy_from_ed25519() self.duniterpy_from_ed25519_seed_bytes()
else: else:
raise NotImplementedError('unknown input file format.') raise NotImplementedError('unknown input file format.')
else: else:
@ -551,16 +608,12 @@ class keygen:
def ed25519_from_duniterpy(self): def ed25519_from_duniterpy(self):
log.debug("keygen.ed25519_from_duniterpy()") log.debug("keygen.ed25519_from_duniterpy()")
try: try:
self.ed25519_public_bytes = base58.b58decode(self.duniterpy.pubkey) self.ed25519_seed_bytes_from_duniterpy()
self.ed25519_secret_bytes = self.duniterpy.sk self.ed25519_from_seed_bytes()
self.ed25519_seed_bytes = self.ed25519_secret_bytes[:32] except:
except Exception as e:
log.error(f'Unable to get ed25519 from duniterpy: {e}') log.error(f'Unable to get ed25519 from duniterpy: {e}')
self._cleanup() self._cleanup()
exit(2) exit(2)
log.debug("keygen.ed25519_seed_bytes=%s" % self.ed25519_seed_bytes)
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_gpg(self): def ed25519_from_gpg(self):
log.debug("keygen.ed25519_from_gpg()") log.debug("keygen.ed25519_from_gpg()")
@ -607,28 +660,15 @@ class keygen:
self.ed25519_seed_bytes_from_pgpy() self.ed25519_seed_bytes_from_pgpy()
self.ed25519_from_seed_bytes() self.ed25519_from_seed_bytes()
except Exception as e: except Exception as e:
log.error(f'Unable to get ed25519 from pgpy: {e}') log.error(f'Unable to get ed25519 seed bytes from pgpy: {e}')
self._cleanup() self._cleanup()
exit(2) exit(2)
def ed25519_from_protobuf(self):
log.debug("keygen.ed25519_from_protobuf()")
try:
self.ed25519_secret_bytes = self.ed25519_secret_protobuf.lstrip(b'\x08\x01\x12@')
self.ed25519_seed_bytes = self.ed25519_secret_bytes[:32]
self.ed25519_public_bytes = self.ed25519_secret_bytes.lstrip(self.ed25519_seed_bytes)
except Exception as e:
log.error(f'Unable to get ed25519 from protobuf: {e}')
self._cleanup()
exit(2)
log.debug("keygen.ed25519_seed_bytes=%s" % self.ed25519_seed_bytes)
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_seed_bytes(self): def ed25519_from_seed_bytes(self):
log.debug("keygen.ed25519_from_seed_bytes()") log.debug("keygen.ed25519_from_seed_bytes()")
try: try:
self.ed25519_public_bytes, self.ed25519_secret_bytes = nacl.bindings.crypto_sign_seed_keypair(self.ed25519_seed_bytes) 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: except Exception as e:
log.error(f'Unable to get ed25519 from seed bytes: {e}') log.error(f'Unable to get ed25519 from seed bytes: {e}')
self._cleanup() self._cleanup()
@ -636,6 +676,34 @@ class keygen:
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()")
try: try:
@ -649,21 +717,49 @@ class keygen:
log.debug("keygen.pgpy._key.keymaterial.s=%s" % self.pgpy._key.keymaterial.s) log.debug("keygen.pgpy._key.keymaterial.s=%s" % self.pgpy._key.keymaterial.s)
self.ed25519_seed_bytes = long_to_bytes(self.pgpy._key.keymaterial.s) self.ed25519_seed_bytes = long_to_bytes(self.pgpy._key.keymaterial.s)
else: else:
raise NotImplementedError(f"Getting seed from {self.pgpy_key_type} key is not implemented") raise NotImplementedError(f"getting seed from {self.pgpy_key_type} key is not implemented")
except Exception as e: except Exception as e:
log.error(f'Unable to get ed25519 seed bytes from pgpy: {e}') log.error(f'Unable to get ed25519 seed bytes from pgpy: {e}')
self._cleanup() self._cleanup()
exit(2) exit(2)
log.debug("keygen.ed25519_seed_bytes=%s" % self.ed25519_seed_bytes) 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 jwk_from_ed25519(self):
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)
def jwk_from_json(self, json):
log.debug("keygen.jwk_from_json()")
try:
self.jwk = jwk.JWK.from_json(json)
except Exception as e:
log.error(f'Unable to get jwk from json: {e}')
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: try:
self.ed25519_secret_pem_pkcs8 = ed25519.Ed25519PrivateKey.from_private_bytes(self.ed25519_seed_bytes).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: except Exception as e:
log.error(f'Unable to get pem pkcs8 from ed25519: {e}') log.error(f'Unable to get pem pkcs8 from ed25519: {e}')
self._cleanup() self._cleanup()
@ -690,9 +786,10 @@ class keygen:
log.debug("keygen.gpg_seckey.last_update=%s" % self.gpg_seckey.last_update) log.debug("keygen.gpg_seckey.last_update=%s" % self.gpg_seckey.last_update)
if self.password: if self.password:
self.gpg.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK) self.gpg.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
self.pgp_secret_armored = self.gpg.key_export_secret(self.gpg_seckey.fpr) self.pgp_public_armor = self.gpg.key_export(self.gpg_seckey.fpr)
log.debug("keygen.pgp_secret_armored=%s" % self.pgp_secret_armored) self.pgp_secret_armor = self.gpg.key_export_secret(self.gpg_seckey.fpr)
if not self.pgp_secret_armored: 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!""") 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)
@ -700,7 +797,7 @@ class keygen:
# remove CryptographyDeprecationWarning about deprecated # remove CryptographyDeprecationWarning about deprecated
# SymmetricKeyAlgorithm IDEA, CAST5 and Blowfish (PGPy v0.5.4) # SymmetricKeyAlgorithm IDEA, CAST5 and Blowfish (PGPy v0.5.4)
warnings.simplefilter('ignore') warnings.simplefilter('ignore')
self.pgpy, _ = pgpy.PGPKey.from_blob(self.pgp_secret_armored) self.pgpy, _ = pgpy.PGPKey.from_blob(self.pgp_secret_armor)
except Exception as e: except Exception as e:
log.error(f'Unable to get pgpy from gpg: {e}') log.error(f'Unable to get pgpy from gpg: {e}')
self._cleanup() self._cleanup()

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

@ -4,6 +4,7 @@ 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}/username.ewif" EWIF_FILE="${SHELLSPEC_TMPBASE}/username.ewif"
JWK_FILE="${SHELLSPEC_TMPBASE}/username.jwk"
NACL_FILE="${SHELLSPEC_TMPBASE}/username.nacl" NACL_FILE="${SHELLSPEC_TMPBASE}/username.nacl"
PB2_FILE="${SHELLSPEC_TMPBASE}/username.pb2" PB2_FILE="${SHELLSPEC_TMPBASE}/username.pb2"
PEM_FILE="${SHELLSPEC_TMPBASE}/username.pem" PEM_FILE="${SHELLSPEC_TMPBASE}/username.pem"
@ -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
@ -166,6 +167,15 @@ Describe 'keygen'
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"
@ -197,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"'
@ -277,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