diff --git a/.shellspec b/.shellspec new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile index 1cb0380..b970996 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ --include $(if $(MYOS),$(MYOS),../myos)/make/include.mk +MYOS ?= ../myos +-include $(MYOS)/make/include.mk PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin @@ -14,7 +15,7 @@ install: install dpgpid "$(BINDIR)/dpgpid" shellcheck-%: - shellcheck $*/* + shellcheck $*/*.sh shellspec-%: shellspec -f tap $* diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4ea12f8..8fb66c5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,7 +10,6 @@ services: - USER=${USER} context: .. dockerfile: docker/dpgpid/Dockerfile - command: tail -f /dev/null image: ${DOCKER_REPOSITORY}/dpgpid:${DOCKER_IMAGE_TAG} networks: - private diff --git a/docker/dpgpid/Dockerfile b/docker/dpgpid/Dockerfile index de17bd2..1375a06 100644 --- a/docker/dpgpid/Dockerfile +++ b/docker/dpgpid/Dockerfile @@ -9,44 +9,51 @@ ARG PYTHON_RELEASE=3.10 WORKDIR /opt/dpgpid COPY requirements.txt ./ -RUN apk add --no-cache --virtual .build-deps \ - g++ \ +RUN apk upgrade --no-cache \ + && apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ --virtual .build-deps \ + build-base \ libffi-dev \ protobuf \ + py3-gpgme \ + swig \ && /usr/local/bin/python${PYTHON_RELEASE} -m venv ./ \ && ./bin/pip${PYTHON_RELEASE} install -U pip wheel \ && ./bin/pip${PYTHON_RELEASE} install -r ./requirements.txt \ && wget https://github.com/libp2p/go-libp2p-core/raw/master/crypto/pb/crypto.proto \ && protoc --python_out=./lib/python${PYTHON_RELEASE}/site-packages/ crypto.proto \ + && cp -a /usr/lib/python${PYTHON_RELEASE}/site-packages/gpg ./lib/python${PYTHON_RELEASE}/site-packages/ \ && rm -rf /root/.cache ./build ./crypto.proto \ && apk del --no-network .build-deps \ - && find ./lib -type f -executable \ - -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ - | tr ',' '\n' \ - | sort -u \ - | awk 'system("[ -e /lib/"$1" -o -e /usr/lib/"$1" -o -e /opt/dpgpid/lib/python'"${PYTHON_RELEASE}"'/site-packages/*/"$1" ]") == 0 { next } { print "so:" $1 }' \ - | xargs -rt apk add --no-cache + && find ./lib -type f -executable -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + |tr ',' '\n' \ + |sort -u \ + |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/ -RUN apk add --repository https://dl-cdn.alpinelinux.org/alpine/edge/testing \ - envsubst \ - && apk add --no-cache \ +RUN apk add --no-cache \ bash \ ca-certificates \ + gettext \ + gpg \ + gpg-agent \ libc6-compat \ libsodium \ make \ - gpg \ && OS="$(echo ${OPERATING_SYSTEM} |awk '{print tolower($0)}')"; \ ARCH="$(echo ${PROCESSOR_ARCHITECTURE})"; \ - wget -qO - https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.${OS}.${ARCH}.tar.xz |tar --strip-components 1 -C /usr/local/bin -xJf - \ - && mkdir -p /usr/local/lib/shellspec \ - && wget -qO - https://github.com/shellspec/shellspec/archive/latest.tar.gz |tar --strip-components 1 -C /usr/local/lib/shellspec -xzf - \ - && ln -s /usr/local/lib/shellspec/shellspec /usr/local/bin/shellspec + wget -qO - https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.${OS}.${ARCH}.tar.xz \ + |tar --strip-components 1 -C ./bin -xJf - \ + && mkdir -p /opt/shellspec \ + && wget -qO - https://github.com/shellspec/shellspec/archive/refs/heads/master.tar.gz \ + |tar --strip-components 1 -C /opt/shellspec -xzf - \ + && ln -s /opt/shellspec/shellspec ./bin/shellspec -COPY --from=ipfs/go-ipfs:v0.13.0-rc1 /usr/local/bin/ipfs /usr/local/bin/ +COPY --from=ipfs/go-ipfs:v0.13.0-rc1 /usr/local/bin/ipfs ./bin/ COPY README.md ./ COPY COPYING ./ COPY Makefile ./ +COPY .shellspec ./ +COPY specs/ ./specs/ COPY dpgpid ./bin/dpgpid COPY gpgkey ./bin/gpgkey diff --git a/gpgkey b/gpgkey index 7fe3717..98496e2 100755 --- a/gpgkey +++ b/gpgkey @@ -1,10 +1,14 @@ #!/usr/bin/env python3 # link: https://git.p2p.legal/aya/dpgpid/ -# desc: gpgkey converts ed25519 gpg keys to match duniter and ipfs format +# desc: gpgkey converts ed25519 gpg keys to ed25519 duniter and ipfs keys # Copyleft 2022 Yann Autissier # all crypto science belongs to Pascal Engélibert # coming from files available at https://git.p2p.legal/qo-op/Astroport.ONE/tools +# gpgme stuff has been provided by Ben McGinnes +# and comes from http://files.au.adversary.org/crypto/gpgme-python-howto.html +# gpg key extraction is taken from work of Simon Vareille available at +# https://gist.github.com/SimonVareille/fda49baf5f3e15b5c88e25560aeb2822 # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -27,9 +31,13 @@ import crypto_pb2 import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519 from cryptography.hazmat.primitives import serialization import duniterpy.key +import gpg +import nacl.bindings +import pgpy import logging as log import os import re +import struct import sys import time @@ -80,8 +88,7 @@ class gpgkey: help="duniter|ipfs", nargs="?", ) - self.parser.add_argument( - 'username', + self.parser.add_argument( 'username', nargs="?", ) self.parser.add_argument( @@ -90,53 +97,113 @@ class gpgkey: ) def _check_args(self): - log.debug("func gpgkey._check_args(self)") - log.debug("var self.command: %s" % self.command) - log.debug("var self.username: %s" % self.username) - log.debug("var self.password: %s" % self.password) + log.debug("def gpgkey._check_args(self)") + log.debug("self.command=%s" % self.command) + log.debug("self.username=%s" % self.username) + log.debug("self.password=%s" % self.password) if self.command: if self.input is None: if self.password is None or self.username is None: self.parser.error(f"{self.command} requires an input file or username and password args") def _invalid_command(self): - log.debug("func gpgkey._invalid_command(self)") + log.debug("def gpgkey._invalid_command(self)") self.parser.error(f"{self.command} is not a valid command.") def _load_config(self): - log.debug("func gpgkey._load_config(self)") + log.debug("def gpgkey._load_config(self)") self.config = configparser.RawConfigParser() config_dir = os.path.join(os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), 'dpgpid') - log.debug("var config_dir: %s" % config_dir) + log.debug("config_dir=%s" % config_dir) self.config.read( [config_dir + '/gpgkey.conf'] ) + def base58_from_ed25519(self): + log.debug("def gpgkey.base58_from_ed25519(self)") + self.base58_public_key = base58.b58encode(self.ed25519_public_key).decode('ascii') + self.base58_secret_key = base58.b58encode(self.ed25519_secret_key).decode('ascii') + log.debug("self.base58_public_key=%s" % self.base58_public_key) + log.debug("self.base58_secret_key=%s" % self.base58_secret_key) + def do_duniter(self): - log.debug("func gpgkey.do_duniter(self)") + log.debug("def gpgkey.do_duniter(self)") self.duniterpy_from_salt_and_password() - print(self.duniterpy.pubkey) - if self.output is not None: - self.duniterpy.save_pubsec_file(self.output) + self.ed25519_from_duniterpy() + self.base58_from_ed25519() + if self.output is None: + print("pub: %s" % self.base58_public_key) + print("sec: %s" % self.base58_secret_key) + else: + with open(self.output, "w") as fh: + fh.write(f"""Type: PubSec +Version: 1 +pub: {self.base58_public_key} +sec: {self.base58_secret_key} +""" + ) os.chmod(self.output, 0o600) def do_ipfs(self): - log.info("func do_ipfs(self)") + log.debug("def gpgkey.do_ipfs(self)") if self.input is None: self.duniterpy_from_salt_and_password() - self.ipfs_base58_shared_key=self.duniterpy.pubkey - self.ipfs_base58_secure_key=base58.b58encode(self.duniterpy.sk) + self.ed25519_from_duniterpy() + self.base58_from_ed25519() else: for line in open(self.input, "r"): if re.search("pub", line): - self.ipfs_base58_shared_key=line.replace('\n','').split(': ')[1] + self.base58_public_key = line.replace('\n','').split(': ')[1] elif re.search("sec", line): - self.ipfs_base58_secure_key=line.replace('\n','').split(': ')[1] + self.base58_secret_key = line.replace('\n','').split(': ')[1] + self.ed25519_from_base58() + self.ipfs_from_ed25519() + if self.output is None: + print("PeerID: %s" % self.ipfs_peerid) + print("PrivKEY: %s" % self.ipfs_privkey) + else: + with open(self.output, "w") as fh: + fh.write(f"""Type: PubSec +Version: 1 +pub: {self.base58_public_key} +sec: {self.base58_secret_key} +PeerID: {self.ipfs_peerid} +PrivKEY: {self.ipfs_privkey} +""" + ) + os.chmod(self.output, 0o600) - self.ipfs_from_ed25519_key() - print('PeerID={}'.format(self.ipfs_peerid)) - print('PrivKEY={}'.format(self.ipfs_privkey)) + def do_pgp(self): + log.debug("def gpgkey.do_pgp(self)") + keys = self.gpg.keylist(pattern=self.username) + pubkeys = list(self.gpg.keylist(pattern=None, secret=False)) + seckeys = list(self.gpg.keylist(pattern=None, secret=True)) + self.armored_pgp_public_key = self.gpg.key_export(self.username) + self.armored_pgp_secret_key = self.gpg.key_export_secret(self.username) + log.debug("self.armored_pgp_public_key=%s" % self.armored_pgp_public_key) + log.debug("self.armored_pgp_secret_key=%s" % self.armored_pgp_secret_key) + self.pgpy, _ = pgpy.PGPKey.from_blob(self.armored_pgp_secret_key) + log.debug("self.pgpy.fingerprint.keyid=%s" % self.pgpy.fingerprint.keyid) + self.ed25519_from_pgpy() + self.base58_from_ed25519() + self.ipfs_from_ed25519() + if self.output is None: + print("pub: %s" % self.base58_public_key) + print("sec: %s" % self.base58_secret_key) + print("PeerID: %s" % self.ipfs_peerid) + print("PrivKEY: %s" % self.ipfs_privkey) + else: + with open(self.output, "w") as fh: + fh.write(f"""Type: PubSec +Version: 1 +pub: {self.base58_public_key} +sec: {self.base58_secret_key} +PeerID: {self.ipfs_peerid} +PrivKEY: {self.ipfs_privkey} +""" + ) + os.chmod(self.output, 0o600) def duniterpy_from_salt_and_password(self): - log.debug("func gpgkey.duniterpy_from_salt_and_password(self)") + log.debug("def gpgkey.duniterpy_from_salt_and_password(self)") scrypt_params = duniterpy.key.scrypt_params.ScryptParams( int(self.config.get('scrypt', 'n')) if self.config.has_option('scrypt', 'n') else 4096, int(self.config.get('scrypt', 'r')) if self.config.has_option('scrypt', 'r') else 16, @@ -149,14 +216,33 @@ class gpgkey: scrypt_params ) - def ipfs_from_ed25519_key(self): - log.info("func ipfs_from_ed25519_key(self)") + def ed25519_from_base58(self): + log.debug("def gpgkey.ed25519_from_base58(self)") + self.ed25519_public_key = base58.b58decode(self.base58_public_key) + self.ed25519_secret_key = base58.b58decode(self.base58_secret_key) + log.debug("self.ed25519_public_key=%s" % self.ed25519_public_key) + log.debug("self.ed25519_secret_key=%s" % self.ed25519_secret_key) + + def ed25519_from_duniterpy(self): + log.debug("def gpgkey.ed25519_from_duniterpy(self)") + self.ed25519_public_key = base58.b58decode(self.duniterpy.pubkey) + self.ed25519_secret_key = self.duniterpy.sk + log.debug("self.ed25519_public_key=%s" % self.ed25519_public_key) + log.debug("self.ed25519_secret_key=%s" % self.ed25519_secret_key) + + def ed25519_from_pgpy(self): + log.debug("def gpgkey.ed25519_from_pgpy(self)") + self.pgpy_key_seed() + self.ed25519_public_key, self.ed25519_secret_key = nacl.bindings.crypto_sign_seed_keypair(self.pgpy_key_seed) + log.debug("self.ed25519_public_key=%s" % self.ed25519_public_key) + log.debug("self.ed25519_secret_key=%s" % self.ed25519_secret_key) + + def ipfs_from_ed25519(self): + log.debug("def ipfs_from_ed25519(self)") # Decoding keys - decoded_shared = base58.b58decode(self.ipfs_base58_shared_key) - decoded_secure = base58.b58decode(self.ipfs_base58_secure_key) - ipfs_shared = ed25519.Ed25519PublicKey.from_public_bytes(decoded_shared) - ipfs_secure = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_secure[:32]) + ipfs_shared = ed25519.Ed25519PublicKey.from_public_bytes(self.ed25519_public_key) + ipfs_secure = ed25519.Ed25519PrivateKey.from_private_bytes(self.ed25519_secret_key[:32]) ipfs_shared_bytes = ipfs_shared.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) ipfs_secure_bytes = ipfs_secure.private_bytes(encoding=serialization.Encoding.Raw, @@ -165,7 +251,6 @@ class gpgkey: # Formulating PeerID ipfs_pid = base58.b58encode(b'\x00$\x08\x01\x12 ' + ipfs_shared_bytes) - PeerID = ipfs_pid.decode('ascii') self.ipfs_peerid = ipfs_pid.decode('ascii') # Serializing private key in IPFS-native mode, the private key contains public one @@ -173,8 +258,62 @@ class gpgkey: #pkey.Type = crypto_pb2.KeyType.Ed25519 pkey.Type = 1 pkey.Data = ipfs_secure_bytes + ipfs_shared_bytes - PrivKey = base64.b64encode(pkey.SerializeToString()).decode('ascii') self.ipfs_privkey = base64.b64encode(pkey.SerializeToString()).decode('ascii') + log.debug("self.ipfs_peerid=%s" % self.ipfs_peerid) + log.debug("self.ipfs_privkey=%s" % self.ipfs_privkey) + + def pgpy_key_flags(self): + log.debug("def gpgkey.pgpy_key_flags(self)") + flags = [] + strs = {pgpy.constants.KeyFlags.Certify : 'C', + pgpy.constants.KeyFlags.Sign : 'S', + pgpy.constants.KeyFlags.EncryptCommunications : 'E', + pgpy.constants.KeyFlags.Authentication : 'A'} + for sig in self.pgpy.self_signatures: + if not sig.is_expired: + flags += sig.key_flags + self.pgpy_key_flags = "".join(strs.get(flag, '') for flag in flags) + + def pgpy_key_seed(self): + log.debug("def gpgkey.pgpy_key_seed(self)") + self.pgpy_key_type() + # todo : unlock password protected key + # todo : choose a custom seed for RSA + assert self.pgpy.is_unlocked + if self.pgpy_key_type == 'RSA.disabled': + p = long_to_bytes(self.pgpy._key.keymaterial.p) + q = long_to_bytes(self.pgpy._key.keymaterial.q) + self.pgpy_key_value = "".join([f"{c:02x}" for c in p]) + "".join([f"{c:02x}" for c in q]) + self.pgpy_key_size = (len(p) + len(q)) * 8 + log.debug("self.pgpy_key_value=%s" % self.pgpy_key_value) + log.debug("self.pgpy_key_size=%s" % self.pgpy_key_size) + elif self.pgpy_key_type in ('ECDSA', 'EdDSA', 'ECDH'): + self.pgpy_key_seed = long_to_bytes(self.pgpy._key.keymaterial.s) + self.pgpy_key_value = "".join([f"{c:02x}" for c in self.pgpy_key_seed]) + self.pgpy_key_size = len(self.pgpy_key_seed)*8 + log.debug("self.pgpy_key_seed=%s" % self.pgpy_key_seed) + log.debug("self.pgpy_key_value=%s" % self.pgpy_key_value) + log.debug("self.pgpy_key_size=%s" % self.pgpy_key_size) + else: + raise NotImplementedError(f"Get seed from {self.pgpy_key_type} key is not supported") + + def pgpy_key_type(self): + log.debug("def gpgkey.pgpy_key_type(self)") + if isinstance(self.pgpy._key.keymaterial, pgpy.packet.fields.RSAPriv): + self.pgpy_key_type = 'RSA' + elif isinstance(self.pgpy._key.keymaterial, pgpy.packet.fields.DSAPriv): + self.pgpy_key_type = 'DSA' + elif isinstance(self.pgpy._key.keymaterial, pgpy.packet.fields.ElGPriv): + self.pgpy_key_type = 'ElGamal' + elif isinstance(self.pgpy._key.keymaterial, pgpy.packet.fields.ECDSAPriv): + self.pgpy_key_type = 'ECDSA' + elif isinstance(self.pgpy._key.keymaterial, pgpy.packet.fields.EdDSAPriv): + self.pgpy_key_type = 'EdDSA' + elif isinstance(self.pgpy._key.keymaterial, pgpy.packet.fields.ECDHPriv): + self.pgpy_key_type = 'ECDH' + else: + self.pgpy_key_type = 'undefined' + log.debug("self.pgpy_key_type=%s" % self.pgpy_key_type) def run(self, argv): args = self.parser.parse_args(argv) @@ -200,9 +339,59 @@ class gpgkey: self._check_args() self._load_config() + # self.gpg = gpg.Context(armor=True, offline=True, homedir=GNUPGHOME) + self.gpg = gpg.Context(armor=True, offline=True) method = getattr(self, f'do_{self.command}', self._invalid_command) return method() +# long_to_bytes comes from PyCrypto, which is released into Public Domain +# https://github.com/dlitz/pycrypto/blob/master/lib/Crypto/Util/number.py + +def bytes_to_long(s): + """bytes_to_long(string) : long + Convert a byte string to a long integer. + This is (essentially) the inverse of long_to_bytes(). + """ + acc = 0 + unpack = struct.unpack + length = len(s) + if length % 4: + extra = (4 - length % 4) + s = b'\000' * extra + s + length = length + extra + for i in range(0, length, 4): + acc = (acc << 32) + unpack('>I', s[i:i+4])[0] + return acc + +def long_to_bytes(n, blocksize=0): + """long_to_bytes(n:long, blocksize:int) : string + Convert a long integer to a byte string. + If optional blocksize is given and greater than zero, pad the front of the + byte string with binary zeros so that the length is a multiple of + blocksize. + """ + # after much testing, this algorithm was deemed to be the fastest + s = b'' + n = int(n) + pack = struct.pack + while n > 0: + s = pack('>I', n & 0xffffffff) + s + n = n >> 32 + # strip off leading zeros + for i in range(len(s)): + if s[i] != b'\000'[0]: + break + else: + # only happens when n == 0 + s = b'\000' + i = 0 + s = s[i:] + # add back some pad bytes. this could be done more efficiently w.r.t. the + # de-padding being done above, but sigh... + if blocksize > 0 and len(s) % blocksize: + s = (blocksize - len(s) % blocksize) * b'\000' + s + return s + def main(argv=None): if argv is None: argv = sys.argv[1:] diff --git a/requirements.txt b/requirements.txt index 95d3cbb..7b22c4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -argparse base58 -configparser cryptography duniterpy google +pgpy protobuf +pynacl diff --git a/specs/dpgpid_spec.sh b/specs/dpgpid_spec.sh index a4ed1cd..eb13467 100644 --- a/specs/dpgpid_spec.sh +++ b/specs/dpgpid_spec.sh @@ -12,26 +12,18 @@ dpgpid() { } Describe 'Dependency' - Describe 'awk' + Describe 'gpg:' It 'is available' - When run which awk - The output should include "/awk" + When run gpg --help + The output should include "gpg" The status should be success The stderr should equal "" End End - Describe 'gpg' + Describe 'ipfs:' It 'is available' - When run which gpg - The output should include "/gpg" - The status should be success - The stderr should equal "" - End - End - Describe 'ipfs' - It 'is available' - When run which ipfs - The output should include "/ipfs" + When run ipfs --help + The output should include "ipfs" The status should be success The stderr should equal "" End @@ -39,7 +31,7 @@ Describe 'Dependency' End Describe 'dpgpid' - Describe '--help' + Describe '--help:' It 'prints help' When run dpgpid --help The output should include 'Usage:' @@ -47,7 +39,7 @@ Describe 'dpgpid' The stderr should equal "" End End - Describe '--version' + Describe '--version:' It 'prints version' When run dpgpid --version The output should include 'v0.0.1' diff --git a/specs/gpgkey_spec.sh b/specs/gpgkey_spec.sh index da00919..c0dc89b 100644 --- a/specs/gpgkey_spec.sh +++ b/specs/gpgkey_spec.sh @@ -12,10 +12,10 @@ gpgkey() { } Describe 'Dependency' - Describe 'python3' + Describe 'python3:' It 'is available' - When run which python3 - The output should include "/python3" + When run python3 --help + The output should include "python3" The status should be success The stderr should equal "" End @@ -23,7 +23,7 @@ Describe 'Dependency' End Describe 'gpgkey' - Describe '--help' + Describe '--help:' It 'prints help' When run gpgkey --help The output should include 'usage:' @@ -31,7 +31,7 @@ Describe 'gpgkey' The stderr should equal "" End End - Describe '--version' + Describe '--version:' It 'prints version' When run gpgkey --version The output should include 'v0.0.1' @@ -39,35 +39,101 @@ Describe 'gpgkey' The stderr should equal "" End End - Describe 'duniter username password -o /tmp/test_gpgkey' - rm -f /tmp/test_gpgkey - It 'prints duniter public key and write duniter keys to file /tmp/test_gpgkey for user username' - When run gpgkey duniter username password -o /tmp/test_gpgkey - The output should eq '4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG' - The path '/tmp/test_gpgkey' should exist - The contents of file '/tmp/test_gpgkey' should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG' - The contents of file '/tmp/test_gpgkey' should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC' + Describe 'duniter username password:' + It 'prints duniter keys for user username' + When run gpgkey duniter username password + The output should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG' + The output should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC' The status should be success The stderr should equal "" End End - Describe 'ipfs -i /tmp/test_gpgkey' - It 'prints ipfs PeerID and PrivKEY for duniter keys in file /tmp/test_gpgkey' - When run gpgkey ipfs -i /tmp/test_gpgkey - The output should include 'PeerID=12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC' - The output should include 'PrivKEY=CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas=' + Describe 'duniter username password -o /tmp/test_gpgkey_duniter.pubsec:' + rm -f /tmp/test_gpgkey_duniter.pubsec + It 'writes duniter keys to file for user username' + When run gpgkey duniter username password -o /tmp/test_gpgkey_duniter.pubsec + The path '/tmp/test_gpgkey_duniter.pubsec' should exist + The contents of file '/tmp/test_gpgkey_duniter.pubsec' should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG' + The contents of file '/tmp/test_gpgkey_duniter.pubsec' should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC' The status should be success The stderr should equal "" End - rm -f /tmp/test_gpgkey End - Describe 'ipfs username password' - It 'prints ipfs PeerID and PrivKEY for user username' + Describe 'ipfs -i /tmp/test_gpgkey_duniter.pubsec:' + It 'prints ipfs keys for duniter keys read in pubsec file' + When run gpgkey ipfs -i /tmp/test_gpgkey_duniter.pubsec + The output should include 'PeerID: 12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC' + The output should include 'PrivKEY: CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas=' + The status should be success + The stderr should equal "" + End + End + Describe 'ipfs -i /tmp/test_gpgkey_duniter.pubsec -o /tmp/test_gpgkey_ipfs.pubsec:' + It 'writes duniter and ipfs keys to file for duniter keys read in pubsec file' + When run gpgkey ipfs -i /tmp/test_gpgkey_duniter.pubsec -o /tmp/test_gpgkey_ipfs.pubsec + The path '/tmp/test_gpgkey_ipfs.pubsec' should exist + The contents of file '/tmp/test_gpgkey_ipfs.pubsec' should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG' + The contents of file '/tmp/test_gpgkey_ipfs.pubsec' should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC' + The contents of file '/tmp/test_gpgkey_ipfs.pubsec' should include 'PeerID: 12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC' + The contents of file '/tmp/test_gpgkey_ipfs.pubsec' should include 'PrivKEY: CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas=' + The status should be success + The stderr should equal "" + End + rm -f /tmp/test_gpgkey_duniter.pubsec /tmp/test_gpgkey_ipfs.pubsec + End + Describe 'ipfs username password:' + It 'prints ipfs keys for user username' When run gpgkey ipfs username password - The output should include 'PeerID=12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC' - The output should include 'PrivKEY=CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas=' + The output should include 'PeerID: 12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC' + The output should include 'PrivKEY: CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas=' The status should be success The stderr should equal "" End End + Describe 'ipfs username password -o /tmp/test_gpgkey_ipfs.pubsec:' + It 'writes duniter and ipfs keys to file for user username' + When run gpgkey ipfs username password -o /tmp/test_gpgkey_ipfs.pubsec + The path '/tmp/test_gpgkey_ipfs.pubsec' should exist + The contents of file '/tmp/test_gpgkey_ipfs.pubsec' should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG' + The contents of file '/tmp/test_gpgkey_ipfs.pubsec' should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC' + The contents of file '/tmp/test_gpgkey_ipfs.pubsec' should include 'PeerID: 12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC' + The contents of file '/tmp/test_gpgkey_ipfs.pubsec' should include 'PrivKEY: CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas=' + The status should be success + The stderr should equal "" + End + rm -f /tmp/test_gpgkey_ipfs.pubsec + End + Describe 'pgp username password:' + gpg --import --quiet specs/username.asc + gpg --import --quiet specs/username.pub + It 'creates a gpg key for user username' + Skip "You should implement it !" + When run gpgkey --gen pgp username password + The status should be success + End + End + Describe 'pgp username password:' + It 'prints duniter and ipfs keys for gpg key matching username' + When run gpgkey pgp username password + The output should include 'pub: 2g5UL2zhkn5i7oNYDpWo3fBuWvRYVU1AbMtdVmnGzPNv' + The output should include 'sec: 5WtYFfA26nTfG496gAKhkrLYUMMnwXexmE1E8Q7PvtQEyscHfirsdMzW34zDp7WEkt3exNEVwoG4ajZYrm62wpi2' + The output should include 'PeerID: 12D3KooWBVSe5AaQwgMCXgsxrRG8pTGk1FUBXA5eYxFeskwAtL6r' + The output should include 'PrivKEY: CAESQOHXwPgzoiDca1ZnvhU/W3zdogZXulkoErnUsqt+ut82GN5k4MIbVvz2m6Vq0ij9fQFPNUz+ZZdv2D31K6mzBQc=' + The status should be success + The stderr should equal "" + End + End + Describe 'pgp username password -o /tmp/test_gpgkey_pgp.pubsec:' + It 'writes duniter and ipfs keys to file for gpg key matching username' + When run gpgkey pgp username password -o /tmp/test_gpgkey_pgp.pubsec + The path '/tmp/test_gpgkey_pgp.pubsec' should exist + The contents of file '/tmp/test_gpgkey_pgp.pubsec' should include 'pub: 2g5UL2zhkn5i7oNYDpWo3fBuWvRYVU1AbMtdVmnGzPNv' + The contents of file '/tmp/test_gpgkey_pgp.pubsec' should include 'sec: 5WtYFfA26nTfG496gAKhkrLYUMMnwXexmE1E8Q7PvtQEyscHfirsdMzW34zDp7WEkt3exNEVwoG4ajZYrm62wpi2' + The contents of file '/tmp/test_gpgkey_pgp.pubsec' should include 'PeerID: 12D3KooWBVSe5AaQwgMCXgsxrRG8pTGk1FUBXA5eYxFeskwAtL6r' + The contents of file '/tmp/test_gpgkey_pgp.pubsec' should include 'PrivKEY: CAESQOHXwPgzoiDca1ZnvhU/W3zdogZXulkoErnUsqt+ut82GN5k4MIbVvz2m6Vq0ij9fQFPNUz+ZZdv2D31K6mzBQc=' + The status should be success + The stderr should equal "" + End + rm -f /tmp/test_gpgkey_pgp.pubsec + End End diff --git a/specs/username.asc b/specs/username.asc new file mode 100644 index 0000000..0ccdd87 --- /dev/null +++ b/specs/username.asc @@ -0,0 +1,9 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEAAAAABYJKwYBBAHaRw8BAQdAGN5k4MIbVvz2m6Vq0ij9fQFPNUz+ZZdv2D31 +K6mzBQcAAQDh18D4M6Ig3GtWZ74VP1t83aIGV7pZKBK51LKrfrrfNhCzzQh1c2Vy +bmFtZcJhBBMWCAATBQIAAAAACRAHnlv0chlE+wIbAwAAlzwBAIbSTs0OhkTDykx1 +bqogkOG8lKRY8vVARJnehvcOWf7QAP9GpuySQHL041bDdkBJVXeofSmp3QfR6ZxG +D0VoQE2NCQ== +=7FCh +-----END PGP PRIVATE KEY BLOCK----- diff --git a/specs/username.pub b/specs/username.pub new file mode 100644 index 0000000..78a1862 --- /dev/null +++ b/specs/username.pub @@ -0,0 +1,8 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEAAAAABYJKwYBBAHaRw8BAQdAGN5k4MIbVvz2m6Vq0ij9fQFPNUz+ZZdv2D31 +K6mzBQfNCHVzZXJuYW1lwmEEExYIABMFAmKD2B0JEAeeW/RyGUT7AhsDAADy8AD/ +QvC5UvW8TVUdbCd0EQvPjlfVzRauBlxQP4oy+vjnItcA/R1RgGS0D9zAGHC6CRHt +AwxzDz3dIpKQAJxxliD8ZO0G +=51r3 +-----END PGP PUBLIC KEY BLOCK-----