Skip to content

Commit cee05ce

Browse files
authored
Merge pull request #1572 from AzureAD/ameyapat/msid-jwe-decryptor
Shifted decryptor logic from broker to common-core. Use Big Endian fo…
2 parents 1e08269 + 5fa1af3 commit cee05ce

22 files changed

+1027
-41
lines changed

IdentityCore/IdentityCore.xcodeproj/project.pbxproj

Lines changed: 118 additions & 8 deletions
Large diffs are not rendered by default.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
// All rights reserved.
4+
//
5+
// This code is licensed under the MIT License.
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files(the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions :
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
26+
import Foundation
27+
import CryptoKit
28+
29+
public class MSIDAesGcmDecryptor: NSObject {
30+
31+
@objc public func decryptWithAES256GCMHandler(message ciphertext: Data, iv nonce: Data, key keyData: Data, tag: Data, aad: Data) throws -> Data
32+
{
33+
let sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: nonce), ciphertext: ciphertext, tag: tag)
34+
let key = SymmetricKey(data: keyData)
35+
return try AES.GCM.open(sealedBox, using: key, authenticating: aad)
36+
}
37+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
// All rights reserved.
4+
//
5+
// This code is licensed under the MIT License.
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files(the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions :
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
#import <Foundation/Foundation.h>
26+
#import "MSIDJweResponse.h"
27+
#import "MSIDRequestContext.h"
28+
#import "MSIDJWECrypto.h"
29+
#import "MSIDJsonSerializable.h"
30+
31+
typedef NSString *const MSIDJWECryptoKeyExchangeAlgorithm NS_TYPED_ENUM;
32+
typedef NSString *const MSIDJWECryptoKeyResponseEncryptionAlgorithm NS_TYPED_ENUM;
33+
34+
extern MSIDJWECryptoKeyExchangeAlgorithm const _Nonnull MSID_KEY_EXCHANGE_ALGORITHM_ECDH_ES;
35+
extern MSIDJWECryptoKeyResponseEncryptionAlgorithm const _Nonnull MSID_RESPONSE_ENCRYPTION_ALGORITHM_A256GCM;
36+
37+
NS_ASSUME_NONNULL_BEGIN
38+
@interface MSIDJweResponse (EcdhAesGcm)
39+
- (BOOL)IsJweResponseAlgorithmSupported:(NSError * _Nullable __autoreleasing * _Nullable)error;
40+
- (nullable NSDictionary *)decryptJweResponseWithPrivateStk:(SecKeyRef)privateStk
41+
jweCrypto:(MSIDJWECrypto *)jweCrypto
42+
error:(NSError * _Nullable __autoreleasing * _Nullable)error;
43+
@end
44+
NS_ASSUME_NONNULL_END
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
// All rights reserved.
4+
//
5+
// This code is licensed under the MIT License.
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files(the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions :
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
26+
#import <Foundation/Foundation.h>
27+
#import "MSIDJweResponse+EcdhAesGcm.h"
28+
#import "MSIDJWECrypto.h"
29+
#import "MSIDJweResponse.h"
30+
#import "NSData+MSIDExtensions.h"
31+
#import "NSData+MSIDEccSecKeyRef.h"
32+
#import "MSIDEcdhApv.h"
33+
#import "MSIDJsonSerializer.h"
34+
#import "IdentityCore-Swift.h"
35+
#import "MSIDFlightManager.h"
36+
#import "MSIDConstants.h"
37+
38+
MSIDJWECryptoKeyExchangeAlgorithm const MSID_KEY_EXCHANGE_ALGORITHM_ECDH_ES = @"ECDH-ES";
39+
MSIDJWECryptoKeyResponseEncryptionAlgorithm const MSID_RESPONSE_ENCRYPTION_ALGORITHM_A256GCM = @"A256GCM";
40+
41+
@implementation MSIDJweResponse (EcdhAesGcm)
42+
43+
- (nullable NSDictionary *)decryptJweResponseWithPrivateStk:(nonnull SecKeyRef)privateStkRef
44+
jweCrypto:(nonnull MSIDJWECrypto *)jweCrypto
45+
error:(NSError * _Nullable __autoreleasing * _Nullable)error
46+
{
47+
MSID_LOG_WITH_CTX(MSIDLogLevelInfo, nil, @"Starting to decrypt JWE response using ECDH-AESGCM using jwe_crypto : %@", jweCrypto.urlEncodedJweCrypto);
48+
49+
// 1. Check for necessary request parameters
50+
NSData *apv = [NSData msidDataFromBase64UrlEncodedString:jweCrypto.apv.APV];
51+
52+
if (!apv.length)
53+
{
54+
if (error)
55+
{
56+
*error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @"Unexpected input parameters, no apv present in request JWE Crypto", nil, nil, nil, nil, nil, NO);
57+
}
58+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Unexpected input parameters, no apv present in request JWE Crypto");
59+
return nil;
60+
}
61+
62+
// 2. Check for necessary response parameters, epk, enc, apu
63+
NSDictionary *epk = self.jweHeader[@"epk"];
64+
65+
if (![epk count]
66+
|| ![epk isKindOfClass:[NSDictionary class]])
67+
{
68+
if (error)
69+
{
70+
*error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @"Unexpected server response, no epk present in JWE header", nil, nil, nil, nil, nil, YES);
71+
}
72+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Unexpected server response, no epk present in JWE header, epk %@", epk);
73+
return nil;
74+
}
75+
76+
NSString *encHeader = self.jweHeader[@"enc"];
77+
NSString *apuHeader = self.jweHeader[@"apu"];
78+
79+
if ([NSString msidIsStringNilOrBlank:encHeader]
80+
|| [NSString msidIsStringNilOrBlank:apuHeader])
81+
{
82+
if (error)
83+
{
84+
*error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @"Unexpected server response, no enc or apu present JWE header", nil, nil, nil, nil, nil, NO);
85+
}
86+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Unexpected server response, no enc or apu present JWE header, enc %@, apu %@", encHeader, apuHeader);
87+
return nil;
88+
}
89+
90+
if (![self IsJweResponseAlgorithmSupported:error])
91+
{
92+
return nil;
93+
}
94+
95+
// 3. Create key from server response
96+
SecKeyRef serverKeyRef = [NSData createKeyFromEccJsonWebKey:epk error:error];
97+
98+
if (!serverKeyRef)
99+
{
100+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Could not get EC public key from server JWE response payload.");
101+
return nil;
102+
}
103+
104+
// 4. Calculate shared key through a key exchange
105+
// It performs a Diffie-Hellman key exchange (kSecKeyAlgorithmECDHKeyExchangeStandard) using the Device Encryption public key and the Ephemeral private key.
106+
NSData *sharedSecret = [self calculateSharedSecretUsingEcdhWithOtherPartyPublicEcKey:serverKeyRef
107+
privateStk:privateStkRef
108+
error:error];
109+
110+
if (!sharedSecret)
111+
{
112+
CFRelease(serverKeyRef);
113+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to calculate shared secret using ECDH");
114+
return nil;
115+
}
116+
117+
// 5. Calculate derived key using ConcatKDF
118+
// ConcatKDF is performed using the key exchange result, algorithm, PartyUInfo, and PartyVInfo.
119+
NSData *algorithmId = [encHeader dataUsingEncoding:NSASCIIStringEncoding];
120+
NSData *apu = [NSData msidDataFromBase64UrlEncodedString:apuHeader];
121+
122+
NSData *derivedKey = [self calculateDerivedKeyWithSharedKey:sharedSecret
123+
algorithmId:algorithmId
124+
apu:apu
125+
apv:apv
126+
error:error];
127+
128+
if (!derivedKey)
129+
{
130+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to calculate derived key from shared secret using ConcatKDF");
131+
return nil;
132+
}
133+
134+
// 6. Decrypt JWE using derived key as the AES GCM decryption key
135+
// The key from ConcatKDF is used along with the Initialization Vector, and the Additional Authentication Data (AAD) to encrypt the plain text using AESGCM.
136+
return [self decryptJweResponseUsingSymmetricKey:derivedKey error:error];
137+
}
138+
139+
- (NSData *)calculateSharedSecretUsingEcdhWithOtherPartyPublicEcKey:(SecKeyRef)serverKeyRef
140+
privateStk:(SecKeyRef)privateStkRef
141+
error:(NSError * _Nullable __autoreleasing * _Nullable)error
142+
{
143+
SecKeyAlgorithm algorithm = kSecKeyAlgorithmECDHKeyExchangeStandard;
144+
NSDictionary *attributes = @{(id)kSecKeyKeyExchangeParameterRequestedSize:@32};
145+
146+
CFErrorRef cfError = NULL;
147+
NSData *sharedKey = (NSData *)CFBridgingRelease(SecKeyCopyKeyExchangeResult(privateStkRef, algorithm, serverKeyRef, (__bridge CFDictionaryRef)attributes, &cfError));
148+
149+
if (!sharedKey)
150+
{
151+
NSError *nsError = CFBridgingRelease(cfError);
152+
153+
if (error)
154+
{
155+
*error = nsError;
156+
}
157+
158+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to do key exchange with error %@", nsError);
159+
return nil;
160+
}
161+
162+
return sharedKey;
163+
}
164+
165+
- (NSData *)calculateDerivedKeyWithSharedKey:(NSData *)sharedSecret
166+
algorithmId:(NSData *)algorithmId
167+
apu:(NSData *)apu
168+
apv:(NSData *)apv
169+
error:(NSError * _Nullable __autoreleasing * _Nullable)error
170+
{
171+
NSError *concatKDFError = nil;
172+
MSIDConcatKdfProvider *concatKDFProvider = [MSIDConcatKdfProvider new];
173+
NSData *derivedKey = [concatKDFProvider concatKDFWithSHA256WithSharedSecret:sharedSecret
174+
outputKeyLen:32
175+
algorithmId:algorithmId
176+
partyUInfo:apu
177+
partyVInfo:apv
178+
error:&concatKDFError];
179+
// Deallocating sharedSecret as it is no longer needed
180+
sharedSecret = nil;
181+
if (!derivedKey)
182+
{
183+
if (error)
184+
{
185+
*error = concatKDFError;
186+
}
187+
188+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Failed to calculate ConcatKDF with error %@", concatKDFError);
189+
return nil;
190+
}
191+
192+
return derivedKey;
193+
}
194+
195+
- (NSDictionary *)decryptJweResponseUsingSymmetricKey:(NSData *)symmetricKey
196+
error:(NSError * _Nullable __autoreleasing * _Nullable)error
197+
{
198+
if (!symmetricKey)
199+
{
200+
if (error)
201+
{
202+
*error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @"Symmetric key is nil", nil, nil, nil, nil, nil, YES);
203+
}
204+
return nil;
205+
}
206+
207+
if (![self IsJweResponseAlgorithmSupported:error])
208+
{
209+
return nil;
210+
}
211+
212+
// Since only A256GCM is supported, we can decrypt jwe message using AES256GCM.
213+
MSIDAesGcmDecryptor *decryptor = [MSIDAesGcmDecryptor new];
214+
NSData *decryptedData = [decryptor decryptWithAES256GCMHandlerWithMessage:self.payload iv:self.iv key:symmetricKey tag:self.tag aad:self.aad error:error];
215+
// Deallocate symmetricKey as it is no longer needed
216+
symmetricKey = nil;
217+
if (!decryptedData)
218+
{
219+
if (error)
220+
{
221+
NSError *subError = *error ? *error : nil;
222+
*error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @"Unexpected server response, failed to decrypt JWE", nil, [subError description], subError, nil, nil, YES);
223+
}
224+
MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"Unexpected server response, failed to decrypt JWE");
225+
return nil;
226+
}
227+
228+
MSIDJsonSerializer *serializer = [MSIDJsonSerializer new];
229+
230+
NSDictionary *jsonResult = [serializer deserializeJSON:decryptedData error:error];
231+
if (!jsonResult)
232+
{
233+
if (error)
234+
{
235+
NSError *subError = *error ? *error : nil;
236+
*error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @"Failed to serialize decrypted data to JSON", nil, [subError description], subError, nil, nil, YES);
237+
}
238+
}
239+
return jsonResult;
240+
}
241+
242+
- (BOOL)IsJweResponseAlgorithmSupported:(NSError * _Nullable __autoreleasing * _Nullable)error
243+
{
244+
if (self.headerAlgorithm && ![self.headerAlgorithm isEqualToString:MSID_KEY_EXCHANGE_ALGORITHM_ECDH_ES])
245+
{
246+
if (error)
247+
{
248+
*error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, [NSString stringWithFormat:@"Unsupported JWE algorithm : %@", self.headerAlgorithm], nil, nil, nil, nil, nil, YES);
249+
}
250+
return NO;
251+
}
252+
253+
if (![self.headerEncryptionAlgorithm isEqualToString:MSID_RESPONSE_ENCRYPTION_ALGORITHM_A256GCM])
254+
{
255+
if (error)
256+
{
257+
*error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, [NSString stringWithFormat:@"Unsupported JWE encryption algorithm : %@", self.jweHeader[@"enc"]], nil, nil, nil, nil, nil, YES);
258+
}
259+
return NO;
260+
}
261+
262+
return YES;
263+
}
264+
@end

