def test_login(): '''Performs a basic login intended to be successful''' dbconn = setup_test() dbdata = init_server(dbconn) conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair # Most of the code which was originally written for this test is needed for other tests # because commands like PREREG require being logged in as the administrator. Both of these # functions still perform all the necessary tests that were originally done here. regcode_admin(dbdata, conn) login_admin(dbdata, conn) conn.send_message({'Action': "QUIT"})
def load_signingpair(path: str) -> RetVal: '''Instantiates a signing pair from a file''' if not path: return RetVal(BadParameterValue, 'path may not be empty') if not os.path.exists(path): return RetVal(ResourceNotFound, '%s exists' % path) indata = None try: with open(path, "r") as fhandle: indata = json.load(fhandle) except Exception as e: return RetVal(ExceptionThrown, e) if not isinstance(indata, dict): return RetVal(BadData, 'File does not contain an Anselus JSON signing pair') try: jsonschema.validate(indata, __signing_pair_schema) except jsonschema.ValidationError: return RetVal(BadData, "file data does not validate") except jsonschema.SchemaError: return RetVal(InternalError, "BUG: invalid SigningPair schema") public_key = CryptoString(indata['VerificationKey']) private_key = CryptoString(indata['SigningKey']) if not public_key.is_valid() or not private_key.is_valid(): return RetVal(BadData, 'Failure to base85 decode key data') return RetVal().set_value('keypair', SigningPair(public_key, private_key))
def signingpair_from_string(keystr : str) -> SigningPair: '''Intantiates a signing pair from a saved seed string that is used for the private key''' key = nacl.signing.SigningKey(base64.b85decode(keystr)) return SigningPair( CryptoString('ED25519:' + base64.b85encode(key.verify_key.encode()).decode()), CryptoString('ED25519:' + base64.b85encode(key.encode()).decode()) )
def test_setpassword(): '''Tests the SETPASSWORD command''' dbconn = setup_test() dbdata = init_server(dbconn) conn = ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) badpwhash = '$argon2id$v=19$m=65536,t=2,' \ 'p=1$l0nvPkQDxJDKIMZsA96x4A$c5+XHmZjzSJIg3/vXXr33YNB1Jl52mdXtbMASLP1abs' newpwhash = '$argon2id$v=19$m=65536,t=2,' \ 'p=1$4WOWeLn7oOmrkp41zMzMcQ$8G51UCiIC/eETPJf0RZ5cNkX7jr3NkT92etTwNEO+f0' # Subtest #1: Failure because of wrong password sent conn.send_message({ 'Action': "SETPASSWORD", 'Data': { 'Password-Hash': badpwhash, 'NewPassword-Hash': newpwhash } }) response = conn.read_response(None) assert response['Code'] == 402 and response['Status'] == 'AUTHENTICATION FAILURE', \ 'test_setpassword(): Failed to catch bad password' # Subtest #2: Successful password change conn.send_message({ 'Action': "SETPASSWORD", 'Data': { 'Password-Hash': pwhash, 'NewPassword-Hash': newpwhash } }) response = conn.read_response(None) assert response['Code'] == 200 and response['Status'] == 'OK', \ 'test_setpassword(): Failed to update admin password' conn.send_message({'Action': "QUIT"})
def test_reset_password(): '''Tests password reset code''' dbconn = setup_test() dbdata = init_server(dbconn) conn = serverconn.ServerConnection() status = conn.connect('localhost', 2001) assert not status.error( ), f"test_login(): failed to connect to server: {status.info()}" password = Password('Linguini2Pegboard*Album') devid = '14142135-9c22-4d3e-84a3-2aa281f65714' keypair = EncryptionPair( CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'), CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>')) status = serverconn.regcode(conn, 'admin', dbdata['admin_regcode'], password.hashstring, devid, keypair, '') assert not status.error( ), f"test_reset_password(): regcode failed: {status.info()}" status = serverconn.login(conn, dbdata['admin_wid'], CryptoString(dbdata['oekey'])) assert not status.error( ), f"test_reset_password(): login phase failed: {status.info()}" status = serverconn.password(conn, dbdata['admin_wid'], password.hashstring) assert not status.error( ), f"test_reset_password(): password phase failed: {status.info()}" status = serverconn.device(conn, devid, keypair) assert not status.error(), "test_reset_password(): device phase failed: " \ f"{status.info()}" status = init_user(conn, dbdata) assert not status.error( ), f"test_reset_password(): user init failed: {status.info()}" status = serverconn.reset_password(conn, dbdata['user_wid']) assert not status.error( ), f"test_reset_password(): password reset failed: {status.info()}" resetdata = status status = serverconn.logout(conn) assert not status.error( ), f"test_reset_password(): admin logout failed: {status.info()}" newpassword = Password('SomeOth3rPassw*rd') status = serverconn.passcode(conn, dbdata['user_wid'], resetdata['resetcode'], newpassword.hashstring) assert not status.error( ), f"test_reset_password(): passcode failed: {status.info()}"
def test_preregister_regcode(): '''Test the preregister and regcode commands''' dbconn = setup_test() dbdata = init_server(dbconn) conn = serverconn.ServerConnection() status = conn.connect('localhost', 2001) assert not status.error( ), f"test_login(): failed to connect to server: {status.info()}" password = Password('Linguini2Pegboard*Album') devid = '14142135-9c22-4d3e-84a3-2aa281f65714' keypair = EncryptionPair( CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'), CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>')) status = serverconn.regcode(conn, 'admin', dbdata['admin_regcode'], password.hashstring, devid, keypair, '') assert not status.error( ), f"test_preregister_regcode(): regcode failed: {status.info()}" status = serverconn.login(conn, dbdata['admin_wid'], CryptoString(dbdata['oekey'])) assert not status.error( ), f"test_preregister_regcode(): login phase failed: {status.info()}" status = serverconn.password(conn, dbdata['admin_wid'], password.hashstring) assert not status.error( ), f"test_preregister_regcode(): password phase failed: {status.info()}" status = serverconn.device(conn, devid, keypair) assert not status.error(), "test_preregister_regcode(): device phase failed: " \ f"{status.info()}" status = serverconn.preregister(conn, '', 'csimons', 'example.net') assert not status.error( ), "test_preregister_regcode(): uid preregistration failed" assert status['domain'] == 'example.net' and 'wid' in status and 'regcode' in status and \ status['uid'] == 'csimons', "test_preregister_regcode(): failed to return expected data" regdata = status password = Password('MyS3cretPassw*rd') devpair = EncryptionPair() status = serverconn.regcode(conn, 'csimons', regdata['regcode'], password.hashstring, '11111111-1111-1111-1111-111111111111', devpair, 'example.net') assert not status.error(), "test_preregister_regcode(): uid regcode failed" conn.disconnect()
def __validate_org_data(self) -> RetVal: '''Checks the validity of all data fields''' if self.type != 'Organization': return RetVal(BadData, 'invalid entry type %s' % self.type) outStatus = self.__validate_common_data() if outStatus.error(): return outStatus # Required field: Admin address m = re.match( r'^[\da-fA-F]{8}-?[\da-fA-F]{4}-?[\da-fA-F]{4}-?[\da-fA-F]{4}' r'-?[\da-fA-F]{12}/([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+$', self.fields['Contact-Admin']) if not m: return RetVal(BadData, 'bad admin contact address') # Required fields: Primary Verification Key, Encryption Key # We can't verify the actual key data, but we can at least ensure that it's formatted # correctly and we can b85decode the key itself for keyfield in ['Primary-Verification-Key', 'Encryption-Key']: if not CryptoString(self.fields[keyfield]).is_valid(): return RetVal(BadData, f"bad key field {keyfield}") # Optional fields: Support and Abuse addresses for contactfield in ['Contact-Support', 'Contact-Abuse']: if contactfield in self.fields.keys(): m = re.match( r'^[\da-fA-F]{8}-?[\da-fA-F]{4}-?[\da-fA-F]{4}-?[\da-fA-F]{4}' r'-?[\da-fA-F]{12}/([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+$', self.fields[contactfield]) if not m: return RetVal(BadData, f"bad contact address {contactfield}") # Optional field: Language if 'Language' in self.fields.keys(): m = re.match(r'^[a-zA-Z]{2,3}(,[a-zA-Z]{2,3})*?$', self.fields['Language']) if not m: return RetVal(BadData, 'bad language list') # Optional field: Secondary Verification Key if 'Secondary-Verification-Key' in self.fields.keys(): if not CryptoString( self.fields['Secondary-Verification-Key']).is_valid(): return RetVal(BadData, 'bad secondary verification key') return RetVal()
def chain(self, key: CryptoString, rotate_optional: bool) -> RetVal: '''Appends a new entry to the chain, optionally rotating keys which aren't required to be changed. This method requires that the root entry already exist. Note that user cards will not have all the required signatures when the call returns''' if len(self.entries) < 1: return RetVal(ResourceNotFound, 'missing root entry') # Just in case we get some squirrelly non-Org, non-User card type chain_method = getattr(self.entries[-1], "chain", None) if not chain_method or not callable(chain_method): return RetVal(FeatureNotAvailable, "entry doesn't support chaining") chaindata = self.entries[-1].chain(key, rotate_optional) if chaindata.error(): return chaindata new_entry = chaindata['entry'] skeystring = CryptoString() status = skeystring.set(chaindata['sign.private']) if status.error(): return status if new_entry.type == 'User': status = new_entry.sign(skeystring, 'User') else: status = new_entry.sign(skeystring, 'Organization') if status.error(): return status chaindata['entry'] = new_entry self.entries.append(new_entry) return chaindata
def verify_chain(self, previous: EntryBase) -> RetVal: '''Verifies the chain of custody between the provided previous entry and the current one.''' if previous.type != 'Organization': return RetVal(BadParameterValue, 'entry type mismatch') if 'Custody' not in self.signatures or not self.signatures['Custody']: return RetVal(ResourceNotFound, 'custody signature missing') if 'Primary-Verification-Key' not in previous.fields or \ not previous.fields['Primary-Verification-Key']: return RetVal(ResourceNotFound, 'signing key missing') try: prev_index = int(previous['Index']) except: return RetVal(BadData, 'previous entry has a bad index') try: index = int(self['Index']) except: return RetVal(BadData, 'current entry has a bad index') if index != prev_index + 1: return RetVal(InvalidKeycard, 'entry index compliance failure') status = self.verify_signature( CryptoString(previous.fields['Primary-Verification-Key']), 'Custody') return status
def verify_signature(self, verify_key: CryptoString, sigtype: str) -> RetVal: '''Verifies a signature, given a verification key''' if not verify_key.is_valid(): return RetVal(BadParameterValue, 'bad verify key') sig_names = [x['name'] for x in self.signature_info] if sigtype not in sig_names: return RetVal(BadParameterValue, 'bad signature type') if verify_key.prefix != 'ED25519': return RetVal(UnsupportedEncryptionType, verify_key.prefix) if sigtype in self.signatures and not self.signatures[sigtype]: return RetVal(NotCompliant, 'empty signature ' + sigtype) sig = CryptoString() status = sig.set(self.signatures[sigtype]) if status.error(): return status try: vkey = nacl.signing.VerifyKey(verify_key.raw_data()) except Exception as e: return RetVal(ExceptionThrown, e) try: data = self.make_bytestring(sig_names.index(sigtype)) vkey.verify(data, sig.raw_data()) except nacl.exceptions.BadSignatureError: return RetVal(InvalidKeycard) return RetVal()
def make_test_orgentry() -> keycard.OrgEntry: '''Generates an organizational entry for testing purposes''' # Primary signing key pskey = nacl.signing.SigningKey( b'msvXw(nII<Qm6oBHc+92xwRI3>VFF-RcZ=7DEu3|', Base85Encoder) orgcard = keycard.OrgEntry() orgcard.set_fields({ 'Name': 'Acme Widgets, Inc.', 'Contact-Admin': 'c590b44c-798d-4055-8d72-725a7942f3f6/acme.com', 'Language': 'en', 'Domain': 'acme.com', 'Primary-Verification-Key': 'ED25519:)8id(gE02^S<{3H>9B;X4{DuYcb`%wo^mC&1lN88', 'Encryption-Key': 'CURVE25519:@b?cjpeY;<&y+LSOA&yUQ&ZIrp(JGt{W$*V>ATLG' }) # Organization sign, hash, and verify rv = orgcard.generate_hash('BLAKE2B-256') assert not rv.error(), 'entry failed to hash' pskeystring = CryptoString() pskeystring.set('ED25519:' + base64.b85encode(pskey.encode()).decode()) rv = orgcard.sign(pskeystring, 'Organization') assert not rv.error(), 'Unexpected RetVal error %s' % rv.error() assert orgcard.signatures['Organization'], 'entry failed to user sign' ovkey = nacl.signing.VerifyKey(pskey.verify_key.encode()) ovkeystring = CryptoString() ovkeystring.prefix = 'ED25519' ovkeystring.data = base64.b85encode(ovkey.encode()).decode() rv = orgcard.verify_signature(ovkeystring, 'Organization') assert not rv.error(), 'org entry failed to verify' status = orgcard.is_compliant() assert not status.error(), f"OrgEntry wasn't compliant: {str(status)}" return orgcard
def test_encryptionpair_encrypt_decrypt(): '''Test the encryption and decryption code for the EncryptionPair class''' public_key = CryptoString( r"CURVE25519:(B2XX5|<+lOSR>_0mQ=KX4o<aOvXe6M`Z5ldINd`") private_key = CryptoString( r"CURVE25519:(Rj5)mmd1|YqlLCUP0vE;YZ#o;tJxtlAIzmPD7b&") kp = encryption.EncryptionPair(public_key, private_key) test_data = 'This is some encryption test data' estatus = kp.encrypt(test_data.encode()) assert not estatus.error( ), 'test_encryptionpair_encrypt_decrypt: error encrypting test data' dstatus = kp.decrypt(estatus['data']) assert not dstatus.error( ), 'test_encryptionpair_encrypt_decrypt: error decrypting test data' assert dstatus['data'] == test_data, 'decoded data mismatch'
def test_keycard_chain_verify_load_save(): '''Tests entry rotation of a keycard''' userentry = make_test_userentry() # User contact request signing and verification keys crskeystring = CryptoString( 'ED25519:ip52{ps^jH)t$k-9bc_RzkegpIW?}FFe~BX&<V}9') card = keycard.Keycard() card.entries.append(userentry) chaindata = card.chain(crskeystring, True) assert not chaindata.error(), f'keycard chain failed: {chaindata}' new_entry = chaindata['entry'] oskeystring = CryptoString( 'ED25519:msvXw(nII<Qm6oBHc+92xwRI3>VFF-RcZ=7DEu3|') status = new_entry.sign(oskeystring, 'Organization') assert not status.error(), f'chained entry failed to org sign: {status}' new_entry.prev_hash = userentry.hash new_entry.generate_hash('BLAKE2B-256') assert not status.error(), f'chained entry failed to hash: {status}' skeystring = CryptoString(chaindata['sign.private']) status = new_entry.sign(skeystring, 'User') assert not status.error(), f'chained entry failed to user sign: {status}' card.entries[-1] = new_entry status = card.verify() assert not status.error(), f'keycard failed to verify: {status}' # Although it doesn't make a lot of initial sense to group saving and loading tests with # code that handles chaining and verification, it saves on a lot of duplicate test code test_folder = setup_test('keycard_save') status = card.save(os.path.join(test_folder, 'user_save_test_keycard.kc'), True) assert not status.error(), f'keycard failed to save: {status}' newcard = keycard.Keycard() status = newcard.load( os.path.join(test_folder, 'user_save_test_keycard.kc')) assert not status.error(), f'keycard failed to load: {status}'
def test_user_chaining(): '''Tests chaining of user entries and verification thereof''' userentry = make_test_userentry() # User contact request signing key crskeystring = CryptoString( 'ED25519:ip52{ps^jH)t$k-9bc_RzkegpIW?}FFe~BX&<V}9') # Organization signing key oskeystring = CryptoString( 'ED25519:msvXw(nII<Qm6oBHc+92xwRI3>VFF-RcZ=7DEu3|') chaindata = userentry.chain(crskeystring, True) assert not chaindata.error( ), f'userentry.chain returned an error: {chaindata.error()}' new_entry = chaindata['entry'] # Now that we have a new entry, it only has a valid custody signature. Add all the other # signatures needed to be compliant and then verify the whole thing. # The signing key is replaced during chain() new_crskeystring = CryptoString() status = new_crskeystring.set(chaindata['crsign.private']) assert not status.error( ), 'test_user_chain: new signing key has bad format' status = new_entry.sign(oskeystring, 'Organization') assert not status.error(), f'new entry failed to org sign: {status}' new_entry.prev_hash = userentry.hash status = new_entry.generate_hash('BLAKE2B-256') assert not status.error(), f'new entry failed to hash: {status}' status = new_entry.sign(new_crskeystring, 'User') assert not status.error(), f'new entry failed to user sign: {status}' status = new_entry.is_compliant() assert not status.error(), f'new entry failed compliance check: {status}' # Testing of chain() is complete. Now test verify_chain() status = new_entry.verify_chain(userentry) assert not status.error( ), f'chain of custody verification failed: {status}'
def test_is_compliant_org(): '''Tests compliance testing for the OrgEntry class''' # Organization signing key oskey = nacl.signing.SigningKey( b'msvXw(nII<Qm6oBHc+92xwRI3>VFF-RcZ=7DEu3|', Base85Encoder) orgcard = keycard.OrgEntry() orgcard.set_fields({ 'Name': 'Acme Widgets, Inc', 'Contact-Admin': '54025843-bacc-40cc-a0e4-df48a099c2f3/example.com', 'Contact-Abuse': '54025843-bacc-40cc-a0e4-df48a099c2f3/example.com', 'Language': 'en', 'Primary-Verification-Key': 'ED25519:7dfD==!Jmt4cDtQDBxYa7(dV|N$}8mYwe$=RZuW|', 'Encryption-Key': 'CURVE25519:_`UC|vltn_%P5}~vwV^)oY){#uvQSSy(dOD_l(yE' }) # sign and verify okeystring = CryptoString() okeystring.set('ED25519:' + base64.b85encode(oskey.encode()).decode()) rv = orgcard.sign(okeystring, 'Organization') assert not rv.error(), 'Unexpected RetVal error %s' % rv.error() assert orgcard.signatures['Organization'], 'entry failed to user sign' ovkey = nacl.signing.VerifyKey(oskey.verify_key.encode()) ovkeystring = CryptoString() ovkeystring.prefix = 'ED25519' ovkeystring.data = base64.b85encode(ovkey.encode()).decode() rv = orgcard.verify_signature(ovkeystring, 'Organization') assert not rv.error(), 'entry failed to org verify' rv = orgcard.generate_hash('BLAKE2B-256') assert not rv.error(), 'entry failed to hash' status = orgcard.is_compliant() assert not status.error(), f"OrgEntry wasn't compliant: {str(status)}"
def __validate_user_data(self) -> RetVal: '''Checks the validity of all data fields''' if self.type != 'User': return RetVal(BadData, 'invalid entry type %s' % self.type) outStatus = self.__validate_common_data() if outStatus.error(): return outStatus # Required field: Workspace ID m = re.match( r'^[\da-fA-F]{8}-?[\da-fA-F]{4}-?[\da-fA-F]{4}-?[\da-fA-F]{4}' r'-?[\da-fA-F]{12}$', self.fields['Workspace-ID']) if not m: return RetVal(BadData, 'bad workspace ID') # Required field: Domain # Although mostly freeform, the Name field has a couple requirements: m = re.match(r'([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+', self.fields['Domain']) if not m or len(self.fields['Domain']) >= 64: return RetVal(BadData, 'bad domain value') # Required fields: Contact Request Verification Key, Contact Request Encryption Key, # Public-Encryption-Key for keyfield in [ 'Contact-Request-Verification-Key', 'Contact-Request-Encryption-Key', 'Public-Encryption-Key' ]: if not CryptoString(self.fields[keyfield]).is_valid(): return RetVal(BadData, f"bad key field {keyfield}") # Optional field: User ID if 'User-ID' in self.fields.keys(): if re.findall(r'[\\\/\s"]', self.fields['User-ID']) or \ len(self.fields['User-ID']) >= 64: return RetVal(BadData, 'bad user id value') if 'Alternate-Encryption-Key' in self.fields.keys(): if not CryptoString( self.fields['Alternate-Encryption-Key']).is_valid(): return RetVal(BadData, 'bad alternate encryption key') return RetVal()
def verify_hash(self) -> RetVal: '''Checks that the entry's actual hash matches that in the hash field''' current_hash = CryptoString(self.hash) if not current_hash.is_valid(): return RetVal(InvalidHash, f"{self.hash} is not a valid CryptoString") status = self.get_hash(current_hash.prefix) if status.error(): return status return RetVal()
def __init__(self, public=None, private=None): super().__init__() if public and private: if not isinstance(public, CryptoString) or not isinstance(private, CryptoString): raise TypeError if public.prefix != private.prefix: raise ValueError self.enctype = public.prefix self.public = public self.private = private else: key = nacl.public.PrivateKey.generate() self.enctype = 'CURVE25519' self.public = CryptoString('CURVE25519:' + \ base64.b85encode(key.public_key.encode()).decode()) self.private = CryptoString('CURVE25519:' + \ base64.b85encode(key.encode()).decode()) self.pubhash = blake2hash(self.public.data.encode()) self.privhash = blake2hash(self.private.data.encode())
def __init__(self, key=None): super().__init__() if key: if type(key).__name__ != 'CryptoString': raise TypeError self.key = key else: self.enctype = 'XSALSA20' self.key = CryptoString('XSALSA20:' + \ base64.b85encode(nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)).decode()) self.hash = blake2hash(self.key.data.encode())
def test_signpair_sign_verify(): '''Tests SigningPair's sign() and verify() methods''' public_key = CryptoString( r"ED25519:PnY~pK2|;AYO#1Z;B%T$2}E$^kIpL=>>VzfMKsDx") private_key = CryptoString( r"ED25519:{^A@`5N*T%5ybCU%be892x6%*Rb2rnYd=SGeO4jF") sp = encryption.SigningPair(public_key, private_key) key = nacl.signing.SigningKey(private_key.raw_data()) signed = key.sign(b'1234567890', encryption.Base85Encoder) sstatus = sp.sign(b'1234567890') assert not sstatus.error( ), f"test_signpair_sign_verify: signing failed: {sstatus.info()}" assert sstatus['signature'] == 'ED25519:' + signed.signature.decode(), \ "test_signpair_sign_verify: signature data mismatch" vstatus = sp.verify(b'1234567890', CryptoString(sstatus['signature'])) assert not vstatus.error( ), f"test_signpair_sign_verify: verification failed: {vstatus.info()}"
def test_overflow(): '''Tests the server's command handling for commands greater than 8K''' dbconn = setup_test() dbdata = init_server(dbconn) conn = serverconn.ServerConnection() assert conn.connect('localhost', 2001), "Connection to server at localhost:2001 failed" # password is 'SandstoneAgendaTricycle' pwhash = '$argon2id$v=19$m=65536,t=2,p=1$ew5lqHA5z38za+257DmnTA$0LWVrI2r7XCq' \ 'dcCYkJLok65qussSyhN5TTZP+OTgzEI' devid = '22222222-2222-2222-2222-222222222222' devpair = EncryptionPair( CryptoString(r'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z'), CryptoString(r'CURVE25519:W30{oJ?w~NBbj{F8Ag4~<bcWy6_uQ{i{X?NDq4^l')) dbdata['pwhash'] = pwhash dbdata['devid'] = devid dbdata['devpair'] = devpair regcode_admin(dbdata, conn) login_admin(dbdata, conn) conn.send_message({ 'Action': "REGISTER", 'Data': { 'Workspace-ID': 'A' * 10240, 'Password-Hash': pwhash, 'Device-ID': '11111111-1111-1111-1111-111111111111', 'Device-Key': 'CURVE25519:@X~msiMmBq0nsNnn0%~x{M|NU_{?<Wj)cYybdh&Z' } }) response = conn.read_response(server_response) assert response['Code'] == 400 and response['Status'] == 'BAD REQUEST', \ 'test_overflow: failed to catch overflow' conn.send_message({'Action': "QUIT"})
def test_signpair_save(): '''Tests the save code of the SigningPair class''' test_folder = setup_test('encryption_signpair_save') public_key = CryptoString( r"ED25519:PnY~pK2|;AYO#1Z;B%T$2}E$^kIpL=>>VzfMKsDx") private_key = CryptoString( r"ED25519:{^A@`5N*T%5ybCU%be892x6%*Rb2rnYd=SGeO4jF") sp = encryption.SigningPair(public_key, private_key) keypair_path = os.path.join(test_folder, 'testpair.jk') status = sp.save(keypair_path) assert not status.error( ), f"Failed to create saved signing pair file: {status.info()}" fhandle = open(keypair_path) filedata = json.load(fhandle) fhandle.close() assert filedata['VerificationKey'] == public_key.as_string(), \ "Saved data does not match input data" assert filedata['SigningKey'] == private_key.as_string( ), "Saved data does not match input data"
def test_encryptionpair_save(): '''Tests the save code of the EncryptionPair class''' test_folder = setup_test('encryption_encryptionpair_save') public_key = CryptoString( "CURVE25519:(B2XX5|<+lOSR>_0mQ=KX4o<aOvXe6M`Z5ldINd`") private_key = CryptoString( "CURVE25519:(Rj5)mmd1|YqlLCUP0vE;YZ#o;tJxtlAIzmPD7b&") kp = encryption.EncryptionPair(public_key, private_key) keypair_path = os.path.join(test_folder, 'testpair.jk') status = kp.save(keypair_path) assert not status.error( ), f"Failed to create saved encryption pair file: {status.info()}" fhandle = open(keypair_path) filedata = json.load(fhandle) fhandle.close() assert filedata['PublicKey'] == public_key.as_string( ), "Saved data does not match input data" assert filedata['PrivateKey'] == private_key.as_string( ), "Saved data does not match input data"
def __init__(self, public=None, private=None): super().__init__() if public and private: if type(public).__name__ != 'CryptoString' or \ type(private).__name__ != 'CryptoString': raise TypeError if public.prefix != private.prefix: raise ValueError self.enctype = public.prefix self.public = public self.private = private else: key = nacl.signing.SigningKey.generate() self.enctype = 'ED25519' self.public = CryptoString('ED25519:' + \ base64.b85encode(key.verify_key.encode()).decode()) self.private = CryptoString('ED25519:' + \ base64.b85encode(key.encode()).decode()) self.pubhash = blake2hash(self.public.data.encode()) self.privhash = blake2hash(self.private.data.encode())
def test_devkey(): '''Tests the devkey() command''' dbconn = setup_test() dbdata = init_server(dbconn) conn = serverconn.ServerConnection() status = conn.connect('localhost', 2001) assert not status.error( ), f"test_devkey(): failed to connect to server: {status.info()}" status = init_admin(conn, dbdata) assert not status.error( ), f"test_devkey(): init_admin failed: {status.info()}" newdevpair = EncryptionPair( CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'), CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>')) status = serverconn.devkey(conn, dbdata['admin_devid'], dbdata['admin_devpair'], newdevpair) assert not status.error( ), f"test_devkey(): error returned: {status.info()}" conn.disconnect()
def test_signpair_load(): '''Tests the load code of the SigningPair class''' test_folder = setup_test('encryption_signpair_load') public_key = CryptoString( r"ED25519:PnY~pK2|;AYO#1Z;B%T$2}E$^kIpL=>>VzfMKsDx") private_key = CryptoString( r"ED25519:{^A@`5N*T%5ybCU%be892x6%*Rb2rnYd=SGeO4jF") kp = encryption.SigningPair(public_key, private_key) keypair_path = os.path.join(test_folder, 'testpair.jk') status = kp.save(keypair_path) assert not status.error( ), f"Failed to create saved signing pair file: {status.info()}" status = encryption.load_signingpair(keypair_path) assert not status.error( ), f"Failed to load saved signing pair file: {status.info()}" testpair = status['keypair'] assert testpair.enctype == kp.enctype, "Loaded data does not match input data" assert testpair.public == public_key, "Loaded data does not match input data" assert testpair.private == private_key, "Loaded data does not match input data"
def test_encryptionpair_load(): '''Tests the load code of the EncryptionPair class''' test_folder = setup_test('encryption_encryptionpair_load') public_key = CryptoString( "CURVE25519:(B2XX5|<+lOSR>_0mQ=KX4o<aOvXe6M`Z5ldINd`") private_key = CryptoString( "CURVE25519:(Rj5)mmd1|YqlLCUP0vE;YZ#o;tJxtlAIzmPD7b&") kp = encryption.EncryptionPair(public_key, private_key) keypair_path = os.path.join(test_folder, 'testpair.jk') status = kp.save(keypair_path) assert not status.error( ), f"Failed to create saved encryption pair file: {status.info()}" status = encryption.load_encryptionpair(keypair_path) assert not status.error( ), f"Failed to load saved pair file: {status.info()}" testpair = status['keypair'] assert testpair.enctype == kp.enctype, "Loaded data does not match input data" assert testpair.public == public_key, "Loaded data does not match input data" assert testpair.private == private_key, "Loaded data does not match input data"
def test_secretkey_save(): '''Tests the save code of the SecretKey class''' test_folder = setup_test('encryption_secretkey_save') key = CryptoString(r"XSALSA20:J~T^ko3HCFb$1Z7NudpcJA-dzDpF52IF1Oysh+CY") sk = encryption.SecretKey(key) key_path = os.path.join(test_folder, 'testkey.jk') status = sk.save(key_path) assert not status.error(), "Failed to create saved encryption pair file" fhandle = open(key_path) filedata = json.load(fhandle) fhandle.close() assert filedata['SecretKey'] == key.as_string( ), "Saved data does not match input data"
def verify_chain(self, previous: EntryBase) -> RetVal: '''Verifies the chain of custody between the provided previous entry and the current one.''' if previous.type != 'User': return RetVal(BadParameterValue, 'entry type mismatch') if 'Custody' not in self.signatures or not self.signatures['Custody']: return RetVal(ResourceNotFound, 'custody signature missing') if 'Contact-Request-Verification-Key' not in previous.fields or \ not previous.fields['Contact-Request-Verification-Key']: return RetVal(ResourceNotFound, 'signing key missing') status = self.verify_signature( CryptoString(previous.fields['Contact-Request-Verification-Key']), 'Custody') return status
def keycard_admin(config, conn) -> dict: '''Uploads a keycard entry for the admin account''' conn = serverconn.ServerConnection() status = conn.connect('localhost', 2001) assert not status.error( ), f"test_addentry(): failed to connect to server: {status.info()}" crepair = EncryptionPair( CryptoString(r'CURVE25519:mO?WWA-k2B2O|Z%fA`~s3^$iiN{5R->#jxO@cy6{'), CryptoString(r'CURVE25519:2bLf2vMA?GA2?L~tv<PA9XOw6e}V~ObNi7C&qek>')) crspair = SigningPair( CryptoString(r'ED25519:E?_z~5@+tkQz!iXK?oV<Zx(ec;=27C8Pjm((kRc|'), CryptoString(r'ED25519:u4#h6LEwM6Aa+f<++?lma4Iy63^}V$JOP~ejYkB;')) epair = EncryptionPair( CryptoString(r'CURVE25519:Umbw0Y<^cf1DN|>X38HCZO@Je(zSe6crC6X_C_0F'), CryptoString(r'CURVE25519:Bw`F@ITv#sE)2NnngXWm7RQkxg{TYhZQbebcF5b$')) entry = keycard.UserEntry() entry.set_fields({ 'Name': 'Administrator', 'Workspace-ID': config['admin_wid'], 'User-ID': 'admin', 'Domain': 'example.com', 'Contact-Request-Verification-Key': crspair.get_public_key(), 'Contact-Request-Encryption-Key': crepair.get_public_key(), 'Public-Encryption-Key': epair.get_public_key() }) status = serverconn.addentry(conn, entry, CryptoString(config['ovkey']), crspair) assert not status.error( ), f"test_addentry: failed to add entry: {status.info()}" return { 'admin_crepair': crepair, 'admin_crspair': crspair, 'admin_epair': epair }