Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/oscrypto/asymmetric.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

126 statements  

1# coding: utf-8 

2from __future__ import unicode_literals, division, absolute_import, print_function 

3 

4import hashlib 

5import binascii 

6 

7from . import backend 

8from ._asn1 import ( 

9 armor, 

10 Certificate as Asn1Certificate, 

11 DHParameters, 

12 EncryptedPrivateKeyInfo, 

13 Null, 

14 OrderedDict, 

15 Pbkdf2Salt, 

16 PrivateKeyInfo, 

17 PublicKeyInfo, 

18) 

19from ._asymmetric import _unwrap_private_key_info 

20from ._errors import pretty_message 

21from ._types import type_name, str_cls 

22from .errors import SignatureError 

23from .kdf import pbkdf2, pbkdf2_iteration_calculator 

24from .symmetric import aes_cbc_pkcs7_encrypt 

25from .util import rand_bytes 

26 

27 

28_backend = backend() 

29 

30 

31if _backend == 'mac': 

32 from ._mac.asymmetric import ( 

33 Certificate, 

34 dsa_sign, 

35 dsa_verify, 

36 ecdsa_sign, 

37 ecdsa_verify, 

38 generate_pair, 

39 generate_dh_parameters, 

40 load_certificate, 

41 load_pkcs12, 

42 load_private_key, 

43 load_public_key, 

44 PrivateKey, 

45 PublicKey, 

46 rsa_pkcs1v15_sign, 

47 rsa_pkcs1v15_verify, 

48 rsa_pss_sign, 

49 rsa_pss_verify, 

50 rsa_pkcs1v15_encrypt, 

51 rsa_pkcs1v15_decrypt, 

52 rsa_oaep_encrypt, 

53 rsa_oaep_decrypt, 

54 ) 

55 

56elif _backend == 'win' or _backend == 'winlegacy': 

57 from ._win.asymmetric import ( 

58 Certificate, 

59 dsa_sign, 

60 dsa_verify, 

61 ecdsa_sign, 

62 ecdsa_verify, 

63 generate_pair, 

64 generate_dh_parameters, 

65 load_certificate, 

66 load_pkcs12, 

67 load_private_key, 

68 load_public_key, 

69 PrivateKey, 

70 PublicKey, 

71 rsa_pkcs1v15_sign, 

72 rsa_pkcs1v15_verify, 

73 rsa_pss_sign, 

74 rsa_pss_verify, 

75 rsa_pkcs1v15_encrypt, 

76 rsa_pkcs1v15_decrypt, 

77 rsa_oaep_encrypt, 

78 rsa_oaep_decrypt, 

79 ) 

80 

81else: 

82 from ._openssl.asymmetric import ( 

83 Certificate, 

84 dsa_sign, 

85 dsa_verify, 

86 ecdsa_sign, 

87 ecdsa_verify, 

88 generate_pair, 

89 generate_dh_parameters, 

90 load_certificate, 

91 load_pkcs12, 

92 load_private_key, 

93 load_public_key, 

94 PrivateKey, 

95 PublicKey, 

96 rsa_pkcs1v15_sign, 

97 rsa_pkcs1v15_verify, 

98 rsa_pss_sign, 

99 rsa_pss_verify, 

100 rsa_pkcs1v15_encrypt, 

101 rsa_pkcs1v15_decrypt, 

102 rsa_oaep_encrypt, 

103 rsa_oaep_decrypt, 

104 ) 

105 

106 

107__all__ = [ 

108 'Certificate', 

109 'dsa_sign', 

110 'dsa_verify', 

111 'dump_certificate', 

112 'dump_dh_parameters', 

113 'dump_openssl_private_key', 

114 'dump_private_key', 

115 'dump_public_key', 

116 'ecdsa_sign', 

117 'ecdsa_verify', 

118 'generate_pair', 

119 'generate_dh_parameters', 

120 'load_certificate', 

121 'load_pkcs12', 

122 'load_private_key', 

123 'load_public_key', 

124 'PrivateKey', 

125 'PublicKey', 

126 'rsa_oaep_decrypt', 

127 'rsa_oaep_encrypt', 

128 'rsa_pkcs1v15_decrypt', 

129 'rsa_pkcs1v15_encrypt', 

130 'rsa_pkcs1v15_sign', 

131 'rsa_pkcs1v15_verify', 

132 'rsa_pss_sign', 

133 'rsa_pss_verify', 

134 'supports_sha1_signatures', 

135] 

