/* * Copyright 2006-2015 WebPKI.org (http://webpki.org). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.webpki.tapconnect; import java.awt.Container; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Toolkit; import java.awt.Insets; import java.awt.event.WindowEvent; import java.awt.event.WindowAdapter; import java.awt.image.BufferedImage; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.Vector; import java.util.concurrent.Executors; import java.util.logging.FileHandler; import java.util.logging.Logger; import java.util.logging.Level; import java.util.logging.SimpleFormatter; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import org.webpki.json.JSONObjectReader; import org.webpki.json.JSONObjectWriter; import org.webpki.json.JSONOutputFormats; import org.webpki.json.JSONParser; import org.webpki.util.ArrayUtil; import static org.webpki.tapconnect.TapConnectProperties.*; // A very prototype-ish NFCLauncher using HTTP/WiFi rather than BLE but // it at least works for demonstrating the navigator.tapConnect() concept :-) class StdoutJSONPipe { String writeJSONObject (JSONObjectWriter ow) throws IOException { byte[] utf8 = ow.serializeJSONObject(JSONOutputFormats.NORMALIZED); int l = utf8.length; // Code only works for little-endian machines // Network order, heard of that Google? byte[] blob = new byte[l + 4]; blob[0] = (byte) l; blob[1] = (byte) (l >>> 8); blob[2] = (byte) (l >>> 16); blob[3] = (byte) (l >>> 24); for (int i = 0; i < l; i++) { blob[4 + i] = utf8[i]; } System.out.write(blob); return new String(utf8, "UTF-8"); } } class StdinJSONPipe { String jsonString; DataInputStream dis; StdinJSONPipe() { dis = new DataInputStream(System.in); } JSONObjectReader readJSONObject() throws IOException { byte[] byteBuffer = new byte[4]; dis.readFully(byteBuffer, 0, 4); // Code only works for little-endian machines // Network order, heard of that Google? int l = (byteBuffer[3]) << 24 | (byteBuffer[2] & 0xff) << 16 | (byteBuffer[1] & 0xff) << 8 | (byteBuffer[0] & 0xff); if (l > 100000) System.exit(3); byte[] utf8 = new byte[l]; dis.readFully(utf8); jsonString = new String(utf8, "UTF-8"); return JSONParser.parse(utf8); } String getJSONString () { return jsonString; } } public class NFCLauncher extends Thread { static Logger logger = Logger.getLogger("MyLog"); static String application; static String invocationUrl; static String optionalData; static String ipAddress; static ImageIcon nfcLogo; JLabel nfcIconLabel; static ImageIcon connectedIcon; StdinJSONPipe stdin = new StdinJSONPipe(); StdoutJSONPipe stdout = new StdoutJSONPipe(); boolean initMode = true; class Synchronizer { boolean touched; boolean timeout_flag; JSONObjectReader json; synchronized boolean perform(int timeout) { while (!touched && !timeout_flag) { try { wait(timeout); } catch (InterruptedException e) { return false; } timeout_flag = true; } return touched; } synchronized void haveData4You(JSONObjectReader json) { this.json = json; touched = true; notify(); } } Synchronizer synchronizer; synchronized Synchronizer getSynchronizer (boolean inserter) { if (inserter && synchronizer != null && synchronizer.json != null) { logger.severe("Overrrun!"); System.exit(3); } if (synchronizer != null && !synchronizer.timeout_flag) { Synchronizer temp = synchronizer; synchronizer = null; return temp; } return synchronizer = new Synchronizer(); } class RequestHandler implements HttpHandler { public void handle(HttpExchange exchange) throws IOException { if (exchange.getRequestMethod().equalsIgnoreCase("POST")) { JSONObjectReader request = JSONParser.parse(ArrayUtil.getByteArrayFromInputStream(exchange.getRequestBody())); JSONObjectWriter response = new JSONObjectWriter(); logger.info(request.toString()); if (request.hasProperty(CONTROL_JSON)) { if (request.getBoolean(CONTROL_JSON)) { Synchronizer sync = getSynchronizer(false); if (sync.perform(BACK_CHANNEL_TIMEOUT / 2)) { response = new JSONObjectWriter(sync.json); } else { response.setBoolean(NOTHING_JSON, true); } } else { stdout.writeJSONObject(new JSONObjectWriter(request.getObject(MESSAGE_JSON))); } } else { if (initMode) { stdout.writeJSONObject(new JSONObjectWriter().setBoolean("@connect@", true)); response.setString(APPLICATION_JSON, application); response.setString(INVOCATION_URL_JSON, invocationUrl); response.setString(OPTIONAL_DATA_JSON, optionalData); initMode = false; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { nfcIconLabel.setIcon(connectedIcon); } }); } else { System.exit(3); } } Headers responseHeaders = exchange.getResponseHeaders(); responseHeaders.set("Content-Type", JSON_CONTENT_TYPE); byte[] json = response.serializeJSONObject(JSONOutputFormats.NORMALIZED); exchange.sendResponseHeaders(200, json.length); OutputStream os = exchange.getResponseBody(); os.write(json); exchange.close(); } } } NFCLauncher(Container pane) { // Start by initializing the HTTP server try { final HttpServer server = HttpServer.create(new InetSocketAddress(HTTP_PORT), 0); server.createContext("/", new RequestHandler()); server.setExecutor(Executors.newCachedThreadPool()); server.start(); logger.info("Server is listening on port " + HTTP_PORT); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { server.stop(0); } }); } catch (Exception e) { logger.log(Level.SEVERE, "HTTP Server error", e); System.exit(3); } // Then initialize the GUI int fontSize = Toolkit.getDefaultToolkit().getScreenResolution() / 7; JLabel urlMessageLabel = new JLabel("Invocation URL:"); Font font = urlMessageLabel.getFont(); boolean macOS = System.getProperty("os.name").toLowerCase().contains("mac"); if (font.getSize() > fontSize || macOS) { fontSize = font.getSize(); } Font stdFont = new Font(font.getFontName(), font.getStyle(), fontSize); int stdInset = fontSize/3; urlMessageLabel.setFont(stdFont); pane.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridwidth = 1; c.anchor = GridBagConstraints.NORTHWEST; c.fill = GridBagConstraints.NONE; c.gridx = 0; c.gridy = 0; c.insets = new Insets(stdInset, stdInset, stdInset, stdInset); pane.add(urlMessageLabel, c); JLabel urlLabel = new JLabel(ipAddress); urlLabel.setFont(stdFont); c.gridy = 1; c.insets = new Insets(0, stdInset * 2, 0, stdInset); pane.add(urlLabel, c); nfcIconLabel = new JLabel(nfcLogo); c.weighty = 1.0; c.gridy = 2; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; c.insets = new Insets(0, fontSize * 3, 0, fontSize * 3); pane.add(nfcIconLabel, c); } @Override public void run() { while (true) { try { JSONObjectReader webdata = stdin.readJSONObject(); // Hanging until there is something getSynchronizer(true).haveData4You(webdata); // Yay! } catch (IOException e) { try { // Final effort telling the world that we are closing for today... getSynchronizer(true).haveData4You(JSONParser.parse(new JSONObjectWriter().setBoolean(CLOSE_JSON, true).toString())); } catch (IOException e1) { } logger.log(Level.SEVERE, "Reading", e); System.exit(3); } } } static BufferedImage getIcon(String name) throws IOException { return ImageIO.read(NFCLauncher.class.getResourceAsStream(name)); } static ImageIcon getImageIcon(String name) throws IOException { return new ImageIcon(ArrayUtil.getByteArrayFromInputStream(NFCLauncher.class.getResourceAsStream(name))); } public static void main(String[] args) { Vector<BufferedImage> icons = new Vector<BufferedImage>(); try { logger.setUseParentHandlers(false); FileHandler fh = new FileHandler(args[0] + File.separator + "logs" + File.separator + "nfc-launcher.log"); logger.addHandler(fh); SimpleFormatter formatter = new SimpleFormatter(); fh.setFormatter(formatter); for (int i = 0; i < args.length; i++) { logger.info("ARG[" + i + "]=" + args[i]); } application = args[1]; invocationUrl = args[2]; optionalData = args[3]; icons.add(getIcon("nfc32.png")); icons.add(getIcon("nfc64.png")); nfcLogo = getImageIcon("nfc-logo-vector.png"); connectedIcon = getImageIcon("loading-gears-animation-3.gif"); Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); int foundAddresses = 0; while (networkInterfaces.hasMoreElements()) { NetworkInterface networkInterface = networkInterfaces.nextElement(); if (networkInterface.isUp() && !networkInterface.isVirtual() && !networkInterface.isLoopback() && networkInterface.getDisplayName().indexOf("VMware") < 0) { // Well.... Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); while (inetAddresses.hasMoreElements()) { InetAddress inetAddress = inetAddresses.nextElement(); if (inetAddress instanceof Inet4Address) { foundAddresses++; ipAddress = "http://" + inetAddress.getHostAddress() + ":" + HTTP_PORT; } } } } if (application.equals("webauth.demo")) { ipAddress = JSONParser.parse(org.webpki.util.Base64URL.decode(optionalData)).getString("callme"); } if (foundAddresses != 1) { throw new IOException("Couldn't determine network interface!'"); } final Process nfc = Runtime.getRuntime().exec(new String[]{args[0] + File.separator + "NFCWriter.exe", ipAddress}); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { nfc.destroy(); } }); } catch (Exception e) { logger.log(Level.SEVERE, "Initialization failed", e); System.exit(3); } JFrame frame = new JFrame("NFC Launcher"); frame.setIconImages(icons); NFCLauncher md = new NFCLauncher(frame.getContentPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setAlwaysOnTop(true); frame.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent event) { System.exit(0); } }); frame.setExtendedState(frame.getExtendedState() | JFrame.ICONIFIED); frame.setVisible(true); md.start(); } }