Skip to content

Commit

Permalink
Merge pull request #169 from olcf/ngin_rsa_unittests
Browse files Browse the repository at this point in the history
Ngin rsa unittests
  • Loading branch information
Noah Ginsburg authored May 4, 2020
2 parents 32d4994 + 93763bb commit 1181d79
Show file tree
Hide file tree
Showing 16 changed files with 414 additions and 71 deletions.
14 changes: 9 additions & 5 deletions libpkpass/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ def pk_encrypt_string(plaintext_string, certificate):
##############################################################################

plaintext_derived_key = Fernet.generate_key()

with tempfile.NamedTemporaryFile(delete=False) as fname:
fname.write(certificate)
command = ['openssl', 'rsautl', '-inkey', fname.name, '-certin', '-encrypt', '-pkcs']
if isinstance(certificate, bytes):
with tempfile.NamedTemporaryFile(delete=False) as fname:
fname.write(certificate)
cert_file_path = fname.name
else:
cert_file_path = certificate['certificate_path']
command = ['openssl', 'rsautl', '-inkey', cert_file_path, '-certin', '-encrypt', '-pkcs']
proc = Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT)
stdout, _ = proc.communicate(input=plaintext_derived_key)
os.unlink(fname.name)
if isinstance(certificate, bytes):
os.unlink(fname.name)

