Skip to content

Commit bccd1a0

Browse files
committed
revert tokenservice changes
1 parent b3f0c47 commit bccd1a0

1 file changed

Lines changed: 157 additions & 183 deletions

File tree

lib/MagentaBearer/TokenService.php

Lines changed: 157 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -24,206 +24,180 @@
2424

2525
namespace OCA\UserOIDC\MagentaBearer;
2626

27+
use Jose\Component\Core\Algorithm;
2728
use Jose\Component\Core\AlgorithmManager;
29+
2830
use Jose\Component\Core\JWK;
2931
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
3032
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW;
33+
3134
use Jose\Component\Encryption\Algorithm\KeyEncryption\PBES2HS512A256KW;
3235
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256;
3336
use Jose\Component\Encryption\Compression\CompressionMethodManager;
37+
3438
use Jose\Component\Encryption\Compression\Deflate;
3539
use Jose\Component\Encryption\JWEDecrypter;
36-
use Jose\Component\Encryption\Serializer\JWESerializerManager;
37-
use Jose\Component\Encryption\Serializer\CompactSerializer as JWECompactSerializer;
3840
use Jose\Component\Signature\Algorithm\HS256;
3941
use Jose\Component\Signature\Algorithm\HS384;
42+
4043
use Jose\Component\Signature\Algorithm\HS512;
4144
use Jose\Component\Signature\JWS;
4245
use Jose\Component\Signature\JWSVerifier;
43-
use Jose\Component\Signature\Serializer\JWSSerializerManager;
44-
use Jose\Component\Signature\Serializer\CompactSerializer as JWSCompactSerializer;
4546
use OCP\AppFramework\Utility\ITimeFactory;
4647
use Psr\Log\LoggerInterface;
4748

