BROKEN AF, in the middle of a rewrite

This commit is contained in:
brent s 2017-09-07 16:36:26 -04:00
parent eea9cf778e
commit 20388431aa
8 changed files with 755 additions and 374 deletions

15
gpg/kant/README Normal file
View File

@ -0,0 +1,15 @@
GENERATING THE MAN PAGE:
If you have asciidoctor installed, you can generate the manpage one of two ways.

The first way:

asciidoctor -b manpage kant.1.adoc -o- | groff -Tascii -man | gz -c > kant.1.gz

This will generate a fixed-width man page.


The second way (recommended):

asciidoctor -b manpage kant.1.adoc -o- | gz -c > kant.1.gz

This will generate a dynamic-width man page. Most modern versions of man want this version.

View File

@ -1,14 +1,18 @@
# NOTE: The python csv module does NOT skip
# commented lines!
# This is my personal key. Ultimate trust, push key.
748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1
# This is my personal key. Ultimate trust,
# push key, careful checking, notify
748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1,3,1
# This is a testing junk key generated on a completely separate box,
# and does not exist on ANY keyservers. Never trust, Never push.
A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,-1
# This is jthan's key. assign full trust, push to keyserver.
EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true
# This is paden's key. assign Marginal trust, push to keyserver.
6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True
# and does not exist on ANY keyservers nor the local keyring.
# Never trust, local sig, unknown checking, don't notify
A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,0,0,0
# This is jthan's key.
# assign full trust, push to keyserver, casual checking, notify
EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true,casual,yes
# This is paden's key.
# assign Marginal trust, push to keyserver, casual checking, notify
6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True,Casual,True
# This is the email for the Sysadministrivia serverkey.
# Assign full trust, push to keyserver.
<admin@sysadministrivia.com>, full, yes
# Assign full trust, push to keyserver, careful checking, don't notify
<admin@sysadministrivia.com>, full, yes, careful, false

1 # NOTE: The python csv module does NOT skip
2 # commented lines!
3 # This is my personal key. Ultimate trust, push key. # This is my personal key. Ultimate trust,
4 748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1 # push key, careful checking, notify
5 748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1,3,1
6 # This is a testing junk key generated on a completely separate box,
7 # and does not exist on ANY keyservers. Never trust, Never push. # and does not exist on ANY keyservers nor the local keyring.
8 A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,-1 # Never trust, local sig, unknown checking, don't notify
9 # This is jthan's key. assign full trust, push to keyserver. A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,0,0,0
10 EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true # This is jthan's key.
11 # This is paden's key. assign Marginal trust, push to keyserver. # assign full trust, push to keyserver, casual checking, notify
12 6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true,casual,yes
13 # This is paden's key.
14 # assign Marginal trust, push to keyserver, casual checking, notify
15 6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True,Casual,True
16 # This is the email for the Sysadministrivia serverkey.
17 # Assign full trust, push to keyserver. # Assign full trust, push to keyserver, careful checking, don't notify
18 <admin@sysadministrivia.com>, full, yes <admin@sysadministrivia.com>, full, yes, careful, false

225
gpg/kant/kant.1 Normal file
View File

