/** * @internal * * @brief Import a public key from a ssh string. * * @param[in] key_blob The key blob to import as specified in RFC 4253 section * 6.6 "Public Key Algorithms". * * @param[out] pkey A pointer where the allocated key can be stored. You * need to free the memory. * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, ssh_key *pkey) { ssh_buffer buffer; ssh_string type_s = NULL; enum ssh_keytypes_e type; int rc; if (key_blob == NULL || pkey == NULL) { return SSH_ERROR; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_pki_log("Out of memory!"); return SSH_ERROR; } rc = ssh_buffer_add_data(buffer, ssh_string_data(key_blob), ssh_string_len(key_blob)); if (rc < 0) { ssh_pki_log("Out of memory!"); goto fail; } type_s = buffer_get_ssh_string(buffer); if (type_s == NULL) { ssh_pki_log("Out of memory!"); goto fail; } type = ssh_key_type_from_name(ssh_string_get_char(type_s)); if (type == SSH_KEYTYPE_UNKNOWN) { ssh_pki_log("Unknown key type found!"); goto fail; } ssh_string_free(type_s); rc = pki_import_pubkey_buffer(buffer, type, pkey); ssh_buffer_free(buffer); return rc; fail: ssh_buffer_free(buffer); ssh_string_free(type_s); return SSH_ERROR; }
/** * @brief import a base64 formated key from a memory c-string * * @param[in] b64_key The c-string holding the base64 encoded key * * @param[in] passphrase The passphrase to decrypt the key, or NULL * * @param[in] auth_fn An auth function you may want to use or NULL. * * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer where the allocated key can be stored. You * need to free the memory. * * @return SSH_ERROR in case of error, SSH_OK otherwise. * * @see ssh_key_free() */ int ssh_pki_import_privkey_base64(const char *b64_key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data, ssh_key *pkey) { ssh_key key; if (b64_key == NULL || pkey == NULL) { return SSH_ERROR; } if (b64_key == NULL || !*b64_key) { return SSH_ERROR; } ssh_pki_log("Trying to decode privkey passphrase=%s", passphrase ? "true" : "false"); key = pki_private_key_from_base64(b64_key, passphrase, auth_fn, auth_data); if (key == NULL) { return SSH_ERROR; } *pkey = key; return SSH_OK; }
/** * @brief Compare keys if they are equal. * * @param[in] k1 The first key to compare. * * @param[in] k2 The second key to compare. * * @param[in] what What part or type of the key do you want to compare. * * @return 0 if equal, 1 if not. */ int ssh_key_cmp(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { if (k1 == NULL || k2 == NULL) { return 1; } if (k1->type != k2->type) { ssh_pki_log("key types don't match!"); return 1; } if (what == SSH_KEY_CMP_PRIVATE) { if (!ssh_key_is_private(k1) || !ssh_key_is_private(k2)) { return 1; } } if (k1->type == SSH_KEYTYPE_ED25519) { return pki_ed25519_key_cmp(k1, k2, what); } return pki_key_compare(k1, k2, what); }
/** * @brief import a base64 formated key from a memory c-string * * @param[in] b64_key The c-string holding the base64 encoded key * * @param[in] passphrase The passphrase to decrypt the key, or NULL * * @param[in] auth_fn An auth function you may want to use or NULL. * * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer where the allocated key can be stored. You * need to free the memory. * * @return SSH_ERROR in case of error, SSH_OK otherwise. * * @see ssh_key_free() */ int ssh_pki_import_privkey_base64(const char *b64_key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data, ssh_key *pkey) { ssh_key key; int cmp; if (b64_key == NULL || pkey == NULL) { return SSH_ERROR; } if (b64_key == NULL || !*b64_key) { return SSH_ERROR; } ssh_pki_log("Trying to decode privkey passphrase=%s", passphrase ? "true" : "false"); /* Test for OpenSSH key format first */ cmp = strncmp(b64_key, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); if (cmp == 0) { key = ssh_pki_openssh_privkey_import(b64_key, passphrase, auth_fn, auth_data); } else { /* fallback on PEM decoder */ key = pki_private_key_from_base64(b64_key, passphrase, auth_fn, auth_data); } if (key == NULL) { return SSH_ERROR; } *pkey = key; return SSH_OK; }
/** @internal * @brief exports a private key to a string blob. * @param[in] privkey private key to convert * @param[out] buffer buffer to write the blob in. * @returns SSH_OK on success * @warning only supports ed25519 key type at the moment. */ static int pki_openssh_export_privkey_blob(const ssh_key privkey, ssh_buffer buffer) { int rc; if (privkey->type != SSH_KEYTYPE_ED25519) { ssh_pki_log("Type %s not supported", privkey->type_c); return SSH_ERROR; } if (privkey->ed25519_privkey == NULL || privkey->ed25519_pubkey == NULL){ return SSH_ERROR; } rc = ssh_buffer_pack(buffer, "sdPdP", privkey->type_c, (uint32_t)ED25519_PK_LEN, (size_t)ED25519_PK_LEN, privkey->ed25519_pubkey, (uint32_t)ED25519_SK_LEN, (size_t)ED25519_SK_LEN, privkey->ed25519_privkey); return rc; }
/** * @brief Import a public key from the given filename. * * @param[in] filename The path to the public key. * * @param[out] pkey A pointer to store the allocated public key. You need to * free the memory. * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. * * @see ssh_key_free() */ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) { enum ssh_keytypes_e type; struct stat sb; char *key_buf, *p; const char *q; FILE *file; off_t size; int rc; if (pkey == NULL || filename == NULL || *filename == '\0') { return SSH_ERROR; } file = fopen(filename, "r"); if (file == NULL) { ssh_pki_log("Error opening %s: %s", filename, strerror(errno)); return SSH_EOF; } rc = fstat(fileno(file), &sb); if (rc < 0) { fclose(file); ssh_pki_log("Error gettint stat of %s: %s", filename, strerror(errno)); switch (errno) { case ENOENT: case EACCES: return SSH_EOF; } return SSH_ERROR; } if (sb.st_size > MAX_PUBKEY_SIZE) { fclose(file); return SSH_ERROR; } key_buf = malloc(sb.st_size + 1); if (key_buf == NULL) { fclose(file); ssh_pki_log("Out of memory!"); return SSH_ERROR; } size = fread(key_buf, 1, sb.st_size, file); fclose(file); if (size != sb.st_size) { SAFE_FREE(key_buf); ssh_pki_log("Error reading %s: %s", filename, strerror(errno)); return SSH_ERROR; } key_buf[size] = '\0'; q = p = key_buf; while (!isspace((int)*p)) p++; *p = '\0'; type = ssh_key_type_from_name(q); if (type == SSH_KEYTYPE_UNKNOWN) { SAFE_FREE(key_buf); return SSH_ERROR; } q = ++p; while (!isspace((int)*p)) p++; *p = '\0'; rc = ssh_pki_import_pubkey_base64(q, type, pkey); SAFE_FREE(key_buf); return rc; }
static int pki_import_pubkey_buffer(ssh_buffer buffer, enum ssh_keytypes_e type, ssh_key *pkey) { ssh_key key; int rc; key = ssh_key_new(); if (key == NULL) { return SSH_ERROR; } key->type = type; key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PUBLIC; switch (type) { case SSH_KEYTYPE_DSS: { ssh_string p; ssh_string q; ssh_string g; ssh_string pubkey; p = buffer_get_ssh_string(buffer); if (p == NULL) { goto fail; } q = buffer_get_ssh_string(buffer); if (q == NULL) { ssh_string_burn(p); ssh_string_free(p); goto fail; } g = buffer_get_ssh_string(buffer); if (g == NULL) { ssh_string_burn(p); ssh_string_free(p); ssh_string_burn(q); ssh_string_free(q); goto fail; } pubkey = buffer_get_ssh_string(buffer); if (pubkey == NULL) { ssh_string_burn(p); ssh_string_free(p); ssh_string_burn(q); ssh_string_free(q); ssh_string_burn(g); ssh_string_free(g); goto fail; } rc = pki_pubkey_build_dss(key, p, q, g, pubkey); #ifdef DEBUG_CRYPTO ssh_print_hexa("p", ssh_string_data(p), ssh_string_len(p)); ssh_print_hexa("q", ssh_string_data(q), ssh_string_len(q)); ssh_print_hexa("g", ssh_string_data(g), ssh_string_len(g)); #endif ssh_string_burn(p); ssh_string_free(p); ssh_string_burn(q); ssh_string_free(q); ssh_string_burn(g); ssh_string_free(g); ssh_string_burn(pubkey); ssh_string_free(pubkey); if (rc == SSH_ERROR) { goto fail; } } break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: { ssh_string e; ssh_string n; e = buffer_get_ssh_string(buffer); if (e == NULL) { goto fail; } n = buffer_get_ssh_string(buffer); if (n == NULL) { ssh_string_burn(e); ssh_string_free(e); goto fail; } rc = pki_pubkey_build_rsa(key, e, n); #ifdef DEBUG_CRYPTO ssh_print_hexa("e", ssh_string_data(e), ssh_string_len(e)); ssh_print_hexa("n", ssh_string_data(n), ssh_string_len(n)); #endif ssh_string_burn(e); ssh_string_free(e); ssh_string_burn(n); ssh_string_free(n); if (rc == SSH_ERROR) { goto fail; } } break; case SSH_KEYTYPE_ECDSA: #ifdef HAVE_ECC { ssh_string e; ssh_string i; int nid; i = buffer_get_ssh_string(buffer); if (i == NULL) { goto fail; } nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); ssh_string_free(i); if (nid == -1) { goto fail; } e = buffer_get_ssh_string(buffer); if (e == NULL) { goto fail; } rc = pki_pubkey_build_ecdsa(key, nid, e); ssh_string_burn(e); ssh_string_free(e); if (rc < 0) { goto fail; } /* Update key type */ key->type_c = ssh_pki_key_ecdsa_name(key); } break; #endif case SSH_KEYTYPE_ED25519: { ssh_string pubkey = buffer_get_ssh_string(buffer); if (ssh_string_len(pubkey) != ED25519_PK_LEN) { ssh_pki_log("Invalid public key length"); ssh_string_burn(pubkey); ssh_string_free(pubkey); goto fail; } key->ed25519_pubkey = malloc(ED25519_PK_LEN); if (key->ed25519_pubkey == NULL) { ssh_string_burn(pubkey); ssh_string_free(pubkey); goto fail; } memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_PK_LEN); ssh_string_burn(pubkey); ssh_string_free(pubkey); } break; case SSH_KEYTYPE_UNKNOWN: default: ssh_pki_log("Unknown public key protocol %d", type); goto fail; } *pkey = key; return SSH_OK; fail: ssh_key_free(key); return SSH_ERROR; }
/** * @brief Import a key from a file. * * @param[in] filename The filename of the the private key. * * @param[in] passphrase The passphrase to decrypt the private key. Set to NULL * if none is needed or it is unknown. * * @param[in] auth_fn An auth function you may want to use or NULL. * * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer to store the allocated ssh_key. You need to * free the key. * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. * * @see ssh_key_free() **/ int ssh_pki_import_privkey_file(const char *filename, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data, ssh_key *pkey) { struct stat sb; char *key_buf; FILE *file; off_t size; int rc; if (pkey == NULL || filename == NULL || *filename == '\0') { return SSH_ERROR; } file = fopen(filename, "rb"); if (file == NULL) { ssh_pki_log("Error opening %s: %s", filename, strerror(errno)); return SSH_EOF; } rc = fstat(fileno(file), &sb); if (rc < 0) { fclose(file); ssh_pki_log("Error getting stat of %s: %s", filename, strerror(errno)); switch (errno) { case ENOENT: case EACCES: return SSH_EOF; } return SSH_ERROR; } if (sb.st_size > MAX_PRIVKEY_SIZE) { ssh_pki_log("Private key is bigger than 4M."); fclose(file); return SSH_ERROR; } key_buf = malloc(sb.st_size + 1); if (key_buf == NULL) { fclose(file); ssh_pki_log("Out of memory!"); return SSH_ERROR; } size = fread(key_buf, 1, sb.st_size, file); fclose(file); if (size != sb.st_size) { SAFE_FREE(key_buf); ssh_pki_log("Error reading %s: %s", filename, strerror(errno)); return SSH_ERROR; } key_buf[size] = 0; rc = ssh_pki_import_privkey_base64(key_buf, passphrase, auth_fn, auth_data, pkey); SAFE_FREE(key_buf); return rc; }
ssh_key_t * pki_private_key_from_base64(const char *b64_key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { gcry_sexp_t dsa = NULL; gcry_sexp_t rsa = NULL; ssh_key_t * key = NULL; enum ssh_keytypes_e type; int valid; /* needed for gcrypt initialization */ if (ssh_init() < 0) { return NULL; } type = pki_privatekey_type_from_string(b64_key); if (type == SSH_KEYTYPE_UNKNOWN) { ssh_pki_log("Unknown or invalid private key."); return NULL; } switch (type) { case SSH_KEYTYPE_DSS: if (passphrase == NULL) { if (auth_fn) { valid = b64decode_dsa_privatekey(b64_key, &dsa, auth_fn, auth_data, "Passphrase for private key:"); } else { valid = b64decode_dsa_privatekey(b64_key, &dsa, NULL, NULL, NULL); } } else { valid = b64decode_dsa_privatekey(b64_key, &dsa, NULL, (void *) passphrase, NULL); } if (!valid) { ssh_pki_log("Parsing private key"); goto fail; } break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: if (passphrase == NULL) { if (auth_fn) { valid = b64decode_rsa_privatekey(b64_key, &rsa, auth_fn, auth_data, "Passphrase for private key:"); } else { valid = b64decode_rsa_privatekey(b64_key, &rsa, NULL, NULL, NULL); } } else { valid = b64decode_rsa_privatekey(b64_key, &rsa, NULL, (void *)passphrase, NULL); } if (!valid) { ssh_pki_log("Parsing private key"); goto fail; } break; case SSH_KEYTYPE_ECDSA: case SSH_KEYTYPE_UNKNOWN: ssh_pki_log("Unkown or invalid private key type %d", type); return NULL; } key = ssh_key_new(); if (key == NULL) { goto fail; } key->type = type; key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; key->dsa = dsa; key->rsa = rsa; return key; fail: ssh_key_free(key); gcry_sexp_release(dsa); gcry_sexp_release(rsa); return NULL; }
/** * @internal * * @brief Import a private key from a ssh buffer. * * @param[in] key_blob_buffer The key blob to import as specified in * key.c:key_private_serialize in OpenSSH source * code. * * @param[out] pkey A pointer where the allocated key can be stored. You * need to free the memory. * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ static int pki_openssh_import_privkey_blob(ssh_buffer key_blob_buffer, ssh_key *pkey) { enum ssh_keytypes_e type; char *type_s = NULL; ssh_key key = NULL; ssh_string pubkey = NULL, privkey = NULL; int rc; if (pkey == NULL) { return SSH_ERROR; } rc = ssh_buffer_unpack(key_blob_buffer, "s", &type_s); if (rc == SSH_ERROR){ ssh_pki_log("Unpack error"); return SSH_ERROR; } type = ssh_key_type_from_name(type_s); if (type == SSH_KEYTYPE_UNKNOWN) { ssh_pki_log("Unknown key type found!"); return SSH_ERROR; } SAFE_FREE(type_s); key = ssh_key_new(); if (key == NULL) { ssh_pki_log("Out of memory"); return SSH_ERROR; } key->type = type; key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; switch (type) { case SSH_KEYTYPE_ED25519: rc = ssh_buffer_unpack(key_blob_buffer, "SS", &pubkey, &privkey); if (rc != SSH_OK){ ssh_pki_log("Unpack error"); goto fail; } if(ssh_string_len(pubkey) != ED25519_PK_LEN || ssh_string_len(privkey) != ED25519_SK_LEN){ ssh_pki_log("Invalid ed25519 key len"); goto fail; } key->ed25519_privkey = malloc(ED25519_SK_LEN); key->ed25519_pubkey = malloc(ED25519_PK_LEN); if(key->ed25519_privkey == NULL || key->ed25519_pubkey == NULL){ goto fail; } memcpy(key->ed25519_privkey, ssh_string_data(privkey), ED25519_SK_LEN); memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_PK_LEN); memset(ssh_string_data(privkey), 0, ED25519_SK_LEN); SAFE_FREE(privkey); SAFE_FREE(pubkey); break; case SSH_KEYTYPE_DSS: /* p,q,g,pub_key,priv_key */ case SSH_KEYTYPE_RSA: /* n,e,d,iqmp,p,q */ case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_ECDSA: /* curve_name, group, privkey */ ssh_pki_log("Unsupported private key method %s", key->type_c); goto fail; case SSH_KEYTYPE_UNKNOWN: ssh_pki_log("Unknown private key protocol %s", key->type_c); goto fail; } *pkey = key; return SSH_OK; fail: ssh_key_free(key); if(privkey != NULL){ memset(ssh_string_data(privkey), 0, ssh_string_len(privkey)); } SAFE_FREE(pubkey); SAFE_FREE(privkey); return SSH_ERROR; }
ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { ssh_buffer buffer; ssh_string str = NULL; ssh_string pubkey_s=NULL; ssh_buffer privkey_buffer = NULL; uint32_t rnd; uint32_t rounds = 16; ssh_string salt=NULL; ssh_string kdf_options=NULL; int to_encrypt=0; unsigned char *b64; uint32_t str_len, len; int rc; if (privkey == NULL) { return NULL; } if (privkey->type != SSH_KEYTYPE_ED25519){ ssh_pki_log("Unsupported key type %s", privkey->type_c); return NULL; } if (passphrase != NULL || auth_fn != NULL){ ssh_pki_log("Enabling encryption for private key export"); to_encrypt = 1; } buffer = ssh_buffer_new(); pubkey_s = pki_publickey_to_blob(privkey); if(buffer == NULL || pubkey_s == NULL){ goto error; } ssh_get_random(&rnd, sizeof(rnd), 0); privkey_buffer = ssh_buffer_new(); if (privkey_buffer == NULL) { goto error; } /* checkint1 & 2 */ rc = ssh_buffer_pack(privkey_buffer, "dd", rnd, rnd); if (rc == SSH_ERROR){ goto error; } rc = pki_openssh_export_privkey_blob(privkey, privkey_buffer); if (rc == SSH_ERROR){ goto error; } /* comment */ rc = ssh_buffer_pack(privkey_buffer, "s", "" /* comment */); if (rc == SSH_ERROR){ goto error; } if (to_encrypt){ ssh_buffer kdf_buf; kdf_buf = ssh_buffer_new(); if (kdf_buf == NULL) { goto error; } salt = ssh_string_new(16); if (salt == NULL){ ssh_buffer_free(kdf_buf); goto error; } ssh_get_random(ssh_string_data(salt),16, 0); ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); if (kdf_options == NULL){ ssh_buffer_free(kdf_buf); goto error; } memcpy(ssh_string_data(kdf_options), ssh_buffer_get_begin(kdf_buf), ssh_buffer_get_len(kdf_buf)); ssh_buffer_free(kdf_buf); rc = pki_private_key_encrypt(privkey_buffer, passphrase, "aes128-cbc", "bcrypt", auth_fn, auth_data, rounds, salt); if (rc != SSH_OK){ goto error; } } else { kdf_options = ssh_string_new(0); } rc = ssh_buffer_pack(buffer, "PssSdSdP", (size_t)strlen(OPENSSH_AUTH_MAGIC) + 1, OPENSSH_AUTH_MAGIC, to_encrypt ? "aes128-cbc" : "none", /* ciphername */ to_encrypt ? "bcrypt" : "none", /* kdfname */ kdf_options, /* kdfoptions */ (uint32_t) 1, /* nkeys */ pubkey_s, (uint32_t)ssh_buffer_get_len(privkey_buffer), /* rest of buffer is a string */ (size_t)ssh_buffer_get_len(privkey_buffer), ssh_buffer_get_begin(privkey_buffer)); if (rc != SSH_OK) { goto error; } b64 = bin_to_base64(ssh_buffer_get_begin(buffer), ssh_buffer_get_len(buffer)); if (b64 == NULL){ goto error; } /* we can reuse the buffer */ ssh_buffer_reinit(buffer); rc = ssh_buffer_pack(buffer, "tttttt", OPENSSH_HEADER_BEGIN, "\n", b64, "\n", OPENSSH_HEADER_END, "\n"); BURN_BUFFER(b64, strlen((char *)b64)); SAFE_FREE(b64); if (rc != SSH_OK){ goto error; } str = ssh_string_new(ssh_buffer_get_len(buffer)); if (str == NULL){ goto error; } str_len = ssh_buffer_get_len(buffer); len = buffer_get_data(buffer, ssh_string_data(str), str_len); if (str_len != len) { ssh_string_free(str); str = NULL; } error: if (privkey_buffer != NULL) { void *bufptr = ssh_buffer_get_begin(privkey_buffer); BURN_BUFFER(bufptr, ssh_buffer_get_len(privkey_buffer)); ssh_buffer_free(privkey_buffer); } SAFE_FREE(pubkey_s); SAFE_FREE(kdf_options); SAFE_FREE(salt); if (buffer != NULL) { ssh_buffer_free(buffer); } return str; }
/** @internal * @brief encrypts an ed25519 private key blob * */ static int pki_private_key_encrypt(ssh_buffer privkey_buffer, const char* passphrase, const char *ciphername, const char *kdfname, ssh_auth_callback auth_fn, void *auth_data, uint32_t rounds, ssh_string salt) { struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); struct ssh_cipher_struct cipher; uint8_t key_material[128]; size_t key_material_len; char passphrase_buffer[128]; int rc; int i; uint8_t padding = 1; int cmp; cmp = strcmp(ciphername, "none"); if (cmp == 0){ /* no encryption required */ return SSH_OK; } for (i = 0; ciphers[i].name != NULL; i++) { cmp = strcmp(ciphername, ciphers[i].name); if (cmp == 0){ memcpy(&cipher, &ciphers[i], sizeof(cipher)); break; } } if (ciphers[i].name == NULL){ SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); return SSH_ERROR; } cmp = strcmp(kdfname, "bcrypt"); if (cmp != 0){ SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); return SSH_ERROR; } while (ssh_buffer_get_len(privkey_buffer) % cipher.blocksize != 0) { rc = buffer_add_u8(privkey_buffer, padding); if (rc < 0) { return SSH_ERROR; } padding++; } /* We need material for key (keysize bits / 8) and IV (blocksize) */ key_material_len = cipher.keysize/8 + cipher.blocksize; if (key_material_len > sizeof(key_material)){ ssh_pki_log("Key material too big"); return SSH_ERROR; } ssh_pki_log("Encryption: %d key, %d IV, %d rounds, %zu bytes salt", cipher.keysize/8, cipher.blocksize, rounds, ssh_string_len(salt)); if (passphrase == NULL){ if (auth_fn == NULL){ ssh_pki_log("No passphrase provided"); return SSH_ERROR; } rc = auth_fn("Passphrase", passphrase_buffer, sizeof(passphrase_buffer), 0, 0, auth_data); if (rc != SSH_OK){ return SSH_ERROR; } passphrase = passphrase_buffer; } rc = bcrypt_pbkdf(passphrase, strlen(passphrase), ssh_string_data(salt), ssh_string_len(salt), key_material, key_material_len, rounds); if (rc < 0){ return SSH_ERROR; } cipher.set_encrypt_key(&cipher, key_material, key_material + cipher.keysize/8); cipher.encrypt(&cipher, ssh_buffer_get_begin(privkey_buffer), ssh_buffer_get_begin(privkey_buffer), ssh_buffer_get_len(privkey_buffer)); ssh_cipher_clear(&cipher); BURN_BUFFER(passphrase_buffer, sizeof(passphrase_buffer)); return SSH_OK; }
/** @internal * @brief Import a private key in OpenSSH (new) format. This format is * typically used with ed25519 keys but can be used for others. */ ssh_key ssh_pki_openssh_privkey_import(const char *text_key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { const char *ptr=text_key; const char *end; char *base64; int cmp; int rc; int i; ssh_buffer buffer = NULL, privkey_buffer=NULL; char *magic = NULL, *ciphername = NULL, *kdfname = NULL; uint32_t nkeys = 0, checkint1, checkint2; ssh_string kdfoptions = NULL; ssh_string pubkey0 = NULL; ssh_string privkeys = NULL; ssh_string comment = NULL; ssh_key key = NULL; uint8_t padding; cmp = strncmp(ptr, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); if (cmp != 0){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no header)"); goto error; } ptr += strlen(OPENSSH_HEADER_BEGIN); while(ptr[0] != '\0' && !isspace((int)ptr[0])) { ptr++; } end = strstr(ptr, OPENSSH_HEADER_END); if (end == NULL){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no footer)"); goto error; } base64 = malloc(end - ptr + 1); if (base64 == NULL){ goto error; } for (i = 0; ptr < end; ptr++){ if (!isspace((int)ptr[0])) { base64[i] = ptr[0]; i++; } } base64[i] = '\0'; buffer = base64_to_bin(base64); SAFE_FREE(base64); if (buffer == NULL){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (base64 error)"); goto error; } rc = ssh_buffer_unpack(buffer, "PssSdSS", strlen(OPENSSH_AUTH_MAGIC) + 1, &magic, &ciphername, &kdfname, &kdfoptions, &nkeys, &pubkey0, &privkeys); if (rc == SSH_ERROR){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (unpack error)"); goto error; } cmp = strncmp(magic, OPENSSH_AUTH_MAGIC, strlen(OPENSSH_AUTH_MAGIC)); if (cmp != 0){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (bad magic)"); goto error; } ssh_pki_log("Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %d\n", ciphername, kdfname, nkeys); if (nkeys != 1){ SSH_LOG(SSH_LOG_WARN, "Opening OpenSSH private key: only 1 key supported (%d available)", nkeys); goto error; } rc = pki_private_key_decrypt(privkeys, passphrase, ciphername, kdfname, kdfoptions, auth_fn, auth_data); if (rc == SSH_ERROR){ goto error; } privkey_buffer = ssh_buffer_new(); if (privkey_buffer == NULL) { rc = SSH_ERROR; goto error; } ssh_buffer_set_secure(privkey_buffer); ssh_buffer_add_data(privkey_buffer, ssh_string_data(privkeys), ssh_string_len(privkeys)); rc = ssh_buffer_unpack(privkey_buffer, "dd", &checkint1, &checkint2); if (rc == SSH_ERROR || checkint1 != checkint2){ SSH_LOG(SSH_LOG_WARN, "OpenSSH private key unpack error (correct password?)"); goto error; } rc = pki_openssh_import_privkey_blob(privkey_buffer, &key); if (rc == SSH_ERROR){ goto error; } comment = buffer_get_ssh_string(privkey_buffer); SAFE_FREE(comment); /* verify that the remaining data is correct padding */ for (i=1; buffer_get_rest_len(privkey_buffer) > 0; ++i){ buffer_get_u8(privkey_buffer, &padding); if (padding != i){ ssh_key_free(key); key = NULL; ssh_pki_log("Invalid padding"); goto error; } } error: if(buffer != NULL){ ssh_buffer_free(buffer); buffer = NULL; } if(privkey_buffer != NULL){ ssh_buffer_free(privkey_buffer); privkey_buffer = NULL; } SAFE_FREE(magic); SAFE_FREE(ciphername); SAFE_FREE(kdfname); SAFE_FREE(kdfoptions); SAFE_FREE(pubkey0); SAFE_FREE(privkeys); return key; }
/** * @brief decrypts an encrypted ed25519 private key blob * */ static int pki_private_key_decrypt(ssh_string blob, const char* passphrase, const char *ciphername, const char *kdfname, ssh_string kdfoptions, ssh_auth_callback auth_fn, void *auth_data) { struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); struct ssh_cipher_struct cipher; uint8_t key_material[128]; char passphrase_buffer[128]; size_t key_material_len; ssh_buffer buffer; ssh_string salt; uint32_t rounds; int cmp; int rc; int i; cmp = strcmp(ciphername, "none"); if (cmp == 0){ /* no decryption required */ return SSH_OK; } for (i = 0; ciphers[i].name != NULL; i++) { cmp = strcmp(ciphername, ciphers[i].name); if (cmp == 0){ memcpy(&cipher, &ciphers[i], sizeof(cipher)); break; } } if (ciphers[i].name == NULL){ SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); return SSH_ERROR; } cmp = strcmp(kdfname, "bcrypt"); if (cmp != 0) { SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); return SSH_ERROR; } if (ssh_string_len(blob) % cipher.blocksize != 0) { SSH_LOG(SSH_LOG_WARN, "Encrypted string not multiple of blocksize: %zu", ssh_string_len(blob)); return SSH_ERROR; } buffer = ssh_buffer_new(); if (buffer == NULL){ return SSH_ERROR; } rc = ssh_buffer_add_data(buffer, ssh_string_data(kdfoptions), ssh_string_len(kdfoptions)); if (rc != SSH_ERROR){ rc = ssh_buffer_unpack(buffer, "Sd", &salt, &rounds); } ssh_buffer_free(buffer); if (rc == SSH_ERROR){ return SSH_ERROR; } /* We need material for key (keysize bits / 8) and IV (blocksize) */ key_material_len = cipher.keysize/8 + cipher.blocksize; if (key_material_len > sizeof(key_material)) { ssh_pki_log("Key material too big"); return SSH_ERROR; } ssh_pki_log("Decryption: %d key, %d IV, %d rounds, %zu bytes salt", cipher.keysize/8, cipher.blocksize, rounds, ssh_string_len(salt)); if (passphrase == NULL) { if (auth_fn == NULL) { SAFE_FREE(salt); ssh_pki_log("No passphrase provided"); return SSH_ERROR; } rc = auth_fn("Passphrase", passphrase_buffer, sizeof(passphrase_buffer), 0, 0, auth_data); if (rc != SSH_OK) { SAFE_FREE(salt); return SSH_ERROR; } passphrase = passphrase_buffer; } rc = bcrypt_pbkdf(passphrase, strlen(passphrase), ssh_string_data(salt), ssh_string_len(salt), key_material, key_material_len, rounds); SAFE_FREE(salt); if (rc < 0){ return SSH_ERROR; } BURN_BUFFER(passphrase_buffer, sizeof(passphrase_buffer)); cipher.set_decrypt_key(&cipher, key_material, key_material + cipher.keysize/8); cipher.decrypt(&cipher, ssh_string_data(blob), ssh_string_data(blob), ssh_string_len(blob)); ssh_cipher_clear(&cipher); return SSH_OK; }