136 

137 

138def supports_sha1_signatures(): 

139 """ 

140 Checks if the backend supports creating SHA1 signatures. 

141 

142 :return: 

143 A boolean 

144 """ 

145 

146 if not hasattr(supports_sha1_signatures, 'result'): 

147 _, private_key = generate_pair('rsa', bit_size=2048) 

148 try: 

149 rsa_pkcs1v15_sign(private_key, b'test', 'sha1') 

150 supports_sha1_signatures.result = True 

151 except SignatureError: 

152 supports_sha1_signatures.result = False 

153 

154 return supports_sha1_signatures.result 

155 

156 

157def dump_dh_parameters(dh_parameters, encoding='pem'): 

158 """ 

159 Serializes an asn1crypto.algos.DHParameters object into a byte string 

160 

161 :param dh_parameters: 

162 An asn1crypto.algos.DHParameters object 

163 

164 :param encoding: 

165 A unicode string of "pem" or "der" 

166 

167 :return: 

168 A byte string of the encoded DH parameters 

169 """ 

170 

171 if encoding not in set(['pem', 'der']): 

172 raise ValueError(pretty_message( 

173 ''' 

174 encoding must be one of "pem", "der", not %s 

175 ''', 

176 repr(encoding) 

177 )) 

178 

179 if not isinstance(dh_parameters, DHParameters): 

180 raise TypeError(pretty_message( 

181 ''' 

182 dh_parameters must be an instance of asn1crypto.algos.DHParameters, 

183 not %s 

184 ''', 

185 type_name(dh_parameters) 

186 )) 

187 

188 output = dh_parameters.dump() 

189 if encoding == 'pem': 

190 output = armor('DH PARAMETERS', output) 

191 return output 

192 

193 

194def dump_public_key(public_key, encoding='pem'): 

195 """ 

196 Serializes a public key object into a byte string 

197 

198 :param public_key: 

199 An oscrypto.asymmetric.PublicKey or asn1crypto.keys.PublicKeyInfo object 

200 

201 :param encoding: 

202 A unicode string of "pem" or "der" 

203 

204 :return: 

205 A byte string of the encoded public key 

206 """ 

207 

208 if encoding not in set(['pem', 'der']): 

209 raise ValueError(pretty_message( 

210 ''' 

211 encoding must be one of "pem", "der", not %s 

212 ''', 

213 repr(encoding) 

214 )) 

215 

216 is_oscrypto = isinstance(public_key, PublicKey) 

217 if not isinstance(public_key, PublicKeyInfo) and not is_oscrypto: 

218 raise TypeError(pretty_message( 

219 ''' 

220 public_key must be an instance of oscrypto.asymmetric.PublicKey or 

221 asn1crypto.keys.PublicKeyInfo, not %s 

222 ''', 

223 type_name(public_key) 

224 )) 

225 

226 if is_oscrypto: 

227 public_key = public_key.asn1 

228 

229 output = public_key.dump() 

230 if encoding == 'pem': 

231 output = armor('PUBLIC KEY', output) 

232 return output 

233 

234 

235def dump_certificate(certificate, encoding='pem'): 

236 """ 

237 Serializes a certificate object into a byte string 

238 

239 :param certificate: 

240 An oscrypto.asymmetric.Certificate or asn1crypto.x509.Certificate object 

241 

242 :param encoding: 

243 A unicode string of "pem" or "der" 

244 

245 :return: 

246 A byte string of the encoded certificate 

247 """ 

248 

249 if encoding not in set(['pem', 'der']): 

