From eb7188d04b30dcbc47ac1af621b738cc0923ae38 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 28 Jul 2016 16:18:23 +0100 Subject: [PATCH] [crypto] Add DER image format Add DER-encoded ASN.1 as an image format. There is no fixed signature for DER files. We treat an image as DER if it comprises a single valid SEQUENCE object covering the entire length of the image. Signed-off-by: Michael Brown --- src/config/config.c | 3 + src/config/general.h | 1 + src/image/der.c | 120 +++++++++++++++++++++++++++++++++++++ src/include/ipxe/der.h | 16 +++++ src/include/ipxe/errfile.h | 1 + src/tests/asn1_test.c | 97 ++++++++++++++++++++++++++++++ src/tests/asn1_test.h | 73 ++++++++++++++++++++++ src/tests/der_test.c | 84 ++++++++++++++++++++++++++ src/tests/tests.c | 1 + 9 files changed, 396 insertions(+) create mode 100644 src/image/der.c create mode 100644 src/include/ipxe/der.h create mode 100644 src/tests/asn1_test.c create mode 100644 src/tests/asn1_test.h create mode 100644 src/tests/der_test.c diff --git a/src/config/config.c b/src/config/config.c index e24cfe0d..acdbebaa 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -188,6 +188,9 @@ REQUIRE_OBJECT ( pnm ); #ifdef IMAGE_PNG REQUIRE_OBJECT ( png ); #endif +#ifdef IMAGE_DER +REQUIRE_OBJECT ( der ); +#endif /* * Drag in all requested commands diff --git a/src/config/general.h b/src/config/general.h index a71ba726..6ff4b74a 100644 --- a/src/config/general.h +++ b/src/config/general.h @@ -112,6 +112,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); //#define IMAGE_SDI /* SDI image support */ //#define IMAGE_PNM /* PNM image support */ //#define IMAGE_PNG /* PNG image support */ +//#define IMAGE_DER /* DER image support */ /* * Command-line commands to include diff --git a/src/image/der.c b/src/image/der.c new file mode 100644 index 00000000..fa17e565 --- /dev/null +++ b/src/image/der.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * DER-encoded ASN.1 data + * + */ + +/** + * Extract ASN.1 object from image + * + * @v image DER image + * @v offset Offset within image + * @v cursor ASN.1 cursor to fill in + * @ret next Offset to next image, or negative error + * + * The caller is responsible for eventually calling free() on the + * allocated ASN.1 cursor. + */ +static int der_asn1 ( struct image *image, size_t offset __unused, + struct asn1_cursor **cursor ) { + void *data; + + /* Allocate cursor and data buffer */ + *cursor = malloc ( sizeof ( **cursor ) + image->len ); + if ( ! *cursor ) + return -ENOMEM; + data = ( ( ( void * ) *cursor ) + sizeof ( **cursor ) ); + + /* Populate cursor and data buffer */ + (*cursor)->data = data; + (*cursor)->len = image->len; + copy_from_user ( data, image->data, 0, image->len ); + + return image->len; +} + +/** + * Probe DER image + * + * @v image DER image + * @ret rc Return status code + */ +static int der_probe ( struct image *image ) { + struct asn1_cursor cursor; + uint8_t buf[8]; + size_t extra; + size_t total; + int len; + int rc; + + /* Sanity check: no realistic DER image can be smaller than this */ + if ( image->len < sizeof ( buf ) ) + return -ENOEXEC; + + /* Prepare partial cursor */ + cursor.data = buf; + cursor.len = sizeof ( buf ); + copy_from_user ( buf, image->data, 0, sizeof ( buf ) ); + extra = ( image->len - sizeof ( buf ) ); + + /* Get length of ASN.1 sequence */ + len = asn1_start ( &cursor, ASN1_SEQUENCE, extra ); + if ( len < 0 ) { + rc = len; + DBGC ( image, "DER %s is not valid ASN.1: %s\n", + image->name, strerror ( rc ) ); + return rc; + } + + /* Add length of tag and length bytes consumed by asn1_start() */ + total = ( len + ( cursor.data - ( ( void * ) buf ) ) ); + assert ( total <= image->len ); + + /* Check that image comprises a single well-formed ASN.1 object */ + if ( total != image->len ) { + DBGC ( image, "DER %s is not single ASN.1\n", image->name ); + return -ENOEXEC; + } + + return 0; +} + +/** DER image type */ +struct image_type der_image_type __image_type ( PROBE_NORMAL ) = { + .name = "DER", + .probe = der_probe, + .asn1 = der_asn1, +}; diff --git a/src/include/ipxe/der.h b/src/include/ipxe/der.h new file mode 100644 index 00000000..c63bd975 --- /dev/null +++ b/src/include/ipxe/der.h @@ -0,0 +1,16 @@ +#ifndef _IPXE_DER_H +#define _IPXE_DER_H + +/** @file + * + * DER image format + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +extern struct image_type der_image_type __image_type ( PROBE_NORMAL ); + +#endif /* _IPXE_DER_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index f743dae6..61e208a5 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -276,6 +276,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_embedded ( ERRFILE_IMAGE | 0x00050000 ) #define ERRFILE_pnm ( ERRFILE_IMAGE | 0x00060000 ) #define ERRFILE_png ( ERRFILE_IMAGE | 0x00070000 ) +#define ERRFILE_der ( ERRFILE_IMAGE | 0x00080000 ) #define ERRFILE_asn1 ( ERRFILE_OTHER | 0x00000000 ) #define ERRFILE_chap ( ERRFILE_OTHER | 0x00010000 ) diff --git a/src/tests/asn1_test.c b/src/tests/asn1_test.c new file mode 100644 index 00000000..df3f01b6 --- /dev/null +++ b/src/tests/asn1_test.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * ASN.1 self-tests + * + */ + +/* Forcibly enable assertions */ +#undef NDEBUG + +#include +#include +#include +#include +#include +#include "asn1_test.h" + +/** + * Report ASN.1 test result + * + * @v test ASN.1 test + * @v file Test code file + * @v line Test code line + */ +void asn1_okx ( struct asn1_test *test, const char *file, unsigned int line ) { + struct digest_algorithm *digest = &asn1_test_digest_algorithm; + struct asn1_cursor *cursor; + uint8_t ctx[digest->ctxsize]; + uint8_t out[ASN1_TEST_DIGEST_SIZE]; + unsigned int i; + size_t offset; + int next; + + /* Sanity check */ + assert ( sizeof ( out ) == digest->digestsize ); + + /* Correct image data pointer */ + test->image->data = virt_to_user ( ( void * ) test->image->data ); + + /* Check that image is detected as correct type */ + okx ( register_image ( test->image ) == 0, file, line ); + okx ( test->image->type == test->type, file, line ); + + /* Check that all ASN.1 objects can be extracted */ + for ( offset = 0, i = 0 ; i < test->count ; offset = next, i++ ) { + + /* Extract ASN.1 object */ + next = image_asn1 ( test->image, offset, &cursor ); + okx ( next >= 0, file, line ); + okx ( ( ( size_t ) next ) > offset, file, line ); + if ( next > 0 ) { + + /* Calculate digest of ASN.1 object */ + digest_init ( digest, ctx ); + digest_update ( digest, ctx, cursor->data, + cursor->len ); + digest_final ( digest, ctx, out ); + + /* Compare against expected digest */ + okx ( memcmp ( out, test->expected[i].digest, + sizeof ( out ) ) == 0, file, line ); + + /* Free ASN.1 object */ + free ( cursor ); + } + } + + /* Check that we have reached the end of the image */ + okx ( offset == test->image->len, file, line ); + + /* Unregister image */ + unregister_image ( test->image ); +} diff --git a/src/tests/asn1_test.h b/src/tests/asn1_test.h new file mode 100644 index 00000000..c8167ed3 --- /dev/null +++ b/src/tests/asn1_test.h @@ -0,0 +1,73 @@ +#ifndef _ASN1_TEST_H +#define _ASN1_TEST_H + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include + +/** Digest algorithm used for ASN.1 tests */ +#define asn1_test_digest_algorithm sha1_algorithm + +/** Digest size used for ASN.1 tests */ +#define ASN1_TEST_DIGEST_SIZE SHA1_DIGEST_SIZE + +/** An ASN.1 test digest */ +struct asn1_test_digest { + /** Digest value */ + uint8_t digest[ASN1_TEST_DIGEST_SIZE]; +}; + +/** An ASN.1 test */ +struct asn1_test { + /** Image type */ + struct image_type *type; + /** Source image */ + struct image *image; + /** Expected digests of ASN.1 objects */ + struct asn1_test_digest *expected; + /** Number of ASN.1 objects */ + unsigned int count; +}; + +/** + * Define an ASN.1 test + * + * @v _name Test name + * @v _type Test image file type + * @v _file Test image file data + * @v ... Expected ASN.1 object digests + * @ret test ASN.1 test + */ +#define ASN1( _name, _type, _file, ... ) \ + static const char _name ## __file[] = _file; \ + static struct image _name ## __image = { \ + .refcnt = REF_INIT ( ref_no_free ), \ + .name = #_name, \ + .data = ( userptr_t ) ( _name ## __file ), \ + .len = sizeof ( _name ## __file ), \ + }; \ + static struct asn1_test_digest _name ## _expected[] = { \ + __VA_ARGS__ \ + }; \ + static struct asn1_test _name = { \ + .type = _type, \ + .image = & _name ## __image, \ + .expected = _name ## _expected, \ + .count = ( sizeof ( _name ## _expected ) / \ + sizeof ( _name ## _expected[0] ) ), \ + }; + +extern void asn1_okx ( struct asn1_test *test, const char *file, + unsigned int line ); + +/** + * Report ASN.1 test result + * + * @v test ASN.1 test + */ +#define asn1_ok( test ) asn1_okx ( test, __FILE__, __LINE__ ) + +#endif /* _ASN1_TEST_H */ diff --git a/src/tests/der_test.c b/src/tests/der_test.c new file mode 100644 index 00000000..00cc644f --- /dev/null +++ b/src/tests/der_test.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * DER self-tests + * + */ + +/* Forcibly enable assertions */ +#undef NDEBUG + +#include +#include +#include +#include +#include "asn1_test.h" + +/** Define inline data */ +#define DATA(...) { __VA_ARGS__ } + +/** Define inline expected digest */ +#define DIGEST(...) { { __VA_ARGS__ } } + +/** 32-bit RSA private key */ +ASN1 ( rsa32, &der_image_type, + DATA ( 0x30, 0x2c, 0x02, 0x01, 0x00, 0x02, 0x05, 0x00, 0xb7, 0x56, + 0x5c, 0xb1, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x04, 0x66, + 0xa4, 0xc4, 0x35, 0x02, 0x03, 0x00, 0xda, 0x9f, 0x02, 0x03, + 0x00, 0xd6, 0xaf, 0x02, 0x02, 0x01, 0x59, 0x02, 0x02, 0x4e, + 0xe1, 0x02, 0x03, 0x00, 0xa6, 0x5a ), + DIGEST ( 0x82, 0x66, 0x24, 0xd9, 0xc3, 0x98, 0x1e, 0x5e, 0x56, 0xed, + 0xd0, 0xd0, 0x2a, 0x5e, 0x9c, 0x3a, 0x58, 0xdf, 0x76, 0x0d ) ); + +/** 64-bit RSA private key */ +ASN1 ( rsa64, &der_image_type, + DATA ( 0x30, 0x3e, 0x02, 0x01, 0x00, 0x02, 0x09, 0x00, 0xa1, 0xba, + 0xb5, 0x70, 0x00, 0x89, 0xc0, 0x43, 0x02, 0x03, 0x01, 0x00, + 0x01, 0x02, 0x08, 0x43, 0x98, 0xc6, 0x3c, 0x5f, 0xdc, 0x98, + 0x01, 0x02, 0x05, 0x00, 0xcf, 0x91, 0x1c, 0x5d, 0x02, 0x05, + 0x00, 0xc7, 0x77, 0x85, 0x1f, 0x02, 0x05, 0x00, 0xbc, 0xb3, + 0x33, 0x91, 0x02, 0x04, 0x1b, 0xf9, 0x38, 0x13, 0x02, 0x04, + 0x19, 0xf2, 0x58, 0x86 ), + DIGEST ( 0xee, 0x17, 0x32, 0x31, 0xf0, 0x3d, 0xfd, 0xaa, 0x9b, 0x47, + 0xaf, 0x7b, 0x4b, 0x52, 0x0b, 0xb1, 0xab, 0x25, 0x3f, 0x11 ) ); + +/** + * Perform DER self-test + * + */ +static void der_test_exec ( void ) { + + /* Perform tests */ + asn1_ok ( &rsa32 ); + asn1_ok ( &rsa64 ); +} + +/** DER self-test */ +struct self_test der_test __self_test = { + .name = "der", + .exec = der_test_exec, +}; diff --git a/src/tests/tests.c b/src/tests/tests.c index 0ec885f4..b9679b49 100644 --- a/src/tests/tests.c +++ b/src/tests/tests.c @@ -69,3 +69,4 @@ REQUIRE_OBJECT ( pccrc_test ); REQUIRE_OBJECT ( linebuf_test ); REQUIRE_OBJECT ( iobuf_test ); REQUIRE_OBJECT ( bitops_test ); +REQUIRE_OBJECT ( der_test );