├── Makefile ├── README.md ├── enroll.cc ├── pam-enroll ├── pam_fido-u2f.cc ├── sign.cc ├── u2f.h ├── u2f_hid.h ├── u2f_util.cc └── u2f_util.h /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: u2f-enroll u2f-sign pam 3 | 4 | UNAME := $(shell uname) 5 | 6 | #your own libcrypto install 7 | #SSL=/usr/local/ssl 8 | CXX=c++ 9 | CC=cc 10 | 11 | LDFLAGS=-lcrypto 12 | LDFLAGS=-L$(SSL)/lib -Wl,--rpath=$(SSL)/lib -lcrypto 13 | CFLAGS=-I$(SSL)/include 14 | 15 | CFLAGS+=-fPIC -c -O2 16 | 17 | ifeq ($(UNAME), Linux) 18 | LDFLAGS+=-Wl,-soname=pam_fido-u2f.so 19 | 20 | CFLAGS+=-Wall 21 | CFLAGS+=-Ihidapi/hidapi -D__OS_LINUX 22 | 23 | HIDAPI=hid.o 24 | hid.o: hidapi/linux/hid.c 25 | $(CC) $(CFLAGS) -o hid.o hidapi/linux/hid.c 26 | 27 | endif # Linux 28 | 29 | ifeq ($(UNAME), Darwin) 30 | LDFLAGS+=-framework IOKit -framework CoreFoundation 31 | 32 | CFLAGS+=-w 33 | CFLAGS+=-Ihidapi/hidapi -D__OS_DARWIN 34 | 35 | HIDAPI=hid.o 36 | hid.o: hidapi/mac/hid.c 37 | $(CC) $(CFLAGS) -o hid.o hidapi/mac/hid.c 38 | 39 | endif # Darwin 40 | 41 | CXXFLAGS=$(CFLAGS) -std=c++11 42 | 43 | pam: pam_fido-u2f.o 44 | $(CXX) $^ -shared $(LDFLAGS) -lpam -o pam_fido-u2f.so 45 | 46 | pam_fido-u2f.o: pam_fido-u2f.cc 47 | $(CXX) $(CXXFLAGS) $< 48 | 49 | u2f_util.o: u2f_util.cc u2f_util.h u2f.h u2f_hid.h 50 | $(CXX) $(CXXFLAGS) -o $@ u2f_util.cc 51 | 52 | enroll.o: enroll.cc 53 | $(CXX) $(CXXFLAGS) $< 54 | 55 | sign.o: sign.cc 56 | $(CXX) $(CXXFLAGS) $< 57 | 58 | u2f-enroll: enroll.o u2f_util.o $(HIDAPI) 59 | $(CXX) $^ $(LDFLAGS) -lrt -ludev -o $@ 60 | 61 | u2f-sign: sign.o u2f_util.o $(HIDAPI) 62 | $(CXX) $^ $(LDFLAGS) -lrt -ludev -o $@ 63 | 64 | 65 | install: 66 | cp u2f-sign /usr/local/bin 67 | cp u2f-enroll /usr/local/bin 68 | cp pam-enroll /usr/local/bin 69 | cp pam_fido-u2f.so /lib64/security 70 | 71 | clean: 72 | rm -rf *.o 73 | 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | U2F utils 2 | ========= 3 | 4 | This u2f toolset contains small footprint u2f tools for enrolling 5 | and signing operations as well as a _PAM_ module for authenticating 6 | users on local services where they can physically plug in the 7 | u2f token (i.e. _xdm_, _login_, _su_, ...). 8 | 9 | I wrote this u2f stack in order to get familar with u2f crypto, the 10 | shortcomings of u2f in general and weaknesses of other u2f stacks in 11 | particular. Remote tools for u2f ssh etc. are underway. 12 | 13 | Build 14 | ----- 15 | 16 | You need some of the `-dev` libs installed such as `libpam-dev` and `libudev-dev`. 17 | 18 | Inside this dir, 19 | 20 | ``` 21 | $ git clone https://github.com/signal11/hidapi 22 | ``` 23 | 24 | to get the HIDAPI for accessing the security token. Then: 25 | 26 | ``` 27 | $ make 28 | $ make install 29 | ``` 30 | 31 | You need to set up proper udev rules so the security token 32 | appears as `/dev/hidraw*` device, with the permissions you prefer 33 | or manually load the hid driver. 34 | 35 | Install 36 | ------- 37 | 38 | To enroll a key you either use `u2f-enroll` or `pam-enroll` 39 | if you want to enroll a key suitable for _PAM_ authentication: 40 | 41 | ``` 42 | localhost: # pam-enroll stealth 43 | Remove token 44 | 45 | Insert token of user 'stealth' and press token-button if available. Then 46 | 47 | Got 631 bytes (sw=9000) 48 | 49 | pubkey claims to be signed with cert (unchecked!): 50 | 51 | Certificate: 52 | Data: 53 | Version: 3 (0x2) 54 | Serial Number: 18327115537361868814 (0xfe56fe7ae1ff180e) 55 | Signature Algorithm: ecdsa-with-SHA256 56 | Issuer: CN=Plug-up FIDO Internal Attestation CA #1 57 | Validity 58 | Not Before: Oct 3 08:06:48 2014 GMT 59 | Not After : Oct 3 08:06:48 2034 GMT 60 | Subject: CN=Plug-up FIDO Production Attestation #fe56fe7ae1ff180e 61 | Subject Public Key Info: 62 | Public Key Algorithm: id-ecPublicKey 63 | Public-Key: (256 bit) 64 | pub: 65 | 04:75:ea:06:60:e2:90:63:74:84:37:00:00:af:aa: 66 | 32:25:3e:82:7b:d8:48:74:93:a6:86:a5:68:4c:65: 67 | ca:ce:09:8b:e8:bf:4b:87:25:3d:ef:96:b9:40:23: 68 | 01:06:fc:46:06:1f:7d:65:46:c1:6f:14:b2:5a:bf: 69 | 30:19:d8:f4:27 70 | ASN1 OID: prime256v1 71 | X509v3 extensions: 72 | X509v3 Subject Key Identifier: 73 | 76:2B:44:6F:F2:94:ED:32:2A:E4:29:09:4F:A9:84:D8:85:3E:35:80 74 | X509v3 Authority Key Identifier: 75 | keyid:CF:A7:44:F2:A1:62:50:F0:39:E9:92:85:E3:DA:50:E7:7D:B0:3A:A8 76 | 77 | Signature Algorithm: ecdsa-with-SHA256 78 | 30:44:02:20:6b:5f:ea:7f:dd:ce:65:84:3b:25:d6:a6:fc:8a: 79 | 4d:b7:3b:80:b1:e6:44:2e:ab:06:77:a9:3e:3d:b9:35:1f:22: 80 | 02:20:59:5b:82:32:79:21:c2:8f:ad:20:62:b9:2a:ea:07:c4: 81 | 37:a5:4d:46:a6:2c:8b:e6:ee:fb:69:5b:8a:b1:44:16 82 | 83 | ``` 84 | 85 | If `/dev/hidraw0` is not the right device for you, pass the device path as the 86 | second argument of `pam-enroll`. You can also enroll keys via `u2f-enroll` and 87 | store the keys manually. 88 | 89 | `pam-enroll` stores the key handle along with the public key in `/etc/u2f/keys`: 90 | 91 | ``` 92 | localhost:# cat /etc/u2f/keys/_stealth 93 | H=b67350 [...] 18273a626dc0743c 94 | -----BEGIN PUBLIC KEY----- 95 | MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA 96 | [...] 97 | 98 | [...] 99 | SLI/caIDeYpo3lRlEdIWUX87A1cWC3YpCPJ89G1Hc9Fb9TtELXRiP3tHSfhgyVU= 100 | -----END PUBLIC KEY----- 101 | ``` 102 | 103 | You can then add `pam_fido-u2f.so` to any _PAM_ service file (only 104 | local services) for example to the _xdm_ display manager: 105 | 106 | ``` 107 | localhost: # cat /etc/pam.d/xdm 108 | #%PAM-1.0 109 | auth include common-auth 110 | auth required pam_fido-u2f.so 111 | account include common-account 112 | password include common-password 113 | session required pam_loginuid.so 114 | session include common-session 115 | ``` 116 | 117 | Next time someone logs in via _xdm_ an u2f token is required, which 118 | must contain the private key belonging to the public part 119 | stored in `/etc/u2f/keys`. Note that users which are not enrolled 120 | via `pam-enroll` cannot longer login via _xdm_! __So be sure that 121 | _root_ is properly enrolled__. 122 | 123 | If you have an USB keyboard or any other HID devices besides the u2f token 124 | already attached, you may need to specify the device path in the _PAM_ 125 | file, as in: 126 | 127 | auth required pam_fido-u2f.so device=/dev/hidraw1 128 | 129 | for example. As whatever name it would show up once plugged in. 130 | 131 | 132 | u2f limitations 133 | --------------- 134 | 135 | Please note that 2FA tokens/mechanisms are of limited use to protect 136 | shell access, since there are many ways to plant 2FA-less backdoors once 137 | shell access has been gained by an attacker in the first place. 138 | A Proper gateway and VPN setup is mandatory in order for 2FA to provide a 139 | real security benefit. __Also note that the FIDO U2F standard chose a 140 | NIST ECC curve (NIST P-256 aka `NID_X9_62_prime256v1`) for the crypto 141 | operations.__ Yes, thats the same NIST that apparently already backdoored other 142 | crypto protocols. So you can consider `NID_X9_62_prime256v1` to be weak, 143 | but it might be good enough as a second factor for medium secured sites. 144 | Note again that USB tokens are subject to bad-USB style attacks. Some tokens 145 | even have an API beyond FIDO U2F that allows for easy storage of keystrokes 146 | and replay, once plugged in. So while it is in general a good idea to 147 | have 2FA, you always add an additional attack vector to your site that 148 | has not been there before. 149 | 150 | 151 | _Part of this code is (C) 2014 Google Inc. under a BSD-ish license. 152 | Please refer to the source code for details._ 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /enroll.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2019 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | /* Enroll an FIDO-U2F NIST P-256 key on the security token and 34 | * print out the corresponding public key and certificate. 35 | * 36 | * As per FIDO U2F Raw Message Formats Proposed Standard from 09 Oct. 2014. 37 | */ 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include "u2f_util.h" 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | using namespace std; 62 | 63 | enum msg_type_t { 64 | ERROR_MSG_PERROR = 0, 65 | ERROR_MSG_SSL 66 | }; 67 | 68 | 69 | enum sw_t { 70 | SW_OK = 0x9000, 71 | SW_CONDITIONS_NOT_SATISFIED = 0x6985 72 | }; 73 | 74 | 75 | void die(const char *msg, msg_type_t t = ERROR_MSG_PERROR) 76 | { 77 | if (t == ERROR_MSG_SSL) 78 | fprintf(stderr, "%s: %s\n", msg, ERR_error_string(ERR_get_error(), NULL)); 79 | else 80 | perror(msg); 81 | 82 | exit(errno); 83 | } 84 | 85 | 86 | void usage() 87 | { 88 | printf("Usage: enroll [-A app-id] [-i device] [-d dumpfile] [-o outfile]\n"); 89 | exit(1); 90 | } 91 | 92 | 93 | int main(int argc, char **argv) 94 | { 95 | FILE *f = NULL, *fout = stdout; 96 | int c = 0; 97 | string infile = "/dev/hidraw0", dumpfile = ""; 98 | string app = "pam_fido-u2f,type=u2f,kind=authentication,version=1"; 99 | 100 | while ((c = getopt(argc, argv, "A:i:d:o:")) != -1) { 101 | switch (c) { 102 | case 'A': 103 | app = optarg; 104 | break; 105 | case 'i': 106 | infile = optarg; 107 | break; 108 | case 'd': 109 | dumpfile = optarg; 110 | break; 111 | case 'o': 112 | if (fout != stdout) 113 | break; 114 | if ((fout = fopen(optarg, "w")) == NULL) 115 | die("fopen"); 116 | break; 117 | default: 118 | usage(); 119 | break; 120 | } 121 | } 122 | 123 | ERR_load_crypto_strings(); 124 | OpenSSL_add_all_algorithms(); 125 | OpenSSL_add_all_digests(); 126 | 127 | RAND_load_file("/dev/urandom", 256); 128 | ERR_clear_error(); 129 | 130 | // the string blob the APDU returns for a registration message 131 | string msg = ""; 132 | 133 | if (infile.find("/dev") != string::npos) { 134 | U2Fob *dev = NULL; 135 | if ((dev = U2Fob_create()) == NULL) 136 | die("U2Fob_create"); 137 | 138 | if (U2Fob_open(dev, infile.c_str()) != 0) 139 | die("U2Fob_open: Wrong device? "); 140 | if (U2Fob_init(dev) != 0) 141 | die("U2Fob_init: Wrong device? "); 142 | 143 | int sw = 0; 144 | 145 | unsigned char req[2*32]; 146 | SHA256(reinterpret_cast(app.c_str()), app.size(), req + 32); 147 | if (RAND_bytes(req, 32) != 1) 148 | die("RAND_bytes", ERROR_MSG_SSL); 149 | 150 | string s = string(reinterpret_cast(req), sizeof(req)); 151 | if ((sw = U2Fob_apdu(dev, 0x0, U2F_INS_REGISTER, 0x0, 0, s, &msg)) != SW_OK) { 152 | if (sw == SW_CONDITIONS_NOT_SATISFIED) { 153 | fprintf(stderr, "Do the user-presence test and call command again shortly after that.\n"); 154 | exit(0); 155 | } else { 156 | fprintf(stderr, "Failure on APDU (sw=%x)\n", sw); 157 | die("U2Fob_apdu"); 158 | } 159 | } 160 | U2Fob_destroy(dev); 161 | printf("Got %d bytes (sw=%x)\n", (int)msg.size(), sw); 162 | if (dumpfile.size() > 0) { 163 | if ((f = fopen(dumpfile.c_str(), "w")) == NULL) 164 | die("fopen"); 165 | fwrite(msg.c_str(), msg.size(), 1, f); 166 | fclose(f); 167 | } 168 | } else { 169 | if ((f = fopen(infile.c_str(), "r")) == NULL) 170 | die("fopen"); 171 | struct stat st; 172 | if (fstat(fileno(f), &st) < 0) 173 | die("fstat"); 174 | char *buf = new char[st.st_size]; 175 | if (fread(buf, 1, st.st_size, f) != (size_t)st.st_size) 176 | die("fread"); 177 | fclose(f); 178 | msg = string(buf, st.st_size); 179 | delete [] buf; 180 | } 181 | 182 | if (msg.size() <= 67) 183 | die("Something went wrong with the APDU"); 184 | if (msg.size() < (size_t)(67 + (uint8_t)msg[66])) 185 | die("Something went wrong with the APDU"); 186 | 187 | // Now all the byte fumbling part 188 | EC_GROUP *ecgrp = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1); 189 | EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); 190 | if (!ecgrp || !eckey) 191 | die("EC curve NIST P-256 init", ERROR_MSG_SSL); 192 | 193 | EC_POINT *point = EC_POINT_new(ecgrp); 194 | if (!point) 195 | die("EC_POINT_new", ERROR_MSG_SSL); 196 | 197 | if (EC_POINT_oct2point(ecgrp, point, (unsigned char *)msg.c_str() + 1, 65, NULL) != 1) 198 | die("EC_POINT_oct2point", ERROR_MSG_SSL); 199 | 200 | if (EC_KEY_set_public_key(eckey, point) != 1) 201 | die("EC_KEY_set_public_key", ERROR_MSG_SSL); 202 | 203 | EVP_PKEY *evpk = EVP_PKEY_new(); 204 | if (!evpk) 205 | die("EVP_PKEY_new", ERROR_MSG_SSL); 206 | 207 | if (EVP_PKEY_assign_EC_KEY(evpk, eckey) != 1) 208 | die("EVP_PKEY_assign_EC_KEY", ERROR_MSG_SSL); 209 | 210 | fprintf(fout, "H="); 211 | for (uint8_t i = 0; i < (uint8_t)msg[66]; ++i) 212 | fprintf(fout, "%02x", (uint8_t)msg[66 + i + 1]); 213 | 214 | fprintf(fout, "\n"); 215 | PEM_write_PUBKEY(fout, evpk); 216 | 217 | const unsigned char *cert = reinterpret_cast(msg.c_str() + 67 + (uint8_t)msg[66]); 218 | X509 *x509 = d2i_X509(NULL, &cert, msg.size() - (67 + (uint8_t)msg[66])); 219 | if (!x509) 220 | die("d2i_X509", ERROR_MSG_SSL); 221 | printf("\npubkey claims to be signed with cert (unchecked!):\n\n"); 222 | X509_print_fp(stdout, x509); 223 | 224 | X509_free(x509); 225 | EVP_PKEY_free(evpk); 226 | return 0; 227 | } 228 | 229 | 230 | -------------------------------------------------------------------------------- /pam-enroll: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | 4 | sub usage 5 | { 6 | print "pam-enroll [device]\n"; 7 | exit; 8 | } 9 | 10 | 11 | my $user = shift || usage(); 12 | my $device = shift || "/dev/hidraw0"; 13 | 14 | if (!defined getpwnam($user)) { 15 | print "Invalid username!\n\n"; 16 | exit; 17 | } 18 | 19 | mkdir("/etc/u2f", 0755); 20 | mkdir("/etc/u2f/keys/", 0755); 21 | 22 | print "Remove token \n"; 23 | <>; 24 | print "Insert token of user '$user' and press token-button if available. Then \n"; 25 | <>; 26 | 27 | exec("u2f-enroll", "-i", $device, "-o", "/etc/u2f/keys/_".$user); 28 | 29 | 30 | -------------------------------------------------------------------------------- /pam_fido-u2f.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #define PAM_SM_AUTH 49 | #include 50 | #include 51 | #ifdef __OS_LINUX 52 | #include 53 | #include 54 | #include 55 | #endif 56 | 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | 66 | static const char *default_keydir = "/etc/u2f/keys/"; 67 | static const char *app_id = "pam_fido-u2f,type=u2f,kind=authentication,version=1"; 68 | 69 | #ifndef U2F_SIGNPATH 70 | #define U2F_SIGNPATH "/usr/local/bin/u2f-sign" 71 | #endif 72 | 73 | enum { 74 | SIGN_TIMEOUT = 15 75 | }; 76 | 77 | 78 | using namespace std; 79 | 80 | 81 | struct u2f_auth { 82 | pam_handle_t *pamh; 83 | string devpath, user, keydir, sigdata, sig; 84 | EVP_PKEY *pkey; 85 | }; 86 | 87 | 88 | #ifdef __OS_DARWIN 89 | 90 | #include 91 | #include 92 | 93 | static int pam_syslog(pam_handle_t *pamh, int level, const char *msg, ...) 94 | { 95 | va_list va; 96 | va_start(va, msg); 97 | openlog("pam_fido-u2f", LOG_PID, LOG_AUTH); 98 | vsyslog(level, msg, va); 99 | closelog(); 100 | va_end(va); 101 | } 102 | 103 | #endif 104 | 105 | 106 | static int create_response(struct u2f_auth *auth) 107 | { 108 | FILE *f = NULL; 109 | 110 | string keyfile = auth->keydir; 111 | keyfile += "_"; 112 | keyfile += auth->user; 113 | 114 | // open key file belonging to this user 115 | if ((f = fopen(keyfile.c_str(), "r")) == NULL) { 116 | pam_syslog(auth->pamh, LOG_ERR, "fopen() of keyfile for user '%s' failed.", auth->user.c_str()); 117 | return -1; 118 | } 119 | 120 | // parse keyfile (handle and PEM key) 121 | char line[1024], *h = NULL; 122 | for (;!feof(f);) { 123 | memset(line, 0, sizeof(line)); 124 | if (!fgets(line, sizeof(line) - 1, f)) 125 | break; 126 | if ((h = strstr(line, "H=")) != NULL) { 127 | h += 2; 128 | break; 129 | } 130 | } 131 | void *r = PEM_read_PUBKEY(f, &auth->pkey, NULL, NULL); 132 | fclose(f); 133 | 134 | if (!h || !r) { 135 | pam_syslog(auth->pamh, LOG_ERR, "Failed to find valid key or handle in '%s'.", keyfile.c_str()); 136 | return -1; 137 | } 138 | 139 | // create blob which is sent to token 140 | unsigned char md[32]; 141 | SHA256(reinterpret_cast(app_id), strlen(app_id), md); 142 | auth->sigdata = string(reinterpret_cast(md), sizeof(md)); 143 | 144 | unsigned char rand[32]; 145 | if (RAND_bytes(rand, sizeof(rand)) != 1) { 146 | pam_syslog(auth->pamh, LOG_ERR, "Failed to generate RAND bytes."); 147 | return -1; 148 | } 149 | 150 | // standard demands SHA256 of challenge, so here we go... 151 | SHA256(rand, sizeof(rand), md); 152 | 153 | int p[2]; 154 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, p) < 0) { 155 | pam_syslog(auth->pamh, LOG_ERR, "Failed to spawn sign helper."); 156 | return -1; 157 | } 158 | 159 | // tell user about it 160 | const pam_conv *conv = NULL; 161 | pam_response *presp = NULL; 162 | pam_message pmsg = {PAM_TEXT_INFO, "User presence required! Insert coin and/or press button.\n"}; 163 | const pam_message *cpmsg = &pmsg; 164 | if (pam_get_item(auth->pamh, PAM_CONV, reinterpret_cast(&conv)) == PAM_SUCCESS) { 165 | if (conv) 166 | conv->conv(1, &cpmsg, &presp, NULL); 167 | // free(NULL) is defined 168 | if (presp) 169 | free(presp->resp); 170 | free(presp); 171 | } 172 | 173 | int status = -1; 174 | pid_t pid = fork(); 175 | if (pid == 0) { 176 | close(0); close(1); close(2); close(p[0]); 177 | if (dup2(p[1], 0) < 0 || dup2(p[1], 1) < 0 || dup2(p[1], 2) < 0) 178 | exit(1); 179 | 180 | close(p[1]); 181 | 182 | char *const u2f_sign[] = { 183 | strdup(U2F_SIGNPATH), 184 | strdup("-A"), strdup(app_id), 185 | strdup("-x"), 186 | strdup("-d"), strdup(auth->devpath.c_str()), NULL 187 | }; 188 | 189 | execve(*u2f_sign, u2f_sign, NULL); 190 | exit(1); 191 | } else if (pid < 0) { 192 | pam_syslog(auth->pamh, LOG_ERR, "Failed to spawn sign helper."); 193 | return -1; 194 | } 195 | close(p[1]); 196 | if ((f = fdopen(p[0], "r+")) == NULL) { 197 | pam_syslog(auth->pamh, LOG_ERR, "Failed to spawn sign helper."); 198 | waitpid(pid, &status, WNOHANG); 199 | return -1; 200 | } 201 | // key handle to use 202 | fprintf(f, "%s", h); 203 | // challenge to sign, app challenge goes via -A 204 | for (size_t i = 0; i < sizeof(md); ++i) 205 | fprintf(f, "%02x", md[i]); 206 | fprintf(f, "\n"); fflush(f); 207 | 208 | struct pollfd pfd = {p[0], POLLIN, 0}; 209 | int n = poll(&pfd, 1, SIGN_TIMEOUT*1000); 210 | 211 | // any POLLERR or POLLHUP condition in revent is error 212 | if (n != 1 || pfd.revents != POLLIN) { 213 | pam_syslog(auth->pamh, LOG_ERR, "Error while waiting for sign operation to finish."); 214 | waitpid(pid, &status, WNOHANG); 215 | fclose(f); 216 | return -1; 217 | } 218 | 219 | // input was hex encoding, output goes raw 220 | n = 0; 221 | char sbuf[1024]; 222 | n = read(p[0], sbuf, sizeof(sbuf)); 223 | fclose(f); 224 | //close(p[0]); not needed, fclose() is doing so 225 | 226 | if (n <= 0) { 227 | pam_syslog(auth->pamh, LOG_ERR, "Failed to read signature from sign helper."); 228 | return -1; 229 | } 230 | 231 | // No WNOHANG here: login collects all childs via wait(-1) and closing 232 | // PAM session after it found the first child exiting, which could be u2f-sign 233 | // if we did not properly collect u2f-sign's exit(). u2f-sign is guaranteed to 234 | // finish in at least 30s by alarm(). 235 | if (waitpid(pid, &status, 0) < 0) { 236 | pam_syslog(auth->pamh, LOG_ERR, "Failed to get exit status from sign helper."); 237 | return -1; 238 | } 239 | 240 | if (WEXITSTATUS(status) != 0) { 241 | pam_syslog(auth->pamh, LOG_ERR, "Sign helper did not exit cleanly."); 242 | return -1; 243 | } 244 | 245 | string msg = string(sbuf, n); 246 | 247 | // user presence? 248 | uint8_t up = (uint8_t)msg[0]; 249 | if ((up & 0x1) != 0x1) { 250 | pam_syslog(auth->pamh, LOG_ERR, "Failed to check user presence."); 251 | return -1; 252 | } 253 | 254 | auth->sigdata += msg.substr(0, 1 + 4); 255 | auth->sigdata += string(reinterpret_cast(md), sizeof(md)); 256 | 257 | auth->sig = msg.substr(1 + 4); 258 | return 0; 259 | } 260 | 261 | 262 | // 0 on success 263 | static int check_signature(u2f_auth *auth) 264 | { 265 | EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(auth->pkey, NULL); 266 | if (!ctx) 267 | return -1; 268 | 269 | /* The following seems not necessary since curve params are saved inside the PEM 270 | * file along with EC keydata. However it can also be set explicitely. 271 | if (EVP_PKEY_paramgen_init(ctx) != 1) 272 | return -1; 273 | 274 | if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1) <= 0) 275 | return -1; 276 | */ 277 | 278 | if (EVP_PKEY_verify_init(ctx) != 1) 279 | return -1; 280 | 281 | unsigned char md[32]; 282 | SHA256(reinterpret_cast(auth->sigdata.c_str()), auth->sigdata.size(), md); 283 | 284 | int r = EVP_PKEY_verify(ctx, reinterpret_cast(auth->sig.c_str()), auth->sig.size(), md, sizeof(md)); 285 | 286 | EVP_PKEY_CTX_free(ctx); 287 | 288 | if (r != 1) 289 | return -1; 290 | 291 | return 0; 292 | } 293 | 294 | 295 | extern "C" { 296 | 297 | PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 298 | { 299 | char keydir[1024], device[1024]; 300 | const char *user = NULL; 301 | // only local supported yet 302 | int local_device = 1; 303 | 304 | OpenSSL_add_all_algorithms(); 305 | OpenSSL_add_all_digests(); 306 | 307 | memset(keydir, 0, sizeof(keydir)); 308 | memset(device, 0, sizeof(device)); 309 | 310 | snprintf(device, sizeof(device), "/dev/hidraw0"); 311 | snprintf(keydir, sizeof(keydir), "%s", default_keydir); 312 | 313 | for (int i = 0; i < argc; ++i) { 314 | if (!argv[i]) 315 | continue; 316 | if (sscanf(argv[i], "device=%1023c", device) == 1) 317 | continue; 318 | if (sscanf(argv[i], "local") == 1) { 319 | local_device = 1; 320 | continue; 321 | } 322 | } 323 | 324 | if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) { 325 | pam_syslog(pamh, LOG_ERR, "Unable to find user."); 326 | return PAM_USER_UNKNOWN; 327 | } 328 | 329 | if (getpwnam(user) == NULL) { 330 | pam_syslog(pamh, LOG_ERR, "User does not exist."); 331 | return PAM_USER_UNKNOWN; 332 | } 333 | 334 | pam_syslog(pamh, LOG_INFO, "About to check U2F token for user '%s'.", user); 335 | 336 | struct u2f_auth auth = {pamh, device, user, keydir}; 337 | 338 | int r = -1; 339 | 340 | if (local_device) 341 | r = create_response(&auth); 342 | 343 | if (r != 0) 344 | return PAM_PERM_DENIED; 345 | 346 | if (check_signature(&auth) != 0) 347 | return PAM_PERM_DENIED; 348 | 349 | return PAM_SUCCESS; 350 | } 351 | 352 | 353 | PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 354 | { 355 | return PAM_IGNORE; 356 | } 357 | 358 | 359 | #ifdef PAM_STATIC 360 | 361 | struct pam_module _pam_schroedinger = { 362 | "pam_fido-u2f", 363 | pam_sm_authenticate, 364 | pam_sm_setcred, 365 | NULL, /* acct_mgmt */ 366 | NULL, /* open_session */ 367 | NULL, /* close_session */ 368 | NULL /* chauthtok */ 369 | }; 370 | 371 | 372 | #endif 373 | 374 | } // extern "C" 375 | 376 | -------------------------------------------------------------------------------- /sign.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Sebastian Krahmer. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #include 48 | 49 | #include "u2f.h" 50 | #include "u2f_util.h" 51 | 52 | using namespace std; 53 | 54 | 55 | struct apdu_blob_t { 56 | //uint8_t ctrl; Not here; sent directly as P1 (0x3 == "enforce-user-presence-and-sign") 57 | unsigned char chall[32]; 58 | unsigned char app[32]; 59 | uint8_t kl; 60 | uint8_t kh[255]; 61 | } apdu_blob = {{0,}, {0,}, 0, {0,}}; 62 | 63 | 64 | enum input_filter_t { 65 | INPUT_HEX, 66 | INPUT_BIN 67 | }; 68 | 69 | 70 | enum output_filter_t { 71 | OUTPUT_HEX, 72 | OUTPUT_BIN 73 | }; 74 | 75 | 76 | enum sw_t { 77 | SW_OK = 0x9000, 78 | SW_NOUSER = 0x6985 79 | }; 80 | 81 | 82 | // stop after newline or \0 83 | int hex2bin(const char *from, unsigned char *to, size_t tolen) 84 | { 85 | int has_failed = 0; 86 | size_t i = 0; 87 | 88 | for (i = 0; from[2*i] != '\n' && from[2*i] != 0 && i < tolen; ++i) { 89 | if (sscanf(from + 2*i, "%02hhx", to + i) != 1) { 90 | has_failed = 1; 91 | break; 92 | } 93 | } 94 | 95 | if (has_failed) 96 | return -1; 97 | return (int)i; 98 | } 99 | 100 | 101 | 102 | int input(apdu_blob_t *a, enum input_filter_t f) 103 | { 104 | if (f == INPUT_BIN) { 105 | if ((a->kl = read(fileno(stdin), a->kh, sizeof(a->kh))) <= 0) 106 | return -1; 107 | if (read(fileno(stdin), a->chall, sizeof(a->chall)) != (ssize_t)sizeof(a->chall)) 108 | return -1; 109 | } else { 110 | char line[1024]; 111 | memset(line, 0, sizeof(line)); 112 | if (!fgets(line, sizeof(line) - 1, stdin)) 113 | return -1; 114 | int r = hex2bin(line, reinterpret_cast(a->kh), sizeof(a->kh)); 115 | if (r <= 0) 116 | return -1; 117 | apdu_blob.kl = (uint8_t)(r & 0xff); 118 | if (!fgets(line, sizeof(line) - 1, stdin)) 119 | return -1; 120 | if (hex2bin(line, a->chall, sizeof(a->chall)) != (int)sizeof(a->chall)) 121 | return -1; 122 | } 123 | return 0; 124 | } 125 | 126 | 127 | void sig_alarm(int x) 128 | { 129 | exit(1); 130 | } 131 | 132 | 133 | int main(int argc, char **argv) 134 | { 135 | input_filter_t fin = INPUT_BIN; 136 | output_filter_t fout = OUTPUT_BIN; 137 | string app_id = "pam_fido-u2f,type=u2f,kind=authentication,version=1"; 138 | string devpath = "/dev/hidraw0"; 139 | 140 | int c = 0; 141 | while ((c = getopt(argc, argv, "A:d:xX")) != -1) { 142 | switch (c) { 143 | case 'A': 144 | app_id = optarg; 145 | break; 146 | case 'x': 147 | fin = INPUT_HEX; 148 | break; 149 | case 'X': 150 | fout = OUTPUT_HEX; 151 | break; 152 | case 'd': 153 | devpath = optarg; 154 | break; 155 | } 156 | } 157 | 158 | struct sigaction sa; 159 | memset(&sa, 0, sizeof(sa)); 160 | sa.sa_handler = sig_alarm; 161 | sigaction(SIGALRM, &sa, NULL); 162 | alarm(30); 163 | 164 | if (input(&apdu_blob, fin) < 0) 165 | return -1; 166 | 167 | // create blob which is sent to token 168 | SHA256(reinterpret_cast(app_id.c_str()), app_id.size(), apdu_blob.app); 169 | 170 | string msg = ""; 171 | for (int tries = 0; tries < 3; ++tries) { 172 | // In udev case, it can take a short while until HID device comes up 173 | struct stat st; 174 | int failed = 1; 175 | for (int i = 0; i < 300; ++i) { 176 | if (stat(devpath.c_str(), &st) == 0) { 177 | failed = 0; 178 | break; 179 | } 180 | usleep(10000); 181 | } 182 | 183 | if (failed) 184 | continue; 185 | 186 | U2Fob *dev = NULL; 187 | if ((dev = U2Fob_create()) == NULL) 188 | return -1; 189 | 190 | if (U2Fob_open(dev, devpath.c_str()) != 0) { 191 | U2Fob_destroy(dev); 192 | return -1; 193 | } 194 | if (U2Fob_init(dev) != 0) { 195 | U2Fob_destroy(dev); 196 | return -1; 197 | } 198 | 199 | msg = ""; 200 | string s = string(reinterpret_cast(&apdu_blob), offsetof(apdu_blob_t, kh) + apdu_blob.kl); 201 | int sw = U2Fob_apdu(dev, 0x0, U2F_INS_AUTHENTICATE, 0x3, 0, s, &msg); 202 | U2Fob_destroy(dev); 203 | 204 | if (sw == SW_NOUSER) { 205 | sleep(5); 206 | continue; 207 | } 208 | 209 | if (sw != SW_OK) 210 | return -1; 211 | 212 | // user presence? 213 | uint8_t up = (uint8_t)msg[0]; 214 | if ((up & 0x1) != 0x1) 215 | return -1; 216 | break; 217 | } 218 | 219 | if (fout == OUTPUT_HEX) { 220 | for (string::size_type i = 0; i < msg.size(); ++i) 221 | printf("%02x", (uint8_t)(msg[i] & 0xff)); 222 | printf("\n"); 223 | fflush(stdout); 224 | } else { 225 | if (write(fileno(stdout), msg.c_str(), msg.size()) != (ssize_t)msg.size()) 226 | return -1; 227 | } 228 | 229 | alarm(0); 230 | 231 | return 0; 232 | } 233 | 234 | -------------------------------------------------------------------------------- /u2f.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Google Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | #ifndef __U2F_H_INCLUDED__ 8 | #define __U2F_H_INCLUDED__ 9 | 10 | #include 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | #ifndef __NO_PRAGMA_PACK 17 | #pragma pack(push, 1) 18 | #endif 19 | 20 | // General constants 21 | 22 | #define P256_SCALAR_SIZE 32 // nistp256 in bytes 23 | #define P256_POINT_SIZE ((P256_SCALAR_SIZE * 2) + 1) 24 | 25 | #define MAX_ECDSA_SIG_SIZE 72 // asn1 DER format 26 | #define MAX_KH_SIZE 128 // key handle 27 | #define MAX_CERT_SIZE 2048 // attestation certificate 28 | 29 | #define U2F_APPID_SIZE 32 30 | #define U2F_NONCE_SIZE 32 31 | 32 | #define UNCOMPRESSED_POINT 0x04 33 | 34 | typedef struct { 35 | uint8_t format; 36 | uint8_t x[P256_SCALAR_SIZE]; 37 | uint8_t y[P256_SCALAR_SIZE]; 38 | } P256_POINT; 39 | 40 | // U2Fv2 instructions 41 | 42 | #define U2F_INS_REGISTER 0x01 43 | #define U2F_INS_AUTHENTICATE 0x02 44 | #define U2F_INS_VERSION 0x03 45 | 46 | // U2F_REGISTER instruction defines 47 | 48 | #define U2F_REGISTER_ID 0x05 // magic constant 49 | #define U2F_REGISTER_HASH_ID 0x00 // magic constant 50 | 51 | typedef struct { 52 | uint8_t nonce[U2F_NONCE_SIZE]; 53 | uint8_t appId[U2F_APPID_SIZE]; 54 | } U2F_REGISTER_REQ; 55 | 56 | typedef struct { 57 | uint8_t registerId; 58 | P256_POINT pubKey; 59 | uint8_t keyHandleLen; 60 | uint8_t keyHandleCertSig[ 61 | MAX_KH_SIZE + 62 | MAX_CERT_SIZE + 63 | MAX_ECDSA_SIG_SIZE]; 64 | } U2F_REGISTER_RESP; 65 | 66 | // U2F_AUTHENTICATE instruction defines 67 | 68 | // Authentication parameter byte 69 | #define U2F_AUTH_ENFORCE 0x03 // Require user presence 70 | #define U2F_AUTH_CHECK_ONLY 0x07 // Test but do not consume 71 | 72 | typedef struct { 73 | uint8_t nonce[U2F_NONCE_SIZE]; 74 | uint8_t appId[U2F_APPID_SIZE]; 75 | uint8_t keyHandleLen; 76 | uint8_t keyHandle[MAX_KH_SIZE]; 77 | } U2F_AUTHENTICATE_REQ; 78 | 79 | // Flags values 80 | #define U2F_TOUCHED 0x01 81 | #define U2F_ALTERNATE_INTERFACE 0x02 82 | 83 | typedef struct { 84 | uint8_t flags; 85 | uint32_t ctr; 86 | uint8_t sig[MAX_ECDSA_SIG_SIZE]; 87 | } U2F_AUTHENTICATE_RESP; 88 | 89 | #ifndef __NO_PRAGMA_PACK 90 | #pragma pack(pop) 91 | #endif 92 | 93 | #ifdef __cplusplus 94 | } 95 | #endif 96 | 97 | #endif // __U2F_H_INCLUDED__ 98 | -------------------------------------------------------------------------------- /u2f_hid.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Google Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | #ifndef __U2F_HID_H_INCLUDED__ 8 | #define __U2F_HID_H_INCLUDED__ 9 | 10 | #include 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | #ifndef __NO_PRAGMA_PACK 17 | #pragma pack(push, 1) 18 | #endif 19 | 20 | // HID frame definition 21 | typedef struct { 22 | uint32_t cid; 23 | union { 24 | uint8_t type; 25 | struct { 26 | uint8_t cmd; 27 | uint8_t bcnth; 28 | uint8_t bcntl; 29 | uint8_t data[64 - 7]; 30 | } init; 31 | struct { 32 | uint8_t seq; 33 | uint8_t data[64 - 5]; 34 | } cont; 35 | }; 36 | } U2FHID_FRAME; 37 | 38 | // HID frame accessors 39 | #define TYPE_MASK 0x80 40 | #define TYPE_INIT 0x80 41 | #define TYPE_CONT 0x00 42 | 43 | #define FRAME_TYPE(x) ((x).type & TYPE_MASK) 44 | #define FRAME_SEQ(x) ((x).cont.seq & ~TYPE_MASK) 45 | #define FRAME_CMD(x) ((x).init.cmd & ~TYPE_MASK) 46 | #define MSG_LEN(x) ((x).init.bcnth * 256u + (x).init.bcntl) 47 | 48 | // Commands 49 | #define U2FHID_PING (TYPE_INIT | 1) 50 | #define U2FHID_MSG (TYPE_INIT | 3) 51 | #define U2FHID_LOCK (TYPE_INIT | 4) 52 | #define U2FHID_INIT (TYPE_INIT | 6) 53 | #define U2FHID_WINK (TYPE_INIT | 8) 54 | #define U2FHID_SYNC (TYPE_INIT | 0x3c) 55 | #define U2FHID_ERROR (TYPE_INIT | 0x3f) 56 | 57 | // Errors 58 | #define ERR_NONE 0 59 | #define ERR_INVALID_CMD 1 60 | #define ERR_INVALID_PAR 2 61 | #define ERR_INVALID_LEN 3 62 | #define ERR_INVALID_SEQ 4 63 | #define ERR_MSG_TIMEOUT 5 64 | #define ERR_CHANNEL_BUSY 6 65 | #define ERR_LOCK_REQUIRED 10 66 | #define ERR_INVALID_CID 11 67 | #define ERR_OTHER 127 68 | 69 | // Init command parameters 70 | #define CID_BROADCAST -1 71 | #define INIT_NONCE_SIZE 8 72 | 73 | typedef struct { 74 | uint8_t nonce[INIT_NONCE_SIZE]; 75 | uint32_t cid; 76 | uint8_t versionInterface; 77 | uint8_t versionMajor; 78 | uint8_t versionMinor; 79 | uint8_t versionBuild; 80 | uint8_t capFlags; 81 | } U2FHID_INIT_RESP; 82 | 83 | #define U2FHID_IF_VERSION 2 84 | 85 | #define CAPFLAG_WINK 0x01 86 | #define CAPFLAG_LOCK 0x02 87 | 88 | #ifndef __NO_PRAGMA_PACK 89 | #pragma pack(pop) 90 | #endif 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif 95 | 96 | #endif // __U2F_HID_H_INCLUDED__ 97 | -------------------------------------------------------------------------------- /u2f_util.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Google Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | #include 8 | #include 9 | 10 | #ifdef __OS_WIN 11 | #include // ntohl, htonl 12 | #else 13 | #include // ntohl, htonl 14 | #endif 15 | 16 | #include 17 | 18 | #include "u2f_util.h" 19 | 20 | // This is a "library"; do not abort. 21 | #define AbortOrNot() \ 22 | std::cerr << "returning false" << std::endl; \ 23 | return false 24 | 25 | #ifdef __OS_WIN 26 | #define strdup _strdup 27 | #endif 28 | 29 | #ifdef __OS_DARWIN 30 | // Implement something compatible w/ linux clock_gettime() 31 | 32 | #include 33 | 34 | #define CLOCK_MONOTONIC 0 35 | 36 | static 37 | void clock_gettime(int which, struct timespec* ts) { 38 | static mach_timebase_info_data_t __clock_gettime_inf; 39 | uint64_t now, nano; 40 | 41 | now = mach_absolute_time(); 42 | if (0 == __clock_gettime_inf.denom) 43 | mach_timebase_info(&__clock_gettime_inf); 44 | 45 | nano = now * __clock_gettime_inf.numer / __clock_gettime_inf.denom; 46 | ts->tv_sec = nano * 1e-9; 47 | ts->tv_nsec = nano - (ts->tv_sec * 1e9); 48 | } 49 | #endif // __OS_MAC 50 | 51 | std::string b2a(const void* ptr, size_t size) { 52 | const uint8_t* p = reinterpret_cast(ptr); 53 | std::string result; 54 | 55 | for (size_t i = 0; i < 2 * size; ++i) { 56 | int nib = p[i / 2]; 57 | if ((i & 1) == 0) nib >>= 4; 58 | nib &= 15; 59 | result.push_back("0123456789ABCDEF"[nib]); 60 | } 61 | 62 | return result; 63 | } 64 | 65 | std::string b2a(const std::string& s) { 66 | return b2a(s.data(), s.size()); 67 | } 68 | 69 | std::string a2b(const std::string& s) { 70 | std::string result; 71 | int v; 72 | for (size_t i = 0; i < s.size(); ++i) { 73 | if ((i & 1) == 1) v <<= 4; else v = 0; 74 | char d = s[i]; 75 | if (d >= '0' && d <= '9') v += (d - '0'); 76 | else if (d >= 'A' && d <= 'F') v += (d - 'A' + 10); 77 | else if (d >= 'a' && d <= 'f') v += (d - 'a' + 10); 78 | if ((i & 1) == 1) result.push_back(v & 255); 79 | } 80 | return result; 81 | } 82 | 83 | float U2Fob_deltaTime(uint64_t* state) { 84 | uint64_t now, delta; 85 | #ifdef __OS_WIN 86 | now = (uint64_t) GetTickCount64() * 1000000; 87 | #else 88 | struct timespec ts; 89 | clock_gettime(CLOCK_MONOTONIC, &ts); 90 | now = (uint64_t) (ts.tv_sec * 1e9 + ts.tv_nsec); 91 | #endif 92 | delta = *state ? now - *state : 0; 93 | *state = now; 94 | return (float) (delta / 1.0e9); 95 | } 96 | 97 | struct U2Fob* U2Fob_create() { 98 | struct U2Fob* f = NULL; 99 | if (hid_init() == 0) { 100 | f = (struct U2Fob*)malloc(sizeof(struct U2Fob)); 101 | memset(f, 0, sizeof(struct U2Fob)); 102 | f->cid = -1; 103 | } 104 | return f; 105 | } 106 | 107 | void U2Fob_destroy(struct U2Fob* device) { 108 | if (device) { 109 | U2Fob_close(device); 110 | if (device->path) { 111 | free(device->path); 112 | device->path = NULL; 113 | } 114 | free(device); 115 | } 116 | hid_exit(); 117 | } 118 | 119 | uint32_t U2Fob_getCid(struct U2Fob* device) { 120 | return device->cid; 121 | } 122 | 123 | int U2Fob_open(struct U2Fob* device, const char* path) { 124 | U2Fob_close(device); 125 | if (device->path) { 126 | free(device->path); 127 | device->path = NULL; 128 | } 129 | device->path = strdup(path); 130 | device->dev = hid_open_path(device->path); 131 | return device->dev != NULL ? -ERR_NONE : -ERR_OTHER; 132 | } 133 | 134 | void U2Fob_close(struct U2Fob* device) { 135 | if (device->dev) { 136 | hid_close(device->dev); 137 | device->dev = NULL; 138 | } 139 | } 140 | 141 | int U2Fob_reopen(struct U2Fob* device) { 142 | U2Fob_close(device); 143 | device->dev = hid_open_path(device->path); 144 | return device->dev != NULL ? -ERR_NONE : -ERR_OTHER; 145 | } 146 | 147 | void U2Fob_setLog(struct U2Fob* device, FILE* fd, int level) { 148 | device->logfp = fd; 149 | device->loglevel = level; 150 | device->logtime = 0; 151 | U2Fob_deltaTime(&device->logtime); 152 | } 153 | 154 | static 155 | void U2Fob_logFrame(struct U2Fob* device, 156 | const char* tag, const U2FHID_FRAME* f) { 157 | if (device->logfp) { 158 | fprintf(device->logfp, "t+%.3f", U2Fob_deltaTime(&device->logtime)); 159 | fprintf(device->logfp, "%s %08x:%02x", tag, f->cid, f->type); 160 | if (f->type & TYPE_INIT) { 161 | int len = f->init.bcnth * 256 + f->init.bcntl; 162 | fprintf(device->logfp, "[%d]:", len); 163 | for (size_t i = 0; i < sizeof(f->init.data); ++i) 164 | fprintf(device->logfp, "%02X", f->init.data[i]); 165 | } else { 166 | fprintf(device->logfp, ":"); 167 | for (size_t i = 0; i < sizeof(f->cont.data); ++i) 168 | fprintf(device->logfp, "%02X", f->cont.data[i]); 169 | } 170 | fprintf(device->logfp, "\n"); 171 | } 172 | } 173 | 174 | int U2Fob_sendHidFrame(struct U2Fob* device, U2FHID_FRAME* f) { 175 | uint8_t d[sizeof(U2FHID_FRAME) + 1]; 176 | int res; 177 | 178 | d[0] = 0; // un-numbered report 179 | f->cid = htonl(f->cid); // cid is in network order on the wire 180 | memcpy(d + 1, f, sizeof(U2FHID_FRAME)); 181 | f->cid = ntohl(f->cid); 182 | 183 | if (!device->dev) return -ERR_OTHER; 184 | res = hid_write(device->dev, d, sizeof(d)); 185 | 186 | if (res == sizeof(d)) { 187 | U2Fob_logFrame(device, ">", f); 188 | return 0; 189 | } 190 | 191 | return -ERR_OTHER; 192 | } 193 | 194 | int U2Fob_receiveHidFrame(struct U2Fob* device, U2FHID_FRAME* r, float to) { 195 | if (to <= 0.0) 196 | return -ERR_MSG_TIMEOUT; 197 | 198 | if (!device->dev) return -ERR_OTHER; 199 | memset((int8_t*)r, 0xEE, sizeof(U2FHID_FRAME)); 200 | int res = hid_read_timeout(device->dev, 201 | (uint8_t*) r, sizeof(U2FHID_FRAME), 202 | (int) (to * 1000)); 203 | if (res == sizeof(U2FHID_FRAME)) { 204 | r->cid = ntohl(r->cid); 205 | U2Fob_logFrame(device, "<", r); 206 | return 0; 207 | } 208 | 209 | if (res == -1) 210 | return -ERR_OTHER; 211 | 212 | if (device->logfp) { 213 | fprintf(device->logfp, "t+%.3f", U2Fob_deltaTime(&device->logtime)); 214 | fprintf(device->logfp, "< (timeout)\n"); 215 | } 216 | 217 | return -ERR_MSG_TIMEOUT; 218 | } 219 | 220 | int U2Fob_init(struct U2Fob* device) { 221 | int res; 222 | U2FHID_FRAME challenge; 223 | 224 | for (size_t i = 0; i < sizeof(device->nonce); ++i) { 225 | device->nonce[i] ^= (rand() >> 3); 226 | } 227 | 228 | challenge.cid = device->cid; 229 | challenge.init.cmd = U2FHID_INIT | TYPE_INIT; 230 | challenge.init.bcnth = 0; 231 | challenge.init.bcntl = INIT_NONCE_SIZE; 232 | memcpy(challenge.init.data, device->nonce, INIT_NONCE_SIZE); 233 | 234 | res = U2Fob_sendHidFrame(device, &challenge); 235 | if (res != 0) return res; 236 | 237 | for (;;) { 238 | U2FHID_FRAME response; 239 | res = U2Fob_receiveHidFrame(device, &response, 2.0); 240 | 241 | if (res == -ERR_MSG_TIMEOUT) return res; 242 | if (res == -ERR_OTHER) return res; 243 | 244 | if (response.cid != challenge.cid) continue; 245 | if (response.init.cmd != challenge.init.cmd) continue; 246 | if (MSG_LEN(response) != sizeof(U2FHID_INIT_RESP)) continue; 247 | if (memcmp(response.init.data, challenge.init.data, INIT_NONCE_SIZE)) 248 | continue; 249 | 250 | device->cid = 251 | (response.init.data[8] << 24) | 252 | (response.init.data[9] << 16) | 253 | (response.init.data[10] << 8) | 254 | (response.init.data[11] << 0); 255 | 256 | break; 257 | } 258 | 259 | return 0; 260 | } 261 | 262 | int U2Fob_send(struct U2Fob* device, uint8_t cmd, 263 | const void* data, size_t size) { 264 | U2FHID_FRAME frame; 265 | int res; 266 | size_t frameLen; 267 | uint8_t seq = 0; 268 | uint8_t* pData = (uint8_t*) data; 269 | 270 | frame.cid = device->cid; 271 | frame.init.cmd = TYPE_INIT | cmd; 272 | frame.init.bcnth = (size >> 8) & 255; 273 | frame.init.bcntl = (size & 255); 274 | 275 | frameLen = min(size, sizeof(frame.init.data)); 276 | memset(frame.init.data, 0xEE, sizeof(frame.init.data)); 277 | memcpy(frame.init.data, pData, frameLen); 278 | 279 | do { 280 | res = U2Fob_sendHidFrame(device, &frame); 281 | if (res != 0) return res; 282 | 283 | size -= frameLen; 284 | pData += frameLen; 285 | 286 | frame.cont.seq = seq++; 287 | frameLen = min(size, sizeof(frame.cont.data)); 288 | memset(frame.cont.data, 0xEE, sizeof(frame.cont.data)); 289 | memcpy(frame.cont.data, pData, frameLen); 290 | } while (size); 291 | 292 | return 0; 293 | } 294 | 295 | int U2Fob_recv(struct U2Fob* device, uint8_t* cmd, 296 | void* data, size_t max, 297 | float timeout) { 298 | U2FHID_FRAME frame; 299 | int res, result; 300 | size_t totalLen, frameLen; 301 | uint8_t seq = 0; 302 | uint8_t* pData = (uint8_t*) data; 303 | uint64_t timeTracker = 0; 304 | 305 | U2Fob_deltaTime(&timeTracker); 306 | 307 | do { 308 | res = U2Fob_receiveHidFrame(device, &frame, timeout); 309 | if (res != 0) return res; 310 | 311 | timeout -= U2Fob_deltaTime(&timeTracker); 312 | } while (frame.cid != device->cid || FRAME_TYPE(frame) != TYPE_INIT); 313 | 314 | if (frame.init.cmd == U2FHID_ERROR) return -frame.init.data[0]; 315 | 316 | *cmd = frame.init.cmd; 317 | 318 | totalLen = min(max, MSG_LEN(frame)); 319 | frameLen = min(sizeof(frame.init.data), totalLen); 320 | 321 | result = totalLen; 322 | 323 | memcpy(pData, frame.init.data, frameLen); 324 | totalLen -= frameLen; 325 | pData += frameLen; 326 | 327 | while (totalLen) { 328 | res = U2Fob_receiveHidFrame(device, &frame, timeout); 329 | if (res != 0) return res; 330 | 331 | timeout -= U2Fob_deltaTime(&timeTracker); 332 | 333 | if (frame.cid != device->cid) continue; 334 | if (FRAME_TYPE(frame) != TYPE_CONT) return -ERR_INVALID_SEQ; 335 | if (FRAME_SEQ(frame) != seq++) return -ERR_INVALID_SEQ; 336 | 337 | frameLen = min(sizeof(frame.cont.data), totalLen); 338 | 339 | memcpy(pData, frame.cont.data, frameLen); 340 | totalLen -= frameLen; 341 | pData += frameLen; 342 | } 343 | 344 | return result; 345 | } 346 | 347 | int U2Fob_apdu(struct U2Fob* device, 348 | uint8_t CLA, uint8_t INS, uint8_t P1, uint8_t P2, 349 | const std::string& out, 350 | std::string* in) { 351 | size_t off = 0; 352 | 353 | if (CLA == 0) 354 | off = 5; 355 | else 356 | off = 3; 357 | 358 | uint8_t buf[4096]; 359 | size_t bufSize = out.size() + off + 2 + 2; 360 | uint8_t cmd = U2FHID_MSG; 361 | 362 | // Construct outgoing message. 363 | memset(buf, 0xEE, sizeof(buf)); 364 | buf[0] = CLA; 365 | buf[1] = INS; 366 | buf[2] = P1; 367 | buf[3] = P2; 368 | 369 | if (CLA == 0) { 370 | buf[4] = 0; // extended length 371 | buf[5] = (out.size() >> 8) & 255; 372 | buf[6] = (out.size() & 255); 373 | } else { 374 | buf[4] = out.size() & 0xff; 375 | } 376 | memcpy(buf + off + 2, out.data(), out.size()); 377 | buf[off + 2 + out.size() + 0] = 0; 378 | buf[off + 2 + out.size() + 1] = 0; 379 | 380 | int res = U2Fob_send(device, cmd, buf, bufSize); 381 | if (res != 0) return res; 382 | 383 | memset(buf, 0xEE, sizeof(buf)); 384 | res = U2Fob_recv(device, &cmd, buf, sizeof(buf), 5.0); 385 | if (res < 0) return res; 386 | 387 | if (cmd != U2FHID_MSG) return -ERR_OTHER; 388 | 389 | uint16_t sw12; 390 | 391 | if (res < 2) return -ERR_OTHER; 392 | sw12 = (buf[res - 2] << 8) | buf[res - 1]; 393 | res -= 2; 394 | 395 | in->assign(reinterpret_cast(buf), res); 396 | 397 | return sw12; 398 | } 399 | 400 | bool getCertificate(const U2F_REGISTER_RESP& rsp, 401 | std::string* cert) { 402 | size_t hkLen = rsp.keyHandleLen; 403 | 404 | CHECK_GE(hkLen, 64); 405 | CHECK_LT(hkLen, sizeof(rsp.keyHandleCertSig)); 406 | 407 | size_t certOff = hkLen; 408 | size_t certLen = sizeof(rsp.keyHandleCertSig) - certOff; 409 | const uint8_t* p = &rsp.keyHandleCertSig[certOff]; 410 | 411 | CHECK_GE(certLen, 4); 412 | CHECK_EQ(p[0], 0x30); 413 | 414 | CHECK_GE(p[1], 0x81); 415 | CHECK_LE(p[1], 0x82); 416 | 417 | size_t seqLen; 418 | size_t headerLen; 419 | if (p[1] == 0x81) { 420 | seqLen = p[2]; 421 | headerLen = 3; 422 | } else if (p[1] == 0x82) { 423 | seqLen = p[2] * 256 + p[3]; 424 | headerLen = 4; 425 | } else { 426 | // FAIL 427 | AbortOrNot(); 428 | } 429 | 430 | CHECK_LE(seqLen, certLen - headerLen); 431 | 432 | cert->assign(reinterpret_cast(p), seqLen + headerLen); 433 | return true; 434 | } 435 | 436 | bool getSignature(const U2F_REGISTER_RESP& rsp, 437 | std::string* sig) { 438 | std::string cert; 439 | CHECK_NE(false, getCertificate(rsp, &cert)); 440 | 441 | size_t sigOff = rsp.keyHandleLen + cert.size(); 442 | CHECK_LE(sigOff, sizeof(rsp.keyHandleCertSig)); 443 | 444 | size_t sigLen = sizeof(rsp.keyHandleCertSig) - sigOff; 445 | const uint8_t* p = &rsp.keyHandleCertSig[sigOff]; 446 | 447 | CHECK_GE(sigLen, 2); 448 | CHECK_EQ(p[0], 0x30); 449 | 450 | size_t seqLen = p[1]; 451 | CHECK_LE(seqLen, sigLen - 2); 452 | 453 | sig->assign(reinterpret_cast(p), seqLen + 2); 454 | return true; 455 | } 456 | 457 | bool getSubjectPublicKey(const std::string& cert, 458 | std::string* pk) { 459 | CHECK_GE(cert.size(), P256_POINT_SIZE); 460 | 461 | // Explicitly search for asn1 lead-in sequence of p256-ecdsa public key. 462 | const char asn1[] = "3059301306072A8648CE3D020106082A8648CE3D030107034200"; 463 | std::string pkStart(a2b(asn1)); 464 | 465 | size_t off = cert.find(pkStart); 466 | CHECK_NE(off, std::string::npos); 467 | 468 | off += pkStart.size(); 469 | CHECK_LE(off, cert.size() - P256_POINT_SIZE); 470 | 471 | pk->assign(cert, off, P256_POINT_SIZE); 472 | return true; 473 | } 474 | 475 | bool getCertSignature(const std::string& cert, 476 | std::string* sig) { 477 | // Explicitly search asn1 lead-in sequence of p256-ecdsa signature. 478 | const char asn1[] = "300A06082A8648CE3D04030203"; 479 | std::string sigStart(a2b(asn1)); 480 | 481 | size_t off = cert.find(sigStart); 482 | CHECK_NE(off, std::string::npos); 483 | 484 | off += sigStart.size(); 485 | CHECK_LE(off, cert.size() - 8); 486 | 487 | size_t bitStringLen = cert[off] & 255; 488 | CHECK_EQ(bitStringLen, cert.size() - off - 1); 489 | CHECK_EQ(cert[off + 1], 0); 490 | 491 | sig->assign(cert, off + 2, cert.size() - off - 2); 492 | return true; 493 | } 494 | 495 | bool verifyCertificate(const std::string& pk, 496 | const std::string& cert) { 497 | CHECK_EQ(true, false); // not yet implemented 498 | } 499 | -------------------------------------------------------------------------------- /u2f_util.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Google Inc. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | #ifndef __U2F_UTIL_H_INCLUDED__ 8 | #define __U2F_UTIL_H_INCLUDED__ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include "u2f.h" 19 | #include "u2f_hid.h" 20 | 21 | #include "hidapi.h" 22 | 23 | #ifdef _MSC_VER 24 | #include 25 | #define usleep(x) Sleep((x + 999) / 1000) 26 | #else 27 | #include 28 | #define max(a,b) \ 29 | ({ __typeof__ (a) _a = (a); \ 30 | __typeof__ (b) _b = (b); \ 31 | _a > _b ? _a : _b; }) 32 | 33 | #define min(a,b) \ 34 | ({ __typeof__ (a) _a = (a); \ 35 | __typeof__ (b) _b = (b); \ 36 | _a < _b ? _a : _b; }) 37 | #endif 38 | 39 | #define CHECK_INFO __FUNCTION__ << "[" << __LINE__ << "]:" 40 | 41 | #define CHECK_EQ(a,b) do { if ((a)!=(b)) { std::cerr << "\x1b[31mCHECK_EQ fail at " << CHECK_INFO#a << " != "#b << ":\x1b[0m "; AbortOrNot(); }} while(0) 42 | #define CHECK_NE(a,b) do { if ((a)==(b)) { std::cerr << "\x1b[31mCHECK_NE fail at " << CHECK_INFO#a << " == "#b << ":\x1b[0m "; AbortOrNot(); }} while(0) 43 | #define CHECK_GE(a,b) do { if ((a)<(b)) { std::cerr << "\x1b[31mCHECK_GE fail at " << CHECK_INFO#a << " < "#b << ":\x1b[0m "; AbortOrNot(); }} while(0) 44 | #define CHECK_GT(a,b) do { if ((a)<=(b)) { std::cerr << "\x1b[31mCHECK_GT fail at " << CHECK_INFO#a << " < "#b << ":\x1b[0m "; AbortOrNot(); }} while(0) 45 | #define CHECK_LT(a,b) do { if ((a)>=(b)) { std::cerr << "\x1b[31mCHECK_LT fail at " << CHECK_INFO#a << " >= "#b << ":\x1b[0m "; AbortOrNot(); }} while(0) 46 | #define CHECK_LE(a,b) do { if ((a)>(b)) { std::cerr << "\x1b[31mCHECK_LE fail at " << CHECK_INFO#a << " > "#b << ":\x1b[0m "; AbortOrNot(); }} while(0) 47 | 48 | #define PASS(x) do { (x); std::cout << "\x1b[32mPASS("#x")\x1b[0m" << std::endl; } while(0) 49 | 50 | class U2F_info { 51 | public: 52 | U2F_info(const char* func, int line) { 53 | std::cout << func << "[" << line << "]"; 54 | } 55 | ~U2F_info() { 56 | std::cout << std::endl; 57 | } 58 | std::ostream& operator<<(const char* s) { 59 | std::cout << s; 60 | return std::cout; 61 | } 62 | }; 63 | 64 | extern int arg_Verbose; 65 | #define INFO if (arg_Verbose) U2F_info(__FUNCTION__, __LINE__) << ": " 66 | 67 | std::string b2a(const void* ptr, size_t size); 68 | std::string b2a(const std::string& s); 69 | std::string a2b(const std::string& s); 70 | 71 | float U2Fob_deltaTime(uint64_t* state); 72 | 73 | struct U2Fob { 74 | hid_device* dev; 75 | char* path; 76 | uint32_t cid; 77 | int loglevel; 78 | uint8_t nonce[INIT_NONCE_SIZE]; 79 | uint64_t logtime; 80 | FILE* logfp; 81 | char logbuf[BUFSIZ]; 82 | }; 83 | 84 | struct U2Fob* U2Fob_create(); 85 | 86 | void U2Fob_destroy(struct U2Fob* device); 87 | 88 | void U2Fob_setLog(struct U2Fob* device, FILE* fd, int logMask); 89 | 90 | int U2Fob_open(struct U2Fob* device, const char* pathname); 91 | 92 | void U2Fob_close(struct U2Fob* device); 93 | 94 | int U2Fob_reopen(struct U2Fob* device); 95 | 96 | int U2Fob_init(struct U2Fob* device); 97 | 98 | uint32_t U2Fob_getCid(struct U2Fob* device); 99 | 100 | int U2Fob_sendHidFrame(struct U2Fob* device, U2FHID_FRAME* out); 101 | 102 | int U2Fob_receiveHidFrame(struct U2Fob* device, U2FHID_FRAME* in, 103 | float timeoutSeconds); 104 | 105 | int U2Fob_send(struct U2Fob* device, uint8_t cmd, 106 | const void* data, size_t size); 107 | 108 | int U2Fob_recv(struct U2Fob* device, uint8_t* cmd, 109 | void* data, size_t size, 110 | float timeoutSeconds); 111 | 112 | // returns 113 | // negative error 114 | // positive sw12, e.g. 0x9000, 0x6985 etc. 115 | int U2Fob_apdu(struct U2Fob* device, 116 | uint8_t CLA, uint8_t INS, uint8_t P1, uint8_t P2, 117 | const std::string& out, 118 | std::string* in); 119 | 120 | bool getCertificate(const U2F_REGISTER_RESP& rsp, 121 | std::string* cert); 122 | 123 | bool getSignature(const U2F_REGISTER_RESP& rsp, 124 | std::string* sig); 125 | 126 | bool getSubjectPublicKey(const std::string& cert, 127 | std::string* pk); 128 | 129 | bool getCertSignature(const std::string& cert, 130 | std::string* sig); 131 | 132 | bool verifyCertificate(const std::string& pk, 133 | const std::string& cert); 134 | 135 | #endif // __U2F_UTIL_H_INCLUDED__ 136 | --------------------------------------------------------------------------------