Skip to content

Commit

Permalink
Decrypter now supports all cipher codes including variable sizes of q…
Browse files Browse the repository at this point in the history
…b64, qb2 and sniffable streams
  • Loading branch information
SmithSamuelM committed Aug 5, 2024
1 parent bec32e0 commit 359d61a
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 45 deletions.
6 changes: 3 additions & 3 deletions src/keri/app/keeping.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ def updateAeid(self, aeid, seed):
# re-encrypt root salt secrets by prefix parameters .prms
for keys, data in self.ks.prms.getItemIter(): # keys is tuple of pre qb64
if data.salt:
salter = self.decrypter.decrypt(ser=data.salt)
salter = self.decrypter.decrypt(qb64=data.salt)
data.salt = (self.encrypter.encrypt(prim=salter).qb64
if self.encrypter else salter.qb64)
self.ks.prms.pin(keys, val=data)
Expand Down Expand Up @@ -889,7 +889,7 @@ def salt(self):
"""
salt = self.ks.gbls.get('salt')
if self.decrypter: # given .decrypt secret salt must be encrypted in db
return self.decrypter.decrypt(ser=salt).qb64
return self.decrypter.decrypt(qb64=salt).qb64
return salt


Expand Down Expand Up @@ -1185,7 +1185,7 @@ def rotate(self, pre, ncodes=None, ncount=1,
if self.aeid:
if not self.decrypter:
raise kering.DecryptError("Unauthorized decryption. Aeid but no decrypter.")
salt = self.decrypter.decrypt(ser=salt).qb64
salt = self.decrypter.decrypt(qb64=salt).qb64
else:
salt = core.Salter(qb64=salt).qb64 # ensures salt was unencrypted

Expand Down
10 changes: 7 additions & 3 deletions src/keri/core/coring.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,9 +795,13 @@ def __init__(self, raw=None, code=MtrDex.Ed25519N, soft='', rize=None,
rize (int | None): raw size in bytes when variable sized material not
including lead bytes if any
Otherwise None
qb64b (bytes | None): fully qualified crypto material Base64
qb64 (str | bytes | None): fully qualified crypto material Base64
qb2 (bytes | None): fully qualified crypto material Base2
qb64b (str | bytes | bytearray | memoryview | None): fully qualified
crypto material Base64. When str, encodes as utf-8. Strips when
bytearray and strip is True.
qb64 (str | bytes | bytearray | memoryview | None): fully qualified
crypto material Base64. When str, encodes as utf-8. Ignores strip
qb2 (bytes | bytearray | memoryview | None): fully qualified crypto
material Base2. Strips when bytearray and strip is True.
strip (bool): True means strip (delete) matter from input stream
bytearray after parsing qb64b or qb2. False means do not strip
Expand Down
93 changes: 63 additions & 30 deletions src/keri/core/signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

from .coring import (Tiers, )
from .coring import (SmallVrzDex, LargeVrzDex, Matter, MtrDex, Verfer, Cigar)
from .indexing import IdrDex, Siger
from .indexing import IdrDex, Indexer, Siger
from .streaming import Streamer


DSS_SIG_MODE = "fips-186-3"
Expand Down Expand Up @@ -682,6 +683,9 @@ class Cipher(Matter):

def __init__(self, raw=None, code=None, **kwa):
"""
Inherited Parameters:
(see Matter)
Parmeters:
raw (bytes | str): cipher text (not plain text)
code (str): cipher suite
Expand Down Expand Up @@ -725,7 +729,7 @@ def decrypt(self, prikey=None, seed=None):
signing key seed used to derive private decryption key
"""
decrypter = Decrypter(qb64b=prikey, seed=seed)
return decrypter.decrypt(ser=self.qb64b)
return decrypter.decrypt(qb64=self.qb64b)


class Encrypter(Matter):
Expand Down Expand Up @@ -799,11 +803,12 @@ def encrypt(self, ser=None, prim=None, code=None):
"""
Returns:
Cipher instance of cipher text encryption of plain text serialization
provided by either ser or Matter instance when provided.
provided by either ser or prim as CESR primitive instance.
Parameters:
ser (Union[bytes,str]): qb64b or qb64 serialization of plain text
ser (str | bytes | bytearray | memoryview): qb64b or qb64
serialization of plain text
prim (Matter | Indexer): CESR primitive instance whose serialization
qb64 or qb2 is to be encrypted based on code
code (str): code of plain text type for resultant encrypted cipher
Expand Down Expand Up @@ -893,13 +898,14 @@ def __init__(self, code=MtrDex.X25519_Private, seed=None, **kwa):
"""
Assign decrypting cipher suite function to ._decrypt
Parameters: See Matter for inheirted parameters
raw (bytes): private decryption key derived from seed (private signing key)
qb64b (bytes): fully qualified private decryption key
qb64 (str): fully qualified private decryption key
Inherited Parameters:
(see Matter)
Parameters: See Matter for inherited parameters
code (str): derivation code for private decryption key
seed (Union[bytes, str]): qb64b or qb64 of signing key seed used to
derive raw which is private decryption key
seed (str | bytes | bytearray | memoryview | None): qb64b or qb64
of signing key seed used to derive raw which is private
decryption key
"""
try:
super(Decrypter, self).__init__(code=code, **kwa)
Expand All @@ -921,7 +927,9 @@ def __init__(self, code=MtrDex.X25519_Private, seed=None, **kwa):
else:
raise ValueError("Unsupported decrypter code = {}.".format(self.code))