250 raise ValueError(pretty_message( 

251 ''' 

252 encoding must be one of "pem", "der", not %s 

253 ''', 

254 repr(encoding) 

255 )) 

256 

257 is_oscrypto = isinstance(certificate, Certificate) 

258 if not isinstance(certificate, Asn1Certificate) and not is_oscrypto: 

259 raise TypeError(pretty_message( 

260 ''' 

261 certificate must be an instance of oscrypto.asymmetric.Certificate 

262 or asn1crypto.x509.Certificate, not %s 

263 ''', 

264 type_name(certificate) 

265 )) 

266 

267 if is_oscrypto: 

268 certificate = certificate.asn1 

269 

270 output = certificate.dump() 

271 if encoding == 'pem': 

272 output = armor('CERTIFICATE', output) 

273 return output 

274 

275 

276def dump_private_key(private_key, passphrase, encoding='pem', target_ms=200): 

277 """ 

278 Serializes a private key object into a byte string of the PKCS#8 format 

279 

280 :param private_key: 

281 An oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo 

282 object 

283 

284 :param passphrase: 

285 A unicode string of the passphrase to encrypt the private key with. 

286 A passphrase of None will result in no encryption. A blank string will 

287 result in a ValueError to help ensure that the lack of passphrase is 

288 intentional. 

289 

290 :param encoding: 

291 A unicode string of "pem" or "der" 

292 

293 :param target_ms: 

294 Use PBKDF2 with the number of iterations that takes about this many 

295 milliseconds on the current machine. 

296 

297 :raises: 

298 ValueError - when a blank string is provided for the passphrase 

299 

300 :return: 

301 A byte string of the encoded and encrypted public key 

302 """ 

303 

304 if encoding not in set(['pem', 'der']): 

305 raise ValueError(pretty_message( 

306 ''' 

307 encoding must be one of "pem", "der", not %s 

308 ''', 

309 repr(encoding) 

310 )) 

311 

312 if passphrase is not None: 

313 if not isinstance(passphrase, str_cls): 

314 raise TypeError(pretty_message( 

315 ''' 

316 passphrase must be a unicode string, not %s 

317 ''', 

318 type_name(passphrase) 

319 )) 

320 if passphrase == '': 

321 raise ValueError(pretty_message( 

322 ''' 

323 passphrase may not be a blank string - pass None to disable 

324 encryption 

325 ''' 

326 )) 

327 

328 is_oscrypto = isinstance(private_key, PrivateKey) 

329 if not isinstance(private_key, PrivateKeyInfo) and not is_oscrypto: 

330 raise TypeError(pretty_message( 

331 ''' 

332 private_key must be an instance of oscrypto.asymmetric.PrivateKey 

333 or asn1crypto.keys.PrivateKeyInfo, not %s 

334 ''', 

335 type_name(private_key) 

336 )) 

337 

338 if is_oscrypto: 

339 private_key = private_key.asn1 

340 

341 output = private_key.dump() 

342 

343 if passphrase is not None: 

344 cipher = 'aes256_cbc' 

345 key_length = 32 

346 kdf_hmac = 'sha256' 

347 kdf_salt = rand_bytes(key_length) 

348 iterations = pbkdf2_iteration_calculator(kdf_hmac, key_length, target_ms=target_ms, quiet=True) 

349 # Need a bare minimum of 10,000 iterations for PBKDF2 as of 2015 

350 if iterations < 10000: 

351 iterations = 10000 

352 

353 passphrase_bytes = passphrase.encode('utf-8') 

354 key = pbkdf2(kdf_hmac, passphrase_bytes, kdf_salt, iterations, key_length) 

355 iv, ciphertext = aes_cbc_pkcs7_encrypt(key, output, None) 

356 

