├── .gitignore ├── COPYING ├── Makefile ├── appimage.c ├── appx.c ├── base64.c ├── bele.h ├── clearsign.c ├── cpio.c ├── dist ├── obs-signd.changes ├── obs-signd.spec ├── sign-rpmlintrc ├── sign.permission ├── signd.service └── sysconfig.signd ├── examples ├── privileged_gensig └── privileged_sign ├── gnupg-1.4.7-files_are_digests.patch ├── hash.c ├── inc.h ├── ko.c ├── pe.c ├── pgp.c ├── rpm.c ├── sign.8 ├── sign.c ├── sign.conf ├── sign.conf.5 ├── signd ├── signd.8 ├── sock.c ├── t ├── 000_signd.t ├── 001_sign.t └── fixtures │ ├── empty.rpm │ ├── public-key-expired.asc │ ├── public-key-passphrase.asc │ ├── public-key.asc │ ├── secret-key-expired-encrypted │ ├── secret-key-expired.asc │ ├── secret-key-passphrase.asc │ └── secret-key.asc ├── util.c ├── x509.c └── zip.c /.gitignore: -------------------------------------------------------------------------------- 1 | t/tmp/** 2 | *.sw? 3 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -O3 -Wall -D_FILE_OFFSET_BITS=64 -g 2 | 3 | all: sign 4 | 5 | sign: sign.o hash.o base64.o pgp.o x509.o rpm.o appimage.o sock.o clearsign.o appx.o zip.o pe.o ko.o util.o cpio.o 6 | 7 | clean: 8 | rm -f sign sign.o hash.o base64.o pgp.o x509.o rpm.o appimage.o sock.o clearsign.o appx.o zip.o pe.o ko.o util.o cpio.o 9 | test: 10 | prove t/*.t 11 | -------------------------------------------------------------------------------- /appimage.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 SUSE LLC 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program (see the file COPYING); if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 17 | * 18 | ***************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "inc.h" 25 | 26 | static inline int 27 | elf16(unsigned char *buf, int le) 28 | { 29 | if (le) 30 | return buf[0] | buf[1] << 8; 31 | return buf[0] << 8 | buf[1]; 32 | } 33 | 34 | static inline u32 35 | elf32(unsigned char *buf, int le) 36 | { 37 | if (le) 38 | return buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24; 39 | return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; 40 | } 41 | 42 | static inline u32 43 | elf64(unsigned char *buf, int le, int is64) 44 | { 45 | if (is64) 46 | { 47 | buf += le ? 4 : 0; 48 | if (buf[0] || buf[1] || buf[2] || buf[3]) 49 | return ~0; 50 | buf += le ? -4 : 4; 51 | } 52 | if (le) 53 | return buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24; 54 | return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; 55 | } 56 | 57 | int 58 | appimage_read(char *filename, HASH_CONTEXT *ctx) 59 | { 60 | unsigned char appimagedigest[64]; /* sha256 sum */ 61 | char *digestfilename; 62 | FILE *fp; 63 | 64 | digestfilename = doalloc(strlen(filename) + 8); 65 | sprintf(digestfilename, "%s.digest", filename); 66 | if ((fp = fopen(digestfilename, "r")) == 0 || 64 != fread(appimagedigest, 1, 64, fp)) 67 | dodie_errno(digestfilename); 68 | fclose(fp); 69 | free(digestfilename); 70 | hash_write(ctx, appimagedigest, 64); 71 | return 1; 72 | } 73 | 74 | void 75 | appimage_write_signature(char *filename, byte *signature, int length) 76 | { 77 | // find .sha256_sig section offset in ELF file 78 | unsigned char elfbuf[128]; 79 | 80 | int le, is64; 81 | off_t soff; 82 | int snum, ssiz; 83 | int i, l, stridx; 84 | FILE *fp; 85 | char *armored_signature; 86 | size_t siglen; 87 | off_t sha256_sig_offset = 0; 88 | u32 sha256_sig_size = 0; 89 | unsigned char *sects, *strsect; 90 | u32 slen; 91 | 92 | if ((fp = fopen(filename, "r+")) == 0) 93 | dodie_errno(filename); 94 | l = fread(elfbuf, 1, 128, fp); 95 | if (l < 128) 96 | dodie("offset 1"); 97 | if (elfbuf[0] != 0x7f || elfbuf[1] != 'E' || elfbuf[2] != 'L' || elfbuf[3] != 'F') 98 | dodie("not an ELF appimage file"); 99 | is64 = elfbuf[4] == 2; 100 | le = elfbuf[5] != 2; 101 | if (is64 && l < 0x40) 102 | dodie("appimage EOF"); 103 | soff = elf64(is64 ? elfbuf + 40 : elfbuf + 32, le, is64); 104 | if (soff == (off_t)(u32)~0) 105 | dodie("bad soff"); 106 | ssiz = elf16(elfbuf + (is64 ? 0x40 - 6 : 0x34 - 6), le); 107 | if (ssiz < (is64 ? 64 : 40) || ssiz >= 32768) 108 | dodie("bad ssiz"); 109 | snum = elf16(elfbuf + (is64 ? 0x40 - 4 : 0x34 - 4), le); 110 | stridx = elf16(elfbuf + (is64 ? 0x40 - 2 : 0x34 - 2), le); 111 | if (stridx >= snum) 112 | dodie("bad stridx"); 113 | sects = doalloc(snum * ssiz); 114 | if (fseek(fp, soff, SEEK_SET) != 0 || fread(sects, 1, snum * ssiz, fp) != snum * ssiz) 115 | dodie_errno("seek/read sects"); 116 | strsect = sects + stridx * ssiz; 117 | if (elf32(strsect + 4, le) != 3) 118 | dodie("bad strsect"); 119 | soff = elf64(is64 ? strsect + 24 : strsect + 16, le, is64); 120 | slen = elf64(is64 ? strsect + 32 : strsect + 20, le, is64); 121 | if (soff == (off_t)(u32)~0 || slen > 0xfffffff) 122 | dodie("bad soff/slen"); 123 | strsect = doalloc(slen); 124 | if (fseek(fp, soff, SEEK_SET) != 0 || fread(strsect, 1, slen, fp) != slen) 125 | dodie("seek/read strsect"); 126 | for (i = 0; i < snum; i++) 127 | { 128 | u32 o = elf32(sects + i * ssiz, le); 129 | if (o > slen) 130 | continue; 131 | // printf("sect #%d %s (o=%d)\n", i, strsect + o, o); 132 | 133 | if (o + 11 <= slen && memcmp(strsect + o, ".sha256_sig", 11) == 0) { 134 | u32 sh_offset = i * ssiz + (is64 ? 24 : 16); 135 | sha256_sig_offset = elf64(sects + sh_offset, le, is64); 136 | sha256_sig_size = elf64(sects + sh_offset + (is64 ? 8 : 4), le, is64); 137 | if (sha256_sig_offset == (off_t)(u32)~0 || sha256_sig_size > 0xfffffff) 138 | dodie("bad signature soff/slen"); 139 | break; 140 | } 141 | } 142 | free(strsect); 143 | free(sects); 144 | if (sha256_sig_offset == 0) 145 | dodie(".sha256_sig not found"); 146 | 147 | armored_signature = get_armored_signature(signature, length); 148 | siglen = strlen(armored_signature) + 1; 149 | if (siglen > sha256_sig_size) 150 | dodie("section too small for signature"); 151 | 152 | if (fseek(fp, sha256_sig_offset, SEEK_SET) == (off_t)-1) 153 | dodie_errno("fseek"); 154 | if (fwrite(armored_signature, siglen, 1, fp) != 1) 155 | dodie_errno("signature write"); 156 | for(; siglen < sha256_sig_size; siglen++) 157 | fputc(0x0, fp); 158 | if (fclose(fp)) 159 | dodie_errno("fclose error"); 160 | free(armored_signature); 161 | } 162 | 163 | -------------------------------------------------------------------------------- /appx.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program (see the file COPYING); if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 17 | * 18 | ***************************************************************/ 19 | 20 | #include "inc.h" 21 | 22 | static void 23 | dohash(int fd, u64 size, unsigned char *out) 24 | { 25 | unsigned char buf[65536]; 26 | HASH_CONTEXT ctx; 27 | 28 | hash_init(&ctx); 29 | while (size > 0) 30 | { 31 | int chunk = size > sizeof(buf) ? sizeof(buf) : size; 32 | doread(fd, buf, chunk); 33 | hash_write(&ctx, buf, chunk); 34 | size -= chunk; 35 | } 36 | hash_final(&ctx); 37 | memcpy(out, hash_read(&ctx), hash_len()); 38 | } 39 | 40 | static u32 41 | hashfileentry(struct zip *zip, int fd, char *fn, unsigned char *out) 42 | { 43 | unsigned char *entry; 44 | u64 datasize; 45 | 46 | entry = zip_findentry(zip, fn); 47 | if (!entry) 48 | { 49 | fprintf(stderr, "missing '%s' file", fn); 50 | exit(1); 51 | } 52 | datasize = zip_seekdata(zip, fd, entry); 53 | dohash(fd, datasize, out); 54 | return zip_entry_datetime(entry); 55 | } 56 | 57 | static int 58 | appx_create_contentinfo(struct appxdata *appxdata, int fd) 59 | { 60 | static const unsigned char axmgsig[4] = { 0x41, 0x50, 0x50, 0x58 }; 61 | static const unsigned char axpcsig[4] = { 0x41, 0x58, 0x50, 0x43 }; 62 | static const unsigned char axcdsig[4] = { 0x41, 0x58, 0x43, 0x44 }; 63 | static const unsigned char axctsig[4] = { 0x41, 0x58, 0x43, 0x54 }; 64 | static const unsigned char axbmsig[4] = { 0x41, 0x58, 0x42, 0x4d }; 65 | static const unsigned char axcisig[4] = { 0x41, 0x58, 0x43, 0x49 }; 66 | unsigned char *digest, *dp; 67 | int hlen = hash_len(); 68 | int offset; 69 | 70 | /* rewind */ 71 | doseek(fd, 0); 72 | 73 | /* create digest space */ 74 | digest = doalloc(4 + (4 + hlen) * 5); 75 | memset(digest, 0, 4 + (4 + hlen) * 5); 76 | 77 | /* add magic */ 78 | memcpy(digest, axmgsig, 4); 79 | dp = digest + 4; 80 | 81 | /* hash from start to central dir */ 82 | memcpy(dp, axpcsig, 4); 83 | dohash(fd, appxdata->zip.cd_offset, dp + 4); 84 | dp += 4 + hlen; 85 | 86 | /* hash from central dir to end */ 87 | memcpy(dp, axcdsig, 4); 88 | dohash(fd, appxdata->zip.size - appxdata->zip.cd_offset, dp + 4); 89 | dp += 4 + hlen; 90 | 91 | /* hash content types */ 92 | memcpy(dp, axctsig, 4); 93 | appxdata->datetime = hashfileentry(&appxdata->zip, fd, "[Content_Types].xml", dp + 4); 94 | dp += 4 + hlen; 95 | 96 | /* hash block map */ 97 | memcpy(dp, axbmsig, 4); 98 | hashfileentry(&appxdata->zip, fd, "AppxBlockMap.xml", dp + 4); 99 | dp += 4 + hlen; 100 | 101 | /* zero AppxMetadata/CodeIntegrity.cat */ 102 | memcpy(dp, axcisig, 4); 103 | dp += 4 + hlen; 104 | 105 | x509_init(&appxdata->cb_content); 106 | offset = x509_appx_contentinfo(&appxdata->cb_content, digest, (int)(dp - digest)); 107 | free(digest); 108 | return offset; 109 | } 110 | 111 | static void 112 | appx_create_signedattrs(struct appxdata *appxdata, int offset, time_t t) 113 | { 114 | HASH_CONTEXT ctx; 115 | 116 | /* hash the spccontent */ 117 | hash_init(&ctx); 118 | hash_write(&ctx, appxdata->cb_content.buf + offset, appxdata->cb_content.len - offset); 119 | hash_final(&ctx); 120 | x509_init(&appxdata->cb_signedattrs); 121 | x509_appx_signedattrs(&appxdata->cb_signedattrs, hash_read(&ctx), hash_len(), t); 122 | } 123 | 124 | int 125 | appx_read(struct appxdata *appxdata, int fd, char *filename, HASH_CONTEXT *ctx, time_t t) 126 | { 127 | int offset; 128 | 129 | memset(appxdata, 0, sizeof(*appxdata)); 130 | zip_read(&appxdata->zip, fd); 131 | 132 | if (zip_findentry(&appxdata->zip, "AppxSignature.p7x")) 133 | return 0; 134 | 135 | /* create spccontentinfo */ 136 | offset = appx_create_contentinfo(appxdata, fd); 137 | /* create signedattrs */ 138 | appx_create_signedattrs(appxdata, offset, t); 139 | /* hash signedattrs */ 140 | hash_write(ctx, appxdata->cb_signedattrs.buf, appxdata->cb_signedattrs.len); 141 | return 1; 142 | } 143 | 144 | void 145 | appx_write(struct appxdata *appxdata, int outfd, int fd, struct x509 *cert, int pubalgo, struct x509 *sigcb, struct x509 *othercerts) 146 | { 147 | static const unsigned char p7xmagic[4] = { 0x50, 0x4b, 0x43, 0x58 }; 148 | struct x509 cb; 149 | extern int appxdetached; 150 | 151 | x509_init(&cb); 152 | x509_pkcs7_signed_data(&cb, &appxdata->cb_content, &appxdata->cb_signedattrs, pubalgo, sigcb, cert, othercerts, 0); 153 | /* prepend file magic */ 154 | x509_insert(&cb, 0, p7xmagic, sizeof(p7xmagic)); 155 | if (appxdetached) 156 | { 157 | dowrite(outfd, cb.buf, cb.len); 158 | x509_free(&cb); 159 | return; 160 | } 161 | /* append file to zip, must use deflated compression */ 162 | zip_appendfile(&appxdata->zip, "AppxSignature.p7x", cb.buf, cb.len, 8, appxdata->datetime); 163 | zip_write(&appxdata->zip, fd, outfd); 164 | x509_free(&cb); 165 | } 166 | 167 | void 168 | appx_free(struct appxdata *appxdata) 169 | { 170 | x509_free(&appxdata->cb_content); 171 | x509_free(&appxdata->cb_signedattrs); 172 | zip_free(&appxdata->zip); 173 | } 174 | 175 | -------------------------------------------------------------------------------- /base64.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 SUSE LLC 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program (see the file COPYING); if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 17 | * 18 | ***************************************************************/ 19 | 20 | #include "inc.h" 21 | 22 | static const char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 23 | 24 | void 25 | r64enc(char *out, const byte *str, int len) 26 | { 27 | int a, b, c; 28 | 29 | while (len > 0) 30 | { 31 | a = *str++; 32 | b = --len > 0 ? *str++ : 0; 33 | c = --len > 0 ? *str++ : 0; 34 | --len; 35 | *out++ = bintoasc[a >> 2]; 36 | *out++ = bintoasc[(a & 3) << 4 | b >> 4]; 37 | *out++ = len > -2 ? bintoasc[(b & 15) << 2 | c >> 6] : '='; 38 | *out++ = len > -1 ? bintoasc[c & 63] : '='; 39 | } 40 | *out = 0; 41 | } 42 | 43 | char * 44 | r64dec1(char *p, u32 *vp, int *eofp) 45 | { 46 | int i, x; 47 | unsigned int v = 0; 48 | 49 | for (i = 0; i < 4; ) 50 | { 51 | x = *p++; 52 | if (!x) 53 | return 0; 54 | if (x >= 'A' && x <= 'Z') 55 | x -= 'A'; 56 | else if (x >= 'a' && x <= 'z') 57 | x -= 'a' - 26; 58 | else if (x >= '0' && x <= '9') 59 | x -= '0' - 52; 60 | else if (x == '+') 61 | x = 62; 62 | else if (x == '/') 63 | x = 63; 64 | else if (x == '=' || x == '-') 65 | { 66 | x = 0; 67 | if (i == 0) 68 | { 69 | *eofp = 3; 70 | *vp = 0; 71 | return p - 1; 72 | } 73 | *eofp += 1; 74 | } 75 | else 76 | continue; 77 | v = v << 6 | x; 78 | i++; 79 | } 80 | *vp = v; 81 | return p; 82 | } 83 | 84 | char * 85 | r64dec(char *p, unsigned char **bpp) 86 | { 87 | u32 v; 88 | int eof = 0; 89 | unsigned char *bp = *bpp; 90 | while (!eof) 91 | { 92 | if (!(p = r64dec1(p, &v, &eof))) 93 | return 0; 94 | *bp++ = v >> 16; 95 | *bp++ = v >> 8; 96 | *bp++ = v; 97 | } 98 | *bpp = bp - eof; 99 | return p; 100 | } 101 | 102 | void 103 | printr64(FILE *f, const byte *str, int len) 104 | { 105 | int i = -1; 106 | char *p, *s = doalloc(len * 4 / 3 + 5); 107 | r64enc(s, str, len); 108 | for (p = s; *p; p++) 109 | { 110 | if (++i == 64) 111 | { 112 | i = 0; 113 | putc('\n', f); 114 | } 115 | putc(*p, f); 116 | } 117 | putc('\n', f); 118 | free(s); 119 | } 120 | -------------------------------------------------------------------------------- /bele.h: -------------------------------------------------------------------------------- 1 | 2 | static inline unsigned int 3 | getle2(const unsigned char *b) 4 | { 5 | return b[0] | b[1] << 8; 6 | } 7 | 8 | static inline unsigned int 9 | getle4(const unsigned char *b) 10 | { 11 | return b[0] | b[1] << 8 | b[2] << 16 | b[3] << 24; 12 | } 13 | 14 | static inline void 15 | setle2(unsigned char *b, unsigned int v) 16 | { 17 | b[0] = v & 255; 18 | b[1] = (v >> 8) & 255; 19 | } 20 | 21 | static inline void 22 | setle4(unsigned char *b, unsigned int v) 23 | { 24 | b[0] = v & 255; 25 | b[1] = (v >> 8) & 255; 26 | b[2] = (v >> 16) & 255; 27 | b[3] = (v >> 24) & 255; 28 | } 29 | 30 | 31 | static inline unsigned int 32 | getbe4(const unsigned char *b) 33 | { 34 | return b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; 35 | } 36 | 37 | static inline void 38 | setbe4(unsigned char *b, unsigned int x) 39 | { 40 | b[0] = x >> 24; 41 | b[1] = x >> 16; 42 | b[2] = x >> 8; 43 | b[3] = x; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /clearsign.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 SUSE LLC 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program (see the file COPYING); if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 17 | * 18 | ***************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "inc.h" 25 | 26 | int 27 | clearsign(int fd, char *filename, char *outfilename, HASH_CONTEXT *ctx, const char *hname, int isfilter, int force, FILE **foutp) 28 | { 29 | byte *cbuf; 30 | int l, cbufl; 31 | int have = 0; 32 | int i, j; 33 | int nl = 0; 34 | int first = 1; 35 | FILE *fout; 36 | 37 | cbufl = 8192; 38 | cbuf = doalloc(cbufl); 39 | l = read(fd, cbuf, cbufl); 40 | if (l < 0) 41 | dodie_errno("read"); 42 | if (l >= 34 && !strncmp((char *)cbuf, "-----BEGIN PGP SIGNED MESSAGE-----", 34)) 43 | return 0; /* already signed */ 44 | for (i = 0; i < l; i++) 45 | { 46 | if (cbuf[i] >= 32 || cbuf[i] == '\t' || cbuf[i] == '\r' || cbuf[i] == '\n') 47 | continue; 48 | first++; 49 | } 50 | if (first > 4 && !force) 51 | { 52 | fprintf(stderr, "%s: won't clearsign binaries\n", filename); 53 | exit(1); 54 | } 55 | opensocket(); 56 | if (isfilter) 57 | fout = stdout; 58 | else if ((fout = fopen(outfilename, "w")) == 0) 59 | dodie_errno(outfilename); 60 | fprintf(fout, "-----BEGIN PGP SIGNED MESSAGE-----\nHash: %s\n\n", hname); 61 | while (first || (l = read(fd, cbuf + have, cbufl - have)) > 0 || (l == 0 && have)) 62 | { 63 | first = 0; 64 | if (nl) 65 | hash_write(ctx, (const unsigned char *)"\r\n", 2); 66 | nl = 0; 67 | l += have; 68 | for (i = 0; i < l; i++) 69 | if (cbuf[i] == '\n') 70 | break; 71 | if (i == l && i == cbufl && l != have) 72 | { 73 | cbufl *= 2; 74 | cbuf = dorealloc(cbuf, cbufl); 75 | have = l; 76 | continue; 77 | } 78 | if ((l > 0 && cbuf[0] == '-') || (l > 4 && !strncmp((char *)cbuf, "From ", 5))) 79 | fprintf(fout, "- "); 80 | if (i == l) 81 | { 82 | /* EOF reached, line is unterminated */ 83 | cbuf[l] = '\n'; 84 | l++; 85 | } 86 | if (i > 20000) 87 | dodie("line too long for clearsign"); 88 | fwrite(cbuf, 1, i + 1, fout); 89 | for (j = i - 1; j >= 0; j--) 90 | if (cbuf[j] != '\r' && cbuf[j] != ' ' && cbuf[j] != '\t') 91 | break; 92 | if (j >= 0) 93 | hash_write(ctx, cbuf, j + 1); 94 | nl = 1; 95 | i++; 96 | if (i < l) 97 | memmove(cbuf, cbuf + i, l - i); 98 | have = l - i; 99 | } 100 | if (l < 0) 101 | { 102 | perror("read"); 103 | if (!isfilter) 104 | unlink(outfilename); 105 | exit(1); 106 | } 107 | free(cbuf); 108 | *foutp = fout; 109 | return 1; 110 | } 111 | -------------------------------------------------------------------------------- /cpio.c: -------------------------------------------------------------------------------- 1 | #include "inc.h" 2 | 3 | static u32 4 | cpio_hex(byte *p) 5 | { 6 | u32 x = 0; 7 | int i; 8 | for (i = 0; i < 8; i++, p++) 9 | { 10 | int c = *p; 11 | x <<= 4; 12 | if (c >= '0' && c <= '9') 13 | x += (c - '0'); 14 | else if (c >= 'a' && c <= 'f') 15 | x += (c - 'a') + 10; 16 | else if (c >= 'A' && c <= 'F') 17 | x += (c - 'A') + 10; 18 | else 19 | dodie("invalid cpio data"); 20 | } 21 | return x; 22 | } 23 | 24 | static void 25 | cpio_tohex(byte *p, u32 x) 26 | { 27 | static const char hexstr[] = "0123456789ABCDEF"; 28 | int i; 29 | p += 7; 30 | for (i = 0; i < 8; i++, p--) 31 | { 32 | int c = (x & 15); 33 | x >>= 4; 34 | *p = (byte)hexstr[c]; 35 | } 36 | } 37 | 38 | byte * 39 | cpio_read(int fd, int *typep, int reserve) 40 | { 41 | byte hdr[110]; /* cpio header */ 42 | byte *cpio; 43 | u32 mode, size, namesize, namepad; 44 | 45 | doread(fd, hdr, 110); 46 | if (memcmp(hdr, "070701", 6)) 47 | dodie("bad/unsupported cpio file"); 48 | mode = cpio_hex(hdr + 14); 49 | size = cpio_hex(hdr + 54); 50 | namesize = cpio_hex(hdr + 94); 51 | if (namesize > 8192) 52 | dodie("illegal name size"); 53 | if (size > 0x40000000) 54 | dodie("illegal file size"); 55 | namepad = (6 - (namesize & 3)) & 3; 56 | cpio = doalloc(110 + namesize + namepad + 1 + (reserve ? reserve + 4 : 0)); 57 | memcpy(cpio, hdr, 110); 58 | doread(fd, cpio + 110, namesize + namepad); 59 | cpio[110 + namesize] = 0; 60 | if (!size && !strcmp((char *)cpio + 110, "TRAILER!!!")) 61 | *typep = CPIO_TYPE_TRAILER; 62 | else if (((mode >> 12) & 15) == 8) 63 | *typep = CPIO_TYPE_FILE; 64 | else 65 | *typep = CPIO_TYPE_OTHER; 66 | return cpio; 67 | } 68 | 69 | u32 70 | cpio_name_append(byte *cpio, char *suf) 71 | { 72 | u32 namepad, namesize = strlen((char *)cpio + 110); 73 | strcpy((char *)cpio + 110 + namesize, suf); 74 | namesize += strlen(suf) + 1; 75 | namepad = (6 - (namesize & 3)) & 3; 76 | memset(cpio + 110 + namesize, 0, namepad); 77 | cpio_tohex(cpio + 94, namesize); 78 | return namepad; 79 | } 80 | 81 | u32 82 | cpio_size_set(byte *cpio, u32 size) 83 | { 84 | cpio_tohex(cpio + 54, size); 85 | return (4 - (size & 3)) & 3; 86 | } 87 | 88 | u32 89 | cpio_size_get(byte *cpio, u32 *padp) 90 | { 91 | u32 size = cpio_hex(cpio + 54); 92 | if (padp) 93 | *padp = ((4 - (size & 3)) & 3); 94 | return size; 95 | } 96 | 97 | u32 98 | cpio_headnamesize(byte *cpio) 99 | { 100 | u32 namesize = cpio_hex(cpio + 94); 101 | return 110 + namesize + ((6 - (namesize & 3)) & 3); 102 | } 103 | 104 | -------------------------------------------------------------------------------- /dist/obs-signd.changes: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------- 2 | Wed Jun 19 13:22:39 CEST 2024 - mls@suse.de 3 | 4 | - update to version 2.8.4 5 | * support ECDSA for rpm signing 6 | * use reserved signature space for rpm signing 7 | 8 | ------------------------------------------------------------------- 9 | Fri Jul 7 15:29:31 CEST 2023 - mls@suse.de 10 | 11 | - update to version 2.8.3 12 | * support aes128 cipher in decrypt_cipher_with_gcrypt 13 | * add tls support 14 | 15 | ------------------------------------------------------------------- 16 | Fri Apr 14 14:04:29 CEST 2023 - mls@suse.de 17 | 18 | - update to version 2.8.1 19 | * add privileged log command 20 | * fix sign -t no longer working when aliases are defined 21 | 22 | ------------------------------------------------------------------- 23 | Mon Mar 27 14:13:39 CEST 2023 - mls@suse.de 24 | 25 | - update to version 2.8.0 26 | * support of symmetric encryption of project keys 27 | * support project key decryption with libgcrypt 28 | * support bulk cpio signing 29 | * support privileged operations 30 | 31 | ------------------------------------------------------------------- 32 | Fri Feb 10 10:33:32 CET 2023 - mls@suse.de 33 | 34 | - update to version 2.7.3 35 | * support ecdsa@nistp256 and ecdsa@nistp384 36 | * drop obsolete certgen server side support 37 | * add use-unprivileged-ports config option 38 | * increase keygen request buffer size 39 | 40 | ------------------------------------------------------------------- 41 | Mon Jan 9 10:05:15 CET 2023 - mls@suse.de 42 | 43 | - update to version 2.7.2 44 | * fix dummydeflate not correctly working with big input 45 | 46 | ------------------------------------------------------------------- 47 | Thu Nov 17 10:44:50 CET 2022 - mls@suse.de 48 | 49 | - update to version 2.7.1 50 | * support kernel module signing 51 | * support PE file signing 52 | * support pkcs7/cms signing 53 | * support sha512 hashes 54 | 55 | ------------------------------------------------------------------- 56 | Thu Nov 3 13:25:09 CET 2022 - ro@suse.de 57 | 58 | - drop the no longer required dependency on gpg2_signd_support 59 | 60 | ------------------------------------------------------------------- 61 | Fri Jul 29 12:58:47 CEST 2022 - mls@suse.de 62 | 63 | - update to version 2.6.1 64 | * support directly talking to the gpg agent 65 | * support ed25519 keygen 66 | 67 | ------------------------------------------------------------------- 68 | Wed Jul 6 13:34:39 CEST 2022 - mls@suse.de 69 | 70 | - update to version 2.6.0 71 | * support signature creation with libgcrypt 72 | * support sha512 73 | 74 | ------------------------------------------------------------------- 75 | Mon Jul 26 13:17:04 UTC 2021 - Frank Schreiner 76 | 77 | - update to version 2.5.10 78 | * optional systemd dependency to start after obsapisetup.service 79 | 80 | ------------------------------------------------------------------- 81 | Mon Jul 26 13:14:37 UTC 2021 - Frank Schreiner 82 | 83 | - update to version 2.5.9 84 | * Cleanup for tempdirs 85 | * Require user(obsrun) in spec file 86 | * fix references to var/run to silence systemd complaints 87 | 88 | ------------------------------------------------------------------- 89 | Tue May 4 13:38:16 CEST 2021 - mls@suse.de 90 | 91 | - update to version 2.5.8 92 | * fix unsigned char -> signed int promotion 93 | 94 | ------------------------------------------------------------------- 95 | Mon Apr 19 11:44:05 CEST 2021 - mls@suse.de 96 | 97 | - update to version 2.5.7 98 | * fix signing of rpms with a size greater than 4GByte 99 | * keyextend: make sure that the pubkey does not come from the 100 | future 101 | 102 | ------------------------------------------------------------------- 103 | Wed Mar 11 17:31:56 CET 2020 - ro@suse.de 104 | 105 | - update to version 2.5.6 106 | * fix sysconfig file handling 107 | 108 | ------------------------------------------------------------------- 109 | Mon Mar 9 15:34:30 UTC 2020 - Frank Schreiner 110 | 111 | - update to version 2.5.5 112 | * final switch to systemd 113 | 114 | ------------------------------------------------------------------- 115 | Tue Sep 3 06:05:32 UTC 2019 - Adrian Schröter 116 | 117 | - update to version 2.5.4: 118 | * fix handling of secret key when protected by a passphrase 119 | 120 | ------------------------------------------------------------------- 121 | Tue Jul 2 15:23:31 UTC 2019 - Adrian Schröter 122 | 123 | - update to version 2.5.3 124 | * support containerization 125 | 126 | ------------------------------------------------------------------- 127 | Fri Nov 9 09:32:53 UTC 2018 - Frank Schreiner 128 | 129 | - update to version 2.5.2 130 | * fix privsign with expired key 131 | 132 | ------------------------------------------------------------------- 133 | Fri Feb 9 11:48:14 CET 2018 - ro@suse.de 134 | 135 | - use fillupdir macro 136 | - use macro for buildroot 137 | 138 | ------------------------------------------------------------------- 139 | Mon Nov 6 12:55:14 CET 2017 - mls@suse.de 140 | 141 | - update to version 2.4.2 142 | * support --pkcs1pss option 143 | 144 | ------------------------------------------------------------------- 145 | Mon Mar 6 09:52:35 UTC 2017 - fschreiner@suse.com 146 | 147 | - update to version 2.4.1 148 | * fix dependency cycle in init script 149 | 150 | ------------------------------------------------------------------- 151 | Wed Mar 1 13:44:38 UTC 2017 - adrian@suse.de 152 | 153 | - update to version 2.4.0 154 | * support signing of AppImage files 155 | 156 | ------------------------------------------------------------------- 157 | Wed Jul 6 16:01:37 CEST 2016 - mls@suse.de 158 | 159 | - update to version 2.3.0 160 | * add cert cn/email truncation 161 | * allow 4096 bits keylength 162 | * improved documentation 163 | 164 | ------------------------------------------------------------------- 165 | Thu Mar 21 12:00:31 CET 2013 - mls@suse.de 166 | 167 | - update to version 2.2.1 168 | * new -S output format 169 | 170 | ------------------------------------------------------------------- 171 | Wed Mar 20 14:01:47 UTC 2013 - adrian@suse.de 172 | 173 | - update to version 2.2.0 174 | * complete support for secure boot signing 175 | 176 | ------------------------------------------------------------------- 177 | Thu Jan 17 15:49:07 CET 2013 - mls@suse.de 178 | 179 | - update to 2.1.5.1 to support -O and rsa keys with -p 180 | 181 | ------------------------------------------------------------------- 182 | Mon Aug 13 14:28:34 CEST 2012 - mls@suse.de 183 | 184 | - update to 2.1.5 185 | implement -D option to create raw detchaed signatures 186 | 187 | ------------------------------------------------------------------- 188 | Fri Jul 6 12:15:39 UTC 2012 - adrian@suse.de 189 | 190 | - fix build for 12.2 191 | 192 | ------------------------------------------------------------------- 193 | Fri May 11 14:47:14 UTC 2012 - coolo@suse.com 194 | 195 | - update to 2.1.4 196 | * frontport -S option to speed up repository generation 197 | 198 | ------------------------------------------------------------------- 199 | Mon Sep 19 11:31:34 UTC 2011 - adrian@suse.de 200 | 201 | - update to version 2.1.3 202 | * Fix re-signing failure (#713013) 203 | 204 | ------------------------------------------------------------------- 205 | Tue Jul 27 08:13:18 UTC 2010 - adrian@suse.de 206 | 207 | - update to version 2.1.2 208 | * obsrun group is allowed to run sign by default 209 | * support of gpg key expiration date extension call 210 | 211 | ------------------------------------------------------------------- 212 | Fri Jun 25 13:28:20 UTC 2010 - adrian@suse.de 213 | 214 | - update to version 2.0.81 215 | * runlevel script fixes 216 | 217 | ------------------------------------------------------------------- 218 | Mon Jun 7 09:41:43 UTC 2010 - adrian@suse.de 219 | 220 | - ensure to use the correct version of gpg2 221 | 222 | ------------------------------------------------------------------- 223 | Thu Feb 18 11:43:09 UTC 2010 - adrian@suse.de 224 | 225 | - package files with correct default permissions 226 | (/usr/bin/sign is 4750 now => request to security team in #580857) 227 | 228 | ------------------------------------------------------------------- 229 | Fri Dec 11 07:55:03 UTC 2009 - adrian@suse.de 230 | 231 | - initial own package, calling it version 1.7.0 232 | (needed for obs-server 1.7) 233 | 234 | -------------------------------------------------------------------------------- /dist/obs-signd.spec: -------------------------------------------------------------------------------- 1 | # 2 | # spec file for package obs-signd 3 | # 4 | # Copyright (c) 2023 SUSE LLC 5 | # 6 | # All modifications and additions to the file contributed by third parties 7 | # remain the property of their copyright owners, unless otherwise agreed 8 | # upon. The license for this file, and modifications and additions to the 9 | # file, is the same license as for the pristine package itself (unless the 10 | # license for the pristine package is not an Open Source License, in which 11 | # case the license is the MIT License). An "Open Source License" is a 12 | # license that conforms to the Open Source Definition (Version 1.9) 13 | # published by the Open Source Initiative. 14 | 15 | # Please submit bugfixes or comments via https://bugs.opensuse.org/ 16 | # 17 | 18 | 19 | Name: obs-signd 20 | Summary: The sign daemon 21 | License: GPL-2.0-only 22 | Group: Productivity/Networking/Web/Utilities 23 | 24 | Version: 2.8.4 25 | Release: 0 26 | 27 | URL: http://en.opensuse.org/Build_Service 28 | Source: obs-sign-%version.tar.xz 29 | Source1: obs-signd-rpmlintrc 30 | Requires: user(obsrun) 31 | %if 0%{?suse_version} 32 | PreReq: %fillup_prereq 33 | PreReq: permissions 34 | %endif 35 | 36 | %if ! %{defined _fillupdir} 37 | %define _fillupdir /var/adm/fillup-templates 38 | %endif 39 | 40 | # the following build requires are needed for the testsuite 41 | %if 0%{?suse_version} 42 | BuildRequires: gpg2 43 | %else 44 | BuildRequires: gpg 45 | %endif 46 | BuildRequires: make 47 | BuildRequires: openssl 48 | 49 | %description 50 | The openSUSE Build Service sign client and daemon. 51 | 52 | This daemon can be used to sign anything via gpg, but it speaks with a remote server 53 | to avoid the need to host the private key on the same server. 54 | 55 | %prep 56 | %setup -n obs-sign-%version 57 | 58 | %build 59 | make CFLAGS="$RPM_OPT_FLAGS -fpie -D_FILE_OFFSET_BITS=64" LDFLAGS="-pie" 60 | 61 | %check 62 | make test 63 | 64 | %install 65 | # run level script 66 | mkdir -p %{buildroot}%{_unitdir} 67 | install -m 0755 dist/signd.service %{buildroot}%{_unitdir}/obssignd.service 68 | install -d -m 0755 %{buildroot}%{_sbindir} 69 | ln -sf /usr/sbin/service %{buildroot}%{_sbindir}/rcobssignd 70 | 71 | # man pages 72 | install -d -m 0755 %{buildroot}%{_mandir}/man{5,8} 73 | install -d -m 0755 %{buildroot}/usr/bin 74 | for j in `ls sig*.{5,8}`; do 75 | gzip -9 ${j} 76 | done 77 | for k in 5 8; do 78 | install -m 0644 sig*.${k}.gz %{buildroot}%{_mandir}/man${k}/ 79 | done 80 | 81 | # binaries and configuration 82 | install -d -m 0755 %{buildroot}/etc/permissions.d 83 | install -m 0755 signd %{buildroot}/usr/sbin/ 84 | install -m 0750 sign %{buildroot}/usr/bin/ 85 | install -m 0644 sign.conf %{buildroot}/etc/ 86 | install -m 0644 dist/sign.permission %{buildroot}/etc/permissions.d/sign 87 | 88 | # install fillups 89 | FILLUP_DIR=%{buildroot}%{_fillupdir} 90 | install -d -m 755 $FILLUP_DIR 91 | install -m 0644 dist/sysconfig.signd $FILLUP_DIR/ 92 | 93 | %pre 94 | %service_add_pre obssignd.service 95 | 96 | %preun 97 | %service_del_preun obssignd.service 98 | 99 | %post 100 | %service_add_post obssignd.service 101 | %if 0%{?suse_version} > 1220 102 | %set_permissions /etc/permissions.d/sign 103 | %else 104 | %run_permissions 105 | %endif 106 | %fillup_only -n signd 107 | 108 | %postun 109 | %service_del_postun obssignd.service 110 | 111 | %files 112 | %config(noreplace) /etc/sign.conf 113 | %verify(not mode) %attr(4750,root,obsrun) /usr/bin/sign 114 | %attr(0755,root,root) /usr/sbin/signd 115 | %attr(0755,root,root) /usr/sbin/rcobssignd 116 | %attr(0644,root,root) %{_unitdir}/obssignd.service 117 | %{_fillupdir}/sysconfig.signd 118 | /etc/permissions.d/sign 119 | %doc %{_mandir}/man*/* 120 | 121 | %changelog 122 | -------------------------------------------------------------------------------- /dist/sign-rpmlintrc: -------------------------------------------------------------------------------- 1 | 2 | # for sign executable 3 | addFilter("permissions-file-setuid-bit") 4 | setBadness("permissions-file-setuid-bit", 0) 5 | addFilter("permissions-unauthorized-file") 6 | setBadness("permissions-unauthorized-file", 0) 7 | 8 | -------------------------------------------------------------------------------- /dist/sign.permission: -------------------------------------------------------------------------------- 1 | /usr/bin/sign root:obsrun 4750 2 | -------------------------------------------------------------------------------- /dist/signd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=GPG Sign Daemon 3 | After=syslog.target obsapisetup.service 4 | 5 | [Service] 6 | PIDFile=/run/signd.pid 7 | EnvironmentFile=-/etc/sysconfig/signd 8 | ExecStart=/usr/sbin/signd 9 | Restart=on-abort 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /dist/sysconfig.signd: -------------------------------------------------------------------------------- 1 | ## Path: Applications/OBS 2 | ## Description: Define gpgp home directory for signing daemon 3 | ## Type: string 4 | ## Default: "/srv/obs/gnupg" 5 | ## Config: OBS 6 | # 7 | # An empty setting will lead to a check for /obs/gnupg or /srv/obs/gnupg 8 | # 9 | GNUPGHOME="/srv/obs/gnupg" 10 | -------------------------------------------------------------------------------- /examples/privileged_gensig: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use Encode; 5 | 6 | sub rungpg { 7 | my ($stdin, $prg, @args) = @_; 8 | 9 | local *RH; 10 | local *WH; 11 | local *KID; 12 | 13 | pipe RH, WH; 14 | my $pid = open(KID, "-|"); 15 | if (!defined $pid) { 16 | die("could not fork: $!\n"); 17 | exit(0); 18 | } 19 | if (!$pid) { 20 | close RH; 21 | if (!open(STDERR, ">&STDOUT")) { 22 | print STDOUT "can't dup stdout: $!\n"; 23 | exit(1); 24 | } 25 | open(STDOUT, ">&WH") || die("can't dup writepipe: $!\n"); 26 | close WH; 27 | if (ref($stdin)) { 28 | open(STDIN, '<&=', $stdin) || die("stdin dup: $!\n"); 29 | } elsif (defined $stdin) { 30 | open(STDIN, '<', $stdin) || die("$stdin: $!\n"); 31 | } 32 | exec $prg, @args; 33 | die("$prg: $!\n"); 34 | } 35 | close WH; 36 | my $out = ''; 37 | my $err = ''; 38 | 1 while sysread(KID, $err, 4096, length($err)) > 0; 39 | 1 while sysread(RH, $out, 4096, length($out)) > 0; 40 | close(RH); 41 | my $status = 0; 42 | $status = $? || 255 unless close KID; 43 | $status >>= 8 if $status >= 256; 44 | return ($status, $out, $err); 45 | } 46 | 47 | sub gensig { 48 | my ($arg) = @_; 49 | 50 | my @args = split("\0", $arg); 51 | for (@args) { 52 | die("Bad characters in cookie\n") if /[\000-\037\177]/; 53 | decode('UTF-8', $_, Encode::FB_CROAK | Encode::LEAVE_SRC) if /[\200-\377]/; 54 | } 55 | splice(@args, 3, 1) if @args >= 4; 56 | print "Command: @args\n"; 57 | my $datafd; 58 | open($datafd, '+>', undef) || die; 59 | syswrite($datafd, $arg); 60 | sysseek($datafd, 0, 0); 61 | my ($status, $out, $err) = rungpg($datafd, 'gpg', '-b'); 62 | die($err || "error") if $status; 63 | my $sig = unpack('H*', $out); 64 | print "Signature: $sig\n"; 65 | } 66 | 67 | if (@ARGV == 0) { 68 | $| = 1; 69 | while (1) { 70 | print "\nCookie: "; 71 | my $cookie = ''; 72 | while (1) { 73 | my $r = sysread(\*STDIN, $cookie, 8192, length($cookie)); 74 | die("read: $!\n") unless defined $r; 75 | last if !$r || $cookie =~ s/[\r\n].*//; 76 | } 77 | eval { gensig(pack('H*', $cookie)) }; 78 | print "Error: $@" if $@; 79 | } 80 | } elsif (@ARGV == 1) { 81 | gensig(pack('H*', $ARGV[0])); 82 | } else { 83 | die("bad number of args\n") unless @ARGV > 4; 84 | die("bad nonce\n") unless $ARGV[3] =~ /^\A[0-9a-f]+\z/s; 85 | gensig(join("\0", @ARGV)); 86 | } 87 | -------------------------------------------------------------------------------- /examples/privileged_sign: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | 5 | use Socket; 6 | use POSIX; 7 | use Digest::SHA; 8 | 9 | my $keyring = 'standard'; 10 | my $signuser; 11 | my $hash; 12 | my $pubalgo; 13 | my $bindreservedport = $> ? 0 : 1; 14 | my $signhost = '127.0.0.1'; 15 | my $port = 5167; 16 | my $conf = '/etc/sign.conf'; 17 | my $sighelper; 18 | my $proto; 19 | my $ssl_certfile; 20 | my $ssl_keyfile; 21 | my $ssl_verifyfile; 22 | my $ssl_verifydir; 23 | 24 | my $algouser; 25 | my $tcpproto = getprotobyname('tcp'); 26 | my %cmdline_set; 27 | my $signaddr; # host lookup cache 28 | 29 | sub digestctx { 30 | return Digest::SHA->new($1) if $hash =~ /^sha(\d+)$/i; 31 | die("unsupported hash $hash\n"); 32 | } 33 | 34 | sub bindreservedport { 35 | my ($sock) = @_; 36 | local *S = $sock; 37 | my %blacklist; 38 | local *BL; 39 | if (open(BL, ") { 41 | chomp; 42 | next unless /^\s*(\d+)/; 43 | $blacklist{0 + $1} = 1; 44 | } 45 | close BL; 46 | } 47 | while (1) { 48 | my $po; 49 | for ($po = 600; $po < 1024; $po++) { 50 | next if $blacklist{$po}; 51 | return if bind(S, sockaddr_in($po, INADDR_ANY)); 52 | } 53 | sleep(3); 54 | } 55 | } 56 | 57 | sub swrite { 58 | my ($fh, $data) = @_; 59 | while (length($data)) { 60 | my $l = syswrite($fh, $data, length($data)); 61 | die("write: $!\n") unless defined $l; 62 | $data = substr($data, $l); 63 | } 64 | } 65 | 66 | sub gensig { 67 | my (@args) = @_; 68 | my $nonce = rpc_fatal_1('nonce', '-'); 69 | die("no nonce returned\n") unless $nonce; 70 | splice(@args, 2, 0, $keyring, $nonce); 71 | my $data = unpack('H*', join("\0", @args)); 72 | if ($sighelper) { 73 | my $sig = `$sighelper $data`; 74 | chomp $sig; 75 | die("bad sighelper return") unless $sig =~ /([0-9a-fA-F]+)$/s; 76 | return $1; 77 | } 78 | my $tty; 79 | open($tty, '+>', '/dev/tty') || die("/dev/tty: $!\n"); 80 | my $oldfh = select($tty); 81 | $| = 1; 82 | select($oldfh); 83 | print $tty "Cookie: $data\n"; 84 | print $tty "Signature: "; 85 | my $sig = ''; 86 | while (1) { 87 | my $r = sysread($tty, $sig, 8192, length($sig)); 88 | die("read: $!\n") unless defined $r; 89 | last if !$r || $sig =~ s/[\r\n].*//; 90 | } 91 | close($tty); 92 | die("malformed signature\n") unless $sig =~ /\A(?:[0-9a-fA-F][0-9a-fA-F])+\z/s; 93 | return $sig; 94 | } 95 | 96 | sub pkdecodetaglenoff { 97 | my ($pkg) = @_; 98 | my $tag = unpack('C', $pkg); 99 | die("not a pgp packet\n") unless $tag & 128; 100 | my ($len, $off); 101 | if ($tag & 64) { 102 | # new packet format 103 | $tag &= 63; 104 | $len = unpack('@1C', $pkg); 105 | if ($len < 192) { 106 | $off = 2; 107 | } elsif ($len >= 192 && $len < 224) { 108 | $len = unpack('@1n', $pkg) - 48960; 109 | $off = 3; 110 | } elsif ($len == 255) { 111 | $len = unpack('@2N', $pkg); 112 | $off = 6; 113 | } 114 | } else { 115 | # old packet format 116 | if (($tag & 3) == 0) { 117 | $len = unpack('@1C', $pkg); 118 | $off = 2; 119 | } elsif (($tag & 3) == 1) { 120 | $len = unpack('@1n', $pkg); 121 | $off = 3; 122 | } elsif (($tag & 3) == 2) { 123 | $len = unpack('@1N', $pkg); 124 | $off = 5; 125 | } 126 | $tag = ($tag & 60) >> 2; 127 | } 128 | die("unsupported pgp packet length\n") unless defined $off; 129 | die("truncated pgp packet\n") if length($pkg) < $off + $len; 130 | return ($tag, $len, $off); 131 | } 132 | 133 | sub pkdecodepacket { 134 | my ($pk) = @_; 135 | my ($tag, $len, $off) = pkdecodetaglenoff($pk); 136 | return ($tag, substr($pk, $off, $len), substr($pk, $len + $off)); 137 | } 138 | 139 | sub pkdecodesubpacket { 140 | my ($pk) = @_; 141 | my ($tag, $len, $off) = pkdecodetaglenoff(pack('C', 0xc0).$pk); 142 | return (unpack('C', substr($pk, $off - 1, 1)), substr($pk, $off, $len - 1), substr($pk, $len + $off - 1)); 143 | } 144 | 145 | sub pk2sig { 146 | my ($tag, $sig) = pkdecodepacket($_[0]); 147 | die("not a pgp signature\n") unless $tag == 2; 148 | return $sig; 149 | } 150 | 151 | sub subpkgs2issuer { 152 | my ($subpks) = @_; 153 | my ($stag, $spk); 154 | while ($subpks ne '') { 155 | ($stag, $spk, $subpks) = pkdecodesubpacket($subpks); 156 | return substr($spk, -8) if $stag == 16 || $stag == 33; 157 | } 158 | return undef; 159 | } 160 | 161 | sub sig2issuer { 162 | my ($sig) = @_; 163 | my $issuer; 164 | if (unpack('C', $sig) == 3) { 165 | $issuer = substr($sig, 7, 8); 166 | } elsif (unpack('C', $sig) == 4) { 167 | $issuer = subpkgs2issuer(substr($sig, 4 + 2, unpack('@4n', $sig), '')); 168 | $issuer ||= subpkgs2issuer(substr($sig, 6 + 2, unpack('@6n', $sig))); 169 | } else { 170 | die("unsupported pgp signature version\n"); 171 | } 172 | return $issuer ? uc(unpack('H*', $issuer)) : undef; 173 | } 174 | 175 | sub sig2mpis { 176 | my ($sig) = @_; 177 | my ($sigalgo, $mpidata); 178 | if (unpack('C', $sig) == 3) { 179 | $sigalgo = unpack('@15C', $sig); 180 | $mpidata = substr($sig, 19); 181 | } elsif (unpack('C', $sig) == 4) { 182 | $sigalgo = unpack('@2C', $sig); 183 | $mpidata = substr($sig, 4); 184 | $mpidata = substr($sig, 2 + unpack('n', $mpidata)); 185 | $mpidata = substr($sig, 2 + unpack('n', $mpidata)); 186 | } else { 187 | die("unsupported pgp signature version\n"); 188 | } 189 | my $nmpis; 190 | $nmpis = 1 if $sigalgo == 1; 191 | $nmpis = 2 if $sigalgo == 17 || $sigalgo == 19 || $sigalgo == 22; 192 | die("unsupported pgp algorithm $sigalgo\n") unless defined $nmpis; 193 | my @mpis; 194 | for (1..$nmpis) { 195 | die unless length($mpidata) >= 2; 196 | my $l = unpack('n', substr($mpidata, 0, 2, '')); 197 | die unless length($mpidata) >= int(($l + 7) / 8); 198 | push @mpis, substr($mpidata, 0, int(($l + 7) / 8), ''); 199 | } 200 | return ($sigalgo, \@mpis); 201 | } 202 | 203 | sub x509_pack { 204 | my ($tag, $data) = @_; 205 | my $l = length($data); 206 | return pack("CC", $tag, $l) . $data if $l < 128; 207 | my $ll = $l >> 8 ? $l >> 16 ? $l >> 24 ? 4 : 3 : 2 : 1; 208 | return pack("CCa*", $tag, $ll | 0x80, substr(pack("N", $l), -$ll)) . $data; 209 | } 210 | 211 | sub x509_pack_mpi { 212 | my $mpi = $_[0]; 213 | $mpi = pack('C', 0) if length($mpi) == 0; 214 | $mpi = substr($mpi, 1) while length($mpi) > 1 && unpack('C', $mpi) == 0; 215 | return x509_pack(0x02, unpack('C', $mpi) >= 128 ? pack('C', 0).$mpi : $mpi); 216 | } 217 | 218 | sub x509_pack_signature { 219 | my ($algo, $mpis) = @_; 220 | if ($algo == 1) { 221 | my $sig = $mpis->[0]; 222 | $sig = "\0$sig" while length($sig) % 8; 223 | return $sig; 224 | } elsif ($algo == 17 || $algo == 19) { 225 | return x509_pack(0x30, x509_pack_mpi($mpis->[0]).x509_pack_mpi($mpis->[1])); 226 | } elsif ($algo == 22) { 227 | die("x509_pack_signature: eddsa is not supported\n"); 228 | } 229 | die("x509_pack_signature: unsupported pubkey algorithm $algo\n"); 230 | } 231 | 232 | sub copyfd { 233 | my ($in, $out, $size) = @_; 234 | while ($size > 0) { 235 | my $buf; 236 | my $r = sysread($in, $buf, $size > 8192 ? 8192 : $size); 237 | die("sysread: $!\n") unless defined $r; 238 | die("unexpected EOF\n") unless $r; 239 | swrite($out, $buf); 240 | $size -= length($buf); 241 | } 242 | } 243 | 244 | sub rpc { 245 | my (@args) = @_; 246 | die("bad number of args\n") unless @args >= 2; 247 | splice(@args, 0, 2, 'privileged', $args[1], $args[0]); 248 | splice(@args, 2, 0, $keyring, gensig(@args)) if $args[2] ne 'nonce'; 249 | if (!$signaddr) { 250 | $signaddr = inet_aton($signhost); 251 | die("$signhost: unknown host\n") unless $signaddr; 252 | } 253 | local *S; 254 | socket(S, PF_INET, SOCK_STREAM, $tcpproto) || die("socket: $!\n"); 255 | bindreservedport(\*S) if $bindreservedport; 256 | connect(S, sockaddr_in($port, $signaddr)) || die("connect to $signhost:$port: $!\n"); 257 | if ($proto && $proto eq 'ssl') { 258 | require IO::Socket::SSL; 259 | my %sslconf; 260 | $sslconf{'SSL_verify_mode'} = &IO::Socket::SSL::SSL_VERIFY_PEER; 261 | $sslconf{'SSL_key_file'} = $ssl_keyfile if $ssl_keyfile; 262 | $sslconf{'SSL_cert_file'} = $ssl_certfile if $ssl_certfile; 263 | $sslconf{'SSL_ca_file'} = $ssl_verifyfile if $ssl_verifyfile; 264 | $sslconf{'SSL_ca_path'} = $ssl_verifydir if $ssl_verifydir; 265 | my $ssl = IO::Socket::SSL->start_SSL(\*S, %sslconf); 266 | die("ssl handshake failed: ".($IO::Socket::SSL::SSL_ERROR ? $IO::Socket::SSL::SSL_ERROR : 'unknown error')."\n") unless $ssl; 267 | *S = $ssl; 268 | } 269 | 270 | my $pack = pack('n' x (1 + @args), scalar(@args), map {length($_)} @args).join('', @args); 271 | $pack = pack('nn', length($pack), 0).$pack; 272 | swrite(\*S, $pack); 273 | my $buf = ''; 274 | while (1) { 275 | my $r = sysread(S, $buf, 8192, length($buf)); 276 | if (!defined($r)) { 277 | die("sysread: $!\n") if $! != POSIX::EINTR; 278 | next; 279 | } 280 | last unless $r; 281 | last if length($buf) >= 2 * 65536 + 6; 282 | } 283 | die("answer is too small\n") unless length($buf) >= 6; 284 | my ($status, $outl, $errl) = unpack('nnn', $buf); 285 | die("answer is too small\n") unless length($buf) >= 6 + $outl + $errl; 286 | my $err = substr($buf, 6 + $outl, $errl); 287 | my @r; 288 | if ($outl) { 289 | die("answer is too small\n") unless $outl >= 2; 290 | my $out = substr($buf, 6, $outl); 291 | my $nret = unpack('n', $out); 292 | die("answer is too small\n") unless $outl >= 2 + 2 * $nret; 293 | (undef, @r) = unpack('n' x (1 + $nret), $out); 294 | substr($out, 0, 2 + 2 * $nret, ''); 295 | for (@r) { 296 | die("answer is too small\n") unless length($out) >= $_; 297 | $_ = substr($out, 0, $_, ''); 298 | } 299 | die("excess data in answer\n") if length($out); 300 | } 301 | if ($status == 0 && @args > 4 && ($args[4] eq 'backup' || $args[4] eq 'log')) { 302 | die("bad $args[4] answer\n") unless @r == 1 && $r[0] =~ /^(\d+)$/; 303 | my $size = $1; 304 | substr($buf, 0, 6 + $outl + $errl, ''); 305 | $buf = substr($buf, 0, $size) if $size < length($buf); 306 | swrite(\*STDOUT, $buf) if length($buf); 307 | copyfd(\*S, \*STDOUT, $size - length($buf)); 308 | } 309 | close S; 310 | return ($status, $err, @r); 311 | } 312 | 313 | sub rpc_fatal { 314 | my ($resnum, @args) = @_; 315 | my ($status, $err, @r) = rpc(@args); 316 | die($err || "unknown error\n") if $status; 317 | die("unexpected number of results\n") if defined($resnum) && @r != $resnum; 318 | return @r; 319 | } 320 | 321 | sub rpc_fatal_1 { 322 | return (rpc_fatal(1, @_))[0]; 323 | } 324 | 325 | sub readconfig { 326 | my ($conffile) = @_; 327 | local *F; 328 | open(F, '<', $conffile) || die("$conffile: $!\n"); 329 | while() { 330 | chomp; 331 | next if /^#/; 332 | my @s = split(' ', $_); 333 | next unless @s; 334 | if ($s[0] eq 'server:') { 335 | $signhost = $s[1] unless $cmdline_set{\$signhost}; 336 | } elsif ($s[0] eq 'port:') { 337 | $port = $s[1] unless $cmdline_set{\$port}; 338 | } elsif ($s[0] eq 'proto:') { 339 | $proto = $s[1] unless $cmdline_set{\$proto}; 340 | } elsif ($s[0] eq 'ssl_certfile:') { 341 | $ssl_certfile = $s[1]; 342 | } elsif ($s[0] eq 'ssl_keyfile:') { 343 | $ssl_keyfile = $s[1]; 344 | } elsif ($s[0] eq 'ssl_verifyfile:') { 345 | $ssl_verifyfile = $s[1]; 346 | } elsif ($s[0] eq 'ssl_verifydir:') { 347 | $ssl_verifydir= $s[1]; 348 | } elsif ($s[0] eq 'use-unprivileged-ports:') { 349 | $bindreservedport = 0 if $s[1] eq '1' || $s[1] =~ /^true$/i; 350 | } 351 | } 352 | close F; 353 | } 354 | 355 | sub usage { 356 | print {$_[0]} <<'EOF'; 357 | Usage: privileged_sign [-u user|XXXXXXXX] [-h hash] [--keyring keyring] ... 358 | -O [file] rawopensslsign 359 | -p print pubkey 360 | -k print keyid 361 | -g type expire name email [alias] generate key 362 | -x expire extend key 363 | --keylist list keyring 364 | --keyphrases list allowed keys 365 | --keydel delete key 366 | --keycvt convert key 367 | --aliaslist list aliases 368 | --aliasgen create alias 369 | --aliasdel delete alias 370 | --enckeylist list encryption keys 371 | --enckeygen type [expire] create encryption key 372 | --enckeydel delete encryption key 373 | --backup create encrypted backup 374 | --log get privileged log 375 | 376 | user is either the email adress or last 4 bytes in hex of the keyid. 377 | EOF 378 | exit($_[1] || 0); 379 | } 380 | 381 | my %modeoptions = ( 382 | '-O' => 'rawopensslsign', 383 | '-p' => 'pubkey', 384 | '-g' => 'keygen', 385 | '-k' => 'keyid', 386 | '-x' => 'keyextend', 387 | '--keygen' => 'keygen', 388 | '--keyextend' => 'keyextend', 389 | '--keylist' => 'keylist', 390 | '--keyphrases' => 'keyphrases', 391 | '--keydel' => 'keydel', 392 | '--keycvt' => 'keycvt', 393 | '--keytotpm' => 'keytotpm', 394 | '--aliaslist' => 'aliaslist', 395 | '--aliasgen' => 'aliasgen', 396 | '--aliasdel' => 'aliasdel', 397 | '--enckeylist' => 'enckeylist', 398 | '--enckeygen' => 'enckeygen', 399 | '--enckeydel' => 'enckeydel', 400 | '--backup' => 'backup', 401 | '--log' => 'log', 402 | ); 403 | 404 | my %stringoptions = ( 405 | '-u' => \$signuser, 406 | '-h' => \$hash, 407 | '-A' => \$pubalgo, 408 | '--port' => \$port, 409 | '--proto' => \$proto, 410 | '--server' => \$signhost, 411 | '--keyring' => \$keyring, 412 | '--config' => \$conf, 413 | '--sighelper' => \$sighelper, 414 | ); 415 | 416 | my @modes; 417 | while (@ARGV && $ARGV[0] =~ /^-/) { 418 | my $opt = shift @ARGV; 419 | usage(\*STDOUT, 0) if $opt eq '--help'; 420 | if ($modeoptions{$opt}) { 421 | push @modes, $modeoptions{$opt}; 422 | } elsif ($stringoptions{$opt}) { 423 | my $r = $stringoptions{$opt}; 424 | $$r = shift @ARGV; 425 | $cmdline_set{$r} = 1; 426 | } else { 427 | last if $opt eq '--'; 428 | print STDERR "unsupported option $opt\n\n"; 429 | usage(\*STDERR, 1); 430 | } 431 | } 432 | if (!@modes) { 433 | print STDERR "please specify a mode\n\n"; 434 | usage(\*STDERR, 1); 435 | } 436 | die("please specify only one mode\n") unless @modes == 1; 437 | my $mode = $modes[0]; 438 | warn("WARNING: do not use a sighelper in production!\n") if $sighelper && $conf eq '/etc/sign.conf'; 439 | 440 | readconfig($conf); 441 | 442 | $pubalgo = lc($pubalgo) if $pubalgo; 443 | $hash = uc($hash) || 'SHA256'; 444 | $signuser ||= '-'; 445 | $algouser = "$hash:$signuser"; 446 | die("unsupported socket proto $proto\n") if $proto && $proto ne 'unprotected' && $proto ne 'ssl'; 447 | 448 | die("please specify a user with the -u option\n") if $signuser eq '-' && ($mode ne 'keygen' && $mode ne 'keylist' && $mode ne 'keyphrases' && $mode ne 'aliaslist' && $mode ne 'backup' && $mode ne 'log' && $mode ne 'enckeygen' && $mode ne 'enckeylist'); 449 | 450 | if ($mode eq 'rawopensslsign') { 451 | my $ctx = digestctx(); 452 | if (!@ARGV) { 453 | $ctx->addfile(\*STDIN); 454 | } elsif (@ARGV == 1) { 455 | die("$ARGV[0]: $!\n") unless -e $ARGV[0]; 456 | $ctx->addfile($ARGV[0]); 457 | } else { 458 | die("cannot openssl sign multiple files\n"); 459 | } 460 | my $pk = rpc_fatal_1('sign', $algouser, $ctx->hexdigest().'@0000000000'); 461 | my ($sigalgo, $mpis) = sig2mpis(pk2sig($pk)); 462 | if ($pubalgo) { 463 | die("signature algorithm is not $pubalgo\n") unless ($pubalgo eq 'rsa' && $sigalgo == 1) || ($pubalgo eq 'dsa' && $sigalgo == 17) || ($pubalgo eq 'ecdsa' && $sigalgo == 19) || ($pubalgo eq 'eddsa' && $sigalgo == 22); 464 | } 465 | my $sig = x509_pack_signature($sigalgo, $mpis); 466 | if (!@ARGV) { 467 | print $sig; 468 | } else { 469 | local *F; 470 | open(F, '>', "$ARGV[0].sig") || die("$ARGV[0].sig: $!\n"); 471 | print F $sig; 472 | close(F) || die("$ARGV[0].sig: $!\n"); 473 | } 474 | } elsif ($mode eq 'pubkey') { 475 | my $pubkey = rpc_fatal_1($mode, $algouser, @ARGV); 476 | print $pubkey; 477 | } elsif ($mode eq 'keygen') { 478 | my $pubkey = rpc_fatal_1($mode, $algouser, @ARGV); 479 | print $pubkey; 480 | } elsif ($mode eq 'keyextend') { 481 | my $pubkey = rpc_fatal_1($mode, $algouser, @ARGV); 482 | print $pubkey; 483 | } elsif ($mode eq 'keylist' || $mode eq 'keyphrases') { 484 | my $result = rpc_fatal_1($mode, $algouser, @ARGV); 485 | print $result; 486 | } elsif ($mode eq 'keydel') { 487 | rpc_fatal(undef, $mode, $algouser, @ARGV); 488 | } elsif ($mode eq 'keycvt') { 489 | rpc_fatal(undef, $mode, $algouser, @ARGV); 490 | } elsif ($mode eq 'keytotpm') { 491 | rpc_fatal(undef, $mode, $algouser, @ARGV); 492 | } elsif ($mode eq 'enckeygen') { 493 | my $pubkey = rpc_fatal_1($mode, $algouser, @ARGV); 494 | print $pubkey if @ARGV && $ARGV[0] eq 'sym'; 495 | } elsif ($mode eq 'enckeydel') { 496 | rpc_fatal(undef, $mode, $algouser, @ARGV); 497 | } elsif ($mode eq 'enckeylist') { 498 | my $result = rpc_fatal_1($mode, $algouser, @ARGV); 499 | print $result; 500 | } elsif ($mode eq 'keyid') { 501 | my $ctx = digestctx(); 502 | my $pk = rpc_fatal_1('sign', $algouser, $ctx->hexdigest().'@0000000000'); 503 | my $issuer = sig2issuer(pk2sig($pk)); 504 | print "$issuer\n"; 505 | } elsif ($mode eq 'aliaslist') { 506 | my $result = rpc_fatal_1($mode, $algouser, @ARGV); 507 | print $result; 508 | } elsif ($mode eq 'aliasgen') { 509 | rpc_fatal(undef, $mode, $algouser, @ARGV); 510 | } elsif ($mode eq 'aliasdel') { 511 | rpc_fatal(undef, $mode, $algouser, @ARGV); 512 | } elsif ($mode eq 'backup') { 513 | $keyring = 'system'; 514 | rpc_fatal(undef, $mode, $algouser, @ARGV); 515 | } elsif ($mode eq 'log') { 516 | $keyring = 'system'; 517 | rpc_fatal(undef, $mode, $algouser, @ARGV); 518 | } else { 519 | die("unsupported mode: $mode\n"); 520 | } 521 | 522 | -------------------------------------------------------------------------------- /gnupg-1.4.7-files_are_digests.patch: -------------------------------------------------------------------------------- 1 | --- gnupg-1.4.7/g10/gpg.c.orig 2007-03-05 10:02:57.000000000 +0100 2 | +++ gnupg-1.4.7/g10/gpg.c 2007-03-14 11:35:25.000000000 +0100 3 | @@ -344,6 +344,7 @@ enum cmd_and_opt_values 4 | oTTYtype, 5 | oLCctype, 6 | oLCmessages, 7 | + oFilesAreDigests, 8 | oGroup, 9 | oUnGroup, 10 | oNoGroups, 11 | @@ -689,6 +690,7 @@ static ARGPARSE_OPTS opts[] = { 12 | { oTTYtype, "ttytype", 2, "@" }, 13 | { oLCctype, "lc-ctype", 2, "@" }, 14 | { oLCmessages, "lc-messages", 2, "@" }, 15 | + { oFilesAreDigests, "files-are-digests", 0, "@" }, 16 | { oGroup, "group", 2, "@" }, 17 | { oUnGroup, "ungroup", 2, "@" }, 18 | { oNoGroups, "no-groups", 0, "@" }, 19 | @@ -2725,6 +2727,7 @@ main (int argc, char **argv ) 20 | case oTTYtype: opt.ttytype = pargs.r.ret_str; break; 21 | case oLCctype: opt.lc_ctype = pargs.r.ret_str; break; 22 | case oLCmessages: opt.lc_messages = pargs.r.ret_str; break; 23 | + case oFilesAreDigests: opt.files_are_digests = 1; break; 24 | case oGroup: add_group(pargs.r.ret_str); break; 25 | case oUnGroup: rm_group(pargs.r.ret_str); break; 26 | case oNoGroups: 27 | --- gnupg-1.4.7/g10/options.h.orig 2007-03-05 10:02:57.000000000 +0100 28 | +++ gnupg-1.4.7/g10/options.h 2007-03-14 11:35:25.000000000 +0100 29 | @@ -195,6 +195,7 @@ struct 30 | int no_auto_check_trustdb; 31 | int preserve_permissions; 32 | int no_homedir_creation; 33 | + int files_are_digests; 34 | struct groupitem *grouplist; 35 | int strict; 36 | int mangle_dos_filenames; 37 | --- gnupg-1.4.7/g10/sign.c.orig 2007-02-04 17:27:40.000000000 +0100 38 | +++ gnupg-1.4.7/g10/sign.c 2007-03-14 11:35:25.000000000 +0100 39 | @@ -692,8 +692,12 @@ write_signature_packets (SK_LIST sk_list 40 | build_sig_subpkt_from_sig (sig); 41 | mk_notation_policy_etc (sig, NULL, sk); 42 | 43 | - hash_sigversion_to_magic (md, sig); 44 | - md_final (md); 45 | + if (!opt.files_are_digests) { 46 | + hash_sigversion_to_magic (md, sig); 47 | + md_final (md); 48 | + } else if (sig->version >= 4) { 49 | + log_bug("files-are-digests doesn't work with v4 sigs\n"); 50 | + } 51 | 52 | rc = do_sign( sk, sig, md, hash_for (sk) ); 53 | md_close (md); 54 | @@ -751,6 +755,7 @@ sign_file( STRLIST filenames, int detach 55 | SK_LIST sk_rover = NULL; 56 | int multifile = 0; 57 | u32 create_time=make_timestamp(),duration=0; 58 | + int sigclass = 0x00; 59 | 60 | memset( &afx, 0, sizeof afx); 61 | memset( &zfx, 0, sizeof zfx); 62 | @@ -766,7 +771,16 @@ sign_file( STRLIST filenames, int detach 63 | fname = NULL; 64 | 65 | if( fname && filenames->next && (!detached || encryptflag) ) 66 | - log_bug("multiple files can only be detached signed"); 67 | + log_bug("multiple files can only be detached signed\n"); 68 | + 69 | + if (opt.files_are_digests && (multifile || !fname)) 70 | + log_bug("files-are-digests only works with one file\n"); 71 | + if (opt.files_are_digests && !detached) 72 | + log_bug("files-are-digests can only write detached signatures\n"); 73 | + if (opt.files_are_digests && !opt.def_digest_algo) 74 | + log_bug("files-are-digests needs --digest-algo\n"); 75 | + if (opt.files_are_digests && opt.textmode) 76 | + log_bug("files-are-digests doesn't work with --textmode\n"); 77 | 78 | if(encryptflag==2 79 | && (rc=setup_symkey(&efx.symkey_s2k,&efx.symkey_dek))) 80 | @@ -794,7 +808,7 @@ sign_file( STRLIST filenames, int detach 81 | goto leave; 82 | 83 | /* prepare iobufs */ 84 | - if( multifile ) /* have list of filenames */ 85 | + if( multifile || opt.files_are_digests) /* have list of filenames */ 86 | inp = NULL; /* we do it later */ 87 | else { 88 | inp = iobuf_open(fname); 89 | @@ -924,7 +938,7 @@ sign_file( STRLIST filenames, int detach 90 | md_enable(mfx.md, hash_for(sk)); 91 | } 92 | 93 | - if( !multifile ) 94 | + if( !multifile && !opt.files_are_digests ) 95 | iobuf_push_filter( inp, md_filter, &mfx ); 96 | 97 | if( detached && !encryptflag && !RFC1991 ) 98 | @@ -979,6 +993,8 @@ sign_file( STRLIST filenames, int detach 99 | 100 | write_status (STATUS_BEGIN_SIGNING); 101 | 102 | + sigclass = opt.textmode && !outfile? 0x01 : 0x00; 103 | + 104 | /* Setup the inner packet. */ 105 | if( detached ) { 106 | if( multifile ) { 107 | @@ -1019,6 +1035,45 @@ sign_file( STRLIST filenames, int detach 108 | if( opt.verbose ) 109 | putc( '\n', stderr ); 110 | } 111 | + else if (opt.files_are_digests) { 112 | + byte *mdb, ts[5]; 113 | + size_t mdlen; 114 | + const char *fp; 115 | + int c, d; 116 | + 117 | + md_final(mfx.md); 118 | + /* this assumes md_read returns the same buffer */ 119 | + mdb = md_read(mfx.md, opt.def_digest_algo); 120 | + (void)md_asn_oid(opt.def_digest_algo, (size_t)0, &mdlen); 121 | + if (strlen(fname) != mdlen * 2 + 11) 122 | + log_bug("digests must be %d + @ + 5 bytes\n", mdlen); 123 | + d = -1; 124 | + for (fp = fname ; *fp; ) { 125 | + c = *fp++; 126 | + if (c >= '0' && c <= '9') 127 | + c -= '0'; 128 | + else if (c >= 'a' && c <= 'f') 129 | + c -= 'a' - 10; 130 | + else if (c >= 'A' && c <= 'F') 131 | + c -= 'A' - 10; 132 | + else 133 | + log_bug("filename is not hex\n"); 134 | + if (d >= 0) { 135 | + *mdb++ = d << 4 | c; 136 | + c = -1; 137 | + if (--mdlen == 0) { 138 | + mdb = ts; 139 | + if (*fp++ != '@') 140 | + log_bug("missing time separator\n"); 141 | + } 142 | + } 143 | + d = c; 144 | + } 145 | + sigclass = ts[0]; 146 | + if (sigclass != 0x00 && sigclass != 0x01) 147 | + log_bug("bad cipher class\n"); 148 | + create_time = buffer_to_u32(ts + 1); 149 | + } 150 | else { 151 | /* read, so that the filter can calculate the digest */ 152 | while( iobuf_get(inp) != -1 ) 153 | @@ -1037,7 +1092,7 @@ sign_file( STRLIST filenames, int detach 154 | 155 | /* write the signatures */ 156 | rc = write_signature_packets (sk_list, out, mfx.md, 157 | - opt.textmode && !outfile? 0x01 : 0x00, 158 | + sigclass, 159 | create_time, duration, detached ? 'D':'S'); 160 | if( rc ) 161 | goto leave; 162 | -------------------------------------------------------------------------------- /inc.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef unsigned int u32; 8 | typedef unsigned long long u64; 9 | typedef unsigned char byte; 10 | 11 | #define HASH_SHA1 0 12 | #define HASH_SHA256 1 13 | #define HASH_SHA512 2 14 | 15 | #define PUB_DSA 0 16 | #define PUB_RSA 1 17 | #define PUB_EDDSA 2 18 | #define PUB_ECDSA 3 19 | #define PUB_MLDSA65 4 20 | 21 | #define SOCKPROTO_UNPROTECTED 0 22 | #define SOCKPROTO_SSL 1 23 | 24 | /* sign.c */ 25 | extern int hashalgo; 26 | 27 | /* hash.c */ 28 | typedef struct { 29 | u32 h0,h1,h2,h3,h4; 30 | u32 nblocks; 31 | byte buf[64]; 32 | int count; 33 | } SHA1_CONTEXT; 34 | 35 | typedef struct { 36 | u32 h0,h1,h2,h3,h4,h5,h6,h7; 37 | u32 nblocks; 38 | byte buf[64]; 39 | int count; 40 | } SHA256_CONTEXT; 41 | 42 | typedef struct { 43 | u64 h0,h1,h2,h3,h4,h5,h6,h7; 44 | u64 nblocks; 45 | byte buf[128]; 46 | int count; 47 | } SHA512_CONTEXT; 48 | 49 | typedef struct MD5Context { 50 | u32 buf[4]; 51 | u32 bits[2]; 52 | byte in[64]; 53 | } MD5_CTX; 54 | 55 | typedef union { 56 | SHA1_CONTEXT sha1; 57 | SHA256_CONTEXT sha256; 58 | SHA512_CONTEXT sha512; 59 | } HASH_CONTEXT; 60 | 61 | void sha1_init(SHA1_CONTEXT *hd); 62 | void sha1_write(SHA1_CONTEXT *hd, const byte *inbuf, size_t inlen); 63 | void sha1_final(SHA1_CONTEXT *hd); 64 | byte *sha1_read(SHA1_CONTEXT *hd); 65 | 66 | void sha256_init(SHA256_CONTEXT *hd); 67 | void sha256_write(SHA256_CONTEXT *hd, const byte *inbuf, size_t inlen); 68 | void sha256_final(SHA256_CONTEXT *hd); 69 | byte *sha256_read(SHA256_CONTEXT *hd); 70 | 71 | void sha512_init(SHA512_CONTEXT *hd); 72 | void sha512_write(SHA512_CONTEXT *hd, const byte *inbuf, size_t inlen); 73 | void sha512_final(SHA512_CONTEXT *hd); 74 | byte *sha512_read(SHA512_CONTEXT *hd); 75 | 76 | void md5_init(struct MD5Context *ctx); 77 | void md5_write(struct MD5Context *ctx, byte const *buf, u32 len); 78 | void md5_final(byte *digest, struct MD5Context *ctx); 79 | 80 | void hash_init(HASH_CONTEXT *c); 81 | void hash_write(HASH_CONTEXT *c, const unsigned char *b, size_t l); 82 | void hash_final(HASH_CONTEXT *c); 83 | unsigned char *hash_read(HASH_CONTEXT *c); 84 | int hash_len(void); 85 | 86 | /* base64.c */ 87 | void printr64(FILE *f, const byte *str, int len); 88 | char *r64dec1(char *p, u32 *vp, int *eofp); 89 | char *r64dec(char *p, unsigned char **bpp); 90 | void r64enc(char *out, const byte *str, int len); 91 | 92 | /* pgp.c */ 93 | void write_armored_pubkey(FILE *fp, const byte *pubkey, int length); 94 | void write_armored_signature(FILE *fp, const byte *signature, int length); 95 | char *get_armored_signature(const byte *signature, int length); 96 | unsigned char *unarmor_pubkey(char *pubkey, int *pktlp); 97 | 98 | unsigned char *genv4sigtrail(int clearsign, int pubalgo, int hashalgo, u32 signtime, unsigned char *fp, int *v4sigtraillen); 99 | int fixupsig(unsigned char *sigtrail, unsigned char *v4sigtrail, unsigned char *sigpk, int sigpklen, int tail, int left); 100 | unsigned char *nextpkg(int *tagp, int *pkgl, unsigned char **pp, int *ppl); 101 | unsigned char *findsubpkg(unsigned char *q, int l, int type, int *slp, int fixedsl); 102 | unsigned char *addpkg(unsigned char *to, unsigned char *p, int l, int tag, int newformat); 103 | byte *pkg2sig(byte *pk, int pkl, int *siglp); 104 | byte *findsigissuer(byte *sig, int sigl); 105 | int findsigmpioffset(byte *sig, int sigl); 106 | int findsigpubalgo(byte *sig, int sigl); 107 | int findkeympioffset(byte *key, int keyl); 108 | int findkeypubalgo(byte *key, int keyl); 109 | void calculatekeyfingerprint(byte *key, int keyl, byte *fingerprintp); 110 | int pkg2sigpubalgo(byte *pk, int pkl); 111 | int setmpis(byte *p, int l, int nmpi, byte **mpi, int *mpil, int withcurve); 112 | 113 | /* x509.c */ 114 | struct x509 { 115 | byte *buf; 116 | int len; 117 | int alen; /* allocated length */ 118 | }; 119 | 120 | static inline void x509_init(struct x509 *cb) { memset(cb, 0, sizeof(*cb)); } 121 | static inline void x509_free(struct x509 *cb) { if (cb->buf) free(cb->buf); } 122 | void x509_insert(struct x509 *cb, int offset, const byte *blob, int blobl); 123 | void x509_signature(struct x509 *cb, int pubalgo, byte **mpi, int *mpil); 124 | void x509_tbscert(struct x509 *cb, const char *cn, const char *email, time_t start, time_t end, int pubalgo, byte **mpi, int *mpil); 125 | void x509_finishcert(struct x509 *cb, int pubalgo, struct x509 *sigcb); 126 | void certsizelimit(char *s, int l); 127 | 128 | int x509_addpem(struct x509 *cb, char *buf, char *type); 129 | void x509_signedattrs(struct x509 *cb, unsigned char *digest, int digestlen, time_t signtime); 130 | void x509_pkcs7_signed_data(struct x509 *cb, struct x509 *contentinfo, struct x509 *signedattrs, int pubalgo, struct x509 *sigcb, struct x509 *cert, struct x509 *othercerts, int flags); 131 | int x509_cert2pubalgo(struct x509 *cert); 132 | 133 | int x509_appx_contentinfo(struct x509 *cb, unsigned char *digest, int digestlen); 134 | void x509_appx_signedattrs(struct x509 *cb, unsigned char *digest, int digestlen, time_t signtime); 135 | int x509_pe_contentinfo(struct x509 *cb, unsigned char *digest, int digestlen); 136 | void x509_pe_signedattrs(struct x509 *cb, unsigned char *digest, int digestlen, time_t signtime); 137 | 138 | #define X509_PKCS7_USE_KEYID (1 << 0) 139 | #define X509_PKCS7_NO_CERTS (1 << 1) 140 | 141 | /* zip.c */ 142 | struct zip { 143 | unsigned char *eocd; 144 | u64 eocd_size; 145 | u64 size; 146 | u64 cd_offset; 147 | u64 cd_size; 148 | unsigned char *cd; 149 | u64 num; 150 | unsigned char *appended; 151 | u64 appendedsize; 152 | }; 153 | 154 | void zip_read(struct zip *zip, int fd); 155 | void zip_free(struct zip *zip); 156 | unsigned char *zip_iterentry(struct zip *zip, unsigned char **iterp); 157 | unsigned char *zip_findentry(struct zip *zip, char *fn); 158 | 159 | char *zip_entry_name(unsigned char *entry, int *namel); 160 | u64 zip_entry_fhpos(unsigned char *entry); 161 | u32 zip_entry_datetime(unsigned char *entry); 162 | 163 | u64 zip_seekdata(struct zip *zip, int fd, unsigned char *entry); 164 | void zip_appendfile(struct zip *zip, char *fn, unsigned char *file, u64 filelen, int comp, u32 datetime); 165 | void zip_write(struct zip *zip, int zipfd, int fd); 166 | 167 | /* rpm.c */ 168 | struct rpmdata { 169 | byte rpmlead[96]; 170 | byte rpmsighead[16]; 171 | int rpmsigcnt; 172 | int rpmsigdlen; 173 | int rpmsigsize; 174 | byte *rpmsig; /* signature data (with room for two new signatures) */ 175 | 176 | u32 rpmdataoff; /* header+payload offset in unsigned rpm */ 177 | u64 hdrin_size; 178 | byte *hdrin_md5; /* points into rpmsig */ 179 | int gotsha1; /* did we see a RPMSIGTAG_SHA1 tag? */ 180 | int gotsha256; /* did we see a RPMSIGTAG_SHA256 tag? */ 181 | int gotsigs; /* is this rpm already signed */ 182 | 183 | u32 buildtime; 184 | byte rpmmd5sum[16]; /* md5sum over header+payload */ 185 | 186 | byte chksum_leadmd5[16]; 187 | byte chksum_md5[16]; 188 | byte chksum_sha1[20]; 189 | byte chksum_sha256[32]; 190 | byte chksum_sha512[64]; 191 | }; 192 | 193 | int rpm_insertsig(struct rpmdata *rd, int hdronly, byte *newsig, int newsiglen); 194 | int rpm_delsigs(struct rpmdata *rd); 195 | int rpm_read(struct rpmdata *rd, int fd, char *filename, HASH_CONTEXT *ctx, HASH_CONTEXT *hctx, int getbuildtime); 196 | void rpm_write(struct rpmdata *rd, int foutfd, int fd, int chksumfilefd); 197 | void rpm_free(struct rpmdata *rd); 198 | void rpm_writechecksums(struct rpmdata *rd, int chksumfilefd); 199 | 200 | /* appimage.c */ 201 | int appimage_read(char *filename, HASH_CONTEXT *ctx); 202 | void appimage_write_signature(char *filename, byte *signature, int length); 203 | 204 | /* appx.c */ 205 | struct appxdata { 206 | struct x509 cb_content; 207 | struct x509 cb_signedattrs; 208 | struct zip zip; 209 | u32 datetime; 210 | }; 211 | 212 | int appx_read(struct appxdata *appxdata, int fd, char *filename, HASH_CONTEXT *ctx, time_t t); 213 | void appx_write(struct appxdata *appxdata, int outfd, int fd, struct x509 *cert, int pubalgo, struct x509 *sigcb, struct x509 *othercerts); 214 | void appx_free(struct appxdata *appxdata); 215 | 216 | /* sock.c */ 217 | void opensocket(void); 218 | void closesocket(void); 219 | int doreq_raw(byte *buf, int inbufl, int bufl); 220 | int doreq_old(const char *user, const char *digest, const char *digestalgo, byte *buf, int bufl); 221 | int doreq(int argc, const char **argv, byte *buf, int bufl, int nret); 222 | int doreq_12(int argc, const char **argv, byte *buf, int bufl, int *outl2p); 223 | 224 | /* clearsign.c */ 225 | int clearsign(int fd, char *filename, char *outfilename, HASH_CONTEXT *ctx, const char *hname, int isfilter, int force, FILE **foutp); 226 | 227 | /* pe.c */ 228 | 229 | struct pedata { 230 | struct x509 cb_content; 231 | struct x509 cb_signedattrs; 232 | byte hdr[4096]; 233 | u32 headersize; 234 | u32 c_off; 235 | u32 csum_off; 236 | u32 filesize; 237 | u32 csum; 238 | }; 239 | 240 | int pe_read(struct pedata *pedata, int fd, char *filename, HASH_CONTEXT *hctx, time_t t); 241 | void pe_write(struct pedata *pedata, int outfd, int fd, struct x509 *cert, int pubalgo, struct x509 *sigcb, struct x509 *othercerts); 242 | void pe_free(struct pedata *pedata); 243 | 244 | /* ko.c */ 245 | int ko_read(int fd, char *filename, HASH_CONTEXT *ctx); 246 | void ko_write(int outfd, int fd, struct x509 *cb); 247 | 248 | /* util.c */ 249 | void *doalloc(size_t sz); 250 | void *dorealloc(void *p, size_t sz); 251 | void dodie(const char *msg); 252 | void dodie_errno(const char *msg); 253 | size_t doread_eof(int fd, unsigned char *buf, size_t len); 254 | void doread(int fd, unsigned char *buf, size_t len); 255 | void dowrite(int fd, const unsigned char *buf, size_t len); 256 | void doseek(int fd, u64 pos); 257 | u64 doseek_eof(int fd, u64 pos); 258 | void docopy(int infd, int outfd, u64 len); 259 | 260 | 261 | /* cpio.c */ 262 | #define CPIO_TYPE_TRAILER 0 263 | #define CPIO_TYPE_FILE 1 264 | #define CPIO_TYPE_OTHER 2 265 | 266 | byte *cpio_read(int fd, int *typep, int reserve); 267 | u32 cpio_name_append(byte *cpio, char *suf); 268 | u32 cpio_size_set(byte *cpio, u32 size); 269 | u32 cpio_size_get(byte *cpio, u32 *padp); 270 | u32 cpio_headnamesize(byte *cpio); 271 | 272 | -------------------------------------------------------------------------------- /ko.c: -------------------------------------------------------------------------------- 1 | #include "inc.h" 2 | #include "bele.h" 3 | 4 | int ko_read(int fd, char *filename, HASH_CONTEXT *ctx) 5 | { 6 | u64 length; 7 | byte buf[8192]; 8 | 9 | doread(fd, buf, 4); 10 | if (buf[0] != 0x7f || buf[1] != 0x45 || buf[2] != 0x4c || buf[3] != 0x46) 11 | { 12 | fprintf(stderr, "%s: not an ELF binary\n", filename); 13 | exit(1); 14 | } 15 | length = doseek_eof(fd, 28) + 28; 16 | doread(fd, buf, 28); 17 | if (!memcmp(buf, "~Module signature appended~\n", 28)) 18 | return 0; 19 | doseek(fd, 0); 20 | while (length > 0) 21 | { 22 | int chunk = length > sizeof(buf) ? sizeof(buf) : (int)length; 23 | doread(fd, buf, chunk); 24 | hash_write(ctx, buf, chunk); 25 | length -= chunk; 26 | } 27 | return 1; 28 | } 29 | 30 | void 31 | ko_write(int outfd, int fd, struct x509 *cb) 32 | { 33 | u64 length; 34 | 35 | x509_insert(cb, cb->len, 0, 40); 36 | setbe4(cb->buf + cb->len - 40, 0x00000200); 37 | setbe4(cb->buf + cb->len - 32, cb->len - 40); 38 | memcpy(cb->buf + cb->len - 28, "~Module signature appended~\n", 28); 39 | length = doseek_eof(fd, 0); 40 | doseek(fd, 0); 41 | docopy(fd, outfd, length); 42 | dowrite(outfd, cb->buf, cb->len); 43 | } 44 | -------------------------------------------------------------------------------- /pe.c: -------------------------------------------------------------------------------- 1 | #include "inc.h" 2 | #include "bele.h" 3 | 4 | static void 5 | update_chksum(u32 pos, const unsigned char *b, int l, u32 *csump) 6 | { 7 | u32 c = *csump; 8 | if (!l) 9 | return; 10 | if (l < 0 || l > 0x10000 * 2) 11 | abort(); /* this can overflow */ 12 | if (pos & 1) 13 | { 14 | c = *b++ << 8; 15 | l--; 16 | } 17 | for (; l > 1; l -= 2, b += 2) 18 | c += b[0] + (b[1] << 8); 19 | if (l) 20 | c += b[0]; 21 | c = (c & 0xffff) + (c >> 16); /* fold to [0..0x1fffe] */ 22 | c = (c & 0xffff) + (c >> 16); /* fold to [0..0xffff] */ 23 | *csump = c; 24 | } 25 | 26 | static u32 27 | dohash(int fd, char *filename, u32 pos, u32 l, int toeof, HASH_CONTEXT *ctx, u32 *chkp) 28 | { 29 | unsigned char buf[4096]; 30 | u32 hashed = 0; 31 | 32 | if (pos >= 0x40000000) 33 | dodie("unsupported pe file size"); 34 | if (toeof) 35 | l = sizeof(buf); 36 | while (l > 0) 37 | { 38 | int r = read(fd, buf, l > sizeof(buf) ? sizeof(buf) : (int)l); 39 | if (r < 0) 40 | dodie_errno(filename); 41 | if (r == 0 && toeof) 42 | break; 43 | if (r == 0) 44 | { 45 | fprintf(stderr, "%s: unexpexted EOF\n", filename); 46 | exit(1); 47 | } 48 | if (pos + (u32)r >= 0x40000000) 49 | dodie("unsupported pe file size"); 50 | hash_write(ctx, buf, r); 51 | hashed += r; 52 | if (chkp) 53 | update_chksum(pos, buf, r, chkp); 54 | pos += r; 55 | if (!toeof) 56 | l -= r; 57 | } 58 | return hashed; 59 | } 60 | 61 | static int 62 | sectioncmp(const void *av, const void *bv) 63 | { 64 | const unsigned char *a = av; 65 | const unsigned char *b = bv; 66 | u32 aoff = getle4(a + 20); 67 | u32 boff = getle4(b + 20); 68 | return aoff < boff ? -1 : aoff > boff ? 1 : 0; 69 | } 70 | 71 | int 72 | pe_read(struct pedata *pedata, int fd, char *filename, HASH_CONTEXT *hctx, time_t t) 73 | { 74 | unsigned char hdr[4096]; 75 | HASH_CONTEXT ctx; 76 | u32 stubsize, opthdrsize; 77 | u32 opthdrmagic; 78 | u32 headersize; 79 | u32 hdd_off; 80 | u32 c_off, c_end; 81 | u32 nsections; 82 | u32 bytes_hashed; 83 | u32 cert_pos = 0; 84 | int i; 85 | int offset; 86 | 87 | doread(fd, hdr, 0x40); 88 | stubsize = getle4(hdr + 0x3c); 89 | if (stubsize >= sizeof(hdr) - 24 || stubsize < 0x40) 90 | { 91 | fprintf(stderr, "illegal stub size: %u\n", stubsize); 92 | exit(1); 93 | } 94 | doread(fd, hdr + 0x40, stubsize - 0x40 + 24); 95 | if (getle4(hdr + stubsize) != 0x4550) 96 | { 97 | fprintf(stderr, "%s: not a PE file\n", filename); 98 | exit(1); 99 | } 100 | opthdrsize = getle2(hdr + stubsize + 4 + 16); 101 | if (opthdrsize >= sizeof(hdr) || stubsize + 24 + opthdrsize >= sizeof(hdr) || opthdrsize < 64) 102 | { 103 | fprintf(stderr, "illegal optional header size: %u\n", opthdrsize); 104 | exit(1); 105 | } 106 | doread(fd, hdr + stubsize + 24, opthdrsize); 107 | opthdrmagic = getle2(hdr + stubsize + 24); 108 | if (opthdrmagic != 0x10b && opthdrmagic != 0x20b) 109 | { 110 | fprintf(stderr, "unsupported optional header magic: 0x%08x\n", opthdrmagic); 111 | exit(1); 112 | } 113 | hdd_off = opthdrmagic == 0x10b ? 96 : 112; 114 | if (opthdrsize < hdd_off || ((opthdrsize - hdd_off) & 7) != 0) 115 | { 116 | fprintf(stderr, "weird optional header size: %u\n", opthdrsize); 117 | exit(1); 118 | } 119 | headersize = getle4(hdr + stubsize + 24 + 60); 120 | if (headersize < stubsize + 24 + opthdrsize || headersize > sizeof(hdr)) 121 | { 122 | fprintf(stderr, "unsupported header size: 0x%08x\n", headersize); 123 | exit(1); 124 | } 125 | doread(fd, hdr + stubsize + 24 + opthdrsize, headersize - (stubsize + 24 + opthdrsize)); 126 | 127 | c_off = hdd_off + 8 * 4; 128 | if (c_off > opthdrsize) 129 | c_off = opthdrsize; 130 | c_end = c_off < opthdrsize ? c_off + 8 : c_off; 131 | if (c_end > c_off && getle4(hdr + stubsize + 24 + c_off + 4) != 0) 132 | cert_pos = getle4(hdr + stubsize + 24 + c_off); 133 | 134 | if (cert_pos) 135 | return 0; /* already signed */ 136 | 137 | if (c_end == c_off) 138 | dodie("missing certificate directory entry"); 139 | pedata->headersize = headersize; 140 | pedata->c_off = stubsize + 24 + c_off; 141 | pedata->csum = 0; 142 | pedata->csum_off = stubsize + 24 + 64; 143 | memcpy(pedata->hdr, hdr, headersize); 144 | 145 | hash_init(&ctx); 146 | hash_write(&ctx, hdr, stubsize + 24 + 64); 147 | hash_write(&ctx, hdr + (stubsize + 24 + 68), c_off - 68); 148 | hash_write(&ctx, hdr + (stubsize + 24 + c_end), headersize - (stubsize + 24 + c_end)); 149 | bytes_hashed = headersize; 150 | 151 | nsections = getle2(hdr + stubsize + 4 + 2); 152 | if (stubsize + 24 + opthdrsize + nsections * 40 > headersize) 153 | { 154 | fprintf(stderr, "section data does not fit into header: 0x%08x\n", nsections); 155 | exit(1); 156 | } 157 | if (nsections > 1) 158 | qsort(hdr + (stubsize + 24 + opthdrsize), nsections, 40, sectioncmp); 159 | for (i = 0; i < nsections; i++) 160 | { 161 | unsigned char *sp = hdr + (stubsize + 24 + opthdrsize) + 40 * i; 162 | u32 sz = getle4(sp + 16); 163 | u32 off = getle4(sp + 20); 164 | if (!sz) 165 | continue; 166 | if (off != bytes_hashed) 167 | { 168 | fprintf(stderr, "cannot deal with gap between sections: %x %x\n", off, bytes_hashed); 169 | exit(1); 170 | } 171 | bytes_hashed += dohash(fd, filename, off, sz, 0, &ctx, &pedata->csum); 172 | } 173 | if (cert_pos) 174 | { 175 | u32 sz; 176 | if (cert_pos < bytes_hashed || (cert_pos & 7) != 0) 177 | { 178 | fprintf(stderr, "illegal cert position: 0x%08x\n", cert_pos); 179 | exit(1); 180 | } 181 | sz = cert_pos - bytes_hashed; 182 | bytes_hashed += dohash(fd, filename, bytes_hashed, sz, 0, &ctx, &pedata->csum); 183 | pedata->filesize = cert_pos; 184 | } 185 | else 186 | { 187 | bytes_hashed += dohash(fd, filename, bytes_hashed, 0, 1, &ctx, &pedata->csum); 188 | pedata->filesize = bytes_hashed; 189 | if ((bytes_hashed & 7) != 0) 190 | { 191 | int pad = 8 - (bytes_hashed & 7); 192 | hash_write(&ctx, (const unsigned char *)"\0\0\0\0\0\0\0\0", pad); 193 | /* no need to call update_chksum() on zeros */ 194 | bytes_hashed += pad; 195 | } 196 | } 197 | hash_final(&ctx); 198 | 199 | x509_init(&pedata->cb_content); 200 | offset = x509_pe_contentinfo(&pedata->cb_content, hash_read(&ctx), hash_len()); 201 | 202 | /* hash the spccontent */ 203 | hash_init(&ctx); 204 | hash_write(&ctx, pedata->cb_content.buf + offset, pedata->cb_content.len - offset); 205 | hash_final(&ctx); 206 | 207 | /* create signedattrs */ 208 | x509_init(&pedata->cb_signedattrs); 209 | x509_pe_signedattrs(&pedata->cb_signedattrs, hash_read(&ctx), hash_len(), t); 210 | 211 | /* hash signedattrs */ 212 | hash_write(hctx, pedata->cb_signedattrs.buf, pedata->cb_signedattrs.len); 213 | return 1; 214 | } 215 | 216 | void 217 | pe_write(struct pedata *pedata, int outfd, int fd, struct x509 *cert, int pubalgo, struct x509 *sigcb, struct x509 *othercerts) 218 | { 219 | struct x509 cb; 220 | u32 filesizepad; 221 | 222 | x509_init(&cb); 223 | x509_pkcs7_signed_data(&cb, &pedata->cb_content, &pedata->cb_signedattrs, pubalgo, sigcb, cert, othercerts, 0); 224 | 225 | /* add cert header and pad */ 226 | x509_insert(&cb, 0, 0, 8); 227 | setle4(cb.buf, cb.len); 228 | setle4(cb.buf + 4, 0x00020200); 229 | if (cb.len & 7) 230 | x509_insert(&cb, cb.len, 0, 8 - (cb.len & 7)); 231 | 232 | /* now add into certificate directory */ 233 | filesizepad = (8 - (pedata->filesize & 7)) & 7; 234 | setle4(pedata->hdr + pedata->c_off, pedata->filesize + filesizepad); 235 | setle4(pedata->hdr + pedata->c_off + 4, cb.len); 236 | 237 | /* update checksum with header and cert data, put in header */ 238 | setle4(pedata->hdr + pedata->csum_off, 0); 239 | update_chksum(0, pedata->hdr, pedata->headersize, &pedata->csum); 240 | update_chksum(pedata->filesize + filesizepad, cb.buf, cb.len, &pedata->csum); 241 | pedata->csum += pedata->filesize + filesizepad + cb.len; 242 | setle4(pedata->hdr + pedata->csum_off, pedata->csum); 243 | 244 | /* write signed pe file */ 245 | dowrite(outfd, pedata->hdr, pedata->headersize); 246 | doseek(fd, pedata->headersize); 247 | docopy(outfd, fd, pedata->filesize - pedata->headersize); 248 | if (filesizepad) 249 | dowrite(outfd, (const unsigned char *)"\0\0\0\0\0\0\0\0", filesizepad); 250 | dowrite(outfd, cb.buf, cb.len); 251 | x509_free(&cb); 252 | } 253 | 254 | void 255 | pe_free(struct pedata *pedata) 256 | { 257 | x509_free(&pedata->cb_content); 258 | x509_free(&pedata->cb_signedattrs); 259 | } 260 | 261 | -------------------------------------------------------------------------------- /pgp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 SUSE LLC 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program (see the file COPYING); if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 17 | * 18 | ***************************************************************/ 19 | 20 | #include 21 | #include 22 | 23 | #include "inc.h" 24 | 25 | /* armor handling */ 26 | 27 | #define CRCINIT 0xb704ce 28 | #define CRCPOLY 0x864cfb 29 | 30 | static u32 31 | crc24(const byte *octets, int len) 32 | { 33 | u32 crc = CRCINIT; 34 | int i; 35 | 36 | while (len-- > 0) { 37 | crc ^= (*octets++) << 16; 38 | for (i = 0; i < 8; i++) { 39 | crc <<= 1; 40 | if (crc & 0x1000000) 41 | crc ^= CRCPOLY; 42 | } 43 | } 44 | return crc & 0xffffff; 45 | } 46 | 47 | static void 48 | write_crc(FILE *fp, const byte *buf, int length) 49 | { 50 | byte hash[3]; 51 | u32 crc = crc24(buf, length); 52 | hash[0] = crc >> 16; 53 | hash[1] = crc >> 8; 54 | hash[2] = crc; 55 | putc('=', fp); 56 | printr64(fp, hash, 3); 57 | } 58 | 59 | void 60 | write_armored_signature(FILE *fp, const byte *signature, int length) 61 | { 62 | fprintf(fp, "-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1.0.7 (GNU/Linux)\n\n"); 63 | printr64(fp, signature, length); 64 | write_crc(fp, signature, length); 65 | fprintf(fp, "-----END PGP SIGNATURE-----\n"); 66 | } 67 | 68 | void 69 | write_armored_pubkey(FILE *fp, const byte *pubkey, int length) 70 | { 71 | printf("-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v1.4.5 (GNU/Linux)\n\n"); 72 | printr64(fp, pubkey, length); 73 | write_crc(fp, pubkey, length); 74 | fprintf(fp, "-----END PGP PUBLIC KEY BLOCK-----\n"); 75 | } 76 | 77 | char * 78 | get_armored_signature(const byte *signature, int length) 79 | { 80 | char *ret = 0; 81 | size_t size; 82 | FILE *fp = open_memstream(&ret, &size); 83 | write_armored_signature(fp, signature, length); 84 | fclose(fp); 85 | return ret; 86 | } 87 | 88 | unsigned char * 89 | unarmor_pubkey(char *pubkey, int *pktlp) 90 | { 91 | char *p; 92 | int l, eof; 93 | unsigned char *buf, *bp; 94 | u32 v; 95 | 96 | *pktlp = 0; 97 | while (strncmp(pubkey, "-----BEGIN PGP PUBLIC KEY BLOCK-----", 36) != 0) 98 | { 99 | pubkey = strchr(pubkey, '\n'); 100 | if (!pubkey) 101 | return 0; 102 | pubkey++; 103 | } 104 | pubkey = strchr(pubkey, '\n'); 105 | if (!pubkey++) 106 | return 0; 107 | /* skip header lines */ 108 | for (;;) 109 | { 110 | while (*pubkey == ' ' || *pubkey == '\t') 111 | pubkey++; 112 | if (*pubkey == '\n') 113 | break; 114 | pubkey = strchr(pubkey, '\n'); 115 | if (!pubkey++) 116 | return 0; 117 | } 118 | pubkey++; 119 | p = strchr(pubkey, '='); 120 | if (!p) 121 | return 0; 122 | l = p - pubkey; 123 | buf = doalloc(l * 3 / 4 + 4 + 16); 124 | bp = buf; 125 | pubkey = r64dec(pubkey, &bp); 126 | if (!pubkey) 127 | { 128 | free(buf); 129 | return 0; 130 | } 131 | while (*pubkey == ' ' || *pubkey == '\t' || *pubkey == '\n' || *pubkey == '\r') 132 | pubkey++; 133 | eof = 0; 134 | if (*pubkey != '=' || (pubkey = r64dec1(pubkey + 1, &v, &eof)) == 0) 135 | { 136 | free(buf); 137 | return 0; 138 | } 139 | if (v != crc24(buf, bp - buf)) 140 | { 141 | free(buf); 142 | return 0; 143 | } 144 | while (*pubkey == ' ' || *pubkey == '\t' || *pubkey == '\n' || *pubkey == '\r') 145 | pubkey++; 146 | if (strncmp(pubkey, "-----END PGP PUBLIC KEY BLOCK-----", 34) != 0) 147 | { 148 | free(buf); 149 | return 0; 150 | } 151 | *pktlp = bp - buf; 152 | return buf; 153 | } 154 | 155 | 156 | /* v4 signature support */ 157 | 158 | static const int hashpgpalgo[] = {2, 8, 10}; 159 | static const int pubpgpalgo[] = {17, 1, 22, 19, 100}; 160 | 161 | static unsigned char 162 | v4sig_skel[] = { 163 | 0x04, /* version */ 164 | 0x00, /* type */ 165 | 0x00, /* pubalgo */ 166 | 0x00, /* hashalgo */ 167 | 0x00, 0x06, /* octet count hashed */ 168 | 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, /* sig created subpkg */ 169 | 0x00, 0x0a, /* octet count unhashed */ 170 | 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* issuer subpkg */ 171 | }; 172 | 173 | static unsigned char 174 | v4sig_skel_fpv4[] = { 175 | 0x04, /* version */ 176 | 0x00, /* type */ 177 | 0x00, /* pubalgo */ 178 | 0x00, /* hashalgo */ 179 | 0x00, 0x1d, /* octet count hashed */ 180 | 0x16, 0x21, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 181 | 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, /* sig created subpkg */ 182 | 0x00, 0x0a, /* octet count unhashed */ 183 | 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* issuer subpkg */ 184 | }; 185 | 186 | static unsigned char 187 | v3sig_skel[] = { 188 | 0x03, /* version */ 189 | 0x05, /* size of hashed data */ 190 | 0x00, /* type */ 191 | 0x00, 0x00, 0x00, 0x00, /* sig created */ 192 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* issuer */ 193 | 0x00, /* pubalgo */ 194 | 0x00, /* hashalgo */ 195 | }; 196 | 197 | #define V4SIG_HASHED (4 + 2 + 6) 198 | #define V4SIG_HASHED_FPV4 (4 + 2 + 29) 199 | 200 | unsigned char * 201 | genv4sigtrail(int clearsign, int pubalgo, int hashalgo, u32 signtime, unsigned char *fp, int *v4sigtraillen) 202 | { 203 | int hlen; 204 | unsigned char *v4sigtrail; 205 | if (fp && fp[0] != 4) 206 | fp = 0; /* sorry, only v4 supported */ 207 | hlen = fp ? V4SIG_HASHED_FPV4 : V4SIG_HASHED; 208 | v4sigtrail = doalloc(hlen + 6); 209 | memcpy(v4sigtrail, fp ? v4sig_skel_fpv4 : v4sig_skel, hlen); 210 | v4sigtrail[1] = clearsign ? 0x01 : 0x00; 211 | v4sigtrail[2] = pubpgpalgo[pubalgo]; 212 | v4sigtrail[3] = hashpgpalgo[hashalgo]; 213 | if (fp) 214 | memcpy(v4sigtrail + 8, fp, 20 + 1); 215 | v4sigtrail[hlen - 4] = signtime >> 24; 216 | v4sigtrail[hlen - 3] = signtime >> 16; 217 | v4sigtrail[hlen - 2] = signtime >> 8; 218 | v4sigtrail[hlen - 1] = signtime; 219 | v4sigtrail[hlen ] = 4; 220 | v4sigtrail[hlen + 1] = 255; 221 | v4sigtrail[hlen + 2] = 0; 222 | v4sigtrail[hlen + 3] = 0; 223 | v4sigtrail[hlen + 4] = hlen >> 8; 224 | v4sigtrail[hlen + 5] = hlen; 225 | *v4sigtraillen = hlen + 6; 226 | return v4sigtrail; 227 | } 228 | 229 | static int 230 | fixupsig_fin(unsigned char *sigpk, int sigpklen, int tail, int left, unsigned char *newsig, int newsiglen, unsigned char *mpidata, int mpidatalen) 231 | { 232 | int nhl, nl = newsiglen + mpidatalen; 233 | if (mpidatalen < 2) 234 | dodie("fixupsig: mpidatalen is too small"); 235 | if (nl >= 65536) 236 | dodie("fixupsig: new signature is too big"); 237 | if (sigpk + sigpklen != mpidata + mpidatalen) 238 | dodie("fixupsig: trailing signature data"); 239 | memmove(sigpk + left, sigpk, sigpklen + tail); /* make room, also moves mpidata */ 240 | if (nl < 256) 241 | { 242 | sigpk[0] = 0x88; 243 | sigpk[1] = nl; 244 | nhl = 2; 245 | } 246 | else 247 | { 248 | sigpk[0] = 0x89; 249 | sigpk[1] = nl >> 8; 250 | sigpk[2] = nl; 251 | nhl = 3; 252 | } 253 | if (nhl + nl >= sigpklen + left) 254 | dodie("fixupsig: no room left"); 255 | memmove(sigpk + nhl, newsig, newsiglen); 256 | memmove(sigpk + nhl + newsiglen, mpidata + left, mpidatalen); 257 | if (tail) 258 | memmove(sigpk + nhl + nl, sigpk + left + sigpklen, tail); 259 | return nhl + nl; 260 | } 261 | 262 | int 263 | fixupsig(unsigned char *sigtrail, unsigned char *v4sigtrail, unsigned char *sigpk, int sigpklen, int tail, int left) 264 | { 265 | unsigned char *sig, *issuer; 266 | int sigl, mpioff, alg = 0, halg = 0; 267 | 268 | sig = pkg2sig(sigpk, sigpklen, &sigl); 269 | 270 | if (sig[0] == 3 && !v4sigtrail && sigl >= 19) 271 | { 272 | memcpy(sig + 2, sigtrail, 5); /* all is fine, just patch in sigtrail data */ 273 | return sigpklen; 274 | } 275 | 276 | if (sig[0] == 3) 277 | { 278 | alg = sig[15]; 279 | halg = sig[16]; 280 | } 281 | else if (sig[0] == 4) 282 | { 283 | alg = sig[2]; 284 | halg = sig[3]; 285 | } 286 | else 287 | dodie("unsupported signature version"); 288 | issuer = findsigissuer(sig, sigl); 289 | if (!issuer) 290 | dodie("could not determine issuer"); 291 | mpioff = findsigmpioffset(sig, sigl) - 2; 292 | 293 | if (v4sigtrail) 294 | { 295 | /* we want a v4 sig */ 296 | if (alg != v4sigtrail[2]) 297 | dodie("fixupsig pubkey algo mismatch"); 298 | if (halg != v4sigtrail[3]) 299 | dodie("fixupsig hash algo mismatch"); 300 | if (v4sigtrail[5] == 0x1d && memcmp(issuer, v4sigtrail + 9 + 20 - 8, 8) != 0) 301 | dodie("fixup issuer mismatch"); 302 | if (v4sigtrail[5] == 0x1d) 303 | { 304 | /* we want a v4 sig with a v4 fingerprint */ 305 | unsigned char newsig4[sizeof(v4sig_skel_fpv4)]; 306 | memcpy(newsig4, v4sig_skel_fpv4, sizeof(v4sig_skel_fpv4)); 307 | memcpy(newsig4, v4sigtrail, V4SIG_HASHED_FPV4); /* patch sigtrail data */ 308 | memcpy(newsig4 + V4SIG_HASHED_FPV4 + 4, issuer, 8); /* patch issuer */ 309 | return fixupsig_fin(sigpk, sigpklen, tail, left, newsig4, sizeof(newsig4), sig + mpioff, sigl - mpioff); 310 | } 311 | else 312 | { 313 | unsigned char newsig4[sizeof(v4sig_skel)]; 314 | memcpy(newsig4, v4sig_skel, sizeof(v4sig_skel)); 315 | memcpy(newsig4, v4sigtrail, V4SIG_HASHED); /* patch sigtrail data */ 316 | memcpy(newsig4 + V4SIG_HASHED + 4, issuer, 8); /* patch issuer */ 317 | return fixupsig_fin(sigpk, sigpklen, tail, left, newsig4, sizeof(newsig4), sig + mpioff, sigl - mpioff); 318 | } 319 | } 320 | else 321 | { 322 | /* we want a v3 sig */ 323 | unsigned char newsig3[sizeof(v3sig_skel)]; 324 | memcpy(newsig3, v3sig_skel, sizeof(v3sig_skel)); 325 | memcpy(newsig3 + 2, sigtrail, 5); /* patch sigtrail data */ 326 | memcpy(newsig3 + 7, issuer, 8); /* patch issuer */ 327 | newsig3[15] = alg; 328 | newsig3[16] = halg; 329 | return fixupsig_fin(sigpk, sigpklen, tail, left, newsig3, sizeof(newsig3), sig + mpioff, sigl - mpioff); 330 | } 331 | } 332 | 333 | unsigned char * 334 | nextpkg(int *tagp, int *pkgl, unsigned char **pp, int *ppl) 335 | { 336 | int x, l; 337 | unsigned char *p = *pp; 338 | int pl = *ppl; 339 | int tag; 340 | 341 | *tagp = 0; 342 | if (!pl) 343 | return 0; 344 | x = *p++; 345 | pl--; 346 | if (!(x & 128) || pl <= 0) 347 | return 0; 348 | if ((x & 64) == 0) 349 | { 350 | /* old format */ 351 | tag = (x & 0x3c) >> 2; 352 | x &= 3; 353 | if (x == 3) 354 | return 0; 355 | l = 1 << x; 356 | if (pl < l || (l == 4 && p[0] != 0)) 357 | return 0; 358 | x = 0; 359 | while (l--) 360 | { 361 | x = x << 8 | *p++; 362 | pl--; 363 | } 364 | l = x; 365 | } 366 | else 367 | { 368 | tag = (x & 0x3f); 369 | x = *p++; 370 | pl--; 371 | if (x < 192) 372 | l = x; 373 | else if (x >= 192 && x < 224) 374 | { 375 | if (pl <= 0) 376 | return 0; 377 | l = ((x - 192) << 8) + *p++ + 192; 378 | pl--; 379 | } 380 | else if (x == 255) 381 | { 382 | if (pl <= 4 || p[0] != 0) 383 | return 0; 384 | l = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; 385 | p += 4; 386 | pl -= 4; 387 | } 388 | else 389 | return 0; 390 | } 391 | if (pl < l) 392 | return 0; 393 | *pp = p + l; 394 | *ppl = pl - l; 395 | *pkgl = l; 396 | *tagp = tag; 397 | return p; 398 | } 399 | 400 | unsigned char * 401 | findsubpkg(unsigned char *q, int l, int type, int *slp, int fixedsl) 402 | { 403 | int x; 404 | int ql = q[0] << 8 | q[1]; 405 | q += 2; 406 | if (ql + 2 > l) 407 | return 0; 408 | while (ql) 409 | { 410 | int sl; 411 | x = *q++; 412 | ql--; 413 | if (x < 192) 414 | sl = x; 415 | else if (x == 255) 416 | { 417 | if (ql < 4 || q[0] != 0) 418 | return 0; 419 | sl = q[0] << 24 | q[1] << 16 | q[2] << 8 | q[3]; 420 | q += 4; 421 | ql -= 4; 422 | } 423 | else 424 | { 425 | if (ql < 1) 426 | return 0; 427 | sl = ((x - 192) << 8) + *q++ + 192; 428 | ql--; 429 | } 430 | if (sl == 0 || ql < sl) 431 | return 0; 432 | x = q[0] & 127; 433 | if (x == type && (fixedsl < 0 || sl - 1 == fixedsl)) 434 | { 435 | if (slp) 436 | *slp = sl - 1; 437 | return q + 1; 438 | } 439 | q += sl; 440 | ql -= sl; 441 | } 442 | return 0; 443 | } 444 | 445 | unsigned char * 446 | addpkg(unsigned char *to, unsigned char *p, int l, int tag, int newformat) 447 | { 448 | if (l < 0 || l >= 8192) 449 | abort(); 450 | if (!newformat) 451 | { 452 | if (l < 256) 453 | { 454 | *to++ = 128 | tag << 2; 455 | *to++ = l; 456 | } 457 | else 458 | { 459 | *to++ = 128 | tag << 2 | 1; 460 | *to++ = l >> 8; 461 | *to++ = l; 462 | } 463 | } 464 | else 465 | { 466 | *to++ = 128 | 64 | tag; 467 | if (l < 192) 468 | *to++ = l; 469 | else 470 | { 471 | *to++ = ((l - 192) >> 8) + 192; 472 | *to++ = (l - 192); 473 | } 474 | } 475 | memmove(to, p, l); 476 | return to + l; 477 | } 478 | 479 | byte * 480 | pkg2sig(byte *pk, int pkl, int *siglp) 481 | { 482 | byte *sig; 483 | int l, ll, tag = 0; 484 | sig = nextpkg(&tag, &l, &pk, &pkl); 485 | if (!sig || l < 6 || tag != 2) 486 | { 487 | fprintf(stderr, "packet is not a signature [%d]\n", tag); 488 | exit(1); 489 | } 490 | if (sig[0] == 3) 491 | ll = 19; 492 | else if (sig[0] == 4) 493 | { 494 | int ll = 4; 495 | if (l < ll + 2) 496 | dodie("signature packet is too small"); 497 | ll += 2 + (sig[ll] << 8) + sig[ll + 1]; 498 | if (l < ll + 2) 499 | dodie("signature packet is too small"); 500 | ll += 2 + (sig[ll] << 8) + sig[ll + 1]; 501 | } 502 | else 503 | dodie("not a V3 or V4 signature"); 504 | if (l < ll + 2) 505 | dodie("signature packet is too small"); 506 | *siglp = l; 507 | return sig; 508 | } 509 | 510 | byte * 511 | findsigissuer(byte *sig, int sigl) 512 | { 513 | byte *issuer, *fp; 514 | int hl, fplen; 515 | 516 | if (!sigl) 517 | return 0; 518 | if (sig[0] == 3) 519 | return sigl >= 15 ? sig + 7 : 0; 520 | if (sig[0] != 4) 521 | return 0; 522 | issuer = findsubpkg(sig + 4, sigl - 4, 16, 0, 8); 523 | if (issuer) 524 | return issuer; 525 | fp = findsubpkg(sig + 4, sigl - 4, 33, &fplen, -1); 526 | if (fp && fplen > 16 && *fp >= 4) 527 | return fp + fplen - 8; 528 | hl = 4 + 2 + ((sig[4] << 8) | sig[5]); 529 | return findsubpkg(sig + hl, sigl - hl, 16, 0, 8); 530 | } 531 | 532 | int 533 | findsigmpioffset(byte *sig, int sigl) 534 | { 535 | if (sig[0] == 3) 536 | return 19; 537 | if (sig[0] == 4) 538 | { 539 | int off = 6 + (sig[4] << 8) + sig[5]; 540 | off += 2 + (sig[off] << 8) + sig[off + 1] + 2; 541 | return off; 542 | } 543 | dodie("not a v3 or v4 signature"); 544 | return -1; 545 | } 546 | 547 | int 548 | findsigpubalgo(byte *sig, int sigl) 549 | { 550 | int algo = -1; 551 | if (sig[0] == 3) 552 | algo = sig[15]; 553 | else if (sig[0] == 4) 554 | algo = sig[2]; 555 | if (algo == 1) 556 | return PUB_RSA; 557 | if (algo == 17) 558 | return PUB_DSA; 559 | if (algo == 19) 560 | return PUB_ECDSA; 561 | if (algo == 22) 562 | return PUB_EDDSA; 563 | if (algo == 100) 564 | return PUB_MLDSA65; 565 | return -1; 566 | } 567 | 568 | int 569 | pkg2sigpubalgo(byte *pk, int pkl) 570 | { 571 | int sigl; 572 | byte *sig = pkg2sig(pk, pkl, &sigl); 573 | return findsigpubalgo(sig, sigl); 574 | } 575 | 576 | void 577 | calculatekeyfingerprint(byte *key, int keyl, byte *fingerprintp) 578 | { 579 | byte b[3]; 580 | if (!keyl || *key != 4) 581 | dodie("only know how to calculate the fingerprint of V4 keys"); 582 | SHA1_CONTEXT ctx; 583 | sha1_init(&ctx); 584 | b[0] = 0x99; 585 | b[1] = keyl >> 8; 586 | b[2] = keyl; 587 | sha1_write(&ctx, b, 3); 588 | sha1_write(&ctx, key, keyl); 589 | sha1_final(&ctx); 590 | memcpy(fingerprintp, sha1_read(&ctx), 20); 591 | } 592 | 593 | int 594 | findkeypubalgo(byte *key, int keyl) 595 | { 596 | int algo = -1; 597 | if (keyl >= 8 && key[0] == 3) 598 | algo = key[7]; 599 | else if (keyl >= 6 && key[0] == 4) 600 | algo = key[5]; 601 | if (algo == 1) 602 | return PUB_RSA; 603 | if (algo == 17) 604 | return PUB_DSA; 605 | if (algo == 19) 606 | return PUB_ECDSA; 607 | if (algo == 22) 608 | return PUB_EDDSA; 609 | return -1; 610 | } 611 | 612 | int 613 | findkeympioffset(byte *key, int keyl) 614 | { 615 | if (keyl >= 8 && key[0] == 3) 616 | return 8; 617 | if (keyl >= 6 && key[0] == 4) 618 | return 6; 619 | dodie("not a v3 or v4 key"); 620 | return -1; 621 | } 622 | 623 | int 624 | setmpis(byte *p, int l, int nmpi, byte **mpi, int *mpil, int withcurve) 625 | { 626 | int origl = l; 627 | for (; nmpi > 0; nmpi--) 628 | { 629 | int bytes; 630 | if (l < 2) 631 | dodie("truncated mpi data"); 632 | if (withcurve) 633 | { 634 | withcurve = 0; 635 | bytes = p[0]; 636 | if (bytes == 0 || bytes == 255) 637 | { 638 | fprintf(stderr, "illegal curve length: %d\n", bytes); 639 | exit(1); 640 | } 641 | p++; 642 | l--; 643 | } 644 | else 645 | { 646 | bytes = ((p[0] << 8) + p[1] + 7) >> 3; 647 | p += 2; 648 | l -= 2; 649 | } 650 | if (l < bytes) 651 | dodie("truncated mpi data"); 652 | *mpi++ = p; 653 | *mpil++ = bytes; 654 | p += bytes; 655 | l -= bytes; 656 | } 657 | return origl - l; 658 | } 659 | 660 | -------------------------------------------------------------------------------- /rpm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 SUSE LLC 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program (see the file COPYING); if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 17 | * 18 | ***************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "inc.h" 25 | #include "bele.h" 26 | 27 | #define MAX_SIG_SIZE 1024 /* we pre-allocate space for 2 new signatures */ 28 | 29 | #define HEADER_SIGNATURES 62 30 | #define RPMSIGTAG_DSA 267 /* header only sig */ 31 | #define RPMSIGTAG_RSA 268 /* header only sig */ 32 | #define RPMSIGTAG_SHA1 269 /* payload hash */ 33 | #define RPMSIGTAG_LONGSIZE 270 34 | #define RPMSIGTAG_SHA256 273 35 | #define RPMSIGTAG_OPENPGP 278 36 | #define RPMSIGTAG_RESERVED 999 37 | #define RPMSIGTAG_SIZE 1000 38 | #define RPMSIGTAG_PGP 1002 39 | #define RPMSIGTAG_MD5 1004 40 | #define RPMSIGTAG_GPG 1005 41 | #define RPMSIGTAG_RESERVEDSPACE 1008 42 | 43 | /* RPM constants */ 44 | static const int pubtag[] = { RPMSIGTAG_GPG, RPMSIGTAG_PGP, RPMSIGTAG_GPG, RPMSIGTAG_GPG, 0 }; 45 | static const int pubtagh[] = { RPMSIGTAG_DSA, RPMSIGTAG_RSA, RPMSIGTAG_DSA, RPMSIGTAG_DSA, 0 }; /* header only tags */ 46 | 47 | static inline u32 48 | getbe4c(const unsigned char *p) 49 | { 50 | if (p[0]) 51 | dodie("header data overflow"); 52 | return getbe4(p); 53 | } 54 | 55 | static byte * 56 | rpm_sanitycheck(struct rpmdata *rd) 57 | { 58 | byte *rpmsig = rd->rpmsig; 59 | int rpmsigcnt = rd->rpmsigcnt; 60 | u32 rpmsigdlen = rd->rpmsigdlen; 61 | byte *rsp, *region = 0; 62 | u32 off; 63 | int i, tag; 64 | 65 | for (i = 0, rsp = rpmsig; i < rpmsigcnt; i++, rsp += 16) 66 | { 67 | tag = getbe4c(rsp); 68 | off = getbe4c(rsp + 8); 69 | if (off > (region ? rpmsigdlen - 16 : rpmsigdlen)) 70 | dodie("data offset out of range"); 71 | if (tag >= 61 && tag < 64) 72 | { 73 | if (i != 0) 74 | dodie("region must be first entry"); 75 | region = rsp; 76 | if (getbe4(region + 4) != 7 || getbe4(region + 12) != 16) 77 | dodie("region must have type 7 and size 16"); 78 | if (rpmsigdlen < 16) 79 | dodie("datalen too small for region"); 80 | if (off + 16 != rpmsigdlen) 81 | dodie("region must include the complete signature header"); 82 | } 83 | } 84 | if (region) 85 | { 86 | rsp = rpmsig + rpmsigcnt * 16 + getbe4c(region + 8); 87 | if (getbe4(rsp + 8) != (u32)(-(rpmsigcnt * 16))) 88 | dodie("region data does not match entry count"); 89 | } 90 | return region; 91 | } 92 | 93 | static u32 94 | datalen_strarr(byte *rpmsig, int rpmsigcnt, u32 rpmsigdlen, u32 off, u32 cnt) 95 | { 96 | byte *bpstart = rpmsig + rpmsigcnt * 16 + off; 97 | byte *bpmax = rpmsig + rpmsigcnt * 16 + rpmsigdlen; 98 | byte *bp = bpstart; 99 | while (cnt-- > 0) 100 | { 101 | while (*bp++) 102 | ; 103 | if (bp > bpmax) 104 | dodie("datalen overflow"); 105 | } 106 | return (u32)(bp - bpstart); 107 | } 108 | 109 | static u32 110 | datalen(byte *rpmsig, int rpmsigcnt, u32 rpmsigdlen, byte *rsp) 111 | { 112 | int type = getbe4c(rsp + 4), cnt = getbe4c(rsp + 12); 113 | 114 | if (type == 6 || type == 8 || type == 9) 115 | return datalen_strarr(rpmsig, rpmsigcnt, rpmsigdlen, getbe4c(rsp + 8), type == 6 ? 1 : cnt); 116 | if (type == 3) 117 | return 2 * cnt; 118 | else if (type == 4) 119 | return 4 * cnt; 120 | else if (type == 5) 121 | return 8 * cnt; 122 | return cnt; 123 | } 124 | 125 | static inline u32 126 | dataalign(int type) 127 | { 128 | if (type == 3) 129 | return 2; 130 | else if (type == 4) 131 | return 4; 132 | else if (type == 5) 133 | return 8; 134 | return 1; 135 | } 136 | 137 | static int 138 | realign_cmp(const void *a, const void *b) 139 | { 140 | const byte *rspa = *(const byte **)a, *rspb = *(const byte **)b; 141 | u32 offa = getbe4c(rspa + 8), offb = getbe4c(rspb + 8); 142 | if (offa != offb) 143 | return offa < offb ? -1 : 1; 144 | if (rspa != rspb) 145 | return rspa < rspb ? -1 : 1; 146 | return 0; 147 | } 148 | 149 | static void 150 | rpm_realign(struct rpmdata *rd, byte *region) 151 | { 152 | byte *rpmsig = rd->rpmsig; 153 | int rpmsigcnt = rd->rpmsigcnt; 154 | u32 rpmsigdlen = rd->rpmsigdlen; 155 | u32 maxrpmsigdlen = rpmsigdlen + 16; 156 | byte *rsp, **rsps; 157 | u32 off, lastoff, dl, align; 158 | int i, j; 159 | 160 | /* order entries by offset, put region last */ 161 | rsps = doalloc(rpmsigcnt * sizeof(byte *)); 162 | for (i = 0, rsp = rpmsig + (region ? 16 : 0); i < rpmsigcnt; i++, rsp += 16) 163 | rsps[i] = rsp; 164 | if (region) 165 | rsps[rpmsigcnt - 1] = rpmsig; 166 | if (rpmsigcnt > (region ? 2 : 1)) 167 | qsort(rsps, rpmsigcnt - (region ? 1 : 0), sizeof(byte *), realign_cmp); 168 | 169 | lastoff = 0; 170 | for (i = 0; i < rpmsigcnt; i++) 171 | { 172 | rsp = rsps[i]; 173 | align = dataalign((int)getbe4c(rsp + 4)); 174 | off = getbe4c(rsp + 8); 175 | if (lastoff > off) 176 | { 177 | fprintf(stderr, "lastoff overlaps with data: %d %d\n", lastoff, off); 178 | exit(1); 179 | } 180 | if (align > 1 && (lastoff % align) != 0) 181 | lastoff += align - (lastoff % align); 182 | if (lastoff != off) 183 | { 184 | /* alignment mismatch, move over from off to lastoff */ 185 | if (lastoff > off && rpmsigdlen + (lastoff - off) > maxrpmsigdlen) 186 | dodie("rpm_realign alignment space overflow"); 187 | memmove(rpmsig + rpmsigcnt * 16 + lastoff, rpmsig + rpmsigcnt * 16 + off, rpmsigdlen - off); 188 | if (lastoff > off) 189 | memset(rpmsig + rpmsigcnt * 16 + off, 0, lastoff - off); 190 | rpmsigdlen += lastoff - off; 191 | /* update all offsets */ 192 | for (j = i; j < rpmsigcnt; j++) 193 | setbe4(rsps[j] + 8, getbe4c(rsps[j] + 8) + lastoff - off); 194 | off += lastoff - off; 195 | } 196 | dl = datalen(rpmsig, rpmsigcnt, rpmsigdlen, rsp); 197 | if (dl > 0xffffff) 198 | dodie("datalen overflow"); 199 | lastoff = off + dl; 200 | } 201 | if (lastoff > rpmsigdlen) 202 | { 203 | fprintf(stderr, "lastoff overlaps with data: %d %d\n", lastoff, rpmsigdlen); 204 | exit(1); 205 | } 206 | rd->rpmsigdlen = rpmsigdlen; 207 | free(rsps); 208 | } 209 | 210 | static void 211 | rpm_adaptreserved(struct rpmdata *rd, byte *region, int diff) 212 | { 213 | byte *rpmsig = rd->rpmsig; 214 | int rpmsigcnt = rd->rpmsigcnt; 215 | byte *rpmsigdata = rpmsig + 16 * rpmsigcnt; 216 | int tag, o, l; 217 | byte *rsp; 218 | 219 | if (!rpmsigcnt || !diff) 220 | return; 221 | /* the reserved space must be the last tag and must be the last 222 | * entry in the data segment */ 223 | rsp = rpmsig + 16 * (rpmsigcnt - 1); 224 | tag = getbe4c(rsp); 225 | if ((tag != RPMSIGTAG_RESERVEDSPACE && tag != RPMSIGTAG_RESERVED) || getbe4(rsp + 4) != 7) 226 | return; 227 | o = getbe4c(rsp + 8); 228 | l = getbe4c(rsp + 12); 229 | if (o + l != rd->rpmsigdlen - (region ? 16 : 0)) 230 | return; /* reserved space is not at end of data */ 231 | if (diff < 0 && l + diff < 1) 232 | return; /* not enough space left */ 233 | if (region) 234 | { 235 | /* check region offset again just in case... */ 236 | if (getbe4c(region + 8) != o + l) 237 | dodie("rpm_adaptreserved: unexpected region offset"); 238 | memmove(rpmsigdata + o + l + diff, rpmsigdata + o + l, 16); 239 | setbe4(region + 8, o + l + diff); 240 | } 241 | if (diff > 0) 242 | memset(rpmsigdata + o + l, 0, diff); 243 | else 244 | memset(rpmsigdata + rd->rpmsigdlen + diff, 0, -diff); 245 | l += diff; 246 | setbe4(rsp + 12, l); 247 | rd->rpmsigdlen += diff; 248 | } 249 | 250 | static int 251 | rpm_insertsig_tag(struct rpmdata *rd, int sigtag, byte *newsig, int newsiglen) 252 | { 253 | byte *rpmsig = rd->rpmsig; 254 | int rpmsigcnt = rd->rpmsigcnt; 255 | u32 rpmsigsize = rd->rpmsigsize, rpmsigdlen = rd->rpmsigdlen; 256 | u32 oldsigspace = rpmsigcnt * 16 + rpmsigdlen, newsigspace; 257 | u32 off, before; 258 | int i, myi, tag; 259 | byte *rsp, *region; 260 | int pad; 261 | 262 | if (newsiglen < 0 || newsiglen > MAX_SIG_SIZE) 263 | { 264 | fprintf(stderr, "new signature size is bad: %d\n", newsiglen); 265 | return -1; 266 | } 267 | 268 | /* first do some sanity checking */ 269 | region = rpm_sanitycheck(rd); 270 | 271 | /* now find the correct place to insert the signature */ 272 | for (i = 0, tag = 0, rsp = rpmsig; i < rpmsigcnt; i++, rsp += 16) 273 | { 274 | tag = getbe4c(rsp); 275 | if (tag == sigtag) 276 | abort(); /* must not insert already existing tag */ 277 | if (tag > sigtag) 278 | break; 279 | } 280 | /* fprintf(stderr, "inserting at position %d of %d\n", i, rpmsigcnt); */ 281 | 282 | /* insert it */ 283 | memmove(rsp + 16, rsp, rpmsigsize - i * 16); 284 | memset(rsp, 0, 16); 285 | setbe4(rsp, sigtag); 286 | if (sigtag == RPMSIGTAG_OPENPGP) 287 | { 288 | setbe4(rsp + 4, 8); 289 | setbe4(rsp + 12, 1); 290 | } 291 | else 292 | { 293 | setbe4(rsp + 4, 7); 294 | setbe4(rsp + 12, newsiglen); 295 | } 296 | 297 | if (i < rpmsigcnt) 298 | before = getbe4c(rsp + 16 + 8); 299 | else if (region) 300 | before = getbe4c(region + 8); 301 | else 302 | before = rpmsigdlen; 303 | if (before > rpmsigdlen) 304 | abort(); 305 | 306 | /* fprintf(stderr, "before=%d sigdlen=%d\n", before, rpmsigdlen); */ 307 | rpmsigcnt++; 308 | if (before < rpmsigdlen) 309 | memmove(rpmsig + rpmsigcnt * 16 + before + newsiglen, rpmsig + rpmsigcnt * 16 + before, rpmsigdlen - before); 310 | memmove(rpmsig + rpmsigcnt * 16 + before, newsig, newsiglen); 311 | setbe4(rsp + 8, before); 312 | rpmsigdlen += newsiglen; 313 | 314 | /* now fix up all entries behind us */ 315 | myi = i; 316 | for (i = 0, rsp = rpmsig; i < rpmsigcnt; i++, rsp += 16) 317 | { 318 | if (i == myi) 319 | continue; 320 | off = getbe4c(rsp + 8); 321 | if (off >= before) 322 | setbe4(rsp + 8, off + newsiglen); 323 | } 324 | 325 | /* update entry count in region data */ 326 | if (region) 327 | { 328 | off = getbe4c(region + 8); 329 | rsp = rpmsig + rpmsigcnt * 16 + off; 330 | setbe4(rsp + 8, getbe4(rsp + 8) - 16); 331 | } 332 | rd->rpmsigcnt = rpmsigcnt; 333 | rd->rpmsigdlen = rpmsigdlen; 334 | 335 | /* correct the alignment of all entries */ 336 | rpm_realign(rd, region); 337 | rpmsigdlen = rd->rpmsigdlen; 338 | 339 | /* if the last entry is the reserved tag, shrink it */ 340 | newsigspace = rpmsigcnt * 16 + rpmsigdlen; 341 | if (newsigspace > oldsigspace) 342 | { 343 | rpm_adaptreserved(rd, region, oldsigspace - newsigspace); 344 | rpmsigdlen = rd->rpmsigdlen; 345 | } 346 | 347 | /* pad to multiple of 8 */ 348 | pad = 7 - ((rpmsigdlen + 7) & 7); 349 | if (pad) 350 | memset(rpmsig + rpmsigcnt * 16 + rpmsigdlen, 0, pad); 351 | rpmsigsize = rpmsigcnt * 16 + rpmsigdlen + pad; 352 | rd->rpmsigsize = rpmsigsize; 353 | rd->hdrin_md5 = 0; /* no longer valid */ 354 | 355 | /* update sighead with new values */ 356 | setbe4(rd->rpmsighead + 8, rpmsigcnt); 357 | setbe4(rd->rpmsighead + 12, rpmsigdlen); 358 | return 0; 359 | } 360 | 361 | int 362 | rpm_insertsig(struct rpmdata *rd, int hdronly, byte *newsig, int newsiglen) 363 | { 364 | int sigtag; 365 | int pubalgo; 366 | if (rd->rpmlead[4] == 4) 367 | { 368 | int r; 369 | char *bspace = doalloc(newsiglen * 4 / 3 + 5); 370 | r64enc(bspace, newsig, newsiglen); 371 | r = rpm_insertsig_tag(rd, RPMSIGTAG_OPENPGP, (byte *)bspace, (int)(strlen(bspace) + 1)); 372 | free(bspace); 373 | return r; 374 | } 375 | pubalgo = pkg2sigpubalgo(newsig, newsiglen); 376 | if (pubalgo < 0) 377 | { 378 | fprintf(stderr, "signature has an unknown pubkey algorithm\n"); 379 | return -1; 380 | } 381 | sigtag = hdronly ? pubtagh[pubalgo] : pubtag[pubalgo]; 382 | if (!sigtag) 383 | { 384 | fprintf(stderr, "unsupported pubkey algorithm\n"); 385 | return -1; 386 | } 387 | return rpm_insertsig_tag(rd, sigtag, newsig, newsiglen); 388 | } 389 | 390 | int 391 | rpm_delsigs(struct rpmdata *rd) 392 | { 393 | byte *rpmsig = rd->rpmsig; 394 | int rpmsigcnt = rd->rpmsigcnt; 395 | u32 rpmsigsize = rd->rpmsigsize, rpmsigdlen = rd->rpmsigdlen; 396 | byte *rsp, *region; 397 | u32 oldsigspace = rpmsigcnt * 16 + rpmsigdlen, newsigspace; 398 | u32 off, siglen, before; 399 | int i, myi, tag; 400 | int pad; 401 | 402 | /* first do some sanity checking */ 403 | region = rpm_sanitycheck(rd); 404 | /* now erase all sigs */ 405 | for (i = 0, tag = 0, rsp = rpmsig; i < rpmsigcnt; i++, rsp += 16) 406 | { 407 | tag = getbe4c(rsp); 408 | if (!(tag == RPMSIGTAG_OPENPGP || tag == RPMSIGTAG_GPG || tag == RPMSIGTAG_PGP || tag == RPMSIGTAG_DSA || tag == RPMSIGTAG_RSA)) 409 | continue; 410 | off = getbe4c(rsp + 8); 411 | siglen = datalen(rpmsig, rpmsigcnt, rpmsigdlen, rsp); 412 | before = off + siglen; 413 | if (before > rpmsigdlen) 414 | abort(); 415 | /* delete it */ 416 | memmove(rpmsig + rpmsigcnt * 16 + off, rpmsig + rpmsigcnt * 16 + before, rpmsigdlen - before); 417 | memmove(rsp, rsp + 16, rpmsigsize - i * 16 - 16); 418 | rpmsigcnt--; 419 | rpmsigdlen -= siglen; 420 | /* now fix up all entries behind us */ 421 | myi = i; 422 | for (i = 0, rsp = rpmsig; i < rpmsigcnt; i++, rsp += 16) 423 | { 424 | off = getbe4c(rsp + 8); 425 | if (off >= before) 426 | setbe4(rsp + 8, off - siglen); 427 | } 428 | /* restart */ 429 | i = myi - 1; 430 | rsp = rpmsig + 16 * i; 431 | } 432 | 433 | /* update entry count in region data */ 434 | if (region) 435 | { 436 | off = getbe4c(region + 8); 437 | rsp = rpmsig + rpmsigcnt * 16 + off; 438 | setbe4(rsp + 8, (u32)(-(rpmsigcnt * 16))); 439 | } 440 | rd->rpmsigcnt = rpmsigcnt; 441 | rd->rpmsigdlen = rpmsigdlen; 442 | 443 | /* correct the alignment of all entries */ 444 | rpm_realign(rd, region); 445 | rpmsigdlen = rd->rpmsigdlen; 446 | 447 | /* if the last entry is the reserved tag, grow it */ 448 | newsigspace = rpmsigcnt * 16 + rpmsigdlen; 449 | if (newsigspace < oldsigspace) 450 | { 451 | rpm_adaptreserved(rd, region, oldsigspace - newsigspace); 452 | rpmsigdlen = rd->rpmsigdlen; 453 | } 454 | /* pad to multiple of 8 */ 455 | pad = 7 - ((rpmsigdlen + 7) & 7); 456 | if (pad) 457 | memset(rpmsig + rpmsigcnt * 16 + rpmsigdlen, 0, pad); 458 | rpmsigsize = rpmsigcnt * 16 + rpmsigdlen + pad; 459 | rd->rpmsigsize = rpmsigsize; 460 | rd->hdrin_md5 = 0; /* no longer valid */ 461 | 462 | /* update sighead with new values */ 463 | setbe4(rd->rpmsighead + 8, rpmsigcnt); 464 | setbe4(rd->rpmsighead + 12, rpmsigdlen); 465 | return 0; 466 | } 467 | 468 | static void 469 | rpm_readsigheader(struct rpmdata *rd, int fd, const char *filename) 470 | { 471 | byte *p, *rsp; 472 | int i; 473 | u32 tag; 474 | 475 | doread(fd, rd->rpmlead, 96); 476 | if (getbe4(rd->rpmlead) != 0xedabeedb) 477 | { 478 | fprintf(stderr, "%s: not a rpm\n", filename); 479 | exit(1); 480 | } 481 | if ((rd->rpmlead[4] != 0x03 && rd->rpmlead[4] != 0x04) || rd->rpmlead[0x4e] != 0 || rd->rpmlead[0x4f] != 5) 482 | { 483 | fprintf(stderr, "%s: not a v3/4 rpm or not new header styles\n", filename); 484 | exit(1); 485 | } 486 | doread(fd, rd->rpmsighead, 16); 487 | if (getbe4(rd->rpmsighead) != 0x8eade801) 488 | { 489 | fprintf(stderr, "%s: bad signature header\n", filename); 490 | exit(1); 491 | } 492 | rd->rpmsigcnt = getbe4c(rd->rpmsighead + 8); 493 | rd->rpmsigdlen = getbe4c(rd->rpmsighead + 12); 494 | if (rd->rpmsigcnt > 0xffff || rd->rpmsigdlen > 0xfffffff) 495 | dodie("signature header overflow"); 496 | rd->rpmsigsize = rd->rpmsigcnt * 16 + ((rd->rpmsigdlen + 7) & ~7); 497 | rd->rpmsig = doalloc(rd->rpmsigsize + 2 * (MAX_SIG_SIZE + 16 + 16) + 8); /* 16(entry) + 16(alignment) */ 498 | doread(fd, rd->rpmsig, rd->rpmsigsize); 499 | memset(rd->rpmsig + rd->rpmsigsize, 0, 2 * (MAX_SIG_SIZE + 16 + 16) + 8); /* zero out extra space */ 500 | rd->rpmdataoff = 96 + 16 + rd->rpmsigsize; 501 | for (i = 0, rsp = rd->rpmsig; i < rd->rpmsigcnt; i++, rsp += 16) 502 | { 503 | tag = getbe4c(rsp); 504 | if (tag == RPMSIGTAG_OPENPGP || tag == RPMSIGTAG_GPG || tag == RPMSIGTAG_PGP || tag == RPMSIGTAG_DSA || tag == RPMSIGTAG_RSA) 505 | rd->gotsigs = 1; 506 | if (tag == RPMSIGTAG_SHA1) 507 | rd->gotsha1 = 1; 508 | if (tag == RPMSIGTAG_SHA256) 509 | rd->gotsha256 = 1; 510 | if (tag == RPMSIGTAG_MD5) 511 | { 512 | int o = getbe4c(rsp + 8); 513 | if (getbe4(rsp + 4) != 7 || getbe4(rsp + 12) != 16 || o + 16 > rd->rpmsigdlen) 514 | { 515 | fprintf(stderr, "%s: bad MD5 tag\n", filename); 516 | exit(1); 517 | } 518 | rd->hdrin_md5 = rd->rpmsig + rd->rpmsigcnt * 16 + o; 519 | } 520 | if (tag == RPMSIGTAG_SIZE) 521 | { 522 | int o = getbe4c(rsp + 8); 523 | if (getbe4(rsp + 4) != 4 || getbe4(rsp + 12) != 1 || o + 4 > rd->rpmsigdlen) 524 | { 525 | fprintf(stderr, "%s: bad SIZE tag\n", filename); 526 | exit(1); 527 | } 528 | p = rd->rpmsig + rd->rpmsigcnt * 16 + o; 529 | rd->hdrin_size = (u32)(p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]); 530 | } 531 | } 532 | } 533 | 534 | static void 535 | rpm_readheaderpayload(struct rpmdata *rd, int fd, char *filename, HASH_CONTEXT *ctx, HASH_CONTEXT *hctx, int getbuildtime) 536 | { 537 | byte buf[8192]; 538 | byte btbuf[4]; 539 | MD5_CTX md5ctx; 540 | u32 lenhdr; 541 | u64 lensig; 542 | int l, i; 543 | u32 buildtimeoff = 0; 544 | 545 | md5_init(&md5ctx); 546 | if (hctx) 547 | hash_init(hctx); 548 | 549 | lensig = 0; 550 | lenhdr = 0; 551 | rd->buildtime = 0; 552 | for (;;) 553 | { 554 | l = read(fd, buf, sizeof(buf)); 555 | if (l < 0) 556 | dodie_errno("read"); 557 | if (l == 0) 558 | break; 559 | if (!lensig) 560 | { 561 | if (l < 16) 562 | dodie("cannot calculate header size: short read"); 563 | if (buf[8] || buf[9] || buf[12] > 0x0f) 564 | dodie("header size overflow"); 565 | lenhdr = 16 + 16 * getbe4(buf + 8) + getbe4(buf + 12); 566 | } 567 | if (getbuildtime && !lensig) 568 | { 569 | int n = getbe4(buf + 8); 570 | if ((l - 16) / 16 < n) 571 | n = (l - 16) / 16; 572 | for (i = 0; i < n; i++) 573 | if (!memcmp(buf + 16 + 16 * i, "\0\0\003\356\0\0\0\4", 8)) 574 | break; 575 | if (i == n) 576 | dodie("cannot calculate buildtime: tag not found"); 577 | buildtimeoff = getbe4(buf + 16 + 16 * i + 8); 578 | if (buildtimeoff > 0x0fffffff) 579 | dodie("illegal buildtime offset"); 580 | buildtimeoff += 16 + 16 * getbe4c(buf + 8); 581 | } 582 | if (getbuildtime && lensig < buildtimeoff + 4 && lensig + l > buildtimeoff) 583 | { 584 | for (i = 0; i < l; i++) 585 | if (lensig + i >= buildtimeoff && lensig + i < buildtimeoff + 4) 586 | btbuf[lensig + i - buildtimeoff] = buf[i]; 587 | } 588 | if (ctx) 589 | hash_write(ctx, buf, l); 590 | md5_write(&md5ctx, buf, l); 591 | if (lenhdr) 592 | { 593 | if (l >= lenhdr) 594 | { 595 | if (hctx) 596 | hash_write(hctx, buf, lenhdr); 597 | lenhdr = 0; 598 | } 599 | else 600 | { 601 | if (hctx) 602 | hash_write(hctx, buf, l); 603 | lenhdr -= l; 604 | } 605 | } 606 | lensig += l; 607 | } 608 | md5_final(rd->rpmmd5sum, &md5ctx); 609 | if (lenhdr) 610 | { 611 | fprintf(stderr, "%s: bad header size (%u)\n", filename, lenhdr); 612 | exit(1); 613 | } 614 | if (rd->hdrin_size && lensig != rd->hdrin_size) 615 | { 616 | fprintf(stderr, "%s: SIZE checksum error %llu %llu\n", filename, rd->hdrin_size, lensig); 617 | exit(1); 618 | } 619 | if (rd->hdrin_md5 && memcmp(rd->hdrin_md5, rd->rpmmd5sum, 16)) 620 | { 621 | fprintf(stderr, "%s: MD5 checksum error\n", filename); 622 | exit(1); 623 | } 624 | if (getbuildtime) 625 | { 626 | if (lensig < buildtimeoff + 4) 627 | dodie("cannot calculate buildtime: bad data pointer"); 628 | rd->buildtime = getbe4(btbuf); 629 | } 630 | } 631 | 632 | int 633 | rpm_read(struct rpmdata *rd, int fd, char *filename, HASH_CONTEXT *ctx, HASH_CONTEXT *hctx, int getbuildtime) 634 | { 635 | memset(rd, 0, sizeof(*rd)); 636 | rpm_readsigheader(rd, fd, filename); 637 | if (rd->gotsigs && ctx != NULL) 638 | { 639 | rpm_free(rd); 640 | return 0; /* already signed */ 641 | } 642 | rpm_readheaderpayload(rd, fd, filename, ctx, hctx, getbuildtime); 643 | return 1; 644 | } 645 | 646 | void 647 | rpm_write(struct rpmdata *rd, int foutfd, int fd, int chksumfilefd) 648 | { 649 | byte buf[8192]; 650 | MD5_CTX md5ctx; 651 | MD5_CTX chksum_ctx_md5; 652 | SHA1_CONTEXT chksum_ctx_sha1; 653 | SHA256_CONTEXT chksum_ctx_sha256; 654 | SHA512_CONTEXT chksum_ctx_sha512; 655 | int l; 656 | byte rpmmd5sum2[16]; 657 | 658 | doseek(fd, rd->rpmdataoff); 659 | 660 | dowrite(foutfd, rd->rpmlead, 96); 661 | dowrite(foutfd, rd->rpmsighead, 16); 662 | dowrite(foutfd, rd->rpmsig, rd->rpmsigsize); 663 | 664 | if (chksumfilefd >= 0) 665 | { 666 | md5_init(&md5ctx); 667 | md5_write(&md5ctx, rd->rpmlead, 96); 668 | md5_write(&md5ctx, rd->rpmsighead, 16); 669 | md5_write(&md5ctx, rd->rpmsig, rd->rpmsigsize); 670 | md5_final(rd->chksum_leadmd5, &md5ctx); 671 | 672 | md5_init(&chksum_ctx_md5); 673 | md5_write(&chksum_ctx_md5, rd->rpmlead, 96); 674 | md5_write(&chksum_ctx_md5, rd->rpmsighead, 16); 675 | md5_write(&chksum_ctx_md5, rd->rpmsig, rd->rpmsigsize); 676 | 677 | sha1_init(&chksum_ctx_sha1); 678 | sha1_write(&chksum_ctx_sha1, rd->rpmlead, 96); 679 | sha1_write(&chksum_ctx_sha1, rd->rpmsighead, 16); 680 | sha1_write(&chksum_ctx_sha1, rd->rpmsig, rd->rpmsigsize); 681 | 682 | sha256_init(&chksum_ctx_sha256); 683 | sha256_write(&chksum_ctx_sha256, rd->rpmlead, 96); 684 | sha256_write(&chksum_ctx_sha256, rd->rpmsighead, 16); 685 | sha256_write(&chksum_ctx_sha256, rd->rpmsig, rd->rpmsigsize); 686 | 687 | sha512_init(&chksum_ctx_sha512); 688 | sha512_write(&chksum_ctx_sha512, rd->rpmlead, 96); 689 | sha512_write(&chksum_ctx_sha512, rd->rpmsighead, 16); 690 | sha512_write(&chksum_ctx_sha512, rd->rpmsig, rd->rpmsigsize); 691 | } 692 | md5_init(&md5ctx); 693 | for (;;) 694 | { 695 | l = read(fd, buf, sizeof(buf)); 696 | if (l < 0) 697 | dodie_errno("read"); 698 | if (l == 0) 699 | break; 700 | md5_write(&md5ctx, buf, l); 701 | dowrite(foutfd, buf, l); 702 | if (chksumfilefd >= 0) 703 | { 704 | md5_write(&chksum_ctx_md5, buf, l); 705 | sha1_write(&chksum_ctx_sha1, buf, l); 706 | sha256_write(&chksum_ctx_sha256, buf, l); 707 | sha512_write(&chksum_ctx_sha512, buf, l); 708 | } 709 | } 710 | md5_final(rpmmd5sum2, &md5ctx); 711 | if (chksumfilefd >= 0) 712 | { 713 | md5_final(rd->chksum_md5, &chksum_ctx_md5); 714 | sha1_final(&chksum_ctx_sha1); 715 | memcpy(rd->chksum_sha1, sha1_read(&chksum_ctx_sha1), 20); 716 | sha256_final(&chksum_ctx_sha256); 717 | memcpy(rd->chksum_sha256, sha256_read(&chksum_ctx_sha256), 32); 718 | sha512_final(&chksum_ctx_sha512); 719 | memcpy(rd->chksum_sha512, sha512_read(&chksum_ctx_sha512), 64); 720 | } 721 | if (memcmp(rpmmd5sum2, rd->rpmmd5sum, 16)) 722 | dodie("rpm has changed, bailing out!"); 723 | } 724 | 725 | void 726 | rpm_free(struct rpmdata *rd) 727 | { 728 | if (rd->rpmsig) 729 | free(rd->rpmsig); 730 | rd->rpmsig = 0; 731 | } 732 | 733 | void 734 | rpm_writechecksums(struct rpmdata *rd, int chksumfilefd) 735 | { 736 | char buf[16*2 + 5+16*2 + 6+20*2 + 8+32*2 + 8+64*2 + 1], *bp; 737 | int i; 738 | 739 | if (chksumfilefd < 0) 740 | return; 741 | bp = buf; 742 | for (i = 0; i < 16; i++) 743 | { 744 | sprintf(bp, "%02x", rd->chksum_leadmd5[i]); 745 | bp += 2; 746 | } 747 | strcpy(bp, " md5:"); 748 | bp += 5; 749 | for (i = 0; i < 16; i++) 750 | { 751 | sprintf(bp, "%02x", rd->chksum_md5[i]); 752 | bp += 2; 753 | } 754 | strcpy(bp, " sha1:"); 755 | bp += 6; 756 | for (i = 0; i < 20; i++) 757 | { 758 | sprintf(bp, "%02x", rd->chksum_sha1[i]); 759 | bp += 2; 760 | } 761 | strcpy(bp, " sha256:"); 762 | bp += 8; 763 | for (i = 0; i < 32; i++) 764 | { 765 | sprintf(bp, "%02x", rd->chksum_sha256[i]); 766 | bp += 2; 767 | } 768 | strcpy(bp, " sha512:"); 769 | bp += 8; 770 | for (i = 0; i < 64; i++) 771 | { 772 | sprintf(bp, "%02x", rd->chksum_sha512[i]); 773 | bp += 2; 774 | } 775 | *bp++ = '\n'; 776 | dowrite(chksumfilefd, (unsigned char *)buf, bp - buf); 777 | } 778 | -------------------------------------------------------------------------------- /sign.8: -------------------------------------------------------------------------------- 1 | .\" man page for sign 2 | .TH sign 8 "Apr 2007" 3 | .SH NAME 4 | sign \- sign files or rpms 5 | 6 | .SH SYNOPSIS 7 | .B sign 8 | .RB [ -c | -d | -r | -a ] 9 | .RB [ -u 10 | .IR user ] 11 | .RB [ -h 12 | .IR hash ] 13 | .RI [ file ] 14 | .br 15 | .B sign 16 | .BR -k | -p 17 | .RB [ -u 18 | .IR user ] 19 | .RB [ -h 20 | .IR hash ] 21 | .br 22 | .B sign 23 | .BR -g 24 | .I type 25 | .I expire 26 | .I name 27 | .I email 28 | .br 29 | .B sign 30 | .BR -x 31 | .I expire 32 | .I pubkey 33 | .br 34 | .B sign 35 | .BR -C 36 | .I pubkey 37 | .br 38 | .B sign 39 | .B -t 40 | 41 | .SH DESCRIPTION 42 | sign adds a cryptographic signature to a file. It can add a clearsign signature 43 | (-c option), create a detached signature (-d option), or add a signature block 44 | to a rpm package (-r option). If no mode is specified, sign does a rpm sign 45 | if the file name ends in ".rpm", otherwise it does a clearsign. If no 46 | file name is specified, sign reads from stdin and writes to stdout. 47 | 48 | One can specify a specific user or hash method with the -u and -h option. 49 | Currently sign understands sha1, sha256, and sha512 hashes. 50 | 51 | sign does not create signatures by itself, it needs a running signing 52 | daemon (called signd) to do the work. The host and port information is read 53 | from the /etc/sign.conf file. 54 | 55 | The -k option makes sign print the keyid instead of signing a file, the 56 | -p option makes it print the public key. 57 | 58 | New keys can be created by using the -g option. In that case, a file name 59 | to store the private key needs to be provided with the -P option. This 60 | private key can be used for signing by also using the -P option with the 61 | other modes. The pubkey of the generated key is printed to stdout. 62 | 63 | In case a X509 certificate 64 | is needed (e.g. for linux kernel modules), the key can be converted to 65 | a (self-signed) certificate by using the -C option. 66 | 67 | The expire time of existing keys can be extended with the -x option. 68 | 69 | 70 | .SH SIGNING MODES 71 | .TP 72 | .B \-c 73 | Clearsign 74 | .TP 75 | .B \-r 76 | Sign RPM package 77 | .TP 78 | .B \-d 79 | Create a detached gpg signature 80 | .TP 81 | .B \-D 82 | Create a unarmored detached gpg signature 83 | .TP 84 | .B \-O 85 | Create a raw X509 signature. Enforces RSA unless the -A option is used. 86 | .TP 87 | .B \-a 88 | Sign AppImage container 89 | .TP 90 | .B \-\-appx 91 | Sign a windows appx container 92 | .TP 93 | .B \-\-pesign 94 | Sign a PE/COFF file 95 | .TP 96 | .B \-\-kosign 97 | Sign a linux kernel object 98 | .TP 99 | .B \-\-cmssign 100 | Create a detached pkcs7/CMS signature 101 | 102 | 103 | .SH COMMON OPTIONS 104 | In some cases it it useful to specify the signature time. This can be done 105 | with the -T option. 106 | 107 | The -S option specifies a checksum file, it makes sign append a line containing 108 | the checksum of the signed rpms. This can be used to speed up repository 109 | metadata generation. 110 | 111 | .TP 112 | .B \-v 113 | Verbose mode 114 | .TP 115 | .BI "\-u " username 116 | Specify the key to use. The user must be in the keyring used by signd. 117 | .TP 118 | .BI "\-h " hash 119 | Either sha1, sha256, or sha512. The default is sha1, because some old distributions, 120 | e.g., RHEL 7, cannot handle sha256. On modern systems you should use sha256. 121 | .TP 122 | .BI "\-T " unixtime 123 | Explicit sign time. If RPM mode (\-r) is used also accepts the string "buildtime". 124 | .TP 125 | .BI "\-P " keyfile 126 | Private key file. This file must contain the encrypted privatekey generated 127 | with the \-g option. 128 | .TP 129 | .BI "\-S " checksumfile 130 | Usable only with \-r option: appends checksums into the file. 131 | .TP 132 | .B \-4 133 | Create a pgp v4 signature instead of v3 134 | .TP 135 | .BI "\-A " pubkeyalgo 136 | Enforce that the specified algorithm (rsa, dsa, ecdsa, eddsa) is used for signing 137 | .TP 138 | .BI "\-\-cert " certfile 139 | Speficy a x509 certificate to use. A certificate is needed by some of the 140 | signing modes, like appx signing or kernel object signing. 141 | .TP 142 | .B \-\-cms-nocerts 143 | Do not include certificates in the pkcs7/cms signature 144 | .TP 145 | .B \-\-cms-keyid 146 | Identify the certificate with the keyid instead of the issuer/serial 147 | .TP 148 | .B \-\-bulk-cpio 149 | The input is a cpio archive in newc format. Each file in the archive is 150 | signed. All the signatures are packed into a new cpio archive which filenames 151 | consisting of the original files plus a ".sig" suffix. This mode is 152 | currently only supported for raw X509 signature creation (-O). 153 | .TP 154 | .B \-\-delsign 155 | Remove all existing signatures from the input instead of signing. This 156 | is currently only supported for rpm packages. 157 | 158 | 159 | .SH KEY GENERATION 160 | .TP 161 | .BR "\-g " "[\-P \fIprivkey\fP] " "\fItype\fP \fIexpire\fP \fIname\fP \fIemail\fP" 162 | Generate new key-pair. Where: 163 | .br 164 | type: one of {dsa,rsa}@{1024,2048,4096} ed25519 nistp256 nistp384 165 | expire: integer, days before expire 166 | name: real name 167 | email: email 168 | .br 169 | Write pubkey and the encrypted private key to stdout. 170 | If the \-P option isused, the private key will be 171 | written to the specified file and only the pubkey goes 172 | to stdout. 173 | 174 | .TP 175 | .BI "\-x " expire " \fIpubkey\fP" 176 | Extend key. Requires a private key to be specified with the \-P option. 177 | .TP 178 | .BI "\-C " pubkey 179 | Convert a gpg pubkey to a self-signed X509 certificate 180 | 181 | 182 | .SH OTHER MODES 183 | .TP 184 | .B \-t 185 | Ping signd. If ping was successful, return exit code 0. 186 | .TP 187 | .BR \-k 188 | Print the keyid of the key used for signing (root key or defined by \-u) 189 | .TP 190 | .BR \-p 191 | Print the pubkey of of the key used for signing (root key or defined by \-u) 192 | 193 | 194 | .SH SECURITY 195 | Unless the allow-unprivileged-ports option has been set to true for signd, 196 | sign needs to bind to a reserved port, in which case it works only for user 197 | root or needs to be installed suid-root. If the latter is the case, sign 198 | grants the users specified in the "allowuser" lines of the configuration 199 | the right to sign files. 200 | 201 | sign and signd are supposed to run in isolated networks only. 202 | 203 | .SH EXIT STATUS 204 | sign returns 0 if everything worked, otherwise it returns 1 and 205 | prints an error message to stderr. 206 | 207 | .SH SEE ALSO 208 | .BR signd (8), 209 | .BR sign.conf (5) 210 | -------------------------------------------------------------------------------- /sign.conf: -------------------------------------------------------------------------------- 1 | ### for build service host 2 | #server: 3 | #user: build@suse.de 4 | #allowuser: obsrun 5 | # 6 | ### for sign server 7 | #allow: 8 | #phrases: /root/.phrases 9 | -------------------------------------------------------------------------------- /sign.conf.5: -------------------------------------------------------------------------------- 1 | .\" man page for sign.conf 2 | .TH sign.conf 8 "Apr 2007" 3 | .SH NAME 4 | sign.conf \- sign and signd configuration file 5 | 6 | .SH SYNOPSIS 7 | /etc/sign.conf 8 | 9 | .SH DESCRIPTION 10 | This file holds the configuration both for the sign program and 11 | the signd daemon. Each line in the file has the format 12 | "key: value [value...]". Empty lines or lines starting with "#" are 13 | ignored. 14 | 15 | The following keys are recognized: 16 | 17 | .TP 4 18 | .BR server: " hostname" 19 | Forward all requests with unknown signing users to the specified server. 20 | .TP 4 21 | .BR port: " port" 22 | Use the specified port number instead of the default port "5167". 23 | .TP 4 24 | .BR proto: " unprotected|ssl" 25 | Set the protection protocol to secure the connection to the 26 | sign server. The default is to use an unprotected connection. 27 | .TP 4 28 | .BR user: " user" 29 | Set a default user to use for signing. 30 | .TP 4 31 | .BR hash: " hash" 32 | Set a default hash to use for signing. The default hash 33 | is SHA1 for compatibility reasons. 34 | .TP 4 35 | .BR allow: " ip" 36 | .TQ 37 | .BR allow: " subnet" 38 | .TQ 39 | .BR allow: " hostname" 40 | Allow only connections from the specified ip addresses, 41 | subnets expressed in CIDR notation, and/or hostnames. 42 | Note that hostnames are resolved using reverse DNS 43 | lookups, so there must be reverse entries in the DNS 44 | server, and it should be secured against DNS poisoning 45 | attacks. 46 | All request are rejected if the allow list is empty. 47 | .TP 4 48 | .BR gpg: " path_to_gpg" 49 | Select the gpg program to use instead of "/usr/bin/gpg". 50 | .TP 4 51 | .BR phrases: " phrases_directory" 52 | Set the directory containing gpg phrases for every user. 53 | A phrase file is fed into gpg with the "--passphrase-fd=0" 54 | option. 55 | .TP 4 56 | .BR map: " [hash:]from_signuser to_signuser" 57 | Modify the signuser. This can be used to map hashes and 58 | users to unambiguous key ids. 59 | .TP 4 60 | .BR allowuser: " username|uid" 61 | Grant the user the right to sign. The sign binary must 62 | be installed as suid-root binary for this to work. Multiple 63 | users can be specified by using multiple allowuser 64 | lines in the configuration. 65 | .TP 4 66 | .BR allow-unprivileged-ports: " true|false" 67 | Allow signd to accept connections from source ports > 68 | 1024. 69 | Defaults to false. 70 | .TP 4 71 | .BR use-unprivileged-ports: " true|false" 72 | Use a source port > 1024 when connecting to the signd server. 73 | Defaults to false. 74 | .TP 4 75 | .BR logfile: " filename" 76 | Log requests to the specified filename instead of stdout. 77 | .TP 4 78 | .BR gnupghome: " dirname" 79 | Configures the directory for gpg to use by setting the GNUPGHOME 80 | environment variable. 81 | .TP 4 82 | .BR use-agent: " true|false" 83 | Make signd directly talk to the gpg-agent for signing instead of 84 | calling gpg. If the gpg command does not implement the --files-are-digest 85 | parameter, this option always falls back to true. 86 | .TP 4 87 | .BR keycache: " dirname" 88 | Cache the result of finding the signing key for a username. This 89 | is only done if the gpg-agent is used for signing. The cache 90 | is automatically invalidated if there is a change in the gpg 91 | keyring. 92 | .TP 4 93 | .BR agentsocket: " socketpath [socketpath...]" 94 | Specify the location of the gpg agent socket. It is possible to 95 | specify more than one location, as gpg uses different socket 96 | directories depending on if the user is logged in or not. 97 | As a fallback, signd will call "gpgconf --list-dirs" to find 98 | the current location of the agent socket. 99 | .TP 4 100 | .BR ssl_certfile: " path 101 | .TQ 102 | .BR ssl_keyfile: " path 103 | Specify the certificate and the corresponding private key for 104 | ssl client certification. 105 | .TP 4 106 | .BR ssl_verifyfile: " path 107 | .TQ 108 | .BR ssl_verifydir: " dirpath 109 | Specify the ca locations used to verify the certificate of the 110 | server. If neither a verifydir nor a verifyfile is configured, 111 | the default ca locations are used. 112 | .TP 4 113 | .BR proxyport: " port" 114 | .TQ 115 | .BR proxyproto: " unprotected|ssl" 116 | .TQ 117 | .BR proxyssl_certfile: " path 118 | .TQ 119 | .BR proxyssl_keyfile: " path 120 | .TQ 121 | .BR proxyssl_verifyfile: " path 122 | .TQ 123 | .BR proxyssl_verifydir: " dirpath 124 | Configure the setting for incoming requests. The corresponding 125 | value for outgoing requests is used if a key has not been set. 126 | The keyfile/certfile specifies the server certificate, the 127 | verifydir/verifyfile configures the ca locations to verify 128 | the client certificate. Note that incoming requests with no 129 | client certificate are rejected. 130 | .TP 4 131 | .BR allow_subject: " x509_subject" 132 | .TQ 133 | .BR allow_subject: " /x509_subject_regex/" 134 | Allow only requests that have a verified client certificate 135 | with a subject that matches one of the specified values. 136 | The X509 subject is converted to a string as specified in RFC-2253 137 | before doing a match. If the allow_subject list is empty, no 138 | check is done on the X509 subject but the certificate is 139 | still verified. 140 | 141 | .SH FILES 142 | .I /etc/sign.conf 143 | 144 | .SH SEE ALSO 145 | .BR sign (8), 146 | .BR signd (8) 147 | -------------------------------------------------------------------------------- /signd.8: -------------------------------------------------------------------------------- 1 | .\" man page for signd 2 | .TH signd 8 "Apr 2007" 3 | .SH NAME 4 | signd \- execute or proxy sign requests 5 | 6 | .SH SYNOPSIS 7 | .B signd 8 | .RB [ -f ] 9 | 10 | .SH DESCRIPTION 11 | signd is a little daemon that listens for sign requests from sign, 12 | and either calls gpg to do the signing or forwards the request 13 | to another signd server. The -f option makes signd fork on startup. 14 | 15 | signd uses the same configuration used for sign, /etc/sign.conf. 16 | 17 | .SH SECURITY 18 | Unless the allow-unprivileged-ports option is set to true in 19 | /etc/sign.conf, signd allows only connections from reserved ports 20 | and the ip addresses, subnets expressed in CIDR notation, and 21 | hostnames listed in the "allow" field of the configuration. 22 | 23 | .SH SEE ALSO 24 | .BR sign (8), 25 | .BR sign.conf (5) 26 | -------------------------------------------------------------------------------- /sock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 SUSE LLC 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program (see the file COPYING); if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 17 | * 18 | ***************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "inc.h" 32 | 33 | #ifdef WITH_OPENSSL 34 | # include 35 | # include 36 | #endif 37 | 38 | 39 | static int sock = -1; 40 | 41 | extern char *test_sign; 42 | extern char *host; 43 | extern int port; 44 | extern int sockproto; 45 | extern uid_t uid, euid; 46 | extern int use_unprivileged_ports; 47 | 48 | #ifdef WITH_OPENSSL 49 | extern char *ssl_keyfile; 50 | extern char *ssl_certfile; 51 | extern char *ssl_verifyfile; 52 | extern char *ssl_verifydir; 53 | #endif 54 | 55 | #ifdef WITH_OPENSSL 56 | 57 | static SSL_CTX *ctx; 58 | static SSL *ssl; 59 | 60 | void 61 | dodie_ssl_error(const char *msg) 62 | { 63 | unsigned long e = ERR_get_error(); 64 | fprintf(stderr, "%s: %s\n", msg, ERR_error_string(e, 0)); 65 | exit(1); 66 | } 67 | 68 | void 69 | init_ssl_ctx() 70 | { 71 | SSL_library_init(); 72 | SSL_load_error_strings(); 73 | ctx = SSL_CTX_new(SSLv23_method()); 74 | if (!ctx) 75 | dodie("SSL_CTX_new failed"); 76 | SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_ALL); 77 | if (ssl_keyfile && !SSL_CTX_use_PrivateKey_file(ctx, ssl_keyfile, SSL_FILETYPE_PEM)) 78 | dodie_errno("SSL_CTX_use_PrivateKey_file failed"); 79 | if (ssl_certfile && !SSL_CTX_use_certificate_chain_file(ctx, ssl_certfile)) 80 | dodie_errno("SSL_CTX_use_certificate_chain_file failed"); 81 | if ((ssl_verifyfile || ssl_verifydir) && !SSL_CTX_load_verify_locations(ctx, ssl_verifyfile, ssl_verifydir)) 82 | dodie("SSL_CTX_load_verify_locations failed"); 83 | if (!ssl_verifyfile && !ssl_verifydir && !SSL_CTX_set_default_verify_paths(ctx)) 84 | dodie("SSL_CTX_set_default_verify_paths failed"); 85 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, 0); 86 | } 87 | 88 | void 89 | ssl_connect(const char *hostname) 90 | { 91 | if (!ctx) 92 | init_ssl_ctx(); 93 | ssl = SSL_new(ctx); 94 | if (!ssl) 95 | dodie("SSL_new failed"); 96 | if (!SSL_set_fd(ssl, sock)) 97 | dodie("SSL_set_fd failed"); 98 | if (hostname) 99 | SSL_set_tlsext_host_name(ssl, hostname); 100 | if (SSL_connect(ssl) != 1) 101 | dodie_ssl_error("SSL_connect failed"); 102 | } 103 | 104 | void 105 | ssl_close() 106 | { 107 | if (ssl) 108 | SSL_free(ssl); 109 | ssl = 0; 110 | } 111 | 112 | #endif 113 | 114 | void 115 | opensocket(void) 116 | { 117 | static int hostknown; 118 | static struct sockaddr_in svt; 119 | int optval; 120 | 121 | if (test_sign) 122 | return; 123 | #ifndef WITH_OPENSSL 124 | if (sockproto == SOCKPROTO_SSL) 125 | dodie("not built with SSL support"); 126 | #endif 127 | if (!hostknown) 128 | { 129 | svt.sin_addr.s_addr = inet_addr(host); 130 | svt.sin_family = AF_INET; 131 | if (svt.sin_addr.s_addr == -1) 132 | { 133 | struct hostent *hp; 134 | if (!(hp = gethostbyname(host))) 135 | { 136 | printf("%s: unknown host\n", host); 137 | exit(1); 138 | } 139 | memmove(&svt.sin_addr, hp->h_addr, hp->h_length); 140 | svt.sin_family = hp->h_addrtype; 141 | } 142 | svt.sin_port = htons(port); 143 | hostknown = 1; 144 | } 145 | if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) 146 | dodie_errno("socket"); 147 | if (!use_unprivileged_ports) 148 | { 149 | if (uid && euid != uid) 150 | { 151 | if (seteuid(0)) 152 | dodie_errno("seteuid"); 153 | } 154 | while (bindresvport(sock, NULL) != 0) 155 | { 156 | if (errno != EADDRINUSE) 157 | dodie_errno("bindresvport"); 158 | sleep(1); 159 | } 160 | if (uid && euid != uid) 161 | { 162 | if (seteuid(uid)) 163 | dodie_errno("seteuid"); 164 | } 165 | } 166 | if (connect(sock, (struct sockaddr *)&svt, sizeof(svt))) 167 | dodie_errno(host); 168 | optval = 1; 169 | setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); 170 | #ifdef WITH_OPENSSL 171 | if (sockproto == SOCKPROTO_SSL) 172 | ssl_connect(host); 173 | #endif 174 | } 175 | 176 | void 177 | closesocket() 178 | { 179 | #ifdef WITH_OPENSSL 180 | ssl_close(); 181 | #endif 182 | if (sock != -1) 183 | { 184 | close(sock); 185 | sock = -1; 186 | } 187 | } 188 | 189 | static inline ssize_t 190 | readsocket(void *buf, size_t count) 191 | { 192 | #ifdef WITH_OPENSSL 193 | if (!test_sign && sockproto == SOCKPROTO_SSL) 194 | return SSL_read(ssl, buf, count); 195 | #endif 196 | return read(sock, buf, count); 197 | } 198 | 199 | static inline ssize_t 200 | writesocket(void *buf, size_t count) 201 | { 202 | #ifdef WITH_OPENSSL 203 | if (!test_sign && sockproto == SOCKPROTO_SSL) 204 | return SSL_write(ssl, buf, count); 205 | #endif 206 | return write(sock, buf, count); 207 | } 208 | 209 | 210 | static pid_t 211 | pipe_and_fork(int *pip) 212 | { 213 | pid_t pid; 214 | if (pipe(pip) == -1) 215 | dodie_errno("pipe"); 216 | if ((pid = fork()) == (pid_t)-1) 217 | dodie_errno("fork"); 218 | if (pid == 0) 219 | { 220 | close(pip[0]); 221 | dup2(pip[1], 1); 222 | close(pip[1]); 223 | } 224 | else 225 | close(pip[1]); 226 | return pid; 227 | } 228 | 229 | static void 230 | doreq_test(byte *buf, int inbufl, int bufl) 231 | { 232 | pid_t pid; 233 | int pip[2]; 234 | 235 | pid = pipe_and_fork(pip); 236 | if (pid == 0) 237 | { 238 | pid = pipe_and_fork(pip); 239 | if (pid == 0) 240 | { 241 | while (inbufl > 0) 242 | { 243 | int l = write(1, buf, inbufl); 244 | if (l == -1) 245 | { 246 | perror("write"); 247 | _exit(1); 248 | } 249 | buf += l; 250 | inbufl -= l; 251 | } 252 | _exit(0); 253 | } 254 | dup2(pip[0], 0); 255 | close(pip[0]); 256 | execlp(test_sign, test_sign, "--test-sign", (char *)0); 257 | perror(test_sign); 258 | _exit(1); 259 | } 260 | sock = pip[0]; 261 | } 262 | 263 | static void 264 | reap_test_signd() 265 | { 266 | int status; 267 | pid_t pid = waitpid(0, &status, 0); 268 | if (pid <= 0) 269 | dodie_errno("waitpid"); 270 | if (status) 271 | { 272 | fprintf(stderr, "test signd returned status 0x%x\n", status); 273 | exit(1); 274 | } 275 | } 276 | 277 | int 278 | doreq_raw(byte *buf, int inbufl, int bufl) 279 | { 280 | int l, outl, errl; 281 | 282 | if (sock == -1) 283 | opensocket(); /* better late then never */ 284 | if (test_sign) 285 | doreq_test(buf, inbufl, bufl); 286 | else if (writesocket(buf, inbufl) != inbufl) 287 | { 288 | perror("write"); 289 | closesocket(); 290 | return -1; 291 | } 292 | 293 | l = 0; 294 | for (;;) 295 | { 296 | int ll; 297 | if (l == bufl) 298 | { 299 | fprintf(stderr, "packet too big\n"); 300 | closesocket(); 301 | return -1; 302 | } 303 | ll = readsocket(buf + l, bufl - l); 304 | if (ll == -1) 305 | { 306 | perror("read"); 307 | closesocket(); 308 | return -1; 309 | } 310 | if (ll == 0) 311 | break; 312 | l += ll; 313 | } 314 | closesocket(); 315 | if (test_sign) 316 | reap_test_signd(); 317 | if (l < 6) 318 | { 319 | fprintf(stderr, "packet too small\n"); 320 | return -1; 321 | } 322 | outl = buf[2] << 8 | buf[3]; 323 | errl = buf[4] << 8 | buf[5]; 324 | if (l != outl + errl + 6) 325 | { 326 | fprintf(stderr, "packet size mismatch %d %d %d\n", l, outl, errl); 327 | return -1; 328 | } 329 | if (errl) 330 | fwrite(buf + 6 + outl, 1, errl, stderr); 331 | if (buf[0] << 8 | buf[1]) 332 | return -(buf[0] << 8 | buf[1]); 333 | memmove(buf, buf + 6, outl); 334 | return outl; 335 | } 336 | 337 | int 338 | doreq_old(const char *user, const char *digest, const char *digestalgo, byte *buf, int bufl) 339 | { 340 | size_t userlen = strlen(user); 341 | size_t digestlen = strlen(digest); 342 | size_t digestalgolen = digestalgo ? strlen(digestalgo) + 1 : 0; 343 | if (4 + userlen + digestlen + digestalgolen > bufl) 344 | { 345 | fprintf(stderr, "request buffer overflow\n"); 346 | closesocket(); 347 | return -1; 348 | } 349 | buf[0] = userlen >> 8; 350 | buf[1] = userlen & 255; 351 | buf[2] = (digestlen + digestalgolen) >> 8; 352 | buf[3] = (digestlen + digestalgolen) & 255; 353 | memcpy(buf + 4, user, userlen); 354 | if (digestalgolen) 355 | { 356 | memcpy(buf + 4 + userlen, digestalgo, digestalgolen - 1); 357 | buf[4 + userlen + digestalgolen - 1] = ':'; 358 | } 359 | memcpy(buf + 4 + userlen + digestalgolen, digest, digestlen); 360 | return doreq_raw(buf, 4 + userlen + digestalgolen + digestlen, bufl); 361 | } 362 | 363 | int 364 | doreq(int argc, const char **argv, byte *buf, int bufl, int nret) 365 | { 366 | byte *bp; 367 | int i, l, v, outl; 368 | 369 | bp = buf + 2; 370 | *bp++ = 0; 371 | *bp++ = 0; 372 | *bp++ = argc >> 8; 373 | *bp++ = argc & 255; 374 | for (i = 0; i < argc; i++) 375 | { 376 | v = strlen(argv[i]); 377 | *bp++ = v >> 8; 378 | *bp++ = v & 255; 379 | } 380 | for (i = 0; i < argc; i++) 381 | { 382 | v = strlen(argv[i]); 383 | if (bp + v > buf + bufl) 384 | { 385 | fprintf(stderr, "request buffer overflow\n"); 386 | closesocket(); 387 | return -1; 388 | } 389 | memcpy(bp, argv[i], v); 390 | bp += v; 391 | } 392 | v = bp - (buf + 4); 393 | buf[0] = v >> 8; 394 | buf[1] = v & 255; 395 | 396 | outl = doreq_raw(buf, (int)(bp - buf), bufl); 397 | if (outl < 0) 398 | return outl; 399 | 400 | if (nret) 401 | { 402 | /* verify returned data */ 403 | if (outl < 2 + 2 * nret) 404 | { 405 | fprintf(stderr, "answer too small\n"); 406 | return -1; 407 | } 408 | if (buf[0] != 0 || buf[1] != nret) 409 | { 410 | fprintf(stderr, "bad return count\n"); 411 | return -1; 412 | } 413 | l = 2; 414 | for (i = 0; i < nret; i++) 415 | l += 2 + (buf[2 + i * 2] << 8 | buf[2 + i * 2 + 1]); 416 | if (l != outl) 417 | { 418 | fprintf(stderr, "answer size mismatch\n"); 419 | return -1; 420 | } 421 | } 422 | return outl; 423 | } 424 | 425 | /* do a request with one or two results */ 426 | int 427 | doreq_12(int argc, const char **argv, byte *buf, int bufl, int *outl2p) 428 | { 429 | int outl = doreq(argc, argv, buf, bufl, outl2p ? 2 : 1); 430 | if (outl >= 0) 431 | { 432 | outl = buf[2] << 8 | buf[3]; 433 | if (outl2p) 434 | { 435 | int outl2 = buf[4] << 8 | buf[5]; 436 | memmove(buf, buf + 6, outl + outl2); 437 | *outl2p = outl2; 438 | } 439 | else 440 | memmove(buf, buf + 4, outl); 441 | } 442 | return outl; 443 | } 444 | 445 | -------------------------------------------------------------------------------- /t/000_signd.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use bytes; 6 | use Test::More tests => 16; 7 | use File::Temp qw/tempdir/; 8 | use File::Path qw/remove_tree make_path/; 9 | use Digest::SHA; 10 | use FindBin; 11 | 12 | my $have_gcrypt; 13 | eval { 14 | require Crypt::GCrypt::Sexp; 15 | require Crypt::GCrypt::MPI; 16 | require Crypt::GCrypt; 17 | die unless defined &Crypt::GCrypt::pk_sign; 18 | $have_gcrypt = 1; 19 | }; 20 | 21 | my $user = 'defaultkey@localobs'; 22 | my $usersec = 'defaultkeysec@localobs'; 23 | my $prj_user = 'signd@localhost'; 24 | my $comment = "just for testing"; 25 | my $tmp_dir = "$FindBin::Bin/tmp"; 26 | my $var_dir = "$tmp_dir/var"; 27 | my $fixtures_dir = "$FindBin::Bin/fixtures"; 28 | 29 | ############################################################################### 30 | ### Prepare tests 31 | remove_tree($tmp_dir); 32 | 33 | make_path($var_dir); 34 | $ENV{LANG} = 'C'; 35 | $ENV{GNUPGHOME} = "$tmp_dir/gnupg"; 36 | make_path($ENV{GNUPGHOME}); 37 | chmod 0700, $ENV{GNUPGHOME}; 38 | system("gpg -q --import $fixtures_dir/secret-key.asc"); 39 | if (system("gpg --pinentry-mode=loopback --version >/dev/null 2>&1 /dev/null`; 90 | is($?, 0 , "Checking if decryption was successful"); 91 | my $tmpgnupghome = tempdir("XXXXXXX", DIR => $tmpdir); 92 | 93 | my $tmppriv_decrypted = "$tmpdir/privkey_decrypted"; 94 | $priv_decrypted .= pack('CC', 13 + 192, 8).'privsign'; 95 | spew($tmppriv_decrypted, $priv_decrypted); 96 | 97 | $ENV{GNUPGHOME} = $tmpgnupghome; 98 | 99 | my $importresult = `gpg --batch --allow-non-selfsigned-uid --import $tmppriv_decrypted 2>&1`; 100 | is($?, 0, "Checking import of decrypted privkey"); 101 | 102 | $ENV{GNUPGHOME} = "$tmp_dir/gnupg"; 103 | 104 | ############################################################################### 105 | ### Common vars for sign and privsign 106 | my $trailer = pack("CN", 0, time()); 107 | my $payload = "test"; 108 | my $arg = Digest::SHA::sha1_hex("$payload$trailer").'@'.unpack("H*", $trailer); 109 | 110 | ############################################################################### 111 | ### if ($cmd eq 'privsign') { 112 | $ENV{SIGN_GCRYPT} = 'disable'; 113 | my $cmd = "./signd -t privsign $user $keys[1] $arg"; 114 | my $privsign_result = `$cmd`; 115 | 116 | is($?, 0, "Checking cmd 'privsign' return code [gpg]"); 117 | my @privsign = decode_reply($privsign_result); 118 | spew("$tmpdir/privsign", $payload); 119 | spew("$tmpdir/privsign.sig", $privsign[0]); 120 | $ENV{GNUPGHOME} = $tmpgnupghome; 121 | my $verify_privsign = `gpg --allow-non-selfsigned-uid --verify $tmpdir/privsign.sig 2>&1`; 122 | like( 123 | $verify_privsign, 124 | qr/Good signature from/, 125 | "Checking cmd 'privsign' for 'Good signature' [gpg]" 126 | ); 127 | $ENV{GNUPGHOME} = "$tmp_dir/gnupg"; 128 | 129 | SKIP: { 130 | skip('Crypt::GCrypt not available', 2) unless $have_gcrypt; 131 | 132 | $ENV{SIGN_GCRYPT} = 'force'; 133 | $cmd = "./signd -t privsign $user $keys[1] $arg"; 134 | my $privsign_result = `$cmd`; 135 | $ENV{SIGN_GCRYPT} = 'disable'; 136 | 137 | is($?, 0, "Checking cmd 'privsign' return code [gcrypt]"); 138 | my @privsign = decode_reply($privsign_result); 139 | spew("$tmpdir/privsign", $payload); 140 | spew("$tmpdir/privsign.sig", $privsign[0]); 141 | $ENV{GNUPGHOME} = $tmpgnupghome; 142 | my $verify_privsign = `gpg --allow-non-selfsigned-uid --verify $tmpdir/privsign.sig 2>&1`; 143 | like( 144 | $verify_privsign, 145 | qr/Good signature from/, 146 | "Checking cmd 'privsign' for 'Good signature' [gcrypt]" 147 | ); 148 | $ENV{GNUPGHOME} = "$tmp_dir/gnupg"; 149 | } 150 | 151 | 152 | my $expired_key = slurp("$FindBin::Bin/fixtures/secret-key-expired-encrypted"); 153 | chomp($expired_key); 154 | 155 | $cmd = "./signd -t privsign $user $expired_key $arg"; 156 | #print "$cmd\n"; 157 | $privsign_result = `$cmd`; 158 | 159 | is($?, 0, "Checking cmd 'privsign' return code (expired key)") || print $privsign_result; 160 | @privsign = decode_reply($privsign_result); 161 | spew("$tmpdir/privsign", $payload); 162 | spew("$tmpdir/privsign.sig", $privsign[0]); 163 | $ENV{GNUPGHOME} = $tmpgnupghome; 164 | `gpg --import $FindBin::Bin/fixtures/public-key-expired.asc 2>&1`; 165 | $verify_privsign = `gpg --verify $tmpdir/privsign.sig 2>&1`; 166 | like( 167 | $verify_privsign, 168 | qr/Good signature from/, 169 | "Checking cmd 'privsign' for 'Good signature' (expired key)" 170 | ); 171 | 172 | $ENV{GNUPGHOME} = "$tmp_dir/gnupg"; 173 | 174 | 175 | 176 | ############################################################################### 177 | ### if ($cmd eq 'sign') { 178 | my $sign_result = `./signd -t sign $user $arg`; 179 | is($?, 0, "Checking cmd 'sign' return code"); 180 | my @sign = decode_reply($sign_result); 181 | spew("$tmpdir/sign", $payload); 182 | spew("$tmpdir/sign.sig", $sign[0]); 183 | my $verify_sign = `gpg --verify $tmpdir/sign.sig 2>&1`; 184 | like($verify_sign, qr/Good signature from/, "Checking for 'Good signature'"); 185 | 186 | $sign_result = `./signd -t sign $usersec $arg`; 187 | is($?, 0, "Checking cmd 'sign' return code"); 188 | @sign = decode_reply($sign_result); 189 | spew("$tmpdir/sign", $payload); 190 | spew("$tmpdir/sign.sig", $sign[0]); 191 | $verify_sign = `gpg --verify $tmpdir/sign.sig 2>&1`; 192 | like($verify_sign, qr/Good signature from/, "Checking for 'Good signature'"); 193 | 194 | ############################################################################### 195 | ### cleanup 196 | remove_tree($tmpdir); 197 | exit 0; 198 | 199 | sub slurp { 200 | my ($fn) = @_; 201 | my $fh; 202 | open($fh, '<', $fn) || die "Could not open '$fn': $!\n"; 203 | local $/; 204 | my $content = <$fh>; 205 | close $fh; 206 | return $content; 207 | } 208 | 209 | sub spew { 210 | my ($fn, $content) = @_; 211 | my $fh; 212 | open($fh, '>', $fn) || die "Could not open '$fn': $!\n"; 213 | print $fh $content; 214 | close $fh; 215 | } 216 | 217 | sub decode_reply { 218 | my ($reply, $oldproto) = @_; 219 | return () unless defined($reply) && $reply ne ''; 220 | my @out_parts; 221 | my ($status, $l_out, $l_err) = unpack('nnn', $reply); 222 | my $out = substr($reply, 6, $l_out); 223 | my $err = substr($reply, 6 + $l_out, $l_err); 224 | die $err if $err; 225 | print "l_out: $l_out, l_err: $l_err\n" if $ENV{DEBUG}; 226 | return () unless $l_out; 227 | 228 | return $out if $oldproto; 229 | 230 | my ($parts) = unpack('n',$out); 231 | print "parts: $parts\n" if $ENV{DEBUG}; 232 | $out = substr($out, 2); 233 | my @pl = unpack('n' x $parts, $out); 234 | print "\@pl: (@pl)\n" if $ENV{DEBUG}; 235 | $out = substr($out, 2 * $parts); 236 | for my $l (@pl) { 237 | my $t = substr($out, 0, $l); 238 | $out = substr($out, $l); 239 | push @out_parts, $t; 240 | } 241 | 242 | return @out_parts; 243 | } 244 | -------------------------------------------------------------------------------- /t/001_sign.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use bytes; 6 | use Test::More tests => 36; 7 | use File::Temp qw/tempdir/; 8 | use File::Path qw/remove_tree make_path/; 9 | use Digest::SHA; 10 | use FindBin; 11 | 12 | my $user = 'defaultkey@localobs'; 13 | my $prj_user = 'signd@localhost'; 14 | my $comment = "just for testing"; 15 | my $tmp_dir = "$FindBin::Bin/tmp"; 16 | my $var_dir = "$tmp_dir/var"; 17 | my $fixtures_dir = "$FindBin::Bin/fixtures"; 18 | my $payload = "test"; 19 | 20 | ############################################################################### 21 | ### Prepare tests 22 | remove_tree($tmp_dir); 23 | 24 | make_path($var_dir); 25 | $ENV{LANG} = 'C'; 26 | $ENV{GNUPGHOME} = "$tmp_dir/gnupg"; 27 | make_path($ENV{GNUPGHOME}); 28 | chmod 0700, $ENV{GNUPGHOME}; 29 | system("gpg -q --import $fixtures_dir/secret-key.asc"); 30 | 31 | my $sign_conf = "$tmp_dir/sign.conf"; 32 | $ENV{SIGN_CONF} = $sign_conf; 33 | spew("$sign_conf", "user: $user 34 | server: 127.0.0.1 35 | tmpdir: $var_dir 36 | allow: 127.0.0.1 37 | phrases: $tmp_dir/gnupg/phrases 38 | "); 39 | 40 | make_path("$tmp_dir/gnupg/phrases"); 41 | spew("$tmp_dir/gnupg/phrases/$user", ''); 42 | 43 | my $tmpdir = "$tmp_dir/tmp"; 44 | mkdir($tmpdir, 0700); 45 | 46 | my $sign = "./sign --test-sign ./signd"; 47 | my $result; 48 | 49 | ############################################################################### 50 | ### ping 51 | $result = `$sign -t`; 52 | is($?, 0, "Checking ping return code"); 53 | 54 | ############################################################################### 55 | ### pubkey 56 | $result = `$sign -p`; 57 | is($?, 0, "Checking cmd 'pubkey' return code"); 58 | my $expected = slurp("$fixtures_dir/public-key.asc"); 59 | my @got = split(/\n/, $result); 60 | splice(@got, 1, 1) if ($got[1] eq 'Version: GnuPG v2'); 61 | my $clean_got = join("\n", @got) . "\n"; 62 | is($clean_got, $expected, "Checking exported pubkey"); 63 | 64 | ############################################################################### 65 | ### keygen 66 | my $pubkey = `$sign -P $tmpdir/P -g "rsa\@2048" 800 "$comment" $prj_user`; 67 | is($?, 0, "Checking cmd 'keygen' return code"); 68 | spew("$tmpdir/p", $pubkey); 69 | system("gpg -q --import $tmpdir/p"); 70 | is($?, 0, "Checking pubkey import return code"); 71 | 72 | ############################################################################### 73 | ### keyextend 74 | my $pubkey2 = `$sign -P $tmpdir/P -x 1000 $tmpdir/p`; 75 | is($?, 0, "Checking cmd 'keyextend' return code"); 76 | spew("$tmpdir/p2", $pubkey2); 77 | system("gpg -q --import $tmpdir/p2"); 78 | is($?, 0, "Checking pubkey import return code"); 79 | 80 | ############################################################################### 81 | ### keyid 82 | $result = `$sign -k`; 83 | is($?, 0, "Checking keyid return code"); 84 | 85 | ############################################################################### 86 | ### privkeyid 87 | $result = `$sign -P $tmpdir/P -k`; 88 | is($?, 0, "Checking privkeyid return code"); 89 | 90 | ############################################################################### 91 | ### certgen 92 | my $cert = `$sign -P $tmpdir/P -C $tmpdir/p`; 93 | is($?, 0, "Checking cmd 'certgen' return code"); 94 | my $end_cert = qr/-----END CERTIFICATE-----$/; 95 | like($cert, $end_cert, "Checking for end of certificate"); 96 | spew("$tmpdir/c", $cert); 97 | $result = `openssl verify -check_ss_sig -CAfile $tmpdir/c $tmpdir/c`; 98 | is($?, 0, "Checking openssl verify return code"); 99 | $result = `openssl x509 -in $tmpdir/c -noout -pubkey`; 100 | is($?, 0, "Checking openssl x509 return code"); 101 | spew("$tmpdir/cp", $result); 102 | 103 | ############################################################################### 104 | ### detached sign 105 | spew("$tmpdir/sign", $payload); 106 | $result = `$sign -d $tmpdir/sign`; 107 | is($?, 0, "Checking detached sign return code"); 108 | my $verify_sign = `gpg --verify $tmpdir/sign.asc 2>&1`; 109 | like($verify_sign, qr/Good signature from/, "Checking detached sign signature"); 110 | unlink("$tmpdir/sign.asc"); 111 | 112 | ############################################################################### 113 | ### detached privsign 114 | spew("$tmpdir/privsign", $payload); 115 | $result = `$sign -P $tmpdir/P -d $tmpdir/privsign`; 116 | is($?, 0, "Checking detached privsign return code"); 117 | $result= `gpg --verify $tmpdir/privsign.asc 2>&1`; 118 | like($result, qr/Good signature from/, "Checking detached privsign"); 119 | unlink("$tmpdir/privsign.asc"); 120 | 121 | ############################################################################### 122 | ### detached raw sign 123 | spew("$tmpdir/sign", $payload); 124 | $result = `$sign -D $tmpdir/sign`; 125 | is($?, 0, "Checking detached raw sign return code"); 126 | $result = `gpg --verify $tmpdir/sign.sig 2>&1`; 127 | like($result, qr/Good signature from/, "Checking detached raw sign signature"); 128 | unlink("$tmpdir/sign.sig"); 129 | 130 | ############################################################################### 131 | ### detached privsign 132 | spew("$tmpdir/privsign", $payload); 133 | $result = `$sign -P $tmpdir/P -D $tmpdir/privsign`; 134 | is($?, 0, "Checking detached privsign return code"); 135 | $result = `gpg --verify $tmpdir/privsign.sig 2>&1`; 136 | like($result, qr/Good signature from/, "Checking detached privsign signature"); 137 | unlink("$tmpdir/privsign.sig"); 138 | 139 | ############################################################################### 140 | ### clear sign 141 | spew("$tmpdir/sign", $payload); 142 | $result = `$sign -c $tmpdir/sign`; 143 | is($?, 0, "Checking clear sign return code"); 144 | $result = `gpg --verify $tmpdir/sign 2>&1`; 145 | like($result, qr/Good signature from/, "Checking clear sign signature"); 146 | unlink("$tmpdir/sign"); 147 | 148 | ############################################################################### 149 | ### clear privsign 150 | spew("$tmpdir/privsign", $payload); 151 | $result = `$sign -P $tmpdir/P -c $tmpdir/privsign`; 152 | is($?, 0, "Checking clear privsign return code"); 153 | $result = `gpg --verify $tmpdir/privsign 2>&1`; 154 | like($result, qr/Good signature from/, "Checking clear privsign signature"); 155 | unlink("$tmpdir/privsign"); 156 | 157 | ############################################################################### 158 | ### openssl privsign 159 | spew("$tmpdir/privsign", $payload); 160 | $result = `$sign -P $tmpdir/P -h sha256 -O $tmpdir/privsign`; 161 | is($?, 0, "Checking openssl privsign return code"); 162 | $result = `openssl dgst -verify $tmpdir/cp -signature $tmpdir/privsign.sig $tmpdir/privsign`; 163 | like($result, qr/Verified OK/, "Checking openssl privsign signature"); 164 | 165 | ############################################################################### 166 | ### rpm sign 167 | $result = `rpm --dbpath $tmpdir --initdb`; 168 | $result = `rpm --dbpath $tmpdir --import $fixtures_dir/public-key.asc`; 169 | spew("$tmpdir/empty.rpm", slurp("$fixtures_dir/empty.rpm")); 170 | $result = `$sign -h sha256 -r $tmpdir/empty.rpm`; 171 | is($?, 0, "Checking rpm sign return code"); 172 | $result = `rpm --dbpath $tmpdir --checksig -v $tmpdir/empty.rpm`; 173 | like($result, qr/^\s*Header V3 RSA\/SHA256 Signature, key ID [0-9a-f]*: OK/m, "Checking rpm header signature"); 174 | like($result, qr/^\s*V3 RSA\/SHA256 Signature, key ID [0-9a-f]*: OK/m, "Checking rpm header+payload signature"); 175 | 176 | ############################################################################### 177 | ### rpm v4 sign 178 | $result = `rpm --dbpath $tmpdir --initdb`; 179 | $result = `rpm --dbpath $tmpdir --import $fixtures_dir/public-key.asc`; 180 | spew("$tmpdir/empty.rpm", slurp("$fixtures_dir/empty.rpm")); 181 | $result = `$sign -h sha256 -4 -r $tmpdir/empty.rpm`; 182 | is($?, 0, "Checking rpm v4 sign return code"); 183 | $result = `rpm --dbpath $tmpdir --checksig -v $tmpdir/empty.rpm`; 184 | like($result, qr/^\s*Header V4 RSA\/SHA256 Signature, key ID [0-9a-f]*: OK/m, "Checking rpm v4 header signature"); 185 | like($result, qr/^\s*V4 RSA\/SHA256 Signature, key ID [0-9a-f]*: OK/m, "Checking rpm v4 header+payload signature"); 186 | 187 | ############################################################################### 188 | ### rpm priv sign 189 | $result = `rpm --dbpath $tmpdir --import $tmpdir/p`; 190 | spew("$tmpdir/empty.rpm", slurp("$fixtures_dir/empty.rpm")); 191 | $result = `$sign -P $tmpdir/P -h sha256 -r $tmpdir/empty.rpm`; 192 | is($?, 0, "Checking rpm privsign return code"); 193 | $result = `rpm --dbpath $tmpdir --checksig -v $tmpdir/empty.rpm`; 194 | like($result, qr/^\s*Header V3 RSA\/SHA256 Signature, key ID [0-9a-f]*: OK/m, "Checking rpm header signature"); 195 | like($result, qr/^\s*V3 RSA\/SHA256 Signature, key ID [0-9a-f]*: OK/m, "Checking rpm header+payload signature"); 196 | 197 | ############################################################################### 198 | ### cleanup 199 | remove_tree($tmp_dir); 200 | exit 0; 201 | 202 | sub slurp { 203 | my ($fn) = @_; 204 | my $fh; 205 | open($fh, '<', $fn) || die "Could not open '$fn': $!\n"; 206 | local $/; 207 | my $content = <$fh>; 208 | close $fh; 209 | return $content; 210 | } 211 | 212 | sub spew { 213 | my ($fn, $content) = @_; 214 | my $fh; 215 | open($fh, '>', $fn) || die "Could not open '$fn': $!\n"; 216 | print $fh $content; 217 | close $fh; 218 | } 219 | 220 | -------------------------------------------------------------------------------- /t/fixtures/empty.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openSUSE/obs-sign/bb51d946e431219523f5af192795ccfa0729722a/t/fixtures/empty.rpm -------------------------------------------------------------------------------- /t/fixtures/public-key-expired.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQENBAAAACMBCACuXOhS1uRFosdT4HSLQZ8XbxE/X679QE2audsKRioqAAVVOLqz 4 | wr2aigJ80y74a+9ncZHPOBrNkbiMrbM1Z7MwUdvzO00zwxqBs8RjhFhOlZ/MzYYk 5 | JwOxeFetaf0QoVMK+78Ac2EfrZ7+tNZ+wtSG4jWXJ+UNoeVXuPjerKjT3lwTmjL0 6 | NmSEPWFAtynOFIklri/efN+bFDnbERfHR+r3pkK6SxYI5QxG35IAW0fTTv+8FHPF 7 | UlPgO/DJMrFc2a5KKrZoyY2gjV7QS/ksLIPTu/MXtLt6Sp1zq/7f0NHbrAZQYFoX 8 | yBrjwUw/QwsRE2+OyN6qwUWvmbnJABptSUTDABEBAAG0JFRlc3QgRXhwaXJlZCBL 9 | ZXlzIDx0ZXN0QGV4YW1wbGUuY29tPokBVAQTAQgAPhYhBAoRT0ipzvWOhSZ3kQ2G 10 | 0g18bolnBQIAAAAjAhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJ 11 | EA2G0g18bolnM+sIAJbNN36qPCQXeUNDvJr1NaCNDfO8y4aIQDJJU3Prh+HkO7Qu 12 | fTDBEoNyxux2O8noKgseaYsGA9g1YiLrkvolssA+rEDCYcUodLiAoYRWAaF3j75O 13 | GV9vwf+fs0ap2SIvkufexEO5vjWBBXuXNfN7RSgAJSZ8FGqBg97lO2kGW/awxbXf 14 | LuD4oIOwxIGjhsjZlWl3hDZzmJKxs/Z2hwZoa7C3YOmKAekUcajyyXfC6KQaljtC 15 | IWWQEU8XiMWz6895lTfeUCW46Cxx0Hz8v7mc2Y5PJGcDaR6DSz4DNcYAfQp9imyc 16 | /OB5MTutIk4Gx28Ds2iNYcQ/jz13Xt164Xe83FC5AQ0EAAAAIwEIAOScKGkdEPQX 17 | BF4Yk7PAgI6uzsttwShAHkZcG4tgx3dvauMty6+19Yk4xVtvUgumGMvSGjjNFNry 18 | 4d2DqTOe4a4C5CiORaLJGFHU1KRtYhsvhtH3YRsqQ+5S2U6WObZiZsyihg0pmyiW 19 | WPVbdKF8xFagYp/r3MtQgoHo7odYW6lkI+FzfgCCmg4Xp/f1XDn98Ov4Kl7ymIVu 20 | hrUbQNlP9HowEOVDVg5LCJZusqae3WdX2NTmy7+EO8rt75Lg0bt6VYdutr1msKtf 21 | TA2e6TArgv6KKzt5WIzaQppseyINHFvzA5vXRjHevCKf3E+J6mho1LR5Qy58ayT1 22 | y6T/eNhKQKEAEQEAAYkBPAQYAQgAJhYhBAoRT0ipzvWOhSZ3kQ2G0g18bolnBQIA 23 | AAAjAhsMBQkDwmcAAAoJEA2G0g18bolnG/YH/2W5M1cWS/7C92fWO9jpReO3xnbN 24 | 0sm05K5KqznvjIf3tbSGwrd0xvQW1E8rWzJhIeqWhz3156G5mAltRleWZt6U+Vn7 25 | GufbU4gbbsL6ylKCzSDLnSuF510akbiee5Ml4XTlmxaiODG3EwFDBYXUP0a/fUsP 26 | fgxrAfJfb6trlhE8RYj5sv0yrvcX8heba/YUFXny78XFIhCuJOLF1K9B/v65XGjs 27 | +AtBCvwmLtKaS6phhkjh38bVuY/pYynsbMDrR+DzMtIdll26dom8gGLkMjBe6CTt 28 | 6tFvm7cVGZnHuql8C5Ym3q2wyZaN4PG/DJQZK9nBOv7ioU5TC1E5wwjDvhs= 29 | =qMfN 30 | -----END PGP PUBLIC KEY BLOCK----- 31 | -------------------------------------------------------------------------------- /t/fixtures/public-key-passphrase.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQENBF0xsNQBCADKtjUvkCcedhfc4AIWmBGZTVtEUHrxog9so2SZGmv9r1n/xpcY 4 | ZNLns9b/cXGBwHi0lJw5QrzxclajjkGT4pOYrE8cRHdnbg5I2zYdWs2pV8tOcMQq 5 | Zx2RCqmw6/VHZ/sADgLTvM7GLYEq2IGNYNV+wHNlL/LmzMZ3YPXmJaMT+QaJYgQL 6 | zwG6P5yDZ4qz490T1fDjNhG0LDILlj6MGL1fFmIJIQppcqwoulRgo75DVmQgtZsA 7 | 6Wzuuse67TaIxHcf4SOjusV/pa3Zqap+X5g8/pqrWWElwoCRU3dF+vToXKFDI68c 8 | pyOWwSMz7ATFDl788fU0Z0kdUyjfJm1ofl07ABEBAAG0Mk9CUyAoa2V5IHdpdGgg 9 | cGFzc3BocmFzZSkgPGRlZmF1bHRrZXlzZWNAbG9jYWxvYnM+iQFOBBMBCAA4FiEE 10 | d7Y8OGldRL0G4zgbD2rKlql2snAFAl0xsNQCGwMFCwkIBwIGFQoJCAsCBBYCAwEC 11 | HgECF4AACgkQD2rKlql2snAeDwf/bakWOtDwpm4GLEeUBpUTbpFP++vlQwy9vLNT 12 | hpJf3MDjwpVlqH3qi9mjRUTTulrS79Mxu/7yFNyP7v8ja8VLiXkJGcZjjOFQz56D 13 | M7nqj/F7LTaXSgRthUEzCmMuf1AS0zNx4egxjh4btG4OpND2XTXvtVts4REIGsrB 14 | Eskx2KD0j6c+x16mEMGKXYY1aN9EuU7Kk1lMMvRg1SFDs4ULY3kJymahW8OpVLz0 15 | Qyxrdd5YEZbljQ131pGASvarp7NAkQJ/XBmwamuaR5+oPJ79Jqr8qXFlbUB7hKIi 16 | TfQ7jEnPzVInakXhIvfp7PbTsGoX2N1N9QntslSmYr49OycRjA== 17 | =pX85 18 | -----END PGP PUBLIC KEY BLOCK----- 19 | -------------------------------------------------------------------------------- /t/fixtures/public-key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQENBFu69OoBCAC56ghE41rcnBweNXeAWjwznaglBe3jTKdJiD9iJLQhRvvGY9OC 4 | mPw2JnLIzqhITFBhF0YD1w0CMXphfTXtPJRuoC65rDQSB5zCBZjAYg8m9+wzMHuU 5 | HQ7vIcjFqB444NoV8FBY6rRlGEZ+MaM4y4eidT62VKBtKMwvEWXFNLXNM5oZVmv7 6 | ResbgLUyhNSTQVZyP2kyudPaZDJ4NASnGd9SA9IudiOVLU0HemwJgKf2DmKYLMBe 7 | b70wfxUaM42I4qmqDJCK5hCZng2wY1XBgaWnF8+OeHLc+WLYFQNalFT8ctMHmuDG 8 | 5Z5L6jxXyW1DUBtM4tF7X77KXZiXEAwf8CN/ABEBAAG0OnByaXZhdGUgT0JTIChr 9 | ZXkgd2l0aG91dCBwYXNzcGhyYXNlKSA8ZGVmYXVsdGtleUBsb2NhbG9icz6JATkE 10 | EwECACMFAlu69OoCGy8HCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAAKCRB1gfIr 11 | 5AqaL0hVB/0VXLIX69bGs9xXK13gIp8y0DmugJfj9KnIEdhhXpjkkKbGIriaG912 12 | bSe6b7vVexnM9x2H1I6HdazMFtQLtYB3gw7cKVo+zjHpL+2KbkieFf0+xfM3uMG4 13 | C8flAhEuFtAw3DO1qrmH2PoR39QTutUhG0vNyxU/mUudabm3C7w9hzRSkT8y02GF 14 | lXFO7ecTDFfZyiNWa0vHfmq8SEmnlPzl2JRpRy0LO3XoVq9KFOffAWXDf3O8TGME 15 | uaFpK899yfw7tg1jiv9PHXtDmZ2Sh2SrcaYUymPFFvlAqBW3yvyx1vxu/Omair0L 16 | SMcaFYDkQDJT0s2wUckMRMdCUVLxaaT+uQINBFu69OoQCACh+9dS6eYWKRUjga+W 17 | NBwSDxpinvQ4/EO/0nlwbx+fEcjIY9lcdzTP0Bwq4XUXnpK0TAjzA11AmFldlxJx 18 | 9bBtosZjZKH748mOw1+CpbLFhcbkQmgzh5pav1JBt5bSMwXNi/MXMMi+9i+0G7DV 19 | Mc4Cn1a8w4SX8twR64dT24AbM7FLNOra7FqwcGZs65dXu19ECuRrqxRbg9rA27Pq 20 | odfw9St2b0Ro69ppHdUVkywzcuaiikBUBiJ9P9a89TToPBRJ0Ec8iPB1aPPlo8Id 21 | oR8fMMFb+4SSE+I7PhEkxo3ZqoQ3g5W30i0QPICQb8LIXccTOLiqnjurkp5E4Y35 22 | s9MfAAMHCACAfKyeri3DyapJfv0SqVfgfv808SE6VfOj9gB4vOu6KcV7/xXhkAg5 23 | zLZaaxGYGd27pTQwPFVmfUaxEEnjBmRRYWYLuTahgLGF8d1QPhqDvR4KwXfSkfhz 24 | JtQhXFXbIZO9VEfedlaWfoAF1WnLbekrbksqANx/a/+0HiprEqdQAg/krrGsjySZ 25 | eEJdjrWk9LO+2V8RkFD04LM+Z19EYkYQ+40+JMmfHSv+oVhKoB6fKcaxhvC2FH61 26 | cb5ptducE3O0NtWu+U7kl2fdSAwFg0rZsHBHVybx7ibxNgLhoNDvvkWjRcNsSLPW 27 | ZDNr8MaUw2D6+ajybyTCrgeKFiVsfrNXiQEfBBgBAgAJBQJbuvTqAhsMAAoJEHWB 28 | 8ivkCpov+vcIAJnxuuBgyUI9yRnuGl8gB+fFU42VSmOgYU8i8+ZEblgYylrE7UvP 29 | AB+4Zib4tB4Ug+9MKGKDOXpQRoVvDAvwSiA378kB3iGGZAI1n2KzUapStTFm0mel 30 | u/kN6M76OtIzjsBCZQCufx+QsGH5OQQ5o3ggzo39edqebRQyTDuArqG2VDrodFbX 31 | JpYBW/4aNFlGVfum5F2mUE8UpKNNid/rcnUel9c5L22mIutvL+5IkkZxNix86guN 32 | p7ito2MHkjMKceXfQhzgJ5Dk1x2Ep3JYuFjMl+rqjgeeUHjxwEm7ah5QRmAOjEDq 33 | foSe9DQQ9z21ko8EDRlnZsOtZlsRR1DNSEU= 34 | =7/lu 35 | -----END PGP PUBLIC KEY BLOCK----- 36 | -------------------------------------------------------------------------------- /t/fixtures/secret-key-expired-encrypted: -------------------------------------------------------------------------------- 1 | 85020e0384a9757a0207a14b1007ff44feac890e42cc1699fde7c37073831e87a409b7c75bf74f20bc9cea3a86194f9bcf34473adf5d5022c35787a7633e0cd4b8c104636c17728a273cd64004f549b2164fdb569275200baa9328bb25781a955d4b9cedc3195c64a5958a58881d031d212801630c694d4c6665c57fe6085949aeb69552fdc92a8b5e9eeae03eec833ffb0fc91afce812527b6b426bd536624fc9c4196afdf4e67937397f6bdd3022571ad3a1749933efd84a31d3a1578fd9e28ae5fe1c6b8b7fea68e2975211f32ee7783ce1e0916d5f1ea1d70f64fbb96e789b43b3cc5f248da2299ddf9a8766a646958fb91dab508c7b2b82881d04ded3205c4359c65d65dbe0695f841e82956b0800804d19987999b85de016f0a4f13590bd8da0ca4846f287e8dcf6fc1be10a54d37cc000b616467052b26d17357562f38da42528807dffac56c9b7866bc071e5407cfa8a2e3bb7f1c83acb7875fe17e2df03cfa26e6e7afa6f92bed2d23366997a489d7a7e5c64a8b96bc6b1637e3dc7c77eaf673fc8200ee0b933bfb92303283c63d1016e42fae49b0f051edb1d77096ab9e474120fde67ad2e849a322b44e94615180f8f3c08ea1697eb9f67f5e5152e333250bf4904afe0640fe56e863130b4553e0e6191f2c26cc65ab55e8a5e9ac16f0dc5bbfa60dadd43ce0ca3880c6d99c2edcc99e9a04a70235c026a889a1248c008f2e42d77787adc627802fed8bc29d2eb01869cc3ee119ca13c77987b308df9f58814d2dcf4d200de21a5da919f018a05330ebef1ba7b35d964b35e872f7eeaecfa858c410f3674cbc37bf592102b19aa22fefcbe238b66da7bfe65bd145c5b288814f18d83c9b973726a89301543ded73924120f63ee046e2f6c6a4dfbbcdf3a488866af5f3676600af61add712ba5255c2bacce6ff42dcf38a287bf598d5877e1bc70e0e4091525626d13c00ce35d91116dcc4b633fbeeaea55a55f6b49f4d035871d7be8bb9c6ad4cbd648f43bd2812945fe08f2da27a78d0660b69a0139fa8d05de37ce37cefd9ef574cf49fc7a8bf9c4d45af58c6a0a4c532fb4761d1b16238405858c06385606b8dbe62a8e9175d92095fca624a2e2c9ae159042865446856c0270c700f2b2ef931e8af751c0ec4c13f875e94d4eb113cd048c9f9748536901a939343542fc4d63ae2170aaa21861ed168352a022fef81d93a4d7069d134239cfd78d33339504a772c44310d7fab3fa991af6c034bf6b0d3362131fedd338dc32449bb9ed5b5f84fbea7b8ea7d4d45257b83835d97977c2bf90c64d852794eaa793d0ed886e0bcd483184fe7094b550f5b295b427449dfa3c56e86913edfa4bdc1e1e41b357ceaf626979ecf7942c0acd2ead69e96caf563303d3f14b5e2ac0ec5379c0fc5974ea3fda1792a5d9020ac8d790acaec28bc1062064c0fcde6df8d4f1ca8a9f933742596ab152a8651bece1c9f8d4a839f1c44f4f1cca379fbbf78c9214f788e161f63682089352e9cacde851cbe55e2b064bda5e14fe69fb482369e85982e1d1eb862f1ffc1a51f14ae25b721a4466edad6db63b39f301bd23c5c9b47f12302b749ebfce0ee72518dab4ef9ed68afe2a186e1ac221147219469390f0b3e37c19ca38724f636e797abdbe1e811f8eb46c6ae65c1e7dc4f0a7a409c36ecf8a042c4a74f1d9fdf62964524cc1542c7fdb726c8ed319106f874d6cd5029453e025c1ce2ff332884080d84cc87a07aadd7a17097f763fdaede00338f873bf4d8d0a97a50622c02fa5dc30ab964a67ea578a87d51a2170611a3a6a0694e56898767089ebdef1f1613e3647bc7b15734857455c5efeecfb68f5af53e323ae8aaaec2b0847267d58f04c3b9f897944ffbc457dcaa3e73fc313a790f788c4f2b5db84113e600981c0db6ead15c97ba72aabb7fb9a38e639875bc80fdaad3dc86618277a515de47d4ae0449e8831c30538da29ac8d88773c93561ab61237d543e7ce5d362e16f1fcc3e1f9ba3b166b7790a303003b333efcfd879009a77ddb66a62f0c15c01e62ba7773e170ee9109b5e2e03f9a02443dc2d68f19977696e570528d5f5269d75e88f320bf37d3d2394e4b2b4a0b2b0eeff36d64249cfba90393cea12ba05b35c1f090ffcf1977b0b407eec1fc03715d99233c28290764cdfca494251c5aae5a382e2775a0b3595bfc7f39cb7f1557dcbf80d415a219598fdf10f34f253f60cdf19a664ebaecd3559df0c0622a64f8b7d7632187e25f939fe59e52175e7d20621e175d394b9102722c1d0417d748e24d8fe28f0fbb7beb46a4caad613a4147e9df5b61384a65ea03e4be04f2ef6bdc521d7339875a6ec2f23b3582ded8ede1835f04f24f96c9fd95c4582aba59111ad4a83cfc3d362f4d6cc719a2259fa040b14ab7e4953e002d4c6b122b2b45b2241b20911750f11438f25dafaa8d5a7e17ba910adc4ed4be9cc955c31735e361a4ffb5246fe50860954b4aa2219c667fd2147b0bda1164a34a26c21475aca5a525c6e2d3dd8728a06b8e336acd241c4c081d225a77001e3f3b5d006a1c8d574689e4d2f1799091c8531c6b4cb85c3a6de1b831f94521e03aeff2060f9ee995cb292bac535649e5a8e60c4e8c29d73abaaf9ef58d9c7b92345fa607efc5679ce44ce7dc209f3a4774282201f6e92fbf9384534b24ed2c8fd910a5baaa60ce4d4791994c9245169c11e0f6df3e448350a3d7b45b6b5dc42c4b197fcd668086b7805284a8dedb35a7081fd861163c2d9106c3ba63364e25073e544d376ea3e390bbf8295494706f8ce3af829f9af672bbe87ff1941982e45ca6176fb66ea3baeda315e8f61d9a088ea91348b68b9e7668df86b65a6e18425c83f8fea76f3f354096831614fe3b07b9636d96463435842ac34a2e41a2f703502e75fae92e68959ef782dfc0fc54e4c08cd6bca9f747bd1c683909fa9cbbe6ce49f201e33cadc3acd189dc54d2d5d5277095445ada367c8f9d1e520b7e8e7d167313f5ae211e8ebd5ee64e70b92c6bdaf2003271b36671b88e142084f70739b307107a260542d1750a39a7518444f50e972e438c305117d1f70470fe761480434110675571e47af9c976b264e30a51504d9e32a3e0b64580a7104eea6feba202d3364c00b86b773c7f3cdf40dcd01c5eb5fe66353b04456e57e4c63ffc857293a2bbc2fd14e0fe2726d12fc1cd19625f336112b1a84fe0cb2d8dc8ff2d62fb9a1922f1f4d4dac4413557ce305ca9dc6e92ca06995f68c23e105696ad916ccc454d916281664ecc8273d9a8a4c2a64010ab61ad3743e7c57a57b991ea1330745c6d54abbcfffb3fdeaf811d66858392d924fc12991e3535b6b07ecf5eea2296a7ac2593707145db108b6d169dcfd3133dc079d0c39691bb7884753bc10b803e049d3a1bc820aff6d019b04265ac31232660af4d2cdc78993121f5498021fc0bed37f9ca1154edd43488334bba641e73542ccec2673c90f436a2d767e4638b7be9349f8d99bdf2117d976b0c1839dd3e36a543d49b4bae33b3e4b2565f85a72e1c5873dac2722bb4b4bd1c7214e4edb8981147ad94320b9dfb992ac89a6613479e8532624bb584ba4e4919c146a4893e2be1ef00a8c2dfb024fa65e672f30a1bd0bee2e9d9ec4608ade91b7e2154f0463f0267fc6e81b6a726c7c0364bd0d1aba376812d62ed3a88ebbe63dcd759829e6fe13a132ee104a9f71286e7e75440662251ff6573e9ee774620a03ecef4822460aff68789532e3043e35e19eef5bcac6d4b129dfc4a638c404fbb90faa56345546c8b01435a150f44a32ccff667e804a7367aad43293e7b39c2a23d00e7334d4928f84d651fac397756fe9ed9c077b6f8fe5b9b51fbbeb337bd2a97a5c98ae7fec39a09401e84e44ae61b1ab46173db0f2bf432fd4dfcbe4a4b5a924d7879675b6ffb9e0138ec4266f8a1088d75bb7431b306a1fc44158c9c81701a443bac6cec16ef7a4de8cd96acce163ae8f070091afb72fb5f98d1f64e5fdf7115a728677cc65d35d75e76c62e408154b7a28030052c7ff31d96b3f387b1c445ae16eec90ac8170b33cfdbe890301f15b71d436bd1ab52a6a1cb96b665a142d3ba8b30eb542038b9f94ae3657d499b865e79923b44ff851b57ed2b58cb5c2bb89fb6675b6917fd9976469347185d790c68cc05ef31b559f3e21b4189351d2c554280e50d67877ace59b0a2254d9d3a8452be9792ca44f6c1ffa6f6412af480828b08db27c6ae88769dfccc5d33d9e24c11ee07de32e2e92150a1d5f1e5d6775cb71550bfa0b7e78597db69db34a3acb85e26ff6c5572ccb771491c76fcfca543b5ca767e6920bd95140bc04a043a34a29b1e532efce797474190bd976eeb1cbb083cb320d3f3f9a08971f52b82bff25fc1dacc66f8259ebc93b8754a1bf6081bedef175c51e41a1b0d82015118f388fd0438aa232c66e8d8dc6e 2 | -------------------------------------------------------------------------------- /t/fixtures/secret-key-expired.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | lQOYBAAAACMBCACuXOhS1uRFosdT4HSLQZ8XbxE/X679QE2audsKRioqAAVVOLqz 4 | wr2aigJ80y74a+9ncZHPOBrNkbiMrbM1Z7MwUdvzO00zwxqBs8RjhFhOlZ/MzYYk 5 | JwOxeFetaf0QoVMK+78Ac2EfrZ7+tNZ+wtSG4jWXJ+UNoeVXuPjerKjT3lwTmjL0 6 | NmSEPWFAtynOFIklri/efN+bFDnbERfHR+r3pkK6SxYI5QxG35IAW0fTTv+8FHPF 7 | UlPgO/DJMrFc2a5KKrZoyY2gjV7QS/ksLIPTu/MXtLt6Sp1zq/7f0NHbrAZQYFoX 8 | yBrjwUw/QwsRE2+OyN6qwUWvmbnJABptSUTDABEBAAEAB/oCfLw9hkaYaRxh4Ba0 9 | xSsu+XLdxv2DisNDmU6iWd3Xjd5pKWDy8iMyYBltsE0AIoc2Rgwcs+7xDtZ63cYM 10 | 8eItK3uxRqR8yY7UJb+l0euTJ7L9LeYs+zekI+BsdIEZCWRj2Tgw5iPa7c9ZBHcd 11 | Ieh3gHoFtVz2Tki+s2/Xw/6ly6GJICfuDTstNDKVPIQHeHijMU4svLb3g2goqXR7 12 | sqI9whDThD3TE5dHPYLMJw/9erdXIdvLSqn8jJtBdZ8OpelwX7UnqpdC5ijt/ihR 13 | wJEiQ0FVjQOOptF8y32fT/VmsJ7Y2KXJFJB+tMkrhNrFH6IsS9Oqlqne5yobgAR6 14 | CMUhBADFIFXWx+mPLgn8mikb+ChrcudPNIF5rQNKjzjAeK7sbj0J4ikFwCZAPQSw 15 | HK4SdEZv2D3zN+uq6xcoXXalVaX40cHH3gDvMkg7Px5a7pb0f1C2QT2N9O60iq+L 16 | sssRUgDFvQ+fLj6MF60qW7Y0leONUhuqz3YQ082IQzdXgpnmYwQA4nAneb8BkAij 17 | eGE1BUEQBlJd/+zfpPigzt0O0dys+wrCcExy2lZ8TM1hShbkQX1Y5U6dTlqTWHz1 18 | 5iTvsh3ELbJkbQmeVMmDnXaARsE6fDkjBSkRfVbJENvmFDgi4eURbE1AKFAQSgvc 19 | bPpmAtVMzwg08tqrxQ4P4OTgjk5WxiED/0dZHsHXIVxTlujwhIldfswI7YwY1akD 20 | 1LgljmyvN+dwMchI1j2kZJziJrsjAyp8rzXSOMWN0YN86F89LridXsLhUWWtaG8p 21 | Gpg5mg3boRFCIAvIb4JB0j7yFypnelYmZn0+HRKNmiAPatNntHQBi3AkYwsE1ORk 22 | 6XpvgUqmEU0CNRm0JFRlc3QgRXhwaXJlZCBLZXlzIDx0ZXN0QGV4YW1wbGUuY29t 23 | PokBVAQTAQgAPhYhBAoRT0ipzvWOhSZ3kQ2G0g18bolnBQIAAAAjAhsDBQkDwmcA 24 | BQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEA2G0g18bolnM+sIAJbNN36qPCQX 25 | eUNDvJr1NaCNDfO8y4aIQDJJU3Prh+HkO7QufTDBEoNyxux2O8noKgseaYsGA9g1 26 | YiLrkvolssA+rEDCYcUodLiAoYRWAaF3j75OGV9vwf+fs0ap2SIvkufexEO5vjWB 27 | BXuXNfN7RSgAJSZ8FGqBg97lO2kGW/awxbXfLuD4oIOwxIGjhsjZlWl3hDZzmJKx 28 | s/Z2hwZoa7C3YOmKAekUcajyyXfC6KQaljtCIWWQEU8XiMWz6895lTfeUCW46Cxx 29 | 0Hz8v7mc2Y5PJGcDaR6DSz4DNcYAfQp9imyc/OB5MTutIk4Gx28Ds2iNYcQ/jz13 30 | Xt164Xe83FCdA5gEAAAAIwEIAOScKGkdEPQXBF4Yk7PAgI6uzsttwShAHkZcG4tg 31 | x3dvauMty6+19Yk4xVtvUgumGMvSGjjNFNry4d2DqTOe4a4C5CiORaLJGFHU1KRt 32 | YhsvhtH3YRsqQ+5S2U6WObZiZsyihg0pmyiWWPVbdKF8xFagYp/r3MtQgoHo7odY 33 | W6lkI+FzfgCCmg4Xp/f1XDn98Ov4Kl7ymIVuhrUbQNlP9HowEOVDVg5LCJZusqae 34 | 3WdX2NTmy7+EO8rt75Lg0bt6VYdutr1msKtfTA2e6TArgv6KKzt5WIzaQppseyIN 35 | HFvzA5vXRjHevCKf3E+J6mho1LR5Qy58ayT1y6T/eNhKQKEAEQEAAQAH+wZGKV9C 36 | JJUYXR6yfLpAEB8qWcNQQK74vIP4kupzNmI3IPc5amfI/gM9N0BkSJuIfFF6s4zH 37 | xxjftdZxHreLlpzCmE2rIurd60CSr1/QXzMJDcGNMSFwWmMr66oaGh0g6/qVgsmR 38 | cOBPKQOZ1hR5Ar/wYKiMLff3/o7vbD1zbbeTOlf8SUd3Ndbhl5ahuYTpAGblP+0k 39 | NhjTa2J254I6EkhT6VxVSEUOSaEk31TllrJYln+Q3N527iye4eV7vphJt8H7rDvF 40 | XUdxbRzUKeR4DTzDqzHrLBTZABlrP1SXrbv/9zEg7r5tW5AwMvqEKfIuhkD6F+Bb 41 | LKCXod6Vu5iZDgEEAPFLBowdkhsxJyXC5dGUz/kzhmrmEy6+NH4+2xEfs8Q3WBsU 42 | Slld+LK8unvj7/HwOeoZKiPOiD3A8HU6DhurZWKPj/J85GxjwplQfP7OR4xxfduT 43 | 8A3ZewQVYTCrTX+URuZwgnd1dP2PbLeK5n5haC6w84fsVuPQfdbE8m30+Z8hBADy 44 | izvtdMbbHbW2ZKA3oRo13NLqBt0jNl31np0h4en4lqYeGMIalWCYb3iv/PYbh3HJ 45 | ol5RUmuvQGfnSOpTtR+ZgJ/g/jjaN5syH9FFeR285cTrkMhBZJkXbP6ESnJZ8q5h 46 | uUnm3P4N/Td/HhvSUGn2+2nbAWD40ZgKMaU40FPxgQQAxrAEVMDNqBucIr+Ermmi 47 | dDJO/hAiLyV/kA+Mze5YTfZf0nurvI3xUMFraAsEiaELmsgVEwfOmES/C1niZQXh 48 | HXjHJSa/nQn5My4thgWOc+7aqEUFnrwkB9AUdm7KqR9AeLpr7u+IZOrgFpi3AnRE 49 | BjJ9aBjQb0d+cNiJLJSMOz1Bn4kBPAQYAQgAJhYhBAoRT0ipzvWOhSZ3kQ2G0g18 50 | bolnBQIAAAAjAhsMBQkDwmcAAAoJEA2G0g18bolnG/YH/2W5M1cWS/7C92fWO9jp 51 | ReO3xnbN0sm05K5KqznvjIf3tbSGwrd0xvQW1E8rWzJhIeqWhz3156G5mAltRleW 52 | Zt6U+Vn7GufbU4gbbsL6ylKCzSDLnSuF510akbiee5Ml4XTlmxaiODG3EwFDBYXU 53 | P0a/fUsPfgxrAfJfb6trlhE8RYj5sv0yrvcX8heba/YUFXny78XFIhCuJOLF1K9B 54 | /v65XGjs+AtBCvwmLtKaS6phhkjh38bVuY/pYynsbMDrR+DzMtIdll26dom8gGLk 55 | MjBe6CTt6tFvm7cVGZnHuql8C5Ym3q2wyZaN4PG/DJQZK9nBOv7ioU5TC1E5wwjD 56 | vhs= 57 | =ucfn 58 | -----END PGP PRIVATE KEY BLOCK----- 59 | 60 | -------------------------------------------------------------------------------- /t/fixtures/secret-key-passphrase.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | lQPGBF0xsNQBCADKtjUvkCcedhfc4AIWmBGZTVtEUHrxog9so2SZGmv9r1n/xpcY 4 | ZNLns9b/cXGBwHi0lJw5QrzxclajjkGT4pOYrE8cRHdnbg5I2zYdWs2pV8tOcMQq 5 | Zx2RCqmw6/VHZ/sADgLTvM7GLYEq2IGNYNV+wHNlL/LmzMZ3YPXmJaMT+QaJYgQL 6 | zwG6P5yDZ4qz490T1fDjNhG0LDILlj6MGL1fFmIJIQppcqwoulRgo75DVmQgtZsA 7 | 6Wzuuse67TaIxHcf4SOjusV/pa3Zqap+X5g8/pqrWWElwoCRU3dF+vToXKFDI68c 8 | pyOWwSMz7ATFDl788fU0Z0kdUyjfJm1ofl07ABEBAAH+BwMCfBpiSOUoFl3pbcw2 9 | m5JS1D3EvRo8VRPCxIwp0zB/Kkr1vpII10cHQZPTxmQepl6oGbxJ/yL2/2Lg3/z9 10 | XUKH1ayn0V8r+RxKv8xYdiEvktJFiUK9AmA9MQLKYCIBwLVcRI6eN1KuyuX94Hpz 11 | eXzmoAsJxSieMkZu9tGvKcplaKOB0y79br9kzmB4PBiLaJVvkc0B31l1glFBIezY 12 | o5cu36ALusNNS6nq49EDF3Eb/jXRt9bTZgRpmTcyj4eTPTwnluqvFSb9sJcs/aOL 13 | y+Nk3QC8EREoZ5bHRVc5+GU3w0v61isq4cMr/hu7DwYU5CtNgbw8cuz92v3XBlmT 14 | ENDgV3MkOyD33j2WZYceMOJFhr17cjrU0j2swM9POKsF3GkDoUS+Keg3ko5pQdGu 15 | ET5FZukX50zc3c/FwdYBAOX+fVOTb3tlkZNZ502zOULsGcMrgz4TBG/61nvaXmaQ 16 | ARHFn1jqMiNuonc9PeeWTD0UetKc19tZ17oYa89yThdq/7cM3MAivpAWdJTDyz0m 17 | S/tje9UO5VXeo+A25hCBqJWbyr6ogB045JnvidzjEUlfdIp3AN6SG1kXYXSdlFOR 18 | a4F6tVlw24fwnt6kYsFPoCw+XK2/BzB/UU5H2v6uRqmVkhYr3Ke3jm/7eNFHCYRF 19 | 6CYYAKO4JW1OuDS30x+JfVoaB8lV4hhpRlmU6Y4WTrR1lvy1Jhwqw3ECNM20Y8c2 20 | 3HJNMF1kXQQMx0v51s/dhi+8S/hzdMBOIn1dgaOHom4e+8T6gfwm2V9Oses1xfdh 21 | fhFLP1ctCWGJHgmpdhwHZ6g4OgjVTSerbtqdfaddO0qpnpPu2MiBJ9McSwov2j6Q 22 | e23h8Wr0w32brXsgcxI1zD8gxnjbHYePohPx05Wykt7L7Zs+hb2vGBv1mLs1KVe+ 23 | k9v+TKuWHekStDJPQlMgKGtleSB3aXRoIHBhc3NwaHJhc2UpIDxkZWZhdWx0a2V5 24 | c2VjQGxvY2Fsb2JzPokBTgQTAQgAOBYhBHe2PDhpXUS9BuM4Gw9qypapdrJwBQJd 25 | MbDUAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEA9qypapdrJwHg8H/22p 26 | FjrQ8KZuBixHlAaVE26RT/vr5UMMvbyzU4aSX9zA48KVZah96ovZo0VE07pa0u/T 27 | Mbv+8hTcj+7/I2vFS4l5CRnGY4zhUM+egzO56o/xey02l0oEbYVBMwpjLn9QEtMz 28 | ceHoMY4eG7RuDqTQ9l0177VbbOERCBrKwRLJMdig9I+nPsdephDBil2GNWjfRLlO 29 | ypNZTDL0YNUhQ7OFC2N5CcpmoVvDqVS89EMsa3XeWBGW5Y0Nd9aRgEr2q6ezQJEC 30 | f1wZsGprmkefqDye/Saq/KlxZW1Ae4SiIk30O4xJz81SJ2pF4SL36ez207BqF9jd 31 | TfUJ7bJUpmK+PTsnEYw= 32 | =6cd1 33 | -----END PGP PRIVATE KEY BLOCK----- 34 | -------------------------------------------------------------------------------- /t/fixtures/secret-key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | Version: GnuPG v2 3 | 4 | lQOYBFu69OoBCAC56ghE41rcnBweNXeAWjwznaglBe3jTKdJiD9iJLQhRvvGY9OC 5 | mPw2JnLIzqhITFBhF0YD1w0CMXphfTXtPJRuoC65rDQSB5zCBZjAYg8m9+wzMHuU 6 | HQ7vIcjFqB444NoV8FBY6rRlGEZ+MaM4y4eidT62VKBtKMwvEWXFNLXNM5oZVmv7 7 | ResbgLUyhNSTQVZyP2kyudPaZDJ4NASnGd9SA9IudiOVLU0HemwJgKf2DmKYLMBe 8 | b70wfxUaM42I4qmqDJCK5hCZng2wY1XBgaWnF8+OeHLc+WLYFQNalFT8ctMHmuDG 9 | 5Z5L6jxXyW1DUBtM4tF7X77KXZiXEAwf8CN/ABEBAAEAB/wL/Y9yQWK3wCxU798h 10 | /LkEi3248Gt1P2JwrNsbX68Qa804y97+a1vuBLWDMmvBWxfMU0InrfY2m1tRwis4 11 | /oyWxIKziVjDz35N7pid9zmmPK7Bi0hy0ok998B7rCV2Ru9ebG4n46dKrrrpK6lj 12 | xmr3rAAAypQKfSIv1hbpleqiW9CNawJEQwfwNU6tPVTsv9hJ6woYIzQmsA8LVsMC 13 | iFDHadE7rUxG3jX6+ot5ayJkCU5d05xqXMD9vVSH38RhEactxcUhhNtQ4K9Pacup 14 | illWI4XDZ7bRw01nmNOb1uzJ5sxCIU9ZlUejq7Kc9YbcuEzQ/lXZCQbGfEYrDvYH 15 | mafxBADTkl982fZciWSMoiZTwSKy/6szqmoEO6P0EKHuwWXhG23eyWrhHqonDJgH 16 | 9g4/eIRa3SLTfsWqaLo3m0uU/fDKVWcFuzyvQO+NlVUtZkfBCDEjfLPSgAQVHtKI 17 | M/XFPcWeZxuipOMkQMGvulIr9l8qhfN7nQEwNVufnHKNhNkWkQQA4PRdawgxQeWY 18 | GvbJM7yg2nKC8DicPsqeTvDk+aAXyYCkxIIa7WsF1VDr2uVbsKuoMZUZi5sqst62 19 | duJk1qp7AmTwDZT1+eKiR9esZSCIVoQa0jjHQY+syp8V3+fPbjtj1CUonffIbHH2 20 | QgS34IFZ8DfySFF8ud1Ow/NTk7EtQQ8D/21iKzoT8x8debOHJa4Ay2RFkgoXCrKr 21 | roqtnXJHlG1n/vu+iZ6ww6ORp/EY7Xndh9So/6gYuv0ttJKBczNJOve1fIhnpQFf 22 | o3vMF72bhnILLg+AjS3xPGFWxDEV/JWVjR193nyTL+K1406VnI8YCumxCRczYyi3 23 | UWlshHP8nkGOSrS0OnByaXZhdGUgT0JTIChrZXkgd2l0aG91dCBwYXNzcGhyYXNl 24 | KSA8ZGVmYXVsdGtleUBsb2NhbG9icz6JATkEEwECACMFAlu69OoCGy8HCwkIBwMC 25 | AQYVCAIJCgsEFgIDAQIeAQIXgAAKCRB1gfIr5AqaL0hVB/0VXLIX69bGs9xXK13g 26 | Ip8y0DmugJfj9KnIEdhhXpjkkKbGIriaG912bSe6b7vVexnM9x2H1I6HdazMFtQL 27 | tYB3gw7cKVo+zjHpL+2KbkieFf0+xfM3uMG4C8flAhEuFtAw3DO1qrmH2PoR39QT 28 | utUhG0vNyxU/mUudabm3C7w9hzRSkT8y02GFlXFO7ecTDFfZyiNWa0vHfmq8SEmn 29 | lPzl2JRpRy0LO3XoVq9KFOffAWXDf3O8TGMEuaFpK899yfw7tg1jiv9PHXtDmZ2S 30 | h2SrcaYUymPFFvlAqBW3yvyx1vxu/Omair0LSMcaFYDkQDJT0s2wUckMRMdCUVLx 31 | aaT+nQI9BFu69OoQCACh+9dS6eYWKRUjga+WNBwSDxpinvQ4/EO/0nlwbx+fEcjI 32 | Y9lcdzTP0Bwq4XUXnpK0TAjzA11AmFldlxJx9bBtosZjZKH748mOw1+CpbLFhcbk 33 | Qmgzh5pav1JBt5bSMwXNi/MXMMi+9i+0G7DVMc4Cn1a8w4SX8twR64dT24AbM7FL 34 | NOra7FqwcGZs65dXu19ECuRrqxRbg9rA27Pqodfw9St2b0Ro69ppHdUVkywzcuai 35 | ikBUBiJ9P9a89TToPBRJ0Ec8iPB1aPPlo8IdoR8fMMFb+4SSE+I7PhEkxo3ZqoQ3 36 | g5W30i0QPICQb8LIXccTOLiqnjurkp5E4Y35s9MfAAMHCACAfKyeri3DyapJfv0S 37 | qVfgfv808SE6VfOj9gB4vOu6KcV7/xXhkAg5zLZaaxGYGd27pTQwPFVmfUaxEEnj 38 | BmRRYWYLuTahgLGF8d1QPhqDvR4KwXfSkfhzJtQhXFXbIZO9VEfedlaWfoAF1WnL 39 | bekrbksqANx/a/+0HiprEqdQAg/krrGsjySZeEJdjrWk9LO+2V8RkFD04LM+Z19E 40 | YkYQ+40+JMmfHSv+oVhKoB6fKcaxhvC2FH61cb5ptducE3O0NtWu+U7kl2fdSAwF 41 | g0rZsHBHVybx7ibxNgLhoNDvvkWjRcNsSLPWZDNr8MaUw2D6+ajybyTCrgeKFiVs 42 | frNXAAFTBHAJ22l7NhITvybi7jHH6rOSMeIZP4vnmGyE5IquMQ0hNmCAbNZRT0QO 43 | 1hPHiQEfBBgBAgAJBQJbuvTqAhsMAAoJEHWB8ivkCpov+vcIAJnxuuBgyUI9yRnu 44 | Gl8gB+fFU42VSmOgYU8i8+ZEblgYylrE7UvPAB+4Zib4tB4Ug+9MKGKDOXpQRoVv 45 | DAvwSiA378kB3iGGZAI1n2KzUapStTFm0melu/kN6M76OtIzjsBCZQCufx+QsGH5 46 | OQQ5o3ggzo39edqebRQyTDuArqG2VDrodFbXJpYBW/4aNFlGVfum5F2mUE8UpKNN 47 | id/rcnUel9c5L22mIutvL+5IkkZxNix86guNp7ito2MHkjMKceXfQhzgJ5Dk1x2E 48 | p3JYuFjMl+rqjgeeUHjxwEm7ah5QRmAOjEDqfoSe9DQQ9z21ko8EDRlnZsOtZlsR 49 | R1DNSEU= 50 | =rrdT 51 | -----END PGP PRIVATE KEY BLOCK----- 52 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include "inc.h" 2 | 3 | void * 4 | dorealloc(void *p, size_t sz) 5 | { 6 | if (sz == 0) 7 | sz = 1; 8 | p = p ? realloc(p, sz) : malloc(sz); 9 | if (!p) 10 | { 11 | fprintf(stderr, "out of memory allocating %llu bytes\n", (unsigned long long)sz); 12 | exit(1); 13 | } 14 | return p; 15 | } 16 | 17 | void * 18 | doalloc(size_t sz) 19 | { 20 | return dorealloc(0, sz); 21 | } 22 | 23 | void 24 | dodie(const char *msg) 25 | { 26 | fprintf(stderr, "%s\n", msg); 27 | exit(1); 28 | } 29 | 30 | void 31 | dodie_errno(const char *msg) 32 | { 33 | perror(msg); 34 | exit(1); 35 | } 36 | 37 | size_t 38 | doread_eof(int fd, unsigned char *buf, size_t len) 39 | { 40 | size_t ret = 0; 41 | while (len > 0) 42 | { 43 | ssize_t r = read(fd, buf, len > 65536 ? 65536 : len); 44 | if (r < 0) 45 | dodie_errno("read"); 46 | if (r == 0) 47 | break; 48 | ret += r; 49 | buf += r; 50 | len -= r; 51 | } 52 | return ret; 53 | } 54 | 55 | void 56 | doread(int fd, unsigned char *buf, size_t len) 57 | { 58 | if (doread_eof(fd, buf, len) != len) 59 | dodie("unexpeced EOF"); 60 | } 61 | 62 | void 63 | dowrite(int fd, const unsigned char *buf, size_t len) 64 | { 65 | while (len > 0) 66 | { 67 | ssize_t r = write(fd, buf, len > 65536 ? 65536 : len); 68 | if (r < 0) 69 | dodie_errno("write"); 70 | buf += r; 71 | len -= r; 72 | } 73 | } 74 | 75 | void 76 | doseek(int fd, u64 pos) 77 | { 78 | if (lseek(fd, (off_t)pos, SEEK_SET) == (off_t)-1) 79 | dodie_errno("lseek"); 80 | } 81 | 82 | u64 83 | doseek_eof(int fd, u64 pos) 84 | { 85 | off_t ret = lseek(fd, -(off_t)pos, SEEK_END); 86 | if (ret == (off_t)-1) 87 | dodie_errno("lseek"); 88 | return (u64)ret; 89 | } 90 | 91 | void 92 | docopy(int infd, int outfd, u64 len) 93 | { 94 | unsigned char buf[65536]; 95 | while (len > 0) 96 | { 97 | size_t chunk = len > 65536 ? 65536 : len; 98 | doread(infd, buf, chunk); 99 | dowrite(outfd, buf, chunk); 100 | len -= chunk; 101 | } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /zip.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 SUSE LLC 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License version 2 as 6 | * published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program (see the file COPYING); if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 17 | * 18 | ***************************************************************/ 19 | 20 | #include 21 | 22 | #include "inc.h" 23 | #include "bele.h" 24 | 25 | /* 64bit le support */ 26 | 27 | static u64 28 | getle8(unsigned char *p) 29 | { 30 | return (u64)getle4(p) | ((u64)getle4(p + 4)) << 32; 31 | } 32 | 33 | static void 34 | setle8(unsigned char *p, u64 x) 35 | { 36 | setle4(p, x); 37 | setle4(p + 4, x >> 32); 38 | } 39 | 40 | /* main */ 41 | 42 | void 43 | zip_read(struct zip *zip, int fd) 44 | { 45 | unsigned char eocd[22]; 46 | unsigned char eocd64l[20]; 47 | u64 eocd64_offset; 48 | u64 size; 49 | u64 left; 50 | unsigned char *p; 51 | int fnlen; 52 | 53 | memset(zip, 0, sizeof(*zip)); 54 | size = doseek_eof(fd, 20 + 22); 55 | if (size >= 0x100000000000ULL) 56 | dodie("zip archive too big"); 57 | size += 20 + 22; 58 | zip->size = size; 59 | doread(fd, eocd64l, 20); 60 | doread(fd, eocd, 22); 61 | if (getle4(eocd) != 0x06054b50 || getle2(eocd + 20) != 0) 62 | dodie("not a (commentless) zip archive"); 63 | if (getle2(eocd + 4) || getle2(eocd + 6)) 64 | dodie("multidisc zip archive"); 65 | if (getle2(eocd + 8) != 0xffff || getle2(eocd + 10) != 0xffff || getle4(eocd + 12) != 0xffffffff || getle4(eocd + 16) != 0xffffffff) 66 | dodie("no zip64 end of central directory record"); 67 | if (getle4(eocd64l) != 0x07064b50) 68 | dodie("missing zip64 locator"); 69 | if (getle4(eocd64l + 4) != 0) 70 | dodie("multidisc zip archive?"); 71 | eocd64_offset = getle8(eocd64l + 8); 72 | if (eocd64_offset >= size - (20 + 22) || size - (20 + 22) - eocd64_offset >= 0x10000 || size - (20 + 22) - eocd64_offset < 56) 73 | dodie("invalid eocd64 offset"); 74 | doseek(fd, eocd64_offset); 75 | zip->eocd_size = size - (20 + 22) - eocd64_offset; 76 | zip->eocd = doalloc(zip->eocd_size); 77 | doread(fd, zip->eocd, zip->eocd_size); 78 | if (getle4(zip->eocd) != 0x06064b50) 79 | dodie("missing zip64 end of central directory record"); 80 | if (getle4(zip->eocd + 16) != 0 || getle4(zip->eocd + 20) != 0) 81 | dodie("multidisc zip archive??"); 82 | zip->cd_offset = getle8(zip->eocd + 48); 83 | if (zip->cd_offset > eocd64_offset) 84 | dodie("invalid cd offset"); 85 | zip->cd_size = eocd64_offset - zip->cd_offset; 86 | if (zip->cd_size != getle8(zip->eocd + 40)) 87 | dodie("central directory size mismatch"); 88 | if (zip->cd_size >= 0x1000000) 89 | dodie("central directory too big"); 90 | doseek(fd, zip->cd_offset); 91 | zip->cd = doalloc(zip->cd_size); 92 | doread(fd, zip->cd, zip->cd_size); 93 | /* scan through directory entries */ 94 | p = zip->cd; 95 | left = zip->cd_size; 96 | while (left > 0) 97 | { 98 | if (left < 46 || getle4(p) != 0x02014b50) 99 | dodie("bad directory entry"); 100 | fnlen = getle2(p + 28); 101 | if (fnlen == 0 || left < 46 + fnlen) 102 | dodie("bad directory entry"); 103 | fnlen += getle2(p + 30) + getle2(p + 32) + 46; 104 | if (left < fnlen) 105 | dodie("bad directory entry"); 106 | p += fnlen; 107 | left -= fnlen; 108 | zip->num++; 109 | } 110 | if (getle8(zip->eocd + 24) != zip->num || getle8(zip->eocd + 32) != zip->num) 111 | dodie("central directory entries mismatch"); 112 | } 113 | 114 | unsigned char * 115 | zip_iterentry(struct zip *zip, unsigned char **iter) 116 | { 117 | unsigned char *p = *iter; 118 | if (p == zip->cd + zip->cd_size) 119 | return 0; 120 | *iter = p + 46 + getle2(p + 28) + getle2(p + 30) + getle2(p + 32); 121 | return p; 122 | } 123 | 124 | char * 125 | zip_entry_name(unsigned char *entry, int *lenp) 126 | { 127 | *lenp = getle2(entry + 28); 128 | return (char *)(entry + 46); 129 | } 130 | 131 | u32 132 | zip_entry_datetime(unsigned char *entry) 133 | { 134 | return getle4(entry + 12); 135 | } 136 | 137 | unsigned char * 138 | zip_findentry(struct zip *zip, char *fn) 139 | { 140 | unsigned char *iter = zip->cd; 141 | unsigned char *entry; 142 | int fnl = strlen(fn); 143 | while ((entry = zip_iterentry(zip, &iter)) != 0) 144 | { 145 | int entnamel; 146 | char *entname = zip_entry_name(entry, &entnamel); 147 | if (entnamel == fnl && memcmp(entname, fn, fnl) == 0) 148 | return entry; 149 | } 150 | return 0; 151 | } 152 | 153 | u64 154 | zip_entry_fhpos(unsigned char *entry) 155 | { 156 | u32 pos = getle4(entry + 42); 157 | if (pos == 0xffffffff) 158 | dodie("zip64 not supported yet"); 159 | return pos; 160 | } 161 | 162 | u64 163 | zip_seekdata(struct zip *zip, int fd, unsigned char *entry) 164 | { 165 | u64 pos = zip_entry_fhpos(entry); 166 | unsigned char lfh[30]; 167 | u32 size; 168 | 169 | if (pos >= zip->cd_offset - zip->appendedsize) 170 | dodie("zip_seekdata: illegal file header position"); 171 | doseek(fd, pos); 172 | doread(fd, lfh, 30); 173 | if (getle4(lfh) != 0x04034b50) 174 | dodie("zip_seekdata: not a file header at that position"); 175 | if (getle2(lfh + 8) != 0) 176 | dodie("only uncompressed files supported"); 177 | size = getle4(lfh + 18); 178 | if (size == 0xffffffff) 179 | dodie("zip64 not supported yet"); 180 | pos += 30 + getle2(lfh + 26) + getle2(lfh + 28); 181 | if (pos + size > zip->cd_offset - zip->appendedsize) 182 | dodie("data overlaps central directory"); 183 | doseek(fd, pos); 184 | return size; 185 | } 186 | 187 | void 188 | zip_free(struct zip *zip) 189 | { 190 | if (zip->cd) 191 | free(zip->cd); 192 | if (zip->eocd) 193 | free(zip->eocd); 194 | if (zip->appended) 195 | free(zip->appended); 196 | } 197 | 198 | static unsigned char * 199 | dummydeflate(unsigned char *in, int inlen, int *outlenp) 200 | { 201 | unsigned char *out, *p; 202 | if (inlen > 100000) 203 | dodie("dummydeflate: file too big"); 204 | out = p = doalloc(inlen + ((inlen + 65535) / 65535) * 5); 205 | while (inlen > 0) 206 | { 207 | int chunk = inlen > 65535 ? 65535 : inlen; 208 | inlen -= chunk; 209 | p[0] = inlen ? 0 : 1; 210 | p[1] = chunk & 255; 211 | p[2] = chunk / 256; 212 | p[3] = ~p[1]; 213 | p[4] = ~p[2]; 214 | p += 5; 215 | memcpy(p, in, chunk); 216 | p += chunk; 217 | } 218 | *outlenp = p - out; 219 | return out; 220 | } 221 | 222 | static u32 crc32_tab[] = { 223 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 224 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 225 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 226 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 227 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 228 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 229 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 230 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 231 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 232 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 233 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 234 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 235 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 236 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 237 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 238 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 239 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 240 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 241 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 242 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 243 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 244 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 245 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 246 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 247 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 248 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 249 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 250 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 251 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 252 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 253 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 254 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 255 | }; 256 | 257 | static u32 258 | crc32buf(unsigned char *buf, u64 len) 259 | { 260 | u32 x = 0xffffffff; 261 | for (; len > 0; len--) 262 | x = crc32_tab[(x ^ *buf++) & 0xff] ^ (x >> 8); 263 | return x ^ 0xffffffff; 264 | } 265 | 266 | void 267 | zip_appendfile(struct zip *zip, char *fn, unsigned char *file, u64 filelen, int comp, u32 datetime) 268 | { 269 | unsigned char *compfile = file; 270 | u64 compfilelen = filelen; 271 | unsigned char *lfh, *entry; 272 | u64 size; 273 | u32 crc32; 274 | int fnl = strlen(fn); 275 | if (fnl > 0xfffe) 276 | dodie("zip_appendfile: file name too long"); 277 | if (comp != 0 && comp != 8) 278 | dodie("zip_appendfile: unsupported compression"); 279 | if (filelen > 0xfffffffe || zip->cd_offset > 0xfffffffe) 280 | dodie("zip_appendfile: zip64 not supported"); 281 | crc32 = crc32buf(file, filelen); 282 | if (comp == 8) 283 | { 284 | int deflatelen; 285 | compfile = dummydeflate(file, (int)filelen, &deflatelen); 286 | compfilelen = deflatelen; 287 | } 288 | size = 30 + fnl + compfilelen; 289 | zip->appended = dorealloc(zip->appended, zip->appendedsize + size); 290 | lfh = zip->appended + zip->appendedsize; 291 | zip->cd = dorealloc(zip->cd, zip->cd_size + 46 + fnl); 292 | entry = zip->cd + zip->cd_size; 293 | zip->cd_size += 46 + fnl; 294 | zip->appendedsize += size; 295 | zip->cd_offset += size; 296 | zip->size += size + 46 + fnl; 297 | zip->num++; 298 | 299 | setle4(lfh, 0x04034b50); 300 | memcpy(lfh + 30, fn, fnl); 301 | memcpy(lfh + 30 + fnl, compfile, compfilelen); 302 | setle2(lfh + 4, 20); 303 | setle2(lfh + 6, 0); 304 | setle2(lfh + 8, comp); 305 | setle4(lfh + 10, datetime); 306 | setle4(lfh + 14, crc32); 307 | setle4(lfh + 18, compfilelen); 308 | setle4(lfh + 22, filelen); 309 | setle2(lfh + 26, fnl); 310 | setle2(lfh + 28, 0); 311 | 312 | setle4(entry, 0x02014b50); 313 | memcpy(entry + 46, fn, fnl); 314 | setle2(entry + 4, 45); 315 | setle2(entry + 6, 20); 316 | setle2(entry + 8, 0); 317 | setle2(entry + 10, comp); 318 | setle4(entry + 12, datetime); 319 | setle4(entry + 16, crc32); 320 | setle4(entry + 20, compfilelen); 321 | setle4(entry + 24, filelen); 322 | setle2(entry + 28, fnl); 323 | setle2(entry + 30, 0); 324 | setle2(entry + 32, 0); 325 | setle2(entry + 34, 0); /* disk no */ 326 | setle2(entry + 36, 0); 327 | setle4(entry + 38, 0); 328 | setle4(entry + 42, zip->cd_offset - size); 329 | 330 | /* patch eocd entries */ 331 | setle8(zip->eocd + 24, zip->num); 332 | setle8(zip->eocd + 32, zip->num); 333 | setle8(zip->eocd + 40, zip->cd_size); 334 | setle8(zip->eocd + 48, zip->cd_offset); 335 | 336 | if (file != compfile) 337 | free(compfile); 338 | } 339 | 340 | void 341 | zip_write(struct zip *zip, int zipfd, int fd) 342 | { 343 | unsigned char eocdl[20]; 344 | unsigned char eocdr[22]; 345 | 346 | setle4(eocdl, 0x07064b50); 347 | setle4(eocdl + 4, 0); 348 | setle8(eocdl + 8, zip->cd_offset + zip->cd_size); 349 | setle4(eocdl + 16, 1); 350 | 351 | setle4(eocdr, 0x06054b50); 352 | setle2(eocdr + 4, 0); 353 | setle2(eocdr + 6, 0); 354 | setle2(eocdr + 8, 0xffff); 355 | setle2(eocdr + 10, 0xffff); 356 | setle4(eocdr + 12, 0xffffffff); 357 | setle4(eocdr + 16, 0xffffffff); 358 | setle2(eocdr + 20, 0); 359 | 360 | /* copy old */ 361 | doseek(zipfd, 0); 362 | docopy(zipfd, fd, zip->cd_offset - zip->appendedsize); 363 | dowrite(fd, zip->appended, zip->appendedsize); 364 | dowrite(fd, zip->cd, zip->cd_size); /* central dir */ 365 | dowrite(fd, zip->eocd, zip->eocd_size); /* end of central dir */ 366 | dowrite(fd, eocdl, 20); 367 | dowrite(fd, eocdr, 22); 368 | } 369 | 370 | --------------------------------------------------------------------------------