Exemple #1
0
 def configure_gpg(self, key: str, password: Optional[str]) -> None:
     """Configure the repo to sign tags with GPG."""
     home = os.environ["GNUPGHOME"] = mkdtemp(prefix="tagbot_gpg_")
     os.chmod(home, S_IREAD | S_IWRITE | S_IEXEC)
     logger.debug(f"Set GNUPGHOME to {home}")
     gpg = GPG(gnupghome=home, use_agent=True)
     import_result = gpg.import_keys(self._maybe_decode_private_key(key),
                                     passphrase=password)
     if import_result.sec_imported != 1:
         logger.warning(import_result.stderr)
         raise Abort("Importing key failed")
     key_id = import_result.fingerprints[0]
     logger.debug(f"GPG key ID: {key_id}")
     if password:
         # Sign some dummy data to put our password into the GPG agent,
         # so that we don't need to supply the password when we create a tag.
         sign_result = gpg.sign("test", passphrase=password)
         if sign_result.status != "signature created":
             logger.warning(sign_result.stderr)
             raise Abort("Testing GPG key failed")
     # On Debian, the Git version is too old to recognize tag.gpgSign,
     # so the tag command will need to use --sign.
     self._git._gpgsign = True
     self._git.config("tag.gpgSign", "true")
     self._git.config("user.signingKey", key_id)
Exemple #2
0
def verify_key(email):
    '''
    fetch user's GPG key and make sure it matches given email address
    '''
    gpgkey = None
    gpg = GPG()
    # pylint: disable=no-member
    verified = gpg.verify(gpg.sign('', keyid=email).data)
    logging.debug('verified: %s', verified)
    if not verified.username.endswith('<' + email + '>'):
        raise ValueError('%s no match for GPG certificate %s' %
                         (email, verified.username))
    gpgkey = verified.key_id
    return gpgkey
Exemple #3
0
    def _sign(self, data, digest_algo):
        gpg = GPG(homedir=settings.GPG_SIGN_DIR)
        if not gpg.list_keys():
            # Import key if no private key key in keyring
            with open(settings.GPG_SIGN_KEY, 'r') as f:
                key = f.read()
            gpg.import_keys(key)

        signature = gpg.sign(data,
                             passphrase=settings.GPG_SIGN_KEY_PASSPHRASE,
                             clearsign=False,
                             detach=True,
                             digest_algo=digest_algo)

        return str(signature)
Exemple #4
0
 def configure_gpg(self, key: str, password: Optional[str]) -> None:
     """Configure the repo to sign tags with GPG."""
     home = os.environ["GNUPGHOME"] = mkdtemp(prefix="tagbot_gpg_")
     os.chmod(home, S_IREAD | S_IWRITE | S_IEXEC)
     logger.debug(f"Set GNUPGHOME to {home}")
     gpg = GPG(gnupghome=home, use_agent=True)
     # For some reason, this doesn't require the password even though the CLI does.
     import_result = gpg.import_keys(self._maybe_b64(key))
     if import_result.sec_imported != 1:
         logger.warning(import_result.stderr)
         raise Abort("Importing key failed")
     key_id = import_result.fingerprints[0]
     logger.debug(f"GPG key ID: {key_id}")
     if password:
         # Sign some dummy data to put our password into the GPG agent,
         # so that we don't need to supply the password when we create a tag.
         sign_result = gpg.sign("test", passphrase=password)
         if sign_result.status != "signature created":
             logger.warning(sign_result.stderr)
             raise Abort("Testing GPG key failed")
     self._git.config("user.signingKey", key_id)
     self._git.config("tag.gpgSign", "true")