if proc.returncode != 0:
raise EncryptionError("Error encrypting derived key: %s" % stdout)
Expand Down
44 changes: 29 additions & 15 deletions libpkpass/password.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,12 @@ def _add_recipient(
encrypted_secrets = {}
for cert in identitydb.iddb[recipient]['certs']:
if encryption_algorithm == 'rsautl':
(encrypted_secret, encrypted_derived_key) = crypto.pk_encrypt_string(
secret, cert['cert_bytes'])
if 'key_path' in identitydb.iddb[recipient].keys():
(encrypted_secret, encrypted_derived_key) = crypto.pk_encrypt_string(
secret, identitydb.iddb[recipient])
else:
(encrypted_secret, encrypted_derived_key) = crypto.pk_encrypt_string(
secret, cert['cert_bytes'])
encrypted_secrets[cert['fingerprint']] = {
'encrypted_secret': encrypted_secret,
'derived_key': encrypted_derived_key,
Expand Down Expand Up @@ -207,19 +211,29 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None):
card_slot)
except KeyError:
try:
command = ['pkcs15-tool', '--read-certificate', '1']
proc = Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT)
stdout, _ = proc.communicate()
command = ['openssl', 'x509', '-noout', "-fingerprint"]
proc = Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT)
stdout, _ = proc.communicate(input=stdout)
cert_key = stdout.decode("ASCII").rstrip().split('=')[1]
return crypto.pk_decrypt_string(
recipient_entry['encrypted_secrets'][cert_key]['encrypted_secret'],
recipient_entry['encrypted_secrets'][cert_key]['derived_key'],
identity,
passphrase,
card_slot)
# support rsa
if 'key_path' in identity.keys():
for _, value in recipient_entry['encrypted_secrets'].items():
return crypto.pk_decrypt_string(
value['encrypted_secret'],
value['derived_key'],
identity,
passphrase
)
else:
command = ['pkcs15-tool', '--read-certificate', '1']
proc = Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT)
stdout, _ = proc.communicate()
command = ['openssl', 'x509', '-noout', "-fingerprint"]
proc = Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT)
stdout, _ = proc.communicate(input=stdout)
cert_key = stdout.decode("ASCII").rstrip().split('=')[1]
return crypto.pk_decrypt_string(
recipient_entry['encrypted_secrets'][cert_key]['encrypted_secret'],
recipient_entry['encrypted_secrets'][cert_key]['derived_key'],
identity,
passphrase,
card_slot)
except DecryptionError:
raise DecryptionError(
"Error decrypting password named '%s'. Perhaps a bad pin/passphrase?" %
Expand Down
12 changes: 6 additions & 6 deletions pkpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
# This is so that users don't see tracebacks, an error will still print out
# so that we can investigate
# Comment this out for debugging
except Exception as err: # pylint: disable=broad-except
if str(err):
print(err)
else:
print("Generic exception caught: \n%s" %
traceback.format_exception_only(type(err), err)[0])
# except Exception as err: # pylint: disable=broad-except
# if str(err):
# print(err)
# else:
# print("Generic exception caught: \n%s" %
# traceback.format_exception_only(type(err), err)[0])
3 changes: 3 additions & 0 deletions test/.test_config
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ certpath: test/pki/intermediate/certs
keypath: test/pki/intermediate/private
cabundle: test/pki/intermediate/certs/ca-bundle
pwstore: test/passwords

rules_map:
default: "[^\\s]{20}"
45 changes: 45 additions & 0 deletions test/passwords/gentest
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
metadata:
authorizer: y
creator: r3
description: y
name: gentest
schemaVersion: v1
signature: null
recipients:
r3:
distributor: r3
distributor_hash: 374672d9
encrypted_secrets:
A4:29:BE:AC:3A:21:E2:BF:92:DF:34:91:E5:52:C3:B3:F1:72:56:B8:
derived_key: !!binary |
RXhDN0FJTWwyTHFxMWZLLUZxWnhKR185LVdzNmZEcUZqWUZ2cGZEV0tlVVRZbW00S0VDWnp6cmRw
WDJmQ3dBWGp2dGtheDhNcXFRSS1va0tOaWx5MDJ6NXR4OExqZGtvUTBtY3JqN3BjMUNhVnA5bjgw
R2xVRkFfVVhpak9lNk5qSFpqWTczbHNYempuT0Vjc3YtQmltQ0U2WFVaRUU2T0RldGNiUExZcWV1
UzNwdGc2Q21yYTU0bzJrblJkYnhITGJEYzIwNFJhQ1NNZjBtVjRwTVlpMlJqWUlKRXJINlVUWUNm
VHY5VF9RbzRsQWYzSmVaaEdkNnFTY3VDVXdXcnZXNlNRdU0yS0hxTElneHRSVVc0UC0yeHAxbzgx
ck0wVUc2WDg0RlhRdnR0REZrTGN6Rk0tdUg4Z3pJMC1TX3pCcmpBaFdzT001Mm5veUtDSEtzNVVS
NjJ0cHo3eVNlSkVkTEMzNEdMZ0l0bHJaMjBzSVpmVktnN2MtZ210WVlSQWVkOUFTLS10bG1wbldy
dFp5eEZURkkzNW5mSkN5TzFIQ1hfQVdLSXBFUFRNNF9vbkdMZE9pRHktRG1YaXlYNm83bS1lM25Z
WFhZT0o5OUlrZFc5eVhPZWRjcXVuTEVZUVNydHkyQ04zd1FfRkkxNkhpb1htWmVSa29USkJ1WUpf
YUZhSTJ1d3lHT3VYNC00Sy0zR1VSdGlfR280RjJsM3lKYXdvd3cyaFF1V3hvbzVHRzZJWEtiUlR0
MHl4eTRkQkVOSjV2QmxXZ0tXTWgzdXc5Ul9Mak80ZjJ4NjlKdDNDTXNRS3JFR2V6X040d29VZW9l
dVQ0OGd2T0pHVUd0dU5qcG1TaHZodGJrM05Kb18wR05zVF81bTBxUjJRTjBUenR6cElVZEVmQjg9
encrypted_secret: !!binary |
Z0FBQUFBQmVzRVJVUERrZ0szVm9zMGlpT3hNcG5xVF9qYlFURndvQTFCckROczRLazVSXzNFcjgz
MGN1dzhvUUc3UWRSQXpJYnU2NzBiVEJvdWNMNFVhVXNZTElPemc5aGc9PQ==
recipient_hash: b779ae6e
encryption_algorithm: rsautl
signature: !!binary |
WV9ydXltTzVtdVRjSG05VEN6UHdfWTZUai1ETXdidWxJQ0pnTzViQ3RqX0t5eXBSdER6U05LNmdL
Q0g1Y0hmazREQ2ZwMWYtQWJ4YTdxc0ROOUJWQk1oVW94MEZOZTNNc1pnYlJRbmtZWTBFcVhQdkIy
YVhTWU8zOE9HbW5YSlNXcHNFNU50dmxxSXN4VnQxbXhmd0xmR1BWMUNVMWFPYXh6R0cwRHZzcW0z
NWFaM0dzdU5hVENYaVhnVnltN3p6SF9UbEVWWmFybzcwYjU5N3d4LUFBMzA4ckFuRG5ja245T3lI
YWczRFk4MGpRZ2labDJZT1BtN21TV2hENnhJWjJoVGR6NjhmVWhRYjdEamNwdUowZ3c4by1Ga09i
cWFhcUt3UVFobEJzNkQ1U1gzTEFHd0dVX3JUaWFjd3N3NzBKMmhQNjdqdGhzVmdTRTJzS1JOODNK
Ym5PNjBNX09WZUduSnEtZlc0V002clJMNkFHS3dvXzZEMS0zOG1MZnVnY0tUR3plZUgzTllfWGVx
cjdhR21KWVlUT2VpYkZtcm9YNWUycUFqVDk3V2JJa3RBdDk2MW9xUTZRUGUzZWI3QUg0a2VTMkxz
akc0LUVnZXVsVjlOa0xIYVVXd0pidkNzQlVmUFlxd056MzN2MUVMR2VfcnExVTl3RTAyQ0NodXpB
ZGRtNU8zQXQ1a1liYTZwOUJiS2F3b0NLZTBvbXVQZGMyVEJETEh0YzFwLW9DME1yeGs2NVVMQ21X
QUZIbjRSc3VZZWxwaUhWU2w2bW9WbHhFVlRDY2xOakdfTmVWOUVhNzVaVnpGbVNnaFRVVWc5V3h2
Q3RveE0yZkppcW5DRElhVm1LN3JqQWJOcTVEZUNpOG5wdXNuNmhuMTdKbTZoM09xZUNKdWl6Uk09
timestamp: '1588610132.81'
46 changes: 37 additions & 9 deletions test/passwords/test
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
metadata:
authorizer: ginsburgnm
creator: ginsburgnm
description: fake
authorizer: y
creator: r1
description: y
name: test
schemaVersion: v1
signature: null
recipients:
r1:
derived_key: E5-IRUIY1QIkOAGMB1ZIHk3_jnnOgVk38KSrhNgyT_lUju-d50waQ70qL44ZjVyEj6zgllBCevsFJpZWFHjqhJQMI73gmwVD-qxuvj5RwkHgf1HrEpArmG3r_KJX3aCwqWKa6AVYWva-xaU48YHJpgbDz_isGV7_cqUWoyeKabddNqVw43dRjK-Ts5THyH5SoMJNfibKO3hOKF5B6fc83K2rydAYIIB5gWj3KQTQQI96xydpQ20Frq2R323kP27oghs3RovNkfPLK2YHYY6srC1Q8uy2QTH3k7gmLsYMt_xVbYNaOnqIyuyur4DOPodg5xA9P2cZoug197uNnYURIA==
distributor: ginsburgnm
distributor: r1
distributor_hash: 374672d9
encrypted_secret: gAAAAABbaZvfWLaMT6VME57Kg8HwN1fQ4XCS7tMHuhXuJv4EQDIqqVVmaImsv-gxmrU8GqAVcGc_luJTK1X1MDKbjaPlj9Ej2Q==
encrypted_secrets:
AD:21:5F:4A:F8:8F:69:63:2A:43:34:CD:99:58:27:A1:02:C9:5D:FF:
derived_key: !!binary |
RXB0NEdMZUlEdkxSUVFDaFBtTDZXdmVZazJuUmdMR1FXZmRFRHRnWFlsWnBRWHRUTVZZc3FJcDNm
U2RVcGMxTjZCVDFfNUJvWEZiVXM5aDZfR2VOMHlqdkN0czIzLXpQTG5jMmwtak9JdlNFSDJxS0Rf
WGFHdVRtTG9tTWEyd2tDMWxUMlk5LWtPMV9xREJYT2V3R0h3alJmOFRuOEZ4UDRvaHFoQ3pkMVdJ
Z3VqNl9lZHhMaGUtOWdYM0Ffd3YyazNGV05iVGFZM1NzNnZYcjhFZUlRUGZoRmRYb29GVFBaWnRZ
VHhTS202M0FRamxpQWhRZ3BwUklFUl95ek9HQVViTkRya1JwSVVBQzZ4bUh4cXQzbTVqaklELTY0
TlBzcHFETmRoNFFzWU5ZLW9pNGZMSHhoRkdtcWpoRkVsSGZhTUljWjVqZnAxMlNXYVpWT09nZmdh
TmVwVVJ5UmVjeG9nMWJUOVpJbXU2Y1lrT21hUkJja09aMkpPc0t5cklSRE1OcDgyLTdOaEVFMnhu
RXZtS0NsbTdRbGpiZmVUSEhYcmxBb3JpNTVPVjkxTUtOanZZOGtMVUI3azRzWHo2SEloV2ZhUnlu
WDlWVGlEOUNvbi1SMXdmN00waXVkbUtFaWJrdlIza0FneTY4MG9xT3FrZ2JtNmhYcGZtTFQ2TVRZ
LUtkRXJiWkc0c0RJR2doUEtHY2tqSFJmQ1RMbmQzRlNWUUtuN0VsQXc2UDh4by1LaWxMTjRjNl9E
QktSX3BlRGhXaEZlUnhXblhWMXlDYTRPZzR2UDJDcnNrd2xmQUR1SnFMWUZXaG83SmhobjFHMkla
VU92M0VnSnBZbTMtU2I4N1EtaTR2ejkycVVPcU54WkRfblRrSjZBS2dtUnYzMW56U1RMeDJBOHc9
encrypted_secret: !!binary |
Z0FBQUFBQmVzRVJTak1fblF0MFBYeHBJTzJBM25tc2JhakRJOFRjNGFjRXl1TXlLc1JYbklzZ2Jk
U1BZTE9mX2dlNkFja003Y1llaVZHYnBpOTh1OTJ2cS1RV0RWRkY0Rmc9PQ==
recipient_hash: 63b96030
encryption_algorithm: rsautl
recipient_hash: 374672d9
signature: Tx0Q2zMVEygmulpnKYnZSAh3oZCmBYggd_RSCg8v0sbxcyvf6468ajDiX8tY87j4SPqsbLI1orD9QWOUp2Ve1s_i4FQ5TZRXGbsStobc6PzJ9GVEgCjKf2AJuiAbBN66ike2zZM8MBwH6LYNmW2fImno6SPGLD_t1_GApwR-yM7AZwIcCx0IuWqcYLpHN6JLSUVVyZ0n2A4OAkNEqI6OKESTi69KXEHM7pTrtHHO8rRs4uhKPSPE6Z4yXtBvteH_2xP-CCmpycpK_G_nBPrC3j5By9SGAHchj3CnK_mUZmDgq9fZf3i9ijlRa-BYdXaFh2n9XkD_zPtO-CZxqzNbag==
timestamp: 1533647839.652613
signature: !!binary |
YUpjZXJzdWNQZ2k3NnBFcU40VHlFT1FVbTRwLXBwOF9rWlJndWJ5UDJBSlFzempwanctRHA4dlg0
eDVTUm9ub1VWYmw2ZzlxanU4QnV1OTVrYjZsNW9KM291SHZremVRT3R3TVdEc3lQd3BMYmUzQVZk
M08wQ1hwX3h4cDdZN1YzelBFMzVUcTFqWF9UeklLcDliNWRFbmZpTUFCLUotcUE4NVdwWjZISmVr
VlRscEFYUXpLclBZSzJFYURGUE1vMmdDTHhwY0x2enJhbHJpQUhFOWJfQnFpVTROMjNIT1MxOFpt
N0J5azh0TXJCZTJfeFozZ2kzTEh2aU5kTFB1dGcxbHI4T0Itcy1ROXZmamdjc0t4dU1ZQm1rZmxz
WkwyU3hDUVFBRkFXRmx3LS1mcXlNN2tiWkdvWVRza1NVcGtuYjRpeUc3N3p5aWtsZVg5N2hZVDlk
dGdUME1HanFsS1RIVElmRm8tal9hUjlnTFNhNDEweno2TWZtRFp5NEVWdWp0ZnhhVHpYUUVPQUhT
MDlBN2p6YnVuSC1lVlJRYW9zVS1jaHI1dC1NdElFMFAyb2RWVElKV3RGejl3ZE5FU2tpQmozSDQ1
cnppazc5QU1GWkNQbUoyakh1WjJiNU9XaXRQRlZ2UTZXYmpOUkNCU0M5V1B6ZnRPWW43ZTJCakcz
bU1ydXlCSHhsLTM0U1RVaXg2ajNpMUxYSExvbmhwaS1NQldfZVhtNU8wbHlDZXZ4MHQxVDZnaW1i
T0dZVkJZMUtocnpyVGV6ek9MUndkYjRCVTNMQjk2S3ppX1dVN0hMLU9TVjhNNUtQUHhyN0RST3VB
bWR0cU0tcmZiOVlwaTQ2dnBORFRNZTlrUFV3U215aHpoTHduTGg2MGdSa3RIN2poLU4wUTFpRzQ9
timestamp: '1588610130.62'
49 changes: 49 additions & 0 deletions test/test_create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python
"""This module tests the create module"""
import getpass
import builtins
import unittest
import argparse
import mock
import libpkpass.commands.cli as cli
import libpkpass.commands.create as create
from libpkpass.errors import CliArgumentError, DecryptionError
from .basetest.basetest import CONFIG

class CreateTests(unittest.TestCase):
"""This class tests the create class"""
@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(subparser_name='create', identity='r1',
nopassphrase="true",
pwname='test',
config=CONFIG))
def test_create_success(self, subparser_name):
"""test decryption functionality"""
ret = ""
try:
with mock.patch.object(builtins, 'input', lambda _: 'y'):
with mock.patch.object(getpass, 'getpass', lambda _: 'y'):
create.Create(cli.Cli())
except DecryptionError as error:
ret = error.msg
self.assertEqual(ret, "")

