Skip to content

Commit 5b63740

Browse files
committed
API to get signed JWT for bound refresh token exchange
1 parent ca493d1 commit 5b63740

9 files changed

+1236
-23
lines changed

IdentityCore/IdentityCore.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,8 @@
733733
720B5B582DD58A7F00318FE5 /* MSIDJWECryptoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 720B5B572DD58A6A00318FE5 /* MSIDJWECryptoTests.m */; };
734734
720B5B592DD58A7F00318FE5 /* MSIDJWECryptoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 720B5B572DD58A6A00318FE5 /* MSIDJWECryptoTests.m */; };
735735
72371CEB27051CC200EF5475 /* MSIDKeyOperationUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 72371CEA27051CC200EF5475 /* MSIDKeyOperationUtilTest.m */; };
736+
724C9DD42E6906290039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 724C9DD32E6906270039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m */; };
737+
724C9DD52E6906290039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 724C9DD32E6906270039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m */; };
736738
728209C326FA9C9A00B5F018 /* MSIDBackgroundTaskData.m in Sources */ = {isa = PBXBuildFile; fileRef = 728209C226FA9C9A00B5F018 /* MSIDBackgroundTaskData.m */; };
737739
728209C926FE94D800B5F018 /* MSIDJwtAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = 728209C826FE94D800B5F018 /* MSIDJwtAlgorithm.m */; };
738740
728209CA26FE94D800B5F018 /* MSIDJwtAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = 728209C826FE94D800B5F018 /* MSIDJwtAlgorithm.m */; };
@@ -2664,6 +2666,7 @@
26642666
720B5B542DD57D6600318FE5 /* MSIDEcdhApv.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDEcdhApv.m; sourceTree = "<group>"; };
26652667
720B5B572DD58A6A00318FE5 /* MSIDJWECryptoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDJWECryptoTests.m; sourceTree = "<group>"; };
26662668
72371CEA27051CC200EF5475 /* MSIDKeyOperationUtilTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDKeyOperationUtilTest.m; sourceTree = "<group>"; };
2669+
724C9DD32E6906270039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBoundRefreshTokenRedemptionTests.m; sourceTree = "<group>"; };
26672670
728209C126FA9C9A00B5F018 /* MSIDBackgroundTaskData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDBackgroundTaskData.h; sourceTree = "<group>"; };
26682671
728209C226FA9C9A00B5F018 /* MSIDBackgroundTaskData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBackgroundTaskData.m; sourceTree = "<group>"; };
26692672
728209C826FE94D800B5F018 /* MSIDJwtAlgorithm.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDJwtAlgorithm.m; sourceTree = "<group>"; };
@@ -5748,6 +5751,7 @@
57485751
D6DA89731FBA6A4E004C56C7 /* tests */ = {
57495752
isa = PBXGroup;
57505753
children = (
5754+
724C9DD32E6906270039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m */,
57515755
72C764F82E09CFA400043AB1 /* MSIDBoundRefreshTokenTests.m */,
57525756
720B5B572DD58A6A00318FE5 /* MSIDJWECryptoTests.m */,
57535757
729357F22DDBD3F60001D03C /* MSIDNonceTokenRequestTest.m */,
@@ -7138,6 +7142,7 @@
71387142
2347D6692D5453A400372D20 /* MSIDSwitchBrowserOperationTest.swift in Sources */,
71397143
23419F82239B36F500EA78C5 /* MSIDAccountIdentifierTests.m in Sources */,
71407144
589BDB1D2718CD7D00BF3799 /* MSIDBrokerOperationGetSsoCookiesRequestTests.m in Sources */,
7145+
724C9DD52E6906290039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m in Sources */,
71417146
B210F42E1FDDE6A5005A8F76 /* MSIDJsonObjectTests.m in Sources */,
71427147
B2E97FB32914CC4500AFD558 /* MSIDBrokerNativeAppOperationResponseTests.m in Sources */,
71437148
B29F7805213DFA5600D61FC8 /* MSIDErrorTests.m in Sources */,
@@ -7773,6 +7778,7 @@
77737778
96CD652A20C885E2004813EE /* MSIDWebviewFactoryTests.m in Sources */,
77747779
239DF9C220E04BC9002D428B /* MSIDB2CAuthorityTests.m in Sources */,
77757780
B281B339226BBB1C009619AB /* MSIDOAuthRequestConfiguratorTests.m in Sources */,
7781+
724C9DD42E6906290039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m in Sources */,
77767782
B2808005204CB2A700944D89 /* MSIDAADV1TokenResponseTests.m in Sources */,
77777783
23419F64239896E500EA78C5 /* MSIDBrokerOperationResponseTests.m in Sources */,
77787784
D6D9A4BD1FBE712900EFA430 /* MSIDURLExtensionsTests.m in Sources */,

IdentityCore/src/MSIDOAuth2Constants.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,5 @@ extern NSString *const MSID_CCS_REQUEST_ID_RESPONSE;
179179
extern NSString *const MSID_CCS_REQUEST_SEQUENCE_KEY;
180180
extern NSString *const MSID_CCS_REQUEST_SEQUENCE_RESPONSE;
181181
extern NSString *const MSID_BOUND_DEVICE_ID_CACHE_KEY;
182+
extern NSString *const MSID_MSAL_CLIENT_APV_PREFIX;
183+
extern NSString *const MSID_BOUND_REFRESH_TOKEN_EXCHANGE;

IdentityCore/src/MSIDOAuth2Constants.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,6 @@
179179
NSString *const MSID_CCS_REQUEST_SEQUENCE_KEY = @"x-ms-srs";
180180
NSString *const MSID_CCS_REQUEST_SEQUENCE_RESPONSE = @"ccs-request-sequence";
181181

182+
NSString *const MSID_BOUND_REFRESH_TOKEN_EXCHANGE = @"bound_rt_exchange";
182183
NSString *const MSID_BOUND_DEVICE_ID_CACHE_KEY = @"bound_device_id";
184+
NSString *const MSID_MSAL_CLIENT_APV_PREFIX = @"MsalClient";

IdentityCore/src/oauth2/token/MSIDBoundRefreshToken+Redemption.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
3737
- (NSString *) getTokenRedemptionJwtForTenantId: (nullable NSString *)tenantId
3838
tokenRedemptionParameters: (MSIDBoundRefreshTokenRedemptionParameters *)requestParameters
3939
context:(id<MSIDRequestContext> _Nullable)context
40-
jweCrypto: (NSDictionary * __autoreleasing *)jweCrypto
40+
jweCrypto: (NSDictionary * __nonnull __autoreleasing *__nonnull)jweCrypto
4141
error: (NSError * __autoreleasing *)error;
4242
@end
4343
NS_ASSUME_NONNULL_END

IdentityCore/src/oauth2/token/MSIDBoundRefreshToken+Redemption.m

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#import "MSIDEcdhApv.h"
3131
#import "MSIDJwtAlgorithm.h"
3232
#import "MSIDJWTHelper.h"
33+
#import "MSIDKeyOperationUtil.h"
3334

3435
@implementation MSIDBoundRefreshToken (Redemption)
3536

@@ -38,7 +39,7 @@ @implementation MSIDBoundRefreshToken (Redemption)
3839
- (NSString *)getTokenRedemptionJwtForTenantId:(nullable NSString *)tenantId
3940
tokenRedemptionParameters:(MSIDBoundRefreshTokenRedemptionParameters *) requestParameters
4041
context:(id<MSIDRequestContext> _Nullable)context
41-
jweCrypto:(NSDictionary * __autoreleasing _Nullable *_Nullable)jweCrypto
42+
jweCrypto:(NSDictionary * __autoreleasing __nonnull *__nonnull)jweCrypto
4243
error:(NSError *__autoreleasing _Nullable * _Nullable)error
4344
{
4445
if (![self validateRequestParameters:requestParameters context:context error:error])
@@ -53,22 +54,25 @@ - (NSString *)getTokenRedemptionJwtForTenantId:(nullable NSString *)tenantId
5354
return nil;
5455
}
5556

56-
MSIDWPJKeyPairWithCert *workplacejoinData = [self validateAndGetWorkplaceJoinData:tenantId context:context error:error];
57+
MSIDWPJKeyPairWithCert *workplacejoinData = [self getWorkplaceJoinDataAndValidateForTenantId:tenantId context:context error:error];
5758
if (!workplacejoinData)
5859
{
5960
return nil;
6061
}
6162

62-
// TODO: Use new method to query STK private reference
63-
SecKeyRef publicSessionTransportKeyRef = NULL;
64-
NSString *apvPrefix = @"MsalClient"; // TODO: Make this a constant
65-
MSIDEcdhApv *ecdhPartyVInfoData = [[MSIDEcdhApv alloc] initWithKey:publicSessionTransportKeyRef
66-
apvPrefix:apvPrefix
63+
SecKeyRef publicTransportKeyRef = SecKeyCopyPublicKey(workplacejoinData.privateTransportKeyRef);
64+
MSIDEcdhApv *ecdhPartyVInfoData = [[MSIDEcdhApv alloc] initWithKey:publicTransportKeyRef
65+
apvPrefix:MSID_MSAL_CLIENT_APV_PREFIX
6766
context:context
6867
error:error];
6968
if (!ecdhPartyVInfoData)
7069
{
7170
MSID_LOG_WITH_CTX(MSIDLogLevelError, context, @"[Bound Refresh token redemption] Failed to create ECDH APV data for bound RT redemption JWT.");
71+
if (error)
72+
*error = [self createErrorWithDomain:MSIDErrorDomain
73+
code:MSIDErrorInvalidInternalParameter
74+
description:@"Failed to create ECDH APV data for bound RT redemption JWT."
75+
context:context];
7276
return nil;
7377
}
7478

@@ -80,14 +84,20 @@ - (NSString *)getTokenRedemptionJwtForTenantId:(nullable NSString *)tenantId
8084
if (!jweCryptoObj)
8185
{
8286
MSID_LOG_WITH_CTX(MSIDLogLevelError, context, @"[Bound Refresh token redemption] Failed to create JWE crypto for bound RT redemption JWT.");
87+
if (error)
88+
*error = [self createErrorWithDomain:MSIDErrorDomain
89+
code:MSIDErrorInvalidInternalParameter
90+
description:@"Failed to create JWE crypto for bound RT redemption JWT."
91+
context:context];
8392
return nil;
8493
}
8594

86-
*jweCrypto = [jweCryptoObj.jweCryptoDictionary copy];
95+
if (jweCrypto)
96+
*jweCrypto = [jweCryptoObj.jweCryptoDictionary copy];
8797

8898
NSMutableDictionary *jwtPayload = [requestParameters jsonDictionary];
8999
[jwtPayload setObject:self.refreshToken forKey:MSID_OAUTH2_REFRESH_TOKEN];
90-
[jwtPayload setObject:*jweCrypto forKey:@"jwe_crypto"];
100+
[jwtPayload setObject:jweCryptoObj.jweCryptoDictionary forKey:@"jwe_crypto"];
91101

92102
NSArray *certificateData = @[[NSString stringWithFormat:@"%@", [[workplacejoinData certificateData] base64EncodedStringWithOptions:kNilOptions]]];
93103
NSDictionary *header = @{
@@ -145,9 +155,9 @@ - (BOOL)validateBoundDeviceId:(id<MSIDRequestContext>)context
145155
return YES;
146156
}
147157

148-
- (MSIDWPJKeyPairWithCert *)validateAndGetWorkplaceJoinData:(NSString *)tenantId
149-
context:(id<MSIDRequestContext>)context
150-
error:(NSError *__autoreleasing * _Nullable)error
158+
- (MSIDWPJKeyPairWithCert *)getWorkplaceJoinDataAndValidateForTenantId:(NSString *)tenantId
159+
context:(id<MSIDRequestContext>)context
160+
error:(NSError *__autoreleasing * _Nullable)error
151161
{
152162
MSIDWPJKeyPairWithCert *workplacejoinData = [MSIDWorkPlaceJoinUtil getWPJKeysWithTenantId:tenantId context:context];
153163
if (!workplacejoinData)
@@ -195,6 +205,60 @@ - (MSIDWPJKeyPairWithCert *)validateAndGetWorkplaceJoinData:(NSString *)tenantId
195205
return nil;
196206
}
197207

208+
SecKeyRef privateTransportKey = workplacejoinData.privateTransportKeyRef;
209+
if (!privateTransportKey)
210+
{
211+
MSID_LOG_WITH_CTX(MSIDLogLevelError, context, @"[Bound Refresh token redemption] Failed to obtain private transport key for bound RT redemption JWT.");
212+
if (error)
213+
{
214+
*error = [self createErrorWithDomain:MSIDErrorDomain
215+
code:MSIDErrorWorkplaceJoinRequired
216+
description:@"Failed to obtain private transport key for bound RT redemption JWT."
217+
context:context];
218+
}
219+
return nil;
220+
}
221+
222+
SecKeyRef publicSessionTransportKeyRef = SecKeyCopyPublicKey(privateTransportKey);
223+
if (!publicSessionTransportKeyRef)
224+
{
225+
MSID_LOG_WITH_CTX(MSIDLogLevelError, context, @"[Bound Refresh token redemption] Failed to calculate public session transport key from private key for bound RT redemption JWT.");
226+
if (error)
227+
{
228+
*error = [self createErrorWithDomain:MSIDErrorDomain
229+
code:MSIDErrorWorkplaceJoinRequired
230+
description:@"[Bound Refresh token redemption] Failed to calculate public session transport key from private key for bound RT redemption JWT."
231+
context:context];
232+
}
233+
return nil;
234+
}
235+
236+
if (![[MSIDKeyOperationUtil sharedInstance] isKeyFromSecureEnclave:workplacejoinData.privateKeyRef])
237+
{
238+
MSID_LOG_WITH_CTX(MSIDLogLevelError, context, @"[Bound Refresh token redemption] The private device key for bound RT redemption JWT is not from Secure Enclave. Binding will not be satisfied.");
239+
if (error)
240+
{
241+
*error = [self createErrorWithDomain:MSIDErrorDomain
242+
code:MSIDErrorWorkplaceJoinRequired
243+
description:@"The private device key for bound RT redemption JWT is not from Secure Enclave. Binding will not be satisfied."
244+
context:context];
245+
}
246+
return nil;
247+
}
248+
249+
if (![[MSIDKeyOperationUtil sharedInstance] isKeyFromSecureEnclave:workplacejoinData.privateTransportKeyRef])
250+
{
251+
MSID_LOG_WITH_CTX(MSIDLogLevelError, context, @"[Bound Refresh token redemption] The private transport key for bound RT redemption JWT is not from Secure Enclave. Binding will not be satisfied.");
252+
if (error)
253+
{
254+
*error = [self createErrorWithDomain:MSIDErrorDomain
255+
code:MSIDErrorWorkplaceJoinRequired
256+
description:@"The private transport key for bound RT redemption JWT is not from Secure Enclave. Binding will not be satisfied."
257+
context:context];
258+
}
259+
return nil;
260+
}
261+
198262
return workplacejoinData;
199263
}
200264

IdentityCore/src/oauth2/token/MSIDBoundRefreshToken.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN
5252
@return An initialized instance of MSIDBoundRefreshToken.
5353
*/
5454
- (instancetype)initWithRefreshToken:(MSIDRefreshToken *)refreshToken
55-
boundDeviceId:(NSString *)boundDeviceId;
55+
boundDeviceId:(NSString *)boundDeviceId;
5656

5757
- (instancetype)init NS_UNAVAILABLE;
5858
+ (instancetype)new NS_UNAVAILABLE;

IdentityCore/src/parameters/MSIDBoundRefreshTokenRedemptionParameters.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ NS_ASSUME_NONNULL_BEGIN
3535
// Client nonce GUID to be used in bound refresh token redemption request payload.
3636
@property (nonatomic, copy) NSString *nonce;
3737

38+
// Audience (token endpoint URL) for the bound refresh token redemption request
39+
@property (nonatomic, copy) NSString *audience;
40+
3841
- (instancetype)initWithClientId:(NSString *)clientId
42+
authorityEndpoint:(NSURL *)authorityEndpoint
3943
scopes:(NSSet <NSString *>*)scopes
4044
nonce:(NSString *)nonce;
4145

IdentityCore/src/parameters/MSIDBoundRefreshTokenRedemptionParameters.m

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626

2727
@implementation MSIDBoundRefreshTokenRedemptionParameters
2828

29-
- (nonnull instancetype)initWithClientId:(nonnull NSString *)clientId scopes:(nonnull NSSet *)scopes nonce:(nonnull NSString *)nonce
29+
- (nonnull instancetype)initWithClientId:(nonnull NSString *)clientId
30+
authorityEndpoint:(nonnull NSURL *)authorityEndpoint
31+
scopes:(nonnull NSSet *)scopes
32+
nonce:(nonnull NSString *)nonce
3033
{
3134
self = [super init];
3235
if (self)
@@ -36,17 +39,26 @@ - (nonnull instancetype)initWithClientId:(nonnull NSString *)clientId scopes:(no
3639
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to create bound refresh token redemption parameters: clientId is nil or blank.");
3740
return nil;
3841
}
42+
43+
if ([NSString msidIsStringNilOrBlank:authorityEndpoint.absoluteString])
44+
{
45+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to create bound refresh token redemption parameters: authorityEndpoint is nil.");
46+
return nil;
47+
}
48+
3949
if (!scopes || scopes.count == 0)
4050
{
4151
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to create bound refresh token redemption parameters: scope is nil or empty.");
4252
return nil;
4353
}
44-
if ([NSString msidIsStringNilOrBlank:nonce])
54+
55+
if ([scopes containsObject:@"aza"])
4556
{
46-
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to create bound refresh token redemption parameters: nonce is nil or blank.");
57+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to create bound refresh token redemption parameters: scopes contains aza.");
4758
return nil;
4859
}
4960

61+
_audience = authorityEndpoint.absoluteString;
5062
_clientId = clientId;
5163
_scopes = scopes;
5264
_nonce = nonce;
@@ -58,14 +70,16 @@ - (nonnull NSMutableDictionary *)jsonDictionary
5870
{
5971
NSMutableDictionary *jsonDict = [NSMutableDictionary new];
6072
jsonDict[MSID_OAUTH2_GRANT_TYPE] = MSID_OAUTH2_REFRESH_TOKEN;
61-
jsonDict[@"purpose"] = @"bound_rt_exchange"; // TODO: Add constants for purposes
73+
jsonDict[MSID_BOUND_REFRESH_TOKEN_EXCHANGE] = @1;
74+
jsonDict[@"aud"] = self.audience;
6275
jsonDict[@"iss"] = self.clientId; // Issuer is the client ID
63-
NSNumber *now = @([[NSDate date] timeIntervalSince1970]);
64-
jsonDict[@"iat"] = [NSString stringWithFormat:@"%ld", [now longValue]]; // Issued at time
65-
jsonDict[@"exp"] = @([[NSDate dateWithTimeIntervalSinceNow:5 * 60] timeIntervalSince1970]); // 5 minutes
66-
jsonDict[@"nbf"] = [NSString stringWithFormat:@"%ld", [now longValue]]; // Not before time
76+
NSInteger now = round([[NSDate date] timeIntervalSince1970]);
77+
jsonDict[@"iat"] = [NSNumber numberWithInteger:now]; // Issued at time
78+
jsonDict[@"exp"] = [NSNumber numberWithInteger:now + 300]; // 5 minutes
79+
jsonDict[@"nbf"] = [NSNumber numberWithInteger:now]; // Not before time
6780
[jsonDict setObject:self.clientId forKey:MSID_OAUTH2_CLIENT_ID];
68-
[jsonDict setObject:self.nonce forKey:@"nonce"];
81+
if (![NSString msidIsStringNilOrBlank:self.nonce])
82+
[jsonDict setObject:self.nonce forKey:@"nonce"];
6983
NSString *scopeString = [self.scopes.allObjects componentsJoinedByString:@" "];
7084
[jsonDict setObject:scopeString forKey:MSID_OAUTH2_SCOPE];
7185
return jsonDict;

0 commit comments

Comments
 (0)