X509.php 188 KB

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