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

116 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 .kdf import pbkdf2, pbkdf2_iteration_calculator 

23from .symmetric import aes_cbc_pkcs7_encrypt 

24from .util import rand_bytes 

25 

26 

27_backend = backend() 

28 

29 

30if _backend == 'mac': 

31 from ._mac.asymmetric import ( 

32 Certificate, 

33 dsa_sign, 

34 dsa_verify, 

35 ecdsa_sign, 

36 ecdsa_verify, 

37 generate_pair, 

38 generate_dh_parameters, 

39 load_certificate, 

40 load_pkcs12, 

41 load_private_key, 

42 load_public_key, 

43 PrivateKey, 

44 PublicKey, 

45 rsa_pkcs1v15_sign, 

46 rsa_pkcs1v15_verify, 

47 rsa_pss_sign, 

48 rsa_pss_verify, 

49 rsa_pkcs1v15_encrypt, 

50 rsa_pkcs1v15_decrypt, 

51 rsa_oaep_encrypt, 

52 rsa_oaep_decrypt, 

53 ) 

54 

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

56 from ._win.asymmetric import ( 

57 Certificate, 

58 dsa_sign, 

59 dsa_verify, 

60 ecdsa_sign, 

61 ecdsa_verify, 

62 generate_pair, 

63 generate_dh_parameters, 

64 load_certificate, 

65 load_pkcs12, 

66 load_private_key, 

67 load_public_key, 

68 PrivateKey, 

69 PublicKey, 

70 rsa_pkcs1v15_sign, 

71 rsa_pkcs1v15_verify, 

72 rsa_pss_sign, 

73 rsa_pss_verify, 

74 rsa_pkcs1v15_encrypt, 

75 rsa_pkcs1v15_decrypt, 

76 rsa_oaep_encrypt, 

77 rsa_oaep_decrypt, 

78 ) 

79 

80else: 

81 from ._openssl.asymmetric import ( 

82 Certificate, 

83 dsa_sign, 

84 dsa_verify, 

85 ecdsa_sign, 

86 ecdsa_verify, 

87 generate_pair, 

88 generate_dh_parameters, 

89 load_certificate, 

90 load_pkcs12, 

91 load_private_key, 

92 load_public_key, 

93 PrivateKey, 

94 PublicKey, 

95 rsa_pkcs1v15_sign, 

96 rsa_pkcs1v15_verify, 

97 rsa_pss_sign, 

98 rsa_pss_verify, 

99 rsa_pkcs1v15_encrypt, 

100 rsa_pkcs1v15_decrypt, 

101 rsa_oaep_encrypt, 

102 rsa_oaep_decrypt, 

103 ) 

104 

105 

106__all__ = [ 

107 'Certificate', 

108 'dsa_sign', 

109 'dsa_verify', 

110 'dump_certificate', 

111 'dump_dh_parameters', 

112 'dump_openssl_private_key', 

113 'dump_private_key', 

114 'dump_public_key', 

115 'ecdsa_sign', 

116 'ecdsa_verify', 

117 'generate_pair', 

118 'generate_dh_parameters', 

119 'load_certificate', 

120 'load_pkcs12', 

121 'load_private_key', 

122 'load_public_key', 

123 'PrivateKey', 

124 'PublicKey', 

125 'rsa_oaep_decrypt', 

126 'rsa_oaep_encrypt', 

127 'rsa_pkcs1v15_decrypt', 

128 'rsa_pkcs1v15_encrypt', 

129 'rsa_pkcs1v15_sign', 

130 'rsa_pkcs1v15_verify', 

131 'rsa_pss_sign', 

132 'rsa_pss_verify', 

133] 

134 

135 

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

137 """ 

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

139 

140 :param dh_parameters: 

141 An asn1crypto.algos.DHParameters object 

142 

143 :param encoding: 

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

145 

146 :return: 

147 A byte string of the encoded DH parameters 

148 """ 

149 

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

151 raise ValueError(pretty_message( 

152 ''' 

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

154 ''', 

155 repr(encoding) 

156 )) 

157 

158 if not isinstance(dh_parameters, DHParameters): 

159 raise TypeError(pretty_message( 

160 ''' 

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

162 not %s 

163 ''', 

164 type_name(dh_parameters) 

165 )) 

166 

167 output = dh_parameters.dump() 

168 if encoding == 'pem': 

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

170 return output 

171 

172 

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

174 """ 

175 Serializes a public key object into a byte string 

176 

177 :param public_key: 

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

179 

180 :param encoding: 

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

182 

183 :return: 

184 A byte string of the encoded public key 

185 """ 

186 

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

188 raise ValueError(pretty_message( 

189 ''' 

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

191 ''', 

192 repr(encoding) 

193 )) 

194 

195 is_oscrypto = isinstance(public_key, PublicKey) 

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

197 raise TypeError(pretty_message( 

198 ''' 

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

200 asn1crypto.keys.PublicKeyInfo, not %s 

201 ''', 

202 type_name(public_key) 

203 )) 

204 

205 if is_oscrypto: 

206 public_key = public_key.asn1 

207 

208 output = public_key.dump() 

209 if encoding == 'pem': 

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

211 return output 

212 

213 

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

215 """ 

216 Serializes a certificate object into a byte string 

217 

218 :param certificate: 

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

220 

221 :param encoding: 

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

223 

224 :return: 

225 A byte string of the encoded certificate 

226 """ 