@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(subparser_name='create', identity='r1',
nopassphrase="true",
pwname=None,
config=CONFIG))
def test_create_no_pass(self, subparser_name):
"""test decryption functionality"""
ret = ""
try:
with mock.patch.object(builtins, 'input', lambda _: 'y'):
with mock.patch.object(getpass, 'getpass', lambda _: 'y'):
create.Create(cli.Cli())
except CliArgumentError as error:
ret = error.msg
self.assertEqual(ret, "'pwname' is a required argument")


if __name__ == '__main__':
unittest.main()
19 changes: 18 additions & 1 deletion test/test_distribute.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#!/usr/bin/env python
"""This module tests the distribute module"""
import builtins
import unittest
import argparse
import mock
import libpkpass.commands.cli as cli
import libpkpass.commands.distribute as distribute
from libpkpass.errors import CliArgumentError
from libpkpass.errors import CliArgumentError, DecryptionError
from .basetest.basetest import CONFIG

class DistributeTests(unittest.TestCase):
Expand Down Expand Up @@ -40,5 +41,21 @@ def test_distribute_cli_error(self, subparser_name):
ret = True
self.assertTrue(ret)

@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(subparser_name='distribute', identity='r1',
nopassphrase="true",
pwname='test',
config=CONFIG))
def test_distribute_cli_decrypt(self, subparser_name):
"""test decryption functionality"""
ret = ""
try:
with mock.patch.object(builtins, 'input', lambda _: 'y'):
distribute.Distribute(cli.Cli())
except DecryptionError as error:
ret = error.msg
self.assertEqual(ret, "")


