X509.php 166 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583
  1. <?php
  2. /**
  3. * Pure-PHP X.509 Parser
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * Encode and decode X.509 certificates.
  8. *
  9. * The extensions are from {@link http://tools.ietf.org/html/rfc5280 RFC5280} and
  10. * {@link http://web.archive.org/web/19961027104704/http://www3.netscape.com/eng/security/cert-exts.html Netscape Certificate Extensions}.
  11. *
  12. * Note that loading an X.509 certificate and resaving it may invalidate the signature. The reason being that the signature is based on a
  13. * portion of the certificate that contains optional parameters with default values. ie. if the parameter isn't there the default value is
  14. * used. Problem is, if the parameter is there and it just so happens to have the default value there are two ways that that parameter can
  15. * be encoded. It can be encoded explicitly or left out all together. This would effect the signature value and thus may invalidate the
  16. * the certificate all together unless the certificate is re-signed.
  17. *
  18. * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
  19. * of this software and associated documentation files (the "Software"), to deal
  20. * in the Software without restriction, including without limitation the rights
  21. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  22. * copies of the Software, and to permit persons to whom the Software is
  23. * furnished to do so, subject to the following conditions:
  24. *
  25. * The above copyright notice and this permission notice shall be included in
  26. * all copies or substantial portions of the Software.
  27. *
  28. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  29. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  30. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  31. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  32. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  33. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  34. * THE SOFTWARE.
  35. *
  36. * @category File
  37. * @package File_X509
  38. * @author Jim Wigginton <terrafrost@php.net>
  39. * @copyright 2012 Jim Wigginton
  40. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  41. * @link http://phpseclib.sourceforge.net
  42. */
  43. /**
  44. * Include File_ASN1
  45. */
  46. if (!class_exists('File_ASN1')) {
  47. include_once 'ASN1.php';
  48. }
  49. /**
  50. * Flag to only accept signatures signed by certificate authorities
  51. *
  52. * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs
  53. *
  54. * @access public
  55. */
  56. define('FILE_X509_VALIDATE_SIGNATURE_BY_CA', 1);
  57. /**#@+
  58. * @access public
  59. * @see File_X509::getDN()
  60. */
  61. /**
  62. * Return internal array representation
  63. */
  64. define('FILE_X509_DN_ARRAY', 0);
  65. /**
  66. * Return string
  67. */
  68. define('FILE_X509_DN_STRING', 1);
  69. /**
  70. * Return ASN.1 name string
  71. */
  72. define('FILE_X509_DN_ASN1', 2);
  73. /**
  74. * Return OpenSSL compatible array
  75. */
  76. define('FILE_X509_DN_OPENSSL', 3);
  77. /**
  78. * Return canonical ASN.1 RDNs string
  79. */
  80. define('FILE_X509_DN_CANON', 4);
  81. /**
  82. * Return name hash for file indexing
  83. */
  84. define('FILE_X509_DN_HASH', 5);
  85. /**#@-*/
  86. /**#@+
  87. * @access public
  88. * @see File_X509::saveX509()
  89. * @see File_X509::saveCSR()
  90. * @see File_X509::saveCRL()
  91. */
  92. /**
  93. * Save as PEM
  94. *
  95. * ie. a base64-encoded PEM with a header and a footer
  96. */
  97. define('FILE_X509_FORMAT_PEM', 0);
  98. /**
  99. * Save as DER
  100. */
  101. define('FILE_X509_FORMAT_DER', 1);
  102. /**
  103. * Save as a SPKAC
  104. *
  105. * Only works on CSRs. Not currently supported.
  106. */
  107. define('FILE_X509_FORMAT_SPKAC', 2);
  108. /**#@-*/
  109. /**
  110. * Attribute value disposition.
  111. * If disposition is >= 0, this is the index of the target value.
  112. */
  113. define('FILE_X509_ATTR_ALL', -1); // All attribute values (array).
  114. define('FILE_X509_ATTR_APPEND', -2); // Add a value.
  115. define('FILE_X509_ATTR_REPLACE', -3); // Clear first, then add a value.
  116. /**
  117. * Pure-PHP X.509 Parser
  118. *
  119. * @package File_X509
  120. * @author Jim Wigginton <terrafrost@php.net>
  121. * @access public
  122. */
  123. class File_X509
  124. {
  125. /**
  126. * ASN.1 syntax for X.509 certificates
  127. *
  128. * @var Array
  129. * @access private
  130. */
  131. var $Certificate;
  132. /**#@+
  133. * ASN.1 syntax for various extensions
  134. *
  135. * @access private
  136. */
  137. var $DirectoryString;
  138. var $PKCS9String;
  139. var $AttributeValue;
  140. var $Extensions;
  141. var $KeyUsage;
  142. var $ExtKeyUsageSyntax;
  143. var $BasicConstraints;
  144. var $KeyIdentifier;
  145. var $CRLDistributionPoints;
  146. var $AuthorityKeyIdentifier;
  147. var $CertificatePolicies;
  148. var $AuthorityInfoAccessSyntax;
  149. var $SubjectAltName;
  150. var $PrivateKeyUsagePeriod;
  151. var $IssuerAltName;
  152. var $PolicyMappings;
  153. var $NameConstraints;
  154. var $CPSuri;
  155. var $UserNotice;
  156. var $netscape_cert_type;
  157. var $netscape_comment;
  158. var $netscape_ca_policy_url;
  159. var $Name;
  160. var $RelativeDistinguishedName;
  161. var $CRLNumber;
  162. var $CRLReason;
  163. var $IssuingDistributionPoint;
  164. var $InvalidityDate;
  165. var $CertificateIssuer;
  166. var $HoldInstructionCode;
  167. var $SignedPublicKeyAndChallenge;
  168. /**#@-*/
  169. /**
  170. * ASN.1 syntax for Certificate Signing Requests (RFC2986)
  171. *
  172. * @var Array
  173. * @access private
  174. */
  175. var $CertificationRequest;
  176. /**
  177. * ASN.1 syntax for Certificate Revocation Lists (RFC5280)
  178. *
  179. * @var Array
  180. * @access private
  181. */
  182. var $CertificateList;
  183. /**
  184. * Distinguished Name
  185. *
  186. * @var Array
  187. * @access private
  188. */
  189. var $dn;
  190. /**
  191. * Public key
  192. *
  193. * @var String
  194. * @access private
  195. */
  196. var $publicKey;
  197. /**
  198. * Private key
  199. *
  200. * @var String
  201. * @access private
  202. */
  203. var $privateKey;
  204. /**
  205. * Object identifiers for X.509 certificates
  206. *
  207. * @var Array
  208. * @access private
  209. * @link http://en.wikipedia.org/wiki/Object_identifier
  210. */
  211. var $oids;
  212. /**
  213. * The certificate authorities
  214. *
  215. * @var Array
  216. * @access private
  217. */
  218. var $CAs;
  219. /**
  220. * The currently loaded certificate
  221. *
  222. * @var Array
  223. * @access private
  224. */
  225. var $currentCert;
  226. /**
  227. * The signature subject
  228. *
  229. * There's no guarantee File_X509 is going to reencode an X.509 cert in the same way it was originally
  230. * encoded so we take save the portion of the original cert that the signature would have made for.
  231. *
  232. * @var String
  233. * @access private
  234. */
  235. var $signatureSubject;
  236. /**
  237. * Certificate Start Date
  238. *
  239. * @var String
  240. * @access private
  241. */
  242. var $startDate;
  243. /**
  244. * Certificate End Date
  245. *
  246. * @var String
  247. * @access private
  248. */
  249. var $endDate;
  250. /**
  251. * Serial Number
  252. *
  253. * @var String
  254. * @access private
  255. */
  256. var $serialNumber;
  257. /**
  258. * Key Identifier
  259. *
  260. * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and
  261. * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}.
  262. *
  263. * @var String
  264. * @access private
  265. */
  266. var $currentKeyIdentifier;
  267. /**
  268. * CA Flag
  269. *
  270. * @var Boolean
  271. * @access private
  272. */
  273. var $caFlag = false;
  274. /**
  275. * SPKAC Challenge
  276. *
  277. * @var String
  278. * @access private
  279. */
  280. var $challenge;
  281. /**
  282. * Default Constructor.
  283. *
  284. * @return File_X509
  285. * @access public
  286. */
  287. function File_X509()
  288. {
  289. if (!class_exists('Math_BigInteger')) {
  290. include_once 'Math/BigInteger.php';
  291. }
  292. // Explicitly Tagged Module, 1988 Syntax
  293. // http://tools.ietf.org/html/rfc5280#appendix-A.1
  294. $this->DirectoryString = array(
  295. 'type' => FILE_ASN1_TYPE_CHOICE,
  296. 'children' => array(
  297. 'teletexString' => array('type' => FILE_ASN1_TYPE_TELETEX_STRING),
  298. 'printableString' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING),
  299. 'universalString' => array('type' => FILE_ASN1_TYPE_UNIVERSAL_STRING),
  300. 'utf8String' => array('type' => FILE_ASN1_TYPE_UTF8_STRING),
  301. 'bmpString' => array('type' => FILE_ASN1_TYPE_BMP_STRING)
  302. )
  303. );
  304. $this->PKCS9String = array(
  305. 'type' => FILE_ASN1_TYPE_CHOICE,
  306. 'children' => array(
  307. 'ia5String' => array('type' => FILE_ASN1_TYPE_IA5_STRING),
  308. 'directoryString' => $this->DirectoryString
  309. )
  310. );
  311. $this->AttributeValue = array('type' => FILE_ASN1_TYPE_ANY);
  312. $AttributeType = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
  313. $AttributeTypeAndValue = array(
  314. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  315. 'children' => array(
  316. 'type' => $AttributeType,
  317. 'value'=> $this->AttributeValue
  318. )
  319. );
  320. /*
  321. In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
  322. but they can be useful at times when either there is no unique attribute in the entry or you
  323. want to ensure that the entry's DN contains some useful identifying information.
  324. - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
  325. */
  326. $this->RelativeDistinguishedName = array(
  327. 'type' => FILE_ASN1_TYPE_SET,
  328. 'min' => 1,
  329. 'max' => -1,
  330. 'children' => $AttributeTypeAndValue
  331. );
  332. // http://tools.ietf.org/html/rfc5280#section-4.1.2.4
  333. $RDNSequence = array(
  334. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  335. // RDNSequence does not define a min or a max, which means it doesn't have one
  336. 'min' => 0,
  337. 'max' => -1,
  338. 'children' => $this->RelativeDistinguishedName
  339. );
  340. $this->Name = array(
  341. 'type' => FILE_ASN1_TYPE_CHOICE,
  342. 'children' => array(
  343. 'rdnSequence' => $RDNSequence
  344. )
  345. );
  346. // http://tools.ietf.org/html/rfc5280#section-4.1.1.2
  347. $AlgorithmIdentifier = array(
  348. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  349. 'children' => array(
  350. 'algorithm' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER),
  351. 'parameters' => array(
  352. 'type' => FILE_ASN1_TYPE_ANY,
  353. 'optional' => true
  354. )
  355. )
  356. );
  357. /*
  358. A certificate using system MUST reject the certificate if it encounters
  359. a critical extension it does not recognize; however, a non-critical
  360. extension may be ignored if it is not recognized.
  361. http://tools.ietf.org/html/rfc5280#section-4.2
  362. */
  363. $Extension = array(
  364. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  365. 'children' => array(
  366. 'extnId' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER),
  367. 'critical' => array(
  368. 'type' => FILE_ASN1_TYPE_BOOLEAN,
  369. 'optional' => true,
  370. 'default' => false
  371. ),
  372. 'extnValue' => array('type' => FILE_ASN1_TYPE_OCTET_STRING)
  373. )
  374. );
  375. $this->Extensions = array(
  376. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  377. 'min' => 1,
  378. // technically, it's MAX, but we'll assume anything < 0 is MAX
  379. 'max' => -1,
  380. // if 'children' isn't an array then 'min' and 'max' must be defined
  381. 'children' => $Extension
  382. );
  383. $SubjectPublicKeyInfo = array(
  384. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  385. 'children' => array(
  386. 'algorithm' => $AlgorithmIdentifier,
  387. 'subjectPublicKey' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
  388. )
  389. );
  390. $UniqueIdentifier = array('type' => FILE_ASN1_TYPE_BIT_STRING);
  391. $Time = array(
  392. 'type' => FILE_ASN1_TYPE_CHOICE,
  393. 'children' => array(
  394. 'utcTime' => array('type' => FILE_ASN1_TYPE_UTC_TIME),
  395. 'generalTime' => array('type' => FILE_ASN1_TYPE_GENERALIZED_TIME)
  396. )
  397. );
  398. // http://tools.ietf.org/html/rfc5280#section-4.1.2.5
  399. $Validity = array(
  400. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  401. 'children' => array(
  402. 'notBefore' => $Time,
  403. 'notAfter' => $Time
  404. )
  405. );
  406. $CertificateSerialNumber = array('type' => FILE_ASN1_TYPE_INTEGER);
  407. $Version = array(
  408. 'type' => FILE_ASN1_TYPE_INTEGER,
  409. 'mapping' => array('v1', 'v2', 'v3')
  410. );
  411. // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm'])
  412. $TBSCertificate = array(
  413. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  414. 'children' => array(
  415. // technically, default implies optional, but we'll define it as being optional, none-the-less, just to
  416. // reenforce that fact
  417. 'version' => array(
  418. 'constant' => 0,
  419. 'optional' => true,
  420. 'explicit' => true,
  421. 'default' => 'v1'
  422. ) + $Version,
  423. 'serialNumber' => $CertificateSerialNumber,
  424. 'signature' => $AlgorithmIdentifier,
  425. 'issuer' => $this->Name,
  426. 'validity' => $Validity,
  427. 'subject' => $this->Name,
  428. 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo,
  429. // implicit means that the T in the TLV structure is to be rewritten, regardless of the type
  430. 'issuerUniqueID' => array(
  431. 'constant' => 1,
  432. 'optional' => true,
  433. 'implicit' => true
  434. ) + $UniqueIdentifier,
  435. 'subjectUniqueID' => array(
  436. 'constant' => 2,
  437. 'optional' => true,
  438. 'implicit' => true
  439. ) + $UniqueIdentifier,
  440. // <http://tools.ietf.org/html/rfc2459#page-74> doesn't use the EXPLICIT keyword but if
  441. // it's not IMPLICIT, it's EXPLICIT
  442. 'extensions' => array(
  443. 'constant' => 3,
  444. 'optional' => true,
  445. 'explicit' => true
  446. ) + $this->Extensions
  447. )
  448. );
  449. $this->Certificate = array(
  450. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  451. 'children' => array(
  452. 'tbsCertificate' => $TBSCertificate,
  453. 'signatureAlgorithm' => $AlgorithmIdentifier,
  454. 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
  455. )
  456. );
  457. $this->KeyUsage = array(
  458. 'type' => FILE_ASN1_TYPE_BIT_STRING,
  459. 'mapping' => array(
  460. 'digitalSignature',
  461. 'nonRepudiation',
  462. 'keyEncipherment',
  463. 'dataEncipherment',
  464. 'keyAgreement',
  465. 'keyCertSign',
  466. 'cRLSign',
  467. 'encipherOnly',
  468. 'decipherOnly'
  469. )
  470. );
  471. $this->BasicConstraints = array(
  472. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  473. 'children' => array(
  474. 'cA' => array(
  475. 'type' => FILE_ASN1_TYPE_BOOLEAN,
  476. 'optional' => true,
  477. 'default' => false
  478. ),
  479. 'pathLenConstraint' => array(
  480. 'type' => FILE_ASN1_TYPE_INTEGER,
  481. 'optional' => true
  482. )
  483. )
  484. );
  485. $this->KeyIdentifier = array('type' => FILE_ASN1_TYPE_OCTET_STRING);
  486. $OrganizationalUnitNames = array(
  487. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  488. 'min' => 1,
  489. 'max' => 4, // ub-organizational-units
  490. 'children' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
  491. );
  492. $PersonalName = array(
  493. 'type' => FILE_ASN1_TYPE_SET,
  494. 'children' => array(
  495. 'surname' => array(
  496. 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
  497. 'constant' => 0,
  498. 'optional' => true,
  499. 'implicit' => true
  500. ),
  501. 'given-name' => array(
  502. 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
  503. 'constant' => 1,
  504. 'optional' => true,
  505. 'implicit' => true
  506. ),
  507. 'initials' => array(
  508. 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
  509. 'constant' => 2,
  510. 'optional' => true,
  511. 'implicit' => true
  512. ),
  513. 'generation-qualifier' => array(
  514. 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
  515. 'constant' => 3,
  516. 'optional' => true,
  517. 'implicit' => true
  518. )
  519. )
  520. );
  521. $NumericUserIdentifier = array('type' => FILE_ASN1_TYPE_NUMERIC_STRING);
  522. $OrganizationName = array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING);
  523. $PrivateDomainName = array(
  524. 'type' => FILE_ASN1_TYPE_CHOICE,
  525. 'children' => array(
  526. 'numeric' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING),
  527. 'printable' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
  528. )
  529. );
  530. $TerminalIdentifier = array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING);
  531. $NetworkAddress = array('type' => FILE_ASN1_TYPE_NUMERIC_STRING);
  532. $AdministrationDomainName = array(
  533. 'type' => FILE_ASN1_TYPE_CHOICE,
  534. // if class isn't present it's assumed to be FILE_ASN1_CLASS_UNIVERSAL or
  535. // (if constant is present) FILE_ASN1_CLASS_CONTEXT_SPECIFIC
  536. 'class' => FILE_ASN1_CLASS_APPLICATION,
  537. 'cast' => 2,
  538. 'children' => array(
  539. 'numeric' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING),
  540. 'printable' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
  541. )
  542. );
  543. $CountryName = array(
  544. 'type' => FILE_ASN1_TYPE_CHOICE,
  545. // if class isn't present it's assumed to be FILE_ASN1_CLASS_UNIVERSAL or
  546. // (if constant is present) FILE_ASN1_CLASS_CONTEXT_SPECIFIC
  547. 'class' => FILE_ASN1_CLASS_APPLICATION,
  548. 'cast' => 1,
  549. 'children' => array(
  550. 'x121-dcc-code' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING),
  551. 'iso-3166-alpha2-code' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
  552. )
  553. );
  554. $AnotherName = array(
  555. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  556. 'children' => array(
  557. 'type-id' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER),
  558. 'value' => array(
  559. 'type' => FILE_ASN1_TYPE_ANY,
  560. 'constant' => 0,
  561. 'optional' => true,
  562. 'explicit' => true
  563. )
  564. )
  565. );
  566. $ExtensionAttribute = array(
  567. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  568. 'children' => array(
  569. 'extension-attribute-type' => array(
  570. 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING,
  571. 'constant' => 0,
  572. 'optional' => true,
  573. 'implicit' => true
  574. ),
  575. 'extension-attribute-value' => array(
  576. 'type' => FILE_ASN1_TYPE_ANY,
  577. 'constant' => 1,
  578. 'optional' => true,
  579. 'explicit' => true
  580. )
  581. )
  582. );
  583. $ExtensionAttributes = array(
  584. 'type' => FILE_ASN1_TYPE_SET,
  585. 'min' => 1,
  586. 'max' => 256, // ub-extension-attributes
  587. 'children' => $ExtensionAttribute
  588. );
  589. $BuiltInDomainDefinedAttribute = array(
  590. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  591. 'children' => array(
  592. 'type' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING),
  593. 'value' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING)
  594. )
  595. );
  596. $BuiltInDomainDefinedAttributes = array(
  597. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  598. 'min' => 1,
  599. 'max' => 4, // ub-domain-defined-attributes
  600. 'children' => $BuiltInDomainDefinedAttribute
  601. );
  602. $BuiltInStandardAttributes = array(
  603. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  604. 'children' => array(
  605. 'country-name' => array('optional' => true) + $CountryName,
  606. 'administration-domain-name' => array('optional' => true) + $AdministrationDomainName,
  607. 'network-address' => array(
  608. 'constant' => 0,
  609. 'optional' => true,
  610. 'implicit' => true
  611. ) + $NetworkAddress,
  612. 'terminal-identifier' => array(
  613. 'constant' => 1,
  614. 'optional' => true,
  615. 'implicit' => true
  616. ) + $TerminalIdentifier,
  617. 'private-domain-name' => array(
  618. 'constant' => 2,
  619. 'optional' => true,
  620. 'explicit' => true
  621. ) + $PrivateDomainName,
  622. 'organization-name' => array(
  623. 'constant' => 3,
  624. 'optional' => true,
  625. 'implicit' => true
  626. ) + $OrganizationName,
  627. 'numeric-user-identifier' => array(
  628. 'constant' => 4,
  629. 'optional' => true,
  630. 'implicit' => true
  631. ) + $NumericUserIdentifier,
  632. 'personal-name' => array(
  633. 'constant' => 5,
  634. 'optional' => true,
  635. 'implicit' => true
  636. ) + $PersonalName,
  637. 'organizational-unit-names' => array(
  638. 'constant' => 6,
  639. 'optional' => true,
  640. 'implicit' => true
  641. ) + $OrganizationalUnitNames
  642. )
  643. );
  644. $ORAddress = array(
  645. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  646. 'children' => array(
  647. 'built-in-standard-attributes' => $BuiltInStandardAttributes,
  648. 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes,
  649. 'extension-attributes' => array('optional' => true) + $ExtensionAttributes
  650. )
  651. );
  652. $EDIPartyName = array(
  653. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  654. 'children' => array(
  655. 'nameAssigner' => array(
  656. 'constant' => 0,
  657. 'optional' => true,
  658. 'implicit' => true
  659. ) + $this->DirectoryString,
  660. // partyName is technically required but File_ASN1 doesn't currently support non-optional constants and
  661. // setting it to optional gets the job done in any event.
  662. 'partyName' => array(
  663. 'constant' => 1,
  664. 'optional' => true,
  665. 'implicit' => true
  666. ) + $this->DirectoryString
  667. )
  668. );
  669. $GeneralName = array(
  670. 'type' => FILE_ASN1_TYPE_CHOICE,
  671. 'children' => array(
  672. 'otherName' => array(
  673. 'constant' => 0,
  674. 'optional' => true,
  675. 'implicit' => true
  676. ) + $AnotherName,
  677. 'rfc822Name' => array(
  678. 'type' => FILE_ASN1_TYPE_IA5_STRING,
  679. 'constant' => 1,
  680. 'optional' => true,
  681. 'implicit' => true
  682. ),
  683. 'dNSName' => array(
  684. 'type' => FILE_ASN1_TYPE_IA5_STRING,
  685. 'constant' => 2,
  686. 'optional' => true,
  687. 'implicit' => true
  688. ),
  689. 'x400Address' => array(
  690. 'constant' => 3,
  691. 'optional' => true,
  692. 'implicit' => true
  693. ) + $ORAddress,
  694. 'directoryName' => array(
  695. 'constant' => 4,
  696. 'optional' => true,
  697. 'explicit' => true
  698. ) + $this->Name,
  699. 'ediPartyName' => array(
  700. 'constant' => 5,
  701. 'optional' => true,
  702. 'implicit' => true
  703. ) + $EDIPartyName,
  704. 'uniformResourceIdentifier' => array(
  705. 'type' => FILE_ASN1_TYPE_IA5_STRING,
  706. 'constant' => 6,
  707. 'optional' => true,
  708. 'implicit' => true
  709. ),
  710. 'iPAddress' => array(
  711. 'type' => FILE_ASN1_TYPE_OCTET_STRING,
  712. 'constant' => 7,
  713. 'optional' => true,
  714. 'implicit' => true
  715. ),
  716. 'registeredID' => array(
  717. 'type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER,
  718. 'constant' => 8,
  719. 'optional' => true,
  720. 'implicit' => true
  721. )
  722. )
  723. );
  724. $GeneralNames = array(
  725. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  726. 'min' => 1,
  727. 'max' => -1,
  728. 'children' => $GeneralName
  729. );
  730. $this->IssuerAltName = $GeneralNames;
  731. $ReasonFlags = array(
  732. 'type' => FILE_ASN1_TYPE_BIT_STRING,
  733. 'mapping' => array(
  734. 'unused',
  735. 'keyCompromise',
  736. 'cACompromise',
  737. 'affiliationChanged',
  738. 'superseded',
  739. 'cessationOfOperation',
  740. 'certificateHold',
  741. 'privilegeWithdrawn',
  742. 'aACompromise'
  743. )
  744. );
  745. $DistributionPointName = array(
  746. 'type' => FILE_ASN1_TYPE_CHOICE,
  747. 'children' => array(
  748. 'fullName' => array(
  749. 'constant' => 0,
  750. 'optional' => true,
  751. 'implicit' => true
  752. ) + $GeneralNames,
  753. 'nameRelativeToCRLIssuer' => array(
  754. 'constant' => 1,
  755. 'optional' => true,
  756. 'implicit' => true
  757. ) + $this->RelativeDistinguishedName
  758. )
  759. );
  760. $DistributionPoint = array(
  761. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  762. 'children' => array(
  763. 'distributionPoint' => array(
  764. 'constant' => 0,
  765. 'optional' => true,
  766. 'explicit' => true
  767. ) + $DistributionPointName,
  768. 'reasons' => array(
  769. 'constant' => 1,
  770. 'optional' => true,
  771. 'implicit' => true
  772. ) + $ReasonFlags,
  773. 'cRLIssuer' => array(
  774. 'constant' => 2,
  775. 'optional' => true,
  776. 'implicit' => true
  777. ) + $GeneralNames
  778. )
  779. );
  780. $this->CRLDistributionPoints = array(
  781. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  782. 'min' => 1,
  783. 'max' => -1,
  784. 'children' => $DistributionPoint
  785. );
  786. $this->AuthorityKeyIdentifier = array(
  787. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  788. 'children' => array(
  789. 'keyIdentifier' => array(
  790. 'constant' => 0,
  791. 'optional' => true,
  792. 'implicit' => true
  793. ) + $this->KeyIdentifier,
  794. 'authorityCertIssuer' => array(
  795. 'constant' => 1,
  796. 'optional' => true,
  797. 'implicit' => true
  798. ) + $GeneralNames,
  799. 'authorityCertSerialNumber' => array(
  800. 'constant' => 2,
  801. 'optional' => true,
  802. 'implicit' => true
  803. ) + $CertificateSerialNumber
  804. )
  805. );
  806. $PolicyQualifierId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
  807. $PolicyQualifierInfo = array(
  808. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  809. 'children' => array(
  810. 'policyQualifierId' => $PolicyQualifierId,
  811. 'qualifier' => array('type' => FILE_ASN1_TYPE_ANY)
  812. )
  813. );
  814. $CertPolicyId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
  815. $PolicyInformation = array(
  816. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  817. 'children' => array(
  818. 'policyIdentifier' => $CertPolicyId,
  819. 'policyQualifiers' => array(
  820. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  821. 'min' => 0,
  822. 'max' => -1,
  823. 'optional' => true,
  824. 'children' => $PolicyQualifierInfo
  825. )
  826. )
  827. );
  828. $this->CertificatePolicies = array(
  829. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  830. 'min' => 1,
  831. 'max' => -1,
  832. 'children' => $PolicyInformation
  833. );
  834. $this->PolicyMappings = array(
  835. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  836. 'min' => 1,
  837. 'max' => -1,
  838. 'children' => array(
  839. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  840. 'children' => array(
  841. 'issuerDomainPolicy' => $CertPolicyId,
  842. 'subjectDomainPolicy' => $CertPolicyId
  843. )
  844. )
  845. );
  846. $KeyPurposeId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
  847. $this->ExtKeyUsageSyntax = array(
  848. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  849. 'min' => 1,
  850. 'max' => -1,
  851. 'children' => $KeyPurposeId
  852. );
  853. $AccessDescription = array(
  854. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  855. 'children' => array(
  856. 'accessMethod' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER),
  857. 'accessLocation' => $GeneralName
  858. )
  859. );
  860. $this->AuthorityInfoAccessSyntax = array(
  861. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  862. 'min' => 1,
  863. 'max' => -1,
  864. 'children' => $AccessDescription
  865. );
  866. $this->SubjectAltName = $GeneralNames;
  867. $this->PrivateKeyUsagePeriod = array(
  868. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  869. 'children' => array(
  870. 'notBefore' => array(
  871. 'constant' => 0,
  872. 'optional' => true,
  873. 'implicit' => true,
  874. 'type' => FILE_ASN1_TYPE_GENERALIZED_TIME),
  875. 'notAfter' => array(
  876. 'constant' => 1,
  877. 'optional' => true,
  878. 'implicit' => true,
  879. 'type' => FILE_ASN1_TYPE_GENERALIZED_TIME)
  880. )
  881. );
  882. $BaseDistance = array('type' => FILE_ASN1_TYPE_INTEGER);
  883. $GeneralSubtree = array(
  884. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  885. 'children' => array(
  886. 'base' => $GeneralName,
  887. 'minimum' => array(
  888. 'constant' => 0,
  889. 'optional' => true,
  890. 'implicit' => true,
  891. 'default' => new Math_BigInteger(0)
  892. ) + $BaseDistance,
  893. 'maximum' => array(
  894. 'constant' => 1,
  895. 'optional' => true,
  896. 'implicit' => true,
  897. ) + $BaseDistance
  898. )
  899. );
  900. $GeneralSubtrees = array(
  901. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  902. 'min' => 1,
  903. 'max' => -1,
  904. 'children' => $GeneralSubtree
  905. );
  906. $this->NameConstraints = array(
  907. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  908. 'children' => array(
  909. 'permittedSubtrees' => array(
  910. 'constant' => 0,
  911. 'optional' => true,
  912. 'implicit' => true
  913. ) + $GeneralSubtrees,
  914. 'excludedSubtrees' => array(
  915. 'constant' => 1,
  916. 'optional' => true,
  917. 'implicit' => true
  918. ) + $GeneralSubtrees
  919. )
  920. );
  921. $this->CPSuri = array('type' => FILE_ASN1_TYPE_IA5_STRING);
  922. $DisplayText = array(
  923. 'type' => FILE_ASN1_TYPE_CHOICE,
  924. 'children' => array(
  925. 'ia5String' => array('type' => FILE_ASN1_TYPE_IA5_STRING),
  926. 'visibleString' => array('type' => FILE_ASN1_TYPE_VISIBLE_STRING),
  927. 'bmpString' => array('type' => FILE_ASN1_TYPE_BMP_STRING),
  928. 'utf8String' => array('type' => FILE_ASN1_TYPE_UTF8_STRING)
  929. )
  930. );
  931. $NoticeReference = array(
  932. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  933. 'children' => array(
  934. 'organization' => $DisplayText,
  935. 'noticeNumbers' => array(
  936. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  937. 'min' => 1,
  938. 'max' => 200,
  939. 'children' => array('type' => FILE_ASN1_TYPE_INTEGER)
  940. )
  941. )
  942. );
  943. $this->UserNotice = array(
  944. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  945. 'children' => array(
  946. 'noticeRef' => array(
  947. 'optional' => true,
  948. 'implicit' => true
  949. ) + $NoticeReference,
  950. 'explicitText' => array(
  951. 'optional' => true,
  952. 'implicit' => true
  953. ) + $DisplayText
  954. )
  955. );
  956. // mapping is from <http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html>
  957. $this->netscape_cert_type = array(
  958. 'type' => FILE_ASN1_TYPE_BIT_STRING,
  959. 'mapping' => array(
  960. 'SSLClient',
  961. 'SSLServer',
  962. 'Email',
  963. 'ObjectSigning',
  964. 'Reserved',
  965. 'SSLCA',
  966. 'EmailCA',
  967. 'ObjectSigningCA'
  968. )
  969. );
  970. $this->netscape_comment = array('type' => FILE_ASN1_TYPE_IA5_STRING);
  971. $this->netscape_ca_policy_url = array('type' => FILE_ASN1_TYPE_IA5_STRING);
  972. // attribute is used in RFC2986 but we're using the RFC5280 definition
  973. $Attribute = array(
  974. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  975. 'children' => array(
  976. 'type' => $AttributeType,
  977. 'value'=> array(
  978. 'type' => FILE_ASN1_TYPE_SET,
  979. 'min' => 1,
  980. 'max' => -1,
  981. 'children' => $this->AttributeValue
  982. )
  983. )
  984. );
  985. // adapted from <http://tools.ietf.org/html/rfc2986>
  986. $Attributes = array(
  987. 'type' => FILE_ASN1_TYPE_SET,
  988. 'min' => 1,
  989. 'max' => -1,
  990. 'children' => $Attribute
  991. );
  992. $CertificationRequestInfo = array(
  993. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  994. 'children' => array(
  995. 'version' => array(
  996. 'type' => FILE_ASN1_TYPE_INTEGER,
  997. 'mapping' => array('v1')
  998. ),
  999. 'subject' => $this->Name,
  1000. 'subjectPKInfo' => $SubjectPublicKeyInfo,
  1001. 'attributes' => array(
  1002. 'constant' => 0,
  1003. 'optional' => true,
  1004. 'implicit' => true
  1005. ) + $Attributes,
  1006. )
  1007. );
  1008. $this->CertificationRequest = array(
  1009. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  1010. 'children' => array(
  1011. 'certificationRequestInfo' => $CertificationRequestInfo,
  1012. 'signatureAlgorithm' => $AlgorithmIdentifier,
  1013. 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
  1014. )
  1015. );
  1016. $RevokedCertificate = array(
  1017. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  1018. 'children' => array(
  1019. 'userCertificate' => $CertificateSerialNumber,
  1020. 'revocationDate' => $Time,
  1021. 'crlEntryExtensions' => array(
  1022. 'optional' => true
  1023. ) + $this->Extensions
  1024. )
  1025. );
  1026. $TBSCertList = array(
  1027. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  1028. 'children' => array(
  1029. 'version' => array(
  1030. 'optional' => true,
  1031. 'default' => 'v1'
  1032. ) + $Version,
  1033. 'signature' => $AlgorithmIdentifier,
  1034. 'issuer' => $this->Name,
  1035. 'thisUpdate' => $Time,
  1036. 'nextUpdate' => array(
  1037. 'optional' => true
  1038. ) + $Time,
  1039. 'revokedCertificates' => array(
  1040. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  1041. 'optional' => true,
  1042. 'min' => 0,
  1043. 'max' => -1,
  1044. 'children' => $RevokedCertificate
  1045. ),
  1046. 'crlExtensions' => array(
  1047. 'constant' => 0,
  1048. 'optional' => true,
  1049. 'explicit' => true
  1050. ) + $this->Extensions
  1051. )
  1052. );
  1053. $this->CertificateList = array(
  1054. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  1055. 'children' => array(
  1056. 'tbsCertList' => $TBSCertList,
  1057. 'signatureAlgorithm' => $AlgorithmIdentifier,
  1058. 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
  1059. )
  1060. );
  1061. $this->CRLNumber = array('type' => FILE_ASN1_TYPE_INTEGER);
  1062. $this->CRLReason = array('type' => FILE_ASN1_TYPE_ENUMERATED,
  1063. 'mapping' => array(
  1064. 'unspecified',
  1065. 'keyCompromise',
  1066. 'cACompromise',
  1067. 'affiliationChanged',
  1068. 'superseded',
  1069. 'cessationOfOperation',
  1070. 'certificateHold',
  1071. // Value 7 is not used.
  1072. 8 => 'removeFromCRL',
  1073. 'privilegeWithdrawn',
  1074. 'aACompromise'
  1075. )
  1076. );
  1077. $this->IssuingDistributionPoint = array('type' => FILE_ASN1_TYPE_SEQUENCE,
  1078. 'children' => array(
  1079. 'distributionPoint' => array(
  1080. 'constant' => 0,
  1081. 'optional' => true,
  1082. 'explicit' => true
  1083. ) + $DistributionPointName,
  1084. 'onlyContainsUserCerts' => array(
  1085. 'type' => FILE_ASN1_TYPE_BOOLEAN,
  1086. 'constant' => 1,
  1087. 'optional' => true,
  1088. 'default' => false,
  1089. 'implicit' => true
  1090. ),
  1091. 'onlyContainsCACerts' => array(
  1092. 'type' => FILE_ASN1_TYPE_BOOLEAN,
  1093. 'constant' => 2,
  1094. 'optional' => true,
  1095. 'default' => false,
  1096. 'implicit' => true
  1097. ),
  1098. 'onlySomeReasons' => array(
  1099. 'constant' => 3,
  1100. 'optional' => true,
  1101. 'implicit' => true
  1102. ) + $ReasonFlags,
  1103. 'indirectCRL' => array(
  1104. 'type' => FILE_ASN1_TYPE_BOOLEAN,
  1105. 'constant' => 4,
  1106. 'optional' => true,
  1107. 'default' => false,
  1108. 'implicit' => true
  1109. ),
  1110. 'onlyContainsAttributeCerts' => array(
  1111. 'type' => FILE_ASN1_TYPE_BOOLEAN,
  1112. 'constant' => 5,
  1113. 'optional' => true,
  1114. 'default' => false,
  1115. 'implicit' => true
  1116. )
  1117. )
  1118. );
  1119. $this->InvalidityDate = array('type' => FILE_ASN1_TYPE_GENERALIZED_TIME);
  1120. $this->CertificateIssuer = $GeneralNames;
  1121. $this->HoldInstructionCode = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER);
  1122. $PublicKeyAndChallenge = array(
  1123. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  1124. 'children' => array(
  1125. 'spki' => $SubjectPublicKeyInfo,
  1126. 'challenge' => array('type' => FILE_ASN1_TYPE_IA5_STRING)
  1127. )
  1128. );
  1129. $this->SignedPublicKeyAndChallenge = array(
  1130. 'type' => FILE_ASN1_TYPE_SEQUENCE,
  1131. 'children' => array(
  1132. 'publicKeyAndChallenge' => $PublicKeyAndChallenge,
  1133. 'signatureAlgorithm' => $AlgorithmIdentifier,
  1134. 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING)
  1135. )
  1136. );
  1137. // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
  1138. $this->oids = array(
  1139. '1.3.6.1.5.5.7' => 'id-pkix',
  1140. '1.3.6.1.5.5.7.1' => 'id-pe',
  1141. '1.3.6.1.5.5.7.2' => 'id-qt',
  1142. '1.3.6.1.5.5.7.3' => 'id-kp',
  1143. '1.3.6.1.5.5.7.48' => 'id-ad',
  1144. '1.3.6.1.5.5.7.2.1' => 'id-qt-cps',
  1145. '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice',
  1146. '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp',
  1147. '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers',
  1148. '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping',
  1149. '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository',
  1150. '2.5.4' => 'id-at',
  1151. '2.5.4.41' => 'id-at-name',
  1152. '2.5.4.4' => 'id-at-surname',
  1153. '2.5.4.42' => 'id-at-givenName',
  1154. '2.5.4.43' => 'id-at-initials',
  1155. '2.5.4.44' => 'id-at-generationQualifier',
  1156. '2.5.4.3' => 'id-at-commonName',
  1157. '2.5.4.7' => 'id-at-localityName',
  1158. '2.5.4.8' => 'id-at-stateOrProvinceName',
  1159. '2.5.4.10' => 'id-at-organizationName',
  1160. '2.5.4.11' => 'id-at-organizationalUnitName',
  1161. '2.5.4.12' => 'id-at-title',
  1162. '2.5.4.13' => 'id-at-description',
  1163. '2.5.4.46' => 'id-at-dnQualifier',
  1164. '2.5.4.6' => 'id-at-countryName',
  1165. '2.5.4.5' => 'id-at-serialNumber',
  1166. '2.5.4.65' => 'id-at-pseudonym',
  1167. '2.5.4.17' => 'id-at-postalCode',
  1168. '2.5.4.9' => 'id-at-streetAddress',
  1169. '2.5.4.45' => 'id-at-uniqueIdentifier',
  1170. '2.5.4.72' => 'id-at-role',
  1171. '0.9.2342.19200300.100.1.25' => 'id-domainComponent',
  1172. '1.2.840.113549.1.9' => 'pkcs-9',
  1173. '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress',
  1174. '2.5.29' => 'id-ce',
  1175. '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
  1176. '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
  1177. '2.5.29.15' => 'id-ce-keyUsage',
  1178. '2.5.29.16' => 'id-ce-privateKeyUsagePeriod',
  1179. '2.5.29.32' => 'id-ce-certificatePolicies',
  1180. '2.5.29.32.0' => 'anyPolicy',
  1181. '2.5.29.33' => 'id-ce-policyMappings',
  1182. '2.5.29.17' => 'id-ce-subjectAltName',
  1183. '2.5.29.18' => 'id-ce-issuerAltName',
  1184. '2.5.29.9' => 'id-ce-subjectDirectoryAttributes',
  1185. '2.5.29.19' => 'id-ce-basicConstraints',
  1186. '2.5.29.30' => 'id-ce-nameConstraints',
  1187. '2.5.29.36' => 'id-ce-policyConstraints',
  1188. '2.5.29.31' => 'id-ce-cRLDistributionPoints',
  1189. '2.5.29.37' => 'id-ce-extKeyUsage',
  1190. '2.5.29.37.0' => 'anyExtendedKeyUsage',
  1191. '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth',
  1192. '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth',
  1193. '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning',
  1194. '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection',
  1195. '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping',
  1196. '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning',
  1197. '2.5.29.54' => 'id-ce-inhibitAnyPolicy',
  1198. '2.5.29.46' => 'id-ce-freshestCRL',
  1199. '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess',
  1200. '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess',
  1201. '2.5.29.20' => 'id-ce-cRLNumber',
  1202. '2.5.29.28' => 'id-ce-issuingDistributionPoint',
  1203. '2.5.29.27' => 'id-ce-deltaCRLIndicator',
  1204. '2.5.29.21' => 'id-ce-cRLReasons',
  1205. '2.5.29.29' => 'id-ce-certificateIssuer',
  1206. '2.5.29.23' => 'id-ce-holdInstructionCode',
  1207. '1.2.840.10040.2' => 'holdInstruction',
  1208. '1.2.840.10040.2.1' => 'id-holdinstruction-none',
  1209. '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer',
  1210. '1.2.840.10040.2.3' => 'id-holdinstruction-reject',
  1211. '2.5.29.24' => 'id-ce-invalidityDate',
  1212. '1.2.840.113549.2.2' => 'md2',
  1213. '1.2.840.113549.2.5' => 'md5',
  1214. '1.3.14.3.2.26' => 'id-sha1',
  1215. '1.2.840.10040.4.1' => 'id-dsa',
  1216. '1.2.840.10040.4.3' => 'id-dsa-with-sha1',
  1217. '1.2.840.113549.1.1' => 'pkcs-1',
  1218. '1.2.840.113549.1.1.1' => 'rsaEncryption',
  1219. '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
  1220. '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption',
  1221. '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption',
  1222. '1.2.840.10046.2.1' => 'dhpublicnumber',
  1223. '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm',
  1224. '1.2.840.10045' => 'ansi-X9-62',
  1225. '1.2.840.10045.4' => 'id-ecSigType',
  1226. '1.2.840.10045.4.1' => 'ecdsa-with-SHA1',
  1227. '1.2.840.10045.1' => 'id-fieldType',
  1228. '1.2.840.10045.1.1' => 'prime-field',
  1229. '1.2.840.10045.1.2' => 'characteristic-two-field',
  1230. '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis',
  1231. '1.2.840.10045.1.2.3.1' => 'gnBasis',
  1232. '1.2.840.10045.1.2.3.2' => 'tpBasis',
  1233. '1.2.840.10045.1.2.3.3' => 'ppBasis',
  1234. '1.2.840.10045.2' => 'id-publicKeyType',
  1235. '1.2.840.10045.2.1' => 'id-ecPublicKey',
  1236. '1.2.840.10045.3' => 'ellipticCurve',
  1237. '1.2.840.10045.3.0' => 'c-TwoCurve',
  1238. '1.2.840.10045.3.0.1' => 'c2pnb163v1',
  1239. '1.2.840.10045.3.0.2' => 'c2pnb163v2',
  1240. '1.2.840.10045.3.0.3' => 'c2pnb163v3',
  1241. '1.2.840.10045.3.0.4' => 'c2pnb176w1',
  1242. '1.2.840.10045.3.0.5' => 'c2pnb191v1',
  1243. '1.2.840.10045.3.0.6' => 'c2pnb191v2',
  1244. '1.2.840.10045.3.0.7' => 'c2pnb191v3',
  1245. '1.2.840.10045.3.0.8' => 'c2pnb191v4',
  1246. '1.2.840.10045.3.0.9' => 'c2pnb191v5',
  1247. '1.2.840.10045.3.0.10' => 'c2pnb208w1',
  1248. '1.2.840.10045.3.0.11' => 'c2pnb239v1',
  1249. '1.2.840.10045.3.0.12' => 'c2pnb239v2',
  1250. '1.2.840.10045.3.0.13' => 'c2pnb239v3',
  1251. '1.2.840.10045.3.0.14' => 'c2pnb239v4',
  1252. '1.2.840.10045.3.0.15' => 'c2pnb239v5',
  1253. '1.2.840.10045.3.0.16' => 'c2pnb272w1',
  1254. '1.2.840.10045.3.0.17' => 'c2pnb304w1',
  1255. '1.2.840.10045.3.0.18' => 'c2pnb359v1',
  1256. '1.2.840.10045.3.0.19' => 'c2pnb368w1',
  1257. '1.2.840.10045.3.0.20' => 'c2pnb431r1',
  1258. '1.2.840.10045.3.1' => 'primeCurve',
  1259. '1.2.840.10045.3.1.1' => 'prime192v1',
  1260. '1.2.840.10045.3.1.2' => 'prime192v2',
  1261. '1.2.840.10045.3.1.3' => 'prime192v3',
  1262. '1.2.840.10045.3.1.4' => 'prime239v1',
  1263. '1.2.840.10045.3.1.5' => 'prime239v2',
  1264. '1.2.840.10045.3.1.6' => 'prime239v3',
  1265. '1.2.840.10045.3.1.7' => 'prime256v1',
  1266. '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP',
  1267. '1.2.840.113549.1.1.9' => 'id-pSpecified',
  1268. '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS',
  1269. '1.2.840.113549.1.1.8' => 'id-mgf1',
  1270. '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
  1271. '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption',
  1272. '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
  1273. '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption',
  1274. '2.16.840.1.101.3.4.2.4' => 'id-sha224',
  1275. '2.16.840.1.101.3.4.2.1' => 'id-sha256',
  1276. '2.16.840.1.101.3.4.2.2' => 'id-sha384',
  1277. '2.16.840.1.101.3.4.2.3' => 'id-sha512',
  1278. '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94',
  1279. '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001',
  1280. '1.2.643.2.2.20' => 'id-GostR3410-2001',
  1281. '1.2.643.2.2.19' => 'id-GostR3410-94',
  1282. // Netscape Object Identifiers from "Netscape Certificate Extensions"
  1283. '2.16.840.1.113730' => 'netscape',
  1284. '2.16.840.1.113730.1' => 'netscape-cert-extension',
  1285. '2.16.840.1.113730.1.1' => 'netscape-cert-type',
  1286. '2.16.840.1.113730.1.13' => 'netscape-comment',
  1287. '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
  1288. // the following are X.509 extensions not supported by phpseclib
  1289. '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype',
  1290. '1.2.840.113533.7.65.0' => 'entrustVersInfo',
  1291. '2.16.840.1.113733.1.6.9' => 'verisignPrivate',
  1292. // for Certificate Signing Requests
  1293. // see http://tools.ietf.org/html/rfc2985
  1294. '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', // PKCS #9 unstructured name
  1295. '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', // Challenge password for certificate revocations
  1296. '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' // Certificate extension request
  1297. );
  1298. }
  1299. /**
  1300. * Load X.509 certificate
  1301. *
  1302. * Returns an associative array describing the X.509 cert or a false if the cert failed to load
  1303. *
  1304. * @param String $cert
  1305. * @access public
  1306. * @return Mixed
  1307. */
  1308. function loadX509($cert)
  1309. {
  1310. if (is_array($cert) && isset($cert['tbsCertificate'])) {
  1311. unset($this->currentCert);
  1312. unset($this->currentKeyIdentifier);
  1313. $this->dn = $cert['tbsCertificate']['subject'];
  1314. if (!isset($this->dn)) {
  1315. return false;
  1316. }
  1317. $this->currentCert = $cert;
  1318. $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
  1319. $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
  1320. unset($this->signatureSubject);
  1321. return $cert;
  1322. }
  1323. $asn1 = new File_ASN1();
  1324. $cert = $this->_extractBER($cert);
  1325. if ($cert === false) {
  1326. $this->currentCert = false;
  1327. return false;
  1328. }
  1329. $asn1->loadOIDs($this->oids);
  1330. $decoded = $asn1->decodeBER($cert);
  1331. if (!empty($decoded)) {
  1332. $x509 = $asn1->asn1map($decoded[0], $this->Certificate);
  1333. }
  1334. if (!isset($x509) || $x509 === false) {
  1335. $this->currentCert = false;
  1336. return false;
  1337. }
  1338. $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
  1339. $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1);
  1340. $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'];
  1341. $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key);
  1342. $this->currentCert = $x509;
  1343. $this->dn = $x509['tbsCertificate']['subject'];
  1344. $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
  1345. $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
  1346. return $x509;
  1347. }
  1348. /**
  1349. * Save X.509 certificate
  1350. *
  1351. * @param Array $cert
  1352. * @param Integer $format optional
  1353. * @access public
  1354. * @return String
  1355. */
  1356. function saveX509($cert, $format = FILE_X509_FORMAT_PEM)
  1357. {
  1358. if (!is_array($cert) || !isset($cert['tbsCertificate'])) {
  1359. return false;
  1360. }
  1361. switch (true) {
  1362. // "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()"
  1363. case !($algorithm = $this->_subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')):
  1364. case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
  1365. break;
  1366. default:
  1367. switch ($algorithm) {
  1368. case 'rsaEncryption':
  1369. $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']
  1370. = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])));
  1371. }
  1372. }
  1373. $asn1 = new File_ASN1();
  1374. $asn1->loadOIDs($this->oids);
  1375. $filters = array();
  1376. $type_utf8_string = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
  1377. $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string;
  1378. $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string;
  1379. $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string;
  1380. $filters['tbsCertificate']['subject']['rdnSequence']['value'] = $type_utf8_string;
  1381. $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = $type_utf8_string;
  1382. $filters['signatureAlgorithm']['parameters'] = $type_utf8_string;
  1383. $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
  1384. //$filters['policyQualifiers']['qualifier'] = $type_utf8_string;
  1385. $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
  1386. $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string;
  1387. /* in the case of policyQualifiers/qualifier, the type has to be FILE_ASN1_TYPE_IA5_STRING.
  1388. FILE_ASN1_TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
  1389. characters.
  1390. */
  1391. $filters['policyQualifiers']['qualifier']
  1392. = array('type' => FILE_ASN1_TYPE_IA5_STRING);
  1393. $asn1->loadFilters($filters);
  1394. $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1);
  1395. $cert = $asn1->encodeDER($cert, $this->Certificate);
  1396. switch ($format) {
  1397. case FILE_X509_FORMAT_DER:
  1398. return $cert;
  1399. // case FILE_X509_FORMAT_PEM:
  1400. default:
  1401. return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert), 64) . '-----END CERTIFICATE-----';
  1402. }
  1403. }
  1404. /**
  1405. * Map extension values from octet string to extension-specific internal
  1406. * format.
  1407. *
  1408. * @param Array ref $root
  1409. * @param String $path
  1410. * @param Object $asn1
  1411. * @access private
  1412. */
  1413. function _mapInExtensions(&$root, $path, $asn1)
  1414. {
  1415. $extensions = &$this->_subArray($root, $path);
  1416. if (is_array($extensions)) {
  1417. for ($i = 0; $i < count($extensions); $i++) {
  1418. $id = $extensions[$i]['extnId'];
  1419. $value = &$extensions[$i]['extnValue'];
  1420. $value = base64_decode($value);
  1421. $decoded = $asn1->decodeBER($value);
  1422. /* [extnValue] contains the DER encoding of an ASN.1 value
  1423. corresponding to the extension type identified by extnID */
  1424. $map = $this->_getMapping($id);
  1425. if (!is_bool($map)) {
  1426. $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => array($this, '_decodeIP')));
  1427. $value = $mapped === false ? $decoded[0] : $mapped;
  1428. if ($id == 'id-ce-certificatePolicies') {
  1429. for ($j = 0; $j < count($value); $j++) {
  1430. if (!isset($value[$j]['policyQualifiers'])) {
  1431. continue;
  1432. }
  1433. for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
  1434. $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
  1435. $map = $this->_getMapping($subid);
  1436. $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
  1437. if ($map !== false) {
  1438. $decoded = $asn1->decodeBER($subvalue);
  1439. $mapped = $asn1->asn1map($decoded[0], $map);
  1440. $subvalue = $mapped === false ? $decoded[0] : $mapped;
  1441. }
  1442. }
  1443. }
  1444. }
  1445. } elseif ($map) {
  1446. $value = base64_encode($value);
  1447. }
  1448. }
  1449. }
  1450. }
  1451. /**
  1452. * Map extension values from extension-specific internal format to
  1453. * octet string.
  1454. *
  1455. * @param Array ref $root
  1456. * @param String $path
  1457. * @param Object $asn1
  1458. * @access private
  1459. */
  1460. function _mapOutExtensions(&$root, $path, $asn1)
  1461. {
  1462. $extensions = &$this->_subArray($root, $path);
  1463. if (is_array($extensions)) {
  1464. $size = count($extensions);
  1465. for ($i = 0; $i < $size; $i++) {
  1466. $id = $extensions[$i]['extnId'];
  1467. $value = &$extensions[$i]['extnValue'];
  1468. switch ($id) {
  1469. case 'id-ce-certificatePolicies':
  1470. for ($j = 0; $j < count($value); $j++) {
  1471. if (!isset($value[$j]['policyQualifiers'])) {
  1472. continue;
  1473. }
  1474. for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
  1475. $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
  1476. $map = $this->_getMapping($subid);
  1477. $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
  1478. if ($map !== false) {
  1479. // by default File_ASN1 will try to render qualifier as a FILE_ASN1_TYPE_IA5_STRING since it's
  1480. // actual type is FILE_ASN1_TYPE_ANY
  1481. $subvalue = new File_ASN1_Element($asn1->encodeDER($subvalue, $map));
  1482. }
  1483. }
  1484. }
  1485. break;
  1486. case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string
  1487. if (isset($value['authorityCertSerialNumber'])) {
  1488. if ($value['authorityCertSerialNumber']->toBytes() == '') {
  1489. $temp = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0";
  1490. $value['authorityCertSerialNumber'] = new File_ASN1_Element($temp);
  1491. }
  1492. }
  1493. }
  1494. /* [extnValue] contains the DER encoding of an ASN.1 value
  1495. corresponding to the extension type identified by extnID */
  1496. $map = $this->_getMapping($id);
  1497. if (is_bool($map)) {
  1498. if (!$map) {
  1499. user_error($id . ' is not a currently supported extension');
  1500. unset($extensions[$i]);
  1501. }
  1502. } else {
  1503. $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP')));
  1504. $value = base64_encode($temp);
  1505. }
  1506. }
  1507. }
  1508. }
  1509. /**
  1510. * Map attribute values from ANY type to attribute-specific internal
  1511. * format.
  1512. *
  1513. * @param Array ref $root
  1514. * @param String $path
  1515. * @param Object $asn1
  1516. * @access private
  1517. */
  1518. function _mapInAttributes(&$root, $path, $asn1)
  1519. {
  1520. $attributes = &$this->_subArray($root, $path);
  1521. if (is_array($attributes)) {
  1522. for ($i = 0; $i < count($attributes); $i++) {
  1523. $id = $attributes[$i]['type'];
  1524. /* $value contains the DER encoding of an ASN.1 value
  1525. corresponding to the attribute type identified by type */
  1526. $map = $this->_getMapping($id);
  1527. if (is_array($attributes[$i]['value'])) {
  1528. $values = &$attributes[$i]['value'];
  1529. for ($j = 0; $j < count($values); $j++) {
  1530. $value = $asn1->encodeDER($values[$j], $this->AttributeValue);
  1531. $decoded = $asn1->decodeBER($value);
  1532. if (!is_bool($map)) {
  1533. $mapped = $asn1->asn1map($decoded[0], $map);
  1534. if ($mapped !== false) {
  1535. $values[$j] = $mapped;
  1536. }
  1537. if ($id == 'pkcs-9-at-extensionRequest') {
  1538. $this->_mapInExtensions($values, $j, $asn1);
  1539. }
  1540. } elseif ($map) {
  1541. $values[$j] = base64_encode($value);
  1542. }
  1543. }
  1544. }
  1545. }
  1546. }
  1547. }
  1548. /**
  1549. * Map attribute values from attribute-specific internal format to
  1550. * ANY type.
  1551. *
  1552. * @param Array ref $root
  1553. * @param String $path
  1554. * @param Object $asn1
  1555. * @access private
  1556. */
  1557. function _mapOutAttributes(&$root, $path, $asn1)
  1558. {
  1559. $attributes = &$this->_subArray($root, $path);
  1560. if (is_array($attributes)) {
  1561. $size = count($attributes);
  1562. for ($i = 0; $i < $size; $i++) {
  1563. /* [value] contains the DER encoding of an ASN.1 value
  1564. corresponding to the attribute type identified by type */
  1565. $id = $attributes[$i]['type'];
  1566. $map = $this->_getMapping($id);
  1567. if ($map === false) {
  1568. user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
  1569. unset($attributes[$i]);
  1570. } elseif (is_array($attributes[$i]['value'])) {
  1571. $values = &$attributes[$i]['value'];
  1572. for ($j = 0; $j < count($values); $j++) {
  1573. switch ($id) {
  1574. case 'pkcs-9-at-extensionRequest':
  1575. $this->_mapOutExtensions($values, $j, $asn1);
  1576. break;
  1577. }
  1578. if (!is_bool($map)) {
  1579. $temp = $asn1->encodeDER($values[$j], $map);
  1580. $decoded = $asn1->decodeBER($temp);
  1581. $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue);
  1582. }
  1583. }
  1584. }
  1585. }
  1586. }
  1587. }
  1588. /**
  1589. * Associate an extension ID to an extension mapping
  1590. *
  1591. * @param String $extnId
  1592. * @access private
  1593. * @return Mixed
  1594. */
  1595. function _getMapping($extnId)
  1596. {
  1597. if (!is_string($extnId)) { // eg. if it's a File_ASN1_Element object
  1598. return true;
  1599. }
  1600. switch ($extnId) {
  1601. case 'id-ce-keyUsage':
  1602. return $this->KeyUsage;
  1603. case 'id-ce-basicConstraints':
  1604. return $this->BasicConstraints;
  1605. case 'id-ce-subjectKeyIdentifier':
  1606. return $this->KeyIdentifier;
  1607. case 'id-ce-cRLDistributionPoints':
  1608. return $this->CRLDistributionPoints;
  1609. case 'id-ce-authorityKeyIdentifier':
  1610. return $this->AuthorityKeyIdentifier;
  1611. case 'id-ce-certificatePolicies':
  1612. return $this->CertificatePolicies;
  1613. case 'id-ce-extKeyUsage':
  1614. return $this->ExtKeyUsageSyntax;
  1615. case 'id-pe-authorityInfoAccess':
  1616. return $this->AuthorityInfoAccessSyntax;
  1617. case 'id-ce-subjectAltName':
  1618. return $this->SubjectAltName;
  1619. case 'id-ce-privateKeyUsagePeriod':
  1620. return $this->PrivateKeyUsagePeriod;
  1621. case 'id-ce-issuerAltName':
  1622. return $this->IssuerAltName;
  1623. case 'id-ce-policyMappings':
  1624. return $this->PolicyMappings;
  1625. case 'id-ce-nameConstraints':
  1626. return $this->NameConstraints;
  1627. case 'netscape-cert-type':
  1628. return $this->netscape_cert_type;
  1629. case 'netscape-comment':
  1630. return $this->netscape_comment;
  1631. case 'netscape-ca-policy-url':
  1632. return $this->netscape_ca_policy_url;
  1633. // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
  1634. // back around to asn1map() and we don't want it decoded again.
  1635. //case 'id-qt-cps':
  1636. // return $this->CPSuri;
  1637. case 'id-qt-unotice':
  1638. return $this->UserNotice;
  1639. // the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
  1640. case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
  1641. case 'entrustVersInfo':
  1642. // http://support.microsoft.com/kb/287547
  1643. case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION
  1644. case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION
  1645. // "SET Secure Electronic Transaction Specification"
  1646. // http://www.maithean.com/docs/set_bk3.pdf
  1647. case '2.23.42.7.0': // id-set-hashedRootKey
  1648. return true;
  1649. // CSR attributes
  1650. case 'pkcs-9-at-unstructuredName':
  1651. return $this->PKCS9String;
  1652. case 'pkcs-9-at-challengePassword':
  1653. return $this->DirectoryString;
  1654. case 'pkcs-9-at-extensionRequest':
  1655. return $this->Extensions;
  1656. // CRL extensions.
  1657. case 'id-ce-cRLNumber':
  1658. return $this->CRLNumber;
  1659. case 'id-ce-deltaCRLIndicator':
  1660. return $this->CRLNumber;
  1661. case 'id-ce-issuingDistributionPoint':
  1662. return $this->IssuingDistributionPoint;
  1663. case 'id-ce-freshestCRL':
  1664. return $this->CRLDistributionPoints;
  1665. case 'id-ce-cRLReasons':
  1666. return $this->CRLReason;
  1667. case 'id-ce-invalidityDate':
  1668. return $this->InvalidityDate;
  1669. case 'id-ce-certificateIssuer':
  1670. return $this->CertificateIssuer;
  1671. case 'id-ce-holdInstructionCode':
  1672. return $this->HoldInstructionCode;
  1673. }
  1674. return false;
  1675. }
  1676. /**
  1677. * Load an X.509 certificate as a certificate authority
  1678. *
  1679. * @param String $cert
  1680. * @access public
  1681. * @return Boolean
  1682. */
  1683. function loadCA($cert)
  1684. {
  1685. $olddn = $this->dn;
  1686. $oldcert = $this->currentCert;
  1687. $oldsigsubj = $this->signatureSubject;
  1688. $oldkeyid = $this->currentKeyIdentifier;
  1689. $cert = $this->loadX509($cert);
  1690. if (!$cert) {
  1691. $this->dn = $olddn;
  1692. $this->currentCert = $oldcert;
  1693. $this->signatureSubject = $oldsigsubj;
  1694. $this->currentKeyIdentifier = $oldkeyid;
  1695. return false;
  1696. }
  1697. /* From RFC5280 "PKIX Certificate and CRL Profile":
  1698. If the keyUsage extension is present, then the subject public key
  1699. MUST NOT be used to verify signatures on certificates or CRLs unless
  1700. the corresponding keyCertSign or cRLSign bit is set. */
  1701. //$keyUsage = $this->getExtension('id-ce-keyUsage');
  1702. //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
  1703. // return false;
  1704. //}
  1705. /* From RFC5280 "PKIX Certificate and CRL Profile":
  1706. The cA boolean indicates whether the certified public key may be used
  1707. to verify certificate signatures. If the cA boolean is not asserted,
  1708. then the keyCertSign bit in the key usage extension MUST NOT be
  1709. asserted. If the basic constraints extension is not present in a
  1710. version 3 certificate, or the extension is present but the cA boolean
  1711. is not asserted, then the certified public key MUST NOT be used to
  1712. verify certificate signatures. */
  1713. //$basicConstraints = $this->getExtension('id-ce-basicConstraints');
  1714. //if (!$basicConstraints || !$basicConstraints['cA']) {
  1715. // return false;
  1716. //}
  1717. $this->CAs[] = $cert;
  1718. $this->dn = $olddn;
  1719. $this->currentCert = $oldcert;
  1720. $this->signatureSubject = $oldsigsubj;
  1721. return true;
  1722. }
  1723. /**
  1724. * Validate an X.509 certificate against a URL
  1725. *
  1726. * From RFC2818 "HTTP over TLS":
  1727. *
  1728. * Matching is performed using the matching rules specified by
  1729. * [RFC2459]. If more than one identity of a given type is present in
  1730. * the certificate (e.g., more than one dNSName name, a match in any one
  1731. * of the set is considered acceptable.) Names may contain the wildcard
  1732. * character * which is considered to match any single domain name
  1733. * component or component fragment. E.g., *.a.com matches foo.a.com but
  1734. * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
  1735. *
  1736. * @param String $url
  1737. * @access public
  1738. * @return Boolean
  1739. */
  1740. function validateURL($url)
  1741. {
  1742. if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
  1743. return false;
  1744. }
  1745. $components = parse_url($url);
  1746. if (!isset($components['host'])) {
  1747. return false;
  1748. }
  1749. if ($names = $this->getExtension('id-ce-subjectAltName')) {
  1750. foreach ($names as $key => $value) {
  1751. $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value);
  1752. switch ($key) {
  1753. case 'dNSName':
  1754. /* From RFC2818 "HTTP over TLS":
  1755. If a subjectAltName extension of type dNSName is present, that MUST
  1756. be used as the identity. Otherwise, the (most specific) Common Name
  1757. field in the Subject field of the certificate MUST be used. Although
  1758. the use of the Common Name is existing practice, it is deprecated and
  1759. Certification Authorities are encouraged to use the dNSName instead. */
  1760. if (preg_match('#^' . $value . '$#', $components['host'])) {
  1761. return true;
  1762. }
  1763. break;
  1764. case 'iPAddress':
  1765. /* From RFC2818 "HTTP over TLS":
  1766. In some cases, the URI is specified as an IP address rather than a
  1767. hostname. In this case, the iPAddress subjectAltName must be present
  1768. in the certificate and must exactly match the IP in the URI. */
  1769. if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
  1770. return true;
  1771. }
  1772. }
  1773. }
  1774. return false;
  1775. }
  1776. if ($value = $this->getDNProp('id-at-commonName')) {
  1777. $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]);
  1778. return preg_match('#^' . $value . '$#', $components['host']);
  1779. }
  1780. return false;
  1781. }
  1782. /**
  1783. * Validate a date
  1784. *
  1785. * If $date isn't defined it is assumed to be the current date.
  1786. *
  1787. * @param Integer $date optional
  1788. * @access public
  1789. */
  1790. function validateDate($date = null)
  1791. {
  1792. if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
  1793. return false;
  1794. }
  1795. if (!isset($date)) {
  1796. $date = time();
  1797. }
  1798. $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
  1799. $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime'];
  1800. $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
  1801. $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
  1802. switch (true) {
  1803. case $date < @strtotime($notBefore):
  1804. case $date > @strtotime($notAfter):
  1805. return false;
  1806. }
  1807. return true;
  1808. }
  1809. /**
  1810. * Validate a signature
  1811. *
  1812. * Works on X.509 certs, CSR's and CRL's.
  1813. * Returns true if the signature is verified, false if it is not correct or null on error
  1814. *
  1815. * By default returns false for self-signed certs. Call validateSignature(false) to make this support
  1816. * self-signed.
  1817. *
  1818. * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
  1819. *
  1820. * @param Boolean $caonly optional
  1821. * @access public
  1822. * @return Mixed
  1823. */
  1824. function validateSignature($caonly = true)
  1825. {
  1826. if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
  1827. return null;
  1828. }
  1829. /* TODO:
  1830. "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")."
  1831. -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6
  1832. implement pathLenConstraint in the id-ce-basicConstraints extension */
  1833. switch (true) {
  1834. case isset($this->currentCert['tbsCertificate']):
  1835. // self-signed cert
  1836. if ($this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']) {
  1837. $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
  1838. $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier');
  1839. switch (true) {
  1840. case !is_array($authorityKey):
  1841. case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
  1842. $signingCert = $this->currentCert; // working cert
  1843. }
  1844. }
  1845. if (!empty($this->CAs)) {
  1846. for ($i = 0; $i < count($this->CAs); $i++) {
  1847. // even if the cert is a self-signed one we still want to see if it's a CA;
  1848. // if not, we'll conditionally return an error
  1849. $ca = $this->CAs[$i];
  1850. if ($this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
  1851. $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
  1852. $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
  1853. switch (true) {
  1854. case !is_array($authorityKey):
  1855. case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
  1856. $signingCert = $ca; // working cert
  1857. break 2;
  1858. }
  1859. }
  1860. }
  1861. if (count($this->CAs) == $i && $caonly) {
  1862. return false;
  1863. }
  1864. } elseif (!isset($signingCert) || $caonly) {
  1865. return false;
  1866. }
  1867. return $this->_validateSignature(
  1868. $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
  1869. $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
  1870. $this->currentCert['signatureAlgorithm']['algorithm'],
  1871. substr(base64_decode($this->currentCert['signature']), 1),
  1872. $this->signatureSubject
  1873. );
  1874. case isset($this->currentCert['certificationRequestInfo']):
  1875. return $this->_validateSignature(
  1876. $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
  1877. $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
  1878. $this->currentCert['signatureAlgorithm']['algorithm'],
  1879. substr(base64_decode($this->currentCert['signature']), 1),
  1880. $this->signatureSubject
  1881. );
  1882. case isset($this->currentCert['publicKeyAndChallenge']):
  1883. return $this->_validateSignature(
  1884. $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'],
  1885. $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'],
  1886. $this->currentCert['signatureAlgorithm']['algorithm'],
  1887. substr(base64_decode($this->currentCert['signature']), 1),
  1888. $this->signatureSubject
  1889. );
  1890. case isset($this->currentCert['tbsCertList']):
  1891. if (!empty($this->CAs)) {
  1892. for ($i = 0; $i < count($this->CAs); $i++) {
  1893. $ca = $this->CAs[$i];
  1894. if ($this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']) {
  1895. $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
  1896. $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
  1897. switch (true) {
  1898. case !is_array($authorityKey):
  1899. case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
  1900. $signingCert = $ca; // working cert
  1901. break 2;
  1902. }
  1903. }
  1904. }
  1905. }
  1906. if (!isset($signingCert)) {
  1907. return false;
  1908. }
  1909. return $this->_validateSignature(
  1910. $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
  1911. $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
  1912. $this->currentCert['signatureAlgorithm']['algorithm'],
  1913. substr(base64_decode($this->currentCert['signature']), 1),
  1914. $this->signatureSubject
  1915. );
  1916. default:
  1917. return false;
  1918. }
  1919. }
  1920. /**
  1921. * Validates a signature
  1922. *
  1923. * Returns true if the signature is verified, false if it is not correct or null on error
  1924. *
  1925. * @param String $publicKeyAlgorithm
  1926. * @param String $publicKey
  1927. * @param String $signatureAlgorithm
  1928. * @param String $signature
  1929. * @param String $signatureSubject
  1930. * @access private
  1931. * @return Integer
  1932. */
  1933. function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
  1934. {
  1935. switch ($publicKeyAlgorithm) {
  1936. case 'rsaEncryption':
  1937. if (!class_exists('Crypt_RSA')) {
  1938. include_once 'Crypt/RSA.php';
  1939. }
  1940. $rsa = new Crypt_RSA();
  1941. $rsa->loadKey($publicKey);
  1942. switch ($signatureAlgorithm) {
  1943. case 'md2WithRSAEncryption':
  1944. case 'md5WithRSAEncryption':
  1945. case 'sha1WithRSAEncryption':
  1946. case 'sha224WithRSAEncryption':
  1947. case 'sha256WithRSAEncryption':
  1948. case 'sha384WithRSAEncryption':
  1949. case 'sha512WithRSAEncryption':
  1950. $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
  1951. $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
  1952. if (!@$rsa->verify($signatureSubject, $signature)) {
  1953. return false;
  1954. }
  1955. break;
  1956. default:
  1957. return null;
  1958. }
  1959. break;
  1960. default:
  1961. return null;
  1962. }
  1963. return true;
  1964. }
  1965. /**
  1966. * Reformat public keys
  1967. *
  1968. * Reformats a public key to a format supported by phpseclib (if applicable)
  1969. *
  1970. * @param String $algorithm
  1971. * @param String $key
  1972. * @access private
  1973. * @return String
  1974. */
  1975. function _reformatKey($algorithm, $key)
  1976. {
  1977. switch ($algorithm) {
  1978. case 'rsaEncryption':
  1979. return
  1980. "-----BEGIN RSA PUBLIC KEY-----\r\n" .
  1981. // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits
  1982. // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox
  1983. // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do.
  1984. chunk_split(base64_encode(substr(base64_decode($key), 1)), 64) .
  1985. '-----END RSA PUBLIC KEY-----';
  1986. default:
  1987. return $key;
  1988. }
  1989. }
  1990. /**
  1991. * Decodes an IP address
  1992. *
  1993. * Takes in a base64 encoded "blob" and returns a human readable IP address
  1994. *
  1995. * @param String $ip
  1996. * @access private
  1997. * @return String
  1998. */
  1999. function _decodeIP($ip)
  2000. {
  2001. $ip = base64_decode($ip);
  2002. list(, $ip) = unpack('N', $ip);
  2003. return long2ip($ip);
  2004. }
  2005. /**
  2006. * Encodes an IP address
  2007. *
  2008. * Takes a human readable IP address into a base64-encoded "blob"
  2009. *
  2010. * @param String $ip
  2011. * @access private
  2012. * @return String
  2013. */
  2014. function _encodeIP($ip)
  2015. {
  2016. return base64_encode(pack('N', ip2long($ip)));
  2017. }
  2018. /**
  2019. * "Normalizes" a Distinguished Name property
  2020. *
  2021. * @param String $propName
  2022. * @access private
  2023. * @return Mixed
  2024. */
  2025. function _translateDNProp($propName)
  2026. {
  2027. switch (strtolower($propName)) {
  2028. case 'id-at-countryname':
  2029. case 'countryname':
  2030. case 'c':
  2031. return 'id-at-countryName';
  2032. case 'id-at-organizationname':
  2033. case 'organizationname':
  2034. case 'o':
  2035. return 'id-at-organizationName';
  2036. case 'id-at-dnqualifier':
  2037. case 'dnqualifier':
  2038. return 'id-at-dnQualifier';
  2039. case 'id-at-commonname':
  2040. case 'commonname':
  2041. case 'cn':
  2042. return 'id-at-commonName';
  2043. case 'id-at-stateorprovincename':
  2044. case 'stateorprovincename':
  2045. case 'state':
  2046. case 'province':
  2047. case 'provincename':
  2048. case 'st':
  2049. return 'id-at-stateOrProvinceName';
  2050. case 'id-at-localityname':
  2051. case 'localityname':
  2052. case 'l':
  2053. return 'id-at-localityName';
  2054. case 'id-emailaddress':
  2055. case 'emailaddress':
  2056. return 'pkcs-9-at-emailAddress';
  2057. case 'id-at-serialnumber':
  2058. case 'serialnumber':
  2059. return 'id-at-serialNumber';
  2060. case 'id-at-postalcode':
  2061. case 'postalcode':
  2062. return 'id-at-postalCode';
  2063. case 'id-at-streetaddress':
  2064. case 'streetaddress':
  2065. return 'id-at-streetAddress';
  2066. case 'id-at-name':
  2067. case 'name':
  2068. return 'id-at-name';
  2069. case 'id-at-givenname':
  2070. case 'givenname':
  2071. return 'id-at-givenName';
  2072. case 'id-at-surname':
  2073. case 'surname':
  2074. case 'sn':
  2075. return 'id-at-surname';
  2076. case 'id-at-initials':
  2077. case 'initials':
  2078. return 'id-at-initials';
  2079. case 'id-at-generationqualifier':
  2080. case 'generationqualifier':
  2081. return 'id-at-generationQualifier';
  2082. case 'id-at-organizationalunitname':
  2083. case 'organizationalunitname':
  2084. case 'ou':
  2085. return 'id-at-organizationalUnitName';
  2086. case 'id-at-pseudonym':
  2087. case 'pseudonym':
  2088. return 'id-at-pseudonym';
  2089. case 'id-at-title':
  2090. case 'title':
  2091. return 'id-at-title';
  2092. case 'id-at-description':
  2093. case 'description':
  2094. return 'id-at-description';
  2095. case 'id-at-role':
  2096. case 'role':
  2097. return 'id-at-role';
  2098. case 'id-at-uniqueidentifier':
  2099. case 'uniqueidentifier':
  2100. case 'x500uniqueidentifier':
  2101. return 'id-at-uniqueIdentifier';
  2102. default:
  2103. return false;
  2104. }
  2105. }
  2106. /**
  2107. * Set a Distinguished Name property
  2108. *
  2109. * @param String $propName
  2110. * @param Mixed $propValue
  2111. * @param String $type optional
  2112. * @access public
  2113. * @return Boolean
  2114. */
  2115. function setDNProp($propName, $propValue, $type = 'utf8String')
  2116. {
  2117. if (empty($this->dn)) {
  2118. $this->dn = array('rdnSequence' => array());
  2119. }
  2120. if (($propName = $this->_translateDNProp($propName)) === false) {
  2121. return false;
  2122. }
  2123. foreach ((array) $propValue as $v) {
  2124. if (!is_array($v) && isset($type)) {
  2125. $v = array($type => $v);
  2126. }
  2127. $this->dn['rdnSequence'][] = array(
  2128. array(
  2129. 'type' => $propName,
  2130. 'value'=> $v
  2131. )
  2132. );
  2133. }
  2134. return true;
  2135. }
  2136. /**
  2137. * Remove Distinguished Name properties
  2138. *
  2139. * @param String $propName
  2140. * @access public
  2141. */
  2142. function removeDNProp($propName)
  2143. {
  2144. if (empty($this->dn)) {
  2145. return;
  2146. }
  2147. if (($propName = $this->_translateDNProp($propName)) === false) {
  2148. return;
  2149. }
  2150. $dn = &$this->dn['rdnSequence'];
  2151. $size = count($dn);
  2152. for ($i = 0; $i < $size; $i++) {
  2153. if ($dn[$i][0]['type'] == $propName) {
  2154. unset($dn[$i]);
  2155. }
  2156. }
  2157. $dn = array_values($dn);
  2158. }
  2159. /**
  2160. * Get Distinguished Name properties
  2161. *
  2162. * @param String $propName
  2163. * @param Array $dn optional
  2164. * @param Boolean $withType optional
  2165. * @return Mixed
  2166. * @access public
  2167. */
  2168. function getDNProp($propName, $dn = null, $withType = false)
  2169. {
  2170. if (!isset($dn)) {
  2171. $dn = $this->dn;
  2172. }
  2173. if (empty($dn)) {
  2174. return false;
  2175. }
  2176. if (($propName = $this->_translateDNProp($propName)) === false) {
  2177. return false;
  2178. }
  2179. $dn = $dn['rdnSequence'];
  2180. $result = array();
  2181. $asn1 = new File_ASN1();
  2182. for ($i = 0; $i < count($dn); $i++) {
  2183. if ($dn[$i][0]['type'] == $propName) {
  2184. $v = $dn[$i][0]['value'];
  2185. if (!$withType && is_array($v)) {
  2186. foreach ($v as $type => $s) {
  2187. $type = array_search($type, $asn1->ANYmap, true);
  2188. if ($type !== false && isset($asn1->stringTypeSize[$type])) {
  2189. $s = $asn1->convert($s, $type);
  2190. if ($s !== false) {
  2191. $v = $s;
  2192. break;
  2193. }
  2194. }
  2195. }
  2196. if (is_array($v)) {
  2197. $v = array_pop($v); // Always strip data type.
  2198. }
  2199. }
  2200. $result[] = $v;
  2201. }
  2202. }
  2203. return $result;
  2204. }
  2205. /**
  2206. * Set a Distinguished Name
  2207. *
  2208. * @param Mixed $dn
  2209. * @param Boolean $merge optional
  2210. * @param String $type optional
  2211. * @access public
  2212. * @return Boolean
  2213. */
  2214. function setDN($dn, $merge = false, $type = 'utf8String')
  2215. {
  2216. if (!$merge) {
  2217. $this->dn = null;
  2218. }
  2219. if (is_array($dn)) {
  2220. if (isset($dn['rdnSequence'])) {
  2221. $this->dn = $dn; // No merge here.
  2222. return true;
  2223. }
  2224. // handles stuff generated by openssl_x509_parse()
  2225. foreach ($dn as $prop => $value) {
  2226. if (!$this->setDNProp($prop, $value, $type)) {
  2227. return false;
  2228. }
  2229. }
  2230. return true;
  2231. }
  2232. // handles everything else
  2233. $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
  2234. for ($i = 1; $i < count($results); $i+=2) {
  2235. $prop = trim($results[$i], ', =/');
  2236. $value = $results[$i + 1];
  2237. if (!$this->setDNProp($prop, $value, $type)) {
  2238. return false;
  2239. }
  2240. }
  2241. return true;
  2242. }
  2243. /**
  2244. * Get the Distinguished Name for a certificates subject
  2245. *
  2246. * @param Mixed $format optional
  2247. * @param Array $dn optional
  2248. * @access public
  2249. * @return Boolean
  2250. */
  2251. function getDN($format = FILE_X509_DN_ARRAY, $dn = null)
  2252. {
  2253. if (!isset($dn)) {
  2254. $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn;
  2255. }
  2256. switch ((int) $format) {
  2257. case FILE_X509_DN_ARRAY:
  2258. return $dn;
  2259. case FILE_X509_DN_ASN1:
  2260. $asn1 = new File_ASN1();
  2261. $asn1->loadOIDs($this->oids);
  2262. $filters = array();
  2263. $filters['rdnSequence']['value'] = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
  2264. $asn1->loadFilters($filters);
  2265. return $asn1->encodeDER($dn, $this->Name);
  2266. case FILE_X509_DN_OPENSSL:
  2267. $dn = $this->getDN(FILE_X509_DN_STRING, $dn);
  2268. if ($dn === false) {
  2269. return false;
  2270. }
  2271. $attrs = preg_split('#((?:^|, *|/)[a-z][a-z0-9]*=)#i', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
  2272. $dn = array();
  2273. for ($i = 1; $i < count($attrs); $i += 2) {
  2274. $prop = trim($attrs[$i], ', =/');
  2275. $value = $attrs[$i + 1];
  2276. if (!isset($dn[$prop])) {
  2277. $dn[$prop] = $value;
  2278. } else {
  2279. $dn[$prop] = array_merge((array) $dn[$prop], array($value));
  2280. }
  2281. }
  2282. return $dn;
  2283. case FILE_X509_DN_CANON:
  2284. // No SEQUENCE around RDNs and all string values normalized as
  2285. // trimmed lowercase UTF-8 with all spacing as one blank.
  2286. $asn1 = new File_ASN1();
  2287. $asn1->loadOIDs($this->oids);
  2288. $filters = array();
  2289. $filters['value'] = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
  2290. $asn1->loadFilters($filters);
  2291. $result = '';
  2292. foreach ($dn['rdnSequence'] as $rdn) {
  2293. foreach ($rdn as $i=>$attr) {
  2294. $attr = &$rdn[$i];
  2295. if (is_array($attr['value'])) {
  2296. foreach ($attr['value'] as $type => $v) {
  2297. $type = array_search($type, $asn1->ANYmap, true);
  2298. if ($type !== false && isset($asn1->stringTypeSize[$type])) {
  2299. $v = $asn1->convert($v, $type);
  2300. if ($v !== false) {
  2301. $v = preg_replace('/\s+/', ' ', $v);
  2302. $attr['value'] = strtolower(trim($v));
  2303. break;
  2304. }
  2305. }
  2306. }
  2307. }
  2308. }
  2309. $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName);
  2310. }
  2311. return $result;
  2312. case FILE_X509_DN_HASH:
  2313. $dn = $this->getDN(FILE_X509_DN_CANON, $dn);
  2314. if (!class_exists('Crypt_Hash')) {
  2315. include_once 'Crypt/Hash.php';
  2316. }
  2317. $hash = new Crypt_Hash('sha1');
  2318. $hash = $hash->hash($dn);
  2319. extract(unpack('Vhash', $hash));
  2320. return strtolower(bin2hex(pack('N', $hash)));
  2321. }
  2322. // Default is to return a string.
  2323. $start = true;
  2324. $output = '';
  2325. $asn1 = new File_ASN1();
  2326. foreach ($dn['rdnSequence'] as $field) {
  2327. $prop = $field[0]['type'];
  2328. $value = $field[0]['value'];
  2329. $delim = ', ';
  2330. switch ($prop) {
  2331. case 'id-at-countryName':
  2332. $desc = 'C=';
  2333. break;
  2334. case 'id-at-stateOrProvinceName':
  2335. $desc = 'ST=';
  2336. break;
  2337. case 'id-at-organizationName':
  2338. $desc = 'O=';
  2339. break;
  2340. case 'id-at-organizationalUnitName':
  2341. $desc = 'OU=';
  2342. break;
  2343. case 'id-at-commonName':
  2344. $desc = 'CN=';
  2345. break;
  2346. case 'id-at-localityName':
  2347. $desc = 'L=';
  2348. break;
  2349. case 'id-at-surname':
  2350. $desc = 'SN=';
  2351. break;
  2352. case 'id-at-uniqueIdentifier':
  2353. $delim = '/';
  2354. $desc = 'x500UniqueIdentifier=';
  2355. break;
  2356. default:
  2357. $delim = '/';
  2358. $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop) . '=';
  2359. }
  2360. if (!$start) {
  2361. $output.= $delim;
  2362. }
  2363. if (is_array($value)) {
  2364. foreach ($value as $type => $v) {
  2365. $type = array_search($type, $asn1->ANYmap, true);
  2366. if ($type !== false && isset($asn1->stringTypeSize[$type])) {
  2367. $v = $asn1->convert($v, $type);
  2368. if ($v !== false) {
  2369. $value = $v;
  2370. break;
  2371. }
  2372. }
  2373. }
  2374. if (is_array($value)) {
  2375. $value = array_pop($value); // Always strip data type.
  2376. }
  2377. }
  2378. $output.= $desc . $value;
  2379. $start = false;
  2380. }
  2381. return $output;
  2382. }
  2383. /**
  2384. * Get the Distinguished Name for a certificate/crl issuer
  2385. *
  2386. * @param Integer $format optional
  2387. * @access public
  2388. * @return Mixed
  2389. */
  2390. function getIssuerDN($format = FILE_X509_DN_ARRAY)
  2391. {
  2392. switch (true) {
  2393. case !isset($this->currentCert) || !is_array($this->currentCert):
  2394. break;
  2395. case isset($this->currentCert['tbsCertificate']):
  2396. return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']);
  2397. case isset($this->currentCert['tbsCertList']):
  2398. return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']);
  2399. }
  2400. return false;
  2401. }
  2402. /**
  2403. * Get the Distinguished Name for a certificate/csr subject
  2404. * Alias of getDN()
  2405. *
  2406. * @param Integer $format optional
  2407. * @access public
  2408. * @return Mixed
  2409. */
  2410. function getSubjectDN($format = FILE_X509_DN_ARRAY)
  2411. {
  2412. switch (true) {
  2413. case !empty($this->dn):
  2414. return $this->getDN($format);
  2415. case !isset($this->currentCert) || !is_array($this->currentCert):
  2416. break;
  2417. case isset($this->currentCert['tbsCertificate']):
  2418. return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']);
  2419. case isset($this->currentCert['certificationRequestInfo']):
  2420. return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']);
  2421. }
  2422. return false;
  2423. }
  2424. /**
  2425. * Get an individual Distinguished Name property for a certificate/crl issuer
  2426. *
  2427. * @param String $propName
  2428. * @param Boolean $withType optional
  2429. * @access public
  2430. * @return Mixed
  2431. */
  2432. function getIssuerDNProp($propName, $withType = false)
  2433. {
  2434. switch (true) {
  2435. case !isset($this->currentCert) || !is_array($this->currentCert):
  2436. break;
  2437. case isset($this->currentCert['tbsCertificate']):
  2438. return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType);
  2439. case isset($this->currentCert['tbsCertList']):
  2440. return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType);
  2441. }
  2442. return false;
  2443. }
  2444. /**
  2445. * Get an individual Distinguished Name property for a certificate/csr subject
  2446. *
  2447. * @param String $propName
  2448. * @param Boolean $withType optional
  2449. * @access public
  2450. * @return Mixed
  2451. */
  2452. function getSubjectDNProp($propName, $withType = false)
  2453. {
  2454. switch (true) {
  2455. case !empty($this->dn):
  2456. return $this->getDNProp($propName, null, $withType);
  2457. case !isset($this->currentCert) || !is_array($this->currentCert):
  2458. break;
  2459. case isset($this->currentCert['tbsCertificate']):
  2460. return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType);
  2461. case isset($this->currentCert['certificationRequestInfo']):
  2462. return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType);
  2463. }
  2464. return false;
  2465. }
  2466. /**
  2467. * Get the certificate chain for the current cert
  2468. *
  2469. * @access public
  2470. * @return Mixed
  2471. */
  2472. function getChain()
  2473. {
  2474. $chain = array($this->currentCert);
  2475. if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
  2476. return false;
  2477. }
  2478. if (empty($this->CAs)) {
  2479. return $chain;
  2480. }
  2481. while (true) {
  2482. $currentCert = $chain[count($chain) - 1];
  2483. for ($i = 0; $i < count($this->CAs); $i++) {
  2484. $ca = $this->CAs[$i];
  2485. if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
  2486. $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert);
  2487. $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
  2488. switch (true) {
  2489. case !is_array($authorityKey):
  2490. case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
  2491. if ($currentCert === $ca) {
  2492. break 3;
  2493. }
  2494. $chain[] = $ca;
  2495. break 2;
  2496. }
  2497. }
  2498. }
  2499. if ($i == count($this->CAs)) {
  2500. break;
  2501. }
  2502. }
  2503. foreach ($chain as $key=>$value) {
  2504. $chain[$key] = new File_X509();
  2505. $chain[$key]->loadX509($value);
  2506. }
  2507. return $chain;
  2508. }
  2509. /**
  2510. * Set public key
  2511. *
  2512. * Key needs to be a Crypt_RSA object
  2513. *
  2514. * @param Object $key
  2515. * @access public
  2516. * @return Boolean
  2517. */
  2518. function setPublicKey($key)
  2519. {
  2520. $key->setPublicKey();
  2521. $this->publicKey = $key;
  2522. }
  2523. /**
  2524. * Set private key
  2525. *
  2526. * Key needs to be a Crypt_RSA object
  2527. *
  2528. * @param Object $key
  2529. * @access public
  2530. */
  2531. function setPrivateKey($key)
  2532. {
  2533. $this->privateKey = $key;
  2534. }
  2535. /**
  2536. * Set challenge
  2537. *
  2538. * Used for SPKAC CSR's
  2539. *
  2540. * @param String $challenge
  2541. * @access public
  2542. */
  2543. function setChallenge($challenge)
  2544. {
  2545. $this->challenge = $challenge;
  2546. }
  2547. /**
  2548. * Gets the public key
  2549. *
  2550. * Returns a Crypt_RSA object or a false.
  2551. *
  2552. * @access public
  2553. * @return Mixed
  2554. */
  2555. function getPublicKey()
  2556. {
  2557. if (isset($this->publicKey)) {
  2558. return $this->publicKey;
  2559. }
  2560. if (isset($this->currentCert) && is_array($this->currentCert)) {
  2561. foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) {
  2562. $keyinfo = $this->_subArray($this->currentCert, $path);
  2563. if (!empty($keyinfo)) {
  2564. break;
  2565. }
  2566. }
  2567. }
  2568. if (empty($keyinfo)) {
  2569. return false;
  2570. }
  2571. $key = $keyinfo['subjectPublicKey'];
  2572. switch ($keyinfo['algorithm']['algorithm']) {
  2573. case 'rsaEncryption':
  2574. if (!class_exists('Crypt_RSA')) {
  2575. include_once 'Crypt/RSA.php';
  2576. }
  2577. $publicKey = new Crypt_RSA();
  2578. $publicKey->loadKey($key);
  2579. $publicKey->setPublicKey();
  2580. break;
  2581. default:
  2582. return false;
  2583. }
  2584. return $publicKey;
  2585. }
  2586. /**
  2587. * Load a Certificate Signing Request
  2588. *
  2589. * @param String $csr
  2590. * @access public
  2591. * @return Mixed
  2592. */
  2593. function loadCSR($csr)
  2594. {
  2595. if (is_array($csr) && isset($csr['certificationRequestInfo'])) {
  2596. unset($this->currentCert);
  2597. unset($this->currentKeyIdentifier);
  2598. unset($this->signatureSubject);
  2599. $this->dn = $csr['certificationRequestInfo']['subject'];
  2600. if (!isset($this->dn)) {
  2601. return false;
  2602. }
  2603. $this->currentCert = $csr;
  2604. return $csr;
  2605. }
  2606. // see http://tools.ietf.org/html/rfc2986
  2607. $asn1 = new File_ASN1();
  2608. $csr = $this->_extractBER($csr);
  2609. $orig = $csr;
  2610. if ($csr === false) {
  2611. $this->currentCert = false;
  2612. return false;
  2613. }
  2614. $asn1->loadOIDs($this->oids);
  2615. $decoded = $asn1->decodeBER($csr);
  2616. if (empty($decoded)) {
  2617. $this->currentCert = false;
  2618. return false;
  2619. }
  2620. $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest);
  2621. if (!isset($csr) || $csr === false) {
  2622. $this->currentCert = false;
  2623. return false;
  2624. }
  2625. $this->dn = $csr['certificationRequestInfo']['subject'];
  2626. $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
  2627. $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
  2628. $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'];
  2629. $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'];
  2630. $key = $this->_reformatKey($algorithm, $key);
  2631. switch ($algorithm) {
  2632. case 'rsaEncryption':
  2633. if (!class_exists('Crypt_RSA')) {
  2634. include_once 'Crypt/RSA.php';
  2635. }
  2636. $this->publicKey = new Crypt_RSA();
  2637. $this->publicKey->loadKey($key);
  2638. $this->publicKey->setPublicKey();
  2639. break;
  2640. default:
  2641. $this->publicKey = null;
  2642. }
  2643. $this->currentKeyIdentifier = null;
  2644. $this->currentCert = $csr;
  2645. return $csr;
  2646. }
  2647. /**
  2648. * Save CSR request
  2649. *
  2650. * @param Array $csr
  2651. * @param Integer $format optional
  2652. * @access public
  2653. * @return String
  2654. */
  2655. function saveCSR($csr, $format = FILE_X509_FORMAT_PEM)
  2656. {
  2657. if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) {
  2658. return false;
  2659. }
  2660. switch (true) {
  2661. case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
  2662. case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']);
  2663. break;
  2664. default:
  2665. switch ($algorithm) {
  2666. case 'rsaEncryption':
  2667. $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']
  2668. = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])));
  2669. }
  2670. }
  2671. $asn1 = new File_ASN1();
  2672. $asn1->loadOIDs($this->oids);
  2673. $filters = array();
  2674. $filters['certificationRequestInfo']['subject']['rdnSequence']['value']
  2675. = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
  2676. $asn1->loadFilters($filters);
  2677. $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
  2678. $csr = $asn1->encodeDER($csr, $this->CertificationRequest);
  2679. switch ($format) {
  2680. case FILE_X509_FORMAT_DER:
  2681. return $csr;
  2682. // case FILE_X509_FORMAT_PEM:
  2683. default:
  2684. return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
  2685. }
  2686. }
  2687. /**
  2688. * Load a SPKAC CSR
  2689. *
  2690. * SPKAC's are produced by the HTML5 keygen element:
  2691. *
  2692. * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen
  2693. *
  2694. * @param String $csr
  2695. * @access public
  2696. * @return Mixed
  2697. */
  2698. function loadSPKAC($spkac)
  2699. {
  2700. if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) {
  2701. unset($this->currentCert);
  2702. unset($this->currentKeyIdentifier);
  2703. unset($this->signatureSubject);
  2704. $this->currentCert = $spkac;
  2705. return $spkac;
  2706. }
  2707. // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge
  2708. $asn1 = new File_ASN1();
  2709. // OpenSSL produces SPKAC's that are preceeded by the string SPKAC=
  2710. $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
  2711. $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
  2712. if ($temp != false) {
  2713. $spkac = $temp;
  2714. }
  2715. $orig = $spkac;
  2716. if ($spkac === false) {
  2717. $this->currentCert = false;
  2718. return false;
  2719. }
  2720. $asn1->loadOIDs($this->oids);
  2721. $decoded = $asn1->decodeBER($spkac);
  2722. if (empty($decoded)) {
  2723. $this->currentCert = false;
  2724. return false;
  2725. }
  2726. $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge);
  2727. if (!isset($spkac) || $spkac === false) {
  2728. $this->currentCert = false;
  2729. return false;
  2730. }
  2731. $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
  2732. $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm'];
  2733. $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'];
  2734. $key = $this->_reformatKey($algorithm, $key);
  2735. switch ($algorithm) {
  2736. case 'rsaEncryption':
  2737. if (!class_exists('Crypt_RSA')) {
  2738. include_once 'Crypt/RSA.php';
  2739. }
  2740. $this->publicKey = new Crypt_RSA();
  2741. $this->publicKey->loadKey($key);
  2742. $this->publicKey->setPublicKey();
  2743. break;
  2744. default:
  2745. $this->publicKey = null;
  2746. }
  2747. $this->currentKeyIdentifier = null;
  2748. $this->currentCert = $spkac;
  2749. return $spkac;
  2750. }
  2751. /**
  2752. * Save a SPKAC CSR request
  2753. *
  2754. * @param Array $csr
  2755. * @param Integer $format optional
  2756. * @access public
  2757. * @return String
  2758. */
  2759. function saveSPKAC($spkac, $format = FILE_X509_FORMAT_PEM)
  2760. {
  2761. if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
  2762. return false;
  2763. }
  2764. $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
  2765. switch (true) {
  2766. case !$algorithm:
  2767. case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']);
  2768. break;
  2769. default:
  2770. switch ($algorithm) {
  2771. case 'rsaEncryption':
  2772. $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']
  2773. = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])));
  2774. }
  2775. }
  2776. $asn1 = new File_ASN1();
  2777. $asn1->loadOIDs($this->oids);
  2778. $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge);
  2779. switch ($format) {
  2780. case FILE_X509_FORMAT_DER:
  2781. return $spkac;
  2782. // case FILE_X509_FORMAT_PEM:
  2783. default:
  2784. // OpenSSL's implementation of SPKAC requires the SPKAC be preceeded by SPKAC= and since there are pretty much
  2785. // no other SPKAC decoders phpseclib will use that same format
  2786. return 'SPKAC=' . base64_encode($spkac);
  2787. }
  2788. }
  2789. /**
  2790. * Load a Certificate Revocation List
  2791. *
  2792. * @param String $crl
  2793. * @access public
  2794. * @return Mixed
  2795. */
  2796. function loadCRL($crl)
  2797. {
  2798. if (is_array($crl) && isset($crl['tbsCertList'])) {
  2799. $this->currentCert = $crl;
  2800. unset($this->signatureSubject);
  2801. return $crl;
  2802. }
  2803. $asn1 = new File_ASN1();
  2804. $crl = $this->_extractBER($crl);
  2805. $orig = $crl;
  2806. if ($crl === false) {
  2807. $this->currentCert = false;
  2808. return false;
  2809. }
  2810. $asn1->loadOIDs($this->oids);
  2811. $decoded = $asn1->decodeBER($crl);
  2812. if (empty($decoded)) {
  2813. $this->currentCert = false;
  2814. return false;
  2815. }
  2816. $crl = $asn1->asn1map($decoded[0], $this->CertificateList);
  2817. if (!isset($crl) || $crl === false) {
  2818. $this->currentCert = false;
  2819. return false;
  2820. }
  2821. $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
  2822. $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
  2823. $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
  2824. if (is_array($rclist)) {
  2825. foreach ($rclist as $i => $extension) {
  2826. $this->_mapInExtensions($rclist, "$i/crlEntryExtensions", $asn1);
  2827. }
  2828. }
  2829. $this->currentKeyIdentifier = null;
  2830. $this->currentCert = $crl;
  2831. return $crl;
  2832. }
  2833. /**
  2834. * Save Certificate Revocation List.
  2835. *
  2836. * @param Array $crl
  2837. * @param Integer $format optional
  2838. * @access public
  2839. * @return String
  2840. */
  2841. function saveCRL($crl, $format = FILE_X509_FORMAT_PEM)
  2842. {
  2843. if (!is_array($crl) || !isset($crl['tbsCertList'])) {
  2844. return false;
  2845. }
  2846. $asn1 = new File_ASN1();
  2847. $asn1->loadOIDs($this->oids);
  2848. $filters = array();
  2849. $filters['tbsCertList']['issuer']['rdnSequence']['value']
  2850. = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
  2851. $filters['tbsCertList']['signature']['parameters']
  2852. = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
  2853. $filters['signatureAlgorithm']['parameters']
  2854. = array('type' => FILE_ASN1_TYPE_UTF8_STRING);
  2855. if (empty($crl['tbsCertList']['signature']['parameters'])) {
  2856. $filters['tbsCertList']['signature']['parameters']
  2857. = array('type' => FILE_ASN1_TYPE_NULL);
  2858. }
  2859. if (empty($crl['signatureAlgorithm']['parameters'])) {
  2860. $filters['signatureAlgorithm']['parameters']
  2861. = array('type' => FILE_ASN1_TYPE_NULL);
  2862. }
  2863. $asn1->loadFilters($filters);
  2864. $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
  2865. $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
  2866. if (is_array($rclist)) {
  2867. foreach ($rclist as $i => $extension) {
  2868. $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1);
  2869. }
  2870. }
  2871. $crl = $asn1->encodeDER($crl, $this->CertificateList);
  2872. switch ($format) {
  2873. case FILE_X509_FORMAT_DER:
  2874. return $crl;
  2875. // case FILE_X509_FORMAT_PEM:
  2876. default:
  2877. return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----';
  2878. }
  2879. }
  2880. /**
  2881. * Helper function to build a time field according to RFC 3280 section
  2882. * - 4.1.2.5 Validity
  2883. * - 5.1.2.4 This Update
  2884. * - 5.1.2.5 Next Update
  2885. * - 5.1.2.6 Revoked Certificates
  2886. * by choosing utcTime iff year of date given is before 2050 and generalTime else.
  2887. *
  2888. * @param String $date in format date('D, d M Y H:i:s O')
  2889. * @access private
  2890. * @return Array
  2891. */
  2892. function _timeField($date)
  2893. {
  2894. $year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this
  2895. if ($year < 2050) {
  2896. return array('utcTime' => $date);
  2897. } else {
  2898. return array('generalTime' => $date);
  2899. }
  2900. }
  2901. /**
  2902. * Sign an X.509 certificate
  2903. *
  2904. * $issuer's private key needs to be loaded.
  2905. * $subject can be either an existing X.509 cert (if you want to resign it),
  2906. * a CSR or something with the DN and public key explicitly set.
  2907. *
  2908. * @param File_X509 $issuer
  2909. * @param File_X509 $subject
  2910. * @param String $signatureAlgorithm optional
  2911. * @access public
  2912. * @return Mixed
  2913. */
  2914. function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
  2915. {
  2916. if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
  2917. return false;
  2918. }
  2919. if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) {
  2920. return false;
  2921. }
  2922. $currentCert = isset($this->currentCert) ? $this->currentCert : null;
  2923. $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
  2924. if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
  2925. $this->currentCert = $subject->currentCert;
  2926. $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm;
  2927. $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
  2928. if (!empty($this->startDate)) {
  2929. $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate);
  2930. }
  2931. if (!empty($this->endDate)) {
  2932. $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate);
  2933. }
  2934. if (!empty($this->serialNumber)) {
  2935. $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
  2936. }
  2937. if (!empty($subject->dn)) {
  2938. $this->currentCert['tbsCertificate']['subject'] = $subject->dn;
  2939. }
  2940. if (!empty($subject->publicKey)) {
  2941. $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
  2942. }
  2943. $this->removeExtension('id-ce-authorityKeyIdentifier');
  2944. if (isset($subject->domains)) {
  2945. $this->removeExtension('id-ce-subjectAltName');
  2946. }
  2947. } else if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) {
  2948. return false;
  2949. } else {
  2950. if (!isset($subject->publicKey)) {
  2951. return false;
  2952. }
  2953. $startDate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
  2954. $endDate = !empty($this->endDate) ? $this->endDate : @date('D, d M Y H:i:s O', strtotime('+1 year'));
  2955. $serialNumber = !empty($this->serialNumber) ? $this->serialNumber : new Math_BigInteger();
  2956. $this->currentCert = array(
  2957. 'tbsCertificate' =>
  2958. array(
  2959. 'version' => 'v3',
  2960. 'serialNumber' => $serialNumber, // $this->setserialNumber()
  2961. 'signature' => array('algorithm' => $signatureAlgorithm),
  2962. 'issuer' => false, // this is going to be overwritten later
  2963. 'validity' => array(
  2964. 'notBefore' => $this->_timeField($startDate), // $this->setStartDate()
  2965. 'notAfter' => $this->_timeField($endDate) // $this->setEndDate()
  2966. ),
  2967. 'subject' => $subject->dn,
  2968. 'subjectPublicKeyInfo' => $subjectPublicKey
  2969. ),
  2970. 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
  2971. 'signature' => false // this is going to be overwritten later
  2972. );
  2973. // Copy extensions from CSR.
  2974. $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
  2975. if (!empty($csrexts)) {
  2976. $this->currentCert['tbsCertificate']['extensions'] = $csrexts;
  2977. }
  2978. }
  2979. $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;
  2980. if (isset($issuer->currentKeyIdentifier)) {
  2981. $this->setExtension('id-ce-authorityKeyIdentifier', array(
  2982. //'authorityCertIssuer' => array(
  2983. // array(
  2984. // 'directoryName' => $issuer->dn
  2985. // )
  2986. //),
  2987. 'keyIdentifier' => $issuer->currentKeyIdentifier
  2988. )
  2989. );
  2990. //$extensions = &$this->currentCert['tbsCertificate']['extensions'];
  2991. //if (isset($issuer->serialNumber)) {
  2992. // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
  2993. //}
  2994. //unset($extensions);
  2995. }
  2996. if (isset($subject->currentKeyIdentifier)) {
  2997. $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier);
  2998. }
  2999. $altName = array();
  3000. if (isset($subject->domains) && count($subject->domains) > 1) {
  3001. $altName = array_map(array('File_X509', '_dnsName'), $subject->domains);
  3002. }
  3003. if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
  3004. // should an IP address appear as the CN if no domain name is specified? idk
  3005. //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
  3006. $ipAddresses = array();
  3007. foreach ($subject->ipAddresses as $ipAddress) {
  3008. $encoded = $subject->_ipAddress($ipAddress);
  3009. if ($encoded !== false) {
  3010. $ipAddresses[] = $encoded;
  3011. }
  3012. }
  3013. if (count($ipAddresses)) {
  3014. $altName = array_merge($altName, $ipAddresses);
  3015. }
  3016. }
  3017. if (!empty($altName)) {
  3018. $this->setExtension('id-ce-subjectAltName', $altName);
  3019. }
  3020. if ($this->caFlag) {
  3021. $keyUsage = $this->getExtension('id-ce-keyUsage');
  3022. if (!$keyUsage) {
  3023. $keyUsage = array();
  3024. }
  3025. $this->setExtension('id-ce-keyUsage',
  3026. array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign'))))
  3027. );
  3028. $basicConstraints = $this->getExtension('id-ce-basicConstraints');
  3029. if (!$basicConstraints) {
  3030. $basicConstraints = array();
  3031. }
  3032. $this->setExtension('id-ce-basicConstraints',
  3033. array_unique(array_merge(array('cA' => true), $basicConstraints)), true);
  3034. if (!isset($subject->currentKeyIdentifier)) {
  3035. $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false);
  3036. }
  3037. }
  3038. // resync $this->signatureSubject
  3039. // save $tbsCertificate in case there are any File_ASN1_Element objects in it
  3040. $tbsCertificate = $this->currentCert['tbsCertificate'];
  3041. $this->loadX509($this->saveX509($this->currentCert));
  3042. $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
  3043. $result['tbsCertificate'] = $tbsCertificate;
  3044. $this->currentCert = $currentCert;
  3045. $this->signatureSubject = $signatureSubject;
  3046. return $result;
  3047. }
  3048. /**
  3049. * Sign a CSR
  3050. *
  3051. * @access public
  3052. * @return Mixed
  3053. */
  3054. function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption')
  3055. {
  3056. if (!is_object($this->privateKey) || empty($this->dn)) {
  3057. return false;
  3058. }
  3059. $origPublicKey = $this->publicKey;
  3060. $class = get_class($this->privateKey);
  3061. $this->publicKey = new $class();
  3062. $this->publicKey->loadKey($this->privateKey->getPublicKey());
  3063. $this->publicKey->setPublicKey();
  3064. if (!($publicKey = $this->_formatSubjectPublicKey())) {
  3065. return false;
  3066. }
  3067. $this->publicKey = $origPublicKey;
  3068. $currentCert = isset($this->currentCert) ? $this->currentCert : null;
  3069. $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
  3070. if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) {
  3071. $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
  3072. if (!empty($this->dn)) {
  3073. $this->currentCert['certificationRequestInfo']['subject'] = $this->dn;
  3074. }
  3075. $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
  3076. } else {
  3077. $this->currentCert = array(
  3078. 'certificationRequestInfo' =>
  3079. array(
  3080. 'version' => 'v1',
  3081. 'subject' => $this->dn,
  3082. 'subjectPKInfo' => $publicKey
  3083. ),
  3084. 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
  3085. 'signature' => false // this is going to be overwritten later
  3086. );
  3087. }
  3088. // resync $this->signatureSubject
  3089. // save $certificationRequestInfo in case there are any File_ASN1_Element objects in it
  3090. $certificationRequestInfo = $this->currentCert['certificationRequestInfo'];
  3091. $this->loadCSR($this->saveCSR($this->currentCert));
  3092. $result = $this->_sign($this->privateKey, $signatureAlgorithm);
  3093. $result['certificationRequestInfo'] = $certificationRequestInfo;
  3094. $this->currentCert = $currentCert;
  3095. $this->signatureSubject = $signatureSubject;
  3096. return $result;
  3097. }
  3098. /**
  3099. * Sign a SPKAC
  3100. *
  3101. * @access public
  3102. * @return Mixed
  3103. */
  3104. function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption')
  3105. {
  3106. if (!is_object($this->privateKey)) {
  3107. return false;
  3108. }
  3109. $origPublicKey = $this->publicKey;
  3110. $class = get_class($this->privateKey);
  3111. $this->publicKey = new $class();
  3112. $this->publicKey->loadKey($this->privateKey->getPublicKey());
  3113. $this->publicKey->setPublicKey();
  3114. $publicKey = $this->_formatSubjectPublicKey();
  3115. if (!$publicKey) {
  3116. return false;
  3117. }
  3118. $this->publicKey = $origPublicKey;
  3119. $currentCert = isset($this->currentCert) ? $this->currentCert : null;
  3120. $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
  3121. // re-signing a SPKAC seems silly but since everything else supports re-signing why not?
  3122. if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
  3123. $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
  3124. $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
  3125. if (!empty($this->challenge)) {
  3126. // the bitwise AND ensures that the output is a valid IA5String
  3127. $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
  3128. }
  3129. } else {
  3130. $this->currentCert = array(
  3131. 'publicKeyAndChallenge' =>
  3132. array(
  3133. 'spki' => $publicKey,
  3134. // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
  3135. // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
  3136. // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way
  3137. // we could alternatively do this instead if we ignored the specs:
  3138. // crypt_random_string(8) & str_repeat("\x7F", 8)
  3139. 'challenge' => !empty($this->challenge) ? $this->challenge : ''
  3140. ),
  3141. 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
  3142. 'signature' => false // this is going to be overwritten later
  3143. );
  3144. }
  3145. // resync $this->signatureSubject
  3146. // save $publicKeyAndChallenge in case there are any File_ASN1_Element objects in it
  3147. $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
  3148. $this->loadSPKAC($this->saveSPKAC($this->currentCert));
  3149. $result = $this->_sign($this->privateKey, $signatureAlgorithm);
  3150. $result['publicKeyAndChallenge'] = $publicKeyAndChallenge;
  3151. $this->currentCert = $currentCert;
  3152. $this->signatureSubject = $signatureSubject;
  3153. return $result;
  3154. }
  3155. /**
  3156. * Sign a CRL
  3157. *
  3158. * $issuer's private key needs to be loaded.
  3159. *
  3160. * @param File_X509 $issuer
  3161. * @param File_X509 $crl
  3162. * @param String $signatureAlgorithm optional
  3163. * @access public
  3164. * @return Mixed
  3165. */
  3166. function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption')
  3167. {
  3168. if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
  3169. return false;
  3170. }
  3171. $currentCert = isset($this->currentCert) ? $this->currentCert : null;
  3172. $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
  3173. $thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
  3174. if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
  3175. $this->currentCert = $crl->currentCert;
  3176. $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
  3177. $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
  3178. } else {
  3179. $this->currentCert = array(
  3180. 'tbsCertList' =>
  3181. array(
  3182. 'version' => 'v2',
  3183. 'signature' => array('algorithm' => $signatureAlgorithm),
  3184. 'issuer' => false, // this is going to be overwritten later
  3185. 'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate()
  3186. ),
  3187. 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
  3188. 'signature' => false // this is going to be overwritten later
  3189. );
  3190. }
  3191. $tbsCertList = &$this->currentCert['tbsCertList'];
  3192. $tbsCertList['issuer'] = $issuer->dn;
  3193. $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate);
  3194. if (!empty($this->endDate)) {
  3195. $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate()
  3196. } else {
  3197. unset($tbsCertList['nextUpdate']);
  3198. }
  3199. if (!empty($this->serialNumber)) {
  3200. $crlNumber = $this->serialNumber;
  3201. } else {
  3202. $crlNumber = $this->getExtension('id-ce-cRLNumber');
  3203. $crlNumber = $crlNumber !== false ? $crlNumber->add(new Math_BigInteger(1)) : null;
  3204. }
  3205. $this->removeExtension('id-ce-authorityKeyIdentifier');
  3206. $this->removeExtension('id-ce-issuerAltName');
  3207. // Be sure version >= v2 if some extension found.
  3208. $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0;
  3209. if (!$version) {
  3210. if (!empty($tbsCertList['crlExtensions'])) {
  3211. $version = 1; // v2.
  3212. } elseif (!empty($tbsCertList['revokedCertificates'])) {
  3213. foreach ($tbsCertList['revokedCertificates'] as $cert) {
  3214. if (!empty($cert['crlEntryExtensions'])) {
  3215. $version = 1; // v2.
  3216. }
  3217. }
  3218. }
  3219. if ($version) {
  3220. $tbsCertList['version'] = $version;
  3221. }
  3222. }
  3223. // Store additional extensions.
  3224. if (!empty($tbsCertList['version'])) { // At least v2.
  3225. if (!empty($crlNumber)) {
  3226. $this->setExtension('id-ce-cRLNumber', $crlNumber);
  3227. }
  3228. if (isset($issuer->currentKeyIdentifier)) {
  3229. $this->setExtension('id-ce-authorityKeyIdentifier', array(
  3230. //'authorityCertIssuer' => array(
  3231. // array(
  3232. // 'directoryName' => $issuer->dn
  3233. // )
  3234. //),
  3235. 'keyIdentifier' => $issuer->currentKeyIdentifier
  3236. )
  3237. );
  3238. //$extensions = &$tbsCertList['crlExtensions'];
  3239. //if (isset($issuer->serialNumber)) {
  3240. // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
  3241. //}
  3242. //unset($extensions);
  3243. }
  3244. $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert);
  3245. if ($issuerAltName !== false) {
  3246. $this->setExtension('id-ce-issuerAltName', $issuerAltName);
  3247. }
  3248. }
  3249. if (empty($tbsCertList['revokedCertificates'])) {
  3250. unset($tbsCertList['revokedCertificates']);
  3251. }
  3252. unset($tbsCertList);
  3253. // resync $this->signatureSubject
  3254. // save $tbsCertList in case there are any File_ASN1_Element objects in it
  3255. $tbsCertList = $this->currentCert['tbsCertList'];
  3256. $this->loadCRL($this->saveCRL($this->currentCert));
  3257. $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
  3258. $result['tbsCertList'] = $tbsCertList;
  3259. $this->currentCert = $currentCert;
  3260. $this->signatureSubject = $signatureSubject;
  3261. return $result;
  3262. }
  3263. /**
  3264. * X.509 certificate signing helper function.
  3265. *
  3266. * @param Object $key
  3267. * @param File_X509 $subject
  3268. * @param String $signatureAlgorithm
  3269. * @access public
  3270. * @return Mixed
  3271. */
  3272. function _sign($key, $signatureAlgorithm)
  3273. {
  3274. switch (strtolower(get_class($key))) {
  3275. case 'crypt_rsa':
  3276. switch ($signatureAlgorithm) {
  3277. case 'md2WithRSAEncryption':
  3278. case 'md5WithRSAEncryption':
  3279. case 'sha1WithRSAEncryption':
  3280. case 'sha224WithRSAEncryption':
  3281. case 'sha256WithRSAEncryption':
  3282. case 'sha384WithRSAEncryption':
  3283. case 'sha512WithRSAEncryption':
  3284. $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
  3285. $key->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
  3286. $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject));
  3287. return $this->currentCert;
  3288. }
  3289. default:
  3290. return false;
  3291. }
  3292. }
  3293. /**
  3294. * Set certificate start date
  3295. *
  3296. * @param String $date
  3297. * @access public
  3298. */
  3299. function setStartDate($date)
  3300. {
  3301. $this->startDate = @date('D, d M Y H:i:s O', @strtotime($date));
  3302. }
  3303. /**
  3304. * Set certificate end date
  3305. *
  3306. * @param String $date
  3307. * @access public
  3308. */
  3309. function setEndDate($date)
  3310. {
  3311. /*
  3312. To indicate that a certificate has no well-defined expiration date,
  3313. the notAfter SHOULD be assigned the GeneralizedTime value of
  3314. 99991231235959Z.
  3315. -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
  3316. */
  3317. if (strtolower($date) == 'lifetime') {
  3318. $temp = '99991231235959Z';
  3319. $asn1 = new File_ASN1();
  3320. $temp = chr(FILE_ASN1_TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
  3321. $this->endDate = new File_ASN1_Element($temp);
  3322. } else {
  3323. $this->endDate = @date('D, d M Y H:i:s O', @strtotime($date));
  3324. }
  3325. }
  3326. /**
  3327. * Set Serial Number
  3328. *
  3329. * @param String $serial
  3330. * @param $base optional
  3331. * @access public
  3332. */
  3333. function setSerialNumber($serial, $base = -256)
  3334. {
  3335. $this->serialNumber = new Math_BigInteger($serial, $base);
  3336. }
  3337. /**
  3338. * Turns the certificate into a certificate authority
  3339. *
  3340. * @access public
  3341. */
  3342. function makeCA()
  3343. {
  3344. $this->caFlag = true;
  3345. }
  3346. /**
  3347. * Get a reference to a subarray
  3348. *
  3349. * @param array $root
  3350. * @param String $path absolute path with / as component separator
  3351. * @param Boolean $create optional
  3352. * @access private
  3353. * @return array item ref or false
  3354. */
  3355. function &_subArray(&$root, $path, $create = false)
  3356. {
  3357. $false = false;
  3358. if (!is_array($root)) {
  3359. return $false;
  3360. }
  3361. foreach (explode('/', $path) as $i) {
  3362. if (!is_array($root)) {
  3363. return $false;
  3364. }
  3365. if (!isset($root[$i])) {
  3366. if (!$create) {
  3367. return $false;
  3368. }
  3369. $root[$i] = array();
  3370. }
  3371. $root = &$root[$i];
  3372. }
  3373. return $root;
  3374. }
  3375. /**
  3376. * Get a reference to an extension subarray
  3377. *
  3378. * @param array $root
  3379. * @param String $path optional absolute path with / as component separator
  3380. * @param Boolean $create optional
  3381. * @access private
  3382. * @return array ref or false
  3383. */
  3384. function &_extensions(&$root, $path = null, $create = false)
  3385. {
  3386. if (!isset($root)) {
  3387. $root = $this->currentCert;
  3388. }
  3389. switch (true) {
  3390. case !empty($path):
  3391. case !is_array($root):
  3392. break;
  3393. case isset($root['tbsCertificate']):
  3394. $path = 'tbsCertificate/extensions';
  3395. break;
  3396. case isset($root['tbsCertList']):
  3397. $path = 'tbsCertList/crlExtensions';
  3398. break;
  3399. case isset($root['certificationRequestInfo']):
  3400. $pth = 'certificationRequestInfo/attributes';
  3401. $attributes = &$this->_subArray($root, $pth, $create);
  3402. if (is_array($attributes)) {
  3403. foreach ($attributes as $key => $value) {
  3404. if ($value['type'] == 'pkcs-9-at-extensionRequest') {
  3405. $path = "$pth/$key/value/0";
  3406. break 2;
  3407. }
  3408. }
  3409. if ($create) {
  3410. $key = count($attributes);
  3411. $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array());
  3412. $path = "$pth/$key/value/0";
  3413. }
  3414. }
  3415. break;
  3416. }
  3417. $extensions = &$this->_subArray($root, $path, $create);
  3418. if (!is_array($extensions)) {
  3419. $false = false;
  3420. return $false;
  3421. }
  3422. return $extensions;
  3423. }
  3424. /**
  3425. * Remove an Extension
  3426. *
  3427. * @param String $id
  3428. * @param String $path optional
  3429. * @access private
  3430. * @return Boolean
  3431. */
  3432. function _removeExtension($id, $path = null)
  3433. {
  3434. $extensions = &$this->_extensions($this->currentCert, $path);
  3435. if (!is_array($extensions)) {
  3436. return false;
  3437. }
  3438. $result = false;
  3439. foreach ($extensions as $key => $value) {
  3440. if ($value['extnId'] == $id) {
  3441. unset($extensions[$key]);
  3442. $result = true;
  3443. }
  3444. }
  3445. $extensions = array_values($extensions);
  3446. return $result;
  3447. }
  3448. /**
  3449. * Get an Extension
  3450. *
  3451. * Returns the extension if it exists and false if not
  3452. *
  3453. * @param String $id
  3454. * @param Array $cert optional
  3455. * @param String $path optional
  3456. * @access private
  3457. * @return Mixed
  3458. */
  3459. function _getExtension($id, $cert = null, $path = null)
  3460. {
  3461. $extensions = $this->_extensions($cert, $path);
  3462. if (!is_array($extensions)) {
  3463. return false;
  3464. }
  3465. foreach ($extensions as $key => $value) {
  3466. if ($value['extnId'] == $id) {
  3467. return $value['extnValue'];
  3468. }
  3469. }
  3470. return false;
  3471. }
  3472. /**
  3473. * Returns a list of all extensions in use
  3474. *
  3475. * @param array $cert optional
  3476. * @param String $path optional
  3477. * @access private
  3478. * @return Array
  3479. */
  3480. function _getExtensions($cert = null, $path = null)
  3481. {
  3482. $exts = $this->_extensions($cert, $path);
  3483. $extensions = array();
  3484. if (is_array($exts)) {
  3485. foreach ($exts as $extension) {
  3486. $extensions[] = $extension['extnId'];
  3487. }
  3488. }
  3489. return $extensions;
  3490. }
  3491. /**
  3492. * Set an Extension
  3493. *
  3494. * @param String $id
  3495. * @param Mixed $value
  3496. * @param Boolean $critical optional
  3497. * @param Boolean $replace optional
  3498. * @param String $path optional
  3499. * @access private
  3500. * @return Boolean
  3501. */
  3502. function _setExtension($id, $value, $critical = false, $replace = true, $path = null)
  3503. {
  3504. $extensions = &$this->_extensions($this->currentCert, $path, true);
  3505. if (!is_array($extensions)) {
  3506. return false;
  3507. }
  3508. $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value);
  3509. foreach ($extensions as $key => $value) {
  3510. if ($value['extnId'] == $id) {
  3511. if (!$replace) {
  3512. return false;
  3513. }
  3514. $extensions[$key] = $newext;
  3515. return true;
  3516. }
  3517. }
  3518. $extensions[] = $newext;
  3519. return true;
  3520. }
  3521. /**
  3522. * Remove a certificate, CSR or CRL Extension
  3523. *
  3524. * @param String $id
  3525. * @access public
  3526. * @return Boolean
  3527. */
  3528. function removeExtension($id)
  3529. {
  3530. return $this->_removeExtension($id);
  3531. }
  3532. /**
  3533. * Get a certificate, CSR or CRL Extension
  3534. *
  3535. * Returns the extension if it exists and false if not
  3536. *
  3537. * @param String $id
  3538. * @param Array $cert optional
  3539. * @access public
  3540. * @return Mixed
  3541. */
  3542. function getExtension($id, $cert = null)
  3543. {
  3544. return $this->_getExtension($id, $cert);
  3545. }
  3546. /**
  3547. * Returns a list of all extensions in use in certificate, CSR or CRL
  3548. *
  3549. * @param array $cert optional
  3550. * @access public
  3551. * @return Array
  3552. */
  3553. function getExtensions($cert = null)
  3554. {
  3555. return $this->_getExtensions($cert);
  3556. }
  3557. /**
  3558. * Set a certificate, CSR or CRL Extension
  3559. *
  3560. * @param String $id
  3561. * @param Mixed $value
  3562. * @param Boolean $critical optional
  3563. * @param Boolean $replace optional
  3564. * @access public
  3565. * @return Boolean
  3566. */
  3567. function setExtension($id, $value, $critical = false, $replace = true)
  3568. {
  3569. return $this->_setExtension($id, $value, $critical, $replace);
  3570. }
  3571. /**
  3572. * Remove a CSR attribute.
  3573. *
  3574. * @param String $id
  3575. * @param Integer $disposition optional
  3576. * @access public
  3577. * @return Boolean
  3578. */
  3579. function removeAttribute($id, $disposition = FILE_X509_ATTR_ALL)
  3580. {
  3581. $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes');
  3582. if (!is_array($attributes)) {
  3583. return false;
  3584. }
  3585. $result = false;
  3586. foreach ($attributes as $key => $attribute) {
  3587. if ($attribute['type'] == $id) {
  3588. $n = count($attribute['value']);
  3589. switch (true) {
  3590. case $disposition == FILE_X509_ATTR_APPEND:
  3591. case $disposition == FILE_X509_ATTR_REPLACE:
  3592. return false;
  3593. case $disposition >= $n:
  3594. $disposition -= $n;
  3595. break;
  3596. case $disposition == FILE_X509_ATTR_ALL:
  3597. case $n == 1:
  3598. unset($attributes[$key]);
  3599. $result = true;
  3600. break;
  3601. default:
  3602. unset($attributes[$key]['value'][$disposition]);
  3603. $attributes[$key]['value'] = array_values($attributes[$key]['value']);
  3604. $result = true;
  3605. break;
  3606. }
  3607. if ($result && $disposition != FILE_X509_ATTR_ALL) {
  3608. break;
  3609. }
  3610. }
  3611. }
  3612. $attributes = array_values($attributes);
  3613. return $result;
  3614. }
  3615. /**
  3616. * Get a CSR attribute
  3617. *
  3618. * Returns the attribute if it exists and false if not
  3619. *
  3620. * @param String $id
  3621. * @param Integer $disposition optional
  3622. * @param Array $csr optional
  3623. * @access public
  3624. * @return Mixed
  3625. */
  3626. function getAttribute($id, $disposition = FILE_X509_ATTR_ALL, $csr = null)
  3627. {
  3628. if (empty($csr)) {
  3629. $csr = $this->currentCert;
  3630. }
  3631. $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
  3632. if (!is_array($attributes)) {
  3633. return false;
  3634. }
  3635. foreach ($attributes as $key => $attribute) {
  3636. if ($attribute['type'] == $id) {
  3637. $n = count($attribute['value']);
  3638. switch (true) {
  3639. case $disposition == FILE_X509_ATTR_APPEND:
  3640. case $disposition == FILE_X509_ATTR_REPLACE:
  3641. return false;
  3642. case $disposition == FILE_X509_ATTR_ALL:
  3643. return $attribute['value'];
  3644. case $disposition >= $n:
  3645. $disposition -= $n;
  3646. break;
  3647. default:
  3648. return $attribute['value'][$disposition];
  3649. }
  3650. }
  3651. }
  3652. return false;
  3653. }
  3654. /**
  3655. * Returns a list of all CSR attributes in use
  3656. *
  3657. * @param array $csr optional
  3658. * @access public
  3659. * @return Array
  3660. */
  3661. function getAttributes($csr = null)
  3662. {
  3663. if (empty($csr)) {
  3664. $csr = $this->currentCert;
  3665. }
  3666. $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
  3667. $attrs = array();
  3668. if (is_array($attributes)) {
  3669. foreach ($attributes as $attribute) {
  3670. $attrs[] = $attribute['type'];
  3671. }
  3672. }
  3673. return $attrs;
  3674. }
  3675. /**
  3676. * Set a CSR attribute
  3677. *
  3678. * @param String $id
  3679. * @param Mixed $value
  3680. * @param Boolean $disposition optional
  3681. * @access public
  3682. * @return Boolean
  3683. */
  3684. function setAttribute($id, $value, $disposition = FILE_X509_ATTR_ALL)
  3685. {
  3686. $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true);
  3687. if (!is_array($attributes)) {
  3688. return false;
  3689. }
  3690. switch ($disposition) {
  3691. case FILE_X509_ATTR_REPLACE:
  3692. $disposition = FILE_X509_ATTR_APPEND;
  3693. case FILE_X509_ATTR_ALL:
  3694. $this->removeAttribute($id);
  3695. break;
  3696. }
  3697. foreach ($attributes as $key => $attribute) {
  3698. if ($attribute['type'] == $id) {
  3699. $n = count($attribute['value']);
  3700. switch (true) {
  3701. case $disposition == FILE_X509_ATTR_APPEND:
  3702. $last = $key;
  3703. break;
  3704. case $disposition >= $n;
  3705. $disposition -= $n;
  3706. break;
  3707. default:
  3708. $attributes[$key]['value'][$disposition] = $value;
  3709. return true;
  3710. }
  3711. }
  3712. }
  3713. switch (true) {
  3714. case $disposition >= 0:
  3715. return false;
  3716. case isset($last):
  3717. $attributes[$last]['value'][] = $value;
  3718. break;
  3719. default:
  3720. $attributes[] = array('type' => $id, 'value' => $disposition == FILE_X509_ATTR_ALL ? $value: array($value));
  3721. break;
  3722. }
  3723. return true;
  3724. }
  3725. /**
  3726. * Sets the subject key identifier
  3727. *
  3728. * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
  3729. *
  3730. * @param String $value
  3731. * @access public
  3732. */
  3733. function setKeyIdentifier($value)
  3734. {
  3735. if (empty($value)) {
  3736. unset($this->currentKeyIdentifier);
  3737. } else {
  3738. $this->currentKeyIdentifier = base64_encode($value);
  3739. }
  3740. }
  3741. /**
  3742. * Compute a public key identifier.
  3743. *
  3744. * Although key identifiers may be set to any unique value, this function
  3745. * computes key identifiers from public key according to the two
  3746. * recommended methods (4.2.1.2 RFC 3280).
  3747. * Highly polymorphic: try to accept all possible forms of key:
  3748. * - Key object
  3749. * - File_X509 object with public or private key defined
  3750. * - Certificate or CSR array
  3751. * - File_ASN1_Element object
  3752. * - PEM or DER string
  3753. *
  3754. * @param Mixed $key optional
  3755. * @param Integer $method optional
  3756. * @access public
  3757. * @return String binary key identifier
  3758. */
  3759. function computeKeyIdentifier($key = null, $method = 1)
  3760. {
  3761. if (is_null($key)) {
  3762. $key = $this;
  3763. }
  3764. switch (true) {
  3765. case is_string($key):
  3766. break;
  3767. case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
  3768. return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method);
  3769. case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
  3770. return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method);
  3771. case !is_object($key):
  3772. return false;
  3773. case strtolower(get_class($key)) == 'file_asn1_element':
  3774. // Assume the element is a bitstring-packed key.
  3775. $asn1 = new File_ASN1();
  3776. $decoded = $asn1->decodeBER($key->element);
  3777. if (empty($decoded)) {
  3778. return false;
  3779. }
  3780. $raw = $asn1->asn1map($decoded[0], array('type' => FILE_ASN1_TYPE_BIT_STRING));
  3781. if (empty($raw)) {
  3782. return false;
  3783. }
  3784. $raw = base64_decode($raw);
  3785. // If the key is private, compute identifier from its corresponding public key.
  3786. if (!class_exists('Crypt_RSA')) {
  3787. include_once 'Crypt/RSA.php';
  3788. }
  3789. $key = new Crypt_RSA();
  3790. if (!$key->loadKey($raw)) {
  3791. return false; // Not an unencrypted RSA key.
  3792. }
  3793. if ($key->getPrivateKey() !== false) { // If private.
  3794. return $this->computeKeyIdentifier($key, $method);
  3795. }
  3796. $key = $raw; // Is a public key.
  3797. break;
  3798. case strtolower(get_class($key)) == 'file_x509':
  3799. if (isset($key->publicKey)) {
  3800. return $this->computeKeyIdentifier($key->publicKey, $method);
  3801. }
  3802. if (isset($key->privateKey)) {
  3803. return $this->computeKeyIdentifier($key->privateKey, $method);
  3804. }
  3805. if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) {
  3806. return $this->computeKeyIdentifier($key->currentCert, $method);
  3807. }
  3808. return false;
  3809. default: // Should be a key object (i.e.: Crypt_RSA).
  3810. $key = $key->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
  3811. break;
  3812. }
  3813. // If in PEM format, convert to binary.
  3814. $key = $this->_extractBER($key);
  3815. // Now we have the key string: compute its sha-1 sum.
  3816. if (!class_exists('Crypt_Hash')) {
  3817. include_once 'Crypt/Hash.php';
  3818. }
  3819. $hash = new Crypt_Hash('sha1');
  3820. $hash = $hash->hash($key);
  3821. if ($method == 2) {
  3822. $hash = substr($hash, -8);
  3823. $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40);
  3824. }
  3825. return $hash;
  3826. }
  3827. /**
  3828. * Format a public key as appropriate
  3829. *
  3830. * @access private
  3831. * @return Array
  3832. */
  3833. function _formatSubjectPublicKey()
  3834. {
  3835. if (!isset($this->publicKey) || !is_object($this->publicKey)) {
  3836. return false;
  3837. }
  3838. switch (strtolower(get_class($this->publicKey))) {
  3839. case 'crypt_rsa':
  3840. // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason.
  3841. // the former is a good example of how to do fuzzing on the public key
  3842. //return new File_ASN1_Element(base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey())));
  3843. return array(
  3844. 'algorithm' => array('algorithm' => 'rsaEncryption'),
  3845. 'subjectPublicKey' => $this->publicKey->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1)
  3846. );
  3847. default:
  3848. return false;
  3849. }
  3850. }
  3851. /**
  3852. * Set the domain name's which the cert is to be valid for
  3853. *
  3854. * @access public
  3855. * @return Array
  3856. */
  3857. function setDomain()
  3858. {
  3859. $this->domains = func_get_args();
  3860. $this->removeDNProp('id-at-commonName');
  3861. $this->setDNProp('id-at-commonName', $this->domains[0]);
  3862. }
  3863. /**
  3864. * Set the IP Addresses's which the cert is to be valid for
  3865. *
  3866. * @access public
  3867. * @param String $ipAddress optional
  3868. */
  3869. function setIPAddress()
  3870. {
  3871. $this->ipAddresses = func_get_args();
  3872. /*
  3873. if (!isset($this->domains)) {
  3874. $this->removeDNProp('id-at-commonName');
  3875. $this->setDNProp('id-at-commonName', $this->ipAddresses[0]);
  3876. }
  3877. */
  3878. }
  3879. /**
  3880. * Helper function to build domain array
  3881. *
  3882. * @access private
  3883. * @param String $domain
  3884. * @return Array
  3885. */
  3886. function _dnsName($domain)
  3887. {
  3888. return array('dNSName' => $domain);
  3889. }
  3890. /**
  3891. * Helper function to build IP Address array
  3892. *
  3893. * (IPv6 is not currently supported)
  3894. *
  3895. * @access private
  3896. * @param String $address
  3897. * @return Array
  3898. */
  3899. function _iPAddress($address)
  3900. {
  3901. return array('iPAddress' => $address);
  3902. }
  3903. /**
  3904. * Get the index of a revoked certificate.
  3905. *
  3906. * @param array $rclist
  3907. * @param String $serial
  3908. * @param Boolean $create optional
  3909. * @access private
  3910. * @return Integer or false
  3911. */
  3912. function _revokedCertificate(&$rclist, $serial, $create = false)
  3913. {
  3914. $serial = new Math_BigInteger($serial);
  3915. foreach ($rclist as $i => $rc) {
  3916. if (!($serial->compare($rc['userCertificate']))) {
  3917. return $i;
  3918. }
  3919. }
  3920. if (!$create) {
  3921. return false;
  3922. }
  3923. $i = count($rclist);
  3924. $rclist[] = array('userCertificate' => $serial,
  3925. 'revocationDate' => $this->_timeField(@date('D, d M Y H:i:s O')));
  3926. return $i;
  3927. }
  3928. /**
  3929. * Revoke a certificate.
  3930. *
  3931. * @param String $serial
  3932. * @param String $date optional
  3933. * @access public
  3934. * @return Boolean
  3935. */
  3936. function revoke($serial, $date = null)
  3937. {
  3938. if (isset($this->currentCert['tbsCertList'])) {
  3939. if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
  3940. if ($this->_revokedCertificate($rclist, $serial) === false) { // If not yet revoked
  3941. if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
  3942. if (!empty($date)) {
  3943. $rclist[$i]['revocationDate'] = $this->_timeField($date);
  3944. }
  3945. return true;
  3946. }
  3947. }
  3948. }
  3949. }
  3950. return false;
  3951. }
  3952. /**
  3953. * Unrevoke a certificate.
  3954. *
  3955. * @param String $serial
  3956. * @access public
  3957. * @return Boolean
  3958. */
  3959. function unrevoke($serial)
  3960. {
  3961. if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
  3962. if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
  3963. unset($rclist[$i]);
  3964. $rclist = array_values($rclist);
  3965. return true;
  3966. }
  3967. }
  3968. return false;
  3969. }
  3970. /**
  3971. * Get a revoked certificate.
  3972. *
  3973. * @param String $serial
  3974. * @access public
  3975. * @return Mixed
  3976. */
  3977. function getRevoked($serial)
  3978. {
  3979. if (is_array($rclist = $this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
  3980. if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
  3981. return $rclist[$i];
  3982. }
  3983. }
  3984. return false;
  3985. }
  3986. /**
  3987. * List revoked certificates
  3988. *
  3989. * @param array $crl optional
  3990. * @access public
  3991. * @return array
  3992. */
  3993. function listRevoked($crl = null)
  3994. {
  3995. if (!isset($crl)) {
  3996. $crl = $this->currentCert;
  3997. }
  3998. if (!isset($crl['tbsCertList'])) {
  3999. return false;
  4000. }
  4001. $result = array();
  4002. if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
  4003. foreach ($rclist as $rc) {
  4004. $result[] = $rc['userCertificate']->toString();
  4005. }
  4006. }
  4007. return $result;
  4008. }
  4009. /**
  4010. * Remove a Revoked Certificate Extension
  4011. *
  4012. * @param String $serial
  4013. * @param String $id
  4014. * @access public
  4015. * @return Boolean
  4016. */
  4017. function removeRevokedCertificateExtension($serial, $id)
  4018. {
  4019. if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
  4020. if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
  4021. return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
  4022. }
  4023. }
  4024. return false;
  4025. }
  4026. /**
  4027. * Get a Revoked Certificate Extension
  4028. *
  4029. * Returns the extension if it exists and false if not
  4030. *
  4031. * @param String $serial
  4032. * @param String $id
  4033. * @param Array $crl optional
  4034. * @access public
  4035. * @return Mixed
  4036. */
  4037. function getRevokedCertificateExtension($serial, $id, $crl = null)
  4038. {
  4039. if (!isset($crl)) {
  4040. $crl = $this->currentCert;
  4041. }
  4042. if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
  4043. if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
  4044. return $this->_getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
  4045. }
  4046. }
  4047. return false;
  4048. }
  4049. /**
  4050. * Returns a list of all extensions in use for a given revoked certificate
  4051. *
  4052. * @param String $serial
  4053. * @param array $crl optional
  4054. * @access public
  4055. * @return Array
  4056. */
  4057. function getRevokedCertificateExtensions($serial, $crl = null)
  4058. {
  4059. if (!isset($crl)) {
  4060. $crl = $this->currentCert;
  4061. }
  4062. if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
  4063. if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
  4064. return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
  4065. }
  4066. }
  4067. return false;
  4068. }
  4069. /**
  4070. * Set a Revoked Certificate Extension
  4071. *
  4072. * @param String $serial
  4073. * @param String $id
  4074. * @param Mixed $value
  4075. * @param Boolean $critical optional
  4076. * @param Boolean $replace optional
  4077. * @access public
  4078. * @return Boolean
  4079. */
  4080. function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
  4081. {
  4082. if (isset($this->currentCert['tbsCertList'])) {
  4083. if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
  4084. if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
  4085. return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
  4086. }
  4087. }
  4088. }
  4089. return false;
  4090. }
  4091. /**
  4092. * Extract raw BER from Base64 encoding
  4093. *
  4094. * @access private
  4095. * @param String $str
  4096. * @return String
  4097. */
  4098. function _extractBER($str)
  4099. {
  4100. /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
  4101. * above and beyond the ceritificate.
  4102. * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
  4103. *
  4104. * Bag Attributes
  4105. * localKeyID: 01 00 00 00
  4106. * subject=/O=organization/OU=org unit/CN=common name
  4107. * issuer=/O=organization/CN=common name
  4108. */
  4109. $temp = preg_replace('#.*?^-+[^-]+-+#ms', '', $str, 1);
  4110. // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
  4111. $temp = preg_replace('#-+[^-]+-+#', '', $temp);
  4112. // remove new lines
  4113. $temp = str_replace(array("\r", "\n", ' '), '', $temp);
  4114. $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
  4115. return $temp != false ? $temp : $str;
  4116. }
  4117. }