PageRenderTime 43ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/src/lib/datahandler/gpass.py

https://github.com/dcreager/revelation
Python | 439 lines | 365 code | 45 blank | 29 comment | 12 complexity | 1effb69fef4e2b96e1b65d07a469fc0a MD5 | raw file
Possible License(s): GPL-2.0
  1. #
  2. # Revelation - a password manager for GNOME 2
  3. # http://oss.codepoet.no/revelation/
  4. # $Id$
  5. #
  6. # Module for handling GPass data
  7. #
  8. #
  9. # Copyright (c) 2003-2006 Erik Grinaker
  10. #
  11. # This program is free software; you can redistribute it and/or
  12. # modify it under the terms of the GNU General Public License
  13. # as published by the Free Software Foundation; either version 2
  14. # of the License, or (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License
  22. # along with this program; if not, write to the Free Software
  23. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  24. #
  25. import base
  26. from revelation import data, entry
  27. import locale, re
  28. from Crypto.Cipher import Blowfish
  29. from Crypto.Hash import SHA
  30. IV = "\x05\x17\x01\x7b\x0c\x03\x36\x5e"
  31. def decrypt(ciphertext, password, magic = None):
  32. "Decrypts a data stream"
  33. # decrypt data
  34. if len(ciphertext) % 8 != 0:
  35. raise base.FormatError
  36. key = SHA.new(password).digest()
  37. cipher = Blowfish.new(key, Blowfish.MODE_CBC, IV)
  38. plaintext = cipher.decrypt(ciphertext)
  39. # check magic string
  40. if magic != None:
  41. if plaintext[:len(magic)] != magic:
  42. raise base.PasswordError
  43. else:
  44. plaintext = plaintext[len(magic):]
  45. # remove padding
  46. padchar = plaintext[-1]
  47. if plaintext[-ord(padchar):] != padchar * ord(padchar):
  48. raise base.FormatError
  49. plaintext = plaintext[:-ord(padchar)]
  50. return plaintext
  51. def encrypt(plaintext, password):
  52. "Encrypts a data stream"
  53. # right-pad data
  54. padlen = 8 - len(plaintext) % 8
  55. if padlen == 0:
  56. padlen = 8
  57. plaintext += chr(padlen) * padlen
  58. # encrypt data
  59. key = SHA.new(password).digest()
  60. cipher = Blowfish.new(key, Blowfish.MODE_CBC, IV)
  61. return cipher.encrypt(plaintext)
  62. class GPass04(base.DataHandler):
  63. "Data handler for GPass 0.4.x data"
  64. name = "GPass 0.4.x"
  65. importer = True
  66. exporter = True
  67. encryption = True
  68. def __init__(self):
  69. base.DataHandler.__init__(self)
  70. def export_data(self, entrystore, password):
  71. "Exports data to a data stream"
  72. data = "GNOME Password Manager\n"
  73. iter = entrystore.iter_nth_child(None, 0)
  74. while iter is not None:
  75. e = entrystore.get_entry(iter)
  76. if type(e) != entry.FolderEntry:
  77. e = e.convert_generic()
  78. data += e.name + "\n"
  79. data += e[entry.UsernameField] + "\n"
  80. data += e[entry.PasswordField] + "\n"
  81. data += e[entry.HostnameField] + "\n"
  82. data += str(e.updated) + "\n"
  83. data += str(e.updated) + "\n"
  84. data += "0\n"
  85. data += str(len(e.description) + 1) + "\n"
  86. data += e.description + "\n"
  87. iter = entrystore.iter_traverse_next(iter)
  88. return encrypt(data, password)
  89. def import_data(self, input, password):
  90. "Imports data from a data stream to an entrystore"
  91. plaintext = decrypt(input, password, "GNOME Password Manager\n")
  92. entrystore = data.EntryStore()
  93. lines = plaintext.splitlines()
  94. while len(lines) > 0:
  95. e = entry.GenericEntry()
  96. e.name = lines[0]
  97. e[entry.UsernameField] = lines[1]
  98. e[entry.PasswordField] = lines[2]
  99. e[entry.HostnameField] = lines[3]
  100. e.updated = int(lines[5])
  101. desclen = int(lines[7])
  102. if e[entry.HostnameField] == "http://":
  103. e[entry.HostnameField] = ""
  104. del lines[:8]
  105. d = ""
  106. while len(d) < desclen and len(lines) > 0:
  107. d += lines[0] + "\n"
  108. del lines[0]
  109. e.description = re.sub("[\r\n]+", " ", d).strip()
  110. entrystore.add_entry(e)
  111. return entrystore
  112. class GPass05(base.DataHandler):
  113. "Data handler for GPass 0.5.x data"
  114. name = "GPass 0.5.x (or newer)"
  115. importer = True
  116. exporter = True
  117. encryption = True
  118. def __init__(self):
  119. base.DataHandler.__init__(self)
  120. def __getint(self, input):
  121. "Fetches an integer from the input"
  122. if len(input) < 4:
  123. raise base.FormatError
  124. return ord(input[0]) << 0 | ord(input[1]) << 8 | ord(input[2]) << 16 | ord(input[3]) << 24
  125. def __getstr(self, input):
  126. "Fetches a string from the input"
  127. length = self.__getint(input[:4])
  128. if len(input) < (4 + length):
  129. raise base.FormatError
  130. string = input[4:4 + length]
  131. if len(string) != length:
  132. raise base.FormatError
  133. return string
  134. def __mkint(self, input):
  135. "Creates a string-representation of an integer"
  136. string = ""
  137. for i in range(4):
  138. string += chr(input >> i * 8 & 0xff)
  139. return string
  140. def __mkstr(self, input):
  141. "Makes a string suitable for inclusion in the data stream"
  142. return self.__mkint(len(input)) + input
  143. def __normstr(self, string):
  144. "Normalizes a string"
  145. string = re.sub("[\r\n]+", " ", string)
  146. string = string.decode(locale.getpreferredencoding(), "replace")
  147. string = string.encode("utf-8", "replace")
  148. return string
  149. def __packint(self, input):
  150. "Packs an integer"
  151. if input == 0:
  152. return "\x00"
  153. string = ""
  154. while input > 0:
  155. c = input % 0x80
  156. input = input / 0x80
  157. if input > 0:
  158. c |= 0x80
  159. string += chr(c)
  160. return string
  161. def __packstr(self, input):
  162. "Packs a string"
  163. return self.__packint(len(input)) + input
  164. def __unpackint(self, input):
  165. "Fetches a packed number from the input"
  166. value = 0
  167. b = 1
  168. for i in range(min(len(input), 6)):
  169. c = ord(input[i])
  170. if c & 0x80:
  171. value += b * (c & 0x7f)
  172. b *= 0x80;
  173. else:
  174. value += b * c
  175. return i + 1, value
  176. # if we didn't return in the for-loop, the input is invalid
  177. else:
  178. raise base.FormatError
  179. def __unpackstr(self, input):
  180. "Unpacks a string from the input"
  181. cut, length = self.__unpackint(input[:6])
  182. if len(input) < cut + length:
  183. raise base.FormatError
  184. return cut + length, input[cut:cut + length]
  185. def export_data(self, entrystore, password):
  186. "Exports data from an entrystore"
  187. plaintext = "GPassFile version 1.1.0"
  188. iter = entrystore.iter_children(None)
  189. id = 0
  190. foldermap = {}
  191. while iter != None:
  192. id += 1
  193. path = entrystore.get_path(iter)
  194. parentpath = path[:-1]
  195. if len(parentpath) > 0 and foldermap.has_key(parentpath):
  196. parentid = foldermap[parentpath]
  197. else:
  198. parentid = 0
  199. e = entrystore.get_entry(iter)
  200. if type(e) == entry.FolderEntry:
  201. foldermap[path] = id
  202. elif type(e) != entry.GenericEntry:
  203. e = e.convert_generic()
  204. entrydata = ""
  205. entrydata += self.__mkint(id)
  206. entrydata += self.__mkint(parentid)
  207. entrydata += self.__mkstr(type(e) == entry.FolderEntry and "folder" or "general")
  208. attrdata = ""
  209. attrdata += self.__packstr(e.name)
  210. attrdata += self.__packstr(e.description)
  211. attrdata += self.__packint(e.updated)
  212. attrdata += self.__packint(e.updated)
  213. attrdata += self.__packint(0)
  214. attrdata += self.__packint(0)
  215. if type(e) == entry.GenericEntry:
  216. attrdata += self.__packstr(e[entry.UsernameField])
  217. attrdata += self.__packstr(e[entry.PasswordField])
  218. attrdata += self.__packstr(e[entry.HostnameField])
  219. entrydata += self.__mkstr(attrdata)
  220. plaintext += entrydata
  221. iter = entrystore.iter_traverse_next(iter)
  222. return encrypt(plaintext, password)
  223. def import_data(self, input, password):
  224. "Imports data from a data stream to an entrystore"
  225. plaintext = decrypt(input, password, "GPassFile version 1.1.0")
  226. entrystore = data.EntryStore()
  227. foldermap = {}
  228. while len(plaintext) > 0:
  229. # parse data
  230. id = self.__getint(plaintext[:4])
  231. plaintext = plaintext[4:]
  232. parentid = self.__getint(plaintext[:4])
  233. plaintext = plaintext[4:]
  234. entrytype = self.__getstr(plaintext)
  235. plaintext = plaintext[4 + len(entrytype):]
  236. attrdata = self.__getstr(plaintext)
  237. plaintext = plaintext[4 + len(attrdata):]
  238. l, name = self.__unpackstr(attrdata)
  239. attrdata = attrdata[l:]
  240. l, desc = self.__unpackstr(attrdata)
  241. attrdata = attrdata[l:]
  242. l, ctime = self.__unpackint(attrdata)
  243. attrdata = attrdata[l:]
  244. l, mtime = self.__unpackint(attrdata)
  245. attrdata = attrdata[l:]
  246. l, expire = self.__unpackint(attrdata)
  247. attrdata = attrdata[l:]
  248. l, etime = self.__unpackint(attrdata)
  249. attrdata = attrdata[l:]
  250. if entrytype == "general":
  251. l, username = self.__unpackstr(attrdata)
  252. attrdata = attrdata[l:]
  253. l, password = self.__unpackstr(attrdata)
  254. attrdata = attrdata[l:]
  255. l, hostname = self.__unpackstr(attrdata)
  256. attrdata = attrdata[l:]
  257. else:
  258. username = password = hostname = ""
  259. # create entry
  260. if entrytype == "general":
  261. e = entry.GenericEntry()
  262. e.name = self.__normstr(name)
  263. e.description = self.__normstr(desc)
  264. e.updated = mtime
  265. e[entry.HostnameField] = self.__normstr(hostname)
  266. e[entry.UsernameField] = self.__normstr(username)
  267. e[entry.PasswordField] = self.__normstr(password)
  268. elif entrytype == "folder":
  269. e = entry.FolderEntry()
  270. e.name = self.__normstr(name)
  271. e.description = self.__normstr(desc)
  272. e.updated = mtime
  273. else:
  274. continue
  275. # add entry to entrystore
  276. if foldermap.has_key(parentid):
  277. parent = foldermap[parentid]
  278. else:
  279. parent = None
  280. iter = entrystore.add_entry(e, parent)
  281. if type(e) == entry.FolderEntry:
  282. foldermap[id] = iter
  283. return entrystore