if __name__ == '__main__':
unittest.main()
49 changes: 49 additions & 0 deletions test/test_generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python
"""This module tests the generate module"""
import getpass
import builtins
import unittest
import argparse
import mock
import libpkpass.commands.cli as cli
import libpkpass.commands.generate as generate
from libpkpass.errors import CliArgumentError, DecryptionError
from .basetest.basetest import CONFIG

class GenerateTests(unittest.TestCase):
"""This class tests the generate class"""
@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(subparser_name='generate', identity='r3',
nopassphrase="true",
pwname='gentest',
config=CONFIG))
def test_generate_success(self, subparser_name):
"""test decryption functionality"""
ret = ""
try:
with mock.patch.object(builtins, 'input', lambda _: 'y'):
with mock.patch.object(getpass, 'getpass', lambda _: 'y'):
generate.Generate(cli.Cli())
except DecryptionError as error:
ret = error.msg
self.assertEqual(ret, "")

@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(subparser_name='generate', identity='r3',
nopassphrase="true",
pwname=None,
config=CONFIG))
def test_generate_no_pass(self, subparser_name):
"""test decryption functionality"""
ret = ""
try:
with mock.patch.object(builtins, 'input', lambda _: 'y'):
with mock.patch.object(getpass, 'getpass', lambda _: 'y'):
generate.Generate(cli.Cli())
except CliArgumentError as error:
ret = error.msg
self.assertEqual(ret, "'pwname' is a required argument")


