#define _POSIX_C_SOURCE 200809L #include "sign.h" #include #include #include #include #include #include #include #include #include static int names(const char *digest, char index[71], char signature[75]) { size_t i; if (!digest || strlen(digest) != 64) return 0; for (i = 0; i < 64; ++i) if (!((digest[i] >= '0' && digest[i] <= '9') || (digest[i] >= 'a' && digest[i] <= 'f'))) return 0; snprintf(index, 71, "index.%s", digest); snprintf(signature, 75, "signature.%s", digest); return 1; } static unsigned char *index_data(int dir, const char *digest, size_t *size) { char index[71], signature[75], actual[65]; struct stat st; unsigned char *data = NULL, hash[32]; unsigned int length; size_t i; int fd = -1; static const char hex[] = "0123456789abcdef"; if (!names(digest, index, signature)) return NULL; fd = openat(dir, index, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK); if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 || st.st_size > 128LL * 1024 * 1024) goto done; *size = (size_t)st.st_size; data = malloc(*size ? *size : 1); if (!data) goto done; for (i = 0; i < *size;) { ssize_t got = read(fd, data + i, *size - i); if (got < 0 && errno == EINTR) continue; if (got <= 0) goto fail; i += (size_t)got; } if (read(fd, hash, 1) != 0 || fstat(fd, &st) || st.st_size != (off_t)*size || EVP_Digest(data, *size, hash, &length, EVP_sha256(), NULL) != 1 || length != 32) goto fail; for (i = 0; i < 32; ++i) { actual[i * 2] = hex[hash[i] >> 4]; actual[i * 2 + 1] = hex[hash[i] & 15]; } actual[64] = 0; if (strcmp(actual, digest)) goto fail; close(fd); return data; fail: free(data); data = NULL; done: if (fd >= 0) close(fd); return data; } static int signature_data(int dir, const char *name, unsigned char signature[64]) { struct stat st; int fd = openat(dir, name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK); size_t used = 0; if (fd < 0) return 0; if (fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size != 64) goto fail; while (used < 64) { ssize_t got = read(fd, signature + used, 64 - used); if (got < 0 && errno == EINTR) continue; if (got <= 0) goto fail; used += (size_t)got; } if (fstat(fd, &st) || st.st_size != 64) goto fail; close(fd); return 1; fail: close(fd); return 0; } static int verify_data(EVP_PKEY *key, const unsigned char *data, size_t size, const unsigned char signature[64]) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); int ok = ctx && EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, key) == 1 && EVP_DigestVerify(ctx, signature, 64, data, size) == 1; EVP_MD_CTX_free(ctx); return ok; } static EVP_PKEY *read_public_key(const char *spec) { EVP_PKEY *key = NULL; FILE *stream; unsigned char raw[32]; size_t i; if (!strncmp(spec, "raw:", 4)) { const char *hex = spec + 4; if (strlen(hex) != 64) return NULL; for (i = 0; i < sizeof raw; ++i) { unsigned a = (unsigned char)hex[i * 2], b = (unsigned char)hex[i * 2 + 1]; a = a >= '0' && a <= '9' ? a - '0' : a >= 'a' && a <= 'f' ? a - 'a' + 10 : 256; b = b >= '0' && b <= '9' ? b - '0' : b >= 'a' && b <= 'f' ? b - 'a' + 10 : 256; if (a > 15 || b > 15) return NULL; raw[i] = (unsigned char)(a * 16 + b); } return EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, raw, sizeof raw); } stream = fopen(spec, "r"); if (!stream) return NULL; key = PEM_read_PUBKEY(stream, NULL, NULL, NULL); fclose(stream); return key; } int holy_public_key_hex(const char *path, char output[65]) { EVP_PKEY *key = path ? read_public_key(path) : NULL; unsigned char raw[32]; static const char hex[] = "0123456789abcdef"; size_t size = sizeof raw, i; int ok = key && EVP_PKEY_base_id(key) == EVP_PKEY_ED25519 && EVP_PKEY_get_raw_public_key(key, raw, &size) == 1 && size == 32; if (ok) { for (i = 0; i < 32; ++i) { output[i * 2] = hex[raw[i] >> 4]; output[i * 2 + 1] = hex[raw[i] & 15]; } output[64] = 0; } EVP_PKEY_free(key); return ok; } int holy_sign_index(int dir, const char *digest, const char *private_key) { char index[71], name[75]; unsigned char signature[64], existing[64], *data = NULL; size_t size = 0, length = sizeof signature, written = 0; FILE *stream = NULL; EVP_PKEY *key = NULL; EVP_MD_CTX *ctx = NULL; int fd = -1, ok = 0; if (!names(digest, index, name) || !(data = index_data(dir, digest, &size)) || !(stream = fopen(private_key, "r")) || !(key = PEM_read_PrivateKey(stream, NULL, NULL, NULL)) || EVP_PKEY_base_id(key) != EVP_PKEY_ED25519 || !(ctx = EVP_MD_CTX_new()) || EVP_DigestSignInit(ctx, NULL, NULL, NULL, key) != 1 || EVP_DigestSign(ctx, signature, &length, data, size) != 1 || length != 64) goto done; fd = openat(dir, name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0644); if (fd < 0) { if (errno == EEXIST && signature_data(dir, name, existing) && verify_data(key, data, size, existing)) ok = 1; goto done; } while (written < length) { ssize_t sent = write(fd, signature + written, length - written); if (sent < 0 && errno == EINTR) continue; if (sent <= 0) goto done; written += (size_t)sent; } if (!fsync(fd) && !close(fd)) { fd = -1; ok = !fsync(dir); } done: if (fd >= 0) { close(fd); unlinkat(dir, name, 0); } EVP_MD_CTX_free(ctx); EVP_PKEY_free(key); if (stream) fclose(stream); free(data); return ok; } int holy_verify_index_bytes(int dir, const char *digest, const char *public_key, const unsigned char signature[64], char key_hash[65]) { char index[71], name[75]; unsigned char *data = NULL, raw[32], hash[32]; static const char hex[] = "0123456789abcdef"; size_t size = 0; size_t raw_size = sizeof raw, i; unsigned int hash_size; EVP_PKEY *key = NULL; int ok = 0; if (!names(digest, index, name) || !(data = index_data(dir, digest, &size)) || !(key = read_public_key(public_key)) || EVP_PKEY_base_id(key) != EVP_PKEY_ED25519) goto done; ok = verify_data(key, data, size, signature); if (ok && key_hash) { ok = EVP_PKEY_get_raw_public_key(key, raw, &raw_size) == 1 && raw_size == 32 && EVP_Digest(raw, raw_size, hash, &hash_size, EVP_sha256(), NULL) == 1 && hash_size == 32; if (ok) { for (i = 0; i < 32; ++i) { key_hash[i * 2] = hex[hash[i] >> 4]; key_hash[i * 2 + 1] = hex[hash[i] & 15]; } key_hash[64] = 0; } } done: EVP_PKEY_free(key); free(data); return ok; } int holy_verify_index_keyhash(int dir, const char *digest, const char *public_key, char key_hash[65]) { char index[71], name[75]; unsigned char signature[64]; if (!names(digest, index, name) || !signature_data(dir, name, signature)) return 0; return holy_verify_index_bytes(dir, digest, public_key, signature, key_hash); } int holy_verify_index(int dir, const char *digest, const char *public_key) { return holy_verify_index_keyhash(dir, digest, public_key, NULL); }