├── .gitignore ├── Makefile ├── README.md └── ssh-otp.c /.gitignore: -------------------------------------------------------------------------------- 1 | ssh-otp 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ssh-otp: ssh-otp.c 2 | $(CC) -o $@ $< -lcrypto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ssh-otp 2 | ======= 3 | 4 | This program offers ssh logins an optional authentication code (TOTP, 5 | compatible with Google Authenticator mobile apps). It is based on a [ruby 6 | implementation by Richard Taylor][1]. 7 | 8 | Using this authentication code requires using SSH keys. Change your 9 | `authorized_keys` file to add a `command=` argument: 10 | 11 | command="/usr/bin/ssh-otp 4rr7kc47sc5a2fgt" ssh-dsa AAA... 12 | 13 | Modify the path for `ssh-otp` as appropriate. 14 | 15 | The `4rr7kc47sc5a2fgt` is a secret key that you should generate yourself 16 | (obviously, don't use this one). If you like, you can [generate a new secret 17 | key at random.org][2], or use any other method you trust. They key is a 18 | 16-character base32 (a-z2-7) string, so if you're using random.org, substitute 19 | any other letters or numbers for 0, 1, 8, and 9. 20 | 21 | Configure your Google Authenticator mobile app by adding a new entry with the 22 | same secret key. Be sure the clock on your mobile device is reasonably 23 | synchronised with your ssh server. 24 | 25 | [1]: https://moocode.com/posts/5-simple-two-factor-ssh-authentication 26 | [2]: https://www.random.org/strings/?num=10&len=16&digits=on&loweralpha=on&unique=on&format=html&rnd=new 27 | -------------------------------------------------------------------------------- /ssh-otp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #define MAX_SKEW 1 8 | 9 | int b32decode(const char *s, unsigned char *b) 10 | { 11 | int i; 12 | 13 | memset(b, 0, 10); 14 | for (i = 0; i < 16; i++) { 15 | unsigned char x; 16 | if (isalpha(s[i])) { 17 | x = toupper(s[i]) - 'A'; 18 | } else if (s[i] >= '2' && s[i] <= '7') { 19 | x = s[i] - '2' + 26; 20 | } else { 21 | return 0; 22 | } 23 | b[5*i / 8] |= (x << 3) >> (5*i % 8); 24 | if (5*i % 8 >= 4) { 25 | b[5*i / 8 + 1] |= x << (3 + 8 - (5*i % 8)); 26 | } 27 | } 28 | return 1; 29 | } 30 | 31 | void hotp(const unsigned char *sbytes, time_t movingFactor, char *code) 32 | { 33 | unsigned char data[8]; 34 | int i, offset, bin_code, otp; 35 | 36 | for (i = 0; i < 8; i++) { 37 | data[i] = i < 4 ? 0 : movingFactor >> (56 - 8*i); 38 | } 39 | unsigned char *r = HMAC(EVP_sha1(), sbytes, 10, data, sizeof(data), NULL, NULL); 40 | offset = r[19] & 0xf; 41 | bin_code = ((r[offset] << 24) | (r[offset+1] << 16) | (r[offset+2] << 8) | r[offset+3]) & 0x7fffffff; 42 | otp = bin_code % 1000000; 43 | sprintf(code, "%06d", otp); 44 | } 45 | 46 | void proceed() 47 | { 48 | if (getenv("SSH_ORIGINAL_COMMAND") != NULL) { 49 | execl("/bin/sh", "/bin/sh", "-c", getenv("SSH_ORIGINAL_COMMAND"), NULL); 50 | } else { 51 | execl(getenv("SHELL"), "-", NULL); 52 | } 53 | } 54 | 55 | int main(int argc, char *argv[]) 56 | { 57 | int i; 58 | unsigned char sbytes[10]; 59 | char code[7], input_a[10]; 60 | char *input; 61 | time_t now; 62 | 63 | if (argc < 2) { 64 | exit(1); 65 | } 66 | 67 | input = getenv("OTP_TOKEN"); 68 | if (!input || strcmp(input, "") == 0) { 69 | fprintf(stderr, "Enter the validation code: "); 70 | if (fgets(input_a, sizeof(input_a), stdin) == NULL) { 71 | exit(1); 72 | } 73 | input = input_a; 74 | } 75 | 76 | if (!b32decode(argv[1], sbytes)) { 77 | exit(1); 78 | } 79 | 80 | now = time(NULL); 81 | for (i = 0; i <= MAX_SKEW; i++) { 82 | hotp(sbytes, now / 30 + i, code); 83 | if (strncmp(input, code, 6) == 0) { 84 | proceed(); 85 | } 86 | hotp(sbytes, now / 30 - i, code); 87 | if (strncmp(input, code, 6) == 0) { 88 | proceed(); 89 | } 90 | } 91 | 92 | fprintf(stderr, "Invalid"); 93 | 94 | return 1; 95 | } 96 | --------------------------------------------------------------------------------