X509.php 158 KB

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