def _main():
    descr = 'Generate Gerrit release announcement email text'
    parser = argparse.ArgumentParser(
        description=descr,
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('-v', '--version', dest='version',
                        required=True,
                        help='gerrit version to release')
    parser.add_argument('-p', '--previous', dest='previous',
                        help='previous gerrit version (optional)')
    parser.add_argument('-s', '--summary', dest='summary',
                        help='summary of the release content (optional)')
    options = parser.parse_args()

    summary = options.summary
    if summary and not summary.endswith("."):
        summary = summary + "."

    data = {
        "version": Version(options.version),
        "previous": options.previous,
        "summary": summary
    }

    war = os.path.join(
        os.path.expanduser("~/.m2/repository/com/google/gerrit/gerrit-war/"),
        "%(version)s/gerrit-war-%(version)s.war" % data)
    if not os.path.isfile(war):
        print("Could not find war file for Gerrit %s in local Maven repository"
              % data["version"], file=sys.stderr)
        sys.exit(1)

    md5 = hashlib.md5()
    sha1 = hashlib.sha1()
    sha256 = hashlib.sha256()
    BUF_SIZE = 65536  # Read data in 64kb chunks
    with open(war, 'rb') as f:
        while True:
            d = f.read(BUF_SIZE)
            if not d:
                break
            md5.update(d)
            sha1.update(d)
            sha256.update(d)

    data["sha1"] = sha1.hexdigest()
    data["sha256"] = sha256.hexdigest()
    data["md5"] = md5.hexdigest()

    template_path = os.path.join(
        os.path.dirname(os.path.realpath(__file__)),
        "release-announcement-template.txt")
    template = Template(open(template_path).read())
    output = template.render(data=data)

    filename = "release-announcement-gerrit-%s.txt" % data["version"]
    with open(filename, "w") as f:
        f.write(output)

    gpghome = os.path.abspath(os.path.expanduser("~/.gnupg"))
    if not os.path.isdir(gpghome):
        print("Skipping signing due to missing gnupg home folder")
    else:
        try:
            gpg = GPG(homedir=gpghome)
        except TypeError:
            gpg = GPG(gnupghome=gpghome)
        signed = gpg.sign(output)
        filename = filename + ".asc"
        with open(filename, "w") as f:
            f.write(str(signed))
cnx = sqlite3.connect(db_file) 
cursor = cnx.cursor()
p,e,c,ce,ec,s,sc,es,esc,cnt = (0,)*10
cursor.execute("select mtype || \" \" || message from statistics_messages where mtype='HELO_INFO' order by rtime desc limit %d"%tries)
tries = 0
for (rec,) in cursor:
    plain_text = rec.encode("utf8")
    
    enc_data = str(gpg.encrypt(plain_text, public_key, always_trust=True))
    enc_data_comp = zlib.compress(enc_data)
    
    comp_data = zlib.compress(plain_text)
    comp_data_enc = str(gpg.encrypt(comp_data, public_key, always_trust=True))
    
    sign_data = str(gpg.sign(plain_text))
    sign_data_comp = zlib.compress(sign_data)
        
    sign_enc_data = str(gpg.encrypt(plain_text, public_key, sign=public_key, always_trust=True))
    sign_enc_data_comp = zlib.compress(sign_enc_data)
    
    p+=len(plain_text)
    e+=len(enc_data)
    c+=len(comp_data)
    ce+=len(comp_data_enc)
    ec+=len(enc_data_comp)
    s+=len(sign_data)
    sc+=len(sign_data_comp)
    es+=len(sign_enc_data)
    esc+=len(sign_enc_data_comp)
    if len(enc_data_comp)<len(enc_data):
class CryptoTxt:
    """Crypto operation provider for plaintext.

    We use GnuPG for now. Support for X.509 and other options might
    appear in the future.
    """

    def __init__(self, gpg_binary, gpg_home):
        """Initialize the GnuPG instance."""

        self.gpg_binary = gpg_binary
        self.gpg_home = gpg_home
        if not GPG:
            raise TracError(_("Unable to load the python-gnupg module. "
                              "Please check and correct your installation."))
        try:
            self.gpg = GPG(gpgbinary=self.gpg_binary, gnupghome=self.gpg_home)
        except ValueError:
            raise TracError(_("Missing the crypto binary. Please check and "
                              "set full path with option 'gpg_binary'."))
        else:
            # get list of available public keys once for later use
            self.pub_keys = self.gpg.list_keys()

    def sign(self, content, private_key=None):
        private_key = self._get_private_key(private_key)

        cipher = self.gpg.sign(content, keyid=private_key, passphrase='')
        return str(cipher)

    def encrypt(self, content, pubkeys):
        # always_trust needed for making it work with just any pubkey
        cipher = self.gpg.encrypt(content, pubkeys, always_trust=True)
        return str(cipher)

    def sign_encrypt(self, content, pubkeys, private_key=None):
        private_key = self._get_private_key(private_key)

        # always_trust needed for making it work with just any pubkey
        cipher = self.gpg.encrypt(content, pubkeys, always_trust=True,
                                  sign=private_key, passphrase='')
        return str(cipher)

    def get_pubkey_ids(self, addr):
        """Find public key with UID matching address to encrypt to."""

        pubkey_ids = []
        if self.pub_keys and 'uids' in self.pub_keys[-1] and \
                'fingerprint' in self.pub_keys[-1]:
            # compile pattern before use for better performance
            rcpt_re = re.compile(addr)
            for k in self.pub_keys:
                for uid in k['uids']:
                    match = rcpt_re.search(uid)
                    if match is not None:
                        # check for key expiration
                        if k['expires'] == '':
                            pubkey_ids.append(k['fingerprint'][-16:])
                        elif (time.time() + 60) < float(k['expires']):
                            pubkey_ids.append(k['fingerprint'][-16:])
                        break
        return pubkey_ids

    def _get_private_key(self, privkey=None):
        """Find private (secret) key to sign with."""

        # read private keys from keyring
        privkeys = self.gpg.list_keys(True)  # True => private keys
        if privkeys > 0 and 'fingerprint' in privkeys[-1]:
            fingerprints = []
            for k in privkeys:
                fingerprints.append(k['fingerprint'])
        else:
            # no private key in keyring
            return None

        if privkey:
            # check for existence of private key received as argument
            # DEVEL: check for expiration as well
            if 7 < len(privkey) <= 40:
                for fp in fingerprints:
                    if fp.endswith(privkey):
                        # work with last 16 significant chars internally,
                        # even if only 8 are required in trac.ini
                        privkey = fp[-16:]
                        break
                # no fingerprint matching key ID
                else:
                    privkey = None
            else:
                # reset invalid key ID
                privkey = None
        else:
            # select (last) private key from keyring
            privkey = fingerprints[-1][-16:]

        return privkey
Exemple #8
0
class CryptoTxt:
    """Crypto operation provider for plaintext.

    We use GnuPG for now. Support for X.509 and other options might
    appear in the future.
    """
    def __init__(self, gpg_binary, gpg_home):
        """Initialize the GnuPG instance."""

        self.gpg_binary = gpg_binary
        self.gpg_home = gpg_home
        if not GPG:
            raise TracError(
                _("Unable to load the python-gnupg module. "
                  "Please check and correct your installation."))
        try:
            self.gpg = GPG(gpgbinary=self.gpg_binary, gnupghome=self.gpg_home)
        except ValueError:
            raise TracError(
                _("Missing the crypto binary. Please check and "
                  "set full path with option 'gpg_binary'."))
        else:
            # get list of available public keys once for later use
            self.pub_keys = self.gpg.list_keys()

    def sign(self, content, private_key=None):
        private_key = self._get_private_key(private_key)

        cipher = self.gpg.sign(content, keyid=private_key, passphrase='')
        return str(cipher)

    def encrypt(self, content, pubkeys):
        # always_trust needed for making it work with just any pubkey
        cipher = self.gpg.encrypt(content, pubkeys, always_trust=True)
        return str(cipher)

    def sign_encrypt(self, content, pubkeys, private_key=None):
        private_key = self._get_private_key(private_key)

        # always_trust needed for making it work with just any pubkey
        cipher = self.gpg.encrypt(content,
                                  pubkeys,
                                  always_trust=True,
                                  sign=private_key,
                                  passphrase='')
        return str(cipher)

    def get_pubkey_ids(self, addr):
        """Find public key with UID matching address to encrypt to."""

        pubkey_ids = []
        if self.pub_keys and 'uids' in self.pub_keys[-1] and \
                'fingerprint' in self.pub_keys[-1]:
            # compile pattern before use for better performance
            rcpt_re = re.compile(addr)
            for k in self.pub_keys:
                for uid in k['uids']:
                    match = rcpt_re.search(uid)
                    if match is not None:
                        # check for key expiration
                        if k['expires'] == '':
                            pubkey_ids.append(k['fingerprint'][-16:])
                        elif (time.time() + 60) < float(k['expires']):
                            pubkey_ids.append(k['fingerprint'][-16:])
                        break
        return pubkey_ids

    def _get_private_key(self, privkey=None):
        """Find private (secret) key to sign with."""

        # read private keys from keyring
        privkeys = self.gpg.list_keys(True)  # True => private keys
        if privkeys > 0 and 'fingerprint' in privkeys[-1]:
            fingerprints = []
            for k in privkeys:
                fingerprints.append(k['fingerprint'])
        else:
            # no private key in keyring
            return None

        if privkey:
            # check for existence of private key received as argument
            # DEVEL: check for expiration as well
            if 7 < len(privkey) <= 40:
                for fp in fingerprints:
                    if fp.endswith(privkey):
                        # work with last 16 significant chars internally,
                        # even if only 8 are required in trac.ini
                        privkey = fp[-16:]
                        break
                # no fingerprint matching key ID
                else:
                    privkey = None
            else:
                # reset invalid key ID
                privkey = None
        else:
            # select (last) private key from keyring
            privkey = fingerprints[-1][-16:]

        return privkey
Exemple #9
0
class GPGMail(object):

    def __init__(self, gpg=None):
        if gpg:
            self.gpg = gpg
        else:
            self.gpg = GPG(gpgbinary="gpg2", use_agent=True)

        GPGLogger.setLevel(logging.DEBUG)

        self.logger = logging.getLogger('GPGMail')

    def _armor(self, container, message, signature):
        """
        Make the armor signed message
        """
        if container.get_param('protocol') == 'application/pgp-signature':
            m = re.match(r'^pgp-(.*)$', container.get_param('micalg'))

            if m:
                TEMPLATE = '-----BEGIN PGP SIGNED MESSAGE-----\n' \
                           'Hash: %s\n\n' \
                           '%s\n%s\n'
                s = StringIO()
                text = re.sub(r'(?m)^(-.*)$', r'- \1', self._flatten(message))

                s.write(TEMPLATE % (m.group(1).upper(),
                                    text,
                                    signature.get_payload()))
                return s.getvalue()
        return None

    def _filter_parts(sefl, m, f):
        """Iterate over messages that satisfy predicate."""
        for x in m.walk():
            if f(x):
                yield x

    def _flatten(self, message):
        """Return raw string representation of message."""
        try:
            s = StringIO()
            g = Generator(s, mangle_from_=False, maxheaderlen=0)

            g.flatten(message)
            return s.getvalue()
        finally:
            s.close()

    def _signed_parts(self, message):
        """Iterate over signed parts of message yielding
        GPG verification status and signed contents."""

        f = lambda m: \
            m.is_multipart() and m.get_content_type() == 'multipart/signed' \
            or not m.is_multipart() and m.get_content_maintype() == 'text'

        for part in self._filter_parts(message, f):
            if part.is_multipart():
                try:
                    signed_part, signature = part.get_payload()
                    s = None
                    sign_type = signature.get_content_type()
                    if sign_type == 'application/pgp-signature':
                        s = self._armor(part, signed_part, signature)
                        yield self.gpg.verify(s), True, signature.get_filename()

                except ValueError:
                    pass

            else:
                payload = part.get_payload(decode=True)
                yield self.gpg.verify(payload), False, None

    def verify(self, message):
        """Verify signature of a email message and returns the GPG info"""
        result = {}
        for verified, sign_attached, filename in self._signed_parts(message):
            if verified is not None:
                result = verified.__dict__
                break
        if 'status' in result and result['status'] is None:
            return None
        if 'gpg' in result:
            del(result['gpg'])
        if sign_attached:
            result['filename'] = filename
        return result

    def _get_digest_algo(self, signature):
        """
        Returns a string representation of the digest algo used in signature.

        Raises a TypeError if signature.hash_algo does not exists.

        Acceptable values for signature.hash_algo are:
            MD5       1
            SHA1      2
            RMD160    3
            SHA256    8
            SHA384    9
            SHA512   10
            SHA224   11
        See gnupg/include/cipher.h for more info
        """
        values = {
            1:  "MD5",
            2:  "SHA1",
            3:  "RMD160",
            8:  "SHA256",
            9:  "SHA384",
            10: "SHA512",
            11: "SHA224"
        }
        hash_algo = signature.hash_algo
        try:
            if isinstance(hash_algo, (str, unicode)):
                hash_algo = int(hash_algo)
            return values[hash_algo]
        except:
            raise TypeError("Invalid signature hash_algo {}".format(hash_algo))

    def sign(self, message):
        """Sign a email message and return the new message signed as string"""

        if isinstance(message, (str, unicode)):
            message = email.message_from_string(message)

        # Create the basetxt, which contains the original body message

        # If original message is multipart, take the
        # Content-Type and the Body - ignore other Headers..
        if message.is_multipart():
            content_type = 'Content-Type: {}'.format(message['Content-Type'])
            try:
                body = self._flatten(message).split('\n\n', 1)[1]
            except:
                raise ValueError("Message cannot be split in Headers and Body")
            basetxt = '{}\n\n{}'.format(content_type, body)

        # else get only the body message
        else:
            basetxt = MIMEUTF8QPText(message).as_string()

        # See RFC 3156 (Section 5.) to understand these transformations
        # 1. all the lines must end with <CR><LF>
        basetxt = basetxt.replace('\r\n', '\n')\
                         .replace('\n', '\r\n')
        # 2. remove trailing spaces
        basetxt = re.sub(r' +\r\n', '\r\n', basetxt, flags=re.M)

        # signing
        signature = self.gpg.sign(basetxt, detach=True)
        self.logger.error("signature %s %s" % (basetxt, signature))

        # create the new message as multipart/signed (see RFC 3156)
        micalg = "pgp-{}".format(self._get_digest_algo(signature).lower())
        new_message = MIMEMultipart(_subtype="signed", micalg=micalg,
                                    protocol="application/pgp-signature")

        # copy the headers
        header_to_copy = [
            "Date",
            "Subject",
            "From",
            "To",
            "Bcc",
            "Cc",
            "Reply-To",
            "References",
            "In-Reply-To"
        ]
        for h in header_to_copy:
            if h in message:
                new_message[h] = message[h]

        # attach signed_part and signature and return message as string
        return self._attach_signed_parts(new_message, basetxt, signature)

    def _attach_signed_parts(self, message, signed_part, signature):
        """
        Attach the signed_part and signature to the message (MIMEMultipart)
        and returns the new message as str
        """
        # According with RFC 3156 the signed_part in the message
        # must be equal to the signed one.
        # The best way to do this is "hard" attach the parts
        # using strings.

        if not isinstance(signature, (str, unicode)):
            signature = str(signature)

        # get the body of the MIMEMultipart message,
        # remove last lines which close the boundary
        msg_lines = message.as_string().split('\n')[:-3]

        # get the last opening boundary
        boundary = msg_lines.pop()

        # create the signature as attachment
        sigmsg = Message()
        sigmsg['Content-Type'] = 'application/pgp-signature; ' + \
                                 'name="signature.asc"'
        sigmsg['Content-Description'] = 'GooPG digital signature'
        sigmsg.set_payload(signature)

        # attach the signed_part
        msg_lines += [boundary, signed_part]
        # attach the signature
        msg_lines += [boundary,
                      sigmsg.as_string(),
                      '{}--'.format(boundary)]
        # return message a string
        return '\n'.join(msg_lines)
Exemple #10
0
class gpg(object):
    """
    module to wrap the gnupg library and gpg binary
    """
    def __init__(self, folder_path=None, binary_path=None):
        # ugly but working
        if binary_path is None:
            binary_path = join(project_base, "gpg", "gpg.exe")
            if not isfile(binary_path):
                binary_path = join(project_base, 'gpg')
                if not isfile(binary_path):
                    binary_path = find_executable('gpg')
                    if not isfile(binary_path):
                        raise RuntimeError('gpg not found')
        self.gpg = GPG(
            binary_path,  # gpgbinary
            folder_path,  # gnupghome
            verbose=False,
            options=["--allow-non-selfsigned-uid"])

    def get_key(self, fingerprint, private, passphrase):
        """
        returns the key belonging to the fingerprint given. if 'private' is
        True, the private key is returned. If 'private' is False, the public
        key will be returned.
        """
        key = self.gpg.export_keys(fingerprint,
                                   private,
                                   armor=False,
                                   passphrase=passphrase)
        return b64encode(key)

    def add_keypair(self, public_key, private_key, site, user, passphrase):
        """
        add a keypair into the gpg key database
        """
        try:
            result1 = self.gpg.import_keys(b64decode(public_key))
            result2 = self.gpg.import_keys(b64decode(private_key))
        except TypeError as error:
            getLogger(__name__).critical("add_keypair TypeError " + str(error))
        # make sure this is a key _pair_
        try:
            assert result1.fingerprints[0] == result2.fingerprints[0]
        except (IndexError, AssertionError) as error:
            getLogger(__name__).exception(
                'add_keypair IndexError/AssertionError: ' + str(error))
            return None
        fingerprint = result1.fingerprints[0]

        if self.is_passphrase_valid(passphrase=passphrase,
                                    fingerprint=fingerprint):
            old_fingerprint = self.get_fingerprint(site, user)
            if not old_fingerprint:
                sql = "INSERT INTO keys (site, user, fingerprint) VALUES (?, ?, ?)"
                database_execute(sql, (site, user, fingerprint))
            else:
                sql = "UPDATE keys SET SITE=? WHERE fingerprint=?"
                database_execute(sql, (site, old_fingerprint))
                if fingerprint != old_fingerprint:
                    getLogger(__name__).warn('updating %s fingerprint to %s' %
                                             (user, fingerprint))
            return fingerprint
        else:
            return None

    def add_public_key(self, site, user, public_key):
        """
        add a public key into the gpg key database
        """
        try:
            result1 = self.gpg.import_keys(b64decode(public_key))
            fingerprint = result1.fingerprints[0]

            sql = "INSERT INTO keys (site, user, fingerprint) VALUES (?, ?, ?)"
            database_execute(sql, (site, user, fingerprint))
            return fingerprint
        except TypeError as error:
            getLogger(__name__).critical("add_public_key TypeError " +
                                         str(error))
        except DatabaseError as error:
            getLogger(__name__).critical("add_public_key DatabaseError " +
                                         str(error))

    def is_passphrase_valid(self,
                            passphrase,
                            label=None,
                            user=None,
                            fingerprint=None):
        if not fingerprint:
            fingerprint = self.get_fingerprint(label, user)

        sign_result = self.gpg.sign("test",
                                    keyid=fingerprint,
                                    passphrase=passphrase)

        return sign_result.data != ''

    def generate(self, passphrase, site, user):
        """
        Generate a new 2048 bit GPG key and add it to the gpg manager.
        """
        data = self.gpg.gen_key_input(key_length=2048, passphrase=passphrase)
        dat = self.gpg.gen_key(data)
        fingerprint = dat.fingerprint
        sql = "INSERT INTO keys (site, user, fingerprint) VALUES (?, ?, ?);"
        database_execute(sql, (site, user, fingerprint))
        return fingerprint

    def get_fingerprint(self, site, user):
        sql = "select fingerprint from keys where site = ? and user = ?"
        result = database_execute(sql, (site, user))
        if not len(result):
            return None
        return result[0][0]

    def has_key(self, site, user):
        """
        Check whether a key is present for a certain site and user
        """
        return self.get_fingerprint(site, user) is None

    def encrypt(self, data, site, user, armor=False):
        """
        encrypt data for user at site.
        """
        fingerprint = self.get_fingerprint(site, user)
        cryptdata = self.gpg.encrypt_file(StringIO(data),
                                          fingerprint,
                                          always_trust=True,
                                          armor=armor)
        return str(cryptdata)

    def decrypt(self, data, passphrase):
        """
        decrypt data received from site.
        """
        datafile = StringIO(data)
        result = self.gpg.decrypt_file(datafile,
                                       passphrase=passphrase,
                                       always_trust=True)
        return str(result)

    @staticmethod
    def add_pkcs7_padding(contents):
        # Input strings must be a multiple of the segment size 16 bytes in length
        segment_size = 16

        # calculate how much padding is needed
        old_contents_length = len(contents)
        next_mult = old_contents_length + (segment_size -
                                           old_contents_length % segment_size)
        getLogger(__name__).debug(
            'old contents length %s || new contents length %s' %
            (old_contents_length, next_mult))

        # do the padding
        padding_byte = chr(next_mult - old_contents_length)
        contents = contents.ljust(next_mult, padding_byte)

        return contents

    @staticmethod
    def remove_pkcs7_padding(contents):
        """
        Remove PKCS#7 padding bytes

        >>> gpg.remove_pkcs7_padding('some_content'.ljust(16, chr(4)))
        'some_content'

        >>> gpg.remove_pkcs7_padding('some_content')
        'some_content'

        >>> gpg.remove_pkcs7_padding('')
        ''

        :param contents:
        :return: contents without padding
        """
        if len(contents) < 1:
            getLogger(__name__).debug(
                'contents is empty. No PKCS#7 padding removed')
            return contents

        bytes_to_remove = ord(contents[-1])  # will work up to 255 bytes

        # check if contents have valid PKCS#7 padding
        if bytes_to_remove > 1 and contents[-2] != contents[-1]:
            getLogger(__name__).debug('no PKCS#7 padding detected')
            return contents

        getLogger(__name__).debug('removing %s bytes of PKCS#7 padding' %
                                  bytes_to_remove)
        return contents[0:(len(contents) - bytes_to_remove)]
Exemple #11
0
class GnuPGMessage(EmailMessage):
    def __init__(self, *args, **kwargs):
        super(GnuPGMessage, self).__init__(*args, **kwargs)
        self.gpg = GPG(gnupghome=settings.GNUPG_HOMEDIR)

    def _normalize(self, original):
        return "\r\n".join(str(original).splitlines()[1:]) + "\r\n"

    def _sign(self, original):
        sig = self.gpg.sign(
            self._normalize(original), detach=True, clearsign=False)
        signature = MIMEApplication(
            str(sig), 'pgp-signature', encode_noop, name='signature.asc')
        signature.add_header('Content-Description', 'Digital signature')
        del signature['MIME-Version']
        return signature

    def message(self):
        encoding = self.encoding or settings.DEFAULT_CHARSET
        msg = MIMEUTF8QPText(self.body, encoding)
        msg = self._create_message(msg)

        msg['Subject'] = self.subject
        msg['From'] = self.extra_headers.get('From', self.from_email)
        msg['To'] = self.extra_headers.get('To', ', '.join(self.to))
        if self.cc:
            msg['Cc'] = ', '.join(self.cc)

        header_names = [key.lower() for key in self.extra_headers]
        if 'date' not in header_names:
            msg['Date'] = formatdate()
        if 'message-id' not in header_names:
            msg['Message-ID'] = make_msgid()
        for name, value in self.extra_headers.items():
            if name.lower() in ('from', 'to'):
                # From and To are already handled
                continue
            msg[name] = value

        del msg['MIME-Version']

        wrapper = SafeMIMEMultipart(
            'signed', protocol='application/pgp-signature',
            micalg='pgp-sha512')
        wrapper.preamble = (
            "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)"
        )

        # copy headers from original message to PGP/MIME envelope
        for header in msg.keys():
            if header.lower() not in (
                    'content-disposition', 'content-type', 'mime-version'
            ):
                for value in msg.get_all(header):
                    wrapper.add_header(header, value)
                del msg[header]

        for part in msg.walk():
            del part['MIME-Version']

        signature = self._sign(msg)

        wrapper['Content-Disposition'] = 'inline'
        wrapper.attach(msg)
        wrapper.attach(signature)

        return wrapper
def gpg_sign_message(message):
    gpg = GPG(gnupghome = KEYS_DIR)
    return gpg.sign(message, detach=True)
Exemple #13
0
class GPGMail(object):
    def __init__(self, gpg=None):
        if gpg:
            self.gpg = gpg
        else:
            self.gpg = GPG(gpgbinary="gpg2", use_agent=True)

        GPGLogger.setLevel(logging.DEBUG)

        self.logger = logging.getLogger('GPGMail')

    def _armor(self, container, message, signature):
        """
        Make the armor signed message
        """
        if container.get_param('protocol') == 'application/pgp-signature':
            m = re.match(r'^pgp-(.*)$', container.get_param('micalg'))

            if m:
                TEMPLATE = '-----BEGIN PGP SIGNED MESSAGE-----\n' \
                           'Hash: %s\n\n' \
                           '%s\n%s\n'
                s = StringIO()
                text = re.sub(r'(?m)^(-.*)$', r'- \1', self._flatten(message))

                s.write(TEMPLATE %
                        (m.group(1).upper(), text, signature.get_payload()))
                return s.getvalue()
        return None

    def _filter_parts(sefl, m, f):
        """Iterate over messages that satisfy predicate."""
        for x in m.walk():
            if f(x):
                yield x

    def _flatten(self, message):
        """Return raw string representation of message."""
        try:
            s = StringIO()
            g = Generator(s, mangle_from_=False, maxheaderlen=0)

            g.flatten(message)
            return s.getvalue()
        finally:
            s.close()

    def _signed_parts(self, message):
        """Iterate over signed parts of message yielding
        GPG verification status and signed contents."""

        f = lambda m: \
            m.is_multipart() and m.get_content_type() == 'multipart/signed' \
            or not m.is_multipart() and m.get_content_maintype() == 'text'

        for part in self._filter_parts(message, f):
            if part.is_multipart():
                try:
                    signed_part, signature = part.get_payload()
                    s = None
                    sign_type = signature.get_content_type()
                    if sign_type == 'application/pgp-signature':
                        s = self._armor(part, signed_part, signature)
                        yield self.gpg.verify(
                            s), True, signature.get_filename()

                except ValueError:
                    pass

            else:
                payload = part.get_payload(decode=True)
                yield self.gpg.verify(payload), False, None

    def verify(self, message):
        """Verify signature of a email message and returns the GPG info"""
        result = {}
        for verified, sign_attached, filename in self._signed_parts(message):
            if verified is not None:
                result = verified.__dict__
                break
        if 'status' in result and result['status'] is None:
            return None
        if 'gpg' in result:
            del (result['gpg'])
        if sign_attached:
            result['filename'] = filename
        return result

    def _get_digest_algo(self, signature):
        """
        Returns a string representation of the digest algo used in signature.

        Raises a TypeError if signature.hash_algo does not exists.

        Acceptable values for signature.hash_algo are:
            MD5       1
            SHA1      2
            RMD160    3
            SHA256    8
            SHA384    9
            SHA512   10
            SHA224   11
        See gnupg/include/cipher.h for more info
        """
        values = {
            1: "MD5",
            2: "SHA1",
            3: "RMD160",
            8: "SHA256",
            9: "SHA384",
            10: "SHA512",
            11: "SHA224"
        }
        hash_algo = signature.hash_algo
        try:
            if isinstance(hash_algo, (str, unicode)):
                hash_algo = int(hash_algo)
            return values[hash_algo]
        except:
            raise TypeError("Invalid signature hash_algo {}".format(hash_algo))

    def sign(self, message):
        """Sign a email message and return the new message signed as string"""

        if isinstance(message, (str, unicode)):
            message = email.message_from_string(message)

        # Create the basetxt, which contains the original body message

        # If original message is multipart, take the
        # Content-Type and the Body - ignore other Headers..
        if message.is_multipart():
            content_type = 'Content-Type: {}'.format(message['Content-Type'])
            try:
                body = self._flatten(message).split('\n\n', 1)[1]
            except:
                raise ValueError("Message cannot be split in Headers and Body")
            basetxt = '{}\n\n{}'.format(content_type, body)

        # else get only the body message
        else:
            basetxt = MIMEUTF8QPText(message).as_string()

        # See RFC 3156 (Section 5.) to understand these transformations
        # 1. all the lines must end with <CR><LF>
        basetxt = basetxt.replace('\r\n', '\n')\
                         .replace('\n', '\r\n')
        # 2. remove trailing spaces
        basetxt = re.sub(r' +\r\n', '\r\n', basetxt, flags=re.M)

        # signing
        signature = self.gpg.sign(basetxt, detach=True)
        self.logger.error("signature %s %s" % (basetxt, signature))

        # create the new message as multipart/signed (see RFC 3156)
        micalg = "pgp-{}".format(self._get_digest_algo(signature).lower())
        new_message = MIMEMultipart(_subtype="signed",
                                    micalg=micalg,
                                    protocol="application/pgp-signature")

        # copy the headers
        header_to_copy = [
            "Date", "Subject", "From", "To", "Bcc", "Cc", "Reply-To",
            "References", "In-Reply-To"
        ]
        for h in header_to_copy:
            if h in message:
                new_message[h] = message[h]

        # attach signed_part and signature and return message as string
        return self._attach_signed_parts(new_message, basetxt, signature)

    def _attach_signed_parts(self, message, signed_part, signature):
        """
        Attach the signed_part and signature to the message (MIMEMultipart)
        and returns the new message as str
        """
        # According with RFC 3156 the signed_part in the message
        # must be equal to the signed one.
        # The best way to do this is "hard" attach the parts
        # using strings.

        if not isinstance(signature, (str, unicode)):
            signature = str(signature)

        # get the body of the MIMEMultipart message,
        # remove last lines which close the boundary
        msg_lines = message.as_string().split('\n')[:-3]

        # get the last opening boundary
        boundary = msg_lines.pop()

        # create the signature as attachment
        sigmsg = Message()
        sigmsg['Content-Type'] = 'application/pgp-signature; ' + \
                                 'name="signature.asc"'
        sigmsg['Content-Description'] = 'GooPG digital signature'
        sigmsg.set_payload(signature)

        # attach the signed_part
        msg_lines += [boundary, signed_part]
        # attach the signature
        msg_lines += [boundary, sigmsg.as_string(), '{}--'.format(boundary)]
        # return message a string
        return '\n'.join(msg_lines)