71 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			71 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from base64 import b64decode, b64encode
 | |
| from hashlib import sha3_224, sha1
 | |
| from functools import cache
 | |
| import hmac
 | |
| 
 | |
| from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
 | |
| from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, PublicFormat, NoEncryption
 | |
| 
 | |
| 
 | |
| @cache
 | |
| def generate_ed25519_key_pair(secret):
 | |
|     privkey_bytes = Ed25519PrivateKey.from_private_bytes(secret)
 | |
| 
 | |
|     # PRIVATE KEY
 | |
| 
 | |
|     nondeterministic_privatekey = privkey_bytes.private_bytes(
 | |
|         encoding=Encoding.PEM,
 | |
|         format=PrivateFormat.OpenSSH,
 | |
|         encryption_algorithm=NoEncryption()
 | |
|     ).decode()
 | |
| 
 | |
|     # get relevant lines from string
 | |
|     nondeterministic_bytes = b64decode(''.join(nondeterministic_privatekey.split('\n')[1:-2]))
 | |
| 
 | |
|     # sanity check
 | |
|     if nondeterministic_bytes[98:102] != nondeterministic_bytes[102:106]:
 | |
|         raise Exception("checksums should be the same: whats going on here?")
 | |
| 
 | |
|     # replace random bytes with deterministic values
 | |
|     random_bytes = sha3_224(secret).digest()[0:4]
 | |
|     deterministic_bytes = nondeterministic_bytes[:98] + random_bytes + random_bytes + nondeterministic_bytes[106:]
 | |
| 
 | |
|     # reassemble file
 | |
|     deterministic_privatekey = '\n'.join([
 | |
|         '-----BEGIN OPENSSH PRIVATE KEY-----',
 | |
|         b64encode(deterministic_bytes).decode(),
 | |
|         '-----END OPENSSH PRIVATE KEY-----',
 | |
|     ])
 | |
| 
 | |
|     # PUBLIC KEY
 | |
| 
 | |
|     public_key = privkey_bytes.public_key().public_bytes(
 | |
|         encoding=Encoding.OpenSSH,
 | |
|         format=PublicFormat.OpenSSH,
 | |
|     ).decode()
 | |
| 
 | |
|     # RETURN
 | |
| 
 | |
|     return (deterministic_privatekey, public_key)
 | |
| 
 | |
| 
 | |
| # https://www.fragmentationneeded.net/2017/10/ssh-hashknownhosts-file-format.html
 | |
| # test this:
 | |
| # - `ssh-keyscan -H 10.0.0.5`
 | |
| # - take the salt from the ssh-ed25519 entry (first field after '|1|')
 | |
| # - `bw debug -c 'repo.libs.ssh.known_hosts_entry_for(repo.get_node(<node with hostname 10.0.0.5>), <salt from ssh-keygen>)'`
 | |
| @cache
 | |
| def known_hosts_entry_for(node_id, hostnames, pubkey, test_salt=None):
 | |
|     lines = set()
 | |
| 
 | |
|     for hostname in hostnames:
 | |
|         if test_salt:
 | |
|             salt = b64decode(test_salt)
 | |
|         else:
 | |
|             salt = sha1((node_id + hostname).encode()).digest()
 | |
| 
 | |
|         hash = hmac.new(salt, hostname.encode(), sha1).digest()
 | |
| 
 | |
|         lines.add(f'|1|{b64encode(salt).decode()}|{b64encode(hash).decode()} {" ".join(pubkey.split()[:2])}')
 | |
| 
 | |
|     return lines
 |