diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2019-01-17 11:11:27 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2019-01-17 16:48:28 +0100 |
commit | 3f58832474a4b270e136544016a401ef773ac065 (patch) | |
tree | c617160250c3040ca964c1b72ab5957cd872b82f /openpgp-ffi | |
parent | 38b4108cc1eac851ac17932c5c33623dd535bebb (diff) |
openpgp-ffi: New crate.
- This creates a new crate, 'sequoia-openpgp-ffi', and moves a
handful of functions from 'sequoia-ffi' to it.
- The 'sequoia-ffi' crate is a superset of the 'sequoia-openpgp-ffi'
crate. This is accomplished by some include! magic.
- My first attempt involved having 'sequoia-ffi' depend on
'sequoia-openpgp-ffi', so that the former just re-exports the
symbols. However, that turned out to be unreliable, and might be
not what we want, because it could also duplicate parts of Rust's
standard library.
- Fixes #144.
Diffstat (limited to 'openpgp-ffi')
28 files changed, 6542 insertions, 0 deletions
diff --git a/openpgp-ffi/Cargo.toml b/openpgp-ffi/Cargo.toml new file mode 100644 index 00000000..1bb6aab1 --- /dev/null +++ b/openpgp-ffi/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "sequoia-openpgp-ffi" +description = "C API for Sequoia's low-level OpenPGP crate" +version = "0.3.0" +authors = [ + "Justus Winter <justus@sequoia-pgp.org>", + "Kai Michaelis <kai@sequoia-pgp.org>", + "Neal H. Walfield <neal@sequoia-pgp.org>", +] +documentation = "https://docs.sequoia-pgp.org/sequoia_openpgp_ffi" +homepage = "https://sequoia-pgp.org/" +repository = "https://gitlab.com/sequoia-pgp/sequoia" +readme = "../README.md" +keywords = ["cryptography", "openpgp", "pgp", "encryption", "signing"] +categories = ["cryptography", "authentication", "development-tools::ffi", + "email"] +license = "GPL-3.0" + +[badges] +gitlab = { repository = "sequoia-pgp/sequoia" } +maintenance = { status = "actively-developed" } + +[dependencies] +sequoia-ffi-macros = { path = "../ffi-macros" } +sequoia-openpgp = { path = "../openpgp" } +failure = "0.1.2" +lazy_static = "1.0.0" +libc = "0.2.33" +time = "0.1.40" + +[dev-dependencies] +nettle = "2.0" + +[lib] +crate-type = ["lib", "cdylib", "staticlib"] diff --git a/openpgp-ffi/Makefile b/openpgp-ffi/Makefile new file mode 100644 index 00000000..ba4e1d0f --- /dev/null +++ b/openpgp-ffi/Makefile @@ -0,0 +1,66 @@ +# Makefile for Sequoia's bindings. + +# Configuration. +PREFIX ?= /usr/local +DESTDIR ?= + +CARGO ?= cargo +CARGO_TARGET_DIR ?= $(shell pwd)/../target +# We currently only support absolute paths. +CARGO_TARGET_DIR := $(abspath $(CARGO_TARGET_DIR)) + +VERSION ?= $(shell grep '^version[[:space:]]*=[[:space:]]*' Cargo.toml | cut -d'"' -f2) +VERSION_MAJOR = $(shell echo $(VERSION) | cut -d'.' -f1) + +# Tools. +INSTALL ?= install + +# Make sure subprocesses pick these up. +export PREFIX +export DESTDIR + +all: build + +.PHONY: build +build: + : + +# Testing and examples. +.PHONY: test check +test check: + : + +.PHONY: examples +examples: + $(MAKE) -Cexamples + +# Installation. +.PHONY: build-release +build-release: + : + +.PHONY: install +install: + $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/pkgconfig + sed -e 's|VERSION|$(VERSION)|g' \ + -e 's|PREFIX|$(PREFIX)|g' \ + sequoia-openpgp.pc.in \ + > $(DESTDIR)$(PREFIX)/share/pkgconfig/sequoia-openpgp.pc + $(INSTALL) -d $(DESTDIR)$(PREFIX)/include + $(INSTALL) -d $(DESTDIR)$(PREFIX)/include/sequoia + $(INSTALL) -t $(DESTDIR)$(PREFIX)/include/sequoia \ + include/sequoia/*.h + $(INSTALL) -d $(DESTDIR)$(PREFIX)/lib + $(INSTALL) $(CARGO_TARGET_DIR)/release/libsequoia_openpgp_ffi.so \ + $(DESTDIR)$(PREFIX)/lib/libsequoia_openpgp_ffi.so.$(VERSION) + ln -fs libsequoia_openpgp_ffi.so.$(VERSION) \ + $(DESTDIR)$(PREFIX)/lib/libsequoia_openpgp_ffi.so.$(VERSION_MAJOR) + ln -fs libsequoia_openpgp_ffi.so.$(VERSION) \ + $(DESTDIR)$(PREFIX)/lib/libsequoia_openpgp_ffi.so + $(INSTALL) $(CARGO_TARGET_DIR)/release/libsequoia_openpgp_ffi.a \ + $(DESTDIR)$(PREFIX)/lib/libsequoia_openpgp_ffi.a + +# Housekeeping. +.PHONY: clean +clean: + $(MAKE) -Cexamples clean diff --git a/openpgp-ffi/examples/.gitignore b/openpgp-ffi/examples/.gitignore new file mode 100644 index 00000000..6a47c497 --- /dev/null +++ b/openpgp-ffi/examples/.gitignore @@ -0,0 +1,5 @@ +armor +encrypt-for +example +parser +reader diff --git a/openpgp-ffi/examples/Makefile b/openpgp-ffi/examples/Makefile new file mode 100644 index 00000000..29408634 --- /dev/null +++ b/openpgp-ffi/examples/Makefile @@ -0,0 +1,17 @@ +# Makefile for examples written in C. + +CARGO ?= cargo +CARGO_TARGET_DIR ?= $(shell pwd)/../../target +# We currently only support absolute paths. +CARGO_TARGET_DIR := $(abspath $(CARGO_TARGET_DIR)) + +TARGETS = example reader parser encrypt-for armor +CFLAGS = -I../include -O0 -g -Wall -Werror +LDFLAGS = -L$(CARGO_TARGET_DIR)/debug -lsequoia_openpgp_ffi + +all: $(TARGETS) + +clean: + rm -f $(TARGETS) + +$(TARGETS): ../include/sequoia/openpgp.h diff --git a/openpgp-ffi/examples/armor.c b/openpgp-ffi/examples/armor.c new file mode 100644 index 00000000..9b965c5b --- /dev/null +++ b/openpgp-ffi/examples/armor.c @@ -0,0 +1,60 @@ +#define _GNU_SOURCE +#include <assert.h> +#include <error.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sequoia/openpgp.h> + +const char *armored = + "-----BEGIN PGP ARMORED FILE-----\n" + "Key0: Value0\n" + "Key1: Value1\n" + "\n" + "SGVsbG8gd29ybGQh\n" + "=s4Gu\n" + "-----END PGP ARMORED FILE-----\n"; + +int +main (int argc, char **argv) +{ + sq_error_t err; + sq_reader_t bytes; + sq_reader_t armor; + sq_armor_kind_t kind; + char message[12]; + sq_armor_header_t *header; + size_t header_len; + + bytes = sq_reader_from_bytes ((uint8_t *) armored, strlen (armored)); + armor = sq_armor_reader_new (bytes, SQ_ARMOR_KIND_ANY); + + header = sq_armor_reader_headers (&err, armor, &header_len); + if (header == NULL) + error (1, 0, "Getting headers failed: %s", sq_error_string (err)); + + assert (header_len == 2); + assert (strcmp (header[0].key, "Key0") == 0 + && strcmp (header[0].value, "Value0") == 0); + assert (strcmp (header[1].key, "Key1") == 0 + && strcmp (header[1].value, "Value1") == 0); + for (size_t i = 0; i < header_len; i++) + { + free (header[i].key); + free (header[i].value); + } + free (header); + + kind = sq_armor_reader_kind (armor); + assert (kind == SQ_ARMOR_KIND_FILE); + + if (sq_reader_read (&err, armor, (uint8_t *) message, 12) < 0) + error (1, 0, "Reading failed: %s", sq_error_string (err)); + + assert (memcmp (message, "Hello world!", 12) == 0); + + sq_reader_free (armor); + sq_reader_free (bytes); + return 0; +} diff --git a/openpgp-ffi/examples/encrypt-for.c b/openpgp-ffi/examples/encrypt-for.c new file mode 100644 index 00000000..ab0943ba --- /dev/null +++ b/openpgp-ffi/examples/encrypt-for.c @@ -0,0 +1,96 @@ +/* This example demonstrates how to use the low-level interface to + encrypt a file. */ + +#define _GNU_SOURCE +#include <error.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <time.h> + +#include <sequoia/openpgp.h> + +int +main (int argc, char **argv) +{ + struct stat st; + int fd; + uint8_t *b; + sq_status_t rc; + sq_error_t err; + int use_armor = 1; + sq_tpk_t tpk; + sq_writer_t sink; + sq_writer_stack_t writer = NULL; + void *cipher = NULL; + size_t cipher_bytes = 0; + + if (argc != 2) + error (1, 0, "Usage: %s <keyfile> <plain >cipher", argv[0]); + + if (stat (argv[1], &st)) + error (1, errno, "%s", argv[1]); + + fd = open (argv[1], O_RDONLY); + if (fd == -1) + error (1, errno, "%s", argv[1]); + + b = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + close (fd); + if (b == MAP_FAILED) + error (1, errno, "mmap"); + + tpk = sq_tpk_from_bytes (&err, b, st.st_size); + if (tpk == NULL) + error (1, 0, "sq_packet_parser_from_bytes: %s", sq_error_string (err)); + + sink = sq_writer_alloc (&cipher, &cipher_bytes); + + if (use_armor) + sink = sq_armor_writer_new (&err, sink, SQ_ARMOR_KIND_MESSAGE, + NULL, 0); + + writer = sq_writer_stack_message (sink); + writer = sq_encryptor_new (&err, + writer, + NULL, 0, /* no passwords */ + &tpk, 1, + SQ_ENCRYPTION_MODE_FOR_TRANSPORT); + if (writer == NULL) + error (1, 0, "sq_encryptor_new: %s", sq_error_string (err)); + + writer = sq_literal_writer_new (&err, writer); + if (writer == NULL) + error (1, 0, "sq_literal_writer_new: %s", sq_error_string (err)); + + size_t nread; + uint8_t buf[4096]; + while ((nread = fread (buf, 1, sizeof buf, stdin))) + { + uint8_t *b = buf; + while (nread) + { + ssize_t written; + written = sq_writer_stack_write (&err, writer, b, nread); + if (written < 0) + error (1, 0, "sq_writer_stack_write: %s", sq_error_string (err)); + + b += written; + nread -= written; + } + } + + rc = sq_writer_stack_finalize (&err, writer); + writer = NULL; + if (rc) + error (1, 0, "sq_writer_stack_write: %s", sq_error_string (err)); + + fwrite (cipher, 1, cipher_bytes, stdout); + + munmap (b, st.st_size); + return 0; +} diff --git a/openpgp-ffi/examples/example.c b/openpgp-ffi/examples/example.c new file mode 100644 index 00000000..ac23ff57 --- /dev/null +++ b/openpgp-ffi/examples/example.c @@ -0,0 +1,45 @@ +#define _GNU_SOURCE +#include <error.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <sequoia/openpgp.h> + +int +main (int argc, char **argv) +{ + struct stat st; + int fd; + uint8_t *b; + sq_error_t err; + sq_tpk_t tpk; + + if (argc != 2) + error (1, 0, "Usage: %s <file>", argv[0]); + + if (stat (argv[1], &st)) + error (1, errno, "%s", argv[1]); + + fd = open (argv[1], O_RDONLY); + if (fd == -1) + error (1, errno, "%s", argv[1]); + + b = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (b == MAP_FAILED) + error (1, errno, "mmap"); + + tpk = sq_tpk_from_bytes (&err, b, st.st_size); + if (tpk == NULL) + error (1, 0, "sq_tpk_from_bytes: %s", sq_error_string (err)); + + sq_tpk_dump (tpk); + sq_tpk_free (tpk); + munmap (b, st.st_size); + close (fd); + return 0; +} diff --git a/openpgp-ffi/examples/parser.c b/openpgp-ffi/examples/parser.c new file mode 100644 index 00000000..81b097af --- /dev/null +++ b/openpgp-ffi/examples/parser.c @@ -0,0 +1,93 @@ +/* This example demonstrates how to use the packet parser from C. It + * also serves as a simple benchmark. */ + +#define _GNU_SOURCE +#include <error.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <time.h> + +#include <sequoia/openpgp.h> + +int +main (int argc, char **argv) +{ + struct stat st; + int fd; + uint8_t *b; + sq_status_t rc; + sq_error_t err; + sq_packet_parser_result_t ppr; + sq_packet_parser_t pp; + + if (argc != 2) + error (1, 0, "Usage: %s <file>", argv[0]); + + if (stat (argv[1], &st)) + error (1, errno, "%s", argv[1]); + + fd = open (argv[1], O_RDONLY); + if (fd == -1) + error (1, errno, "%s", argv[1]); + + b = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + close (fd); + if (b == MAP_FAILED) + error (1, errno, "mmap"); + + size_t n = 0; + time_t start = time (NULL); + time_t elapsed; + size_t tens_of_s = 0; + + ppr = sq_packet_parser_from_bytes (&err, b, st.st_size); + while (ppr && (pp = sq_packet_parser_result_packet_parser (ppr))) + { + // Get a reference to the packet that is currently being parsed. + sq_packet_t p = sq_packet_parser_packet (pp); + + if (sq_packet_tag(p) == SQ_TAG_LITERAL) + { + // Stream the packet here. + } + + // Finish parsing the current packet (returned in p), and read + // the header of the next packet (returned in ppr). + rc = sq_packet_parser_next (&err, pp, &p, &ppr); + if (rc) + error (1, 0, "sq_packet_parser_from_bytes: %s", + sq_error_string (err)); + + // We now own p. If we want, we can save it in some structure. + // This would be useful when collecting PKESK packets. Either + // way, we need to free it when we are done. + + n += 1; + + sq_packet_free (p); + + elapsed = time (NULL) - start; + if (elapsed % 10 == 0 && tens_of_s != elapsed / 10) + { + fprintf (stderr, + "Parsed %ld packets in %ld seconds, %.2f packets/s.\n", + n, elapsed, (double) n / (double) elapsed); + fflush (stderr); + tens_of_s = elapsed / 10; + } + } + if (ppr == NULL) + error (1, 0, "sq_packet_parser_from_bytes: %s", sq_error_string (err)); + + fprintf (stderr, "Parsed %ld packets in %ld seconds, %.2f packets/s.\n", + n, elapsed, (double) n / (double) elapsed); + + sq_packet_parser_result_free (ppr); + munmap (b, st.st_size); + return 0; +} diff --git a/openpgp-ffi/examples/reader.c b/openpgp-ffi/examples/reader.c new file mode 100644 index 00000000..019d87d9 --- /dev/null +++ b/openpgp-ffi/examples/reader.c @@ -0,0 +1,48 @@ +#define _GNU_SOURCE +#include <error.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <sequoia/openpgp.h> + +int +main (int argc, char **argv) +{ + struct stat st; + int fd; + uint8_t *b; + sq_error_t err; + sq_reader_t reader; + sq_tpk_t tpk; + + if (argc != 2) + error (1, 0, "Usage: %s <file>", argv[0]); + + if (stat (argv[1], &st)) + error (1, errno, "%s", argv[1]); + + fd = open (argv[1], O_RDONLY); + if (fd == -1) + error (1, errno, "%s", argv[1]); + + b = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (b == MAP_FAILED) + error (1, errno, "mmap"); + + reader = sq_reader_from_bytes (b, st.st_size); + tpk = sq_tpk_from_reader (&err, reader); + if (tpk == NULL) + error (1, 0, "sq_tpk_from_reader: %s", sq_error_string (err)); + + sq_tpk_dump (tpk); + sq_tpk_free (tpk); + sq_reader_free (reader); + munmap (b, st.st_size); + close (fd); + return 0; +} diff --git a/openpgp-ffi/include/sequoia/io.h b/openpgp-ffi/include/sequoia/io.h new file mode 100644 index 00000000..c84de166 --- /dev/null +++ b/openpgp-ffi/include/sequoia/io.h @@ -0,0 +1,83 @@ +#ifndef SEQUOIA_IO_H +#define SEQUOIA_IO_H + +#include <sequoia/openpgp/error.h> + +/*/ +/// A generic reader. +/*/ +typedef struct sq_reader *sq_reader_t; + +/*/ +/// Opens a file returning a reader. +/*/ +sq_reader_t sq_reader_from_file (sq_error_t *errp, const char *filename); + +/*/ +/// Opens a file descriptor returning a reader. +/*/ +sq_reader_t sq_reader_from_fd (int fd); + +/*/ +/// Creates a reader from a buffer. +/*/ +sq_reader_t sq_reader_from_bytes (const uint8_t *buf, size_t len); + +/*/ +/// Frees a reader. +/*/ +void sq_reader_free (sq_reader_t reader); + +/*/ +/// Reads up to `len` bytes into `buf`. +/*/ +ssize_t sq_reader_read (sq_error_t *errp, sq_reader_t reader, + uint8_t *buf, size_t len); + +/*/ +/// A generic writer. +/*/ +typedef struct sq_writer *sq_writer_t; + +/*/ +/// Opens a file returning a writer. +/// +/// The file will be created if it does not exist, or be truncated +/// otherwise. If you need more control, use `sq_writer_from_fd`. +/*/ +sq_writer_t sq_writer_from_file (sq_error_t *errp, const char *filename); + +/*/ +/// Opens a file descriptor returning a writer. +/*/ +sq_writer_t sq_writer_from_fd (int fd); + +/*/ +/// Creates a writer from a buffer. +/*/ +sq_writer_t sq_writer_from_bytes (uint8_t *buf, size_t len); + +/*/ +/// Creates an allocating writer. +/// +/// This writer allocates memory using `malloc`, and stores the +/// pointer to the memory and the number of bytes written to the given +/// locations `buf`, and `len`. Both must either be set to zero, or +/// reference a chunk of memory allocated using libc's heap allocator. +/// The caller is responsible to `free` it once the writer has been +/// destroyed. +/*/ +sq_writer_t sq_writer_alloc (void **buf, size_t *len); + +/*/ +/// Frees a writer. +/*/ +void sq_writer_free (sq_writer_t writer); + +/*/ +/// Writes up to `len` bytes of `buf` into `writer`. +/*/ +ssize_t sq_writer_write (sq_error_t *errp, sq_writer_t writer, + const uint8_t *buf, size_t len); + +#endif diff --git a/openpgp-ffi/include/sequoia/openpgp.h b/openpgp-ffi/include/sequoia/openpgp.h new file mode 100644 index 00000000..ddc6ed5a --- /dev/null +++ b/openpgp-ffi/include/sequoia/openpgp.h @@ -0,0 +1,1663 @@ +#ifndef SEQUOIA_OPENPGP_H +#define SEQUOIA_OPENPGP_H + +#include <stddef.h> +#include <stdint.h> +#include <limits.h> +#include <unistd.h> +#include <time.h> + +#include <sequoia/io.h> +#include <sequoia/openpgp/error.h> +#include <sequoia/openpgp/crypto.h> + +/*/ +/// A low-level OpenPGP message parser. +/// +/// A `PacketParser` provides a low-level, iterator-like interface to +/// parse OpenPGP messages. +/// +/// For each iteration, the user is presented with a [`Packet`] +/// corresponding to the last packet, a `PacketParser` for the next +/// packet, and their positions within the message. +/// +/// Using the `PacketParser`, the user is able to configure how the +/// new packet will be parsed. For instance, it is possible to stream +/// the packet's contents (a `PacketParser` implements the +/// `std::io::Read` and the `BufferedReader` traits), buffer them +/// within the [`Packet`], or drop them. The user can also decide to +/// recurse into the packet, if it is a container, instead of getting +/// the following packet. +/*/ +typedef struct sq_packet_parser *sq_packet_parser_t; + +/*/ +/// Like an `Option<PacketParser>`, but the `None` variant +/// (`PacketParserEOF`) contains some summary information. +/*/ +typedef struct sq_packet_parser_result *sq_packet_parser_result_t; + +/*/ +/// The `None` variant of a `PacketParserResult`. +/*/ +typedef struct sq_packet_parser_eof *sq_packet_parser_eof_t; + +/* sequoia::openpgp::KeyID. */ + +/*/ +/// Holds a KeyID. +/*/ +typedef struct sq_keyid *sq_keyid_t; + +/*/ +/// Reads a binary key ID. +/*/ +sq_keyid_t sq_keyid_from_bytes (const uint8_t *id); + +/*/ +/// Reads a hex-encoded Key ID. +/*/ +sq_keyid_t sq_keyid_from_hex (const char *id); + +/*/ +/// Frees a sq_keyid_t. +/*/ +void sq_keyid_free (sq_keyid_t keyid); + +/*/ +/// Clones the |