/* * This program converts encrypted or plaintext OpenSSH SSH2 RSA * keys in PEM format, and writes plaintext PuTTY User-Key private * keys to stdout. The code is horribly ugly, has memory leaks and * what not, but it works. The code is basically a mishmash of * OpenSSH and PuTTY code, with some crazy glue to bind it. * * This was created as a quick hack to be able to use OpenSSH generated * SSH2 RSA keys in PuTTY, nothing more, nothing less. Use it at your own * risk. * * Potentially it holds a great security risk because it outputs * unencrypted private keys. You are strongly advised to load the key * into PuttyGen and resave it encrypted! * * Compile with something like: * gcc pem2putty.c -o pem2putty -lcrypto * * Troels Walsted Hansen */ #include #include #include #include #include #include #include #define PUT_32BIT(cp, value) do { \ (cp)[3] = (value); \ (cp)[2] = (value) >> 8; \ (cp)[1] = (value) >> 16; \ (cp)[0] = (value) >> 24; } while (0) int base64_lines(int datalen) { /* When encoding, we use 64 chars/line, which equals 48 real chars. */ return (datalen + 47) / 48; } int BN_mpint_bytes(BIGNUM *a) { int bytes = BN_num_bytes(a); u_char *buf = malloc(bytes); int hasnohigh = 0; BN_bn2bin(a, buf); hasnohigh = (*buf & 0x80) ? 0 : 1; free(buf); return 5+bytes-hasnohigh; } int BN_bn2mpint(BIGNUM *a, unsigned char *to) { int bytes = BN_num_bytes(a) + 1; u_char *buf = malloc(bytes); int oi; int hasnohigh = 0; buf[0] = '\0'; /* Get the value of in binary */ oi = BN_bn2bin(a, buf+1); hasnohigh = (buf[1] & 0x80) ? 0 : 1; if (a->neg) { /**XXX should be two's-complement */ int i, carry; u_char *uc = buf; for(i = bytes-1, carry = 1; i>=0; i--) { uc[i] ^= 0xff; if(carry) carry = !++uc[i]; } } PUT_32BIT(to, bytes-hasnohigh); memcpy(to+4, buf+hasnohigh, bytes-hasnohigh); free(buf); return 4+bytes-hasnohigh; } int main(int argc, char **argv) { FILE *fp; EVP_PKEY *pk; RSA *rsa; BIO *bio, *b64; int pubkey_blob_size, prvkey_blob_size, offset; char *pubkey_blob, *prvkey_blob; const char *name = "ssh-rsa", *enc = "none", *comm = "none"; int namelen = strlen(name); int enclen = strlen(enc); int commlen = strlen(comm); OpenSSL_add_all_algorithms(); if(argc < 2) { fprintf(stderr, "Need filename arg\n"); exit(1); } fp = fopen(argv[1], "r"); if(!fp) { perror("Unable to open file"); exit(1); } pk = PEM_read_PrivateKey(fp, NULL, NULL, NULL); if(!pk) { ERR_load_crypto_strings(); fprintf(stderr, "PEM_read_PrivateKey() failed: %s\n", ERR_error_string(ERR_get_error(), NULL)); exit(1); } if(pk->type != EVP_PKEY_RSA) { fprintf(stderr, "Not an RSA key\n"); exit(1); } rsa = EVP_PKEY_get1_RSA(pk); //RSA_print_fp(stdout, rsa, 0); bio = BIO_new_fp(stdout, BIO_NOCLOSE); b64 = BIO_new(BIO_f_base64()); BIO_printf(bio, "PuTTY-User-Key-File-2: ssh-rsa\n"); BIO_printf(bio, "Encryption: none\n"); BIO_printf(bio, "Comment: none\n"); /* * Next there is a line saying "Public-Lines: " plus a number N. * The following N lines contain a base64 encoding of the public * part of the key. This is encoded as the standard SSH2 public key * blob (with no initial length): so for RSA, for example, it will * read * * string "ssh-rsa" * mpint exponent * mpint modulus */ pubkey_blob_size = 11 + BN_mpint_bytes(rsa->e) + BN_mpint_bytes(rsa->n); pubkey_blob = malloc(pubkey_blob_size); memset(pubkey_blob, '\0', pubkey_blob_size); PUT_32BIT(pubkey_blob, namelen); strcpy(pubkey_blob+4, "ssh-rsa"); offset = 11; offset += BN_bn2mpint(rsa->e, &pubkey_blob[offset]); offset += BN_bn2mpint(rsa->n, &pubkey_blob[offset]); BIO_printf(bio, "Public-Lines: %d\n", base64_lines(pubkey_blob_size)); bio = BIO_push(b64, bio); BIO_write(bio, pubkey_blob, pubkey_blob_size); BIO_flush(bio); bio = BIO_pop(b64); /* * Next, there is a line saying "Private-Lines: " plus a number N, * and then N lines containing the (potentially encrypted) private * part of the key. For the key type "ssh-rsa", this will be * composed of * * mpint private_exponent * mpint p (the larger of the two primes) * mpint q (the smaller prime) * mpint iqmp (the inverse of q modulo p) * data padding (to reach a multiple of the cipher block size ) */ prvkey_blob_size = BN_mpint_bytes(rsa->d) + BN_mpint_bytes(rsa->p) + BN_mpint_bytes(rsa->q) + BN_mpint_bytes(rsa->iqmp); prvkey_blob = malloc(prvkey_blob_size); memset(prvkey_blob, '\0', prvkey_blob_size); offset = BN_bn2mpint(rsa->d, prvkey_blob); offset += BN_bn2mpint(rsa->p, &prvkey_blob[offset]); offset += BN_bn2mpint(rsa->q, &prvkey_blob[offset]); offset += BN_bn2mpint(rsa->iqmp, &prvkey_blob[offset]); BIO_printf(bio, "Private-Lines: %d\n", base64_lines(prvkey_blob_size)); BIO_flush(bio); bio = BIO_push(b64, bio); BIO_write(bio, prvkey_blob, prvkey_blob_size); BIO_flush(bio); bio = BIO_pop(b64); /* * Finally, there is a line saying "Private-MAC: " plus a hex * representation of a HMAC-SHA-1 of: * * string name of algorithm ("ssh-dss", "ssh-rsa") * string encryption type * string comment * string public-blob * string private-plaintext (the plaintext version of the * private part, including the final * padding) * * The key to the MAC is itself a SHA-1 hash of: * * data "putty-private-key-file-mac-key" * data passphrase */ { unsigned char mackey_output[SHA_DIGEST_LENGTH]; const char *mackey_input = "putty-private-key-file-mac-key"; unsigned char *macdata; int macdata_len; unsigned char *p; unsigned char mac[EVP_MAX_MD_SIZE]; unsigned int mac_len, i; BIO_printf(bio, "Private-MAC: "); SHA1(mackey_input, strlen(mackey_input), mackey_output); macdata_len = (4 + namelen + 4 + enclen + 4 + commlen + 4 + pubkey_blob_size + 4 + prvkey_blob_size); macdata = malloc(macdata_len); p = macdata; #define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len) DO_STR(name, namelen); DO_STR(enc, enclen); DO_STR(comm, commlen); DO_STR(pubkey_blob, pubkey_blob_size); DO_STR(prvkey_blob, prvkey_blob_size); memset(mac, '\0', EVP_MAX_MD_SIZE); HMAC(EVP_sha1(), mackey_output, SHA_DIGEST_LENGTH, macdata, macdata_len, mac, &mac_len); for (i = 0; i < mac_len; i++) BIO_printf(bio, "%02x", mac[i]); BIO_printf(bio, "\n"); BIO_flush(bio); } return 0; }