/* -*- mode:C; c-file-style: "bsd" -*- */ /* * Copyright (c) 2008, 2009, Yubico AB * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <ykpers.h> #include <yubikey.h> /* To get yubikey_modhex_encode and yubikey_hex_encode */ const char *usage = "Usage: ykpersonalize [options]\n" "-1 change the first configuration. This is the default and\n" " is normally used for true OTP generation.\n" " In this configuration, TKTFLAG_APPEND_CR is set by default.\n" "-2 change the second configuration. This is for Yubikey II only\n" " and is then normally used for static key generation.\n" " In this configuration, TKTFLAG_APPEND_CR, CFGFLAG_STATIC_TICKET,\n" " CFGFLAG_STRONG_PW1, CFGFLAG_STRONG_PW2 and CFGFLAG_MAN_UPDATE\n" " are set by default.\n" "-sFILE save configuration to FILE instead of key.\n" " (if FILE is -, send to stdout)\n" "-iFILE read configuration from FILE.\n" " (if FILE is -, read from stdin)\n" "-aXXX.. A 32 char hex value (not modhex) of a fixed AES key to use\n" "-cXXX.. A 12 char hex value to use as access code for programming\n" " (this does NOT SET the access code, that's done with -oaccess=)\n" "-oOPTION change configuration option. Possible OPTION arguments are:\n" " salt=ssssssss Salt to be used for key generation. If\n" " none is given, a unique random one will be\n" " generated.\n" " fixed=xxxxxxxxxxx The public identity of key, in MODHEX.\n" " This is 0-16 characters long.\n" " uid=xxxxxx The uid part of the generated ticket, in HEX.\n" " MUST be 12 characters long.\n" " access=xxxxxxxxxxx New access code to set, in HEX.\n" " MUST be 12 characters long.\n" " [-]tab-first set/clear the TAB_FIRST ticket flag.\n" " [-]append-tab1 set/clear the APPEND_TAB1 ticket flag.\n" " [-]append-tab2 set/clear the APPEND_TAB2 ticket flag.\n" " [-]append-delay1 set/clear the APPEND_DELAY1 ticket flag.\n" " [-]append-delay2 set/clear the APPEND_DELAY2 ticket flag.\n" " [-]append-cr set/clear the APPEND_CR ticket flag.\n" " [-]protect-cfg2 set/clear the PROTECT_CFG2 ticket flag.\n" " (only with Yubikey II!)\n" " [-]send-ref set/clear the SEND_REF configuration flag.\n" " [-]ticket-first set/clear the TICKET_FIRST configuration flag.\n" " (only with Yubikey I!)\n" " [-]pacing-10ms set/clear the PACING_10MS configuration flag.\n" " [-]pacing-20ms set/clear the PACING_20MS configuration flag.\n" " [-]allow-hidtrig set/clear the ALLOW_HIDTRIG configuration flag.\n" " (only with Yubikey I!)\n" " [-]static-ticket set/clear the STATIC_TICKET configuration flag.\n" " [-]short-ticket set/clear the SHORT_TICKET configuration flag.\n" " (only with Yubikey II!)\n" " [-]strong-pw1 set/clear the STRONG_PW1 configuration flag.\n" " (only with Yubikey II!)\n" " [-]strong-pw2 set/clear the STRONG_PW2 configuration flag.\n" " (only with Yubikey II!)\n" " [-]man-update set/clear the MAN_UPDATE configuration flag.\n" " (only with Yubikey II!)\n" "-y always commit (do not prompt)\n" "\n" "-v verbose\n" "-h help (this text)\n" ; const char *optstring = "12a:c:hi:o:s:vy"; static int reader(char *buf, size_t count, void *stream) { return (int)fread(buf, 1, count, (FILE *)stream); } static int writer(const char *buf, size_t count, void *stream) { return (int)fwrite(buf, 1, count, (FILE *)stream); } static int hex_modhex_decode(unsigned char *result, size_t *resultlen, const char *str, size_t strl, size_t minsize, size_t maxsize, bool primarily_modhex) { if (strl >= 2) { if (strncmp(str, "m:", 2) == 0 || strncmp(str, "M:", 2) == 0) { str += 2; strl -= 2; primarily_modhex = true; } else if (strncmp(str, "h:", 2) == 0 || strncmp(str, "H:", 2) == 0) { str += 2; strl -= 2; primarily_modhex = false; } } if ((strl % 2 != 0) || (strl < minsize) || (strl > maxsize)) { return -1; } *resultlen = strl / 2; if (primarily_modhex) { if (yubikey_modhex_p(str)) { yubikey_modhex_decode((char *)result, str, strl); return 1; } } else { if (yubikey_hex_p(str)) { yubikey_hex_decode((char *)result, str, strl); return 1; } } return 0; } static void report_yk_error() { if (ykp_errno) fprintf(stderr, "Yubikey personalization error: %s\n", ykp_strerror(ykp_errno)); if (yk_errno) { if (yk_errno == YK_EUSBERR) { fprintf(stderr, "USB error: %s\n", yk_usb_strerror()); } else { fprintf(stderr, "Yubikey core error: %s\n", yk_strerror(yk_errno)); } } } int main(int argc, char **argv) { char c; FILE *inf = NULL; const char *infname = NULL; FILE *outf = NULL; const char *outfname = NULL; bool verbose = false; bool aesviahash = false; const char *aeshash = NULL; bool use_access_code = false, new_access_code = false; unsigned char access_code[256]; YK_KEY *yk = 0; YKP_CONFIG *cfg = ykp_create_config(); YK_STATUS *st = ykds_alloc(); bool autocommit = false; bool error = false; int exit_code = 0; /* Options */ char *salt = NULL; ykp_errno = 0; yk_errno = 0; /* Assume the worst */ error = true; if (!yk_init()) { exit_code = 1; goto err; } if (argc == 2 && strcmp (argv[1], "-h") == 0) { fputs(usage, stderr); goto err; } if (!(yk = yk_open_first_key())) { exit_code = 1; goto err; } if (!yk_get_status(yk, st)) { exit_code = 1; goto err; } printf("Firmware version %d.%d.%d Touch level %d ", ykds_version_major(st), ykds_version_minor(st), ykds_version_build(st), ykds_touch_level(st)); if (ykds_pgm_seq(st)) printf("Program sequence %d\n", ykds_pgm_seq(st)); else printf("Unconfigured\n"); if (!ykp_configure_for(cfg, 1, st)) goto err; while((c = getopt(argc, argv, optstring)) != -1) { switch (c) { case '1': if (!ykp_configure_for(cfg, 1, st)) goto err; break; case '2': if (!ykp_configure_for(cfg, 2, st)) goto err; break; case 'i': infname = optarg; break; case 's': outfname = optarg; break; case 'a': aesviahash = true; aeshash = optarg; break; case 'c': { size_t access_code_len = 0; int rc = hex_modhex_decode(access_code, &access_code_len, optarg, strlen(optarg), 12, 12, false); if (rc <= 0) { fprintf(stderr, "Invalid access code string: %s\n", optarg); exit_code = 1; goto err; } if (!new_access_code) ykp_set_access_code(cfg, access_code, access_code_len); use_access_code = true; break; } case 'o': if (strncmp(optarg, "salt=", 5) == 0) salt = strdup(optarg+5); else if (strncmp(optarg, "fixed=", 6) == 0) { const char *fixed = optarg+6; size_t fixedlen = strlen (fixed); unsigned char fixedbin[256]; size_t fixedbinlen = 0; int rc = hex_modhex_decode(fixedbin, &fixedbinlen, fixed, fixedlen, 0, 16, true); if (rc <= 0) { fprintf(stderr, "Invalid fixed string: %s\n", fixed); exit_code = 1; goto err; } ykp_set_fixed(cfg, fixedbin, fixedbinlen); } else if (strncmp(optarg, "uid=", 4) == 0) { const char *uid = optarg+4; size_t uidlen = strlen (uid); unsigned char uidbin[256]; size_t uidbinlen = 0; int rc = hex_modhex_decode(uidbin, &uidbinlen, uid, uidlen, 12, 12, false); if (rc <= 0) { fprintf(stderr, "Invalid uid string: %s\n", uid); exit_code = 1; goto err; } ykp_set_uid(cfg, uidbin, uidbinlen); } else if (strncmp(optarg, "access=", 7) == 0) { const char *acc = optarg+7; size_t acclen = strlen (acc); unsigned char accbin[256]; size_t accbinlen = 0; int rc = hex_modhex_decode (accbin, &accbinlen, acc, acclen, 12, 12, false); if (rc <= 0) { fprintf(stderr, "Invalid access code string: %s\n", acc); exit_code = 1; goto err; } ykp_set_access_code(cfg, accbin, accbinlen); new_access_code = true; } else if (strcmp(optarg, "tab-first") == 0) ykp_set_tktflag_TAB_FIRST(cfg, true); else if (strcmp(optarg, "-tab-first") == 0) ykp_set_tktflag_TAB_FIRST(cfg, false); else if (strcmp(optarg, "append-tab1") == 0) ykp_set_tktflag_APPEND_TAB1(cfg, true); else if (strcmp(optarg, "-append-tab1") == 0) ykp_set_tktflag_APPEND_TAB1(cfg, false); else if (strcmp(optarg, "append-tab2") == 0) ykp_set_tktflag_APPEND_TAB1(cfg, true); else if (strcmp(optarg, "-append-tab2") == 0) ykp_set_tktflag_APPEND_TAB1(cfg, false); else if (strcmp(optarg, "append-delay1") == 0) ykp_set_tktflag_APPEND_DELAY1(cfg, true); else if (strcmp(optarg, "-append-delay1") == 0) ykp_set_tktflag_APPEND_DELAY1(cfg, false); else if (strcmp(optarg, "append-delay2") == 0) ykp_set_tktflag_APPEND_DELAY2(cfg, true); else if (strcmp(optarg, "-append-delay2") == 0) ykp_set_tktflag_APPEND_DELAY2(cfg, false); else if (strcmp(optarg, "append-cr") == 0) ykp_set_tktflag_APPEND_CR(cfg, true); else if (strcmp(optarg, "-append-cr") == 0) ykp_set_tktflag_APPEND_CR(cfg, false); else if (strcmp(optarg, "protect-cfg2") == 0) ykp_set_tktflag_PROTECT_CFG2(cfg, true); else if (strcmp(optarg, "-protect-cfg2") == 0) ykp_set_tktflag_PROTECT_CFG2(cfg, false); else if (strcmp(optarg, "send-ref") == 0) ykp_set_cfgflag_SEND_REF(cfg, true); else if (strcmp(optarg, "-send-ref") == 0) ykp_set_cfgflag_SEND_REF(cfg, false); else if (strcmp(optarg, "ticket-first") == 0) ykp_set_cfgflag_TICKET_FIRST(cfg, true); else if (strcmp(optarg, "-ticket-first") == 0) ykp_set_cfgflag_TICKET_FIRST(cfg, false); else if (strcmp(optarg, "pacing-10ms") == 0) ykp_set_cfgflag_PACING_10MS(cfg, true); else if (strcmp(optarg, "-pacing-10ms") == 0) ykp_set_cfgflag_PACING_10MS(cfg, false); else if (strcmp(optarg, "pacing-20ms") == 0) ykp_set_cfgflag_PACING_20MS(cfg, true); else if (strcmp(optarg, "-pacing-20ms") == 0) ykp_set_cfgflag_PACING_20MS(cfg, false); else if (strcmp(optarg, "allow-hidtrig") == 0) ykp_set_cfgflag_ALLOW_HIDTRIG(cfg, true); else if (strcmp(optarg, "-allow-hidtrig") == 0) ykp_set_cfgflag_ALLOW_HIDTRIG(cfg, false); else if (strcmp(optarg, "static-ticket") == 0) ykp_set_cfgflag_STATIC_TICKET(cfg, true); else if (strcmp(optarg, "-static-ticket") == 0) ykp_set_cfgflag_STATIC_TICKET(cfg, false); else if (strcmp(optarg, "short-ticket") == 0) ykp_set_cfgflag_SHORT_TICKET(cfg, true); else if (strcmp(optarg, "-short-ticket") == 0) ykp_set_cfgflag_SHORT_TICKET(cfg, false); else if (strcmp(optarg, "strong-pw1") == 0) ykp_set_cfgflag_STRONG_PW1(cfg, true); else if (strcmp(optarg, "-strong-pw1") == 0) ykp_set_cfgflag_STRONG_PW1(cfg, false); else if (strcmp(optarg, "strong-pw2") == 0) ykp_set_cfgflag_STRONG_PW2(cfg, true); else if (strcmp(optarg, "-strong-pw2") == 0) ykp_set_cfgflag_STRONG_PW2(cfg, false); else if (strcmp(optarg, "man-update") == 0) ykp_set_cfgflag_MAN_UPDATE(cfg, true); else if (strcmp(optarg, "-man-update") == 0) ykp_set_cfgflag_MAN_UPDATE(cfg, false); else { fprintf(stderr, "Unknown option '%s'\n", optarg); fputs(usage, stderr); exit_code = 1; goto err; } break; case 'v': verbose = true; break; case 'y': autocommit = true; break; case 'h': default: fputs(usage, stderr); exit_code = 0; goto err; } } if (infname) { if (strcmp(infname, "-") == 0) inf = stdin; else inf = fopen(infname, "r"); if (inf == NULL) { fprintf(stderr, "Couldn't open %s for reading: %s\n", infname, strerror(errno)); exit_code = 1; goto err; } } if (outfname) { if (strcmp(outfname, "-") == 0) outf = stdout; else outf = fopen(outfname, "w"); if (outf == NULL) { fprintf(stderr, "Couldn't open %s for writing: %s\n", outfname, strerror(errno)); exit(1); } } if (inf) { if (!ykp_read_config(cfg, reader, inf)) goto err; } else if (aesviahash) { if (ykp_AES_key_from_hex(cfg, aeshash)) { fprintf(stderr, "Bad AES key: %s\n", aeshash); fflush(stderr); goto err; } } else { char passphrasebuf[256]; size_t passphraselen; fprintf(stderr, "Passphrase to create AES key: "); fflush(stderr); fgets(passphrasebuf, sizeof(passphrasebuf), stdin); passphraselen = strlen(passphrasebuf); if (passphrasebuf[passphraselen - 1] == '\n') passphrasebuf[passphraselen - 1] = '\0'; if (!ykp_AES_key_from_passphrase(cfg, passphrasebuf, salt)) goto err; } if (outf) { if (!ykp_write_config(cfg, writer, outf)) goto err; } else { char commitbuf[256]; size_t commitlen; fprintf(stderr, "Configuration data to be written to key configuration %d:\n\n", ykp_config_num(cfg)); ykp_write_config(cfg, writer, stderr); fprintf(stderr, "\nCommit? (y/n) [n]: "); if (autocommit) { strcpy(commitbuf, "yes"); puts(commitbuf); } else { fgets(commitbuf, sizeof(commitbuf), stdin); } commitlen = strlen(commitbuf); if (commitbuf[commitlen - 1] == '\n') commitbuf[commitlen - 1] = '\0'; if (strcmp(commitbuf, "y") == 0 || strcmp(commitbuf, "yes") == 0) { exit_code = 2; if (verbose) printf("Attempting to write configuration to the yubikey..."); if (!yk_write_config(yk, ykp_core_config(cfg), ykp_config_num(cfg), use_access_code ? access_code : NULL)) { if (verbose) printf(" failure\n"); goto err; } if (verbose) printf(" success\n"); } } exit_code = 0; error = false; err: if (error) { report_yk_error(); } if (salt) free(salt); if (st) free(st); if (inf) fclose(inf); if (outf) fclose(outf); if (yk && !yk_close_key(yk)) { report_yk_error(); exit_code = 2; } if (!yk_release()) { report_yk_error(); exit_code = 2; } if (cfg) ykp_free_config(cfg); exit(exit_code); }