227 

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

229 raise ValueError(pretty_message( 

230 ''' 

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

232 ''', 

233 repr(encoding) 

234 )) 

235 

236 is_oscrypto = isinstance(certificate, Certificate) 

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

238 raise TypeError(pretty_message( 

239 ''' 

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

241 or asn1crypto.x509.Certificate, not %s 

242 ''', 

243 type_name(certificate) 

244 )) 

245 

246 if is_oscrypto: 

247 certificate = certificate.asn1 

248 

249 output = certificate.dump() 

250 if encoding == 'pem': 

251 output = armor('CERTIFICATE', output) 

252 return output 

253 

254 

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

256 """ 

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

258 

259 :param private_key: 

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

261 object 

262 

263 :param passphrase: 

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

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

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

267 intentional. 

268 

269 :param encoding: 

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

271 

272 :param target_ms: 

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

274 milliseconds on the current machine. 

275 

276 :raises: 

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

278 

279 :return: 

280 A byte string of the encoded and encrypted public key 

281 """ 

282 

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

284 raise ValueError(pretty_message( 

285 ''' 

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

287 ''', 

288 repr(encoding) 

289 )) 

290 

291 if passphrase is not None: 

292 if not isinstance(passphrase, str_cls): 

293 raise TypeError(pretty_message( 

294 ''' 

295 passphrase must be a unicode string, not %s 

296 ''', 

297 type_name(passphrase) 

298 )) 

299 if passphrase == '': 

300 raise ValueError(pretty_message( 

301 ''' 

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

303 encryption 

304 ''' 

305 )) 

306 

307 is_oscrypto = isinstance(private_key, PrivateKey) 

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

309 raise TypeError(pretty_message( 

310 ''' 

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

312 or asn1crypto.keys.PrivateKeyInfo, not %s 

313 ''', 

314 type_name(private_key) 

315 )) 

316 

317 if is_oscrypto: 

318 private_key = private_key.asn1 

319 

320 output = private_key.dump() 

321 

322 if passphrase is not None: 

323 cipher = 'aes256_cbc' 

324 key_length = 32 

325 kdf_hmac = 'sha256' 

326 kdf_salt = rand_bytes(key_length) 

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

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

329 if iterations < 10000: 

330 iterations = 10000 

331 

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

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

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

335 

336 output = EncryptedPrivateKeyInfo({ 

337 'encryption_algorithm': { 

338 'algorithm': 'pbes2', 

339 'parameters': { 

340 'key_derivation_func': { 

341 'algorithm': 'pbkdf2', 

342 'parameters': { 

343 'salt': Pbkdf2Salt( 

344 name='specified', 

345 value=kdf_salt 

346 ), 

347 'iteration_count': iterations, 

348 'prf': { 

349 'algorithm': kdf_hmac, 

350 'parameters': Null() 

351 } 

352 } 

353 }, 

354 'encryption_scheme': { 

355 'algorithm': cipher, 

356 'parameters': iv 

357 } 

358 } 

359 }, 

360 'encrypted_data': ciphertext 

361 }).dump() 

362 

363 if encoding == 'pem': 

364 if passphrase is None: 

365 object_type = 'PRIVATE KEY' 

366 else: 

367 object_type = 'ENCRYPTED PRIVATE KEY' 

368 output = armor(object_type, output) 

369 

370 return output 

371 

372 

373def dump_openssl_private_key(private_key, passphrase): 

374 """ 

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

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

377 DSA or EC. 

378 

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

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

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

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

383 brute-force. 

384 

385 :param private_key: 

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

387 object 

388 

389 :param passphrase: 

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

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

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

393 intentional. 

394 

395 :raises: 

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

397 

398 :return: 

399 A byte string of the encoded and encrypted public key 

400 """ 

401 

402 if passphrase is not None: 

403 if not isinstance(passphrase, str_cls): 

404 raise TypeError(pretty_message( 

405 ''' 

406 passphrase must be a unicode string, not %s 

407 ''', 

408 type_name(passphrase) 

409 )) 

410 if passphrase == '': 

411 raise ValueError(pretty_message( 

412 ''' 

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

414 encryption 

415 ''' 

416 )) 

417 

418 is_oscrypto = isinstance(private_key, PrivateKey) 

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

420 raise TypeError(pretty_message( 

421 ''' 

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

423 asn1crypto.keys.PrivateKeyInfo, not %s 

424 ''', 

425 type_name(private_key) 

426 )) 

427 

428 if is_oscrypto: 

429 private_key = private_key.asn1 

430 

431 output = _unwrap_private_key_info(private_key).dump() 

432 

433 headers = None 

434 if passphrase is not None: 

435 iv = rand_bytes(16) 

436 

437 headers = OrderedDict() 

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

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

440 

441 key_length = 16 

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

443 

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

445 while key_length > len(key): 

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

447 key = key[0:key_length] 

448 

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

450 

451 if private_key.algorithm == 'ec': 

452 object_type = 'EC PRIVATE KEY' 

453 elif private_key.algorithm == 'rsa': 

454 object_type = 'RSA PRIVATE KEY' 

455 elif private_key.algorithm == 'dsa': 

456 object_type = 'DSA PRIVATE KEY' 

457 

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