├── .gitignore ├── Makefile ├── README.md ├── b64dec.c ├── b64dec.h ├── b64write.c ├── b64write.h ├── bech32.c ├── bech32.h ├── ctassert.h ├── dae.c ├── dae.h ├── freadline.c ├── freadline.h ├── main.c ├── progname.c ├── progname.h ├── reallocn.c ├── reallocn.h ├── strprefix.c ├── strprefix.h ├── t_bech32.c ├── t_bech32.exp ├── t_dae.c └── t_dae.exp /.gitignore: -------------------------------------------------------------------------------- 1 | *.d 2 | *.o 3 | *.out 4 | *.tmp 5 | *~ 6 | 7 | /age-plugin-fido 8 | /t_bech32 9 | /t_dae 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default-target: all 2 | default-target: .PHONY 3 | .PHONY: 4 | 5 | 6 | # Parameters 7 | # 8 | DESTDIR = 9 | 10 | prefix = /usr/local 11 | 12 | libexecdir = $(prefix)/libexec 13 | 14 | INSTALL = install 15 | INSTALL_DIR = $(INSTALL) -d 16 | INSTALL_PROGRAM = $(INSTALL) 17 | 18 | 19 | # Public targets 20 | all: .PHONY 21 | all: age-plugin-fido 22 | all: check 23 | 24 | clean: .PHONY 25 | check: .PHONY 26 | test: .PHONY check 27 | install: .PHONY 28 | 29 | 30 | # Installation targets 31 | # 32 | install: install-libexec 33 | install-libexec: .PHONY 34 | $(INSTALL_DIR) $(DESTDIR)$(libexecdir) 35 | $(INSTALL_PROGRAM) age-plugin-fido $(DESTDIR)$(libexecdir)/ 36 | 37 | 38 | # Suffix rules 39 | # 40 | .c.o: 41 | $(CC) $(_CFLAGS) $(_CPPFLAGS) -c $< 42 | 43 | _CFLAGS = -g -Og -Wall -Wextra -Werror -std=c99 $(CFLAGS) 44 | _CPPFLAGS = -MD -MF $@.d -D_POSIX_C_SOURCE=200809L -I. $(CPPFLAGS) 45 | 46 | 47 | # age-plugin-fido 48 | # 49 | SRCS = \ 50 | b64dec.c \ 51 | b64write.c \ 52 | bech32.c \ 53 | dae.c \ 54 | freadline.c \ 55 | main.c \ 56 | progname.c \ 57 | reallocn.c \ 58 | strprefix.c \ 59 | # end of SRCS_age-plugin-fido 60 | DEPS = $(SRCS:.c=.o.d) 61 | -include $(DEPS) 62 | 63 | age-plugin-fido: $(SRCS:.c=.o) 64 | $(CC) -o $@ $(_CFLAGS) $(LDFLAGS) $(SRCS:.c=.o) \ 65 | -lfido2 -lcrypto 66 | 67 | clean: clean-age-plugin-fido 68 | clean-age-plugin-fido: .PHONY 69 | -rm -f $(SRCS:.c=.o) 70 | -rm -f $(SRCS:.c=.o.d) 71 | -rm -f age-plugin-fido 72 | 73 | 74 | # Tests 75 | # 76 | check: check-bech32 77 | check-bech32: .PHONY 78 | check-bech32: t_bech32.exp t_bech32.out 79 | diff -u t_bech32.exp t_bech32.out 80 | 81 | t_bech32.out: t_bech32 82 | ./t_bech32 > $@.tmp && mv -f $@.tmp $@ 83 | 84 | SRCS_t_bech32 = \ 85 | bech32.c \ 86 | t_bech32.c \ 87 | # end of SRCS_t_bech32 88 | DEPS_t_bech32 = $(SRCS_t_bech32:.c=.o.d) 89 | -include $(DEPS_t_bech32) 90 | 91 | t_bech32: $(SRCS_t_bech32:.c=.o) 92 | $(CC) -o $@ $(_CFLAGS) $(LDFLAGS) $(SRCS_t_bech32:.c=.o) 93 | 94 | clean: clean-bech32 95 | clean-bech32: .PHONY 96 | -rm -f $(SRCS_t_bech32:.c=.o) 97 | -rm -f $(SRCS_t_bech32:.c=.o.d) 98 | -rm -f t_bech32 99 | -rm -f t_bech32.out 100 | -rm -f t_bech32.out.tmp 101 | 102 | 103 | check: check-dae 104 | check-dae: .PHONY 105 | check-dae: t_dae.exp t_dae.out 106 | diff -u t_dae.exp t_dae.out 107 | 108 | t_dae.out: t_dae 109 | ./t_dae > $@.tmp && mv -f $@.tmp $@ 110 | 111 | SRCS_t_dae = \ 112 | dae.c \ 113 | t_dae.c \ 114 | # end of SRCS_t_dae 115 | DEPS_t_dae = $(SRCS_t_dae:.c=.o.d) 116 | -include $(DEPS_t_dae) 117 | 118 | t_dae: $(SRCS_t_dae:.c=.o) 119 | $(CC) -o $@ $(_CFLAGS) $(LDFLAGS) $(SRCS_t_dae:.c=.o) -lcrypto 120 | 121 | clean: clean-dae 122 | clean-dae: .PHONY 123 | -rm -f $(SRCS_t_dae:.c=.o) 124 | -rm -f $(SRCS_t_dae:.c=.o.d) 125 | -rm -f t_dae 126 | -rm -f t_dae.out 127 | -rm -f t_dae.out.tmp 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | age-plugin-fido -- draft fido plugin for age(1) 2 | === 3 | 4 | - WARNING: early draft, likely buggy, protocol not finalized 5 | - WARNING: useful only for symmetric encryption to self 6 | - WARNING: works only with fido2 keys using hmac-secret 7 | - WARNING: does not work with u2f-only keys 8 | - WARNING: look behind you, a three-headed monkey! 9 | - WARNING: usability issues with multiple fido keys 10 | - WARNING: not actually tested with age(1) yet 11 | 12 | Plugin specification: https://hackmd.io/@str4d/age-plugin-spec 13 | 14 | 15 | Example 16 | --- 17 | 18 | Generate an identity: 19 | 20 | ```none 21 | $ ./age-plugin-fido 22 | # tap fido2 device 23 | < age1fido1yvzqylncrhhrnw8sz64shsg34jdeeths2h70kfw7hqqgznf26ke55y972xws6v74cdy2twsjss77g2xzwfkweejasgfkny6qe2fm64q0aqv5p 24 | < AGE-PLUGIN-FIDO-1YVZQYLNCRHHRNW8SZ64SHSG34JDEETHS2H70KFW7HQQGZNF26KE55Y972XWS6V74CDY2TWSJSS77G2XZWFKWEEJASGFKNY6QE2FM64QGMZC3C 25 | ``` 26 | 27 | Encapsulate a key: 28 | 29 | ```none 30 | $ ./age-plugin-fido --age-plugin=recipient-v1 31 | > -> add-recipient age1fido1yvzqylncrhhrnw8sz64shsg34jdeeths2h70kfw7hqqgznf26ke55y972xws6v74cdy2twsjss77g2xzwfkweejasgfkny6qe2fm64q0aqv5p 32 | > -> wrap-file-key 33 | > 4bgH0XAZjfFoWzu9kPEc1X3LLDtrJhqsVzKbrdpfFtw= 34 | > -> done 35 | # tap fido2 device 36 | < -> recipient-stanza 0 fido 7bupApLfkhzCgdmpgM6PcC1uDEIm3QZeSVjg2rgE3Ck= wx3Sr82kfmOQttx7ND7ic2uCpmxrA5Es6s+GUb9uAfM= 37 | < DiI1KqY+rLlliM0dUBPWwjJtBNWC2k9V3hFheWZ2izI2wPP/q1mvftAhsixI6zPY 38 | < dPjOM5s812VOgccOjJl51g== 39 | < -> done 40 | ``` 41 | 42 | Decapsulate the key: 43 | 44 | ```none 45 | $ ./age-plugin-fido --age-plugin=identity-v1 46 | > -> add-identity AGE-PLUGIN-FIDO-1YVZQYLNCRHHRNW8SZ64SHSG34JDEETHS2H70KFW7HQQGZNF26KE55Y972XWS6V74CDY2TWSJSS77G2XZWFKWEEJASGFKNY6QE2FM64QGMZC3C 47 | > -> recipient-stanza 0 fido 7bupApLfkhzCgdmpgM6PcC1uDEIm3QZeSVjg2rgE3Ck= wx3Sr82kfmOQttx7ND7ic2uCpmxrA5Es6s+GUb9uAfM= 48 | > DiI1KqY+rLlliM0dUBPWwjJtBNWC2k9V3hFheWZ2izI2wPP/q1mvftAhsixI6zPY 49 | > dPjOM5s812VOgccOjJl51g== 50 | > -> done 51 | # tap fido2 device 52 | < -> file-key 0 53 | < 4bgH0XAZjfFoWzu9kPEc1X3LLDtrJhqsVzKbrdpfFtw= 54 | < -> done 55 | ``` 56 | 57 | 58 | Format 59 | --- 60 | 61 | Identity and recipient share the same payload, a FIDO2 credential id 62 | created with relying party id `x-age://fido`. Identity is encoded with 63 | bech32 human-readable part `AGE-PLUGIN-FIDO-`, and recipient is encoded 64 | with `age1fido`. 65 | 66 | Recipient stanza has form: 67 | 68 | ```none 69 | fido 70 | 71 | ``` 72 | 73 | - `` is the whitespace-free base64 encoding of the 74 | SHA-256 hash of: 75 | 76 | - `AGEFIDO1` (US-ASCII text) 77 | - the 2-byte big-endian encoding of the number of bytes in the 78 | credential id 79 | - the credential id 80 | 81 | - `` is the whitespace-free base64 encoding of a 32-byte 82 | salt chosen independently uniformly at random for each wrapped key 83 | 84 | - `` is the line-folded base64 encoding of the 85 | ChaCha20-HMACSHA256-SIV ciphertext wrapping a key, with the 86 | credential id as associated data, under HMAC secret key obtained from 87 | the device with the given credential id and salt 88 | 89 | The identity and recipient store the same information, the credential 90 | id -- there is no private/public separation of powers. 91 | -------------------------------------------------------------------------------- /b64dec.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #include "b64dec.h" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | /* 36 | * b64dec(in, inlen, &out, &outlen) 37 | * 38 | * Decode base64 data from inlen bytes at in into a newly 39 | * allocated buffer of outlen bytes at out. Return 0 on success, 40 | * -1 on failure. 41 | * 42 | * XXX variable-time 43 | */ 44 | int 45 | b64dec(const char *in, size_t inlen, void **outp, size_t *outlenp) 46 | { 47 | EVP_ENCODE_CTX *ctx = NULL; 48 | void *out = NULL; 49 | int outlen, addendum; 50 | int error = -1; 51 | 52 | /* Create a base64-decoding context. */ 53 | if ((ctx = EVP_ENCODE_CTX_new()) == NULL) 54 | goto out; 55 | EVP_DecodeInit(ctx); 56 | 57 | /* Cheap overflow avoidance. */ 58 | if (inlen > SIZE_MAX/3 - 4 || inlen > INT_MAX) 59 | goto out; 60 | 61 | /* 62 | * Allocate a buffer of the maximum size. This may be larger 63 | * than we need, owing to whitespace and padding. 64 | */ 65 | outlen = (int)(3*inlen + 4 - 1)/4; 66 | assert(outlen >= 0); 67 | if ((out = malloc((size_t)outlen)) == NULL) 68 | goto out; 69 | 70 | /* Decode and determine the actual length. */ 71 | if (EVP_DecodeUpdate(ctx, out, &outlen, (const unsigned char *)in, 72 | (int)inlen) == -1) 73 | goto out; 74 | assert(outlen >= 0); 75 | assert(outlen <= (int)(3*inlen + 4 - 1)/4); 76 | 77 | /* Flush buffer. (XXX ???) */ 78 | if (EVP_DecodeFinal(ctx, out + outlen, &addendum) == -1) 79 | goto out; 80 | assert(addendum <= (int)(3*inlen + 4 - 1)/4 - outlen); 81 | outlen += addendum; 82 | 83 | /* Success! */ 84 | *outp = out; 85 | out = NULL; 86 | *outlenp = (size_t)outlen; 87 | error = 0; 88 | 89 | out: free(out); 90 | if (ctx) 91 | EVP_ENCODE_CTX_free(ctx); 92 | return error; 93 | } 94 | -------------------------------------------------------------------------------- /b64dec.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef B64DEC_H 28 | #define B64DEC_H 29 | 30 | #include 31 | #include 32 | 33 | int b64dec(const char *, size_t, void **, size_t *); 34 | 35 | #endif /* B64DEC_H */ 36 | -------------------------------------------------------------------------------- /b64write.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #include "b64write.h" 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | /* 36 | * b64write(buf, len, file, flags) 37 | * 38 | * Write len bytes at buf base64-encoded to the specified file 39 | * stream. flags may be 0, to wrap the output lines, or 40 | * BIO_FLAGS_BASE64_NO_NL, to create just base64 data without 41 | * whitespace. Return 0 on success, -1 on error. 42 | * 43 | * XXX variable-time 44 | */ 45 | int 46 | b64write(const void *buf, size_t len, FILE *file, int flags) 47 | { 48 | BIO *bio_file = NULL, *bio_b64 = NULL; 49 | int error = -1; 50 | 51 | if (len > INT_MAX) 52 | goto out; 53 | 54 | if ((bio_file = BIO_new_fp(file, BIO_NOCLOSE)) == NULL) 55 | goto out; 56 | if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL) 57 | goto out; 58 | BIO_set_flags(bio_b64, flags); 59 | BIO_push(bio_b64, bio_file); 60 | 61 | if (!BIO_write(bio_b64, buf, len)) 62 | goto out; 63 | if (BIO_flush(bio_b64) != 1) /* returns 0 _or_ -1 for failure */ 64 | goto out; 65 | 66 | /* Success! */ 67 | error = 0; 68 | 69 | out: if (bio_b64) 70 | BIO_free(bio_b64); 71 | if (bio_file) 72 | BIO_free(bio_file); 73 | return error; 74 | } 75 | -------------------------------------------------------------------------------- /b64write.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef B64WRITE_H 28 | #define B64WRITE_H 29 | 30 | #include 31 | #include 32 | 33 | int b64write(const void *, size_t, FILE *, int); 34 | 35 | #endif /* B64WRITE_H */ 36 | -------------------------------------------------------------------------------- /bech32.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | /* 28 | * Bech32 encoding of octet strings, as used in Zcash, based on the 29 | * somewhat more complicated encoding of Bitcoin BIP 173 segwit 30 | * addresses. 31 | * 32 | * Daira Hopwood, `Bech32 Format', Zcash Improvement Proposal, ZIP 33 | * 173, 2018-06-13. 34 | * https://zips.z.cash/zip-0173 35 | */ 36 | 37 | #define _POSIX_C_SOURCE 200809L 38 | 39 | #include "bech32.h" 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #include "ctassert.h" 52 | 53 | #define arraycount(A) (sizeof(A)/sizeof(*(A))) 54 | 55 | /* 56 | * bech32_tolower_8(out, in) 57 | * 58 | * Convert eight 7-bit US-ASCII code points in[0], in[1], ..., 59 | * in[7] to lowercase at out[0], out[1], ..., out[7] in constant 60 | * time. Returns -1 on error if any in[i] has the eighth bit set, 61 | * or 0 on success. 62 | */ 63 | static int 64 | bech32_tolower_8(char out[static 8], const char in[static 8]) 65 | { 66 | uint64_t x, y, error, mask; 67 | 68 | /* 69 | * Load input. byte order doesn't matter as long as it matches 70 | * on input and output -- each byte is independent. 71 | */ 72 | memcpy(&x, in, 8); 73 | 74 | /* 75 | * Input should be US-ASCII; take eighth bit as error 76 | * indicator, and then set it to borrow from while we work on 77 | * 7-bit units. 78 | */ 79 | error = x & UINT64_C(0x8080808080808080); 80 | y = x; 81 | x |= UINT64_C(0x8080808080808080); 82 | 83 | /* 84 | * Borrow if less than `A' (0x41): clear eighth bit in each 85 | * unit less than `A'. 86 | */ 87 | CTASSERT('A' == 0x41); 88 | mask = x - UINT64_C(0x4141414141414141); 89 | 90 | /* 91 | * Borrow if not greater than `Z' (0x5a) and invert: clear 92 | * eighth bit in each unit greater than `Z'. 93 | */ 94 | CTASSERT('Z' == 0x5a); 95 | mask &= ~(x - UINT64_C(0x5b5b5b5b5b5b5b5b)); 96 | 97 | /* 98 | * Clear all bits other than the borrow. After this point, the 99 | * eighth bit of each 8-bit unit is set iff that unit lies in 100 | * US-ASCII A-Z. 101 | */ 102 | mask &= 0x8080808080808080; 103 | 104 | /* Shift 0x80 to 0x20 to get the case-changing bit mask. */ 105 | mask >>= 2; 106 | assert(mask == (mask & 0x2020202020202020)); 107 | 108 | /* Change case. */ 109 | y ^= mask; 110 | 111 | /* Store output. */ 112 | memcpy(out, &y, 8); 113 | 114 | /* 115 | * Map zero to 0, nonzero to -1. In this case, all the nonzero 116 | * bits will be at positions 7 mod 8, so shift them to 0 mod 8 117 | * and then combine them all at 0. 118 | */ 119 | error >>= 7; 120 | error |= error >> 8; 121 | error |= error >> 16; 122 | error |= error >> 32; 123 | return -(error & 1); 124 | } 125 | 126 | /* 127 | * bech32_tolower(out, in, n) 128 | * 129 | * Convert n 7-bit US-ASCII code points in[0], in[1], ..., in[n-1] 130 | * to lowercase at out[0], out[1], ..., out[n-1] in constant time. 131 | * Returns -1 on error if any in[i] has the eighth bit set, or 0 132 | * on success. 133 | */ 134 | static int 135 | bech32_tolower(char *out, const char *in, size_t n) 136 | { 137 | int error = 0; 138 | 139 | for (; 8 <= n; out += 8, in += 8, n -= 8) 140 | error |= bech32_tolower_8(out, in); 141 | if (n) { 142 | char buf[8]; 143 | 144 | memcpy(buf, in, n); 145 | memset(buf + n, 0, 8 - n); 146 | error |= bech32_tolower_8(buf, buf); 147 | memcpy(out, buf, n); 148 | } 149 | 150 | return error; 151 | } 152 | 153 | /* 154 | * bech32_toupper_8(out, in) 155 | * 156 | * Convert eight 7-bit US-ASCII code points in[0], in[1], ..., 157 | * in[7] to uppercase at out[0], out[1], ..., out[7] in constant 158 | * time. Returns -1 on error if any in[i] has the eighth bit set, 159 | * or 0 on success. 160 | */ 161 | static int 162 | bech32_toupper_8(char out[static 8], const char in[static 8]) 163 | { 164 | uint64_t x, y, error, mask; 165 | 166 | /* 167 | * Load input. byte order doesn't matter as long as it matches 168 | * on input and output -- each byte is independent. 169 | */ 170 | memcpy(&x, in, 8); 171 | 172 | /* 173 | * Input should be US-ASCII; take eighth bit as error 174 | * indicator, and then set it to borrow from while we work on 175 | * 7-bit units. 176 | */ 177 | error = x & UINT64_C(0x8080808080808080); 178 | y = x; 179 | x |= UINT64_C(0x8080808080808080); 180 | 181 | /* 182 | * Borrow if less than `a' (0x61): clear eighth bit in each 183 | * unit less than `a'. 184 | */ 185 | CTASSERT('a' == 0x61); 186 | mask = x - UINT64_C(0x6161616161616161); 187 | 188 | /* 189 | * Borrow if not greater than `z' (0x7a) and invert: clear 190 | * eighth bit in each unit greater than `z'. 191 | */ 192 | CTASSERT('z' == 0x7a); 193 | mask &= ~(x - UINT64_C(0x7b7b7b7b7b7b7b7b)); 194 | 195 | /* 196 | * Clear all bits other than the borrow. After this point, the 197 | * eighth bit of each 8-bit unit is set iff that unit lies in 198 | * US-ASCII a-z. 199 | */ 200 | mask &= 0x8080808080808080; 201 | 202 | /* Shift 0x80 to 0x20 to get the case-changing bit mask. */ 203 | mask >>= 2; 204 | assert(mask == (mask & 0x2020202020202020)); 205 | 206 | /* Change case. */ 207 | y ^= mask; 208 | 209 | /* Store output. */ 210 | memcpy(out, &y, 8); 211 | 212 | /* 213 | * Map zero to 0, nonzero to -1. In this case, all the nonzero 214 | * bits will be at positions 7 mod 8, so shift them to 0 mod 8 215 | * and then combine them all at 0. 216 | */ 217 | error >>= 7; 218 | error |= error >> 8; 219 | error |= error >> 16; 220 | error |= error >> 32; 221 | return -(error & 1); 222 | } 223 | 224 | /* 225 | * bech32_toupper(out, in, n) 226 | * 227 | * Convert n 7-bit US-ASCII code points in[0], in[1], ..., in[n-1] 228 | * to uppercase at out[0], out[1], ..., out[n-1] in constant time. 229 | * Returns -1 on error if any in[i] has the eighth bit set, or 0 230 | * on success. 231 | */ 232 | static int 233 | bech32_toupper(char *out, const char *in, size_t n) 234 | { 235 | int error = 0; 236 | 237 | for (; 8 <= n; out += 8, in += 8, n -= 8) 238 | error |= bech32_toupper_8(out, in); 239 | if (n) { 240 | char buf[8]; 241 | 242 | memcpy(buf, in, n); 243 | memset(buf + n, 0, 8 - n); 244 | error |= bech32_toupper_8(buf, buf); 245 | memcpy(out, buf, n); 246 | } 247 | 248 | return error; 249 | } 250 | 251 | /* 252 | * Conservatively avoid overflow. We can actually handle substantially 253 | * larger inputs since the expansion is only a factor of 8/5, but this 254 | * saves the trouble of avoiding size_t overflow in the intermediate 255 | * quantity 8*n. 256 | */ 257 | #define BECH32_8TO5_SIZE_MAX ((SIZE_MAX - 4)/8) 258 | 259 | /* 260 | * bech32_8to5_size(n) 261 | * 262 | * Return the number of 5-bit groups needed to encode n 8-bit 263 | * groups, rounded up to include zero padding if necessary. 264 | * 265 | * n must be at most BECH32_8TO5_SIZE_MAX. 266 | */ 267 | static size_t 268 | bech32_8to5_size(size_t n8) 269 | { 270 | 271 | assert(n8 <= BECH32_8TO5_SIZE_MAX); 272 | return (8*n8 + 4)/5; 273 | } 274 | 275 | /* 276 | * bech32_8to5(d, nd, s, ns) 277 | * 278 | * Convert 8-bit groups to 5-bit groups: read ns bytes from s and 279 | * store up to nd bytes at d; nd must be at least (8*ns + 4)/5, 280 | * and ns must be at most BECH32_8TO5_SIZE_MAX. Use 281 | * bech32_8to5_size(ns) to compute the number of 5-bit groups 282 | * needed to encode ns 8-bit groups. 283 | */ 284 | static void 285 | bech32_8to5(uint8_t *d, size_t nd, const uint8_t *s, size_t ns) 286 | { 287 | 288 | assert(ns <= BECH32_8TO5_SIZE_MAX); 289 | assert(nd >= bech32_8to5_size(ns)); 290 | 291 | while (5 <= ns) { 292 | d[0] = ((s[0] & 0370) >> 3); 293 | d[1] = ((s[0] & 007) << 2) | ((s[1] & 0300) >> 6); 294 | d[2] = ((s[1] & 0076) >> 1); 295 | d[3] = ((s[1] & 001) << 4) | ((s[2] & 0360) >> 4); 296 | d[4] = ((s[2] & 017) << 1) | ((s[3] & 0200) >> 7); 297 | d[5] = ((s[3] & 0174) >> 2); 298 | d[6] = ((s[3] & 003) << 3) | ((s[4] & 0340) >> 5); 299 | d[7] = (s[4] & 037); 300 | 301 | d += 8, nd -= 8; 302 | s += 5, ns -= 5; 303 | } 304 | 305 | if (ns) { 306 | uint8_t s0, s1, s2, s3; 307 | 308 | assert(1 <= ns && ns <= 4); 309 | s0 = s[0]; 310 | s1 = (ns <= 1? 0 : s[1]); 311 | s2 = (ns <= 2? 0 : s[2]); 312 | s3 = (ns <= 3? 0 : s[3]); 313 | 314 | assert(2 <= nd); 315 | d[0] = ((s0 & 0370) >> 3); 316 | d[1] = ((s0 & 007) << 2) | ((s1 & 0300) >> 6); 317 | if (ns <= 1) 318 | return; 319 | assert(4 <= nd); 320 | d[2] = ((s1 & 0076) >> 1); 321 | d[3] = ((s1 & 001) << 4) | ((s2 & 0360) >> 4); 322 | if (ns <= 2) 323 | return; 324 | assert(5 <= nd); 325 | d[4] = ((s2 & 017) << 1) | ((s3 & 0200) >> 7); 326 | if (ns <= 3) 327 | return; 328 | assert(7 <= nd); 329 | d[5] = ((s3 & 0174) >> 2); 330 | d[6] = ((s3 & 003) << 3); 331 | } 332 | } 333 | 334 | /* Conservatively avoid overflowas for BECH32_8TO5_SIZE_MAX. */ 335 | #define BECH32_5TO8_SIZE_MAX (SIZE_MAX/5) 336 | 337 | /* 338 | * bech32_5to8_size(n) 339 | * 340 | * Return the number of 8-bit groups encoded by n 5-bit groups, 341 | * which may include discarded padding. 342 | * 343 | * n must be at most BECH32_5TO8_SIZE_MAX. 344 | */ 345 | static size_t 346 | bech32_5to8_size(size_t n5) 347 | { 348 | 349 | return (5*n5)/8; 350 | } 351 | 352 | /* 353 | * bech32_5to8(d, nd, s, ns) 354 | * 355 | * Convert 5-bit groups to 8-bit groups: read ns bytes from s and 356 | * store up to nd bytes at d; nd must be at least 5*ns/8, and ns 357 | * must be at most BECH32_5TO8_SIZE_MAX. Use bech32_5to8_size(ns) 358 | * to compute the number of 8-bit groups encoded by ns 5-bit 359 | * groups. 360 | * 361 | * Returns zero if padding is correct, and some nonzero value if 362 | * padding is invalid. 363 | */ 364 | static int 365 | bech32_5to8(uint8_t *d, size_t nd, const uint8_t *s, size_t ns) 366 | { 367 | 368 | assert(nd >= bech32_5to8_size(ns)); 369 | 370 | /* 371 | * ceiling(8*n/5) is always congruent to 0, 2, 4, 5, or 7 372 | * modulo 8; other lengths are not allowed. 373 | */ 374 | switch (ns % 8) { 375 | case 1: 376 | case 3: 377 | case 6: 378 | return -1; 379 | } 380 | 381 | while (8 <= ns) { 382 | uint8_t s0 = s[0], s1 = s[1], s2 = s[2], s3 = s[3]; 383 | uint8_t s4 = s[4], s5 = s[5], s6 = s[6], s7 = s[7]; 384 | 385 | d[0] = (s0 & 037) << 3 | (s1 & 034) >> 2; 386 | d[1] = (s1 & 003) << 6 | s2 << 1 | (s3 & 020) >> 4; 387 | d[2] = (s3 & 017) << 4 | (s4 & 036) >> 1; 388 | d[3] = (s4 & 001) << 7 | s5 << 2 | (s6 & 030) >> 3; 389 | d[4] = (s6 & 007) << 5 | s7; 390 | 391 | d += 5, nd -= 5; 392 | s += 8, ns -= 8; 393 | } 394 | 395 | if (ns) { 396 | uint8_t s0, s1, s2, s3, s4, s5, s6; 397 | 398 | assert(1 <= ns && ns <= 7); 399 | s0 = s[0]; 400 | s1 = (ns <= 2? 0 : s[1]); 401 | s2 = (ns <= 2? 0 : s[2]); 402 | s3 = (ns <= 4? 0 : s[3]); 403 | s4 = (ns <= 4? 0 : s[4]); 404 | s5 = (ns <= 5? 0 : s[5]); 405 | s6 = 0; 406 | 407 | assert(1 <= nd); 408 | d[0] = (s0 & 037) << 3 | (s1 & 034) >> 2; 409 | if (ns <= 2) 410 | return s1 & 003; 411 | assert(2 <= nd); 412 | d[1] = (s1 & 003) << 6 | s2 << 1 | (s3 & 020) >> 4; 413 | if (ns <= 4) 414 | return s3 & 017; 415 | assert(3 <= nd); 416 | d[2] = (s3 & 017) << 4 | (s4 & 036) >> 1; 417 | if (ns <= 5) 418 | return s4 & 001; 419 | assert(4 <= nd); 420 | d[3] = (s4 & 001) << 7 | s5 << 2 | (s6 & 030) >> 3; 421 | } 422 | 423 | return 0; 424 | } 425 | 426 | static uint8_t bech32tab[32] = { 427 | 'q','p','z','r', 'y','9','x','8', /* 0..7 */ 428 | 'g','f','2','t', 'v','d','w','0', /* 8..15 */ 429 | 's','3','j','n', '5','4','k','h', /* 16..23 */ 430 | 'c','e','6','m', 'u','a','7','l', /* 24..31 */ 431 | }; 432 | 433 | /* 434 | * bech32_b2c_8(out, in) 435 | * 436 | * Convert eight 5-bit groups from binary integer values in[0], 437 | * in[1], ..., in[7] to characters out[0], out[1], ..., out[7] in 438 | * the bech32 set, in constant time. 439 | * 440 | * Caller is responsible for ensuring in[i] == in[i] & 0x1f. 441 | */ 442 | static void 443 | bech32_b2c_8(char out[static 8], const uint8_t in[static 8]) 444 | { 445 | uint64_t out64 = 0; 446 | uint64_t in64; 447 | uint64_t i; 448 | 449 | /* 450 | * We use 8 bits to store each unit: 7 bits for the data (5 451 | * bits for each 5-bit input unit, 7 bits for each US-ASCII 452 | * output unit), and 1 bit to borrow from in order to test for 453 | * equality. 454 | */ 455 | 456 | /* 457 | * Load input. byte order doesn't matter as long as it matches 458 | * on input and output -- each byte is independent. 459 | */ 460 | memcpy(&in64, in, 8); 461 | assert(in64 == (in64 & UINT64_C(0x1f1f1f1f1f1f1f1f))); 462 | 463 | for (i = 0; (i & 0x20) == 0; i += UINT64_C(0x0101010101010101)) { 464 | uint64_t m, c; 465 | 466 | /* Create masks: all ones if equal, all zeros if no. */ 467 | m = in64 ^ i; /* zero if equal */ 468 | assert(m == (m & UINT64_C(0x1f1f1f1f1f1f1f1f))); 469 | m |= UINT64_C(0x2020202020202020); /* set high bit */ 470 | m -= UINT64_C(0x0101010101010101); /* borrow if zero */ 471 | m &= UINT64_C(0x2020202020202020); /* clear low bits */ 472 | m >>= 5; /* smear */ 473 | assert(m == (m & UINT64_C(0x0101010101010101))); 474 | m |= m << 1; 475 | assert(m == (m & UINT64_C(0x0303030303030303))); 476 | m |= m << 2; 477 | assert(m == (m & UINT64_C(0x0f0f0f0f0f0f0f0f))); 478 | m |= m << 3; 479 | assert(m == (m & UINT64_C(0x7f7f7f7f7f7f7f7f))); 480 | 481 | /* Copy the table entry to all positions. */ 482 | c = bech32tab[i & 0x1f]; 483 | c |= c << 8; 484 | c |= c << 16; 485 | c |= c << 32; 486 | assert(c == (c & UINT64_C(0x7f7f7f7f7f7f7f7f))); 487 | 488 | /* Conditional swap. */ 489 | out64 = (out64 & m) | (c & ~m); 490 | assert(out64 == (out64 & UINT64_C(0x7f7f7f7f7f7f7f7f))); 491 | } 492 | 493 | /* Store output. */ 494 | memcpy(out, &out64, 8); 495 | } 496 | 497 | /* 498 | * bech32_b2c(out, in, n) 499 | * 500 | * Convert 5-bit groups from binary integer values in[0], in[1], 501 | * ..., in[n-1] to characters out[0], out[1], ..., out[n-1] in 502 | * the bech32 set, in constant time. 503 | */ 504 | static void 505 | bech32_b2c(char *out, const uint8_t *in, size_t n) 506 | { 507 | 508 | while (8 <= n) { 509 | bech32_b2c_8(out, in); 510 | out += 8; 511 | in += 8; 512 | n -= 8; 513 | } 514 | 515 | if (n) { 516 | uint8_t buf[8]; 517 | 518 | memcpy(buf, in, n); 519 | memset(buf + n, 0, 8 - n); 520 | bech32_b2c_8((char *)buf, buf); 521 | memcpy(out, buf, n); 522 | } 523 | } 524 | 525 | /* 526 | * bech32_c2b_8(out, in) 527 | * 528 | * Convert eight 5-bit groups from characters in[0], in[1], ..., 529 | * in[7] in the bech32 set to binary integer values out[0], 530 | * out[1], ..., out[7], in constant time. 531 | * 532 | * Returns 0 if the encoding is valid, -1 if invalid. 533 | */ 534 | static int 535 | bech32_c2b_8(uint8_t out[static 8], const char in[static 8]) 536 | { 537 | uint64_t out64 = UINT64_C(0x8080808080808080); /* error indicators */ 538 | uint64_t in64; 539 | uint64_t i; 540 | uint64_t error = 0; 541 | 542 | /* 543 | * We use 8 bits to store each unit: 7 bits for the data (7 544 | * bits for each US-ASCII input unit, 5 bits for each 5-bit 545 | * output unit), and 1 bit to borrow from in order to test for 546 | * equality. 547 | */ 548 | 549 | /* 550 | * Load input. Byte order doesn't matter as long as it matches 551 | * on input and output -- each byte is independent. 552 | */ 553 | memcpy(&in64, in, 8); 554 | 555 | /* 556 | * Input should be US-ASCII; take eighth bit as error 557 | * indicator, and then clear it while we work on 7-bit units. 558 | */ 559 | error |= in64 & UINT64_C(0x8080808080808080); 560 | in64 &= ~UINT64_C(0x8080808080808080); 561 | 562 | for (i = 0; (i & 0x20) == 0; i += UINT64_C(0x0101010101010101)) { 563 | uint64_t m, c; 564 | 565 | /* Copy the table entry to all positions. */ 566 | c = bech32tab[i & 0x1f]; 567 | c |= c << 8; 568 | c |= c << 16; 569 | c |= c << 32; 570 | assert(c == (c & UINT64_C(0x7f7f7f7f7f7f7f7f))); 571 | 572 | /* Create masks: all ones if equal, all zeros if not. */ 573 | m = in64 ^ c; /* zero if equal */ 574 | assert(m == (m & UINT64_C(0x7f7f7f7f7f7f7f7f))); 575 | m |= UINT64_C(0x8080808080808080); /* set high bit */ 576 | m -= UINT64_C(0x0101010101010101); /* borrow if zero */ 577 | m &= UINT64_C(0x8080808080808080); /* clear low bits */ 578 | m >>= 7; /* smear */ 579 | assert(m == (m & UINT64_C(0x0101010101010101))); 580 | m |= m << 1; 581 | assert(m == (m & UINT64_C(0x0303030303030303))); 582 | m |= m << 2; 583 | assert(m == (m & UINT64_C(0x0f0f0f0f0f0f0f0f))); 584 | m |= m << 4; 585 | 586 | /* Conditional swap. */ 587 | out64 = (out64 & m) | (i & ~m); 588 | } 589 | 590 | /* 591 | * If any error indicators are still there in out64, report 592 | * them as error. 593 | */ 594 | error |= out64 & UINT64_C(0x8080808080808080); 595 | 596 | /* 597 | * Clear error indicators to avoid violating invariants 598 | * downstream that assume only bits 0x1f are set. 599 | */ 600 | out64 &= UINT64_C(0x7f7f7f7f7f7f7f7f); 601 | 602 | /* Store output. */ 603 | memcpy(out, &out64, 8); 604 | 605 | /* 606 | * Map zero to 0, nonzero to -1. In this case, all the nonzero 607 | * bits will be at positions 7 mod 8, so shift them to 0 mod 8 608 | * and then combine them all at 0. 609 | */ 610 | error >>= 7; 611 | error |= error >> 8; 612 | error |= error >> 16; 613 | error |= error >> 32; 614 | return -(error & 1); 615 | } 616 | 617 | /* 618 | * bech32_c2b(out, in, n) 619 | * 620 | * Convert 5-bit groups from characters in[0], in[1], ..., 621 | * in[n-1]] in the bech32 set to binary integer values out[0], 622 | * out[1], ..., out[n-1], in constant time. 623 | * 624 | * Returns 0 if the encoding is valid, -1 if invalid. 625 | */ 626 | static int 627 | bech32_c2b(uint8_t *out, const char *in, size_t n) 628 | { 629 | int error = 0; 630 | 631 | while (8 <= n) { 632 | error |= bech32_c2b_8(out, in); 633 | out += 8; 634 | in += 8; 635 | n -= 8; 636 | } 637 | 638 | if (n) { 639 | uint8_t buf[8]; 640 | 641 | memcpy(buf, in, n); 642 | memset(buf + n, 'q', 8 - n); 643 | error |= bech32_c2b_8(buf, (const char *)buf); 644 | memcpy(out, buf, n); 645 | } 646 | 647 | return error; 648 | } 649 | 650 | /* 651 | * bech32 checksum 652 | */ 653 | 654 | static inline uint32_t 655 | bch_step(uint32_t c, uint8_t x) 656 | { 657 | uint32_t b = c >> 25; 658 | 659 | assert(x == (x & 0x1f)); 660 | 661 | c &= UINT32_C(0x01ffffff); 662 | c <<= 5; 663 | c ^= x; 664 | 665 | c ^= UINT32_C(0x3b6a57b2) & -(b & 1); b >>= 1; 666 | c ^= UINT32_C(0x26508e6d) & -(b & 1); b >>= 1; 667 | c ^= UINT32_C(0x1ea119fa) & -(b & 1); b >>= 1; 668 | c ^= UINT32_C(0x3d4233dd) & -(b & 1); b >>= 1; 669 | c ^= UINT32_C(0x2a1462b3) & -(b & 1); b >>= 1; 670 | 671 | return c; 672 | } 673 | 674 | static uint32_t 675 | bch_hrp(uint32_t c, const uint8_t *x, size_t n) 676 | { 677 | size_t i; 678 | 679 | for (i = 0; i < n; i++) 680 | c = bch_step(c, x[i] >> 5); 681 | c = bch_step(c, 0); 682 | for (i = 0; i < n; i++) 683 | c = bch_step(c, x[i] & 0x1f); 684 | 685 | return c; 686 | } 687 | 688 | static uint32_t 689 | bch_data(uint32_t c, const uint8_t *x, size_t n) 690 | { 691 | size_t i; 692 | 693 | for (i = 0; i < n; i++) 694 | c = bch_step(c, x[i]); 695 | 696 | return c; 697 | } 698 | 699 | static void 700 | bech32_cksum(uint8_t ck[static 6], const void *hrp, size_t nhrp, 701 | const void *data, size_t ndata) 702 | { 703 | uint32_t c = 1; 704 | unsigned i; 705 | 706 | c = bch_hrp(c, hrp, nhrp); 707 | c = bch_data(c, data, ndata); 708 | for (i = 0; i < 6; i++) 709 | c = bch_step(c, 0); 710 | c ^= 1; 711 | 712 | ck[0] = (c >> 25) & 0x1f; 713 | ck[1] = (c >> 20) & 0x1f; 714 | ck[2] = (c >> 15) & 0x1f; 715 | ck[3] = (c >> 10) & 0x1f; 716 | ck[4] = (c >> 5) & 0x1f; 717 | ck[5] = c & 0x1f; 718 | } 719 | 720 | static uint32_t 721 | bech32_verify(const void *hrp, size_t nhrp, 722 | const void *datacksum, size_t ndatacksum) 723 | { 724 | uint32_t c = 1; 725 | 726 | c = bch_hrp(c, hrp, nhrp); 727 | c = bch_data(c, datacksum, ndatacksum); 728 | 729 | return c - 1; 730 | } 731 | 732 | /* 733 | * bech32 format 734 | */ 735 | 736 | #define BECH32_CKSUMLEN 6u 737 | 738 | /* HRP (nonempty) || `1' || cksum */ 739 | #define BECH32_MIN (1u + 1u + BECH32_CKSUMLEN) 740 | 741 | #define BECH32_DATA_MAX (BECH32_MAX - BECH32_MIN) 742 | 743 | CTASSERT(BECH32_HRP_MAX == BECH32_MAX - 1 - BECH32_CKSUMLEN); 744 | CTASSERT(BECH32_PAYLOAD_MAX == (5*(BECH32_MAX - BECH32_MIN))/8); 745 | CTASSERT(BECH32_PAYLOAD_MAX <= BECH32_8TO5_SIZE_MAX); 746 | 747 | /* 748 | * bech32enc_size(nhrp, npayload) 749 | * 750 | * Returns the number of characters in the bech32 encoding for 751 | * given HRP and payload lengths, which must be at most 752 | * BECH32_HRP_MAX and BECH32_PAYLOAD_MAX. The result is at most 753 | * BECH32_MAX, and does not include space for a NUL terminator. 754 | */ 755 | int 756 | bech32enc_size(size_t nhrp, size_t npayload) 757 | { 758 | size_t nbech32; 759 | 760 | assert(nhrp <= BECH32_HRP_MAX); 761 | assert(npayload <= BECH32_PAYLOAD_MAX); 762 | 763 | nbech32 = nhrp + 1 + bech32_8to5_size(npayload) + BECH32_CKSUMLEN; 764 | 765 | return (nbech32 <= BECH32_MAX ? (int)nbech32 : -1); 766 | } 767 | 768 | /* 769 | * bech32dec_size(nhrp, nbech32) 770 | * 771 | * Returns the number of bytes encoded in a bech32 string of 772 | * nbech32 characters long, not including a NUL terminator, with 773 | * an HRP of length nhrp. nhrp must be at most BECH32_HRP_MAX, 774 | * and nbech32 must be at most BECH32_MAX. The result is at most 775 | * BECH32_PAYLOAD_MAX. 776 | */ 777 | int 778 | bech32dec_size(size_t nhrp, size_t nbech32) 779 | { 780 | size_t npayload; 781 | 782 | assert(nhrp <= BECH32_HRP_MAX); 783 | assert(nbech32 <= BECH32_MAX); 784 | 785 | npayload = bech32_5to8_size(nbech32 - nhrp - 1 - BECH32_CKSUMLEN); 786 | 787 | return (npayload <= BECH32_PAYLOAD_MAX ? (int)npayload : -1); 788 | } 789 | 790 | /* 791 | * bech32enc(bech32, nbech32, hrp, nhrp, payload, npayload) 792 | * 793 | * Encode the given nhrp-byte HRP and npayload-byte payload in the 794 | * nbech32-byte buffer at bech32, and NUL-terminate the buffer. 795 | * Return -1 on failure (if any of the sizes involved are 796 | * invalid), or the number of bytes in the bech32 encoding, 797 | * excluding the NUL terminator, on success. 798 | * 799 | * Note: This encodes lowercase bech32. Caller is responsible for 800 | * specifying a lowercase HRP. 801 | * 802 | * bech32enc runs in time independent of the values of hrp[0], 803 | * hrp[1], ..., hrp[n - 1] and payload[0], payload[1], ..., 804 | * payload[npayload - 1]. However, the timing does depend on the 805 | * values of nhrp and npayload. 806 | */ 807 | int 808 | bech32enc(char *bech32, size_t nbech32, const void *hrp, size_t nhrp, 809 | const void *payload, size_t npayload) 810 | { 811 | uint8_t datacksum[BECH32_DATA_MAX + BECH32_CKSUMLEN]; 812 | size_t ndata; 813 | 814 | if (nhrp == 0) 815 | return -1; 816 | if (nhrp > BECH32_HRP_MAX) 817 | return -1; 818 | if (npayload > BECH32_PAYLOAD_MAX) 819 | return -1; 820 | ndata = bech32_8to5_size(npayload); 821 | assert(ndata <= BECH32_DATA_MAX); 822 | CTASSERT(BECH32_DATA_MAX <= SIZE_MAX - 1 - BECH32_CKSUMLEN - 1); 823 | if (nbech32 < nhrp + 1 + ndata + BECH32_CKSUMLEN + 1) 824 | return -1; 825 | if (nhrp + 1 + ndata + BECH32_CKSUMLEN > BECH32_MAX) 826 | return -1; 827 | 828 | /* Copy the HRP and `1'. */ 829 | memcpy(bech32, hrp, nhrp); 830 | bech32[nhrp] = '1'; 831 | 832 | /* Convert 8-bit groups to 5-bit groups in our temporary buffer. */ 833 | bech32_8to5(datacksum, ndata, payload, npayload); 834 | 835 | /* Compute the checksum. */ 836 | bech32_cksum(datacksum + ndata, hrp, nhrp, datacksum, ndata); 837 | assert(bech32_verify(hrp, nhrp, datacksum, ndata + BECH32_CKSUMLEN) 838 | == 0); 839 | 840 | /* Encode 5-bit groups. */ 841 | bech32_b2c(bech32 + nhrp + 1, datacksum, ndata + BECH32_CKSUMLEN); 842 | 843 | /* NUL-terminate. */ 844 | bech32[nhrp + 1 + ndata + BECH32_CKSUMLEN] = '\0'; 845 | 846 | /* Return the length of the output string, excluding NUL. */ 847 | assert(nhrp + 1 + ndata + BECH32_CKSUMLEN <= INT_MAX); 848 | return (int)(nhrp + 1 + ndata + BECH32_CKSUMLEN); 849 | } 850 | 851 | /* 852 | * bech32enc_upper(bech32, nbech32, hrp, nhrp, payload, npayload) 853 | * 854 | * Encode the given nhrp-byte HRP and npayload-byte payload in the 855 | * nbech32-byte buffer at bech32, and NUL-terminate the buffer. 856 | * Return -1 on failure (if any of the sizes involved are 857 | * invalid), or the number of bytes in the bech32 encoding, 858 | * excluding the NUL terminator, on success. 859 | * 860 | * Note: This encodes uppercase bech32. Caller is responsible for 861 | * specifying a _lowercase_ HRP, not an uppercase HRP. 862 | * 863 | * bech32enc runs in time independent of the values of hrp[0], 864 | * hrp[1], ..., hrp[n - 1] and payload[0], payload[1], ..., 865 | * payload[npayload - 1]. However, the timing does depend on the 866 | * values of nhrp and npayload. 867 | */ 868 | int 869 | bech32enc_upper(char *bech32, size_t nbech32, const void *hrp, size_t nhrp, 870 | const void *payload, size_t npayload) 871 | { 872 | int n; 873 | int error; 874 | 875 | n = bech32enc(bech32, nbech32, hrp, nhrp, payload, npayload); 876 | if (n == -1) 877 | return -1; 878 | assert(n >= 0); 879 | assert(bech32[n] == '\0'); 880 | error = bech32_toupper(bech32, bech32, (size_t)n); 881 | (void)error; 882 | assert(error == 0); 883 | 884 | return n; 885 | } 886 | 887 | /* 888 | * bech32dec(payload, npayload, hrp, nhrp, bech32, nbech32) 889 | * 890 | * Verify and decode the nbech32-byte string at bech32. Return -1 891 | * on failure (mismatched HRP, not all uppercase or all lowercase, 892 | * wrong character set, bad checksum), or the number of bytes 893 | * encoded by the bech32 string on success. 894 | * 895 | * Note: Caller is responsible for specifying a valid lowercase 896 | * HRP. 897 | * 898 | * bech32dec runs in time independent of the values of hrp[0], 899 | * hrp[1], ..., hrp[n - 1] and bech32[0], bech32[1], ..., 900 | * bech32[nbech32 - 1]. However, the timing does depend on the 901 | * values of nhrp and nbech32. 902 | */ 903 | int 904 | bech32dec(void *payload, size_t npayload, const void *hrp, size_t nhrp, 905 | const char *bech32, size_t nbech32) 906 | { 907 | char buf[BECH32_MAX + 1]; 908 | uint8_t datacksum[BECH32_DATA_MAX + BECH32_CKSUMLEN]; 909 | size_t i, ndata; 910 | int notupper, notlower, error = 0; 911 | 912 | if (nhrp == 0) /* hrp must be nonempty */ 913 | return -1; 914 | if (nbech32 < BECH32_MIN || nbech32 > BECH32_MAX) 915 | return -1; 916 | CTASSERT(BECH32_MIN >= BECH32_CKSUMLEN + 1); 917 | if (nbech32 - BECH32_CKSUMLEN - 1 < nhrp) 918 | return -1; 919 | 920 | /* Convert to uppercase and see whether it matches. */ 921 | error |= bech32_toupper(buf, bech32, nbech32); 922 | for (notupper = 0, i = 0; i < nbech32; i++) 923 | notupper |= bech32[i] ^ buf[i]; 924 | 925 | /* 926 | * Convert to lowercase and see whether it matches. Leave the 927 | * buffer as lowercase for subsequent processing. 928 | */ 929 | error |= bech32_tolower(buf, bech32, nbech32); 930 | for (notlower = 0, i = 0; i < nbech32; i++) 931 | notlower |= bech32[i] ^ buf[i]; 932 | 933 | /* Map zero to 0, nonzero to -1. */ 934 | notupper = ~((notupper - 1) >> 8); 935 | notlower = ~((notlower - 1) >> 8); 936 | 937 | /* Error if notlower and notupper. */ 938 | error |= notupper & notlower; 939 | 940 | /* Verify that bech32 starts with hrp followed by `1'. */ 941 | for (i = 0; i < nhrp; i++) 942 | error |= buf[i] ^ ((const char *)hrp)[i]; 943 | error |= buf[nhrp] ^ '1'; 944 | 945 | /* Determine the length of the encoded data part. */ 946 | ndata = nbech32 - nhrp - 1 - BECH32_CKSUMLEN; 947 | if (npayload < bech32_5to8_size(ndata)) 948 | return -1; 949 | 950 | /* Decode 5-bit groups. */ 951 | error |= bech32_c2b(datacksum, buf + nhrp + 1, 952 | ndata + BECH32_CKSUMLEN); 953 | 954 | /* Verify the checksum. */ 955 | error |= bech32_verify(hrp, nhrp, datacksum, ndata + BECH32_CKSUMLEN); 956 | 957 | /* Convert 5-bit groups in our temporary buffer to 8-bit groups. */ 958 | error |= bech32_5to8(payload, npayload, datacksum, ndata); 959 | 960 | /* Map zero error to 0, nonzero error to 1. */ 961 | error |= error >> 1; 962 | error |= error >> 2; 963 | error |= error >> 4; 964 | error |= error >> 8; 965 | error |= error >> 16; 966 | error &= 1; 967 | 968 | /* Return -1 on error, true length of payload on success. */ 969 | return -error | bech32_5to8_size(ndata); 970 | } 971 | -------------------------------------------------------------------------------- /bech32.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef BECH32_H 28 | #define BECH32_H 29 | 30 | #include 31 | 32 | #define BECH32_MAX 1024u 33 | #define BECH32_HRP_MAX 1017u 34 | #define BECH32_PAYLOAD_MAX 635u 35 | 36 | int bech32enc_size(size_t, size_t); 37 | int bech32dec_size(size_t, size_t); 38 | 39 | int bech32enc(char *, size_t, const void *, size_t, const void *, size_t); 40 | int bech32dec(void *, size_t, const void *, size_t, const char *, size_t); 41 | 42 | int bech32enc_upper(char *, size_t, const void *, size_t, 43 | const void *, size_t); 44 | 45 | #endif /* BECH32_H */ 46 | -------------------------------------------------------------------------------- /ctassert.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef CTASSERT_H 28 | #define CTASSERT_H 29 | 30 | #if __STDC_VERSION__ >= 201112L 31 | #include 32 | #define CTASSERT(x) static_assert(x, #x) 33 | #else 34 | #ifdef __COUNTER__ 35 | #define CTASSERT(x) CTASSERT1(x, ctassert, __COUNTER__) 36 | #else 37 | #define CONCAT(u,v) u##v 38 | #define CTASSERT(x) CTASSERT0(x, __INCLUDE_LEVEL__, __LINE__) 39 | #define CTASSERT0(x,u,v) CTASSERT1(x, CONCAT(level_,u), CONCAT(line_,v)) 40 | #endif 41 | #define CTASSERT1(x,u,v) CTASSERT2(x,u,v) 42 | #define CTASSERT2(x,u,v) \ 43 | struct ctassert_##u##_##v { \ 44 | unsigned int u##v : ((x) ? 1 : -1); \ 45 | } 46 | #endif 47 | 48 | #endif /* CTASSERT_H */ 49 | -------------------------------------------------------------------------------- /dae.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C -*- */ 2 | 3 | /*- 4 | * Copyright (c) 2020 Taylor R. Campbell 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /* 30 | * Deterministic authenticated encryption with HMAC-SHA256 and ChaCha20 31 | * in SIV -- won't break any speed records but it'll serve for this 32 | * low-performance application, and everyone and their dog has the 33 | * parts lying around handy. 34 | * 35 | * Given key, header, and payload, the 32-byte tag is 36 | * 37 | * HMAC-SHA256(key, header || payload || 38 | * le64(nbytes(header)) || le64(nbytes(payload)) || 0), 39 | * 40 | * the derived 32-byte subkey is 41 | * 42 | * HMAC-SHA256(key, tag || 1), 43 | * 44 | * and the (unauthenticated) ciphertext is 45 | * 46 | * payload ^ ChaCha20_subkey(0); 47 | * 48 | * finally, the authenticated ciphertext is the concatenation 49 | * 50 | * tag || (payload ^ ChaCha20_subkey(0)). 51 | * 52 | * Decryption and verification are defined the obvious way. The 53 | * tag is a commitment to the key and the payload as long as 54 | * HMAC-SHA256 is collision-resistant. 55 | */ 56 | 57 | #include "dae.h" 58 | 59 | #include 60 | 61 | #include 62 | #include 63 | 64 | struct DAE_CTX { 65 | HMAC_CTX *hmac; 66 | EVP_CIPHER_CTX *cipher; 67 | }; 68 | 69 | static void 70 | dae_fini(struct DAE_CTX *D) 71 | { 72 | 73 | if (D->cipher) 74 | EVP_CIPHER_CTX_free(D->cipher); 75 | if (D->hmac) 76 | HMAC_CTX_free(D->hmac); 77 | } 78 | 79 | static int 80 | dae_init(struct DAE_CTX *D, const uint8_t key[static DAE_KEYBYTES]) 81 | { 82 | 83 | memset(D, 0, sizeof(*D)); 84 | if ((D->hmac = HMAC_CTX_new()) == NULL) 85 | goto fail; 86 | if (!HMAC_Init_ex(D->hmac, key, DAE_KEYBYTES, EVP_sha256(), NULL)) 87 | goto fail; 88 | if ((D->cipher = EVP_CIPHER_CTX_new()) == NULL) 89 | goto fail; 90 | 91 | return 1; 92 | 93 | fail: dae_fini(D); 94 | return 0; 95 | } 96 | 97 | static void 98 | enc64le(void *buf, uint64_t x) 99 | { 100 | uint8_t *p = buf; 101 | 102 | p[0] = x & 0xff; 103 | p[1] = (x >> 8) & 0xff; 104 | p[2] = (x >> 16) & 0xff; 105 | p[3] = (x >> 24) & 0xff; 106 | p[4] = (x >> 32) & 0xff; 107 | p[5] = (x >> 40) & 0xff; 108 | p[6] = (x >> 48) & 0xff; 109 | p[7] = (x >> 56) & 0xff; 110 | } 111 | 112 | static int 113 | auth(uint8_t tag[static DAE_TAGBYTES], 114 | const uint8_t *h, size_t nh, 115 | const uint8_t *m, size_t nm, 116 | struct DAE_CTX *D) 117 | { 118 | uint8_t len64[16]; 119 | uint8_t ds = 0; /* domain separation */ 120 | int ok = 0; 121 | 122 | /* HMAC API takes size as int. */ 123 | if (nh > INT_MAX) 124 | goto out; 125 | if (nm > INT_MAX) 126 | goto out; 127 | 128 | if (!HMAC_Init_ex(D->hmac, NULL, 0, NULL, NULL)) 129 | goto out; 130 | if (!HMAC_Update(D->hmac, h, (int)nh)) 131 | goto out; 132 | if (!HMAC_Update(D->hmac, m, (int)nm)) 133 | goto out; 134 | enc64le(&len64[0], nh); 135 | enc64le(&len64[8], nm); 136 | if (!HMAC_Update(D->hmac, len64, 16)) 137 | goto out; 138 | if (!HMAC_Update(D->hmac, &ds, 1)) 139 | goto out; 140 | if (!HMAC_Final(D->hmac, tag, NULL)) 141 | goto out; 142 | 143 | /* Success! */ 144 | ok = 1; 145 | 146 | out: return ok; 147 | } 148 | 149 | static int 150 | kdf(uint8_t subkey[static 32], 151 | const uint8_t tag[static DAE_TAGBYTES], 152 | struct DAE_CTX *D) 153 | { 154 | uint8_t ds = 1; 155 | int ok = 0; 156 | 157 | if (!HMAC_Init_ex(D->hmac, NULL, 0, NULL, NULL)) 158 | goto out; 159 | if (!HMAC_Update(D->hmac, tag, DAE_TAGBYTES)) 160 | goto out; 161 | if (!HMAC_Update(D->hmac, &ds, 1)) 162 | goto out; 163 | if (!HMAC_Final(D->hmac, subkey, NULL)) 164 | goto out; 165 | 166 | /* Success! */ 167 | ok = 1; 168 | 169 | out: return ok; 170 | } 171 | 172 | static int 173 | stream_xor(uint8_t *out, const uint8_t *in, size_t n, 174 | const uint8_t tag[DAE_TAGBYTES], struct DAE_CTX *D) 175 | { 176 | static const uint8_t noncectr[16]; 177 | uint8_t subkey[32]; 178 | int outl; 179 | int ok = 0; 180 | 181 | /* EVP_CipherUpdate takes size as int. */ 182 | if (n > INT_MAX) 183 | goto out; 184 | 185 | if (!kdf(subkey, tag, D)) 186 | goto out; 187 | if (!EVP_EncryptInit(D->cipher, EVP_chacha20(), subkey, noncectr)) 188 | goto out; 189 | if (!EVP_CipherUpdate(D->cipher, out, &outl, in, (int)n)) 190 | goto out; 191 | if (outl != (int)n) 192 | goto out; 193 | 194 | /* Success! */ 195 | ok = 1; 196 | 197 | out: OPENSSL_cleanse(subkey, sizeof(subkey)); 198 | return ok; 199 | } 200 | 201 | int 202 | dae_encrypt(uint8_t c[static DAE_TAGBYTES], 203 | const uint8_t *h, size_t nh, 204 | const uint8_t *m, size_t nm, 205 | const uint8_t key[static DAE_KEYBYTES]) 206 | { 207 | struct DAE_CTX ctx; 208 | size_t nc; 209 | int ok = 0; 210 | 211 | if (nm > SIZE_MAX - DAE_TAGBYTES) 212 | return 0; 213 | nc = nm + DAE_TAGBYTES; 214 | 215 | if (!dae_init(&ctx, key)) 216 | goto out; 217 | if (!auth(c, h, nh, m, nm, &ctx)) 218 | goto out; 219 | if (!stream_xor(c + DAE_TAGBYTES, m, nm, c, &ctx)) 220 | goto out; 221 | 222 | /* Success! */ 223 | ok = 1; 224 | 225 | out: dae_fini(&ctx); 226 | if (!ok) 227 | OPENSSL_cleanse(c, nc); 228 | return ok; 229 | } 230 | 231 | int 232 | dae_decrypt(uint8_t *m, 233 | const uint8_t *h, size_t nh, 234 | const uint8_t c[static DAE_TAGBYTES], size_t nc, 235 | const uint8_t key[static DAE_KEYBYTES]) 236 | { 237 | struct DAE_CTX ctx; 238 | uint8_t tag[DAE_TAGBYTES]; 239 | size_t nm; 240 | int ok = 0; 241 | 242 | if (nc < DAE_TAGBYTES) 243 | return 0; 244 | nm = nc - DAE_TAGBYTES; 245 | 246 | if (!dae_init(&ctx, key)) 247 | goto out; 248 | if (!stream_xor(m, c + DAE_TAGBYTES, nm, c, &ctx)) 249 | goto out; 250 | if (!auth(tag, h, nh, m, nm, &ctx)) 251 | goto out; 252 | if (CRYPTO_memcmp(c, tag, DAE_TAGBYTES) != 0) 253 | goto out; 254 | 255 | /* Success! */ 256 | ok = 1; 257 | 258 | out: dae_fini(&ctx); 259 | OPENSSL_cleanse(tag, sizeof(tag)); 260 | if (!ok) 261 | OPENSSL_cleanse(m, nm); 262 | return ok; 263 | } 264 | -------------------------------------------------------------------------------- /dae.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C -*- */ 2 | 3 | /*- 4 | * Copyright (c) 2020 Taylor R. Campbell 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #ifndef DAE_H 30 | #define DAE_H 31 | 32 | #include 33 | #include 34 | 35 | #define DAE_KEYBYTES 32u 36 | #define DAE_TAGBYTES 32u 37 | 38 | int dae_encrypt(uint8_t[static DAE_TAGBYTES], 39 | const uint8_t *, size_t, 40 | const uint8_t *, size_t, 41 | const uint8_t[static DAE_KEYBYTES]); 42 | int dae_decrypt(uint8_t *, 43 | const uint8_t *, size_t, 44 | const uint8_t[static DAE_TAGBYTES], size_t, 45 | const uint8_t[static DAE_KEYBYTES]); 46 | 47 | #endif /* DAE_H */ 48 | -------------------------------------------------------------------------------- /freadline.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2015 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #define _POSIX_C_SOURCE 200809L 28 | 29 | #include "freadline.h" 30 | 31 | #include 32 | #include 33 | 34 | int 35 | freadline(char *buf, size_t size, size_t *lenp, FILE *stream) 36 | { 37 | char *p = buf; 38 | int ch, ret = 0; 39 | 40 | if (size == 0) { 41 | *lenp = 0; 42 | return 0; 43 | } 44 | 45 | flockfile(stream); 46 | while (size --> 1) { 47 | if ((ch = getc_unlocked(stream)) == EOF) { 48 | ret = EOF; 49 | break; 50 | } 51 | *p++ = ch; 52 | if (ch == '\n') 53 | break; 54 | } 55 | funlockfile(stream); 56 | 57 | *p = '\0'; 58 | *lenp = (size_t)(p - buf); 59 | return ret; 60 | } 61 | -------------------------------------------------------------------------------- /freadline.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2015 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef FREADLINE_H 28 | #define FREADLINE_H 29 | 30 | #include 31 | #include 32 | 33 | int freadline(char *, size_t, size_t *, FILE *); 34 | 35 | #endif /* FREADLINE_H */ 36 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | /* 28 | * XXX TODO: 29 | * 30 | * - Report errors according to protocol, not with errx(1, "..."). 31 | * 32 | * - Give feedback in the key-unwrapping protocol about when we need 33 | * device interaction. 34 | * 35 | * - Consider supporting PINs for FIDO2 devices. 36 | * 37 | * - Read from all devices on system in parallel. 38 | */ 39 | 40 | #define _POSIX_C_SOURCE 200809L 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | 50 | #include 51 | 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #include "b64dec.h" 58 | #include "b64write.h" 59 | #include "bech32.h" 60 | #include "ctassert.h" 61 | #include "dae.h" 62 | #include "freadline.h" 63 | #include "progname.h" 64 | #include "reallocn.h" 65 | #include "strprefix.h" 66 | 67 | #define arraycount(A) (sizeof(A)/sizeof(*(A))) 68 | 69 | #define AGEFIDO_RP_ID "x-age://fido" 70 | #define AGEFIDO_HRP "age1fido" 71 | #define AGEFIDO_ID_HRP "age-plugin-fido-" 72 | #define AGEFIDO_HASHTAG "AGEFIDO1" 73 | #define AGEFIDO_HASHTAGLEN 8 74 | 75 | static fido_dev_t * 76 | opendev(const char *devpath) 77 | { 78 | fido_dev_t *dev = NULL; 79 | int error; 80 | 81 | /* Create a fido dev representative. */ 82 | if ((dev = fido_dev_new()) == NULL) 83 | errx(1, "fido_dev_new"); 84 | 85 | if (devpath) { 86 | /* If the user provided a device path, just open it. */ 87 | if ((error = fido_dev_open(dev, devpath)) != FIDO_OK) 88 | errx(1, "fido_dev_open: %s", fido_strerr(error)); 89 | } else { 90 | /* None provided -- try the first one from the system. */ 91 | fido_dev_info_t *devlist = NULL; 92 | const fido_dev_info_t *devinfo; 93 | size_t ndevs = 0; 94 | 95 | if ((devlist = fido_dev_info_new(1)) == NULL) 96 | errx(1, "fido_dev_info_new"); 97 | if ((error = fido_dev_info_manifest(devlist, 1, &ndevs)) 98 | != FIDO_OK) 99 | errx(1, "fido_dev_info_manifest: %s", 100 | fido_strerr(error)); 101 | if (ndevs < 1) 102 | errx(1, "no devices found"); 103 | if ((devinfo = fido_dev_info_ptr(devlist, 0)) == NULL) 104 | errx(1, "fido_dev_info_ptr"); 105 | if ((error = fido_dev_open(dev, fido_dev_info_path(devinfo))) 106 | != FIDO_OK) 107 | errx(1, "fido_dev_open: %s", fido_strerr(error)); 108 | fido_dev_info_free(&devlist, ndevs); 109 | } 110 | 111 | return dev; 112 | } 113 | 114 | static void * 115 | enroll(size_t *ncredential_idp) 116 | { 117 | uint8_t challenge[32]; 118 | fido_dev_t *dev = NULL; 119 | fido_cred_t *cred = NULL; 120 | const void *credential_id; 121 | size_t ncredential_id; 122 | void *credidcopy = NULL; 123 | int error; 124 | 125 | /* Generate a challenge. */ 126 | if (RAND_bytes(challenge, sizeof(challenge)) != 1) 127 | errx(1, "RAND_bytes"); 128 | 129 | /* Create the credential and set its parameters. */ 130 | if ((cred = fido_cred_new()) == NULL) 131 | errx(1, "fido_cred_new"); 132 | if ((error = fido_cred_set_type(cred, COSE_ES256)) != FIDO_OK) 133 | errx(1, "fido_cred_set_type: %s", fido_strerr(error)); 134 | if ((error = fido_cred_set_rp(cred, AGEFIDO_RP_ID, NULL)) != FIDO_OK) 135 | errx(1, "fido_cred_set_rp: %s", fido_strerr(error)); 136 | if ((error = fido_cred_set_user(cred, 137 | (const unsigned char *)"age", strlen("age"), 138 | /*user_name*/"age(1)", /*displayname*/NULL, /*icon*/NULL)) 139 | != FIDO_OK) 140 | errx(1, "fido_cred_set_user: %s", fido_strerr(error)); 141 | if ((error = fido_cred_set_clientdata_hash(cred, 142 | challenge, sizeof(challenge))) != FIDO_OK) 143 | errx(1, "fido_cred_set_clientdata_hash: %s", 144 | fido_strerr(error)); 145 | 146 | /* Open a device. */ 147 | dev = opendev(NULL); 148 | 149 | /* Make the credential. */ 150 | if ((error = fido_dev_make_cred(dev, cred, NULL)) != FIDO_OK) { 151 | (void)fido_dev_cancel(dev); 152 | errx(1, "fido_dev_make_cred: %s", fido_strerr(error)); 153 | } 154 | 155 | /* Close the device -- we're done with it now. */ 156 | fido_dev_close(dev); 157 | 158 | /* Get the credential id. */ 159 | if ((credential_id = fido_cred_id_ptr(cred)) == NULL || 160 | (ncredential_id = fido_cred_id_len(cred)) == 0) 161 | errx(1, "missingfido_cred_id"); 162 | 163 | /* Verify the credential. */ 164 | if (fido_cred_x5c_ptr(cred) == NULL) { 165 | if ((error = fido_cred_verify_self(cred)) != FIDO_OK) 166 | errx(1, "fido_cred_verify_self: %s", 167 | fido_strerr(error)); 168 | } else { 169 | if ((error = fido_cred_verify(cred)) != FIDO_OK) 170 | errx(1, "fido_cred_verify: %s", fido_strerr(error)); 171 | } 172 | 173 | /* 174 | * Copy the credential id. We don't care about anything else 175 | * like the public key. 176 | */ 177 | if ((credidcopy = malloc(ncredential_id)) == NULL) 178 | err(1, "malloc"); 179 | memcpy(credidcopy, credential_id, ncredential_id); 180 | 181 | /* Success! */ 182 | fido_cred_free(&cred); 183 | fido_dev_free(&dev); 184 | OPENSSL_cleanse(challenge, sizeof(challenge)); 185 | 186 | *ncredential_idp = ncredential_id; 187 | return credidcopy; 188 | } 189 | 190 | static void * 191 | encap(const void *credential_id, size_t ncredential_id, 192 | const void *salt, size_t nsalt, 193 | const void *payload, size_t npayload, 194 | size_t *lenp) 195 | { 196 | uint8_t challenge[32]; 197 | fido_dev_t *dev = NULL; 198 | fido_assert_t *assertion = NULL; 199 | const unsigned char *hmacsecret; 200 | size_t nhmacsecret; 201 | unsigned char *ciphertext = NULL; 202 | size_t nciphertext = 0; 203 | int error; 204 | 205 | /* Sanity-check the salt and payload sizes. */ 206 | if (nsalt != 32) 207 | errx(1, "invalid salt size: %zu", nsalt); 208 | if (npayload > SIZE_MAX - DAE_TAGBYTES) 209 | errx(1, "payload too long"); 210 | 211 | /* Generate a challenge. */ 212 | if (RAND_bytes(challenge, sizeof(challenge)) != 1) 213 | errx(1, "RAND_bytes"); 214 | 215 | /* Create the assertion and set its parameters. */ 216 | if ((assertion = fido_assert_new()) == NULL) 217 | errx(1, "fido_assert_new"); 218 | if ((error = fido_assert_set_rp(assertion, AGEFIDO_RP_ID)) != FIDO_OK) 219 | errx(1, "fido_assert_set_rp: %s", fido_strerr(error)); 220 | if ((error = fido_assert_set_clientdata_hash(assertion, 221 | challenge, sizeof(challenge))) != FIDO_OK) 222 | errx(1, "fido_assert_set_clientdata_hash: %s", 223 | fido_strerr(error)); 224 | if ((error = fido_assert_allow_cred(assertion, credential_id, 225 | ncredential_id)) != FIDO_OK) 226 | errx(1, "fido_assert_allow_cred: %s", fido_strerr(error)); 227 | if ((error = fido_assert_set_extensions(assertion, 228 | FIDO_EXT_HMAC_SECRET)) 229 | != FIDO_OK) 230 | errx(1, "fido_assert_set_extensions(FIDO_EXT_HMAC_SECRET): %s", 231 | fido_strerr(error)); 232 | if ((error = fido_assert_set_hmac_salt(assertion, salt, nsalt)) 233 | != FIDO_OK) 234 | errx(1, "fido_assert_set_hmac_salt: %s", fido_strerr(error)); 235 | 236 | /* Open a device. */ 237 | dev = opendev(NULL); 238 | 239 | /* Get an assertion response. */ 240 | if ((error = fido_dev_get_assert(dev, assertion, NULL)) != FIDO_OK) { 241 | (void)fido_dev_cancel(dev); 242 | errx(1, "fido_dev_get_assert: %s", fido_strerr(error)); 243 | } 244 | 245 | /* Close the device -- we're done with it now. */ 246 | fido_dev_close(dev); 247 | 248 | /* Verify we got an assertion response. */ 249 | if (fido_assert_count(assertion) != 1) 250 | errx(1, "failed to get one assertion response"); 251 | 252 | /* Get the HMAC secret. It should be 32 bytes long. */ 253 | hmacsecret = fido_assert_hmac_secret_ptr(assertion, 0); 254 | nhmacsecret = fido_assert_hmac_secret_len(assertion, 0); 255 | if (nhmacsecret != 32) 256 | errx(1, "invalid hmac secret length: %zu", nhmacsecret); 257 | 258 | /* Allocate ciphertext. */ 259 | nciphertext = npayload + DAE_TAGBYTES; 260 | if ((ciphertext = malloc(nciphertext)) == NULL) 261 | err(1, "malloc"); 262 | CTASSERT(DAE_KEYBYTES == 32); 263 | if (!dae_encrypt(ciphertext, credential_id, ncredential_id, 264 | payload, npayload, hmacsecret)) 265 | errx(1, "encrypt failed"); 266 | 267 | /* Success! */ 268 | fido_assert_free(&assertion); 269 | fido_dev_free(&dev); 270 | OPENSSL_cleanse(challenge, sizeof(challenge)); 271 | 272 | *lenp = nciphertext; 273 | return ciphertext; 274 | } 275 | 276 | static void * 277 | decap(const void *credential_id, size_t ncredential_id, 278 | const void *salt, size_t nsalt, 279 | const void *ciphertext, size_t nciphertext, 280 | size_t *lenp) 281 | { 282 | uint8_t challenge[32]; 283 | fido_dev_t *dev = NULL; 284 | fido_assert_t *assertion = NULL; 285 | const unsigned char *hmacsecret; 286 | size_t nhmacsecret; 287 | unsigned char *payload = NULL; 288 | size_t npayload = 0; 289 | int error; 290 | 291 | /* Sanity-check the salt and ciphertext sizes. */ 292 | if (nsalt != 32) 293 | errx(1, "invalid salt size: %zu", nsalt); 294 | if (nciphertext < DAE_TAGBYTES) 295 | errx(1, "ciphertext too short"); 296 | 297 | /* Generate a challenge. */ 298 | if (RAND_bytes(challenge, sizeof(challenge)) != 1) 299 | errx(1, "RAND_bytes"); 300 | 301 | /* Create the assertion and set its parameters. */ 302 | if ((assertion = fido_assert_new()) == NULL) 303 | errx(1, "fido_assert_new"); 304 | if ((error = fido_assert_set_rp(assertion, AGEFIDO_RP_ID)) != FIDO_OK) 305 | errx(1, "fido_assert_set_rp: %s", fido_strerr(error)); 306 | if ((error = fido_assert_set_clientdata_hash(assertion, 307 | challenge, sizeof(challenge))) != FIDO_OK) 308 | errx(1, "fido_assert_set_clientdata_hash: %s", 309 | fido_strerr(error)); 310 | if ((error = fido_assert_allow_cred(assertion, credential_id, 311 | ncredential_id)) != FIDO_OK) 312 | errx(1, "fido_assert_allow_cred: %s", fido_strerr(error)); 313 | if ((error = fido_assert_set_extensions(assertion, 314 | FIDO_EXT_HMAC_SECRET)) 315 | != FIDO_OK) 316 | errx(1, "fido_assert_set_extensions(FIDO_EXT_HMAC_SECRET): %s", 317 | fido_strerr(error)); 318 | if ((error = fido_assert_set_hmac_salt(assertion, salt, nsalt)) 319 | != FIDO_OK) 320 | errx(1, "fido_assert_set_hmac_salt: %s", fido_strerr(error)); 321 | 322 | /* Open a device. */ 323 | dev = opendev(NULL); 324 | 325 | /* Get an assertion response. */ 326 | if ((error = fido_dev_get_assert(dev, assertion, NULL)) != FIDO_OK) { 327 | (void)fido_dev_cancel(dev); 328 | errx(1, "fido_dev_get_assert: %s", fido_strerr(error)); 329 | } 330 | 331 | /* Close the device -- we're done with it now. */ 332 | fido_dev_close(dev); 333 | 334 | /* Verify we got an assertion response. */ 335 | if (fido_assert_count(assertion) != 1) 336 | errx(1, "failed to get one assertion response"); 337 | 338 | /* Get the HMAC secret. It should be 32 bytes long. */ 339 | hmacsecret = fido_assert_hmac_secret_ptr(assertion, 0); 340 | nhmacsecret = fido_assert_hmac_secret_len(assertion, 0); 341 | if (nhmacsecret != 32) 342 | errx(1, "invalid hmac secret length: %zu", nhmacsecret); 343 | 344 | /* Allocate payload. */ 345 | npayload = nciphertext - DAE_TAGBYTES; 346 | if ((payload = malloc(npayload)) == NULL) 347 | err(1, "malloc"); 348 | CTASSERT(DAE_KEYBYTES == 32); 349 | if (!dae_decrypt(payload, credential_id, ncredential_id, 350 | ciphertext, nciphertext, hmacsecret)) 351 | errx(1, "decrypt failed"); /* XXX return gracefully */ 352 | 353 | /* Success! */ 354 | fido_assert_free(&assertion); 355 | fido_dev_free(&dev); 356 | OPENSSL_cleanse(challenge, sizeof(challenge)); 357 | 358 | *lenp = npayload; 359 | return payload; 360 | } 361 | 362 | static void 363 | hashcred(uint8_t hash[static SHA256_DIGEST_LENGTH], 364 | const void *credential_id, size_t ncredential_id) 365 | { 366 | SHA256_CTX ctx; 367 | unsigned char len16be[2]; 368 | 369 | assert(ncredential_id <= 0xffff); 370 | 371 | len16be[0] = (ncredential_id >> 8) & 0xff; 372 | len16be[0] = ncredential_id & 0xff; 373 | 374 | SHA256_Init(&ctx); 375 | SHA256_Update(&ctx, AGEFIDO_HASHTAG, AGEFIDO_HASHTAGLEN); 376 | SHA256_Update(&ctx, len16be, sizeof(len16be)); 377 | SHA256_Update(&ctx, credential_id, ncredential_id); 378 | SHA256_Final(hash, &ctx); 379 | } 380 | 381 | static bool 382 | eat(char **bufp, size_t *lenp, const char *prefix) 383 | { 384 | 385 | if (strprefix(*bufp, *lenp, prefix) != 0) 386 | return false; 387 | *bufp += strlen(prefix); 388 | *lenp -= strlen(prefix); 389 | return true; 390 | } 391 | 392 | char buf[1024]; 393 | 394 | static void 395 | read_b64_until_cmd(unsigned char **bufp, size_t *lenp, size_t *cmdlenp) 396 | { 397 | EVP_ENCODE_CTX *ctx; 398 | size_t len; 399 | int n0, n; 400 | 401 | /* Initialize the output buffer to empty. */ 402 | *bufp = NULL; 403 | *lenp = 0; 404 | 405 | /* Create a base64-decoding context. */ 406 | if ((ctx = EVP_ENCODE_CTX_new()) == NULL) 407 | errx(1, "EVP_ENCODE_CTX_new"); 408 | EVP_DecodeInit(ctx); 409 | 410 | /* Read lines until we get a `-> ' line. */ 411 | for (;;) { 412 | if (freadline(buf, sizeof buf, &len, stdin) == EOF) 413 | errx(1, "premature EOF"); 414 | 415 | if (len == 0 || buf[len - 1] != '\n') 416 | errx(1, "invalid payload"); 417 | 418 | /* 419 | * If the line starts with `-> ', we're done -- break 420 | * out of the loop and back into parsing the command in 421 | * buf. 422 | */ 423 | if (strprefix(buf, len, "-> ") == 0) 424 | break; 425 | 426 | /* 427 | * Otherwise, the line may encode up to 428 | * (3/4)*sizeof(buf) bytes of data -- possibly fewer, 429 | * considering base64 padding and whitespace. 430 | * 431 | * Compute an upper bound n0 on the number of bytes 432 | * this might add, as an int because OpenSSL's 433 | * EVP_DecodeUpdate deals only in int sizes; prove that 434 | * the intermediate does not overflow int. 435 | */ 436 | CTASSERT(sizeof buf <= (INT_MAX - 4 + 1)/3); 437 | n0 = (3*len + 4 - 1)/4; 438 | 439 | /* 440 | * Stop here if this would overflow the maximum key 441 | * length. We arbitrarily set 2048 bytes as the 442 | * maximum -- this is plenty for, e.g., RSA-4096. 443 | */ 444 | CTASSERT((3*sizeof buf + 4 - 1)/4 < 2048); 445 | if (*lenp > (size_t)(2048 - n0)) 446 | errx(1, "payload too long"); 447 | 448 | /* Ensure there's enough space. */ 449 | if (reallocn(bufp, *lenp + n0, 1) == -1) 450 | err(1, "realloc"); 451 | 452 | /* Decode the data and extend the buffer. */ 453 | if (EVP_DecodeUpdate(ctx, *bufp + *lenp, &n, 454 | (const unsigned char *)buf, len) == -1) 455 | errx(1, "invalid base64"); 456 | assert(n >= 0); 457 | assert(n <= n0); 458 | *lenp += n; 459 | } 460 | 461 | if (EVP_DecodeFinal(ctx, *bufp + *lenp, &n) == -1) 462 | errx(1, "invalid base64"); 463 | assert(n >= 0); 464 | *lenp += n; 465 | 466 | /* Done with the base64-decoding context. */ 467 | EVP_ENCODE_CTX_free(ctx); 468 | 469 | /* Return the command length we just read. */ 470 | *cmdlenp = len; 471 | } 472 | 473 | static void 474 | do_keygen(void) 475 | { 476 | void *credential_id = NULL; 477 | size_t ncredential_id = 0; 478 | char bech32[BECH32_MAX + 1]; 479 | int n; 480 | 481 | credential_id = enroll(&ncredential_id); 482 | if (ncredential_id > BECH32_PAYLOAD_MAX) 483 | errx(1, "overlong credential id: %zu bytes", ncredential_id); 484 | 485 | /* Print the recipient. */ 486 | if (bech32enc(bech32, sizeof(bech32), 487 | AGEFIDO_HRP, strlen(AGEFIDO_HRP), 488 | credential_id, ncredential_id) == -1) 489 | errx(1, "bech32enc"); 490 | printf("%s\n", bech32); 491 | 492 | /* Print the identity. */ 493 | if ((n = bech32enc_upper(bech32, sizeof(bech32), 494 | AGEFIDO_ID_HRP, strlen(AGEFIDO_ID_HRP), 495 | credential_id, ncredential_id)) == -1) 496 | errx(1, "bech32enc"); 497 | printf("%s\n", bech32); 498 | 499 | OPENSSL_cleanse(bech32, sizeof(bech32)); 500 | OPENSSL_cleanse(credential_id, ncredential_id); 501 | free(credential_id); 502 | } 503 | 504 | static void 505 | do_recipient(void) 506 | { 507 | struct { 508 | char *bech32; 509 | unsigned char *credential_id; 510 | size_t ncredential_id; 511 | uint8_t *sha256; 512 | } recips[32], *R; 513 | unsigned nrecips = 0; 514 | struct { 515 | unsigned char *buf; 516 | unsigned len; 517 | } keys[32], *K; 518 | unsigned nkeys = 0; 519 | struct { 520 | unsigned char salt[32]; 521 | unsigned char *ciphertext; 522 | size_t nciphertext; 523 | } *keywraps, *KW; 524 | char *p; 525 | size_t n; 526 | unsigned i, j; 527 | 528 | /* 529 | * Phase 1 [client, uni-directional] 530 | */ 531 | for (;;) { 532 | /* Read a line into buf. */ 533 | if (freadline(buf, sizeof buf, &n, stdin) == EOF) 534 | errx(1, "read command"); 535 | 536 | parsecmd: /* Parse the n-byte line in buf, including trailing LF. */ 537 | p = buf; 538 | 539 | /* Verify the line has form `-> ...\n' and back up over LF. */ 540 | if (!eat(&p, &n, "-> ") || n == 0 || p[n - 1] != '\n') 541 | errx(1, "bad command (n=%zu) %s", n, buf); 542 | 543 | /* NUL-terminate the line without LF. */ 544 | p[--n] = '\0'; 545 | 546 | /* Dispatch on the command. */ 547 | if (eat(&p, &n, "add-recipient")) { 548 | /* 549 | * Verify there's arguments after the command, 550 | * separated by a space. 551 | */ 552 | if (n-- == 0 || *p++ != ' ') 553 | errx(1, "bad add-recipient command"); 554 | 555 | /* Verify we haven't filled the recipient list. */ 556 | if (nrecips >= arraycount(recips)) 557 | errx(1, "too many recipients"); 558 | 559 | /* Copy the recipient and put it at the end. */ 560 | if ((recips[nrecips++].bech32 = strndup(p, n)) == NULL) 561 | err(1, "strndup"); 562 | 563 | } else if (eat(&p, &n, "wrap-file-key")) { 564 | unsigned char *key; 565 | size_t keylen; 566 | 567 | /* Verify there are no arguments. */ 568 | if (n) 569 | errx(1, "bad wrap-file-key command"); 570 | 571 | /* Verify we haven't filled the key list. */ 572 | if (nkeys >= arraycount(keys)) 573 | errx(1, "too many keys"); 574 | 575 | /* 576 | * Read the key to wrap from the base64 body of 577 | * the stanza. This leaves the first `-> ' 578 | * line after the stanza in buf, or aborts if 579 | * anything went wrong. 580 | */ 581 | read_b64_until_cmd(&key, &keylen, &n); 582 | 583 | /* Verify the key was nonempty. */ 584 | /* XXX Is this really a problem? */ 585 | if (key == NULL) 586 | errx(1, "empty key"); 587 | 588 | /* Put the key at the end of the list. */ 589 | keys[nkeys].buf = key; 590 | keys[nkeys].len = keylen; 591 | nkeys++; 592 | 593 | /* 594 | * We already have a command in the buffer -- 595 | * go back to parsing it. 596 | */ 597 | goto parsecmd; 598 | 599 | } else if (eat(&p, &n, "done")) { 600 | /* Verify there are no arguments. */ 601 | if (n) 602 | errx(1, "bad done command"); 603 | 604 | /* Break out of this phase of the state machine. */ 605 | break; 606 | 607 | } else { 608 | errx(1, "unknown command"); 609 | } 610 | } 611 | 612 | /* Validate and decode all the recipients. */ 613 | for (i = 0; i < nrecips; i++) { 614 | int n; 615 | 616 | R = &recips[i]; 617 | 618 | /* 619 | * Find the length of the credential id and allocate a 620 | * buffer for it. 621 | */ 622 | if ((n = bech32dec_size(strlen(AGEFIDO_HRP), 623 | strlen(R->bech32))) == -1) 624 | errx(1, "invalid recipient"); 625 | assert(n >= 0); 626 | if ((R->credential_id = malloc(n ? n : 1)) == NULL) 627 | err(1, "malloc"); 628 | 629 | /* 630 | * Decode and verify the credential id. This silently 631 | * accepts both uppercase and lowercase bech32; it is 632 | * the age client's responsibility to verify the 633 | * recipient is lowercase. 634 | */ 635 | if (bech32dec(R->credential_id, (size_t)n, 636 | AGEFIDO_HRP, strlen(AGEFIDO_HRP), 637 | R->bech32, strlen(R->bech32)) != n) 638 | errx(1, "invalid recipient"); 639 | R->ncredential_id = (size_t)n; 640 | 641 | /* Hash the credential id. */ 642 | if ((R->sha256 = malloc(SHA256_DIGEST_LENGTH)) == NULL) 643 | err(1, "malloc"); 644 | hashcred(R->sha256, R->credential_id, R->ncredential_id); 645 | } 646 | 647 | /* Encapsulate all the keys. */ 648 | CTASSERT(arraycount(recips) <= SIZE_MAX/arraycount(keys)); 649 | if ((keywraps = calloc(nrecips*nkeys, sizeof(keywraps[0]))) == NULL) 650 | err(1, "calloc"); 651 | for (i = 0; i < nrecips; i++) { 652 | R = &recips[i]; 653 | for (j = 0; j < nkeys; j++) { 654 | K = &keys[j]; 655 | KW = &keywraps[nrecips*i + j]; 656 | 657 | if (RAND_bytes(KW->salt, sizeof(KW->salt)) != 1) 658 | errx(1, "RAND_bytes"); 659 | KW->ciphertext = 660 | encap(R->credential_id, R->ncredential_id, 661 | KW->salt, sizeof(KW->salt), 662 | K->buf, K->len, 663 | &KW->nciphertext); 664 | } 665 | } 666 | 667 | /* 668 | * Phase 2 [plugin, uni-directional] 669 | */ 670 | for (i = 0; i < nrecips; i++) { 671 | R = &recips[i]; 672 | 673 | /* Print all the keys wrapped for this recipient. */ 674 | for (j = 0; j < nkeys; j++) { 675 | KW = &keywraps[nrecips*i + j]; 676 | 677 | printf("-> recipient-stanza %d fido ", j); 678 | b64write(R->sha256, SHA256_DIGEST_LENGTH, stdout, 679 | BIO_FLAGS_BASE64_NO_NL); 680 | printf(" "); 681 | b64write(KW->salt, sizeof(KW->salt), stdout, 682 | BIO_FLAGS_BASE64_NO_NL); 683 | printf("\n"); 684 | b64write(KW->ciphertext, KW->nciphertext, stdout, 0); 685 | } 686 | } 687 | printf("-> done\n"); 688 | } 689 | 690 | static void 691 | do_identity(void) 692 | { 693 | struct { 694 | char *bech32; 695 | unsigned char *credential_id; 696 | size_t ncredential_id; 697 | unsigned char *sha256; 698 | } ids[32], *I; 699 | unsigned nids = 0; 700 | struct { 701 | int file_index; 702 | char *credhash_b64; 703 | char *salt_b64; 704 | unsigned char *buf; 705 | unsigned len; 706 | } *stanzas = NULL, *S; 707 | size_t nstanzas = 0; 708 | char *p, *q; 709 | int file_index; 710 | size_t i, j, n; 711 | 712 | /* 713 | * Phase 1 [client, uni-directional] 714 | */ 715 | for (;;) { 716 | /* Read a line into buf. */ 717 | if (freadline(buf, sizeof buf, &n, stdin) == EOF) 718 | errx(1, "read command"); 719 | 720 | parsecmd: /* Parse the n-byte line in buf, including trailing LF. */ 721 | p = buf; 722 | 723 | /* Verify the line has form `-> ...\n' and back up over LF. */ 724 | if (!eat(&p, &n, "-> ") || n == 0 || p[--n] != '\n') 725 | errx(1, "bad command"); 726 | 727 | /* NUL-terminate the line without LF. */ 728 | p[n] = '\0'; 729 | 730 | /* Dispatch on the command. */ 731 | if (eat(&p, &n, "add-identity")) { 732 | /* 733 | * Verify there's arguments after the command, 734 | * separated by a space. 735 | */ 736 | if (n-- == 0 || *p++ != ' ') 737 | errx(1, "bad add-identity command"); 738 | 739 | /* Verify we haven't filled the identity list. */ 740 | if (nids >= arraycount(ids)) 741 | errx(1, "too many identities"); 742 | 743 | /* Copy the identity and put it at the end. */ 744 | if ((ids[nids++].bech32 = strndup(p, n)) == NULL) 745 | err(1, "strndup"); 746 | 747 | } else if (eat(&p, &n, "recipient-stanza")) { 748 | unsigned char *ciphertext; 749 | size_t clen; 750 | int m = -1; 751 | 752 | /* Allocate a stanza. */ 753 | if (reallocn(&stanzas, nstanzas + 1, 754 | sizeof(stanzas[0])) == -1) 755 | err(1, "realloc"); 756 | 757 | S = &stanzas[nstanzas++]; 758 | 759 | /* Parse stanza number and verify type. */ 760 | sscanf(p, " %d fido %n", &S->file_index, &m); 761 | if (m == -1) 762 | /* XXX print, not err */ 763 | errx(1, "invalid recipient"); 764 | if (S->file_index < 0) 765 | errx(1, "invalid file index"); 766 | assert(m > 0); 767 | assert((size_t)m <= n); 768 | p += m; 769 | n -= m; 770 | 771 | if ((q = strchr(p, ' ')) == NULL) 772 | /* XXX print, not err */ 773 | errx(1, "invalid recipient"); 774 | *q++ = '\0'; 775 | if ((S->credhash_b64 = strdup(p)) == NULL || 776 | (S->salt_b64 = strdup(q)) == NULL) 777 | err(1, "strdup"); 778 | 779 | /* 780 | * Read the ciphertext from the base64 body of 781 | * the stanza. This leaves the first `-> ' 782 | * line after the stanza in buf, or aborts if 783 | * anything went wrong. 784 | */ 785 | read_b64_until_cmd(&ciphertext, &clen, &n); 786 | 787 | /* Verify the ciphertext was nonempty. */ 788 | if (ciphertext == NULL) 789 | errx(1, "empty ciphertext"); 790 | 791 | /* Put the key at the end of the list. */ 792 | S->buf = ciphertext; 793 | S->len = clen; 794 | nstanzas++; 795 | 796 | /* 797 | * We already have a command in the buffer -- 798 | * go back to parsing it. 799 | */ 800 | goto parsecmd; 801 | 802 | } else if (eat(&p, &n, "done")) { 803 | /* Verify there are no arguments. */ 804 | if (n) 805 | errx(1, "bad done command"); 806 | 807 | /* Break out of this phase of the state machine. */ 808 | break; 809 | 810 | } else { 811 | /* 812 | * Unknown stanza type. Consume and ignore the 813 | * stanza body until we hit `-> ' -- `Unknown 814 | * stanza types MUST be ignored by the plugin.' 815 | */ 816 | while (freadline(buf, sizeof buf, &n, stdin) != EOF) { 817 | /* Verify the line ends with LF. */ 818 | if (n == 0 || buf[n - 1] != '\n') 819 | errx(1, "invalid payload"); 820 | 821 | /* If it's a new command, back to the top. */ 822 | if (strprefix(buf, n, "-> ") == 0) 823 | goto parsecmd; 824 | } 825 | 826 | /* We only get here if freadline returned EOF. */ 827 | errx(1, "premature EOF"); 828 | } 829 | } 830 | 831 | /* Verify that all the identities are valid and decode them. */ 832 | for (i = 0; i < nids; i++) { 833 | int n; 834 | 835 | I = &ids[i]; 836 | 837 | /* 838 | * Find the length of the credential id and allocate a 839 | * buffer for it. 840 | */ 841 | if ((n = bech32dec_size(strlen(AGEFIDO_ID_HRP), 842 | strlen(I->bech32))) == -1) 843 | errx(1, "invalid identity length"); 844 | assert(n >= 0); 845 | if ((I->credential_id = malloc(n ? n : 1)) == NULL) 846 | err(1, "malloc"); 847 | I->ncredential_id = (size_t)n; 848 | 849 | /* 850 | * Decode the credential id. This silently accepts 851 | * both uppercase and lowercase bech32; it is the age 852 | * client's responsibility to verify the identity is 853 | * uppercase. 854 | */ 855 | if (bech32dec(I->credential_id, I->ncredential_id, 856 | AGEFIDO_ID_HRP, strlen(AGEFIDO_ID_HRP), 857 | I->bech32, strlen(I->bech32)) == -1) 858 | errx(1, "invalid identity"); 859 | 860 | /* Hash the credential id. */ 861 | if ((I->sha256 = malloc(SHA256_DIGEST_LENGTH)) == NULL) 862 | err(1, "malloc"); 863 | hashcred(I->sha256, I->credential_id, I->ncredential_id); 864 | } 865 | 866 | /* 867 | * Phase 2 [plugin, bi-directional] 868 | */ 869 | for (file_index = -1, i = 0; i < nstanzas; i++) { 870 | void *credhash = NULL; 871 | size_t ncredhash = 0; 872 | void *salt = NULL; 873 | size_t nsalt = 0; 874 | void *key = NULL; 875 | size_t nkey = 0; 876 | 877 | S = &stanzas[i]; 878 | 879 | /* Skip files we've already decapsulated. */ 880 | if (S->file_index == file_index) 881 | continue; 882 | 883 | /* Decode the base64 recipient credential id. */ 884 | if (b64dec(S->credhash_b64, strlen(S->credhash_b64), 885 | &credhash, &ncredhash) == -1 || 886 | ncredhash != SHA256_DIGEST_LENGTH) 887 | errx(1, "invalid recipient stanza"); 888 | 889 | /* Find the matching identity. */ 890 | for (j = 0; j < nids; j++) { 891 | I = &ids[j]; 892 | if (CRYPTO_memcmp(credhash, I->sha256, 893 | SHA256_DIGEST_LENGTH) == 0) 894 | break; 895 | } 896 | if (j == nids) 897 | errx(1, "missing identity"); 898 | 899 | /* Decode the base64 salt. */ 900 | if (b64dec(S->salt_b64, strlen(S->salt_b64), &salt, &nsalt) 901 | == -1) 902 | errx(1, "invalid salt"); 903 | 904 | /* Decapsulate the key. */ 905 | key = decap(I->credential_id, I->ncredential_id, salt, nsalt, 906 | S->buf, S->len, &nkey); 907 | 908 | printf("-> file-key %d\n", S->file_index); 909 | b64write(key, nkey, stdout, 0); 910 | file_index = S->file_index; 911 | 912 | free(key); 913 | free(salt); 914 | free(credhash); 915 | } 916 | printf("-> done\n"); 917 | } 918 | 919 | static void 920 | usage(void) 921 | { 922 | 923 | fprintf(stderr, "Usage: %s --age-plugin=\n", getprogname()); 924 | exit(1); 925 | } 926 | 927 | int 928 | main(int argc, char **argv) 929 | { 930 | 931 | setprogname(argv[0]); 932 | if (argc == 1) 933 | do_keygen(); 934 | else if (argc != 2) 935 | usage(); 936 | else if (strcmp(argv[1], "--age-plugin=recipient-v1") == 0) 937 | do_recipient(); 938 | else if (strcmp(argv[1], "--age-plugin=identity-v1") == 0) 939 | do_identity(); 940 | else 941 | usage(); 942 | 943 | fflush(stdout); 944 | return ferror(stdout); 945 | } 946 | -------------------------------------------------------------------------------- /progname.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2017 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #define _POSIX_C_SOURCE 200809L 28 | 29 | #include "progname.h" 30 | 31 | #include 32 | 33 | static const char *progname = "(unknown)"; 34 | 35 | /* 36 | * setprogname(argv0) 37 | * 38 | * Set the program name from argv[0], for future use with 39 | * getprogname(). 40 | */ 41 | void 42 | setprogname(const char *argv0) 43 | { 44 | 45 | progname = strrchr(argv0, '/'); 46 | if (progname == NULL) 47 | progname = argv0; 48 | else 49 | progname++; 50 | } 51 | 52 | /* 53 | * getprogname() 54 | * 55 | * Return the program name set by setprogname. 56 | */ 57 | const char * 58 | getprogname(void) 59 | { 60 | 61 | return progname; 62 | } 63 | -------------------------------------------------------------------------------- /progname.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2017 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef PROGNAME_H 28 | #define PROGNAME_H 29 | 30 | void setprogname(const char *); 31 | const char * getprogname(void); 32 | 33 | #endif /* PROGNAME_H */ 34 | -------------------------------------------------------------------------------- /reallocn.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #define _POSIX_C_SOURCE 200809L 28 | 29 | #include "reallocn.h" 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | int 37 | reallocn(void *p, size_t nelem, size_t elemsize) 38 | { 39 | void *optr, *nptr; 40 | int saved_errno = errno; 41 | 42 | memcpy(&optr, p, sizeof(optr)); 43 | if (nelem == 0 || elemsize == 0) { 44 | free(optr); 45 | nptr = NULL; 46 | } else { 47 | if (nelem > SIZE_MAX/elemsize) { 48 | errno = EOVERFLOW; 49 | return -1; 50 | } 51 | if ((nptr = realloc(optr, nelem * elemsize)) == NULL) 52 | return -1; 53 | } 54 | memcpy(p, &nptr, sizeof(nptr)); 55 | 56 | errno = saved_errno; 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /reallocn.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef REALLOCN_H 28 | #define REALLOCN_H 29 | 30 | #include 31 | 32 | int reallocn(void *, size_t, size_t); 33 | 34 | #endif /* REALLOCN_H */ 35 | -------------------------------------------------------------------------------- /strprefix.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2017 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #define _POSIX_C_SOURCE 200809L 28 | 29 | #include "strprefix.h" 30 | 31 | #include 32 | 33 | /* 34 | * strprefix(s, n, p) 35 | * 36 | * Compares the bytes s[0], s[1], ..., s[n - 1], no farther than 37 | * the first NUL byte, to the NUL-terminated string at p, 38 | * returning negative if less, zero if equal, or positive if 39 | * greater. 40 | */ 41 | int 42 | strprefix(const char *s, size_t n, const char *p) 43 | { 44 | 45 | if (strlen(p) < n) 46 | n = strlen(p); 47 | return strncmp(s, p, n); 48 | } 49 | -------------------------------------------------------------------------------- /strprefix.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2017 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef STRPREFIX_H 28 | #define STRPREFIX_H 29 | 30 | #include 31 | 32 | int strprefix(const char *, size_t, const char *); 33 | 34 | #endif /* STRPREFIX_H */ 35 | -------------------------------------------------------------------------------- /t_bech32.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2021 Taylor R. Campbell 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "bech32.h" 33 | 34 | #define arraycount(A) (sizeof(A)/sizeof((A)[0])) 35 | 36 | static const struct { 37 | const char *hrp; 38 | const char *bech32; 39 | } V[] = { /* valid */ 40 | [0] = { .hrp = "a", .bech32 = "a12uel5l" }, 41 | [1] = { 42 | .hrp = 43 | "an83characterlonghumanreadablepartthatco" 44 | "ntainsthenumber1andtheexcludedcharacters" 45 | "bio", 46 | .bech32 = 47 | "an83characterlonghumanreadablepartthatco" 48 | "ntainsthenumber1andtheexcludedcharacters" 49 | "bio1tt5tgs", 50 | }, 51 | [2] = { 52 | .hrp = 53 | "an84characterslonghumanreadablepartthatc" 54 | "ontainsthenumber1andtheexcludedcharacter" 55 | "sbio", 56 | .bech32 = 57 | "an84characterslonghumanreadablepartthatc" 58 | "ontainsthenumber1andtheexcludedcharacter" 59 | "sbio1569pvx", 60 | }, 61 | [3] = { 62 | .hrp = "1", 63 | .bech32 = 64 | "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" 65 | "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" 66 | "qqqqc8247j", 67 | }, 68 | [4] = { 69 | .hrp = "split", 70 | .bech32 = 71 | "split1checkupstagehandshakeupstreamerran" 72 | "terredcaperred2y9e3w", 73 | }, 74 | [5] = { .hrp = "?", .bech32 = "?1ezyfcl" }, 75 | }, I[] = { /* invalid */ 76 | [0] = { .hrp = "\020", .bech32 = "\0201nwldj5" }, 77 | #if 0 78 | /* 79 | * We skip these tests because it's the caller's responsibility 80 | * to pass in a valid HRP; the API treats the HRP as a 81 | * hard-coded part of the software logic, not as a variable 82 | * field. 83 | */ 84 | [1] = { .hrp = "\177", .bech32 = "\177""1axkwrx" }, 85 | [2] = { .hrp = "\200", .bech32 = "\200""1eym55h" }, 86 | #endif 87 | [3] = { .hrp = "pzry", .bech32 = "pzry9x0s0muk" }, 88 | [4] = { .hrp = "", .bech32 = "1pzry9x0s0muk" }, 89 | [5] = { .hrp = "x", .bech32 = "x1b4n0q5v" }, 90 | [6] = { .hrp = "li", .bech32 = "li1dgmt3" }, 91 | [7] = { .hrp = "de", .bech32 = "de1lg7wt\377" }, 92 | [8] = { .hrp = "a", .bech32 = "A1G7SGD8" }, 93 | [9] = { .hrp = "", .bech32 = "10a06t8" }, 94 | [10] = { .hrp = "", .bech32 = "1qzzfhee" }, 95 | [11] = { 96 | .hrp = "bc", 97 | .bech32 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", 98 | }, 99 | [12] = { 100 | .hrp = "tb", 101 | .bech32 = 102 | "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj" 103 | "0gdcccefvpysxf3q0sL5k7", 104 | }, 105 | #if 0 106 | /* 107 | * This one is not actually invalid but is incorrectly 108 | * described as invalid in the spec. 109 | */ 110 | [13] = { 111 | .hrp = "bc", 112 | .bech32 = "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", 113 | }, 114 | #endif 115 | [14] = { 116 | .hrp = "tb", 117 | .bech32 = 118 | "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj" 119 | "0gdcccefvpysxf3pjxtptv", 120 | }, 121 | }; 122 | 123 | int 124 | main(void) 125 | { 126 | uint8_t payload[BECH32_PAYLOAD_MAX]; 127 | char bech32[BECH32_MAX + 1]; 128 | unsigned i; 129 | int n, m, j; 130 | 131 | /* Valid tests. */ 132 | for (i = 0; i < arraycount(V); i++) { 133 | /* Decode as is (lowercase). */ 134 | if ((n = bech32dec(payload, sizeof(payload), 135 | V[i].hrp, strlen(V[i].hrp), 136 | V[i].bech32, strlen(V[i].bech32))) == -1) { 137 | printf("valid %u decode fail\n", i); 138 | memset(payload, 0, sizeof(payload)); 139 | } else { 140 | printf("valid %u decode ok (%d bytes)", i, n); 141 | for (j = 0; j < n; j++) 142 | printf(" %02hhx", payload[j]); 143 | printf("\n"); 144 | } 145 | 146 | /* Map to uppercase and decode again. */ 147 | for (j = 0; j < (int)strlen(V[i].bech32); j++) 148 | bech32[j] = tolower((unsigned char)V[i].bech32[j]); 149 | bech32[j] = '\0'; 150 | if ((n = bech32dec(payload, sizeof(payload), 151 | V[i].hrp, strlen(V[i].hrp), 152 | bech32, strlen(bech32))) == -1) { 153 | printf("valid %u decode fail\n", i); 154 | memset(payload, 0, sizeof(payload)); 155 | } else { 156 | printf("valid %u decode ok (%d bytes)", i, n); 157 | for (j = 0; j < n; j++) 158 | printf(" %02hhx", payload[j]); 159 | printf("\n"); 160 | } 161 | 162 | /* Encode as lowercase. */ 163 | if ((m = bech32enc(bech32, sizeof(bech32), 164 | V[i].hrp, strlen(V[i].hrp), payload, n)) == -1) { 165 | printf("valid %u encode fail\n", i); 166 | } else { 167 | printf("valid %u encode ok (%d bytes) %s\n", i, m, 168 | bech32); 169 | } 170 | 171 | /* Encode as uppercase. */ 172 | if ((m = bech32enc_upper(bech32, sizeof(bech32), 173 | V[i].hrp, strlen(V[i].hrp), payload, n)) == -1) { 174 | printf("valid %u encode fail\n", i); 175 | } else { 176 | printf("valid %u encode ok (%d bytes) %s\n", i, m, 177 | bech32); 178 | } 179 | } 180 | 181 | /* Invalid tests. */ 182 | for (i = 0; i < arraycount(I); i++) { 183 | if (I[i].bech32 == NULL) 184 | continue; 185 | printf("invalid %u decoded %d\n", i, 186 | n = bech32dec(payload, sizeof(payload), 187 | I[i].hrp, strlen(I[i].hrp), 188 | I[i].bech32, strlen(I[i].bech32))); 189 | if (n == -1) 190 | continue; 191 | for (j = 0; j < n; j++) 192 | printf(" %02hhx", payload[j]); 193 | printf("\n"); 194 | } 195 | 196 | return 0; 197 | } 198 | -------------------------------------------------------------------------------- /t_bech32.exp: -------------------------------------------------------------------------------- 1 | valid 0 decode ok (0 bytes) 2 | valid 0 decode ok (0 bytes) 3 | valid 0 encode ok (8 bytes) a12uel5l 4 | valid 0 encode ok (8 bytes) A12UEL5L 5 | valid 1 decode ok (0 bytes) 6 | valid 1 decode ok (0 bytes) 7 | valid 1 encode ok (90 bytes) an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs 8 | valid 1 encode ok (90 bytes) AN83CHARACTERLONGHUMANREADABLEPARTTHATCONTAINSTHENUMBER1ANDTHEEXCLUDEDCHARACTERSBIO1TT5TGS 9 | valid 2 decode ok (0 bytes) 10 | valid 2 decode ok (0 bytes) 11 | valid 2 encode ok (91 bytes) an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx 12 | valid 2 encode ok (91 bytes) AN84CHARACTERSLONGHUMANREADABLEPARTTHATCONTAINSTHENUMBER1ANDTHEEXCLUDEDCHARACTERSBIO1569PVX 13 | valid 3 decode ok (51 bytes) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 | valid 3 decode ok (51 bytes) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 15 | valid 3 encode ok (90 bytes) 11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j 16 | valid 3 encode ok (90 bytes) 11QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQC8247J 17 | valid 4 decode ok (30 bytes) c5 f3 8b 70 30 5f 51 9b f6 6d 85 fb 6c f0 30 58 f3 dd e4 63 ec d7 91 8f 2d c7 43 91 8f 2d 18 | valid 4 decode ok (30 bytes) c5 f3 8b 70 30 5f 51 9b f6 6d 85 fb 6c f0 30 58 f3 dd e4 63 ec d7 91 8f 2d c7 43 91 8f 2d 19 | valid 4 encode ok (60 bytes) split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w 20 | valid 4 encode ok (60 bytes) SPLIT1CHECKUPSTAGEHANDSHAKEUPSTREAMERRANTERREDCAPERRED2Y9E3W 21 | valid 5 decode ok (0 bytes) 22 | valid 5 decode ok (0 bytes) 23 | valid 5 encode ok (8 bytes) ?1ezyfcl 24 | valid 5 encode ok (8 bytes) ?1EZYFCL 25 | invalid 0 decoded -1 26 | invalid 3 decoded -1 27 | invalid 4 decoded -1 28 | invalid 5 decoded -1 29 | invalid 6 decoded -1 30 | invalid 7 decoded -1 31 | invalid 8 decoded -1 32 | invalid 9 decoded -1 33 | invalid 10 decoded -1 34 | invalid 11 decoded -1 35 | invalid 12 decoded -1 36 | invalid 14 decoded -1 37 | -------------------------------------------------------------------------------- /t_dae.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C -*- */ 2 | 3 | /*- 4 | * Copyright (c) 2020 Taylor R. Campbell 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #include "dae.h" 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | static const uint8_t key[DAE_KEYBYTES] = { 36 | 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, 37 | 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f, 38 | 0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17, 39 | 0x18,0x19,0x1a,0x1b, 0x1c,0x1d,0x1e,0x1f, 40 | }; 41 | 42 | /* Note: No NUL terminator on these. */ 43 | 44 | static const uint8_t header[9] = "The Raven"; 45 | 46 | static const uint8_t payload[126] = 47 | "Once upon a midnight dreary,\n" 48 | " while I pondered, weak and weary,\n" 49 | "Over many a quaint and curious\n" 50 | " volume of forgotten lore...\n"; 51 | 52 | int 53 | main(void) 54 | { 55 | uint8_t c[DAE_TAGBYTES + sizeof(payload)]; 56 | uint8_t m[sizeof(payload)]; 57 | unsigned i; 58 | 59 | if (!dae_encrypt(c, header, sizeof(header), payload, sizeof(payload), 60 | key)) 61 | errx(1, "encrypt"); 62 | printf("ciphertext:\n"); 63 | for (i = 0; i < sizeof(c); i++) 64 | printf("%02hhx", c[i]); 65 | printf("\n"); 66 | 67 | if (!dae_decrypt(m, header, sizeof(header), c, sizeof(c), key)) 68 | errx(1, "encrypt"); 69 | printf("plaintext:\n"); 70 | for (i = 0; i < sizeof(m); i++) 71 | printf("%02hhx", m[i]); 72 | printf("\n"); 73 | 74 | c[12] ^= 0x08; 75 | if (dae_decrypt(m, header, sizeof(header), c, sizeof(c), key)) 76 | errx(1, "accepted forgery"); 77 | 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /t_dae.exp: -------------------------------------------------------------------------------- 1 | ciphertext: 2 | 55c8a5b9124861b1f7849d60c80cf49cafb6374c1165682d2d0bfdbfad1da2a5ba4c961b05f32478b7a0ee0a9f69d74586efe24d73e20e3973615e03485a4cc66f5ac5d0d3a70c09bda76da79d672f028977dc9559174c90960e31aa13b41eeab6fda729045cfd7f66b2aa27e27c1e4aad912d431f46b0699c9f7e2b216c610a076b06cea7d37ebcfb3ac897d652efd3b7cd49f081ba971fc6f3b0f232ea 3 | plaintext: 4 | 4f6e63652075706f6e2061206d69646e69676874206472656172792c0a20207768696c65204920706f6e64657265642c207765616b20616e642077656172792c0a4f766572206d616e79206120717561696e7420616e6420637572696f75730a2020766f6c756d65206f6620666f72676f7474656e206c6f72652e2e2e0a 5 | --------------------------------------------------------------------------------