LayeredParameterizedPassword.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. <?php
  2. /**
  3. * Implements the LayeredParameterizedPassword class for the MediaWiki software.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. */
  22. /**
  23. * This password hash type layers one or more parameterized password types
  24. * on top of each other.
  25. *
  26. * The underlying types must be parameterized. This wrapping type accumulates
  27. * all the parameters and arguments from each hash and then passes the hash of
  28. * the last layer as the password for the next layer.
  29. *
  30. * @since 1.24
  31. */
  32. class LayeredParameterizedPassword extends ParameterizedPassword {
  33. protected function getDelimiter() {
  34. return '!';
  35. }
  36. protected function getDefaultParams() {
  37. $params = [];
  38. foreach ( $this->config['types'] as $type ) {
  39. $passObj = $this->factory->newFromType( $type );
  40. if ( !$passObj instanceof ParameterizedPassword ) {
  41. throw new MWException( 'Underlying type must be a parameterized password.' );
  42. } elseif ( $passObj->getDelimiter() === $this->getDelimiter() ) {
  43. throw new MWException( 'Underlying type cannot use same delimiter as encapsulating type.' );
  44. }
  45. $params[] = implode( $passObj->getDelimiter(), $passObj->getDefaultParams() );
  46. }
  47. return $params;
  48. }
  49. public function crypt( $password ) {
  50. $lastHash = $password;
  51. foreach ( $this->config['types'] as $i => $type ) {
  52. // Construct pseudo-hash based on params and arguments
  53. /** @var ParameterizedPassword $passObj */
  54. $passObj = $this->factory->newFromType( $type );
  55. '@phan-var ParameterizedPassword $passObj';
  56. $params = '';
  57. $args = '';
  58. if ( $this->params[$i] !== '' ) {
  59. $params = $this->params[$i] . $passObj->getDelimiter();
  60. }
  61. if ( isset( $this->args[$i] ) && $this->args[$i] !== '' ) {
  62. $args = $this->args[$i] . $passObj->getDelimiter();
  63. }
  64. $existingHash = ":$type:" . $params . $args . $this->hash;
  65. // Hash the last hash with the next type in the layer
  66. $passObj = $this->factory->newFromCiphertext( $existingHash );
  67. '@phan-var ParameterizedPassword $passObj';
  68. $passObj->crypt( $lastHash );
  69. // Move over the params and args
  70. $this->params[$i] = implode( $passObj->getDelimiter(), $passObj->params );
  71. $this->args[$i] = implode( $passObj->getDelimiter(), $passObj->args );
  72. $lastHash = $passObj->hash;
  73. }
  74. $this->hash = $lastHash;
  75. }
  76. /**
  77. * Finish the hashing of a partially hashed layered hash
  78. *
  79. * Given a password hash that is hashed using the first layer of this object's
  80. * configuration, perform the remaining layers of password hashing in order to
  81. * get an updated hash with all the layers.
  82. *
  83. * @param ParameterizedPassword $passObj Password hash of the first layer
  84. *
  85. * @throws MWException If the first parameter is not of the correct type
  86. */
  87. public function partialCrypt( ParameterizedPassword $passObj ) {
  88. $type = $passObj->config['type'];
  89. if ( $type !== $this->config['types'][0] ) {
  90. throw new MWException( 'Only a hash in the first layer can be finished.' );
  91. }
  92. // Gather info from the existing hash
  93. $this->params[0] = implode( $passObj->getDelimiter(), $passObj->params );
  94. $this->args[0] = implode( $passObj->getDelimiter(), $passObj->args );
  95. $lastHash = $passObj->hash;
  96. // Layer the remaining types
  97. foreach ( $this->config['types'] as $i => $type ) {
  98. if ( $i == 0 ) {
  99. continue;
  100. }
  101. // Construct pseudo-hash based on params and arguments
  102. /** @var ParameterizedPassword $passObj */
  103. $passObj = $this->factory->newFromType( $type );
  104. '@phan-var ParameterizedPassword $passObj';
  105. $params = '';
  106. $args = '';
  107. if ( $this->params[$i] !== '' ) {
  108. $params = $this->params[$i] . $passObj->getDelimiter();
  109. }
  110. if ( isset( $this->args[$i] ) && $this->args[$i] !== '' ) {
  111. $args = $this->args[$i] . $passObj->getDelimiter();
  112. }
  113. $existingHash = ":$type:" . $params . $args . $this->hash;
  114. // Hash the last hash with the next type in the layer
  115. $passObj = $this->factory->newFromCiphertext( $existingHash );
  116. '@phan-var ParameterizedPassword $passObj';
  117. $passObj->crypt( $lastHash );
  118. // Move over the params and args
  119. $this->params[$i] = implode( $passObj->getDelimiter(), $passObj->params );
  120. $this->args[$i] = implode( $passObj->getDelimiter(), $passObj->args );
  121. $lastHash = $passObj->hash;
  122. }
  123. $this->hash = $lastHash;
  124. }
  125. }