pgpdump-1.4/0000755000175000001440000000000012041264012013425 5ustar dmcgeeusers00000000000000pgpdump-1.4/testdata/0000755000175000001440000000000012041264012015236 5ustar dmcgeeusers00000000000000pgpdump-1.4/testdata/sessionkey_rsa.asc0000644000175000001440000000107011753473643021012 0ustar dmcgeeusers00000000000000-----BEGIN PGP MESSAGE----- Version: GnuPG v2.0.19 (GNU/Linux) hQEMAxw5p70RS/+lAQf+PKkPn604EcH56EEWdP64SebRVBkIED+Fj1NMLBOu9KCG keUWYe2LBfxTHK5Con1oR0rbWk3V3TXZMUs8S6l7VJ2mF9dVXftOhhqxU3s2Idjj QHifQreYLk73FHvONdrIAYIEKcWAyK3s1wZ1WqobRMSXTk/cA7b9YUAQkoctc0Ix EtgLF/azlR1pc6w97wZI5MKrUnAnCqfbDd9TCHvPE3OhQzceTicHs+q5MLrLd1M3 zBL2AD+mO6kVs3HvEy/jPd16wAE2SNakeDtJzypcU4PoDy0vpbNOTKnQievRDRCA 58qxnf0r0Yf/R5e6Jgfaf/k64YxZDstnTAigN8I/09JJAcl0dyZmrXLpC4DPTKrN 6UgOjeE2+o6Z6OHvHNMaZSibmZ3vbTQG/C7xl+8mNNZEywcjLSx43zgmWNY8ajH0 COwqvQVAe7A9kA== =DQda -----END PGP MESSAGE----- pgpdump-1.4/testdata/README.asc0000644000175000001440000000144311734405742016704 0ustar dmcgeeusers00000000000000-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 # python-pgpdump: a Python library for parsing PGP packets This is based on the C version published at: http://www.mew.org/~kazu/proj/pgpdump/ The intent here is not on completeness, as we don't currently decode every packet type, but on being able to do what people actually have to 95% of the time. Currently supported things include: * Signature packets * Public key packets * ASCII-armor decoding and CRC check A single codebase with dependencies on only the standard python library is compatible across Python 2.7, Python 3.2, and PyPy 1.8. -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.18 (GNU/Linux) iEwEARECAAwFAk9yC2AFgwAFRgAACgkQXC5GoPU6du2kwACfS0g3q5j0LBD84k26 A+fLXJj203YAnjPKkIlQD3fBtmDf/BwI6fMoRsTw =QVW3 -----END PGP SIGNATURE----- pgpdump-1.4/testdata/v3elgpk.asc0000644000175000001440000000165211753357022017322 0ustar dmcgeeusers00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: SKS 1.1.0 mQEPAzT4vAMAABAEAJIlqDQYvqIEvx3jnxgGmBaseV6Ly/OXNZeO352ymcnhCuCD8pwZmXOw LTVpf7UIoQ51j6XUcN6QZSu6QuKG13NH2TJjyOcI4mkwlaGK+NYKo+YEIKlChB3dqFiP1OYa ZU4T3hsvZyTSOo7ieug9FvF05wiMByRYTtZjw9z/VwoDAAMFA/96ojAJ71ePXWieIkuIIBrj LdFOQ9uOisSEACoM/UT1+vhJb1Fo8/s8xESGoN2jR5DjAn7RbDqv1ryFCaP7J/Qidcf8X1x8 yJb5ELbvI+jjNCGAMcPU9tVcE1IyoMEptwSqIuvNFdOq0puSnRdrzRLJJZLvA8fJ7V3Cxl+j It71brQ+V2ltIFZhbmRlcHV0dGUgKG1pZyBsaW51eCAyLjAuMjkpIDx3dmRwdXR0ZUByZXB0 aWxlLnJ1Zy5hYy5iZT6JARcDBRM0+LwEXFt+e5KvZo8QA4tDA/9fK8HynDW9/U4a0WT9rJbO Y0p7QMRdjJOVu44jkhvpc7VLR2OJ0ETlC98WDz688J8Rspz5LeBvxILuYfXkjOTSnLaqZTWY lykDNPQJmetdanCFoDtutfbmhOOuNgd/bHcjlYuKPgE6AXkFnoL9F4ScCG7sjesaLeplqAjK AnIUvgP+LHiYpq2Apo2zi2D3K1H3ZPN4qubONK7A+otntSGazj0ks/DAQs0ua78f73If7B3v W+jjnxwhp8+c5hqrlKSurdXZ91wAd7V8gUCFIC9pUbZ8UodECxBmOwARnzRfI5yDuGA5vlMu 8LY/SG5mEsxk5YL9sa3xuCX89oHP51vQwDs= =pG5J -----END PGP PUBLIC KEY BLOCK----- pgpdump-1.4/testdata/sessionkey_elg.asc0000644000175000001440000000162511753473472021002 0ustar dmcgeeusers00000000000000-----BEGIN PGP MESSAGE----- Version: GnuPG v2.0.19 (GNU/Linux) hQIOA7cF06TDdR04EAf/S4Y0EIc+gZpGMMIeRbWKBHgKDlTYSbcZCcRRpWQuhNp3 +3aAvtufR18rlmMPHKIZXP9TMqA84GIqexHPNufTzoDbPkcNcPk/fiO09gK4GewD 44Gp2iDV5JBAeNMF84KzYSpCTshUNdkfWdLE+XwwxxEFkHvpRGOAz3IUDqARRCXy wJZ2xEyvu/04VSe8l0vv0HFCsr8an+KxLDpfU9kp4p1Qi6eyEzIAM5HBPRCZzHMg mOjuRD+Qyp8DoRX62e2kUczH2c1//yBXBqA6DCz1DXG/4XObmzKwD75en40CvmOZ wSMF8guIV12tC1ZVJP0iK+0rchaKP8AX2MC9YiaQGgf+KUImjjW9Ik6EtBI9A/H5 1i/EbcflZq8JscH17jkhHB1f7WUaBMQZnLUtPoj/28U3gQb0O9Fc8PD0EBhsenhz jkIrJXmnMLW9qhdD/GeymIK1GQqVSMBqXXCS0XJINNCW/ZWIgPL9fKtYhAEhA3uj EILo1vxncOQBIhIKmPctVffB54/hoNakr3hsqZBoGYe3iLipF+W0Tg+/xqjdxVal szeFjmkbNClX+ou8Opl560ZJen9gujmljLQUbfWfqAfAzpHg5tkbaKY5I+U03GE2 crcwA5p6D8HObZvOS6GPJWKgXbpAOWX4Q9gFUsQx8K5K1sDk9lhXjtBlvsg+UZgS m9JKARkukSZKKf1FBHailjJlbpgBt3Uq6QTLJVCqnvwoHZ62+El2Qo4AytcGmKC3 JqKOwpEwu7k7BD9azZJldRzO94lPxEyuOW20zxQ= =6Tor -----END PGP MESSAGE----- pgpdump-1.4/testdata/linus.asc0000644000175000001440000005731611651667662017124 0ustar dmcgeeusers00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.11 (GNU/Linux) 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 E1MpbWQToKVKp6FSy5dPlVzV6HrTv16s3fL4k3GXrNk8L2/7kY3JzbypJ816O3jC bBr/LzpPuhB+Cjvujwa79o7/HM6PDuNIMNHe72n+EVW2yrOm3FnO+zeFVqItL7SZ qv2phmF5oJSo5c4pWjfxWDmCQ6EwTAq9mz3Hd8g1T+3xyTE4eroqItqnkGrztc/R kmwJowHB0tiE4XECIRW2XPDKjcO8ymUhqGqo0QtvvVgOGkyr8e77jahHemoBII8t 87/QZRJ0AZclWG/svueDTYRNIGZU5QARAQABiQEfBBgBAgAJBQJOeQiSAhsMAAoJ EHm+PkMAQRiG6hsH/0WLUZUbmqc+rXhLRYpgRbc3z3UvfstpeYOH3vuv+PZ3Jk6h giJXivHprq6uGr1RtyA3D+R7TVM0zjLsSFb+UWfElBdIyN2xcZxv0fBR68OTYUxX WH2CQcuWpFSVOmNV1DBMH3Ax3htaFmzHJtOcaHqjx0LAERoY2wexIFg7zU5etMT9 9xkSAoJ4pbbF0pGJrO7oy7lYtTqAHLac5zqgvbMolmMAJ+WVDMHHn7rPY3hKPQkE 6hvPhFHYksSUwRyQxt2pshL5z6fTYi0yYI5GBqR/viT5MRtpnHdlNUeeG+pCpqdn PWLgjkjAcnGRSxgt12BivKVkf4zA0OUHm7cjXfw= =4yz9 -----END PGP PUBLIC KEY BLOCK----- pgpdump-1.4/testdata/v4_secret_encrypted.gpg0000644000175000001440000000603112041261412021711 0ustar dmcgeeusers00000000000000yP%AQ&gVv8 ]Ȁj%yMZuk5P88BpD/$߆Y1K/|-Z%ƴń;19j%*̪1}L881H ,LR"01wc}_Չ`QlZUO,j ^֩QNhC$=bI }kh2r5T33}rR o~qs4+ {#DN̑Y\y,Gf4X; 2e%R֭Gs]l}K)U]xeuk{UCr[=qMYϐd/z8r<֝Y N2FT RAa1Qߥӏ_V 'lRcޯޘ-qAj9{&L wk xB$mZK'qFأ߻A$alq HSj /nYF> iM1PaD|XANoi^e2vUWMi9IP/+FRT4a>Dq\>|J0ev]Stxc"{QA8;X!A21,Yg @>nk-ަ (δSecret Key Testingz"P%   IE:K#S^k! 6 -ܽ eh01[D¶h͌${V|"C"f3 ,hzf]+gr3\["ܭ`JkXҼmdrfsuccK^<-)ypǓ:Ĝl#~đ't? R{VDuֲq,_^49?Ќ/pkB.K܊dICu7)Zu53NVȄ>Z1t[kŚYwe09s>}\nSf)hGq 9UvZ$o3$䭅p R\ى~2yJ*`"ͤ{&'U<ިow)Xp;!lFUUA4bhoPg$a P% IE:*}]l/$'O38P%&:΄2ptS#|Q\fq (`rƶj¼KF鈗"/s uꥏ5֣b EC#~R" W^nUs~qC=AMWf6G$p$҃qZw9ͳY\r/faD%FP&@ y|A=KS-' ep-%zw`WYd8 p_oxoݺlP` k(Aaj#)!V F9:N rN=wmpgpdump-1.4/testdata/junio.gpg0000644000175000001440000000576211734406131017103 0ustar dmcgeeusers00000000000000@X4^2ߟqQLP$u4ύm-gK~@[#}lٛWFeJήr˱{V-T5FcաQ? txx^##E|104KSeU7oi/ dZa nj^R9Zvz^@X  ٤Q4lV' /iz(`4ȉ^Jq_EVyXJunio C Hamano ` M,   ٤53>K#F#ZhdڤߙG Junio C Hamano b"NZ   ٤>l6}@ݕVlX 76"Junio C Hamano c# Fb] ٤];Cw;||kU2HĞE xvʵ<:JFIFHHC    $.' ",#(7),01444'9=82<.342C  2!!2222222222222222222222222222222222222222222222222200"4!1"A2QaBR#U'1A!Q"R ?S~>ֽݭ W32k) v1΀ 2{sNFRI%E$(v8ǩM 5M&(Xn%[8B{' kvLJQAbSŧhA7tMG&E!꙲&-R֝G,5{an8tXΩF(f錗N%QbtǎS%$ `{t 1܆yy}F&RA*[.-*Yu{X t/@iJ "*ZlZR{6&WLx`&7rV*IV!Cb*l BgRwV4܎GFW /BThu Vq7U#>ϸItd,Anֳ% Zf7irBPz(D5+yu5Nᾇ" !h>`,E"^E fI+Z}>`?^\;@9|T"=/g5ʨ(ALF-1(`$v=`YrlG8쇤r[Gϋ@~".>;U:s"#%u8TTUkq=}>XO3*SMlfR NڵuXOBssd(AEu0Hn- @xgR[3x2Żiv(-ъUqky1 1z; CרIM Nݷ2Qb<>l\gSKaiЄF[ #};ps860A RBË C Գo'~oSfԝw3vdzidVN T%RE:U*pYV<4\A"N%n8q@n& }f~(l_qtdEL$|4Qk`;ЀY=KGI @X ٤TSK,#v68Ò`{ eݓXRܶ==rK8pgpdump-1.4/testdata/dan.gpg0000644000175000001440000000264711726041240016515 0ustar dmcgeeusers00000000000000F:9}~)-bֆ:lBu/>He6@ӶhlneS]=k2 EIBq!ؤߙ_=`p೯H9<Uy,eK|f=Qo3gq d$;#rh񜀙;@#V &7zۓK>W>cF,Y^!竴GYn]; rY%m6֣*NAٶ|Œƫﱱkؔx1 TB5D\,d0F|]1ֶ30թdĠUZ6W)D~:1HQcT1:e]TwgF'pra/_/⻨M."xH\7@rqDan McGee e%NN   \.F:vj5uaLQ ꖉ%دϦSunڷ})Dan McGee (Developer) b"NR   \.F:v>#}kcDRM[=Rej@MKd)Mô,Dan McGee (Jabber) b"NR   \.F:vj ,܇͒t>[Ur;jM0̽ F:[}b]<8c)޺Ry&ދχ ^Fx/\$TcILMT1`0c%sLvѝ0vrcQG :n9x`jOrX34WO9WǬP4 /0Ȯ  ؂4tt?0\iah4}M~"*2nk`%L xK~,٥w9їZ@)#1qrgNf>6!W5 eAB,sNUD(f*bIhd4 UP2T H 4z*98Km֕a5"izh,օ2iNHJ7pb+cam:qIhggm}LZP"NQ- +(0бR_pISmػ++> qpVttU)vBJAna]qpv^즊/BxI F: \.F:v9.T`{Pg94;uB[wStu M^rpgpdump-1.4/testdata/linus.gpg0000644000175000001440000004265511651652157017124 0ustar dmcgeeusers00000000000000 Ny.kWQ;|;z"2)?Q{3' -ll TaAB:cVbV)3.*7[hJqZg&f~\ ]_zΏ;~+1SocAY7$;!e7奫/_34X0xht>.U(8 L";omjX7F" ,AHQf2'Xg|c C\.Linus Torvalds FNy v,FvEoZcBLrW?<*kJxE{FNyJ ˔}h|u\) kËШEvTT*%w57gl@8Qf?$o FNzJ s:Y6SdIU吻BZڝv1!P"磁T-_FNz tgEH㟜,w_TOc: Č/={q/FNƺ q;aM@zOYK/݊ҒREL\DqKQ.~[=Jv>FN 1G -)f&@cF#.7$hPi4<+x@/"FN% Me=v'"|#Lh@(SIMOXvpAވFNzd :9gL@LbJ~6-^)AJ/ҞT|-7y/+-bD͉Ny ;E>0J*j8R t0-H!s^`e믦Izֆ˂l1}Z1 =qVj28hNRVJ^MPW.'| i2m[[7Q?,XNz lyx*ϗ'IٷZa\&$puJAzdڽ#,B+K;}v}KA%=qN~`9H]c nl+ăZš. en\rQa| cYޕ[RlYkЫa.6m_>љKL|Xno5tHHL c$v;\]O;3 N'|T'e hN! 8VJII{Eғw7T)ަ$gx1|QYD8X~F`{2˛ltFi"ll9 TEX-WhTar`nJa/wPPa's&>BWisw_;d~^e1Bm":Y3w[`e^`-Ed:EuEy!KB/SCU&,iܠ%yԤR(pݸΦ8"Ny   y>CAnh7 j(Ů[DޜZYd!Lzg<}7uDZEzOG>+sGu5Csbz90SE<'RmRh>`HG`<E\>=WxˬM¦pV ?;g"NqҞ5u'L:Zm+5BL"ʎE̬x ;21uh\4ROh77%Vŭ`D?z9֑?RQo *#U=f.N~  &T!0Yo`C([Ǚ?mBuE([6$#=x0: 4agIP q1Ep1䃛RFſum b&Vb(fJ0N!RDGRK?A :'JծB6o7Tv|@OP,p&ybCO\ctIAp 5 iGRg%OԙIz0QdOHI}. @|ȝ|LC&!S/3u58ߵw.uש"S5ᆍ7q +: (E:K]4IDZ@+:k:5HB`h>(RdBZ:U0 Qx ͤxe̓|h_%3NTO<A#`dx\P'2Փmhl)@n++59*}@̯D׏3 .TbbAü]2 +9TL-J<2BW hwyx;fP"19"*)ڥk$fO^@ < d}ʾ^Rj$mNGodɞ?OV6Sƥ = ^ %W# C?ϒqWqu5L۫{Y!v\sE U `TjNnu(D}B"Kw Yw#Y(`@gp+99`xtHhjₛZ@#ߗ;Upnë:[,J6mjUP?F'eRJ ULvMq+O-Kr| 9r& ݬ/N\ |ln,LεҲ<ǖ=0hLzj~1 O#ѭzE] iX R~d"ֱ,/vRpJJl[\]Ml8rTRѫ (q}]#_"8,nj!W:57hBUi ( cwtqxGGH|>M(3$eyz߆ZSvAcs<1! z=BM߭Dz|_0YF MC;tE/ u܊Ū7Eξ"]G I,s:'i`_">DmA/N~7f>o$Pǵ3` g- ;V\+9ƀLĤ,#CidHTlc~2K8S:"!xsUfnk) |qfwz)tE T ޳ OƋN 5ʤD*7tj] 16*Ny_D5*Q$5fmjPV'{N:_ȒUOQr4hn6=hUiEG| E}8 vovPs-g#e< :F^Z*1!wҿ1+)q`d.lurZ9YhesXg__wӽ*@yjcU6/I y pb1pFG?zͦ%z`NJ;I 5 M[U>0==z0.Bl@S|AK^}dB0X@Um/hqUdEnh͞,ɖEe4]nq'E) FOGqt&Bڥ;r  z 1Q3Q,Z-zLԫ[zvݷta>Ǘ{q䅐Y$K2z:gD >|mg]7 #@PRXd/Y^Ҙx˼7_f207?%=1??PJ.eTo|(u `T:P솯;󫋖My%r;Ow4Z1Wc,p@iGN3RBvz/dmo#Sj=f Z3:esd9"gvBy"J{5՟Xd6}ێMjׁ!p*VZbE3,%.uWZ^Kٕ n4&D6 ?[feS웤șD0CZ=cӍkbxתOt麾_0҈ڨm-Dke`lG\7P-6e:sg#Fivۑ`m8bF@} >oPioeo1m2B.ckƸj/TV%C2>nKuK2$#¯N̟ ^Y1#,a2fCHzgi"f):ӣ[QPOSTmo^oҴeO +!,cJzX$B߽8&ah5)+j}ٜ9i?SbZ?LcCOۓ.1 pI_'QOA-o BI]SW㜇)i75 ܿD%7]??yo3hB ?sknxd=+jI֛p+=2ߟ|ښ1 2^|S/-6ӽ NQ4#R>]Rl69( h32}&v>M"_:ꖳMGCFǐ51\~vrmKH1 Z|B'y8y*4+ޘ/ , c?_FZ/wv,"E0s񫔴s<XOkW@ؐ2Aީ/3F^h“CR.N a:5^X $`JLvm|G^Dŀw ráBXWe 7!DO&CJHjN+sחXmoRrfb6T4<ѣqw`V| 'EtkQ 'j OdG6|cj#)sōK1DGw26<)!ꚈBpjf`5-#dO*ޯXQ v)ճufb)Aw0d^`57 3ПX&bnOzz&Q]!Go4\G e8Z[=N ʟ]&Y}dخ5|I[Vlx[5$=RR~k[~YK( )!w!0Gr2^ˍG'`~ vȼ$9mLbHH֦'}xISZ#kEI/Fc##NJsрɏN~=?yseBx-{sږ74a\ULn^+x3c6~+)Ÿ0b[w&uD7F5&TtjFsctK[FPnAU"_@ڶ P]W%1K#SJ00CV [G+Ķe_7X6zﶄko]tjHFq[y%fV-ݿE DʞuYb49Xht%H`,]w~~Z5Cvv^ cr|B#٠DX^gNxx>pSY8H)VX xqTvNN 溟\^T߂P|t$mN{Ly#ifwN}CG}HT{#loubҗY q#S`N@sތbq j-HyGXٵNXH XhI+nX"7% _v0po3#l8QuL5i[+*zd0qʼn'DXћt> \čz8[jIM))襚ZeA֌S[ݭ3R $ $_8r-6ϑM\KUЩ)MŧeinY[+IST5[}7b:ﱿ% ə2Uh1qrFEW7޿2N G*gXWT$;#pb<:.vٍ%vB6(V1bŸab9vz(k`'JY\ *|=Ac?|c>ڀ~"5wXм9т/kk >:^Jpˤ'^]Oл0 jRPAdLV%QcCX D;;]ʇ' SͿ9 ֩.[Fp7IIp&nŧB@Rv74eM[R6VZ/W$^wTV.~q#yeyNޔw2pwۜdEsDX֓~V9)"}sVm^$nj_M !|5?ܚ-]({)\ wUX\~+^xntۍHCKҽy24nU朡|XiVW$,+"[c߬ݩԳqZ>LBB0sV{dQw "'j%8rͯ{'Ϟ:WG*)]k9md8ѡşI@rlF*6h3wѩ.H58{1w*FqVR(-1JANbua*H]r6O$3tuj-K>A)eR"coAm~ę" kduM`/3Y X*62@ $)kN sAa7  V),sQnNo*#: `BNId<6g>54\vU3iaCxc_" UWǶ<>vR)FZbE׀ 'm/(;/-5S\bs/{J["wLb&lMC=W4VIT8=P^1s*ӝE9pt>!]]@tD[k'5 H4NvC~գ8Q#uӎ+oՄ[p[4.G9=v1j~>6(CثzUj'83GWYD3 )}_$jn)tRة(\mHɒ^ؽgqW >WEz*r5ӕdI\ ٴyv'`Ua+VaH)/Nr ы8Y%*c~\ôEN Ac;7VR{W jqnjkx&g])hϜ6"q́xBD;ƫFzNpACl/lElIuyi^#w1@jWr'4'DkAeنNkkh DI2~;O*BHo?M~tMnPYUb,c9rq׀=%K:}Խ& G!E%P7s9Gվsax]aE6P o?8icx[ 6`2cb̈P*|kCnRzC¸;t-ٔ|?b 4(O;i:cN/g Ic{ō()m"gI^.B}paTkKO=ӎ^B-jt]KΔc=76llz0),^ć^#6h١1;fJ)8>y1}[nrP瞉N 8۽`i>2h{$+w,s;OzБ 8pȺbjRmFJcE0σ]zEo\MVSL@jZK{o?Co{u 0AL&zoY0O'D\5vzA'4(-~Yj,؋Н 0 \Pe(K<&gmX!7Uᦐ:Bm&ES2~{ X LPD&kw f'zSC/vâ1#/oRs}bY0G_Abdoۧ^x-Z{ⱱݾd@ñ{ /I*-0ppz~% V+̪3<+ܠ{9XVڿfZ%t̲[eK]#-ё$@Pzzsr?$#rؙ8g}&eKN!Q,R|ht]p|>R©ON Wk kd l (k'AM/уjgP )W, 0nd-4`#`x\Y h^J~w[VօT-OK9\_Ė|ogNul%.b̐ARV.fN9ao5fY$< pW^_2 [ʷ!S7fa2Q)d*;Odx$o,S"j*\,v7HKHӧRFq{U%c O<wOPI)G @ؚ=:{eɶ$-7Z(ʢF|?;/$ߖbl%1K0h08_rzВmv!6iq"d/Ra>+*+,zκZW|L?bI{%s( 2YŠyHd eFX;5j8񠻫~:$O#Лa1]yg[|g#rs()#?G샫#QrO>9h-e?5E j$Akem)49#zx?H  Z,aڜ%&]}$Fpzf⨹?.S>kS]d#ưقc*={EoqL)ON l <ބwi1,u~LDwVr?BR`18 ($ɽ_5hS"?rb$ 28-E[/F`Ū>V-'-}`~5Oczx/uLPFMAZ5% avhǐhE1֖X4HZ&.!I{g9L/ji&ȵ6tl,l·yݵ N:907. vɢ~X>^ay2'QN. .k+FmԘ'[H^el+,{vkLk;*>@QR<7Hο!ŚG-?q/s ۇj]s$`/t̰م PW {_c }t8G4u5ώ9rvUTB-ԇ7˞K@1Y3yTUXWxeJk1bh6śoi?=8I۰R7{ sg.[FgC.p  ~gMlqƙ#Q=^7~@fn4 w>$H63ޢ WcEQ9~ɟv,o1]LĆvj&C n˴ l%ѡP3q#3/UG&D[ɳg4K3,qdXL" Yc upm^Wrzz/waO %;Shpps't@zVmnUDda`Gƭ&Z3vB*)|\Jȉ~AzN@f c x\*L,[b-%s:pʔOI \ǸupAC#0W!zH k}]+w :L`@'A|SLW$݇u|ۓ̷u">X:è{Jjt]l톹J\Л3 z 5E !f/U'Z|WV o*IO[SWQsuP/ PN 앆 &s~+Yz';*posқ3AErYrj𡌽uWJj:YT# ߅5,=:~V4;2Bl#M*efq ^5GyfSnkf/oOQ,rGK+Bv4'>zQJCî\iB8e}so6ۓ:2q9W^u!Si*Z#qFM}}=ܩ@ɇpƜTL3B]%>*ɵoEZuQ4_I_{S߮ u(-oߤh%y̿Ne)RPY''ȠпPzxV-M {( 7m`z^gGcq.˙VĭOn:q2iT-ZrbR#n{+fHYE1{BuH#9Nda!A.-o[ @TZp2Nv 1S{o1fIHH &|D Ӟʁ7lGQه~t h.$j] 9Ocst,\|yP. ~9,֐Ïxa(DF4C epV`@ Uw4J&A+=D8Bm(~xi!{i7|cgg.J32=bڜA?UK*=(QPO~ϰϛ!ξhyd A#=;CAEQ>xKE`E7u/~iyw&N"W鮮Q 7{MS42HVQgĔHݱqoQÓaLWX}A˖T:cU0Lp1Zl&ӜhzB X;N^xґ˹X::(c' ǟcxJ= τQؒĔݩϧb-2`F$1iwe5GBg=bHrqK-`bd#]pgpdump-1.4/testdata/v4_secret_plain.gpg0000644000175000001440000000561212041261412021023 0ustar dmcgeeusers000000000000006P%AQ&gVv8 ]Ȁj%yMZuk5P88BpD/$߆Y1K/|-Z%ƴń;19j%*̪1}L881H ,LR"01wc}_Չ`QlZUO,j ^֩QNhC$=bI }kh2r5T33}rR o~qs4+ {#DN̑Y\y,Gf4X; 2e%R֭Gs]l}K)U]xeuk{UCr[=qMYϐd/z8r<֝Y N2FT RAa1Qߥӏ_V 'lRcޯޘ-qAj9{&L wk xB$mZK'qFأ߻A$alq HSj /nYF> iM1PaD|XANoi^e2vUWMi9IP/+FRT4a>Dq\>|J0ev]Stxc"{QA8;X!A2K#S^k! 6 -ܽ eh01[D¶h͌${V|"C"f3 ,hzf]+gr3\["ܭ`JkXҼmdrfsuccK^<-)ypǓ:Ĝl#~đ't? R{VDuֲq,_^49?Ќ/pkB.K܊dICu7)Zu53NVȄ>Z1t[kŚYwe09s>}\nSf)hGq 9UvZ$o3$䭅p R\ى~2yQ&"'=ӹnLUL* WS {(4)~_Ta P% IE:*}]l/$'O38P%&:΄2ptS#|Q\fq (`rƶj¼KF鈗"/s uꥏ5֣b EC#~R" W^nUs~qC=AMWf6G$p$҃qZw9ͳY\r/faD%FP&@ y|A=KS-' ep-%zw`WYd8 p_ox./Wn%BJ$$ѧ&'%d nnݲ-~ELCW=NQ)m7":iۓQ@.!󀦧YES Hlh<(7<!g/ꔵwX;֜%x}gkgӨ|Y1(/C;?_;@m1IA$pliAp H|ȖFKyʋ'9/?gchDXSU.DjJs6Dѵ*J bhTݳhހ vgQл[Ђf27ruh`08+qK A/݄GOn65zC'.)j7UY@Ψ05'ʚyX?LZLEs뗺 ]Rq?|z`dF@j '>k&,R`J~r-Z87vҹcjoJ{vQx.x7<ˑ+C?ZiqgD{uyCbD*?[I!. X2wr| k78RRM_ l{˶|fl~#YN%Ip, @` P%) IE:" % ( self.__class__.__name__, self.length) class AsciiData(BinaryData): '''A wrapper class that supports ASCII-armored input. It searches for the first PGP magic header and extracts the data contained within.''' def __init__(self, data): self.original_data = data data = self.strip_magic(data) data, known_crc = self.split_data_crc(data) data = bytearray(b64decode(data)) if known_crc: # verify it if we could find it actual_crc = crc24(data) if known_crc != actual_crc: raise PgpdumpException( "CRC failure: known 0x%x, actual 0x%x" % ( known_crc, actual_crc)) super(AsciiData, self).__init__(data) @staticmethod def strip_magic(data): '''Strip away the '-----BEGIN PGP SIGNATURE-----' and related cruft so we can safely base64 decode the remainder.''' idx = 0 magic = b'-----BEGIN PGP ' ignore = b'-----BEGIN PGP SIGNED ' # find our magic string, skiping our ignored string while True: idx = data.find(magic, idx) if data[idx:len(ignore)] != ignore: break idx += 1 if idx >= 0: # find the start of the actual data. it always immediately follows # a blank line, meaning headers are done. nl_idx = data.find(b'\n\n', idx) if nl_idx < 0: nl_idx = data.find(b'\r\n\r\n', idx) if nl_idx < 0: raise PgpdumpException( "found magic, could not find start of data") # now find the end of the data. end_idx = data.find(b'-----', nl_idx) if end_idx: data = data[nl_idx:end_idx] else: data = data[nl_idx:] return data @staticmethod def split_data_crc(data): '''The Radix-64 format appends any CRC checksum to the end of the data block, in the form '=alph', where there are always 4 ASCII characters correspnding to 3 digits (24 bits). Look for this special case.''' # don't let newlines trip us up data = data.rstrip() # this funkyness makes it work without changes in Py2 and Py3 if data[-5] in (b'=', ord(b'=')): # CRC is returned without the = and converted to a decimal crc = b64decode(data[-4:]) # same noted funkyness as above, due to bytearray implementation crc = [ord(c) if isinstance(c, str) else c for c in crc] crc = (crc[0] << 16) + (crc[1] << 8) + crc[2] return (data[:-5], crc) return (data, None) pgpdump-1.4/pgpdump/utils.py0000644000175000001440000001235111753357022016632 0ustar dmcgeeusers00000000000000import binascii class PgpdumpException(Exception): '''Base exception class raised by any parsing errors, etc.''' pass # 256 values corresponding to each possible byte CRC24_TABLE = ( 0x000000, 0x864cfb, 0x8ad50d, 0x0c99f6, 0x93e6e1, 0x15aa1a, 0x1933ec, 0x9f7f17, 0xa18139, 0x27cdc2, 0x2b5434, 0xad18cf, 0x3267d8, 0xb42b23, 0xb8b2d5, 0x3efe2e, 0xc54e89, 0x430272, 0x4f9b84, 0xc9d77f, 0x56a868, 0xd0e493, 0xdc7d65, 0x5a319e, 0x64cfb0, 0xe2834b, 0xee1abd, 0x685646, 0xf72951, 0x7165aa, 0x7dfc5c, 0xfbb0a7, 0x0cd1e9, 0x8a9d12, 0x8604e4, 0x00481f, 0x9f3708, 0x197bf3, 0x15e205, 0x93aefe, 0xad50d0, 0x2b1c2b, 0x2785dd, 0xa1c926, 0x3eb631, 0xb8faca, 0xb4633c, 0x322fc7, 0xc99f60, 0x4fd39b, 0x434a6d, 0xc50696, 0x5a7981, 0xdc357a, 0xd0ac8c, 0x56e077, 0x681e59, 0xee52a2, 0xe2cb54, 0x6487af, 0xfbf8b8, 0x7db443, 0x712db5, 0xf7614e, 0x19a3d2, 0x9fef29, 0x9376df, 0x153a24, 0x8a4533, 0x0c09c8, 0x00903e, 0x86dcc5, 0xb822eb, 0x3e6e10, 0x32f7e6, 0xb4bb1d, 0x2bc40a, 0xad88f1, 0xa11107, 0x275dfc, 0xdced5b, 0x5aa1a0, 0x563856, 0xd074ad, 0x4f0bba, 0xc94741, 0xc5deb7, 0x43924c, 0x7d6c62, 0xfb2099, 0xf7b96f, 0x71f594, 0xee8a83, 0x68c678, 0x645f8e, 0xe21375, 0x15723b, 0x933ec0, 0x9fa736, 0x19ebcd, 0x8694da, 0x00d821, 0x0c41d7, 0x8a0d2c, 0xb4f302, 0x32bff9, 0x3e260f, 0xb86af4, 0x2715e3, 0xa15918, 0xadc0ee, 0x2b8c15, 0xd03cb2, 0x567049, 0x5ae9bf, 0xdca544, 0x43da53, 0xc596a8, 0xc90f5e, 0x4f43a5, 0x71bd8b, 0xf7f170, 0xfb6886, 0x7d247d, 0xe25b6a, 0x641791, 0x688e67, 0xeec29c, 0x3347a4, 0xb50b5f, 0xb992a9, 0x3fde52, 0xa0a145, 0x26edbe, 0x2a7448, 0xac38b3, 0x92c69d, 0x148a66, 0x181390, 0x9e5f6b, 0x01207c, 0x876c87, 0x8bf571, 0x0db98a, 0xf6092d, 0x7045d6, 0x7cdc20, 0xfa90db, 0x65efcc, 0xe3a337, 0xef3ac1, 0x69763a, 0x578814, 0xd1c4ef, 0xdd5d19, 0x5b11e2, 0xc46ef5, 0x42220e, 0x4ebbf8, 0xc8f703, 0x3f964d, 0xb9dab6, 0xb54340, 0x330fbb, 0xac70ac, 0x2a3c57, 0x26a5a1, 0xa0e95a, 0x9e1774, 0x185b8f, 0x14c279, 0x928e82, 0x0df195, 0x8bbd6e, 0x872498, 0x016863, 0xfad8c4, 0x7c943f, 0x700dc9, 0xf64132, 0x693e25, 0xef72de, 0xe3eb28, 0x65a7d3, 0x5b59fd, 0xdd1506, 0xd18cf0, 0x57c00b, 0xc8bf1c, 0x4ef3e7, 0x426a11, 0xc426ea, 0x2ae476, 0xaca88d, 0xa0317b, 0x267d80, 0xb90297, 0x3f4e6c, 0x33d79a, 0xb59b61, 0x8b654f, 0x0d29b4, 0x01b042, 0x87fcb9, 0x1883ae, 0x9ecf55, 0x9256a3, 0x141a58, 0xefaaff, 0x69e604, 0x657ff2, 0xe33309, 0x7c4c1e, 0xfa00e5, 0xf69913, 0x70d5e8, 0x4e2bc6, 0xc8673d, 0xc4fecb, 0x42b230, 0xddcd27, 0x5b81dc, 0x57182a, 0xd154d1, 0x26359f, 0xa07964, 0xace092, 0x2aac69, 0xb5d37e, 0x339f85, 0x3f0673, 0xb94a88, 0x87b4a6, 0x01f85d, 0x0d61ab, 0x8b2d50, 0x145247, 0x921ebc, 0x9e874a, 0x18cbb1, 0xe37b16, 0x6537ed, 0x69ae1b, 0xefe2e0, 0x709df7, 0xf6d10c, 0xfa48fa, 0x7c0401, 0x42fa2f, 0xc4b6d4, 0xc82f22, 0x4e63d9, 0xd11cce, 0x575035, 0x5bc9c3, 0xdd8538 ) def crc24(data): '''Implementation of the CRC-24 algorithm used by OpenPGP.''' # CRC-24-Radix-64 # x24 + x23 + x18 + x17 + x14 + x11 + x10 + x7 + x6 # + x5 + x4 + x3 + x + 1 (OpenPGP) # 0x864CFB / 0xDF3261 / 0xC3267D crc = 0x00b704ce # this saves a bunch of slower global accesses crc_table = CRC24_TABLE for byte in data: tbl_idx = ((crc >> 16) ^ byte) & 0xff crc = (crc_table[tbl_idx] ^ (crc << 8)) & 0x00ffffff return crc def get_int2(data, offset): '''Pull two bytes from data at offset and return as an integer.''' return (data[offset] << 8) + data[offset + 1] def get_int4(data, offset): '''Pull four bytes from data at offset and return as an integer.''' return ((data[offset] << 24) + (data[offset + 1] << 16) + (data[offset + 2] << 8) + data[offset + 3]) def get_int8(data, offset): '''Pull eight bytes from data at offset and return as an integer.''' return (get_int4(data, offset) << 32) + get_int4(data, offset + 4) def get_mpi(data, offset): '''Gets a multi-precision integer as per RFC-4880. Returns the MPI and the new offset. See: http://tools.ietf.org/html/rfc4880#section-3.2''' mpi_len = get_int2(data, offset) offset += 2 to_process = (mpi_len + 7) // 8 mpi = 0 i = -4 for i in range(0, to_process - 3, 4): mpi <<= 32 mpi += get_int4(data, offset + i) for j in range(i + 4, to_process): mpi <<= 8 mpi += data[offset + j] # Python 3.2 and later alternative: #mpi = int.from_bytes(data[offset:offset + to_process], byteorder='big') offset += to_process return mpi, offset def get_key_id(data, offset): '''Pull eight bytes from data at offset and return as a 16-byte hex-encoded string.''' key_id = binascii.hexlify(data[offset:offset + 8]) return key_id.upper() def get_int_bytes(data): '''Get the big-endian byte form of an integer or MPI.''' hexval = '%X' % data new_len = (len(hexval) + 1) // 2 * 2 hexval = hexval.zfill(new_len) return binascii.unhexlify(hexval.encode('ascii')) def same_key(key_a, key_b): '''Comparison function for key ID or fingerprint strings, taking into account varying length.''' if len(key_a) == len(key_b): return key_a == key_b elif len(key_a) < len(key_b): return key_b.endswith(key_a) else: return key_a.endswith(key_b) pgpdump-1.4/pgpdump/__init__.py0000644000175000001440000000333612041261412017220 0ustar dmcgeeusers00000000000000# Copyright (C) 2011-2012, Dan McGee. # All rights reserved. # # Derived from 'pgpdump'. http://www.mew.org/~kazu/proj/pgpdump/ # Copyright (C) 1998, Kazuhiko Yamamoto. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the author nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. __version__ = "1.4" __author__ = "Dan McGee" from .data import AsciiData, BinaryData pgpdump-1.4/pgpdump/packet.py0000644000175000001440000006760312041261412016737 0ustar dmcgeeusers00000000000000from datetime import datetime, timedelta import hashlib import re from warnings import warn from .utils import (PgpdumpException, get_int2, get_int4, get_mpi, get_key_id, get_int_bytes) class Packet(object): '''The base packet object containing various fields pulled from the packet header as well as a slice of the packet data.''' def __init__(self, raw, name, new, data): self.raw = raw self.name = name self.new = new self.length = len(data) self.data = data # now let subclasses work their magic self.parse() def parse(self): '''Perform any parsing necessary to populate fields on this packet. This method is called as the last step in __init__(). The base class method is a no-op; subclasses should use this as required.''' return 0 def __repr__(self): new = "old" if self.new: new = "new" return "<%s: %s (%d), %s, length %d>" % ( self.__class__.__name__, self.name, self.raw, new, self.length) class AlgoLookup(object): '''Mixin class containing algorithm lookup methods.''' pub_algorithms = { 1: "RSA Encrypt or Sign", 2: "RSA Encrypt-Only", 3: "RSA Sign-Only", 16: "ElGamal Encrypt-Only", 17: "DSA Digital Signature Algorithm", 18: "Elliptic Curve", 19: "ECDSA", 20: "Formerly ElGamal Encrypt or Sign", 21: "Diffie-Hellman", } @classmethod def lookup_pub_algorithm(cls, alg): if 100 <= alg <= 110: return "Private/Experimental algorithm" return cls.pub_algorithms.get(alg, "Unknown") hash_algorithms = { 1: "MD5", 2: "SHA1", 3: "RIPEMD160", 8: "SHA256", 9: "SHA384", 10: "SHA512", 11: "SHA224", } @classmethod def lookup_hash_algorithm(cls, alg): # reserved values check if alg in (4, 5, 6, 7): return "Reserved" if 100 <= alg <= 110: return "Private/Experimental algorithm" return cls.hash_algorithms.get(alg, "Unknown") sym_algorithms = { # (Name, IV length) 0: ("Plaintext or unencrypted", 0), 1: ("IDEA", 8), 2: ("Triple-DES", 8), 3: ("CAST5", 8), 4: ("Blowfish", 8), 5: ("Reserved", 8), 6: ("Reserved", 8), 7: ("AES with 128-bit key", 16), 8: ("AES with 192-bit key", 16), 9: ("AES with 256-bit key", 16), 10: ("Twofish with 256-bit key", 16), 11: ("Camellia with 128-bit key", 16), 12: ("Camellia with 192-bit key", 16), 13: ("Camellia with 256-bit key", 16), } @classmethod def _lookup_sym_algorithm(cls, alg): return cls.sym_algorithms.get(alg, ("Unknown", 0)) @classmethod def lookup_sym_algorithm(cls, alg): return cls._lookup_sym_algorithm(alg)[0] @classmethod def lookup_sym_algorithm_iv(cls, alg): return cls._lookup_sym_algorithm(alg)[1] class SignatureSubpacket(object): '''A signature subpacket containing a type, type name, some flags, and the contained data.''' CRITICAL_BIT = 0x80 CRITICAL_MASK = 0x7f def __init__(self, raw, hashed, data): self.raw = raw self.subtype = raw & self.CRITICAL_MASK self.hashed = hashed self.critical = bool(raw & self.CRITICAL_BIT) self.length = len(data) self.data = data subpacket_types = { 2: "Signature Creation Time", 3: "Signature Expiration Time", 4: "Exportable Certification", 5: "Trust Signature", 6: "Regular Expression", 7: "Revocable", 9: "Key Expiration Time", 10: "Placeholder for backward compatibility", 11: "Preferred Symmetric Algorithms", 12: "Revocation Key", 16: "Issuer", 20: "Notation Data", 21: "Preferred Hash Algorithms", 22: "Preferred Compression Algorithms", 23: "Key Server Preferences", 24: "Preferred Key Server", 25: "Primary User ID", 26: "Policy URI", 27: "Key Flags", 28: "Signer's User ID", 29: "Reason for Revocation", 30: "Features", 31: "Signature Target", 32: "Embedded Signature", } @property def name(self): if self.subtype in (0, 1, 8, 13, 14, 15, 17, 18, 19): return "Reserved" return self.subpacket_types.get(self.subtype, "Unknown") def __repr__(self): extra = "" if self.hashed: extra += "hashed, " if self.critical: extra += "critical, " return "<%s: %s, %slength %d>" % ( self.__class__.__name__, self.name, extra, self.length) class SignaturePacket(Packet, AlgoLookup): def __init__(self, *args, **kwargs): self.sig_version = None self.raw_sig_type = None self.raw_pub_algorithm = None self.raw_hash_algorithm = None self.raw_creation_time = None self.creation_time = None self.raw_expiration_time = None self.expiration_time = None self.key_id = None self.hash2 = None self.subpackets = [] super(SignaturePacket, self).__init__(*args, **kwargs) def parse(self): self.sig_version = self.data[0] offset = 1 if self.sig_version in (2, 3): # 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f # | | [ ctime ] [ key_id ] | # | |-type pub_algo-| # |-hash material # 10 11 12 # | [hash2] # |-hash_algo # "hash material" byte must be 0x05 if self.data[offset] != 0x05: raise PgpdumpException("Invalid v3 signature packet") offset += 1 self.raw_sig_type = self.data[offset] offset += 1 self.raw_creation_time = get_int4(self.data, offset) self.creation_time = datetime.utcfromtimestamp( self.raw_creation_time) offset += 4 self.key_id = get_key_id(self.data, offset) offset += 8 self.raw_pub_algorithm = self.data[offset] offset += 1 self.raw_hash_algorithm = self.data[offset] offset += 1 self.hash2 = self.data[offset:offset + 2] offset += 2 elif self.sig_version == 4: # 00 01 02 03 ... [hash2] # | | |-hash_algo # | |-pub_algo # |-type self.raw_sig_type = self.data[offset] offset += 1 self.raw_pub_algorithm = self.data[offset] offset += 1 self.raw_hash_algorithm = self.data[offset] offset += 1 # next is hashed subpackets length = get_int2(self.data, offset) offset += 2 self.parse_subpackets(offset, length, True) offset += length # followed by subpackets length = get_int2(self.data, offset) offset += 2 self.parse_subpackets(offset, length, False) offset += length self.hash2 = self.data[offset:offset + 2] offset += 2 else: raise PgpdumpException("Unsupported signature packet, version %d" % self.sig_version) return offset def parse_subpackets(self, outer_offset, outer_length, hashed=False): offset = outer_offset while offset < outer_offset + outer_length: # each subpacket is [variable length] [subtype] [data] sub_offset, sub_len, sub_part = new_tag_length(self.data, offset) # sub_len includes the subtype single byte, knock that off sub_len -= 1 # initial length bytes offset += 1 + sub_offset subtype = self.data[offset] offset += 1 sub_data = self.data[offset:offset + sub_len] if len(sub_data) != sub_len: raise PgpdumpException( "Unexpected subpackets length: expected %d, got %d" % ( sub_len, len(sub_data))) subpacket = SignatureSubpacket(subtype, hashed, sub_data) if subpacket.subtype == 2: self.raw_creation_time = get_int4(subpacket.data, 0) self.creation_time = datetime.utcfromtimestamp( self.raw_creation_time) elif subpacket.subtype == 3: self.raw_expiration_time = get_int4(subpacket.data, 0) elif subpacket.subtype == 16: self.key_id = get_key_id(subpacket.data, 0) offset += sub_len self.subpackets.append(subpacket) if self.raw_expiration_time: self.expiration_time = self.creation_time + timedelta( seconds=self.raw_expiration_time) @property def datetime(self): warn("deprecated, use creation_time", DeprecationWarning) return self.creation_time sig_types = { 0x00: "Signature of a binary document", 0x01: "Signature of a canonical text document", 0x02: "Standalone signature", 0x10: "Generic certification of a User ID and Public Key packet", 0x11: "Persona certification of a User ID and Public Key packet", 0x12: "Casual certification of a User ID and Public Key packet", 0x13: "Positive certification of a User ID and Public Key packet", 0x18: "Subkey Binding Signature", 0x19: "Primary Key Binding Signature", 0x1f: "Signature directly on a key", 0x20: "Key revocation signature", 0x28: "Subkey revocation signature", 0x30: "Certification revocation signature", 0x40: "Timestamp signature", 0x50: "Third-Party Confirmation signature", } @property def sig_type(self): return self.sig_types.get(self.raw_sig_type, "Unknown") @property def pub_algorithm(self): return self.lookup_pub_algorithm(self.raw_pub_algorithm) @property def hash_algorithm(self): return self.lookup_hash_algorithm(self.raw_hash_algorithm) def __repr__(self): return "<%s: %s, %s, length %d>" % ( self.__class__.__name__, self.pub_algorithm, self.hash_algorithm, self.length) class PublicKeyPacket(Packet, AlgoLookup): def __init__(self, *args, **kwargs): self.pubkey_version = None self.fingerprint = None self.key_id = None self.raw_creation_time = None self.creation_time = None self.raw_days_valid = None self.expiration_time = None self.raw_pub_algorithm = None self.pub_algorithm_type = None self.modulus = None self.exponent = None self.prime = None self.group_order = None self.group_gen = None self.key_value = None super(PublicKeyPacket, self).__init__(*args, **kwargs) def parse(self): self.pubkey_version = self.data[0] offset = 1 if self.pubkey_version in (2, 3): self.raw_creation_time = get_int4(self.data, offset) self.creation_time = datetime.utcfromtimestamp( self.raw_creation_time) offset += 4 self.raw_days_valid = get_int2(self.data, offset) offset += 2 if self.raw_days_valid > 0: self.expiration_time = self.creation_time + timedelta( days=self.raw_days_valid) self.raw_pub_algorithm = self.data[offset] offset += 1 offset = self.parse_key_material(offset) md5 = hashlib.md5() # Key type must be RSA for v2 and v3 public keys if self.pub_algorithm_type == "rsa": key_id = ('%X' % self.modulus)[-8:].zfill(8) self.key_id = key_id.encode('ascii') md5.update(get_int_bytes(self.modulus)) md5.update(get_int_bytes(self.exponent)) elif self.pub_algorithm_type == "elg": # Of course, there are ELG keys in the wild too. This formula # for calculating key_id and fingerprint is derived from an old # key and there is a test case based on it. key_id = ('%X' % self.prime)[-8:].zfill(8) self.key_id = key_id.encode('ascii') md5.update(get_int_bytes(self.prime)) md5.update(get_int_bytes(self.group_gen)) else: raise PgpdumpException("Invalid non-RSA v%d public key" % self.pubkey_version) self.fingerprint = md5.hexdigest().upper().encode('ascii') elif self.pubkey_version == 4: sha1 = hashlib.sha1() seed_bytes = (0x99, (self.length >> 8) & 0xff, self.length & 0xff) sha1.update(bytearray(seed_bytes)) sha1.update(self.data) self.fingerprint = sha1.hexdigest().upper().encode('ascii') self.key_id = self.fingerprint[24:] self.raw_creation_time = get_int4(self.data, offset) self.creation_time = datetime.utcfromtimestamp( self.raw_creation_time) offset += 4 self.raw_pub_algorithm = self.data[offset] offset += 1 offset = self.parse_key_material(offset) else: raise PgpdumpException("Unsupported public key packet, version %d" % self.pubkey_version) return offset def parse_key_material(self, offset): if self.raw_pub_algorithm in (1, 2, 3): self.pub_algorithm_type = "rsa" # n, e self.modulus, offset = get_mpi(self.data, offset) self.exponent, offset = get_mpi(self.data, offset) elif self.raw_pub_algorithm == 17: self.pub_algorithm_type = "dsa" # p, q, g, y self.prime, offset = get_mpi(self.data, offset) self.group_order, offset = get_mpi(self.data, offset) self.group_gen, offset = get_mpi(self.data, offset) self.key_value, offset = get_mpi(self.data, offset) elif self.raw_pub_algorithm in (16, 20): self.pub_algorithm_type = "elg" # p, g, y self.prime, offset = get_mpi(self.data, offset) self.group_gen, offset = get_mpi(self.data, offset) self.key_value, offset = get_mpi(self.data, offset) elif 100 <= self.raw_pub_algorithm <= 110: # Private/Experimental algorithms, just move on pass else: raise PgpdumpException("Unsupported public key algorithm %d" % self.raw_pub_algorithm) return offset @property def datetime(self): warn("deprecated, use creation_time", DeprecationWarning) return self.creation_time @property def pub_algorithm(self): return self.lookup_pub_algorithm(self.raw_pub_algorithm) def __repr__(self): return "<%s: 0x%s, %s, length %d>" % ( self.__class__.__name__, self.key_id.decode('ascii'), self.pub_algorithm, self.length) class PublicSubkeyPacket(PublicKeyPacket): '''A Public-Subkey packet (tag 14) has exactly the same format as a Public-Key packet, but denotes a subkey.''' pass class SecretKeyPacket(PublicKeyPacket): def __init__(self, *args, **kwargs): self.s2k_id = None self.s2k_type = None self.s2k_cipher = None self.s2k_hash = None self.s2k_iv = None self.checksum = None # RSA fields self.exponent_d = None self.prime_p = None self.prime_q = None self.multiplicative_inverse = None # DSA and Elgamal self.exponent_x = None super(SecretKeyPacket, self).__init__(*args, **kwargs) def parse(self): # parse the public part offset = PublicKeyPacket.parse(self) # parse secret-key packet format from section 5.5.3 self.s2k_id = self.data[offset] offset += 1 if self.s2k_id == 0: # plaintext key data offset = self.parse_private_key_material(offset) self.checksum = get_int2(self.data, offset) offset += 2 elif self.s2k_id in (254, 255): # encrypted key data cipher_id = self.data[offset] offset += 1 self.s2k_cipher = self.lookup_sym_algorithm(cipher_id) # s2k_length is the len of the entire S2K specifier, as per # section 3.7.1 in RFC 4880 # we parse the info inside the specifier, but verify the # of # octects we've parsed matches the expected length of the s2k offset_before_s2k = offset s2k_type_id = self.data[offset] offset += 1 name, s2k_length = S2K_TYPES.get(s2k_type_id, ("Unknown", 0)) self.s2k_type = name has_iv = True if s2k_type_id == 0: # simple string-to-key hash_id = self.data[offset] offset += 1 self.s2k_hash = self.lookup_hash_algorithm(hash_id) elif s2k_type_id == 1: # salted string-to-key hash_id = self.data[offset] offset += 1 self.s2k_hash = self.lookup_hash_algorithm(hash_id) # ignore 8 bytes offset += 8 elif s2k_type_id == 2: # reserved pass elif s2k_type_id == 3: # iterated and salted hash_id = self.data[offset] offset += 1 self.s2k_hash = self.lookup_hash_algorithm(hash_id) # ignore 8 bytes offset += 8 # ignore count offset += 1 # TODO: parse and store count ? elif 100 <= s2k_type_id <= 110: # GnuPG string-to-key # According to g10/parse-packet.c near line 1832, the 101 packet # type is a special GnuPG extension. This S2K extension is # 6 bytes in total: # # Octet 0: 101 # Octet 1: hash algorithm # Octet 2-4: "GNU" # Octet 5: mode integer hash_id = self.data[offset] offset += 1 self.s2k_hash = self.lookup_hash_algorithm(hash_id) gnu = self.data[offset:offset + 3] offset += 3 if gnu != bytearray(b"GNU"): raise PgpdumpException( "S2K parsing error: expected 'GNU', got %s" % gnu) mode = self.data[offset] mode += 1000 offset += 1 if mode == 1001: has_iv = False else: # TODO implement other modes? raise PgpdumpException( "Unsupported GnuPG S2K extension, encountered mode %d" % mode) else: raise PgpdumpException( "Unsupported public key algorithm %d" % s2k_type_id) if s2k_length != (offset - offset_before_s2k): raise PgpdumpException( "Error parsing string-to-key specifier, mismatched length") if has_iv: s2k_iv_len = self.lookup_sym_algorithm_iv(cipher_id) self.s2k_iv = self.data[offset:offset + s2k_iv_len] offset += s2k_iv_len # TODO decrypt key data # TODO parse checksum return offset def parse_private_key_material(self, offset): if self.raw_pub_algorithm in (1, 2, 3): self.pub_algorithm_type = "rsa" # d, p, q, u self.exponent_d, offset = get_mpi(self.data, offset) self.prime_p, offset = get_mpi(self.data, offset) self.prime_q, offset = get_mpi(self.data, offset) self.multiplicative_inverse, offset = get_mpi(self.data, offset) elif self.raw_pub_algorithm == 17: self.pub_algorithm_type = "dsa" # x self.exponent_x, offset = get_mpi(self.data, offset) elif self.raw_pub_algorithm in (16, 20): self.pub_algorithm_type = "elg" # x self.exponent_x, offset = get_mpi(self.data, offset) elif 100 <= self.raw_pub_algorithm <= 110: # Private/Experimental algorithms, just move on pass else: raise PgpdumpException("Unsupported public key algorithm %d" % self.raw_pub_algorithm) return offset class SecretSubkeyPacket(SecretKeyPacket): '''A Secret-Subkey packet (tag 7) has exactly the same format as a Secret-Key packet, but denotes a subkey.''' pass class UserIDPacket(Packet): '''A User ID packet consists of UTF-8 text that is intended to represent the name and email address of the key holder. By convention, it includes an RFC 2822 mail name-addr, but there are no restrictions on its content.''' def __init__(self, *args, **kwargs): self.user = None self.user_name = None self.user_email = None super(UserIDPacket, self).__init__(*args, **kwargs) user_re = re.compile(r'^([^<]+)? ?<([^>]*)>?') def parse(self): self.user = self.data.decode('utf8', errors='replace') matches = self.user_re.match(self.user) if matches: if matches.group(1): self.user_name = matches.group(1).strip() if matches.group(2): self.user_email = matches.group(2).strip() return self.length def __repr__(self): return "<%s: %r (%r), length %d>" % ( self.__class__.__name__, self.user_name, self.user_email, self.length) class UserAttributePacket(Packet): def __init__(self, *args, **kwargs): self.raw_image_format = None self.image_format = None self.image_data = None super(UserAttributePacket, self).__init__(*args, **kwargs) def parse(self): offset = sub_offset = sub_len = 0 while offset + sub_len < self.length: # each subpacket is [variable length] [subtype] [data] sub_offset, sub_len, sub_part = new_tag_length(self.data, offset) # sub_len includes the subtype single byte, knock that off sub_len -= 1 # initial length bytes offset += 1 + sub_offset sub_type = self.data[offset] offset += 1 # there is only one currently known type- images (1) if sub_type == 1: # the only little-endian encoded value in OpenPGP hdr_size = self.data[offset] + (self.data[offset + 1] << 8) hdr_version = self.data[offset + 2] self.raw_image_format = self.data[offset + 3] offset += hdr_size self.image_data = self.data[offset:] if self.raw_image_format == 1: self.image_format = "jpeg" else: self.image_format = "unknown" return self.length class TrustPacket(Packet): def __init__(self, *args, **kwargs): self.trust = None super(TrustPacket, self).__init__(*args, **kwargs) def parse(self): '''GnuPG public keyrings use a 2-byte trust value that appears to be integer values into some internal enumeration.''' if self.length == 2: self.trust = get_int2(self.data, 0) return 2 return 0 class PublicKeyEncryptedSessionKeyPacket(Packet, AlgoLookup): def __init__(self, *args, **kwargs): self.session_key_version = None self.key_id = None self.raw_pub_algorithm = None self.pub_algorithm = None super(PublicKeyEncryptedSessionKeyPacket, self).__init__( *args, **kwargs) def parse(self): self.session_key_version = self.data[0] if self.session_key_version == 3: self.key_id = get_key_id(self.data, 1) self.raw_pub_algorithm = self.data[9] self.pub_algorithm = self.lookup_pub_algorithm(self.raw_pub_algorithm) else: raise PgpdumpException( "Unsupported encrypted session key packet, version %d" % self.session_key_version) # this is hardcoded to work with the only known session key version return 10 def __repr__(self): return "<%s: 0x%s (%s), length %d>" % ( self.__class__.__name__, self.key_id, self.pub_algorithm, self.length) TAG_TYPES = { # (Name, PacketType) tuples 0: ("Reserved", None), 1: ("Public-Key Encrypted Session Key Packet", PublicKeyEncryptedSessionKeyPacket), 2: ("Signature Packet", SignaturePacket), 3: ("Symmetric-Key Encrypted Session Key Packet", None), 4: ("One-Pass Signature Packet", None), 5: ("Secret Key Packet", SecretKeyPacket), 6: ("Public Key Packet", PublicKeyPacket), 7: ("Secret Subkey Packet", SecretSubkeyPacket), 8: ("Compressed Data Packet", None), 9: ("Symmetrically Encrypted Data Packet", None), 10: ("Marker Packet", None), 11: ("Literal Data Packet", None), 12: ("Trust Packet", TrustPacket), 13: ("User ID Packet", UserIDPacket), 14: ("Public Subkey Packet", PublicSubkeyPacket), 17: ("User Attribute Packet", UserAttributePacket), 18: ("Symmetrically Encrypted and MDC Packet", None), 19: ("Modification Detection Code Packet", None), 60: ("Private", None), 61: ("Private", None), 62: ("Private", None), 63: ("Private", None), } S2K_TYPES = { # (Name, Length) 0: ("Simple S2K", 2), 1: ("Salted S2K", 10), 2: ("Reserved value", 0), 3: ("Iterated and Salted S2K", 11), 101: ("GnuPG S2K", 6), } def new_tag_length(data, start): '''Takes a bytearray of data as input, as well as an offset of where to look. Returns a derived (offset, length, partial) tuple.''' first = data[start] offset = length = 0 partial = False if first < 192: length = first elif first < 224: offset = 1 length = ((first - 192) << 8) + data[start + 1] + 192 elif first == 255: offset = 4 length = get_int4(data, start + 1) else: # partial length, 224 <= l < 255 length = 1 << (first & 0x1f) partial = True return (offset, length, partial) def old_tag_length(data, start): '''Takes a bytearray of data as input, as well as an offset of where to look. Returns a derived (offset, length) tuple.''' offset = length = 0 temp_len = data[start] & 0x03 if temp_len == 0: offset = 1 length = data[start + 1] elif temp_len == 1: offset = 2 length = get_int2(data, start + 1) elif temp_len == 2: offset = 4 length = get_int4(data, start + 1) elif temp_len == 3: length = len(data) - start - 1 return (offset, length) def construct_packet(data, start): '''Returns a (length, packet) tuple constructed from 'data' at index 'start'. If there is a next packet, it will be found at start + length.''' tag = data[start] & 0x3f new = bool(data[start] & 0x40) if new: data_offset, length, partial = new_tag_length(data, start + 1) data_offset += 1 else: tag >>= 2 data_offset, length = old_tag_length(data, start) partial = False data_offset += 1 start += data_offset name, PacketType = TAG_TYPES.get(tag, ("Unknown", None)) end = start + length packet_data = data[start:end] if not PacketType: PacketType = Packet packet = PacketType(tag, name, new, packet_data) return (data_offset + length, packet) pgpdump-1.4/pgpdump/__main__.py0000644000175000001440000000112411753357022017206 0ustar dmcgeeusers00000000000000import sys from . import AsciiData, BinaryData def parsefile(name): with open(name, 'rb') as infile: if name.endswith('.asc'): data = AsciiData(infile.read()) else: data = BinaryData(infile.read()) counter = length = 0 for packet in data.packets(): counter += 1 length += packet.length print('%d packets, length %d' % (counter, length)) def main(): for filename in sys.argv[1:]: parsefile(filename) if __name__ == '__main__': #import cProfile #cProfile.run('main()', 'pgpdump.profile') main() pgpdump-1.4/README0000777000175000001440000000000011651746002015573 2README.mdustar dmcgeeusers00000000000000pgpdump-1.4/setup.py0000644000175000001440000000134212041263723015147 0ustar dmcgeeusers00000000000000from distutils.core import setup from pgpdump import __version__, __author__ classifiers = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Security :: Cryptography', 'Topic :: Software Development :: Libraries :: Python Modules' ] setup( name = 'pgpdump', version = __version__, author = __author__, license = 'BSD', description = 'PGP packet parser library', url = 'https://github.com/toofishes/python-pgpdump', keywords = 'pgp gpg rfc2440 rfc4880 crypto cryptography', classifiers = classifiers, packages = ['pgpdump'] ) pgpdump-1.4/README.md0000644000175000001440000000115212041261412014704 0ustar dmcgeeusers00000000000000# python-pgpdump: a Python library for parsing PGP packets This is based on the C version published at: http://www.mew.org/~kazu/proj/pgpdump/ The intent here is not on completeness, as we don't currently decode every packet type, but on being able to do what people actually have to 95% of the time. Currently supported things include: * Signature packets * Public key packets * Secret key packets * Trust, user ID, and user attribute packets * ASCII-armor decoding and CRC check A single codebase with dependencies on only the standard python library is compatible across Python 2.7, Python 3.2+, and PyPy 1.8+. pgpdump-1.4/COPYRIGHT0000644000175000001440000000307311701204503014724 0ustar dmcgeeusers00000000000000Copyright (C) 2011-2012, Dan McGee. All rights reserved. Derived from 'pgpdump'. http://www.mew.org/~kazu/proj/pgpdump/ Copyright (C) 1998, Kazuhiko Yamamoto. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.