You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libsxmp/sxt/ppkp_ops.c

1343 lines
34 KiB
C

/*
* Secure eXtended Message Passing framework
* Secure eXtended Transport layer implementation: (libsxt)
* - very similar to SSH2/TLS
* - using already proven and tested crypto algos
* - better than TLS for message passing
*
* PublicPrivateKeyPairs operation API
*
* (c) Alexander Vdolainen 2016 <avdolainen@zoho.com>
*
* libsxmp is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* libsxmp is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.";
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/engine.h>
#include <ndbuf/ndbuf.h>
#include <sxt/errno.h>
#include <sxt/safebuf.h>
#include <sxt/crypt.h>
#include <sxt/sxtkey.h>
#include <sxt/ed25519.h>
#include <sxt/ciphers.h>
#include <sxt/base64.h>
#if 0
#define blub(a) printf("%s:%d -> %d\n", __FUNCTION__, __LINE__, (a))
#endif
/* locals */
static int sxtkey_export_priv_ed25519(const sxtkey_t *key, const char *pass, sxtsafebuffer_t **bin);
static int sxt_public_ed25519_2sb(const sxtkey_t *key, sxtsafebuffer_t **buf);
static int sxt_public_ed25519_2rdb(const sxtkey_t *key, ndbuf_t **out);
static int _sxtkey_check_fmtrawbuf(const char *buffer);
static int _sxtkey_privkey_decrypt(ndbuf_t *privkey, ndbuf_t *kdfopt, const char *passkey);
int sxtkey_generate(sxtkey_t *key, int type, int opt)
{
int r = 0;
if(!key) return SXT_EINVAL;
/* set defaults */
key->type = type;
key->flags = SXT_PPKP_PRIVATE | SXT_PPKP_PUBLIC;
switch(type) {
case PPKP_ED25519:
key->pubkey = malloc(sizeof(ed25519_pubkey));
if(!key->pubkey) {
r = SXT_ENOMEM;
goto __fall;
}
key->privkey = malloc(sizeof(ed25519_privkey));
if(!key->privkey) {
r = SXT_ENOMEM;
goto __safefall0;
}
r = crypto_sign_ed25519_keypair(*key->pubkey, *key->privkey);
if(r) {
r = SXT_ECRYPTO;
goto __safefall0;
}
break;
default:
return SXT_EINVAL;
}
if(!r) return SXT_SUCCESS;
__safefall0:
if(key->pubkey) free(key->pubkey);
if(key->privkey) free(key->privkey);
__fall:
return r;
}
sxtkey_t *sxtkey_alloc(void)
{
sxtkey_t *key = malloc(sizeof(sxtkey_t));
if(!key) return NULL;
else memset(key, 0, sizeof(sxtkey_t));
return key;
}
void sxtkey_burn(sxtkey_t *key)
{
if(!key) return;
key->priv = NULL;
switch(key->type) {
case PPKP_ED25519:
if(key->pubkey) memset(key->pubkey, 0, sizeof(ed25519_pubkey));
if(key->privkey) memset(key->privkey, 0, sizeof(ed25519_privkey));
break;
default:
return;
}
key->type = 0;
key->flags = 0;
return;
}
void sxtkey_free(sxtkey_t *key)
{
if(!key) return;
switch(key->type) {
case PPKP_ED25519:
if(key->pubkey) free(key->pubkey);
if(key->privkey) free(key->privkey);
break;
default: return; /* cannot free unrecognized key due to the
* potential memleak
*/
}
free(key);
return;
}
const char *sxtkey_name(int type)
{
switch(type) {
case PPKP_ED25519:
return "ppkp-ed25519";
default: return NULL;
}
return NULL;
}
uint8_t sxtkey_type_fname(const char *name)
{
if(!name) return 0;
if(!strcmp(name, "ppkp-ed25519")) return PPKP_ED25519;
else return 0;
}
int sxtkey_public(const sxtkey_t *key)
{
if(!key) return 0;
return (key->flags & SXT_PPKP_PUBLIC) == SXT_PPKP_PUBLIC;
}
int sxtkey_private(const sxtkey_t *key)
{
if(!key) return 0;
return (key->flags & SXT_PPKP_PRIVATE) == SXT_PPKP_PRIVATE;
}
int sxtkey_assign_hash(sxtkey_t *key, uint64_t hash)
{
if(!key) return SXT_EINVAL;
// if(!sxtkey_private(key)) return SXT_EINVAL;
//if(key->hash) return SXT_EKEY;
key->hash = hash;
return SXT_SUCCESS;
}
uint64_t sxtkey_hash(const sxtkey_t *key)
{
if(!key) return 0;
return key->hash;
}
/* will duplicate a key depends on it's kind */
int sxtkey_dup(const sxtkey_t *key, sxtkey_t **dest)
{
int r = SXT_EKEY;
if(!key) return SXT_EINVAL;
if(sxtkey_public(key)) return sxtkey_dup_public(key, dest);
else if(sxtkey_private(key)) return sxtkey_dup_private(key, dest);
else return sxtkey_dup_private(key, dest);
/* never riched */
return r;
}
/* will duplicate public key, if key was private or pair - it becomes public */
int sxtkey_dup_public(const sxtkey_t *key, sxtkey_t **dest)
{
sxtkey_t *nkey = NULL;
int r = SXT_SUCCESS;
if(!key) return SXT_EINVAL;
if(!sxtkey_private(key)) return SXT_EKEY;
if(!(nkey = sxtkey_alloc())) return SXT_ENOMEM;
else nkey->flags = 0;
switch(key->type) {
case PPKP_ED25519: /* actually the same still, but in case of other key types will differ */
/* check first */
if(!key->pubkey || !key->privkey) {
r = SXT_EKEY;
goto __failed;
}
/* allocate new values */
if(!(nkey->pubkey = malloc(ED25519_PK_LEN))) {
r = SXT_ENOMEM;
goto __failed;
}
if(!(nkey->privkey = malloc(ED25519_SK_LEN))) {
free(nkey->pubkey);
r = SXT_ENOMEM;
goto __failed;
}
/* copy that */
memcpy(nkey->pubkey, key->pubkey, ED25519_PK_LEN);
memcpy(nkey->privkey, key->privkey, ED25519_SK_LEN);
break;
default: /* don't now about it */
sxtkey_free(nkey);
return SXT_EKEY;
}
/* copy other values */
nkey->flags |= SXT_PPKP_PUBLIC;
nkey->type = key->type;
nkey->hash = key->hash;
nkey->priv = key->priv;
*dest = nkey;
return r;
__failed:
sxtkey_free(nkey);
return r;
}
/* will duplicate private key, if key public error will returns */
int sxtkey_dup_private(const sxtkey_t *key, sxtkey_t **dest)
{
sxtkey_t *nkey = NULL;
int r = SXT_SUCCESS;
if(!key) return SXT_EINVAL;
//if(sxt_key_public(key)) return SXT_EKEY; /* cannot duplicate private key from public */
if(!(nkey = sxtkey_alloc())) return SXT_ENOMEM;
else nkey->flags = 0;
switch(key->type) {
case PPKP_ED25519:
/* check first */
if(!key->pubkey || !key->privkey) {
r = SXT_EKEY;
goto __failed;
}
/* allocate new values */
if(!(nkey->pubkey = malloc(ED25519_PK_LEN))) {
r = SXT_ENOMEM;
goto __failed;
}
if(!(nkey->privkey = malloc(ED25519_SK_LEN))) {
free(nkey->pubkey);
r = SXT_ENOMEM;
goto __failed;
}
/* copy that */
memcpy(nkey->pubkey, key->pubkey, ED25519_PK_LEN);
memcpy(nkey->privkey, key->privkey, ED25519_SK_LEN);
break;
default: /* don't now about it */
sxtkey_free(nkey);
return SXT_EKEY;
}
/* copy other values */
nkey->flags = SXT_PPKP_PRIVATE;
nkey->type = key->type;
nkey->hash = key->hash;
nkey->priv = key->priv;
*dest = nkey;
return r;
__failed:
sxtkey_free(nkey);
return r;
}
int sxtkey_import_priv_file(const char *file, const char *passkey,
int (*ask_passkey)(char *pkbuf, size_t length, int confirm, void *priv),
void *priv, sxtkey_t **ik)
{
FILE *stream = NULL;
char *rawfbuf = NULL, *tbuf, *tebuf, *tuple = NULL, *b64pk = NULL;
struct stat stbuf;
uint8_t keytype = 0;
int r = SXT_SUCCESS, tcc = 0, len;
*ik = NULL;
/* check input file */
if(!file) return SXT_EINVAL;
if(stat(file, &stbuf) != 0) return SXT_EIO;
else if(!S_ISREG(stbuf.st_mode)) return SXT_EINVAL;
if(stbuf.st_size >= SXT_PPKP_MAXCSIZE) return SXT_EINVAL;
if((stream = fopen(file, "r")) == NULL) return SXT_EIO;
if(!(rawfbuf = malloc(stbuf.st_size + sizeof(char)))) goto __failed;
else rawfbuf[stbuf.st_size] = '\0';
if(fread(rawfbuf, stbuf.st_size, 1, stream) != 1) {
r = SXT_EIO;
goto __failed;
}
/* check general format */
r = _sxtkey_check_fmtrawbuf(rawfbuf);
if(r != SXT_SUCCESS) goto __failed;
/* now we need to get a keytype and decode b64blob */
for(tbuf = rawfbuf, tcc = 0, tebuf = NULL; *tbuf != '\0'; tbuf++) {
switch(tcc) {
case 0: /* just entering */
if(*tbuf == '(') tcc++;
break;
case 1: /* first e.g. key type plain text description */
if(!tebuf) tebuf = tbuf;
else if(tebuf && *tbuf == ' ') {
len = (tbuf - tebuf) + sizeof(char);
if(!(tuple = malloc(len))) {
r = SXT_ENOMEM;
goto __failed;
} else memset(tuple, 0, len);
/* check keytype */
memcpy(tuple, tebuf, len - sizeof(char));
keytype = sxtkey_type_fname(tuple);
free(tuple);
if(keytype == 0) {
r = SXT_EKEY; /* unsupported key type */
goto __failed;
}
/* going further */
tebuf = NULL;
tcc++;
}
break;
case 2: /* private key contents */
if(!tebuf && *tbuf != ' ') tebuf = tbuf;
else if(tebuf && *tbuf == '"') {
tebuf++;
len = (tbuf - tebuf) + sizeof(char);
if(len <= 1) {
r = SXT_EKEYFMT;
goto __failed;
}
if(!(tuple = malloc(len))) { r = SXT_ENOMEM; goto __failed; }
else memset(tuple, 0, len);
memcpy(tuple, tebuf, len - sizeof(char));
b64pk = tuple;
tuple = tebuf = NULL;
tcc++;
}
break;
case 3:
if(*tbuf == ')') tcc++;
else if(*tbuf != ' ') {
r = SXT_EKEYFMT;
goto __failed;
}
break;
default: break;
}
}
if(tcc < 3) {
r = SXT_EKEYFMT;
goto __failed;
}
r = sxtkey_privkey_import_fbase64blob(b64pk, keytype, ask_passkey, priv, ik);
__failed:
if(stream) fclose(stream);
if(rawfbuf) free(rawfbuf);
if(b64pk) free(b64pk);
return r;
}
int sxtkey_privkey_import_rdbuf(ndbuf_t *buf, uint8_t keytype,
int (*ask_passkey)(char *pkbuf, size_t length,
int confirm, void *priv),
void *priv, sxtkey_t **ik)
{
uint8_t flags = 0, chk = 0;
char *passkey = NULL;
ndbuf_t *kdfopt = NULL, *ctrl_pubkey = NULL, *privkey = NULL;
ndbuf_t *ed25519_pubbuf = NULL, *ed25519_privbuf = NULL;
sxtkey_t *key = NULL;
char *keyname = NULL, *ciphername = NULL, *kdfname = NULL, *magic = NULL;
uint64_t hash = 0;
uint32_t r1 = 0, r2 = 0, ctrl_pklen = 0;
int r = SXT_SUCCESS, decrypt = 0;
if(!buf) return SXT_EINVAL;
if(!ndbuf_length(buf)) return SXT_EINVAL;
if(keytype == 0) return SXT_EKEY;
r = ndbuf_escan(buf, "sbsssRdRR", &magic, &flags, &keyname, &ciphername, &kdfname,
&kdfopt, &ctrl_pklen, &ctrl_pubkey, &privkey);
if(r != SXT_SUCCESS) goto __failed;
if(strcmp(PPKP_MAGIC, magic)) { /* magic should be right */
__eekey:
r = SXT_EKEY;
goto __failed;
}
if(strcmp(sxtkey_name(keytype), keyname)) goto __eekey;
/* check for auth function */
if(flags & SXT_PPKP_ENCRYPT) decrypt++;
if(decrypt && !ask_passkey) {
r = SXT_EAUTH;
goto __failed;
}
switch(keytype) {
case PPKP_ED25519:
/* check public key length first */
if(ctrl_pklen != ED25519_PK_LEN + sizeof(uint32_t)) {
r = SXT_EKEY;
goto __failed;
}
/* check for encryption details */
if(decrypt) {
if(strcmp(ciphername, "aes128-cbc") || strcmp(kdfname, "bcrypt")) {
r = SXT_EKEY;
goto __failed;
}
/* get passkey */
passkey = malloc(64*sizeof(char));
if(!passkey) {
r = SXT_ENOMEM;
goto __failed;
}
r = ask_passkey(passkey, 63, 0, priv);
if(r != SXT_SUCCESS) { /* FIXME: might be return SXT_EAUTH here ? */
goto __failed;
}
/* decrypt it */
r = _sxtkey_privkey_decrypt(privkey, kdfopt, passkey);
if(r != SXT_SUCCESS) goto __failed;
} else {
/* nil expected */
if(strcmp(ciphername, "nil") || strcmp(kdfname, "nil")) {
r = SXT_EKEY;
goto __failed;
}
}
/* here we have privkey decrypted */
r = ndbuf_escan(privkey, "ddRRqb", &r1, &r2, &ed25519_pubbuf, &ed25519_privbuf, &hash, &chk);
if(r != SXT_SUCCESS) {
hash = 0;
chk = 0;
r = SXT_EKEY;
goto __failed;
}
/* control check for random numbers */
if(r1 != r2) goto __invalid;
/* control check of public keys */
if(ndbuf_cmp(ctrl_pubkey, ed25519_pubbuf)) {
__invalid:
ndbuf_free(ed25519_pubbuf);
ndbuf_free(ed25519_privbuf);
r = SXT_EKEY;
goto __failed;
}
/* alloc a key and copy data */
if(!(key = sxtkey_alloc())) {
__enomem:
ndbuf_free(ed25519_pubbuf);
ndbuf_free(ed25519_privbuf);
r = SXT_ENOMEM;
goto __failed;
}
/* allocate space */
if(!(key->pubkey = malloc(ED25519_PK_LEN)) ||
!(key->privkey = malloc(ED25519_SK_LEN))) {
if(key->pubkey) free(key->pubkey);
if(key->privkey) free(key->privkey);
sxtkey_free(key);
goto __enomem;
}
/* copy and set all required data */
memcpy(key->pubkey, ndbuf_rdata(ed25519_pubbuf), ED25519_PK_LEN);
memcpy(key->privkey, ndbuf_rdata(ed25519_privbuf), ED25519_SK_LEN);
/* free it */
ndbuf_free(ed25519_pubbuf);
ndbuf_free(ed25519_privbuf);
key->type = keytype;
key->hash = hash;
key->flags = SXT_PPKP_PRIVATE;
/* and the rest */
*ik = key;
r = SXT_SUCCESS;
break;
default: /* unsupported key type ... */
r = SXT_EKEY;
goto __failed;
break;
}
__failed:
/* free temproary stuff */
if(magic) free(magic);
if(keyname) free(keyname);
if(ciphername) free(ciphername);
if(kdfname) free(kdfname);
if(passkey) {
memset(passkey, 0, 64);
free(passkey);
}
if(kdfopt) ndbuf_free(kdfopt);
if(ctrl_pubkey) ndbuf_free(ctrl_pubkey);
if(privkey) ndbuf_free(privkey);
return r;
}
int sxtkey_privkey_import_fbase64blob(const char *b64pk, uint8_t keytype,
int (*ask_passkey)(char *pkbuf, size_t length,
int confirm, void *priv),
void *priv, sxtkey_t **ik)
{
ndbuf_t *fullkeybuf = NULL;
char *decoded = NULL;
int len, r = SXT_SUCCESS;
if(!b64pk) return SXT_EINVAL;
else len = strlen(b64pk);
if(keytype == 0) return SXT_EKEY;
if(!len) return SXT_EINVAL;
if(!(decoded = malloc(len + sizeof(char)))) return SXT_ENOMEM;
/* decode */
len = sxt_b64decode_in(b64pk, len, decoded, len);
if(!len) {
r = SXT_EKEYFMT;
goto __failed;
}
/* allocate a buffer and insert raw data */
if(!(fullkeybuf = ndbuf_new())) {
r = SXT_ENOMEM;
goto __failed;
} else ndbuf_setflags(fullkeybuf, NDBUF_BURN);
ndbuf_write_raw_head(fullkeybuf, decoded, len);
r = sxtkey_privkey_import_rdbuf(fullkeybuf, keytype, ask_passkey, priv, ik);
__failed:
if(decoded) {
memset(decoded, 0, len);
free(decoded);
}
if(fullkeybuf) ndbuf_free(fullkeybuf);
return r;
}
int sxtkey_pubkey_import_fbase64blob(const char *b64b, uint8_t keytype,
uint64_t ctrl_hash, sxtkey_t **ik)
{
sxtkey_t *key = NULL;
ndbuf_t *keybuf = NULL;
char *blob = NULL, *ed25519pk = NULL;
uint64_t hash;
uint32_t klen = 0;
int len, r = SXT_SUCCESS;
if(!b64b) return SXT_EINVAL;
if(!sxtkey_name(keytype)) return SXT_EKEY;
/* ok, first decode */
len = strlen(b64b) + sizeof(char);
if(!(blob = malloc(len))) return SXT_ENOMEM;
else memset(blob, 0, len);
len = sxt_b64decode_in(b64b, len - 1, blob, len);
if(!(keybuf = ndbuf_new())) {
r = SXT_ENOMEM;
goto __failed;
}
switch(keytype) {
case PPKP_ED25519:
if(!(ed25519pk = malloc(ED25519_PK_LEN))) {
r = SXT_ENOMEM;
goto __failed;
}
/* <pubkey><hash> */
ndbuf_write_raw(keybuf, blob, len);
ndbuf_resetcur(keybuf);
ndbuf_escan(keybuf, "dpq", &klen, ED25519_PK_LEN, ed25519pk, &hash);
if(klen != ED25519_PK_LEN) {
r = SXT_EKEY;
goto __failed;
}
if(hash != ctrl_hash) {
r = SXT_EKEY;
goto __failed;
}
if(!(key = sxtkey_alloc())) {
r = SXT_ENOMEM; goto __failed;
}
key->type = keytype;
if(!(key->pubkey = malloc(ED25519_PK_LEN))) {
sxtkey_free(key);
r = SXT_ENOMEM; goto __failed;
}
key->hash = hash;
key->flags = SXT_PPKP_PUBLIC;
memcpy(key->pubkey, ed25519pk, ED25519_PK_LEN);
r = SXT_SUCCESS;
*ik = key;
break;
default: r = SXT_EKEYFMT; break;
}
__failed:
if(blob) free(blob);
if(keybuf) ndbuf_free(keybuf);
if(ed25519pk) free(ed25519pk);
return r;
}
int sxtkey_import_public_file(const char *file, sxtkey_t **ik)
{
FILE *stream = NULL;
char *rawfbuf = NULL, *tbuf, *tebuf, *tuple = NULL, *b64pk = NULL;
ndbuf_t *chbuf = NULL;
int tc = 0, tcc = 0, len;
struct stat stbuf;
int r = SXT_ENOMEM;
uint64_t ctrl_hash;
uint8_t keytype = 0;
*ik = NULL;
if(!file) return SXT_EINVAL;
if(stat(file, &stbuf) != 0) return SXT_EIO;
else if(!S_ISREG(stbuf.st_mode)) return SXT_EINVAL;
if(stbuf.st_size >= SXT_PPKP_MAXCSIZE) return SXT_EINVAL;
if((stream = fopen(file, "r")) == NULL) return SXT_EIO;
if(!(rawfbuf = malloc(stbuf.st_size + sizeof(char)))) goto __failed;
else rawfbuf[stbuf.st_size] = '\0';
if(fread(rawfbuf, stbuf.st_size, 1, stream) != 1) {
r = SXT_EIO;
goto __failed;
}
/* check general format */
r = _sxtkey_check_fmtrawbuf(rawfbuf);
if(r != SXT_SUCCESS) goto __failed;
/* ok, now we can parse it, avoid to use libsexpr here,
* to make sxt inpedendent as possible */
for(tbuf = rawfbuf, tc = 0, tcc = 0, tebuf = NULL; *tbuf != '\0'; tbuf++) {
switch(tcc) {
case 0: /* just entering */
if(*tbuf == '(') tcc++;
break;
case 1: /* first e.g. key type plain text description */
if(!tebuf) tebuf = tbuf;
else if(tebuf && *tbuf == ' ') {
if(!tebuf) goto __failed;
len = (tbuf - tebuf) + sizeof(char);
if(!(tuple = malloc(len))) {
r = SXT_ENOMEM;
goto __failed;
} else memset(tuple, 0, len);
/* check keytype */
memcpy(tuple, tebuf, len - sizeof(char));
keytype = sxtkey_type_fname(tuple);
free(tuple);
if(keytype == 0) {
r = SXT_EKEY; /* unsupported key type */
goto __failed;
}
/* going further */
tebuf = NULL;
tcc++;
}
break;
case 2: /* public sign keyword */
if(!tebuf && *tbuf != ' ') tebuf = tbuf;
else if(tebuf && *tbuf == ' ') {
if(*tebuf != '\'') { /* invalid format */
r = SXT_EKEYFMT;
goto __failed;
}
/* get tuple */
len = (tbuf - tebuf) + sizeof(char);
if(!(tuple = malloc(len))) {
r = SXT_ENOMEM;
goto __failed;
} else memset(tuple, 0, len);
memcpy(tuple, tebuf, len - sizeof(char));
if(strcmp(tuple, "'public")) tc = SXT_EKEYFMT;
else tc = 0;
free(tuple);
if(tc) {
r = tc;
goto __failed;
}
tebuf = NULL;
tcc++;
}
break;
case 3: /* key contents */
if(!tebuf && *tbuf != ' ') tebuf = tbuf;
else if(tebuf && *tbuf == ' ') {
len = (tbuf - tebuf) + sizeof(char);
/* check format */
if(*tebuf != '"') tc = SXT_EKEYFMT;
if(!tc && tebuf[len - 2] != '"') tc = SXT_EKEYFMT;
len -= 2;
if(tc || len <= 0) { r = tc; goto __failed; }
tebuf++;
if(!(tuple = malloc(len))) { r = SXT_ENOMEM; goto __failed; }
else memset(tuple, 0, len);
memcpy(tuple, tebuf, len - sizeof(char));
b64pk = tuple;
tuple = tebuf = NULL;
tcc++;
}
break;
case 4: /* base64 encoded hash to verify contents */
if(!tebuf && *tbuf == '"') tebuf = tbuf;
else if(tebuf && *tbuf == '"') {
tebuf++;
len = (tbuf - tebuf) + sizeof(char);
if(len <= 1) tc = SXT_EKEYFMT;
if(tc) {r = tc; goto __failed;}
if(!(tuple = malloc(len))) { r = SXT_ENOMEM; goto __failed; }
else memset(tuple, 0, len);
memcpy(tuple, tebuf, len - 1);
/* decode hash */
tebuf = NULL;
if(!(tebuf = malloc(len))) {
free(tuple);
r = SXT_ENOMEM; goto __failed;
} else memset(tebuf, 0, len);
len = sxt_b64decode_in(tuple, strlen(tuple), tebuf, len);
free(tuple);
if(!(chbuf = ndbuf_new())) {
free(tebuf);
r = SXT_ENOMEM; goto __failed;
}
ndbuf_write_raw(chbuf, tebuf, len);
ndbuf_resetcur(chbuf);
ndbuf_read_u64(chbuf, &ctrl_hash);
ndbuf_free(chbuf);
free(tebuf);
tebuf = tuple = NULL;
tcc++;
}
break;
case 5:
if(*tbuf == ')') tcc++;
else if(*tbuf != ' ') {
r = SXT_EKEYFMT;
goto __failed;
}
break;
default: break;
}
}
/* ok check fmt last time */
if(tcc < 6) {
r = SXT_EKEYFMT; goto __failed;
}
/* import from base64 encoded blob */
r = sxtkey_pubkey_import_fbase64blob(b64pk, keytype, ctrl_hash, ik);
__failed:
if(stream) fclose(stream);
if(rawfbuf) free(rawfbuf);
if(b64pk) free(b64pk);
return r;
}
int sxtkey_export_priv_file(const sxtkey_t *key, const char *file, const char *passkey,
int (*ask_passkey)(char *pkbuf, size_t length, int confirm, void *priv),
void *priv)
{
FILE *out = NULL;
char *pass = NULL;
sxtsafebuffer_t *bin = NULL;
int r = 0;
if(!key) return SXT_EINVAL;
if(!sxtkey_private(key)) return SXT_EKEY;
if((out = fopen(file, "wb")) == NULL) return SXT_EIO;
/* passkey challenge */
if(passkey) pass = (char *)passkey;
else if(ask_passkey) {
if((pass = malloc(sizeof(char)*64)) == NULL) {
r = SXT_ENOMEM;
goto __failed;
}
r = ask_passkey(pass, 64, 1, priv);
if(r != SXT_SUCCESS) goto __failed;
}
/* ok, get the base64 encoded data for the key */
switch(key->type) {
case PPKP_ED25519:
r = sxtkey_export_priv_ed25519(key, pass, &bin);
if(!bin) goto __failed;
break;
default:
r = SXT_EKEY;
goto __failed;
}
/* sxt key container as follows: (<key-type> "<base64 encoded content of the key>")\n */
fprintf(out, "(%s \"%s\")\n", sxtkey_name(key->type), (char *)sxtsafebuffer_getdata(bin));
sxtsafebuffer_destroy(bin);
__failed:
if(pass && pass != passkey) {
memset(pass, 0, sizeof(char)*64); /* clean to disallow passkeys found in coredumps */
free(pass);
}
fclose(out);
return r;
}
static int sxtkey_publickey2blob_whash(const sxtkey_t *key, sxtsafebuffer_t **o)
{
sxtsafebuffer_t *buf = NULL, *pk = NULL;
ndbuf_t *b = NULL;
int r = SXT_SUCCESS;
if(!key) return SXT_EINVAL;
/* get a public key blob without hash */
switch(key->type) {
case PPKP_ED25519:
if((r = sxt_public_ed25519_2sb(key, &pk)) != SXT_SUCCESS)
return r;
break;
default: return SXT_EKEY;
}
if(!(b = ndbuf_new())) {
r = SXT_ENOMEM;
goto __failed;
}
ndbuf_print(b, "pq", sxtsafebuffer_length(pk), sxtsafebuffer_getdata(pk),
key->hash);
if(!(buf = sxtsafebuffer_new(ndbuf_length(b)))) {
r = SXT_ENOMEM;
goto __failed;
}
memcpy(sxtsafebuffer_getdata(buf), ndbuf_rdata(b), ndbuf_length(b));
*o = buf;
__failed:
if(pk) sxtsafebuffer_destroy(pk);
if(b) ndbuf_free(b);
return r;
}
static int sxtkey_hash2blob(const sxtkey_t *key, sxtsafebuffer_t **o)
{
sxtsafebuffer_t *buf = NULL;
ndbuf_t *b = NULL;
int r = SXT_SUCCESS;
if(!key) return SXT_EINVAL;
if(!(b = ndbuf_new())) return SXT_ENOMEM;
ndbuf_print(b, "q", key->hash);
if(!(buf = sxtsafebuffer_new(ndbuf_length(b)))) {
r = SXT_ENOMEM;
goto __failed;
}
memcpy(sxtsafebuffer_getdata(buf), ndbuf_rdata(b), ndbuf_length(b));
*o = buf;
__failed:
if(b) ndbuf_free(b);
return r;
}
int sxtkey_export_public_file(const sxtkey_t *key, const char *file)
{
FILE *out = NULL;
sxtsafebuffer_t *hashbuf = NULL, *pubkeybuf = NULL;
sxtsafebuffer_t *pubkey_sb = NULL, *hash_sb = NULL;
char *pckey = NULL, *pchash = NULL;
int r = SXT_SUCCESS;
if(!key) return SXT_EINVAL;
if(!sxtkey_public(key)) return SXT_EKEY;
if((out = fopen(file, "wb")) == NULL) return SXT_EIO;
/*
* format described as follows:
* (<key-type> 'public "<pubkey><hash>" "<hash>")\n
* all scoped values are base64 encoded
*/
/* get a public key with hash blob */
if((r = sxtkey_publickey2blob_whash(key, &pubkey_sb)) != SXT_SUCCESS) goto __failed;
/* get key hash blob */
if((r = sxtkey_hash2blob(key, &hash_sb)) != SXT_SUCCESS) goto __failed;
/* allocate buffers for base64 encoded data */
if(!(pubkeybuf = sxtsafebuffer_new(sxt_rawlen2b64len(sxtsafebuffer_length(pubkey_sb))))) {
r = SXT_ENOMEM;
goto __failed;
}
if(!(hashbuf = sxtsafebuffer_new(sxt_rawlen2b64len(sxtsafebuffer_length(hash_sb))))) {
r = SXT_ENOMEM;
goto __failed;
}
/* do base64 encode */
sxt_b64encode_in(sxtsafebuffer_getdata(pubkey_sb), sxtsafebuffer_getdata(pubkeybuf),
sxtsafebuffer_length(pubkey_sb)); /* pubkey */
sxt_b64encode_in(sxtsafebuffer_getdata(hash_sb), sxtsafebuffer_getdata(hashbuf),
sxtsafebuffer_length(hash_sb)); /* hash */
/* get cstring to output */
if(!(pckey = sxtsafebuffer_getcstr(pubkeybuf))) {
r = SXT_ENOMEM;
goto __failed;
}
if(!(pchash = sxtsafebuffer_getcstr(hashbuf))) {
r = SXT_ENOMEM;
sxtsafebuffer_freecstr(pckey);
goto __failed;
}
/* output this */
fprintf(out, "(%s 'public \"%s\" \"%s\")\n", sxtkey_name(key->type), pckey, pchash);
/* free all stuff */
sxtsafebuffer_freecstr(pckey);
sxtsafebuffer_freecstr(pchash);
__failed:
if(pubkey_sb) sxtsafebuffer_destroy(pubkey_sb);
if(hash_sb) sxtsafebuffer_destroy(hash_sb);
if(hashbuf) sxtsafebuffer_destroy(hashbuf);
if(pubkeybuf) sxtsafebuffer_destroy(pubkeybuf);
fclose(out);
return r;
}
static int sxtkey_export_priv_ed25519(const sxtkey_t *key, const char *pass, sxtsafebuffer_t **bin)
{
int r = 0;
sxtsafebuffer_t *b64 = NULL, *pubkey_sb = NULL;
ndbuf_t *keybuf = NULL, *privbuf = NULL;
ndbuf_t *kdfopts = NULL;
uint32_t urand;
uint8_t flags = 0;
if(!key) return SXT_EINVAL;
if(key->type != PPKP_ED25519) return SXT_EKEY;
/* allocate a buffer for the key at all */
if(!(keybuf = ndbuf_new())) return SXT_ENOMEM;
else ndbuf_setflags(keybuf, NDBUF_BURN);
/* ok firstly get private-key-data according to SXT key container doc */
if((r = sxt_public_ed25519_2sb(key, &pubkey_sb)) != SXT_SUCCESS) goto __failed;
/* now we need random number */
sxt_get_random(&urand, sizeof(uint32_t), 1);
if(!(privbuf = ndbuf_new())) {
r = SXT_ENOMEM;
goto __failed;
} else ndbuf_setflags(privbuf, NDBUF_BURN);
/* two random numbers */
if(!ndbuf_print(privbuf, "dd", urand, urand)) {
r = SXT_ENOMEM;
goto __failed;
}
/* pub, priv, hash, zero padding */
if(!ndbuf_print(privbuf, "dpdpqb", (uint32_t)ED25519_PK_LEN, (size_t)ED25519_PK_LEN,
key->pubkey, (uint32_t)ED25519_SK_LEN, (size_t)ED25519_SK_LEN,
key->privkey, key->hash, (uint8_t)'\0')) {
r = SXT_ENOMEM;
goto __failed;
}
/* if hash is non-zero - include it i.e. set flag */
if(key->hash) flags |= SXT_PPKP_IHASH;
if(pass) { /* we need to encrypt the key i.e. privbuf *must* be encrypted */
/* to avoid yet another wheel invention,
* the following was taken from ssh implementation, i mean
* kdf and crypto in use to encrypt the key
*/
uint8_t pad = 1;
size_t keystub_len;
sxtsafebuffer_t *salt;
sxt_cipher_t *cipher = sxt_cipher_get("aes128-cbc");
uint8_t keystub[128];
/* set flags */
flags |= SXT_PPKP_ENCRYPT;
if(!cipher) {
/* it might be in case of forgotten initialization function */
r = SXT_EKEY;
goto __failed;
}
/* get the salt */
if(!(salt = sxtsafebuffer_new(16))) {
__enomem1:
sxt_cipher_free(cipher);
r = SXT_ENOMEM;
goto __failed;
} else sxt_get_random(sxtsafebuffer_getdata(salt), 16, 1);
/* generate kdf description <salt><rounds> */
if(!(kdfopts = ndbuf_new())) {
sxtsafebuffer_destroy(salt);
goto __enomem1;
}
ndbuf_print(kdfopts, "Pd", 16, sxtsafebuffer_getdata(salt), (uint32_t)32);
/* ok, now we need to get a padding, just to be sure to be aligned for the
cipher block size */
while(ndbuf_length(privbuf) % cipher->blksize != 0) {
if(ndbuf_write_u8(privbuf, pad) != sizeof(uint8_t)) {
sxt_cipher_free(cipher);
r = SXT_ENOMEM;
goto __failed;
}
pad++;
}
/* key stub */
keystub_len = cipher->keysize/8 + cipher->blksize;
if(keystub_len > sizeof(keystub)) {
r = SXT_EKEY;
sxt_cipher_free(cipher);
goto __failed;
}
/* bcrypt those stuff */
if(bcrypt_pbkdf(pass, strlen(pass), sxtsafebuffer_getdata(salt),
sxtsafebuffer_length(salt), keystub, keystub_len, 32)) {
r = SXT_ERROR;
sxt_cipher_free(cipher);
goto __failed;
}
/* encrypt with cipher */
sxt_cipher_set_encrypt_key(cipher, keystub, keystub + cipher->keysize/8);
sxt_cipher_encrypt(cipher, ndbuf_rdata(privbuf), ndbuf_rdata(privbuf), ndbuf_length(privbuf));
/* clean up all */
memset(keystub, 0, sizeof(keystub));
sxt_cipher_free(cipher);
sxtsafebuffer_destroy(salt);
}
/* in case of non-crypted key we anyways should include encrypt options */
if(!kdfopts) {
if(!(kdfopts = ndbuf_new())) {
r = SXT_ENOMEM;
goto __failed;
}
ndbuf_print(kdfopts, "Pd", 3, "nil", (uint32_t)0);
}
/* there are a time to print all required content to the buffer */
/* <MAGIC VERSION SIGN><8bit flags><key-type>
* <cipher-name><kdf-name><kdf-opts>
* <public-key>
* <private-key-data>
* according to the documentation
*/
ndbuf_print(keybuf, "sbsssPPP", PPKP_MAGIC, flags, "ppkp-ed25519",
pass ? "aes128-cbc" : "nil", pass ? "bcrypt" : "nil",
ndbuf_length(kdfopts), ndbuf_rdata(kdfopts),
sxtsafebuffer_length(pubkey_sb), sxtsafebuffer_getdata(pubkey_sb),
ndbuf_length(privbuf), ndbuf_rdata(privbuf));
/* as described - encode all with base64 */
b64 = sxtsafebuffer_new(sxt_rawlen2b64len(ndbuf_length(keybuf)));
if(!b64) {
r = SXT_ENOMEM;
goto __failed;
}
/* encode buffer */
sxt_b64encode_in((const char *)ndbuf_rdata(keybuf), (char *)sxtsafebuffer_getdata(b64),
ndbuf_length(keybuf));
/* the last ... */
*bin = b64;
r = SXT_SUCCESS;
__failed:
if(keybuf) ndbuf_free(keybuf); /* container free */
if(pubkey_sb) sxtsafebuffer_destroy(pubkey_sb); /* public key bin free */
if(privbuf) ndbuf_free(privbuf); /* private key buffer free */
return r;
}
static int sxt_public_ed25519_2sb(const sxtkey_t *key, sxtsafebuffer_t **buf)
{
ndbuf_t *rbuf = NULL;
sxtsafebuffer_t *o = NULL;
int r = SXT_SUCCESS;
if(!key->pubkey) return SXT_EKEY;
r = sxt_public_ed25519_2rdb(key, &rbuf);
if(!rbuf) return r;
if(!(o = sxtsafebuffer_new((size_t)ndbuf_length(rbuf)))) {
r = SXT_ENOMEM;
goto __failed;
}
sxtsafebuffer_setdata(o, ndbuf_rdata(rbuf), (size_t)ndbuf_length(rbuf));
*buf = o;
__failed:
ndbuf_free(rbuf);
return r;
}
static int sxt_public_ed25519_2rdb(const sxtkey_t *key, ndbuf_t **out)
{
ndbuf_t *buf = NULL;
if(!key->pubkey) return SXT_EKEY;
if(!(buf = ndbuf_new())) return SXT_ENOMEM;
else ndbuf_setflags(buf, NDBUF_BURN);
if(!ndbuf_print(buf, "dp", (uint32_t)ED25519_PK_LEN, (size_t)ED25519_PK_LEN,
key->pubkey)) {
ndbuf_free(buf);
return SXT_EKEY;
}
*out = buf;
return SXT_SUCCESS;
}
static int _sxtkey_check_fmtrawbuf(const char *buffer)
{
char *tbuf;
int r = SXT_SUCCESS, tc = 0, tcc = 0;
/* currently only own container supported,
* let's checkout format:
* = () and ""
*/
for(tbuf = (char *)buffer; *tbuf != '\0'; tbuf++) {
switch(*tbuf) {
case '(': tc++; break;
case ')': tc--; break;
case '"':
if(!tc) {
r = SXT_EKEYFMT;
goto __failed;
} else tcc++;
break;
default: break; /* just ignore */
}
}
r = SXT_EKEYFMT;
if(tc) goto __failed;
if(tcc%2) goto __failed;
r = SXT_SUCCESS;
__failed:
return r;
}
static int _sxtkey_privkey_decrypt(ndbuf_t *privkey, ndbuf_t *kdfopt,
const char *passkey)
{
int r = SXT_SUCCESS;
ndbuf_t *salt = NULL;
sxt_cipher_t *cipher = sxt_cipher_get("aes128-cbc");
uint32_t rounds, keystub_len;
uint8_t keystub[128];
/* default check */
if(!privkey || !kdfopt) return SXT_EINVAL;
if(!passkey) return SXT_EAUTH;
/* cipher */
if(!(cipher = sxt_cipher_get("aes128-cbc"))) return SXT_EKEY;
/* get salt and rounds */
if((r = ndbuf_escan(kdfopt, "Rd", &salt, &rounds)) != SXT_SUCCESS)
goto __failed;
/* check blob length and cipher block size */
if(ndbuf_length(privkey) % cipher->blksize != 0) {
r = SXT_EKEY;
goto __failed;
}
/* bcrypt */
keystub_len = cipher->keysize/8 + cipher->blksize;
if(keystub_len > sizeof(keystub)) {
r = SXT_EKEY;
goto __failed;
}
if(bcrypt_pbkdf(passkey, strlen(passkey), ndbuf_rdata(salt), ndbuf_length(salt),
keystub, keystub_len, rounds) < 0) {
memset(keystub, 0, keystub_len);
r = SXT_EAUTH;
goto __failed;
}
/* set decrypt key and decrypt */
cipher->f->set_decrypt_key(cipher, keystub, keystub + cipher->keysize/8);
cipher->f->decrypt(cipher, ndbuf_rdata(privkey), ndbuf_rdata(privkey),
ndbuf_length(privkey));
/* free/burn unused data */
memset(keystub, 0, keystub_len);
__failed:
if(cipher) sxt_cipher_free(cipher);
if(salt) ndbuf_free(salt);
return r;
}