def decrypt(self, *, cipher=None, ser=None, klas=None, transferable=False):

def decrypt(self, *, cipher=None, qb64=None, qb2=None, klas=None,
transferable=False, **kwa):
"""
Returns:
Salter or Signer instance derived from plain text decrypted from
Expand All @@ -931,28 +939,37 @@ def decrypt(self, *, cipher=None, ser=None, klas=None, transferable=False):
encryption/decryption round trip.
Parameters:
cipher (Cipher): optional Cipher instance when ser is None
ser (bytes | str): serialization of cipher text
klas (Matter, Indexer, Streamer): Class used to create instance from
cipher (Cipher): instance. One of cipher, qb64, or qb2 required.
qb64 (str | bytes | bytearray | memoryview | None ): serialization
of cipher text as fully qualified base64. When str, encodes as
utf-8. When bytearray and strip in kwa is True then strips.
qb2 (bytes | bytearray | memoryview | None ): serialization
of cipher text as fully qualified base2. Strips when bytearray
and strip in kwa is True.
klas (Matter | Indexer | Streamer): Class used to create instance from
decrypted serialization.
transferable (bool): Modifier of Klas instance creation. When klas
is signer;
transferable (bool): Modifier of Klas instance creation.
When klas init (such as Signer) supports transferabe parm;
True means verfer of returned signer is transferable.
False means non-transferable
"""
if not (ser or cipher):
raise EmptyMaterialError("Neither ser or cipher are provided.")
if not cipher:
if qb64: # create cipher from qb64
cipher = Cipher(qb64b=qb64, **kwa)

if ser: # create cipher to ensure valid derivation code of material in ser
cipher = Cipher(qb64b=ser)
elif qb2:
cipher = Cipher(qb2=qb2, **kwa)

else:
raise EmptyMaterialError(f"Need one of cipher, qb64, or qb2.")

return (self._decrypt(cipher=cipher,
prikey=self.raw,
klas=klas,
transferable=transferable))

@staticmethod
def _x25519(cipher, prikey, klas, transferable=False):
def _x25519(cipher, prikey, klas=None, transferable=False):
"""
Returns plain text as Salter or Signer instance depending on the cipher
code and the embedded encrypted plain text derivation code.
Expand All @@ -961,19 +978,35 @@ def _x25519(cipher, prikey, klas, transferable=False):
cipher (Cipher): instance of encrypted seed or salt
prikey (bytes): raw binary decryption private key derived from
signing seed or sigkey
klas (Matter, Indexer, Streamer): Class used to create instance from
decrypted serialization.
transferable (bool): Modifier of Klas instance creation. When klas
is signer;
klas (Matter, Indexer, Streamer | None): Class used to create instance from
decrypted serialization. Default depends on cipher.code.
transferable (bool): Modifier of Klas instance creation.
When klas init (such as Signer) supports transferabe parm;
True means verfer of returned signer is transferable.
False means non-transferable
"""
pubkey = pysodium.crypto_scalarmult_curve25519_base(prikey)
plain = pysodium.crypto_box_seal_open(cipher.raw, pubkey, prikey) # qb64b
# ensure raw plain text is qb64b or qb64 so its derivation code is round tripped
if cipher.code == MtrDex.X25519_Cipher_Salt:
return Salter(qb64b=plain)
elif cipher.code == MtrDex.X25519_Cipher_Seed:
return Signer(qb64b=plain, transferable=transferable)

if not klas:
if cipher.code == CiXFixQB64Dex.X25519_Cipher_Salt:
#return Salter(qb64b=plain)
klas = Salter
elif cipher.code == CiXFixQB64Dex.X25519_Cipher_Seed:
#return Signer(qb64b=plain, transferable=transferable)
klas = Signer
elif cipher.code in CiXVarStrmDex:
klas = Streamer
else:
raise InvalidCodeError(f"Unsupported cipher code = {cipher.code}"
f" when klas missing.")

