1 require "duktape" 2 require "socket" 3 4 def readFramed(socket) 5 inputLen = socket.read_bytes(UInt32, IO::ByteFormat::BigEndian); 6 7 input = Bytes.new(inputLen); 8 socket.read(input); 9 10 return(input) 11 end 12 13 def writeFramed(socket, message) 14 socket.write_bytes(UInt32.new(message.size), IO::ByteFormat::BigEndian); 15 socket.write(message); 16 17 return(message.size); 18 end 19 20 def pushArray(ctx, data) 21 arrIdx = ctx.push_array(); 22 dataLen = data.size 23 24 idx = UInt32.new(0); 25 while idx < dataLen 26 ctx.push_uint(data[idx]); 27 ctx.put_prop_index(arrIdx, idx); 28 idx += 1; 29 end 30 31 return(nil); 32 end 33 34 def getArray(ctx, stackIndex) 35 dataLen = ctx.get_length(stackIndex); 36 data = Bytes.new(dataLen); 37 38 idx = UInt32.new(0); 39 while idx < dataLen 40 ctx.get_prop_index(stackIndex, idx); 41 data[idx] = UInt8.new(ctx.get_uint(-1)); 42 ctx.pop(); 43 idx += 1; 44 end 45 46 return(data); 47 end 48 49 def initJS() 50 ctx = Duktape::Context.new(); 51 ctx.eval_file_noresult!("chrome-emu.js"); 52 ctx.eval_file_noresult!("ssh-agent-noasync.js"); 53 54 ctx.push_global_proc("writeFramed", 2) {|ptr| 55 msgCtx = Duktape::Context.new(ptr); 56 socket = Box(UNIXSocket).unbox(msgCtx.get_pointer(0)); 57 data = getArray(msgCtx, 1); 58 59 writeFramed(socket, data); 60 61 0; 62 } 63 64 ctx.push_global_proc("cackeyListCertificates", 0) {|ptr| 65 msgCtx = Duktape::Context.new(ptr); 66 pushArray(msgCtx, Bytes.new(0)); 67 1; 68 } 69 70 ctx.eval! <<-EOF 71 var goog = {DEBUG: true}; 72 cackeySSHAgentFeatures.enabled = true; 73 74 function connection(callback) { 75 this.sender = { 76 id: "pnhechapfaindjhompbnflcldabbghjo" 77 }; 78 this.onMessage = { 79 listeners: [], 80 addListener: function(callback) { 81 this.listeners.push(callback); 82 } 83 }; 84 this.postMessage = function(message) { 85 return(callback(this, message)); 86 }; 87 this.send = function(message) { 88 this.onMessage.listeners.forEach(function(listener) { 89 listener(message); 90 }); 91 }; 92 } 93 94 function handleDataFromAgent(socket, data) { 95 if (!data || !data.type || !data.data) { 96 return; 97 } 98 99 if (data.type != "auth-agent@openssh.com") { 100 return; 101 } 102 103 writeFramed(socket.handle, data.data); 104 } 105 106 function handleDataFromSocket(socket, data) { 107 socket.send({ 108 type: "auth-agent@openssh.com", 109 data: data 110 }); 111 } 112 EOF 113 114 return(ctx); 115 end 116 117 def incomingConnection(socket) 118 # Createa a JavaScript instance to deal with this socket 119 ctx = initJS(); 120 121 # Create a socket handle on the JavaScript side that 122 # corresponds to the Crystal handle 123 ctx.eval!("var socket = new connection(handleDataFromAgent);"); 124 125 # Link the Crystal handle to the JavaScript handle 126 ctx.get_global_string("socket"); 127 ctx.push_pointer(Box.box(socket)); 128 ctx.put_prop_string(-2, "handle"); 129 130 # Notify the agent that we have received a connection 131 ctx.eval!("chrome.runtime.externalConnect(socket);"); 132 133 while true 134 begin 135 # Read a packet from the peer 136 input = readFramed(socket); 137 138 # Send that packet to the agent 139 ctx.get_global_string("handleDataFromSocket"); 140 ctx.get_global_string("socket"); 141 pushArray(ctx, input); 142 ctx.call(2); 143 if ctx.is_error(-1) 144 puts("Error: #{ctx.json_encode(-1)}"); 145 break 146 end 147 148 ctx.pop(); 149 rescue 150 break 151 end 152 end 153 154 socket.close(); 155 end 156 157 listener = UNIXServer.new("./agent"); 158 while socket = listener.accept? 159 spawn incomingConnection(socket); 160 end |