From 36a889034c3d34ae4ac4530ea7b6b16e82476fae Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Tue, 14 Apr 2015 13:52:34 +0100 Subject: [PATCH] Add helper code to validate public key signatures We'll use this in the future for checking device firmware. --- configure.ac | 15 + contrib/fwupd.spec.in | 1 + data/Makefile.am | 2 +- data/pki/GPG-KEY-Hughski-Limited | 30 ++ data/pki/Makefile.am | 4 + data/tests/Makefile.am | 5 +- data/tests/firmware.bin | Bin 0 -> 8000 bytes data/tests/pki/GPG-KEY-Hughski-Limited | 1 + libfwupd/fwupd-error.c | 3 + libfwupd/fwupd-error.h | 2 + src/Makefile.am | 7 + src/fu-keyring.c | 368 +++++++++++++++++++++++++ src/fu-keyring.h | 70 +++++ src/fu-self-test.c | 41 +++ 14 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 data/pki/GPG-KEY-Hughski-Limited create mode 100644 data/pki/Makefile.am create mode 100644 data/tests/firmware.bin create mode 120000 data/tests/pki/GPG-KEY-Hughski-Limited create mode 100644 src/fu-keyring.c create mode 100644 src/fu-keyring.h diff --git a/configure.ac b/configure.ac index 894fd05d0..4ea8fd178 100644 --- a/configure.ac +++ b/configure.ac @@ -132,6 +132,20 @@ if test x$enable_colorhug != xno; then fi AM_CONDITIONAL(HAVE_COLORHUG, test x$enable_colorhug = xyes) +# gpgme support +AC_MSG_CHECKING([for gpgme]) +if ! test -x "/usr/bin/gpgme-config"; then + AC_MSG_ERROR([Cannot locate gpgme]) +else + AC_MSG_RESULT([yes]) + GPGME_CFLAGS="`\"/usr/bin/gpgme-config\" --cflags`" + GPGME_LIBS="`\"/usr/bin/gpgme-config\" --libs`" + GPGME_CFLAGS+=" `\"/usr/bin/gpg-error-config\" --cflags`" + GPGME_LIBS+=" `\"/usr/bin/gpg-error-config\" --libs`" + AC_SUBST([GPGME_CFLAGS]) + AC_SUBST([GPGME_LIBS]) +fi + # UEFI support AC_ARG_ENABLE(uefi, AS_HELP_STRING([--enable-uefi],[Enable ColorHug support]), enable_uefi=$enableval, enable_uefi=yes) @@ -161,6 +175,7 @@ libfwupd/fwupd.pc libfwupd/Makefile man/Makefile data/Makefile +data/pki/Makefile data/tests/Makefile policy/Makefile po/Makefile.in diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index fc71a6bb6..1c0b0cb6b 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -76,6 +76,7 @@ find %{buildroot} -name '*.la' -exec rm -f {} ';' %license COPYING %{_libexecdir}/fwupd %{_bindir}/fwupdmgr +%{_sysconfdir}/pki/fwupd %{_sysconfdir}/dbus-1/system.d/org.freedesktop.fwupd.conf %{_datadir}/dbus-1/interfaces/org.freedesktop.fwupd.xml %{_datadir}/polkit-1/actions/org.freedesktop.fwupd.policy diff --git a/data/Makefile.am b/data/Makefile.am index 8e364d4ad..5568208ae 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = tests +SUBDIRS = tests pki dbusdir = $(sysconfdir)/dbus-1/system.d dist_dbus_DATA = org.freedesktop.fwupd.conf diff --git a/data/pki/GPG-KEY-Hughski-Limited b/data/pki/GPG-KEY-Hughski-Limited new file mode 100644 index 000000000..10fb38773 --- /dev/null +++ b/data/pki/GPG-KEY-Hughski-Limited @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQENBFUr0UoBCACsdOLuTJ81dICrSvUhyznBsL4WgEa2RUbEjJuaXwrEyPMikHE1 +Clda2YI7VbpCgIVq8Zy63CGJ4Xqs2T6pyetaXnbX8J0C+7wg2IfPv7pUyCsP7/JR +HRB2GNelCWrsGArN1cOPI0ESH4yHWKF9KCGlpsLfSHmvF7D8vcKlKQUlO4T6lxOP +SNjMSXkMsxfDDhl1mzqrwxfU4V6nnPcuMwU7tvg+39PioP4Ny1tKP4SSpBfh7qwz +XXRd505dqNLOubxmOPZ5rznVkKmW2cwahO6fr5zVA8/2TDZQ79mdbfvSJVlW06qs +C5PYmLnBjyzE5uQ4oxSIuUEiMfqrn3Qs6PhhABEBAAG0Ikh1Z2hza2kgTGltaXRl +ZCA8aW5mb0BodWdoc2tpLmNvbT6JATgEEwECACIFAlUr0UoCGwMGCwkIBwMCBhUI +AgkKCwQWAgMBAh4BAheAAAoJEK2KUo/sRIge/fUH/Rblgzh5GeB0Zp2U9W+r26iJ +t1AD5a/fKxQahz/pwMkevQCCMzI1vpX12P3HtACZOD3Zjh9RXY6Z3033YZjrRApe +FkOVfcyUF1nP/z2Ox3jE3+B8v1u0UzH/MqtF/1095mqvR7gllE288KDqu7bvd5l3 +z4IETk5qqoeCe9LYc8aob973dbocyS/gou/FLCKxoXVEe8DPRwv8qmXlXOujxdxd +FcslpYqtjj4fgUswQ/cY/a1UcAX5zCnVqFbU7oJH2uTNewKuaZ2wgPbnzvwx8JYl +VfFdPN7GZ0NMrZDLeJ0SLXer/9+qAKNH4UpQS9axXQL+VKOzsZCXuv31VDCj5Jy5 +AQ0EVSvRSgEIAMgVrZP3LmA9bx7B8l+agVh5DNXrMixX9jhZ0Yfn8+UIMMNTZziD +ZV3nXxswKPrcsqQ+KP9iUwq3V2oio46bvHiMMoZSGCaTv4yiKOliFOMYr9NAOSTZ +8mOI24dNXI9XqQ7ZA8m4uKmgHZQUIUUlx693uRI2Wmk/Y5XEBoL2+XdA5KalO+36 +27YXpdyU3GiMCOtSBLWNfBxXw6oKdNUp+8o/fYrmQnBxuGgmVlcZEmjhrIGXaCH1 +iDeWIFqaM/S+DXMF3bgqvqRZq1U2RwT2oxapAuaG/0I5JaKKpb3HqMCXfOUxpFPk +zgUYpHatUcePG/94K8N8CRjnJ+l83H5PewcAEQEAAYkBHwQYAQIACQUCVSvRSgIb +DAAKCRCtilKP7ESIHrrcCACc6UTZzVGbVq9pXSz2Bw2xQpAEAhnnedPgfXwEJMM0 +24bMUNsyJcQZAW1d5KfJYNAihOfse3oDQ/hJAycTK3GAHsPfljEQjWGn27eC8Fxu +mHpfNpxbTirChfepCNctZG818Hp2v+K4X/PjyQMQ6J5H9oinnlasVQ6wzdZifnWm +7E5OL0NV/ni9xqq4fC5y5qxNBeYVmHUF4H0E3VOuCbESAOnUDpCo998Dc68eZEmV +f3IMukvvnxM9VOZQSnp7J/kkhPB5fim2z2qrlJK9N+tBjAMugxtnAV2fIaZYTiba +SnN2hheFd9Y0nMmWbwRqFtwMG1m/tS3JlD52Rpwzk59B +=WFoi +-----END PGP PUBLIC KEY BLOCK----- diff --git a/data/pki/Makefile.am b/data/pki/Makefile.am new file mode 100644 index 000000000..17dcca7ab --- /dev/null +++ b/data/pki/Makefile.am @@ -0,0 +1,4 @@ +pkidir = $(sysconfdir)/pki/fwupd +dist_pki_DATA = GPG-KEY-Hughski-Limited + +-include $(top_srcdir)/git.mk diff --git a/data/tests/Makefile.am b/data/tests/Makefile.am index d6347b2d6..0f4da014f 100644 --- a/data/tests/Makefile.am +++ b/data/tests/Makefile.am @@ -1,4 +1,7 @@ -test_files = colorhug-als-3.0.2.cab +test_files = \ + colorhug-als-3.0.2.cab \ + firmware.bin \ + pki/GPG-KEY-Hughski-Limited EXTRA_DIST = $(test_files) diff --git a/data/tests/firmware.bin b/data/tests/firmware.bin new file mode 100644 index 0000000000000000000000000000000000000000..c3f129f25766c71fb82a420de7945d78d4460add GIT binary patch literal 8000 zcmeHMd3aREk+0T_G>wpi5C|mDzzh(Wd)|YA57`+V_9l?T>-gY^gAE3AnN5smbiw*U zSq$b2(Fzd`2{2%JlaF{YHi=nZ*b)Ma6A}zYh8PTXzy{+GtXnoQ+F$j1BSswgcK?s& z>(^IRb#--Bb#eoi{fwMlGvgx zCDNXzQChV=+6j2{1^NYMpXcNI1?X2}{ zOchy@oD`Cl?5VDhTt>@#OUBk&KtMw&l*Sf3qQE_AA z&9O&O8@R*hIjVIV7!7JRL389S!@A4q6r}dFNYEHEgND#My$`u+-KQW!$VA(cXUI5y z{xs&V;vZM|Npbj>Fi+^ek9{0LIgIiiUB!4$VOZlZq`MZfm@XZ36w;i5x{*E*$HZ#= z&~Vn1sgS|@eL;=ya0Ss$btmF`i<@T)+*$Z;b`?WbtZ~0{ zU$1e0!up43m-|oRkocjz(`vCFcTbkAKdf<1NX%$&r6$po>@fGvduGmCpb;B%UG4f^ z`kwqPptjru#z_qEJLtcIZsUF0g{MFc&86|&a#Gzu2Ng=}+};I#UlcDU^XQ9$q1lEQ zHUYDe{zdl5CG5;-oq(oHORy;W94#FRf@$p??DH_>3(#9=raVE@4`%Kie5dC@&@rgq+ub(kOq1m%|HYW$HOq(o#ItwsT(#D= zyoXh7tWe&6Dd-wdz_UuS8tWv*FxPvYiib!v#>K!yjjiM;aG9@0$o~WMk3pX@N37B} zc^oEHG0>_xNb#O@I%``uM+d>t4wIvu@f@Y!+GD+cyv|Y2t%`d04iX(oD(mx3&`FV| zoeWQtAJP5t6kP$;2Oy2C)i2>Rw+N`ak$FwS{$o|?>k=c$@ z-zjz`k5)R8<^|lKG;bwfGx}!?fLEY%G!J&X6J9wzJHtNMd)XWdQ)ObczSdc#11HDn zbx=lMN$FV`Ib*zQ^v&O@(pe@k>??6t>4P$c2JqDR}QKgazChaDh?{Ms&W>3yA5VsG|Nk+W1iS5ny+I+KRN|Gh} ziZ^AD%@?B~w2y7T4}%WMnebUJs3ckJnLbcaHK?c>R8Tv7YzYScypN;sH8=W9jSpPGueGcV(c)pxxook%~A353Ag_XJ>sV=P%g;%;6 z*DfF5z^id98IqYTd*hB=qgk><-qS>o*Pq3Q;)IwcTj|H}sQ1ZcNU#Yf-HG$)pt-W0 z=Eye0B)1P+trtTpPE!#qD+&vX$diDZft_sj&6CqrJy$-U>N)a$NcJ~3;B>w`2Ff3R zaw8}+?+!#U?}~Fi!wXOB@1%gTz7`*5`nRZYK`X?xidT_QOv?eqaC4w?O zG&iJ$#)T-9h@UN#6zYr7UZI4L8)E`L3e|!iW>FJTF4SLUzA#SOSjjE=-Og-Sva>lOe%p?h`}+I68ppfOQFP-q_N`6xvw z|Az7~iUZ{ilwy?GC;^l#;M|USA<9q8I*K};P9ge?cP~l?_~E_r4iS)s@kO1iL(|Gd zsWk3%ZkhKfTH6#Kyc@=Ew<)}w#DGin9dvGBDs;dbRdyNk(jz zT##R6tEIyH;_&m3dZ{b{))RpeInj(VS`e!fHAsPko3$o{hkzKQS7x8m-fJN(sTVB z^j8&;ZPFk87E6k)n&UZ+$M{<)IV$Z8Sc6WwUHK7fa5!vx4Q#uY_GRD}+0>;5%->3he#$3vFs^W z0la%wO>la*#!rvac&2v>8V{J9XM%gLp(w7U*w3XnV4HBL*59D*Oylo}wYi2BPy@K% z7fe_4J0S^<1dJG{L5=l{Jqm9a(~p+)vH!6tE!tkFw`tTjdI%(qcrf$~ON{Zq6{QJzP69Az=eN|f~| zZ}?2qe^&pLz%>$ZP#V05$dzQUa31RrJ0&jqM*gt98odVrZn&_BT1KXBS{q$9jC@pn^Jk~ zvKnLmvqKbjTWKTi-3_g4^^`zU-m{g;!{IV{B)mwz56|8foKBWAt}_l=}>_+mco4(y~E-nMz{ir!CnIHrKMsA%gyJVj%p zwpR38RsZLn;<0U1B0Fp>OXM5+HDRBdJ!Qb>7^&858F5n-yD{KIzcOhxVvvC8#NS^?1|*^%Aq+fSTXXHON^V6BcbMZlm+qp#eGL8q8Q<;Ku!crpRM9 zg^_c}Gn=aP&xh6Mm3r7wqo2oHf44w9fivQGZ#&u-#Z!1U8Yeyr83QYNv*d-On~xf& z#jqSv){8x6RJl=~GonVf+(fHu^k?-Kpdsy$wonAL3o^B+gu})ekG5GD&j&@h;zN z-@Ph2^!ng)vM2aB?w#!`Q(tb6RPWpYfMZW&M6kd_jQo;%Q%kJ*3C#p`j+-{?!$(P4 zr%%DRp*vPB=nq*W*TGVMssDC_q*eL_?11xad-FJsHeSpz7>+?XWBQ4jXRk0*NhiU~ z8}_^vm`2JGJ&ftqeqX2f3VO8|CwRnH#QK$$?uU+K zX7*c?k(m*dRiX;Mf0bAT-`@nk-VEPZVq@(!{(~UoYG|IwYX|R(!yT|Q=%&3BwD_fG z_48a$_+MV5n+aMc=zbMG=!@wl>j&?aG%;AyNU|9C7(}?Wt3;Z#CyH?v+7dOH8h1Kmc*}oL@V{01v*A7oH)6ZJjbNrd>M(~Cj z%&(}6xLNSx1s+S6Z?IQrS6pwG;)`#yO8)}oVamk;z!^K`yHA>YGacMhn8VGfAbB?S z(tX6VoCrKt6dsG;x>xT{#@wFFxW?-;9y4akF-(408MbV!j@+aCCf8yQ1Qw3V&pm@( zoKZK^asNZe3!J6dl3x~Q=pno;tjGwWPBL$HL`!{gQ?WO1;4^SO7bl++h;dqwe+KX7 zvw&X)8p}z@Pm!%EP?{T+v~pXaZpkW7#>#;H3^@l-%IWYIsQlyEZpeqQ_u1Hc zjsE2@iCkJ1zTd$57A7*D6?{nAr1!^rmL(B^_49wEZcC8x#f9K)YM@ZP3R`G5PUwe$ z-C}q0)IeBi&D4OAuiA}Fz+2{Pyos7cQ?KS;O~?c^BNGs@)niA|@E*iFd`iZ7vJ@w( z^_=u3735R|ejUhzJ?2Z!3^viXl}$_#jl z!Dj={4Xp?FY@5(WXwl2q-R{5!egAML?Gmp*LnUSn%_vnm#a7D^lHrX@A=fe$H33R- z%TP;!6SD&Dv!AIO`gP!||F$--c6IF=QREy2N0QRa zG+^)apTQN%$8uI8!^|NtpZxmA*^O;Z?W09(oT-h5aT@vTYeoLsIGbk515ARNjY>;a zG?`C}^EdU~vz8mo?tvUQ8o3Snv?6$Q9lGH<@p{lKBA^|WOo3yZ*q8GgoRJ8q?8JDL Oi*/ FWUPD_ERROR_LAST } FwupdError; diff --git a/src/Makefile.am b/src/Makefile.am index 55594de27..d7d9df3d1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ AM_CPPFLAGS = \ $(PIE_CFLAGS) \ $(POLKIT_CFLAGS) \ $(UEFI_CFLAGS) \ + $(GPGME_CFLAGS) \ -I$(top_srcdir) \ -I$(top_builddir) \ -I$(top_srcdir)/libfwupd \ @@ -80,6 +81,8 @@ fwupd_SOURCES = \ fu-debug.h \ fu-device.c \ fu-device.h \ + fu-keyring.c \ + fu-keyring.h \ fu-pending.c \ fu-pending.h \ fu-provider.c \ @@ -109,6 +112,7 @@ fwupd_LDADD = \ $(GUDEV_LIBS) \ $(POLKIT_LIBS) \ $(SQLITE_LIBS) \ + $(GPGME_LIBS) \ $(UEFI_LIBS) fwupd_LDFLAGS = \ @@ -133,6 +137,8 @@ fu_self_test_SOURCES = \ fu-cab.h \ fu-device.c \ fu-device.h \ + fu-keyring.c \ + fu-keyring.h \ fu-pending.c \ fu-pending.h \ fu-provider.c \ @@ -146,6 +152,7 @@ fu_self_test_LDADD = \ $(APPSTREAM_GLIB_LIBS) \ $(SQLITE_LIBS) \ $(GCAB_LIBS) \ + $(GPGME_LIBS) \ $(GLIB_LIBS) fu_self_test_CFLAGS = $(WARNINGFLAGS_C) diff --git a/src/fu-keyring.c b/src/fu-keyring.c new file mode 100644 index 000000000..6e4855d95 --- /dev/null +++ b/src/fu-keyring.c @@ -0,0 +1,368 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include +#include + +#include "fu-cleanup.h" +#include "fu-keyring.h" + +static void fu_keyring_finalize (GObject *object); + +#define FU_KEYRING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), FU_TYPE_KEYRING, FuKeyringPrivate)) + +/** + * FuKeyringPrivate: + * + * Private #FuKeyring data + **/ +struct _FuKeyringPrivate +{ + gpgme_ctx_t ctx; +}; + +G_DEFINE_TYPE (FuKeyring, fu_keyring, G_TYPE_OBJECT) + +/** + * fu_keyring_setup: + **/ +static gboolean +fu_keyring_setup (FuKeyring *keyring, GError **error) +{ + gpgme_error_t rc; + + g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE); + + if (keyring->priv->ctx != NULL) + return TRUE; + + /* check version */ + gpgme_check_version (NULL); + + /* startup gpgme */ + rc = gpg_err_init (); + if (rc != GPG_ERR_NO_ERROR) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to startup GPG: %s", + gpgme_strerror (rc)); + return FALSE; + } + + /* create a new GPG context */ + rc = gpgme_new (&keyring->priv->ctx); + if (rc != GPG_ERR_NO_ERROR) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to create context: %s", + gpgme_strerror (rc)); + return FALSE; + } + + /* set the protocol */ + rc = gpgme_set_protocol (keyring->priv->ctx, GPGME_PROTOCOL_OpenPGP); + if (rc != GPG_ERR_NO_ERROR) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to set protocol: %s", + gpgme_strerror (rc)); + return FALSE; + } + + /* enable armor mode */ + gpgme_set_armor (keyring->priv->ctx, TRUE); + + return TRUE; +} + +/** + * fu_keyring_add_public_key: + **/ +gboolean +fu_keyring_add_public_key (FuKeyring *keyring, const gchar *filename, GError **error) +{ + gboolean ret = TRUE; + gpgme_data_t data = NULL; + gpgme_error_t rc; + + g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + /* import public key */ + rc = gpgme_data_new_from_file (&data, filename, 1); + if (rc != GPG_ERR_NO_ERROR) { + ret = FALSE; + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to load %s: %s", + filename, gpgme_strerror (rc)); + goto out; + } + rc = gpgme_op_import (keyring->priv->ctx, data); + if (rc != GPG_ERR_NO_ERROR) { + ret = FALSE; + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to import %s: %s", + filename, gpgme_strerror (rc)); + goto out; + } +out: + gpgme_data_release (data); + return ret; +} + +/** + * fu_keyring_add_public_keys: + **/ +gboolean +fu_keyring_add_public_keys (FuKeyring *keyring, const gchar *dirname, GError **error) +{ + _cleanup_dir_close_ GDir *dir = NULL; + + g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE); + g_return_val_if_fail (dirname != NULL, FALSE); + + /* setup context */ + if (!fu_keyring_setup (keyring, error)) + return FALSE; + + /* search all the public key files */ + dir = g_dir_open (dirname, 0, error); + if (dir == NULL) + return FALSE; + do { + const gchar *filename; + _cleanup_free_ gchar *path_tmp = NULL; + filename = g_dir_read_name (dir); + if (filename == NULL) + break; + path_tmp = g_build_filename (dirname, filename, NULL); + if (!fu_keyring_add_public_key (keyring, path_tmp, error)) + return FALSE; + } while (TRUE); + return TRUE; +} + +/** + * fu_keyring_check_signature: + **/ +static gboolean +fu_keyring_check_signature (gpgme_signature_t signature, GError **error) +{ + gboolean ret = FALSE; + + /* look at the signature status */ + switch (gpgme_err_code (signature->status)) { + case GPG_ERR_NO_ERROR: + ret = TRUE; + break; + case GPG_ERR_SIG_EXPIRED: + case GPG_ERR_KEY_EXPIRED: + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_SIGNATURE_INVALID, + "valid signature '%s' has expired", + signature->fpr); + break; + case GPG_ERR_CERT_REVOKED: + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_SIGNATURE_INVALID, + "valid signature '%s' has been revoked", + signature->fpr); + break; + case GPG_ERR_BAD_SIGNATURE: + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_SIGNATURE_INVALID, + "'%s' is not a valid signature", + signature->fpr); + break; + case GPG_ERR_NO_PUBKEY: + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_SIGNATURE_INVALID, + "Could not check signature '%s' as no public key", + signature->fpr); + break; + default: + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_SIGNATURE_INVALID, + "gpgme failed to verify signature '%s'", + signature->fpr); + break; + } + return ret; +} + +/** + * fu_keyring_verify_file: + **/ +gboolean +fu_keyring_verify_file (FuKeyring *keyring, + const gchar *filename, + const gchar *signature, + GError **error) +{ + gboolean has_header; + gboolean ret = TRUE; + gpgme_data_t data = NULL; + gpgme_data_t sig = NULL; + gpgme_error_t rc; + gpgme_signature_t s; + gpgme_verify_result_t result; + _cleanup_string_free_ GString *sig_v1 = NULL; + + g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (signature != NULL, FALSE); + + /* setup context */ + if (!fu_keyring_setup (keyring, error)) + return FALSE; + + /* has header already */ + has_header = g_strstr_len (signature, -1, "BEGIN PGP SIGNATURE") != NULL; + + /* load file data */ + rc = gpgme_data_new_from_file (&data, filename, 1); + if (rc != GPG_ERR_NO_ERROR) { + ret = FALSE; + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to load %s: %s", + filename, gpgme_strerror (rc)); + goto out; + } + + /* load signature */ + sig_v1 = g_string_new (""); + if (!has_header) { + g_string_append (sig_v1, "-----BEGIN PGP SIGNATURE-----\n"); + g_string_append (sig_v1, "Version: GnuPG v1\n\n"); + } + g_string_append_printf (sig_v1, "%s\n", signature); + if (!has_header) + g_string_append (sig_v1, "-----END PGP SIGNATURE-----\n"); + rc = gpgme_data_new_from_mem (&sig, sig_v1->str, sig_v1->len, 0); + if (rc != GPG_ERR_NO_ERROR) { + ret = FALSE; + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to load signature %s: %s", + signature, gpgme_strerror (rc)); + goto out; + } + + /* verify */ + rc = gpgme_op_verify (keyring->priv->ctx, sig, data, NULL); + if (rc != GPG_ERR_NO_ERROR) { + ret = FALSE; + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to verify %s: %s", + filename, gpgme_strerror (rc)); + goto out; + } + + + /* verify the result */ + result = gpgme_op_verify_result (keyring->priv->ctx); + if (result == NULL) { + ret = FALSE; + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "no result record from libgpgme"); + goto out; + } + + /* look at each signature */ + for (s = result->signatures; s != NULL ; s = s->next ) { + ret = fu_keyring_check_signature (s, error); + if (!ret) + goto out; + } +out: + if (data != NULL) + gpgme_data_release (data); + if (sig != NULL) + gpgme_data_release (sig); + return ret; +} + +/** + * fu_keyring_class_init: + **/ +static void +fu_keyring_class_init (FuKeyringClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = fu_keyring_finalize; + g_type_class_add_private (klass, sizeof (FuKeyringPrivate)); +} + +/** + * fu_keyring_init: + **/ +static void +fu_keyring_init (FuKeyring *keyring) +{ + keyring->priv = FU_KEYRING_GET_PRIVATE (keyring); +} + +/** + * fu_keyring_finalize: + **/ +static void +fu_keyring_finalize (GObject *object) +{ + FuKeyring *keyring = FU_KEYRING (object); + FuKeyringPrivate *priv = keyring->priv; + + if (priv->ctx != NULL) + gpgme_release (priv->ctx); + + G_OBJECT_CLASS (fu_keyring_parent_class)->finalize (object); +} + +/** + * fu_keyring_new: + **/ +FuKeyring * +fu_keyring_new (void) +{ + FuKeyring *keyring; + keyring = g_object_new (FU_TYPE_KEYRING, NULL); + return FU_KEYRING (keyring); +} diff --git a/src/fu-keyring.h b/src/fu-keyring.h new file mode 100644 index 000000000..75cd6fb78 --- /dev/null +++ b/src/fu-keyring.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __FU_KEYRING_H +#define __FU_KEYRING_H + +#include +#include + +G_BEGIN_DECLS + +#define FU_TYPE_KEYRING (fu_keyring_get_type ()) +#define FU_KEYRING(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FU_TYPE_KEYRING, FuKeyring)) +#define FU_KEYRING_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), FU_TYPE_KEYRING, FuKeyringClass)) +#define FU_IS_KEYRING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), FU_TYPE_KEYRING)) +#define FU_IS_KEYRING_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), FU_TYPE_KEYRING)) +#define FU_KEYRING_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), FU_TYPE_KEYRING, FuKeyringClass)) +#define FU_KEYRING_ERROR fu_keyring_error_quark() + +typedef struct _FuKeyringPrivate FuKeyringPrivate; +typedef struct _FuKeyring FuKeyring; +typedef struct _FuKeyringClass FuKeyringClass; + +struct _FuKeyring +{ + GObject parent; + FuKeyringPrivate *priv; +}; + +struct _FuKeyringClass +{ + GObjectClass parent_class; +}; + +GType fu_keyring_get_type (void); +FuKeyring *fu_keyring_new (void); + +gboolean fu_keyring_add_public_keys (FuKeyring *keyring, + const gchar *dirname, + GError **error); +gboolean fu_keyring_add_public_key (FuKeyring *keyring, + const gchar *filename, + GError **error); +gboolean fu_keyring_verify_file (FuKeyring *keyring, + const gchar *filename, + const gchar *signature, + GError **error); + +G_END_DECLS + +#endif /* __FU_KEYRING_H */ + diff --git a/src/fu-self-test.c b/src/fu-self-test.c index 946693424..b8bd393b6 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -28,6 +28,7 @@ #include "fu-cab.h" #include "fu-cleanup.h" +#include "fu-keyring.h" #include "fu-pending.h" #include "fu-provider-fake.h" @@ -287,6 +288,45 @@ fu_pending_func (void) g_clear_error (&error); } +static void +fu_keyring_func (void) +{ + gboolean ret; + _cleanup_error_free_ GError *error = NULL; + _cleanup_free_ gchar *fw_fail = NULL; + _cleanup_free_ gchar *fw_pass = NULL; + _cleanup_free_ gchar *pki_dir = NULL; + _cleanup_object_unref_ FuKeyring *keyring = NULL; + const gchar *sig = + "iQEcBAABAgAGBQJVK9RSAAoJEBesuo36lw4XvmoH/3tJL5wVRN+rsvoo/FMc3w4g" + "I7rizJNIgQ04WVTREX6tRZJfxYzGAaeokVeqah2JUC4u1j22BDkoG/Fs+/2/Z/OP" + "PTxMoiEzfzryWpVwt20As+H9CmMZGdCfvKgnWiosAENCzE7JE1miJ4YvTpRtdPMh" + "erz8DqLTFAfr72aimf5hBs8ZFkBGPGjljdTDv78hk2WDep5E1+1swGoFbhDcXyih" + "8GZjSLP7XkKo23/p6odCJD3SkkDE7jIUMA8GrTHHXIhF41UsriKx2ERYoau5k3cX" + "OdK3/cRQ6BeuSBMLr7hUpa0RwlKUKex/I7+p/T9Ohk4lNnGS7GpE45RbpflK1VQ=" + "=0D8+"; + + /* add test keys to keyring */ + keyring = fu_keyring_new (); + pki_dir = fu_test_get_filename ("pki"); + ret = fu_keyring_add_public_keys (keyring, pki_dir, &error); + g_assert_no_error (error); + g_assert (ret); + + /* verify */ + fw_pass = fu_test_get_filename ("firmware.bin"); + ret = fu_keyring_verify_file (keyring, fw_pass, sig, &error); + g_assert_no_error (error); + g_assert (ret); + + /* verify will fail */ + fw_fail = fu_test_get_filename ("colorhug-als-3.0.2.cab"); + ret = fu_keyring_verify_file (keyring, fw_fail, sig, &error); + g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID); + g_assert (!ret); + g_clear_error (&error); +} + int main (int argc, char **argv) { @@ -299,6 +339,7 @@ main (int argc, char **argv) g_test_add_func ("/fwupd/cab", fu_cab_func); g_test_add_func ("/fwupd/pending", fu_pending_func); g_test_add_func ("/fwupd/provider", fu_provider_func); + g_test_add_func ("/fwupd/keyring", fu_keyring_func); return g_test_run (); }