X509.php 172 KB

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