Skip to content

Instantly share code, notes, and snippets.

@dnet
Created March 13, 2021 22:46
Show Gist options
  • Save dnet/c841003ee3aae1985aa17f523310fed1 to your computer and use it in GitHub Desktop.
Save dnet/c841003ee3aae1985aa17f523310fed1 to your computer and use it in GitHub Desktop.
Quick and dirty OpenSSH agent to OpenPGPpy bridge
#!/usr/bin/env python3
import base64
import getpass
import os
import socket
import struct
import sys
import OpenPGPpy # requires https://github.com/dnet/OpenPGPpy/commit/d97a2184a1c1e40599ad1db30b06d33bb7178e59
server_address = './ssh.sock'
SSH_AGENTC_REQUEST_IDENTITIES = 11
SSH_AGENT_IDENTITIES_ANSWER = 12
SSH_AGENTC_SIGN_REQUEST = 13
SSH_AGENT_SIGN_RESPONSE = 14
ALGO = b"ssh-ed25519"
try:
os.unlink(server_address)
except OSError:
if os.path.exists(server_address):
raise
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(server_address)
sock.listen(1)
while True:
connection, client_address = sock.accept()
try:
while True:
msglen = connection.recv(4)
if not msglen:
continue
print(repr(msglen))
msg = connection.recv(struct.unpack('>I', msglen)[0])
cmd = msg[0]
print(f'cmd = {cmd}')
if cmd == SSH_AGENTC_REQUEST_IDENTITIES:
with open('dnet-yubikey-openpgp2.pub', 'rb') as f:
keyblob = base64.b64decode(f.read().split(b' ')[1].strip())
response = b''.join((
bytes([SSH_AGENT_IDENTITIES_ANSWER]),
struct.pack('>I', 1),
struct.pack('>I', len(keyblob)),
keyblob,
struct.pack('>I', 0), # no comment
))
connection.sendall(struct.pack('>I', len(response)) + response)
elif cmd == SSH_AGENTC_SIGN_REQUEST:
(keylen,) = struct.unpack('>I', msg[1:5])
(datalen,) = struct.unpack('>I', msg[(1+4+keylen):][:4])
data = msg[(1+4+keylen+4):][:datalen]
assert 1 + 4 + datalen + 4 + keylen + 4 == len(msg)
mydevice = OpenPGPpy.OpenPGPcard()
mydevice.verify_pin(1, getpass.getpass())
ed25519sig = mydevice.sign(data)
signature = b''.join((
struct.pack('>I', len(ALGO)),
ALGO,
struct.pack('>I', len(ed25519sig)),
ed25519sig,
))
response = b''.join((
bytes([SSH_AGENT_SIGN_RESPONSE]),
struct.pack('>I', len(signature)),
signature,
))
connection.sendall(struct.pack('>I', len(response)) + response)
else:
raise ValueError(f'Unsupported command {cmd}')
finally:
connection.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment