diff --git a/demo/fw/src/cage.c b/demo/fw/src/cage.c index b60e157..1205653 100644 --- a/demo/fw/src/cage.c +++ b/demo/fw/src/cage.c @@ -1,3 +1,22 @@ +/* cage - a minimal (partial) C implementation of the age asymmetric file encryption format. + * Copyright (c) 2021 Jan Götte + * + * 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 . + * + * --- + * You can find a usage example at the end of this file. + */ #include #include @@ -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 ", 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); diff --git a/demo/fw/src/cage.h b/demo/fw/src/cage.h index 2e7c9fb..86ed1e1 100644 --- a/demo/fw/src/cage.h +++ b/demo/fw/src/cage.h @@ -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); diff --git a/demo/fw/src/cage_base64.c b/demo/fw/src/cage_base64.c index 7ba87d9..cfe7130 100644 --- a/demo/fw/src/cage_base64.c +++ b/demo/fw/src/cage_base64.c @@ -1,3 +1,22 @@ +/* cage - a minimal (partial) C implementation of the age asymmetric file encryption format. + * Copyright (c) 2021 Jan Götte + * + * 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 . + * + * --- + * This file contains a minimal base64 decoder for use in cage's stanza parsing. + */ #include #include diff --git a/demo/fw/src/con_usart.c b/demo/fw/src/con_usart.c index 7bdc941..03a9fad 100644 --- a/demo/fw/src/con_usart.c +++ b/demo/fw/src/con_usart.c @@ -3,19 +3,18 @@ * * Copyright (C) 2021 jaseg * - * - * 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 . + * 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 . * */ diff --git a/demo/fw/src/dma_util.c b/demo/fw/src/dma_util.c index 48de215..f1fa7bb 100644 --- a/demo/fw/src/dma_util.c +++ b/demo/fw/src/dma_util.c @@ -1,3 +1,22 @@ +/* + * This file is part of the tachibana project + * + * Copyright (C) 2021 jaseg + * + * 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 . + * + */ #include diff --git a/demo/fw/src/gpio_helpers.c b/demo/fw/src/gpio_helpers.c index 07b9a33..6394f67 100644 --- a/demo/fw/src/gpio_helpers.c +++ b/demo/fw/src/gpio_helpers.c @@ -1,3 +1,22 @@ +/* + * This file is part of the tachibana project + * + * Copyright (C) 2021 jaseg + * + * 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 . + * + */ #include "gpio_helpers.h" diff --git a/demo/fw/src/main.c b/demo/fw/src/main.c index 5c8f39f..6f6d0fb 100644 --- a/demo/fw/src/main.c +++ b/demo/fw/src/main.c @@ -3,23 +3,21 @@ * * Copyright (C) 2021 jaseg * - * - * 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 . + * 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 . * */ - #include #include #include @@ -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"); } diff --git a/demo/fw/src/rng.c b/demo/fw/src/rng.c index fe41216..d183dee 100644 --- a/demo/fw/src/rng.c +++ b/demo/fw/src/rng.c @@ -3,20 +3,22 @@ * * Copyright (C) 2021 jaseg * - * - * 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 . * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see . + * --- * + * This file contains driver code for hooking up mbedtls to the STM32's hardware RNG. */ #include @@ -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; diff --git a/demo/fw/src/serial.c b/demo/fw/src/serial.c index cb244b3..175ee2f 100644 --- a/demo/fw/src/serial.c +++ b/demo/fw/src/serial.c @@ -3,20 +3,21 @@ * * Copyright (C) 2021 jaseg * - * - * 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 . + * 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 . * + * --- + * This file contains an USART driver with DMA-accelerated nonblocking transmission. */ #include diff --git a/demo/fw/src/spi.c b/demo/fw/src/spi.c index a7f936e..fa51c6d 100644 --- a/demo/fw/src/spi.c +++ b/demo/fw/src/spi.c @@ -3,19 +3,18 @@ * * Copyright (C) 2021 jaseg * - * - * 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 . + * 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 . * */