Add anonid command.
Standalone command which implements MAC address
anonymization. Intended for use in catawampus.
Change-Id: I8195659d2b1c4ee8ee00a0f9bc357e07e88ac030
diff --git a/cmds/Makefile b/cmds/Makefile
index 3d53395..4f7db2b 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -19,6 +19,7 @@
TARGETS=\
$(PORTABLE_TARGETS) \
alivemonitor \
+ anonid \
bsa2bluez \
burnin-flash \
buttonmon \
@@ -255,6 +256,9 @@
--includes --output-file=$@ $<
hostnamelookup.tmp.o: CFLAGS += -Wno-missing-field-initializers
host-hostnamelookup.tmp.o: CFLAGS += -Wno-missing-field-initializers
+anonid: anonid.o
+host-anonid: host-anonid.o
+anonid host-anonid: LIBS += -lcrypto
TESTS = $(wildcard test-*.sh) $(wildcard test-*.py) $(wildcard *_test.py) $(TEST_TARGETS)
diff --git a/cmds/anonid.c b/cmds/anonid.c
new file mode 100644
index 0000000..e7854ad
--- /dev/null
+++ b/cmds/anonid.c
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <openssl/md5.h>
+#include <openssl/hmac.h>
+
+
+const char SOFT[] = "AEIOUY" "V";
+const char HARD[] = "BCDFGHJKLMNPQRSTVWXYZ" "AEIOU";
+const char *consensus_key_file = "/tmp/waveguide/consensus_key";
+#define CONSENSUS_KEY_LEN 16
+uint8_t consensus_key[CONSENSUS_KEY_LEN] = {0};
+#define MAC_ADDR_LEN 17
+
+void default_consensus_key()
+{
+ int fd;
+
+ if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) {
+ ssize_t siz = sizeof(consensus_key);
+ if (read(fd, consensus_key, siz) != siz) {
+ /* https://xkcd.com/221/ */
+ memset(consensus_key, time(NULL), siz);
+ }
+ close(fd);
+ }
+}
+
+/* Read the waveguide consensus_key, if any. Returns 0 if
+ * a key was present, 1 if not or something fails. */
+int get_consensus_key()
+{
+ int fd, rc = 1;
+ uint8_t new_key[sizeof(consensus_key)];
+
+ if ((fd = open(consensus_key_file, O_RDONLY)) < 0) {
+ return 1;
+ }
+
+ if (read(fd, new_key, sizeof(new_key)) == sizeof(new_key)) {
+ memcpy(consensus_key, new_key, sizeof(consensus_key));
+ rc = 0;
+ }
+ close(fd);
+
+ return rc;
+}
+
+/* Given a value from 0..4095, encode it as a cons+vowel+cons sequence. */
+void trigraph(int num, char *out)
+{
+ int ns = sizeof(SOFT) - 1;
+ int nh = sizeof(HARD) - 1;
+ int c1, c2, c3;
+
+ c3 = num % nh;
+ c2 = (num / nh) % ns;
+ c1 = num / nh / ns;
+ out[0] = HARD[c1];
+ out[1] = SOFT[c2];
+ out[2] = HARD[c3];
+}
+
+int hex_chr_to_int(char hex) {
+ switch(hex) {
+ case '0' ... '9':
+ return hex - '0';
+ case 'a' ... 'f':
+ return hex - 'a' + 10;
+ case 'A' ... 'F':
+ return hex - 'A' + 10;
+ }
+
+ return 0;
+}
+
+/*
+ * Convert a string of the form "00:11:22:33:44:55" to
+ * a binary array 001122334455.
+ */
+void get_binary_mac(const char *mac, uint8_t *out) {
+ int i;
+ for (i = 0; i < MAC_ADDR_LEN; i += 3) {
+ *out = (hex_chr_to_int(mac[i]) << 4) | hex_chr_to_int(mac[i+1]);
+ out++;
+ }
+}
+
+
+void get_anonid_for_mac(const char *mac, char *out) {
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ unsigned int digest_len = sizeof(digest);
+ uint8_t macbin[6];
+ uint32_t num;
+
+ get_binary_mac(mac, macbin);
+ HMAC(EVP_md5(), consensus_key, CONSENSUS_KEY_LEN, macbin, sizeof(macbin),
+ digest, &digest_len);
+ num = (digest[0] << 16) | (digest[1] << 8) | digest[2];
+ trigraph((num >> 12) & 0x0fff, out);
+ trigraph((num ) & 0x0fff, out + 3);
+}
+
+
+void usage(const char *progname)
+{
+ fprintf(stderr, "usage: %s: -a ##:##:##:##:##:## [-k consensus_key]\n",
+ progname);
+ fprintf(stderr, "\t-a addr: MAC address to generate an anonid for\n");
+ fprintf(stderr, "\t-k key: Use a specific consensus_key. "
+ "Default is to read it from %s\n", consensus_key_file);
+ exit(1);
+}
+
+
+int main(int argc, char **argv)
+{
+ struct option long_options[] = {
+ {"addr", required_argument, 0, 'a'},
+ {"consensus_key", required_argument, 0, 'k'},
+ {0, 0, 0, 0},
+ };
+ const char *addr = NULL;
+ char anonid[7];
+ size_t lim;
+ int c;
+
+ setlinebuf(stdout);
+ alarm(30);
+
+ if (get_consensus_key()) {
+ default_consensus_key();
+ }
+
+ while ((c = getopt_long(argc, argv, "a:k:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'a':
+ addr = optarg;
+ break;
+ case 'k':
+ lim = (sizeof(consensus_key) > strlen(optarg)) ? strlen(optarg) :
+ sizeof(consensus_key);
+ memset(consensus_key, 0, sizeof(consensus_key));
+ memcpy(consensus_key, optarg, lim);
+ break;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+
+ if (addr == NULL) {
+ usage(argv[0]);
+ }
+
+ get_anonid_for_mac(addr, anonid);
+ printf("%s\n", anonid);
+
+ exit(0);
+}
diff --git a/cmds/test-anonid.sh b/cmds/test-anonid.sh
new file mode 100755
index 0000000..87f2b0f
--- /dev/null
+++ b/cmds/test-anonid.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+. ./wvtest/wvtest.sh
+
+WVSTART "anonid test"
+ANONID="./host-anonid"
+
+WVPASSEQ "$($ANONID -a 00:11:22:33:44:55 -k 0123456789)" "KEALAE"
+WVPASSEQ "$($ANONID -a 00:11:22:33:44:66 -k 6789abcdef)" "AAKLYK"