if cipher.code in CiXAllQB64Dex:
return klas(qb64b=plain, transferable=transferable)
elif cipher.code in CiXVarQB2Dex:
return klas(qb2=plain)
elif cipher.code in CiXVarStrmDex:
return klas(stream=plain)
else:
raise ValueError("Unsupported cipher text code = {}.".format(cipher.code))
raise InvalidCodeError(f"Unsupported cipher code = {cipher.code}.")
4 changes: 2 additions & 2 deletions src/keri/db/subing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,7 @@ def get(self, keys: Union[str, Iterable], decrypter: core.Decrypter = None):
keys = self._tokeys(key) # verkey is last split if any
verfer = coring.Verfer(qb64b=keys[-1]) # last split
if decrypter:
return (decrypter.decrypt(ser=bytes(val),
return (decrypter.decrypt(qb64=bytes(val),
transferable=verfer.transferable))
return (self.klas(qb64b=bytes(val), transferable=verfer.transferable))

Expand Down Expand Up @@ -1037,7 +1037,7 @@ def getItemIter(self, keys: Union[str, Iterable]=b"",
ikeys = self._tokeys(key) # verkey is last split if any
verfer = coring.Verfer(qb64b=ikeys[-1]) # last split
if decrypter:
yield (ikeys, decrypter.decrypt(ser=bytes(val),
yield (ikeys, decrypter.decrypt(qb64=bytes(val),
transferable=verfer.transferable))
else:
yield (ikeys, self.klas(qb64b=bytes(val),
Expand Down
4 changes: 2 additions & 2 deletions tests/app/test_keeping.py
Original file line number Diff line number Diff line change
Expand Up @@ -1591,7 +1591,7 @@ def test_manager_with_aeid():
pp = manager.ks.prms.get(spre)
assert pp.pidx == 0
assert pp.algo == keeping.Algos.salty
assert manager.decrypter.decrypt(ser=pp.salt).qb64 == salt
assert manager.decrypter.decrypt(qb64=pp.salt).qb64 == salt
assert pp.stem == ''
assert pp.tier == core.Tiers.low

Expand Down Expand Up @@ -1674,7 +1674,7 @@ def test_manager_with_aeid():
pp = manager.ks.prms.get(spre)
assert pp.pidx == 0
assert pp.algo == keeping.Algos.salty
assert manager.decrypter.decrypt(ser=pp.salt).qb64 == salt
assert manager.decrypter.decrypt(qb64=pp.salt).qb64 == salt
assert pp.stem == ''
assert pp.tier == core.Tiers.low

Expand Down
10 changes: 5 additions & 5 deletions tests/core/test_signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ def test_decrypter():
assert decrypter.raw == prikey

# decrypt seed cipher using ser
designer = decrypter.decrypt(ser=seedcipher.qb64b, transferable=signer.verfer.transferable)
designer = decrypter.decrypt(qb64=seedcipher.qb64b, transferable=signer.verfer.transferable)
assert designer.qb64b == seedqb64b
assert designer.code == MtrDex.Ed25519_Seed
assert designer.verfer.code == MtrDex.Ed25519
Expand All @@ -887,7 +887,7 @@ def test_decrypter():
# each encryption uses a nonce so not a stable representation for testing

# decrypt salt cipher using ser
desalter = decrypter.decrypt(ser=saltcipher.qb64b)
desalter = decrypter.decrypt(qb64=saltcipher.qb64b)
assert desalter.qb64b == saltqb64b
assert desalter.code == MtrDex.Salt_128

Expand All @@ -900,7 +900,7 @@ def test_decrypter():
# get from seedcipher above
cipherseed = ('PM9jOGWNYfjM_oLXJNaQ8UlFSAV5ACjsUY7J16xfzrlpc9Ve3A5WYrZ4o_'
'NHtP5lhp78Usspl9fyFdnCdItNd5JyqZ6dt8SXOt6TOqOCs-gy0obrwFkPPqBvVkEw')
designer = decrypter.decrypt(ser=cipherseed, transferable=signer.verfer.transferable)
designer = decrypter.decrypt(qb64=cipherseed, transferable=signer.verfer.transferable)
assert designer.qb64b == seedqb64b
assert designer.code == MtrDex.Ed25519_Seed
assert designer.verfer.code == MtrDex.Ed25519
Expand All @@ -909,7 +909,7 @@ def test_decrypter():
# get from saltcipher above
ciphersalt = ('1AAHjlR2QR9J5Et67Wy-ZaVdTryN6T6ohg44r73GLRPnHw-5S3ABFkhWy'
'IwLOI6TXUB_5CT13S8JvknxLxBaF8ANPK9FSOPD8tYu')
desalter = decrypter.decrypt(ser=ciphersalt)
desalter = decrypter.decrypt(qb64=ciphersalt)
assert desalter.qb64b == saltqb64b
assert desalter.code == MtrDex.Salt_128

Expand All @@ -920,7 +920,7 @@ def test_decrypter():
assert decrypter.raw == prikey

# decrypt ciphersalt
desalter = decrypter.decrypt(ser=saltcipher.qb64b)
desalter = decrypter.decrypt(qb64=saltcipher.qb64b)
assert desalter.qb64b == saltqb64b
assert desalter.code == MtrDex.Salt_128

Expand Down

0 comments on commit 359d61a

Please sign in to comment.