mirror of
https://github.com/jedisct1/minisign.git
synced 2025-04-04 11:27:42 +03:00
Initial import
This commit is contained in:
parent
9d72e182bc
commit
bd9ffc828e
12 changed files with 1256 additions and 0 deletions
27
.gitignore
vendored
27
.gitignore
vendored
|
@ -0,0 +1,27 @@
|
||||||
|
*.cmake
|
||||||
|
*.dSYM
|
||||||
|
*.exp
|
||||||
|
*.gcda
|
||||||
|
*.gcno
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
*.log
|
||||||
|
*.mem
|
||||||
|
*.o
|
||||||
|
*.plist
|
||||||
|
*.scan
|
||||||
|
*.sdf
|
||||||
|
*.status
|
||||||
|
*.tar.*
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
.deps
|
||||||
|
.dirstamp
|
||||||
|
.done
|
||||||
|
.libs
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
Makefile
|
||||||
|
cmake_install.cmake
|
||||||
|
minisign
|
||||||
|
|
14
CMakeLists.txt
Normal file
14
CMakeLists.txt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
project(minisign C)
|
||||||
|
|
||||||
|
include(CheckLibraryExists)
|
||||||
|
|
||||||
|
find_library(LIB_SODIUM NAMES sodium REQUIRED)
|
||||||
|
|
||||||
|
add_executable(minisign
|
||||||
|
src/minisign.c src/base64.c src/helpers.c src/get_line.c)
|
||||||
|
|
||||||
|
target_link_libraries(minisign ${LIB_SODIUM})
|
||||||
|
|
||||||
|
install(TARGETS minisign DESTINATION bin)
|
16
LICENSE
Normal file
16
LICENSE
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015
|
||||||
|
* Frank Denis <j at pureftpd dot org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
122
README.md
Normal file
122
README.md
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
|
||||||
|
Minisign
|
||||||
|
========
|
||||||
|
|
||||||
|
Minisign is a dead simple tool to sign files and verify signatures.
|
||||||
|
|
||||||
|
Compilation / installation
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
* [libsodium](http://doc.libsodium.org/)
|
||||||
|
* cmake
|
||||||
|
|
||||||
|
Compilation:
|
||||||
|
|
||||||
|
$ cmake .
|
||||||
|
$ make
|
||||||
|
# make install
|
||||||
|
|
||||||
|
Creating a key pair
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
$ minisign -G
|
||||||
|
|
||||||
|
The public key is put into the `minisign.pub` file, and the secret key
|
||||||
|
into the `minisign.key` file.
|
||||||
|
|
||||||
|
Signing a file
|
||||||
|
--------------
|
||||||
|
|
||||||
|
$ minisign -S -m myfile.txt
|
||||||
|
|
||||||
|
Or to include a comment in the signature, that will be verified and
|
||||||
|
displayed when verifying the file:
|
||||||
|
|
||||||
|
$ minisign -S -m myfile.txt -t 'This comment will be signed as well'
|
||||||
|
|
||||||
|
The signature is put into `myfile.txt.minisig`.
|
||||||
|
|
||||||
|
Verifying a file
|
||||||
|
----------------
|
||||||
|
|
||||||
|
$ minisign -V -m myfile.txt
|
||||||
|
|
||||||
|
This requires the signature `myfile.txt.minisig` to be present in the same
|
||||||
|
directory.
|
||||||
|
|
||||||
|
Options list
|
||||||
|
------------
|
||||||
|
|
||||||
|
$ minisign -G -p pubkey -s seckey
|
||||||
|
|
||||||
|
$ minisign -S -s seckey -m file [-x sigfile] [-c untrusted_comment] [-t trusted_comment]
|
||||||
|
|
||||||
|
$ minisign -V -p pubkey -m file [-x sigfile] [-q]
|
||||||
|
|
||||||
|
Trusted comments
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Signature files include an untrusted comment line that can be freely
|
||||||
|
modified, even after signature creation.
|
||||||
|
|
||||||
|
They also include a second comment line, that cannot be modified
|
||||||
|
without the secret key.
|
||||||
|
|
||||||
|
Trusted comments can be used to add instructions or application-specific
|
||||||
|
metadata (timestamps, version numbers, resource identifiers).
|
||||||
|
|
||||||
|
Compatibility with OpenBSD signify
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
Signature written by minisign can be verified using OpenBSD's signify
|
||||||
|
tool: public key files and signature files are compatible.
|
||||||
|
|
||||||
|
However, minisign uses a slightly different format to store private keys.
|
||||||
|
|
||||||
|
minisign signatures include trusted comments in addition to untrusted
|
||||||
|
comments. Trusted comments are signed, thus verified, before being
|
||||||
|
displayed.
|
||||||
|
This adds two lines to the signature files, that signify silently ignores.
|
||||||
|
minisign has to be used in order to verify trusted comments.
|
||||||
|
|
||||||
|
Signature format
|
||||||
|
----------------
|
||||||
|
|
||||||
|
untrusted comment: <arbitrary text>
|
||||||
|
base64(<signature_algorithm> || <key_id> || <signature>)
|
||||||
|
trusted_comment: <arbitrary text>
|
||||||
|
base64(<global_signature>)
|
||||||
|
|
||||||
|
* `signature_algorithm`: `Ed`
|
||||||
|
* `key_id`: 8 random bytes, matching the public key
|
||||||
|
* `signature`: ed25519(<file data>)
|
||||||
|
* `global_signature`: ed25519(<signature> || <trusted_comment>)
|
||||||
|
|
||||||
|
Public key format
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
untrusted comment: <arbitrary text>
|
||||||
|
base64(<signature_algorithm> || <key_id> || <public_key>)
|
||||||
|
|
||||||
|
* `signature_algorithm`: `Ed`
|
||||||
|
* `key_id`: 8 random bytes
|
||||||
|
* `public_key`: Ed25519 public key
|
||||||
|
|
||||||
|
Secret key format
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
untrusted comment: <arbitrary text>
|
||||||
|
base64(<signature_algorithm> || <kdf_algorithm> || <cksum_algorithm> ||
|
||||||
|
<kdf_salt> || <kdf_opslimit> || <kdf_memlimit> || <keynum_sk>)
|
||||||
|
|
||||||
|
* `signature_algorithm`: `Ed`
|
||||||
|
* `kdf_algorithm`: `Sc`
|
||||||
|
* `cksum_algorithm`: `B2`
|
||||||
|
* `kdf_salt`: 32 random bytes
|
||||||
|
* `kdf_opslimit`: `crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE`
|
||||||
|
* `kdf_memlimit`: `crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE`
|
||||||
|
* `keynum_sk`: `<kdf_output> ^ (<key_id> || secret_key> || <checksum>)`
|
||||||
|
* `key_id`: 8 random bytes
|
||||||
|
* `secret_key`: Ed25519 secret key
|
||||||
|
* `checksum`: `Blake2b(<key_id> || <secret_key>)`, 32 bytes
|
113
src/base64.c
Normal file
113
src/base64.c
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
unsigned char *
|
||||||
|
b64_to_bin(unsigned char * const bin, const char *b64,
|
||||||
|
size_t bin_maxlen, size_t b64_len, size_t * const bin_len_p)
|
||||||
|
{
|
||||||
|
#define REV64_EOT 128U
|
||||||
|
#define REV64_NONE 64U
|
||||||
|
#define REV64_PAD '='
|
||||||
|
|
||||||
|
static const unsigned char rev64chars[256] = {
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE,
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE,
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, 62U, REV64_NONE, REV64_NONE, REV64_NONE, 63U, 52U, 53U, 54U, 55U, 56U, 57U, 58U, 59U, 60U, 61U, REV64_NONE, REV64_NONE, REV64_NONE, REV64_EOT, REV64_NONE, REV64_NONE, REV64_NONE, 0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U,
|
||||||
|
8U, 9U, 10U, 11U, 12U, 13U, 14U, 15U, 16U, 17U, 18U, 19U, 20U, 21U, 22U, 23U, 24U, 25U, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, 26U, 27U, 28U, 29U, 30U, 31U, 32U, 33U, 34U, 35U, 36U, 37U, 38U, 39U, 40U, 41U, 42U,
|
||||||
|
43U, 44U, 45U, 46U, 47U, 48U, 49U, 50U, 51U, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE,
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE,
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE,
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE,
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE,
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE,
|
||||||
|
REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE, REV64_NONE
|
||||||
|
};
|
||||||
|
const unsigned char *b64_u = (const unsigned char *) b64;
|
||||||
|
unsigned char *bin_w = bin;
|
||||||
|
unsigned char mask;
|
||||||
|
unsigned char t0, t1, t2, t3;
|
||||||
|
uint32_t t;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (b64_len % 4U != 0U || (i = b64_len / 4U) <= 0U || bin_maxlen < i * 3U -
|
||||||
|
(b64_u[b64_len - 1U] == REV64_PAD) - (b64_u[b64_len - 2U] == REV64_PAD)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
while (i-- > 0U) {
|
||||||
|
t0 = rev64chars[*b64++];
|
||||||
|
t1 = rev64chars[*b64++];
|
||||||
|
t2 = rev64chars[*b64++];
|
||||||
|
t3 = rev64chars[*b64++];
|
||||||
|
t = t3 | ((uint32_t) t2 << 6) | ((uint32_t) t1 << 12) |
|
||||||
|
((uint32_t) t0 << 18);
|
||||||
|
mask = t0 | t1 | t2 | t3;
|
||||||
|
if ((mask & (REV64_NONE | REV64_EOT)) != 0U) {
|
||||||
|
if ((mask & REV64_NONE) != 0U || i > 0U) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*bin_w++ = (unsigned char) (t >> 16);
|
||||||
|
*bin_w++ = (unsigned char) (t >> 8);
|
||||||
|
*bin_w++ = (unsigned char) t;
|
||||||
|
}
|
||||||
|
if ((mask & REV64_EOT) != 0U) {
|
||||||
|
if (((t0 | t1) & REV64_EOT) != 0U || t3 != REV64_EOT) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*bin_w++ = (unsigned char) (t >> 16);
|
||||||
|
if (t2 != REV64_EOT) {
|
||||||
|
*bin_w++ = (unsigned char) (t >> 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bin_len_p != NULL) {
|
||||||
|
*bin_len_p = (size_t) (bin_w - bin);
|
||||||
|
}
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *bin_to_b64(char * const b64, const unsigned char *bin,
|
||||||
|
size_t b64_maxlen, size_t bin_len)
|
||||||
|
{
|
||||||
|
#define B64_PAD '='
|
||||||
|
|
||||||
|
static const char b64chars[64] =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
char *b64_w = b64;
|
||||||
|
|
||||||
|
if (b64_maxlen < (((bin_len + 2U) / 3U) * 4U + 1U)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
while (bin_len > (size_t) 2U) {
|
||||||
|
const unsigned char t0 = (unsigned char) *bin++;
|
||||||
|
const unsigned char t1 = (unsigned char) *bin++;
|
||||||
|
const unsigned char t2 = (unsigned char) *bin++;
|
||||||
|
|
||||||
|
*b64_w++ = b64chars[(t0 >> 2) & 63];
|
||||||
|
*b64_w++ = b64chars[((t0 << 4) & 48) | ((t1 >> 4) & 15)];
|
||||||
|
*b64_w++ = b64chars[((t1 << 2) & 60) | ((t2 >> 6) & 3)];
|
||||||
|
*b64_w++ = b64chars[t2 & 63];
|
||||||
|
bin_len -= (size_t) 3U;
|
||||||
|
}
|
||||||
|
if (bin_len > (size_t) 0U) {
|
||||||
|
const unsigned char t0 = (unsigned char) bin[0];
|
||||||
|
|
||||||
|
*b64_w++ = b64chars[(t0 >> 2) & 63];
|
||||||
|
if (bin_len == 1U) {
|
||||||
|
*b64_w++ = b64chars[((t0 << 4) & 48)];
|
||||||
|
*b64_w++ = B64_PAD;
|
||||||
|
} else {
|
||||||
|
const unsigned char t1 = (unsigned char) bin[1];
|
||||||
|
|
||||||
|
*b64_w++ = b64chars[((t0 << 4) & 48) | ((t1 >> 4) & 15)];
|
||||||
|
*b64_w++ = b64chars[((t1 << 2) & 60)];
|
||||||
|
}
|
||||||
|
*b64_w++ = B64_PAD;
|
||||||
|
}
|
||||||
|
*b64_w = 0;
|
||||||
|
|
||||||
|
return b64;
|
||||||
|
}
|
17
src/base64.h
Normal file
17
src/base64.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
#ifndef BASE64_H
|
||||||
|
#define BASE64_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
unsigned char *b64_to_bin(unsigned char * const bin, const char *b64,
|
||||||
|
size_t bin_maxlen, size_t b64_len,
|
||||||
|
size_t * const bin_len_p);
|
||||||
|
|
||||||
|
char *bin_to_b64(char * const b64, const unsigned char *bin,
|
||||||
|
size_t b64_maxlen, size_t bin_len);
|
||||||
|
|
||||||
|
#define B64_MAX_LEN_FROM_BIN_LEN(X) (((X) + 2) / 3 * 4 + 1)
|
||||||
|
#define BIN_MAX_LEN_FROM_B64_LEN(X) ((X) / 4 * 3)
|
||||||
|
|
||||||
|
#endif
|
88
src/get_line.c
Normal file
88
src/get_line.c
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "get_line.h"
|
||||||
|
#include "helpers.h"
|
||||||
|
|
||||||
|
#ifndef TCSAFLUSH
|
||||||
|
# define TCSAFLUSH 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef VERIFY_ONLY
|
||||||
|
|
||||||
|
static void
|
||||||
|
disable_echo(void)
|
||||||
|
{
|
||||||
|
struct termios p;
|
||||||
|
|
||||||
|
fpurge(stdin);
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
if (!isatty(0) || tcgetattr(0, &p) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p.c_lflag &= ~ECHO;
|
||||||
|
tcsetattr(0, TCSAFLUSH, &p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
enable_echo(void)
|
||||||
|
{
|
||||||
|
struct termios p;
|
||||||
|
|
||||||
|
fpurge(stdin);
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
if (!isatty(0) || tcgetattr(0, &p) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p.c_lflag |= ECHO;
|
||||||
|
tcsetattr(0, TCSAFLUSH, &p);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
get_line(char *line, size_t max_len, const char *prompt)
|
||||||
|
{
|
||||||
|
memset(line, 0, max_len);
|
||||||
|
if (max_len < 2U || max_len > INT_MAX) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
xfprintf(stderr, "%s", prompt);
|
||||||
|
fflush(stderr);
|
||||||
|
if (fgets(line, (int) max_len, stdin) == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
trim(line);
|
||||||
|
if (strlen(line) >= max_len) {
|
||||||
|
fprintf(stderr, "(truncated to %u characters)\n", (int) max_len);
|
||||||
|
} else if (*line == 0) {
|
||||||
|
fprintf(stderr, "(empty)\n");
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
get_password(char *pwd, size_t max_len, const char *prompt)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
disable_echo();
|
||||||
|
ret = get_line(pwd, max_len, prompt);
|
||||||
|
enable_echo();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
8
src/get_line.h
Normal file
8
src/get_line.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
#ifndef GET_LINE_H
|
||||||
|
#define GET_LINE_H
|
||||||
|
|
||||||
|
int get_line(char *line, size_t max_len, const char *prompt);
|
||||||
|
int get_password(char *pwd, size_t max_len, const char *prompt);
|
||||||
|
|
||||||
|
#endif
|
184
src/helpers.c
Normal file
184
src/helpers.c
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
|
||||||
|
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
|
||||||
|
# include <sys/types.h>
|
||||||
|
# include <sys/fcntl.h>
|
||||||
|
# include <sys/stat.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
#include "base64.h"
|
||||||
|
#include "helpers.h"
|
||||||
|
|
||||||
|
uint64_t
|
||||||
|
le64_load(const unsigned char *p)
|
||||||
|
{
|
||||||
|
return ((uint64_t)(p[0])) | ((uint64_t)(p[1]) << 8) |
|
||||||
|
((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) |
|
||||||
|
((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40) |
|
||||||
|
((uint64_t)(p[6]) << 48) | ((uint64_t)(p[7]) << 56);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
le64_store(unsigned char *p, uint64_t x)
|
||||||
|
{
|
||||||
|
p[0] = (unsigned char) x;
|
||||||
|
p[1] = (unsigned char) (x >> 8);
|
||||||
|
p[2] = (unsigned char) (x >> 16);
|
||||||
|
p[3] = (unsigned char) (x >> 24);
|
||||||
|
p[4] = (unsigned char) (x >> 32);
|
||||||
|
p[5] = (unsigned char) (x >> 40);
|
||||||
|
p[6] = (unsigned char) (x >> 48);
|
||||||
|
p[7] = (unsigned char) (x >> 56);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
exit_err(const char *msg)
|
||||||
|
{
|
||||||
|
perror(msg == NULL ? "" : msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
exit_msg(const char *msg)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s\n", msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
xmalloc(size_t size)
|
||||||
|
{
|
||||||
|
void *pnt;
|
||||||
|
|
||||||
|
if ((pnt = malloc(size)) == NULL) {
|
||||||
|
exit_err("malloc()");
|
||||||
|
}
|
||||||
|
return pnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
xsodium_malloc(size_t size)
|
||||||
|
{
|
||||||
|
void *pnt;
|
||||||
|
|
||||||
|
if ((pnt = sodium_malloc(size)) == NULL) {
|
||||||
|
exit_err("sodium_malloc()");
|
||||||
|
}
|
||||||
|
return pnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
xor_buf(unsigned char *dst, const unsigned char *src, size_t len)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = (size_t) 0U; i < len; i++) {
|
||||||
|
dst[i] ^= src[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
xfprintf(FILE *fp, const char *format, ...)
|
||||||
|
{
|
||||||
|
char *out;
|
||||||
|
size_t out_maxlen = 4096U;
|
||||||
|
int len;
|
||||||
|
va_list va;
|
||||||
|
|
||||||
|
va_start(va, format);
|
||||||
|
out = xsodium_malloc(out_maxlen);
|
||||||
|
len = vsnprintf(out, out_maxlen, format, va);
|
||||||
|
if (len < 0 || len >= (int) out_maxlen) {
|
||||||
|
va_end(va);
|
||||||
|
exit_msg("xfprintf() overflow");
|
||||||
|
}
|
||||||
|
va_end(va);
|
||||||
|
if (fwrite(out, (size_t) len, 1U, fp) != 1U) {
|
||||||
|
sodium_free(out);
|
||||||
|
va_end(va);
|
||||||
|
exit_err("fwrite()");
|
||||||
|
}
|
||||||
|
sodium_free(out);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
xfput_b64(FILE *fp, const unsigned char *bin, size_t bin_len)
|
||||||
|
{
|
||||||
|
const size_t b64_maxlen = (bin_len + 2) * 4 / 3 + 1;
|
||||||
|
char *b64;
|
||||||
|
|
||||||
|
b64 = xsodium_malloc(b64_maxlen);
|
||||||
|
if (bin_to_b64(b64, bin, b64_maxlen, bin_len) == NULL) {
|
||||||
|
sodium_free(b64);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
xfprintf(fp, "%s\n", b64);
|
||||||
|
sodium_free(b64);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
xfclose(FILE *fp)
|
||||||
|
{
|
||||||
|
if (fp == NULL) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
if (fclose(fp) != 0) {
|
||||||
|
exit_err("fclose()");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
trim(char *str)
|
||||||
|
{
|
||||||
|
size_t i = strlen(str);
|
||||||
|
|
||||||
|
while (i-- > (size_t) 0U) {
|
||||||
|
if (str[i] == '\n' || str[i] == '\r') {
|
||||||
|
str[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
file_basename(const char *file)
|
||||||
|
{
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
if ((ptr = strrchr(file, '/')) != NULL) {
|
||||||
|
return ptr + 1;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
if ((ptr = strrchr(file, '\\')) != NULL) {
|
||||||
|
return ptr + 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *
|
||||||
|
fopen_create_useronly(const char *file)
|
||||||
|
{
|
||||||
|
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
if ((fd = open(file, O_CREAT | O_TRUNC | O_WRONLY,
|
||||||
|
(mode_t) 0600)) == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return fdopen(fd, "w");
|
||||||
|
#else
|
||||||
|
return fopen(file, "w");
|
||||||
|
#endif
|
||||||
|
}
|
34
src/helpers.h
Normal file
34
src/helpers.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
#ifndef HELPERS_H
|
||||||
|
#define HELPERS_H 1
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
uint64_t le64_load(const unsigned char *p);
|
||||||
|
|
||||||
|
void le64_store(unsigned char *p, uint64_t x);
|
||||||
|
|
||||||
|
void exit_err(const char *msg) __attribute__((noreturn));
|
||||||
|
|
||||||
|
void exit_msg(const char *msg) __attribute__((noreturn));
|
||||||
|
|
||||||
|
void * xmalloc(size_t size);
|
||||||
|
|
||||||
|
void * xsodium_malloc(size_t size);
|
||||||
|
|
||||||
|
void xor_buf(unsigned char *dst, const unsigned char *src, size_t len);
|
||||||
|
|
||||||
|
int xfput_b64(FILE *fp, const unsigned char *bin, size_t bin_len);
|
||||||
|
|
||||||
|
int xfprintf(FILE *fp, const char *format, ...) __attribute__((format(printf, 2, 3)));
|
||||||
|
|
||||||
|
int xfclose(FILE *fp);
|
||||||
|
|
||||||
|
void trim(char *str);
|
||||||
|
|
||||||
|
const char * file_basename(const char *file);
|
||||||
|
|
||||||
|
FILE * fopen_create_useronly(const char *file);
|
||||||
|
|
||||||
|
#endif
|
575
src/minisign.c
Normal file
575
src/minisign.c
Normal file
|
@ -0,0 +1,575 @@
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
#include "base64.h"
|
||||||
|
#include "get_line.h"
|
||||||
|
#include "helpers.h"
|
||||||
|
#include "minisign.h"
|
||||||
|
|
||||||
|
#ifndef VERIFY_ONLY
|
||||||
|
static const char *getopt_options = "GSVhc:m:p:qs:t:x:";
|
||||||
|
#else
|
||||||
|
static const char *getopt_options = "Vhm:p:qx:";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void usage(void) __attribute__((noreturn));
|
||||||
|
|
||||||
|
static void
|
||||||
|
usage(void)
|
||||||
|
{
|
||||||
|
puts("Usage:\n"
|
||||||
|
#ifndef VERIFY_ONLY
|
||||||
|
"minisign -G -p pubkey -s seckey\n"
|
||||||
|
"minisign -S -s seckey -m file [-x sigfile] [-c untrusted_comment] [-t trusted_comment]\n"
|
||||||
|
#endif
|
||||||
|
"minisign -V -p pubkey -m file [-x sigfile] [-q]\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char *
|
||||||
|
message_load(size_t *message_len, const char *message_file)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
unsigned char *message;
|
||||||
|
off_t message_len_;
|
||||||
|
|
||||||
|
if ((fp = fopen(message_file, "r")) == NULL ||
|
||||||
|
fseeko(fp, 0 , SEEK_END) != 0 ||
|
||||||
|
(message_len_ = ftello(fp)) == (off_t) -1) {
|
||||||
|
exit_err(message_file);
|
||||||
|
}
|
||||||
|
if (message_len_ > (off_t) 1L << 30) {
|
||||||
|
exit_msg("Data has to be smaller than 1 Gb");
|
||||||
|
}
|
||||||
|
if ((uintmax_t) message_len_ > (uintmax_t) SIZE_MAX ||
|
||||||
|
message_len_ < (off_t) 0) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
message = xmalloc((*message_len = (size_t) message_len_));
|
||||||
|
rewind(fp);
|
||||||
|
if (fread(message, *message_len, (size_t) 1U, fp) != 1U) {
|
||||||
|
exit_err(message_file);
|
||||||
|
}
|
||||||
|
xfclose(fp);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SigStruct *
|
||||||
|
sig_load(const char *sig_file, unsigned char global_sig[crypto_sign_BYTES],
|
||||||
|
char trusted_comment[TRUSTEDCOMMENTMAXBYTES],
|
||||||
|
size_t trusted_comment_maxlen)
|
||||||
|
{
|
||||||
|
char comment[COMMENTMAXBYTES];
|
||||||
|
SigStruct *sig_struct;
|
||||||
|
FILE *fp;
|
||||||
|
char *global_sig_s;
|
||||||
|
char *sig_s;
|
||||||
|
size_t global_sig_len;
|
||||||
|
size_t global_sig_s_size;
|
||||||
|
size_t sig_s_size;
|
||||||
|
size_t sig_struct_len;
|
||||||
|
|
||||||
|
if ((fp = fopen(sig_file, "r")) == NULL) {
|
||||||
|
exit_err(sig_file);
|
||||||
|
}
|
||||||
|
if (fgets(comment, (int) sizeof comment, fp) == NULL) {
|
||||||
|
exit_err(sig_file);
|
||||||
|
}
|
||||||
|
if (strncmp(comment, COMMENT_PREFIX, (sizeof COMMENT_PREFIX) - 1U) != 0) {
|
||||||
|
exit_msg("Untrusted signature comment should start with "
|
||||||
|
"\"" COMMENT_PREFIX "\"");
|
||||||
|
}
|
||||||
|
sig_s_size = B64_MAX_LEN_FROM_BIN_LEN(sizeof *sig_struct) + 1U;
|
||||||
|
sig_s = xmalloc(sig_s_size);
|
||||||
|
if (fgets(sig_s, (int) sig_s_size, fp) == NULL) {
|
||||||
|
exit_err(sig_file);
|
||||||
|
}
|
||||||
|
trim(sig_s);
|
||||||
|
if (fgets(trusted_comment, (int) trusted_comment_maxlen, fp) == NULL) {
|
||||||
|
exit_err(sig_file);
|
||||||
|
}
|
||||||
|
if (strncmp(trusted_comment, TRUSTED_COMMENT_PREFIX,
|
||||||
|
(sizeof TRUSTED_COMMENT_PREFIX) - 1U) != 0) {
|
||||||
|
exit_msg("Trusted signature comment should start with "
|
||||||
|
"\"" TRUSTED_COMMENT_PREFIX "\"");
|
||||||
|
}
|
||||||
|
memmove(trusted_comment,
|
||||||
|
trusted_comment + sizeof TRUSTED_COMMENT_PREFIX - 1U,
|
||||||
|
strlen(trusted_comment + sizeof TRUSTED_COMMENT_PREFIX - 1U) + 1U);
|
||||||
|
trim(trusted_comment);
|
||||||
|
global_sig_s_size = B64_MAX_LEN_FROM_BIN_LEN(crypto_sign_BYTES) + 1U;
|
||||||
|
global_sig_s = xmalloc(global_sig_s_size);
|
||||||
|
if (fgets(global_sig_s, (int) global_sig_s_size, fp) == NULL) {
|
||||||
|
exit_err(sig_file);
|
||||||
|
}
|
||||||
|
trim(global_sig_s);
|
||||||
|
xfclose(fp);
|
||||||
|
|
||||||
|
sig_struct = xmalloc(sizeof *sig_struct);
|
||||||
|
if (b64_to_bin((unsigned char *) (void *) sig_struct, sig_s,
|
||||||
|
sizeof *sig_struct, strlen(sig_s),
|
||||||
|
&sig_struct_len) == NULL ||
|
||||||
|
sig_struct_len != sizeof *sig_struct) {
|
||||||
|
exit_msg("base64 conversion failed");
|
||||||
|
}
|
||||||
|
free(sig_s);
|
||||||
|
if (memcmp(sig_struct->sig_alg, SIGALG, sizeof sig_struct->sig_alg) != 0) {
|
||||||
|
exit_msg("Unsupported signature algorithm");
|
||||||
|
}
|
||||||
|
if (b64_to_bin(global_sig, global_sig_s, crypto_sign_BYTES,
|
||||||
|
strlen(global_sig_s), &global_sig_len) == NULL ||
|
||||||
|
global_sig_len != crypto_sign_BYTES) {
|
||||||
|
exit_msg("base64 conversion failed");
|
||||||
|
}
|
||||||
|
free(global_sig_s);
|
||||||
|
|
||||||
|
return sig_struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PubkeyStruct *
|
||||||
|
pubkey_load(const char *pk_file)
|
||||||
|
{
|
||||||
|
char pk_comment[COMMENTMAXBYTES];
|
||||||
|
PubkeyStruct *pubkey_struct;
|
||||||
|
FILE *fp;
|
||||||
|
char *pubkey_s;
|
||||||
|
size_t pubkey_s_size;
|
||||||
|
size_t pubkey_struct_len;
|
||||||
|
|
||||||
|
if ((fp = fopen(pk_file, "r")) == NULL) {
|
||||||
|
exit_err(pk_file);
|
||||||
|
}
|
||||||
|
if (fgets(pk_comment, (int) sizeof pk_comment, fp) == NULL) {
|
||||||
|
exit_err(pk_file);
|
||||||
|
}
|
||||||
|
pubkey_s_size = B64_MAX_LEN_FROM_BIN_LEN(sizeof *pubkey_struct) + 1U;
|
||||||
|
pubkey_s = xmalloc(pubkey_s_size);
|
||||||
|
if (fgets(pubkey_s, (int) pubkey_s_size, fp) == NULL) {
|
||||||
|
exit_err(pk_file);
|
||||||
|
}
|
||||||
|
trim(pubkey_s);
|
||||||
|
xfclose(fp);
|
||||||
|
pubkey_struct = xmalloc(sizeof *pubkey_struct);
|
||||||
|
if (b64_to_bin((unsigned char *) (void *) pubkey_struct, pubkey_s,
|
||||||
|
sizeof *pubkey_struct, strlen(pubkey_s),
|
||||||
|
&pubkey_struct_len) == NULL ||
|
||||||
|
pubkey_struct_len != sizeof *pubkey_struct) {
|
||||||
|
exit_msg("base64 conversion failed");
|
||||||
|
}
|
||||||
|
free(pubkey_s);
|
||||||
|
if (memcmp(pubkey_struct->sig_alg, SIGALG,
|
||||||
|
sizeof pubkey_struct->sig_alg) != 0) {
|
||||||
|
exit_msg("Unsupported signature algorithm");
|
||||||
|
}
|
||||||
|
return pubkey_struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
seckey_chk(unsigned char chk[crypto_generichash_BYTES],
|
||||||
|
const SeckeyStruct *seckey_struct)
|
||||||
|
{
|
||||||
|
crypto_generichash_state hs;
|
||||||
|
|
||||||
|
crypto_generichash_init(&hs, NULL, 0U, sizeof seckey_struct->keynum_sk.chk);
|
||||||
|
crypto_generichash_update(&hs, seckey_struct->keynum_sk.keynum,
|
||||||
|
sizeof seckey_struct->keynum_sk.keynum);
|
||||||
|
crypto_generichash_update(&hs, seckey_struct->keynum_sk.sk,
|
||||||
|
sizeof seckey_struct->keynum_sk.sk);
|
||||||
|
crypto_generichash_final(&hs, chk, sizeof seckey_struct->keynum_sk.chk);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef VERIFY_ONLY
|
||||||
|
static SeckeyStruct *
|
||||||
|
seckey_load(const char *sk_file)
|
||||||
|
{
|
||||||
|
char sk_comment[COMMENTMAXBYTES];
|
||||||
|
unsigned char chk[crypto_generichash_BYTES];
|
||||||
|
SeckeyStruct *seckey_struct;
|
||||||
|
FILE *fp;
|
||||||
|
char *pwd = xsodium_malloc(PASSWORDMAXBYTES);
|
||||||
|
char *seckey_s;
|
||||||
|
unsigned char *stream;
|
||||||
|
size_t seckey_s_size;
|
||||||
|
size_t seckey_struct_len;
|
||||||
|
|
||||||
|
if ((fp = fopen(sk_file, "r")) == NULL) {
|
||||||
|
exit_err(sk_file);
|
||||||
|
}
|
||||||
|
if (fgets(sk_comment, (int) sizeof sk_comment, fp) == NULL) {
|
||||||
|
exit_err(sk_file);
|
||||||
|
}
|
||||||
|
sodium_memzero(sk_comment, sizeof sk_comment);
|
||||||
|
seckey_s_size = B64_MAX_LEN_FROM_BIN_LEN(sizeof *seckey_struct) + 1U;
|
||||||
|
seckey_s = xsodium_malloc(seckey_s_size);
|
||||||
|
seckey_struct = xsodium_malloc(sizeof *seckey_struct);
|
||||||
|
if (fgets(seckey_s, (int) seckey_s_size, fp) == NULL) {
|
||||||
|
exit_err(sk_file);
|
||||||
|
}
|
||||||
|
trim(seckey_s);
|
||||||
|
xfclose(fp);
|
||||||
|
if (b64_to_bin((unsigned char *) (void *) seckey_struct, seckey_s,
|
||||||
|
sizeof *seckey_struct, strlen(seckey_s),
|
||||||
|
&seckey_struct_len) == NULL ||
|
||||||
|
seckey_struct_len != sizeof *seckey_struct) {
|
||||||
|
exit_msg("base64 conversion failed");
|
||||||
|
}
|
||||||
|
sodium_free(seckey_s);
|
||||||
|
if (memcmp(seckey_struct->sig_alg, SIGALG,
|
||||||
|
sizeof seckey_struct->sig_alg) != 0) {
|
||||||
|
exit_msg("Unsupported signature algorithm");
|
||||||
|
}
|
||||||
|
if (memcmp(seckey_struct->kdf_alg, KDFALG,
|
||||||
|
sizeof seckey_struct->kdf_alg) != 0) {
|
||||||
|
exit_msg("Unsupported key derivation function");
|
||||||
|
}
|
||||||
|
if (memcmp(seckey_struct->chk_alg, CHKALG,
|
||||||
|
sizeof seckey_struct->chk_alg) != 0) {
|
||||||
|
exit_msg("Unsupported checksum function");
|
||||||
|
}
|
||||||
|
if (get_password(pwd, PASSWORDMAXBYTES, "Password: ") != 0) {
|
||||||
|
exit_msg("get_password()");
|
||||||
|
}
|
||||||
|
stream = xsodium_malloc(sizeof seckey_struct->keynum_sk);
|
||||||
|
if (crypto_pwhash_scryptsalsa208sha256
|
||||||
|
(stream, sizeof seckey_struct->keynum_sk, pwd, strlen(pwd),
|
||||||
|
seckey_struct->kdf_salt,
|
||||||
|
le64_load(seckey_struct->kdf_opslimit_le),
|
||||||
|
le64_load(seckey_struct->kdf_memlimit_le)) != 0) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
xor_buf((unsigned char *) (void *) &seckey_struct->keynum_sk, stream,
|
||||||
|
sizeof seckey_struct->keynum_sk);
|
||||||
|
sodium_free(stream);
|
||||||
|
seckey_chk(chk, seckey_struct);
|
||||||
|
if (memcmp(chk, seckey_struct->keynum_sk.chk, sizeof chk) != 0) {
|
||||||
|
exit_msg("Wrong password for that key");
|
||||||
|
}
|
||||||
|
sodium_memzero(chk, sizeof chk);
|
||||||
|
|
||||||
|
return seckey_struct;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int
|
||||||
|
verify(const char *pk_file, const char *message_file, const char *sig_file,
|
||||||
|
int quiet)
|
||||||
|
{
|
||||||
|
char trusted_comment[TRUSTEDCOMMENTMAXBYTES];
|
||||||
|
unsigned char global_sig[crypto_sign_BYTES];
|
||||||
|
unsigned char *sig_and_trusted_comment;
|
||||||
|
SigStruct *sig_struct;
|
||||||
|
PubkeyStruct *pubkey_struct;
|
||||||
|
unsigned char *message;
|
||||||
|
size_t message_len;
|
||||||
|
size_t trusted_comment_len;
|
||||||
|
|
||||||
|
pubkey_struct = pubkey_load(pk_file);
|
||||||
|
message = message_load(&message_len, message_file);
|
||||||
|
sig_struct = sig_load(sig_file, global_sig,
|
||||||
|
trusted_comment, sizeof trusted_comment);
|
||||||
|
if (memcmp(sig_struct->keynum, pubkey_struct->keynum_pk.keynum,
|
||||||
|
sizeof sig_struct->keynum) != 0) {
|
||||||
|
fprintf(stderr, "Signature key id in %s is %" PRIX64 "\n"
|
||||||
|
"but the key id in %s is %" PRIX64 "\n",
|
||||||
|
sig_file, le64_load(sig_struct->keynum),
|
||||||
|
pk_file, le64_load(pubkey_struct->keynum_pk.keynum));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (crypto_sign_verify_detached(sig_struct->sig, message, message_len,
|
||||||
|
pubkey_struct->keynum_pk.pk) != 0) {
|
||||||
|
if (quiet == 0) {
|
||||||
|
puts("Signature verification failed");
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
free(message);
|
||||||
|
|
||||||
|
trusted_comment_len = strlen(trusted_comment);
|
||||||
|
sig_and_trusted_comment = xmalloc((sizeof sig_struct->sig) +
|
||||||
|
trusted_comment_len);
|
||||||
|
memcpy(sig_and_trusted_comment, sig_struct->sig, sizeof sig_struct->sig);
|
||||||
|
memcpy(sig_and_trusted_comment + sizeof sig_struct->sig, trusted_comment,
|
||||||
|
trusted_comment_len);
|
||||||
|
if (crypto_sign_verify_detached(global_sig, sig_and_trusted_comment,
|
||||||
|
(sizeof sig_struct->sig) + trusted_comment_len,
|
||||||
|
pubkey_struct->keynum_pk.pk) != 0) {
|
||||||
|
if (quiet == 0) {
|
||||||
|
puts("Comment signature verification failed");
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
free(sig_and_trusted_comment);
|
||||||
|
free(pubkey_struct);
|
||||||
|
free(sig_struct);
|
||||||
|
if (quiet == 0) {
|
||||||
|
puts("Signature and comment signature verified");
|
||||||
|
printf("Trusted comment: %s\n", trusted_comment);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef VERIFY_ONLY
|
||||||
|
static int
|
||||||
|
sign(const char *sk_file, const char *message_file, const char *sig_file,
|
||||||
|
const char *comment, const char *trusted_comment)
|
||||||
|
{
|
||||||
|
unsigned char global_sig[crypto_sign_BYTES];
|
||||||
|
SigStruct sig_struct;
|
||||||
|
FILE *fp;
|
||||||
|
SeckeyStruct *seckey_struct;
|
||||||
|
unsigned char *message;
|
||||||
|
unsigned char *sig_and_trusted_comment;
|
||||||
|
size_t comment_len;
|
||||||
|
size_t trusted_comment_len;
|
||||||
|
size_t message_len;
|
||||||
|
|
||||||
|
seckey_struct = seckey_load(sk_file);
|
||||||
|
message = message_load(&message_len, message_file);
|
||||||
|
memcpy(sig_struct.sig_alg, SIGALG, sizeof sig_struct.sig_alg);
|
||||||
|
memcpy(sig_struct.keynum, seckey_struct->keynum_sk.keynum,
|
||||||
|
sizeof sig_struct.keynum);
|
||||||
|
crypto_sign_detached(sig_struct.sig, NULL, message, message_len,
|
||||||
|
seckey_struct->keynum_sk.sk);
|
||||||
|
free(message);
|
||||||
|
if ((fp = fopen(sig_file, "w")) == NULL) {
|
||||||
|
exit_err(sig_file);
|
||||||
|
}
|
||||||
|
comment_len = strlen(comment);
|
||||||
|
assert(strrchr(comment, '\r') == NULL && strrchr(comment, '\n') == NULL);
|
||||||
|
assert(COMMENTMAXBYTES > sizeof COMMENT_PREFIX);
|
||||||
|
if (comment_len >= COMMENTMAXBYTES - sizeof COMMENT_PREFIX) {
|
||||||
|
fprintf(stderr, "Warning: comment too long. "
|
||||||
|
"This breaks compatibility with signify.\n");
|
||||||
|
}
|
||||||
|
xfprintf(fp, "%s%s\n", COMMENT_PREFIX, comment);
|
||||||
|
xfput_b64(fp, (unsigned char *) (void *) &sig_struct, sizeof sig_struct);
|
||||||
|
|
||||||
|
xfprintf(fp, "%s%s\n", TRUSTED_COMMENT_PREFIX, trusted_comment);
|
||||||
|
trusted_comment_len = strlen(trusted_comment);
|
||||||
|
assert(strrchr(trusted_comment, '\r') == NULL &&
|
||||||
|
strrchr(trusted_comment, '\n') == NULL);
|
||||||
|
if (trusted_comment_len >=
|
||||||
|
TRUSTEDCOMMENTMAXBYTES - sizeof TRUSTED_COMMENT_PREFIX) {
|
||||||
|
exit_msg("Trusted comment too long");
|
||||||
|
}
|
||||||
|
sig_and_trusted_comment = xmalloc((sizeof sig_struct.sig) +
|
||||||
|
trusted_comment_len);
|
||||||
|
memcpy(sig_and_trusted_comment, sig_struct.sig, sizeof sig_struct.sig);
|
||||||
|
memcpy(sig_and_trusted_comment + sizeof sig_struct.sig, trusted_comment,
|
||||||
|
trusted_comment_len);
|
||||||
|
crypto_sign_detached(global_sig, NULL, sig_and_trusted_comment,
|
||||||
|
(sizeof sig_struct.sig) + trusted_comment_len,
|
||||||
|
seckey_struct->keynum_sk.sk);
|
||||||
|
sodium_free(seckey_struct);
|
||||||
|
xfput_b64(fp, (unsigned char *) (void *) &global_sig, sizeof global_sig);
|
||||||
|
free(sig_and_trusted_comment);
|
||||||
|
xfclose(fp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
generate(const char *pk_file, const char *sk_file)
|
||||||
|
{
|
||||||
|
char *pwd = xsodium_malloc(PASSWORDMAXBYTES);
|
||||||
|
char *pwd2 = xsodium_malloc(PASSWORDMAXBYTES);
|
||||||
|
SeckeyStruct *seckey_struct = xsodium_malloc(sizeof(SeckeyStruct));
|
||||||
|
PubkeyStruct *pubkey_struct = xsodium_malloc(sizeof(PubkeyStruct));
|
||||||
|
unsigned char *stream ;
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
randombytes_buf(seckey_struct->keynum_sk.keynum,
|
||||||
|
sizeof seckey_struct->keynum_sk.keynum);
|
||||||
|
crypto_sign_keypair(pubkey_struct->keynum_pk.pk,
|
||||||
|
seckey_struct->keynum_sk.sk);
|
||||||
|
memcpy(seckey_struct->sig_alg, SIGALG, sizeof seckey_struct->sig_alg);
|
||||||
|
memcpy(seckey_struct->kdf_alg, KDFALG, sizeof seckey_struct->kdf_alg);
|
||||||
|
memcpy(seckey_struct->chk_alg, CHKALG, sizeof seckey_struct->chk_alg);
|
||||||
|
randombytes_buf(seckey_struct->kdf_salt, sizeof seckey_struct->kdf_salt);
|
||||||
|
le64_store(seckey_struct->kdf_opslimit_le,
|
||||||
|
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE);
|
||||||
|
le64_store(seckey_struct->kdf_memlimit_le,
|
||||||
|
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE);
|
||||||
|
seckey_chk(seckey_struct->keynum_sk.chk, seckey_struct);
|
||||||
|
memcpy(pubkey_struct->keynum_pk.keynum, seckey_struct->keynum_sk.keynum,
|
||||||
|
sizeof pubkey_struct->keynum_pk.keynum);
|
||||||
|
memcpy(pubkey_struct->sig_alg, SIGALG, sizeof pubkey_struct->sig_alg);
|
||||||
|
|
||||||
|
if (get_password(pwd, PASSWORDMAXBYTES, "Password: ") != 0 ||
|
||||||
|
get_password(pwd2, PASSWORDMAXBYTES, "Password (one more time): ") != 0) {
|
||||||
|
exit_msg("get_password()");
|
||||||
|
}
|
||||||
|
if (strcmp(pwd, pwd2) != 0) {
|
||||||
|
exit_msg("Passwords don't match");
|
||||||
|
}
|
||||||
|
stream = xsodium_malloc(sizeof seckey_struct->keynum_sk);
|
||||||
|
if (crypto_pwhash_scryptsalsa208sha256
|
||||||
|
(stream, sizeof seckey_struct->keynum_sk, pwd, strlen(pwd),
|
||||||
|
seckey_struct->kdf_salt,
|
||||||
|
le64_load(seckey_struct->kdf_opslimit_le),
|
||||||
|
le64_load(seckey_struct->kdf_memlimit_le)) != 0) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
sodium_free(pwd);
|
||||||
|
sodium_free(pwd2);
|
||||||
|
xor_buf((unsigned char *) (void *) &seckey_struct->keynum_sk, stream,
|
||||||
|
sizeof seckey_struct->keynum_sk);
|
||||||
|
sodium_free(stream);
|
||||||
|
|
||||||
|
if ((fp = fopen_create_useronly(sk_file)) == NULL) {
|
||||||
|
exit_err(sk_file);
|
||||||
|
}
|
||||||
|
xfprintf(fp, "untrusted comment: minisign encrypted secret key\n");
|
||||||
|
xfput_b64(fp, (unsigned char *) (void *) seckey_struct,
|
||||||
|
sizeof *seckey_struct);
|
||||||
|
xfclose(fp);
|
||||||
|
sodium_free(seckey_struct);
|
||||||
|
|
||||||
|
if ((fp = fopen(pk_file, "w")) == NULL) {
|
||||||
|
exit_err(pk_file);
|
||||||
|
}
|
||||||
|
xfprintf(fp, "untrusted comment: minisign public key %" PRIX64 "\n",
|
||||||
|
le64_load(pubkey_struct->keynum_pk.keynum));
|
||||||
|
xfput_b64(fp, (unsigned char *) (void *) pubkey_struct,
|
||||||
|
sizeof *pubkey_struct);
|
||||||
|
xfclose(fp);
|
||||||
|
sodium_free(pubkey_struct);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static char *
|
||||||
|
append_sig_suffix(const char *message_file)
|
||||||
|
{
|
||||||
|
char *sig_file;
|
||||||
|
size_t message_file_len = strlen(message_file);
|
||||||
|
|
||||||
|
sig_file = xmalloc(message_file_len + sizeof SIG_SUFFIX);
|
||||||
|
memcpy(sig_file, message_file, message_file_len);
|
||||||
|
memcpy(sig_file + message_file_len, SIG_SUFFIX, sizeof SIG_SUFFIX);
|
||||||
|
|
||||||
|
return sig_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef VERIFY_ONLY
|
||||||
|
static char *
|
||||||
|
default_trusted_comment(const char *message_file)
|
||||||
|
{
|
||||||
|
char *ret;
|
||||||
|
time_t ts = time(NULL);
|
||||||
|
|
||||||
|
if (asprintf(&ret, "timestamp: %lu file: %s",
|
||||||
|
(unsigned long) ts, file_basename(message_file)) < 0 ||
|
||||||
|
ret == NULL) {
|
||||||
|
exit_err("asprintf()");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *pk_file = SIG_DEFAULT_PKFILE;
|
||||||
|
const char *sk_file = SIG_DEFAULT_SKFILE;
|
||||||
|
const char *sig_file = NULL;
|
||||||
|
const char *message_file = NULL;
|
||||||
|
const char *comment = NULL;
|
||||||
|
const char *trusted_comment = NULL;
|
||||||
|
int opt_flag;
|
||||||
|
int quiet = 0;
|
||||||
|
Action action = ACTION_NONE;
|
||||||
|
|
||||||
|
while ((opt_flag = getopt(argc, argv, getopt_options)) != -1) {
|
||||||
|
switch(opt_flag) {
|
||||||
|
case 'G':
|
||||||
|
if (action != ACTION_NONE && action != ACTION_GENERATE) {
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
action = ACTION_GENERATE;
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
if (action != ACTION_NONE && action != ACTION_SIGN) {
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
action = ACTION_SIGN;
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
if (action != ACTION_NONE && action != ACTION_VERIFY) {
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
action = ACTION_VERIFY;
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
comment = optarg;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
usage();
|
||||||
|
case 'm':
|
||||||
|
message_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
pk_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
quiet = 1;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
sk_file = optarg;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
trusted_comment = optarg;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
sig_file = optarg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sodium_init();
|
||||||
|
switch (action) {
|
||||||
|
#ifndef VERIFY_ONLY
|
||||||
|
case ACTION_GENERATE:
|
||||||
|
return generate(pk_file, sk_file) != 0;
|
||||||
|
case ACTION_SIGN:
|
||||||
|
if (message_file == NULL) {
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
if (sig_file == NULL || *sig_file == 0) {
|
||||||
|
sig_file = append_sig_suffix(message_file);
|
||||||
|
}
|
||||||
|
if (comment == NULL || *comment == 0) {
|
||||||
|
comment = DEFAULT_COMMENT;
|
||||||
|
}
|
||||||
|
if (trusted_comment == NULL || *trusted_comment == 0) {
|
||||||
|
trusted_comment = default_trusted_comment(message_file);
|
||||||
|
}
|
||||||
|
return sign(sk_file, message_file, sig_file, comment,
|
||||||
|
trusted_comment) != 0;
|
||||||
|
#endif
|
||||||
|
case ACTION_VERIFY:
|
||||||
|
if (message_file == NULL) {
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
if (sig_file == NULL || *sig_file == 0) {
|
||||||
|
sig_file = append_sig_suffix(message_file);
|
||||||
|
}
|
||||||
|
return verify(pk_file, message_file, sig_file, quiet) != 0;
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
58
src/minisign.h
Normal file
58
src/minisign.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
#ifndef MINISIGN_H
|
||||||
|
#define MINISIGN_H 1
|
||||||
|
|
||||||
|
#define COMMENTMAXBYTES 1024
|
||||||
|
#define KEYNUMBYTES 8
|
||||||
|
#define PASSWORDMAXBYTES 1024
|
||||||
|
#define TRUSTEDCOMMENTMAXBYTES 8192
|
||||||
|
#define SIGALG "Ed"
|
||||||
|
#define KDFALG "Sc"
|
||||||
|
#define CHKALG "B2"
|
||||||
|
#define COMMENT_PREFIX "untrusted comment: "
|
||||||
|
#define DEFAULT_COMMENT "signature from minisign secret key"
|
||||||
|
#define TRUSTED_COMMENT_PREFIX "trusted comment: "
|
||||||
|
#define SIG_DEFAULT_PKFILE "minisign.pub";
|
||||||
|
#define SIG_DEFAULT_SKFILE "minisign.key";
|
||||||
|
#define SIG_SUFFIX ".minisig"
|
||||||
|
|
||||||
|
typedef struct KeynumSK_ {
|
||||||
|
unsigned char keynum[KEYNUMBYTES];
|
||||||
|
unsigned char sk[crypto_sign_SECRETKEYBYTES];
|
||||||
|
unsigned char chk[crypto_generichash_BYTES];
|
||||||
|
} KeynumSK;
|
||||||
|
|
||||||
|
typedef struct KeynumPK_ {
|
||||||
|
unsigned char keynum[KEYNUMBYTES];
|
||||||
|
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
|
||||||
|
} KeynumPK;
|
||||||
|
|
||||||
|
typedef struct SeckeyStruct_ {
|
||||||
|
unsigned char sig_alg[2];
|
||||||
|
unsigned char kdf_alg[2];
|
||||||
|
unsigned char chk_alg[2];
|
||||||
|
unsigned char kdf_salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES];
|
||||||
|
unsigned char kdf_opslimit_le[8];
|
||||||
|
unsigned char kdf_memlimit_le[8];
|
||||||
|
KeynumSK keynum_sk;
|
||||||
|
} SeckeyStruct;
|
||||||
|
|
||||||
|
typedef struct PubkeyStruct_ {
|
||||||
|
unsigned char sig_alg[2];
|
||||||
|
KeynumPK keynum_pk;
|
||||||
|
} PubkeyStruct;
|
||||||
|
|
||||||
|
typedef struct SigStruct_ {
|
||||||
|
unsigned char sig_alg[2];
|
||||||
|
unsigned char keynum[KEYNUMBYTES];
|
||||||
|
unsigned char sig[crypto_sign_BYTES];
|
||||||
|
} SigStruct;
|
||||||
|
|
||||||
|
typedef enum Action_ {
|
||||||
|
ACTION_NONE,
|
||||||
|
ACTION_GENERATE,
|
||||||
|
ACTION_SIGN,
|
||||||
|
ACTION_VERIFY
|
||||||
|
} Action;
|
||||||
|
|
||||||
|
#endif
|
Loading…
Add table
Add a link
Reference in a new issue