add support of password protected pgp keys

This commit is contained in:
Yann Autissier 2022-08-28 23:26:59 +02:00
parent 2a0ddd43af
commit d9cf671b98
6 changed files with 156 additions and 53 deletions

View File

@ -3,8 +3,8 @@ FROM python:${PYTHON_RELEASE}-alpine as dist
LABEL maintainer aynic.os <support+docker@asycn.io>
ARG DOCKER_BUILD_DIR=.
ARG OPERATING_SYSTEM=Linux
ARG PROCESSOR_ARCHITECTURE=x86_64
ARG OPERATING_SYSTEM=$(uname -s)
ARG PROCESSOR_ARCHITECTURE=$(uname -m)
ARG PYTHON_RELEASE=3.10
WORKDIR /opt/dpgpid
@ -19,7 +19,7 @@ RUN apk upgrade --no-cache \
&& /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 \
&& wget https://raw.githubusercontent.com/libp2p/go-libp2p/master/core/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 \
@ -48,7 +48,7 @@ RUN apk add --no-cache \
|tar --strip-components 1 -C /opt/shellspec -xzf - \
&& ln -s /opt/shellspec/shellspec ./bin/shellspec
COPY --from=ipfs/go-ipfs:v0.13.0 /usr/local/bin/ipfs ./bin/
COPY --from=ipfs/kubo:v0.14.0 /usr/local/bin/ipfs ./bin/
COPY README.md ./
COPY COPYING ./
COPY Makefile ./
@ -66,27 +66,38 @@ FROM dist as master
ARG UID
ARG USER
ENV UID=${UID:-999}
ENV GID=${UID}
ENV USER=dpgpid
# If we provide a specific UID
RUN let $UID >/dev/null 2>&1 \
# Remove user with $UID if it is not our $USER
&& if [ "$(getent passwd $UID |awk 'BEGIN {FS=":"} {print $1}')" != "$USER" ]; then \
sed -i '/^'$(getent passwd $UID |awk 'BEGIN {FS=":"} {print $1}')':x:'$UID':/d' /etc/passwd; \
sed -i '/^'$(getent group $GID |awk 'BEGIN {FS=":"} {print $1}')':x:'$GID':/d' /etc/group; \
fi \
# Force $UID if our $USER already exists
&& sed -i 's/^'$USER':x:[0-9]\+:[0-9]\+:/'$USER':x:'$UID':'$GID':/' /etc/passwd \
&& sed -i 's/^'$USER':x:[0-9]\+:/'$USER':x:'$GID':/' /etc/group \
# Create $USER if it does not exist
&& if [ "$(getent passwd $UID)" = "" ]; then \
echo "$USER:x:$UID:$GID::/home/$USER:/bin/false" >> /etc/passwd; \
echo "$USER:!:$(($(date +%s) / 60 / 60 / 24)):0:99999:7:::" >> /etc/shadow; \
echo "$USER:x:$GID:" >> /etc/group; \
fi \
&& mkdir -p /home/$USER \
&& chown $UID:$GID /home/$USER \
|| true
# If we provide a numeric UID
RUN if [ "${UID}" -eq "${UID}" ] 2>/dev/null; then \
# Force $UID of $USER if it exists
if [ "$(awk -F: '$1 == "'"${USER}"'" {print $3}' /etc/passwd)" != "${UID}" ]; then \
sed -i 's/^\('"${USER}"':x\):[0-9]\+:/\1:'"${UID}"':/' /etc/passwd; \
fi; \
# Create $USER if $UID does not exist
if [ "$(awk -F: '$3 == "'"${UID}"'" {print $1}' /etc/passwd)" = "" ]; then \
echo "${USER}:x:${UID}:${GID:-${UID}}::/home/${USER}:${SHELL:-/bin/sh}" >> /etc/passwd; \
echo "${USER}:\!:$(($(date +%s) / 60 / 60 / 24)):0:99999:7:::" >> /etc/shadow; \
mkdir -p /home/"${USER}"; \
fi; \
chown "${UID}" $(awk -F: '$1 == "'"${USER}"'" {print $(NF-1)}' /etc/passwd); \
fi
# If we provide a numeric GID
RUN if [ "${GID}" -eq "${GID}" ] 2>/dev/null; then \
# Force $GID of $GROUP if it already exists
if [ "$(awk -F: '$1 == "'"${GROUP}"'" {print $3}' /etc/group)" != "${GID}" ]; then \
sed -i 's/^\('"${GROUP}"':x\):[0-9]\+:/\1:'"${GID}"':/' /etc/group; \
fi; \
# Create $GROUP if $GID does not exist
if [ "$(awk -F: '$3 == "'"${GID}"'" {print $1}' /etc/group)" = "" ]; then \
echo "${GROUP}:x:${GID}:" >> /etc/group; \
fi; \
# Force $GID of $USER if it exists
if [ "$(awk -F: '$1 == "'"${USER}"'" {print $4}' /etc/passwd)" != "${GID}" ]; then \
sed -i 's/^\('"${USER}"':x:[0-9]\+\):[0-9]\+:/\1:'"${GID}"':/' /etc/passwd; \
fi; \
chgrp "${GID}" $(awk -F: '$1 == "'"${USER}"'" {print $(NF-1)}' /etc/passwd); \
fi
USER $USER