IdentityCore/src/MSIDConstants.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,13 @@ extern NSString * _Nonnull const MSID_FLIGHT_IGNORE_COOKIES_IN_DUNA_RESUME;
232232
*/
233233
extern NSString * _Nonnull const MSID_FLIGHT_DISABLE_REMOVE_ACCOUNT_ARTIFACTS;
234234

235-
extern NSString * _Nonnull const MSID_FLIGHT_ENABLE_QUERYING_STK;
235+
/// Flight to enable support for bound app RT
236+
/// Owner: amepatil
237+
/// ECS configuration id: /1678824
236238
extern NSString * _Nonnull const MSID_FLIGHT_IS_BART_SUPPORTED;
237239

240+
extern NSString * _Nonnull const MSID_FLIGHT_ENABLE_QUERYING_STK;
241+
238242
extern NSString * _Nonnull const MSID_DOMAIN_HINT_KEY;
239243

240244
#define METHODANDLINE [NSString stringWithFormat:@"%s [Line %d]", __PRETTY_FUNCTION__, __LINE__]

IdentityCore/src/MSIDConstants.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,10 @@
9898
NSString *const MSID_FLIGHT_DISABLE_REMOVE_ACCOUNT_ARTIFACTS = @"disable_rm_metadata";
9999

100100
NSString *const MSID_FLIGHT_ENABLE_QUERYING_STK = @"enable_querying_stk";
101-
NSString *const MSID_FLIGHT_IS_BART_SUPPORTED = @"is_msal_bart_supported";
102-
103101
NSString *const MSID_DOMAIN_HINT_KEY = @"domain_hint";
104102

105103

104+
NSString *const MSID_FLIGHT_IS_BART_SUPPORTED = @"is_bound_app_rt_supported";
105+
106+
106107
#define METHODANDLINE [NSString stringWithFormat:@"%s [Line %d]", __PRETTY_FUNCTION__, __LINE__]

0 commit comments

Comments
 (0)