1from stem.util import ed25519 2 3from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey 4from cryptography.hazmat.primitives import serialization 5 6from onionbalance.hs_v3.ext import ed25519_exts_ref 7from onionbalance.hs_v3.ext import slow_ed25519 8 9def load_tor_key_from_disk(key_bytes): 10 """ 11 Load a private identity key from little-t-tor. 12 """ 13 # Verify header 14 if (key_bytes[:29] != b'== ed25519v1-secret: type0 =='): 15 raise ValueError("Tor key does not start with Tor header") 16 17 expanded_sk = key_bytes[32:] 18 19 # The rest should be 64 bytes (a,h): 20 # 32 bytes for secret scalar 'a' 21 # 32 bytes for PRF key 'h' 22 if (len(expanded_sk) != 64): 23 raise ValueError("Tor private key has the wrong length") 24 25 return TorEd25519PrivateKey(expanded_sk) 26 27def _blinded_sign_with_tor_key(msg, identity_key, blinded_key, blinding_nonce): 28 """ 29 This is identical to stem's hidden_service.py:_blinded_sign() but takes an 30 extended private key (i.e. in tor format) as its argument, instead of the 31 standard format that hazmat does. It basically omits the "extended the key" 32 step and does everything else the same. 33 """ 34 identity_key_bytes = identity_key.private_bytes( 35 encoding = serialization.Encoding.Raw, 36 format = serialization.PrivateFormat.Raw, 37 encryption_algorithm = serialization.NoEncryption(), 38 ) 39 40 # blind the ESK with this nonce 41 esk = identity_key_bytes 42 43 mult = 2 ** (ed25519.b - 2) + sum(2 ** i * ed25519.bit(blinding_nonce, i) for i in range(3, ed25519.b - 2)) 44 s = ed25519.decodeint(esk[:32]) 45 s_prime = (s * mult) % ed25519.l 46 k = esk[32:] 47 k_prime = ed25519.H(b'Derive temporary signing key hash input' + k)[:32] 48 blinded_esk = ed25519.encodeint(s_prime) + k_prime 49 50 # finally, sign the message 51 52 a = ed25519.decodeint(blinded_esk[:32]) 53 r = ed25519.Hint(b''.join([blinded_esk[i:i + 1] for i in range(ed25519.b // 8, ed25519.b // 4)]) + msg) 54 R = ed25519.scalarmult(ed25519.B, r) 55 S = (r + ed25519.Hint(ed25519.encodepoint(R) + blinded_key + msg) * a) % ed25519.l 56 57 return ed25519.encodepoint(R) + ed25519.encodeint(S) 58 59""" 60Tor ed25519 keys 61 62Expose classes for ed25519 keys in Tor's extended key format which can mimic 63the hazmat ed25519 public/private key classes, so that we can use them 64interchangeably in stem. 65 66Tor uses the "extended" (a,h) format for its private keys, whereas hazmat uses 67the "standard" (seed,A) format: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ 68 69Since you can't go from the "extended" format to the "standard" format, we 70created these wrappers that act exactly like hazmat keys when it comes to their 71interface. 72""" 73 74 75class TorEd25519PrivateKey(object): 76 """ 77 Represents the private part of a blinded ed25519 key of an onion service 78 and should expose a public_key() method and a sign() method. 79 """ 80 def __init__(self, expanded_sk): 81 self.priv_key = expanded_sk 82 self.pub_key_bytes = ed25519_exts_ref.publickeyFromESK(self.priv_key) 83 self.pub_key = TorEd25519PublicKey(self.pub_key_bytes) 84 85 def public_key(self): 86 return self.pub_key 87 88 def private_bytes(self, encoding=None, format=None, encryption_algorithm=None): 89 return self.priv_key 90 91 def sign(self, msg): 92 return ed25519_exts_ref.signatureWithESK(msg, self.priv_key, self.pub_key_bytes) 93 94 @property 95 def __class__(self): 96 """ 97 This is an epic hack to make this class look like a hazmat ed25519 public 98 key in the eyes of stem: 99 https://github.com/asn-d6/onionbalance/issues/10#issuecomment-610425916 100 101 The __class_ attribute is what's being used by unittest.mock and the C 102 API to trick isinstance() checks, so as long as stem uses isinstance() 103 this is gonna work: 104 https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.__class__ 105 https://docs.python.org/3/c-api/object.html#c.PyObject_IsInstance 106 """ 107 return Ed25519PrivateKey 108 109class TorEd25519PublicKey(object): 110 """ 111 Represents the public blinded ed25519 key of an onion service and should 112 expose a public_bytes() method and a verify() method. 113 """ 114 def __init__(self, public_key): 115 self.public_key = public_key 116 117 def public_bytes(self, encoding=None, format=None): 118 return self.public_key 119 120 def verify(self, signature, message): 121 """ 122 raises exception if sig not valid 123 """ 124 slow_ed25519.checkvalid(signature, message, self.public_key) 125 126 @property 127 def __class__(self): 128 return Ed25519PublicKey 129