1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Collection of all Keyczar classes used to perform cryptographic functions:
19 encrypt, decrypt, sign and verify.
20
21 @author: arkajit.dey@gmail.com (Arkajit Dey)
22 """
23
24 import os
25
26 from Crypto.Cipher import AES
27
28 import errors
29 import keydata
30 import keyinfo
31 import keys
32 import readers
33 import util
34
35 VERSION = 1
36 KEY_HASH_SIZE = 4
37 HEADER_SIZE = 1 + KEY_HASH_SIZE
40 """Abstract Keyczar base class."""
41
66
67 versions = property(lambda self: [k for k in self._keys.keys()
68 if isinstance(k, keydata.KeyVersion)],
69 doc="""List of versions in key set.""")
70 primary_key = property(lambda self: self.GetKey(self.primary_version),
71 doc="""The primary key for this key set.""")
72
74 return str(self.metadata)
75
77 """
78 Parse the header and verify version, format info. Return key if exists.
79
80 @param header: the bytes of the header of Keyczar output
81 @type header: string
82
83 @return: the key identified by the hash in the header
84 @rtype: L{keys.Key}
85
86 @raise BadVersionError: if header specifies an illegal version
87 @raise KeyNotFoundError: if key specified in header doesn't exist
88 """
89 version = ord(header[0])
90 if version != VERSION:
91 raise errors.BadVersionError(version)
92 hash = util.Encode(header[1:])
93 return self.GetKey(hash)
94
95 @staticmethod
97 """
98 Return a Keyczar object created from FileReader at given location.
99
100 @param location: pathname of the directory storing the key files
101 @type location: string
102
103 @return: a Keyczar to manage the keys stored at the given location
104 @rtype: L{Keyczar}
105 """
106 return Keyczar(readers.FileReader(location))
107
109 """Indicates whether purpose is valid. Abstract method."""
110
112 """
113 Returns the key associated with the given id, a hash or a version.
114
115 @param id: Either the hash identifier of the key or its version.
116 @type id: string or L{keydata.KeyVersion}
117
118 @return: key associated with this id or None if id doesn't exist.
119 @rtype: L{keys.Key}
120
121 @raise KeyNotFoundError: if key with given id doesn't exist
122 """
123 try:
124 return self._keys[id]
125 except KeyError:
126 raise errors.KeyNotFoundError(id)
127
129 self._keys[version] = self._keys[key.hash] = key
130 self.metadata.AddVersion(version)
131
133
134 """To be used by Keyczart."""
135
136 @staticmethod
137 - def Read(location):
140
142 """All purposes ok for Keyczart."""
143 return True
144
146 """
147 Adds a new key version with given status to key set.
148
149 Generates a new key of same type (repeated until hash identifier is unique)
150 for this version. Uses supplied key size (if provided) in lieu of the
151 default key size. If this is an unacceptable key size, uses the default
152 key size. Uses next available version number.
153
154 @param status: the status of the new key to be added
155 @type status: L{keyinfo.KeyStatus}
156
157 @param size: size of key in bits, uses default size if not provided.
158 @type size: integer
159
160 @raise KeyczarError: if key type unsupported
161 """
162 if size is None:
163 size = self.default_size
164
165 version = keydata.KeyVersion(len(self.versions) + 1, status, False)
166
167 if status == keyinfo.PRIMARY:
168 if self.primary_version is not None:
169 self.primary_version.status = keyinfo.ACTIVE
170 self.primary_version = version
171
172 if size < self.default_size:
173 print("WARNING: %d-bit key size is less than recommended default key" +
174 "size of %d bits for %s keys."
175 % (size, self.default_size, str(self.metadata.type)))
176
177
178 while True:
179 key = keys.GenKey(self.metadata.type, size)
180 if self._keys.get(key.hash) is None:
181 break
182
183 self._AddKey(version, key)
184
207
208 - def Demote(self, version_number):
209 """
210 Demotes the status of key with given version number.
211
212 Demoting PRIMARY key results in a key set with no primary version.
213
214 @param version_number: the version number to demote
215 @type version_number: integer
216
217 @raise KeyczarError: if invalid version number or trying to demote an
218 inactive key, use L{Revoke} instead.
219 """
220 version = self.metadata.GetVersion(version_number)
221 if version.status == keyinfo.PRIMARY:
222 version.status = keyinfo.ACTIVE
223 self.primary_version = None
224 elif version.status == keyinfo.ACTIVE:
225 version.status = keyinfo.INACTIVE
226 elif version.status == keyinfo.INACTIVE:
227 raise errors.KeyczarError("Can't demote an inactive key, only revoke.")
228
229 - def Revoke(self, version_number):
230 """
231 Revokes the key with given version number if scheduled to be revoked.
232
233 @param version_number: integer version number to revoke
234 @type version_number: integer
235
236 @raise KeyczarError: if invalid version number or key is not inactive.
237 """
238 version = self.metadata.GetVersion(version_number)
239 if version.status == keyinfo.INACTIVE:
240 self.metadata.RemoveVersion(version_number)
241 else:
242 raise errors.KeyczarError("Can't revoke key if not inactive.")
243
245 """Export the public keys corresponding to our key set to destination."""
246 kmd = self.metadata
247 pubkmd = None
248 if kmd.type == keyinfo.DSA_PRIV and kmd.purpose == keyinfo.SIGN_AND_VERIFY:
249 pubkmd = keydata.KeyMetadata(kmd.name, keyinfo.VERIFY, keyinfo.DSA_PUB)
250 elif kmd.type == keyinfo.RSA_PRIV:
251 if kmd.purpose == keyinfo.DECRYPT_AND_ENCRYPT:
252 pubkmd = keydata.KeyMetadata(kmd.name, keyinfo.ENCRYPT, keyinfo.RSA_PUB)
253 elif kmd.purpose == keyinfo.SIGN_AND_VERIFY:
254 pubkmd = keydata.KeyMetadata(kmd.name, keyinfo.VERIFY, keyinfo.RSA_PUB)
255 if pubkmd is None:
256 raise errors.KeyczarError("Cannot export public key")
257 for v in self.versions:
258 pubkmd.AddVersion(v)
259 pubkey = self.GetKey(v).public_key
260 util.WriteFile(str(pubkey), os.path.join(dest, str(v.version_number)))
261 util.WriteFile(str(pubkmd), os.path.join(dest, "meta"))
262
263 - def Write(self, loc, encrypter=None):
264 if encrypter:
265 self.metadata.encrypted = True
266 util.WriteFile(str(self.metadata), os.path.join(loc, "meta"))
267 for v in self.versions:
268 key = str(self.GetKey(v))
269 if self.metadata.encrypted:
270 key = encrypter.Encrypt(key)
271 util.WriteFile(key, os.path.join(loc, str(v.version_number)))
272
274 """Capable of encrypting only."""
275
276 @staticmethod
277 - def Read(location):
278 """
279 Return an Encrypter object created from FileReader at given location.
280
281 @param location: pathname of the directory storing the key files
282 @type location: string
283
284 @return: an Encrypter to manage the keys stored at the given location and
285 perform encryption functions.
286 @rtype: L{Encrypter}
287 """
288 return Encrypter(readers.FileReader(location))
289
293
295 """
296 Encrypt the data and return the ciphertext.
297
298 @param data: message to encrypt
299 @type data: string
300
301 @return: ciphertext encoded as a Base64 string
302 @rtype: string
303
304 @raise NoPrimaryKeyError: if no primary key can be found to encrypt
305 """
306 encrypting_key = self.primary_key
307 if encrypting_key is None:
308 raise errors.NoPrimaryKeyError()
309 return util.Encode(encrypting_key.Encrypt(data))
310
312 """Capable of verifying only."""
313
314 @staticmethod
315 - def Read(location):
316 """
317 Return a Verifier object created from FileReader at given location.
318
319 @param location: pathname of the directory storing the key files
320 @type location: string
321
322 @return: a Verifier to manage the keys stored at the given location and
323 perform verify functions.
324 @rtype: L{Verifier}
325 """
326 return Verifier(readers.FileReader(location))
327
331
333 """
334 Verifies whether the signature corresponds to the given data.
335
336 @param data: message that has been signed with sig
337 @type data: string
338
339 @param sig: Base64 string formatted as Header|Signature
340 @type sig: string
341
342 @return: True if sig corresponds to data, False otherwise.
343 @rtype: boolean
344 """
345 sig_bytes = util.Decode(sig)
346 if len(sig_bytes) < HEADER_SIZE:
347 raise errors.ShortSignatureError(len(sig_bytes))
348 key = self._ParseHeader(sig_bytes[:HEADER_SIZE])
349 return key.Verify(sig_bytes[:HEADER_SIZE] + data, sig_bytes[HEADER_SIZE:])
350
352
353 """Capable of encrypting and decrypting."""
354
355 @staticmethod
356 - def Read(location):
357 """
358 Return a Crypter object created from FileReader at given location.
359
360 @param location: pathname of the directory storing the key files
361 @type location: string
362
363 @return: a Crypter to manage the keys stored at the given location and
364 perform encryption and decryption functions.
365 @rtype: L{Crypter}
366 """
367 return Crypter(readers.FileReader(location))
368
372
374 """
375 Decrypts the given ciphertext and returns the plaintext.
376
377 @param ciphertext: Base64 encoded string ciphertext to be decrypted.
378 @type ciphertext: string
379
380 @return: plaintext message
381 @rtype: string
382
383 @raise ShortCiphertextError: if length is too short to have Header, IV, Sig
384 @raise BadVersionError: if header specifies an illegal version
385 @raise BadFormatError: if header specifies an illegal format
386 @raise KeyNotFoundError: if key specified in header doesn't exist
387 @raise InvalidSignatureError: if the signature can't be verified
388 """
389 data_bytes = util.Decode(ciphertext)
390 if len(data_bytes) < HEADER_SIZE:
391 raise errors.ShortCiphertextError(len(data_bytes))
392 key = self._ParseHeader(data_bytes[:HEADER_SIZE])
393 return key.Decrypt(data_bytes)
394
396 """Capable of both signing and verifying."""
397
398 @staticmethod
399 - def Read(location):
400 """
401 Return a Signer object created from FileReader at given location.
402
403 @param location: pathname of the directory storing the key files
404 @type location: string
405
406 @return: a Signer to manage the keys stored at the given location and
407 perform sign and verify functions.
408 @rtype: L{Signer}
409 """
410 return Signer(readers.FileReader(location))
411
415
416 - def Sign(self, data):
417 """
418 Sign given data and return corresponding signature.
419
420 For message M, outputs the signature as Header|Sig(Header.M).
421
422 @param data: message to be signed
423 @type data: string
424
425 @return: signature on the data encoded as a Base64 string
426 @rtype: string
427 """
428 signing_key = self.primary_key
429 if signing_key is None:
430 raise errors.NoPrimaryKeyError()
431 header = signing_key.Header()
432 return util.Encode(header + signing_key.Sign(header + data))
433