fw: Add lots of comments to source

This commit is contained in:
jaseg 2021-12-17 18:20:49 +01:00
parent 8f28367b2d
commit de4e34992c
10 changed files with 191 additions and 79 deletions

View file

@ -1,3 +1,22 @@
/* cage - a minimal (partial) C implementation of the age asymmetric file encryption format.
* Copyright (c) 2021 Jan Götte <cage.code@jaseg.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---
* You can find a usage example at the end of this file.
*/
#include <string.h>
#include <assert.h>
@ -35,7 +54,7 @@
#define MBEDTLS_CHECK(fun_call) MBEDTLS_CHECK_VAL(fun_call, CA_ERR_MBEDTLS_ERROR)
/* Constant-time memcmp because inexplicably mbedtls doesn't have one.
/* Constant-time memcmp because mbedtls inexplicably doesn't have one.
* See https://github.com/ARMmbed/mbedtls/issues/3040
*/
static inline int constant_time_memcmp( const void *a, const void *b, size_t n )
@ -53,18 +72,24 @@ static inline int constant_time_memcmp( const void *a, const void *b, size_t n )
}
/* Parse a single stanza (decryption key line). Invokes specialized parsers depending on asmmmetric crypto used. */
static enum ca_error parse_stanza(struct ca_keystore *ks, const char *stanza_head, size_t len, unsigned char file_key[16]);
/* Decrypt file encryption key from stanza, X25519 variant. */
static enum ca_error parse_stanza_x25519(struct ca_keystore *ks, size_t nargs, const char **args, size_t body_len, const unsigned char *body, unsigned char file_key[16]);
/* Check symmetric file key by checking decrypted header MAC */
static enum ca_error check_file_key(const unsigned char *buf, size_t buflen, const unsigned char file_key[16]);
/* Initialize keystore struct */
void ca_keystore_init(struct ca_keystore *ks) {
mbedtls_ecp_keypair_init(&(ks->x25519_kp));
}
/* Free keystore struct */
void ca_keystore_free(struct ca_keystore *ks) {
mbedtls_ecp_keypair_free(&(ks->x25519_kp));
}
/* Convert error code to human-readable string */
const char *ca_errstr(enum ca_error err) {
switch (err) {
case CA_ERR_SUCCESS: return "success";
@ -85,6 +110,7 @@ const char *ca_errstr(enum ca_error err) {
}
}
/* Load X25519 private key into keystore */
enum ca_error ca_keystore_load_x25519_private_key(struct ca_keystore *ks, const unsigned char buf[32]) {
enum ca_error err = CA_ERR_CORRUPTED_STATE;
/*
@ -108,19 +134,27 @@ cleanup:
return err;
}
enum ca_error parse_age_buf(struct ca_keystore *ks, const char *buf, size_t buflen, unsigned char file_key[16]) {
/* Public API: Read encrypted age file from given buffer and try to decrypt file key using private keys in the given
* keystore. Returns 0 on success, error code on failure. If the file key cannot be decrypted using any of the private
* keys in the keystore, CA_ERR_KEY_NOT_FOUND is returned.
*/
enum ca_error parse_age_buf(struct ca_keystore *ks, const char *buf, size_t buflen, /* output */ unsigned char file_key[16]) {
enum ca_error err = CA_ERR_CORRUPTED_STATE;
/* Check that the input buffer is well-formed. */
if (buflen <= 0) {
LOG_PRINTF("Error: parse_age_buf: (buflen == %d) <= 0\r\n", buflen);
return CA_ERR_INVALID_HEADER;
}
if (strnlen(buf, buflen) >= buflen) {
LOG_PRINTF("Error: parse_age_buf: Buffer is not null-terminated\r\n");
return CA_ERR_INVALID_HEADER; /* buffer is not null-terminated */
}
/* First character of line currently being processed */
const char *current_line = buf;
/* Newline character or null byte at the end of line currently being processed */
const char *line_end = strchr(buf, '\n');
if (!line_end) {
@ -128,12 +162,14 @@ enum ca_error parse_age_buf(struct ca_keystore *ks, const char *buf, size_t bufl
return CA_ERR_INVALID_HEADER; /* header has to contain at least magic string, one stanza and payload separator line */
}
/* Check header prefix */
const char *header_start = "age-encryption.org/v";
if (strncmp(current_line, header_start, strlen(header_start))) {
LOG_PRINTF("Error: parse_age_buf: header magic string corrupt or missing\r\n");
return CA_ERR_INVALID_HEADER; /* header magic string corrupt or missing */
}
/* Parse and validate header version number */
char *endptr = NULL;
unsigned long header_version = strtoul(current_line + strlen(header_start), &endptr, 10);
@ -147,7 +183,13 @@ enum ca_error parse_age_buf(struct ca_keystore *ks, const char *buf, size_t bufl
return CA_ERR_FILE_FORMAT_TOO_NEW;
}
const char *stanza_head = NULL;
/* Stanza parse loop.
*
* In age, a stanza is one line containing an encryption of the symmetric file key under one asymmetric public key.
* Stanzas can be validated by decrypting the header and checking a MAC. To decrypt the file key, iterate through
* all stanzas and try to decrypt each one in turn.
*/
const char *stanza_head = NULL; /* Start of current stanza */
size_t stanza_num = 0;
for (size_t i=0; i<buflen; i++) {
/* we forced buf above to be nul-terminated and we know line_end is \n (!= \0), so line_end+1 must be valid. */
@ -159,34 +201,31 @@ enum ca_error parse_age_buf(struct ca_keystore *ks, const char *buf, size_t bufl
return CA_ERR_INVALID_HEADER; /* the header must always end with '\n' after the --- separator line */
}
if (stanza_head) {
err = parse_stanza(ks, stanza_head, current_line - stanza_head, file_key);
if (err != CA_ERR_SUCCESS)
return err;
if (!check_file_key((const unsigned char *)buf, buflen, file_key)) {
/* LOG_PRINTF("Found file key in stanza %d\n", stanza_num); */
return 0;
} /* else { LOG_PRINTF("stanza %d does not match\n", stanza_num); } */
}
if (!strncmp(current_line, "-> ", 3)) { /* stanza start */
/* Next line contains a stanza */
stanza_num += 1;
if (stanza_num > CA_ERR_TOO_MANY_STANZAS) {
if (stanza_num > CA_MAX_STANZAS) {
return CA_ERR_TOO_MANY_STANZAS;
}
if (stanza_head) {
err = parse_stanza(ks, stanza_head, current_line - stanza_head, file_key);
if (err != CA_ERR_SUCCESS)
return err;
if (!check_file_key((const unsigned char *)buf, buflen, file_key)) {
/*
LOG_PRINTF("Found file key in stanza %d\n", stanza_num);
*/
return 0;
} else {
/*
LOG_PRINTF("stanza %d does not match\n", stanza_num);
*/
}
}
stanza_head = current_line;
continue;
} else if (!strncmp(current_line, "---", 3)) { /* Payload separator line */
} else if (!strncmp(current_line, "---", 3)) {
/* Next line contains the payload separator, this is the last stanza. */
if (stanza_head) {
err = parse_stanza(ks, stanza_head, current_line - stanza_head, file_key);
@ -194,16 +233,11 @@ enum ca_error parse_age_buf(struct ca_keystore *ks, const char *buf, size_t bufl
return err;
if (!check_file_key((const unsigned char *)buf, buflen, file_key)) {
/*
LOG_PRINTF("Found file key in stanza %d\n", stanza_num);
*/
/* LOG_PRINTF("Found file key in stanza %d\n", stanza_num); */
return 0;
} else {
/*
LOG_PRINTF("stanza %d does not match\n", stanza_num);
*/
}
} /* else { LOG_PRINTF("stanza %d does not match\n", stanza_num); } */
} else {
LOG_PRINTF("Error: parse_age_buf: header does not contain any stanzas\r\n");
return CA_ERR_INVALID_HEADER; /* the header must contain at least one stanza */
@ -226,6 +260,7 @@ enum ca_error parse_age_buf(struct ca_keystore *ks, const char *buf, size_t bufl
return CA_ERR_KEY_NOT_FOUND;
}
/* Internal: parse single stanza */
enum ca_error parse_stanza(struct ca_keystore *ks, const char *stanza_head, size_t len, unsigned char file_key[16]) {
const char *stanza_x25519 = "X25519";
enum ca_error err = CA_ERR_CORRUPTED_STATE;
@ -471,6 +506,7 @@ cleanup:
return err;
}
/* Public API: Decrypt file contents given file key */
enum ca_error stream_decrypt(unsigned char *out, size_t outlen, size_t *out_written, const unsigned char *in, size_t inlen, const unsigned char file_key[16]) {
enum ca_error err = CA_ERR_CORRUPTED_STATE;
@ -479,7 +515,7 @@ enum ca_error stream_decrypt(unsigned char *out, size_t outlen, size_t *out_writ
return CA_ERR_INVALID_HEADER;
}
const unsigned char *endl = (const unsigned char *)strchr(found+1, '\n');
const unsigned char *endl = (const unsigned char *)memchr(found+1, '\n', inlen-((const unsigned char *)(found+1)-in));
if (!endl) {
return CA_ERR_INVALID_HEADER;
}
@ -608,6 +644,7 @@ int main(int argc, char **argv) {
char hrp[64];
char private_key[64];
size_t bech32_key_len;
/* the age design doc says that keys use the slightly obscure "bech32" format for storage. */
enum bech32_err b32_err = bech32_decode(hrp, sizeof(hrp), private_key, sizeof(private_key), &bech32_key_len, argv[1], NULL);
if (b32_err) {
fprintf(stderr, "Error: bech32_decode: rc=%d\n", b32_err);

View file

@ -27,6 +27,9 @@ enum ca_error {
CA_ERR_TOO_MANY_STANZAS = 13,
};
/* Maximum number of stanzas that are checked before returning CA_ERR_TOO_MANY_STANZAS.
* This is a compile-time constant to set an upper bound for stanza decryption. For very large values, a bad age file
* that contains many stanzas could take very long to decrypt. */
#ifndef CA_MAX_STANZAS
#define CA_MAX_STANZAS 8
#endif
@ -36,6 +39,7 @@ struct ca_keystore {
};
/* Public API */
enum ca_error parse_age_buf(struct ca_keystore *ks, const char *buf, size_t buflen, unsigned char file_key[16]);
void ca_keystore_init(struct ca_keystore *ks);
void ca_keystore_free(struct ca_keystore *ks);

View file

@ -1,3 +1,22 @@
/* cage - a minimal (partial) C implementation of the age asymmetric file encryption format.
* Copyright (c) 2021 Jan Götte <cage.code@jaseg.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---
* This file contains a minimal base64 decoder for use in cage's stanza parsing.
*/
#include <string.h>
#include <assert.h>

View file

@ -3,19 +3,18 @@
*
* Copyright (C) 2021 jaseg <code@jaseg.de>
*
*
* libusbhost is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
*
* This program 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 library. If not, see <http://www.gnu.org/licenses/>.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

View file

@ -1,3 +1,22 @@
/*
* This file is part of the tachibana project
*
* Copyright (C) 2021 jaseg <code@jaseg.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include <assert.h>

View file

@ -1,3 +1,22 @@
/*
* This file is part of the tachibana project
*
* Copyright (C) 2021 jaseg <code@jaseg.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "gpio_helpers.h"

View file

@ -3,23 +3,21 @@
*
* Copyright (C) 2021 jaseg <code@jaseg.de>
*
*
* tachibana is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
*
* This program 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 library. If not, see <http://www.gnu.org/licenses/>.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include <stdbool.h>
#include <stdint.h>
#include <assert.h>
@ -37,6 +35,7 @@
#include "cage.h"
#include "bech32.h"
/* Calculated system clock speeds */
unsigned int sysclk_speed = 0;
unsigned int apb1_speed = 0;
unsigned int apb2_speed = 0;
@ -44,9 +43,11 @@ unsigned int auxclk_speed = 0;
unsigned int apb1_timer_speed = 0;
unsigned int apb2_timer_speed = 0;
/* Device driver handles */
struct leds leds;
struct spi_fpga_if spif;
/* Test age payload */
uint8_t cage_startup_test_data[] = {
0x61, 0x67, 0x65, 0x2d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x6f,
0x72, 0x67, 0x2f, 0x76, 0x31, 0x0a, 0x2d, 0x3e, 0x20, 0x58, 0x32, 0x35, 0x35, 0x31, 0x39, 0x20,
@ -66,12 +67,14 @@ uint8_t cage_startup_test_data[] = {
0x5d, 0x66, 0x00
};
/* libc support */
void __libc_init_array(void) { /* we don't need this. */ }
void __assert_func (unused_a const char *file, unused_a int line, unused_a const char *function, unused_a const char *expr) {
asm volatile ("bkpt");
while(1) {}
}
/* Set all clocks to max, sysclk = 84 MHz */
static void clock_setup(void)
{
/* 8MHz HSE clock as PLL source. */
@ -163,6 +166,7 @@ static void led_setup(void)
GPIOB->BSRR = 0xf << 11;
}
/* Support code for SPI interface */
static void spi_fpga_if_set_cs(bool val) {
if (val)
GPIOA->BSRR = 1<<4;
@ -198,6 +202,7 @@ static void spi_fpga_setup(void)
spif_init(&spif, SPI1, &spi_fpga_if_set_cs);
}
/* libc replacement functions */
unsigned long strtoul(const char *nptr, char **endptr, int base) {
if (endptr)
*endptr = NULL;
@ -226,6 +231,8 @@ unsigned long strtoul(const char *nptr, char **endptr, int base) {
}
}
/* Analogous to same transformation done on FPGA: Sometimes we receive pixel data with lsb corrupted (maybe a graphics
* stack bug?). Fix this corruption before displaying values. */
static uint8_t unfuck_channel_val(uint8_t val) {
if ((val & 0xf) == 0xf)
return val + 1;
@ -240,16 +247,21 @@ static void unpack_px_data(uint8_t *buf, size_t size) {
}
}
/* Hooks for mbedtls to use tinyalloc. */
void *malloc(size_t size) { return ta_alloc(size); }
void free(void *ptr) { ta_free(ptr); }
void *calloc(size_t nmemb, size_t size) { return ta_calloc(nmemb, size); }
unsigned char ta_heap[0x10000];
/* buffers for FPGA-facing SPI protocol */
uint8_t payload_buf[16384];
uint8_t dec_buf[16384];
int main(void)
{
/* Check if FPU is enabled */
if (((SCB->CPACR>>20) & 0xf) != 0xf) {
asm volatile ("bkpt");
}

View file

@ -3,20 +3,22 @@
*
* Copyright (C) 2021 jaseg <code@jaseg.de>
*
*
* tachibana is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
*
* This program 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.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
* ---
*
* This file contains driver code for hooking up mbedtls to the STM32's hardware RNG.
*/
#include <stdbool.h>
@ -33,9 +35,11 @@
struct rng_state {
/* Error flags */
bool seed_error;
bool clock_error;
/* Hardware RNG status */
size_t data_available;
uint32_t data[MBEDTLS_CTR_DRBG_ENTROPY_LEN];
};
@ -44,9 +48,8 @@ static volatile struct rng_state rng_state;
static mbedtls_ctr_drbg_context drbg_ctx;
void *g_drbg_ctx = &drbg_ctx;
/* RNG callback for mbedtls, registered below in rng_init. */
static int drbg_f_entropy(void *p, unsigned char *buf, size_t len);
int drbg_f_entropy(void *p, unsigned char *buf, size_t len) {
(void) p;

View file

@ -3,20 +3,21 @@
*
* Copyright (C) 2021 jaseg <code@jaseg.de>
*
*
* libusbhost is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
*
* This program 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 library. If not, see <http://www.gnu.org/licenses/>.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---
* This file contains an USART driver with DMA-accelerated nonblocking transmission.
*/
#include <string.h>

View file

@ -3,19 +3,18 @@
*
* Copyright (C) 2021 jaseg <code@jaseg.de>
*
*
* tachibana is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
*
* This program 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 library. If not, see <http://www.gnu.org/licenses/>.
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/