commit 019a2cced0855da7775b48483e731130e2a8cc50
Author: Radek Novacek <rnovacek(a)redhat.com>
Date: Tue Jun 3 17:37:25 2014 +0200
Add ability to encrypt passwords
password/__init__.py | 107 ++++++++++++++++++++++++++++++++++++++++++++++++
tests/test_password.py | 62 ++++++++++++++++++++++++++++
virt-who-config.5.gz | Bin 0 -> 643 bytes
virt-who-password | 10 ++++
virt-who-password.8 | 18 ++++++++
virt-who-password.8.gz | Bin 0 -> 376 bytes
virt-who.8.gz | Bin 0 -> 2237 bytes
virtwhopassword.py | 60 +++++++++++++++++++++++++++
8 files changed, 257 insertions(+), 0 deletions(-)
---
diff --git a/password/__init__.py b/password/__init__.py
new file mode 100644
index 0000000..1c5e867
--- /dev/null
+++ b/password/__init__.py
@@ -0,0 +1,107 @@
+"""
+Module for encrypting and decrypting passwords.
+
+Copyright (C) 2014 Radek Novacek <rnovacek(a)redhat.com>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+
+import os
+import stat
+from M2Crypto import EVP
+from binascii import hexlify, unhexlify
+from cStringIO import StringIO
+
+
+class InvalidKeyFile(Exception):
+ pass
+
+
+class UnwrittableKeyFile(Exception):
+ pass
+
+
+class Password(object):
+ KEYFILE = '/var/lib/virt-who/key'
+ ENCRYPT = 1
+ DECRYPT = 0
+
+ BLOCKSIZE = 16
+
+ @classmethod
+ def _pad(cls, s):
+ return s + (cls.BLOCKSIZE - len(s) % cls.BLOCKSIZE) * chr(cls.BLOCKSIZE - len(s)
% cls.BLOCKSIZE)
+
+ @classmethod
+ def _unpad(cls, s):
+ return s[0:-ord(s[-1])]
+
+
+ @classmethod
+ def _crypt(cls, op, key, iv, data):
+ cipher = EVP.Cipher(alg='aes_128_cbc', key=unhexlify(key),
iv=unhexlify(iv), op=op, padding=False)
+ inf = StringIO(data)
+ outf = StringIO()
+ while 1:
+ buf = inf.read()
+ if not buf:
+ break
+ outf.write(cipher.update(buf))
+ outf.write(cipher.final())
+ return outf.getvalue()
+
+ @classmethod
+ def encrypt(cls, password):
+ key, iv = cls._read_or_generate_key_iv()
+ return cls._crypt(cls.ENCRYPT, key, iv, cls._pad(password))
+
+ @classmethod
+ def decrypt(cls, enc):
+ key, iv = cls._read_key_iv()
+ return cls._unpad(cls._crypt(cls.DECRYPT, key, iv, enc))
+
+ @classmethod
+ def _read_key_iv(cls):
+ try:
+ with open(cls.KEYFILE, 'r') as f:
+ key = f.readline().strip()
+ iv = f.readline().strip()
+ if not iv or not key:
+ raise InvalidKeyFile("Invalid format")
+ return key, iv
+ except IOError, e:
+ raise InvalidKeyFile(str(e))
+
+ @classmethod
+ def _read_or_generate_key_iv(cls):
+ try:
+ return cls._read_key_iv()
+ except InvalidKeyFile:
+ pass
+ if os.getuid() != 0:
+ raise UnwrittableKeyFile("Only root can write keyfile")
+ key = hexlify(cls._generate_key())
+ iv = hexlify(cls._generate_key())
+ try:
+ with open(cls.KEYFILE, 'w') as f:
+ f.write("%s\n%s\n" % (key, iv))
+ except IOError, e:
+ raise UnwrittableKeyFile(str(e))
+ os.chmod(cls.KEYFILE, stat.S_IRUSR | stat.S_IWUSR)
+ return key, iv
+
+ @classmethod
+ def _generate_key(cls):
+ return os.urandom(32)
diff --git a/tests/test_password.py b/tests/test_password.py
new file mode 100644
index 0000000..177b18e
--- /dev/null
+++ b/tests/test_password.py
@@ -0,0 +1,62 @@
+"""
+Test for password encryption/decryption.
+
+Copyright (C) 2014 Radek Novacek <rnovacek(a)redhat.com>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+
+import os
+import tempfile
+from binascii import hexlify, unhexlify
+
+from base import unittest
+
+from password import Password
+
+
+class TestPassword(unittest.TestCase):
+ def testEncrypt(self):
+ self.assertEqual(hexlify(
+ Password._crypt(
+ Password.ENCRYPT,
+ '06a9214036b8a15b512e03d534120006',
+ '3dafba429d9eb430b422da802c9fac41',
+ 'Single block msg')),
+ 'e353779c1079aeb82708942dbe77181a')
+
+ def testDecrypt(self):
+ self.assertEqual(
+ Password._crypt(
+ Password.DECRYPT,
+ '06a9214036b8a15b512e03d534120006',
+ '3dafba429d9eb430b422da802c9fac41',
+ unhexlify('e353779c1079aeb82708942dbe77181a')),
+ 'Single block msg')
+
+ def testBoth(self):
+ f, filename = tempfile.mkstemp()
+ self.addCleanup(os.unlink, filename)
+ Password.KEYFILE = filename
+ pwd = "Test password"
+ encrypted = Password.encrypt(pwd)
+ self.assertEqual(pwd, Password.decrypt(encrypted))
+ self.assertEqual(os)
+
+ def testPad(self):
+
self.assertEqual(hexlify(Password._pad(unhexlify("00010203040506070809"))),
"00010203040506070809060606060606")
+
+ def testUnpad(self):
+
self.assertEqual(hexlify(Password._unpad(unhexlify("00010203040506070809060606060606"))),
"00010203040506070809")
diff --git a/virt-who-config.5.gz b/virt-who-config.5.gz
new file mode 100644
index 0000000..7a6ab3c
Binary files /dev/null and b/virt-who-config.5.gz differ
diff --git a/virt-who-password b/virt-who-password
new file mode 100644
index 0000000..33fd430
--- /dev/null
+++ b/virt-who-password
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+if [ -f ./virtwhopassword.py ];
+then
+ # Run it from local directory when available
+ exec /usr/bin/python ./virtwhopassword.py "$@"
+else
+ # Run it from /usr/share/virt-who
+ exec /usr/bin/python /usr/share/virt-who/virtwhopassword.py "$@"
+fi
diff --git a/virt-who-password.8 b/virt-who-password.8
new file mode 100644
index 0000000..7668fd3
--- /dev/null
+++ b/virt-who-password.8
@@ -0,0 +1,18 @@
+.TH VIRT-WHO "8" "June 2014" "virt-who"
+.SH NAME
+virt-who-password - Utility that encrypts passwords for virt-who.
+.SH SYNOPSIS
+virt-who-password [-h|--help]
+.SH USAGE
+virt-who-password prompts for password and writes encrypted entered password
+to the standard output.
+
+This utility must be executed as root, because the encryption key is written
+into file that is only readable by root. Note that root can decrypt the
+password.
+
+Encryption key is written into file \fB/var/lib/virt-who/key\fR, make sure
+that this file is only readable and writtable by root.
+.SH AUTHOR
+Radek Novacek <rnovacek at redhat dot com>
+
diff --git a/virt-who-password.8.gz b/virt-who-password.8.gz
new file mode 100644
index 0000000..7cfabbd
Binary files /dev/null and b/virt-who-password.8.gz differ
diff --git a/virt-who.8.gz b/virt-who.8.gz
new file mode 100644
index 0000000..c032311
Binary files /dev/null and b/virt-who.8.gz differ
diff --git a/virtwhopassword.py b/virtwhopassword.py
new file mode 100644
index 0000000..6c1b4bb
--- /dev/null
+++ b/virtwhopassword.py
@@ -0,0 +1,60 @@
+#!/usr/bin/python2
+"""
+Command line script for password encryption.
+
+Copyright (C) 2012 Radek Novacek <rnovacek(a)redhat.com>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+
+
+import os
+import sys
+from getpass import getpass
+from password import Password, UnwrittableKeyFile, InvalidKeyFile
+from binascii import hexlify
+
+if __name__ == '__main__':
+ if len(sys.argv) == 2 and sys.argv[1] in ('-h', '--help'):
+ print """Utility that encrypts passwords for virt-who.
+
+Enter password that should be encrypted. This encrypted password then can be
+supplied to virt-who configuration.
+
+This command must be executed as root!
+
+WARNING: root user can still decrypt encrypted passwords!
+"""
+ sys.exit(0)
+
+ if os.getuid() != 0:
+ print >>sys.stderr, "Only root can encrypt passwords"
+ sys.exit(1)
+
+ try:
+ pwd = getpass("Password: ")
+ except (KeyboardInterrupt, EOFError):
+ print
+ sys.exit(1)
+ try:
+ enc = Password.encrypt(pwd)
+ except UnwrittableKeyFile:
+ print >>sys.stderr, "Keyfile %s doesn't exist and can't be
created, rerun as root" % Password.KEYFILE
+ sys.exit(1)
+ except InvalidKeyFile:
+ print >>sys.stderr, "Can't access keyfile %s, rerun as root"
% Password.KEYFILE
+ sys.exit(1)
+ print >>sys.stderr, "Use following as value for encrypted_password key in
the configuration file:"
+ print hexlify(enc)