Skip to content

Commit a3275b3

Browse files
authored
Add support for hashing unique part of subject-id (#2006)
* Add support for hashing unique part of subject-id * Add unit test * Update docs
1 parent ebc31fc commit a3275b3

4 files changed

Lines changed: 92 additions & 27 deletions

File tree

modules/saml/docs/authproc_subjectid.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ This filter will take an attribute and a scope as input and transforms this
1010
into a scoped identifier that is globally unique for a given user.
1111

1212
**Note**
13-
If privacy is of your concern, you may want to use the PairwiseID-filter
14-
instead.
13+
If privacy is of your concern, you may want to hash the unique part of the subject-id. Hashing also ensures
14+
that the output is compliant with the specification. If you do not want to hash the unique part, you _have_
15+
to ensure that the `identifyingAttribute` always contains a value that is in line with the specification!
16+
17+
If you are also worried about correlation of IDs between diffent SP's, use the PairwiseID-filter instead.
1518

1619
**Note**
1720
Since the subject-id is specified as single-value attribute, only the first
@@ -26,6 +29,7 @@ Examples
2629
'class' => 'saml:SubjectID',
2730
'identifyingAttribute' => 'uid',
2831
'scopeAttribute' => 'scope',
32+
'hashed' => true,
2933
],
3034
],
3135
```

modules/saml/src/Auth/Process/PairwiseID.php

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44

55
namespace SimpleSAML\Module\saml\Auth\Process;
66

7-
use SimpleSAML\{Auth, Utils};
87
use SimpleSAML\SAML2\Constants as C;
98

10-
use function hash_hmac;
119
use function strtolower;
1210

1311
/**
@@ -44,11 +42,6 @@ class PairwiseID extends SubjectID
4442
*/
4543
public const NAME = 'PairwiseID';
4644

47-
/**
48-
* @var \SimpleSAML\Utils\Config
49-
*/
50-
protected Utils\Config $configUtils;
51-
5245

5346
/**
5447
* Initialize this filter.
@@ -59,8 +52,6 @@ class PairwiseID extends SubjectID
5952
public function __construct(array &$config, $reserved)
6053
{
6154
parent::__construct($config, $reserved);
62-
63-
$this->configUtils = new Utils\Config();
6455
}
6556

6657

@@ -87,23 +78,11 @@ public function process(array &$state): void
8778
}
8879

8980
// Calculate hash
90-
$salt = $this->configUtils->getSecretSalt();
91-
$hash = hash_hmac('sha256', $userID . '|' . $sp_entityid, $salt, false);
81+
$hash = $this->calculateHash($userID . '|' . $sp_entityid);
9282

93-
$value = $hash . '@' . strtolower($scope);
83+
$value = strtolower($hash . '@' . $scope);
9484
$this->validateGeneratedIdentifier($value);
9585

9686
$state['Attributes'][C::ATTR_PAIRWISE_ID] = [$value];
9787
}
98-
99-
100-
/**
101-
* Inject the \SimpleSAML\Utils\Config dependency.
102-
*
103-
* @param \SimpleSAML\Utils\Config $configUtils
104-
*/
105-
public function setConfigUtils(Utils\Config $configUtils): void
106-
{
107-
$this->configUtils = $configUtils;
108-
}
10988
}

modules/saml/src/Auth/Process/SubjectID.php

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace SimpleSAML\Module\saml\Auth\Process;
66

7-
use SimpleSAML\{Auth, Logger};
7+
use SimpleSAML\{Auth, Logger, Utils};
88
use SimpleSAML\Assert\Assert;
99
use SimpleSAML\SAML2\Constants as C;
1010
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
1111

1212
use function array_key_exists;
1313
use function explode;
14+
use function hash_hmac;
1415
use function preg_match;
1516
use function strpos;
1617
use function strtolower;
@@ -85,6 +86,18 @@ class SubjectID extends Auth\ProcessingFilter
8586
*/
8687
protected string $scopeAttribute;
8788

89+
/**
90+
* Whether the unique part of the subject id must be hashed
91+
*
92+
* @var bool
93+
*/
94+
private bool $hashed = false;
95+
96+
/**
97+
* @var \SimpleSAML\Utils\Config
98+
*/
99+
protected Utils\Config $configUtils;
100+
88101
/**
89102
* @var \SimpleSAML\Logger|string
90103
* @psalm-var \SimpleSAML\Logger|class-string
@@ -109,6 +122,13 @@ public function __construct(array &$config, $reserved)
109122

110123
$this->identifyingAttribute = $config['identifyingAttribute'];
111124
$this->scopeAttribute = $config['scopeAttribute'];
125+
126+
if (array_key_exists('hashed', $config)) {
127+
Assert::boolean($config['hashed']);
128+
$this->hashed = $config['hashed'];
129+
}
130+
131+
$this->configUtils = new Utils\Config();
112132
}
113133

114134

@@ -127,7 +147,12 @@ public function process(array &$state): void
127147
return;
128148
}
129149

130-
$value = strtolower($userID . '@' . $scope);
150+
if ($this->hashed === true) {
151+
$value = strtolower($this->calculateHash($userID) . '@' . $scope);
152+
} else {
153+
$value = strtolower($userID . '@' . $scope);
154+
}
155+
131156
$this->validateGeneratedIdentifier($value);
132157

133158
$state['Attributes'][C::ATTR_SUBJECT_ID] = [$value];
@@ -232,6 +257,16 @@ protected function validateGeneratedIdentifier(string $value): void
232257
}
233258

234259

260+
/**
261+
* Calculate the hash for the unique part of the identifier.
262+
*/
263+
protected function calculateHash(string $input): string
264+
{
265+
$salt = $this->configUtils->getSecretSalt();
266+
return hash_hmac('sha256', $input, $salt, false);
267+
}
268+
269+
235270
/**
236271
* Inject the \SimpleSAML\Logger dependency.
237272
*
@@ -241,4 +276,15 @@ public function setLogger(Logger $logger): void
241276
{
242277
$this->logger = $logger;
243278
}
279+
280+
281+
/**
282+
* Inject the \SimpleSAML\Utils\Config dependency.
283+
*
284+
* @param \SimpleSAML\Utils\Config $configUtils
285+
*/
286+
public function setConfigUtils(Utils\Config $configUtils): void
287+
{
288+
$this->configUtils = $configUtils;
289+
}
244290
}

tests/modules/saml/src/Auth/Process/SubjectIDTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class SubjectIDTest extends TestCase
2121
/** @var \SimpleSAML\Configuration */
2222
protected Configuration $config;
2323

24+
/** @var \SimpleSAML\Utils\Config */
25+
protected static Utils\Config $configUtils;
26+
2427
/** @var \SimpleSAML\Logger */
2528
protected static Logger $logger;
2629

@@ -32,6 +35,14 @@ protected function setUp(): void
3235
{
3336
parent::setUp();
3437

38+
self::$configUtils = new class () extends Utils\Config {
39+
public function getSecretSalt(): string
40+
{
41+
// stub
42+
return 'secretsalt';
43+
}
44+
};
45+
3546
self::$logger = new class () extends Logger {
3647
public static function warning(string $string): void
3748
{
@@ -52,8 +63,10 @@ public static function warning(string $string): void
5263
private static function processFilter(array $config, array $request): array
5364
{
5465
$filter = new SubjectID($config, null);
66+
$filter->setConfigUtils(self::$configUtils);
5567
$filter->setLogger(self::$logger);
5668
$filter->process($request);
69+
5770
return $request;
5871
}
5972

@@ -78,6 +91,29 @@ public function testBasic(): void
7891
}
7992

8093

94+
/**
95+
* Test the most basic functionality with hash
96+
*/
97+
public function testBasicWithHash(): void
98+
{
99+
$config = ['identifyingAttribute' => 'uid', 'scopeAttribute' => 'scope', 'hashed' => true];
100+
$request = [
101+
'Attributes' => ['uid' => ['u=se-r2'], 'scope' => ['ex-ample.org']],
102+
];
103+
$result = self::processFilter($config, $request);
104+
$attributes = $result['Attributes'];
105+
$this->assertArrayHasKey(C::ATTR_SUBJECT_ID, $attributes);
106+
$this->assertMatchesRegularExpression(
107+
SubjectID::SPEC_PATTERN,
108+
$attributes[C::ATTR_SUBJECT_ID][0]
109+
);
110+
$this->assertEquals(
111+
'42738d01c2a66c449d010962e79da27c608c5244fd9ec311ed7c013517abf7ee@ex-ample.org',
112+
$attributes[C::ATTR_SUBJECT_ID][0],
113+
);
114+
}
115+
116+
81117
/**
82118
* Test the most basic functionality, but with a scoped scope-attribute
83119
*/

0 commit comments

Comments
 (0)