pax_global_header00006660000000000000000000000064147534551550014530gustar00rootroot0000000000000052 comment=fb1c459af0912286792a49dc654b67c262bbdfb5 stateless-openpgp-docs-13.0/000077500000000000000000000000001475345515500160565ustar00rootroot00000000000000stateless-openpgp-docs-13.0/.gitignore000066400000000000000000000001711475345515500200450ustar00rootroot00000000000000sop.html sop.xml sop.txt sop.pdf ietf-*/*.pdf ietf-*/*.html metadata.min.js manpages/*.1 manpages/*.html .refcache/*.xml stateless-openpgp-docs-13.0/.gitlab-ci.yml000066400000000000000000000015571475345515500205220ustar00rootroot00000000000000image: debian:testing spellcheck: script: - apt update -y -qq && apt install -y -qq --no-install-recommends codespell make - make check variables: DEBIAN_FRONTEND: noninteractive pages: stage: deploy script: - echo 'deb [signed-by=/usr/share/keyrings/debian-archive-keyring.gpg] http://ftp.debian.org/debian testing non-free' > /etc/apt/sources.list.d/testing.list # use xml2rfc (non-free) - apt update -y -qq && apt install -y -qq --no-install-recommends make xml2rfc ruby-kramdown-rfc2629 - "sed -i 's/^docname: draft-dkg-openpgp-stateless-cli-.*/docname: draft-dkg-openpgp-stateless-cli-latest/' sop.md" - make -j4 sop.html sop.txt - mkdir -p public - cp -v sop.html sop.txt public/ - ln -s sop.html public/index.html artifacts: paths: - public only: - main variables: DEBIAN_FRONTEND: noninteractive stateless-openpgp-docs-13.0/.ignore-words000066400000000000000000000000061475345515500204720ustar00rootroot00000000000000crate stateless-openpgp-docs-13.0/LICENSE000066400000000000000000000156101475345515500170660ustar00rootroot00000000000000Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. stateless-openpgp-docs-13.0/Makefile000066400000000000000000000011371475345515500175200ustar00rootroot00000000000000#!/usr/bin/make -f # dependencies: # apt install weasyprint xml2rfc ruby-kramdown-rfc2629 draft = sop OUTPUT = $(draft).txt $(draft).html $(draft).xml $(draft).pdf INCLUSIONS = sop.h test/simple-sop-test test/setup-sopv-test test/sopv-test all: $(OUTPUT) %.xml: $(draft).md $(INCLUSIONS) kramdown-rfc --v3 < $< > $@.tmp mv $@.tmp $@ %.html: %.xml xml2rfc $< --html -o $@ %.txt: %.xml xml2rfc $< --text -o $@ %.pdf: %.xml xml2rfc $< --pdf -o $@ clean: -rm -rf $(OUTPUT) manpages/*.1 .refcache metadata.min.js check: codespell --ignore-words .ignore-words $(draft).md .PHONY: clean all check stateless-openpgp-docs-13.0/README.md000066400000000000000000000021051475345515500173330ustar00rootroot00000000000000Stateless OpenPGP Command-Line Interface ======================================== This repository documents a CLI for OpenPGP that is entirely stateless. It is currently published with the IETF at https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli You might also want to see [the latest editor's copy](https://dkg.gitlab.io/openpgp-stateless-cli/). The intention is to get multiple implementers to co-author it, and to get multiple implementations to exist, with different backends. Examples of how it would be used: ``` sop generate-key "Alice Lovelace " > alice.sec sop extract-cert < alice.sec > alice.pgp sop sign --as=text alice.sec < announcement.txt > announcement.txt.asc sop verify announcement.txt.asc alice.pgp < announcement.txt sop encrypt --sign-with=alice.sec bob.pgp < msg.eml > encrypted.asc sop decrypt alice.sec < ciphertext.asc > cleartext.out ``` If you're an OpenPGP implementer, and you've built out this interface (even some subset of it) with your toolkit, please send a merge request that points to your implementation. stateless-openpgp-docs-13.0/ietf-112/000077500000000000000000000000001475345515500173065ustar00rootroot00000000000000stateless-openpgp-docs-13.0/ietf-112/Makefile000066400000000000000000000005371475345515500207530ustar00rootroot00000000000000#!/usr/bin/make -f BASIS = openpgp-sop-ietf-112 $(BASIS).pdf: $(BASIS).html weasyprint $< $@ $(BASIS).html: $(BASIS).md theme/css/theme.css darkslide --theme ./theme --embed --destination=$@ $^ # alternate conversion strategy: # open sop.html in chromium, hit 'x' to expose all slides, then print to pdf clean: rm -f $(BASIS).html $(BASIS).pdf stateless-openpgp-docs-13.0/ietf-112/openpgp-sop-ietf-112.md000066400000000000000000000041501475345515500233250ustar00rootroot00000000000000# Stateless OpenPGP Interface ## IETF 112 OpenPGP (Nov 2021) Daniel Kahn Gillmor [draft-dkg-openpgp-stateless-cli](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) --- # What is `sop` - Abstract interface for OpenPGP - "Stateless" -- all arguments are explicitly specified - Not in-charter for WG --- # Why `sop`? - Interop testing - Clarify concepts - Encourage best practices --- # Why "stateless"? Why command line? - Specify all parts explicitly - Avoid hidden side effects - CLI is a "common denominator" --- # Focus on data management - key/cert generation - encrypt/decrypt - sign/verify --- # `sop` Examples sop generate-key "Alice " > alice.sec sop extract-cert < alice.sec > alice.pgp sop sign --as=text alice.sec < notes.txt > notes.txt.asc sop verify notes.txt.asc alice.pgp < notes.txt sop encrypt --sign-with=alice.sec --as=mime bob.pgp\ < msg.eml > encrypted.asc sop decrypt alice.sec < ciphertext.asc > cleartext.out --- # Interaction with Crypto Refresh - Generic interface explicitly does *not* expose algorithm- or version-specific details. - Can implementation `X` deal with/interact with wire format object `Y`? --- # Missing: Inline Signatures? - Currently expects and works with detached signatures - How to deal with bundled message+signature objects? - See [issue 25](https://gitlab.com/dkg/openpgp-stateless-cli/-/issues/25) --- # Next (1/2): language-specific frameworks - [Java](https://github.com/pgpainless/pgpainless/tree/master/sop-java) - [Rust](https://crates.io/crates/sop) - [Python](https://pypi.org/project/sop/) - C (shared object)? - Your preferred language? --- # Next (2/2): Certificate Management - Merge - Validate - Maintain - Revoke - Certify - …? --- # Recent `sop` Changes (from -02 to -03: minor changes) - added `--micalg-out` to `sop sign` - change from `KEY` to `KEYS` - new error code `KEY_CANNOT_SIGN` - `sop version` expanded for more detailed output --- # Critique, Suggest, Contribute! [https://gitlab.com/dkg/openpgp-stateless-cli](https://gitlab.com/dkg/openpgp-stateless-cli) stateless-openpgp-docs-13.0/ietf-112/theme/000077500000000000000000000000001475345515500204105ustar00rootroot00000000000000stateless-openpgp-docs-13.0/ietf-112/theme/css/000077500000000000000000000000001475345515500212005ustar00rootroot00000000000000stateless-openpgp-docs-13.0/ietf-112/theme/css/theme.css000066400000000000000000000066001475345515500230160ustar00rootroot00000000000000h1, h2, h3, h4, h5, h6, body, header { font-family: "Linux Biolinum", sans-serif; } section { font-family: "Linux Libertine", serif; } body { background: black; color: white; } footer { color: #93a1a1; } a, a:hover { color: #268bd2; padding: 0; text-decoration: none; background-color: transparent; background-image: linear-gradient(#268bd2, #268bd2); background-size: 1px 1px; background-repeat: repeat-x; background-position: 0% 95%; } a:hover { color: #2aa198; background-image: linear-gradient(#2aa198, #2aa198); } .presenter_notes a, .presenter_notes a:hover, div#current_presenter_notes a, div#current_presenter_notes a:hover { } code, tt.docutils.literal { color: #6c71c4; } .slide { background-color: #ffffff; color: #000000; } .expose .slide:hover { box-shadow: 0 0 0 15px #268bd2; } .expose .slide.current { box-shadow: 0 0 0 30px #b58900; } .expose .slide.current:hover { box-shadow: 0 0 0 15px #268bd2, 0 0 0 30px #b58900; } .presenter_notes { background: #000000; color: #ffffff; } @media screen, projection { ::-webkit-scrollbar-thumb { background: #ffffff; border-color: #eee8d5; } ::-webkit-scrollbar { background: #eee8d5; } ::-webkit-scrollbar-corner { background: #ffffff; } } .qr svg { box-shadow: inset 0 0 10px #eee8d5; } .slides, .expose .slides.nocontext, .expose .slide.current, .expose .slide.current .inner { /*background: #000;*/ } .slide { width: 1333px; } .slide header:only-child h1 { width: 1333px; } @media screen { .slide { margin-left: -667px; } .slide.next { margin-left: 677px; } .slide.next_2 { margin-left: 1368px; } .slide.next_1 { margin-left: 2020px; } .show_next .slide.prev { margin-left: -2681px; } .show_next .slide.current { margin-left: -1338px; } .show_next .slide.next { margin-left: 5px; } .show_next .slide.next_1 { margin-left: 1348px; } .presenter_view .slide.prev { margin-left: -1348px; } .presenter_view .slide.current { margin-left: -667px; } .presenter_view .slide.next_1 { margin-left: 692px; } .expose .slide-wrapper { width: 414px; } } @media print { #toc, #help, .slide aside, .slide .notes, .presenter_notes, #current_presenter_notes, #presenter_note { display: none; } aside.page_number { display: block; } pre code { font-size: 150%; } @page { margin: 0; size: 1365px 781px; } .slide, .slide.current, .slides, body, .presentation { position: relative; margin: 0; box-shadow: none !important; } .inner { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } section { position: relative; padding-top: 2em; } section p { margin: 1em; } section ul { font-size: 140%; } .slide { margin: 0 !important; page-break-after: avoid; } .slide-wrapper { page-break-after: always; position: relative; margin: 0; padding: 15px; } * { -webkit-print-color-adjust: exact; color-adjust: exact; } } stateless-openpgp-docs-13.0/ietf-121/000077500000000000000000000000001475345515500173065ustar00rootroot00000000000000stateless-openpgp-docs-13.0/ietf-121/stateless-openpgp-ietf-121.odp000066400000000000000000002017651475345515500247300ustar00rootroot00000000000000PKZacY3&//mimetypeapplication/vnd.oasis.opendocument.presentationPKZacYConfigurations2/toolbar/PKZacYConfigurations2/popupmenu/PKZacYConfigurations2/menubar/PKZacYConfigurations2/progressbar/PKZacYConfigurations2/accelerator/PKZacYConfigurations2/images/Bitmaps/PKZacYConfigurations2/toolpanel/PKZacYConfigurations2/floater/PKZacYConfigurations2/statusbar/PKZacY styles.xml][۸r~_ҩTB]gg|7{}NUNQ$$qM,SA_\)P$x)U _7Fz<?\i8kZio/o~]-<'vݱvOý !~}uwAOmzpo߾Qp̨7iiԪ,kC]zO6ܥP2R ^!aK÷0 ud>-a!FOhkT\׍CCg2&=+K`KkWeCf  ڶbAHо~" ljɉF#mLdc``HQ=.wL2V~tW{!6єQX/1_;<[9 =7) &Pi)8Av1 <뾅8_q4b'a&>XP9fgsBW#N /Ɵlp}2𧛮Eil̝r{囇F`s'*2Von]|*,N&FdmHnfcQ\H{?_3F /5C<뎾U 0y$DZpd',N!uӂy?9߲m-5hg~BOkC'V-N`EUJ/ѝ j]MVo"SҠn 1:"aa$hR̗zf{p͸ػR呂3-g#\xc^b"},TΚ7 {k!-)GB}-  Ԇl7cC_xggk/=C>6$ >5Jw/h,1-@Y/l4'KЇ{%=tԳvAf4*+cJhko6w/"IujQ: * .n񃍀}sXmIA.׶!?,߈V2HdFfG*,73i[\)\@<{:u}6P~~Z&:C(10D'ie6Xr%D[GiD%F5}!lGY6 e"< MH=4)D?" <  .N q՝H%gK7N^lan"[|L58}i˒k N;{nR?v{nZ7vhr/N#w?+Ţn&R,nV EiǗ &FV4Gف{ Ɗ>dSw?m1vlO)ŽB";>)u' 7a t7qm"ܣpVX$14v*VsvNPYPyz=Ciĩc^w*G,CK߁`7Ir!{JZ.Kʏ |eJaZǀ' 9\H+pՊKDb`"4Q >:Q8muʺiF@g,({V3 r\ Q܆?:PIN?aN)!v\?z# 8>D05P+5}t%=oP8[^0 wg-}yiZaNʫJgdpkaY=BQqO|L`A:վDo'W#{V)I{V1RL[7&g 19sUm 7Xzt{ݴPdswu4{`Tׁ fYdK1`A5@ASq?(x6,fllL \bqɚ`#hNE)"!Jj@Ņ64$B<&|'P19 (s8fj=`c*nV4M`Ǵń-O tGiO{Ek 䬋|歺`5B䂒Ԫ O{򗻿~5q8n Fy ,vQ\^tEy4ϊhDŽ\Y9=[r9N.GAU0uAJՀ3CW]c٥Jl |G.7ꂦ-c2 md{vq@Va7P}3~qCzv޵-"A=A׌?OUh=my/-[*:(_,@)A0w]߁b4v@Kq WZK:ć>ya9胭p1YVFZP+@G,S%AtN: @ $N2O`u `ZFӺ0 0+#Ynz,`Qe[;@_(N=Hn=2Mx^p^>ES)X®Oښ>kOڱߖr V?muT ?*ʙϨèαb[En-ֻź]b[Kn-nXwk]߭5n&Խ[qVȾ `7Li`d&y`ҧxQ bn6iwMn({OHQ9#;;YlmurS}FMRݝlku!e[[ )ڪw]HV캐ص?e[[Dʶ׍mEOv%X[{7tNc.ybL&@- I؄B/}7S $`Kj5CkҠxM(w3Aҵ4諦\ Ρ[Kj5zA4諦Y ΡXKj5lc1軖Z^m+v]m+v]m+v]m+vm+r]m+v]m+v]m+vC>mxqvBPqvQGP^:ٻzWW]]wuu ջzWW]]ݮ=}.΅R_=9Eo_ܼ SKƃgbp8 b$p9k`|O.?GtYןŗKO0rP' 7?@ְvh%}Lm*^r*dN6db֖a%WEk\: ^rY3]FQLkNuhѪ톼UA]y3iV|N 9lȠdJ/N# 9>~Nj']&R!i5IO@uNJzPV4W*+wTVD ̝kRUmY7y)d 3],e*`g R\M=sN9Jw;KlmVo rι-+\vݡs> i h-Գ@c\g1 l.Øt6arB9 0b9!pv7;YNfs,/T@ͶMPM) mĬۄ6bmB1k6{FھMh#fm&oڈY 6A 6&t ~omB 6&~MӤ$ayj,G"L g>%h,&tcPNQ:Iõ];j`9 `ݣI$H,5_S8qp߭ݲGFs-G2b#U],ߙg[p2Ҧ5Md-rz]p )$xHڇ#e,;Q],naV-Fr1+s|caA4$bsdn徐gt-bYydkBR3od걿=8}q=I8+*Ԓ"XGY*­;jqki:MI4I7i`7ـs)˚rR֛'珛'9GpHm^ E-\ى\Ɗ%i0%cIQPu4)6H8*b1 &њ]a'ɯymrڱ𯜦ճN.ڴnBdh$p8ꅫkuM[V_[V7K,Y]U_} `ڶ{$|tUO'G #ļCSHY&&nu4S="ý?6kSDIk>m>`%ypXm"CSQ2 ArbE%ZՒ !Q&#m"TsMT3kEge_it>+ Yf flEK^Gr a\ƀXo|Q;MRh\rΫ<,q`_P5|Zl 8,,0.TFmLOt^ק|pGXAܻ9g[[Km6y+d;U7 \fς- Gh;\eOU~7fg _O/;;8&LY'L~MK'_f~~;z^,Abӛg īڤm,5,n,b^f:%"se*ՏH+~tLA.Aji\2LS-ӤZ^+N/NE7ʮ 35^K(~ȷݐ.(qJߜ$i;3wMҦ_˝ζ 2I_=lv=ӛpgiҊE\d5i/zc2Py}6dU oCvyYt=7]c(xPKN`hPKZacYuAUU-Pictures/1000000100000487000001AFF48D6CBA.pngPNG  IHDRg IDATxy\U?te @$]vBH#+4. bq~3"F1$0h#*+B4$]I(YDdU YH/UtN9UyKu}{ΡZ:s!u F"һ}ADDDDDDDV;`[/ľƐx>;bMdk*E7=i EDDDDDDd9ԼyĮ7|,"}|DDDDDDDd09tKǖJx,"[3 xN>,"""""""{*Cް|g闡Q쉊ߐ:WQ Jw1$ͭV%""""""U'0A;?96%N6r`Ѿ17^…S{|Gٕl[. `wA3Y_"""""""3rWw!rӮ^ """""""P-'~\YDcI99m"""""""h[;k}E$ +ɳ2YDDDDDDD9tL*op "i3l2s5.("""""""/ |=_A!1p܋|GyPˊ8Wf ;iY&}E*r`t?-EDDDDDD/͡b;0ia dfnEDDDDDD'͡\!!]C^G".G}I [ m 1$S vO}'ɡm=Nh3{< EDDDDDDTC>[Z7߶0^ADDDDDDdxH949G T Ԯ4|ڗ?t\rx VcHdPF;Ծ!o:sqe$ c #a`A;Զ!]V&!{Pa̮=0>p^YDDDDDD Ys|@ݐ2Ҳ%(;T\g̮%e7քIx'ADDDDDD6 94oN!4 fX/jE*[{3v|g%S||G7.X_>>2l"|Gpq$L~Yv^ |wn{sT SImqvqw.bDYv̺-~w^9p/*1t_hj `_7t#oβ+$\!7w^s3SM~R*{6"Ս [ -")Yv`_-""""""R}[ ޘ?96%Hh)\X1w]1=pねmMm%YDDDDDDz9z!+os>_;Ԧ =β+ml^=;Hues(W] *ycY/ RrزhYvp/Q/H9|Ks8Iȹ4p{46,,2ꋈHak+.ThЖx_.Xuđo30 Ư=fMih#71u^WDDDDDD*[:Z>K,/r\-̝0( gw%ђ|eF-Ŗ7\5EǺ)""""""- zs 4C=+Nr?0rgl ɀ[W-@-͡Ldq$Hf! ڌZDDDDDD2=Zfyx]լvb\1 g'5'>iu3w0bIvW5=4EDDDDDT2fYKK~^s[ m0;M\!K:.y0 N5؍.lHuN.۽ab1.4hYٶrH Z;Z/XVb1*>MP7oaڜ9$A\Rly(ũ~ M6 ƍХk.ZR!BosGR \V)g6]CDDDDDDK R`Y9![!9`䊹Ѿx`YYhrHDDDDDDjYL2C ~wJDç6f6&:/e^RsHDDDDDDlYY9)ees: ;GE#Zr9ci f<Ҭ!""""""g-B Ph5 R v+&Cݘԛ$7Ү#""""""eˤ!6,Cܻ;G5 xვ[! î9dI;G 0s|TDDDDDDee6効0UͭCyby>7O-1Cwe3[.,?do6AWkRP%:)_;4~}pǀ)Ѳ2Q,o) 8l5i1?B|kOUfxs~zϻx;|%WՁ8x:|}t9nIv}^<ղ2ϖ!N&tM8k<}uKڧ|/7$L!O;C /0ym4O59$""""""rZYieiYY`J(Z4qEѢSxi3kv^>hj!OJAiChah͡V\*8iQĢh 63`3DXҲek$'s︪鍃橖H' 6, {1U=3K@|0XgҪrITDDDDDDeeCƷ9G^_wQuOY ޒ+F狋i@DDDDDDd3sH,ᰬ,W8aɯx'i.ꑬNpQ+2˚І4 ee41QQm?ꪖ/.N6lߩ6H7ivF{B0KCvR_5 GNqT'ʨee""""""WCKqMҲYN]ԸWNjDrK/騖&DDDDDDd4*(ts`G0/uvgqNuP*W?Ao,+ղ2ߗ98V͡yk<$Bbu'uNo>ɡQee""""""/+f7];@7ГvI6[}hcgxfS5DDDDDD*9]VfANeO|:5 ߘ82`i/]GDDDDDDee%5;9ZO5 u]gI}})וooHDDDDDDtYYhaNLE=h0sPgO=z{RrMSHuʼNia [ӭ084јzϪfv$d OӦM/ ãINr?3$o߻M0@ɱz<3 H&iϓ|̞5'Zb,I3ٵG=6I`21r=Lrӓa nn/6+ߘT˿Hk6)LIFQtfvcHYWW7qnF$Q.f8~fvo3fʥKTPDDDd455e6l8d- Կ>{z:ۿ!(ߓP(<2Hd7jv!ݿ^2TeK~P[]1'MS5dqjo7( =. `i$53lܸqS z$""c/H̞# ɿ3aAf_fv*Y$Br@6_A3P(+P.U=$7K7<}b&9 $Jj o(VT*ݼjժF 4@;mY7q9<>d2paZifΜyXTzs h*v"^3{/(~ff^zpEr:} ??f3C$IE$2ˬs(ᆴ/ VC0lJ}Uန+ ˢ[ZV&0% 2E\@{L&8Lw%""Q蛸lf퉢n\nV8EQr%z$o}7?&;< a$O{q?3qWW׏׬Y+?e7[l .7"f۔C;*e8:[jU!V&;xL$ts~4384Oş%""'8l8 Bplq|}jB8[|csɀ4rq,I[Ӛ{a}?5)@״k<(WMN35p6\Aq9I$ߊo m/KQ"c}$0Q=Ef;WZ~8yr8/&I3|qdyr(; a$  2A{qAP`]}INדie̞={\7'9 +6;w dkN0W$7&nE7q^&zj S/dLIk뉋QRgl6W$ґof߉x,"""lV(w=Eѥf ~]'gƌ}CU$oƩSa`ێ/趥m5;G2uv+.~SҥLWg|5 =Ҳ2IM6=Z , YDDD*Q$ƦNW*?IOEGYCWb~ܫ.m._ K_tQkgr܉\-)suŲbRy( ]wEѥT2oذ᧕|Vǟpn,m #:mCf/=)k"eDyɡ>kh}zs uזo\O=j 3qO}dp|9!""RH\.lhhI8sTA>s̃}!1ϳiymCdIPuk.r4W-GכMK,a Cush5k(3量%f7766;H0c3 q 5br\MՌAq$L*E* sXm%9s.߆`$rQHޛOK/ 6494~d5dqk"""w8'm`d|w2#ݝ0|YO,Kes&ޓ+ii^< xQug0_O Amnkj+]G*CCC4qa9}=;}^S{%͞; ax'}/+$w}Z3֛~C0aÆ e XR@rTW!""R X)477A, S6lp2tH'9_Vv7~qmTL}[ز<ˮ+<= ^b~vլa,ɡa9$96];0뢏>^n"aM>I8/+ڟGI~W}rAf-}oa㈍X gcfklhA76.վ;r3"%m ^V-$clqزG1 IBw&EDDttt<j m{|ul$I `<-7p; #G YdȌrl5AMsȘCCPʟ-<(Y 6\^w&j@Y$Bw0EQE$IN'yP7HK_,;*"""UfqYa ND#8bu{{{y02cƌ$9hސ|)$/ ‹uO k@,`Y/u!|^ߤDe4Y( JW~n(`߿뛚Fnڴ$I.#9c(jCsٗI&߷2urp(Yf[l*9$3Tj+&-f{Dаːj셡|F83a$0z(k lS+d,+{YOw~'}gx Z N<V'ş(f8c͛Y'BK.p8~}4kJB!?Eq<:IY$O!ny7zaXbMZ5-[)S\?a„O`60 jD0 |']"8&Ir23M@@_sh1 ?l-+Sw[bsa> A2g6,.괲ZW(^PqtR6詮. :;;Y,?Y(^7q{ oLmy섴74C[[vmwPlfϺ53{뚵̬bxmP8=I?l7dCawiY5.gw V*9s09Է(3iBi`ҥB ~ٕ`4.۪eeL49TkUbkkVPXN=1.""2hK.-'F"Q~X{iYTcVӋA ̪2ɪ*^ jYN! ඖ-G̴L*_wwwPsHDDdw u HCs'Ѳ~䊹KI^;kI{ {Բ2|cǎ5Uu~iB%zqYYmMw9٭kGβPC""""20rY! qCsl+sWΝ`w5oyҗ|(\!&""""&dD CmMm%CʻWɲyk'Im|g< `(l){7zKr\ulyAjO6ZUX4T^뻿@w=`ޠ~w8o/l~-ɣHArâv,qQvHϡ]9Dp=*J2d%c0Ӫr`;ɀ2 vWtà#Ο2hYr$M;Brd+ސ{# ʁ&Dq\˗y-R!;If"""~2Tfs•[gu#/0< nG,x< ?o]A P_#xQvްW!IլYFuww p }}jly128u)4Idᥗ^Zf͚Sjjj~0<?$Ir0$'bVFcrt_̞$\$O1˗?S+oo$6I5f`{4Q#F@E=?xdaݺu]VdԠ]MjDyD;)gQǢ}sڝ4p};G쎙ۜ9Y]{YC[k=:L;qog̘1]˖-~} #s(C<~ABr$<:iJ3èQz8.Q?ZbŪA8 @5<,J(EGr ˖-P}.$ ===Cg՛c=$I3I`2$ƖkH|{c"4 'h|f &FQON5EDjîJU&so&ygQ,0w 2vr*jLpaF]gײ *6mڴ Lf/{{{b=3g\.ߞ>#0+Inp}PxEjԔٸqfv&7a+WQ)~ƐlVM$RUV#T3{`Ĉclfuvv~wO>n̙r;4go/)八VsED$]0Z%w}msl GپPY8}Q ysӅ[})ǥ$`g]Ƀ"R-pgcc3f̘^.0m!>(~l߁|#DQ8o޸q &y%;z3̳Qݖfύ8# n_1)wxKאd&y,oq|L""28ZKQpl5}s0U:&ѝ%_\d>7x# `l>slmt'PD+wuu6hjjE7I^;`}KSgdٳz{{~=$A(~Ѿxr,Θ1cz_*luuunhhwٵC`9 +gr( |  yː3?9 =Gyߜ9 ٝ4yE<o7nx7s}"F<9sq̽7(^_*˿$?;ʆa4;\x,8P1+$rk70cqWKP}b'z̙{u/'Ngɑ$?EqO5w>_Ϙ1ADD6fYY 85l'e;>zǽ ,в2<\.X)HW$IK})fq 2dHSLDDDvdx,+p~2 N_и!}iC[r[)RXSsDDph$8Hr8 i6},2df?~""~C4,Օ. =g/h\y V3=0)œykyke0V&"|C3:RIq&^ݵLu_As ;D2>7IwJ7I(|g!iӦ;]{dz2zt+{^Vz#=8Znڱ_{⎋Nd9Op1EnuYsZV&"؜Ɵvttݿ;TXf0wHSWW%E֐R=3 Ç||sc0kRڵ6"k֍Z1E+܂:s!s?,jrHDdkf+W|w##{}Q-,IBK3HuwYY]VeWݎ˾̹}_[J>_ds\6psHDd;A?C@ǧg}6$'IYfEO ""[~%mlGc\4O~kYM2qY`tYo.ZV&"=Ϙ1c ӧO?H;-F!C͍'!""h- eWxÀowZP\غNkV%-A E rd5_)ie Mrg0 x·8>w{I\;/-k9,W,I\~N{UjuPtufU=ga.N++i!|[6}fgoRզkYgqfn*"bfiOd3ΛC8ľK~}q^V.wZpz/s,5DD h_v]V<&s}琽C2>9d0 G ";"s2Xbb&ʖUUE:짮Imhszױ mKLDd'ӧOGIf3O577s^;wy6{L7v!!3;t3-i3?X a4'IOv>y E=o4&32jC̒M)2""U0 Eٳg0E=3[ KF3B03{]mMGy=;#Ox Et:]}$3;!f6t#}f_{A="2`?7wް)P,pj9xD7'"9Uc\1+'$w͡1ul `r$σPwww+}\ڍMO{===w^~`SS7AgRKgZPC!3+|C4 x8GBwoʔ)&L8  E?PsȿyҮPC0F75_Njզk9D$_uQ )DdlVnq&mf:sM!9"5uԱk֬٘fS5jԇӬ1Owuu-t.566cfs"{! 7466 CN+L׮] .wM:#Fz}Qi3;|R5DjIԷBis6P.KFWjԏ.diiئ^b=搈 =T52ׯo ,3;iѣG'i5jԻf]cǎL_sgttt<ಙ3g^U*>M@s}@C5kqԩ9r^ IDATWMMMKqj䠆H=<&ie.Nlچa:F}IfmeW8|;&DDҥKK ':;;p{؁f ֖/_dX9:bfg[+֬YS,;ք7f=ՖKmJH- 0NC 'ht237!;N6ဤݔ͙G;&DbYR(sGL3YjdIu/;nYc'a(EW;::hf+\|O6m?ukMGGR4 lԩSGpq͡A=@wqeLN%-'QOhIKbEW.]d&⾻캻wW]WETE ÊBJ䙤@9 )Bi23K!<}|Y~[f;©dwQKEOtQptKʈs1X#7C|O a/ RWWjvڧ&Vkjj:ED\Lsr4(zs/qP#7椎7/`O(CDT1(jSU'EmmmnʥgBkn+ Vv oף:EyEj0W1.j!#\*טPlxRC7̽aZUO;z: urfQpjOV> Xxq " m<{"9>l/f͚Ƙꨫn/vaޣw\$7tTaGu*^E:*ksZYɛW.8搓[VFD( {D u1܇~$xި Vo^+DӮjՊ;+\lNѾ͛7LtTGu*ڢEGgs8^2G(bS+cppb-}\+#JV˚QYy=.6ziӦxQ#Ν{Z5pYP(8BDa9NM–-[.9*No.Vf`|{]ԩ1O]N9CDTTU()"Vm߄kWZ'&eݺuc>^]]b644Swr՟vjiiy8*"6`D?Pìke.kR'ID>nvn Z\qtQmDD iz=TcVn_NPUaI'Ӽykc:GT2[}6l q<_ }(mש9에Dg_gvbֿRU ٚv? pZ%[r?uQKU]vRK8y!wښ UMU]f1QEJ&K|eMcL 6'0.nmMyvZ#< 'n,a""rl``T* Lo\b.ĄU`9TgJzc~fY:;<,qso TuG?hObT*5#U%"Nٜ%"1eVUoN2 9DDDDZ6ecąUTҍ}ΈV^#8w? wVfese=|͡'"=;UU-}('ϟE%sLt Akn_fݮHj:swPUCNqk瑼ihh!IX+"3::͡DSb""""""*G(>7<<ܫ; Ql>wtv搪n!"""""\=88+ah'U"ғܲX+ӈ!""""""T("U-aU}w1r OA7 NI pfhrQG#=}pl:zk;u| """"|>k>;a`1a.x3[,/3j.H#s ރ> !""""zODp#"+򝍨Eܧ?\nDU#AV}=ʈSƜol7ADDDDm=|"QU!"Ϫ"AD6ƬxM p;glTuTDvQNTDDDS0DEnwHVkFzDR/|@ ج2"a~w"""|jMqɝ]=eʮ}V'ڟL l^Τ:>2ZU57gjqrJ6P>Zg֛CʞbR͡BPQSU_ Z]GU+94>>$ '9*{#GU]P,wP;q&"NXBV&\+S+ڿ`T~DśyjXH$.ꈈ "mJU 8E"2fTD#^UOpT8rʵ2:E-ZVU6jo"Ro<(Z_ YovQGDbwix:DDaWu:` >9$*D%E &/&JA9---.jEQ>i{󖶶6^[LD#Z[[_t(bCoWG|뾉\sq C57fU]NZ[[[EX"?=s!qQ+H<3(pH>?Q-"rPEg3U/h&Rq!>`Tֻ(""纨'UZQ9PK&&a]RBX|<644<@~>,'4"2Ey:SpZ۷oEr+1ٟIh)MgYtA3$:x<o/ozݺ[8F~mδkeIUq~pN*:3 _(6]mmm.~oTp!Xu|fy< |M&rtŝd2n?pPȣ.Ν{Ț5k6W*9Q ###["&B[XLH6m[k7l7ES @&OOUŽB ga:Kr6ȭjbDaǾZ5aܹ aTug\+W-XիWՔbpX~ @D k+… g\RG\!&2CeH ΥOrIv;a/G (v1(!-GsQ'@TfZ?5keYsHD&ɋ,wUs*R >檞]-={v}cc7DهE"o٪JDN݉1bZ9DDqٺuc|pR.["׶ڮU8E-yEro VVF .yy&s{~U"_"r@bD iq3)\ q֩f.&Ok}.J-ZQUaYEd+m=XD}P2DD^[nLU]yt>QIIRGU=U}U-rk͡kg_ @b@=~V:L(G!o9qD$ LYe>eg("lrrQU\''tK!"u"겮Z:%gnN&fj_ wRre*ʦR)~o&ZsXs޼ys'U]ќ0 EnM2{&3yg{>``~,"B\_źt.}W:T5.ɡZ4222."._  Λ7uiA2pvC.A :dMMM_Ǯwa0|FUH{DD\ދc-;^%O]\#wJ˴~譢YXweN{2|(~$")yJJ|i[3C8hzJjhN$1wR=~|[=<388h0j7o޼9T"' "TC}]p*f@DJEN'yEXhii9eݥR/>f͚'$G&[Qjs^ED̒%a p/-\que osc̥]~;P[ZeYYyL0Yf*IR]U]ɃKJNPշ 8wcwl׈/wP(dm"(~544g""W1!<+'cR0U *4[I!1clP#EX(*FC0_Gt':#ߙ|Rՙ{j ;δ~՞DdR'|E(J-TNyZtR2ca٢EdnVUgLƭX,>Oj[SNw\hI&A|r``)[DĤRŪ>S]r5 Nmۖw> 2&}gŕQ!Px1#x7|2\aeppp8/WճU#"2  c"}!b |U!U|UIxďDv2U z| "T"9FD:dkƘat'BRU}MϜ Umlld_I͡Ole2̉G/<`i 2&ㅢ 'jڊ.Gȿ%!"( 0  ^,7Ƽ."r c<ȘzWǯ^a;-Q]eϛΨjd2y;;1K$?{gR(k1z*"~ $"?_r%sTjXeNS'L|Kڞk'Ss)t&ɡ666MMMx,|1PU$IRMED73U$k w/l?J_W5k3\>!ȿ?'_2:<*01{|6"򹑑q9ȮC\+Of(s._܂Υ?; f 7 IDATsmEDӲjo޼sД|ߴi'W*h"*;Gy#oL,3y~)oWfCئE_JTc>n:NUHD:0 nݺ1נ1ܜ˱UYd0 AV9X+spO\ tx5R2L9ܰd3~Ӗ*Zo04e488_j׷w"*/EcLla-[db"RTu DT ?xw \.RPdrZʞ=I9w2u!lpVA,W~߫*oԊroLDmandOW,",J1CQZf( HUo9s} Jj\ ɡL.(w2wNz0}q#kE*NHNUWnڴ);mYĻ1w} AmSqoADmhh(pxФ}|u@I͡`3_+K"F>wG+!ڻ0 WUovW{(:n'Sy Uk|||SH p&% ÛUsTU}JUϺyw=EѩCCCB~ZpVUlP5C\Hes;J%P 3C69bdY  wUP_V++#Ƙ6Ue 6xgSa^lU<."NQI&^} ר 0<ʚl2+cءQ1'*ThB`PW'fõ20 rǷ]S'f wra'vyqv% 3Ȗ\.wU$.w@DmڵOrwe<3/];D@EFř[-bQ&y@=/6n^`>7̽a#5?)0@J@vJu pE)rrJ29s|2[^Qͅ8aqޏ'E5CCCzFQtK}EUyMkN:餛";e0 oOo]~hH'uQA?!Sn ];"ޘ3N.P!βL L!knٹs~#H\"r\Ecz 7M/Z[[OQ՟xG>s].(kBD1Lg8w6WTutǎCsQXrܹ]DU|Ȳc9ޢ0TJ Ql^U =vC`uUZTlAϴ׳a2p=æҼ]zn̴` C4-k֬ys\""|3+\NRTH٨X Vg&ٝ?lmm=VU`!7x-ᅈy.ٰf͚G\pٱc"<ǚ2U݁K,r|Wke#"s\ThA tx~6EC '4Sm؇ÚrVfX3wWAn)7ׁLUo6" r:7"a~Μ9M")C, "[m>0೩T"&U=YUOAyzNUE3#,׸+: 3_,Cd&5HExT520v\6p 'v~hE3 TUNuWyTf.\8sllعrvN%/"/p~;N N 1u~lEG =iQ={vsCCÉ"xblmGܯc+ Xf\ZVZECgC_spǻ( N>]k{#9&̄忪EfT&ƭ/^<#GDQta1ڌMm "nT]]݆7""""FFFW͛wHD^diqZ|kEDUuKEOc6aXxSe)ϵ2iޗ#nu e> TtT`sX?32v/Q(Lq=5񃈈& os*Bɭ Q }\DtG&2t.,8W[oPhZ u"֛.DDDDDDTJn؟TPhENXM׵^7^)Tj]drNjHHnHDDDDDDRP%uu&gBbYu&'ٳ 눈ѫm׉[ʈhJ_+V7OxX^􍗠g~ψ:!E0qQ'N.&VFDDDDDDŵp2٢No';};lQՊkAaLOQu+}r(p09$TLT\a.LV6ݎ}\+8ڟRQ6hZJnՏ?sw@]CU{mיlCm׈&'ʈhJnభعvWNiV5ɩhzJnu3m6<\-?ڮ1EYP_.Z"<~=`\QFE/c!&*B!""""""5vߐ /軠j V|z7 -89DDDDDDD3搪n;Ȟf6X_ӊvzSh`lUF jňM˔C.nH " X)7/rSt֛oq ΂)pr<5_Q!- }SKTOɁyL9RʺJ O9ҋ.z:Sv>iFD\e&5mehPQ͡H]1jNsQg2Lj;F߼8f fټDDDDDDTVA] `wf|Z{h3V&"""""XZTZ.W]5wxwP\t]u 75Մ7U=kPh_̶d;GDdV!""""""ʊT#mqWY;G'CĮ9prb12cLDvȎCn2/]˥l2{;g?pBP#*5u\EDDDDDDa͡7Y%KuQϕl2%~wO~\s_-P@$|spՆ9TtrBsotXӺdSGU5U;ww,]!Kj*89DDDDDDDrs@[3]ֵ;}5ŁeG:窶F}e>iVN.y=e͡`Si\ل ݭߋ `,6%dNtV͔T&9a<DV5 M9}v?ggazE&~+=ɞ;&?/}gc*hv,p@ŵ2""""""˔CgRG5+CV[ṇ{ĴMeS|3${VL.a-z'"""""xL@j-o/ԯX*9b{No;D6yBUpv6=}wDaBP3!""""""Eb:XU7K 3":-ꪎ]]Yⴼeysz(}~R /@Tu|qn7ιT K-mHoN=xz_RQ P7W[A-2Uu4&}G!"""""0tIu4Ő)6 ]Qg]r4q&D#D${]Ƿt.}|Xq aDDDDDD94+]O҃VY-Y\Υ&/[co8E}q>wAqK5dFӷdpɱűJmMe; UXCp>q[=-OdrL.S;\)CZY>2ћ-BDDDDDD-I\ŕPLx] '\`Aȯ`}MD^;prOKOw""""""~VCPͦ$z?Lљ2HҗC[n7eC d1A6XkғHπbZ%: Ng_g"K_'/-?rˑX`fAv8ys}m Ei"`]}h$Y^@l2޹;gdh<:EU#dr=_hLβ0@ww""""""MNj-3˺:} a(^a|0!NOif[񝅈j3Wl;}~u?Yz,Y(Uv/e#E)=;6/*TSݝ@1#> :ݱdp}GIҗwemM=ɞ">sIE)2-P߼8H鯔d+=ndm|U]; Rߴ&ґK=ȥ_n$(}!""""""{su;D /cMx,S1dDDDDDDTʢ9_?7jxB;r&eE/11GJ |gٝBo&7BDDDDDD7eXvdχ&3K/˔5/3yFz1m=ɞ;|G!""""""ڗjLT.PngGUj_^^MΥ/'fβ;k^WyEDDDDDDkҕ^#};IDH .֗j\z@ g_ظl2 DDDDDDDR!nxw݉E)6˔ǵґK9 |gyɺ;;duskwj;D3Ly]^.hOD;Tu w;MV7 ̮]βWxX IDAT\Ą&!Hβgxk% AJU1 ܸ'svd.o2'˜;w=sZYboMfZԕ"*YvODԮT|g!""""""rCΛ̲ ۪9[n2 e>!8w=Ѣ3g DDDDDDDDshdb}gٝ L.s,6캑 (~`aςr;ȉj@%{'ȱVv|guKҹ7FeٲjTs!Ju DMU;S.;|lح>;{w#WYq?]ƴ`QA0H;M-FI*ݙٖ 7\PvΙ.lTh"$Pv.3/DP҅y|?vW<<ZtE壕rInnf{ cBS}}Lgty}ꆤ;qҠe!Ith";]R9LfoGwn:3t|_aI2y3[YΕN -ZzU3I.ْƛlcGnxn9]F7XCz2q.@8?I.5R寇n9%*mr^萷ɪSRN;~Q|g/SHơYJݥ}._/ntN5*WNy+Gz c=f]T}'ݓdv펋v4b:I9W~.t薓,?9tl}.9!Iia"׷8j`&t i8{ʹg.q-YGha t$NJϴg2tqw)) ^8xx+$ 2l{qx{Prt~SxAVKnH7?MIKt䏚ld⎚HrXZ1~|z.t q4򽑢=JM\t'k+L%-fSg.ɮs+[h$CP)4tl.ΪãF$9g'484O}}D7y[Z]~sҝ NQ1}F{lEv«դ'+t,Uߍi/r:?/[Z?%}awC,?nfC wC)-+)t ̀qh"EE[?F />fke +t\6+XXZ@O[Tٹ[7PSC lΚLLe[OṙơtpSg%],UqW!43ơ&wL]=:f8Tc|t7:Vme5V. InI=O&>0@prNlTժeHq+HqcŬofݡ[R%=)"ơ:wןywl}XRw`ZPd&Ysӡ[heCy -tK]Ew}zKShuVP9W.|_ R/.wHN@qx>vdsٯh:PJl|v= +tBs}-K[\VC]CϷutB, UatbJ<~U.Qr"[$oR0VI[̬!uIeO5{5ER?Pg㵲&0xd jQu%͇CM7=%im@'LKnmt 鸌aɡ&/7W어5 ҅qmJT9 G.wIw2P0CMpҨ=VƏiS].C-HoKvKT?򿫪uIO2R4P0GM]줿>2c3k aj1F%uGOe^6|<`qF XdI_R?7к~#KbIENDB`PKZacY-Pictures/10000C700000675A000026789364A0E4.svgVnF}e ½)vDu.@BK+L =3.Ml@@ gwgΜ,^],V(]Ͳ^].WLgHYiz]էx׶7- MqqwZo?ڛ+nY(VN{"(W7п0鮎AX\gOrs^ηht{{nZFVk=a],FzIE*v8Zӟֱ \a)  6Mr(#1XD4Aw&֐su3 DqIR$m+kDSf2)ֿ`!Ƭʁ^b$%Ksi"glT)̔<\h`FsDvQiC dMM #|s>H rZgJT|DZ_:{rmQMLRB$`)9"2Ďˁ2˂M_9yBȑK ~~ @]<6BO@&&F#%>hx:P4|RX=HC*~4h~MQ{S_!a2+J& B0P-x 9x*I* uj"ʖW \h]: N<$#0?CL|**ݑZVA$L ,`<]v>9OT* ([#X(*RZz!@`'1R1<_GMہ"Nr67([ ^=Z\ЂvLr ZH,稍*A·ȩQe,'cT<.R{x|8) ZIHyȅ8x#weWۈjSGq\S,ռ$;A29CQSjqp7 >ӟ PKdUp PKZacY content.xml]Ks@)ne)z{j;2nrۂHHB$ d9?!\k~tDQ2 u A>{~ɐ e;-|[8ܟ~|m[ϾꙘ͸Na/=+_;m-"hz,ym^ezLѪ6GXN]KodeYXf3\^ 7[G-3NuI"fh1163f!Ԑgi KB%ȏLRrA}[ 9-7@BUHnlc̙sބy9+\\{s/߱ehגt0£~AJ!/K. XzC y-QGH_Olt;6><jAU[0%_7]xC LcG&ޓ.D6t_YV:f<85:g #_Ϥ,$D~Js d!1KvߘRrKo  u]4$*## Y f=u.alWtw{*T*\\`875Sӊθ/u!ѼأL9)>CタQv+dBHwA]>f]&CWKv6%,%S4IIK$s멻{/}Kr[p5A&)R %``FW+DW.,*18N[T Rҙנ1`9zB% gr$^od{{LXP=,*xC;+ 5=\b1w Ӑh2DMW.C*bS]o62jnCDˑ` :||0j%R aw% u~cǗM0D5 },:d^.`^'8󝃇/4ҕ6ǮmC/ #-8/ ҡVX}!=6*ex e)ɡ,cVR0?mǁ!'FXNo6hrR,V쳶UiMw!vlLeT`n=M^g`,m]LvEsYp&Cyhp vU3ΫrjU,x:&x([DW`Jq30q$1_승ӥ2EJ,ӊ.*G:mD$SnKPvCH9\]%qOpY :H5e8 ~HfټD{[doqNLH1Ջ>~uc6~ Ŭ?~̆b{jFKz1 `R7f_:6x. KQ1K>,BQ4=lzD̚%4g MĬYB1kvDĚ%4g MĬYB1k|po%=K،KǤ`:Gua6zCmֳfy;ҳ<Kk F_BdWs1y՜?XF@Xփu2Həpnt:{YprtY j9HfJZ3kbugьx:z7>V+9Pz/Хy w'Q+ވeۋeg J(f7;Hs꣔<D7x9o߾wls[-Ahx_t^o+nng}ä,\7eNRDkRoM{cFԃfDŽ yhڃ&D?q4+w/ځG#SI{{AavۃqVK퉕QN[Б gu[glְnl}U7)wn"xÜ쏻pmW䚑=v_!bkw+pF`mJ3ʚv`qmgn<bpozI+'5YxH~{)x1h[ɦTӣkmawK{_TRk"Zw%lҶz'>^4 %y:+)꩕%<;N[* x?~:z;껥88M]@}A~6oj%r\A OL=Ta;d?/F0P:&z't]n%t3JgnM*BAvڶh%o%E>a/@`t@rP+9U5S:n}N~+:V'{r5:lE }M~)ò~忏Օ׵UQ]f72ʝ+7l7k>wrX<^Ta)s?HYH.%ӥ"x6YY*Y x*3"7JGe6"0 b+>É'FM'l,ZgH5hN ?q[< Zg3J./[ Ko<Í+;alQ[ Qy7(syS+ g3"u 03qe % T2V!{)/?V Š;ߥw4?g1ȧ?w&qƍ|{+{Zpt9Ϻ "Ff."4/>:ͫ~z>*f ?o(t+iqO7_Tז_;|wk?2?LdX]ln|̀)LČ`/ Mdmȿ*`RmvX䣧FWX}3> 9! UU΅3)<ܫ9}\ ~0 w-~"b@n!sfK.oXBzՂ rC6/y~:3O\SKO=ɓ߹'`3Zpߝe~wϓG'WRt ,(O7>#_SL\QmλCn$zȍS@/usAiYHJQcC/L}c0X:̾EƗ; X1_'3D}np1Iaz{pPK&SPKZacY settings.xmlZ[s~_vvTK.3PI@Bmބ-@,ye9@~id'Sm?u[u.=y']g_sl6Θ~=͗/W|<&6.9\䩏1 1:3+elu+l!+fWי;:V'~!%iԫϱHKW7}иSHQV'CqŽ},1ĕ%lj|u~/_,>2*֫NlenlN ζaGA]b]/Isת_Ğx!kLS@S"6=RSO.=>߿:va0Ԟ wD^nDv,=BR[H8ZDk"Zc`@&#TdT՘bXEY]0RcCۓޏ5fC.ŽJg,Ɣ[xej[_(ԡ0s1KK g hzD.3,CBic )P%^V/'rȺOPpu#!whbU󴹌N]*#F} ߢ2 '(^eE{{jTb>~=_&e ZsۥփxzRqCЁkMM9[${k +/"7|l zaEơA1ZgO vR%PGԩšv @A_籝)9ܼUͿ׼v{Gwfn%֭iL\{(aֹSFάlh=욗FVݰr&t^y=k^ޮ/(zQႂ}VlŚe{tlZkXlhK0tZюUaZZOf;a[[&zO ^+"`k$ fgHଇ(`ϳ>% Fs'AAJ5BE{Ii4PFq0V [>ZP2{ kb,Cj.~ v:kٱ]eߏkC8M5%Tv=1Nj^i+,\})2j&H0vzy 7'+wϷbps&y~ Nb8TopA^ _6 {h-%;,̖A3=.k`ףXYapF?6@]8d _y#U Pȃ `o| <QPMCW1;ncV!@I*:"Y/@*[;??PK(YnK-PKZacYmeta.xmlT[o0~ϯ@`lsIJSiRFj&-"üi?sKHMy|sY޿u-\\K]{s>͖phSri̱\Y>ZⲏJgwdR+ђ5Ykb(i 9c3c#]y}.Eq@*+/TҼNV)gkƊ ǣce&^?~oumS7FOVUΈQ ޒj}F[!Gb5W؟nmaء{yMgθgmjލڳ) $HQ(0zK3t(?u6G>{yǻ8t&]oN F̃YwLj<=ݠ-y`$wZ: >{6un{%י%dYAh@c$Dmf ɹa >k|8q}ٮz>Xia^$ '|μ{yX;a'ιI'Ju*k#վ;iVn].tfŏ-PK(PKZacY߭'5555Thumbnails/thumbnail.pngPNG  IHDR ;cPLTE ###+++333;;;CCCKKKSSS[[[ccckkksss{{{SSXXcckkrrvv{{Ԃ׉،ڒܚߡফ⫴崹繽=] IDATx}r6-sc<.I{LdVw$!;ZR@2@.LL20d`!C 20d`!C 20d`!C ow.Mk m}ݎFƪ3ԛG{`(e3f9yŬ|G+z f")֧&Il  p/!wXiа\t8}l}{#49xg73ϭzR?>~J/ $(^|il$ڱ07ͨ&:-엧75OY[hm;3|YVԍ[흚z>j|xe[$yl9!-?o.π%kh 8yn 8;.,f#cilys;|+uʒfb/J.Y$bpw^AˬJ-VG1UY% [&LIEk&XG%V8llE\Pʩ~UcaR륞Y0<.dI0_G)ݜd,0`g'" @v)M-X$ {ٲ̎NhDJ"feVē g.X8U[6D!ƧtMjTZ*Ͱ;|,*=2=>>5wPzQ[Lh/wa|N t|ƐJaFQq0ۮV4͡GX. }dǪB{CCЇ zvWJ@HE"U33*`ҭ!SĖ6d"v1 cLR^= >1j8@Т9L_E]лUr5/8TRa69\\]<$~8qbUy'f,jj33ਉD~}?1_[sqBĹ%WqȔU ˺wWqXFB9c`l YIJD&&lW1t<\58<[ȅHnH3],]x.csC"@#c׺X-W)HPtϕցYt2O #kly+ FA6c E=>սkynXCո4 Ưa8w>HS %mLj/jAQrP?Т_.՛8.uoIT|C4Do%' mUVp+s9ݺԸ2WbPGz/UHo͝X&uӴJ\(mM YVܖp?m̛[]5H2ZڄE:Dj"j9z+!ОviI^v)Plz̈LjfC  EcQO cs]L gZ cl,yS5 fc 6þЊ\ۺb*l) eo!8 ֋)חo:zn?-0'޵[Vp+g.yqq/Z N_uI|3zW}F>!d8Ч>,-g5cɕ]ԹTg38~20d`!C 20d`!C 20d``럌չ|h ChlFi`qمH{>w궛2v{^`5esIIv3¤5Q)ȍe}Yǜ מל_5#-8*XW77oy)9X =o*{8ܹs(䢰bb$z {{>j:\{݈h-#9Mx,+vI89`Nuȯ|9a;aXڤq< ){;;ג6rjD,*23QAu ٔ37B {ϣN'֮nIcPT-=V\C.b(9C -e1:|Lls(~0ݞ# [YxB[vci7 kcDv Zz:Ȃ@RWvqM>v ~UB b@k5; i)V;",2І,"& Seܵ ɸhcAMP Fh|w&اgu8C5~nE8;ҿ\ XG gYD ?B5bdL3rdam@w(>`\k{'V^^yScx1ef F=Tq$f@[('f8p \P1&qW袟-1_R7nrB5 Qq#O,fV2!Zg6HiLb%čc ' :5)zC-;l'EVGp;M4T 8@ ,J8.{b"3e݉,Hdq%g-8J 9)K ט+QD 1t@VZ{LvpnrgKsաRꏖvD_ 3J2. ĻP=FjƤr@${ Q<ij}W ^70x{#Q]96- 3Ȳ}(Rw jAsp0,흇Z u3h ڎ(MU"B"u@/7[8Q`f;]*t:6t)v`HU : ,.dܕ:0x!ՃvA(akkEm2"dsj*]QxB{**/ڋjӦ_yӴeæuqG2Ԡ1cqZ&,+O{C#FQ#dċ `T0DZd 17P ,ULn]rzg! fcjrYE+vPx6?> q'|1de`˶Ay. ,`(!L">3\e5{Z ~'Ԡ >p3zZ~YWu2=ZQ̪|bcOnjoୁ$RC9o %n,$pXn_ݰVTx쀷gN m7|'1eۛ0P7ݱVĿBw E6&v&3Q?'Z&Ip3L 20d`!_J# r<߱vʸLJ /חcXSCH+mcBp k%AsCk%2*I Q/5A-z]vowprqcM5Ygp )M,`EWMSL8 zaSxDP"6A#?G.|FX'.I)AKs!|I."@?;SV\e hއNN=B7 4U&Q;t_m,L~:FiD`DVʐ88R̭3_E-^UCДG̃fw=td8RlF.`9 Uq3+HD3}i}s}"xX%+S.TPN>ok'4eX8@si^@ w r9q:?2g_m B?<#V+)6wRV4B ;Tkc)aDi&j+arl_LNدN;TO(h jt9q R 3cvIKW{Fu-n!.c]/kuv7x^;c\`CDh]bl(C.ɦ  讦,-v9+0j@.4aΣ0nW=8`z8qaТ\/n՞@/Մlt@~tvч4i'uw$5@4d#]e=QRQE<#!|VPO^ h[w,rM(q=]pJ|WWU4ujfpS=32zX*OGI`,Mյ}(șj bA U9Q1' rƲ΄ZgZ. Զkp cRg(Ӏ|,=X`謨yWqp^4CAt,\^a:P]?%EGqky yv3Q7F3ܒ^Ī6ZN=).֨f٤*8yB ZLQ=jgE'Gh\1unTmu pgn-aFP|Г57F )(7/rKw]$*a ǟS._ơ&}S9 -_P-x5lx֤YYѷ2{UR|lzs200d`!{D+[KݭU=5DznlO*Y뗕PcO!f۫w)+U?jϒK3v]>9H.fBl;4{'9Q rpJ!ory$cA &0[ONTSo'9X,ϒ҈/м(!WYx ?6Lvۡ^Y%V:s3]((xQVio2jR%ƒuXxam,/ЯfW-f]>ma7ԇs6NpVT@}`F &u L"mr>7݋boP ؼ{ No2%(? L2k5 iBF =4ixJ~RH1 QbVdUCAxP>i0sĨb4E_e-o*IaD]QQ!F.Jgyi3 [XɋS!U%&u_ĜG>j/TlLF.pgz sqOJF rkk_0z'W {F*!+:h%ql1T(+Dv/U܅t8zN?9Ka P`8HЄQi7F3}8}%I6 F@ D߀pSyQ:s^_ZsSc!r ! ւˇ@ĿrT@}%h's a~&A::,;`' ="|je3;)|qVA" <Phcv>V=ِWhS.\Yr.ċE TMWB'ܘa9iǐs1#>˸Q20ܪpԅaQYN#/^jHQvv ُe LK>c NP.P:VʻC@J:xGZdmOˡvw&pBi9Lx֋| %S DݪiZMnr8 ּ-Gm6"bLxjk7tջ ,} ׏BRO8{zn?7:u="1*w0xr4| x!C 20C_nwCֶHZ}˴醞 {C<}.ˇMa9fvp1b쮣E~0n֏{Co9.O;`Cf9+eKqf ^;Y,ZExA(Ḵ^BīBlroٞ$À{zCN:M!{xv$^`,`DM_.re@=wpXV8f|r2@Wچ(1 ߝ낻0R4v9Ag{M5}G9dXu*`" \0G;Ws0d8!Cb-IDAT 20d`2 {*cJ ecuCJ3kW=4ڌ̭+d yloH |U1 p}1Ҡ8ÌQzeY;xd `a`I,umJQy.s6p7l̆8h;C C6 ,},aWf\䔥b&+Ёn yw af9@o%P"a#3`sT9ؘ S ]J rX#$1D >1 |8@̄1D_JD`AFn/±YD)9b|@d$rčX ѭCtN(,d(W6\37d ۃigJб 1suԬo(J 2ԝ'/ٕ1pϬ}DIw1<1T0"PƐP;wC 20d`#`]ujEw[^"Dz+evf#S {djT(p1DDA_;h)_"{D =bx/gBp1bĆ@Oo,Ez<h?l}D A,NT7wC9z{l}?%ң5LG v I57״G/L"x eGCІHT6% ;2b9L0*+3 ¾^z ǵge}G'BB2I}`ЛV(UbuV8NUpnTE#c9 sX:hRPm&/e KS 9YK,,hfxY]XJ]YT>M]DV c *Z.yIڙ6=4k#KdSJ$?|cȮ`,d'Ь+wxX]/sS{Hƣ ID#wXJN>r_ n`r)`1<5!xlTSuЉ+{ c:[aJLьՓ N^SD=r@u.KV5>(;Cu|pMM0H/ѲұuykȾ0Fy0 F> ;883gPdJ9f#zΠyKC1ƶ6䲼)K0 IUF94E:hʨ'ẜQPƬN/,h!]of$pd]_ `%2ׇ}݀VlZlq\Еvw]_d C s\BDCZ hHۇ#,F )\Xp>l)r8ֳ|hrPUG%->F‹ŖFe oKx#v%^FY8s*5"sq8= 52MjK wE'4( 5S( Wt4x U[8NuZ$08#bv* cGJts,H:"q ۾:_qy0]x0 ͠wKJ(A<D|-(oԪ}Ĉ4 dkC|._ lETdþ+yjd~%95'$UY85&|)l^viD@-0|"`v+$͸\Op8"p'A~`Amka*XK蠔@]b[:VsZ\{RV;#g7Q@,83P,U8U2j~lGkTS29h?F쁥 =jq`=x<{[n:.hf-b&j( S3Nrfʒ zbܖbNcGmS²J{<*) ]U˜%) bȷ!NiMA=".:O\VI; Ҳ0%kzŞXK(1 yn=,k8i1(@4V55*҂8 7^?"%٠9Kt}\< [X1xB$'˒.#k+ (XɎFCVkCtiD+F*鸁St9l'mLqO-ۦ X(ysY. {,h '{߱8(PcA0"+G`p 1]xT?Y\?6/Kn!s?, L(s=%L3 b2vY >u `ٲp8vNTdšDn(j٠ȷv\%J ?(|7?/B߿E0q`C~TbJ( ,ӽMqE_?)~tPbnɶdm!ĚQṬ>m-?Pbn0qc1WbӾ4\՝h?.~[eXAuP1Ƈh0cO\t"d b&t?1V?_/R[+>vC4rM%&i۔"Lo>xȱͽVʂ>N"LȚ.Y״kR/[%ӀqmA[$:"*_r;ŝEԉΝ*Wtř+OJDcl">~m1kfbz=@C%'Њ2\E؀P*??Ͽ.E<&i-&Q[5ɸ~1BzBrPyZj7U4Խ_?~C7_ 7JĝZq"K8DN~^N>n ,? P l"YZu[ EAU. (EɘVI i46ɼ`~ErXeՃ. a`LWJkEaz)tǿL7~N?~_Ӌ/?1_6] ` Ч!C?dyIENDB`PKZacYMETA-INF/manifest.xmlT]o0}W.~0dh{p? 6)-,ds=t:C11@*㲈vGh-J&ynsR_ZR4T45)ULiz2]:C .h++3f\ԼI1AUheZk 9x[!]M]W 7 bĪJD2í:jЍ!ZJ漰u{TOɝY-ia6GAkIੱ2i )J6 z ׯ dqA>1Y] [`--not-after=`] [`--verifications-out=`] [`--`] [...] < ## DESCRIPTION `sopv inline-verify` evaluates OpenPGP signatures bundled in a message. Its standard input can be either a OpenPGP Signed Message or a message signed with the OpenPGP Cleartext Signing Framework. If a valid signature is found, it returns 0 and emits the contents of the message (without any signatures) on standard output. If no valid OpenPGP signature is found, `sopv inline-verify` returns non-zero. ## EXAMPLES ``` if sopv inline-verify signer.cert < message.signed > message.txt; then echo "The information found in message.txt was signed" else rm message.txt echo "no valid signature found" fi ``` ## OPTIONS * `--not-before=`: Do not accept signatures made before the specified . Supply in ISO-8601 format, preferably in UTC (see `DATE` in sopv(1)). * `--not-after=`: Do not accept signatures made after the specified . Supply in ISO-8601 format, preferably in UTC (see `DATE` in sopv(1)). * `--verifications-out=`: If the caller wants to inspect the details of the valid signatures, it can use this argument to request those details. See `VERIFICATIONS` in sopv(1) for more details about this format. ## ARGUMENTS One or more arguments should point to OpenPGP certificates that would be acceptable signers. ## RETURN CODE `sopv inline-verify` returns 0 to to indicate that at least one valid signature was found. It may fail for other reason, but `NO_SIGNATURE` (3) is a likely failure mode when the message contains no valid signature from any of the . ## AUTHOR This manual page was written by Daniel Kahn Gillmor. Your implementation of `sopv` is likely written by someone else in alignment with the SOP specification. Please run `sopv version` to learn more about your implementation. ## SEE ALSO sopv(1), sopv-version(1), sopv-inline-verify(1), [Stateless OpenPGP Command Line Interface][draft-dkg-openpgp-stateless-cli], [RFC 9580][RFC9580] stateless-openpgp-docs-13.0/manpages/sopv-verify.1.ronn000066400000000000000000000052131475345515500232000ustar00rootroot00000000000000sopv-verify(1) -- Verify detached OpenPGP signatures on a message ================================================================= ## SYNOPSIS `sopv` [`--debug`] `verify` [`--not-before=`] [`--not-after=`] [`--`] [...] < ## DESCRIPTION `sopv verify` returns 0 if any valid OpenPGP detached signature is made over the data on standard input from one of the specified OpenPGP certificates. If no valid OpenPGP signature is found, `sopv verify` returns non-zero. It emits a stream of `VERIFICATIONS` (see `VERIFICATIONS` in sopv(1)) to standard output. ## EXAMPLES ``` if sopv verify message.sig signer.cert < message > /dev/null; then echo "message is signed" else echo "no valid signature found" fi ``` To implement a no-rollbacks mechanism (e.g. for software upgrades): ``` LASTSIGDATE=$(cat lastsigdate || echo '1970-01-01T00:00:00Z') rm -f verifs.out if sopv verify --not-before=$LASTSIGDATE $SIG author.cert < $PKG > verifs.out; then # do something with the now-verified "$PKG": # ... # prevent rollback to prior version: cut -f1 -d' ' < verifs.out | head -n1 > lastsigdate fi ``` ## OPTIONS * `--not-before=`: Do not accept signatures made before the specified . Supply in ISO-8601 format, preferably in UTC (see `DATE` in sopv(1)). * `--not-after=`: Do not accept signatures made after the specified . Supply in ISO-8601 format, preferably in UTC (see `DATE` in sopv(1)). ## ARGUMENTS `sopv verify` looks for OpenPGP signatures in the argument, either as a series of raw OpenPGP signature packets, or as an ASCII-armored series of OpenPGP signature packets. For the signatures to be verified, they must be made by one of the supplied over the message provided on standard input. Any Invalid or broken signature will be ignored, as will any signature made by an unknown signer. One or more arguments should point to OpenPGP certificates that would be acceptable signers. ## RETURN CODE `sopv verify` returns 0 to to indicate that at least one valid signature was found. It may fail for other reason, but `NO_SIGNATURE` (3) is a likely failure mode when none of the can be verified as being from any of the . ## AUTHOR This manual page was written by Daniel Kahn Gillmor. Your implementation of `sopv` is likely written by someone else in alignment with the SOP specification. Please run `sopv version` to learn more about your implementation. ## SEE ALSO sopv(1), sopv-version(1), sopv-inline-verify(1), [Stateless OpenPGP Command Line Interface][draft-dkg-openpgp-stateless-cli], [RFC 9580][RFC9580] stateless-openpgp-docs-13.0/manpages/sopv-version.1.ronn000066400000000000000000000043421475345515500233630ustar00rootroot00000000000000sopv-version(1) -- Get sopv version, build, and compatibility information ========================================================================= ## SYNOPSIS `sopv` [`--debug`] `version` [`--backend`\|`--extended`\|`--sop-spec`\|`--sopv`] ## DESCRIPTION `sopv version` emits version, build, and compatibility information about the local implementation of `sopv`. With no options supplied, `sopv version` produces a single line of text to standard output and exits 0. The line of output is separated by whitespace into two fields. The first field is the name of the `sopv` implementation and the second field is the version number. ## EXAMPLES ``` $ sopv version ExampleSop 5.2.3 $ sopv version --sopv 1.1 $ ``` ## OPTIONS At most one of the following options is allowed. * `--backend`: Emit a single line describing the primary backend of the tool and its version information. * `--extended`: Emit multiple lines of arbitrary build and configuration information. The first line of output will be the same as the line emitted when running `sopv version` with no options. This information is always useful when submitting a bug report about any `sopv` implementation. * `--sop-spec`: Indicate the targeted version of the sop [internet-draft][draft-dkg-openpgp-stateless-cli]. If the output begins with a `~`, it indicates that the targeted draft is incomplete. * `--sopv`: Indicate the level of compliance with `sopv`. If no level of `sopv` is fully supported, this will fail with `UNSUPPORTED_OPTION`. Otherwise, it will emit a level to standard output, like "1.0\n" or "1.1\n". See the VERSION HISTORY section of sopv(1) for more details. ## RETURN CODE `sopv version` returns 0 to to indicate success. If the implementation does not understand one of the variants, it will return the non-zero code for `UNSUPPORTED_OPTION`. ## AUTHOR This manual page was written by Daniel Kahn Gillmor. Your implementation of `sopv` is likely written by someone else in alignment with the SOP specification. Please run `sopv version` to learn more about your implementation. ## SEE ALSO sopv(1), sopv-verify(1), sopv-inline-verify(1), [Stateless OpenPGP Command Line Interface][draft-dkg-openpgp-stateless-cli], [RFC 9580][RFC9580] stateless-openpgp-docs-13.0/manpages/sopv.1.ronn000066400000000000000000000170541475345515500217040ustar00rootroot00000000000000sopv(1) -- Verify OpenPGP signatures ==================================== ## SYNOPSIS `sopv` [`--debug`] ## DESCRIPTION `sopv` is the verification-only subset of the OpenPGP Stateless Command Line Interface, also known as "SOP". `sopv` is designed to verify OpenPGP signatures. It can verify detached signatures as well as inline signatures. The caller indicates which signers are acceptable by supplying a set of OpenPGP certificates. ## EXAMPLES ``` $ sopv version ExampleSop 2.3.0 $ sopv verify libfoo-3.1.2.tgz.sig libfoo-keys.pgp < libfoo-3.1.2.tgz 2025-02-03T0:02:25Z 8CD219FC05D9DE9F3D59B784160B8EF5536B0D27 8CD219FC05D9DE9F3D59B784160B8EF5536B0D27 mode:binary {"signers":["libfoo-keys.pgp"]} $ sopv inline-verify --verifications-out=verifs.txt alice.cert < alice-message.csf This is a message from Alice $ cat verifs.txt 2025-02-13T00:04:49Z 63339423454CA210DAA886C08723C4D38E0802F6 F255F2A602AC1DFF2331085E5DAE32C783FC418D mode:text {"signers":["alice.cert"]} $ ``` To do something only when a detached signature is valid: ``` if sopv verify libfoo-3.1.2.tgz.sig libfoo-keys.pgp < libfoo-3.1.2.tgz > /dev/null; then # The software was signed correctly ... fi ``` To do something only when an inline signature is valid: ``` if sopv inline-verify alice.cert < alice-message.csf > alice.message; then # alice.message is data that was signed by alice ... fi ``` ## SUBCOMMANDS Exactly one subcommand must be supplied. * `sopv version`: Get version, build, and compatibility information about the `sopv` implementation. See sopv-version(1) for more information. * `sopv verify`: Verify detached OpenPGP signatures over a message. See sopv-verify(1) for more information. * `sopv inline-verify`: Verify an inline-signed OpenPGP message. See sopv-inline-verify(1) for more information. ## COMMON OPTIONS All `sopv` subcommands accept this option. * `--debug`: Emit more detailed output on standard error, if available. Each subcommand also has its own distinct options and arguments, see the corresponding manual page. ## INPUT DATA TYPES Some `sopv` subcommands take data types as inputs, either as arguments or on standard input. * `DATE`: Dates are represented directly in ISO-8601-compliant format, in UTC with the Z suffix. For example, `2025-02-12T05:22:33Z` * `CERTS`: A collection of OpenPGP certificates, also known as "Transferable Public Keys". Each `CERTS` input may be unarmored, or may use OpenPGP ASCII armor. * `SIGNATURES`: A collection of OpenPGP signatures. Each `SIGNATURES` input may be unarmored, or may use OpenPGP ASCII armor. * `INLINESIGNED`: An OpenPGP Signed Message or a text document that is signed internally with the OpenPGP Cleartext Signing Framework. An OpenPGP Signed Message input may be unarmored, or may use OpenPGP ASCII armor. ## VERIFICATIONS In some cases, `sopv` emits a `VERIFICATIONS` text stream, which contains a concise description of every valid OpenPGP signature discovered. Each line in a `VERIFICATIONS` stream represents a valid signature from an acceptable signer. There are at least three fixed fields which are separated from one another and the final optional fields by whitespace. The fields are, in order: * : The time of the signature, in ISO-8601 date format, in UTC. * : The fingerprint of the signing key (may be a primary key or a signing-capable subkey). * : The fingerprint of the primary key of the OpenPGP certificate that contains the signing key (may be the same as the signing key) * : This optional field is either `mode:text` or `mode:binary`. * : If is present, the final optional field extends to the end of the line, and is either free-form text, or a JSON object. If it starts with `{` it is a JSON object. The JSON object may contain a "`signers`" member, which is a JSON list of the names of each `CERTS` object that could have authored the signature. ## SPECIAL DESIGNATORS Wherever a `CERTS` or `SIGNATURES` or `VERIFICATIONS` object is pointed to on the command line, it is typically presented as a path to a filename. In addition, `sopv` should also accept a special designator, which is any string starting with a `@` character. There are two established kinds of special designator: * `@FD:`: This means to read from or write to the file descriptor identified numerically by . Note that this is also a valid argument for `--verifications-out` in `sop inline-verify`. It can be used there to operate `sopv` on an entirely read-only filesystem. * `@ENV:`: This means to read the value from the environment variable named . Note that typically only text-based data is transmittable in this way; a `CERTS` argument should be armored, for example. Note also that this can only be used for input to `sopv`, not output (as `sopv` cannot set the values in its callers' environment). If you want to refer to a file in the filesystem whose name actually begins with an `@` (for example, `@foo`), you should indicate that file to `sopv` using `./@foo` to avoid an `AMBIGUOUS_INPUT` error. ## VERSION HISTORY The `sopv` specification keeps a version history similar to [semantic versioning][semver]. Implementations indicate their compliance with a specific level of the spec with `sopv version --sopv` (see sopv-version(1)) * 1.0: The subcommands `version`, `verify`, and `inline-verify`. The common argument `--debug` (even if it produces no additional messages to stderr). `sopv version` arguments `--extended`, `--sop-spec`, `--backend`, `--sopv`. For `sopv verify` and `sopv inline-verify`, the arguments `--not-before` and `--not-after`. For `sopv inline-verify`, the `--verifications-out` argument. Special designators `@ENV:` and `@FD:` for any `CERTS` and `SIGNATURES` input. Special designator `@FD:` for any `VERIFICATIONS` output. At least the first three fields in any `VERIFICATIONS` output. * 1.1: Everything from 1.0. Additionally, the `VERIFICATIONS` output includes the fourth field (), and the final field of `VERIFICATIONS` is a JSON object containing at least a "`signers`" member as described above. ## RETURN CODES `sopv` indicates success by returning 0. A failure is indicated by returning any non-zero return code, often using values from the following table. Value | Mnemonic | Meaning ---:|----|---------------------------------------------- 0 | `OK` | Success 1 | `UNSPECIFIED_FAILURE` | An otherwise unspecified failure occurred 3 | `NO_SIGNATURE` | No acceptable signatures found 19 | `MISSING_ARG` | Missing required argument 37 | `UNSUPPORTED_OPTION` | Unsupported option 41 | `BAD_DATA` | Invalid data type (secret key where `CERTS` expected, etc) 59 | `OUTPUT_EXISTS` | Output file already exists 61 | `MISSING_INPUT` | Input file does not exist 69 | `UNSUPPORTED_SUBCOMMAND` | Unsupported subcommand 71 | `UNSUPPORTED_SPECIAL_PREFIX` | `sopv` does not know how to handle the special designator 73 | `AMBIGUOUS_INPUT` | A file with the name of the special designator is present Details about warnings or errors may also be emitted to standard error. ## AUTHOR This manual page was written by Daniel Kahn Gillmor. Your implementation of `sopv` is likely written by someone else in alignment with the SOP specification. Please run `sopv version` to learn more about your implementation. ## SEE ALSO sopv-version(1), sopv-verify(1), sopv-inline-verify(1), [Stateless OpenPGP Command Line Interface][draft-dkg-openpgp-stateless-cli], [RFC 9580][RFC9580] stateless-openpgp-docs-13.0/sop.h000066400000000000000000000376551475345515500170500ustar00rootroot00000000000000#ifndef __SOP_H__ #define __SOP_H__ #include #include #include #include #include /* C API for Stateless OpenPGP */ /* Depends on C99 */ /* statically-defined, non-opaque definitions */ typedef enum { SOP_OK = 0, SOP_INTERNAL_ERROR = 1, /* Not part of sop CLI */ SOP_INVALID_ARG = 2, /* Not part of sop CLI */ SOP_NO_SIGNATURE = 3, SOP_OPERATION_ALREADY_EXECUTED = 4, /* Not part of sop CLI */ SOP_UNSUPPORTED_ASYMMETRIC_ALGO = 13, SOP_CERT_CANNOT_ENCRYPT = 17, SOP_MISSING_ARG = 19, SOP_INCOMPLETE_VERIFICATION = 23, SOP_CANNOT_DECRYPT = 29, SOP_PASSWORD_NOT_HUMAN_READABLE = 31, SOP_UNSUPPORTED_OPTION = 37, SOP_BAD_DATA = 41, SOP_EXPECTED_TEXT = 53, SOP_OUTPUT_EXISTS = 59, SOP_MISSING_INPUT = 61, SOP_KEY_IS_PROTECTED = 67, SOP_UNSUPPORTED_SUBCOMMAND = 69, SOP_UNSUPPORTED_SPECIAL_PREFIX = 71, SOP_AMBIGUOUS_INPUT = 73, SOP_KEY_CANNOT_SIGN = 79, SOP_INCOMPATIBLE_OPTIONS = 83, SOP_UNSUPPORTED_PROFILE = 89, SOP_NO_HARDWARE_KEY_FOUND = 97, SOP_HARDWARE_KEY_FAILURE = 101, SOP_PRIMARY_KEY_BAD = 103, SOP_CERT_USERID_NO_MATCH = 107, SOP_KEY_CANNOT_CERTIFY = 109, /* ensures a stable size for the enum -- do not use! */ SOP_MAX_ERR = INT_MAX, } sop_err; typedef enum { SOP_SIGN_AS_BINARY = 0, SOP_SIGN_AS_TEXT = 1, /* ensures a stable size for the enum -- do not use! */ SOP_SIGN_AS_MAX = INT_MAX, } sop_sign_as; typedef enum { SOP_INLINE_SIGN_AS_BINARY = 0, SOP_INLINE_SIGN_AS_TEXT = 1, SOP_INLINE_SIGN_AS_CLEARSIGNED = 2, /* ensures a stable size for the enum -- do not use! */ SOP_INLINE_SIGN_AS_MAX = INT_MAX, } sop_inline_sign_as; typedef enum { SOP_ENCRYPT_AS_BINARY = 0, SOP_ENCRYPT_AS_TEXT = 1, /* ensures a stable size for the enum -- do not use! */ SOP_ENCRYPT_AS_MAX = INT_MAX, } sop_encrypt_as; /* FIXME: timestamps */ /* time_t is 32-bit on some architectures; we also want this to be able to represent a "none" value as well as a "now" value without removing some value from the range of time_t */ typedef time_t sop_time; #define sop_time_none ((sop_time)0) #define sop_time_now ((sop_time)-1) /* Context object * * Each SOP object is bound back to a context object, and, when used * in combination with other SOP objects, all SOP objects should come * from the same context. * * A SOP context object need not be thread-safe; it should probably * not be used across multiple threads. See "Zero global state" in * the README file in * https://git.kernel.org/pub/scm/linux/kernel/git/kay/libabc.git */ struct sop_ctx_st; typedef struct sop_ctx_st sop_ctx; sop_ctx* sop_ctx_new (); void sop_ctx_free (sop_ctx *sop); /* Logging: */ typedef enum { SOP_LOG_NEVER = 0, SOP_LOG_ERROR = 1, SOP_LOG_WARNING = 2, SOP_LOG_INFO = 3, SOP_LOG_DEBUG = 4, /* ensures a stable size for the enum -- do not use! */ SOP_LOG_MAX = INT_MAX, } sop_log_level; static inline const char * sop_log_level_name (sop_log_level log_level) { #define rep(x) if (log_level == SOP_LOG_ ## x) return #x rep(ERROR); rep(WARNING); rep(INFO); rep(DEBUG); #undef rep return "Unknown"; } /* Handle warnings and other feedback. * * A SOP implementation that is capable of producing log messages * will invoke the requested function with the log level of the * message, and a NULL-terminated UTF-8 human-readable string with * no trailing whitespace. * * the "passthrough" pointer is supplied by the library user via * sop_set_log_level. */ typedef void (*sop_log_func) (sop_log_level log_level, void *passthrough, const char *); sop_err sop_set_log_function (sop_ctx *sop, sop_log_func func, void *passthrough); /* Set the logging verbosity. * * Only log warnings up to max_level. (by default, max_level is * SOP_LOG_WARNING, meaning SOP_LOG_INFO and SOP_LOG_DEBUG will be * suppressed). */ sop_err sop_set_log_level (sop_ctx *sop, sop_log_level max_level); /* Information about the library: */ /* The name and version of the implementation of the C API (simple * NUL-terminated string, no newlines), or NULL if there is an error * producing the version. */ const char * sop_version (sop_ctx *sop); /* The name and version of the primary underlying OpenPGP toolkit * (or NULL if there is no backend, or if there was an error * producing the backend version) */ const char * sop_version_backend (sop_ctx *sop); /* Any arbitrary extended version information other than sop_ctx_version. Version info should be UTF-8 text, separated by newlines (a NUL-terminated string, no trailing newline). Can return NULL if there is nothing more to report beyond sop_version. */ const char * sop_version_extended (sop_ctx *sop); /* note: there is nothing comparable to sop version --sop-spec * because that should be visible based on the exported symbols in * the shared object */ /* PROFILE objects: */ /* These describe a profile (e.g. for generate-key or encrypt). * This use used when the implementation might legitimately want to * offer the user some minimal amount of control over what is done. * The profile-listing functions return blocks of four profiles. A * sop_profile value of NULL represents no profile at all. In a * list of sop_profile objects, once a NULL profile appears, no * non-NULL profiles may follow. */ struct sop_profile_st; typedef struct sop_profile_st sop_profile; /* the NUL-terminated string returned by sop_profile_name MUST be a UTF-8 encoded string, and MUST NOT include any whitespace or colon (`:`) characters. It MUST NOT vary depending on locale. */ const char * sop_profile_name (const sop_profile *profile); /* The NUL-terminated string returned by sop_profile_description cannot contain any newlines, and it MAY vary depending on locale(7) if the implementation is internationalized. */ const char * sop_profile_description (const sop_profile *profile); #define SOP_MAX_PROFILE_COUNT 4 typedef struct { sop_profile *profile[SOP_MAX_PROFILE_COUNT]; } sop_profiles; static inline int sop_profiles_count(const sop_profiles profiles) { for (int i = 0; i < SOP_MAX_PROFILE_COUNT; i++) if (profiles.profile[i] == NULL) return i; return SOP_MAX_PROFILE_COUNT; } /* Return a list of profiles supported by the library for generating * keys. */ sop_err sop_list_profiles_generate_key (sop_ctx *sop, sop_profiles *out); /* CLEARTEXT (and other raw data): */ /* This is a standard buffer for bytestrings produced by sop. Users never create this kind of object, but it is sometimes returned from the library. */ struct sop_buf_st; typedef struct sop_buf_st sop_buf; void sop_buf_free (sop_buf *buf); size_t sop_buf_size (const sop_buf *buf); const uint8_t * sop_buf_data (const sop_buf *buf); /* KEYS objects: */ struct sop_keys_st; typedef struct sop_keys_st sop_keys; sop_err sop_keys_from_bytes (sop_ctx *sop, const uint8_t* data, size_t len, sop_keys **out); sop_err sop_keys_to_bytes (const sop_keys *keys, bool armor, sop_buf **out); void sop_keys_free (sop_keys *keys); /* Generate a new, minimal OpenPGP Transferable secret key. `profile` can be NULL to mean the default profile. */ sop_err sop_generate_key_with_profile (sop_ctx *sop, sop_profile *profile, bool sign_only, sop_keys **out); static inline sop_err sop_generate_key (sop_ctx *sop, sop_keys **out) { return sop_generate_key_with_profile (sop, NULL, false, out); } /* For each key in the sop_keys object, add the given user ID, and return a new sop_keys object containing the updated keys. If the supplied user ID is not valid UTF-8 text, this call will fail and return SOP_EXPECTED_TEXT. If the implementation rejects the user ID string by policy for any other reason, this call will fail and return SOP_BAD_DATA. */ sop_err sop_keys_add_uid (const sop_keys *keys, const char *uid, sop_keys **out); /* returns true if any of the secret key material is currently locked with a password */ sop_err sop_keys_locked (const sop_keys *keys, bool *out); /* return a new sop_keys object with any secret key material encrypted with `password` unlocked, Returns SOP_OK if all keys have now been unlocked. If any locked key material could not be unlocked, return SOP_KEY_IS_PROTECTED, while also unlocking what key material can be unlocked. This allows the user to try an arbitrary bytestream as a password. Most users will just invoke the inlined sop_keys_unlock, below. An implementation MUST NOT reject proposed passwords by policy during unlock, but rather should try them as requested. */ sop_err sop_keys_unlock_raw (const sop_keys *keys, const uint8_t *raw_password, size_t len, sop_keys **out); static inline sop_err sop_keys_unlock (const sop_keys *keys, const char *password, sop_keys **out) { return sop_keys_unlock_raw (keys, (const uint8_t *)password, strlen (password), out); } /* return a new sop_keys object where all secret key material is locked with `password` where possible. During locking, a safety-oriented implementation MAY reject the supplied password by policy for any number of reasons. This helps libsop ensure that the proposed password can be successfully re-supplied during some future unlock attempt. If the implementation requires passwords to be UTF-8 text and the supplied password is not valid UTF-8, the implementation will fail, returning SOP_EXPECTED_TEXT. If an implementation rejects a supplied password for some other reason (for example, if it contains an NUL, unprintable, or otherwise forbidden character), this call will fail and return SOP_BAD_DATA. If any key material is already locked, it does nothing and returns SOP_KEY_IS_PROTECTED. Upon a successful locking, the user probably wants to use sop_keys_free to free the original keys object. */ sop_err sop_keys_lock_raw (const sop_keys *keys, const uint8_t *password, size_t len, sop_keys **out); static inline sop_err sop_keys_lock (const sop_keys *keys, const char *password, sop_keys **out) { return sop_keys_lock_raw (keys, (const uint8_t *)password, strlen (password), out); } /* CERTS objects: */ struct sop_certs_st; typedef struct sop_certs_st sop_certs; sop_err sop_certs_from_bytes (sop_ctx *sop, const uint8_t* data, size_t len, sop_certs **out); sop_err sop_certs_to_bytes (const sop_certs *certs, bool armor, sop_buf **out); void sop_certs_free (sop_certs *certs); /* Return the OpenPGP certificates ("Transferable Public Keys") that correspond to the OpenPGP Transferable Secret Keys. */ sop_err sop_keys_extract_certs (const sop_keys *keys, sop_certs **out); /* Return an OpenPGP revocation certificate for each Transferable Secret Key found in the input. */ sop_err sop_keys_revoke_keys (const sop_keys *keys, sop_certs **out); /* SIGNATURES objects: */ struct sop_sigs_st; typedef struct sop_sigs_st sop_sigs; sop_err sop_sigs_from_bytes (sop_ctx *sop, const uint8_t* data, size_t len, sop_sigs **out); sop_err sop_sigs_to_bytes (const sop_sigs *sigs, bool armor, sop_buf **out); void sop_sigs_free (sop_sigs *sigs); /* VERIFICATIONS (output only, describes valid, verified signatures): */ struct sop_verifications_st; typedef struct sop_verifications_st sop_verifications; void sop_verifications_free (sop_verifications *verifs); sop_err sop_verifications_count (const sop_verifications *verifs, int *out); /* textual representations of verifications, in the form described by VERIFICATIONS in the CLI */ sop_err sop_verifications_to_text (const sop_verifications *verifs, sop_buf **out); /* returns SOP_INTERNAL_ERROR if index is out of bounds. */ sop_err sop_verifications_get_time (const sop_verifications *verifs, int index, sop_time *out); /* returns SOP_INTERNAL_ERROR if index is out of bounds. If the signature is neither type 0x00 nor 0x01, this should probably not be considered a valid, verified signature. */ sop_err sop_verifications_get_mode (const sop_verifications *verifs, int index, sop_sign_as *out); /* returns SOP_INTERNAL_ERROR if index is out of bounds. */ sop_err sop_verifications_get_signer_count (const sop_verifications *verifs, int index, int *out); /* returns SOP_INTERNAL_ERROR if either verif_index or signer_index is out of bounds. Yields a pointer to the sop_certs object that could have made the signature. */ sop_err sop_verifications_get_signer (const sop_verifications *verifs, int verif_index, int signer_index, const sop_certs **out); /* FIXME: (do we want to get more detailed info programmatically? each verification should also have an issuing key fingerprint, a primary key fingerprint, and a trailing text string) */ /* create detached signatures: */ struct sop_op_sign_st; typedef struct sop_op_sign_st sop_op_sign; sop_err sop_op_sign_new (sop_ctx *sop, sop_op_sign** out); void sop_op_sign_free (sop_op_sign *sign); sop_err sop_op_sign_use_keys (sop_op_sign *sign, const sop_keys *keys); sop_err sop_op_sign_detached_execute (sop_op_sign *sign, sop_sign_as sign_as, const uint8_t *msg, size_t sz, sop_buf **micalg_out, sop_sigs **out); /* verify detached signatures: */ struct sop_op_verify_st; typedef struct sop_op_verify_st sop_op_verify; sop_err sop_op_verify_new (sop_ctx *sop, sop_op_verify** out); void sop_op_verify_free (sop_op_verify *verify); sop_err sop_op_verify_not_before (sop_op_verify *verify, sop_time when); sop_err sop_op_verify_not_after (sop_op_verify *verify, sop_time when); sop_err sop_op_verify_add_signers (sop_op_verify *verify, const sop_certs *signers); /* if no verifications are possible with the set of signers, this returns SOP_NO_SIGNATURE, and *out is set to NULL */ sop_err sop_op_verify_detached_execute (sop_op_verify *verify, const sop_sigs *sigs, const uint8_t *msg, size_t sz, sop_verifications **out); /* INLINESIGNED object: */ struct sop_inlinesigned_st; typedef struct sop_inlinesigned_st sop_inlinesigned; sop_err sop_inlinesigned_from_bytes (sop_ctx *sop, const uint8_t* data, size_t len, sop_inlinesigned **out); /* if the inlinesigned object uses the Cleartext Signing framework, * the armor parameter is ignored. */ sop_err sop_inlinesigned_to_bytes (const sop_inlinesigned *inlinesigned, bool armor, sop_buf **out); void sop_inlinesigned_free (sop_inlinesigned *inlinesigned); /* sop inline-sign */ sop_err sop_op_sign_inline_execute (sop_op_sign *sign, sop_inline_sign_as sign_as, const uint8_t *msg, size_t sz, sop_inlinesigned **out); /* sop inline-verify */ sop_err sop_op_verify_inline_execute (sop_op_verify *verify, const sop_inlinesigned *msg, sop_verifications **verifications_out, sop_buf **msg_out); /* sop inline-detach */ sop_err sop_inlinesigned_detach (const sop_inlinesigned *msg, sop_sigs **sigs_out, sop_buf **msg_out); #endif // __SOP_H__ stateless-openpgp-docs-13.0/sop.md000066400000000000000000003400441475345515500172060ustar00rootroot00000000000000--- title: Stateless OpenPGP Command Line Interface docname: draft-dkg-openpgp-stateless-cli-14 category: info ipr: trust200902 area: int workgroup: openpgp keyword: Internet-Draft submissionType: IETF stand_alone: yes pi: [toc, sortrefs, symrefs] author: - ins: D. K. Gillmor name: Daniel Kahn Gillmor org: American Civil Liberties Union street: 125 Broad St. city: New York, NY code: 10004 country: USA abbrev: ACLU email: dkg@fifthhorseman.net venue: group: "OpenPGP" type: "Working Group" mail: "openpgp@ietf.org" arch: "https://mailarchive.ietf.org/arch/browse/openpgp/" repo: "https://gitlab.com/dkg/openpgp-stateless-cli/" latest: "https://dkg.gitlab.io/openpgp-stateless-cli/" informative: OpenPGP-Interoperability-Test-Suite: target: https://tests.sequoia-pgp.org/ title: OpenPGP Interoperability Test Suite date: 2021-10-25 Charset-Switching: target: https://dkg.fifthhorseman.net/notes/inline-pgp-harmful/ title: Inline PGP Considered Harmful author: - ins: D. K. Gillmor name: Daniel Kahn Gillmor date: 2014-02-24 DISPLAYING-SIGNATURES: target: https://admin.hostpoint.ch/pipermail/enigmail-users_enigmail.net/2017-November/004683.html title: "On Displaying Signatures" author: - ins: P. Brunschwig name: Patrick Brunschwig EFAIL: target: https://efail.de title: "Efail: Breaking S/MIME and OpenPGP Email Encryption using Exfiltration Channels" author: - ins: D. Poddebniak name: Damian Poddebniak - ins: C. Dresen name: Christian Dresen PYTHON-SOP: target: https://pypi.org/project/sop/ title: SOP for python author: name: Daniel Kahn Gillmor ins: D. Gillmor RUST-SOP: target: https://sequoia-pgp.gitlab.io/sop-rs/ title: A Rust implementation of the Stateless OpenPGP Protocol author: name: Justus Winter ins: J. Winter org: Sequoia SEMVER: target: https://semver.org/ title: Semantic Versioning 2.0.0 date: 2013-06-18 author: name: Tom Preston-Werner ins: T. Preston-Werner SOP-JAVA: target: https://github.com/pgpainless/sop-java title: Stateless OpenPGP Protocol for Java. author: - name: Paul Schaub ins: P. Schaub UNICODE-NORMALIZATION: target: https://unicode.org/reports/tr15/ title: Unicode Normalization Forms date: 2019-02-04 author: name: Ken Whistler ins: K. Whistler org: Unicode Consortium OPENPGP-SMARTCARD: target: https://www.gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.pdf title: Functional Specification of the OpenPGP application on ISO Smart Card Operating Systems, Version 3.4 date: 2020-03-18 author: - name: Achim Pietig GNUPG-SECRET-STUB: target: "https://dev.gnupg.org/source/gnupg/browse/master/doc/DETAILS;gnupg-2.4.3$1511" title: GNU Extensions to the S2K algorithm date: 2023-07-04 author: - name: Werner Koch org: g10 Code DKG-SOP: title: "dkg-sop" author: - name: Heiko Stamer target: "https://git.savannah.nongnu.org/cgit/dkgpg.git/tree/tools/dkg-sop.cc" GOSOP: title: "gosop" author: - org: Proton target: "https://github.com/ProtonMail/gosop" GPGME-SOP: title: "gpgme-sop" author: - name: Justus Winter target: "https://gitlab.com/sequoia-pgp/gpgme-sop" PGPAINLESS-CLI: title: "pgpainless-cli" author: - name: Paul Schaub target: "https://codeberg.org/PGPainless/pgpainless/src/branch/master/pgpainless-sop" RNP-SOP: title: "rnp-sop" author: - name: Justus Winter target: "https://gitlab.com/sequoia-pgp/rnp-sop" RSOP: title: "rsop" author: - name: Heiko Schaefer target: "https://codeberg.org/heiko/rsop" SQOP: title: "sqop" author: - org: Sequoia PGP target: "https://gitlab.com/sequoia-pgp/sequoia-sop" SOP-OPENPGPJS: title: "sop-openpgp.js" author: - org: Proton target: "https://github.com/openpgpjs/sop-openpgpjs" SOPGPY: title: "sopgpy" author: - name: Daniel Kahn Gillmor target: "https://github.com/SecurityInnovation/PGPy/pull/440" --- abstract This document defines a generic stateless command-line interface for dealing with OpenPGP messages, certificates, and secret key material, known as `sop`. It aims for a minimal, well-structured API covering OpenPGP object security and maintenance of credentials and secrets. --- middle # Introduction Different OpenPGP implementations have many different requirements, which typically break down in two main categories: key/certificate management and object security. The purpose of this document is to provide a "stateless" interface that can handle both object security side and key and certificate management in a way that would be usable by applications across the full OpenPGP lifecycle. A priority for this interface is to facilitate interoperability testing for OpenPGP implementations, for example as described in {{test-suite}}. This document defines a generic stateless command-line interface for dealing with OpenPGP messages, secret keys, and certificates, known here by the placeholder `sop`. It aims for a minimal, well-structured API. An OpenPGP implementation should not name its executable `sop` to implement this specification. It just needs to provide a program that conforms to this interface. A `sop` implementation should leave no trace on the system, and its behavior should not be affected by anything other than command-line arguments and input. Inputs to `sop` are immutable inputs. Any named files that it receives as input should only need read access, and it must not write to or modify any of its inputs. The only places a `sop` implementation should write to are standard output and (in some special cases) a location specified by an `--*-out=` argument. Obviously, the user (or consuming application) will need to manage persistent secret keys and certificates somehow, but the goal of this interface is to separate out that task from the task of processing and handling OpenPGP objects. While this document identifies a command-line interface, the rough outlines of this interface should also be amenable to relatively straightforward library implementations in different languages. {{libsop}} offers a preliminary sketch of a C library interface that also has no implicit state. ## Requirements Language {::boilerplate bcp14-tagged} ## Terminology This document uses the term "key" to refer exclusively to OpenPGP Transferable Secret Keys (see {{Section 10.2 of !RFC9580}}). It uses the term "certificate" to refer to OpenPGP Transferable Public Key (see {{Section 10.1 of RFC9580}}). "Stateless" in "Stateless OpenPGP" means avoiding any sort of persistent and implicit state. The user is responsible for managing all OpenPGP certificates and secret keys themselves, and passing them to `sop` as needed. The user should also not be concerned that any state could affect the underlying operations. OpenPGP revocations can have "Reason for Revocation" ({{Section 5.2.3.31 of RFC9580}}), which can be either "soft" or "hard". The set of "soft" reasons is: "Key is superseded" and "Key is retired and no longer used". All other reasons (and revocations that do not state a reason) are "hard" revocations. This document refers to a special verification-only subset of the `sop` command-line interface as `sopv` (see {{sopv}} for more details). ## Using sop in a Test Suite {#test-suite} If an OpenPGP implementation provides a `sop` interface, it can be used to test interoperability (e.g., [OpenPGP-Interoperability-Test-Suite]). Such an interop test suite can, for example, use custom code (*not* `sop`) to generate a new OpenPGP object that incorporates new primitives, and feed that object to a stable of `sop` implementations, to determine whether those implementations can consume the new form. Or, the test suite can drive each `sop` implementation with a simple input, and observe which cryptographic primitives each implementation chooses to use as it produces output. A simple self-test can be found in {{simple-self-test}}. ## Semantics vs. Wire Format The semantics of `sop` are deliberately simple and very high-level compared to the vast complexity and nuance available within the OpenPGP specification. This reflects the perspective of nearly every piece of tooling that relies on OpenPGP to accomplish its task: most toolchains don't care about the specifics, they just want the high-level object security properties. Given this framing, this document generally tries to avoid overconstraining the details of the wire format objects emitted, or what kinds of wire format structures should be acceptable or unacceptable. This allows a test suite to evaluate and contrast the wire format choices made by different implementations in as close to their native configuration as possible. It also makes it easier to promote interoperability by ensuring that the native wire formats emitted by one implementation can be consumed by another, without relying on their choices of wire format being constrained by this draft. Where this draft does identify specific wire format requirements, that might be due to an ambiguity in the existing specifications (which maybe needs fixing elsewhere), or to a bug in this specification that could be improved. # Examples These examples show no error checking, but give a flavor of how `sop` might be used in practice from a shell. The key and certificate files described in them (e.g., `alice.sec`) could be for example those found in {{?I-D.draft-bre-openpgp-samples-01}}. ~~~ sop generate-key "Alice Lovelace " > alice.sec sop extract-cert < alice.sec > alice.pgp sop generate-key "Bob Babbage " > bob.sec sop extract-cert < bob.sec > bob.pgp sop sign --as=text alice.sec < statement.txt > statement.txt.asc sop verify statement.txt.asc alice.pgp < statement.txt sop encrypt --sign-with=alice.sec bob.pgp < msg.eml > ciphertext.asc sop decrypt bob.sec < ciphertext.asc > cleartext.eml ~~~ See {{failure-modes}} for more information about errors and error handling. # `sopv` Subset {#sopv} While the primary goal of this document is to provide a full `sop` interface, as a special case, an implementer may choose to produce a version of the command-line interface that only supports OpenPGP signature verification. As a shorthand, this document refers to such an interface as `sopv`, or "the `sopv` subset". This can be useful for constrained environments where the only thing needed is signature verification, for example, system installation or update media. A full implementation of `sop` by definition provides `sopv`, of course. Only the following subcommands and their associated options MUST be implemented for `sopv`: - `version` ({{version}}) - `verify` ({{verify}}) - `inline-verify` ({{inline-verify}}) ## `sopv` Versioning The abstract `sopv` interface is itself versioned using {{SEMVER}}. The definition of the relevant subcommands and options specified in this document is known as `sopv` version 1.0. If backward-incompatible changes are made to the `sopv` subset, the major version number will be increased. If the `sopv` subset is extended without backward-incompatible changes, the minor version number will be increased. Elements of the CLI relevant to `sopv` are annotated in this document with the `sopv` version in which they were introduced. See also {{sopv-changelog}} for enumerated version history. # Universal Options Every invocation of `sop` or `sopv` MAY use the options described in this section, even though they are not specified in the synopsis for any specific subcommand. ## --debug: emit more verbose output When the `--debug` option is present, `sop` MAY emit implementation-specific debugging information to standard error. A locale-aware, internationalized `sop` implementation will localize this debugging information. # Subcommands `sop` uses a subcommand interface, similar to those popularized by systems like `git` and `svn`. If the user supplies a subcommand that `sop` does not implement, it fails with `UNSUPPORTED_SUBCOMMAND`. If a `sop` implementation does not handle a supplied option for a given subcommand, it fails with `UNSUPPORTED_OPTION`. All subcommands that produce OpenPGP material on standard output produce ASCII-armored ({{Section 6 of !RFC9580}}) objects by default (except for `sop dearmor`). These subcommands have a `--no-armor` option, which causes them to produce binary OpenPGP material instead. All subcommands that accept OpenPGP material on input should be able to accept either ASCII-armored or binary inputs (see {{optional-input-armoring}}) and behave accordingly. See {{indirect-types}} for details about how various forms of OpenPGP material are expected to be structured. ## Meta Subcommands The subcommands grouped in this section are related to the `sop` implementation itself. ### version: Version Information {#version} sop version [--backend|--extended|--sop-spec|--sopv] - Standard Input: ignored - Standard Output: version information This subcommand emits version information as UTF-8-encoded text. With no arguments, the version string emitted should contain the name of the `sop` implementation, followed by a single space, followed by the version number. A `sop` implementation should use a version number that respects an established standard that is easily comparable and parsable, like {{SEMVER}}. If `--backend` is supplied, the implementation should produce a comparable line of implementation and version information about the primary underlying OpenPGP toolkit. If `--extended` is supplied, the implementation may emit multiple lines of version information. The first line MUST match the information produced by a simple invocation, but the rest of the text has no defined structure. If `--sop-spec` is supplied, the implementation should emit a single line of text indicating the latest version of this draft that it targets, for example, `draft-dkg-openpgp-stateless-cli-06`. If the implementation targets a specific draft but the implementer knows the implementation is incomplete, it should prefix the draft title with a ~, for example: `~draft-dkg-openpgp-stateless-cli-06`. The implementation MAY emit additional text about its relationship to the targeted draft on the lines following the versioned title. If `--sopv` is supplied, the implementation should produce a single line with the implemented {{SEMVER}} version of the `sopv` interface subset (see {{sopv}}) that this implementation provides complete coverage for. If the implementation does not provide complete coverage for any `sopv` interface, it should emit nothing on standard out and return `UNSUPPORTED_OPTION`. `--backend`, `--extended`, `--sop-spec`, and `--sopv` are mutually-exclusive options. Example: $ sop version ExampleSop 0.2.1 $ sop version --backend LibExamplePGP 3.4.2 $ sop version --extended ExampleSop 0.2.1 Running on MonkeyScript 4.5 LibExamplePGP 3.4.2 LibExampleCrypto 3.1.1 LibXCompression 4.0.2 See https://pgp.example/sop/ for more information $ sop version --sop-spec ~draft-dkg-openpgp-stateless-cli-06 This implementation does not handle @FD: special designators for output. $ sop version --sopv 1.0 $ The `version` subcommand and all of its options are part of the `sopv` subset (see {{sopv}}) starting at `sopv` version 1.0. ### list-profiles: Describe Available Profiles {#list-profiles} sop list-profiles SUBCOMMAND - Standard Input: ignored - Standard Output: `PROFILELIST` ({{profilelist}}) This subcommand emits a list of profiles supported by the identified subcommand. The first profile listed is the default profile, as described in {{profilelist}}. If the indicated `SUBCOMMAND` does not accept a `--profile` option, it returns `UNSUPPORTED_PROFILE`. Example: $ sop list-profiles generate-key default: use the implementer's recommendations security: higher-security, maybe reduced performance performance: higher-performance, maybe reduced security rfc4880: use algorithms from RFC 4880 (alias: compatibility) $ ## Key and Certificate Management Subcommands The subcommands grouped in this section are primarily intended to manipulate keys and certificates. ### generate-key: Generate a Secret Key {#generate-key} sop generate-key [--no-armor] [--with-key-password=PASSWORD] [--profile=PROFILE] [--signing-only] [--] [USERID...] - Standard Input: ignored - Standard Output: `KEYS` ({{keys}}) Generate a single default OpenPGP key with zero or more User IDs. The generated secret key SHOULD be usable for as much of the `sop` functionality as possible. In particular: - It should be possible to extract an OpenPGP certificate from the key in `KEYS` with `sop extract-cert`. - The key in `KEYS` should be able to create signatures (with `sop sign`) that are verifiable by using `sop verify` with the extracted certificate. - Unless the `--signing-only` parameter is supplied, the key in `KEYS` should be able to decrypt messages (with `sop decrypt`) that are encrypted by using `sop encrypt` with the extracted certificate. The detailed internal structure of the certificate is left to the discretion of the `sop` implementation. If the `--with-key-password` option is supplied, the generated key will be password-protected (locked) with the supplied password. Note that `PASSWORD` is an indirect data type from which the actual password is acquired ({{indirect-types}}). See also the guidance on ensuring that the password is human-readable in {{generating-human-readable}}. If no `--with-key-password` option is supplied, the generated key will be unencrypted. If the `--profile` argument is supplied and the indicated `PROFILE` is not supported by the implementation, `sop` will fail with `UNSUPPORTED_PROFILE`. The presence of the `--signing-only` option is intended to create a key that is only capable of signing, not decrypting. This is useful for deployments where only signing and verification are necessary. If any of the `USERID` options are not valid `UTF-8`, `sop generate-key` fails with `EXPECTED_TEXT`. If the implementation rejects any `USERID` option that is valid `UTF-8` (e.g., due to internal policy, see {{userid}}), `sop generate-key` fails with `BAD_DATA`. Example: $ sop generate-key 'Alice Lovelace ' > alice.sec $ head -n1 < alice.sec -----BEGIN PGP PRIVATE KEY BLOCK----- $ ### change-key-password: Update a Key's Password {#change-key-password} sop change-key-password [--no-armor] [--new-key-password=PASSWORD] [--old-key-password=PASSWORD...] - Standard Input: `KEYS` ({{keys}}) - Standard Output: `KEYS` ({{keys}}) The output will be the same set of OpenPGP Transferable Secret Keys as the input, but with all secret key material locked according to the password indicated by the `--new-key-password` option (or, with no password at all, if `--new-key-password` is absent). Note that `--old-key-password` can be supplied multiple times, and each supplied password will be tried as a means to unlock any locked key material encountered. It will normalize a Transferable Secret Key to use a single password even if it originally had distinct passwords locking each of the subkeys. If any secret key packet is locked but cannot be unlocked with any of the supplied `--old-key-password` arguments, this subcommand should fail with `KEY_IS_PROTECTED`. Example: # adding a password to an unlocked key: $ sop change-key-password --new-key-password=@ENV:keypass \ < unlocked.key > locked.key # removing a password: $ sop change-key-password --old-key-password=@ENV:keypass \ < locked.key > unlocked.key # changing a password: $ sop change-key-password --old-key-password=@ENV:keypass \ --new-key-password=@ENV:newpass < locked.key > refreshed.key $ ### revoke-key: Create a Revocation Certificate {#revoke-key} sop revoke-key [--no-armor] [--with-key-password=PASSWORD...] - Standard Input: `KEYS` ({{keys}}) - Standard Output: `CERTS` ({{certs}}) Generate a revocation certificate for each Transferable Secret Key found. See {{Section 10.1.2 of RFC9580}} for a discussion of common forms of revocation certificate. Example: $ sop revoke-key < alice.key > alice-revoked.pgp $ ### extract-cert: Extract a Certificate from a Secret Key {#extract-cert} sop extract-cert [--no-armor] - Standard Input: `KEYS` ({{keys}}) - Standard Output: `CERTS` ({{certs}}) The output should contain one OpenPGP certificate in `CERTS` per OpenPGP Transferable Secret Key found in `KEYS`. There is no guarantee what order the `CERTS` will be in. `sop extract-cert` SHOULD work even if any of the keys in `KEYS` is password-protected. Example: $ sop extract-cert < alice.sec > alice.pgp $ head -n1 < alice.pgp -----BEGIN PGP PUBLIC KEY BLOCK----- $ ### update-key: Keep a Secret Key Up-To-Date {#update-key} sop update-key [--no-armor] [--signing-only] [--no-added-capabilities] [--with-key-password=PASSWORD...] [--merge-certs=CERTS...] - Standard Input: `KEYS` ({{keys}}) - Standard Output: `KEYS` ({{keys}}) The input OpenPGP Transferable Secret Keys that arrive on standard input will be updated by the implementation, and their updated forms will be produced on standard output. This update will "fix" everything that the implementation knows how to fix to bring each Transferable Secret Key up to reasonable modern practice. Each Transferable Secret Key output must be capable of signing, and (unless `--signing-only` is provided) capable of decryption. The primary key of each Transferable Secret Key will not be changed in any way that affects its fingerprint. One important aspect of `sop update-key` is how it handles advertisement of support for various OpenPGP capabilities (algorithms, mechanisms, etc). All capabilities that the implementation knows it does not support, or knows to be weak and/or deprecated MUST be removed from the output Transferable Secret Keys. This includes unknown/deprecated flags in the Features subpacket, and any unknown/deprecated algorithm IDs in algorithm preferences subpackets. For example, an implementation compliant with {{RFC9580}} will never emit a Transferable Secret Key with a Preferred Hash Preferences subpacket that explicitly indicates support for `MD5`, `RIPEMD160`, or `SHA1`. If `--no-added-capabilities` is not present, then any capability that the implementation supports and encourages that was not advertised in the input Transferable Secret Key MAY be added to the advertisements in the output Transferable Secret Key. If `--no-added-capabilities` is present, then new capabilities MUST NOT be added to the advertised sets during the update. Beyond cleanup of the advertised capabilities, `--signing-only`, and `--no-added-capabilities`, the choice of exactly what updates to do are up to the implementation. It is expected that an implementer will document and describe the specific considerations and updates they make for this operation. It is acceptable to propagate any non-critical unknown subpackets from old self-signatures to the new, replacement self-signatures. Possible updates might include: - Refresh or replace any subkey approaching expiry. - Refresh any self-signature (including cross-sigs) that is approaching expiry. - Refresh any self-signature (including cross-sigs) that is made using weak or risky algorithms. - Correct any mistaken 2-octet hash prefix found in a signature (see {{Section 5.2.3 of RFC9580}}). - Ensure proof of "aliveness": if no self-signatures are more recent than some cutoff in the recent past, re-issue the same self-signatures. If there is nothing to be updated because all the incoming Transferable Secret Keys are already in good shape, then the same set of Transferable Secret Keys will be emitted to standard output and `sop update-key` succeeds. If any Transferable Secret Key cannot be fixed (for example, because its primary key uses a weak algorithm, or because the whole certificate is hard-revoked), `sop update-key` fails with `PRIMARY_KEY_BAD`, emits an explanation on stderr, and nothing on stdout. If any secret key that needs to make a signature to update the key cannot be unlocked with any of the supplied `PASSWORD` objects, `sop update-key` fails with `KEY_IS_PROTECTED`, emits an explanation on stderr, and nothing on stdout. If `--merge-certs` is supplied, and any of the `CERTS` objects correspond to the Transferable Secret Keys being updated, then any additional elements found in the corresponding `CERTS` are merged into the Transferable Secret Key before it is emitted. This can be used, for example, to absorb a third-party certification into the Transferable Secret Key. Example (keeping certificates fresh): $ sop update-key < alice.key > alice-updated.key $ mv alice-updated.key alice.key $ sop extract-cert < alice.key > alice.pgp $ Example (advertising the intersection of features supported by two Stateless OpenPGP implementations, rendered here as `sop1` and `sop2`): $ sop1 update-key < alice.key | sop2 update-key | \ sop1 --no-added-capabilities update-key > alice-updated.key $ mv alice-updated.key alice.key $ sop1 extract-cert < alice.key > alice.pgp $ ### merge-certs: Merge OpenPGP Certificates {#merge-certs} sop merge-certs [--no-armor] [--] CERTS [CERTS...] - Standard Input: `CERTS` ({{certs}}) - Standard Output: `CERTS` ({{certs}}) The OpenPGP certificates on standard input will be produced on standard output, merged with the corresponding elements of any of the `CERTS` objects named on the command line. This can be used, for example, to absorb a third-party certification into a certificate, or to update a certificate's feature advertisements without losing local annotations. The certificates produced on standard output are only the certificates received on standard input. If any certificate found via named command line parameters does not share a primary key with any standard input certificate, the certificate from the command line is ignored. If any of the OpenPGP certificates on standard input share the same primary key, they are also merged and de-deduplicated on standard output. If multiple OpenPGP certificates named on the command line share a primary key with one of the certificates on standard input, their certificate updates are cumulatively merged for output. Example: $ sop merge-certs alice-certified-by-bob.pgp \ < alice.pgp > alice-updated.pgp $ mv alice-updated.pgp alice.pgp $ ## User Identity Subcommands The subcommands in this section handle OpenPGP user identities. OpenPGP certificates contain cryptographic certifications which bind text-based "User IDs" to primary key material, which is in turn cryptographically bound to additional key material. These subcommands are related to the network of cryptographic identity assertions that has traditionally been called the "Web of Trust". Note also the similarity in structure between these subcommands and `sop sign` ({{sign}}) and `sop verify` ({{verify}}) ### certify-userid: Certify OpenPGP Certificate User IDs {#certify-userid} sop certify-userid [--no-armor] --userid=USERID [--userid=USERID...] [--with-key-password=PASSWORD...] [--no-require-self-sig] [--] KEYS [KEYS...] - Standard Input: `CERTS` ({{certs}}) - Standard Output: `CERTS` ({{certs}}) With each Transferable Secret Key in all `KEYS` objects, add a third-party certification to `CERTS` found on standard input, and emit the updated OpenPGP certificates (including the new certification(s)) on standard output. If the caller does not specify at least one `--userid=USERID` option, `sop certify-userid` fails with `MISSING_ARG`. If the certification-capable key of any Transferable Secret Key in `KEYS` is locked and cannot be unlocked by any of the supplied `PASSWORD`s, fail with `KEY_IS_PROTECTED`. If any incoming `CERTS` object does not already have all of the specified User IDs as valid, self-signed User IDs, then `sop certify-userid` fails with `CERT_USERID_NO_MATCH`, unless `--no-require-self-sig` is supplied. If `--no-require-self-sig` is supplied, then each incoming OpenPGP certificate will have each specified User ID added to it (if it did not have it already), and certified directly, regardless of self-signatures. This may be useful for associating a certificate with a specific identity even in cases where the certificate does not itself advertise the identity. If any key in the `KEYS` objects is not capable of producing a certification, `sop sign` will fail with `KEY_CANNOT_CERTIFY`. Example: $ sop certify-userid \ --userid="Alice Lovelace " \ bob.key < alice.pgp > alice-signed-by-bob.pgp $ Example (adding a User ID to your own certificate): $ sop certify-userid \ --userid="Alice Lovelace " \ alice.key < alice.pgp > alice-updated.pgp $ sop update-key --merge-certs alice-updated.pgp \ < alice.key > alice-updated.key $ mv alice-updated.key alice.key $ rm alice-updated.pgp $ sop extract-cert < alice.key > alice.pgp $ ### validate-userid: Validate a User ID in an OpenPGP Certificate {#validate-userid} sop validate-userid [--addr-spec-only] [--validate-at=DATE] [--] USERID CERTS [CERTS...] - Standard Input: `CERTS` ({{certs}}) - Standard Output: none Given a set of authority OpenPGP certificates on the command line, succeed if and only if all OpenPGP certificates on standard input are correctly bound by at least one valid signature from one authority to the `USERID` in question. If `--addr-spec-only` is present, then the `USERID` is treated as an e-mail address, and matched only against the e-mail address part of each correctly bound User ID. The rest of each correctly bound User ID is ignored. If any correctly bound User ID is not a conventional OpenPGP User ID, it will not match with `--addr-spec-only` at all. Note that {{RFC9580}} (and {{?RFC4880}} and {{?RFC2440}} before them) mislabeled an OpenPGP User ID as a `name-addr`, but that is likely to be wrong. If `--validate-at` is present, then evaluate the validity of the User ID at the specified time. If `--validate-at` is not present (or if it is present with the literal value `now`), the User ID validity is evaluated at the current time. If any OpenPGP certificate in the `CERTS` on standard input does not have a correctly bound User ID that matches `USERID`, `sop validate-userid` fails with `CERT_USERID_NO_MATCH`. Example: $ if sop validate-userid "Alice Lovelace " \ bob.pgp < alice.pgp; then echo Good; fi Good $ if sop validate-userid --addr-spec-only "alice@openpgp.example" \ bob.pgp < alice.pgp; then echo Good; fi Good $ ## Messaging Subcommands The subcommands in this section handle OpenPGP messages: encrypting, decrypting, signing, and verifying. ### sign: Create Detached Signatures {#sign} sop sign [--no-armor] [--micalg-out=MICALG] [--with-key-password=PASSWORD...] [--as={binary|text}] [--] KEYS [KEYS...] - Standard Input: `DATA` ({{data}}) - Standard Output: `SIGNATURES` ({{signature}}) Exactly one signature will be made by each key in the supplied `KEYS` arguments. `--as` defaults to `binary`. If `--as=text` and the input `DATA` is not valid `UTF-8` ({{utf8}}), `sop sign` fails with `EXPECTED_TEXT`. `--as=binary` SHOULD result in OpenPGP signatures of type 0x00 ("Signature of a binary document"). `--as=text` SHOULD result in OpenPGP signatures of type 0x01 ("Signature of a canonical text document"). See {{Section 5.2.1 of RFC4880}} for more details. When generating PGP/MIME messages ({{!RFC3156}}), it is useful to know what digest algorithm was used for the generated signature. When `--micalg-out` is supplied, `sop sign` emits the digest algorithm used to the specified `MICALG` file in a way that can be used to populate the `micalg` parameter for the Content-Type (see {{micalg}}). If the specified `MICALG` file already exists in the filesystem, `sop sign` will fail with `OUTPUT_EXISTS`. When `--micalg-out` is supplied, the `DATA` on standard input should already be in canonical text form (7-bit clean, CRLF line endings, no trailing whitespace), as specified in {{Section 3 of RFC3156}}. If the incoming `DATA` does not already meet these requirements, `sop sign` will fail with `EXPECTED_TEXT`, regardless of any argument supplied for `--as`. When `--micalg-out` is supplied, and multiple signatures are made but they do not all use the same digest algorithm, `sop sign` MUST emit the empty string to the designated `MICALG`. If the signing key material in any key in the `KEYS` objects is password-protected, `sop sign` SHOULD try all supplied `--with-key-password` options to unlock the key material until it finds one that enables the use of the key for signing. If none of the `PASSWORD` options unlock the key (or if no such option is supplied), `sop sign` will fail with `KEY_IS_PROTECTED`. Note that `PASSWORD` is an indirect data type from which the actual password is acquired ({{indirect-types}}). Note also the guidance for retrying variants of a non-human-readable password in {{consuming-passwords}}. If any key in the `KEYS` objects is not capable of producing a signature, `sop sign` will fail with `KEY_CANNOT_SIGN`. `sop sign` MUST NOT produce any extra signatures beyond those from `KEYS` objects supplied on the command line. Example: $ sop sign --as=text alice.sec < message.txt > message.txt.asc $ head -n1 < message.txt.asc -----BEGIN PGP SIGNATURE----- $ ### verify: Verify Detached Signatures {#verify} sop verify [--not-before=DATE] [--not-after=DATE] [--] SIGNATURES CERTS [CERTS...] - Standard Input: `DATA` ({{data}}) - Standard Output: `VERIFICATIONS` ({{verifications}}) `--not-before` and `--not-after` indicate that signatures with dates outside certain range MUST NOT be considered valid. `--not-before` defaults to the beginning of time. Accepts the special value `-` to indicate the beginning of time (i.e., no lower boundary). `--not-after` defaults to the current system time (`now`). Accepts the special value `-` to indicate the end of time (i.e., no upper boundary). `sop verify` only returns `OK` if at least one certificate included in any `CERTS` object made a valid signature in the time window specified over the `DATA` supplied. For details about the valid signatures, the user MUST inspect the `VERIFICATIONS` output. If no `CERTS` are supplied, `sop verify` fails with `MISSING_ARG`. If no valid signatures are found, `sop verify` fails with `NO_SIGNATURE`. In this case, `sop verify` MAY emit some human-readable explanation to standard error about why no valid signatures were found, see {{explaining-non-verification}}. See {{signature-verification}} for more details about signature verification. Example: (In this example, we see signature verification succeed first, and then fail on a modified version of the message.) $ sop verify message.txt.asc alice.pgp < message.txt 2019-10-29T18:36:45Z EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E mode:text {"signers": ["alice.pgp"]} $ echo $? 0 $ tr a-z A-Z < message.txt | sop verify message.txt.asc alice.pgp $ echo $? 3 $ The `verify` subcommand and all of its options are part of the `sopv` subset (see {{sopv}}) starting at `sopv` version 1.0. ### encrypt: Encrypt a Message {#encrypt} sop encrypt [--as={binary|text}] [--no-armor] [--with-password=PASSWORD...] [--sign-with=KEYS...] [--with-key-password=PASSWORD...] [--profile=PROFILE] [--session-key-out=SESSIONKEY] [--] [CERTS...] - Standard Input: `DATA` ({{data}}) - Standard Output: `CIPHERTEXT` ({{ciphertext}}) `--as` defaults to `binary`. The setting of `--as` corresponds to the one octet format field found in the Literal Data packet at the core of the output `CIPHERTEXT`. If `--as` is set to `binary`, the octet is `b` (`0x62`). If it is `text`, the format octet is `u` (`0x75`). `--with-password` enables symmetric encryption (and can be used multiple times if multiple passwords are desired). `--sign-with` creates exactly one signature by for each secret key found in the supplied `KEYS` object (this can also be used multiple times if signatures from keys found in separate files are desired). If any key in any supplied `KEYS` object is not capable of producing a signature, `sop sign` will fail with `KEY_CANNOT_SIGN`. If any signing key material in any supplied `KEYS` object is password-protected, `sop encrypt` SHOULD try all supplied `--with-key-password` options to unlock the key material until it finds one that enables the use of the key for signing. If none of the `--with-key-password=PASSWORD` options can unlock any locked signing key material (or if no such option is supplied), `sop encrypt` will fail with `KEY_IS_PROTECTED`. All signatures made must be placed inside the encryption produced by `sop encrypt`. Note that both `--with-password` and `--with-key-password` supply `PASSWORD` arguments, but they do so in different contexts which are not interchangeable. A `PASSWORD` supplied for symmetric encryption (`--with-password`) MUST NOT be used to try to unlock a signing key (`--with-key-password`) and a `PASSWORD` supplied to unlock a signing key MUST NOT be used to symmetrically encrypt the message. Regardless of context, each `PASSWORD` argument is presented as an indirect data type from which the actual password is acquired ({{indirect-types}}). If `sop encrypt` encounters a password which is not a valid `UTF-8` string ({{utf8}}), or is otherwise not robust in its representation to humans, it fails with `PASSWORD_NOT_HUMAN_READABLE`. If `sop encrypt` sees trailing whitespace at the end of a password, it will trim the trailing whitespace before using the password. See {{human-readable-passwords}} for more discussion about passwords. If `--as` is set to `binary`, then `--sign-with` will sign as a binary document (OpenPGP signature type `0x00`). If `--as` is set to `text`, then `--sign-with` will sign as a canonical text document (OpenPGP signature type `0x01`). In this case, if the input `DATA` is not valid `UTF-8` ({{utf8}}), `sop encrypt` fails with `EXPECTED_TEXT`. If `--sign-with` is supplied for input `DATA` that is not valid `UTF-8`, `sop encrypt` MAY sign as a binary document (OpenPGP signature type `0x00`). `sop encrypt` MUST NOT produce any extra signatures beyond those from `KEYS` objects identified by `--sign-with`. The resulting `CIPHERTEXT` should be decryptable by the secret keys corresponding to every certificate included in all `CERTS`, as well as each password given with `--with-password`. If no `CERTS` or `--with-password` options are present, `sop encrypt` fails with `MISSING_ARG`. If at least one of the identified certificates requires encryption to an unsupported asymmetric algorithm, `sop encrypt` fails with `UNSUPPORTED_ASYMMETRIC_ALGO`. If at least one of the identified certificates is not encryption-capable (e.g., revoked, expired, no encryption-capable flags on primary key and valid subkeys), `sop encrypt` fails with `CERT_CANNOT_ENCRYPT`. If the `--profile` argument is supplied and the indicated `PROFILE` is not supported by the implementation, `sop` will fail with `UNSUPPORTED_PROFILE`. The use of a profile for this subcommand allows an implementation faced with parametric or algorithmic choices to make a decision coarsely guided by the operator. For example, when encrypting with a password, there is no knowledge about the capabilities of the recipient, and an implementation may prefer cryptographically modern algorithms, or it may prefer more broad compatibility. In the event that a known recipient (i.e., one of the `CERTS`) explicitly indicates a lack of support for one of the features preferred by the indicated profile, the implementation SHOULD conform to the recipient's advertised capabilities where possible. If `--session-key-out` argument is supplied, the session key generated for this encrypted will be written to the indicated location. This can be useful, for example, when Alice encrypts a message to Bob, but also wants to retain the ability to read it without having any of her own secret key material available (see {{Section 9.1 of ?I-D.ietf-lamps-e2e-mail-guidance-11}}). If `sop encrypt` fails for any reason, it emits no `CIPHERTEXT`. Example: (In this example, `bob.bin` is a file containing Bob's binary-formatted OpenPGP certificate. Alice is encrypting a message to both herself and Bob.) $ sop encrypt --as=text --sign-with=alice.key \ alice.asc bob.bin < message.eml > encrypted.asc $ head -n1 encrypted.asc -----BEGIN PGP MESSAGE----- $ ### decrypt: Decrypt a Message {#decrypt} sop decrypt [--session-key-out=SESSIONKEY] [--with-session-key=SESSIONKEY...] [--with-password=PASSWORD...] [--with-key-password=PASSWORD...] [--verifications-out=VERIFICATIONS [--verify-with=CERTS...] [--verify-not-before=DATE] [--verify-not-after=DATE] ] [--] [KEYS...] - Standard Input: `CIPHERTEXT` ({{ciphertext}}) - Standard Output: `DATA` ({{data}}) The caller can ask `sop` for the session key discovered during decryption by supplying the `--session-key-out` option. If the specified file already exists in the filesystem, `sop decrypt` will fail with `OUTPUT_EXISTS`. When decryption is successful, `sop decrypt` writes the discovered session key to the specified file. `--with-session-key` enables decryption of the `CIPHERTEXT` using the session key directly against the `SEIPD` packet. This option can be used multiple times if several possible session keys should be tried. `SESSIONKEY` is an indirect data type from which the actual `sessionkey` value is acquired ({{indirect-types}}). `--with-password` enables decryption based on any `SKESK` ({{Section 5.3 of RFC9580}}) packets in the `CIPHERTEXT`. This option can be used multiple times if the user wants to try more than one password. `--with-key-password` lets the user use password-protected (locked) secret key material. If the decryption-capable secret key material in any key in the `KEYS` objects is password-protected, `sop decrypt` SHOULD try all supplied `--with-key-password` options to unlock the key material until it finds one that enables the use of the key for decryption. If none of the `--with-key-password` options unlock the key (or if no such option is supplied), and the message cannot be decrypted with any other `KEYS`, `--with-session-key`, or `--with-password` options, `sop decrypt` will fail with `KEY_IS_PROTECTED`. Note that the two kinds of `PASSWORD` options are for different domains: `--with-password` is for unlocking an `SKESK`, and `--with-key-password` is for unlocking secret key material in `KEYS`. `sop decrypt` SHOULD NOT apply the `--with-key-password` argument to any `SKESK`, or the `--with-password` argument to any `KEYS`. Each `PASSWORD` argument is an indirect data type from which the actual password is acquired ({{indirect-types}}). If `sop decrypt` tries and fails to use a password supplied by a `PASSWORD`, and it observes that there is trailing `UTF-8` whitespace at the end of the password, it will retry with the trailing whitespace stripped. See {{consuming-passwords}} for more discussion about consuming password-protected key material. `--verifications-out` produces signature verification status to the designated file. If the designated file already exists in the filesystem, `sop decrypt` will fail with `OUTPUT_EXISTS`. The return code of `sop decrypt` is not affected by the results of signature verification. The caller MUST check the returned `VERIFICATIONS` to confirm signature status. An empty `VERIFICATIONS` output indicates that no valid signatures were found. If no valid signatures were found, but `--verifications-out` was supplied, `sop decrypt` MAY emit some human-readable explanation to standard error about why no valid signatures were found, see {{explaining-non-verification}}. `--verify-with` identifies a set of certificates whose signatures would be acceptable for signatures over this message. If the caller is interested in signature verification, both `--verifications-out` and at least one `--verify-with` must be supplied. If only one of these options is supplied, `sop decrypt` fails with `INCOMPLETE_VERIFICATION`. `--verify-not-before` and `--verify-not-after` provide a date range for acceptable signatures, by analogy with the options for `sop verify` (see {{verify}}). They should only be supplied when doing signature verification. See {{signature-verification}} for more details about signature verification. If no `KEYS` or `--with-password` or `--with-session-key` options are present, `sop decrypt` fails with `MISSING_ARG`. If unable to decrypt, `sop decrypt` fails with `CANNOT_DECRYPT`. `sop decrypt` only emits cleartext to Standard Output that was successfully decrypted. Example: (In this example, Alice stashes and reuses the session key of an encrypted message.) $ sop decrypt --session-key-out=session.key \ alice.sec < ciphertext.asc > cleartext.out $ ls -l ciphertext.asc cleartext.out -rw-r--r-- 1 user user 321 Oct 28 01:34 ciphertext.asc -rw-r--r-- 1 user user 285 Oct 28 01:34 cleartext.out $ sop decrypt --with-session-key=session.key \ < ciphertext.asc > cleartext2.out $ diff cleartext.out cleartext2.out $ #### Historic Options for sop decrypt The `sop decrypt` option `--verifications-out` used to be named `--verify-out`. An implementation SHOULD accept either form of this option, and SHOULD produce a deprecation warning to standard error if the old form is used. ### inline-detach: Split Signatures from an Inline-Signed Message {#inline-detach} sop inline-detach [--no-armor] --signatures-out=SIGNATURES - Standard Input: `INLINESIGNED` - Standard Output: `DATA` (the message without any signatures) In some contexts, the user may expect an inline-signed message of some form or another (`INLINESIGNED`, see {{inlinesigned}}) rather than a message and its detached signature. `sop inline-detach` takes such an inline-signed message on standard input, and splits it into: - the potentially signed material on standard output, and - a detached signature block to the destination identified by `--signatures-out` Note that no cryptographic verification of the signatures is done by this subcommand. Once the inline-signed message is separated, verification of the detached signature can be done with `sop verify`. If no `--signatures-out` is supplied, `sop inline-detach` fails with `MISSING_ARG`. Note that there may be more than one Signature packet in an inline-signed message. All signatures found in the inline-signed message will be emitted to the `--signatures-out` destination. If the inline-signed message uses the Cleartext Signature Framework, it may be dash-escaped (see {{Section 7.2 of RFC9580}}). The output of `sop detach-inband-signature-and-message` will have any dash-escaping removed. If the input is not an `INLINESIGNED` message, `sop inline-detach` fails with `BAD_DATA`. If the input contains more than one object that could be interpreted as an `INLINESIGNED` message, `sop inline-detach` also fails with `BAD_DATA`. A `sop` implementation MAY accept (and discard) leading and trailing data when the incoming `INLINESIGNED` message uses the Cleartext Signature Framework. If the file designated by `--signatures-out` already exists in the filesystem, `sop detach-inband-signature-and-message` will fail with `OUTPUT_EXISTS`. Note that `--no-armor` here governs the data written to the `--signatures-out` destination. Standard output is always the raw message, not an OpenPGP packet. Example: $ sop inline-detach --signatures-out=Release.pgp < InRelease >Release $ sop verify Release.pgp archive-keyring.pgp < Release $ ### inline-verify: Verify an Inline-Signed Message {#inline-verify} sop inline-verify [--not-before=DATE] [--not-after=DATE] [--verifications-out=VERIFICATIONS] [--] CERTS [CERTS...] - Standard Input: `INLINESIGNED` ({{inlinesigned}}) - Standard Output: `DATA` ({{data}}) This command is similar to `sop verify` ({{verify}}) except that it takes an `INLINESIGNED` message (see {{inlinesigned}}) and produces the message body (without signatures) on standard output. It is also similar to `sop inline-detach` ({{inline-detach}}) except that it actually performs signature verification. `--not-before` and `--not-after` indicate that signatures with dates outside certain range MUST NOT be considered valid. See {{verify}} for their syntax and defaults. `sop inline-verify` only returns `OK` if `INLINESIGNED` contains at least one valid signature made during the time window specified by a certificate included in any `CERTS` object. For details about the valid signatures, the user MUST inspect the `VERIFICATIONS` output. If no `CERTS` are supplied, `sop inline-verify` fails with `MISSING_ARG`. If no valid signatures are found, `sop inline-verify` fails with `NO_SIGNATURE` and emits nothing on standard output. In this case, `sop inline-verify` MAY emit some human-readable explanation to standard error about why no valid signatures were found, see {{explaining-non-verification}}. See {{signature-verification}} for more details about signature verification. Example: (In this example, we see signature verification succeed first, and then fail on a modified version of the message.) $ sop inline-verify -- alice.pgp < message.txt Hello, world! $ echo $? 0 $ sed s/Hello/Goodbye/ < message.txt | sop inline-verify -- alice.pgp $ echo $? 3 $ The `inline-verify` subcommand and all of its options are part of the `sopv` subset (see {{sopv}}) starting at `sopv` version 1.0. ### inline-sign: Create an Inline-Signed Message {#inline-sign} sop inline-sign [--no-armor] [--with-key-password=PASSWORD...] [--as={binary|text|clearsigned}] [--] KEYS [KEYS...] - Standard Input: `DATA` ({{data}}) - Standard Output: `INLINESIGNED` ({{inlinesigned}}) Exactly one signature will be made by each key in the supplied `KEYS` arguments. The generated output stream will be an inline-signed message, by default producing an OpenPGP "Signed Message" packet stream. `--as` defaults to `binary`. If `--as=` is set to either `text` or `clearsigned`, and the input `DATA` is not valid `UTF-8` ({{utf8}}), `sop inline-sign` fails with `EXPECTED_TEXT`. `--as=binary` SHOULD result in OpenPGP signatures of type 0x00 ("Signature of a binary document", see {{Section 5.2.1.1 of RFC9580}}). `--as=text` SHOULD result in an OpenPGP signature of type 0x01 ("Signature of a canonical text document" see {{Section 5.2.1.2 of RFC9580}}). `--as=clearsigned` SHOULD behave the same way as `--as=text` except that it produces an output stream using the Cleartext Signature Framework (see {{Section 7 of RFC9580}} and {{csf-risks}}). If both `--no-armor` and `--as=clearsigned` are supplied, `sop inline-sign` fails with `INCOMPATIBLE_OPTIONS`. If the signing key material in any key in the `KEYS` objects is password-protected, `sop inline-sign` SHOULD try all supplied `--with-key-password` options to unlock the key material until it finds one that enables the use of the key for signing. If none of the `PASSWORD` options unlock the key (or if no such option is supplied), `sop inline-sign` will fail with `KEY_IS_PROTECTED`. Note that `PASSWORD` is an indirect data type from which the actual password is acquired ({{indirect-types}}). Note also the guidance for retrying variants of a non-human-readable password in {{consuming-passwords}}. If any key in the `KEYS` objects is not capable of producing a signature, `sop inline-sign` will fail with `KEY_CANNOT_SIGN`. `sop inline-sign` MUST NOT produce any extra signatures beyond those from `KEYS` objects supplied on the command line. Example: $ sop inline-sign --as=clearsigned alice.sec \ < message.txt > message-signed.txt $ head -n5 < message-signed.txt -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 This is the message. -----BEGIN PGP SIGNATURE----- $ ## Transport Subcommands The commands in this section handle manipulating OpenPGP objects for transport: armoring and dearmoring for 7-bit cleanness and compactness, respectively. ### armor: Convert Binary to ASCII sop armor - Standard Input: OpenPGP material (`SIGNATURES`, `KEYS`, `CERTS`, `CIPHERTEXT`, or `INLINESIGNED`) - Standard Output: the same material with ASCII-armoring added, if not already present `sop armor` inspects the input and chooses the label appropriately, based on the OpenPGP packets encountered. `sop armor` ought to be able to correctly re-armor any of the packet streams that are produced by `sop` with the `--no-armor` option. For example, if the type of the first OpenPGP packet is: - `0x05` (Secret-Key), the packet stream should be parsed as a `KEYS` input (with Armor Header `BEGIN PGP PRIVATE KEY BLOCK`). - `0x06` (Public-Key), the packet stream should be parsed as a `CERTS` input (with Armor Header `BEGIN PGP PUBLIC KEY BLOCK`). - `0x01` (Public-key Encrypted Session Key) or `0x03` (Symmetric-key Encrypted Session Key), the packet stream should be parsed as a `CIPHERTEXT` input (with Armor Header `BEGIN PGP MESSAGE`). - `0x04` (One-Pass Signature), the packet stream should be parsed as an `INLINESIGNED` input (with Armor Header `BEGIN PGP MESSAGE`). - `0x02` (Signature), the packet stream may be either a `SIGNATURES` input or an `INLINESIGNED` input. If the packet stream contains only Signature packets, it should be parsed as a`SIGNATURES` input (with Armor Header `BEGIN PGP SIGNATURE`). If it contains any packet other than a Signature packet, it should be parsed as an `INLINESIGNED` input (with Armor Header `BEGIN PGP MESSAGE`). If the input packet stream does not match any expected sequence of packet types, `sop armor` fails with `BAD_DATA`. Since `sop armor` accepts ASCII-armored input as well as binary input, this operation is idempotent on well-structured data. A caller can use this subcommand blindly to ensure that any well-formed OpenPGP packet stream is 7-bit clean. FIXME: what to do if the input is a CSF `INLINESIGNED` message? Three choices: - Leave it untouched -- this violates the claim about blindly ensuring 7-bit clean, since UTF-8-encoded message text is not necessarily 7-bit clean. - Convert to ASCII-armored `INLINESIGNED` -- this requires synthesis of OPS packet (from signatures block) and Literal Data packet (from the message body). - Raise a specific error. Example: $ sop armor < bob.bin > bob.pgp $ head -n1 bob.pgp -----BEGIN PGP PUBLIC KEY BLOCK----- $ #### Historic Options for sop armor `sop armor` used to be specified as having a `--label` option, with an argument that took one of the following values: `auto`, `sig`, `key`, `cert`, or `message`, which allowed the user to specify the label used in the header and tail of the armoring. The default value for `--label` was `auto`, which matches the currently specified behavior. This option is now deprecated, as it offers no useful functionality. ### dearmor: Convert ASCII to Binary sop dearmor - Standard Input: OpenPGP material (`SIGNATURES`, `KEYS`, `CERTS`, `CIPHERTEXT`, or `INLINESIGNED`) - Standard Output: the same material with any ASCII-armoring removed If the input packet stream does not match any of the expected sequence of packet types, `sop dearmor` fails with `BAD_DATA`. See also {{optional-input-armoring}}. Since `sop dearmor` accepts binary-formatted input as well as ASCII-armored input, this operation is idempotent on well-structured data. A caller can use this subcommand blindly ensure that any well-formed OpenPGP packet stream is in its standard binary representation. FIXME: what to do if the input is a CSF `INLINESIGNED`? Three choices: - Leave it untouched -- output data is not really in binary format. - Convert to binary-format `INLINESIGNED` -- this requires synthesis of OPS packet (from CSF `Hash` header) and Literal Data packet (from the message body). - Raise a specific error. Example: $ sop dearmor < message.txt.asc > message.txt.sig $ # Input String Types Some material is passed to `sop` directly as a string on the command line. ## DATE {#date} An ISO-8601 formatted timestamp with time zone, or the special value `now` to indicate the current system time. Examples: - `now` - `2019-10-29T12:11:04+00:00` - `2019-10-24T23:48:29Z` - `20191029T121104Z` In some cases where used to specify lower and upper boundaries, a `DATE` value can be set to `-` to indicate "no time limit". A flexible implementation of `sop` MAY accept date inputs in other unambiguous forms. Note that whenever `sop` emits a timestamp (e.g., in {{verifications}}) it MUST produce only a UTC-based ISO-8601 compliant representation with a resolution of one second, using the literal `Z` suffix to indicate timezone. ## USERID {#userid} This is an arbitrary `UTF-8` string ({{utf8}}). By convention, most User IDs are of the form `Display Name `, but they do not need to be. By internal policy, an implementation MAY reject a `USERID` if there are certain `UTF-8` strings it declines to work with as a User ID. For example, an implementation may reject the empty string, or a string with characters in it that it considers problematic. Of course, refusing to create a particular User ID does not prevent an implementation from encountering such a User ID in its input. ## SUBCOMMAND {#subcommand} This is an `ASCII` string that matches the name of one of the subcommands listed in {{subcommands}}. ## PROFILE {#profile} Some `sop` subcommands can accept a `--profile` option, which takes as an argument the name of a profile. A profile name is a UTF-8 string that has no whitespace in it. Which profiles are available depends on the `sop` implementation. Similar to OpenPGP Notation names, profile names are divided into two namespaces: the IETF namespace and the user namespace. A profile name in the user namespace ends with the `@` character (0x40) followed by a DNS domain name. A profile name in the IETF namespace does not have an `@` character. A profile name in the user space is owned and controlled by the owner of the domain in the suffix. A `sop` implementation that implements a user profile but does not own the domain in question SHOULD hew as closely as possible to the semantics described by the owner of the domain. A profile name in the IETF namespace that begins with the string `rfc` should have semantics that hew as closely as possible to the referenced RFC. Similarly, a profile name in the IETF namespace that begins with the string `draft-` should have semantics that hew as closely as possible to the referenced Internet Draft. The reserved profile name `default` in the IETF namespace simply refers to the implementation's default choices. It is not mandatory to name the default profile `default`. The first profile listed in the `list-profiles` output is considered the default configuration, as specified in {{profilelist}}. The reserved profile names `security`, `performance`, and `compatibility` refer to the implementation's choices when increased emphasis on security, performance or compatibility is required, respectively. It is not mandatory to name any profile `security`, `performance`, or `compatibility`; in that case, those profile names MUST act as aliases of another profile name. They are also allowed to be aliases of the default profile. Note that this profile mechanism is intended to provide a limited way for an implementation to select among a small set of options that the implementer has vetted and is satisfied with. It is not intended to provide an arbitrary channel for complex configuration, and a `sop` implementation MUST NOT use it in that way. # Input/Output Indirect Types {#indirect-types} Some material is passed to `sop` indirectly, typically by referring to a filename containing the data in question. This type of data may also be passed to `sop` on Standard Input, or delivered by `sop` to Standard Output. If any input data is specified explicitly to be read from a file that does not exist, `sop` will fail with `MISSING_INPUT`. If any input data does not meet the requirements described below, `sop` will fail with `BAD_DATA`. ## Special Designators for Indirect Types {#special-designators} An indirect argument or parameter that starts with @ is not treated as a filename, but is reserved for special handling, based on the prefix that follows the `@`. We describe two of those prefixes (`@ENV:` and `@FD:`) here. A `sop` implementation that receives such a special designator but does not know how to handle a given prefix in that context MUST fail with `UNSUPPORTED_SPECIAL_PREFIX`. See {{special-designators-guidance}} for more details about safe handling of these special designators. ### @ENV: Special Designator for Environment Variable If the filename for any indirect material used as input has the special form `@ENV:xxx`, then contents of environment variable `$xxx` is used instead of looking in the filesystem. `@ENV` is for input only: if the prefix `@ENV:` is used for any output argument, `sop` fails with `UNSUPPORTED_SPECIAL_PREFIX`. The `sopv` subset (see {{sopv}}) MUST be capable of supporting the `@ENV` special designator for all relevant inputs starting at `sopv` version 1.0. ### @FD: Special Designator for File Descriptor If the filename for any indirect material used as either input or output has the special form `@FD:nnn` where `nnn` is a decimal integer, then the associated data is read from file descriptor `nnn`. On platforms which support file descriptors, the `sopv` subset (see {{sopv}}) MUST be capable of supporting the `@FD` special designator for all relevant inputs and outputs starting at `sopv` version 1.0. ## CERTS {#certs} One or more OpenPGP certificates ({{Section 10.1 of RFC9580}}), aka "Transferable Public Key". May be armored (see {{optional-input-armoring}}). Although some existing workflows may prefer to use one `CERTS` object with multiple certificates in it (a "keyring"), supplying exactly one certificate per `CERTS` input will make error reporting clearer and easier. If any `CERTS` input contains secret key material, `sop` MUST fail with `BAD_DATA`. This strictness is intended to keep the consumer of the `sop` interface clear about what material they are dealing with in what locations. This should reduce the consumer's risk of accidentally exposing secret key material where they meant to expose a `CERTS` object. ## KEYS {#keys} One or more OpenPGP Transferable Secret Keys ({{Section 10.2 of RFC9580}}). May be armored (see {{optional-input-armoring}}). Secret key material is often locked with a password to ensure that it cannot be simply copied and reused. If any secret key material is locked with a password and no `--with-key-password` option is supplied, `sop` may fail with error `KEY_IS_PROTECTED`. However, when a cleartext secret key (that is, one not locked with a password) is available, `sop` should always be able to use it, whether a `--with-key-password` option is supplied or not. Although some existing workflows may prefer to use one `KEYS` object with multiple keys in it (a "secret keyring"), supplying exactly one key per `KEYS` input will make error reporting clearer and easier. ## CIPHERTEXT {#ciphertext} `sop` accepts only a restricted subset of the arbitrarily-nested grammar allowed by the OpenPGP Messages definition ({{Section 10.3 of RFC9580}}). In particular, it accepts and generates only: An OpenPGP message, consisting of a sequence of PKESKs ({{Section 5.1 of RFC9580}}) and SKESKs ({{Section 5.3 of RFC9580}}), followed by one SEIPD ({{Section 5.13 of RFC9580}}). The SEIPD can decrypt into one of two things: - "Maybe Signed Data" (see below), or - Compressed data packet that contains "Maybe Signed Data" "Maybe Signed Data" is a sequence of: - N (zero or more) one-pass signature packets, followed by - zero or more signature packets, followed by - one Literal data packet, followed by - N signature packets (corresponding to the outer one-pass signatures packets) FIXME: does any tool do compression inside signing? Do we need to handle that? May be armored (see {{optional-input-armoring}}). ## INLINESIGNED {#inlinesigned} An inline-signed message may take any one of three different forms: - A binary sequence of OpenPGP packets that matches a subset of the "Signed Message" element in the grammar in {{Section 10.3 of RFC9580}} - The same sequence of packets, but ASCII-armored (see {{optional-input-armoring}}) - A message using the Cleartext Signature Framework described in {{Section 7 of RFC9580}} The subset of the packet grammar expected in the first two forms consists of either: - a series of Signature packets followed by a Literal Data packet - a series of One-Pass Signature (OPS) packets, followed by one Literal Data packet, followed by an equal number of Signature packets corresponding to the OPS packets When the message is in the third form (Cleartext Signature Framework), it has the following properties: - The stream SHOULD consist solely of `UTF-8` text - Every Signature packet found in the stream SHOULD have Signature Type 0x01 (canonical text document). - It SHOULD NOT contain leading text (before the `-----BEGIN PGP SIGNED MESSAGE-----` cleartext header) or trailing text (after the `-----END PGP SIGNATURE-----` armor tail). While some OpenPGP implementations MAY produce more complicated inline signed messages, a `sop` implementation SHOULD limit itself to producing these straightforward forms. ## SIGNATURES {#signature} One or more OpenPGP Signature packets. May be armored (see {{optional-input-armoring}}). ## SESSIONKEY {#sessionkey} This documentation uses the GnuPG defacto `ASCII` representation: `ALGONUM:HEXKEY` where `ALGONUM` is the decimal value associated with the OpenPGP Symmetric Key Algorithms ({{Section 9.3 of RFC9580}}) and `HEXKEY` is the hexadecimal representation of the binary key. Example AES-256 session key: 9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD A `sop` implementation SHOULD produce session key data in this format. When consuming such a session key, `sop` SHOULD be willing to accept either upper or lower case hexadecimal digits, and to gracefully ignore any trailing whitespace. ## MICALG {#micalg} This output-only type indicates the cryptographic digest used when making a signature. It is useful specifically when generating signed PGP/MIME objects, which want a `micalg=` parameter for the `multipart/signed` content type as described in {{Section 5 of RFC3156}}. It will typically be a string like `pgp-sha512`, but in some situations (multiple signatures using different digests) it will be the empty string. If the user of `sop` is assembling a PGP/MIME signed object, and the `MICALG` output is the empty string, the user should omit the `micalg=` parameter entirely. ## PASSWORD {#password} This input-only is expected to be a `UTF-8` string ({{utf8}}), but for `sop decrypt`, any bytestring that the user supplies will be accepted. Note the details in `sop encrypt` and `sop decrypt` about trailing whitespace! See also {{human-readable-passwords}} for more discussion. ## VERIFICATIONS {#verifications} This output-only type consists of one line per successful signature verification. Each line has four structured fields delimited by a single space, followed by a single-line JSON object or arbitrary text to the end of the line. - ISO-8601 UTC datestamp of the signature, to one second precision, using the `Z` suffix - Fingerprint of the signing key (may be a subkey) - Fingerprint of primary key of signing certificate (if signed by primary key, same as the previous field) - A string describing the mode of the signature, either `mode:text` or `mode:binary` - A JSON object or free-form message describing the verification (see {{verifications-json}}) Note that while {{date}} permits a `sop` implementation to accept other unambiguous date representations, its date output here MUST be a strict ISO-8601 UTC date timestamp. In particular: - the date and time fields MUST be separated by `T`, not by whitespace, since whitespace is used as a delimiter - the time MUST be emitted in UTC, with the explicit suffix `Z` - the time MUST be emitted with one-second precision Example: 2019-10-24T23:48:29Z C90E6D36200A1B922A1509E77618196529AE5FF8 C4BC2DDB38CCE96485EBE9C2F20691179038E5C6 mode:binary {"signers": ["dkg.asc"]} ### VERIFICATIONS extension JSON {#verifications-json} The final field of each `VERIFICATIONS` line is either JSON data or arbitrary text. If the final field begins and ends with curly brackets ({ and }, it is JSON data. Otherwise, the final field is arbitrary text (whose content and structure are up to the discretion of the implementation). JSON data allows for sophisticated future extensions, and is the preferred form of this field. Arbitrary text is deprecated. The rest of this subsection describes the JSON data. The JSON data is a single JSON object, coerced into a one-line representation (there are no literal LINE FEED (U+000A) characters in it, though there may be appropriately escaped LINE FEED characters within the JSON text). The JSON object MAY contain the following keys: - `signers`: a list the supplied `CERTS` objects that could have issued the signature, identified by the name given on the command line. If this key is present, as long as any OpenPGP certificate in a given `CERTS` object could have issued the signature, that `CERTS` object MUST be listed here. If multiple `CERTS` objects contain certificates that could have issued the signature, each `CERTS` MUST be listed here. - `comment`: Free-form UTF-8-encoded text describing the verification. An internationalized, locale-aware `sop` implementation should localize this field. - `ext`: A "extensions" JSON object containing arbitrary, implementation-specific data. To avoid collisions with future definitions, the top-level JSON object MUST NOT contain any other keys. For forward compatibility, when consuming a JSON object produced by a SOP implementation, unknown keys MUST be ignored. ## DATA {#data} Cleartext, arbitrary data. This is either a bytestream or `UTF-8` text. It MUST only be `UTF-8` text in the case of input supplied to `sop sign --as=text` or `sop encrypt --as=text`. If `sop` receives `DATA` containing non-`UTF-8` octets in this case, it will fail (see {{utf8}}) with `EXPECTED_TEXT`. ## PROFILELIST {#profilelist} This output-only type consists of simple UTF-8 textual output, with one line per profile. Each line consists of the profile name optionally followed by a colon (0x31), a space (0x20), and a brief human-readable description of the intended semantics of the profile. Each line may be at most 1000 bytes, and no more than 4 profiles may be listed. These limits are intended to force `sop` implementers to make hard decisions and to keep things simple. The first profile MAY be explicitly named `default`. If it is not named `default`, then `default` is an alias for the first profile listed. No profile after the first listed may be named `default`. Any of the profiles MAY be explicitly named `security`, `performance`, or `compatibility`. If none of the listed profiles have (some of) these names, the profiles of which they are an alias should indicate as much in the human-readable description. See {{profile}} for more discussion about the namespace and intended semantics of each profile. # Failure Modes {#failure-modes} `sop` return codes have both mnemonics and numeric values. When `sop` succeeds, it will return 0 (`OK`) and emit nothing to Standard Error. When `sop` fails, it fails with a non-zero return code, and emits one or more warning messages on Standard Error. Known return codes include: {: title="Error return codes"} Value | Mnemonic | Meaning ---:|----|---------------------------------------------- 0 | `OK` | Success 1 | `UNSPECIFIED_FAILURE` | An otherwise unspecified failure occurred 3 | `NO_SIGNATURE` | No acceptable signatures found (`sop verify`) 13 | `UNSUPPORTED_ASYMMETRIC_ALGO` | Asymmetric algorithm unsupported (`sop encrypt`) 17 | `CERT_CANNOT_ENCRYPT` | Certificate not encryption-capable (e.g., expired, revoked, unacceptable usage flags) (`sop encrypt`) 19 | `MISSING_ARG` | Missing required argument 23 | `INCOMPLETE_VERIFICATION` | Incomplete verification instructions (`sop decrypt`) 29 | `CANNOT_DECRYPT` | Unable to decrypt (`sop decrypt`) 31 | `PASSWORD_NOT_HUMAN_READABLE` | Non-`UTF-8` or otherwise unreliable password (`sop encrypt`, `sop generate-key`) 37 | `UNSUPPORTED_OPTION` | Unsupported option 41 | `BAD_DATA` | Invalid data type (no secret key where `KEYS` expected, secret key where `CERTS` expected, etc) 53 | `EXPECTED_TEXT` | Non-text input where text expected 59 | `OUTPUT_EXISTS` | Output file already exists 61 | `MISSING_INPUT` | Input file does not exist 67 | `KEY_IS_PROTECTED` | A `KEYS` input is password-protected (locked), and `sop` cannot unlock it with any of the `--with-key-password` (or `--old-key-password`) options 69 | `UNSUPPORTED_SUBCOMMAND` | Unsupported subcommand 71 | `UNSUPPORTED_SPECIAL_PREFIX` | An indirect parameter is a special designator (it starts with `@`) but `sop` does not know how to handle the prefix 73 | `AMBIGUOUS_INPUT` | A indirect input parameter is a special designator (it starts with `@`), and a filename matching the designator is actually present 79 | `KEY_CANNOT_SIGN` | Key not signature-capable (e.g., expired, revoked, unacceptable usage flags) (`sop sign` and `sop encrypt` with `--sign-with`) 83 | `INCOMPATIBLE_OPTIONS` | Options were supplied that are incompatible with each other 89 | `UNSUPPORTED_PROFILE` | The requested profile is unsupported (`sop generate-key`, `sop encrypt`), or the indicated subcommand does not accept profiles (`sop list-profiles`) 97 | `NO_HARDWARE_KEY_FOUND` | The `sop` implementation supports some form of hardware-backed secret keys, but could not identify the hardware device (see {{hardware-backed-secrets}}) 101 | `HARDWARE_KEY_FAILURE` | The `sop` implementation tried to use a hardware-backed secret key, but the cryptographic hardware refused the operation for some reason other than a bad PIN or password (see {{hardware-backed-secrets}}) 103 | `PRIMARY_KEY_BAD` | The primary key of a `KEYS` object is too weak or revoked 107 | `CERT_USERID_NO_MATCH` | The `CERTS` object has no matching User ID 109 | `KEY_CANNOT_CERTIFY` | Key not certification-capable (e.g., expired, revoked, unacceptable usage flags) (`sop certify-userid`) If a `sop` implementation fails in some way not contemplated by this document, it MAY return `UNSPECIFIED_FAILURE` or any non-zero error code, not only those listed above. # Known Implementations The following implementations are known at the time of this draft: {: title="Known implementations"} Project name | cli name | notes ---|---|---|--- dkg-sop | `dkg-sop` | Implemented in C++ using the LibTMCG library ({{DKG-SOP}}) gosop | `gosop` | Implemented in golang (Go) using GOpenPGP ({{GOSOP}}) gpgme-sop | `gpgme-sop` | A Rust wrapper around the gpgme C library ({{GPGME-SOP}}) PGPainless SOP | `pgpainless-cli` | Implemented in Java using PGPainless ({{PGPAINLESS-CLI}}) RNP-sop | `rnp-sop` | A Rust wrapper around the librnp C library ({{RNP-SOP}}) rsop | `rsop` | Implemented in Rust using the `rpgpie` crate ({{RSOP}}) Sequoia SOP | `sqop` | Implemented in Rust using the `sequoia-openpgp` crate ({{SQOP}}) sop-openpgp.js | `sop-openpgp.js` | Implemented in JavaScript using OpenPGP.js ({{SOP-OPENPGPJS}}) sopgpy | `sopgpy` | Implemented in Python using PGPy ({{SOPGPY}}) # Alternate Interfaces This draft primarily defines a command line interface, but future versions may try to outline a comparable idiomatic interface for C or some other widely-used programming language. Comparable idiomatic interfaces are already active in the wild for different programming languages, in particular: - Rust: {{RUST-SOP}} - Java: {{SOP-JAVA}} - Python: {{PYTHON-SOP}} These programmatic interfaces are typically coupled with a wrapper that can automatically generate a command-line tool compatible with this draft. An implementation that uses one of these languages should target the corresponding idiomatic interface for ease of development and interoperability. # Guidance for Implementers `sop` uses a few assumptions that implementers might want to consider. ## One OpenPGP Message at a Time `sop` is intended to be a simple tool that operates on one OpenPGP object at a time. It should be composable, if you want to use it to deal with multiple OpenPGP objects. FIXME: discuss what this means for streaming. The stdio interface doesn't necessarily imply streamed output. ## Simplified Subset of OpenPGP Message While the formal grammar for OpenPGP Message is arbitrarily nestable, `sop` constrains itself to what it sees as a single "layer" (see {{ciphertext}}). This is a deliberate choice, because it is what most consumers expect. Also, if an arbitrarily-nested structure is parsed with a recursive algorithm, this risks a denial of service vulnerability. `sop` intends to be implementable with a parser that defensively declines to do recursive descent into an OpenPGP Message. Note that an implementation of `sop decrypt` MAY choose to handle more complex structures, but if it does, it should document the other structures it handles and why it chooses to do so. We can use such documentation to improve future versions of this spec. ## Validate Signatures Only from Known Signers There are generally only a few signers who are relevant for a given OpenPGP message. When verifying signatures, `sop` expects that the caller can identify those relevant signers ahead of time. ## OpenPGP Inputs can be either Binary or ASCII-armored {#optional-input-armoring} OpenPGP material on input can be in either ASCII-armored or binary form. This is a deliberate choice because there are typical scenarios where the program can't predict which form will appear. Expecting the caller of `sop` to detect the form and adjust accordingly seems both redundant and error-prone. The simple way to detect possible ASCII-armoring is to see whether the high bit of the first octet is set: {{Section 4.2 of RFC9580}} indicates that bit 7 is always one in the first octet of an OpenPGP packet. In standard ASCII-armor, the first character is -, so the high bit should be cleared. When considering an input as ASCII-armored OpenPGP material, `sop` MAY reject an input based on any of the following variations (see {{Section 6.2 of RFC9580}} for precise definitions): - An unknown Armor Header Line - Any text before the Armor Header Line - Malformed lines in the Armor Headers section - Any non-whitespace data after the Armor Tail - Any Radix-64 encoded line with more than 76 characters - Invalid characters in the Radix-64-encoded data - An invalid Armor Checksum - A mismatch between the Armor Header Line and the Armor Tail - More than one ASCII-armored object in the input For robustness, `sop` SHOULD be willing to ignore whitespace after the Armor Tail. For any plural data type (i.e.,`SIGNATURES`, `CERTS`, or `KEYS`), the unarmored form is trivially concatenatable with another object of the same type (e.g., with Unix's `cat` utility). But the armored forms are not concatenatable without first dearmoring. To avoid inconsistent behavior, a `sop` implementation SHOULD reject anything that appears to be a concatenated series of ASCII-armored objects. When considering OpenPGP material as input, regardless of whether it is ASCII-armored or binary, `sop` SHOULD reject any material that doesn't produce a valid stream of OpenPGP packets. For example, `sop` SHOULD raise an error if an OpenPGP packet header is malformed, or if there is trailing garbage after the end of a packet. For a given type of OpenPGP input material (i.e., `SIGNATURES`, `CERTS`, `KEYS`, `INLINESIGNED`, or `CIPHERTEXT`), `sop` SHOULD also reject any input that does not conform to the expected packet stream. See {{indirect-types}} for the expected packet stream for different types. ## Complexities of the Cleartext Signature Framework {#csf-risks} `sop` prefers a detached signature as the baseline form of OpenPGP signature, but provides affordances for dealing with inline-signed messages (see `INLINESIGNED`, {{inlinesigned}}) as well. The most complex form of inline-signed messages is the Cleartext Signature Framework (CSF). Handling the CSF structure requires parsing to delimit the multiple parts of the document, including at least: - any preamble before the message - the inline message header (delimiter line, OpenPGP headers) - the message itself - the divider between the message and the signature (including any OpenPGP headers there) - the signature - the divider that terminates the signature - any suffix after the signature Note also that the preamble or the suffix might be arbitrary text, and might themselves contain OpenPGP messages (whether signatures or otherwise). If the parser that does this split differs in any way from the parser that does the verification, or parts of the message are confused, it would be possible to produce a verification status and an actual signed message that don't correspond to one another. Blurred boundary problems like this can produce ugly attacks similar to those found in [EFAIL]. A user of `sop` that receives an inline-signed message (whether the message uses the CSF or not) can detach the signature from the message with `sop inline-detach` (see {{inline-detach}}). Alternately, the user can send the message through `sop inline-verify` to confirm required signatures, and then (if signatures are valid) supply its output to the consumer of the signed message. ## Reliance on Supplied Certs and Keys {#cert-validity-performance} A truly stateless implementation may find that it spends more time validating the internal consistency of certificates and keys than it does on the actual object security operations. For performance reasons, an implementation may choose to ignore validation on certificate and key material supplied to it. The security implications of doing so depend on how the certs and keys are managed outside of `sop`. ## Text is always UTF-8 {#utf8} Various places in this specification require UTF-8 {{!RFC3629}} when encoding text. `sop` implementations SHOULD NOT consider textual data in any other character encoding. OpenPGP Implementations MUST already handle UTF-8, because various parts of {{RFC9580}} require it, including: - User ID - Notation name - Reason for revocation - ASCII-armor Comment: header Dealing with messages in other charsets leads to weird security failures like {{Charset-Switching}}, especially when the charset indication is not covered by any sort of cryptographic integrity check. Restricting textual data to `UTF-8` universally across the OpenPGP ecosystem eliminates any such risk without losing functionality, since `UTF-8` can encode all known characters. ## Passwords are Human-Readable {#human-readable-passwords} Passwords are generally expected to be human-readable, as they are typically recorded and transmitted as human-visible, human-transferable strings. However, they are used in the OpenPGP protocol as bytestrings, so it is important to ensure that there is a reliable bidirectional mapping between strings and bytes. The maximally robust behavior here is for `sop encrypt` and `sop generate-key` (that is, commands that use a password to encrypt) to constrain the choice of passwords to strings that have such a mapping, and for `sop decrypt` and `sop sign` (and `sop inline-sign`, as well as`sop encrypt` when decrypting a signing key; that is, commands that use a password to decrypt) to try multiple plausible versions of any password supplied by `PASSWORD`. ### Generating Material with Human-Readable Passwords {#generating-human-readable} When generating material based on a password, `sop encrypt` and `sop generate-key` enforce that the password is actually meaningfully human-transferable. In particular, an implementation generating material based on a new password SHOULD apply the following considerations to the supplied password: - require `UTF-8` - trim trailing whitespace Some `sop encrypt` and `sop generate-key` implementations may make even more strict requirements on input to ensure that they are transferable between humans in a robust way. For example, a more strict `sop encrypt` or `sop generate-key` MAY also: - forbid leading whitespace - forbid non-printing characters other than `SPACE (U+0020)`, such as `ZERO WIDTH NON-JOINER (U+200C)` or `TAB (U+0009)` - require the password to be in Unicode Normal Form C ({{UNICODE-NORMALIZATION}}) Violations of these more-strict policies SHOULD result in an error of `PASSWORD_NOT_HUMAN_READABLE`. A `sop encrypt` or `sop generate-key` implementation typically SHOULD NOT attempt enforce a minimum "password strength", but in the event that some implementation does, it MUST NOT represent a weak password with `PASSWORD_NOT_HUMAN_READABLE`. ### Consuming Password-protected Material {#consuming-passwords} When `sop decrypt` receives a `PASSWORD` input, either from a `--with-key-password` or `--with-password` option, it sees its content as a bytestring. `sop sign` also sees the content of any `PASSWORD` input supplied to its `--with-key-password` option as a bytestring. If the bytestring fails to work as a password, but ends in `UTF-8` whitespace, it will try again with the trailing whitespace removed. This handles a common pattern of using a file with a final newline, for example. The pattern here is one of robustness in the face of typical errors in human-transferred textual data. A more robust `sop decrypt` or `sop sign` implementation that finds neither of the above two attempts work for a given `PASSWORD` MAY try additional variations if they produce a different bytestring, such as: - trimming any leading whitespace, if discovered - trimming any internal non-printable characters other than `SPACE (U+0020)` - converting the supplied `PASSWORD` into Unicode Normal Form C ({{UNICODE-NORMALIZATION}}) A `sop decrypt` or `sop sign` implementation that stages multiple decryption attempts like this SHOULD consider the computational resources consumed by each attempt, to avoid presenting an attack surface for resource exhaustion in the face of a non-standard `PASSWORD` input. ## Be Careful with Special Designators {#special-designators-guidance} As documented in {{special-designators}}, special designators for indirect inputs like `@ENV:` and `@FD:` (and indirect outputs using `@FD:`) warrant some special/cautious handling. For one thing, it's conceivable that the filesystem could contain a file with these literal names. If `sop` receives an indirect output parameter that starts with an @ it MUST NOT write to the filesystem for that parameter. A `sop` implementation that receives such a parameter as input MAY test for the presence of such a file in the filesystem and fail with `AMBIGUOUS_INPUT` to warn the user of the ambiguity and possible confusion. These special designators are likely to be used to pass sensitive data (like secret key material or passwords) so that it doesn't need to touch the filesystem. Given this sensitivity, `sop` should be careful with such an input, and minimize its leakage to other processes. In particular, `sop` SHOULD NOT leak any environment variable identified by `@ENV:` or file descriptor identified by `@FD:` to any subprocess unless the subprocess specifically needs access to that data. ## Nuances for Hardware-backed Secret Key Material {#hardware-backed-secrets} There are a number of limitations and nuances to be aware of for hardware-backed secret key support in this interface. Some `sop` implementations will simply not support hardware-backed secret key material. Other implementations might support only a single kind of hardware-backing (e.g., an OpenPGP Smartcard {{OPENPGP-SMARTCARD}} but not a TPM, or vice versa). There is no formally adopted OpenPGP standard for identifying that a given secret key is backed by hardware based on the OpenPGP wire format. {{?I-D.dkg-openpgp-hardware-secrets}} proposes one simple and straightforward approach for how the wire format could cover this use case. This simple mechanism is deliberately agnostic about the specific kind of cryptographic hardware, but it does imply a sort of rough shape of what the interface to the hardware would permit. In particular, it will work best with hardware that has the following properties: - The hardware does specific asymmetric secret key operations, using secret keys that it does not release. - The user can ask the hardware to provide a list of corresponding public key material (or OpenPGP key fingerprints) for any of the secret keys held by the device. - The hardware MAY require the provision of a PIN or password to enable secret key operation, but does not require a PIN or password for the list of public key material. The `sop` interface does not currently provide for provisioning cryptographic hardware with secret key material, or for changing the PIN or password for the cryptographic hardware. Users of cryptographic hardware need to do provisioning and PIN or password setting outside of `sop`. If a user has two attached hardware tokens that both hold the same secret key, and they are both password-locked, and they use different passwords, `sop` offers no way for the user to clearly indicate which password belongs to which device. Some cryptographic hardware is designed to lock the device if the wrong password is entered too many times, so users in this configuration are at risk of accidental lockout. The easiest resolution for this is for the user to detach any duplicate devices before invoking `sop`. Note that some OpenPGP implementations use the private codepoint ranges in the OpenPGP specification within an OpenPGP Transferable Secret Key (e.g., {{GNUPG-SECRET-STUB}}) to indicate that the secret key can be found on a smartcard. While hardware-backed secret key operations can be significantly slower than modern computers, and physical affordances like button-presses or NFC tapping can themselves incur delay, it's bad form for an invocation of `sop` to hang forever. This specification doesn't define a specific maximum allowable delay, but if an implementation calls into a hardware device either for public key listing or for secret key operations, it should not allow the cryptographic hardware to take an arbitrary amount of time to respond. ## Statelessness exemptions While this specification strives to define all operations as stateless implementers MAY, for practical reasons, rely on the global state of the system. For example, the following items constitute a system state but are not considered to violate the stateless rule: - current time Implementers are advised to document which global state items they rely on to help in troubleshooting issues for consumers. # Guidance for Consumers While `sop` is originally conceived of as an interface for interoperability testing, it's conceivable that an application that uses OpenPGP for object security would want to use it. FIXME: more guidance for how to use such a tool safely and efficiently goes here. FIXME: if an encrypted OpenPGP message arrives without metadata, it is difficult to know which signers to consider when decrypting. How do we do this efficiently without invoking `sop decrypt` twice, once without `--verify-*` and again with the expected identity material? ## Choosing Between --as=text and --as=binary A program that invokes `sop` to generate an OpenPGP signature typically needs to decide whether it is making a text or binary signature. By default, `sop` will make a binary signature. The caller of `sop sign` should choose `--as=text` only when it knows that: - the data being signed is in fact textual, and encoded in `UTF-8`, and - the signed data might be transmitted to the recipient (the verifier of the signature) over a channel that has the propensity to transform line-endings. Examples of such channels include FTP ({{?RFC0959}}) and SMTP ({{?RFC5321}}). ## Special Designators and Unusual Filenames In some cases, a user of `sop` might want to pass all the files in a given directory as positional parameters (e.g., a list of CERTS files to test a signature against). If one of the files has a name that starts with `--`, it might be confused by `sop` for an option. If one of the files has a name that starts with `@`, it might be confused by `sop` as a special designator ({{special-designators}}). If the user wants to deliberately refer to such an ambiguously-named file in the filesystem, they should prefix the filename with `./` or use an absolute path. Any specific `@FD:` special designator SHOULD NOT be supplied more than once to an invocation of `sop`. If a `sop` invocation sees multiple copies of a specific `@FD:n` input (e.g., `sop sign @FD:3 @FD:3`), it MAY fail with `MISSING_INPUT` even if file descriptor 3 contains a valid `KEYS`, because the bytestream for the `KEYS` was consumed by the first argument. Doubling up on the same `@FD:` for output (e.g., `sop decrypt --session-key-out=@FD:3 --verifications-out=@FD:3`) also results in an ambiguous data stream. # Security Considerations The OpenPGP object security model is typically used for confidentiality and authenticity purposes. ## Signature Verification {#signature-verification} In many contexts, an OpenPGP signature is verified to prove the origin and integrity of an underlying object. When `sop` checks a signature over data (e.g., via `sop verify` or `sop decrypt --verify-with`), it MUST NOT consider it to be verified unless all of these conditions are met: - The signature must be made by a signing-capable public key that is present in one of the supplied certificates - The certificate and signing subkey must have been created before or at the signature time - The certificate and signing subkey must not have been expired at the signature time - The certificate and signing subkey must not be revoked with a "hard" revocation - If the certificate or signing subkey is revoked with a "soft" revocation, then the signature time must predate the revocation - The signing subkey must be properly bound to the primary key, and cross-signed - The signature (and any dependent signature, such as the cross-sig or subkey binding signatures) must be made with strong cryptographic algorithms (e.g., not `MD5` or a 1024-bit `RSA` key) - The signature must be of type 0x00 ("Signature of a binary document") or 0x01 ("Signature of a canonical text document"); other signature types are inappropriate for data signatures Implementers MAY also consider other factors in addition to the origin and authenticity, including application-specific information. For example, consider the application domain of checking software updates. If software package Foo version 13.3.2 was signed on 2019-10-04, and the user receives a copy of Foo version 12.4.8 that was signed on 2019-10-16, it may be authentic and have a more recent signature date. But it is not an upgrade (12.4.8 < 13.3.2), and therefore it should not be applied automatically. In such cases, it is critical that the application confirms that the other information verified is *also* protected by the relevant OpenPGP signature. Signature validity is a complex topic (see for example the discussion at {{DISPLAYING-SIGNATURES}}), and this documentation cannot list all possible details. ### Explaining Non-Verification on Standard Error {#explaining-non-verification} When verifying OpenPGP signatures, sometimes no valid signatures are found. This will cause the verifying subcommand to produce an empty `VERIFICATIONS` output, and for some subcommands (`sop verify` and `sop inline-verify` in particular) will also cause the subcommand to fail with `NO_SIGNATURE`. When this happens, some consumers will want to know more details about the verification failure, since some verification failures may be indications that something is wrong with the verifier's setup, such as outdated OpenPGP implementations (which can be upgraded), expired signing certificates (which can be refreshed), and so on. To address this, when no valid signatures are found at all, `sop` MAY emit a human-readable explanation to standard error. Some example explanations for complete signature validation failure include: - Version 7 signature found, but FooPGP 2.0.3 only supports versions 4 and 6. - Version 3 signature found, but BarPGP 0.9.7 rejects all version 3 signatures. - Signature from pubkey algorithm 94 found, but BazPGP 1.1 does not support this pubkey algorithm. - Signature using hash algorithm 22 found, but QuxPGP 19.0.5 does not support this hash algorithm. - Two signatures found, both made by unknown OpenPGP certificates. - Signature does not match hash prefix. - No OpenPGP signatures found. In some cases (such as when two OpenPGP signatures are discovered, and they both fail to validate for different reasons), a `sop` implementation may choose to emit a more complex warning. Unless `--debug` is present, `sop` SHOULD NOT emit any such warning (even if true for one of the OpenPGP signatures found) if another signature was found in the same `SIGNATURES` object or `INLINESIGNED` message that does verify correctly. This keeps the upgrade path smooth for the whole ecosystem. As the ecosystem evolves, signatures using new versions and algorithms, or signatures simply using new signing keys, are typically introduced as a second signature distributed alongside the first. A warning about a signature with a new or unknown algorithm (or key) when an accompanying signature still verifies from a known key with a known algorithm will discourage signers from adopting new algorithms or keys. And introducing a warning about a signature using a deprecated algorithm (or key), when an accompanying signature still verifies using a more modern algorithm or key will discourage a verifier from upgrading their OpenPGP implementation or dropping old, deprecated keys. Implementers should avoid emitting dangerous explanations. For example, an explanation like "Signature from 0xDEADBEEF found, but not in list of acceptable signers" might encourage a user to go hunting for any certificate with short key ID 0xDEADBEEF and start using it to verify signatures. This would be a very dangerous explanation, in particular because short key IDs are trivially forgeable. But it would also be nearly as dangerous to use a full fingerprint (instead of a short Key ID) in such a message because then all an attacker has to do is to get their signature to appear in the place where the verifier is looking for a signature, and then the warning will encourage the verifier go look up the attacker's certificate by fingerprint. An internationalized, locale-aware `sop` implementation should localize these warning messages. ## Compression {#compression} The interface as currently specified does not allow for control of compression. Compressing and encrypting data that may contain both attacker-supplied material and sensitive material could leak information about the sensitive material (see the CRIME attack). Unless an application knows for sure that no attacker-supplied material is present in the input, it should not compress during encryption. # Privacy Considerations Material produced by `sop encrypt` may be placed on an untrusted machine (e.g., sent through the public `SMTP` network). That material may contain metadata that leaks associational information (e.g., recipient identifiers in PKESK packets ({{Section 5.1 of RFC9580}})). FIXME: document things like PURBs and `--hidden-recipient`) ## Object Security vs. Transport Security OpenPGP offers an object security model, but says little to nothing about how the secured objects get to the relevant parties. When sending or receiving OpenPGP material, the implementer should consider what privacy leakage is implicit with the transport. --- back # sopv Version Changelog {#sopv-changelog} This is a reverse-chronological order changelog for the `sopv` subset. ## sopv Version 1.1 {#sopv-1.1} - `VERIFICATIONS` output always includes the fourth `mode:` field - `VERIFICATIONS` output always uses JSON format for the trailer of each line, and always populates the `signers` member (see {{verifications-json}}) ## sopv Version 1.0 {#sopv-1.0} The following subcommands: - `sop version` - `sop verify` - `sop inline-verify` And the following features: - Special designators `@FD:` and `@ENV:` as input for `CERTS` objects - Special designator `@FD:` as possible output for `--verifications-out` argument to `sopv inline-verify` - Multiple certificates in each `CERTS` object - `--not-before` and `--not-after` constraints # C Library API (Tentative) {#libsop} As specified in this draft, SOP is a command-line tool. However, it can also be useful to have a comparable API exposed as a C library. This library can be implemented as a shared object (e.g., `.so`, `.dll`, or `.dylib` depending on the platform) or as a statically linked object. This interface can be reused in many different places, as most modern programming languages offer "bindings" to C libraries. A proposed interface to a C library follows here as a C header file. The primary goal of this shared object interface is to make it easy to implement the command-line interface described in this document. That said, it is also intended to be relatively ergonomic to use in plausible OpenPGP workflows where the caller has access to all of the explicit state. If there is a plausible OpenPGP workflow that is not supported by this library API, please propose improvements and explain the specific workflow. {: sourcecode-name="sop.h"} ~~~ text/x-chdr {::include sop.h} ~~~ This proposed interface currently deals only with signing. Encryption and decryption will be added in a future revision. ## Design Choices for Library API The library is deliberately minimal, with data types and functionality corresponding to the SOP CLI. The interface itself should expose no dependencies beyond libc. All datatypes are opaque structs. Library implementations MUST NOT expose library users to the memory layout of the underlying objects. The library deals with data that is all in RAM, and produces data in RAM. For simplicity, it does not currently expose a streaming interface. It should be fairly straightforward to implement the SOP CLI on top of such a library. ## Library Use Patterns There are two main kinds of data structures: operations (e.g., `sop_op_sign` and `sop_op_verify`) and datatypes (e.g., `sop_keys` and `sop_certs`). Operation objects are one-shot objects. They are used in the following pattern: - create an operations object (`sop_op_*_new`) - adjust it to behave in certain ways (e.g., `sop_op_sign_use_keys`, `sop_op_verify_not_before`) - execute it (with some specific `sop_op_*_execute` function) - dispose of it (`sop_op_*_free`) The library user MUST NOT execute the same operation object more than once. When a single operation object is executed more than once, it should fail with `SOP_OPERATION_ALREADY_EXECUTED`. FIXME: if a use case arises with a reasonable need to re-execute an already adjusted object, we could extend the API to allow the user to clone an object. Datatype objects are reusable objects. For example, it is fine for a library user to pass the same `sop_certs` to multiple `sop_op_*` operation objects, as long as the `sop_certs` object is not freed before the execution of all the operation objects it has been passed to. Datatype objects are also immutable. Any function which modifies a datatype object always creates a new copy of the object, with the specific change applied. This immutability avoids any ambiguity about what should happen when a datatype object is adjusted after it was passed to an operation object but before it was executed. ## `libsopv` C API Subset A minimalist library subset that only does OpenPGP signature verification might be called `libsopv`. This library is useful wherever the use case is just OpenPGP signature verification. Such a library MUST implement the following functions from the C API: - `sop_ctx_new` - `sop_ctx_free` - `sop_set_log_function` - `sop_set_log_level` - `sop_version` - `sop_version_backend` - `sop_version_extended` - `sop_buf_size` - `sop_buf_data` - `sop_buf_free` - `sop_certs_from_bytes` - `sop_certs_free` - `sop_sigs_from_bytes` - `sop_sigs_free` - `sop_verifications_count` - `sop_verifications_get_time` - `sop_verifications_to_text` - `sop_verifications_free` - `sop_inlinesigned_from_bytes` - `sop_op_verify_new` - `sop_op_verify_not_before` - `sop_op_verify_not_after` - `sop_op_verify_add_signers` - `sop_op_verify_detached_execute` - `sop_op_verify_inline_execute` - `sop_op_verify_free` This minimal library interface should be sufficient to implement the `sopv` subset version 1.0 (see {{sopv}}). ### `libsopv` 1.1 C API Subset In addition to the above, to implement `sopv` version 1.1, the additional functions are necessary: - `sop_verifications_get_mode` - `sop_verifications_get_signer_count` - `sop_verifications_get_signer` # Simple CLI Test {#simple-self-test} The following POSIX-compliant shell script can be pointed to a SOP implementation. It will report which subcommands have basic coverage. It does not consider all possible combinations of all options. {: sourcecode-name="simple-sop-test"} ~~~ text/x-sh {::include test/simple-sop-test} ~~~ # Testing the `sopv` Subset {#sopv-subset-test} The following two POSIX-compliant shell scripts can be used (with a signing-capable `sop` implementation) to exhaustively test a `sopv` implementation. First, use `setup-sopv-test` with a signing-capable `sop` implementation to generate a set of test vectors in the current working directory. Then, run `sopv-test` against the `sopv` implementation: ~~~ ./setup-sopv-test some-sop ./sopv-test my-sopv ~~~ ## `setup-sopv-test` {: sourcecode-name="setup-sopv-test"} ~~~ text/x-sh {::include test/setup-sopv-test} ~~~ ## `sopv-test` {: sourcecode-name="sopv-test"} ~~~ text/x-sh {::include test/sopv-test} ~~~ # Acknowledgements This work was inspired by Justus Winter's {{OpenPGP-Interoperability-Test-Suite}}. The following people contributed helpful feedback and considerations to this draft, but are not responsible for its problems: - Allan Nordhøy - Antoine Beaupré - Edwin Taylor - Guillem Jover - Heiko Schaefer - Jameson Rollins - Justus Winter - Paul Schaub - Vincent Breitmoser # Future Work - certificate transformation into popular publication forms: - WKD - DANE OPENPGPKEY - Autocrypt - `sop encrypt` -- specify compression? (see {{compression}}) - `sop encrypt` -- specify padding policy/mechanism? - `sop decrypt` -- how can it more safely handle zip bombs? - `sop decrypt` -- what should it do when encountering weakly-encrypted (or unencrypted) input? - `sop encrypt` -- minimize metadata (e.g., `--throw-keyids`)? - specify an error if a `DATE` arrives as input without a time zone? - add considerations about what it means for armored `CERTS` to contain multiple certificates -- multiple armorings? one big blob? - do we need an interface or option (for performance?) with the semantics that `sop` doesn't validate certificates internally, it just accepts whatever's given as legit data? (see {{cert-validity-performance}}) - do we need to be able to convert a message with a text-based signature to a CSF `INLINESIGNED` message? I'd rather not, given the additional complications. - add encryption and decryption to C Library API # Document History ## Substantive Changes between -12 and -13: - Define `sopv` 1.1 (structured json VERIFICATIONS output) - Fix misspellings ## Substantive Changes between -11 and -12: - Improvements in POSIX shell tests (including robust `sopv` test) - Define `security`, `performance`, and `compatibility` profile aliases - Clarify that `sop armor` ought to be able to armor any output that `sop` produces - Intro: acknowledge increased attention to key/cert management - Add `KEY_CANNOT_CERTIFY` error code - Relax guidance on multi-signature operations and `--micalg-out` - Define `sopv` 1.0 (with changelog) - Document exceptions to statelessness for system-level state ## Substantive Changes between -10 and -11: - `update-key`: new key management subcommand - `merge-certs`: new certificate management subcommand - `certify-userid`: new User ID certification subcommand - `validate-userid`: new User ID verification subcommand - Replace references to RFC 4880 with RFC 9580 - Set aside error 1 as `UNSPECIFIED_FAILURE` - Encourage JSON output in tail of `VERIFICATIONS` lines - Add universal (ignorable) `--debug` option - Add simple (and incomplete) shell-script test in appendix ## Substantive Changes between -09 and -10: - drop `@HARDWARE:` special designator in favor of the simple {{I-D.dkg-openpgp-hardware-secrets}} or other magic - drop hardware-specific C API function - define SemVer-versioned `sopv` subset of CLI - `sop version`: add `--sopv` option - define libsopv subset of C API - explicitly require `BAD_DATA` failure when `KEYS` are passed as `CERTS` ## Substantive Changes between -08 and -09: - enable the use of hardware-backed secret key material via the `@HARDWARE:` special designator - C API: clarify design goals and usage patterns - C API: major overhaul and normalization: - allow passthrough "cookie" for logging - allow NULL return from `sop_version_*` - explicitly offer `SOP_LOG_NEVER` - use `*_from_bytes` and `*_to_bytes` instead of `*_import` and `*_export` - datatype objects are now immutable - operation objects are one-shot - always return `sop_err`, even at a slight cost to C caller ergonomics ## Substantive Changes between -07 and -08: - `revoke-key`, `change-key-password`: add `--no-armor` option - `generate-key`: should fail on non-UTF-8 `USERID` - `generate-key`: acknowledge that implementations MAY reject `USERID`s that seem bad - `armor`: drop `--label` option - `encrypt`: add `--session-key-out` option - ASCII-armored objects should not be concatenated - signature verification should only work for sigtypes 0x00 (binary) and 0x01 (canonical text) - `sign`: Constrain input when `--micalg-out` is present for alignment with {{RFC3156}} - propose simple C API for signing and verification ## Substantive Changes between -06 and -07: - `generate-key`: add `--signing-only` option - new key management subcommand: `change-key-password` - new key management subcommand: `revoke-key` ## Substantive Changes between -05 and -06: - `version`: add `--sop-spec` argument - `encrypt`: add `--profile` argument ## Substantive Changes between -04 and -05: - `decrypt`: change `--verify-out` to `--verifications-out` - `encrypt`: add missing `--with-key-password` - add the concept of "profiles", use with `generate-key` - include table of known implementations - `VERIFICATIONS` can now indicate the type of the signature (`mode:text` or `mode:binary`) ## Substantive Changes between -03 and -04: - Reinforce that PASSWORD and SESSIONKEY are indirect data types - `encrypt`: remove `--as=mime` option - Handle password-locked secret key material: add `--with-key-password` options to `generate-key`, `sign`, and `decrypt`. - Introduce `INLINESIGNED` message type ({{inlinesigned}}) - Rename `detach-inband-signature-and-message` to `inline-detach`, clarify its possible inputs - Add `inline-verify` - Add `inline-sign` ## Substantive Changes between -02 and -03: - Added `--micalg-out` parameter to `sign` - Change from `KEY` to `KEYS` (permit multiple secret keys in each blob) - New error code: `KEY_CANNOT_SIGN` - `version` now has `--backend` and `--extended` options ## Substantive Changes between -01 and -02: - Added mnemonics for return codes - `decrypt` should fail when asked to output to a pre-existing file - Removed superfluous `--armor` option - Much more specific about what `armor --label=auto` should do - `armor` and `dearmor` are now fully idempotent, but work only well-formed OpenPGP streams - Dropped `armor --allow-nested` - Specified what `encrypt --as=` means - New error code: `KEY_IS_PROTECTED` - Documented expectations around human-readable, human-transferable passwords - New subcommand: `detach-inband-signature-and-message` - More specific guidance about special designators like `@FD:` and `@ENV:`, including new error codes `UNSUPPORTED_SPECIAL_PREFIX` and `AMBIGUOUS_INPUT` ## Substantive Changes between -00 and -01: - Changed `generate` subcommand to `generate-key` - Changed `convert` subcommand to `extract-cert` - Added "Input String Types" section as distinct from indirect I/O - Made implicit arguments potentially explicit (e.g., `sop armor --label=auto`) - Added `--allow-nested` to `sop armor` to make it idempotent by default - Added fingerprint of signing (sub)key to `VERIFICATIONS` output - Dropped `--mode` and `--session-key` arguments for `sop encrypt` (no plausible use, not needed for interop) - Added `--with-session-key` argument to `sop decrypt` to allow for session-key-based decryption - Added examples to each subcommand - More detailed error codes for `sop encrypt` - Move from `CERT` to `CERTS` (each `CERTS` argument might contain multiple certificates) stateless-openpgp-docs-13.0/test/000077500000000000000000000000001475345515500170355ustar00rootroot00000000000000stateless-openpgp-docs-13.0/test/evaluate000077500000000000000000000233631475345515500206000ustar00rootroot00000000000000#!/bin/bash if [ "$1" = --keep ]; then KEEP_WORKDIR=true shift fi sop_bin="$1" declare -a errors=() declare -A errors_by_version=() declare -A errors_by_tag=() declare -a successes=() if [ -z "$sop_bin" ]; then printf 'usage: ./evaluate [--keep] /path/to/sop\n' >&2 exit 1 fi err() { printf "$@" >&2 exit 1 } sop_bin=$(realpath "$(which "$sop_bin")") || err "Could not find path\n" if [ -v AUTOPKGTEST_ARTIFACTS ]; then workdir=$(mktemp -p "$AUTOPKGTEST_ARTIFACTS" -d) KEEP_WORKDIR=true else workdir=$(mktemp -d) fi cd "$workdir" cleanup() { unset ret printf '\n%d Successes\n' "${#successes[@]}" >&2 if [ "${#errors[@]}" -gt 0 ]; then printf '\n%d Failure(s):\n' "${#errors[@]}" >&2 printf ' - %s\n' "${errors[@]}" >&2 printf '\nFailures by draft version:\n' >&2 for v in $(sort -n <(printf '%d\n' "${!errors_by_version[@]}")); do printf ' - v%02d: %d\n' "$v" "${errors_by_version[$v]}" >&2 done if [ "${#errors_by_tag[@]}" -gt 0 ]; then printf '\nFailures by tag:\n' >&2 for tag in $(sort <(printf '%s\n' "${!errors_by_tag[@]}")); do printf ' - %s: %d\n' "$tag" "${errors_by_tag[$tag]}" >&2 done fi ret=1 fi if [ -z "$KEEP_WORKDIR" ]; then rm -rf "$workdir" else printf 'Artifacts are available in %s\n' "$workdir" fi if [ -n "$ret" ]; then exit "$ret" fi } trap cleanup EXIT sop() { "$sop_bin" "$@" } t_end() { if ! "$@"; then errors+=("$(printf '%s (v%02d)' "$curtest" "$curver")") errors_by_version[$curver]=$(( "${errors_by_version[$curver]:-0}" + 1 )) for tag in "${curtags[@]}"; do errors_by_tag[$tag]=$(( "${errors_by_tag[$tag]:-0}" + 1 )) done else successes+=("$(printf '%s (v%02d)' "$curtest" "$curver")") fi } t_start() { curver="$1" curtest="$2" shift 2 curtags=("$@") printf "=== %s (v%02d) ===\n" "$curtest" "$curver" } confirmsig() { local file="$1" local count="${2:-1}" local SIG_VERIFIER='^[0-9]{4}(-[0-9]{2}){2}T[0-9]{2}(:[0-9]{2}){2}Z( [0-9A-Fa-f]{40}){2}( .*)?$' if [ $(egrep -c "$SIG_VERIFIER" "$file") -ne "$count" ] || \ [ $(wc -l "$file" | awk '{ print $1 }') -ne "$count" ]; then cat "$file" return 1 fi return 0 } threematch() { diff -u "$1" "$2" && diff -u "$1" "$3" } non_empty_files() { local file unset ret for file in "$@"; do if ! test -s "$file"; then printf '%q does not exist\n' "$file" ret=1 fi done if [ -n "$ret" ]; then return 1 fi return 0 } printf "Testing SOP implementation %s\n" "$sop_bin" t_start 0 "Version" version t_end sop version t_start 3 "Extended Version" version t_end sop version --extended t_start 3 "Backend Version" version t_end sop version --backend t_start 1 "Key Generation" generate-key extract-cert for x in alice bob; do sop generate-key "$x" > "$x.key" sop extract-cert < "$x.key" > "$x.crt" done t_end non_empty_files {alice,bob}.{key,crt} printf '%s\n' test 'test test' 'test test test' > test.txt t_start 1 "Sign/Verify roundtrip" sign verify sop sign bob.key < test.txt > test.sig sop verify test.sig bob.crt < test.txt > verify-detached.txt t_end confirmsig verify-detached.txt t_start 1 "Asymmetric Encrypt/Decrypt roundtrip" encrypt decrypt sop encrypt bob.crt < test.txt > test.pgp sop decrypt bob.key < test.pgp > test.txt.out t_end diff -u test.txt test.txt.out t_start 1 "Asymmetric Decrypt with session key" decrypt session-key sop decrypt --session-key-out session.key bob.key < test.pgp > test.txt.skey1.out sop decrypt --with-session-key session.key < test.pgp > test.txt.skey2.out t_end threematch test.txt test.txt.skey1.out test.txt.skey2.out t_start 1 "Asymmetric Encrypt/Decrypt roundtrip with signature" encrypt decrypt verify sop encrypt --sign-with alice.key bob.crt < test.txt > test.pgp sop decrypt --verify-with alice.crt --verify-out verify.txt bob.key < test.pgp > test.txt.sig.out t_end confirmsig verify.txt t_end diff -u test.txt test.txt.sig.out t_start 1 "Asymmetric Encrypt/Decrypt roundtrip with two signatures" encrypt decrypt verify sop encrypt --sign-with alice.key --sign-with bob.key bob.crt < test.txt > test.twosigs.pgp sop decrypt --verify-with alice.crt --verify-with bob.crt --verify-out verify.twosigs.txt bob.key < test.twosigs.pgp > test.txt.twosigs.out t_end confirmsig verify.twosigs.txt 2 t_end diff -u test.txt test.txt.twosigs.out t_start 1 "Password-based Encrypt/Decrypt roundtrip" encrypt decrypt password printf abc123 > password.txt sop encrypt --with-password password.txt < test.txt > test.pass.pgp sop decrypt --with-password password.txt < test.pass.pgp > test.txt.pass.out t_end diff -u test.txt test.txt.pass.out t_start 1 "Password-based Encrypt/Decrypt roundtrip with password (different filenames)" encrypt decrypt password cp password.txt password2.txt sop decrypt --with-password password2.txt < test.pass.pgp > test.txt.pass2.out t_end diff -u test.txt test.txt.pass2.out t_start 1 "Password-based Encrypt/Decrypt roundtrip using @ENV special designator" encrypt decrypt password @ENV THISPASS=abc123 sop encrypt --with-password @ENV:THISPASS < test.txt > test.txt.pass.env.pgp THISPASS=abc123 sop decrypt --with-password @ENV:THISPASS < test.txt.pass.env.pgp > test.txt.pass.env.out t_end diff -u test.txt test.txt.pass.env.out t_start 1 "Password-based Encrypt/Decrypt roundtrip using @ENV special designator (different varnames)" encrypt decrypt password @ENV INPASS=abc123 sop encrypt --with-password @ENV:INPASS < test.txt > test.txt.pass.env2.pgp OUTPASS=abc123 sop decrypt --with-password @ENV:OUTPASS < test.txt.pass.env2.pgp > test.txt.pass.env2.out t_end diff -u test.txt test.txt.pass.env2.out t_start 1 "Password-based Encrypt/Decrypt roundtrip using @FD special designator" encrypt decrypt password @FD 4<< test.txt.pass.fd.pgp 4<< test.txt.pass.fd.out t_end diff -u test.txt test.txt.pass.fd.out t_start 1 "Password-based Encrypt/Decrypt roundtrip using @FD special designator (different FDs)" encrypt decrypt password @FD 5<< test.txt.pass.fd2.pgp 4<< test.txt.pass.fd2.out t_end diff -u test.txt test.txt.pass.fd2.out t_start 1 "Dearmor/Armor roundtrips" armor dearmor pgpfiles=({alice,bob}.{key,crt} test.pgp test.pass.pgp test.sig) err=0 for ff in "${pgpfiles[@]}"; do if ! sop dearmor < "$ff" > "$ff.bin"; then printf 'dearmoring %q failed\n' "$ff" err=1 fi if ! sop armor < "$ff.bin" > "$ff.asc"; then printf 'armoring %q failed\n' "$ff.bin" err=1 fi if ! sop dearmor < "$ff.asc" > "$ff.bin.again"; then printf 'dearmoring %q failed\n' "$ff.asc" err=1 fi done test_armor_sizes() { local file local ret=$1 shift for file in "${@}"; do if ! diff -u "$file" "$file.asc" >&2 ; then printf 'Warning! (variant ascii-armored forms are not necessarily failures, but they are strange)\n' >&2 fi if test $(stat -c %s "$file") -le $(stat -c %s "$file.bin"); then printf "%q should be larger than %q\n" "$file" "$file.bin" >&2 ret=1 fi if test $(stat -c %s "$file.bin") -ge $(stat -c %s "$file.asc"); then printf "%q should be smaller than %q\n" "$file.bin" "$file.asc" >&2 ret=1 fi if ! cmp "$file.bin" "$file.bin.again"; then printf "dearmored form %q should be bytewise identical to %q\n" "$file.bin" "$file.bin.again" >&2 ret=1 fi done return "$ret" } t_end test_armor_sizes "$err" "${pgpfiles[@]}" t_start 1 "Armor/Dearmor idempotency" armor dearmor err=0 for ff in "${pgpfiles[@]}"; do if ! sop dearmor < "$ff.bin" > "$ff.bin2" ; then printf 'Failed to re-dearmor %s\n' "$ff.bin" err=1 fi if ! sop armor < "$ff.asc" > "$ff.asc2" ; then printf 'Failed to re-armor %s\n' "$ff.asc" err=1 fi done test_armor_idempotency() { local file local ret="$1" shift for file in "${@}"; do cmp "$file.bin" "$file.bin2" || ret=1 diff -u "$file.asc" "$file.asc2" || ret=1 done return "$ret" } t_end test_armor_idempotency "$err" "${pgpfiles[@]}" # TODO: (new tests) # - tests each substantive change in draft-02 # - tests for each substantive change in draft-03 # - tests for obscure options like --as # - decryption with two session keys (only one of which is the correct one) # - decryption with two passwords (only one of which is correct) # - encryption with two passwords (both of which should decrypt) # - symmetric encryption with signature verification # - passwords with trailing whitespace # - if encrypted without trailing whitespace, and decrypted with trailing # - if encrypted with trailing, and decrypted without trailing # - joint symmetric and asymmetric encryption # - can we encrypt to both symmetric and asymmetric at once? # - can we decrypt such a message with either? # - for a message encrypted only with a password, can we decrypt while also offering a key (that will be unused)? # - for a message encrypted only to a certificate, can we decrypt while also offering a password (that will be unused)? # - signature verification date window (faketime?) # - distinct error codes # - multiple certs (separate files) encrypt detach-verify # - multiple certs (single file) encrypt verify detach-verify # - multiple keys (separate files) decrypt detach-sign # - multiple keys (single file) decrypt sign detach-sign stateless-openpgp-docs-13.0/test/setup-sopv-test000077500000000000000000000077651475345515500221040ustar00rootroot00000000000000#!/bin/sh # Create a-test environment for sopv: Stateless OpenPGP # implementation Verification-only subset. This needs a # signing-capable SOP implementation to work. # https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/ # Author: Daniel Kahn Gillmor # License: CC-0 set -e SOP=$1 if [ -z "$SOP" ]; then cat >&2 < "$uid.key" sop extract-cert < "$uid.key" > "$uid.cert" sop dearmor < "$uid.cert" > "$uid.cert.bin" done cat alice.cert.bin bob.cert.bin > both.cert.bin sop armor < both.cert.bin > both.cert cat > msg.text < msg.binary < msg.$form.$signer.sig sop dearmor < msg.$form.$signer.sig \ > msg.$form.$signer.sig.bin sop inline-sign --as=$form $signer.key < msg.$form \ > msg.$form.$signer.inlinesigned sop dearmor < msg.$form.$signer.inlinesigned \ > msg.$form.$signer.inlinesigned.bin done sop inline-sign --as=clearsigned $signer.key < msg.text \ > msg.text.$signer.csf done for form in text binary; do sop sign --as=$form alice.key bob.key < msg.$form \ > msg.$form.both.sig sop dearmor < msg.$form.both.sig > msg.$form.both.sig.bin sop inline-sign --as=$form alice.key bob.key < msg.$form \ > msg.$form.both.inlinesigned sop dearmor < msg.$form.both.inlinesigned \ > msg.$form.both.inlinesigned.bin done sop inline-sign --as=clearsigned alice.key bob.key \ < msg.text > msg.text.both.csf if ! ls $(objs) > /dev/null; then exit 1 fi stateless-openpgp-docs-13.0/test/simple-sop-test000077500000000000000000000240611475345515500220330ustar00rootroot00000000000000#!/bin/sh # Simple, positive self-test for Stateless OpenPGP implementations # https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/ # This does not test all possible combinations of options or # argument structures, it merely confirms that the standard # subcommands and options are all implemented. # This code makes many simplifying assumptions (e.g., there is no # whitespace or metacharacters in filenames; filenames follow a # strict convention) in order to be simple POSIX-compliant shell. # The invocations are not necessarily safe shell programming if # those assumptions are not met. Please use caution when borrowing # from this test script. # Author: Daniel Kahn Gillmor # License: CC-0 SOP=$1 if [ -z "$SOP" ]; then cat >&2 <&2 "No such command: %s\n" "$SOP" exit 1 fi shift # We skip commands whose inputs are not available. # Return 0 if the test should be skipped, 1 otherwise. # missing inputs are printed to stdout. skip_test() { # do not skip commands that consume no input. if [ "$1" = generate-key \ -o "$1" = list-profiles \ -o "$1" = version ]; then return 1 fi shift local arg="" local ret=1 for arg in $SIN "$@"; do local noninput='^--\(\(.*out\|as\|profile\|userid\|' noninput="${noninput}"'validate-at\|\(verify-\|\)' noninput="${noninput}"'not-\(before\|after\)\)=\|[^=]*$\)' if printf %s "$arg" | grep -q "$noninput" ; then continue fi arg=$(printf %s "$arg" | sed 's/^--.*=\(.*\)$/\1/') if ! [ -r "$arg" ]; then ret=0 printf ' %s' "$arg" fi done return "$ret" } sop() { local suffix="" if [ -n "$SIN" ]; then suffix=" < $SIN" fi if [ -n "$SOUT" ]; then suffix="$suffix > $SOUT" fi local missing="" if missing=$(skip_test "$@"); then printf "⛅ skipped [%s %s%s] due to missing inputs%s\n" \ "$SOP" "$*" "$suffix" "$missing" SKIPCOUNT=$(( $SKIPCOUNT + 1 )) return fi printf "🔒 [%s %s%s]\n" "$SOP" "$*" "$suffix" if ! ( if [ -n "$SIN" ]; then exec < "$SIN"; fi; if [ -n "$SOUT" ]; then exec > "$SOUT"; fi; $SOP "$@") ; then printf "💣 Failed: %s%s\n" "$*" "$suffix" rm -f "$SOUT" ERRORS="$ERRORS $*$suffix" else PASSCOUNT=$(( $PASSCOUNT + 1 )) fi } sop_fail() { local suffix="" if [ -n "$SIN" ]; then suffix=" < $SIN" fi if [ -n "$SOUT" ]; then printf 'ERROR: do not call sop_fail with expected stdout\n' exit 1 fi local missing="" if missing=$(skip_test "$@"); then printf "⛅ skipped failing test [%s %s%s] due to %s%s\n" \ "$SOP" "$*" "$suffix" "missing input" "$missing" SKIPCOUNT=$(( $SKIPCOUNT + 1 )) return fi printf "🔒⚠ [%s %s%s]\n" "$SOP" "$*" "$suffix" if ( if [ -n "$SIN" ]; then exec < "$SIN"; fi; $SOP "$@"); then printf >&2 "💣 succeeded when it should have failed: %s%s\n" \ "$*" "$suffix" ERRORS="$ERRORS ! $*$suffix" else PASSCOUNT=$(( $PASSCOUNT + 1 )) fi } compare() { local args="" if [ "$1" = text -o "$1" = clearsigned ]; then args=--ignore-trailing-space fi comptype="$1" shift if ! [ -r "$1" -a -r "$2" ]; then printf "⛅ skipped %s comparison (%s) of %s and %s\n" \ "missing inputs" "$comptype" "$1" "$2" SKIPCOUNT=$(( $SKIPCOUNT + 1 )) return fi if diff --unified $args "$1" "$2"; then printf "👍 %s and %s match!\n" "$1" "$2" PASSCOUNT=$(( $PASSCOUNT + 1 )) else printf " 💣 %s and %s do not match!\n" "$1" "$2" ERRORS="$ERRORS Mismatch ($*)" fi } show_errs() { if [ -z "$1" ]; then if [ 0 -ne $SKIPCOUNT ]; then printf "No errors, but %d tests skipped somehow\n" \ $SKIPCOUNT else printf "No errors!\n" fi else local SKIPMSG='' if [ 0 -ne $SKIPCOUNT ]; then SKIPMSG=$(printf "%d tests skipped due to prior errors" \ $SKIPCOUNT) fi cat <" dearmor test.key SIN=test.key SOUT=test.cert sop extract-cert dearmor test.cert SOUT=zeina.key sop generate-key "Zeina " dearmor zeina.key SIN=zeina.key SOUT=zeina.cert sop extract-cert dearmor zeina.cert for f in cert key; do cat zeina.$f.bin test.$f.bin > both.$f.bin SIN=both.$f.bin SOUT=both.$f sop armor done SIN=test.key SOUT=test-revoked.cert sop revoke-key dearmor test-revoked.cert echo b4n4n4s > pw-orig.txt SIN=test.key SOUT=test-locked.key sop change-key-password \ --new-key-password=pw-orig.txt dearmor test-locked.key # ensure that the key password is based on content, not filename mv pw-orig.txt pw.txt echo no-bananas > wrong-pw.txt SIN=test-locked.key sop_fail change-key-password \ --old-key-password=wrong-pw.txt SIN=test-locked.key SOUT=test-unlocked.key sop change-key-password \ --old-key-password=pw.txt dearmor test-unlocked.key compare binary test.key.bin test-unlocked.key.bin cat > test.txt <&2 < "$SOUT"; fi; if [ -n "$FD_3" ]; then exec 3< "$FD_3"; fi; if [ -n "$FD_4" ]; then exec 4< "$FD_4"; fi; if [ -n "$FD_5" ]; then exec 5< "$FD_5"; fi; if [ -n "$FD_9" ]; then exec 9> "$FD_9"; fi; $SOPV "$@") ; then printf "💣 Failed: %s%s\n" "$*" "$suffix" rm -f "$SOUT" ERRORS="$ERRORS $*$suffix" return 1 else PASSCOUNT=$(( $PASSCOUNT + 1 )) return 0 fi } sopv_fail() { local suffix="" if [ -n "$SIN" ]; then suffix=" < $SIN" fi if [ -n "$SOUT" ]; then printf 'ERROR: do not call sopv_fail and expect stdout\n' exit 1 fi if [ -n "$FD_3" ]; then suffix="$suffix 3< $FD_3" fi if [ -n "$FD_4" ]; then suffix="$suffix 4< $FD_4" fi if [ -n "$FD_5" ]; then suffix="$suffix 5< $FD_5" fi # FD 9 is used for output, not input if [ -n "$FD_9" ]; then suffix="$suffix 9> $FD_9" fi local missing="" printf "🔒⚠ [%s %s%s]\n" "$SOPV" "$*" "$suffix" if ( if [ -n "$SIN" ]; then exec < "$SIN"; fi; if [ -n "$FD_3" ]; then exec 3< "$FD_3"; fi; if [ -n "$FD_4" ]; then exec 4< "$FD_4"; fi; if [ -n "$FD_5" ]; then exec 5< "$FD_5"; fi; if [ -n "$FD_9" ]; then exec 9> "$FD_9"; fi; $SOPV "$@" > fail.out); then printf >&2 "💣 succeeded when it should have failed: %s%s\n" \ "$*" "$suffix" ERRORS="$ERRORS ! $*$suffix" else if [ -s fail.out ]; then printf >&2 "💣 produced material to stdout: %s%s\n" \ "$*" "$suffix" sed 's/^/ 💣> /' < fail.out >&2 ERRORS="$ERRORS ! $*$suffix ⚠PRODUCED OUTPUT⚠" else PASSCOUNT=$(( $PASSCOUNT + 1 )) fi fi rm -f fail.out } compare() { local args="" if [ "$1" = text -o "$1" = clearsigned ]; then args=--ignore-trailing-space fi comptype="$1" shift if ! [ -r "$1" -a -r "$2" ]; then printf "⛅ skipped %s cmp (missing inputs): %s and %s\n" \ "$comptype" "$1" "$2" SKIPCOUNT=$(( $SKIPCOUNT + 1 )) return fi if diff --unified $args "$1" "$2"; then printf "👍 %s and %s match!\n" "$1" "$2" PASSCOUNT=$(( $PASSCOUNT + 1 )) else printf " 💣 %s and %s do not match!\n" "$1" "$2" ERRORS="$ERRORS Mismatch ($*)" fi } reject_output() { for f in "$@"; do if [ -s "$f" ]; then printf "💣 %s should not exist with content!\n" "$f" ERRORS="$ERRORS Should-not-exist $f" else PASSCOUNT=$(( $PASSCOUNT + 1 )) fi done } confirm_mode() { local foundmode='' for m in $(cut -f4 -d\ < "$2"); do if [ "$m" != "mode:$1" ]; then printf "💣 %s should have mentioned mode:%s, was %s!\n" \ "$2" "$1" "$m" ERRORS="$ERRORS VERIFICATIONS-bad-mode $2 (was: $m; wanted mode:$1)" else foundmode=yes fi done if [ -z "$foundmode" ]; then printf "💣 %s had no mode, wanted %s!\n" "$2" "$1" ERRORS="$ERRORS VERIFICATIONS-no-mode $2 (wanted mode:$1)" else PASSCOUNT=$(( $PASSCOUNT + 1 )) fi } show_errs() { if [ -z "$1" ]; then if [ 0 -ne $SKIPCOUNT ]; then printf "No errors, %d tests passed.\n" printf "but %d tests skipped somehow\n" \ $PASSCOUNT $SKIPCOUNT else printf "No errors! %d tests passed\n" $PASSCOUNT fi else local SKIPMSG='' if [ 0 -ne $SKIPCOUNT ]; then SKIPMSG=$(printf "%d tests skipped due to prior errors" \ $SKIPCOUNT) fi cat <