Add the ability to sign and verify files.
This commit is contained in:
parent
7418e9077f
commit
d8ca00fe40
4 changed files with 291 additions and 1 deletions
249
src/tincctl.c
249
src/tincctl.c
|
|
@ -154,6 +154,8 @@ static void usage(bool status) {
|
|||
" join INVITATION Join a VPN using an INVITATION\n"
|
||||
" network [NETNAME] List all known networks, or switch to the one named NETNAME.\n"
|
||||
" fsck Check the configuration files for problems.\n"
|
||||
" sign [FILE] Generate a signed version of a file.\n"
|
||||
" verify NODE [FILE] Verify that a file was signed by the given NODE.\n"
|
||||
"\n");
|
||||
printf("Report bugs to tinc@tinc-vpn.org.\n");
|
||||
}
|
||||
|
|
@ -1431,6 +1433,29 @@ char *get_my_name(bool verbose) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static ecdsa_t *get_pubkey(FILE *f) {
|
||||
char buf[4096];
|
||||
char *value;
|
||||
while(fgets(buf, sizeof buf, f)) {
|
||||
int len = strcspn(buf, "\t =");
|
||||
value = buf + len;
|
||||
value += strspn(value, "\t ");
|
||||
if(*value == '=') {
|
||||
value++;
|
||||
value += strspn(value, "\t ");
|
||||
}
|
||||
if(!rstrip(value))
|
||||
continue;
|
||||
buf[len] = 0;
|
||||
if(strcasecmp(buf, "Ed25519PublicKey"))
|
||||
continue;
|
||||
if(*value)
|
||||
return ecdsa_set_base64_public_key(value);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const var_t variables[] = {
|
||||
/* Server configuration */
|
||||
{"AddressFamily", VAR_SERVER},
|
||||
|
|
@ -2331,6 +2356,228 @@ static int cmd_fsck(int argc, char *argv[]) {
|
|||
return fsck(orig_argv[0]);
|
||||
}
|
||||
|
||||
static void *readfile(FILE *in, size_t *len) {
|
||||
size_t count = 0;
|
||||
size_t alloced = 4096;
|
||||
char *buf = xmalloc(alloced);
|
||||
|
||||
while(!feof(in)) {
|
||||
size_t read = fread(buf + count, 1, alloced - count, in);
|
||||
if(!read)
|
||||
break;
|
||||
count += read;
|
||||
if(count >= alloced) {
|
||||
alloced *= 2;
|
||||
buf = xrealloc(buf, alloced);
|
||||
}
|
||||
}
|
||||
|
||||
if(len)
|
||||
*len = count;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int cmd_sign(int argc, char *argv[]) {
|
||||
if(argc > 2) {
|
||||
fprintf(stderr, "Too many arguments!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!name) {
|
||||
name = get_my_name(true);
|
||||
if(!name)
|
||||
return 1;
|
||||
}
|
||||
|
||||
char fname[PATH_MAX];
|
||||
snprintf(fname, sizeof fname, "%s" SLASH "ed25519_key.priv", confbase);
|
||||
FILE *fp = fopen(fname, "r");
|
||||
if(!fp) {
|
||||
fprintf(stderr, "Could not open %s: %s\n", fname, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
ecdsa_t *key = ecdsa_read_pem_private_key(fp);
|
||||
|
||||
if(!key) {
|
||||
fprintf(stderr, "Could not read private key from %s\n", fname);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
FILE *in;
|
||||
|
||||
if(argc == 2) {
|
||||
in = fopen(argv[1], "rb");
|
||||
if(!in) {
|
||||
fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno));
|
||||
ecdsa_free(key);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
in = stdin;
|
||||
}
|
||||
|
||||
size_t len;
|
||||
char *data = readfile(in, &len);
|
||||
if(in != stdin)
|
||||
fclose(in);
|
||||
if(!data) {
|
||||
fprintf(stderr, "Error reading %s: %s\n", argv[1], strerror(errno));
|
||||
ecdsa_free(key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Ensure we sign our name and current time as well
|
||||
long t = time(NULL);
|
||||
char *trailer;
|
||||
xasprintf(&trailer, " %s %ld", name, t);
|
||||
int trailer_len = strlen(trailer);
|
||||
|
||||
data = xrealloc(data, len + trailer_len);
|
||||
memcpy(data + len, trailer, trailer_len);
|
||||
free(trailer);
|
||||
|
||||
char sig[87];
|
||||
if(!ecdsa_sign(key, data, len + trailer_len, sig)) {
|
||||
fprintf(stderr, "Error generating signature\n");
|
||||
free(data);
|
||||
ecdsa_free(key);
|
||||
return 1;
|
||||
}
|
||||
b64encode(sig, sig, 64);
|
||||
ecdsa_free(key);
|
||||
|
||||
fprintf(stdout, "Signature = %s %ld %s\n", name, t, sig);
|
||||
fwrite(data, len, 1, stdout);
|
||||
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_verify(int argc, char *argv[]) {
|
||||
if(argc < 2) {
|
||||
fprintf(stderr, "Not enough arguments!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(argc > 3) {
|
||||
fprintf(stderr, "Too many arguments!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *node = argv[1];
|
||||
if(!strcmp(node, ".")) {
|
||||
if(!name) {
|
||||
name = get_my_name(true);
|
||||
if(!name)
|
||||
return 1;
|
||||
}
|
||||
node = name;
|
||||
} else if(!strcmp(node, "*")) {
|
||||
node = NULL;
|
||||
} else {
|
||||
if(!check_id(node)) {
|
||||
fprintf(stderr, "Invalid node name\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
FILE *in;
|
||||
|
||||
if(argc == 3) {
|
||||
in = fopen(argv[2], "rb");
|
||||
if(!in) {
|
||||
fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
in = stdin;
|
||||
}
|
||||
|
||||
size_t len;
|
||||
char *data = readfile(in, &len);
|
||||
if(in != stdin)
|
||||
fclose(in);
|
||||
if(!data) {
|
||||
fprintf(stderr, "Error reading %s: %s\n", argv[1], strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *newline = memchr(data, '\n', len);
|
||||
if(!newline || (newline - data > MAX_STRING_SIZE - 1)) {
|
||||
fprintf(stderr, "Invalid input\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
*newline++ = '\0';
|
||||
|
||||
char signer[MAX_STRING_SIZE] = "";
|
||||
char sig[MAX_STRING_SIZE] = "";
|
||||
long t = 0;
|
||||
|
||||
if(sscanf(data, "Signature = %s %ld %s", signer, &t, sig) != 3 || strlen(sig) != 86 || !t || !check_id(signer)) {
|
||||
fprintf(stderr, "Invalid input\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(node && strcmp(node, signer)) {
|
||||
fprintf(stderr, "Signature is not made by %s\n", node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!node)
|
||||
node = signer;
|
||||
|
||||
char *trailer;
|
||||
xasprintf(&trailer, " %s %ld", signer, t);
|
||||
int trailer_len = strlen(trailer);
|
||||
|
||||
data = xrealloc(data, len + trailer_len);
|
||||
memcpy(data + len, trailer, trailer_len);
|
||||
free(trailer);
|
||||
|
||||
char fname[PATH_MAX];
|
||||
snprintf(fname, sizeof fname, "%s" SLASH "hosts" SLASH "%s", confbase, node);
|
||||
FILE *fp = fopen(fname, "r");
|
||||
if(!fp) {
|
||||
fprintf(stderr, "Could not open %s: %s\n", fname, strerror(errno));
|
||||
free(data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ecdsa_t *key = get_pubkey(fp);
|
||||
if(!key) {
|
||||
rewind(fp);
|
||||
key = ecdsa_read_pem_public_key(fp);
|
||||
}
|
||||
if(!key) {
|
||||
fprintf(stderr, "Could not read public key from %s\n", fname);
|
||||
fclose(fp);
|
||||
free(data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if(b64decode(sig, sig, 86) != 64 || !ecdsa_verify(key, newline, len + trailer_len - (newline - data), sig)) {
|
||||
fprintf(stderr, "Invalid signature\n");
|
||||
free(data);
|
||||
ecdsa_free(key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ecdsa_free(key);
|
||||
|
||||
fwrite(newline, len - (newline - data), 1, stdout);
|
||||
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *command;
|
||||
int (*function)(int argc, char *argv[]);
|
||||
|
|
@ -2375,6 +2622,8 @@ static const struct {
|
|||
{"join", cmd_join},
|
||||
{"network", cmd_network},
|
||||
{"fsck", cmd_fsck},
|
||||
{"sign", cmd_sign},
|
||||
{"verify", cmd_verify},
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue