make e2e work with py3
This commit is contained in:
parent
8a6ed2c04e
commit
8ca7281480
|
@ -28,9 +28,9 @@ from hashlib import sha256 as SHA256
|
||||||
# convert a large integer to a big-endian bitstring
|
# convert a large integer to a big-endian bitstring
|
||||||
def encode_mpi(n):
|
def encode_mpi(n):
|
||||||
if n >= 256:
|
if n >= 256:
|
||||||
return encode_mpi(n / 256) + chr(n % 256)
|
return encode_mpi(n // 256) + bytes([n % 256])
|
||||||
else:
|
else:
|
||||||
return chr(n)
|
return bytes([n])
|
||||||
|
|
||||||
# convert a large integer to a big-endian bitstring, padded with \x00s to
|
# convert a large integer to a big-endian bitstring, padded with \x00s to
|
||||||
# a multiple of 16 bytes
|
# a multiple of 16 bytes
|
||||||
|
@ -56,7 +56,7 @@ def decode_mpi(s):
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return 256 * decode_mpi(s[:-1]) + ord(s[-1])
|
return 256 * decode_mpi(s[:-1]) + s[-1]
|
||||||
|
|
||||||
def sha256(string):
|
def sha256(string):
|
||||||
sh = SHA256()
|
sh = SHA256()
|
||||||
|
@ -66,13 +66,13 @@ def sha256(string):
|
||||||
base28_chr = "acdefghikmopqruvwxy123456789"
|
base28_chr = "acdefghikmopqruvwxy123456789"
|
||||||
|
|
||||||
def sas_28x5(m_a, form_b):
|
def sas_28x5(m_a, form_b):
|
||||||
sha = sha256(m_a + form_b + 'Short Authentication String')
|
sha = sha256(m_a + form_b + b'Short Authentication String')
|
||||||
lsb24 = decode_mpi(sha[-3:])
|
lsb24 = decode_mpi(sha[-3:])
|
||||||
return base28(lsb24)
|
return base28(lsb24)
|
||||||
|
|
||||||
def base28(n):
|
def base28(n):
|
||||||
if n >= 28:
|
if n >= 28:
|
||||||
return base28(n / 28) + base28_chr[n % 28]
|
return base28(n // 28) + base28_chr[n % 28]
|
||||||
else:
|
else:
|
||||||
return base28_chr[n]
|
return base28_chr[n]
|
||||||
|
|
||||||
|
@ -147,6 +147,5 @@ def powmod(base, exp, mod):
|
||||||
result = (result * square) % mod
|
result = (result * square) % mod
|
||||||
|
|
||||||
square = (square * square) % mod
|
square = (square * square) % mod
|
||||||
exp /= 2
|
exp //= 2
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -224,6 +224,7 @@ def hex_to_decimal(stripee):
|
||||||
if not stripee:
|
if not stripee:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return int(stripee.translate(all_ascii, string.whitespace), 16)
|
return int(stripee.translate(all_ascii).translate(str.maketrans("", "",
|
||||||
|
string.whitespace)), 16)
|
||||||
|
|
||||||
primes = map(hex_to_decimal, hex_primes)
|
primes = list(map(hex_to_decimal, hex_primes))
|
||||||
|
|
|
@ -410,13 +410,13 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
|
|
||||||
c = stanza.NT.c
|
c = stanza.NT.c
|
||||||
c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
|
c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
|
||||||
c.NT.data = base64.b64encode(m_final)
|
c.NT.data = base64.b64encode(m_final).decode('utf-8')
|
||||||
|
|
||||||
# FIXME check for rekey request, handle <key/> elements
|
# FIXME check for rekey request, handle <key/> elements
|
||||||
|
|
||||||
m_content = ''.join(map(str, c.getChildren()))
|
m_content = (''.join(map(str, c.getChildren()))).encode('utf-8')
|
||||||
c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
|
c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
|
||||||
crypto.encode_mpi(old_en_counter)))
|
crypto.encode_mpi(old_en_counter))).decode('utf-8')
|
||||||
|
|
||||||
msgtxt = '[This is part of an encrypted session. ' \
|
msgtxt = '[This is part of an encrypted session. ' \
|
||||||
'If you see this message, something went wrong.]'
|
'If you see this message, something went wrong.]'
|
||||||
|
@ -436,14 +436,14 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
return HMAC(key, content, self.hash_alg).digest()
|
return HMAC(key, content, self.hash_alg).digest()
|
||||||
|
|
||||||
def generate_initiator_keys(self, k):
|
def generate_initiator_keys(self, k):
|
||||||
return (self.hmac(k, 'Initiator Cipher Key'),
|
return (self.hmac(k, b'Initiator Cipher Key'),
|
||||||
self.hmac(k, 'Initiator MAC Key'),
|
self.hmac(k, b'Initiator MAC Key'),
|
||||||
self.hmac(k, 'Initiator SIGMA Key'))
|
self.hmac(k, b'Initiator SIGMA Key'))
|
||||||
|
|
||||||
def generate_responder_keys(self, k):
|
def generate_responder_keys(self, k):
|
||||||
return (self.hmac(k, 'Responder Cipher Key'),
|
return (self.hmac(k, b'Responder Cipher Key'),
|
||||||
self.hmac(k, 'Responder MAC Key'),
|
self.hmac(k, b'Responder MAC Key'),
|
||||||
self.hmac(k, 'Responder SIGMA Key'))
|
self.hmac(k, b'Responder SIGMA Key'))
|
||||||
|
|
||||||
def compress(self, plaintext):
|
def compress(self, plaintext):
|
||||||
if self.compression is None:
|
if self.compression is None:
|
||||||
|
@ -473,6 +473,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
|
|
||||||
# contents of <c>, minus <mac>, minus whitespace
|
# contents of <c>, minus <mac>, minus whitespace
|
||||||
macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac')
|
macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac')
|
||||||
|
macable = macable.encode('utf-8')
|
||||||
|
|
||||||
received_mac = base64.b64decode(c.getTagData('mac'))
|
received_mac = base64.b64decode(c.getTagData('mac'))
|
||||||
calculated_mac = self.hmac(self.km_o, macable + \
|
calculated_mac = self.hmac(self.km_o, macable + \
|
||||||
|
@ -483,7 +484,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
|
|
||||||
m_final = base64.b64decode(c.getTagData('data'))
|
m_final = base64.b64decode(c.getTagData('data'))
|
||||||
m_compressed = self.decrypt(m_final)
|
m_compressed = self.decrypt(m_final)
|
||||||
plaintext = self.decompress(m_compressed)
|
plaintext = self.decompress(m_compressed).decode('utf-8')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = nbxmpp.Node(node='<node>' + plaintext + '</node>')
|
parsed = nbxmpp.Node(node='<node>' + plaintext + '</node>')
|
||||||
|
@ -536,7 +537,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
if i_o == 'a' and self.sas_algs == 'sas28x5':
|
if i_o == 'a' and self.sas_algs == 'sas28x5':
|
||||||
# we don't need to calculate this if there's a verified retained secret
|
# we don't need to calculate this if there's a verified retained secret
|
||||||
# (but we do anyways)
|
# (but we do anyways)
|
||||||
self.sas = crypto.sas_28x5(m_o, self.form_s)
|
self.sas = crypto.sas_28x5(m_o, self.form_s.encode('utf-8'))
|
||||||
|
|
||||||
if self.negotiated['recv_pubkey']:
|
if self.negotiated['recv_pubkey']:
|
||||||
plaintext = self.decrypt(id_o)
|
plaintext = self.decrypt(id_o)
|
||||||
|
@ -564,18 +565,18 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), )
|
signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), )
|
||||||
else:
|
else:
|
||||||
mac_o = self.decrypt(id_o)
|
mac_o = self.decrypt(id_o)
|
||||||
pubkey_o = ''
|
pubkey_o = b''
|
||||||
|
|
||||||
c7l_form = self.c7lize_mac_id(form)
|
c7l_form = self.c7lize_mac_id(form)
|
||||||
|
|
||||||
content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o
|
content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o
|
||||||
|
|
||||||
if sigmai:
|
if sigmai:
|
||||||
self.form_o = c7l_form
|
self.form_o = c7l_form.encode('utf-8')
|
||||||
content += self.form_o
|
content += self.form_o
|
||||||
else:
|
else:
|
||||||
form_o2 = c7l_form
|
form_o2 = c7l_form.encode('utf-8')
|
||||||
content += self.form_o + form_o2
|
content += self.form_o.encode('utf-8') + form_o2
|
||||||
|
|
||||||
mac_o_calculated = self.hmac(self.ks_o, content)
|
mac_o_calculated = self.hmac(self.ks_o, content)
|
||||||
|
|
||||||
|
@ -598,18 +599,18 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in
|
cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in
|
||||||
fields]
|
fields]
|
||||||
|
|
||||||
pubkey_s = '<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"'
|
pubkey_s = b'<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"'
|
||||||
'><Modulus>%s</Modulus><Exponent>%s</Exponent></RSAKeyValue>' % \
|
'><Modulus>%s</Modulus><Exponent>%s</Exponent></RSAKeyValue>' % \
|
||||||
tuple(cb_fields)
|
tuple(cb_fields)
|
||||||
else:
|
else:
|
||||||
pubkey_s = ''
|
pubkey_s = b''
|
||||||
|
|
||||||
form_s2 = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
|
form_s2 = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
|
||||||
in form.getChildren())
|
in form.getChildren())
|
||||||
|
|
||||||
old_c_s = self.c_s
|
old_c_s = self.c_s
|
||||||
content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \
|
content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \
|
||||||
self.form_s + form_s2
|
self.form_s.encode('utf-8') + form_s2.encode('utf-8')
|
||||||
|
|
||||||
mac_s = self.hmac(self.ks_s, content)
|
mac_s = self.hmac(self.ks_s, content)
|
||||||
|
|
||||||
|
@ -632,14 +633,16 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5':
|
if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5':
|
||||||
# we're alice; check for a retained secret
|
# we're alice; check for a retained secret
|
||||||
# if none exists, prompt the user with the SAS
|
# if none exists, prompt the user with the SAS
|
||||||
self.sas = crypto.sas_28x5(m_s, self.form_o)
|
self.sas = crypto.sas_28x5(m_s, self.form_o.encode('utf-8'))
|
||||||
|
|
||||||
if self.sigmai:
|
if self.sigmai:
|
||||||
# FIXME save retained secret?
|
# FIXME save retained secret?
|
||||||
self.check_identity(tuple)
|
self.check_identity(tuple)
|
||||||
|
|
||||||
return (nbxmpp.DataField(name='identity', value=base64.b64encode(id_s)),
|
return (nbxmpp.DataField(name='identity',
|
||||||
nbxmpp.DataField(name='mac', value=base64.b64encode(m_s)))
|
value=base64.b64encode(id_s).decode('utf-8')),
|
||||||
|
nbxmpp.DataField(name='mac',
|
||||||
|
value=base64.b64encode(m_s).decode('utf-8')))
|
||||||
|
|
||||||
def negotiate_e2e(self, sigmai):
|
def negotiate_e2e(self, sigmai):
|
||||||
self.negotiated = {}
|
self.negotiated = {}
|
||||||
|
@ -695,7 +698,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
self.n_s = crypto.generate_nonce()
|
self.n_s = crypto.generate_nonce()
|
||||||
|
|
||||||
x.addChild(node=nbxmpp.DataField(name='my_nonce',
|
x.addChild(node=nbxmpp.DataField(name='my_nonce',
|
||||||
value=base64.b64encode(self.n_s), typ='hidden'))
|
value=base64.b64encode(self.n_s).decode('utf-8'), typ='hidden'))
|
||||||
|
|
||||||
modp_options = [ int(g) for g in gajim.config.get('esession_modp').split(
|
modp_options = [ int(g) for g in gajim.config.get('esession_modp').split(
|
||||||
',') ]
|
',') ]
|
||||||
|
@ -845,7 +848,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
'nonce': self.n_o}
|
'nonce': self.n_o}
|
||||||
|
|
||||||
for name in to_add:
|
for name in to_add:
|
||||||
b64ed = base64.b64encode(to_add[name])
|
b64ed = base64.b64encode(to_add[name]).decode('utf-8')
|
||||||
x.addChild(node=nbxmpp.DataField(name=name, value=b64ed))
|
x.addChild(node=nbxmpp.DataField(name=name, value=b64ed))
|
||||||
|
|
||||||
self.form_o = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
|
self.form_o = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
|
||||||
|
@ -935,7 +938,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
value='urn:xmpp:ssn'))
|
value='urn:xmpp:ssn'))
|
||||||
result.addChild(node=nbxmpp.DataField(name='accept', value='1'))
|
result.addChild(node=nbxmpp.DataField(name='accept', value='1'))
|
||||||
result.addChild(node=nbxmpp.DataField(name='nonce',
|
result.addChild(node=nbxmpp.DataField(name='nonce',
|
||||||
value=base64.b64encode(self.n_o)))
|
value=base64.b64encode(self.n_o).decode('utf-8')))
|
||||||
|
|
||||||
self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k)
|
self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k)
|
||||||
|
|
||||||
|
@ -952,11 +955,12 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
rshash_size = self.hash_alg().digest_size
|
rshash_size = self.hash_alg().digest_size
|
||||||
rshashes.append(crypto.random_bytes(rshash_size))
|
rshashes.append(crypto.random_bytes(rshash_size))
|
||||||
|
|
||||||
rshashes = [base64.b64encode(rshash) for rshash in rshashes]
|
rshashes = [base64.b64encode(rshash).decode('utf-8') for rshash in \
|
||||||
|
rshashes]
|
||||||
result.addChild(node=nbxmpp.DataField(name='rshashes',
|
result.addChild(node=nbxmpp.DataField(name='rshashes',
|
||||||
value=rshashes))
|
value=rshashes))
|
||||||
result.addChild(node=nbxmpp.DataField(name='dhkeys',
|
result.addChild(node=nbxmpp.DataField(name='dhkeys',
|
||||||
value=base64.b64encode(crypto.encode_mpi(e))))
|
value=base64.b64encode(crypto.encode_mpi(e)).decode('utf-8')))
|
||||||
|
|
||||||
self.form_o = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) \
|
self.form_o = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) \
|
||||||
for el in form.getChildren())
|
for el in form.getChildren())
|
||||||
|
@ -1006,7 +1010,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
self.verify_identity(form, e, False, 'a')
|
self.verify_identity(form, e, False, 'a')
|
||||||
|
|
||||||
# 4.5.4 generating bob's final session keys
|
# 4.5.4 generating bob's final session keys
|
||||||
srs = ''
|
srs = b''
|
||||||
|
|
||||||
srses = secrets.secrets().retained_secrets(self.conn.name,
|
srses = secrets.secrets().retained_secrets(self.conn.name,
|
||||||
self.jid.getStripped())
|
self.jid.getStripped())
|
||||||
|
@ -1021,7 +1025,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
|
|
||||||
# other shared secret
|
# other shared secret
|
||||||
# (we're not using one)
|
# (we're not using one)
|
||||||
oss = ''
|
oss = b''
|
||||||
|
|
||||||
k = crypto.sha256(k + srs + oss)
|
k = crypto.sha256(k + srs + oss)
|
||||||
|
|
||||||
|
@ -1030,16 +1034,16 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
|
|
||||||
# 4.5.5
|
# 4.5.5
|
||||||
if srs:
|
if srs:
|
||||||
srshash = self.hmac(srs, 'Shared Retained Secret')
|
srshash = self.hmac(srs, b'Shared Retained Secret')
|
||||||
else:
|
else:
|
||||||
srshash = crypto.random_bytes(32)
|
srshash = crypto.random_bytes(32)
|
||||||
|
|
||||||
x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
|
x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
|
||||||
value='urn:xmpp:ssn'))
|
value='urn:xmpp:ssn'))
|
||||||
x.addChild(node=nbxmpp.DataField(name='nonce', value=base64.b64encode(
|
x.addChild(node=nbxmpp.DataField(name='nonce', value=base64.b64encode(
|
||||||
self.n_o)))
|
self.n_o).decode('utf-8')))
|
||||||
x.addChild(node=nbxmpp.DataField(name='srshash', value=base64.b64encode(
|
x.addChild(node=nbxmpp.DataField(name='srshash', value=base64.b64encode(
|
||||||
srshash)))
|
srshash).decode('utf-8')))
|
||||||
|
|
||||||
for datafield in self.make_identity(x, self.d):
|
for datafield in self.make_identity(x, self.d):
|
||||||
x.addChild(node=datafield)
|
x.addChild(node=datafield)
|
||||||
|
@ -1062,7 +1066,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
self.stop_archiving_for_session()
|
self.stop_archiving_for_session()
|
||||||
|
|
||||||
def final_steps_alice(self, form):
|
def final_steps_alice(self, form):
|
||||||
srs = ''
|
srs = b''
|
||||||
srses = secrets.secrets().retained_secrets(self.conn.name,
|
srses = secrets.secrets().retained_secrets(self.conn.name,
|
||||||
self.jid.getStripped())
|
self.jid.getStripped())
|
||||||
|
|
||||||
|
@ -1073,11 +1077,11 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
|
|
||||||
for s in srses:
|
for s in srses:
|
||||||
secret = s[0]
|
secret = s[0]
|
||||||
if self.hmac(secret, 'Shared Retained Secret') == srshash:
|
if self.hmac(secret, b'Shared Retained Secret') == srshash:
|
||||||
srs = secret
|
srs = secret
|
||||||
break
|
break
|
||||||
|
|
||||||
oss = ''
|
oss = b''
|
||||||
k = crypto.sha256(self.k + srs + oss)
|
k = crypto.sha256(self.k + srs + oss)
|
||||||
del self.k
|
del self.k
|
||||||
|
|
||||||
|
@ -1109,7 +1113,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
the remote party's identity. Set up callbacks for when the identity has
|
the remote party's identity. Set up callbacks for when the identity has
|
||||||
been verified
|
been verified
|
||||||
"""
|
"""
|
||||||
new_srs = self.hmac(k, 'New Retained Secret')
|
new_srs = self.hmac(k, b'New Retained Secret')
|
||||||
self.srs = new_srs
|
self.srs = new_srs
|
||||||
|
|
||||||
account = self.conn.name
|
account = self.conn.name
|
||||||
|
@ -1155,11 +1159,11 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
|
||||||
self.es[modp] = e
|
self.es[modp] = e
|
||||||
|
|
||||||
if sigmai:
|
if sigmai:
|
||||||
dhs.append(base64.b64encode(crypto.encode_mpi(e)))
|
dhs.append(base64.b64encode(crypto.encode_mpi(e)).decode('utf-8'))
|
||||||
name = 'dhkeys'
|
name = 'dhkeys'
|
||||||
else:
|
else:
|
||||||
He = crypto.sha256(crypto.encode_mpi(e))
|
He = crypto.sha256(crypto.encode_mpi(e))
|
||||||
dhs.append(base64.b64encode(He))
|
dhs.append(base64.b64encode(He).decode('utf-8'))
|
||||||
name = 'dhhashes'
|
name = 'dhhashes'
|
||||||
|
|
||||||
return nbxmpp.DataField(name=name, typ='hidden', value=dhs)
|
return nbxmpp.DataField(name=name, typ='hidden', value=dhs)
|
||||||
|
|
|
@ -42,7 +42,7 @@ class Secrets:
|
||||||
raise exceptions.Cancelled
|
raise exceptions.Cancelled
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
f = open(secrets_filename, 'w')
|
f = open(secrets_filename, 'wb')
|
||||||
pickle.dump(self, f)
|
pickle.dump(self, f)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
@ -95,14 +95,22 @@ class Secrets:
|
||||||
return pk
|
return pk
|
||||||
|
|
||||||
def load_secrets(filename):
|
def load_secrets(filename):
|
||||||
f = open(filename, 'r')
|
f = open(filename, 'rb')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
secrets = pickle.load(f)
|
secrets = pickle.load(f, encoding='latin1')
|
||||||
|
# We do that to be able to read files written in py2
|
||||||
|
for acct in secrets.srs:
|
||||||
|
for jid in secrets.srs[acct]:
|
||||||
|
for (secret, verified) in list(secrets.srs[acct][jid]):
|
||||||
|
if type(secret) is str:
|
||||||
|
secrets.srs[acct][jid].remove((secret, verified))
|
||||||
|
secrets.srs[acct][jid].append((secret.encode('latin1'), verified))
|
||||||
except (KeyError, EOFError):
|
except (KeyError, EOFError):
|
||||||
f.close()
|
f.close()
|
||||||
secrets = Secrets(filename)
|
secrets = Secrets(filename)
|
||||||
|
|
||||||
|
f.close()
|
||||||
return secrets
|
return secrets
|
||||||
|
|
||||||
def secrets():
|
def secrets():
|
||||||
|
|
Loading…
Reference in New Issue