72
keygen
View File

@ -35,12 +35,14 @@ import gpg
import nacl.bindings
import nacl.encoding
import pgpy
import pynentry
import logging as log
import os
import re
import struct
import sys
import time
import warnings
__version__='0.0.1'
@ -109,7 +111,6 @@ class keygen:
def _check_args(self, args):
log.debug("def keygen._check_args(self, args)")
log.debug("self.username=%s" % self.username)
log.debug("self.password=%s" % self.password)
if self.input is None:
if self.password is None:
if self.username is None or args.gpg is False:
@ -212,27 +213,73 @@ sec: {self.base58_secret_key}
def ed25519_from_gpg(self):
log.debug("def keygen.ed25519_from_gpg(self)")
self.gpg_pubkeys = list(self.gpg.keylist(pattern=self.username, secret=False))
self.gpg_seckeys = list(self.gpg.keylist(pattern=self.username, secret=True))
log.debug("self.gpg_pubkeys=%s" % self.gpg_pubkeys)
self.gpg_seckeys = list(self.gpg.keylist(pattern=self.username, secret=True))
log.debug("self.gpg_seckeys=%s" % self.gpg_seckeys)
self.gpg_seckey = self.gpg_seckeys[0]
if not self.gpg_seckeys:
log.error(f"""Unable to find any key matching username "{self.username}".""")
exit(1)
else:
self.gpg_seckey = self.gpg_seckeys[0]
log.info(f"""Found key id "{self.gpg_seckey.fpr}" matching username "{self.username}".""")
log.debug("self.gpg_seckey.__repr__=%s" % self.gpg_seckey.__repr__)
log.debug("self.gpg_seckey.fpr=%s" % self.gpg_seckey.fpr)
log.debug("self.gpg_seckey.key=%s" % self.gpg_seckey.__repr__)
self.armored_pgp_public_key = self.gpg.key_export(self.gpg_seckey.fpr)
self.armored_pgp_secret_key = self.gpg.key_export_secret(self.gpg_seckey.fpr)
log.debug("self.armored_pgp_public_key=%s" % self.armored_pgp_public_key)
if self.password:
self.gpg.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
self.armored_pgp_secret_key = self.gpg.key_export_secret(self.gpg_seckey.fpr)
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)
if not self.armored_pgp_secret_key:
log.error(f"""Unable to export secret key id "{self.gpg_seckey.fpr}" of user "{self.username}". Please check your password!""")
exit(2)
with warnings.catch_warnings():
# remove CryptographyDeprecationWarning about deprecated
# SymmetricKeyAlgorithm IDEA, CAST5 and Blowfish (PGPy v0.5.4)
warnings.simplefilter('ignore')
self.pgpy, _ = pgpy.PGPKey.from_blob(self.armored_pgp_secret_key)
self.ed25519_from_pgpy()
def ed25519_from_pgpy(self):
log.debug("def keygen.ed25519_from_pgpy(self)")
self.pgpy_key_seed()
log.debug("self.pgpy.fingerprint.keyid=%s" % self.pgpy.fingerprint.keyid)
log.debug("self.pgpy.is_protected=%s" % self.pgpy.is_protected)
if self.pgpy.is_protected:
if not self.password:
with pynentry.PynEntry() as p:
p.description = f"""The exported key id "{self.pgpy.fingerprint.keyid}" of user "{self.username}" is password protected.
Please enter the passphrase again to unlock it.
"""
p.prompt = 'Passphrase:'
try:
self.password = p.get_pin()
except pynentry.PinEntryCancelled:
log.warning('Cancelled! Goodbye.')
exit(2)
try:
with warnings.catch_warnings():
# remove CryptographyDeprecationWarning about deprecated
# SymmetricKeyAlgorithm IDEA, CAST5 and Blowfish (PGPy v0.5.4)
warnings.simplefilter('ignore')
with self.pgpy.unlock(self.password):
assert self.pgpy.is_unlocked
log.debug("self.pgpy.is_unlocked=%s" % self.pgpy.is_unlocked)
self.pgpy_key_seed()
except Exception as e:
log.error(f"""Unable to unlock secret key id "{self.gpg_seckey.fpr}" of user "{self.username}". Please check your password!""")
exit(2)
else:
self.pgpy_key_seed()
self.ed25519_public_bytes, self.ed25519_secret_bytes = nacl.bindings.crypto_sign_seed_keypair(self.pgpy_key_seed)
log.debug("self.ed25519_public_bytes=%s" % self.ed25519_public_bytes)
log.debug("self.ed25519_secret_bytes=%s" % self.ed25519_secret_bytes)
def gpg_passphrase_cb(self, uid_hint, passphrase_info, prev_was_bad):
log.debug("uid_hint=%s" % uid_hint)
log.debug("passphrase_info=%s" % passphrase_info)
log.debug("prev_was_bad=%s" % prev_was_bad)
return self.password
def ipfs_from_ed25519(self):
log.debug("def keygen.ipfs_from_ed25519(self)")
@ -275,8 +322,6 @@ sec: {self.base58_secret_key}
def pgpy_key_seed(self):
log.debug("def keygen.pgpy_key_seed(self)")
self.pgpy_key_type()
# todo : unlock password protected key
# assert self.pgpy.is_unlocked
if self.pgpy_key_type == 'RSA':
log.debug("self.pgpy._key.keymaterial.p=%s" % self.pgpy._key.keymaterial.p)
log.debug("self.pgpy._key.keymaterial.q=%s" % self.pgpy._key.keymaterial.q)
@ -289,7 +334,6 @@ sec: {self.base58_secret_key}
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)
log.debug("self.pgpy._key.keymaterial.encbytes=%s" % self.pgpy._key.keymaterial.encbytes)
elif self.pgpy_key_type in ('ECDSA', 'EdDSA', 'ECDH'):
log.debug("self.pgpy._key.keymaterial.s=%s" % self.pgpy._key.keymaterial.s)
self.pgpy_key_seed = long_to_bytes(self.pgpy._key.keymaterial.s)
@ -299,7 +343,7 @@ sec: {self.base58_secret_key}
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")
raise NotImplementedError(f"Getting seed from {self.pgpy_key_type} key is not implemented")
def pgpy_key_type(self):
log.debug("def keygen.pgpy_key_type(self)")
@ -344,7 +388,9 @@ sec: {self.base58_secret_key}
self._check_args(args)
self._load_config()
# self.gpg = gpg.Context(armor=True, offline=True, homedir=GNUPGHOME)
self.gpg = gpg.Context(armor=True, offline=True)
self.gpg = gpg.Context( armor=True,
offline=True)
self.gpg.set_passphrase_cb(self.gpg_passphrase_cb)
self.ed25519(args)
method = getattr(self, f'do_{self.type}', self._invalid_type)
return method()

