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 # NOTE: The python csv module does NOT skip
# commented lines! # commented lines!
# This is my personal key. Ultimate trust, push key. # This is my personal key. Ultimate trust,
748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1 # push key, careful checking, notify
748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1,3,1
# This is a testing junk key generated on a completely separate box, # This is a testing junk key generated on a completely separate box,
# and does not exist on ANY keyservers. Never trust, Never push. # and does not exist on ANY keyservers nor the local keyring.
A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,-1 # Never trust, local sig, unknown checking, don't notify
# This is jthan's key. assign full trust, push to keyserver. A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,0,0,0
EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true # This is jthan's key.
# This is paden's key. assign Marginal trust, push to keyserver. # assign full trust, push to keyserver, casual checking, notify
6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True 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. # This is the email for the Sysadministrivia serverkey.
# Assign full trust, push to keyserver. # Assign full trust, push to keyserver, careful checking, don't notify
<admin@sysadministrivia.com>, full, yes <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 == 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 == 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_:: *-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. 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_:: *-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 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). (it will be displayed in *-h*/*--help* output).


*-t* _TRUSTLEVEL_, *--trustlevel* _TRUSTLEVEL_:: *-t* _TRUSTLEVEL_, *--trust* _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. 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 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. the default is for KANT to prompt for each key we sign. See *BATCHFILE/CHECKLEVEL* 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).


*-l* _LOCAL_, *--local* _LOCAL_:: *-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)_:: *-s* _KEYSERVER(S)_, *--keyservers* _KEYSERVER(S)_::
The comma-separated keyserver(s) to push to. The default keyserver list is automatically generated at runtime. 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*:: *-b*, *--batch*::
If specified, operate in batch mode. See *BATCHFILE* for more information. 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, 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. 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* *DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF*


The second way to specify a key, as far as KANT is concerned, is to use an email address. 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. 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 == BATCHFILE
@ -78,22 +81,85 @@ correct key ID anyways so it's usually a better idea to have the owner present t
=== Format === 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: 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 === KEY_ID
See *KEY ID FORMAT*. See *KEY ID FORMAT*.


=== TRUSTLEVEL === 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 == SEE ALSO
gpg(1), gpgcong(1) gpg(1), gpgconf(1)


== RESOURCES == RESOURCES


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


== COPYING == COPYING



View File

@ -11,7 +11,9 @@ import subprocess
from io import BytesIO from io import BytesIO
from socket import * from socket import *
import urllib.parse 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: # TODO:
# - http://tanguy.ortolo.eu/blog/article9/pgp-signature-infos edit certification level- possible with pygpgme? # - http://tanguy.ortolo.eu/blog/article9/pgp-signature-infos edit certification level- possible with pygpgme?
@ -36,307 +38,323 @@ import gpgme # non-stdlib; Arch package is "python-pygpgme"
# #
#Thanks again! #Thanks again!


def getKeys(args): class sigsession(object):
# Get our concept def __init__(self, args):
os.environ['GNUPGHOME'] = args['gpgdir'] self.args = args
gpg = gpgme.Context()
keys = {} def getKeys(self):
allkeys = [] # Get our concept
# Do we have the key already? If not, fetch. os.environ['GNUPGHOME'] = self.args['gpgdir']
for k in args['rcpts'].keys(): ctx = gpg.Context()
if args['rcpts'][k]['type'] == 'fpr': keys = {}
allkeys.append(k) self.keyids = []
if args['rcpts'][k]['type'] == 'email': # Do we have the key already? If not, fetch.
# We need to actually do a lookup on the email address. for k in list(self.args['rcpts']):
with open(os.devnull, 'w') as f: if self.args['rcpts'][k]['type'] == 'fpr':
# TODO: replace with gpg.keylist_mode(gpgme.KEYLIST_MODE_EXTERN) and internal mechanisms? self.keyids.append(k)
keyout = subprocess.run(['gpg2', if self.args['rcpts'][k]['type'] == 'email':
'--search-keys', # We need to actually do a lookup on the email address.
'--with-colons', with open(os.devnull, 'w') as f:
'--batch', # TODO: replace with gpg.keylist_mode(gpgme.KEYLIST_MODE_EXTERN) and internal mechanisms?
k], keyout = subprocess.run(['gpg2',
stdout = subprocess.PIPE, '--search-keys',
stderr = f) '--with-colons',
keyout = keyout.stdout.decode('utf-8').splitlines() '--batch',
for line in keyout: k],
if line.startswith('pub:'): stdout = subprocess.PIPE,
key = line.split(':')[1] stderr = f)
keys[key] = {} keyout = keyout.stdout.decode('utf-8').splitlines()
keys[key]['uids'] = {} for line in keyout:
keys[key]['time'] = int(line.split(':')[4]) if line.startswith('pub:'):
elif line.startswith('uid:'): key = line.split(':')[1]
uid = re.split('<(.*)>', urllib.parse.unquote(line.split(':')[1].strip())) keys[key] = {}
uid.remove('') keys[key]['uids'] = {}
uid = [u.strip() for u in uid] keys[key]['time'] = int(line.split(':')[4])
keys[key]['uids'][uid[1]] = {} elif line.startswith('uid:'):
keys[key]['uids'][uid[1]]['comment'] = uid[0] uid = re.split('<(.*)>', urllib.parse.unquote(line.split(':')[1].strip()))
keys[key]['uids'][uid[1]]['time'] = int(line.split(':')[2]) uid.remove('')
if len(keys) > 1: # Print the keys and prompt for a selection. uid = [u.strip() for u in uid]
print('\nWe found the following keys for <{0}>...\n\nKEY ID:'.format(k)) keys[key]['uids'][uid[1]] = {}
for k in keys: keys[key]['uids'][uid[1]]['comment'] = uid[0]
print('{0}\n{1:6}(Generated at {2}) UIDs:'.format(k, '', datetime.datetime.utcfromtimestamp(keys[k]['time']))) keys[key]['uids'][uid[1]]['time'] = int(line.split(':')[2])
for email in keys[k]['uids']: if len(keys) > 1: # Print the keys and prompt for a selection.
print('{0:42}(Generated {3}) <{2}> {1}'.format('', print('\nWe found the following keys for <{0}>...\n\nKEY ID:'.format(k))
keys[k]['uids'][email]['comment'], for k in keys:
email, print('{0}\n{1:6}(Generated at {2}) UIDs:'.format(k, '', datetime.datetime.utcfromtimestamp(keys[k]['time'])))
datetime.datetime.utcfromtimestamp(keys[k]['uids'][email]['time']))) for email in keys[k]['uids']:
print('{0:42}(Generated {3}) <{2}> {1}'.format('',
keys[k]['uids'][email]['comment'],
email,
datetime.datetime.utcfromtimestamp(keys[k]['uids'][email]['time'])))
print()
while True:
key = input('Please enter the (full) appropriate key: ')
if key not in keys.keys():
print('Please enter a full key ID from the list above or hit ctrl-d to exit.')
else:
self.keyids.append(key)
break
else:
if not len(keys.keys()) >= 1:
print('Could not find {0}!'.format(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'])))
for email in keys[key]['uids']:
print('\t(Generated {2}) {0} <{1}>'.format(keys[key]['uids'][email]['comment'],
email,
datetime.datetime.utcfromtimestamp(keys[key]['uids'][email]['time'])))
self.keyids.append(key)
print() print()
while True: ## And now we can (FINALLY) fetch the key(s).
key = input('Please enter the (full) appropriate key: ') # TODO: replace with gpg.keylist_mode(gpgme.KEYLIST_MODE_EXTERN) and internal mechanisms?
if key not in keys.keys(): recvcmd = ['gpg2', '--recv-keys', '--batch', '--yes'] # We'll add the keys onto the end of this next.
print('Please enter a full key ID from the list above or hit ctrl-d to exit.') recvcmd.extend(self.keyids)
else: with open(os.devnull, 'w') as f:
allkeys.append(key) fetchout = subprocess.run(recvcmd, stdout = f, stderr = f) # We hide stderr because gpg, for some unknown reason, spits non-errors to stderr.
break return(self.keyids)

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],
1: ['untrusted', gpgme.VALIDITY_UNDEFINED],
2: ['marginal', gpgme.VALIDITY_MARGINAL],
3: ['full', gpgme.VALIDITY_FULL],
4: ['ultimate', gpgme.VALIDITY_ULTIMATE]}
locmap = {0: ['no', False],
1: ['yes', True]}
def promptTrust(kinfo):
for k in list(kinfo):
if 'trust' not in kinfo[k].keys():
trust_lvl = None
trust_in = input(('\nWhat trust level should we assign to {0}?\n\t\t\t\t ({1} <{2}>)' +
'\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\nTrust: ').format(k, kinfo[k]['name'], kinfo[k]['email']))
for dictk, dictv in trustmap.items():
if trust_in.lower().strip() == dictv[0]:
trust_lvl = int(dictk)
elif trust_in == str(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 'local' not in kinfo[k].keys():
local = False
if args['keyservers']:
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 self.keyids:
if k not in kinfo.keys():
kinfo[k] = {}
else: else:
if not len(keys.keys()) >= 1: continue # The key was already parsed; don't waste time adding the info
print('Could not find {0}!'.format(k)) try:
del(args['rcpts'][k]) kobj = gpg.get_key(k)
continue kinfo[k]['name'] = kobj.uids[0].name
key = list(keys.keys())[0] kinfo[k]['email'] = kobj.uids[0].email
print('\nFound key {0} for {1} (Generated at {2}):'.format(key, k, datetime.datetime.utcfromtimestamp(keys[key]['time']))) except gpgme.GpgmeError:
for email in keys[key]['uids']: print('Can\'t get information about key {0}; skipping.'.format(k))
print('\t(Generated {2}) {0} <{1}>'.format(keys[key]['uids'][email]['comment'], del(kinfo[k])
email, if not args['batch']:
datetime.datetime.utcfromtimestamp(keys[key]['uids'][email]['time']))) if not args['trustlevel']:
allkeys.append(key) self.trusts = promptTrust(kinfo)
print() else:
## And now we can (FINALLY) fetch the key(s). for k in list(kinfo):
# TODO: replace with gpg.keylist_mode(gpgme.KEYLIST_MODE_EXTERN) and internal mechanisms? local = False
recvcmd = ['gpg2', '--recv-keys', '--batch', '--yes'] # We'll add the keys onto the end of this next. if 'trust' not in kinfo[k].keys():
recvcmd.extend(allkeys) for dictk, dictv in trustmap.items():
with open(os.devnull, 'w') as f: if args['trustlevel'].lower().strip() == dictv[0]:
fetchout = subprocess.run(recvcmd, stdout = f, stderr = f) # We hide stderr because gpg, for some unknown reason, spits non-errors to stderr. trust_lvl = int(dictk)
return(allkeys) elif args['trustlevel'] == str(dictk):

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


def sigKeys(trusts, args): # The More Business-End(TM) def sigKeys(self): # The More Business-End(TM)
import pprint os.environ['GNUPGHOME'] = args['gpgdir']
pprint.pprint(trusts) ctx = gpg.Context()
os.environ['GNUPGHOME'] = args['gpgdir'] ctx.keylist_mode = gpg.KEYLIST_MODE_SIGS
gpg = gpgme.Context() mkey = ctx.get_key(args['sigkey'])
gpg.keylist_mode = gpgme.KEYLIST_MODE_SIGS ctx.signers = [mkey]
mkey = gpg.get_key(args['sigkey']) global_policy = {}
gpg.signers = [mkey] for k in list(self.trusts):
global_policy = {} sign = True
global_policy['push'] = True # I may be able to provide a way to explicitly change this at runtime later key = ctx.get_key(k)
global_policy['sign'] = True for uid in key.uids:
if not args['keyservers']: for s in uid.signatures:
global_policy['sign'] = 'local' try:
global_policy['push'] = False signerkey = ctx.get_key(s.keyid).subkeys[0].fpr
for k in list(trusts): if signerkey == mkey.subkeys[0].fpr:
sign = True sign = False # We already signed this key
key = gpg.get_key(k) except gpgme.GpgError:
for uid in key.uids: pass # usually if we get this it means we don't have a signer's key in our keyring
for s in uid.signatures: self.trusts[k]['sign'] = sign
try: import pprint
signerkey = gpg.get_key(s.keyid).subkeys[0].fpr pprint.pprint(self.trusts)
if signerkey == mkey.subkeys[0].fpr: # edit_sign(ctx, key, index=0, local=False, norevoke=False, expire=True, check=0)
sign = False # We already signed this key # index: the index of the user ID to sign, starting at 1. Sign all
except gpgme.GpgmeError: # user IDs if set to 0.
pass # usually if we get this it means we don't have a signer's key in our keyring # local: make a local signature
trusts[k]['sign'] = sign # norevoke: make a non-revokable signature
# command: the type of signature. One of sign, lsign, tsign or nrsign.
# expire: whether the signature should expire with the key.
# check: Amount of checking performed. One of:
# 0 - no answer
# 1 - no checking
# 2 - casual checking
# 3 - careful checking


# edit_sign(ctx, key, index=0, local=False, norevoke=False, expire=True, check=0) #gpgme.editutil.edit_sign(gpg, k, index = 0, lo
# index: the index of the user ID to sign, starting at 1. Sign all
# user IDs if set to 0.
# local: make a local signature
# norevoke: make a non-revokable signature
# command: the type of signature. One of sign, lsign, tsign or nrsign.
# expire: whether the signature should expire with the key.
# check: Amount of checking performed. One of:
# 0 - no answer
# 1 - no checking
# 2 - casual checking
# 3 - careful checking

#gpgme.editutil.edit_sign(gpg, k, index = 0, lo




def pushKeys(): # The Last Business-End(TM) def pushKeys(): # The Last Business-End(TM)
pass pass


def modifyDirmngr(op, args): def modifyDirmngr(self, op):
if not args['keyservers']: if not self.args['keyservers']:
return() return()
pid = str(os.getpid()) 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) bakcfg = '{0}.{1}'.format(activecfg, pid)
if op in ('new', 'start'): if op in ('new', 'start'):
if os.path.lexists(activecfg): if os.path.lexists(activecfg):
shutil.copy2(activecfg, bakcfg) shutil.copy2(activecfg, bakcfg)
with open(bakcfg, 'r') as read, open(activecfg, 'w') as write:
for line in read:
if not line.startswith('keyserver '):
write.write(line)
with open(activecfg, 'a') as f:
for s in 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'):
if os.path.lexists(bakcfg):
with open(bakcfg, 'r') as read, open(activecfg, 'w') as write: with open(bakcfg, 'r') as read, open(activecfg, 'w') as write:
for line in read: for line in read:
write.write(line) if not line.startswith('keyserver '):
os.remove(bakcfg) write.write(line)
else: with open(activecfg, 'a') as f:
os.remove(activecfg) for s in self.args['keyservers']:
subprocess.run(['gpgconf', uri = '{0}://{1}:{2}'.format(s['proto'], s['server'], s['port'][0])
'--reload', f.write('keyserver {0}\n'.format(uri))
'dirmngr']) if op in ('old', 'stop'):
return() if os.path.lexists(bakcfg):
with open(bakcfg, 'r') as read, open(activecfg, 'w') as write:
for line in read:
write.write(line)
os.remove(bakcfg)
else:
os.remove(activecfg)
subprocess.run(['gpgconf',
'--reload',
'dirmngr'])
return()


def serverParser(uri): def serverParser(uri):
# https://en.wikipedia.org/wiki/Key_server_(cryptographic)#Keyserver_examples # https://en.wikipedia.org/wiki/Key_server_(cryptographic)#Keyserver_examples
@ -385,8 +403,8 @@ def parseArgs():
if not defgpgdir: if not defgpgdir:
return(None) return(None)
defkey = None defkey = None
gpg = gpgme.Context() ctx = gpg.Context()
for k in gpg.keylist(None, True): # params are query and secret keyring, respectively 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): if k.can_sign and True not in (k.revoked, k.expired, k.disabled):
defkey = k.subkeys[0].fpr defkey = k.subkeys[0].fpr
break # We'll just use the first primary key we find that's valid as the default. 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) defkey = getDefKey(defgpgdir)
defkeyservers = getDefKeyservers(defgpgdir) defkeyservers = getDefKeyservers(defgpgdir)
args = argparse.ArgumentParser(description = 'Keysigning Assistance and Notifying Tool (KANT)', args = argparse.ArgumentParser(description = 'Keysigning Assistance and Notifying Tool (KANT)',
epilog = 'brent s. || 2017 || https://square-r00t.net', epilog = 'brent s. || 2017 || https://square-r00t.net')
formatter_class = argparse.RawTextHelpFormatter)
args.add_argument('-k', args.add_argument('-k',
'--keys', '--keys',
dest = 'keys', dest = 'keys',
metavar = 'KEYS | /path/to/batchfile', metavar = 'KEYS | /path/to/batchfile',
required = True, required = True,
help = 'A single or comma-separated list of keys to sign,\n' + help = 'A single/comma-separated list of keys to sign, ' +
'trust, and notify. Can also be an email address.\n' + 'trust, & notify. Can also be an email address. ' +
'If -b/--batch is specified, this should instead be\n' + 'If -b/--batch is specified, this should instead be ' +
'a path to the batch file.') 'a path to the batch file. See the man page for more info.')
args.add_argument('-K', args.add_argument('-K',
'--sigkey', '--sigkey',
dest = 'sigkey', dest = 'sigkey',
default = defkey, 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', args.add_argument('-t',
'--trust', '--trust',
dest = 'trustlevel', dest = 'trustlevel',
default = None, default = None,
help = 'The trust level to automatically apply to all keys\n' + help = 'The trust level to automatically apply to all keys ' +
'(if not specified, kant will prompt for each key).\n' + '(if not specified, kant will prompt for each key). ' +
'See -b/--batch for trust level notations.') 'See BATCHFILE/TRUSTLEVEL in the man page for trust ' +
'level notations.')
args.add_argument('-c', args.add_argument('-c',
'--check', '--check',
dest = 'checklevel', dest = 'checklevel',
default = None, 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.') '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', args.add_argument('-l',
'--local', '--local',
dest = 'local', dest = 'local',
default = 'false', default = 'false',
help = 'Make the signature(s) local-only (i.e. don\'t push to a keyserver).') 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', args.add_argument('-s',
'--keyservers', '--keyservers',
dest = 'keyservers', dest = 'keyservers',
default = defkeyservers, default = defkeyservers,
help = 'The comma-separated keyserver(s) to push to.\n' + 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))) 'Default keyserver list is: \n\n\t\033[1m{0}\033[0m\n\n'.format(re.sub(',',
# This will require some restructuring... '\n\t',
defkeyservers)))
args.add_argument('-b', args.add_argument('-b',
'--batch', '--batch',
dest = 'batch', dest = 'batch',
action = 'store_true', action = 'store_true',
help = 'If specified, -k/--keys is a CSV file to use as a\n' + help = 'If specified, -k/--keys is a CSV file to use as a ' +
'batch run in the format of (one per line):\n' + 'batch run. See the BATCHFILE section in the man page for more info.')
'\n\033[1mKEY_ID,TRUSTLEVEL,PUSH,CHECKLEVEL,EXPORT\033[0m\n' args.add_argument('-D',
'\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',
'--gpgdir', '--gpgdir',
dest = 'gpgdir', dest = 'gpgdir',
default = defgpgdir, default = defgpgdir,
@ -533,12 +541,12 @@ def verifyArgs(args):
raise NotADirectoryError('{0} is not a directory'.format(args['gpgdir'])) raise NotADirectoryError('{0} is not a directory'.format(args['gpgdir']))
try: try:
os.environ['GNUPGHOME'] = args['gpgdir'] os.environ['GNUPGHOME'] = args['gpgdir']
gpg = gpgme.Context() ctx = gpg.Context()
except: except:
raise RuntimeError('Could not use {0} as a GnuPG home'.format(args['gpgdir'])) raise RuntimeError('Could not use {0} as a GnuPG home'.format(args['gpgdir']))
# Now we need to verify that the private key exists... # Now we need to verify that the private key exists...
try: try:
sigkey = gpg.get_key(args['sigkey'], True) sigkey = ctx.get_key(args['sigkey'], True)
except GpgmeError: except GpgmeError:
raise ValueError('Cannot use key {0}'.format(args['sigkey'])) raise ValueError('Cannot use key {0}'.format(args['sigkey']))
# And that it is an eligible candidate to use to sign. # And that it is an eligible candidate to use to sign.
@ -548,12 +556,12 @@ def verifyArgs(args):
if args['testkeyservers']: if args['testkeyservers']:
for s in args['keyservers']: for s in args['keyservers']:
# Test to make sure the keyserver is accessible. # Test to make sure the keyserver is accessible.
# First we need to construct a way to use python's socket connector v6test = socket(AF_INET6, SOCK_DGRAM)
# Great. Now we need to just quickly check to make sure it's accessible - if specified. try:
if args['netproto'] == '4': v6test.connect(('ipv6.square-r00t.net', 0))
nettype = AF_INET nettype = AF_INET6 # We have IPv6 intarwebz
elif args['netproto'] == '6': except:
nettype = AF_INET6 nettype = AF_INET # No IPv6, default to IPv4
for proto in s['port'][1]: for proto in s['port'][1]:
if proto == 'udp': if proto == 'udp':
netproto = SOCK_DGRAM netproto = SOCK_DGRAM
@ -564,7 +572,7 @@ def verifyArgs(args):
tests = sock.connect_ex((s['server'], int(s['port'][0]))) 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()) uristr = '{0}://{1}:{2} ({3})'.format(s['proto'], s['server'], s['port'][0], proto.upper())
if not tests == 0: if not tests == 0:
raise RuntimeError('Keyserver {0} is not available'.format(uristr)) raise OSError('Keyserver {0} is not available'.format(uristr))
else: else:
print('Keyserver {0} is accepting connections.'.format(uristr)) print('Keyserver {0} is accepting connections.'.format(uristr))
sock.close() sock.close()
@ -573,11 +581,12 @@ def verifyArgs(args):
def main(): def main():
rawargs = parseArgs() rawargs = parseArgs()
args = verifyArgs(vars(rawargs.parse_args())) args = verifyArgs(vars(rawargs.parse_args()))
modifyDirmngr('new', args) sess = sigsession(args)
fprs = getKeys(args) sess.modifyDirmngr('new')
trusts = trustKeys(fprs, args) sess.getKeys()
sigs = sigKeys(trusts, args) sess.trustKeys()
modifyDirmngr('old', args) sess.sigKeys()
sess.modifyDirmngr('old')


if __name__ == '__main__': if __name__ == '__main__':
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 748231EBCBD808A14F5E85D28C004C2F93481F6B,4,1,3,1
A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,-1 A03CACFD7123AF443A3A185298A8A46921C8DDEF,-1,0,0,0
EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true EFD9413B17293AFDFE6EA6F1402A088DEDF104CB,full,true,casual,yes
6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True 6FA8AE12AEC90B035EEE444FE70457341A63E830,2,True,Casual,True
<admin@sysadministrivia.com>, full, yes <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) # 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', 'logfile': '/var/log/sksdump.log',
# If not None value, where we should push the dumps when done. Can be a local path too, obviously. # 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? # How many previous days of dumps should we keep?
'days': 1, 'days': 1,
# How many keys to include per dump file # How many keys to include per dump file