Merge branch 'fix/2131-unittests' into develop
commit
4706932275
2
Makefile
2
Makefile
|
@ -11,7 +11,7 @@ cleantest: clean
|
|||
rm gnupg/tests/*.log
|
||||
|
||||
test: cleantest
|
||||
python gnupg/tests/test_gnupg.py basic genkey sign
|
||||
python gnupg/tests/test_gnupg.py parsers basic genkey sign
|
||||
|
||||
install:
|
||||
python setup.py install --record installed-files.txt
|
||||
|
|
117
gnupg/gnupg.py
117
gnupg/gnupg.py
|
@ -137,26 +137,25 @@ def _copy_data(instream, outstream):
|
|||
if len(data) == 0:
|
||||
break
|
||||
sent += len(data)
|
||||
logger.debug("sending chunk (%d): %r", sent, data[:256])
|
||||
logger.debug("_copy_data(): sending chunk (%d):\n%s" % (sent, data[:256]))
|
||||
try:
|
||||
outstream.write(data)
|
||||
except UnicodeError:
|
||||
try:
|
||||
outstream.write(data.encode(enc))
|
||||
except IOError:
|
||||
logger.exception('Error sending data: Broken pipe')
|
||||
logger.exception('_copy_data(): Error sending data: Broken pipe')
|
||||
break
|
||||
except IOError:
|
||||
# Can sometimes get 'broken pipe' errors even when the
|
||||
# data has all been sent
|
||||
logger.exception('Error sending data: Broken pipe')
|
||||
# Can get 'broken pipe' errors even when all data was sent
|
||||
logger.exception('_copy_data(): Error sending data: Broken pipe')
|
||||
break
|
||||
try:
|
||||
outstream.close()
|
||||
except IOError:
|
||||
logger.exception('Got IOError while trying to close FD outstream')
|
||||
logger.exception('_copy_data(): Got IOError while closing %s' % outstream)
|
||||
else:
|
||||
logger.debug("closed output, %d bytes sent", sent)
|
||||
logger.debug("_copy_data(): Closed output, %d bytes sent." % sent)
|
||||
|
||||
def _threaded_copy_data(instream, outstream):
|
||||
"""Copy data from one stream to another in a separate thread.
|
||||
|
@ -413,10 +412,6 @@ class GPG(object):
|
|||
if isinstance(message, file):
|
||||
result = self._sign_file(message, **kwargs)
|
||||
elif not util._is_stream(message):
|
||||
if isinstance(message, str):
|
||||
if not util._py3k:
|
||||
message = unicode(message, self.encoding)
|
||||
message = message.encode(self.encoding)
|
||||
f = util._make_binary_stream(message, self.encoding)
|
||||
result = self._sign_file(f, **kwargs)
|
||||
f.close()
|
||||
|
@ -429,7 +424,7 @@ class GPG(object):
|
|||
def _sign_file(self, file, keyid=None, passphrase=None, clearsign=True,
|
||||
detach=False, binary=False):
|
||||
"""Create a signature for a file."""
|
||||
logger.debug("GPG._sign_file(): %s", file)
|
||||
logger.debug("_sign_file(): %s", file)
|
||||
if binary:
|
||||
args = ['--sign']
|
||||
else:
|
||||
|
@ -438,10 +433,8 @@ class GPG(object):
|
|||
if clearsign:
|
||||
args.append("--clearsign")
|
||||
if detach:
|
||||
logger.warn(
|
||||
"Cannot use --clearsign and --detach-sign simultaneously.")
|
||||
logger.warn(
|
||||
"Using default GPG behaviour: --clearsign only.")
|
||||
logger.warn("Cannot use both --clearsign and --detach-sign.")
|
||||
logger.warn("Using default GPG behaviour: --clearsign only.")
|
||||
elif detach and not clearsign:
|
||||
args.append("--detach-sign")
|
||||
|
||||
|
@ -449,8 +442,8 @@ class GPG(object):
|
|||
args.append(str("--default-key %s" % keyid))
|
||||
|
||||
result = self._result_map['sign'](self)
|
||||
#We could use _handle_io here except for the fact that if the
|
||||
#passphrase is bad, gpg bails and you can't write the message.
|
||||
## We could use _handle_io here except for the fact that if the
|
||||
## passphrase is bad, gpg bails and you can't write the message.
|
||||
p = self._open_subprocess(args, passphrase is not None)
|
||||
try:
|
||||
stdin = p.stdin
|
||||
|
@ -458,7 +451,7 @@ class GPG(object):
|
|||
_write_passphrase(stdin, passphrase, self.encoding)
|
||||
writer = _threaded_copy_data(file, stdin)
|
||||
except IOError:
|
||||
logging.exception("error writing message")
|
||||
logger.exception("_sign_file(): Error writing message")
|
||||
writer = None
|
||||
self._collect_output(p, result, writer, stdin)
|
||||
return result
|
||||
|
@ -483,50 +476,43 @@ class GPG(object):
|
|||
f.close()
|
||||
return result
|
||||
|
||||
def verify_file(self, file, data_filename=None):
|
||||
"""
|
||||
Verify the signature on the contents of a file or file-like
|
||||
def verify_file(self, file, sig_file=None):
|
||||
"""Verify the signature on the contents of a file or file-like
|
||||
object. Can handle embedded signatures as well as detached
|
||||
signatures. If using detached signatures, the file containing the
|
||||
detached signature should be specified as the :param:data_filename.
|
||||
detached signature should be specified as the ``sig_file``.
|
||||
|
||||
:param file file: A file descriptor object. Its type will be checked
|
||||
with :func:util._is_file.
|
||||
:param file data_filename: A file containing the GPG signature data for
|
||||
:param:file. If given, :param:file is
|
||||
verified via this detached signature.
|
||||
with :func:`util._is_file`.
|
||||
:param str sig_file: A file containing the GPG signature data for
|
||||
``file``. If given, ``file`` is verified via this
|
||||
detached signature.
|
||||
"""
|
||||
## attempt to wrap any escape characters in quotes:
|
||||
safe_file = _fix_unsafe(file)
|
||||
|
||||
## check that :param:file is actually a file:
|
||||
util._is_file(safe_file)
|
||||
|
||||
logger.debug('verify_file: %r, %r', safe_file, data_filename)
|
||||
fn = None
|
||||
result = self._result_map['verify'](self)
|
||||
args = ['--verify']
|
||||
if data_filename is None:
|
||||
self._handle_io(args, safe_file, result, binary=True)
|
||||
|
||||
if sig_file is None:
|
||||
logger.debug("verify_file(): Handling embedded signature")
|
||||
args = ["--verify"]
|
||||
proc = self._open_subprocess(args)
|
||||
writer = _threaded_copy_data(file, proc.stdin)
|
||||
self._collect_output(proc, result, writer, stdin=proc.stdin)
|
||||
else:
|
||||
safe_data_filename = _fix_unsafe(data_filename)
|
||||
|
||||
logger.debug('Handling detached verification')
|
||||
fd, fn = tempfile.mkstemp(prefix='pygpg')
|
||||
|
||||
with open(safe_file) as sf:
|
||||
contents = sf.read()
|
||||
os.write(fd, s)
|
||||
os.close(fd)
|
||||
logger.debug('Wrote to temp file: %r', contents)
|
||||
args.append(fn)
|
||||
args.append('"%s"' % safe_data_filename)
|
||||
|
||||
try:
|
||||
p = self._open_subprocess(args)
|
||||
self._collect_output(p, result, stdin=p.stdin)
|
||||
finally:
|
||||
os.unlink(fn)
|
||||
|
||||
if not util._is_file(sig_file):
|
||||
logger.debug("verify_file(): '%r' is not a file" % sig_file)
|
||||
return result
|
||||
logger.debug('verify_file(): Handling detached verification')
|
||||
sig_fh = None
|
||||
try:
|
||||
sig_fh = open(sig_file)
|
||||
args = ["--verify %s - " % sig_fh.name]
|
||||
proc = self._open_subprocess(args)
|
||||
writer = _threaded_copy_data(file, proc.stdin)
|
||||
self._collect_output(proc, result, stdin=proc.stdin)
|
||||
finally:
|
||||
if sig_fh and not sig_fh.closed:
|
||||
sig_fh.close()
|
||||
return result
|
||||
|
||||
#
|
||||
|
@ -659,13 +645,12 @@ class GPG(object):
|
|||
>>> pubkeys = gpg.list_keys()
|
||||
>>> assert print1 in pubkeys.fingerprints
|
||||
>>> assert print2 in pubkeys.fingerprints
|
||||
|
||||
"""
|
||||
|
||||
which='public-keys'
|
||||
if secret:
|
||||
which='secret-keys'
|
||||
args = "--list-%s --fixed-list-mode --fingerprint --with-colons" % (which,)
|
||||
args = "--list-%s --fixed-list-mode --fingerprint --with-colons --no-show-photos" % (which,)
|
||||
args = [args]
|
||||
p = self._open_subprocess(args)
|
||||
|
||||
|
@ -692,6 +677,17 @@ class GPG(object):
|
|||
getattr(result, keyword)(L)
|
||||
return result
|
||||
|
||||
def list_sigs(self, *keyids):
|
||||
"""xxx implement me
|
||||
|
||||
The GnuPG option '--show-photos', according to the GnuPG manual, "does
|
||||
not work with --with-colons", but since we can't rely on all versions
|
||||
of GnuPG to explicitly handle this correctly, we should probably
|
||||
include it in the args.
|
||||
"""
|
||||
## we will want to include "--no-show-photos" in the args
|
||||
raise NotImplemented("Functionality for '--list-sigs' not implemented.")
|
||||
|
||||
def gen_key(self, input):
|
||||
"""
|
||||
Generate a key; you might use gen_key_input() to create the control
|
||||
|
@ -871,8 +867,7 @@ class GPG(object):
|
|||
return result
|
||||
|
||||
def decrypt(self, message, **kwargs):
|
||||
"""
|
||||
Decrypt the contents of a string or file-like object :param:message .
|
||||
"""Decrypt the contents of a string or file-like object ``message``.
|
||||
|
||||
:param message: A string or file-like object to decrypt.
|
||||
"""
|
||||
|
@ -967,9 +962,7 @@ class GPGWrapper(GPG):
|
|||
passphrase=passphrase)
|
||||
|
||||
def send_keys(self, keyserver, *keyids):
|
||||
"""
|
||||
Send keys to a keyserver
|
||||
"""
|
||||
"""Send keys to a keyserver."""
|
||||
result = self._result_map['list'](self)
|
||||
gnupg.logger.debug('send_keys: %r', keyids)
|
||||
data = gnupg.util._make_binary_stream("", self.encoding)
|
||||
|
|
189
gnupg/parsers.py
189
gnupg/parsers.py
|
@ -34,6 +34,7 @@ import util
|
|||
|
||||
|
||||
ESCAPE_PATTERN = re.compile(r'\\x([0-9a-f][0-9a-f])', re.I)
|
||||
HEXIDECIMAL = re.compile('([0-9A-F]{2})+')
|
||||
|
||||
|
||||
class ProtectedOption(Exception):
|
||||
|
@ -268,16 +269,13 @@ def _is_allowed(input):
|
|||
## assertion will check that GPG will recognise them
|
||||
##
|
||||
## xxx checkout the --store option for creating rfc1991 data packets
|
||||
## xxx also --multifile use with verify encrypt & decrypt
|
||||
## xxx key fetching/retrieving options: [fetch_keys, merge_only, recv_keys]
|
||||
##
|
||||
## xxx which ones do we want as defaults?
|
||||
## eg, --no-show-photos would mitigate things like
|
||||
## https://www-01.ibm.com/support/docview.wss?uid=swg21620982
|
||||
_allowed = frozenset(
|
||||
['--list-keys', '--list-key', '--fixed-list-mode',
|
||||
'--list-secret-keys', '--list-public-keys',
|
||||
'--list-packets', '--with-colons',
|
||||
'--no-show-photos',
|
||||
'--delete-keys', '--delete-secret-keys',
|
||||
'--encrypt', '--encrypt-files',
|
||||
'--decrypt', '--decrypt-files',
|
||||
|
@ -327,6 +325,17 @@ def _is_allowed(input):
|
|||
return input
|
||||
return None
|
||||
|
||||
def _is_hex(string):
|
||||
"""Check that a string is hexidecimal, with alphabetic characters
|
||||
capitalized and without whitespace.
|
||||
|
||||
:param str string: The string to check.
|
||||
"""
|
||||
matched = HEXIDECIMAL.match(string)
|
||||
if matched is not None and len(matched.group()) >= 2:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _sanitise(*args):
|
||||
"""Take an arg or the key portion of a kwarg and check that it is in the
|
||||
set of allowed GPG options and flags, and that it has the correct
|
||||
|
@ -388,65 +397,93 @@ def _sanitise(*args):
|
|||
if flag in ['--encrypt', '--encrypt-files', '--decrypt',
|
||||
'--decrypt-file', '--import', '--verify']:
|
||||
## Place checks here:
|
||||
if _is_file(val):
|
||||
if util._is_file(val):
|
||||
safe_option += (val + " ")
|
||||
else:
|
||||
logger.debug("_check_option(): %s not file: %s"
|
||||
% (flag, val))
|
||||
elif flag in ['--default-key']:
|
||||
if _is_hex(val):
|
||||
safe_option += (val + " ")
|
||||
else:
|
||||
logger.debug("_check_option(): '%s %s' not hex."
|
||||
% (flag, val))
|
||||
else:
|
||||
safe_option += (val + " ")
|
||||
logger.debug("_check_option(): No checks for %s"
|
||||
% val)
|
||||
return safe_option
|
||||
|
||||
is_flag = lambda x: x.startswith('-')
|
||||
checked = []
|
||||
is_flag = lambda x: x.startswith('--')
|
||||
|
||||
def _make_filo(args_string):
|
||||
filo = arg.split(' ')
|
||||
filo.reverse()
|
||||
logger.debug("_make_filo(): Converted to reverse list: %s" % filo)
|
||||
return filo
|
||||
|
||||
def _make_groups(filo):
|
||||
groups = {}
|
||||
while len(filo) >= 1:
|
||||
last = filo.pop()
|
||||
if is_flag(last):
|
||||
logger.debug("_make_groups(): Got arg: %s" % last)
|
||||
if last == '--verify':
|
||||
groups[last] = str(filo.pop())
|
||||
## accept the read-from-stdin arg:
|
||||
if len(filo) >= 1 and filo[len(filo)-1] == '-':
|
||||
groups[last] += str(' - \'\'') ## gross hack
|
||||
else:
|
||||
groups[last] = str()
|
||||
while len(filo) > 1 and not is_flag(filo[len(filo)-1]):
|
||||
logger.debug("_make_groups(): Got value: %s"
|
||||
% filo[len(filo)-1])
|
||||
groups[last] += (filo.pop() + " ")
|
||||
else:
|
||||
if len(filo) == 1 and not is_flag(filo[0]):
|
||||
logger.debug("_make_groups(): Got value: %s" % filo[0])
|
||||
groups[last] += filo.pop()
|
||||
else:
|
||||
logger.debug("_make_groups(): Got solitary value: %s" % last)
|
||||
groups["xxx"] = last
|
||||
return groups
|
||||
|
||||
def _check_groups(groups):
|
||||
logger.debug("_check_groups(): Got groups: %s" % groups)
|
||||
checked_groups = []
|
||||
for a,v in groups.items():
|
||||
v = None if len(v) == 0 else v
|
||||
safe = _check_option(a, v)
|
||||
if safe is not None and not safe.strip() == "":
|
||||
logger.debug("_check_groups(): appending option: %s" % safe)
|
||||
checked_groups.append(safe)
|
||||
else:
|
||||
logger.debug("_check_groups(): dropped option '%s %s'" % (a,v))
|
||||
return checked_groups
|
||||
|
||||
if args is not None:
|
||||
option_groups = {}
|
||||
for arg in args:
|
||||
## if we're given a string with a bunch of options in it split them
|
||||
## up and deal with them separately
|
||||
if isinstance(arg, str):
|
||||
logger.debug("_sanitise(): Got arg string: %s" % arg)
|
||||
## if we're given a string with a bunch of options in it split
|
||||
## them up and deal with them separately
|
||||
if arg.find(' ') > 0:
|
||||
filo = arg.split()
|
||||
filo.reverse()
|
||||
new_arg, new_value = str(), str()
|
||||
while len(filo) > 0:
|
||||
if not is_flag(filo[0]):
|
||||
logger.debug("_sanitise(): Got non-flag arg %s"
|
||||
% filo[0])
|
||||
new_value += (filo.pop() + " ")
|
||||
else:
|
||||
logger.debug("_sanitise(): Got arg: %s" % filo[0])
|
||||
new_arg = filo.pop()
|
||||
if len(filo) > 0:
|
||||
while not is_flag(filo[0]):
|
||||
logger.debug("_sanitise(): Got value: %s"
|
||||
% filo[0])
|
||||
new_value += (filo.pop() + " ")
|
||||
safe = _check_option(new_arg, new_value)
|
||||
if safe is not None and not safe.strip() == "":
|
||||
logger.debug("_sanitise(): appending option: %s"
|
||||
% safe)
|
||||
checked.append(safe)
|
||||
filo = _make_filo(arg)
|
||||
option_groups.update(_make_groups(filo))
|
||||
else:
|
||||
safe = _check_option(arg, None)
|
||||
if safe is not None:
|
||||
logger.debug("_sanitise(): appending args: %s" % safe)
|
||||
checked.append(safe)
|
||||
else:
|
||||
logger.debug("_sanitise(): got None for safe")
|
||||
option_groups.update({ arg: "" })
|
||||
elif isinstance(arg, list):
|
||||
logger.debug("_sanitise(): Got arg list: %s" % arg)
|
||||
allow = _one_flag(arg)
|
||||
if allow is not None:
|
||||
checked.append(allow)
|
||||
arg.reverse()
|
||||
option_groups.update(_make_groups(arg))
|
||||
else:
|
||||
logger.debug("_sanitise(): got non string or list arg: %s"
|
||||
% arg)
|
||||
|
||||
sanitised = ' '.join(x for x in checked)
|
||||
return sanitised
|
||||
logger.debug("_sanitise(): Got non str or list arg: %s" % arg)
|
||||
checked = _check_groups(option_groups)
|
||||
sanitised = ' '.join(x for x in checked)
|
||||
return sanitised
|
||||
else:
|
||||
logger.debug("_sanitise(): Got None for args")
|
||||
|
||||
def _sanitise_list(arg_list):
|
||||
"""A generator for iterating through a list of gpg options and sanitising
|
||||
|
@ -474,30 +511,48 @@ class Verify(object):
|
|||
TRUST_FULLY = 3
|
||||
TRUST_ULTIMATE = 4
|
||||
|
||||
TRUST_LEVELS = {
|
||||
"TRUST_UNDEFINED" : TRUST_UNDEFINED,
|
||||
"TRUST_NEVER" : TRUST_NEVER,
|
||||
"TRUST_MARGINAL" : TRUST_MARGINAL,
|
||||
"TRUST_FULLY" : TRUST_FULLY,
|
||||
"TRUST_ULTIMATE" : TRUST_ULTIMATE,
|
||||
}
|
||||
TRUST_LEVELS = { "TRUST_UNDEFINED" : TRUST_UNDEFINED,
|
||||
"TRUST_NEVER" : TRUST_NEVER,
|
||||
"TRUST_MARGINAL" : TRUST_MARGINAL,
|
||||
"TRUST_FULLY" : TRUST_FULLY,
|
||||
"TRUST_ULTIMATE" : TRUST_ULTIMATE, }
|
||||
|
||||
#: True if the signature is valid, False otherwise.
|
||||
valid = False
|
||||
#: A string describing the status of the signature verification.
|
||||
#: Can be one of ``'signature bad'``, ``'signature good'``,
|
||||
#: ``'signature valid'``, ``'signature error'``, ``'decryption failed'``,
|
||||
#: ``'no public key'``, ``'key exp'``, or ``'key rev'``.
|
||||
status = None
|
||||
#: The fingerprint of the signing keyid.
|
||||
fingerprint = None
|
||||
#: The fingerprint of the corresponding public key, which may be different
|
||||
#: if the signature was created with a subkey.
|
||||
pubkey_fingerprint = None
|
||||
#: The keyid of the signing key.
|
||||
key_id = None
|
||||
#: xxx I'm not sure how this is different to key_id.
|
||||
signature_id = None
|
||||
#: The creation date of the signing key.
|
||||
creation_date = None
|
||||
#: The timestamp of the purported signature, if we are unable to parse it.
|
||||
timestamp = None
|
||||
#: The userid of the signing key which was used to create the signature.
|
||||
username = None
|
||||
#: When the signing key is due to expire.
|
||||
expire_timestamp = None
|
||||
#: The timestamp for when the signature was created.
|
||||
sig_timestamp = None
|
||||
#: A number 0-4 describing the trust level of the signature.
|
||||
trust_level = None
|
||||
#: The string corresponding to the ``trust_level`` number.
|
||||
trust_text = None
|
||||
|
||||
def __init__(self, gpg):
|
||||
self.gpg = gpg
|
||||
self.valid = False
|
||||
self.fingerprint = self.creation_date = self.timestamp = None
|
||||
self.signature_id = self.key_id = None
|
||||
self.username = None
|
||||
self.status = None
|
||||
self.pubkey_fingerprint = None
|
||||
self.expire_timestamp = None
|
||||
self.sig_timestamp = None
|
||||
self.trust_text = None
|
||||
self.trust_level = None
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.valid
|
||||
|
||||
__bool__ = __nonzero__
|
||||
|
||||
def handle_status(self, key, value):
|
||||
|
@ -521,7 +576,6 @@ class Verify(object):
|
|||
self.creation_date,
|
||||
self.sig_timestamp,
|
||||
self.expire_timestamp) = value.split()[:4]
|
||||
# may be different if signature is made with a subkey
|
||||
self.pubkey_fingerprint = value.split()[-1]
|
||||
self.status = 'signature valid'
|
||||
elif key == "SIG_ID":
|
||||
|
@ -574,7 +628,7 @@ class Crypt(Verify):
|
|||
__bool__ = __nonzero__
|
||||
|
||||
def __str__(self):
|
||||
return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
|
||||
return self.data.decode(self.gpg.encoding, self.gpg._decode_errors)
|
||||
|
||||
def handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
@ -676,19 +730,14 @@ class Sign(object):
|
|||
|
||||
#: The type of signature created.
|
||||
sig_type = None
|
||||
|
||||
#: The algorithm used to create the signature.
|
||||
sig_algo = None
|
||||
|
||||
#: The hash algorithm used to create the signature.
|
||||
sig_hash_also = None
|
||||
|
||||
#: The fingerprint of the signing keyid.
|
||||
fingerprint = None
|
||||
|
||||
#: The timestamp on the signature.
|
||||
timestamp = None
|
||||
|
||||
#: xxx fill me in
|
||||
what = None
|
||||
|
||||
|
@ -705,7 +754,7 @@ class Sign(object):
|
|||
__bool__ = __nonzero__
|
||||
|
||||
def __str__(self):
|
||||
return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
|
||||
return self.data.decode(self.gpg.encoding, self.gpg._decode_errors)
|
||||
|
||||
def handle_status(self, key, value):
|
||||
"""Parse a status code from the attached GnuPG process.
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
Privacy is necessary for an open society in the electronic age. Privacy is not
|
||||
secrecy. A private matter is something one doesn't want the whole world to
|
||||
know, but a secret matter is something one doesn't want anybody to
|
||||
know. Privacy is the power to selectively reveal oneself to the world.
|
||||
|
||||
If two parties have some sort of dealings, then each has a memory of their
|
||||
interaction. Each party can speak about their own memory of this; how could
|
||||
anyone prevent it? One could pass laws against it, but the freedom of speech,
|
||||
even more than privacy, is fundamental to an open society; we seek not to
|
||||
restrict any speech at all. If many parties speak together in the same forum,
|
||||
each can speak to all the others and aggregate together knowledge about
|
||||
individuals and other parties. The power of electronic communications has
|
||||
enabled such group speech, and it will not go away merely because we might want
|
||||
it to.
|
||||
|
||||
Since we desire privacy, we must ensure that each party to a transaction have
|
||||
knowledge only of that which is directly necessary for that transaction. Since
|
||||
any information can be spoken of, we must ensure that we reveal as little as
|
||||
possible. In most cases personal identity is not salient. When I purchase a
|
||||
magazine at a store and hand cash to the clerk, there is no need to know who I
|
||||
am. When I ask my electronic mail provider to send and receive messages, my
|
||||
provider need not know to whom I am speaking or what I am saying or what others
|
||||
are saying to me; my provider only need know how to get the message there and
|
||||
how much I owe them in fees. When my identity is revealed by the underlying
|
||||
mechanism of the transaction, I have no privacy. I cannot here selectively
|
||||
reveal myself; I must always reveal myself.
|
||||
|
||||
Therefore, privacy in an open society requires anonymous transaction
|
||||
systems. Until now, cash has been the primary such system. An anonymous
|
||||
transaction system is not a secret transaction system. An anonymous system
|
||||
empowers individuals to reveal their identity when desired and only when
|
||||
desired; this is the essence of privacy.
|
||||
|
||||
Privacy in an open society also requires cryptography. If I say something, I
|
||||
want it heard only by those for whom I intend it. If the content of my speech
|
||||
is available to the world, I have no privacy. To encrypt is to indicate the
|
||||
desire for privacy, and to encrypt with weak cryptography is to indicate not
|
||||
too much desire for privacy. Furthermore, to reveal one's identity with
|
||||
assurance when the default is anonymity requires the cryptographic signature.
|
||||
|
||||
We cannot expect governments, corporations, or other large, faceless
|
||||
organizations to grant us privacy out of their beneficence. It is to their
|
||||
advantage to speak of us, and we should expect that they will speak. To try to
|
||||
prevent their speech is to fight against the realities of
|
||||
information. Information does not just want to be free, it longs to be
|
||||
free. Information expands to fill the available storage space. Information is
|
||||
Rumor's younger, stronger cousin; Information is fleeter of foot, has more
|
||||
eyes, knows more, and understands less than Rumor.
|
||||
|
||||
We must defend our own privacy if we expect to have any. We must come together
|
||||
and create systems which allow anonymous transactions to take place. People
|
||||
have been defending their own privacy for centuries with whispers, darkness,
|
||||
envelopes, closed doors, secret handshakes, and couriers. The technologies of
|
||||
the past did not allow for strong privacy, but electronic technologies do.
|
||||
|
||||
We the Cypherpunks are dedicated to building anonymous systems. We are
|
||||
defending our privacy with cryptography, with anonymous mail forwarding
|
||||
systems, with digital signatures, and with electronic money.
|
||||
|
||||
Cypherpunks write code. We know that someone has to write software to defend
|
||||
privacy, and since we can't get privacy unless we all do, we're going to write
|
||||
it. We publish our code so that our fellow Cypherpunks may practice and play
|
||||
with it. Our code is free for all to use, worldwide. We don't much care if you
|
||||
don't approve of the software we write. We know that software can't be
|
||||
destroyed and that a widely dispersed system can't be shut down.
|
||||
|
||||
Cypherpunks deplore regulations on cryptography, for encryption is
|
||||
fundamentally a private act. The act of encryption, in fact, removes
|
||||
information from the public realm. Even laws against cryptography reach only so
|
||||
far as a nation's border and the arm of its violence. Cryptography will
|
||||
ineluctably spread over the whole globe, and with it the anonymous transactions
|
||||
systems that it makes possible.
|
||||
|
||||
For privacy to be widespread it must be part of a social contract. People must
|
||||
come and together deploy these systems for the common good. Privacy only
|
||||
extends so far as the cooperation of one's fellows in society. We the
|
||||
Cypherpunks seek your questions and your concerns and hope we may engage you so
|
||||
that we do not deceive ourselves. We will not, however, be moved out of our
|
||||
course because some may disagree with our goals.
|
||||
|
||||
The Cypherpunks are actively engaged in making the networks safer for
|
||||
privacy. Let us proceed together apace.
|
||||
|
||||
Onward.
|
||||
|
||||
Eric Hughes <hughes@soda.berkeley.edu>
|
||||
9 March 1993
|
|
@ -17,6 +17,7 @@ import os
|
|||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
## Use unittest2 if we're on Python2.6 or less:
|
||||
if sys.version_info.major == 2 and sys.version_info.minor <= 6:
|
||||
|
@ -45,6 +46,8 @@ def _make_tempfile(*args, **kwargs):
|
|||
*args, **kwargs)
|
||||
|
||||
logger = logging.getLogger('gnupg')
|
||||
_here = os.path.join(os.path.join(util._repo, 'gnupg'), 'tests')
|
||||
_files = os.path.join(_here, 'files')
|
||||
|
||||
KEYS_TO_IMPORT = """-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1.4.9 (MingW32)
|
||||
|
@ -146,6 +149,26 @@ class GPGTestCase(unittest.TestCase):
|
|||
self.pubring = os.path.join(self.homedir, 'pubring.gpg')
|
||||
self.secring = os.path.join(self.homedir, 'secring.gpg')
|
||||
|
||||
def test_parsers_fix_unsafe(self):
|
||||
"""Test that unsafe inputs are quoted out and then ignored."""
|
||||
shell_input = "\"&coproc /bin/sh\""
|
||||
fixed = parsers._fix_unsafe(shell_input)
|
||||
print fixed
|
||||
test_file = os.path.join(_files, 'cypherpunk_manifesto')
|
||||
self.assertTrue(os.path.isfile(test_file))
|
||||
has_shell = self.gpg.verify_file(test_file, fixed)
|
||||
self.assertFalse(has_shell.valid)
|
||||
|
||||
def test_parsers_is_hex_valid(self):
|
||||
"""Test that valid hexidecimal passes the parsers._is_hex() check"""
|
||||
valid_hex = '0A6A58A14B5946ABDE18E207A3ADB67A2CDB8B35'
|
||||
self.assertTrue(parsers._is_hex(valid_hex))
|
||||
|
||||
def test_parsers_is_hex_invalid(self):
|
||||
"""Test that invalid hexidecimal fails the parsers._is_hex() check"""
|
||||
invalid_hex = 'cipherpunks write code'
|
||||
self.assertFalse(parsers._is_hex(invalid_hex))
|
||||
|
||||
def test_gpghome_creation(self):
|
||||
"""Test the environment by ensuring that setup worked."""
|
||||
hd = self.homedir
|
||||
|
@ -271,51 +294,39 @@ class GPGTestCase(unittest.TestCase):
|
|||
self.assertGreater(key_input.find('Francisco Ferrer'), 0)
|
||||
|
||||
def test_rsa_key_generation(self):
|
||||
"""
|
||||
Test that RSA key generation succeeds.
|
||||
"""
|
||||
key = self.generate_key("Barbara Brown", "beta.com")
|
||||
"""Test that RSA key generation succeeds."""
|
||||
key = self.generate_key("Ralph Merkle", "xerox.com")
|
||||
self.assertIsNotNone(key.type)
|
||||
self.assertIsNotNone(key.fingerprint)
|
||||
|
||||
def test_rsa_key_generation_with_unicode(self):
|
||||
"""
|
||||
Test that RSA key generation succeeds with unicode characters.
|
||||
"""
|
||||
"""Test that RSA key generation succeeds with unicode characters."""
|
||||
key = self.generate_key("Anaïs de Flavigny", "êtrerien.fr")
|
||||
self.assertIsNotNone(key.type)
|
||||
self.assertIsNotNone(key.fingerprint)
|
||||
|
||||
def test_rsa_key_generation_with_subkey(self):
|
||||
"""
|
||||
Test that RSA key generation succeeds with additional subkey.
|
||||
"""
|
||||
key = self.generate_key("Need Caffeine", "nowplea.se",
|
||||
"""Test that RSA key generation succeeds with additional subkey."""
|
||||
key = self.generate_key("John Gilmore", "isapu.nk",
|
||||
subkey_type='RSA')
|
||||
self.assertIsNotNone(key.type)
|
||||
self.assertIsNotNone(key.fingerprint)
|
||||
|
||||
def test_dsa_key_generation(self):
|
||||
"""
|
||||
Test that DSA key generation succeeds.
|
||||
"""
|
||||
key = self.generate_key("DSA Signonly", "test.com")
|
||||
"""Test that DSA key generation succeeds."""
|
||||
key = self.generate_key("Ross Anderson", "bearli.on")
|
||||
self.assertIsNotNone(key.type)
|
||||
self.assertIsNotNone(key.fingerprint)
|
||||
|
||||
def test_dsa_key_generation_with_unicode(self):
|
||||
"""
|
||||
Test that DSA key generation succeeds with unicode characters.
|
||||
"""
|
||||
"""Test that DSA key generation succeeds with unicode characters."""
|
||||
key = self.generate_key("破壊合計する", "破壊合計する.日本")
|
||||
self.assertIsNotNone(key.type)
|
||||
self.assertIsNotNone(key.fingerprint)
|
||||
|
||||
def test_dsa_key_generation_with_subkey(self):
|
||||
"""
|
||||
Test that RSA key generation succeeds with additional subkey.
|
||||
"""
|
||||
key = self.generate_key("OMG Moar Coffee", "giveitto.me",
|
||||
"""Test that RSA key generation succeeds with additional subkey."""
|
||||
key = self.generate_key("Eli Biham", "bearli.on",
|
||||
subkey_type='ELG-E')
|
||||
self.assertIsNotNone(key.type)
|
||||
self.assertIsNotNone(key.fingerprint)
|
||||
|
@ -505,49 +516,45 @@ class GPGTestCase(unittest.TestCase):
|
|||
|
||||
def test_signature_algorithm(self):
|
||||
"""Test that determining the signing algorithm works."""
|
||||
key = self.generate_key("Werner Koch", "gnupg.org")
|
||||
message = "Damn, I really wish GnuPG had ECC support."
|
||||
key = self.generate_key("Ron Rivest", "rsa.com")
|
||||
message = "Someone should add GCM block cipher mode to PyCrypto."
|
||||
sig = self.gpg.sign(message, keyid=key.fingerprint,
|
||||
passphrase='wernerkoch')
|
||||
passphrase='ronrivest')
|
||||
print "ALGORITHM:\n", sig.sig_algo
|
||||
self.assertIsNotNone(sig.sig_algo)
|
||||
|
||||
def test_signature_string_bad_passphrase(self):
|
||||
"""Test that signing and verification works."""
|
||||
key = self.generate_key("Ron Rivest", "rsa.com")
|
||||
message = 'Hello, André!'
|
||||
key = self.generate_key("Taher ElGamal", "cryto.me")
|
||||
message = 'أصحاب المصالح لا يحبون الثوراتز'
|
||||
sig = self.gpg.sign(message, keyid=key.fingerprint, passphrase='foo')
|
||||
self.assertFalse(sig, "Bad passphrase should fail")
|
||||
|
||||
def test_signature_string_alternate_encoding(self):
|
||||
key = self.generate_key("Adi Shamir", "rsa.com")
|
||||
key = self.generate_key("Nos Oignons", "nos-oignons.net")
|
||||
self.gpg.encoding = 'latin-1'
|
||||
message = 'Hello, André!'
|
||||
message = "Mêle-toi de tes oignons"
|
||||
sig = self.gpg.sign(message, keyid=key.fingerprint,
|
||||
passphrase='adishamir')
|
||||
passphrase='nosoignons')
|
||||
self.assertTrue(sig)
|
||||
|
||||
def test_signature_file(self):
|
||||
"""Test that signing a message file works."""
|
||||
key = self.generate_key("Leonard Adleman", "rsa.com")
|
||||
message = "Someone should add GCM block cipher mode to PyCrypto."
|
||||
message_fn = os.path.join(tempfile.gettempdir(), 'test_signature_file')
|
||||
with open(message_fn, 'w+b') as msg:
|
||||
msg.write(message)
|
||||
|
||||
message_file = buffer(open(message_fn, "rb").read())
|
||||
mf = io.BytesIO(message_file)
|
||||
|
||||
sig = self.gpg.sign(mf, keyid=key.fingerprint,
|
||||
passphrase='leonardadleman')
|
||||
self.assertTrue(sig, "Good passphrase should succeed")
|
||||
message_file = os.path.join(_files, 'cypherpunk_manifesto')
|
||||
with open(message_file) as msg:
|
||||
sig = self.gpg.sign(msg, keyid=key.fingerprint,
|
||||
passphrase='leonardadleman')
|
||||
self.assertTrue(sig, "I thought I typed my password correctly...")
|
||||
|
||||
def test_signature_string_verification(self):
|
||||
"""Test verification of a signature from a message string."""
|
||||
key = self.generate_key("Andrew Able", "alpha.com")
|
||||
message = 'Hello, André!'
|
||||
key = self.generate_key("Bruce Schneier", "schneier.com")
|
||||
message = '...the government uses the general fear of '
|
||||
message += '[hackers in popular culture] to push for more power'
|
||||
sig = self.gpg.sign(message, keyid=key.fingerprint,
|
||||
passphrase='andrewable')
|
||||
passphrase='bruceschneier')
|
||||
now = time.mktime(time.gmtime())
|
||||
self.assertTrue(sig, "Good passphrase should succeed")
|
||||
verified = self.gpg.verify(sig.data)
|
||||
self.assertIsNotNone(verified.fingerprint)
|
||||
|
@ -556,15 +563,18 @@ class GPGTestCase(unittest.TestCase):
|
|||
logger.debug("ver: %r", verified.fingerprint)
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint,
|
||||
"Fingerprints must match")
|
||||
self.assertEqual(verified.trust_level, verified.TRUST_ULTIMATE)
|
||||
self.assertEqual(verified.trust_text, 'TRUST_ULTIMATE')
|
||||
self.assertEqual(verified.status, 'signature valid')
|
||||
self.assertAlmostEqual(int(now), int(verified.timestamp), delta=1000)
|
||||
self.assertEqual(
|
||||
verified.username,
|
||||
u'Bruce Schneier (python-gnupg tester) <bruceschneier@schneier.com>')
|
||||
|
||||
def test_signature_file_verification(self):
|
||||
"""Test verfication of a signature on a message file."""
|
||||
key = self.generate_key("Taher ElGamal", "cryto.me")
|
||||
message = 'أصحاب المصالح لا يحبون الثوراتز'
|
||||
def test_signature_verification_clearsign(self):
|
||||
"""Test verfication of an embedded signature."""
|
||||
key = self.generate_key("Johan Borst", "rijnda.el")
|
||||
message = "You're *still* using AES? Really?"
|
||||
sig = self.gpg.sign(message, keyid=key.fingerprint,
|
||||
passphrase='taherelgamal')
|
||||
passphrase='johanborst')
|
||||
self.assertTrue(sig, "Good passphrase should succeed")
|
||||
try:
|
||||
file = util._make_binary_stream(sig.data, self.gpg.encoding)
|
||||
|
@ -576,22 +586,41 @@ class GPGTestCase(unittest.TestCase):
|
|||
logger.debug("ver: %r", verified.fingerprint)
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint,
|
||||
"Fingerprints must match")
|
||||
data_file = open('random_binary_data', 'rb')
|
||||
sig = self.gpg._sign_file(data_file, keyid=key.fingerprint,
|
||||
passphrase='andrewable', detach=True)
|
||||
data_file.close()
|
||||
self.assertTrue(sig, "File signing should succeed")
|
||||
try:
|
||||
file = gnupg._make_binary_stream(sig.data, self.gpg.encoding)
|
||||
verified = self.gpg.verify_file(file, 'random_binary_data')
|
||||
except UnicodeDecodeError: #happens in Python 2.6
|
||||
verified = self.gpg.verify_file(io.BytesIO(sig.data))
|
||||
if key.fingerprint != verified.fingerprint:
|
||||
logger.debug("key: %r", key.fingerprint)
|
||||
logger.debug("ver: %r", verified.fingerprint)
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint,
|
||||
"Fingerprints must match")
|
||||
logger.debug("test_signature_verification ends")
|
||||
|
||||
def test_signature_verification_detached(self):
|
||||
"""Test that verification of a detached signature of a file works."""
|
||||
key = self.generate_key("Paulo S.L.M. Barreto", "anub.is")
|
||||
with open(os.path.join(_files, 'cypherpunk_manifesto'),
|
||||
'rb') as manifesto:
|
||||
sig = self.gpg.sign(manifesto, keyid=key.fingerprint,
|
||||
passphrase='paulos.l.m.barreto',
|
||||
detach=True, clearsign=False)
|
||||
self.assertTrue(sig.data, "File signing should succeed")
|
||||
sigfilename = os.path.join(_files, 'cypherpunk_manifesto.sig')
|
||||
with open(sigfilename,'w') as sigfile:
|
||||
sigfile.write(sig.data)
|
||||
sigfile.seek(0)
|
||||
|
||||
verified = self.gpg.verify_file(manifesto, sigfilename)
|
||||
|
||||
if key.fingerprint != verified.fingerprint:
|
||||
logger.debug("key: %r", key.fingerprint)
|
||||
logger.debug("ver: %r", verified.fingerprint)
|
||||
|
||||
self.assertEqual(key.fingerprint, verified.fingerprint,
|
||||
"Fingerprints must match")
|
||||
|
||||
def test_signature_verification_detached_binary(self):
|
||||
"""Test that detached signature verification in binary mode fails."""
|
||||
key = self.generate_key("Adi Shamir", "rsa.com")
|
||||
with open(os.path.join(_files, 'cypherpunk_manifesto'),
|
||||
'rb') as manifesto:
|
||||
sig = self.gpg.sign(manifesto, keyid=key.fingerprint,
|
||||
passphrase='adishamir',
|
||||
detach=True, binary=True, clearsign=False)
|
||||
self.assertTrue(sig.data, "File signing should succeed")
|
||||
with self.assertRaises(UnicodeDecodeError):
|
||||
print "SIG=", sig
|
||||
|
||||
def test_deletion(self):
|
||||
"""Test that key deletion works."""
|
||||
|
@ -639,7 +668,10 @@ class GPGTestCase(unittest.TestCase):
|
|||
logger.debug("test_file_encryption_and_decryption ends")
|
||||
|
||||
|
||||
suites = { 'basic': set(['test_gpghome_creation',
|
||||
suites = { 'parsers': set(['test_parsers_fix_unsafe',
|
||||
'test_parsers_is_hex_valid',
|
||||
'test_parsers_is_hex_invalid',]),
|
||||
'basic': set(['test_gpghome_creation',
|
||||
'test_gpg_binary',
|
||||
'test_gpg_binary_not_abs',
|
||||
'test_gpg_binary_version_str',
|
||||
|
@ -659,7 +691,9 @@ suites = { 'basic': set(['test_gpghome_creation',
|
|||
'test_key_generation_with_empty_value',
|
||||
'test_key_generation_override_default_value',
|
||||
'test_key_generation_with_colons']),
|
||||
'sign': set(['test_signature_file_verification',
|
||||
'sign': set(['test_signature_verification_clearsign',
|
||||
'test_signature_verification_detached',
|
||||
'test_signature_verification_detached_binary',
|
||||
'test_signature_file',
|
||||
'test_signature_string_bad_passphrase',
|
||||
'test_signature_string_alternate_encoding',
|
||||
|
|
|
@ -140,7 +140,7 @@ def _is_file(input):
|
|||
"""
|
||||
try:
|
||||
assert os.lstat(input).st_size > 0, "not a file: %s" % input
|
||||
except (AssertionError, TypeError) as error:
|
||||
except (AssertionError, TypeError, IOError, OSError) as error:
|
||||
logger.debug(error.message)
|
||||
return False
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue