X509.php 130 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719
  1. <?php
  2. /**
  3. * Pure-PHP X.509 Parser
  4. *
  5. * PHP version 5
  6. *
  7. * Encode and decode X.509 certificates.
  8. *
  9. * The extensions are from {@link http://tools.ietf.org/html/rfc5280 RFC5280} and
  10. * {@link http://web.archive.org/web/19961027104704/http://www3.netscape.com/eng/security/cert-exts.html Netscape Certificate Extensions}.
  11. *
  12. * Note that loading an X.509 certificate and resaving it may invalidate the signature. The reason being that the signature is based on a
  13. * portion of the certificate that contains optional parameters with default values. ie. if the parameter isn't there the default value is
  14. * used. Problem is, if the parameter is there and it just so happens to have the default value there are two ways that that parameter can
  15. * be encoded. It can be encoded explicitly or left out all together. This would effect the signature value and thus may invalidate the
  16. * the certificate all together unless the certificate is re-signed.
  17. *
  18. * @category File
  19. * @package X509
  20. * @author Jim Wigginton <terrafrost@php.net>
  21. * @copyright 2012 Jim Wigginton
  22. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  23. * @link http://phpseclib.sourceforge.net
  24. */
  25. namespace phpseclib\File;
  26. use ParagonIE\ConstantTime\Base64;
  27. use ParagonIE\ConstantTime\Hex;
  28. use phpseclib\Crypt\Hash;
  29. use phpseclib\Crypt\Random;
  30. use phpseclib\Crypt\RSA;
  31. use phpseclib\Exception\UnsupportedAlgorithmException;
  32. use phpseclib\File\ASN1\Element;
  33. use phpseclib\Math\BigInteger;
  34. use phpseclib\File\ASN1\Maps;
  35. /**
  36. * Pure-PHP X.509 Parser
  37. *
  38. * @package X509
  39. * @author Jim Wigginton <terrafrost@php.net>
  40. * @access public
  41. */
  42. class X509
  43. {
  44. /**
  45. * Flag to only accept signatures signed by certificate authorities
  46. *
  47. * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs
  48. *
  49. * @access public
  50. */
  51. const VALIDATE_SIGNATURE_BY_CA = 1;
  52. /**#@+
  53. * @access public
  54. * @see \phpseclib\File\X509::getDN()
  55. */
  56. /**
  57. * Return internal array representation
  58. */
  59. const DN_ARRAY = 0;
  60. /**
  61. * Return string
  62. */
  63. const DN_STRING = 1;
  64. /**
  65. * Return ASN.1 name string
  66. */
  67. const DN_ASN1 = 2;
  68. /**
  69. * Return OpenSSL compatible array
  70. */
  71. const DN_OPENSSL = 3;
  72. /**
  73. * Return canonical ASN.1 RDNs string
  74. */
  75. const DN_CANON = 4;
  76. /**
  77. * Return name hash for file indexing
  78. */
  79. const DN_HASH = 5;
  80. /**#@-*/
  81. /**#@+
  82. * @access public
  83. * @see \phpseclib\File\X509::saveX509()
  84. * @see \phpseclib\File\X509::saveCSR()
  85. * @see \phpseclib\File\X509::saveCRL()
  86. */
  87. /**
  88. * Save as PEM
  89. *
  90. * ie. a base64-encoded PEM with a header and a footer
  91. */
  92. const FORMAT_PEM = 0;
  93. /**
  94. * Save as DER
  95. */
  96. const FORMAT_DER = 1;
  97. /**
  98. * Save as a SPKAC
  99. *
  100. * Only works on CSRs. Not currently supported.
  101. */
  102. const FORMAT_SPKAC = 2;
  103. /**
  104. * Auto-detect the format
  105. *
  106. * Used only by the load*() functions
  107. */
  108. const FORMAT_AUTO_DETECT = 3;
  109. /**#@-*/
  110. /**
  111. * Attribute value disposition.
  112. * If disposition is >= 0, this is the index of the target value.
  113. */
  114. const ATTR_ALL = -1; // All attribute values (array).
  115. const ATTR_APPEND = -2; // Add a value.
  116. const ATTR_REPLACE = -3; // Clear first, then add a value.
  117. /**
  118. * Distinguished Name
  119. *
  120. * @var array
  121. * @access private
  122. */
  123. private $dn;
  124. /**
  125. * Public key
  126. *
  127. * @var string
  128. * @access private
  129. */
  130. private $publicKey;
  131. /**
  132. * Private key
  133. *
  134. * @var string
  135. * @access private
  136. */
  137. private $privateKey;
  138. /**
  139. * Object identifiers for X.509 certificates
  140. *
  141. * @var array
  142. * @access private
  143. * @link http://en.wikipedia.org/wiki/Object_identifier
  144. */
  145. private $oids;
  146. /**
  147. * The certificate authorities
  148. *
  149. * @var array
  150. * @access private
  151. */
  152. private $CAs;
  153. /**
  154. * The currently loaded certificate
  155. *
  156. * @var array
  157. * @access private
  158. */
  159. private $currentCert;
  160. /**
  161. * The signature subject
  162. *
  163. * There's no guarantee \phpseclib\File\X509 is going to re-encode an X.509 cert in the same way it was originally
  164. * encoded so we take save the portion of the original cert that the signature would have made for.
  165. *
  166. * @var string
  167. * @access private
  168. */
  169. private $signatureSubject;
  170. /**
  171. * Certificate Start Date
  172. *
  173. * @var string
  174. * @access private
  175. */
  176. private $startDate;
  177. /**
  178. * Certificate End Date
  179. *
  180. * @var string
  181. * @access private
  182. */
  183. private $endDate;
  184. /**
  185. * Serial Number
  186. *
  187. * @var string
  188. * @access private
  189. */
  190. private $serialNumber;
  191. /**
  192. * Key Identifier
  193. *
  194. * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and
  195. * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}.
  196. *
  197. * @var string
  198. * @access private
  199. */
  200. private $currentKeyIdentifier;
  201. /**
  202. * CA Flag
  203. *
  204. * @var bool
  205. * @access private
  206. */
  207. private $caFlag = false;
  208. /**
  209. * SPKAC Challenge
  210. *
  211. * @var string
  212. * @access private
  213. */
  214. private $challenge;
  215. /**
  216. * OIDs loaded
  217. *
  218. * @var bool
  219. * @access private
  220. */
  221. private static $oidsLoaded = false;
  222. /**
  223. * Default Constructor.
  224. *
  225. * @return \phpseclib\File\X509
  226. * @access public
  227. */
  228. public function __construct()
  229. {
  230. // Explicitly Tagged Module, 1988 Syntax
  231. // http://tools.ietf.org/html/rfc5280#appendix-A.1
  232. if (!self::$oidsLoaded) {
  233. // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
  234. ASN1::loadOIDs([
  235. '1.3.6.1.5.5.7' => 'id-pkix',
  236. '1.3.6.1.5.5.7.1' => 'id-pe',
  237. '1.3.6.1.5.5.7.2' => 'id-qt',
  238. '1.3.6.1.5.5.7.3' => 'id-kp',
  239. '1.3.6.1.5.5.7.48' => 'id-ad',
  240. '1.3.6.1.5.5.7.2.1' => 'id-qt-cps',
  241. '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice',
  242. '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp',
  243. '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers',
  244. '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping',
  245. '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository',
  246. '2.5.4' => 'id-at',
  247. '2.5.4.41' => 'id-at-name',
  248. '2.5.4.4' => 'id-at-surname',
  249. '2.5.4.42' => 'id-at-givenName',
  250. '2.5.4.43' => 'id-at-initials',
  251. '2.5.4.44' => 'id-at-generationQualifier',
  252. '2.5.4.3' => 'id-at-commonName',
  253. '2.5.4.7' => 'id-at-localityName',
  254. '2.5.4.8' => 'id-at-stateOrProvinceName',
  255. '2.5.4.10' => 'id-at-organizationName',
  256. '2.5.4.11' => 'id-at-organizationalUnitName',
  257. '2.5.4.12' => 'id-at-title',
  258. '2.5.4.13' => 'id-at-description',
  259. '2.5.4.46' => 'id-at-dnQualifier',
  260. '2.5.4.6' => 'id-at-countryName',
  261. '2.5.4.5' => 'id-at-serialNumber',
  262. '2.5.4.65' => 'id-at-pseudonym',
  263. '2.5.4.17' => 'id-at-postalCode',
  264. '2.5.4.9' => 'id-at-streetAddress',
  265. '2.5.4.45' => 'id-at-uniqueIdentifier',
  266. '2.5.4.72' => 'id-at-role',
  267. '2.5.4.16' => 'id-at-postalAddress',
  268. '0.9.2342.19200300.100.1.25' => 'id-domainComponent',
  269. '1.2.840.113549.1.9' => 'pkcs-9',
  270. '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress',
  271. '2.5.29' => 'id-ce',
  272. '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
  273. '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
  274. '2.5.29.15' => 'id-ce-keyUsage',
  275. '2.5.29.16' => 'id-ce-privateKeyUsagePeriod',
  276. '2.5.29.32' => 'id-ce-certificatePolicies',
  277. '2.5.29.32.0' => 'anyPolicy',
  278. '2.5.29.33' => 'id-ce-policyMappings',
  279. '2.5.29.17' => 'id-ce-subjectAltName',
  280. '2.5.29.18' => 'id-ce-issuerAltName',
  281. '2.5.29.9' => 'id-ce-subjectDirectoryAttributes',
  282. '2.5.29.19' => 'id-ce-basicConstraints',
  283. '2.5.29.30' => 'id-ce-nameConstraints',
  284. '2.5.29.36' => 'id-ce-policyConstraints',
  285. '2.5.29.31' => 'id-ce-cRLDistributionPoints',
  286. '2.5.29.37' => 'id-ce-extKeyUsage',
  287. '2.5.29.37.0' => 'anyExtendedKeyUsage',
  288. '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth',
  289. '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth',
  290. '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning',
  291. '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection',
  292. '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping',
  293. '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning',
  294. '2.5.29.54' => 'id-ce-inhibitAnyPolicy',
  295. '2.5.29.46' => 'id-ce-freshestCRL',
  296. '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess',
  297. '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess',
  298. '2.5.29.20' => 'id-ce-cRLNumber',
  299. '2.5.29.28' => 'id-ce-issuingDistributionPoint',
  300. '2.5.29.27' => 'id-ce-deltaCRLIndicator',
  301. '2.5.29.21' => 'id-ce-cRLReasons',
  302. '2.5.29.29' => 'id-ce-certificateIssuer',
  303. '2.5.29.23' => 'id-ce-holdInstructionCode',
  304. '1.2.840.10040.2' => 'holdInstruction',
  305. '1.2.840.10040.2.1' => 'id-holdinstruction-none',
  306. '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer',
  307. '1.2.840.10040.2.3' => 'id-holdinstruction-reject',
  308. '2.5.29.24' => 'id-ce-invalidityDate',
  309. '1.2.840.113549.2.2' => 'md2',
  310. '1.2.840.113549.2.5' => 'md5',
  311. '1.3.14.3.2.26' => 'id-sha1',
  312. '1.2.840.10040.4.1' => 'id-dsa',
  313. '1.2.840.10040.4.3' => 'id-dsa-with-sha1',
  314. '1.2.840.113549.1.1' => 'pkcs-1',
  315. '1.2.840.113549.1.1.1' => 'rsaEncryption',
  316. '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
  317. '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption',
  318. '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption',
  319. '1.2.840.10046.2.1' => 'dhpublicnumber',
  320. '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm',
  321. '1.2.840.10045' => 'ansi-X9-62',
  322. '1.2.840.10045.4' => 'id-ecSigType',
  323. '1.2.840.10045.4.1' => 'ecdsa-with-SHA1',
  324. '1.2.840.10045.1' => 'id-fieldType',
  325. '1.2.840.10045.1.1' => 'prime-field',
  326. '1.2.840.10045.1.2' => 'characteristic-two-field',
  327. '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis',
  328. '1.2.840.10045.1.2.3.1' => 'gnBasis',
  329. '1.2.840.10045.1.2.3.2' => 'tpBasis',
  330. '1.2.840.10045.1.2.3.3' => 'ppBasis',
  331. '1.2.840.10045.2' => 'id-publicKeyType',
  332. '1.2.840.10045.2.1' => 'id-ecPublicKey',
  333. '1.2.840.10045.3' => 'ellipticCurve',
  334. '1.2.840.10045.3.0' => 'c-TwoCurve',
  335. '1.2.840.10045.3.0.1' => 'c2pnb163v1',
  336. '1.2.840.10045.3.0.2' => 'c2pnb163v2',
  337. '1.2.840.10045.3.0.3' => 'c2pnb163v3',
  338. '1.2.840.10045.3.0.4' => 'c2pnb176w1',
  339. '1.2.840.10045.3.0.5' => 'c2pnb191v1',
  340. '1.2.840.10045.3.0.6' => 'c2pnb191v2',
  341. '1.2.840.10045.3.0.7' => 'c2pnb191v3',
  342. '1.2.840.10045.3.0.8' => 'c2pnb191v4',
  343. '1.2.840.10045.3.0.9' => 'c2pnb191v5',
  344. '1.2.840.10045.3.0.10' => 'c2pnb208w1',
  345. '1.2.840.10045.3.0.11' => 'c2pnb239v1',
  346. '1.2.840.10045.3.0.12' => 'c2pnb239v2',
  347. '1.2.840.10045.3.0.13' => 'c2pnb239v3',
  348. '1.2.840.10045.3.0.14' => 'c2pnb239v4',
  349. '1.2.840.10045.3.0.15' => 'c2pnb239v5',
  350. '1.2.840.10045.3.0.16' => 'c2pnb272w1',
  351. '1.2.840.10045.3.0.17' => 'c2pnb304w1',
  352. '1.2.840.10045.3.0.18' => 'c2pnb359v1',
  353. '1.2.840.10045.3.0.19' => 'c2pnb368w1',
  354. '1.2.840.10045.3.0.20' => 'c2pnb431r1',
  355. '1.2.840.10045.3.1' => 'primeCurve',
  356. '1.2.840.10045.3.1.1' => 'prime192v1',
  357. '1.2.840.10045.3.1.2' => 'prime192v2',
  358. '1.2.840.10045.3.1.3' => 'prime192v3',
  359. '1.2.840.10045.3.1.4' => 'prime239v1',
  360. '1.2.840.10045.3.1.5' => 'prime239v2',
  361. '1.2.840.10045.3.1.6' => 'prime239v3',
  362. '1.2.840.10045.3.1.7' => 'prime256v1',
  363. '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP',
  364. '1.2.840.113549.1.1.9' => 'id-pSpecified',
  365. '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS',
  366. '1.2.840.113549.1.1.8' => 'id-mgf1',
  367. '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
  368. '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption',
  369. '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
  370. '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption',
  371. '2.16.840.1.101.3.4.2.4' => 'id-sha224',
  372. '2.16.840.1.101.3.4.2.1' => 'id-sha256',
  373. '2.16.840.1.101.3.4.2.2' => 'id-sha384',
  374. '2.16.840.1.101.3.4.2.3' => 'id-sha512',
  375. '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94',
  376. '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001',
  377. '1.2.643.2.2.20' => 'id-GostR3410-2001',
  378. '1.2.643.2.2.19' => 'id-GostR3410-94',
  379. // Netscape Object Identifiers from "Netscape Certificate Extensions"
  380. '2.16.840.1.113730' => 'netscape',
  381. '2.16.840.1.113730.1' => 'netscape-cert-extension',
  382. '2.16.840.1.113730.1.1' => 'netscape-cert-type',
  383. '2.16.840.1.113730.1.13' => 'netscape-comment',
  384. '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
  385. // the following are X.509 extensions not supported by phpseclib
  386. '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype',
  387. '1.2.840.113533.7.65.0' => 'entrustVersInfo',
  388. '2.16.840.1.113733.1.6.9' => 'verisignPrivate',
  389. // for Certificate Signing Requests
  390. // see http://tools.ietf.org/html/rfc2985
  391. '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', // PKCS #9 unstructured name
  392. '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', // Challenge password for certificate revocations
  393. '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' // Certificate extension request
  394. ]);
  395. }
  396. }
  397. /**
  398. * Load X.509 certificate
  399. *
  400. * Returns an associative array describing the X.509 cert or a false if the cert failed to load
  401. *
  402. * @param string $cert
  403. * @param int $mode
  404. * @access public
  405. * @return mixed
  406. */
  407. public function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT)
  408. {
  409. if (is_array($cert) && isset($cert['tbsCertificate'])) {
  410. unset($this->currentCert);
  411. unset($this->currentKeyIdentifier);
  412. $this->dn = $cert['tbsCertificate']['subject'];
  413. if (!isset($this->dn)) {
  414. return false;
  415. }
  416. $this->currentCert = $cert;
  417. $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
  418. $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
  419. unset($this->signatureSubject);
  420. return $cert;
  421. }
  422. if ($mode != self::FORMAT_DER) {
  423. $newcert = ASN1::extractBER($cert);
  424. if ($mode == self::FORMAT_PEM && $cert == $newcert) {
  425. return false;
  426. }
  427. $cert = $newcert;
  428. }
  429. if ($cert === false) {
  430. $this->currentCert = false;
  431. return false;
  432. }
  433. $decoded = ASN1::decodeBER($cert);
  434. if (!empty($decoded)) {
  435. $x509 = ASN1::asn1map($decoded[0], Maps\Certificate::MAP);
  436. }
  437. if (!isset($x509) || $x509 === false) {
  438. $this->currentCert = false;
  439. return false;
  440. }
  441. $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
  442. if ($this->isSubArrayValid($x509, 'tbsCertificate/extensions')) {
  443. $this->mapInExtensions($x509, 'tbsCertificate/extensions');
  444. }
  445. $this->mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence');
  446. $this->mapInDNs($x509, 'tbsCertificate/subject/rdnSequence');
  447. $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'];
  448. $key = $this->reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key);
  449. $this->currentCert = $x509;
  450. $this->dn = $x509['tbsCertificate']['subject'];
  451. $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
  452. $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;
  453. return $x509;
  454. }
  455. /**
  456. * Save X.509 certificate
  457. *
  458. * @param array $cert
  459. * @param int $format optional
  460. * @access public
  461. * @return string
  462. */
  463. public function saveX509($cert, $format = self::FORMAT_PEM)
  464. {
  465. if (!is_array($cert) || !isset($cert['tbsCertificate'])) {
  466. return false;
  467. }
  468. switch (true) {
  469. // "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()"
  470. case !($algorithm = $this->subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')):
  471. case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
  472. break;
  473. default:
  474. switch ($algorithm) {
  475. case 'rsaEncryption':
  476. $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']
  477. = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])));
  478. /* "[For RSA keys] the parameters field MUST have ASN.1 type NULL for this algorithm identifier."
  479. -- https://tools.ietf.org/html/rfc3279#section-2.3.1
  480. given that and the fact that RSA keys appear ot be the only key type for which the parameters field can be blank,
  481. it seems like perhaps the ASN.1 description ought not say the parameters field is OPTIONAL, but whatever.
  482. */
  483. $cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = null;
  484. // https://tools.ietf.org/html/rfc3279#section-2.2.1
  485. $cert['signatureAlgorithm']['parameters'] = null;
  486. $cert['tbsCertificate']['signature']['parameters'] = null;
  487. }
  488. }
  489. $filters = [];
  490. $type_utf8_string = ['type' => ASN1::TYPE_UTF8_STRING];
  491. $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string;
  492. $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string;
  493. $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string;
  494. $filters['tbsCertificate']['subject']['rdnSequence']['value'] = $type_utf8_string;
  495. $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = $type_utf8_string;
  496. $filters['signatureAlgorithm']['parameters'] = $type_utf8_string;
  497. $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
  498. //$filters['policyQualifiers']['qualifier'] = $type_utf8_string;
  499. $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
  500. $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string;
  501. /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib\File\ASN1::TYPE_IA5_STRING.
  502. \phpseclib\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
  503. characters.
  504. */
  505. $filters['policyQualifiers']['qualifier']
  506. = ['type' => ASN1::TYPE_IA5_STRING];
  507. ASN1::setFilters($filters);
  508. $this->mapOutExtensions($cert, 'tbsCertificate/extensions');
  509. $this->mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence');
  510. $this->mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence');
  511. $cert = ASN1::encodeDER($cert, Maps\Certificate::MAP);
  512. switch ($format) {
  513. case self::FORMAT_DER:
  514. return $cert;
  515. // case self::FORMAT_PEM:
  516. default:
  517. return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(Base64::encode($cert), 64) . '-----END CERTIFICATE-----';
  518. }
  519. }
  520. /**
  521. * Map extension values from octet string to extension-specific internal
  522. * format.
  523. *
  524. * @param array ref $root
  525. * @param string $path
  526. * @access private
  527. */
  528. private function mapInExtensions(&$root, $path)
  529. {
  530. $extensions = &$this->subArrayUnchecked($root, $path);
  531. if ($extensions) {
  532. for ($i = 0; $i < count($extensions); $i++) {
  533. $id = $extensions[$i]['extnId'];
  534. $value = &$extensions[$i]['extnValue'];
  535. $decoded = ASN1::decodeBER($value);
  536. /* [extnValue] contains the DER encoding of an ASN.1 value
  537. corresponding to the extension type identified by extnID */
  538. $map = $this->getMapping($id);
  539. if (!is_bool($map)) {
  540. $mapped = ASN1::asn1map($decoded[0], $map, ['iPAddress' => [$this, 'decodeIP']]);
  541. $value = $mapped === false ? $decoded[0] : $mapped;
  542. if ($id == 'id-ce-certificatePolicies') {
  543. for ($j = 0; $j < count($value); $j++) {
  544. if (!isset($value[$j]['policyQualifiers'])) {
  545. continue;
  546. }
  547. for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
  548. $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
  549. $map = $this->getMapping($subid);
  550. $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
  551. if ($map !== false) {
  552. $decoded = ASN1::decodeBER($subvalue);
  553. $mapped = ASN1::asn1map($decoded[0], $map);
  554. $subvalue = $mapped === false ? $decoded[0] : $mapped;
  555. }
  556. }
  557. }
  558. }
  559. }
  560. }
  561. }
  562. }
  563. /**
  564. * Map extension values from extension-specific internal format to
  565. * octet string.
  566. *
  567. * @param array ref $root
  568. * @param string $path
  569. * @access private
  570. */
  571. private function mapOutExtensions(&$root, $path)
  572. {
  573. $extensions = &$this->subArray($root, $path);
  574. if (is_array($extensions)) {
  575. $size = count($extensions);
  576. for ($i = 0; $i < $size; $i++) {
  577. if ($extensions[$i] instanceof Element) {
  578. continue;
  579. }
  580. $id = $extensions[$i]['extnId'];
  581. $value = &$extensions[$i]['extnValue'];
  582. switch ($id) {
  583. case 'id-ce-certificatePolicies':
  584. for ($j = 0; $j < count($value); $j++) {
  585. if (!isset($value[$j]['policyQualifiers'])) {
  586. continue;
  587. }
  588. for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
  589. $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
  590. $map = $this->getMapping($subid);
  591. $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
  592. if ($map !== false) {
  593. // by default \phpseclib\File\ASN1 will try to render qualifier as a \phpseclib\File\ASN1::TYPE_IA5_STRING since it's
  594. // actual type is \phpseclib\File\ASN1::TYPE_ANY
  595. $subvalue = new Element(ASN1::encodeDER($subvalue, $map));
  596. }
  597. }
  598. }
  599. break;
  600. case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string
  601. if (isset($value['authorityCertSerialNumber'])) {
  602. if ($value['authorityCertSerialNumber']->toBytes() == '') {
  603. $temp = chr((ASN1::CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0";
  604. $value['authorityCertSerialNumber'] = new Element($temp);
  605. }
  606. }
  607. }
  608. /* [extnValue] contains the DER encoding of an ASN.1 value
  609. corresponding to the extension type identified by extnID */
  610. $map = $this->getMapping($id);
  611. if (is_bool($map)) {
  612. if (!$map) {
  613. //user_error($id . ' is not a currently supported extension');
  614. unset($extensions[$i]);
  615. }
  616. } else {
  617. $value = ASN1::encodeDER($value, $map, ['iPAddress' => [$this, 'encodeIP']]);
  618. }
  619. }
  620. }
  621. }
  622. /**
  623. * Map attribute values from ANY type to attribute-specific internal
  624. * format.
  625. *
  626. * @param array ref $root
  627. * @param string $path
  628. * @access private
  629. */
  630. private function mapInAttributes(&$root, $path)
  631. {
  632. $attributes = &$this->subArray($root, $path);
  633. if (is_array($attributes)) {
  634. for ($i = 0; $i < count($attributes); $i++) {
  635. $id = $attributes[$i]['type'];
  636. /* $value contains the DER encoding of an ASN.1 value
  637. corresponding to the attribute type identified by type */
  638. $map = $this->getMapping($id);
  639. if (is_array($attributes[$i]['value'])) {
  640. $values = &$attributes[$i]['value'];
  641. for ($j = 0; $j < count($values); $j++) {
  642. $value = ASN1::encodeDER($values[$j], Maps\AttributeValue::MAP);
  643. $decoded = ASN1::decodeBER($value);
  644. if (!is_bool($map)) {
  645. $mapped = ASN1::asn1map($decoded[0], $map);
  646. if ($mapped !== false) {
  647. $values[$j] = $mapped;
  648. }
  649. if ($id == 'pkcs-9-at-extensionRequest' && $this->isSubArrayValid($values, $j)) {
  650. $this->mapInExtensions($values, $j);
  651. }
  652. } elseif ($map) {
  653. $values[$j] = $value;
  654. }
  655. }
  656. }
  657. }
  658. }
  659. }
  660. /**
  661. * Map attribute values from attribute-specific internal format to
  662. * ANY type.
  663. *
  664. * @param array ref $root
  665. * @param string $path
  666. * @access private
  667. */
  668. private function mapOutAttributes(&$root, $path)
  669. {
  670. $attributes = &$this->subArray($root, $path);
  671. if (is_array($attributes)) {
  672. $size = count($attributes);
  673. for ($i = 0; $i < $size; $i++) {
  674. /* [value] contains the DER encoding of an ASN.1 value
  675. corresponding to the attribute type identified by type */
  676. $id = $attributes[$i]['type'];
  677. $map = $this->getMapping($id);
  678. if ($map === false) {
  679. //user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
  680. unset($attributes[$i]);
  681. } elseif (is_array($attributes[$i]['value'])) {
  682. $values = &$attributes[$i]['value'];
  683. for ($j = 0; $j < count($values); $j++) {
  684. switch ($id) {
  685. case 'pkcs-9-at-extensionRequest':
  686. $this->mapOutExtensions($values, $j);
  687. break;
  688. }
  689. if (!is_bool($map)) {
  690. $temp = ASN1::encodeDER($values[$j], $map);
  691. $decoded = ASN1::decodeBER($temp);
  692. $values[$j] = ASN1::asn1map($decoded[0], Maps\AttributeValue::MAP);
  693. }
  694. }
  695. }
  696. }
  697. }
  698. }
  699. /**
  700. * Map DN values from ANY type to DN-specific internal
  701. * format.
  702. *
  703. * @param array ref $root
  704. * @param string $path
  705. * @access private
  706. */
  707. private function mapInDNs(&$root, $path)
  708. {
  709. $dns = &$this->subArray($root, $path);
  710. if (is_array($dns)) {
  711. for ($i = 0; $i < count($dns); $i++) {
  712. for ($j = 0; $j < count($dns[$i]); $j++) {
  713. $type = $dns[$i][$j]['type'];
  714. $value = &$dns[$i][$j]['value'];
  715. if (is_object($value) && $value instanceof Element) {
  716. $map = $this->getMapping($type);
  717. if (!is_bool($map)) {
  718. $decoded = ASN1::decodeBER($value);
  719. $value = ASN1::asn1map($decoded[0], $map);
  720. }
  721. }
  722. }
  723. }
  724. }
  725. }
  726. /**
  727. * Map DN values from DN-specific internal format to
  728. * ANY type.
  729. *
  730. * @param array ref $root
  731. * @param string $path
  732. * @access private
  733. */
  734. private function mapOutDNs(&$root, $path)
  735. {
  736. $dns = &$this->subArray($root, $path);
  737. if (is_array($dns)) {
  738. $size = count($dns);
  739. for ($i = 0; $i < $size; $i++) {
  740. for ($j = 0; $j < count($dns[$i]); $j++) {
  741. $type = $dns[$i][$j]['type'];
  742. $value = &$dns[$i][$j]['value'];
  743. if (is_object($value) && $value instanceof Element) {
  744. continue;
  745. }
  746. $map = $this->getMapping($type);
  747. if (!is_bool($map)) {
  748. $value = new Element(ASN1::encodeDER($value, $map));
  749. }
  750. }
  751. }
  752. }
  753. }
  754. /**
  755. * Associate an extension ID to an extension mapping
  756. *
  757. * @param string $extnId
  758. * @access private
  759. * @return mixed
  760. */
  761. private function getMapping($extnId)
  762. {
  763. if (!is_string($extnId)) { // eg. if it's a \phpseclib\File\ASN1\Element object
  764. return true;
  765. }
  766. switch ($extnId) {
  767. case 'id-ce-keyUsage':
  768. return Maps\KeyUsage::MAP;
  769. case 'id-ce-basicConstraints':
  770. return Maps\BasicConstraints::MAP;
  771. case 'id-ce-subjectKeyIdentifier':
  772. return Maps\KeyIdentifier::MAP;
  773. case 'id-ce-cRLDistributionPoints':
  774. return Maps\CRLDistributionPoints::MAP;
  775. case 'id-ce-authorityKeyIdentifier':
  776. return Maps\AuthorityKeyIdentifier::MAP;
  777. case 'id-ce-certificatePolicies':
  778. return Maps\CertificatePolicies::MAP;
  779. case 'id-ce-extKeyUsage':
  780. return Maps\ExtKeyUsageSyntax::MAP;
  781. case 'id-pe-authorityInfoAccess':
  782. return Maps\AuthorityInfoAccessSyntax::MAP;
  783. case 'id-ce-subjectAltName':
  784. return Maps\SubjectAltName::MAP;
  785. case 'id-ce-subjectDirectoryAttributes':
  786. return Maps\SubjectDirectoryAttributes::MAP;
  787. case 'id-ce-privateKeyUsagePeriod':
  788. return Maps\PrivateKeyUsagePeriod::MAP;
  789. case 'id-ce-issuerAltName':
  790. return Maps\IssuerAltName::MAP;
  791. case 'id-ce-policyMappings':
  792. return Maps\PolicyMappings::MAP;
  793. case 'id-ce-nameConstraints':
  794. return Maps\NameConstraints::MAP;
  795. case 'netscape-cert-type':
  796. return Maps\netscape_cert_type::MAP;
  797. case 'netscape-comment':
  798. return Maps\netscape_comment::MAP;
  799. case 'netscape-ca-policy-url':
  800. return Maps\netscape_ca_policy_url::MAP;
  801. // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
  802. // back around to asn1map() and we don't want it decoded again.
  803. //case 'id-qt-cps':
  804. // return Maps\CPSuri::MAP;
  805. case 'id-qt-unotice':
  806. return Maps\UserNotice::MAP;
  807. // the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
  808. case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
  809. case 'entrustVersInfo':
  810. // http://support.microsoft.com/kb/287547
  811. case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION
  812. case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION
  813. // "SET Secure Electronic Transaction Specification"
  814. // http://www.maithean.com/docs/set_bk3.pdf
  815. case '2.23.42.7.0': // id-set-hashedRootKey
  816. // "Certificate Transparency"
  817. // https://tools.ietf.org/html/rfc6962
  818. case '1.3.6.1.4.1.11129.2.4.2':
  819. return true;
  820. // CSR attributes
  821. case 'pkcs-9-at-unstructuredName':
  822. return Maps\PKCS9String::MAP;
  823. case 'pkcs-9-at-challengePassword':
  824. return Maps\DirectoryString::MAP;
  825. case 'pkcs-9-at-extensionRequest':
  826. return Maps\Extensions::MAP;
  827. // CRL extensions.
  828. case 'id-ce-cRLNumber':
  829. return Maps\CRLNumber::MAP;
  830. case 'id-ce-deltaCRLIndicator':
  831. return Maps\CRLNumber::MAP;
  832. case 'id-ce-issuingDistributionPoint':
  833. return Maps\IssuingDistributionPoint::MAP;
  834. case 'id-ce-freshestCRL':
  835. return Maps\CRLDistributionPoints::MAP;
  836. case 'id-ce-cRLReasons':
  837. return Maps\CRLReason::MAP;
  838. case 'id-ce-invalidityDate':
  839. return Maps\InvalidityDate::MAP;
  840. case 'id-ce-certificateIssuer':
  841. return Maps\CertificateIssuer::MAP;
  842. case 'id-ce-holdInstructionCode':
  843. return Maps\HoldInstructionCode::MAP;
  844. case 'id-at-postalAddress':
  845. return Maps\PostalAddress::MAP;
  846. }
  847. return false;
  848. }
  849. /**
  850. * Load an X.509 certificate as a certificate authority
  851. *
  852. * @param string $cert
  853. * @access public
  854. * @return bool
  855. */
  856. public function loadCA($cert)
  857. {
  858. $olddn = $this->dn;
  859. $oldcert = $this->currentCert;
  860. $oldsigsubj = $this->signatureSubject;
  861. $oldkeyid = $this->currentKeyIdentifier;
  862. $cert = $this->loadX509($cert);
  863. if (!$cert) {
  864. $this->dn = $olddn;
  865. $this->currentCert = $oldcert;
  866. $this->signatureSubject = $oldsigsubj;
  867. $this->currentKeyIdentifier = $oldkeyid;
  868. return false;
  869. }
  870. /* From RFC5280 "PKIX Certificate and CRL Profile":
  871. If the keyUsage extension is present, then the subject public key
  872. MUST NOT be used to verify signatures on certificates or CRLs unless
  873. the corresponding keyCertSign or cRLSign bit is set. */
  874. //$keyUsage = $this->getExtension('id-ce-keyUsage');
  875. //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
  876. // return false;
  877. //}
  878. /* From RFC5280 "PKIX Certificate and CRL Profile":
  879. The cA boolean indicates whether the certified public key may be used
  880. to verify certificate signatures. If the cA boolean is not asserted,
  881. then the keyCertSign bit in the key usage extension MUST NOT be
  882. asserted. If the basic constraints extension is not present in a
  883. version 3 certificate, or the extension is present but the cA boolean
  884. is not asserted, then the certified public key MUST NOT be used to
  885. verify certificate signatures. */
  886. //$basicConstraints = $this->getExtension('id-ce-basicConstraints');
  887. //if (!$basicConstraints || !$basicConstraints['cA']) {
  888. // return false;
  889. //}
  890. $this->CAs[] = $cert;
  891. $this->dn = $olddn;
  892. $this->currentCert = $oldcert;
  893. $this->signatureSubject = $oldsigsubj;
  894. return true;
  895. }
  896. /**
  897. * Validate an X.509 certificate against a URL
  898. *
  899. * From RFC2818 "HTTP over TLS":
  900. *
  901. * Matching is performed using the matching rules specified by
  902. * [RFC2459]. If more than one identity of a given type is present in
  903. * the certificate (e.g., more than one dNSName name, a match in any one
  904. * of the set is considered acceptable.) Names may contain the wildcard
  905. * character * which is considered to match any single domain name
  906. * component or component fragment. E.g., *.a.com matches foo.a.com but
  907. * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
  908. *
  909. * @param string $url
  910. * @access public
  911. * @return bool
  912. */
  913. public function validateURL($url)
  914. {
  915. if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
  916. return false;
  917. }
  918. $components = parse_url($url);
  919. if (!isset($components['host'])) {
  920. return false;
  921. }
  922. if ($names = $this->getExtension('id-ce-subjectAltName')) {
  923. foreach ($names as $key => $value) {
  924. $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value);
  925. switch ($key) {
  926. case 'dNSName':
  927. /* From RFC2818 "HTTP over TLS":
  928. If a subjectAltName extension of type dNSName is present, that MUST
  929. be used as the identity. Otherwise, the (most specific) Common Name
  930. field in the Subject field of the certificate MUST be used. Although
  931. the use of the Common Name is existing practice, it is deprecated and
  932. Certification Authorities are encouraged to use the dNSName instead. */
  933. if (preg_match('#^' . $value . '$#', $components['host'])) {
  934. return true;
  935. }
  936. break;
  937. case 'iPAddress':
  938. /* From RFC2818 "HTTP over TLS":
  939. In some cases, the URI is specified as an IP address rather than a
  940. hostname. In this case, the iPAddress subjectAltName must be present
  941. in the certificate and must exactly match the IP in the URI. */
  942. if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
  943. return true;
  944. }
  945. }
  946. }
  947. return false;
  948. }
  949. if ($value = $this->getDNProp('id-at-commonName')) {
  950. $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value[0]);
  951. return preg_match('#^' . $value . '$#', $components['host']);
  952. }
  953. return false;
  954. }
  955. /**
  956. * Validate a date
  957. *
  958. * If $date isn't defined it is assumed to be the current date.
  959. *
  960. * @param int $date optional
  961. * @access public
  962. */
  963. public function validateDate($date = null)
  964. {
  965. if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
  966. return false;
  967. }
  968. if (!isset($date)) {
  969. $date = time();
  970. }
  971. $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
  972. $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime'];
  973. $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
  974. $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
  975. switch (true) {
  976. case $date < @strtotime($notBefore):
  977. case $date > @strtotime($notAfter):
  978. return false;
  979. }
  980. return true;
  981. }
  982. /**
  983. * Validate a signature
  984. *
  985. * Works on X.509 certs, CSR's and CRL's.
  986. * Returns true if the signature is verified, false if it is not correct or null on error
  987. *
  988. * By default returns false for self-signed certs. Call validateSignature(false) to make this support
  989. * self-signed.
  990. *
  991. * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
  992. *
  993. * @param bool $caonly optional
  994. * @access public
  995. * @return mixed
  996. */
  997. public function validateSignature($caonly = true)
  998. {
  999. if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
  1000. return null;
  1001. }
  1002. /* TODO:
  1003. "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")."
  1004. -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6
  1005. implement pathLenConstraint in the id-ce-basicConstraints extension */
  1006. switch (true) {
  1007. case isset($this->currentCert['tbsCertificate']):
  1008. // self-signed cert
  1009. switch (true) {
  1010. case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']:
  1011. case defined('FILE_X509_IGNORE_TYPE') && $this->getIssuerDN(self::DN_STRING) === $this->getDN(self::DN_STRING):
  1012. $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
  1013. $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier');
  1014. switch (true) {
  1015. case !is_array($authorityKey):
  1016. case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
  1017. $signingCert = $this->currentCert; // working cert
  1018. }
  1019. }
  1020. if (!empty($this->CAs)) {
  1021. for ($i = 0; $i < count($this->CAs); $i++) {
  1022. // even if the cert is a self-signed one we still want to see if it's a CA;
  1023. // if not, we'll conditionally return an error
  1024. $ca = $this->CAs[$i];
  1025. switch (true) {
  1026. case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']:
  1027. case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertificate']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
  1028. $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
  1029. $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
  1030. switch (true) {
  1031. case !is_array($authorityKey):
  1032. case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
  1033. $signingCert = $ca; // working cert
  1034. break 3;
  1035. }
  1036. }
  1037. }
  1038. if (count($this->CAs) == $i && $caonly) {
  1039. return false;
  1040. }
  1041. } elseif (!isset($signingCert) || $caonly) {
  1042. return false;
  1043. }
  1044. return $this->validateSignatureHelper(
  1045. $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
  1046. $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
  1047. $this->currentCert['signatureAlgorithm']['algorithm'],
  1048. substr($this->currentCert['signature'], 1),
  1049. $this->signatureSubject
  1050. );
  1051. case isset($this->currentCert['certificationRequestInfo']):
  1052. return $this->validateSignatureHelper(
  1053. $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
  1054. $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
  1055. $this->currentCert['signatureAlgorithm']['algorithm'],
  1056. substr($this->currentCert['signature'], 1),
  1057. $this->signatureSubject
  1058. );
  1059. case isset($this->currentCert['publicKeyAndChallenge']):
  1060. return $this->validateSignatureHelper(
  1061. $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'],
  1062. $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'],
  1063. $this->currentCert['signatureAlgorithm']['algorithm'],
  1064. substr($this->currentCert['signature'], 1),
  1065. $this->signatureSubject
  1066. );
  1067. case isset($this->currentCert['tbsCertList']):
  1068. if (!empty($this->CAs)) {
  1069. for ($i = 0; $i < count($this->CAs); $i++) {
  1070. $ca = $this->CAs[$i];
  1071. switch (true) {
  1072. case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']:
  1073. case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertList']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
  1074. $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
  1075. $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
  1076. switch (true) {
  1077. case !is_array($authorityKey):
  1078. case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
  1079. $signingCert = $ca; // working cert
  1080. break 3;
  1081. }
  1082. }
  1083. }
  1084. }
  1085. if (!isset($signingCert)) {
  1086. return false;
  1087. }
  1088. return $this->validateSignatureHelper(
  1089. $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
  1090. $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
  1091. $this->currentCert['signatureAlgorithm']['algorithm'],
  1092. substr($this->currentCert['signature'], 1),
  1093. $this->signatureSubject
  1094. );
  1095. default:
  1096. return false;
  1097. }
  1098. }
  1099. /**
  1100. * Validates a signature
  1101. *
  1102. * Returns true if the signature is verified and false if it is not correct.
  1103. * If the algorithms are unsupposed an exception is thrown.
  1104. *
  1105. * @param string $publicKeyAlgorithm
  1106. * @param string $publicKey
  1107. * @param string $signatureAlgorithm
  1108. * @param string $signature
  1109. * @param string $signatureSubject
  1110. * @access private
  1111. * @throws \phpseclib\Exception\UnsupportedAlgorithmException if the algorithm is unsupported
  1112. * @return bool
  1113. */
  1114. private function validateSignatureHelper($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
  1115. {
  1116. switch ($publicKeyAlgorithm) {
  1117. case 'rsaEncryption':
  1118. $rsa = new RSA();
  1119. $rsa->load($publicKey);
  1120. switch ($signatureAlgorithm) {
  1121. case 'md2WithRSAEncryption':
  1122. case 'md5WithRSAEncryption':
  1123. case 'sha1WithRSAEncryption':
  1124. case 'sha224WithRSAEncryption':
  1125. case 'sha256WithRSAEncryption':
  1126. case 'sha384WithRSAEncryption':
  1127. case 'sha512WithRSAEncryption':
  1128. $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
  1129. if (!@$rsa->verify($signatureSubject, $signature, RSA::PADDING_PKCS1)) {
  1130. return false;
  1131. }
  1132. break;
  1133. default:
  1134. throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
  1135. }
  1136. break;
  1137. default:
  1138. throw new UnsupportedAlgorithmException('Public key algorithm unsupported');
  1139. }
  1140. return true;
  1141. }
  1142. /**
  1143. * Reformat public keys
  1144. *
  1145. * Reformats a public key to a format supported by phpseclib (if applicable)
  1146. *
  1147. * @param string $algorithm
  1148. * @param string $key
  1149. * @access private
  1150. * @return string
  1151. */
  1152. private function reformatKey($algorithm, $key)
  1153. {
  1154. switch ($algorithm) {
  1155. case 'rsaEncryption':
  1156. return
  1157. "-----BEGIN RSA PUBLIC KEY-----\r\n" .
  1158. // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits
  1159. // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox
  1160. // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do.
  1161. chunk_split(Base64::encode(substr($key, 1)), 64) .
  1162. '-----END RSA PUBLIC KEY-----';
  1163. default:
  1164. return $key;
  1165. }
  1166. }
  1167. /**
  1168. * Decodes an IP address
  1169. *
  1170. * Takes in a base64 encoded "blob" and returns a human readable IP address
  1171. *
  1172. * @param string $ip
  1173. * @access private
  1174. * @return string
  1175. */
  1176. public function decodeIP($ip)
  1177. {
  1178. return inet_ntop($ip);
  1179. }
  1180. /**
  1181. * Encodes an IP address
  1182. *
  1183. * Takes a human readable IP address into a base64-encoded "blob"
  1184. *
  1185. * @param string $ip
  1186. * @access private
  1187. * @return string
  1188. */
  1189. public function encodeIP($ip)
  1190. {
  1191. return inet_pton($ip);
  1192. }
  1193. /**
  1194. * "Normalizes" a Distinguished Name property
  1195. *
  1196. * @param string $propName
  1197. * @access private
  1198. * @return mixed
  1199. */
  1200. private function translateDNProp($propName)
  1201. {
  1202. switch (strtolower($propName)) {
  1203. case 'id-at-countryname':
  1204. case 'countryname':
  1205. case 'c':
  1206. return 'id-at-countryName';
  1207. case 'id-at-organizationname':
  1208. case 'organizationname':
  1209. case 'o':
  1210. return 'id-at-organizationName';
  1211. case 'id-at-dnqualifier':
  1212. case 'dnqualifier':
  1213. return 'id-at-dnQualifier';
  1214. case 'id-at-commonname':
  1215. case 'commonname':
  1216. case 'cn':
  1217. return 'id-at-commonName';
  1218. case 'id-at-stateorprovincename':
  1219. case 'stateorprovincename':
  1220. case 'state':
  1221. case 'province':
  1222. case 'provincename':
  1223. case 'st':
  1224. return 'id-at-stateOrProvinceName';
  1225. case 'id-at-localityname':
  1226. case 'localityname':
  1227. case 'l':
  1228. return 'id-at-localityName';
  1229. case 'id-emailaddress':
  1230. case 'emailaddress':
  1231. return 'pkcs-9-at-emailAddress';
  1232. case 'id-at-serialnumber':
  1233. case 'serialnumber':
  1234. return 'id-at-serialNumber';
  1235. case 'id-at-postalcode':
  1236. case 'postalcode':
  1237. return 'id-at-postalCode';
  1238. case 'id-at-streetaddress':
  1239. case 'streetaddress':
  1240. return 'id-at-streetAddress';
  1241. case 'id-at-name':
  1242. case 'name':
  1243. return 'id-at-name';
  1244. case 'id-at-givenname':
  1245. case 'givenname':
  1246. return 'id-at-givenName';
  1247. case 'id-at-surname':
  1248. case 'surname':
  1249. case 'sn':
  1250. return 'id-at-surname';
  1251. case 'id-at-initials':
  1252. case 'initials':
  1253. return 'id-at-initials';
  1254. case 'id-at-generationqualifier':
  1255. case 'generationqualifier':
  1256. return 'id-at-generationQualifier';
  1257. case 'id-at-organizationalunitname':
  1258. case 'organizationalunitname':
  1259. case 'ou':
  1260. return 'id-at-organizationalUnitName';
  1261. case 'id-at-pseudonym':
  1262. case 'pseudonym':
  1263. return 'id-at-pseudonym';
  1264. case 'id-at-title':
  1265. case 'title':
  1266. return 'id-at-title';
  1267. case 'id-at-description':
  1268. case 'description':
  1269. return 'id-at-description';
  1270. case 'id-at-role':
  1271. case 'role':
  1272. return 'id-at-role';
  1273. case 'id-at-uniqueidentifier':
  1274. case 'uniqueidentifier':
  1275. case 'x500uniqueidentifier':
  1276. return 'id-at-uniqueIdentifier';
  1277. case 'postaladdress':
  1278. case 'id-at-postaladdress':
  1279. return 'id-at-postalAddress';
  1280. default:
  1281. return false;
  1282. }
  1283. }
  1284. /**
  1285. * Set a Distinguished Name property
  1286. *
  1287. * @param string $propName
  1288. * @param mixed $propValue
  1289. * @param string $type optional
  1290. * @access public
  1291. * @return bool
  1292. */
  1293. public function setDNProp($propName, $propValue, $type = 'utf8String')
  1294. {
  1295. if (empty($this->dn)) {
  1296. $this->dn = ['rdnSequence' => []];
  1297. }
  1298. if (($propName = $this->translateDNProp($propName)) === false) {
  1299. return false;
  1300. }
  1301. foreach ((array) $propValue as $v) {
  1302. if (!is_array($v) && isset($type)) {
  1303. $v = [$type => $v];
  1304. }
  1305. $this->dn['rdnSequence'][] = [
  1306. [
  1307. 'type' => $propName,
  1308. 'value'=> $v
  1309. ]
  1310. ];
  1311. }
  1312. return true;
  1313. }
  1314. /**
  1315. * Remove Distinguished Name properties
  1316. *
  1317. * @param string $propName
  1318. * @access public
  1319. */
  1320. public function removeDNProp($propName)
  1321. {
  1322. if (empty($this->dn)) {
  1323. return;
  1324. }
  1325. if (($propName = $this->translateDNProp($propName)) === false) {
  1326. return;
  1327. }
  1328. $dn = &$this->dn['rdnSequence'];
  1329. $size = count($dn);
  1330. for ($i = 0; $i < $size; $i++) {
  1331. if ($dn[$i][0]['type'] == $propName) {
  1332. unset($dn[$i]);
  1333. }
  1334. }
  1335. $dn = array_values($dn);
  1336. }
  1337. /**
  1338. * Get Distinguished Name properties
  1339. *
  1340. * @param string $propName
  1341. * @param array $dn optional
  1342. * @param bool $withType optional
  1343. * @return mixed
  1344. * @access public
  1345. */
  1346. public function getDNProp($propName, $dn = null, $withType = false)
  1347. {
  1348. if (!isset($dn)) {
  1349. $dn = $this->dn;
  1350. }
  1351. if (empty($dn)) {
  1352. return false;
  1353. }
  1354. if (($propName = $this->translateDNProp($propName)) === false) {
  1355. return false;
  1356. }
  1357. $filters = [];
  1358. $filters['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
  1359. ASN1::setFilters($filters);
  1360. $this->mapOutDNs($dn, 'rdnSequence');
  1361. $dn = $dn['rdnSequence'];
  1362. $result = [];
  1363. for ($i = 0; $i < count($dn); $i++) {
  1364. if ($dn[$i][0]['type'] == $propName) {
  1365. $v = $dn[$i][0]['value'];
  1366. if (!$withType) {
  1367. if (is_array($v)) {
  1368. foreach ($v as $type => $s) {
  1369. $type = array_search($type, ASN1::ANY_MAP);
  1370. if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
  1371. $s = ASN1::convert($s, $type);
  1372. if ($s !== false) {
  1373. $v = $s;
  1374. break;
  1375. }
  1376. }
  1377. }
  1378. if (is_array($v)) {
  1379. $v = array_pop($v); // Always strip data type.
  1380. }
  1381. } elseif (is_object($v) && $v instanceof Element) {
  1382. $map = $this->getMapping($propName);
  1383. if (!is_bool($map)) {
  1384. $decoded = ASN1::decodeBER($v);
  1385. $v = ASN1::asn1map($decoded[0], $map);
  1386. }
  1387. }
  1388. }
  1389. $result[] = $v;
  1390. }
  1391. }
  1392. return $result;
  1393. }
  1394. /**
  1395. * Set a Distinguished Name
  1396. *
  1397. * @param mixed $dn
  1398. * @param bool $merge optional
  1399. * @param string $type optional
  1400. * @access public
  1401. * @return bool
  1402. */
  1403. public function setDN($dn, $merge = false, $type = 'utf8String')
  1404. {
  1405. if (!$merge) {
  1406. $this->dn = null;
  1407. }
  1408. if (is_array($dn)) {
  1409. if (isset($dn['rdnSequence'])) {
  1410. $this->dn = $dn; // No merge here.
  1411. return true;
  1412. }
  1413. // handles stuff generated by openssl_x509_parse()
  1414. foreach ($dn as $prop => $value) {
  1415. if (!$this->setDNProp($prop, $value, $type)) {
  1416. return false;
  1417. }
  1418. }
  1419. return true;
  1420. }
  1421. // handles everything else
  1422. $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=|postalAddress=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
  1423. for ($i = 1; $i < count($results); $i+=2) {
  1424. $prop = trim($results[$i], ', =/');
  1425. $value = $results[$i + 1];
  1426. if (!$this->setDNProp($prop, $value, $type)) {
  1427. return false;
  1428. }
  1429. }
  1430. return true;
  1431. }
  1432. /**
  1433. * Get the Distinguished Name for a certificates subject
  1434. *
  1435. * @param mixed $format optional
  1436. * @param array $dn optional
  1437. * @access public
  1438. * @return bool
  1439. */
  1440. public function getDN($format = self::DN_ARRAY, $dn = null)
  1441. {
  1442. if (!isset($dn)) {
  1443. $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn;
  1444. }
  1445. switch ((int) $format) {
  1446. case self::DN_ARRAY:
  1447. return $dn;
  1448. case self::DN_ASN1:
  1449. $filters = [];
  1450. $filters['rdnSequence']['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
  1451. ASN1::setFilters($filters);
  1452. $this->mapOutDNs($dn, 'rdnSequence');
  1453. return ASN1::encodeDER($dn, Maps\Name::MAP);
  1454. case self::DN_CANON:
  1455. // No SEQUENCE around RDNs and all string values normalized as
  1456. // trimmed lowercase UTF-8 with all spacing as one blank.
  1457. // constructed RDNs will not be canonicalized
  1458. $filters = [];
  1459. $filters['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
  1460. ASN1::setFilters($filters);
  1461. $result = '';
  1462. $this->mapOutDNs($dn, 'rdnSequence');
  1463. foreach ($dn['rdnSequence'] as $rdn) {
  1464. foreach ($rdn as $i => $attr) {
  1465. $attr = &$rdn[$i];
  1466. if (is_array($attr['value'])) {
  1467. foreach ($attr['value'] as $type => $v) {
  1468. $type = array_search($type, ASN1::ANY_MAP, true);
  1469. if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
  1470. $v = ASN1::convert($v, $type);
  1471. if ($v !== false) {
  1472. $v = preg_replace('/\s+/', ' ', $v);
  1473. $attr['value'] = strtolower(trim($v));
  1474. break;
  1475. }
  1476. }
  1477. }
  1478. }
  1479. }
  1480. $result .= ASN1::encodeDER($rdn, Maps\RelativeDistinguishedName::MAP);
  1481. }
  1482. return $result;
  1483. case self::DN_HASH:
  1484. $dn = $this->getDN(self::DN_CANON, $dn);
  1485. $hash = new Hash('sha1');
  1486. $hash = $hash->hash($dn);
  1487. extract(unpack('Vhash', $hash));
  1488. return strtolower(Hex::encode(pack('N', $hash)));
  1489. }
  1490. // Default is to return a string.
  1491. $start = true;
  1492. $output = '';
  1493. $result = [];
  1494. $filters = [];
  1495. $filters['rdnSequence']['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
  1496. ASN1::setFilters($filters);
  1497. $this->mapOutDNs($dn, 'rdnSequence');
  1498. foreach ($dn['rdnSequence'] as $field) {
  1499. $prop = $field[0]['type'];
  1500. $value = $field[0]['value'];
  1501. $delim = ', ';
  1502. switch ($prop) {
  1503. case 'id-at-countryName':
  1504. $desc = 'C';
  1505. break;
  1506. case 'id-at-stateOrProvinceName':
  1507. $desc = 'ST';
  1508. break;
  1509. case 'id-at-organizationName':
  1510. $desc = 'O';
  1511. break;
  1512. case 'id-at-organizationalUnitName':
  1513. $desc = 'OU';
  1514. break;
  1515. case 'id-at-commonName':
  1516. $desc = 'CN';
  1517. break;
  1518. case 'id-at-localityName':
  1519. $desc = 'L';
  1520. break;
  1521. case 'id-at-surname':
  1522. $desc = 'SN';
  1523. break;
  1524. case 'id-at-uniqueIdentifier':
  1525. $delim = '/';
  1526. $desc = 'x500UniqueIdentifier';
  1527. break;
  1528. case 'id-at-postalAddress':
  1529. $delim = '/';
  1530. $desc = 'postalAddress';
  1531. break;
  1532. default:
  1533. $delim = '/';
  1534. $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop);
  1535. }
  1536. if (!$start) {
  1537. $output.= $delim;
  1538. }
  1539. if (is_array($value)) {
  1540. foreach ($value as $type => $v) {
  1541. $type = array_search($type, ASN1::ANY_MAP, true);
  1542. if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
  1543. $v = ASN1::convert($v, $type);
  1544. if ($v !== false) {
  1545. $value = $v;
  1546. break;
  1547. }
  1548. }
  1549. }
  1550. if (is_array($value)) {
  1551. $value = array_pop($value); // Always strip data type.
  1552. }
  1553. } elseif (is_object($value) && $value instanceof Element) {
  1554. $callback = function($x) { return '\x' . bin2hex($x[0]); };
  1555. $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
  1556. }
  1557. $output.= $desc . '=' . $value;
  1558. $result[$desc] = isset($result[$desc]) ?
  1559. array_merge((array) $dn[$prop], [$value]) :
  1560. $value;
  1561. $start = false;
  1562. }
  1563. return $format == self::DN_OPENSSL ? $result : $output;
  1564. }
  1565. /**
  1566. * Get the Distinguished Name for a certificate/crl issuer
  1567. *
  1568. * @param int $format optional
  1569. * @access public
  1570. * @return mixed
  1571. */
  1572. public function getIssuerDN($format = self::DN_ARRAY)
  1573. {
  1574. switch (true) {
  1575. case !isset($this->currentCert) || !is_array($this->currentCert):
  1576. break;
  1577. case isset($this->currentCert['tbsCertificate']):
  1578. return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']);
  1579. case isset($this->currentCert['tbsCertList']):
  1580. return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']);
  1581. }
  1582. return false;
  1583. }
  1584. /**
  1585. * Get the Distinguished Name for a certificate/csr subject
  1586. * Alias of getDN()
  1587. *
  1588. * @param int $format optional
  1589. * @access public
  1590. * @return mixed
  1591. */
  1592. public function getSubjectDN($format = self::DN_ARRAY)
  1593. {
  1594. switch (true) {
  1595. case !empty($this->dn):
  1596. return $this->getDN($format);
  1597. case !isset($this->currentCert) || !is_array($this->currentCert):
  1598. break;
  1599. case isset($this->currentCert['tbsCertificate']):
  1600. return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']);
  1601. case isset($this->currentCert['certificationRequestInfo']):
  1602. return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']);
  1603. }
  1604. return false;
  1605. }
  1606. /**
  1607. * Get an individual Distinguished Name property for a certificate/crl issuer
  1608. *
  1609. * @param string $propName
  1610. * @param bool $withType optional
  1611. * @access public
  1612. * @return mixed
  1613. */
  1614. public function getIssuerDNProp($propName, $withType = false)
  1615. {
  1616. switch (true) {
  1617. case !isset($this->currentCert) || !is_array($this->currentCert):
  1618. break;
  1619. case isset($this->currentCert['tbsCertificate']):
  1620. return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType);
  1621. case isset($this->currentCert['tbsCertList']):
  1622. return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType);
  1623. }
  1624. return false;
  1625. }
  1626. /**
  1627. * Get an individual Distinguished Name property for a certificate/csr subject
  1628. *
  1629. * @param string $propName
  1630. * @param bool $withType optional
  1631. * @access public
  1632. * @return mixed
  1633. */
  1634. public function getSubjectDNProp($propName, $withType = false)
  1635. {
  1636. switch (true) {
  1637. case !empty($this->dn):
  1638. return $this->getDNProp($propName, null, $withType);
  1639. case !isset($this->currentCert) || !is_array($this->currentCert):
  1640. break;
  1641. case isset($this->currentCert['tbsCertificate']):
  1642. return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType);
  1643. case isset($this->currentCert['certificationRequestInfo']):
  1644. return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType);
  1645. }
  1646. return false;
  1647. }
  1648. /**
  1649. * Get the certificate chain for the current cert
  1650. *
  1651. * @access public
  1652. * @return mixed
  1653. */
  1654. public function getChain()
  1655. {
  1656. $chain = [$this->currentCert];
  1657. if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
  1658. return false;
  1659. }
  1660. if (empty($this->CAs)) {
  1661. return $chain;
  1662. }
  1663. while (true) {
  1664. $currentCert = $chain[count($chain) - 1];
  1665. for ($i = 0; $i < count($this->CAs); $i++) {
  1666. $ca = $this->CAs[$i];
  1667. if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
  1668. $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert);
  1669. $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
  1670. switch (true) {
  1671. case !is_array($authorityKey):
  1672. case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
  1673. if ($currentCert === $ca) {
  1674. break 3;
  1675. }
  1676. $chain[] = $ca;
  1677. break 2;
  1678. }
  1679. }
  1680. }
  1681. if ($i == count($this->CAs)) {
  1682. break;
  1683. }
  1684. }
  1685. foreach ($chain as $key => $value) {
  1686. $chain[$key] = new X509();
  1687. $chain[$key]->loadX509($value);
  1688. }
  1689. return $chain;
  1690. }
  1691. /**
  1692. * Set public key
  1693. *
  1694. * Key needs to be a \phpseclib\Crypt\RSA object
  1695. *
  1696. * @param object $key
  1697. * @access public
  1698. * @return bool
  1699. */
  1700. public function setPublicKey($key)
  1701. {
  1702. $key->setPublicKey();
  1703. $this->publicKey = $key;
  1704. }
  1705. /**
  1706. * Set private key
  1707. *
  1708. * Key needs to be a \phpseclib\Crypt\RSA object
  1709. *
  1710. * @param object $key
  1711. * @access public
  1712. */
  1713. public function setPrivateKey($key)
  1714. {
  1715. $this->privateKey = $key;
  1716. }
  1717. /**
  1718. * Set challenge
  1719. *
  1720. * Used for SPKAC CSR's
  1721. *
  1722. * @param string $challenge
  1723. * @access public
  1724. */
  1725. public function setChallenge($challenge)
  1726. {
  1727. $this->challenge = $challenge;
  1728. }
  1729. /**
  1730. * Gets the public key
  1731. *
  1732. * Returns a \phpseclib\Crypt\RSA object or a false.
  1733. *
  1734. * @access public
  1735. * @return mixed
  1736. */
  1737. public function getPublicKey()
  1738. {
  1739. if (isset($this->publicKey)) {
  1740. return $this->publicKey;
  1741. }
  1742. if (isset($this->currentCert) && is_array($this->currentCert)) {
  1743. foreach (['tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo'] as $path) {
  1744. $keyinfo = $this->subArray($this->currentCert, $path);
  1745. if (!empty($keyinfo)) {
  1746. break;
  1747. }
  1748. }
  1749. }
  1750. if (empty($keyinfo)) {
  1751. return false;
  1752. }
  1753. $key = $keyinfo['subjectPublicKey'];
  1754. switch ($keyinfo['algorithm']['algorithm']) {
  1755. case 'rsaEncryption':
  1756. $publicKey = new RSA();
  1757. $publicKey->load($key);
  1758. $publicKey->setPublicKey();
  1759. break;
  1760. default:
  1761. return false;
  1762. }
  1763. return $publicKey;
  1764. }
  1765. /**
  1766. * Load a Certificate Signing Request
  1767. *
  1768. * @param string $csr
  1769. * @access public
  1770. * @return mixed
  1771. */
  1772. public function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT)
  1773. {
  1774. if (is_array($csr) && isset($csr['certificationRequestInfo'])) {
  1775. unset($this->currentCert);
  1776. unset($this->currentKeyIdentifier);
  1777. unset($this->signatureSubject);
  1778. $this->dn = $csr['certificationRequestInfo']['subject'];
  1779. if (!isset($this->dn)) {
  1780. return false;
  1781. }
  1782. $this->currentCert = $csr;
  1783. return $csr;
  1784. }
  1785. // see http://tools.ietf.org/html/rfc2986
  1786. if ($mode != self::FORMAT_DER) {
  1787. $newcsr = ASN1::extractBER($csr);
  1788. if ($mode == self::FORMAT_PEM && $csr == $newcsr) {
  1789. return false;
  1790. }
  1791. $csr = $newcsr;
  1792. }
  1793. $orig = $csr;
  1794. if ($csr === false) {
  1795. $this->currentCert = false;
  1796. return false;
  1797. }
  1798. $decoded = ASN1::decodeBER($csr);
  1799. if (empty($decoded)) {
  1800. $this->currentCert = false;
  1801. return false;
  1802. }
  1803. $csr = ASN1::asn1map($decoded[0], Maps\CertificationRequest::MAP);
  1804. if (!isset($csr) || $csr === false) {
  1805. $this->currentCert = false;
  1806. return false;
  1807. }
  1808. $this->mapInAttributes($csr, 'certificationRequestInfo/attributes');
  1809. $this->mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence');
  1810. $this->dn = $csr['certificationRequestInfo']['subject'];
  1811. $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
  1812. $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'];
  1813. $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'];
  1814. $key = $this->reformatKey($algorithm, $key);
  1815. switch ($algorithm) {
  1816. case 'rsaEncryption':
  1817. $this->publicKey = new RSA();
  1818. $this->publicKey->load($key);
  1819. $this->publicKey->setPublicKey();
  1820. break;
  1821. default:
  1822. $this->publicKey = null;
  1823. }
  1824. $this->currentKeyIdentifier = null;
  1825. $this->currentCert = $csr;
  1826. return $csr;
  1827. }
  1828. /**
  1829. * Save CSR request
  1830. *
  1831. * @param array $csr
  1832. * @param int $format optional
  1833. * @access public
  1834. * @return string
  1835. */
  1836. public function saveCSR($csr, $format = self::FORMAT_PEM)
  1837. {
  1838. if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) {
  1839. return false;
  1840. }
  1841. switch (true) {
  1842. case !($algorithm = $this->subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
  1843. case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
  1844. break;
  1845. default:
  1846. switch ($algorithm) {
  1847. case 'rsaEncryption':
  1848. $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']
  1849. = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])));
  1850. $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null;
  1851. $csr['signatureAlgorithm']['parameters'] = null;
  1852. $csr['certificationRequestInfo']['signature']['parameters'] = null;
  1853. }
  1854. }
  1855. $filters = [];
  1856. $filters['certificationRequestInfo']['subject']['rdnSequence']['value']
  1857. = ['type' => ASN1::TYPE_UTF8_STRING];
  1858. ASN1::setFilters($filters);
  1859. $this->mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence');
  1860. $this->mapOutAttributes($csr, 'certificationRequestInfo/attributes');
  1861. $csr = ASN1::encodeDER($csr, Maps\CertificationRequest::MAP);
  1862. switch ($format) {
  1863. case self::FORMAT_DER:
  1864. return $csr;
  1865. // case self::FORMAT_PEM:
  1866. default:
  1867. return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(Base64::encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
  1868. }
  1869. }
  1870. /**
  1871. * Load a SPKAC CSR
  1872. *
  1873. * SPKAC's are produced by the HTML5 keygen element:
  1874. *
  1875. * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen
  1876. *
  1877. * @param string $csr
  1878. * @access public
  1879. * @return mixed
  1880. */
  1881. public function loadSPKAC($spkac)
  1882. {
  1883. if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) {
  1884. unset($this->currentCert);
  1885. unset($this->currentKeyIdentifier);
  1886. unset($this->signatureSubject);
  1887. $this->currentCert = $spkac;
  1888. return $spkac;
  1889. }
  1890. // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge
  1891. // OpenSSL produces SPKAC's that are preceded by the string SPKAC=
  1892. $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
  1893. $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false;
  1894. if ($temp != false) {
  1895. $spkac = $temp;
  1896. }
  1897. $orig = $spkac;
  1898. if ($spkac === false) {
  1899. $this->currentCert = false;
  1900. return false;
  1901. }
  1902. $decoded = ASN1::decodeBER($spkac);
  1903. if (empty($decoded)) {
  1904. $this->currentCert = false;
  1905. return false;
  1906. }
  1907. $spkac = ASN1::asn1map($decoded[0], Maps\SignedPublicKeyAndChallenge::MAP);
  1908. if (!isset($spkac) || $spkac === false) {
  1909. $this->currentCert = false;
  1910. return false;
  1911. }
  1912. $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
  1913. $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm'];
  1914. $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'];
  1915. $key = $this->reformatKey($algorithm, $key);
  1916. switch ($algorithm) {
  1917. case 'rsaEncryption':
  1918. $this->publicKey = new RSA();
  1919. $this->publicKey->load($key);
  1920. $this->publicKey->setPublicKey();
  1921. break;
  1922. default:
  1923. $this->publicKey = null;
  1924. }
  1925. $this->currentKeyIdentifier = null;
  1926. $this->currentCert = $spkac;
  1927. return $spkac;
  1928. }
  1929. /**
  1930. * Save a SPKAC CSR request
  1931. *
  1932. * @param array $csr
  1933. * @param int $format optional
  1934. * @access public
  1935. * @return string
  1936. */
  1937. public function saveSPKAC($spkac, $format = self::FORMAT_PEM)
  1938. {
  1939. if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
  1940. return false;
  1941. }
  1942. $algorithm = $this->subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
  1943. switch (true) {
  1944. case !$algorithm:
  1945. case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']):
  1946. break;
  1947. default:
  1948. switch ($algorithm) {
  1949. case 'rsaEncryption':
  1950. $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']
  1951. = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])));
  1952. }
  1953. }
  1954. $spkac = ASN1::encodeDER($spkac, Maps\SignedPublicKeyAndChallenge::MAP);
  1955. switch ($format) {
  1956. case self::FORMAT_DER:
  1957. return $spkac;
  1958. // case self::FORMAT_PEM:
  1959. default:
  1960. // OpenSSL's implementation of SPKAC requires the SPKAC be preceded by SPKAC= and since there are pretty much
  1961. // no other SPKAC decoders phpseclib will use that same format
  1962. return 'SPKAC=' . Base64::encode($spkac);
  1963. }
  1964. }
  1965. /**
  1966. * Load a Certificate Revocation List
  1967. *
  1968. * @param string $crl
  1969. * @access public
  1970. * @return mixed
  1971. */
  1972. public function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT)
  1973. {
  1974. if (is_array($crl) && isset($crl['tbsCertList'])) {
  1975. $this->currentCert = $crl;
  1976. unset($this->signatureSubject);
  1977. return $crl;
  1978. }
  1979. if ($mode != self::FORMAT_DER) {
  1980. $newcrl = ASN1::extractBER($crl);
  1981. if ($mode == self::FORMAT_PEM && $crl == $newcrl) {
  1982. return false;
  1983. }
  1984. $crl = $newcrl;
  1985. }
  1986. $orig = $crl;
  1987. if ($crl === false) {
  1988. $this->currentCert = false;
  1989. return false;
  1990. }
  1991. $decoded = ASN1::decodeBER($crl);
  1992. if (empty($decoded)) {
  1993. $this->currentCert = false;
  1994. return false;
  1995. }
  1996. $crl = ASN1::asn1map($decoded[0], Maps\CertificateList::MAP);
  1997. if (!isset($crl) || $crl === false) {
  1998. $this->currentCert = false;
  1999. return false;
  2000. }
  2001. $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
  2002. $this->mapInDNs($crl, 'tbsCertList/issuer/rdnSequence');
  2003. if ($this->isSubArrayValid($crl, 'tbsCertList/crlExtensions')) {
  2004. $this->mapInExtensions($crl, 'tbsCertList/crlExtensions');
  2005. }
  2006. if ($this->isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) {
  2007. $rclist_ref = &$this->subArrayUnchecked($crl, 'tbsCertList/revokedCertificates');
  2008. if ($rclist_ref) {
  2009. $rclist = $crl['tbsCertList']['revokedCertificates'];
  2010. foreach ($rclist as $i => $extension) {
  2011. if ($this->isSubArrayValid($rclist, "$i/crlEntryExtensions")) {
  2012. $this->mapInExtensions($rclist_ref, "$i/crlEntryExtensions");
  2013. }
  2014. }
  2015. }
  2016. }
  2017. $this->currentKeyIdentifier = null;
  2018. $this->currentCert = $crl;
  2019. return $crl;
  2020. }
  2021. /**
  2022. * Save Certificate Revocation List.
  2023. *
  2024. * @param array $crl
  2025. * @param int $format optional
  2026. * @access public
  2027. * @return string
  2028. */
  2029. public function saveCRL($crl, $format = self::FORMAT_PEM)
  2030. {
  2031. if (!is_array($crl) || !isset($crl['tbsCertList'])) {
  2032. return false;
  2033. }
  2034. $filters = [];
  2035. $filters['tbsCertList']['issuer']['rdnSequence']['value']
  2036. = ['type' => ASN1::TYPE_UTF8_STRING];
  2037. $filters['tbsCertList']['signature']['parameters']
  2038. = ['type' => ASN1::TYPE_UTF8_STRING];
  2039. $filters['signatureAlgorithm']['parameters']
  2040. = ['type' => ASN1::TYPE_UTF8_STRING];
  2041. if (empty($crl['tbsCertList']['signature']['parameters'])) {
  2042. $filters['tbsCertList']['signature']['parameters']
  2043. = ['type' => ASN1::TYPE_NULL];
  2044. }
  2045. if (empty($crl['signatureAlgorithm']['parameters'])) {
  2046. $filters['signatureAlgorithm']['parameters']
  2047. = ['type' => ASN1::TYPE_NULL];
  2048. }
  2049. ASN1::setFilters($filters);
  2050. $this->mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence');
  2051. $this->mapOutExtensions($crl, 'tbsCertList/crlExtensions');
  2052. $rclist = &$this->subArray($crl, 'tbsCertList/revokedCertificates');
  2053. if (is_array($rclist)) {
  2054. foreach ($rclist as $i => $extension) {
  2055. $this->mapOutExtensions($rclist, "$i/crlEntryExtensions");
  2056. }
  2057. }
  2058. $crl = ASN1::encodeDER($crl, Maps\CertificateList::MAP);
  2059. switch ($format) {
  2060. case self::FORMAT_DER:
  2061. return $crl;
  2062. // case self::FORMAT_PEM:
  2063. default:
  2064. return "-----BEGIN X509 CRL-----\r\n" . chunk_split(Base64::encode($crl), 64) . '-----END X509 CRL-----';
  2065. }
  2066. }
  2067. /**
  2068. * Helper function to build a time field according to RFC 3280 section
  2069. * - 4.1.2.5 Validity
  2070. * - 5.1.2.4 This Update
  2071. * - 5.1.2.5 Next Update
  2072. * - 5.1.2.6 Revoked Certificates
  2073. * by choosing utcTime iff year of date given is before 2050 and generalTime else.
  2074. *
  2075. * @param string $date in format date('D, d M Y H:i:s O')
  2076. * @access private
  2077. * @return array
  2078. */
  2079. private function timeField($date)
  2080. {
  2081. $year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this
  2082. if ($year < 2050) {
  2083. return ['utcTime' => $date];
  2084. } else {
  2085. return ['generalTime' => $date];
  2086. }
  2087. }
  2088. /**
  2089. * Sign an X.509 certificate
  2090. *
  2091. * $issuer's private key needs to be loaded.
  2092. * $subject can be either an existing X.509 cert (if you want to resign it),
  2093. * a CSR or something with the DN and public key explicitly set.
  2094. *
  2095. * @param \phpseclib\File\X509 $issuer
  2096. * @param \phpseclib\File\X509 $subject
  2097. * @param string $signatureAlgorithm optional
  2098. * @access public
  2099. * @return mixed
  2100. */
  2101. public function sign($issuer, $subject, $signatureAlgorithm = 'sha256WithRSAEncryption')
  2102. {
  2103. if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
  2104. return false;
  2105. }
  2106. if (isset($subject->publicKey) && !($subjectPublicKey = $subject->formatSubjectPublicKey())) {
  2107. return false;
  2108. }
  2109. $currentCert = isset($this->currentCert) ? $this->currentCert : null;
  2110. $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
  2111. if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
  2112. $this->currentCert = $subject->currentCert;
  2113. $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm;
  2114. $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
  2115. if (!empty($this->startDate)) {
  2116. $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->timeField($this->startDate);
  2117. }
  2118. if (!empty($this->endDate)) {
  2119. $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->timeField($this->endDate);
  2120. }
  2121. if (!empty($this->serialNumber)) {
  2122. $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
  2123. }
  2124. if (!empty($subject->dn)) {
  2125. $this->currentCert['tbsCertificate']['subject'] = $subject->dn;
  2126. }
  2127. if (!empty($subject->publicKey)) {
  2128. $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
  2129. }
  2130. $this->removeExtension('id-ce-authorityKeyIdentifier');
  2131. if (isset($subject->domains)) {
  2132. $this->removeExtension('id-ce-subjectAltName');
  2133. }
  2134. } elseif (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) {
  2135. return false;
  2136. } else {
  2137. if (!isset($subject->publicKey)) {
  2138. return false;
  2139. }
  2140. $startDate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
  2141. $endDate = !empty($this->endDate) ? $this->endDate : @date('D, d M Y H:i:s O', strtotime('+1 year'));
  2142. /* "The serial number MUST be a positive integer"
  2143. "Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
  2144. -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2
  2145. for the integer to be positive the leading bit needs to be 0 hence the
  2146. application of a bitmap
  2147. */
  2148. $serialNumber = !empty($this->serialNumber) ?
  2149. $this->serialNumber :
  2150. new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256);
  2151. $this->currentCert = [
  2152. 'tbsCertificate' =>
  2153. array(
  2154. 'version' => 'v3',
  2155. 'serialNumber' => $serialNumber, // $this->setserialNumber()
  2156. 'signature' => array('algorithm' => $signatureAlgorithm),
  2157. 'issuer' => false, // this is going to be overwritten later
  2158. 'validity' => [
  2159. 'notBefore' => $this->timeField($startDate), // $this->setStartDate()
  2160. 'notAfter' => $this->timeField($endDate) // $this->setEndDate()
  2161. ],
  2162. 'subject' => $subject->dn,
  2163. 'subjectPublicKeyInfo' => $subjectPublicKey
  2164. ),
  2165. 'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm],
  2166. 'signature' => false // this is going to be overwritten later
  2167. ];
  2168. // Copy extensions from CSR.
  2169. $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
  2170. if (!empty($csrexts)) {
  2171. $this->currentCert['tbsCertificate']['extensions'] = $csrexts;
  2172. }
  2173. }
  2174. $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;
  2175. if (isset($issuer->currentKeyIdentifier)) {
  2176. $this->setExtension('id-ce-authorityKeyIdentifier', [
  2177. //'authorityCertIssuer' => array(
  2178. // array(
  2179. // 'directoryName' => $issuer->dn
  2180. // )
  2181. //),
  2182. 'keyIdentifier' => $issuer->currentKeyIdentifier
  2183. ]);
  2184. //$extensions = &$this->currentCert['tbsCertificate']['extensions'];
  2185. //if (isset($issuer->serialNumber)) {
  2186. // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
  2187. //}
  2188. //unset($extensions);
  2189. }
  2190. if (isset($subject->currentKeyIdentifier)) {
  2191. $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier);
  2192. }
  2193. $altName = [];
  2194. if (isset($subject->domains) && count($subject->domains) > 1) {
  2195. $altName = array_map(['\phpseclib\File\X509', 'dnsName'], $subject->domains);
  2196. }
  2197. if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
  2198. // should an IP address appear as the CN if no domain name is specified? idk
  2199. //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
  2200. $ipAddresses = [];
  2201. foreach ($subject->ipAddresses as $ipAddress) {
  2202. $encoded = $subject->ipAddress($ipAddress);
  2203. if ($encoded !== false) {
  2204. $ipAddresses[] = $encoded;
  2205. }
  2206. }
  2207. if (count($ipAddresses)) {
  2208. $altName = array_merge($altName, $ipAddresses);
  2209. }
  2210. }
  2211. if (!empty($altName)) {
  2212. $this->setExtension('id-ce-subjectAltName', $altName);
  2213. }
  2214. if ($this->caFlag) {
  2215. $keyUsage = $this->getExtension('id-ce-keyUsage');
  2216. if (!$keyUsage) {
  2217. $keyUsage = [];
  2218. }
  2219. $this->setExtension(
  2220. 'id-ce-keyUsage',
  2221. array_values(array_unique(array_merge($keyUsage, ['cRLSign', 'keyCertSign'])))
  2222. );
  2223. $basicConstraints = $this->getExtension('id-ce-basicConstraints');
  2224. if (!$basicConstraints) {
  2225. $basicConstraints = [];
  2226. }
  2227. $this->setExtension(
  2228. 'id-ce-basicConstraints',
  2229. array_unique(array_merge(['cA' => true], $basicConstraints)),
  2230. true
  2231. );
  2232. if (!isset($subject->currentKeyIdentifier)) {
  2233. $this->setExtension('id-ce-subjectKeyIdentifier', $this->computeKeyIdentifier($this->currentCert), false, false);
  2234. }
  2235. }
  2236. // resync $this->signatureSubject
  2237. // save $tbsCertificate in case there are any \phpseclib\File\ASN1\Element objects in it
  2238. $tbsCertificate = $this->currentCert['tbsCertificate'];
  2239. $this->loadX509($this->saveX509($this->currentCert));
  2240. $result = $this->signHelper($issuer->privateKey, $signatureAlgorithm);
  2241. $result['tbsCertificate'] = $tbsCertificate;
  2242. $this->currentCert = $currentCert;
  2243. $this->signatureSubject = $signatureSubject;
  2244. return $result;
  2245. }
  2246. /**
  2247. * Sign a CSR
  2248. *
  2249. * @access public
  2250. * @return mixed
  2251. */
  2252. public function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption')
  2253. {
  2254. if (!is_object($this->privateKey) || empty($this->dn)) {
  2255. return false;
  2256. }
  2257. $origPublicKey = $this->publicKey;
  2258. $class = get_class($this->privateKey);
  2259. $this->publicKey = new $class();
  2260. $this->publicKey->load($this->privateKey->getPublicKey());
  2261. $this->publicKey->setPublicKey();
  2262. if (!($publicKey = $this->formatSubjectPublicKey())) {
  2263. return false;
  2264. }
  2265. $this->publicKey = $origPublicKey;
  2266. $currentCert = isset($this->currentCert) ? $this->currentCert : null;
  2267. $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
  2268. if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) {
  2269. $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
  2270. if (!empty($this->dn)) {
  2271. $this->currentCert['certificationRequestInfo']['subject'] = $this->dn;
  2272. }
  2273. $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
  2274. } else {
  2275. $this->currentCert = [
  2276. 'certificationRequestInfo' =>
  2277. [
  2278. 'version' => 'v1',
  2279. 'subject' => $this->dn,
  2280. 'subjectPKInfo' => $publicKey
  2281. ],
  2282. 'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm],
  2283. 'signature' => false // this is going to be overwritten later
  2284. ];
  2285. }
  2286. // resync $this->signatureSubject
  2287. // save $certificationRequestInfo in case there are any \phpseclib\File\ASN1\Element objects in it
  2288. $certificationRequestInfo = $this->currentCert['certificationRequestInfo'];
  2289. $this->loadCSR($this->saveCSR($this->currentCert));
  2290. $result = $this->signHelper($this->privateKey, $signatureAlgorithm);
  2291. $result['certificationRequestInfo'] = $certificationRequestInfo;
  2292. $this->currentCert = $currentCert;
  2293. $this->signatureSubject = $signatureSubject;
  2294. return $result;
  2295. }
  2296. /**
  2297. * Sign a SPKAC
  2298. *
  2299. * @access public
  2300. * @return mixed
  2301. */
  2302. public function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption')
  2303. {
  2304. if (!is_object($this->privateKey)) {
  2305. return false;
  2306. }
  2307. $origPublicKey = $this->publicKey;
  2308. $class = get_class($this->privateKey);
  2309. $this->publicKey = new $class();
  2310. $this->publicKey->load($this->privateKey->getPublicKey());
  2311. $this->publicKey->setPublicKey();
  2312. $publicKey = $this->formatSubjectPublicKey();
  2313. if (!$publicKey) {
  2314. return false;
  2315. }
  2316. $this->publicKey = $origPublicKey;
  2317. $currentCert = isset($this->currentCert) ? $this->currentCert : null;
  2318. $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
  2319. // re-signing a SPKAC seems silly but since everything else supports re-signing why not?
  2320. if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
  2321. $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
  2322. $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
  2323. if (!empty($this->challenge)) {
  2324. // the bitwise AND ensures that the output is a valid IA5String
  2325. $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
  2326. }
  2327. } else {
  2328. $this->currentCert = [
  2329. 'publicKeyAndChallenge' =>
  2330. [
  2331. 'spki' => $publicKey,
  2332. // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
  2333. // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
  2334. // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way
  2335. // we could alternatively do this instead if we ignored the specs:
  2336. // Random::string(8) & str_repeat("\x7F", 8)
  2337. 'challenge' => !empty($this->challenge) ? $this->challenge : ''
  2338. ],
  2339. 'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm],
  2340. 'signature' => false // this is going to be overwritten later
  2341. ];
  2342. }
  2343. // resync $this->signatureSubject
  2344. // save $publicKeyAndChallenge in case there are any \phpseclib\File\ASN1\Element objects in it
  2345. $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
  2346. $this->loadSPKAC($this->saveSPKAC($this->currentCert));
  2347. $result = $this->signHelper($this->privateKey, $signatureAlgorithm);
  2348. $result['publicKeyAndChallenge'] = $publicKeyAndChallenge;
  2349. $this->currentCert = $currentCert;
  2350. $this->signatureSubject = $signatureSubject;
  2351. return $result;
  2352. }
  2353. /**
  2354. * Sign a CRL
  2355. *
  2356. * $issuer's private key needs to be loaded.
  2357. *
  2358. * @param \phpseclib\File\X509 $issuer
  2359. * @param \phpseclib\File\X509 $crl
  2360. * @param string $signatureAlgorithm optional
  2361. * @access public
  2362. * @return mixed
  2363. */
  2364. public function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption')
  2365. {
  2366. if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
  2367. return false;
  2368. }
  2369. $currentCert = isset($this->currentCert) ? $this->currentCert : null;
  2370. $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
  2371. $thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
  2372. if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
  2373. $this->currentCert = $crl->currentCert;
  2374. $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
  2375. $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
  2376. } else {
  2377. $this->currentCert = [
  2378. 'tbsCertList' =>
  2379. [
  2380. 'version' => 'v2',
  2381. 'signature' => ['algorithm' => $signatureAlgorithm],
  2382. 'issuer' => false, // this is going to be overwritten later
  2383. 'thisUpdate' => $this->timeField($thisUpdate) // $this->setStartDate()
  2384. ],
  2385. 'signatureAlgorithm' => ['algorithm' => $signatureAlgorithm],
  2386. 'signature' => false // this is going to be overwritten later
  2387. ];
  2388. }
  2389. $tbsCertList = &$this->currentCert['tbsCertList'];
  2390. $tbsCertList['issuer'] = $issuer->dn;
  2391. $tbsCertList['thisUpdate'] = $this->timeField($thisUpdate);
  2392. if (!empty($this->endDate)) {
  2393. $tbsCertList['nextUpdate'] = $this->timeField($this->endDate); // $this->setEndDate()
  2394. } else {
  2395. unset($tbsCertList['nextUpdate']);
  2396. }
  2397. if (!empty($this->serialNumber)) {
  2398. $crlNumber = $this->serialNumber;
  2399. } else {
  2400. $crlNumber = $this->getExtension('id-ce-cRLNumber');
  2401. // "The CRL number is a non-critical CRL extension that conveys a
  2402. // monotonically increasing sequence number for a given CRL scope and
  2403. // CRL issuer. This extension allows users to easily determine when a
  2404. // particular CRL supersedes another CRL."
  2405. // -- https://tools.ietf.org/html/rfc5280#section-5.2.3
  2406. $crlNumber = $crlNumber !== false ? $crlNumber->add(new BigInteger(1)) : null;
  2407. }
  2408. $this->removeExtension('id-ce-authorityKeyIdentifier');
  2409. $this->removeExtension('id-ce-issuerAltName');
  2410. // Be sure version >= v2 if some extension found.
  2411. $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0;
  2412. if (!$version) {
  2413. if (!empty($tbsCertList['crlExtensions'])) {
  2414. $version = 1; // v2.
  2415. } elseif (!empty($tbsCertList['revokedCertificates'])) {
  2416. foreach ($tbsCertList['revokedCertificates'] as $cert) {
  2417. if (!empty($cert['crlEntryExtensions'])) {
  2418. $version = 1; // v2.
  2419. }
  2420. }
  2421. }
  2422. if ($version) {
  2423. $tbsCertList['version'] = $version;
  2424. }
  2425. }
  2426. // Store additional extensions.
  2427. if (!empty($tbsCertList['version'])) { // At least v2.
  2428. if (!empty($crlNumber)) {
  2429. $this->setExtension('id-ce-cRLNumber', $crlNumber);
  2430. }
  2431. if (isset($issuer->currentKeyIdentifier)) {
  2432. $this->setExtension('id-ce-authorityKeyIdentifier', [
  2433. //'authorityCertIssuer' => array(
  2434. // ]
  2435. // 'directoryName' => $issuer->dn
  2436. // ]
  2437. //),
  2438. 'keyIdentifier' => $issuer->currentKeyIdentifier
  2439. ]);
  2440. //$extensions = &$tbsCertList['crlExtensions'];
  2441. //if (isset($issuer->serialNumber)) {
  2442. // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
  2443. //}
  2444. //unset($extensions);
  2445. }
  2446. $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert);
  2447. if ($issuerAltName !== false) {
  2448. $this->setExtension('id-ce-issuerAltName', $issuerAltName);
  2449. }
  2450. }
  2451. if (empty($tbsCertList['revokedCertificates'])) {
  2452. unset($tbsCertList['revokedCertificates']);
  2453. }
  2454. unset($tbsCertList);
  2455. // resync $this->signatureSubject
  2456. // save $tbsCertList in case there are any \phpseclib\File\ASN1\Element objects in it
  2457. $tbsCertList = $this->currentCert['tbsCertList'];
  2458. $this->loadCRL($this->saveCRL($this->currentCert));
  2459. $result = $this->signHelper($issuer->privateKey, $signatureAlgorithm);
  2460. $result['tbsCertList'] = $tbsCertList;
  2461. $this->currentCert = $currentCert;
  2462. $this->signatureSubject = $signatureSubject;
  2463. return $result;
  2464. }
  2465. /**
  2466. * X.509 certificate signing helper function.
  2467. *
  2468. * @param object $key
  2469. * @param \phpseclib\File\X509 $subject
  2470. * @param string $signatureAlgorithm
  2471. * @access public
  2472. * @throws \phpseclib\Exception\UnsupportedAlgorithmException if the algorithm is unsupported
  2473. * @return mixed
  2474. */
  2475. private function signHelper($key, $signatureAlgorithm)
  2476. {
  2477. if ($key instanceof RSA) {
  2478. switch ($signatureAlgorithm) {
  2479. case 'md2WithRSAEncryption':
  2480. case 'md5WithRSAEncryption':
  2481. case 'sha1WithRSAEncryption':
  2482. case 'sha224WithRSAEncryption':
  2483. case 'sha256WithRSAEncryption':
  2484. case 'sha384WithRSAEncryption':
  2485. case 'sha512WithRSAEncryption':
  2486. $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
  2487. $this->currentCert['signature'] = "\0" . $key->sign($this->signatureSubject, RSA::PADDING_PKCS1);
  2488. return $this->currentCert;
  2489. default:
  2490. throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
  2491. }
  2492. }
  2493. throw new UnsupportedAlgorithmException('Unsupported public key algorithm');
  2494. }
  2495. /**
  2496. * Set certificate start date
  2497. *
  2498. * @param string $date
  2499. * @access public
  2500. */
  2501. public function setStartDate($date)
  2502. {
  2503. $this->startDate = @date('D, d M Y H:i:s O', @strtotime($date));
  2504. }
  2505. /**
  2506. * Set certificate end date
  2507. *
  2508. * @param string $date
  2509. * @access public
  2510. */
  2511. public function setEndDate($date)
  2512. {
  2513. /*
  2514. To indicate that a certificate has no well-defined expiration date,
  2515. the notAfter SHOULD be assigned the GeneralizedTime value of
  2516. 99991231235959Z.
  2517. -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
  2518. */
  2519. if (strtolower($date) == 'lifetime') {
  2520. $temp = '99991231235959Z';
  2521. $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . Functions::encodeLength(strlen($temp)) . $temp;
  2522. $this->endDate = new Element($temp);
  2523. } else {
  2524. $this->endDate = @date('D, d M Y H:i:s O', @strtotime($date));
  2525. }
  2526. }
  2527. /**
  2528. * Set Serial Number
  2529. *
  2530. * @param string $serial
  2531. * @param $base optional
  2532. * @access public
  2533. */
  2534. public function setSerialNumber($serial, $base = -256)
  2535. {
  2536. $this->serialNumber = new BigInteger($serial, $base);
  2537. }
  2538. /**
  2539. * Turns the certificate into a certificate authority
  2540. *
  2541. * @access public
  2542. */
  2543. public function makeCA()
  2544. {
  2545. $this->caFlag = true;
  2546. }
  2547. /**
  2548. * Check for validity of subarray
  2549. *
  2550. * This is intended for use in conjunction with _subArrayUnchecked(),
  2551. * implementing the checks included in _subArray() but without copying
  2552. * a potentially large array by passing its reference by-value to is_array().
  2553. *
  2554. * @param array $root
  2555. * @param string $path
  2556. * @return boolean
  2557. * @access private
  2558. */
  2559. private function isSubArrayValid($root, $path)
  2560. {
  2561. if (!is_array($root)) {
  2562. return false;
  2563. }
  2564. foreach (explode('/', $path) as $i) {
  2565. if (!is_array($root)) {
  2566. return false;
  2567. }
  2568. if (!isset($root[$i])) {
  2569. return true;
  2570. }
  2571. $root = $root[$i];
  2572. }
  2573. return true;
  2574. }
  2575. /**
  2576. * Get a reference to a subarray
  2577. *
  2578. * This variant of _subArray() does no is_array() checking,
  2579. * so $root should be checked with _isSubArrayValid() first.
  2580. *
  2581. * This is here for performance reasons:
  2582. * Passing a reference (i.e. $root) by-value (i.e. to is_array())
  2583. * creates a copy. If $root is an especially large array, this is expensive.
  2584. *
  2585. * @param array $root
  2586. * @param string $path absolute path with / as component separator
  2587. * @param bool $create optional
  2588. * @access private
  2589. * @return array|false
  2590. */
  2591. private function &subArrayUnchecked(&$root, $path, $create = false)
  2592. {
  2593. $false = false;
  2594. foreach (explode('/', $path) as $i) {
  2595. if (!isset($root[$i])) {
  2596. if (!$create) {
  2597. return $false;
  2598. }
  2599. $root[$i] = [];
  2600. }
  2601. $root = &$root[$i];
  2602. }
  2603. return $root;
  2604. }
  2605. /**
  2606. * Get a reference to a subarray
  2607. *
  2608. * @param array $root
  2609. * @param string $path absolute path with / as component separator
  2610. * @param bool $create optional
  2611. * @access private
  2612. * @return array|false
  2613. */
  2614. private function &subArray(&$root, $path, $create = false)
  2615. {
  2616. $false = false;
  2617. if (!is_array($root)) {
  2618. return $false;
  2619. }
  2620. foreach (explode('/', $path) as $i) {
  2621. if (!is_array($root)) {
  2622. return $false;
  2623. }
  2624. if (!isset($root[$i])) {
  2625. if (!$create) {
  2626. return $false;
  2627. }
  2628. $root[$i] = [];
  2629. }
  2630. $root = &$root[$i];
  2631. }
  2632. return $root;
  2633. }
  2634. /**
  2635. * Get a reference to an extension subarray
  2636. *
  2637. * @param array $root
  2638. * @param string $path optional absolute path with / as component separator
  2639. * @param bool $create optional
  2640. * @access private
  2641. * @return array|false
  2642. */
  2643. private function &extensions(&$root, $path = null, $create = false)
  2644. {
  2645. if (!isset($root)) {
  2646. $root = $this->currentCert;
  2647. }
  2648. switch (true) {
  2649. case !empty($path):
  2650. case !is_array($root):
  2651. break;
  2652. case isset($root['tbsCertificate']):
  2653. $path = 'tbsCertificate/extensions';
  2654. break;
  2655. case isset($root['tbsCertList']):
  2656. $path = 'tbsCertList/crlExtensions';
  2657. break;
  2658. case isset($root['certificationRequestInfo']):
  2659. $pth = 'certificationRequestInfo/attributes';
  2660. $attributes = &$this->subArray($root, $pth, $create);
  2661. if (is_array($attributes)) {
  2662. foreach ($attributes as $key => $value) {
  2663. if ($value['type'] == 'pkcs-9-at-extensionRequest') {
  2664. $path = "$pth/$key/value/0";
  2665. break 2;
  2666. }
  2667. }
  2668. if ($create) {
  2669. $key = count($attributes);
  2670. $attributes[] = ['type' => 'pkcs-9-at-extensionRequest', 'value' => []];
  2671. $path = "$pth/$key/value/0";
  2672. }
  2673. }
  2674. break;
  2675. }
  2676. $extensions = &$this->subArray($root, $path, $create);
  2677. if (!is_array($extensions)) {
  2678. $false = false;
  2679. return $false;
  2680. }
  2681. return $extensions;
  2682. }
  2683. /**
  2684. * Remove an Extension
  2685. *
  2686. * @param string $id
  2687. * @param string $path optional
  2688. * @access private
  2689. * @return bool
  2690. */
  2691. private function removeExtensionHelper($id, $path = null)
  2692. {
  2693. $extensions = &$this->extensions($this->currentCert, $path);
  2694. if (!is_array($extensions)) {
  2695. return false;
  2696. }
  2697. $result = false;
  2698. foreach ($extensions as $key => $value) {
  2699. if ($value['extnId'] == $id) {
  2700. unset($extensions[$key]);
  2701. $result = true;
  2702. }
  2703. }
  2704. $extensions = array_values($extensions);
  2705. return $result;
  2706. }
  2707. /**
  2708. * Get an Extension
  2709. *
  2710. * Returns the extension if it exists and false if not
  2711. *
  2712. * @param string $id
  2713. * @param array $cert optional
  2714. * @param string $path optional
  2715. * @access private
  2716. * @return mixed
  2717. */
  2718. private function getExtensionHelper($id, $cert = null, $path = null)
  2719. {
  2720. $extensions = $this->extensions($cert, $path);
  2721. if (!is_array($extensions)) {
  2722. return false;
  2723. }
  2724. foreach ($extensions as $key => $value) {
  2725. if ($value['extnId'] == $id) {
  2726. return $value['extnValue'];
  2727. }
  2728. }
  2729. return false;
  2730. }
  2731. /**
  2732. * Returns a list of all extensions in use
  2733. *
  2734. * @param array $cert optional
  2735. * @param string $path optional
  2736. * @access private
  2737. * @return array
  2738. */
  2739. private function getExtensionsHelper($cert = null, $path = null)
  2740. {
  2741. $exts = $this->extensions($cert, $path);
  2742. $extensions = [];
  2743. if (is_array($exts)) {
  2744. foreach ($exts as $extension) {
  2745. $extensions[] = $extension['extnId'];
  2746. }
  2747. }
  2748. return $extensions;
  2749. }
  2750. /**
  2751. * Set an Extension
  2752. *
  2753. * @param string $id
  2754. * @param mixed $value
  2755. * @param bool $critical optional
  2756. * @param bool $replace optional
  2757. * @param string $path optional
  2758. * @access private
  2759. * @return bool
  2760. */
  2761. private function setExtensionHelper($id, $value, $critical = false, $replace = true, $path = null)
  2762. {
  2763. $extensions = &$this->extensions($this->currentCert, $path, true);
  2764. if (!is_array($extensions)) {
  2765. return false;
  2766. }
  2767. $newext = ['extnId' => $id, 'critical' => $critical, 'extnValue' => $value];
  2768. foreach ($extensions as $key => $value) {
  2769. if ($value['extnId'] == $id) {
  2770. if (!$replace) {
  2771. return false;
  2772. }
  2773. $extensions[$key] = $newext;
  2774. return true;
  2775. }
  2776. }
  2777. $extensions[] = $newext;
  2778. return true;
  2779. }
  2780. /**
  2781. * Remove a certificate, CSR or CRL Extension
  2782. *
  2783. * @param string $id
  2784. * @access public
  2785. * @return bool
  2786. */
  2787. public function removeExtension($id)
  2788. {
  2789. return $this->removeExtensionHelper($id);
  2790. }
  2791. /**
  2792. * Get a certificate, CSR or CRL Extension
  2793. *
  2794. * Returns the extension if it exists and false if not
  2795. *
  2796. * @param string $id
  2797. * @param array $cert optional
  2798. * @access public
  2799. * @return mixed
  2800. */
  2801. public function getExtension($id, $cert = null)
  2802. {
  2803. return $this->getExtensionHelper($id, $cert);
  2804. }
  2805. /**
  2806. * Returns a list of all extensions in use in certificate, CSR or CRL
  2807. *
  2808. * @param array $cert optional
  2809. * @access public
  2810. * @return array
  2811. */
  2812. public function getExtensions($cert = null)
  2813. {
  2814. return $this->getExtensionsHelper($cert);
  2815. }
  2816. /**
  2817. * Set a certificate, CSR or CRL Extension
  2818. *
  2819. * @param string $id
  2820. * @param mixed $value
  2821. * @param bool $critical optional
  2822. * @param bool $replace optional
  2823. * @access public
  2824. * @return bool
  2825. */
  2826. public function setExtension($id, $value, $critical = false, $replace = true)
  2827. {
  2828. return $this->setExtensionHelper($id, $value, $critical, $replace);
  2829. }
  2830. /**
  2831. * Remove a CSR attribute.
  2832. *
  2833. * @param string $id
  2834. * @param int $disposition optional
  2835. * @access public
  2836. * @return bool
  2837. */
  2838. public function removeAttribute($id, $disposition = self::ATTR_ALL)
  2839. {
  2840. $attributes = &$this->subArray($this->currentCert, 'certificationRequestInfo/attributes');
  2841. if (!is_array($attributes)) {
  2842. return false;
  2843. }
  2844. $result = false;
  2845. foreach ($attributes as $key => $attribute) {
  2846. if ($attribute['type'] == $id) {
  2847. $n = count($attribute['value']);
  2848. switch (true) {
  2849. case $disposition == self::ATTR_APPEND:
  2850. case $disposition == self::ATTR_REPLACE:
  2851. return false;
  2852. case $disposition >= $n:
  2853. $disposition -= $n;
  2854. break;
  2855. case $disposition == self::ATTR_ALL:
  2856. case $n == 1:
  2857. unset($attributes[$key]);
  2858. $result = true;
  2859. break;
  2860. default:
  2861. unset($attributes[$key]['value'][$disposition]);
  2862. $attributes[$key]['value'] = array_values($attributes[$key]['value']);
  2863. $result = true;
  2864. break;
  2865. }
  2866. if ($result && $disposition != self::ATTR_ALL) {
  2867. break;
  2868. }
  2869. }
  2870. }
  2871. $attributes = array_values($attributes);
  2872. return $result;
  2873. }
  2874. /**
  2875. * Get a CSR attribute
  2876. *
  2877. * Returns the attribute if it exists and false if not
  2878. *
  2879. * @param string $id
  2880. * @param int $disposition optional
  2881. * @param array $csr optional
  2882. * @access public
  2883. * @return mixed
  2884. */
  2885. public function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null)
  2886. {
  2887. if (empty($csr)) {
  2888. $csr = $this->currentCert;
  2889. }
  2890. $attributes = $this->subArray($csr, 'certificationRequestInfo/attributes');
  2891. if (!is_array($attributes)) {
  2892. return false;
  2893. }
  2894. foreach ($attributes as $key => $attribute) {
  2895. if ($attribute['type'] == $id) {
  2896. $n = count($attribute['value']);
  2897. switch (true) {
  2898. case $disposition == self::ATTR_APPEND:
  2899. case $disposition == self::ATTR_REPLACE:
  2900. return false;
  2901. case $disposition == self::ATTR_ALL:
  2902. return $attribute['value'];
  2903. case $disposition >= $n:
  2904. $disposition -= $n;
  2905. break;
  2906. default:
  2907. return $attribute['value'][$disposition];
  2908. }
  2909. }
  2910. }
  2911. return false;
  2912. }
  2913. /**
  2914. * Returns a list of all CSR attributes in use
  2915. *
  2916. * @param array $csr optional
  2917. * @access public
  2918. * @return array
  2919. */
  2920. public function getAttributes($csr = null)
  2921. {
  2922. if (empty($csr)) {
  2923. $csr = $this->currentCert;
  2924. }
  2925. $attributes = $this->subArray($csr, 'certificationRequestInfo/attributes');
  2926. $attrs = [];
  2927. if (is_array($attributes)) {
  2928. foreach ($attributes as $attribute) {
  2929. $attrs[] = $attribute['type'];
  2930. }
  2931. }
  2932. return $attrs;
  2933. }
  2934. /**
  2935. * Set a CSR attribute
  2936. *
  2937. * @param string $id
  2938. * @param mixed $value
  2939. * @param bool $disposition optional
  2940. * @access public
  2941. * @return bool
  2942. */
  2943. public function setAttribute($id, $value, $disposition = self::ATTR_ALL)
  2944. {
  2945. $attributes = &$this->subArray($this->currentCert, 'certificationRequestInfo/attributes', true);
  2946. if (!is_array($attributes)) {
  2947. return false;
  2948. }
  2949. switch ($disposition) {
  2950. case self::ATTR_REPLACE:
  2951. $disposition = self::ATTR_APPEND;
  2952. case self::ATTR_ALL:
  2953. $this->removeAttribute($id);
  2954. break;
  2955. }
  2956. foreach ($attributes as $key => $attribute) {
  2957. if ($attribute['type'] == $id) {
  2958. $n = count($attribute['value']);
  2959. switch (true) {
  2960. case $disposition == self::ATTR_APPEND:
  2961. $last = $key;
  2962. break;
  2963. case $disposition >= $n:
  2964. $disposition -= $n;
  2965. break;
  2966. default:
  2967. $attributes[$key]['value'][$disposition] = $value;
  2968. return true;
  2969. }
  2970. }
  2971. }
  2972. switch (true) {
  2973. case $disposition >= 0:
  2974. return false;
  2975. case isset($last):
  2976. $attributes[$last]['value'][] = $value;
  2977. break;
  2978. default:
  2979. $attributes[] = ['type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: [$value]];
  2980. break;
  2981. }
  2982. return true;
  2983. }
  2984. /**
  2985. * Sets the subject key identifier
  2986. *
  2987. * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
  2988. *
  2989. * @param string $value
  2990. * @access public
  2991. */
  2992. public function setKeyIdentifier($value)
  2993. {
  2994. if (empty($value)) {
  2995. unset($this->currentKeyIdentifier);
  2996. } else {
  2997. $this->currentKeyIdentifier = $value;
  2998. }
  2999. }
  3000. /**
  3001. * Compute a public key identifier.
  3002. *
  3003. * Although key identifiers may be set to any unique value, this function
  3004. * computes key identifiers from public key according to the two
  3005. * recommended methods (4.2.1.2 RFC 3280).
  3006. * Highly polymorphic: try to accept all possible forms of key:
  3007. * - Key object
  3008. * - \phpseclib\File\X509 object with public or private key defined
  3009. * - Certificate or CSR array
  3010. * - \phpseclib\File\ASN1\Element object
  3011. * - PEM or DER string
  3012. *
  3013. * @param mixed $key optional
  3014. * @param int $method optional
  3015. * @access public
  3016. * @return string binary key identifier
  3017. */
  3018. public function computeKeyIdentifier($key = null, $method = 1)
  3019. {
  3020. if (is_null($key)) {
  3021. $key = $this;
  3022. }
  3023. switch (true) {
  3024. case is_string($key):
  3025. break;
  3026. case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
  3027. return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method);
  3028. case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
  3029. return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method);
  3030. case !is_object($key):
  3031. return false;
  3032. case $key instanceof Element:
  3033. // Assume the element is a bitstring-packed key.
  3034. $decoded = ASN1::decodeBER($key->element);
  3035. if (empty($decoded)) {
  3036. return false;
  3037. }
  3038. $raw = ASN1::asn1map($decoded[0], ['type' => ASN1::TYPE_BIT_STRING]);
  3039. if (empty($raw)) {
  3040. return false;
  3041. }
  3042. // If the key is private, compute identifier from its corresponding public key.
  3043. $key = new RSA();
  3044. if (!$key->load($raw)) {
  3045. return false; // Not an unencrypted RSA key.
  3046. }
  3047. if ($key->getPrivateKey() !== false) { // If private.
  3048. return $this->computeKeyIdentifier($key, $method);
  3049. }
  3050. $key = $raw; // Is a public key.
  3051. break;
  3052. case $key instanceof X509:
  3053. if (isset($key->publicKey)) {
  3054. return $this->computeKeyIdentifier($key->publicKey, $method);
  3055. }
  3056. if (isset($key->privateKey)) {
  3057. return $this->computeKeyIdentifier($key->privateKey, $method);
  3058. }
  3059. if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) {
  3060. return $this->computeKeyIdentifier($key->currentCert, $method);
  3061. }
  3062. return false;
  3063. default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA).
  3064. $key = $key->getPublicKey('PKCS1');
  3065. break;
  3066. }
  3067. // If in PEM format, convert to binary.
  3068. $key = ASN1::extractBER($key);
  3069. // Now we have the key string: compute its sha-1 sum.
  3070. $hash = new Hash('sha1');
  3071. $hash = $hash->hash($key);
  3072. if ($method == 2) {
  3073. $hash = substr($hash, -8);
  3074. $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40);
  3075. }
  3076. return $hash;
  3077. }
  3078. /**
  3079. * Format a public key as appropriate
  3080. *
  3081. * @access private
  3082. * @return array
  3083. */
  3084. private function formatSubjectPublicKey()
  3085. {
  3086. if ($this->publicKey instanceof RSA) {
  3087. // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason.
  3088. // the former is a good example of how to do fuzzing on the public key
  3089. //return new Element(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey()));
  3090. return [
  3091. 'algorithm' => array('algorithm' => 'rsaEncryption'),
  3092. 'subjectPublicKey' => $this->publicKey->getPublicKey('PKCS1')
  3093. ];
  3094. }
  3095. return false;
  3096. }
  3097. /**
  3098. * Set the domain name's which the cert is to be valid for
  3099. *
  3100. * @access public
  3101. * @return array
  3102. */
  3103. public function setDomain()
  3104. {
  3105. $this->domains = func_get_args();
  3106. $this->removeDNProp('id-at-commonName');
  3107. $this->setDNProp('id-at-commonName', $this->domains[0]);
  3108. }
  3109. /**
  3110. * Set the IP Addresses's which the cert is to be valid for
  3111. *
  3112. * @access public
  3113. * @param string $ipAddress optional
  3114. */
  3115. public function setIPAddress()
  3116. {
  3117. $this->ipAddresses = func_get_args();
  3118. /*
  3119. if (!isset($this->domains)) {
  3120. $this->removeDNProp('id-at-commonName');
  3121. $this->setDNProp('id-at-commonName', $this->ipAddresses[0]);
  3122. }
  3123. */
  3124. }
  3125. /**
  3126. * Helper function to build domain array
  3127. *
  3128. * @access private
  3129. * @param string $domain
  3130. * @return array
  3131. */
  3132. private function dnsName($domain)
  3133. {
  3134. return ['dNSName' => $domain];
  3135. }
  3136. /**
  3137. * Helper function to build IP Address array
  3138. *
  3139. * (IPv6 is not currently supported)
  3140. *
  3141. * @access private
  3142. * @param string $address
  3143. * @return array
  3144. */
  3145. private function iPAddress($address)
  3146. {
  3147. return ['iPAddress' => $address];
  3148. }
  3149. /**
  3150. * Get the index of a revoked certificate.
  3151. *
  3152. * @param array $rclist
  3153. * @param string $serial
  3154. * @param bool $create optional
  3155. * @access private
  3156. * @return int|false
  3157. */
  3158. private function revokedCertificate(&$rclist, $serial, $create = false)
  3159. {
  3160. $serial = new BigInteger($serial);
  3161. foreach ($rclist as $i => $rc) {
  3162. if (!($serial->compare($rc['userCertificate']))) {
  3163. return $i;
  3164. }
  3165. }
  3166. if (!$create) {
  3167. return false;
  3168. }
  3169. $i = count($rclist);
  3170. $rclist[] = ['userCertificate' => $serial,
  3171. 'revocationDate' => $this->timeField(@date('D, d M Y H:i:s O'))];
  3172. return $i;
  3173. }
  3174. /**
  3175. * Revoke a certificate.
  3176. *
  3177. * @param string $serial
  3178. * @param string $date optional
  3179. * @access public
  3180. * @return bool
  3181. */
  3182. public function revoke($serial, $date = null)
  3183. {
  3184. if (isset($this->currentCert['tbsCertList'])) {
  3185. if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
  3186. if ($this->revokedCertificate($rclist, $serial) === false) { // If not yet revoked
  3187. if (($i = $this->revokedCertificate($rclist, $serial, true)) !== false) {
  3188. if (!empty($date)) {
  3189. $rclist[$i]['revocationDate'] = $this->timeField($date);
  3190. }
  3191. return true;
  3192. }
  3193. }
  3194. }
  3195. }
  3196. return false;
  3197. }
  3198. /**
  3199. * Unrevoke a certificate.
  3200. *
  3201. * @param string $serial
  3202. * @access public
  3203. * @return bool
  3204. */
  3205. public function unrevoke($serial)
  3206. {
  3207. if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
  3208. if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
  3209. unset($rclist[$i]);
  3210. $rclist = array_values($rclist);
  3211. return true;
  3212. }
  3213. }
  3214. return false;
  3215. }
  3216. /**
  3217. * Get a revoked certificate.
  3218. *
  3219. * @param string $serial
  3220. * @access public
  3221. * @return mixed
  3222. */
  3223. public function getRevoked($serial)
  3224. {
  3225. if (is_array($rclist = $this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
  3226. if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
  3227. return $rclist[$i];
  3228. }
  3229. }
  3230. return false;
  3231. }
  3232. /**
  3233. * List revoked certificates
  3234. *
  3235. * @param array $crl optional
  3236. * @access public
  3237. * @return array
  3238. */
  3239. public function listRevoked($crl = null)
  3240. {
  3241. if (!isset($crl)) {
  3242. $crl = $this->currentCert;
  3243. }
  3244. if (!isset($crl['tbsCertList'])) {
  3245. return false;
  3246. }
  3247. $result = [];
  3248. if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
  3249. foreach ($rclist as $rc) {
  3250. $result[] = $rc['userCertificate']->toString();
  3251. }
  3252. }
  3253. return $result;
  3254. }
  3255. /**
  3256. * Remove a Revoked Certificate Extension
  3257. *
  3258. * @param string $serial
  3259. * @param string $id
  3260. * @access public
  3261. * @return bool
  3262. */
  3263. public function removeRevokedCertificateExtension($serial, $id)
  3264. {
  3265. if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
  3266. if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
  3267. return $this->removeExtensionHelper($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
  3268. }
  3269. }
  3270. return false;
  3271. }
  3272. /**
  3273. * Get a Revoked Certificate Extension
  3274. *
  3275. * Returns the extension if it exists and false if not
  3276. *
  3277. * @param string $serial
  3278. * @param string $id
  3279. * @param array $crl optional
  3280. * @access public
  3281. * @return mixed
  3282. */
  3283. public function getRevokedCertificateExtension($serial, $id, $crl = null)
  3284. {
  3285. if (!isset($crl)) {
  3286. $crl = $this->currentCert;
  3287. }
  3288. if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
  3289. if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
  3290. return $this->getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
  3291. }
  3292. }
  3293. return false;
  3294. }
  3295. /**
  3296. * Returns a list of all extensions in use for a given revoked certificate
  3297. *
  3298. * @param string $serial
  3299. * @param array $crl optional
  3300. * @access public
  3301. * @return array
  3302. */
  3303. public function getRevokedCertificateExtensions($serial, $crl = null)
  3304. {
  3305. if (!isset($crl)) {
  3306. $crl = $this->currentCert;
  3307. }
  3308. if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
  3309. if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
  3310. return $this->getExtensionsHelper($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
  3311. }
  3312. }
  3313. return false;
  3314. }
  3315. /**
  3316. * Set a Revoked Certificate Extension
  3317. *
  3318. * @param string $serial
  3319. * @param string $id
  3320. * @param mixed $value
  3321. * @param bool $critical optional
  3322. * @param bool $replace optional
  3323. * @access public
  3324. * @return bool
  3325. */
  3326. public function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
  3327. {
  3328. if (isset($this->currentCert['tbsCertList'])) {
  3329. if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
  3330. if (($i = $this->revokedCertificate($rclist, $serial, true)) !== false) {
  3331. return $this->setExtensionHelper($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
  3332. }
  3333. }
  3334. }
  3335. return false;
  3336. }
  3337. }