357 output = EncryptedPrivateKeyInfo({ 

358 'encryption_algorithm': { 

359 'algorithm': 'pbes2', 

360 'parameters': { 

361 'key_derivation_func': { 

362 'algorithm': 'pbkdf2', 

363 'parameters': { 

364 'salt': Pbkdf2Salt( 

365 name='specified', 

366 value=kdf_salt 

367 ), 

368 'iteration_count': iterations, 

369 'prf': { 

370 'algorithm': kdf_hmac, 

371 'parameters': Null() 

372 } 

373 } 

374 }, 

375 'encryption_scheme': { 

376 'algorithm': cipher, 

377 'parameters': iv 

378 } 

379 } 

380 }, 

381 'encrypted_data': ciphertext 

382 }).dump() 

383 

384 if encoding == 'pem': 

385 if passphrase is None: 

386 object_type = 'PRIVATE KEY' 

387 else: 

388 object_type = 'ENCRYPTED PRIVATE KEY' 

389 output = armor(object_type, output) 

390 

391 return output 

392 

393 

394def dump_openssl_private_key(private_key, passphrase): 

395 """ 

396 Serializes a private key object into a byte string of the PEM formats used 

397 by OpenSSL. The format chosen will depend on the type of private key - RSA, 

398 DSA or EC. 

399 

400 Do not use this method unless you really must interact with a system that 

401 does not support PKCS#8 private keys. The encryption provided by PKCS#8 is 

402 far superior to the OpenSSL formats. This is due to the fact that the 

403 OpenSSL formats don't stretch the passphrase, making it very easy to 

404 brute-force. 

405 

406 :param private_key: 

407 An oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo 

408 object 

409 

410 :param passphrase: 

411 A unicode string of the passphrase to encrypt the private key with. 

412 A passphrase of None will result in no encryption. A blank string will 

413 result in a ValueError to help ensure that the lack of passphrase is 

414 intentional. 

415 

416 :raises: 

417 ValueError - when a blank string is provided for the passphrase 

418 

419 :return: 

420 A byte string of the encoded and encrypted public key 

421 """ 

422 

423 if passphrase is not None: 

424 if not isinstance(passphrase, str_cls): 

425 raise TypeError(pretty_message( 

426 ''' 

427 passphrase must be a unicode string, not %s 

428 ''', 

429 type_name(passphrase) 

430 )) 

431 if passphrase == '': 

432 raise ValueError(pretty_message( 

433 ''' 

434 passphrase may not be a blank string - pass None to disable 

435 encryption 

436 ''' 

437 )) 

438 

439 is_oscrypto = isinstance(private_key, PrivateKey) 

440 if not isinstance(private_key, PrivateKeyInfo) and not is_oscrypto: 

441 raise TypeError(pretty_message( 

442 ''' 

443 private_key must be an instance of oscrypto.asymmetric.PrivateKey or 

444 asn1crypto.keys.PrivateKeyInfo, not %s 

445 ''', 

446 type_name(private_key) 

447 )) 

448 

449 if is_oscrypto: 

450 private_key = private_key.asn1 

451 

452 output = _unwrap_private_key_info(private_key).dump() 

453 

454 headers = None 

455 if passphrase is not None: 

456 iv = rand_bytes(16) 

457 

458 headers = OrderedDict() 

459 headers['Proc-Type'] = '4,ENCRYPTED' 

460 headers['DEK-Info'] = 'AES-128-CBC,%s' % binascii.hexlify(iv).decode('ascii') 

461 

462 key_length = 16 

463 passphrase_bytes = passphrase.encode('utf-8') 

464 

465 key = hashlib.md5(passphrase_bytes + iv[0:8]).digest() 

466 while key_length > len(key): 

467 key += hashlib.md5(key + passphrase_bytes + iv[0:8]).digest() 

468 key = key[0:key_length] 

469 

470 iv, output = aes_cbc_pkcs7_encrypt(key, output, iv) 

471 

472 if private_key.algorithm == 'ec': 

473 object_type = 'EC PRIVATE KEY' 

474 elif private_key.algorithm == 'rsa': 

475 object_type = 'RSA PRIVATE KEY' 

476 elif private_key.algorithm == 'dsa': 

477 object_type = 'DSA PRIVATE KEY' 

478 

479 return armor(object_type, output, headers=headers)