wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 1 | # coding: utf-8 |
wbond | ea25fc2 | 2015-06-19 15:07:04 -0400 | [diff] [blame] | 2 | |
| 3 | """ |
| 4 | ASN.1 type classes for public and private keys. Exports the following items: |
| 5 | |
| 6 | - DSAPrivateKey() |
| 7 | - ECPrivateKey() |
| 8 | - EncryptedPrivateKeyInfo() |
| 9 | - PrivateKeyInfo() |
| 10 | - PublicKeyInfo() |
| 11 | - RSAPrivateKey() |
| 12 | - RSAPublicKey() |
| 13 | |
| 14 | Other type classes are defined that help compose the types listed above. |
| 15 | """ |
| 16 | |
wbond | 6b66ab5 | 2015-06-21 10:26:45 -0400 | [diff] [blame] | 17 | from __future__ import unicode_literals, division, absolute_import, print_function |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 18 | |
| 19 | import hashlib |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 20 | import math |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 21 | |
wbond | 59af99d | 2015-06-15 16:19:04 -0400 | [diff] [blame] | 22 | from .algos import DigestAlgorithm, EncryptionAlgorithm |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 23 | from .core import ( |
| 24 | Any, |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 25 | Asn1Value, |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 26 | Choice, |
| 27 | Integer, |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 28 | IntegerOctetString, |
| 29 | Null, |
| 30 | ObjectIdentifier, |
| 31 | OctetBitString, |
| 32 | OctetString, |
| 33 | Sequence, |
| 34 | SequenceOf, |
| 35 | SetOf, |
| 36 | ) |
wbond | 488eac3 | 2015-06-17 23:35:05 -0400 | [diff] [blame] | 37 | from ._elliptic_curve import ( |
wbond | e7c390c | 2015-06-19 02:01:55 -0400 | [diff] [blame] | 38 | SECP192R1_BASE_POINT, |
| 39 | SECP224R1_BASE_POINT, |
| 40 | SECP256R1_BASE_POINT, |
| 41 | SECP384R1_BASE_POINT, |
| 42 | SECP521R1_BASE_POINT, |
wbond | 488eac3 | 2015-06-17 23:35:05 -0400 | [diff] [blame] | 43 | PrimeCurve, |
| 44 | PrimePoint, |
| 45 | ) |
wbond | c9fec24 | 2015-07-13 09:03:11 -0400 | [diff] [blame] | 46 | from ._int import int_from_bytes |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 47 | |
| 48 | try: |
| 49 | # Python 2 |
| 50 | str_cls = unicode #pylint: disable=E0602 |
| 51 | byte_cls = str |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 52 | |
| 53 | except NameError: |
| 54 | # Python 3 |
| 55 | str_cls = str |
| 56 | byte_cls = bytes |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 57 | |
| 58 | |
| 59 | |
| 60 | class OtherPrimeInfo(Sequence): |
| 61 | """ |
| 62 | Source: https://tools.ietf.org/html/rfc3447#page-46 |
| 63 | """ |
| 64 | |
| 65 | _fields = [ |
| 66 | ('prime', Integer), |
| 67 | ('exponent', Integer), |
| 68 | ('coefficient', Integer), |
| 69 | ] |
| 70 | |
| 71 | |
| 72 | class OtherPrimeInfos(SequenceOf): |
| 73 | """ |
| 74 | Source: https://tools.ietf.org/html/rfc3447#page-46 |
| 75 | """ |
| 76 | |
| 77 | _child_spec = OtherPrimeInfo |
| 78 | |
| 79 | |
| 80 | class RSAPrivateKeyVersion(Integer): |
| 81 | """ |
| 82 | Original Name: Version |
| 83 | Source: https://tools.ietf.org/html/rfc3447#page-45 |
| 84 | """ |
| 85 | |
| 86 | _map = { |
| 87 | 0: 'two-prime', |
| 88 | 1: 'multi', |
| 89 | } |
| 90 | |
| 91 | |
| 92 | class RSAPrivateKey(Sequence): |
| 93 | """ |
| 94 | Source: https://tools.ietf.org/html/rfc3447#page-45 |
| 95 | """ |
| 96 | |
| 97 | _fields = [ |
| 98 | ('version', RSAPrivateKeyVersion), |
| 99 | ('modulus', Integer), |
| 100 | ('public_exponent', Integer), |
| 101 | ('private_exponent', Integer), |
| 102 | ('prime1', Integer), |
| 103 | ('prime2', Integer), |
| 104 | ('exponent1', Integer), |
| 105 | ('exponent2', Integer), |
| 106 | ('coefficient', Integer), |
| 107 | ('other_prime_infos', OtherPrimeInfos, {'optional': True}) |
| 108 | ] |
| 109 | |
| 110 | |
| 111 | class RSAPublicKey(Sequence): |
| 112 | """ |
| 113 | Source: https://tools.ietf.org/html/rfc3447#page-44 |
| 114 | """ |
| 115 | |
| 116 | _fields = [ |
| 117 | ('modulus', Integer), |
| 118 | ('public_exponent', Integer) |
| 119 | ] |
| 120 | |
| 121 | |
| 122 | class DSAPrivateKey(Sequence): |
| 123 | """ |
| 124 | The ASN1 structure that OpenSSL uses to store a DSA private key that is |
| 125 | not part of a PKCS#8 structure. Reversed engineered from english-language |
| 126 | description on linked OpenSSL documentation page. |
| 127 | |
| 128 | Original Name: None |
| 129 | Source: https://www.openssl.org/docs/apps/dsa.html |
| 130 | """ |
| 131 | |
| 132 | _fields = [ |
| 133 | ('version', Integer), |
| 134 | ('p', Integer), |
| 135 | ('q', Integer), |
| 136 | ('g', Integer), |
| 137 | ('public_key', Integer), |
| 138 | ('private_key', Integer), |
| 139 | ] |
| 140 | |
| 141 | |
| 142 | class SpecifiedECDomainVersion(Integer): |
| 143 | """ |
| 144 | Source: http://www.secg.org/sec1-v2.pdf page 104 |
| 145 | """ |
| 146 | _map = { |
| 147 | 1: 'ecdpVer1', |
| 148 | 2: 'ecdpVer2', |
| 149 | 3: 'ecdpVer3', |
| 150 | } |
| 151 | |
| 152 | |
| 153 | class FieldType(ObjectIdentifier): |
| 154 | """ |
| 155 | Original Name: None |
| 156 | Source: http://www.secg.org/sec1-v2.pdf page 101 |
| 157 | """ |
| 158 | |
| 159 | _map = { |
| 160 | '1.2.840.10045.1.1': 'prime_field', |
| 161 | '1.2.840.10045.1.2': 'characteristic_two_field', |
| 162 | } |
| 163 | |
| 164 | |
| 165 | class CharacteristicTwoBasis(ObjectIdentifier): |
| 166 | """ |
| 167 | Original Name: None |
| 168 | Source: http://www.secg.org/sec1-v2.pdf page 102 |
| 169 | """ |
| 170 | |
| 171 | _map = { |
| 172 | '1.2.840.10045.1.2.1.1': 'gn_basis', |
| 173 | '1.2.840.10045.1.2.1.2': 'tp_basis', |
| 174 | '1.2.840.10045.1.2.1.3': 'pp_basis', |
| 175 | } |
| 176 | |
| 177 | |
| 178 | class Pentanomial(Sequence): |
| 179 | """ |
| 180 | Source: http://www.secg.org/sec1-v2.pdf page 102 |
| 181 | """ |
| 182 | |
| 183 | _fields = [ |
| 184 | ('k1', Integer), |
| 185 | ('k2', Integer), |
| 186 | ('k3', Integer), |
| 187 | ] |
| 188 | |
| 189 | |
| 190 | class CharacteristicTwo(Sequence): |
| 191 | """ |
| 192 | Original Name: Characteristic-two |
| 193 | Source: http://www.secg.org/sec1-v2.pdf page 101 |
| 194 | """ |
| 195 | |
| 196 | _fields = [ |
| 197 | ('m', Integer), |
| 198 | ('basis', CharacteristicTwoBasis), |
| 199 | ('parameters', Any), |
| 200 | ] |
| 201 | |
| 202 | _oid_pair = ('basis', 'parameters') |
| 203 | _oid_specs = { |
| 204 | 'gn_basis': Null, |
| 205 | 'tp_basis': Integer, |
| 206 | 'pp_basis': Pentanomial, |
| 207 | } |
| 208 | |
| 209 | |
| 210 | class FieldID(Sequence): |
| 211 | """ |
| 212 | Source: http://www.secg.org/sec1-v2.pdf page 100 |
| 213 | """ |
| 214 | |
| 215 | _fields = [ |
| 216 | ('field_type', FieldType), |
| 217 | ('parameters', Any), |
| 218 | ] |
| 219 | |
| 220 | _oid_pair = ('field_type', 'parameters') |
| 221 | _oid_specs = { |
| 222 | 'prime_field': Integer, |
| 223 | 'characteristic_two_field': CharacteristicTwo, |
| 224 | } |
| 225 | |
| 226 | |
| 227 | class Curve(Sequence): |
| 228 | """ |
| 229 | Source: http://www.secg.org/sec1-v2.pdf page 104 |
| 230 | """ |
| 231 | |
| 232 | _fields = [ |
| 233 | ('a', OctetString), |
| 234 | ('b', OctetString), |
| 235 | ('seed', OctetBitString, {'optional': True}), |
| 236 | ] |
| 237 | |
| 238 | |
| 239 | class SpecifiedECDomain(Sequence): |
| 240 | """ |
| 241 | Source: http://www.secg.org/sec1-v2.pdf page 103 |
| 242 | """ |
| 243 | |
| 244 | _fields = [ |
| 245 | ('version', SpecifiedECDomainVersion), |
| 246 | ('field_id', FieldID), |
| 247 | ('curve', Curve), |
| 248 | ('base', OctetString), |
| 249 | ('order', Integer), |
| 250 | ('cofactor', Integer, {'optional': True}), |
| 251 | ('hash', DigestAlgorithm, {'optional': True}), |
| 252 | ] |
| 253 | |
| 254 | |
| 255 | class NamedCurve(ObjectIdentifier): |
| 256 | """ |
| 257 | Various named curves |
| 258 | |
| 259 | Original Name: None |
| 260 | Source: https://tools.ietf.org/html/rfc3279#page-23, |
| 261 | https://tools.ietf.org/html/rfc5480#page-5 |
| 262 | """ |
| 263 | |
| 264 | _map = { |
| 265 | # https://tools.ietf.org/html/rfc3279#page-23 |
| 266 | '1.2.840.10045.3.0.1': 'c2pnb163v1', |
| 267 | '1.2.840.10045.3.0.2': 'c2pnb163v2', |
| 268 | '1.2.840.10045.3.0.3': 'c2pnb163v3', |
| 269 | '1.2.840.10045.3.0.4': 'c2pnb176w1', |
| 270 | '1.2.840.10045.3.0.5': 'c2tnb191v1', |
| 271 | '1.2.840.10045.3.0.6': 'c2tnb191v2', |
| 272 | '1.2.840.10045.3.0.7': 'c2tnb191v3', |
| 273 | '1.2.840.10045.3.0.8': 'c2onb191v4', |
| 274 | '1.2.840.10045.3.0.9': 'c2onb191v5', |
| 275 | '1.2.840.10045.3.0.10': 'c2pnb208w1', |
| 276 | '1.2.840.10045.3.0.11': 'c2tnb239v1', |
| 277 | '1.2.840.10045.3.0.12': 'c2tnb239v2', |
| 278 | '1.2.840.10045.3.0.13': 'c2tnb239v3', |
| 279 | '1.2.840.10045.3.0.14': 'c2onb239v4', |
| 280 | '1.2.840.10045.3.0.15': 'c2onb239v5', |
| 281 | '1.2.840.10045.3.0.16': 'c2pnb272w1', |
| 282 | '1.2.840.10045.3.0.17': 'c2pnb304w1', |
| 283 | '1.2.840.10045.3.0.18': 'c2tnb359v1', |
| 284 | '1.2.840.10045.3.0.19': 'c2pnb368w1', |
| 285 | '1.2.840.10045.3.0.20': 'c2tnb431r1', |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 286 | '1.2.840.10045.3.1.2': 'prime192v2', |
| 287 | '1.2.840.10045.3.1.3': 'prime192v3', |
| 288 | '1.2.840.10045.3.1.4': 'prime239v1', |
| 289 | '1.2.840.10045.3.1.5': 'prime239v2', |
| 290 | '1.2.840.10045.3.1.6': 'prime239v3', |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 291 | # https://tools.ietf.org/html/rfc5480#page-5 |
| 292 | '1.3.132.0.1': 'sect163k1', |
| 293 | '1.3.132.0.15': 'sect163r2', |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 294 | '1.2.840.10045.3.1.1': 'secp192r1', |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 295 | '1.3.132.0.33': 'secp224r1', |
| 296 | '1.3.132.0.26': 'sect233k1', |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 297 | '1.2.840.10045.3.1.7': 'secp256r1', |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 298 | '1.3.132.0.27': 'sect233r1', |
| 299 | '1.3.132.0.16': 'sect283k1', |
| 300 | '1.3.132.0.17': 'sect283r1', |
| 301 | '1.3.132.0.34': 'secp384r1', |
| 302 | '1.3.132.0.36': 'sect409k1', |
| 303 | '1.3.132.0.37': 'sect409r1', |
| 304 | '1.3.132.0.35': 'secp521r1', |
| 305 | '1.3.132.0.38': 'sect571k1', |
| 306 | '1.3.132.0.39': 'sect571r1', |
| 307 | } |
| 308 | |
| 309 | |
| 310 | class ECDomainParameters(Choice): |
| 311 | """ |
| 312 | Source: http://www.secg.org/sec1-v2.pdf page 102 |
| 313 | """ |
| 314 | |
| 315 | _alternatives = [ |
| 316 | ('specified', SpecifiedECDomain), |
| 317 | ('named', NamedCurve), |
| 318 | ('implicit_ca', Null), |
| 319 | ] |
| 320 | |
| 321 | |
| 322 | class ECPrivateKeyVersion(Integer): |
| 323 | """ |
| 324 | Original Name: None |
| 325 | Source: http://www.secg.org/sec1-v2.pdf page 108 |
| 326 | """ |
| 327 | |
| 328 | _map = { |
| 329 | 1: 'ecPrivkeyVer1', |
| 330 | } |
| 331 | |
| 332 | |
| 333 | class ECPrivateKey(Sequence): |
| 334 | """ |
| 335 | Source: http://www.secg.org/sec1-v2.pdf page 108 |
| 336 | """ |
| 337 | |
| 338 | _fields = [ |
| 339 | ('version', ECPrivateKeyVersion), |
| 340 | ('private_key', IntegerOctetString), |
| 341 | ('parameters', ECDomainParameters, {'tag_type': 'explicit', 'tag': 0, 'optional': True}), |
wbond | 29f8149 | 2015-06-17 22:35:33 -0400 | [diff] [blame] | 342 | ('public_key', OctetBitString, {'tag_type': 'explicit', 'tag': 1, 'optional': True}), |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 343 | ] |
| 344 | |
| 345 | |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 346 | class DSAParams(Sequence): |
| 347 | """ |
| 348 | Parameters for a DSA public or private key |
| 349 | |
| 350 | Original Name: Dss-Parms |
| 351 | Source: https://tools.ietf.org/html/rfc3279#page-9 |
| 352 | """ |
| 353 | |
| 354 | _fields = [ |
| 355 | ('p', Integer), |
| 356 | ('q', Integer), |
| 357 | ('g', Integer), |
| 358 | ] |
| 359 | |
| 360 | |
| 361 | class Attribute(Sequence): |
| 362 | """ |
| 363 | Source: https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-X.501-198811-S!!PDF-E&type=items page 8 |
| 364 | """ |
| 365 | |
| 366 | _fields = [ |
| 367 | ('type', ObjectIdentifier), |
| 368 | ('values', SetOf, {'spec': Any}), |
| 369 | ] |
| 370 | |
| 371 | |
| 372 | class Attributes(SetOf): |
| 373 | """ |
| 374 | Source: https://tools.ietf.org/html/rfc5208#page-3 |
| 375 | """ |
| 376 | |
| 377 | _child_spec = Attribute |
| 378 | |
| 379 | |
| 380 | class PrivateKeyAlgorithmId(ObjectIdentifier): |
| 381 | """ |
| 382 | These OIDs for various public keys are reused when storing private keys |
| 383 | inside of a PKCS#8 structure |
| 384 | |
| 385 | Original Name: None |
| 386 | Source: https://tools.ietf.org/html/rfc3279 |
| 387 | """ |
| 388 | |
| 389 | _map = { |
| 390 | # https://tools.ietf.org/html/rfc3279#page-19 |
| 391 | '1.2.840.113549.1.1.1': 'rsa', |
| 392 | # https://tools.ietf.org/html/rfc3279#page-18 |
| 393 | '1.2.840.10040.4.1': 'dsa', |
| 394 | # https://tools.ietf.org/html/rfc3279#page-13 |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 395 | '1.2.840.10045.2.1': 'ec', |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 396 | } |
| 397 | |
| 398 | |
| 399 | class PrivateKeyAlgorithm(Sequence): |
| 400 | """ |
| 401 | Original Name: PrivateKeyAlgorithmIdentifier |
| 402 | Source: https://tools.ietf.org/html/rfc5208#page-3 |
| 403 | """ |
| 404 | |
| 405 | _fields = [ |
| 406 | ('algorithm', PrivateKeyAlgorithmId), |
| 407 | ('parameters', Any, {'optional': True}), |
| 408 | ] |
| 409 | |
| 410 | _oid_pair = ('algorithm', 'parameters') |
| 411 | _oid_specs = { |
| 412 | 'rsa': Null, |
| 413 | 'dsa': DSAParams, |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 414 | 'ec': ECDomainParameters, |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 415 | } |
| 416 | |
| 417 | |
| 418 | class PrivateKeyInfo(Sequence): |
| 419 | """ |
| 420 | Source: https://tools.ietf.org/html/rfc5208#page-3 |
| 421 | """ |
| 422 | |
| 423 | _fields = [ |
| 424 | ('version', Integer), |
| 425 | ('private_key_algorithm', PrivateKeyAlgorithm), |
| 426 | ('private_key', OctetString), |
| 427 | ('attributes', Attributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}), |
| 428 | ] |
| 429 | |
| 430 | def _private_key_spec(self): |
| 431 | algorithm = self['private_key_algorithm']['algorithm'].native |
| 432 | return { |
| 433 | 'rsa': RSAPrivateKey, |
| 434 | 'dsa': Integer, |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 435 | 'ec': ECPrivateKey, |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 436 | }[algorithm] |
| 437 | |
| 438 | _spec_callbacks = { |
| 439 | 'private_key': _private_key_spec |
| 440 | } |
| 441 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 442 | _algorithm = None |
| 443 | _bit_size = None |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 444 | _public_key = None |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 445 | _fingerprint = None |
| 446 | |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 447 | @classmethod |
| 448 | def wrap(cls, private_key, algorithm): |
| 449 | """ |
| 450 | Wraps a private key in a PrivateKeyInfo structure |
| 451 | |
| 452 | :param private_key: |
| 453 | A byte string or Asn1Value object of the private key |
| 454 | |
| 455 | :param algorithm: |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 456 | A unicode string of "rsa", "dsa" or "ec" |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 457 | |
| 458 | :return: |
| 459 | A PrivateKeyInfo object |
| 460 | """ |
| 461 | |
| 462 | if not isinstance(private_key, byte_cls) and not isinstance(private_key, Asn1Value): |
| 463 | raise ValueError('private_key must be a byte string or Asn1Value, not %s' % private_key.__class__.__name__) |
| 464 | |
| 465 | if algorithm == 'rsa': |
| 466 | if not isinstance(private_key, RSAPrivateKey): |
| 467 | private_key = RSAPrivateKey.load(private_key) |
| 468 | params = Null() |
| 469 | elif algorithm == 'dsa': |
| 470 | if not isinstance(private_key, DSAPrivateKey): |
| 471 | private_key = DSAPrivateKey.load(private_key) |
| 472 | params = DSAParams() |
| 473 | params['p'] = private_key['p'] |
| 474 | params['q'] = private_key['q'] |
| 475 | params['g'] = private_key['g'] |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 476 | public_key = private_key['public_key'] |
wbond | a4bfddd | 2015-06-17 23:36:16 -0400 | [diff] [blame] | 477 | private_key = private_key['private_key'] |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 478 | elif algorithm == 'ec': |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 479 | if not isinstance(private_key, ECPrivateKey): |
| 480 | private_key = ECPrivateKey.load(private_key) |
| 481 | params = private_key['parameters'] |
wbond | a4bfddd | 2015-06-17 23:36:16 -0400 | [diff] [blame] | 482 | del private_key['parameters'] |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 483 | else: |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 484 | raise ValueError('algorithm must be one of "rsa", "dsa", "ec" - is %s' % repr(algorithm)) |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 485 | |
| 486 | private_key_algo = PrivateKeyAlgorithm() |
| 487 | private_key_algo['algorithm'] = PrivateKeyAlgorithmId(algorithm) |
| 488 | private_key_algo['parameters'] = params |
| 489 | |
| 490 | container = cls() |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 491 | container._algorithm = algorithm #pylint: disable=W0212 |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 492 | container['version'] = Integer(0) |
| 493 | container['private_key_algorithm'] = private_key_algo |
wbond | e95da2c | 2015-06-19 01:49:44 -0400 | [diff] [blame] | 494 | container['private_key'] = OctetString(private_key.untag().dump()) |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 495 | |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 496 | # Here we save the DSA public key if possible since it is not contained |
| 497 | # within the PKCS#8 structure for a DSA key |
| 498 | if algorithm == 'dsa': |
| 499 | container._public_key = public_key #pylint: disable=W0212 |
| 500 | |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 501 | return container |
| 502 | |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 503 | def _compute_public_key(self): |
wbond | 488eac3 | 2015-06-17 23:35:05 -0400 | [diff] [blame] | 504 | """ |
| 505 | Computes the public key corresponding to the current private key. |
| 506 | |
| 507 | :return: |
| 508 | For RSA keys, an RSAPublicKey object. For DSA keys, an Integer |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 509 | object. For EC keys, an OctetString. |
wbond | 488eac3 | 2015-06-17 23:35:05 -0400 | [diff] [blame] | 510 | """ |
| 511 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 512 | if self.algorithm == 'dsa': |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 513 | params = self['private_key_algorithm']['parameters'] |
| 514 | return Integer(pow( |
| 515 | params['g'].native, |
| 516 | self['private_key'].native, |
| 517 | params['p'].native |
| 518 | )) |
wbond | 488eac3 | 2015-06-17 23:35:05 -0400 | [diff] [blame] | 519 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 520 | if self.algorithm == 'rsa': |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 521 | key = self['private_key'].parsed |
| 522 | return RSAPublicKey({ |
| 523 | 'modulus': key['modulus'], |
| 524 | 'public_exponent': key['public_exponent'], |
| 525 | }) |
| 526 | |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 527 | if self.algorithm == 'ec': |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 528 | curve_type, details = self.curve |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 529 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 530 | if curve_type == 'implicit_ca': |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 531 | raise ValueError('Unable to compute public key for EC key using Implicit CA parameters') |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 532 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 533 | if curve_type == 'specified': |
| 534 | if details['field_id']['field_type'] == 'characteristic_two_field': |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 535 | raise ValueError('Unable to compute public key for EC key over a characteristic two field') |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 536 | |
| 537 | curve = PrimeCurve( |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 538 | details['field_id']['parameters'], |
| 539 | int_from_bytes(details['curve']['a']), |
| 540 | int_from_bytes(details['curve']['b']) |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 541 | ) |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 542 | base_point = PrimePoint.load(curve, details['base']) |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 543 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 544 | elif curve_type == 'named': |
| 545 | if details not in ('secp192r1', 'secp224r1', 'secp256r1', 'secp384r1', 'secp521r1'): |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 546 | raise ValueError('Unable to compute public key for EC named curve %s, parameters not currently included' % details) |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 547 | |
| 548 | base_point = { |
wbond | e7c390c | 2015-06-19 02:01:55 -0400 | [diff] [blame] | 549 | 'secp192r1': SECP192R1_BASE_POINT, |
| 550 | 'secp224r1': SECP224R1_BASE_POINT, |
| 551 | 'secp256r1': SECP256R1_BASE_POINT, |
| 552 | 'secp384r1': SECP384R1_BASE_POINT, |
| 553 | 'secp521r1': SECP521R1_BASE_POINT, |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 554 | }[details] |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 555 | |
| 556 | public_point = base_point * self['private_key'].parsed['private_key'].native |
| 557 | return OctetString(public_point.dump()) |
| 558 | |
| 559 | def unwrap(self): |
| 560 | """ |
| 561 | Unwraps the private key into an RSAPrivateKey, DSAPrivateKey or |
| 562 | ECPrivateKey object |
| 563 | |
| 564 | :return: |
| 565 | An RSAPrivateKey, DSAPrivateKey or ECPrivateKey object |
| 566 | """ |
| 567 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 568 | if self.algorithm == 'rsa': |
| 569 | return self['private_key'].parsed |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 570 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 571 | if self.algorithm == 'dsa': |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 572 | params = self['private_key_algorithm']['parameters'] |
| 573 | return DSAPrivateKey({ |
| 574 | 'version': 0, |
| 575 | 'p': params['p'], |
| 576 | 'q': params['q'], |
| 577 | 'g': params['g'], |
| 578 | 'public_key': self.public_key, |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 579 | 'private_key': self['private_key'].parsed, |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 580 | }) |
| 581 | |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 582 | if self.algorithm == 'ec': |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 583 | output = self['private_key'].parsed |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 584 | output['parameters'] = self['private_key_algorithm']['parameters'] |
| 585 | output['public_key'] = OctetBitString(self.public_key.native) |
| 586 | return output |
| 587 | |
| 588 | @property |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 589 | def curve(self): |
| 590 | """ |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 591 | Returns information about the curve used for an EC key |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 592 | |
| 593 | :raises: |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 594 | ValueError - when the key is not an EC key |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 595 | |
| 596 | :return: |
| 597 | A two-element tuple, with the first element being a unicode string |
| 598 | of "implicit_ca", "specified" or "named". If the first element is |
| 599 | "implicit_ca", the second is None. If "specified", the second is |
| 600 | an OrderedDict that is the native version of SpecifiedECDomain. If |
| 601 | "named", the second is a unicode string of the curve name. |
| 602 | """ |
| 603 | |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 604 | if self.algorithm != 'ec': |
| 605 | raise ValueError('Only EC keys have a curve, this key is %s' % self.algorithm.upper()) |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 606 | |
| 607 | params = self['private_key_algorithm']['parameters'] |
| 608 | chosen = params.chosen |
| 609 | |
| 610 | if params.name == 'implicit_ca': |
| 611 | value = None |
| 612 | else: |
| 613 | value = chosen.native |
| 614 | |
| 615 | return (params.name, value) |
| 616 | |
| 617 | @property |
wbond | 1b00a59 | 2015-06-28 12:58:26 -0400 | [diff] [blame] | 618 | def hash_algo(self): |
| 619 | """ |
| 620 | Returns the name of the family of hash algorithms used to generate a |
| 621 | DSA key |
| 622 | |
| 623 | :raises: |
| 624 | ValueError - when the key is not a DSA key |
| 625 | |
| 626 | :return: |
| 627 | A unicode string of "sha1" or "sha2" |
| 628 | """ |
| 629 | |
| 630 | if self.algorithm != 'dsa': |
| 631 | raise ValueError('Only DSA keys are generated using a hash algorithm, this key is %s' % self.algorithm.upper()) |
| 632 | |
| 633 | byte_len = math.log(self['private_key_algorithm']['parameters']['q'].native, 2) / 8 |
| 634 | |
| 635 | return 'sha1' if byte_len <= 20 else 'sha2' |
| 636 | |
| 637 | @property |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 638 | def algorithm(self): |
| 639 | """ |
| 640 | :return: |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 641 | A unicode string of "rsa", "dsa" or "ec" |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 642 | """ |
| 643 | |
| 644 | if self._algorithm is None: |
| 645 | self._algorithm = self['private_key_algorithm']['algorithm'].native |
| 646 | return self._algorithm |
| 647 | |
| 648 | @property |
| 649 | def bit_size(self): |
| 650 | """ |
| 651 | :return: |
wbond | f4f176c | 2015-07-07 23:27:19 -0400 | [diff] [blame] | 652 | The bit size of the private key, as an integer |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 653 | """ |
| 654 | |
| 655 | if self._bit_size is None: |
| 656 | if self.algorithm == 'rsa': |
wbond | f4f176c | 2015-07-07 23:27:19 -0400 | [diff] [blame] | 657 | prime = self['private_key'].parsed['modulus'].native |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 658 | elif self.algorithm == 'dsa': |
| 659 | prime = self['private_key_algorithm']['parameters']['p'].native |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 660 | elif self.algorithm == 'ec': |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 661 | prime = self['private_key'].parsed['private_key'].native |
wbond | f4f176c | 2015-07-07 23:27:19 -0400 | [diff] [blame] | 662 | self._bit_size = int(math.ceil(math.log(prime, 2))) |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 663 | return self._bit_size |
| 664 | |
| 665 | @property |
wbond | f4f176c | 2015-07-07 23:27:19 -0400 | [diff] [blame] | 666 | def byte_size(self): |
| 667 | """ |
| 668 | :return: |
| 669 | The byte size of the private key, as an integer |
| 670 | """ |
| 671 | |
| 672 | return int(math.ceil(self.bit_size / 8)) |
| 673 | |
| 674 | @property |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 675 | def public_key(self): |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 676 | """ |
| 677 | :return: |
| 678 | If an RSA key, an RSAPublicKey object. If a DSA key, an Integer |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 679 | object. If an EC key, an OctetString object. |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 680 | """ |
| 681 | |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 682 | if self._public_key is None: |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 683 | if self.algorithm == 'ec': |
wbond | 488eac3 | 2015-06-17 23:35:05 -0400 | [diff] [blame] | 684 | key = self['private_key'].parsed |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 685 | if key['public_key']: |
| 686 | self._public_key = OctetString(key['public_key'].native) |
| 687 | else: |
| 688 | self._public_key = self._compute_public_key() |
| 689 | else: |
| 690 | self._public_key = self._compute_public_key() |
wbond | 488eac3 | 2015-06-17 23:35:05 -0400 | [diff] [blame] | 691 | |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 692 | return self._public_key |
wbond | 488eac3 | 2015-06-17 23:35:05 -0400 | [diff] [blame] | 693 | |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 694 | @property |
| 695 | def fingerprint(self): |
| 696 | """ |
| 697 | Creates a fingerprint that can be compared with a public key to see if |
| 698 | the two form a pair. |
| 699 | |
| 700 | This fingerprint is not compatiable with fingerprints generated by any |
| 701 | other software. |
| 702 | |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 703 | :return: |
| 704 | A byte string that is a sha256 hash of selected components (based |
| 705 | on the key type) |
| 706 | """ |
| 707 | |
| 708 | if self._fingerprint is None: |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 709 | params = self['private_key_algorithm']['parameters'] |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 710 | key = self['private_key'].parsed |
| 711 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 712 | if self.algorithm == 'rsa': |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 713 | to_hash = '%d:%d' % ( |
| 714 | key['modulus'].native, |
| 715 | key['public_exponent'].native, |
| 716 | ) |
| 717 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 718 | elif self.algorithm == 'dsa': |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 719 | public_key = self.public_key |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 720 | to_hash = '%d:%d:%d:%d' % ( |
| 721 | params['p'].native, |
| 722 | params['q'].native, |
| 723 | params['g'].native, |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 724 | public_key.native, |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 725 | ) |
| 726 | |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 727 | elif self.algorithm == 'ec': |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 728 | public_key = key['public_key'].native |
| 729 | if public_key is None: |
wbond | eb5eb6e | 2015-06-18 14:14:12 -0400 | [diff] [blame] | 730 | public_key = self.public_key.native |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 731 | |
| 732 | if params.name == 'named': |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 733 | to_hash = '%s:' % params.chosen.native |
| 734 | to_hash = to_hash.encode('utf-8') |
| 735 | to_hash += public_key |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 736 | |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 737 | elif params.name == 'implicit_ca': |
| 738 | to_hash = public_key |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 739 | |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 740 | elif params.name == 'specified': |
| 741 | to_hash = '%s:' % params.chosen['field_id']['parameters'].native |
| 742 | to_hash = to_hash.encode('utf-8') |
| 743 | to_hash += b':' + params.chosen['curve']['a'].native |
| 744 | to_hash += b':' + params.chosen['curve']['b'].native |
| 745 | to_hash += public_key |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 746 | |
| 747 | if isinstance(to_hash, str_cls): |
| 748 | to_hash = to_hash.encode('utf-8') |
| 749 | |
| 750 | self._fingerprint = hashlib.sha256(to_hash).digest() |
| 751 | |
| 752 | return self._fingerprint |
| 753 | |
| 754 | |
| 755 | class EncryptedPrivateKeyInfo(Sequence): |
| 756 | """ |
| 757 | Source: https://tools.ietf.org/html/rfc5208#page-4 |
| 758 | """ |
| 759 | |
| 760 | _fields = [ |
wbond | 59af99d | 2015-06-15 16:19:04 -0400 | [diff] [blame] | 761 | ('encryption_algorithm', EncryptionAlgorithm), |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 762 | ('encrypted_data', OctetString), |
| 763 | ] |
| 764 | |
| 765 | |
| 766 | # These structures are from https://tools.ietf.org/html/rfc3279 |
| 767 | |
| 768 | class PublicKeyAlgorithmId(ObjectIdentifier): |
| 769 | """ |
| 770 | Original Name: None |
| 771 | Source: https://tools.ietf.org/html/rfc3279 |
| 772 | """ |
| 773 | |
| 774 | _map = { |
| 775 | # https://tools.ietf.org/html/rfc3279#page-19 |
| 776 | '1.2.840.113549.1.1.1': 'rsa', |
| 777 | # https://tools.ietf.org/html/rfc3279#page-18 |
| 778 | '1.2.840.10040.4.1': 'dsa', |
| 779 | # https://tools.ietf.org/html/rfc3279#page-13 |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 780 | '1.2.840.10045.2.1': 'ec', |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 781 | } |
| 782 | |
| 783 | |
| 784 | class PublicKeyAlgorithm(Sequence): |
| 785 | """ |
| 786 | Original Name: AlgorithmIdentifier |
| 787 | Source: https://tools.ietf.org/html/rfc5280#page-18 |
| 788 | """ |
| 789 | |
| 790 | _fields = [ |
| 791 | ('algorithm', PublicKeyAlgorithmId), |
| 792 | ('parameters', Any, {'optional': True}), |
| 793 | ] |
| 794 | |
| 795 | _oid_pair = ('algorithm', 'parameters') |
| 796 | _oid_specs = { |
| 797 | 'rsa': Null, |
| 798 | 'dsa': DSAParams, |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 799 | 'ec': ECDomainParameters, |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 800 | } |
| 801 | |
| 802 | |
| 803 | class PublicKeyInfo(Sequence): |
| 804 | """ |
| 805 | Original Name: SubjectPublicKeyInfo |
| 806 | Source: https://tools.ietf.org/html/rfc5280#page-17 |
| 807 | """ |
| 808 | |
| 809 | _fields = [ |
| 810 | ('algorithm', PublicKeyAlgorithm), |
| 811 | ('public_key', OctetBitString), |
| 812 | ] |
| 813 | |
| 814 | def _public_key_spec(self): |
| 815 | algorithm = self['algorithm']['algorithm'].native |
| 816 | return { |
| 817 | 'rsa': RSAPublicKey, |
| 818 | 'dsa': Integer, |
| 819 | # ECSDA's public key is an ECPoint, which is an OctetString. Since |
| 820 | # we are using OctetBitString here, we don't need further parsing. |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 821 | 'ec': None, |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 822 | }[algorithm] |
| 823 | |
| 824 | _spec_callbacks = { |
| 825 | 'public_key': _public_key_spec |
| 826 | } |
| 827 | |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 828 | _algorithm = None |
| 829 | _bit_size = None |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 830 | _fingerprint = None |
| 831 | |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 832 | @classmethod |
| 833 | def wrap(cls, public_key, algorithm): |
| 834 | """ |
| 835 | Wraps a public key in a PublicKeyInfo structure |
| 836 | |
| 837 | :param public_key: |
| 838 | A byte string or Asn1Value object of the public key |
| 839 | |
| 840 | :param algorithm: |
| 841 | A unicode string of "rsa" |
| 842 | |
| 843 | :return: |
| 844 | A PublicKeyInfo object |
| 845 | """ |
| 846 | |
| 847 | if not isinstance(public_key, byte_cls) and not isinstance(public_key, Asn1Value): |
| 848 | raise ValueError('public_key must be a byte string or Asn1Value, not %s' % public_key.__class__.__name__) |
| 849 | |
| 850 | if algorithm != 'rsa': |
| 851 | raise ValueError('algorithm must be one of "rsa" - is %s' % repr(algorithm)) |
| 852 | |
| 853 | algo = PublicKeyAlgorithm() |
| 854 | algo['algorithm'] = PublicKeyAlgorithmId(algorithm) |
| 855 | algo['parameters'] = Null() |
| 856 | |
| 857 | container = cls() |
| 858 | container['algorithm'] = algo |
| 859 | if isinstance(public_key, Asn1Value): |
wbond | e95da2c | 2015-06-19 01:49:44 -0400 | [diff] [blame] | 860 | public_key = public_key.untag().dump() |
wbond | 8b59e05 | 2015-06-16 00:13:05 -0400 | [diff] [blame] | 861 | container['public_key'] = OctetBitString(public_key) |
| 862 | |
| 863 | return container |
| 864 | |
wbond | b924d33 | 2015-07-07 23:27:48 -0400 | [diff] [blame] | 865 | def unwrap(self): |
| 866 | """ |
| 867 | Unwraps an RSA public key into an RSAPublicKey object. Does not support |
| 868 | DSA or EC public keys since they do not have an unwrapped form. |
| 869 | |
| 870 | :return: |
| 871 | An RSAPublicKey object |
| 872 | """ |
| 873 | |
| 874 | if self.algorithm == 'rsa': |
| 875 | return self['public_key'].parsed |
| 876 | |
| 877 | key_type = self.algorithm.upper() |
| 878 | a_an = 'an' if key_type == 'EC' else 'a' |
| 879 | raise ValueError('Only RSA public keys may be unwrapped - this key is %s %s public key' % (a_an, key_type)) |
| 880 | |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 881 | @property |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 882 | def curve(self): |
| 883 | """ |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 884 | Returns information about the curve used for an EC key |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 885 | |
| 886 | :raises: |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 887 | ValueError - when the key is not an EC key |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 888 | |
| 889 | :return: |
| 890 | A two-element tuple, with the first element being a unicode string |
| 891 | of "implicit_ca", "specified" or "named". If the first element is |
| 892 | "implicit_ca", the second is None. If "specified", the second is |
| 893 | an OrderedDict that is the native version of SpecifiedECDomain. If |
| 894 | "named", the second is a unicode string of the curve name. |
| 895 | """ |
| 896 | |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 897 | if self.algorithm != 'ec': |
| 898 | raise ValueError('Only EC keys have a curve, this key is %s' % self.algorithm.upper()) |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 899 | |
| 900 | params = self['algorithm']['parameters'] |
| 901 | chosen = params.chosen |
| 902 | |
| 903 | if params.name == 'implicit_ca': |
| 904 | value = None |
| 905 | else: |
| 906 | value = chosen.native |
| 907 | |
| 908 | return (params.name, value) |
| 909 | |
| 910 | @property |
wbond | 1b00a59 | 2015-06-28 12:58:26 -0400 | [diff] [blame] | 911 | def hash_algo(self): |
| 912 | """ |
| 913 | Returns the name of the family of hash algorithms used to generate a |
| 914 | DSA key |
| 915 | |
| 916 | :raises: |
| 917 | ValueError - when the key is not a DSA key |
| 918 | |
| 919 | :return: |
| 920 | A unicode string of "sha1" or "sha2" |
| 921 | """ |
| 922 | |
| 923 | if self.algorithm != 'dsa': |
| 924 | raise ValueError('Only DSA keys are generated using a hash algorithm, this key is %s' % self.algorithm.upper()) |
| 925 | |
| 926 | byte_len = math.log(self['algorithm']['parameters']['q'].native, 2) / 8 |
| 927 | |
| 928 | return 'sha1' if byte_len <= 20 else 'sha2' |
| 929 | |
| 930 | @property |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 931 | def algorithm(self): |
| 932 | """ |
| 933 | :return: |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 934 | A unicode string of "rsa", "dsa" or "ec" |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 935 | """ |
| 936 | |
| 937 | if self._algorithm is None: |
| 938 | self._algorithm = self['algorithm']['algorithm'].native |
| 939 | return self._algorithm |
| 940 | |
| 941 | @property |
| 942 | def bit_size(self): |
| 943 | """ |
| 944 | :return: |
wbond | f4f176c | 2015-07-07 23:27:19 -0400 | [diff] [blame] | 945 | The bit size of the public key, as an integer |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 946 | """ |
| 947 | |
| 948 | if self._bit_size is None: |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 949 | if self.algorithm == 'ec': |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 950 | self._bit_size = ((len(self['public_key'].native) - 1) / 2) * 8 |
| 951 | else: |
| 952 | if self.algorithm == 'rsa': |
| 953 | prime = self['public_key'].parsed['modulus'].native |
| 954 | elif self.algorithm == 'dsa': |
wbond | d6b8b2e | 2015-07-13 09:08:23 -0400 | [diff] [blame^] | 955 | prime = self['algorithm']['parameters']['p'].native |
wbond | f4f176c | 2015-07-07 23:27:19 -0400 | [diff] [blame] | 956 | self._bit_size = int(math.ceil(math.log(prime, 2))) |
wbond | f937f48 | 2015-06-19 01:51:11 -0400 | [diff] [blame] | 957 | |
| 958 | return self._bit_size |
| 959 | |
| 960 | @property |
wbond | f4f176c | 2015-07-07 23:27:19 -0400 | [diff] [blame] | 961 | def byte_size(self): |
| 962 | """ |
| 963 | :return: |
| 964 | The byte size of the public key, as an integer |
| 965 | """ |
| 966 | |
| 967 | return int(math.ceil(self.bit_size / 8)) |
| 968 | |
| 969 | @property |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 970 | def fingerprint(self): |
| 971 | """ |
| 972 | Creates a fingerprint that can be compared with a private key to see if |
| 973 | the two form a pair. |
| 974 | |
| 975 | This fingerprint is not compatiable with fingerprints generated by any |
| 976 | other software. |
| 977 | |
| 978 | :return: |
| 979 | A byte string that is a sha256 hash of selected components (based |
| 980 | on the key type) |
| 981 | """ |
| 982 | |
| 983 | if self._fingerprint is None: |
| 984 | key_type = self['algorithm']['algorithm'].native |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 985 | params = self['algorithm']['parameters'] |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 986 | |
| 987 | if key_type == 'rsa': |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 988 | key = self['public_key'].parsed |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 989 | to_hash = '%d:%d' % ( |
| 990 | key['modulus'].native, |
| 991 | key['public_exponent'].native, |
| 992 | ) |
| 993 | |
| 994 | elif key_type == 'dsa': |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 995 | key = self['public_key'].parsed |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 996 | to_hash = '%d:%d:%d:%d' % ( |
| 997 | params['p'].native, |
| 998 | params['q'].native, |
| 999 | params['g'].native, |
| 1000 | key.native, |
| 1001 | ) |
| 1002 | |
wbond | 680cba1 | 2015-07-01 23:53:54 -0400 | [diff] [blame] | 1003 | elif key_type == 'ec': |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 1004 | key = self['public_key'] |
| 1005 | |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 1006 | if params.name == 'named': |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 1007 | to_hash = '%s:' % params.chosen.native |
| 1008 | to_hash = to_hash.encode('utf-8') |
| 1009 | to_hash += key.native |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 1010 | |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 1011 | elif params.name == 'implicit_ca': |
| 1012 | to_hash = key.native |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 1013 | |
wbond | eb02d76 | 2015-06-17 23:37:30 -0400 | [diff] [blame] | 1014 | elif params.name == 'specified': |
| 1015 | to_hash = '%s:' % params.chosen['field_id']['parameters'].native |
| 1016 | to_hash = to_hash.encode('utf-8') |
| 1017 | to_hash += b':' + params.chosen['curve']['a'].native |
| 1018 | to_hash += b':' + params.chosen['curve']['b'].native |
| 1019 | to_hash += key.native |
wbond | e91513e | 2015-06-03 14:52:18 -0400 | [diff] [blame] | 1020 | |
| 1021 | if isinstance(to_hash, str_cls): |
| 1022 | to_hash = to_hash.encode('utf-8') |
| 1023 | |
| 1024 | self._fingerprint = hashlib.sha256(to_hash).digest() |
| 1025 | |
| 1026 | return self._fingerprint |