View File

@ -3,5 +3,6 @@ cryptography
duniterpy
google
pgpy
pynentry
protobuf
pynacl

View File

@ -102,35 +102,53 @@ Describe 'keygen'
rm -f /tmp/keygen_test_ipfs.pem
End
Describe '-t pgp username password birthday:'
gpg --import --quiet specs/username.asc
gpg --import --quiet specs/username.pub
It 'creates a gpg key for user username'
gpg --batch --import --quiet specs/username.asc
gpg --batch --import --quiet specs/username_protected.asc
It 'creates a password protected gpg key for user username'
Skip "You should implement it !"
When run keygen -t pgp username password
When run keygen -t pgp username password birthday
The status should be success
End
End
Describe '-g -t duniter username:'
It 'prints duniter keys for gpg key matching username'
When run keygen -g -t duniter username
When run keygen -g -t duniter 079E5BF4721944FB
The output should include 'pub: 2g5UL2zhkn5i7oNYDpWo3fBuWvRYVU1AbMtdVmnGzPNv'
The output should include 'sec: 5WtYFfA26nTfG496gAKhkrLYUMMnwXexmE1E8Q7PvtQEyscHfirsdMzW34zDp7WEkt3exNEVwoG4ajZYrm62wpi2'
The status should be success
The stderr should equal ""
End
End
Describe '-g -t duniter username@protected password:'
It 'prints duniter keys for gpg key matching username@protected locked with password'
When run keygen -g -t duniter 6222A29CBC31A087 password
The output should include 'pub: C1cRu7yb5rZhsmRHQkeZxusAhtYYJypcnXpY3HycEKsU'
The output should include 'sec: VWaEdDroSCoagJDsBnDNUtXJtKAJYdqL6XKNiomz8DtiyF44FvpiMmhidXt2j8HhDBKPZ67xBGcZPnj4Myk6cB8'
The status should be success
The stderr should equal ""
End
End
Describe '-g -t ipfs username:'
It 'prints ipfs keys for gpg key matching username'
When run keygen -g -t ipfs username
When run keygen -g -t ipfs 079E5BF4721944FB
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 '-g -t ipfs username@protected password:'
It 'prints ipfs keys for gpg key matching username@protected locked with password'
When run keygen -g -t ipfs 6222A29CBC31A087 password
The output should include 'PeerID: 12D3KooWLpybeFZJGkqCHevi3MPujhx1CDbBLfu6k8BZRH8W8GbQ'
The output should include 'PrivKEY: CAESQBiV+XnBNnryoeBs6SNj9e7Cd9Xj6INn24wyxxacylYqo5idwBHJto4Vbbp6NQzuUF+e7aCmrCf6y+BSyL42/i8='
The status should be success
The stderr should equal ""
End
End
Describe '-g -o /tmp/keygen_test_duniter.pubsec -t duniter username:'
It 'writes duniter keys to file for gpg key matching username'
When run keygen -g -o /tmp/keygen_test_duniter.pubsec -t duniter username
When run keygen -g -o /tmp/keygen_test_duniter.pubsec -t duniter 079E5BF4721944FB
The path '/tmp/keygen_test_duniter.pubsec' should exist
The contents of file '/tmp/keygen_test_duniter.pubsec' should include 'pub: 2g5UL2zhkn5i7oNYDpWo3fBuWvRYVU1AbMtdVmnGzPNv'
The contents of file '/tmp/keygen_test_duniter.pubsec' should include 'sec: 5WtYFfA26nTfG496gAKhkrLYUMMnwXexmE1E8Q7PvtQEyscHfirsdMzW34zDp7WEkt3exNEVwoG4ajZYrm62wpi2'
@ -139,9 +157,20 @@ Describe 'keygen'
End
rm -f /tmp/keygen_test_duniter.pubsec
End
Describe '-g -o /tmp/keygen_test_duniter.pubsec -t duniter username@protected password:'
It 'writes duniter keys to file for gpg key matching username@protected locked with password'
When run keygen -g -o /tmp/keygen_test_duniter.pubsec -t duniter 6222A29CBC31A087 password
The path '/tmp/keygen_test_duniter.pubsec' should exist
The contents of file '/tmp/keygen_test_duniter.pubsec' should include 'pub: C1cRu7yb5rZhsmRHQkeZxusAhtYYJypcnXpY3HycEKsU'
The contents of file '/tmp/keygen_test_duniter.pubsec' should include 'sec: VWaEdDroSCoagJDsBnDNUtXJtKAJYdqL6XKNiomz8DtiyF44FvpiMmhidXt2j8HhDBKPZ67xBGcZPnj4Myk6cB8'
The status should be success
The stderr should equal ""
End
rm -f /tmp/keygen_test_duniter.pubsec
End
Describe '-g -o /tmp/keygen_test_ipfs.pem -t ipfs username:'
It 'writes ipfs keys to file for gpg key matching username'
When run keygen -g -o /tmp/keygen_test_ipfs.pem -t ipfs username
When run keygen -g -o /tmp/keygen_test_ipfs.pem -t ipfs 079E5BF4721944FB
The path '/tmp/keygen_test_ipfs.pem' should exist
The contents of file '/tmp/keygen_test_ipfs.pem' should include '-----BEGIN PRIVATE KEY-----'
The contents of file '/tmp/keygen_test_ipfs.pem' should include 'MC4CAQAwBQYDK2VwBCIEIOHXwPgzoiDca1ZnvhU/W3zdogZXulkoErnUsqt+ut82'
@ -151,4 +180,18 @@ Describe 'keygen'
End
rm -f /tmp/keygen_test_ipfs.pem
End
Describe '-g -o /tmp/keygen_test_ipfs.pem -t ipfs username@protected password:'
It 'writes ipfs keys to file for gpg key matching username@protected locked with password'
When run keygen -g -o /tmp/keygen_test_ipfs.pem -t ipfs 6222A29CBC31A087 password
The path '/tmp/keygen_test_ipfs.pem' should exist
The contents of file '/tmp/keygen_test_ipfs.pem' should include '-----BEGIN PRIVATE KEY-----'
The contents of file '/tmp/keygen_test_ipfs.pem' should include 'MC4CAQAwBQYDK2VwBCIEIBiV+XnBNnryoeBs6SNj9e7Cd9Xj6INn24wyxxacylYq'
The contents of file '/tmp/keygen_test_ipfs.pem' should include '-----END PRIVATE KEY-----'
The status should be success
The stderr should equal ""
End
rm -f /tmp/keygen_test_ipfs.pem
End
gpg --batch --delete-secret-and-public-key --yes 4D1CDB77E91FFCD81B10F9A7079E5BF4721944FB
gpg --batch --delete-secret-and-public-key --yes 6AF574897D4979B7956AC31B6222A29CBC31A087
End