if __name__ == '__main__':
unittest.main()
58 changes: 58 additions & 0 deletions test/test_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python
"""This module tests the info module"""
import unittest
import argparse
import mock
import libpkpass.commands.cli as cli
import libpkpass.commands.info as info
from libpkpass.errors import CliArgumentError
from .basetest.basetest import CONFIG, captured_output

class InfoTests(unittest.TestCase):
"""This class tests the info class"""
@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(subparser_name='info', identity='r1',
nopassphrase="true",
pwname='test',
color=False,
config=CONFIG))
def test_info(self, subparser_name):
"""test decryption functionality"""
ret = True
with captured_output() as (out, _):
info.Info(cli.Cli())
output = "".join(out.getvalue().strip().split()).split("timestamp")[0]
check_dict = {
'Metadata': {
'Authorizer': 'y',
'Creator': 'r1',
'Description': 'y',
'Name': 'test',
'Schemaversion': 'v1',
'Signature': 'None',
'Recipients': 'r1',
'TotalRecipients': '1',
}
}
for key, value in check_dict['Metadata'].items():
if key not in output and value not in output:
ret = False
self.assertTrue(ret)

@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(subparser_name='info', identity='r1',
nopassphrase="true",
pwname=None,
config=CONFIG))
def test_info_no_pass(self, subparser_name):
"""test decryption functionality"""
ret = False
try:
info.Info(cli.Cli())
except CliArgumentError as err:
if err.msg == "'pwname' is a required argument":
ret = True
self.assertTrue(ret)

if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 1181d79

Please sign in to comment.