From f0063afa71bc7e71f19d174acc2fde26f0c11850 Mon Sep 17 00:00:00 2001
From: Ben McGinnes
Date: Tue, 15 May 2018 13:13:16 +1000
Subject: docs: python bindings HOWTO - DITA XML version
* Due to the org-babel bug which breaks Python source code examples
beyond the most simple snippets, ported the HOWTO to a source format
which I *know* for sure won't break it.
* Details of the org-mode bug is in https://dev.gnupg.org/T3977
* DITA project uses DITA-OT 2.x (2.4 or 2.5, IIRC) with support for DITA 1.3.
* source files were written with oXygenXML Editor 20.0, hence the
oXygenXML project file in the directory; however only the .ditamap
and .dita files are required to generate any output with the
DITA-OT.
Signed-off-by: Ben McGinnes
---
.../docs/dita/howto/part04/basic-functions.dita | 12 ++
.../docs/dita/howto/part04/clear-signing.dita | 42 ++++++
lang/python/docs/dita/howto/part04/decryption.dita | 31 +++++
.../docs/dita/howto/part04/default-signing.dita | 51 +++++++
.../docs/dita/howto/part04/detached-signing.dita | 42 ++++++
.../docs/dita/howto/part04/encrypt-to-many.dita | 100 ++++++++++++++
.../docs/dita/howto/part04/encrypt-to-one.dita | 83 ++++++++++++
lang/python/docs/dita/howto/part04/encryption.dita | 12 ++
.../dita/howto/part04/signing-key-selection.dita | 28 ++++
lang/python/docs/dita/howto/part04/signing.dita | 11 ++
.../docs/dita/howto/part04/verification.dita | 150 +++++++++++++++++++++
11 files changed, 562 insertions(+)
create mode 100644 lang/python/docs/dita/howto/part04/basic-functions.dita
create mode 100644 lang/python/docs/dita/howto/part04/clear-signing.dita
create mode 100644 lang/python/docs/dita/howto/part04/decryption.dita
create mode 100644 lang/python/docs/dita/howto/part04/default-signing.dita
create mode 100644 lang/python/docs/dita/howto/part04/detached-signing.dita
create mode 100644 lang/python/docs/dita/howto/part04/encrypt-to-many.dita
create mode 100644 lang/python/docs/dita/howto/part04/encrypt-to-one.dita
create mode 100644 lang/python/docs/dita/howto/part04/encryption.dita
create mode 100644 lang/python/docs/dita/howto/part04/signing-key-selection.dita
create mode 100644 lang/python/docs/dita/howto/part04/signing.dita
create mode 100644 lang/python/docs/dita/howto/part04/verification.dita
(limited to 'lang/python/docs/dita/howto/part04')
diff --git a/lang/python/docs/dita/howto/part04/basic-functions.dita b/lang/python/docs/dita/howto/part04/basic-functions.dita
new file mode 100644
index 00000000..a0c64b56
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/basic-functions.dita
@@ -0,0 +1,12 @@
+
+
+
+
+ Basic Functions
+
+ The most frequently called features of any cryptographic library will be the most
+ fundamental tasks for encryption software. In this section we will look at how to
+ programmatically encrypt data, decrypt it, sign it and verify signatures.
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/clear-signing.dita b/lang/python/docs/dita/howto/part04/clear-signing.dita
new file mode 100644
index 00000000..c6104922
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/clear-signing.dita
@@ -0,0 +1,42 @@
+
+
+
+
+ Clear Signatures
+
+ Though PGP/in-line messages are no longer encouraged in favour of PGP/MIME, there is still
+ sometimes value in utilising in-line signatures. This is where clear-signed messages or text
+ is of value.
+
+ import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+
+
+ In spite of the appearance of a clear-signed message, the data handled by GPGME in signing
+ it must still be byte literals.
+
+ import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+with open("/path/to/statement.txt.asc", "wb") as afile:
+ afile.write(signed_data)
+
+
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/decryption.dita b/lang/python/docs/dita/howto/part04/decryption.dita
new file mode 100644
index 00000000..e3918c55
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/decryption.dita
@@ -0,0 +1,31 @@
+
+
+
+
+ Decryption
+
+ Decrypting something encrypted to a key in one's secret keyring is fairly straight
+ forward.
+ In this example code, however, preconfiguring either gpg.Context() or
+ gpg.core.Context() as c is unnecessary because there is
+ no need to modify the Context prior to conducting the decryption and since the Context is
+ only used once, setting it to c simply adds lines for no gain.
+
+ import gpg
+
+ciphertext = input("Enter path and filename of encrypted file: ")
+newfile = input("Enter path and filename of file to save decrypted data to: ")
+
+with open(ciphertext, "rb") as cfile:
+ plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+
+with open(newfile, "wb") as nfile:
+ nfile.write(plaintext)
+
+
+ The data available in plaintext in this example is the decrypted content
+ as a byte object, the recipient key IDs and algorithms in result and the
+ results of verifying any signatures of the data in verify_result.
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/default-signing.dita b/lang/python/docs/dita/howto/part04/default-signing.dita
new file mode 100644
index 00000000..d3a227b5
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/default-signing.dita
@@ -0,0 +1,51 @@
+
+
+
+
+ Default Signatures
+
+ The normal or default signing process is essentially the same as is most often
+ invoked when also encrypting a message or file. So when the encryption component is not
+ utilised, the result is to produce an encoded and signed output which may or may not be
+ ASCII armoured and which may or may not also be compressed.
+ By default compression will be used unless GnuPG detects that the plaintext is already
+ compressed. ASCII armouring will be determined according to the value of
+ gpg.Context().armor.
+ The compression algorithm is selected in much the same way as the symmetric encryption
+ algorithm or the hash digest algorithm is when multiple keys are involved; from the
+ preferences saved into the key itself or by comparison with the preferences with all other
+ keys involved.
+
+ import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context(armor=True, signers=sig_src)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+
+
+ Though everything in this example is accurate, it is more likely that reading the input
+ data from another file and writing the result to a new file will be performed more like the
+ way it is done in the next example. Even if the output format is ASCII armoured.
+
+ import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+with open("/path/to/statement.txt.sig", "wb") as afile:
+ afile.write(signed_data)
+
+
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/detached-signing.dita b/lang/python/docs/dita/howto/part04/detached-signing.dita
new file mode 100644
index 00000000..38406eff
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/detached-signing.dita
@@ -0,0 +1,42 @@
+
+
+
+
+ Detached Signatures
+
+ Detached signatures will often be needed in programmatic uses of GPGME, either for signing
+ files (e.g. tarballs of code releases) or as a component of message signing (e.g. PGP/MIME
+ encoded email).
+
+ import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context(armor=True)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+
+
+ As with normal signatures, detached signatures are best handled as byte literals, even when
+ the output is ASCII armoured.
+
+ import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+c = gpg.Context(signers=sig_src)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+with open("/path/to/statement.txt.sig", "wb") as afile:
+ afile.write(signed_data)
+
+
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/encrypt-to-many.dita b/lang/python/docs/dita/howto/part04/encrypt-to-many.dita
new file mode 100644
index 00000000..df3454f8
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/encrypt-to-many.dita
@@ -0,0 +1,100 @@
+
+
+
+
+ Encrypting to Multiple Keys
+
+ Encrypting to multiple keys essentially just expands upon the key selection process
+ and the recipients from the previous examples.
+ The following example encrypts a message (text) to everyone with an email
+ address on the gnupg.org domain,You probably don't really want to do
+ this. Searching the keyservers for "gnupg.org" produces over 400 results, the majority of
+ which aren't actually at the gnupg.org domain, but just included a comment regarding the
+ project in their key somewhere. but does not encrypt to a default key or other
+ key which is configured to normally encrypt to.
+
+ import gpg
+
+text = b"""Oh look, another test message.
+
+The same rules apply as with the previous example and more likely
+than not, the message will actually be drawn from reading the
+contents of a file or, maybe, from entering data at an input()
+prompt.
+
+Since the text in this case must be bytes, it is most likely that
+the input form will be a separate file which is opened with "rb"
+as this is the simplest method of obtaining the correct data
+format.
+"""
+
+c = gpg.Context(armor=True)
+rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+logrus = []
+
+for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
+
+ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, sign=False,
+ always_trust=True)
+
+with open("secret_plans.txt.asc", "wb") as f:
+ f.write(ciphertext)
+
+
+ All it would take to change the above example to sign the message and also encrypt the
+ message to any configured default keys would be to change the c.encrypt
+ line to this:
+
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ always_trust=True,
+ add_encrypt_to=True)
+
+
+ The only keyword arguments requiring modification are those for which the default values
+ are changing. The default value of sign is True, the
+ default of always_trust is False, the default of
+ add_encrypt_to is False.
+ If always_trust is not set to True and any of the
+ recipient keys are not trusted (e.g. not signed or locally signed) then the encryption will
+ raise an error. It is possible to mitigate this somewhat with something more like this:
+
+ import gpg
+
+with open("secret_plans.txt.asc", "rb") as f:
+ text = f.read()
+
+c = gpg.Context(armor=True)
+rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+logrus = []
+
+for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
+
+try:
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ add_encrypt_to=True)
+except gpg.errors.InvalidRecipients as e:
+ for i in range(len(e.recipients)):
+ for n in range(len(logrus)):
+ if logrus[n].fpr == e.recipients[i].fpr:
+ logrus.remove(logrus[n])
+ else:
+ pass
+ try:
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ add_encrypt_to=True)
+ except:
+ pass
+
+with open("secret_plans.txt.asc", "wb") as f:
+ f.write(ciphertext)
+
+
+ This will attempt to encrypt to all the keys searched for, then remove invalid recipients
+ if it fails and try again.
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/encrypt-to-one.dita b/lang/python/docs/dita/howto/part04/encrypt-to-one.dita
new file mode 100644
index 00000000..2abbe06a
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/encrypt-to-one.dita
@@ -0,0 +1,83 @@
+
+
+
+
+ Encrypting to One Key
+
+ Once the the Context is set the main issues with encrypting data is essentially reduced to
+ key selection and the keyword arguments specified in the
+ gpg.Context().encrypt() method.
+ Those keyword arguments are:
+
+
+ - recipients, a list of keys encrypted to (covered in greater detail in
+ the following section);
+ - sign, whether or not to sign the plaintext data, see subsequent
+ sections on signing and verifying signatures below (defaults to
+ True);
+ - sink, to write results or partial results to a secure sink instead of
+ returning it (defaults to None);
+ - passphrase, only used when utilising symmetric encryption (defaults
+ to None);
+ - always_trust, used to override the trust model settings for recipient
+ keys (defaults to False);
+ - add_encrypt_to, utilises any preconfigured encrypt-to or default-key
+ settings in the user's gpg.conf file (defaults to
+ False);
+ - prepare, prepare for encryption (defaults to
+ False);
+ - expect_sign, prepare for signing (defaults to
+ False);
+ - compress, compresses the plaintext prior to encryption (defaults to
+ True).
+
+
+
+ import gpg
+
+a_key = "0x12345678DEADBEEF"
+text = b"""Some text to test with.
+
+Since the text in this case must be bytes, it is most likely that
+the input form will be a separate file which is opened with "rb"
+as this is the simplest method of obtaining the correct data
+format.
+"""
+
+c = gpg.Context(armor=True)
+rkey = list(c.keylist(pattern=a_key, secret=False))
+ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
+
+with open("secret_plans.txt.asc", "wb") as f:
+ f.write(ciphertext)
+
+
+ Though this is even more likely to be used like this; with the plaintext input read from a
+ file, the recipient keys used for encryption regardless of key trust status and the
+ encrypted output also encrypted to any preconfigured keys set in the
+ gpg.conf file:
+
+ import gpg
+
+a_key = "0x12345678DEADBEEF"
+
+with open("secret_plans.txt", "rb") as f:
+ text = f.read()
+
+c = gpg.Context(armor=True)
+rkey = list(c.keylist(pattern=a_key, secret=False))
+ciphertext, result, sign_result = c.encrypt(text, recipients=rkey,
+ sign=True, always_trust=True,
+ add_encrypt_to=True)
+
+with open("secret_plans.txt.asc", "wb") as f:
+ f.write(ciphertext)
+
+
+ If the recipients paramater is empty then the plaintext is encrypted
+ symmetrically. If no passphrase is supplied as a parameter or via a
+ callback registered with the Context() then an out-of-band prompt for the
+ passphrase via pinentry will be invoked.
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/encryption.dita b/lang/python/docs/dita/howto/part04/encryption.dita
new file mode 100644
index 00000000..572cc9d2
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/encryption.dita
@@ -0,0 +1,12 @@
+
+
+
+
+ Encryption
+
+ Encrypting is very straight forward. In the first example below the message,
+ text, is encrypted to a single recipient's key. In the second example the
+ message will be encrypted to multiple recipients.
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/signing-key-selection.dita b/lang/python/docs/dita/howto/part04/signing-key-selection.dita
new file mode 100644
index 00000000..34d02b4a
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/signing-key-selection.dita
@@ -0,0 +1,28 @@
+
+
+
+
+ Signing Key Selection
+
+ By default GPGME and the Python bindings will use the default key configured for the user
+ invoking the GPGME API. If there is no default key specified and there is more than one
+ secret key available it may be necessary to specify the key or keys with which to sign
+ messages and files.
+
+ import gpg
+
+logrus = input("Enter the email address or string to match signing keys to: ")
+hancock = gpg.Context().keylist(pattern=logrus, secret=True)
+sig_src = list(hancock)
+
+
+ The signing examples in the following sections include the explicitly designated
+ signers parameter in two of the five examples; once where the resulting
+ signature would be ASCII armoured and once where it would not be armoured.
+ While it would be possible to enter a key ID or fingerprint here to match a specific key,
+ it is not possible to enter two fingerprints and match two keys since the patten expects a
+ string, bytes or None and not a list. A string with two fingerprints won't match any single
+ key.
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/signing.dita b/lang/python/docs/dita/howto/part04/signing.dita
new file mode 100644
index 00000000..289e3742
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/signing.dita
@@ -0,0 +1,11 @@
+
+
+
+
+ Signing Text and Files
+
+ The following sections demonstrate how to specify keys to sign with and the types of
+ signatures which can be made.
+
+
+
diff --git a/lang/python/docs/dita/howto/part04/verification.dita b/lang/python/docs/dita/howto/part04/verification.dita
new file mode 100644
index 00000000..d50482a8
--- /dev/null
+++ b/lang/python/docs/dita/howto/part04/verification.dita
@@ -0,0 +1,150 @@
+
+
+
+
+ Signature Verification
+
+ Essentially there are two principal methods of verification of a signature. The first
+ of these is for use with the normal or default signing method and for clear-signed messages.
+ The second is for use with files and data with detached signatures.
+ The following example is intended for use with the default signing method where the file
+ was not ASCII armoured:
+
+ import gpg
+import time
+
+filename = "statement.txt"
+gpg_file = "statement.txt.gpg"
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(gpg_file))
+ verified = True
+except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid,
+ sign.fpr, time.ctime(sign.timestamp)))
+else:
+ pass
+
+
+ Whereas this next example, which is almost identical would work with normal ASCII armoured
+ files and with clear-signed files:
+
+ import gpg
+import time
+
+filename = "statement.txt"
+asc_file = "statement.txt.asc"
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(asc_file))
+ verified = True
+except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid,
+ sign.fpr, time.ctime(sign.timestamp)))
+else:
+ pass
+
+
+ In both of the previous examples it is also possible to compare the original data that was
+ signed against the signed data in data to see if it matches with something
+ like this:
+
+ with open(filename, "rb") as afile:
+ text = afile.read()
+
+if text == data:
+ print("Good signature.")
+else:
+ pass
+
+
+ The following two examples, however, deal with detached signatures. With his method of
+ verification the data that was signed does not get returned since it is already being
+ explicitly referenced in the first argument of c.verify. So
+ data is None and only the information in
+ result is available.
+
+ import gpg
+import time
+
+filename = "statement.txt"
+sig_file = "statement.txt.sig"
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(filename), open(sig_file))
+ verified = True
+except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid,
+ sign.fpr, time.ctime(sign.timestamp)))
+else:
+ pass
+
+
+
+ import gpg
+import time
+
+filename = "statement.txt"
+asc_file = "statement.txt.asc"
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(filename), open(asc_file))
+ verified = True
+except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+if verified is not None:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid,
+ sign.fpr, time.ctime(sign.timestamp)))
+else:
+ pass
+
+
+
+
+
--
cgit v1.2.3