48-
/**
49-
* Token service for handling Magenta/SAM3 bearer tokens (decrypt/verify/claims).
50-
*/
5149
class TokenService {
5250

53-
/** @var LoggerInterface */
54-
private $logger;
55-
56-
/** @var ITimeFactory */
57-
private $timeFactory;
58-
59-
/** @var DiscoveryService|null */
60-
private $discoveryService;
61-
62-
/** @var JWEDecrypter */
63-
private $jweDecrypter;
64-
65-
/** @var JWESerializerManager */
66-
private $encryptionSerializerManager;
67-
68-
/** @var JWSVerifier */
69-
private $jwsVerifier;
70-
71-
/** @var JWSSerializerManager */
72-
private $serializerManager;
73-
74-
public function __construct(LoggerInterface $logger, ITimeFactory $timeFactory, ?DiscoveryService $discoveryService = null) {
75-
$this->logger = $logger;
76-
$this->timeFactory = $timeFactory;
77-
$this->discoveryService = $discoveryService;
78-
79-
// Key encryption algorithms manager
80-
$keyEncryptionAlgorithmManager = new AlgorithmManager([
81-
new PBES2HS512A256KW(),
82-
new RSAOAEP256(),
83-
new ECDHESA256KW(),
84-
]);
85-
86-
// Content encryption algorithm manager
87-
$contentEncryptionAlgorithmManager = new AlgorithmManager([
88-
new A256CBCHS512(),
89-
]);
90-
91-
// Compression manager
92-
$compressionMethodManager = new CompressionMethodManager([
93-
new Deflate(),
94-
]);
95-
96-
// Signature algorithms manager
97-
$signatureAlgorithmManager = new AlgorithmManager([
98-
new HS256(),
99-
new HS384(),
100-
new HS512(),
101-
]);
102-
103-
// JWE decrypter
104-
$this->jweDecrypter = new JWEDecrypter(
105-
$keyEncryptionAlgorithmManager,
106-
$contentEncryptionAlgorithmManager,
107-
$compressionMethodManager
108-
);
109-
110-
// JWESerializerManager
111-
$this->encryptionSerializerManager = new JWESerializerManager([
112-
new JWECompactSerializer(),
113-
]);
114-
115-
// JWS verifier
116-
$this->jwsVerifier = new JWSVerifier($signatureAlgorithmManager);
117-
118-
// JWSSerializerManager
119-
$this->serializerManager = new JWSSerializerManager([
120-
new JWSCompactSerializer(),
121-
]);
122-
}
123-
124-
/**
125-
* Implement JOSE decryption for SAM3 tokens
126-
*
127-
* @param string $rawToken
128-
* @param string $decryptKey
129-
* @return JWS
130-
* @throws InvalidTokenException
131-
*/
132-
public function decryptToken(string $rawToken, string $decryptKey) : JWS {
133-
$numSegments = substr_count($rawToken, '.') + 1;
134-
$this->logger->debug('Bearer access token(segments=' . $numSegments . ')=' . $rawToken);
135-
136-
if ($numSegments > 3) {
137-
$clientSecret = new JWK([
138-
'kty' => 'oct',
139-
'k' => $decryptKey,
140-
]);
141-
142-
$jwe = $this->encryptionSerializerManager->unserialize($rawToken);
143-
144-
if ($this->jweDecrypter->decryptUsingKey($jwe, $clientSecret, 0)) {
145-
return $this->serializerManager->unserialize($jwe->getPayload());
146-
}
147-
148-
throw new InvalidTokenException('Unknown bearer encryption format');
149-
}
150-
151-
return $this->serializerManager->unserialize($rawToken);
152-
}
153-
154-
/**
155-
* Decode the token payload into an object and remap claims
156-
*
157-
* @param JWS $decodedToken
158-
* @return object
159-
*/
160-
public function decode(JWS $decodedToken) : object {
161-
$this->logger->debug('Telekom SAM3 access token: ' . $decodedToken->getPayload());
162-
$samContent = json_decode($decodedToken->getPayload(), false);
163-
164-
$claimArray = $samContent->{'urn:telekom.com:idm:at:attributes'} ?? null;
165-
if (is_array($claimArray)) {
166-
foreach ($claimArray as $claimKeyValue) {
167-
if (isset($claimKeyValue->name)) {
168-
$samContent->{'urn:telekom.com:' . $claimKeyValue->name} = $claimKeyValue->value ?? null;
169-
}
170-
}
171-
unset($samContent->{'urn:telekom.com:idm:at:attributes'});
172-
}
173-
174-
$this->logger->debug('Adapted OpenID-like token; ' . json_encode($samContent));
175-
return $samContent;
176-
}
177-
178-
/**
179-
* Verify the JWS signature using the given symmetric key
180-
*
181-
* @param JWS $decodedToken
182-
* @param string $signKey
183-
* @return void
184-
* @throws SignatureException
185-
*/
186-
public function verifySignature(JWS $decodedToken, string $signKey): void {
187-
$accessSecret = new JWK([
188-
'kty' => 'oct',
189-
'k' => $signKey,
190-
]);
191-
192-
if (!$this->jwsVerifier->verifyWithKey($decodedToken, $accessSecret, 0)) {
193-
throw new SignatureException('Invalid Signature');
194-
}
195-
}
196-
197-
/**
198-
* Verify standard claims (nbf/iat/exp/aud)
199-
*
200-
* @param object $claims
201-
* @param array<int,string> $audiences
202-
* @param int $leeway
203-
* @return void
204-
* @throws InvalidTokenException
205-
*/
206-
public function verifyClaims(object $claims, array $audiences = [], int $leeway = 60): void {
207-
$timestamp = $this->timeFactory->getTime();
208-
209-
if (isset($claims->nbf) && $claims->nbf > ($timestamp + $leeway)) {
210-
throw new InvalidTokenException(
211-
'Cannot handle token prior to ' . \date(\DATE_ATOM, (int)$claims->nbf)
212-
);
213-
}
214-
215-
if (isset($claims->iat) && $claims->iat > ($timestamp + $leeway)) {
216-
throw new InvalidTokenException(
217-
'Cannot handle token prior to ' . \date(\DATE_ATOM, (int)$claims->iat)
218-
);
219-
}
220-
221-
if (isset($claims->exp) && ($timestamp - $leeway) >= $claims->exp) {
222-
throw new InvalidTokenException('Expired token');
223-
}
224-
225-
if (empty(array_intersect((array)($claims->aud ?? []), $audiences))) {
226-
throw new InvalidTokenException('No acceptable audience in token.');
227-
}
228-
}
51+
/** @var LoggerInterface */
52+
private $logger;
53+
54+
/** @var ITimeFactory */
55+
private $timeFactory;
56+
57+
/** @var DiscoveryService */
58+
private $discoveryService;
59+
60+
public function __construct(LoggerInterface $logger,
61+
ITimeFactory $timeFactory) {
62+
$this->logger = $logger;
63+
$this->timeFactory = $timeFactory;
64+
65+
// The key encryption algorithm manager with the A256KW algorithm.
66+
$keyEncryptionAlgorithmManager = new AlgorithmManager([
67+
new PBES2HS512A256KW(),
68+
new RSAOAEP256(),
69+
new ECDHESA256KW() ]);
70+
71+
// The content encryption algorithm manager with the A256CBC-HS256 algorithm.
72+
$contentEncryptionAlgorithmManager = new AlgorithmManager([
73+
new A256CBCHS512(),
74+
]);
75+
76+
// The compression method manager with the DEF (Deflate) method.
77+
$compressionMethodManager = new CompressionMethodManager([
78+
new Deflate(),
79+
]);
80+
81+
$signatureAlgorithmManager = new AlgorithmManager([
82+
new HS256(),
83+
new HS384(),
84+
new HS512(),
85+
]);
86+
87+
// We instantiate our JWE Decrypter.
88+
$this->jweDecrypter = new JWEDecrypter(
89+
$keyEncryptionAlgorithmManager,
90+
$contentEncryptionAlgorithmManager,
91+
$compressionMethodManager
92+
);
93+
94+
// We try to load the token.
95+
$this->encryptionSerializerManager = new \Jose\Component\Encryption\Serializer\JWESerializerManager([
96+
new \Jose\Component\Encryption\Serializer\CompactSerializer(),
97+
]);
98+
99+
100+
// We instantiate our JWS Verifier.
101+
$this->jwsVerifier = new JWSVerifier(
102+
$signatureAlgorithmManager
103+
);
104+
105+
// The serializer manager. We only use the JWE Compact Serialization Mode.
106+
$this->serializerManager = new \Jose\Component\Signature\Serializer\JWSSerializerManager([
107+
new \Jose\Component\Signature\Serializer\CompactSerializer(),
108+
]);
109+
}
110+
111+
/**
112+
* Implement JOSE decryption for SAM3 tokens
113+
*/
114+
public function decryptToken(string $rawToken, string $decryptKey) : JWS {
115+
116+
// web-token library does not like underscores in headers, so replace them with - (which is valid in JWT)
117+
$numSegments = substr_count($rawToken, '.') + 1;
118+
$this->logger->debug('Bearer access token(segments=' . $numSegments . ')=' . $rawToken);
119+
if ($numSegments > 3) {
120+
// trusted authenticator and myself share the client secret,
121+
// so use it is used for encrypted web tokens
122+
$clientSecret = new JWK([
123+
'kty' => 'oct',
124+
'k' => $decryptKey
125+
]);
126+
127+
$jwe = $this->encryptionSerializerManager->unserialize($rawToken);
128+
129+
// We decrypt the token. This method does NOT check the header.
130+
if ($this->jweDecrypter->decryptUsingKey($jwe, $clientSecret, 0)) {
131+
return $this->serializerManager->unserialize($jwe->getPayload());
132+
} else {
133+
throw new InvalidTokenException('Unknown bearer encryption format');
134+
}
135+
} else {
136+
return $this->serializerManager->unserialize($rawToken);
137+
}
138+
}
139+
140+
/**
141+
* Get claims (even before verification to access e.g. aud standard field ...)
142+
* Transform them in a format compatible with id_token representation.
143+
*/
144+
public function decode(JWS $decodedToken) : object {
145+
$this->logger->debug('Telekom SAM3 access token: ' . $decodedToken->getPayload());
146+
$samContent = json_decode($decodedToken->getPayload(), false);
147+
148+
// remap all the custom claims
149+
// adapt into OpenId id_token format (as far as possible)
150+
$claimArray = $samContent->{'urn:telekom.com:idm:at:attributes'};
151+
foreach ($claimArray as $claimKeyValue) {
152+
$samContent->{'urn:telekom.com:' . $claimKeyValue->name} = $claimKeyValue->value;
153+
}
154+
unset($samContent->{'urn:telekom.com:idm:at:attributes'});
155+
156+
$this->logger->debug('Adapted OpenID-like token; ' . json_encode($samContent));
157+
return $samContent;
158+
}
159+
160+
161+
public function verifySignature(JWS $decodedToken, string $signKey) {
162+
$accessSecret = new JWK([
163+
'kty' => 'oct',
164+
'k' => $signKey
165+
]); // TODO: take the additional access key secret from settings
166+
167+
if (!$this->jwsVerifier->verifyWithKey($decodedToken, $accessSecret, 0)) {
168+
throw new SignatureException('Invalid Signature');
169+
}
170+
}
171+
172+
public function verifyClaims(object $claims, array $audiences = [], int $leeway = 60) {
173+
$timestamp = $this->timeFactory->getTime();
174+
175+
// Check the nbf if it is defined. This is the time that the
176+
// token can actually be used. If it's not yet that time, abort.
177+
if (isset($claims->nbf) && $claims->nbf > ($timestamp + $leeway)) {
178+
throw new InvalidTokenException(
179+
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $claims->nbf)
180+
);
181+
}
182+
183+
// Check that this token has been created before 'now'. This prevents
184+
// using tokens that have been created for later use (and haven't
185+
// correctly used the nbf claim).
186+
if (isset($claims->iat) && $claims->iat > ($timestamp + $leeway)) {
187+
throw new InvalidTokenException(
188+
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $claims->iat)
189+
);
190+
}
191+
192+
// Check if this token has expired.
193+
if (isset($claims->exp) && ($timestamp - $leeway) >= $claims->exp) {
194+
throw new InvalidTokenException('Expired token');
195+
}
196+
197+
// Check target audience (if given)
198+
// Check if this token has expired.
199+
if (empty(array_intersect($claims->aud, $audiences))) {
200+
throw new InvalidTokenException('No acceptable audience in token.');
201+
}
202+
}
229203
}

0 commit comments

Comments
 (0)