@ -0,0 +1,225 @@
'\" t
.\" Title: kant
.\" Author: Brent Saner
.\" Generator: Asciidoctor 1.5.5
.\" Date: 2017-09-07
.\" Manual: KANT - Keysigning and Notification Tool
.\" Source: KANT
.\" Language: English
.\"
.TH "KANT" "1" "2017-09-07" "KANT" "KANT \- Keysigning and Notification Tool"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\\$2 \(laURL: \\$1 \(ra\\$3
..
.if \n[.g] .mso www.tmac
.LINKSTYLE blue R < >
.SH "NAME"
kant \- Sign GnuPG/OpenPGP/PGP keys and notify the key owner(s)
.SH "SYNOPSIS"
.sp
\fBkant\fP [\fIOPTION\fP] \-k/\-\-key \fI<KEY_IDS|BATCHFILE>\fP
.SH "OPTIONS"
.sp
Keysigning (and keysigning parties) can be a lot of fun, and can offer someone with new keys a way into the WoT (Web\-of\-Trust).
Unfortunately, they can be intimidating to those new to the experience.
This tool offers a simple and easy\-to\-use interface to sign public keys (normal, local\-only, and/or non\-exportable),
set owner trust, specify level of checking done, and push the signatures to a keyserver. It even supports batch operation via a CSV file.
.sp
\fB\-h\fP, \fB\-\-help\fP
.RS 4
Display brief help/usage and exit.
.RE
.sp
\fB\-k\fP \fIKEY_IDS|BATCHFILE\fP, \fB\-\-key\fP \fIKEY_IDS|BATCHFILE\fP
.RS 4
A single or comma\-separated list of key IDs (see \fBKEY ID FORMAT\fP) to sign, trust, and notify. Can also be an email address.
If \fB\-b\fP/\fB\-\-batch\fP is specified, this should instead be a path to the batch file (see \fBBATCHFILE/Format\fP).
.RE
.sp
\fB\-K\fP \fIKEY_ID\fP, \fB\-\-sigkey\fP \fIKEY_ID\fP
.RS 4
The key to use when signing other keys (see \fBKEY ID FORMAT\fP). The default key is automatically determined at runtime
(it will be displayed in \fB\-h\fP/\fB\-\-help\fP output).
.RE
.sp
\fB\-t\fP \fITRUSTLEVEL\fP, \fB\-\-trust\fP \fITRUSTLEVEL\fP
.RS 4
The trust level to automatically apply to all keys (if not specified, KANT will prompt for each key).
See \fBBATCHFILE/TRUSTLEVEL\fP for trust level notations.
.RE
.sp
\fB\-c\fP \fICHECKLEVEL\fP, \fB\-\-check\fP \fICHECKLEVEL\fP
.RS 4
The level of checking that was done to confirm the validity of ownership for all keys being signed. If not specified,
the default is for KANT to prompt for each key we sign. See \fBBATCHFILE/CHECKLEVEL\fP for check level notations.
.RE
.sp
\fB\-l\fP \fILOCAL\fP, \fB\-\-local\fP \fILOCAL\fP
.RS 4
If specified, make the signature(s) local\-only (i.e. non\-exportable, don\(cqt push to a keyserver).
See \fBBATCHFILE/LOCAL\fP for more information on local signatures.
.RE
.sp
\fB\-n\fP, \fB\-\-no\-notify\fP
.RS 4
This requires some explanation. If you have MSMTP[1] installed and configured for the currently active user,
then we will send out emails to recipients letting them know we have signed their key. However, if MSMTP is installed and configured
but this flag is given, then we will NOT attempt to send emails.
.RE
.sp
\fB\-s\fP \fIKEYSERVER(S)\fP, \fB\-\-keyservers\fP \fIKEYSERVER(S)\fP
.RS 4
The comma\-separated keyserver(s) to push to. The default keyserver list is automatically generated at runtime.
.RE
.sp
\fB\-b\fP, \fB\-\-batch\fP
.RS 4
If specified, operate in batch mode. See \fBBATCHFILE\fP for more information.
.RE
.sp
\fB\-D\fP \fIGPGDIR\fP, \fB\-\-gpgdir\fP \fIGPGDIR\fP
.RS 4
The GnuPG configuration directory to use (containing your keys, etc.). The default is automatically generated at runtime,
but will probably be \fB/home/<yourusername>/.gnupg\fP or similar.
.RE
.sp
\fB\-T\fP, \fB\-\-testkeyservers\fP
.RS 4
If specified, initiate a basic test connection with each set keyserver before anything else. Disabled by default.
.RE
.SH "KEY ID FORMAT"
.sp
Key IDs can be specified in one of two ways. The first (and preferred) way is to use the full 160\-bit (40\-character, hexadecimal) key ID.
A little known fact is the fingerprint of a key:
.sp
\fBDEAD BEEF DEAD BEEF DEAD BEEF DEAD BEEF DEAD BEEF\fP
.sp
is actually the full key ID of the primary key; i.e.:
.sp
\fBDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF\fP
.sp
The second way to specify a key, as far as KANT is concerned, is to use an email address.
Do note that if more than one key is found that matches the email address given (and they usually are), you will be prompted to select the specific
correct key ID anyways so it\(cqs usually a better idea to have the owner present their full key ID/fingerprint right from the get\-go.
.SH "BATCHFILE"
.SS "Format"
.sp
The batch file is a CSV\-formatted (comma\-delimited) file containing keys to sign and other information about them. It keeps the following format:
.sp
\fBKEY_ID,TRUSTLEVEL,LOCAL,CHECKLEVEL,NOTIFY\fP
.sp
For more information on each column, reference the appropriate sub\-section below.
.SS "KEY_ID"
.sp
See \fBKEY ID FORMAT\fP.
.SS "TRUSTLEVEL"
.sp
The \fITRUSTLEVEL\fP is specified by the following levels (you can use either the numeric or string representation):
.sp
.if n \{\
.RS 4
.\}
.nf
\fB\-1 = Never
0 = Unknown
1 = Untrusted
2 = Marginal
3 = Full
4 = Ultimate\fP
.fi
.if n \{\
.RE
.\}
.sp
It is how much trust to assign to a key, and the signatures that key makes on other keys.[2]
.SS "LOCAL"
.sp
Whether or not to push to a keyserver. It can be either the numeric or string representation of the following:
.sp
.if n \{\
.RS 4
.\}
.nf
\fB0 = False
1 = True\fP
.fi
.if n \{\
.RE
.\}
.sp
If \fB1/True\fP, KANT will sign the key with a local signature (and the signature will not be pushed to a keyserver or be exportable).[3]
.SS "CHECKLEVEL"
.sp
The amount of checking that has been done to confirm that the owner of the key is who they say they are and that the key matches their provided information.
It can be either the numeric or string representation of the following:
.sp
.if n \{\
.RS 4
.\}
.nf
\fB0 = Unknown
1 = None
2 = Casual
3 = Careful\fP
.fi
.if n \{\
.RE
.\}
.sp
It is up to you to determine the classification of the amount of checking you have done, but the following is recommended (it is the policy
the author follows):
.sp
.if n \{\
.RS 4
.\}
.nf
\fBUnknown:\fP The key is unknown and has not been reviewed

\fBNone:\fP The key has been signed, but no confirmation of the
ownership of the key has been performed (typically
a local signature)

\fBCasual:\fP The key has been presented and the owner is either
known to the signer or they have provided some form
of non\-government\-issued identification or other
proof (website, Keybase.io, etc.)

\fBCareful:\fP The same as \fBCasual\fP requirements but they have
provided a government\-issued ID and all information
matches
.fi
.if n \{\
.RE
.\}
.sp
It\(cqs important to check each key you sign carefully. Failure to do so may hurt others\(aq trust in your key.[4]
.SH "SEE ALSO"
.sp
gpg(1), gpgconf(1)
.SH "RESOURCES"
.sp
\fBAuthor\(cqs web site:\fP \c
.URL "https://square\-r00t.net/" "" ""
\fBAuthor\(cqs GPG information:\fP \c
.URL "https://square\-r00t.net/gpg\-info" "" ""
.SH "COPYING"
.sp
Copyright (C) 2017 Brent Saner.
.sp
Free use of this software is granted under the terms of the GPLv3 License.
.SH "NOTES"
1. http://msmtp.sourceforge.net/
2. For more information on trust levels and the Web of Trust, see: https://www.gnupg.org/gph/en/manual/x334.html and https://www.gnupg.org/gph/en/manual/x547.html
3. For more information on pushing to keyservers and local signatures, see: https://www.gnupg.org/gph/en/manual/r899.html#LSIGN and https://lists.gnupg.org/pipermail/gnupg-users/2007-January/030242.html
4. GnuPG documentation refers to this as "validity"; see https://www.gnupg.org/gph/en/manual/x334.html
.SH "AUTHOR(S)"
.sp
\fBBrent Saner\fP
.RS 4
Author(s).
.RE

View File

@ -8,7 +8,7 @@ v1.0.0

== NAME

kant - Sign GnuPG/OpenPGP/PGP keys and notify the key owner(s)
KANT - Sign GnuPG/OpenPGP/PGP keys and notify the key owner(s)

== SYNOPSIS

@ -26,25 +26,28 @@ set owner trust, specify level of checking done, and push the signatures to a ke

*-k* _KEY_IDS|BATCHFILE_, *--key* _KEY_IDS|BATCHFILE_::
A single or comma-separated list of key IDs (see *KEY ID FORMAT*) to sign, trust, and notify. Can also be an email address.
If *-b*/*--batch* is specified, this should instead be a path to the batch file (see *BATCHFILE*).
If *-b*/*--batch* is specified, this should instead be a path to the batch file (see *BATCHFILE/Format*).

*-K* _KEY_ID_, *--sigkey* _KEY_ID_::
The key to use when signing other keys (see *KEY ID FORMAT*). The default key is automatically determined at runtime
(it will be displayed in *-h*/*--help* output).

*-t* _TRUSTLEVEL_, *--trustlevel* _TRUSTLEVEL_::
The trust level to automatically apply to all keys (if not specified, kant will prompt for each key). See *BATCHFILE* for trust level notations.
*-t* _TRUSTLEVEL_, *--trust* _TRUSTLEVEL_::
The trust level to automatically apply to all keys (if not specified, KANT will prompt for each key).
See *BATCHFILE/TRUSTLEVEL* for trust level notations.

*-c* _CHECKLEVEL_, *--checklevel* _CHECKLEVEL_::
*-c* _CHECKLEVEL_, *--check* _CHECKLEVEL_::
The level of checking that was done to confirm the validity of ownership for all keys being signed. If not specified,
the default is for kant to prompt for each key we sign. See *BATCHFILE* for check level notations.

*-e* _EXPORT_, *--export* _EXPORT_::
Whether the signature(s) should be made exportable or not. See *BATCHFILE* for more information on exportability.
The default is True (signatures will be exportable).
the default is for KANT to prompt for each key we sign. See *BATCHFILE/CHECKLEVEL* for check level notations.

*-l* _LOCAL_, *--local* _LOCAL_::
Make the signature(s) local-only (i.e. don't push to a keyserver).
If specified, make the signature(s) local-only (i.e. non-exportable, don't push to a keyserver).
See *BATCHFILE/LOCAL* for more information on local signatures.

*-n*, *--no-notify*::
This requires some explanation. If you have MSMTPfootnote:[\http://msmtp.sourceforge.net/] installed and configured for the currently active user,
then we will send out emails to recipients letting them know we have signed their key. However, if MSMTP is installed and configured
but this flag is given, then we will NOT attempt to send emails.

*-s* _KEYSERVER(S)_, *--keyservers* _KEYSERVER(S)_::
The comma-separated keyserver(s) to push to. The default keyserver list is automatically generated at runtime.
@ -52,7 +55,7 @@ set owner trust, specify level of checking done, and push the signatures to a ke
*-b*, *--batch*::
If specified, operate in batch mode. See *BATCHFILE* for more information.

*-d* _GPGDIR_, *--gpgdir* _GPGDIR_::
*-D* _GPGDIR_, *--gpgdir* _GPGDIR_::
The GnuPG configuration directory to use (containing your keys, etc.). The default is automatically generated at runtime,
but will probably be */home/<yourusername>/.gnupg* or similar.

@ -70,7 +73,7 @@ is actually the full key ID of the primary key; i.e.:
*DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF*

The second way to specify a key, as far as KANT is concerned, is to use an email address.
Do note that if more than one key is found that matches the email address given, you will be prompted to select the specific
Do note that if more than one key is found that matches the email address given (and they usually are), you will be prompted to select the specific
correct key ID anyways so it's usually a better idea to have the owner present their full key ID/fingerprint right from the get-go.

== BATCHFILE
@ -78,22 +81,85 @@ correct key ID anyways so it's usually a better idea to have the owner present t
=== Format
The batch file is a CSV-formatted (comma-delimited) file containing keys to sign and other information about them. It keeps the following format:

*KEY_ID,TRUSTLEVEL,PUSH,CHECKLEVEL,EXPORT*
*KEY_ID,TRUSTLEVEL,LOCAL,CHECKLEVEL,NOTIFY*

For more information on each column, reference the appropriate sub-section below.

=== KEY_ID
See *KEY ID FORMAT*.

=== TRUSTLEVEL
The _TRUSTLEVEL_ is specified by the following levels:
The _TRUSTLEVEL_ is specified by the following levels (you can use either the numeric or string representation):

*THIS IS A TEST*
[subs=+quotes]
....
*-1 = Never
0 = Unknown
1 = Untrusted
2 = Marginal
3 = Full
4 = Ultimate*
....

It is how much trust to assign to a key, and the signatures that key makes on other keys.footnote:[For more information
on trust levels and the Web of Trust, see: \https://www.gnupg.org/gph/en/manual/x334.html and \https://www.gnupg.org/gph/en/manual/x547.html]

=== LOCAL
Whether or not to push to a keyserver. It can be either the numeric or string representation of the following:

[subs=+quotes]
....
*0 = False
1 = True*
....

If *1/True*, KANT will sign the key with a local signature (and the signature will not be pushed to a keyserver or be exportable).footnote:[For
more information on pushing to keyservers and local signatures, see: \https://www.gnupg.org/gph/en/manual/r899.html#LSIGN and
\https://lists.gnupg.org/pipermail/gnupg-users/2007-January/030242.html]

=== CHECKLEVEL
The amount of checking that has been done to confirm that the owner of the key is who they say they are and that the key matches their provided information.
It can be either the numeric or string representation of the following:

[subs=+quotes]
....
*0 = Unknown
1 = None
2 = Casual
3 = Careful*
....

It is up to you to determine the classification of the amount of checking you have done, but the following is recommended (it is the policy
the author follows):

[subs=+quotes]
....
*Unknown:* The key is unknown and has not been reviewed

*None:* The key has been signed, but no confirmation of the
ownership of the key has been performed (typically
a local signature)

*Casual:* The key has been presented and the owner is either
known to the signer or they have provided some form
of non-government-issued identification or other
proof (website, Keybase.io, etc.)

*Careful:* The same as *Casual* requirements but they have
provided a government-issued ID and all information
matches
....

It's important to check each key you sign carefully. Failure to do so may hurt others' trust in your key.footnote:[GnuPG documentation refers
to this as "validity"; see \https://www.gnupg.org/gph/en/manual/x334.html]

== SEE ALSO
gpg(1), gpgcong(1)
gpg(1), gpgconf(1)

== RESOURCES

*Author's web site:* https://square-r00t.net/
*Author's GPG information:* https://square-r00t.net/gpg-info

== COPYING


View File

@ -11,7 +11,9 @@ import subprocess
from io import BytesIO
from socket import *
import urllib.parse
import gpgme # non-stdlib; Arch package is "python-pygpgme"
import gpg # non-stdlib; Arch package is "python-gpgme" - see
# https://git.archlinux.org/svntogit/packages.git/tree/trunk/PKGBUILD?h=packages/gpgme and
# https://pypi.python.org/pypi/gpg

# TODO:
# - http://tanguy.ortolo.eu/blog/article9/pgp-signature-infos edit certification level- possible with pygpgme?
@ -36,17 +38,21 @@ import gpgme # non-stdlib; Arch package is "python-pygpgme"
#
#Thanks again!

def getKeys(args):
class sigsession(object):
def __init__(self, args):
self.args = args

def getKeys(self):
# Get our concept
os.environ['GNUPGHOME'] = args['gpgdir']
gpg = gpgme.Context()
os.environ['GNUPGHOME'] = self.args['gpgdir']
ctx = gpg.Context()
keys = {}
allkeys = []
self.keyids = []
# Do we have the key already? If not, fetch.
for k in args['rcpts'].keys():
if args['rcpts'][k]['type'] == 'fpr':
allkeys.append(k)
if args['rcpts'][k]['type'] == 'email':
for k in list(self.args['rcpts']):
if self.args['rcpts'][k]['type'] == 'fpr':
self.keyids.append(k)
if self.args['rcpts'][k]['type'] == 'email':
# We need to actually do a lookup on the email address.
with open(os.devnull, 'w') as f:
# TODO: replace with gpg.keylist_mode(gpgme.KEYLIST_MODE_EXTERN) and internal mechanisms?
@ -86,12 +92,12 @@ def getKeys(args):
if key not in keys.keys():
print('Please enter a full key ID from the list above or hit ctrl-d to exit.')
else:
allkeys.append(key)
self.keyids.append(key)
break
else:
if not len(keys.keys()) >= 1:
print('Could not find {0}!'.format(k))
del(args['rcpts'][k])
del(self.args['rcpts'][k])
continue
key = list(keys.keys())[0]
print('\nFound key {0} for {1} (Generated at {2}):'.format(key, k, datetime.datetime.utcfromtimestamp(keys[key]['time'])))
@ -99,17 +105,17 @@ def getKeys(args):
print('\t(Generated {2}) {0} <{1}>'.format(keys[key]['uids'][email]['comment'],
email,
datetime.datetime.utcfromtimestamp(keys[key]['uids'][email]['time'])))
allkeys.append(key)
self.keyids.append(key)
print()
## And now we can (FINALLY) fetch the key(s).
# TODO: replace with gpg.keylist_mode(gpgme.KEYLIST_MODE_EXTERN) and internal mechanisms?
recvcmd = ['gpg2', '--recv-keys', '--batch', '--yes'] # We'll add the keys onto the end of this next.
recvcmd.extend(allkeys)
recvcmd.extend(self.keyids)
with open(os.devnull, 'w') as f:
fetchout = subprocess.run(recvcmd, stdout = f, stderr = f) # We hide stderr because gpg, for some unknown reason, spits non-errors to stderr.
return(allkeys)
return(self.keyids)

def trustKeys(keyids, args):
def trustKeys(self):
# Map the trust levels to "human" equivalent
trustmap = {-1: ['never', gpgme.VALIDITY_NEVER], # this is... probably? not ideal, but.
0: ['unknown', gpgme.VALIDITY_UNKNOWN],
@ -117,8 +123,7 @@ def trustKeys(keyids, args):
2: ['marginal', gpgme.VALIDITY_MARGINAL],
3: ['full', gpgme.VALIDITY_FULL],
4: ['ultimate', gpgme.VALIDITY_ULTIMATE]}
pushmap = {-1: ['never', None],
0: ['no', False],
locmap = {0: ['no', False],
1: ['yes', True]}
def promptTrust(kinfo):
for k in list(kinfo):
@ -131,26 +136,24 @@ def trustKeys(keyids, args):
if trust_in.lower().strip() == dictv[0]:
trust_lvl = int(dictk)
elif trust_in == str(dictk):
trust_lvl = dictk
trust_lvl = int(dictk)
if not trust_lvl:
print('Not a valid trust level; skipping. Run kant again to fix.')
continue
kinfo[k]['trust'] = trustmap[trust_lvl][1]
if 'push' not in kinfo[k].keys():
push = True
if 'local' not in kinfo[k].keys():
local = False
if args['keyservers']:
push_in = input('\nShould we push {0} to the keyserver(s) (\033[1mYES\033[0m/No/Never)? '.format(k))
if push_in.lower() == 'never':
push = None
elif push_in.lower().startswith('n'):
push = False
kinfo[k]['push'] = push
local_in = input('\nShould we push {0} to the keyserver(s) (\033[1mYES\033[0m/No)? '.format(k))
if local_in.lower().startswith('n'):
local = True
kinfo[k]['local'] = local
return(kinfo)
os.environ['GNUPGHOME'] = args['gpgdir']
gpg = gpgme.Context()
# Build out some info about keys
kinfo = {}
for k in keyids:
for k in self.keyids:
if k not in kinfo.keys():
kinfo[k] = {}
else:
@ -163,13 +166,35 @@ def trustKeys(keyids, args):
print('Can\'t get information about key {0}; skipping.'.format(k))
del(kinfo[k])
if not args['batch']:
trusts = promptTrust(kinfo)
if not args['trustlevel']:
self.trusts = promptTrust(kinfo)
else:
trusts = {}
for k in list(kinfo):
local = False
if 'trust' not in kinfo[k].keys():
for dictk, dictv in trustmap.items():
if args['trustlevel'].lower().strip() == dictv[0]:
trust_lvl = int(dictk)
elif args['trustlevel'] == str(dictk):
trust_lvl = int(dictk)
if not trust_lvl:
print('Not a valid trust level; skipping. Run kant again to fix.')
continue
if 'local' not in kinfo[k].keys():
if args['local']:
local = True
kinfo[k]['local'] = local
kinfo[k]['trust'] = trustmap[trust_lvl][1]
self.trusts = kinfo
else:
self.trusts = {}
csvd = {} # We import the CSV into a totally separate dict so we can do some validation loops
with open(args['keys'], 'r') as f:
with open(self.args['keys'], 'r') as f:
for row in csv.reader(f, delimiter = ',', quotechar = '"'):
csvd[row[0]] = {'trust': row[1], 'push': row[2]}
csvd[row[0]] = {'trust': row[1].strip(),
'local': row[2].strip(),
'check': row[3].strip(),
'notify': row[4].strip()}
for k in list(csvd):
if re.match('^<?[\w\.\+\-]+\@[\w-]+\.[a-z]{2,3}>?$', k): # is it an email address?
fullkey = gpg.get_key(k)
@ -177,44 +202,42 @@ def trustKeys(keyids, args):
del(csvd[k])
k = fullkey.subkeys[0].fpr
if k not in trusts.keys():
trusts[k] = {}
self.trusts[k] = {}
if 'trust' not in trusts[k].keys():
# Properly index the trust
strval = str(csvd[k]['trust']).lower().strip()
if strval == 'true':
trusts[k]['trust'] = True
self.trusts[k]['trust'] = True
elif strval == 'false':
trusts[k]['trust'] = False
self.trusts[k]['trust'] = False
elif strval == 'none':
trusts[k]['trust'] = None
self.trusts[k]['trust'] = None
else:
for dictk, dictv in trustmap.items(): # "no"/"yes"
for dictk, dictv in trustmap.items():
if strval == dictv[0]:
trusts[k]['trust'] = trustmap[dictk][1]
self.trusts[k]['trust'] = trustmap[dictk][1]
elif strval == str(dictk):
trusts[k]['trust'] = trustmap[dictk][1]
if 'trust' not in trusts[k].keys(): # yes, again. we make sure it was set. otherwise, we need to skip this key.
self.trusts[k]['trust'] = trustmap[dictk][1]
if 'trust' not in self.trusts[k].keys(): # yes, again. we make sure it was set. otherwise, we need to skip this key.
print('Key {0}: trust level "{1}" is invalid; skipping.'.format(k, csvd[k]['trust']))
del(trusts[k])
del(self.trusts[k])
continue
# Now we need to index whether we push or not.
if 'push' not in trusts[k].keys():
strval = str(csvd[k]['push']).lower().strip()
if 'local' not in self.trusts[k].keys():
strval = str(csvd[k]['local']).lower().strip()
if strval == 'true':
trusts[k]['push'] = True
self.trusts[k]['local'] = True
elif strval == 'false':
trusts[k]['push'] = False
elif strval == 'none':
trusts[k]['push'] = None
self.trusts[k]['local'] = False
else:
for dictk, dictv in pushmap.items(): # "no"/"yes"
for dictk, dictv in locmap.items():
if strval in dictv[0]:
trusts[k]['push'] = pushmap[dictk][1]
self.trusts[k]['local'] = locmap[dictk][1]
elif strval == str(dictk):
trusts[k]['push'] = pushmap[dictk][1]
if 'push' not in trusts[k].keys(): # yep. double-check
print('Key {0}: push option "{1}" is invalid; skipping.'.format(k, csvd[k]['push']))
del(trusts[k])
self.trusts[k]['local'] = locmap[dictk][1]
if 'local' not in self.trusts[k].keys(): # yep. double-check
print('Key {0}: local option "{1}" is invalid; skipping.'.format(k, csvd[k]['local']))
del(self.trusts[k])
continue
# WHEW. THAT'S A LOT OF VALIDATIONS. Now the Business-End(TM)
# Reverse mapping of constants to human-readable
@ -225,18 +248,18 @@ def trustKeys(keyids, args):
gpgme.VALIDITY_FULL: 'Full',
gpgme.VALIDITY_ULTIMATE: 'Ultimate'}
mykey = gpg.get_key(args['sigkey'])
for k in list(trusts):
for k in list(self.trusts):
keystat = None
try:
tkey = gpg.get_key(k)
except gpgme.GpgmeError:
print('Cannot find {0} in keyring at all; skipping.'.format(k))
del(trusts[k])
del(self.trusts[k])
continue
curtrust = rmap[tkey.owner_trust]
newtrust = rmap[trusts[k]['trust']]
newtrust = rmap[self.trusts[k]['trust']]
if tkey.owner_trust == trusts[k]['trust']:
trusts[k]['change'] = False
self.trusts[k]['change'] = False
continue # Don't bother; we aren't changing the trust level, it's the same (OR we haven't trusted yet)
elif tkey.owner_trust == gpgme.VALIDITY_UNKNOWN:
keystat = 'a NEW TRUST'
@ -244,7 +267,7 @@ def trustKeys(keyids, args):
keystat = 'a DOWNGRADE'
elif tkey.owner_trust < trusts[k]['trust']:
keystat = 'an UPGRADE'
print(('\nKey 0x{0} [{1} ({2})]:\n' +
print(('\nKey {0} [{1} ({2})]:\n' +
'\tThis trust level ({3}) is {4} from the current trust level ({5}).').format(k,
kinfo[k]['name'],
kinfo[k]['email'],
@ -253,42 +276,37 @@ def trustKeys(keyids, args):
curtrust))
tchk = input('Continue? (yes/\033[1mNO\033[0m) ')
if tchk.lower().startswith('y'):
trusts[k]['change'] = True
self.trusts[k]['change'] = True
else:
trusts[k]['change'] = False
for k in list(trusts):
if trusts[k]['change']:
gpgme.editutil.edit_trust(gpg, k, trusts['k']['trust'])
self.trusts[k]['change'] = False
for k in list(self.trusts):
if self.trusts[k]['change']:
print(k)
gpg.editutil.edit_trust(ctx, ctx.get_key(k), self.trusts[k]['trust'])
print()
return(trusts)
return(self.trusts)

def sigKeys(trusts, args): # The More Business-End(TM)
import pprint
pprint.pprint(trusts)
def sigKeys(self): # The More Business-End(TM)
os.environ['GNUPGHOME'] = args['gpgdir']
gpg = gpgme.Context()
gpg.keylist_mode = gpgme.KEYLIST_MODE_SIGS
mkey = gpg.get_key(args['sigkey'])
gpg.signers = [mkey]
ctx = gpg.Context()
ctx.keylist_mode = gpg.KEYLIST_MODE_SIGS
mkey = ctx.get_key(args['sigkey'])
ctx.signers = [mkey]
global_policy = {}
global_policy['push'] = True # I may be able to provide a way to explicitly change this at runtime later
global_policy['sign'] = True
if not args['keyservers']:
global_policy['sign'] = 'local'
global_policy['push'] = False
for k in list(trusts):
for k in list(self.trusts):
sign = True
key = gpg.get_key(k)
key = ctx.get_key(k)
for uid in key.uids:
for s in uid.signatures:
try:
signerkey = gpg.get_key(s.keyid).subkeys[0].fpr
signerkey = ctx.get_key(s.keyid).subkeys[0].fpr
if signerkey == mkey.subkeys[0].fpr:
sign = False # We already signed this key
except gpgme.GpgmeError:
except gpgme.GpgError:
pass # usually if we get this it means we don't have a signer's key in our keyring
trusts[k]['sign'] = sign

self.trusts[k]['sign'] = sign
import pprint
pprint.pprint(self.trusts)
# edit_sign(ctx, key, index=0, local=False, norevoke=False, expire=True, check=0)
# index: the index of the user ID to sign, starting at 1. Sign all
# user IDs if set to 0.
@ -308,11 +326,11 @@ def sigKeys(trusts, args): # The More Business-End(TM)
def pushKeys(): # The Last Business-End(TM)
pass

def modifyDirmngr(op, args):
if not args['keyservers']:
def modifyDirmngr(self, op):
if not self.args['keyservers']:
return()
pid = str(os.getpid())
activecfg = os.path.join(args['gpgdir'], 'dirmngr.conf')
activecfg = os.path.join(self.args['gpgdir'], 'dirmngr.conf')
bakcfg = '{0}.{1}'.format(activecfg, pid)
if op in ('new', 'start'):
if os.path.lexists(activecfg):
@ -322,7 +340,7 @@ def modifyDirmngr(op, args):
if not line.startswith('keyserver '):
write.write(line)
with open(activecfg, 'a') as f:
for s in args['keyservers']:
for s in self.args['keyservers']:
uri = '{0}://{1}:{2}'.format(s['proto'], s['server'], s['port'][0])
f.write('keyserver {0}\n'.format(uri))
if op in ('old', 'stop'):
@ -385,8 +403,8 @@ def parseArgs():
if not defgpgdir:
return(None)
defkey = None
gpg = gpgme.Context()
for k in gpg.keylist(None, True): # params are query and secret keyring, respectively
ctx = gpg.Context()
for k in ctx.keylist(None, True): # params are query and secret keyring, respectively
if k.can_sign and True not in (k.revoked, k.expired, k.disabled):
defkey = k.subkeys[0].fpr
break # We'll just use the first primary key we find that's valid as the default.
@ -410,71 +428,61 @@ def parseArgs():
defkey = getDefKey(defgpgdir)
defkeyservers = getDefKeyservers(defgpgdir)
args = argparse.ArgumentParser(description = 'Keysigning Assistance and Notifying Tool (KANT)',
epilog = 'brent s. || 2017 || https://square-r00t.net',
formatter_class = argparse.RawTextHelpFormatter)
epilog = 'brent s. || 2017 || https://square-r00t.net')
args.add_argument('-k',
'--keys',
dest = 'keys',
metavar = 'KEYS | /path/to/batchfile',
required = True,
help = 'A single or comma-separated list of keys to sign,\n' +
'trust, and notify. Can also be an email address.\n' +
'If -b/--batch is specified, this should instead be\n' +
'a path to the batch file.')
help = 'A single/comma-separated list of keys to sign, ' +
'trust, & notify. Can also be an email address. ' +
'If -b/--batch is specified, this should instead be ' +
'a path to the batch file. See the man page for more info.')
args.add_argument('-K',
'--sigkey',
dest = 'sigkey',
default = defkey,
help = 'The key to use when signing other keys.\nDefault is \033[1m{0}\033[0m.'.format(defkey))
help = 'The key to use when signing other keys. Default is \033[1m{0}\033[0m.'.format(defkey))
args.add_argument('-t',
'--trust',
dest = 'trustlevel',
default = None,
help = 'The trust level to automatically apply to all keys\n' +
'(if not specified, kant will prompt for each key).\n' +
'See -b/--batch for trust level notations.')
help = 'The trust level to automatically apply to all keys ' +
'(if not specified, kant will prompt for each key). ' +
'See BATCHFILE/TRUSTLEVEL in the man page for trust ' +
'level notations.')
args.add_argument('-c',
'--check',
dest = 'checklevel',
default = None,
help = 'The level of checking done (if not specified, kant will\n' +
help = 'The level of checking done (if not specified, kant will ' +
'prompt for each key). See -b/--batch for check level notations.')
args.add_argument('-e',
'--export',
dest = 'export',
default = 'true',
help = 'Make the signatures exportable (default is True).\nSee -b/--batch for more information.')
args.add_argument('-l',
'--local',
dest = 'local',
default = 'false',
help = 'Make the signature(s) local-only (i.e. don\'t push to a keyserver).')
args.add_argument('-n',
'--no-notify',
dest = 'notify',
action = 'store_false',
help = 'If specified, do NOT notify any key recipients that you\'ve signed ' +
'their key, even if KANT is able to.')
args.add_argument('-s',
'--keyservers',
dest = 'keyservers',
default = defkeyservers,
help = 'The comma-separated keyserver(s) to push to.\n' +
'Default keyserver list is: \n\n\t\033[1m{0}\033[0m\n\n'.format(re.sub(',', '\n\t', defkeyservers)))
# This will require some restructuring...
'Default keyserver list is: \n\n\t\033[1m{0}\033[0m\n\n'.format(re.sub(',',
'\n\t',
defkeyservers)))
args.add_argument('-b',
'--batch',
dest = 'batch',
action = 'store_true',
help = 'If specified, -k/--keys is a CSV file to use as a\n' +
'batch run in the format of (one per line):\n' +
'\n\033[1mKEY_ID,TRUSTLEVEL,PUSH,CHECKLEVEL,EXPORT\033[0m\n'
'\n\033[1mKEY_ID\033[0m can be the full 40-char key ID (fingerprint)\n' +
'or an email address of the key.\n\n\033[1mTRUSTLEVEL\033[0m is how much trust to assign, and can\n' +
'be numeric or string:' +
'\n\n\t\033[1m-1 = Never\n\t 0 = Unknown\n\t 1 = Untrusted\n\t 2 = Marginal\n\t 3 = Full\n\t 4 = Ultimate\033[0m\n' +
'\n\033[1mPUSH\033[0m can be \033[1m1/True\033[0m or \033[1m0/False\033[0m.\n' +
'If marked as False, the signature will be made local.\n' +
'\n\033[1mCHECKLEVEL\033[0m is the amount of checking done on the owner\'s\n' +
'validity of identity. Can be numeric or string:' +
'\n\n\t\033[1m 0 = Unknown\n\t 1 = None\n\t 2 = Casual\n\t 3 = Careful\033[0m\n' +
'\n\033[1mEXPORT\033[0m can be either \033[1m1/True\033[0m or \033[1m0/False\033[0m.\n' +
'If True, make the signature exportable.\nIf False, make it non-exportable.')
args.add_argument('-d',
help = 'If specified, -k/--keys is a CSV file to use as a ' +
'batch run. See the BATCHFILE section in the man page for more info.')
args.add_argument('-D',
'--gpgdir',
dest = 'gpgdir',
default = defgpgdir,
@ -533,12 +541,12 @@ def verifyArgs(args):
raise NotADirectoryError('{0} is not a directory'.format(args['gpgdir']))
try:
os.environ['GNUPGHOME'] = args['gpgdir']
gpg = gpgme.Context()
ctx = gpg.Context()
except:
raise RuntimeError('Could not use {0} as a GnuPG home'.format(args['gpgdir']))
# Now we need to verify that the private key exists...
try:
sigkey = gpg.get_key(args['sigkey'], True)
sigkey = ctx.get_key(args['sigkey'], True)
except GpgmeError:
raise ValueError('Cannot use key {0}'.format(args['sigkey']))
# And that it is an eligible candidate to use to sign.
@ -548,12 +556,12 @@ def verifyArgs(args):
if args['testkeyservers']:
for s in args['keyservers']:
# Test to make sure the keyserver is accessible.
# First we need to construct a way to use python's socket connector
# Great. Now we need to just quickly check to make sure it's accessible - if specified.
if args['netproto'] == '4':
nettype = AF_INET
elif args['netproto'] == '6':
nettype = AF_INET6
v6test = socket(AF_INET6, SOCK_DGRAM)
try:
v6test.connect(('ipv6.square-r00t.net', 0))
nettype = AF_INET6 # We have IPv6 intarwebz
except:
nettype = AF_INET # No IPv6, default to IPv4
for proto in s['port'][1]:
if proto == 'udp':
netproto = SOCK_DGRAM
@ -564,7 +572,7 @@ def verifyArgs(args):
tests = sock.connect_ex((s['server'], int(s['port'][0])))
uristr = '{0}://{1}:{2} ({3})'.format(s['proto'], s['server'], s['port'][0], proto.upper())
if not tests == 0:
raise RuntimeError('Keyserver {0} is not available'.format(uristr))
raise OSError('Keyserver {0} is not available'.format(uristr))
else:
print('Keyserver {0} is accepting connections.'.format(uristr))
sock.close()
@ -573,11 +581,12 @@ def verifyArgs(args):
def main():
rawargs = parseArgs()
args = verifyArgs(vars(rawargs.parse_args()))
modifyDirmngr('new', args)
fprs = getKeys(args)
trusts = trustKeys(fprs, args)
sigs = sigKeys(trusts, args)
modifyDirmngr('old', args)
sess = sigsession(args)
sess.modifyDirmngr('new')
sess.getKeys()
sess.trustKeys()
sess.sigKeys()
sess.modifyDirmngr('old')

if __name__ == '__main__':
main()

62
gpg/kant/test.py Executable file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3

# This is more of a documentation on some python-gpgme (https://pypi.python.org/pypi/gpg) examples.
# Because their only documentation for the python bindings is in pydoc, and the C API manual is kind of useless.

import gpg
import gpg.constants
import inspect
import pprint

# my key ID
mykey = '748231EBCBD808A14F5E85D28C004C2F93481F6B'
# a key to test with
theirkey = '63D1CEA387C27A92E0D50AB8343C305F9109D4DC'

# Create a context
# Params:
#armor -- enable ASCII armoring (default False)
#textmode -- enable canonical text mode (default False)
#offline -- do not contact external key sources (default False)
#signers -- list of keys used for signing (default [])
#pinentry_mode -- pinentry mode (default PINENTRY_MODE_DEFAULT)
#protocol -- protocol to use (default PROTOCOL_OpenPGP)
#home_dir -- state directory (default is the engine default)
ctx = gpg.Context()

# Fetch a key from the keyring
#secret -- to request a secret key
mkey = ctx.get_key(mykey)
tkey = ctx.get_key(theirkey)

## Print the attributes of our key and other info
##https://stackoverflow.com/a/41737776
##for k in (mkey, tkey):
#for k in [mkey]:
# for i in inspect.getmembers(k):
# if not i[0].startswith('_'):
# pprint.pprint(i)
#pprint.pprint(ctx.get_engine_info())

# Print the constants
#pprint.pprint(inspect.getmembers(gpg.constants))

# Get remote key. Use an OR to search both keyserver and local.
#ctx.set_keylist_mode(gpg.constants.KEYLIST_MODE_EXTERN|gpg.constants.KEYLIST_MODE_LOCAL)
klmodes = {'local': gpg.constants.KEYLIST_MODE_LOCAL,
'remote': gpg.constants.KEYLIST_MODE_EXTERN,
'both': gpg.constants.KEYLIST_MODE_LOCAL|gpg.constants.KEYLIST_MODE_EXTERN}

# List keys
#pattern -- return keys matching pattern (default: all keys)
#secret -- return only secret keys (default: False)
#mode -- keylist mode (default: list local keys)
#source -- read keys from source instead from the keyring
# (all other options are ignored in this case)
ctx.keylist(pattern = 'bts@square-r00t.net',
secret = False,
mode = klmodes['both'],
source = None)

# Test fetching from a keyserver

View File

@ -1,5 +1,5 @@
748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1
A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,-1
EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true
6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True
<admin@sysadministrivia.com>, full, yes
748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1,3,1
A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,0,0,0
EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true,casual,yes
6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True,Casual,True
<admin@sysadministrivia.com>, full, yes, careful, false

1 748231EBCBD808A14F5E85D28C004C2F93481F6B 4 1 3 1
2 A03CACFD7123AF443A3A185298A8A46921C8DDEF -1 -1 0 0 0
3 EFD9413B17293AFDFE6EA6F1402A088DEDF104CB full true casual yes
4 6FA8AE12AEC90B035EEE444FE70457341A63E830 2 True Casual True
5 <admin@sysadministrivia.com> full yes careful false

View File

@ -42,7 +42,7 @@ sks = {
# I would hope this is self-explanatory. If not, this is where we log the outout of the sks dump process. (and any rsync errors, too)
'logfile': '/var/log/sksdump.log',
# If not None value, where we should push the dumps when done. Can be a local path too, obviously.
'rsync': 'root@sks.mirror.square-r00t.net:/srv/http/sks/dumps/.',
'rsync': 'root@mirror.square-r00t.net:/srv/http/sks/dumps/.',
# How many previous days of dumps should we keep?
'days': 1,
# How many keys to include per dump file