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
|
@ -5,7 +5,7 @@ _tinc() {
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
opts="-c -d -D -K -n -o -L -R -U --config --no-detach --debug --net --option --mlock --logfile --pidfile --chroot --user --help --version"
|
opts="-c -d -D -K -n -o -L -R -U --config --no-detach --debug --net --option --mlock --logfile --pidfile --chroot --user --help --version"
|
||||||
confvars="Address AddressFamily BindToAddress BindToInterface Broadcast BroadcastSubnet Cipher ClampMSS Compression ConnectTo DecrementTTL Device DeviceStandby DeviceType Digest DirectOnly Ed25519PrivateKeyFile Ed25519PublicKey Ed25519PublicKeyFile ExperimentalProtocol Forwarding GraphDumpFile Hostnames IffOneQueue IndirectData Interface KeyExpire ListenAddress LocalDiscovery MACExpire MACLength MaxOutputBufferSize MaxTimeout Mode MTUInfoInterval Name PMTU PMTUDiscovery PingInterval PingTimeout Port PriorityInheritance PrivateKeyFile ProcessPriority Proxy PublicKeyFile ReplayWindow StrictSubnets Subnet TCPOnly TunnelServer UDPDiscovery UDPDiscoveryKeepaliveInterval UDPDiscoveryInterval UDPDiscoveryTimeout UDPInfoInterval UDPRcvBuf UDPSndBuf UPnP UPnPDiscoverWait UPnPRefreshPeriod VDEGroup VDEPort Weight"
|
confvars="Address AddressFamily BindToAddress BindToInterface Broadcast BroadcastSubnet Cipher ClampMSS Compression ConnectTo DecrementTTL Device DeviceStandby DeviceType Digest DirectOnly Ed25519PrivateKeyFile Ed25519PublicKey Ed25519PublicKeyFile ExperimentalProtocol Forwarding GraphDumpFile Hostnames IffOneQueue IndirectData Interface KeyExpire ListenAddress LocalDiscovery MACExpire MACLength MaxOutputBufferSize MaxTimeout Mode MTUInfoInterval Name PMTU PMTUDiscovery PingInterval PingTimeout Port PriorityInheritance PrivateKeyFile ProcessPriority Proxy PublicKeyFile ReplayWindow StrictSubnets Subnet TCPOnly TunnelServer UDPDiscovery UDPDiscoveryKeepaliveInterval UDPDiscoveryInterval UDPDiscoveryTimeout UDPInfoInterval UDPRcvBuf UDPSndBuf UPnP UPnPDiscoverWait UPnPRefreshPeriod VDEGroup VDEPort Weight"
|
||||||
commands="add connect debug del disconnect dump edit export export-all generate-ed25519-keys generate-keys generate-rsa-keys get help import info init invite join list log network pcap pid purge reload restart retry set start stop top version"
|
commands="add connect debug del disconnect dump edit export export-all generate-ed25519-keys generate-keys generate-rsa-keys get help import info init invite join list log network pcap pid purge reload restart retry set sign start stop top verify version"
|
||||||
|
|
||||||
case ${prev} in
|
case ${prev} in
|
||||||
-c|--config)
|
-c|--config)
|
||||||
|
|
|
@ -230,6 +230,30 @@ unknown and obsolete configuration variables, wrong public and/or private keys,
|
||||||
When problems are found, this will be printed on a line with WARNING or ERROR in front of it.
|
When problems are found, this will be printed on a line with WARNING or ERROR in front of it.
|
||||||
Most problems must be corrected by the user itself, however in some cases (like file permissions and missing public keys),
|
Most problems must be corrected by the user itself, however in some cases (like file permissions and missing public keys),
|
||||||
tinc will ask if it should fix the problem.
|
tinc will ask if it should fix the problem.
|
||||||
|
.It sign Op Ar filename
|
||||||
|
Sign a file with the local node's private key.
|
||||||
|
If no
|
||||||
|
.Ar filename
|
||||||
|
is given, the file is read from standard input.
|
||||||
|
The signed file is written to standard output.
|
||||||
|
.It verify Ar name Op Ar filename
|
||||||
|
Check the signature of a file against a node's public key.
|
||||||
|
The
|
||||||
|
.Ar name
|
||||||
|
of the node must be given,
|
||||||
|
or can be
|
||||||
|
.Li .
|
||||||
|
to check against the local node's public key, or
|
||||||
|
.Li *
|
||||||
|
to allow a signature from any node whose public key is known.
|
||||||
|
If no
|
||||||
|
.Ar filename
|
||||||
|
is given, the file is read from standard input.
|
||||||
|
If the verification is succesful,
|
||||||
|
a copy of the input with the signature removed is written to standard output,
|
||||||
|
and the exit code will be zero.
|
||||||
|
If the verification failed,
|
||||||
|
nothing will be written to standard output, and the exit code will be non-zero.
|
||||||
.El
|
.El
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
Examples of some commands:
|
Examples of some commands:
|
||||||
|
|
|
@ -2487,6 +2487,23 @@ When problems are found, this will be printed on a line with WARNING or ERROR in
|
||||||
Most problems must be corrected by the user itself, however in some cases (like file permissions and missing public keys),
|
Most problems must be corrected by the user itself, however in some cases (like file permissions and missing public keys),
|
||||||
tinc will ask if it should fix the problem.
|
tinc will ask if it should fix the problem.
|
||||||
|
|
||||||
|
@cindex sign
|
||||||
|
@item sign [@var{filename}]
|
||||||
|
Sign a file with the local node's private key.
|
||||||
|
If no @var{filename} is given, the file is read from standard input.
|
||||||
|
The signed file is written to standard output.
|
||||||
|
|
||||||
|
@cindex verify
|
||||||
|
@item verify @var{name} [@var{filename}]
|
||||||
|
|
||||||
|
Check the signature of a file against a node's public key.
|
||||||
|
The @var{name} of the node must be given,
|
||||||
|
or can be "." to check against the local node's public key,
|
||||||
|
or "*" to allow a signature from any node whose public key is known.
|
||||||
|
If no @var{filename} is given, the file is read from standard input.
|
||||||
|
If the verification is succesful, a copy of the input with the signature removed is written to standard output, and the exit code will be zero.
|
||||||
|
If the verification failed, nothing will be written to standard output, and the exit code will be non-zero.
|
||||||
|
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
@c ==================================================================
|
@c ==================================================================
|
||||||
|
|
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"
|
" join INVITATION Join a VPN using an INVITATION\n"
|
||||||
" network [NETNAME] List all known networks, or switch to the one named NETNAME.\n"
|
" network [NETNAME] List all known networks, or switch to the one named NETNAME.\n"
|
||||||
" fsck Check the configuration files for problems.\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");
|
"\n");
|
||||||
printf("Report bugs to tinc@tinc-vpn.org.\n");
|
printf("Report bugs to tinc@tinc-vpn.org.\n");
|
||||||
}
|
}
|
||||||
|
@ -1431,6 +1433,29 @@ char *get_my_name(bool verbose) {
|
||||||
return NULL;
|
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[] = {
|
const var_t variables[] = {
|
||||||
/* Server configuration */
|
/* Server configuration */
|
||||||
{"AddressFamily", VAR_SERVER},
|
{"AddressFamily", VAR_SERVER},
|
||||||
|
@ -2331,6 +2356,228 @@ static int cmd_fsck(int argc, char *argv[]) {
|
||||||
return fsck(orig_argv[0]);
|
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 {
|
static const struct {
|
||||||
const char *command;
|
const char *command;
|
||||||
int (*function)(int argc, char *argv[]);
|
int (*function)(int argc, char *argv[]);
|
||||||
|
@ -2375,6 +2622,8 @@ static const struct {
|
||||||
{"join", cmd_join},
|
{"join", cmd_join},
|
||||||
{"network", cmd_network},
|
{"network", cmd_network},
|
||||||
{"fsck", cmd_fsck},
|
{"fsck", cmd_fsck},
|
||||||
|
{"sign", cmd_sign},
|
||||||
|
{"verify", cmd_verify},
|
||||||
{NULL, NULL},
|
{NULL, NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue