Package keyczar :: Module keyczar
[hide private]
[frames] | no frames]

Source Code for Module keyczar.keyczar

  1  #!/usr/bin/python2.4 
  2  # 
  3  # Copyright 2008 Google Inc. 
  4  # 
  5  # Licensed under the Apache License, Version 2.0 (the "License"); 
  6  # you may not use this file except in compliance with the License. 
  7  # You may obtain a copy of the License at 
  8  #  
  9  #      http://www.apache.org/licenses/LICENSE-2.0 
 10  #  
 11  # Unless required by applicable law or agreed to in writing, software 
 12  # distributed under the License is distributed on an "AS IS" BASIS, 
 13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 14  # See the License for the specific language governing permissions and 
 15  # limitations under the License. 
 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 
38 39 -class Keyczar(object):
40 """Abstract Keyczar base class.""" 41
42 - def __init__(self, reader):
43 self.metadata = keydata.KeyMetadata.Read(reader.GetMetadata()) 44 self._keys = {} # maps both KeyVersions and hash ids to keys 45 self.primary_version = None # default if no primary key 46 self.default_size = self.metadata.type.default_size 47 48 if not self.IsAcceptablePurpose(self.metadata.purpose): 49 raise errors.KeyczarError("Unacceptable purpose: %s" 50 % self.metadata.purpose) 51 52 if self.metadata.encrypted and not isinstance(reader, 53 readers.EncryptedReader): 54 raise errors.KeyczarError("Need encrypted reader.") 55 56 for version in self.metadata.versions: 57 if version.status == keyinfo.PRIMARY: 58 if self.primary_version is not None: 59 raise errors.KeyczarError( 60 "Key sets may only have a single primary version") 61 self.primary_version = version 62 key = keys.ReadKey(self.metadata.type, 63 reader.GetKey(version.version_number)) 64 self._keys[version] = key 65 self._keys[key.hash] = key
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
73 - def __str__(self):
74 return str(self.metadata)
75
76 - def _ParseHeader(self, header):
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
96 - def Read(location):
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
108 - def IsAcceptablePurpose(self, purpose):
109 """Indicates whether purpose is valid. Abstract method."""
110
111 - def GetKey(self, id):
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
128 - def _AddKey(self, version, key):
129 self._keys[version] = self._keys[key.hash] = key 130 self.metadata.AddVersion(version)
131
132 -class GenericKeyczar(Keyczar):
133 134 """To be used by Keyczart.""" 135 136 @staticmethod
137 - def Read(location):
138 """Return a GenericKeyczar created from FileReader at given location.""" 139 return GenericKeyczar(readers.FileReader(location))
140
141 - def IsAcceptablePurpose(self, purpose):
142 """All purposes ok for Keyczart.""" 143 return True
144
145 - def AddVersion(self, status, size=None):
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 # Make sure no keys collide on their identifiers 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
185 - def Promote(self, version_number):
186 """ 187 Promotes the status of key with given version number. 188 189 Promoting ACTIVE key automatically demotes current PRIMARY key to ACTIVE. 190 191 @param version_number: the version number to promote 192 @type version_number: integer 193 194 @raise KeyczarError: if invalid version number or trying to promote 195 a primary key 196 """ 197 version = self.metadata.GetVersion(version_number) 198 if version.status == keyinfo.PRIMARY: 199 raise errors.KeyczarError("Can't promote a primary key.") 200 elif version.status == keyinfo.ACTIVE: 201 version.status = keyinfo.PRIMARY 202 if self.primary_version is not None: 203 self.primary_version.status = keyinfo.ACTIVE # only one primary key 204 self.primary_version = version 205 elif version.status == keyinfo.INACTIVE: 206 version.status = keyinfo.ACTIVE
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 # no more primary keys in the set 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
244 - def PublicKeyExport(self, dest):
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")) # just plain 267 for v in self.versions: 268 key = str(self.GetKey(v)) 269 if self.metadata.encrypted: 270 key = encrypter.Encrypt(key) # encrypt key info before outputting 271 util.WriteFile(key, os.path.join(loc, str(v.version_number)))
272
273 -class Encrypter(Keyczar):
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
290 - def IsAcceptablePurpose(self, purpose):
291 """Only valid if purpose includes encrypting.""" 292 return purpose == keyinfo.ENCRYPT or purpose == keyinfo.DECRYPT_AND_ENCRYPT
293
294 - def Encrypt(self, data):
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
311 -class Verifier(Keyczar):
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
328 - def IsAcceptablePurpose(self, purpose):
329 """Only valid if purpose includes verifying.""" 330 return purpose == keyinfo.VERIFY or purpose == keyinfo.SIGN_AND_VERIFY
331
332 - def Verify(self, data, sig):
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
351 -class Crypter(Encrypter):
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
369 - def IsAcceptablePurpose(self, purpose):
370 """Only valid if purpose includes decrypting""" 371 return purpose == keyinfo.DECRYPT_AND_ENCRYPT
372
373 - def Decrypt(self, ciphertext):
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
395 -class Signer(Verifier):
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
412 - def IsAcceptablePurpose(self, purpose):
413 """Only valid if purpose includes signing.""" 414 return purpose == keyinfo.SIGN_AND_VERIFY
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