View File

@ -1,8 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEAAAAABYJKwYBBAHaRw8BAQdAGN5k4MIbVvz2m6Vq0ij9fQFPNUz+ZZdv2D31
K6mzBQfNCHVzZXJuYW1lwmEEExYIABMFAmKD2B0JEAeeW/RyGUT7AhsDAADy8AD/
QvC5UvW8TVUdbCd0EQvPjlfVzRauBlxQP4oy+vjnItcA/R1RgGS0D9zAGHC6CRHt
AwxzDz3dIpKQAJxxliD8ZO0G
=51r3
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,10 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
xYYEAAAAABYJKwYBBAHaRw8BAQdAo5idwBHJto4Vbbp6NQzuUF+e7aCmrCf6y+BS
yL42/i/+CQMIyF96+qn63Oj/SwKcSRdzCPa5/1+HIJs/eXZFqSADs+Qj/e/XRts2
xpcnRErR8CYZo3OzHDtlhj5sejX1nH9mErGvA3JpjO9KT+GKCF+T4s0SdXNlcm5h
bWVAcHJvdGVjdGVkwmEEExYIABMFAgAAAAAJEGIiopy8MaCHAhsDAADFPgD9Fz0e
vsLZpGbBIOE87ITJQWqAguawC57wi1YH3t+qj98BAMyos3sZjh9csrgecDPvpsjd
SOUtnZgEvISe1r4WMKUM
=ZnDt
-----END PGP PRIVATE KEY BLOCK-----