wip: export keys from gpg
This commit is contained in:
parent
48887c6102
commit
5bab667215
5
Makefile
5
Makefile
|
@ -1,4 +1,5 @@
|
||||||
-include $(if $(MYOS),$(MYOS),../myos)/make/include.mk
|
MYOS ?= ../myos
|
||||||
|
-include $(MYOS)/make/include.mk
|
||||||
|
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
BINDIR ?= $(PREFIX)/bin
|
BINDIR ?= $(PREFIX)/bin
|
||||||
|
@ -14,7 +15,7 @@ install:
|
||||||
install dpgpid "$(BINDIR)/dpgpid"
|
install dpgpid "$(BINDIR)/dpgpid"
|
||||||
|
|
||||||
shellcheck-%:
|
shellcheck-%:
|
||||||
shellcheck $*/*
|
shellcheck $*/*.sh
|
||||||
|
|
||||||
shellspec-%:
|
shellspec-%:
|
||||||
shellspec -f tap $*
|
shellspec -f tap $*
|
||||||
|
|
|
@ -10,7 +10,6 @@ services:
|
||||||
- USER=${USER}
|
- USER=${USER}
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: docker/dpgpid/Dockerfile
|
dockerfile: docker/dpgpid/Dockerfile
|
||||||
command: tail -f /dev/null
|
|
||||||
image: ${DOCKER_REPOSITORY}/dpgpid:${DOCKER_IMAGE_TAG}
|
image: ${DOCKER_REPOSITORY}/dpgpid:${DOCKER_IMAGE_TAG}
|
||||||
networks:
|
networks:
|
||||||
- private
|
- private
|
||||||
|
|
|
@ -9,44 +9,51 @@ ARG PYTHON_RELEASE=3.10
|
||||||
|
|
||||||
WORKDIR /opt/dpgpid
|
WORKDIR /opt/dpgpid
|
||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
RUN apk add --no-cache --virtual .build-deps \
|
RUN apk upgrade --no-cache \
|
||||||
g++ \
|
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ --virtual .build-deps \
|
||||||
|
build-base \
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
protobuf \
|
protobuf \
|
||||||
|
py3-gpgme \
|
||||||
|
swig \
|
||||||
&& /usr/local/bin/python${PYTHON_RELEASE} -m venv ./ \
|
&& /usr/local/bin/python${PYTHON_RELEASE} -m venv ./ \
|
||||||
&& ./bin/pip${PYTHON_RELEASE} install -U pip wheel \
|
&& ./bin/pip${PYTHON_RELEASE} install -U pip wheel \
|
||||||
&& ./bin/pip${PYTHON_RELEASE} install -r ./requirements.txt \
|
&& ./bin/pip${PYTHON_RELEASE} install -r ./requirements.txt \
|
||||||
&& wget https://github.com/libp2p/go-libp2p-core/raw/master/crypto/pb/crypto.proto \
|
&& 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 \
|
&& 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 \
|
&& rm -rf /root/.cache ./build ./crypto.proto \
|
||||||
&& apk del --no-network .build-deps \
|
&& apk del --no-network .build-deps \
|
||||||
&& find ./lib -type f -executable \
|
&& find ./lib -type f -executable -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \
|
||||||
-exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \
|
|tr ',' '\n' \
|
||||||
| tr ',' '\n' \
|
|sort -u \
|
||||||
| 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 }' \
|
||||||
| 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 --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/
|
||||||
| xargs -rt apk add --no-cache
|
|
||||||
|
|
||||||
RUN apk add --repository https://dl-cdn.alpinelinux.org/alpine/edge/testing \
|
RUN apk add --no-cache \
|
||||||
envsubst \
|
|
||||||
&& apk add --no-cache \
|
|
||||||
bash \
|
bash \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
gettext \
|
||||||
|
gpg \
|
||||||
|
gpg-agent \
|
||||||
libc6-compat \
|
libc6-compat \
|
||||||
libsodium \
|
libsodium \
|
||||||
make \
|
make \
|
||||||
gpg \
|
|
||||||
&& OS="$(echo ${OPERATING_SYSTEM} |awk '{print tolower($0)}')"; \
|
&& 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 |tar --strip-components 1 -C /usr/local/bin -xJf - \
|
wget -qO - https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.${OS}.${ARCH}.tar.xz \
|
||||||
&& mkdir -p /usr/local/lib/shellspec \
|
|tar --strip-components 1 -C ./bin -xJf - \
|
||||||
&& wget -qO - https://github.com/shellspec/shellspec/archive/latest.tar.gz |tar --strip-components 1 -C /usr/local/lib/shellspec -xzf - \
|
&& mkdir -p /opt/shellspec \
|
||||||
&& ln -s /usr/local/lib/shellspec/shellspec /usr/local/bin/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 README.md ./
|
||||||
COPY COPYING ./
|
COPY COPYING ./
|
||||||
COPY Makefile ./
|
COPY Makefile ./
|
||||||
|
COPY .shellspec ./
|
||||||
|
COPY specs/ ./specs/
|
||||||
COPY dpgpid ./bin/dpgpid
|
COPY dpgpid ./bin/dpgpid
|
||||||
COPY gpgkey ./bin/gpgkey
|
COPY gpgkey ./bin/gpgkey
|
||||||
|
|
||||||
|
|
251
gpgkey
251
gpgkey
|
@ -1,10 +1,14 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# link: https://git.p2p.legal/aya/dpgpid/
|
# 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 <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>
|
||||||
# coming from files available at https://git.p2p.legal/qo-op/Astroport.ONE/tools
|
# 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
|
# 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
|
# 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
|
import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519
|
||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
import duniterpy.key
|
import duniterpy.key
|
||||||
|
import gpg
|
||||||
|
import nacl.bindings
|
||||||
|
import pgpy
|
||||||
import logging as log
|
import logging as log
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -80,8 +88,7 @@ class gpgkey:
|
||||||
help="duniter|ipfs",
|
help="duniter|ipfs",
|
||||||
nargs="?",
|
nargs="?",
|
||||||
)
|
)
|
||||||
self.parser.add_argument(
|
self.parser.add_argument( 'username',
|
||||||
'username',
|
|
||||||
nargs="?",
|
nargs="?",
|
||||||
)
|
)
|
||||||
self.parser.add_argument(
|
self.parser.add_argument(
|
||||||
|
@ -90,53 +97,113 @@ class gpgkey:
|
||||||
)
|
)
|
||||||
|
|
||||||
def _check_args(self):
|
def _check_args(self):
|
||||||
log.debug("func gpgkey._check_args(self)")
|
log.debug("def gpgkey._check_args(self)")
|
||||||
log.debug("var self.command: %s" % self.command)
|
log.debug("self.command=%s" % self.command)
|
||||||
log.debug("var self.username: %s" % self.username)
|
log.debug("self.username=%s" % self.username)
|
||||||
log.debug("var self.password: %s" % self.password)
|
log.debug("self.password=%s" % self.password)
|
||||||
if self.command:
|
if self.command:
|
||||||
if self.input is None:
|
if self.input is None:
|
||||||
if self.password is None or self.username 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")
|
self.parser.error(f"{self.command} requires an input file or username and password args")
|
||||||
|
|
||||||
def _invalid_command(self):
|
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.")
|
self.parser.error(f"{self.command} is not a valid command.")
|
||||||
|
|
||||||
def _load_config(self):
|
def _load_config(self):
|
||||||
log.debug("func gpgkey._load_config(self)")
|
log.debug("def gpgkey._load_config(self)")
|
||||||
self.config = configparser.RawConfigParser()
|
self.config = configparser.RawConfigParser()
|
||||||
config_dir = os.path.join(os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), 'dpgpid')
|
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'] )
|
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):
|
def do_duniter(self):
|
||||||
log.debug("func gpgkey.do_duniter(self)")
|
log.debug("def gpgkey.do_duniter(self)")
|
||||||
self.duniterpy_from_salt_and_password()
|
self.duniterpy_from_salt_and_password()
|
||||||
print(self.duniterpy.pubkey)
|
self.ed25519_from_duniterpy()
|
||||||
if self.output is not None:
|
self.base58_from_ed25519()
|
||||||
self.duniterpy.save_pubsec_file(self.output)
|
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)
|
os.chmod(self.output, 0o600)
|
||||||
|
|
||||||
def do_ipfs(self):
|
def do_ipfs(self):
|
||||||
log.info("func do_ipfs(self)")
|
log.debug("def gpgkey.do_ipfs(self)")
|
||||||
if self.input is None:
|
if self.input is None:
|
||||||
self.duniterpy_from_salt_and_password()
|
self.duniterpy_from_salt_and_password()
|
||||||
self.ipfs_base58_shared_key=self.duniterpy.pubkey
|
self.ed25519_from_duniterpy()
|
||||||
self.ipfs_base58_secure_key=base58.b58encode(self.duniterpy.sk)
|
self.base58_from_ed25519()
|
||||||
else:
|
else:
|
||||||
for line in open(self.input, "r"):
|
for line in open(self.input, "r"):
|
||||||
if re.search("pub", line):
|
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):
|
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()
|
def do_pgp(self):
|
||||||
print('PeerID={}'.format(self.ipfs_peerid))
|
log.debug("def gpgkey.do_pgp(self)")
|
||||||
print('PrivKEY={}'.format(self.ipfs_privkey))
|
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):
|
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(
|
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', '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,
|
int(self.config.get('scrypt', 'r')) if self.config.has_option('scrypt', 'r') else 16,
|
||||||
|
@ -149,14 +216,33 @@ class gpgkey:
|
||||||
scrypt_params
|
scrypt_params
|
||||||
)
|
)
|
||||||
|
|
||||||
def ipfs_from_ed25519_key(self):
|
def ed25519_from_base58(self):
|
||||||
log.info("func ipfs_from_ed25519_key(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
|
# Decoding keys
|
||||||
decoded_shared = base58.b58decode(self.ipfs_base58_shared_key)
|
ipfs_shared = ed25519.Ed25519PublicKey.from_public_bytes(self.ed25519_public_key)
|
||||||
decoded_secure = base58.b58decode(self.ipfs_base58_secure_key)
|
ipfs_secure = ed25519.Ed25519PrivateKey.from_private_bytes(self.ed25519_secret_key[:32])
|
||||||
ipfs_shared = ed25519.Ed25519PublicKey.from_public_bytes(decoded_shared)
|
|
||||||
ipfs_secure = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_secure[:32])
|
|
||||||
ipfs_shared_bytes = ipfs_shared.public_bytes(encoding=serialization.Encoding.Raw,
|
ipfs_shared_bytes = ipfs_shared.public_bytes(encoding=serialization.Encoding.Raw,
|
||||||
format=serialization.PublicFormat.Raw)
|
format=serialization.PublicFormat.Raw)
|
||||||
ipfs_secure_bytes = ipfs_secure.private_bytes(encoding=serialization.Encoding.Raw,
|
ipfs_secure_bytes = ipfs_secure.private_bytes(encoding=serialization.Encoding.Raw,
|
||||||
|
@ -165,7 +251,6 @@ class gpgkey:
|
||||||
|
|
||||||
# Formulating PeerID
|
# Formulating PeerID
|
||||||
ipfs_pid = base58.b58encode(b'\x00$\x08\x01\x12 ' + ipfs_shared_bytes)
|
ipfs_pid = base58.b58encode(b'\x00$\x08\x01\x12 ' + ipfs_shared_bytes)
|
||||||
PeerID = ipfs_pid.decode('ascii')
|
|
||||||
self.ipfs_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
|
# 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 = crypto_pb2.KeyType.Ed25519
|
||||||
pkey.Type = 1
|
pkey.Type = 1
|
||||||
pkey.Data = ipfs_secure_bytes + ipfs_shared_bytes
|
pkey.Data = ipfs_secure_bytes + ipfs_shared_bytes
|
||||||
PrivKey = base64.b64encode(pkey.SerializeToString()).decode('ascii')
|
|
||||||
self.ipfs_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):
|
def run(self, argv):
|
||||||
args = self.parser.parse_args(argv)
|
args = self.parser.parse_args(argv)
|
||||||
|
@ -200,9 +339,59 @@ class gpgkey:
|
||||||
|
|
||||||
self._check_args()
|
self._check_args()
|
||||||
self._load_config()
|
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)
|
method = getattr(self, f'do_{self.command}', self._invalid_command)
|
||||||
return method()
|
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):
|
def main(argv=None):
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
argparse
|
|
||||||
base58
|
base58
|
||||||
configparser
|
|
||||||
cryptography
|
cryptography
|
||||||
duniterpy
|
duniterpy
|
||||||
google
|
google
|
||||||
|
pgpy
|
||||||
protobuf
|
protobuf
|
||||||
|
pynacl
|
||||||
|
|
|
@ -12,26 +12,18 @@ dpgpid() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Describe 'Dependency'
|
Describe 'Dependency'
|
||||||
Describe 'awk'
|
Describe 'gpg:'
|
||||||
It 'is available'
|
It 'is available'
|
||||||
When run which awk
|
When run gpg --help
|
||||||
The output should include "/awk"
|
The output should include "gpg"
|
||||||
The status should be success
|
The status should be success
|
||||||
The stderr should equal ""
|
The stderr should equal ""
|
||||||
End
|
End
|
||||||
End
|
End
|
||||||
Describe 'gpg'
|
Describe 'ipfs:'
|
||||||
It 'is available'
|
It 'is available'
|
||||||
When run which gpg
|
When run ipfs --help
|
||||||
The output should include "/gpg"
|
The output should include "ipfs"
|
||||||
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"
|
|
||||||
The status should be success
|
The status should be success
|
||||||
The stderr should equal ""
|
The stderr should equal ""
|
||||||
End
|
End
|
||||||
|
@ -39,7 +31,7 @@ Describe 'Dependency'
|
||||||
End
|
End
|
||||||
|
|
||||||
Describe 'dpgpid'
|
Describe 'dpgpid'
|
||||||
Describe '--help'
|
Describe '--help:'
|
||||||
It 'prints help'
|
It 'prints help'
|
||||||
When run dpgpid --help
|
When run dpgpid --help
|
||||||
The output should include 'Usage:'
|
The output should include 'Usage:'
|
||||||
|
@ -47,7 +39,7 @@ Describe 'dpgpid'
|
||||||
The stderr should equal ""
|
The stderr should equal ""
|
||||||
End
|
End
|
||||||
End
|
End
|
||||||
Describe '--version'
|
Describe '--version:'
|
||||||
It 'prints version'
|
It 'prints version'
|
||||||
When run dpgpid --version
|
When run dpgpid --version
|
||||||
The output should include 'v0.0.1'
|
The output should include 'v0.0.1'
|
||||||
|
|
|
@ -12,10 +12,10 @@ gpgkey() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Describe 'Dependency'
|
Describe 'Dependency'
|
||||||
Describe 'python3'
|
Describe 'python3:'
|
||||||
It 'is available'
|
It 'is available'
|
||||||
When run which python3
|
When run python3 --help
|
||||||
The output should include "/python3"
|
The output should include "python3"
|
||||||
The status should be success
|
The status should be success
|
||||||
The stderr should equal ""
|
The stderr should equal ""
|
||||||
End
|
End
|
||||||
|
@ -23,7 +23,7 @@ Describe 'Dependency'
|
||||||
End
|
End
|
||||||
|
|
||||||
Describe 'gpgkey'
|
Describe 'gpgkey'
|
||||||
Describe '--help'
|
Describe '--help:'
|
||||||
It 'prints help'
|
It 'prints help'
|
||||||
When run gpgkey --help
|
When run gpgkey --help
|
||||||
The output should include 'usage:'
|
The output should include 'usage:'
|
||||||
|
@ -31,7 +31,7 @@ Describe 'gpgkey'
|
||||||
The stderr should equal ""
|
The stderr should equal ""
|
||||||
End
|
End
|
||||||
End
|
End
|
||||||
Describe '--version'
|
Describe '--version:'
|
||||||
It 'prints version'
|
It 'prints version'
|
||||||
When run gpgkey --version
|
When run gpgkey --version
|
||||||
The output should include 'v0.0.1'
|
The output should include 'v0.0.1'
|
||||||
|
@ -39,35 +39,101 @@ Describe 'gpgkey'
|
||||||
The stderr should equal ""
|
The stderr should equal ""
|
||||||
End
|
End
|
||||||
End
|
End
|
||||||
Describe 'duniter username password -o /tmp/test_gpgkey'
|
Describe 'duniter username password:'
|
||||||
rm -f /tmp/test_gpgkey
|
It 'prints duniter keys for user username'
|
||||||
It 'prints duniter public key and write duniter keys to file /tmp/test_gpgkey for user username'
|
When run gpgkey duniter username password
|
||||||
When run gpgkey duniter username password -o /tmp/test_gpgkey
|
The output should include 'pub: 4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG'
|
||||||
The output should eq '4YLU1xQ9jzb7LzC6d91VZrYTEKS9N2j93Nnvcee6wxZG'
|
The output should include 'sec: K5heSX4xGUPtRbxcZh6zbgaKbDv8FeVc9JuSNWtUs7C1oGNKqv7kQJ3DHdouTPzoW4duKKnuLQK8LbHKfN9fkjC'
|
||||||
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'
|
|
||||||
The status should be success
|
The status should be success
|
||||||
The stderr should equal ""
|
The stderr should equal ""
|
||||||
End
|
End
|
||||||
End
|
End
|
||||||
Describe 'ipfs -i /tmp/test_gpgkey'
|
Describe 'duniter username password -o /tmp/test_gpgkey_duniter.pubsec:'
|
||||||
It 'prints ipfs PeerID and PrivKEY for duniter keys in file /tmp/test_gpgkey'
|
rm -f /tmp/test_gpgkey_duniter.pubsec
|
||||||
When run gpgkey ipfs -i /tmp/test_gpgkey
|
It 'writes duniter keys to file for user username'
|
||||||
The output should include 'PeerID=12D3KooWDMhdm5yrvtrbkshXFjkqLedHieUnPioczy9wzdnzquHC'
|
When run gpgkey duniter username password -o /tmp/test_gpgkey_duniter.pubsec
|
||||||
The output should include 'PrivKEY=CAESQA+XqCWjRqCjNe9oU3QA796bEH+T+rxgyPQ/EkXvE2MvNJoTbvcP+m51+XwxrmWqHaOpI1ZD0USwLjqAmV8Boas='
|
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 status should be success
|
||||||
The stderr should equal ""
|
The stderr should equal ""
|
||||||
End
|
End
|
||||||
rm -f /tmp/test_gpgkey
|
|
||||||
End
|
End
|
||||||
Describe 'ipfs username password'
|
Describe 'ipfs -i /tmp/test_gpgkey_duniter.pubsec:'
|
||||||
It 'prints ipfs PeerID and PrivKEY for user username'
|
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
|
When run gpgkey 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 '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
|
End
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
xVgEAAAAABYJKwYBBAHaRw8BAQdAGN5k4MIbVvz2m6Vq0ij9fQFPNUz+ZZdv2D31
|
||||||
|
K6mzBQcAAQDh18D4M6Ig3GtWZ74VP1t83aIGV7pZKBK51LKrfrrfNhCzzQh1c2Vy
|
||||||
|
bmFtZcJhBBMWCAATBQIAAAAACRAHnlv0chlE+wIbAwAAlzwBAIbSTs0OhkTDykx1
|
||||||
|
bqogkOG8lKRY8vVARJnehvcOWf7QAP9GpuySQHL041bDdkBJVXeofSmp3QfR6ZxG
|
||||||
|
D0VoQE2NCQ==
|
||||||
|
=7FCh
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----
|
|
@ -0,0 +1,8 @@
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
xjMEAAAAABYJKwYBBAHaRw8BAQdAGN5k4MIbVvz2m6Vq0ij9fQFPNUz+ZZdv2D31
|
||||||
|
K6mzBQfNCHVzZXJuYW1lwmEEExYIABMFAmKD2B0JEAeeW/RyGUT7AhsDAADy8AD/
|
||||||
|
QvC5UvW8TVUdbCd0EQvPjlfVzRauBlxQP4oy+vjnItcA/R1RgGS0D9zAGHC6CRHt
|
||||||
|
AwxzDz3dIpKQAJxxliD8ZO0G
|
||||||
|
=51r3
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
Loading…
Reference in New Issue