| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/u2f/u2f_controller.h" |
| |
| #include "base/json/string_escape.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #import "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/google/core/browser/google_util.h" |
| #include "crypto/random.h" |
| #import "ios/chrome/browser/chrome_url_util.h" |
| #include "ios/chrome/common/x_callback_url.h" |
| #include "ios/web/public/web_state/url_verification_constants.h" |
| #import "ios/web/public/web_state/web_state.h" |
| #include "net/base/url_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| const char kIsU2FKey[] = "isU2F"; |
| const char kTabIDKey[] = "tabID"; |
| const char kRequestUUIDKey[] = "requestUUID"; |
| const char kU2FCallbackURL[] = "u2f-callback"; |
| const char kKeyHandleKey[] = "keyHandle"; |
| const char kSignatureDataKey[] = "signatureData"; |
| const char kClientDataKey[] = "clientData"; |
| const char kRegistrationDataKey[] = "registrationData"; |
| const char kErrorKey[] = "error"; |
| const char kErrorCodeKey[] = "errorCode"; |
| const char kRequestIDKey[] = "requestId"; |
| } |
| |
| @interface U2FController () |
| |
| // Generates the JS string to be injected onto the web page to send FIDO U2F |
| // requests' results from the U2F callback URL. |
| - (base::string16)JSStringFromReponseURL:(const GURL&)URL; |
| |
| // Checks if the source URL has Google domain or whitelisted test domain. |
| - (BOOL)shouldAllowSourceURL:(const GURL&)sourceURL; |
| |
| @end |
| |
| @implementation U2FController { |
| // Maps request UUID NString to URL NSString of the tab making the request. |
| NSMutableDictionary* _requestToURLMap; |
| } |
| |
| #pragma mark - Public methods |
| |
| - (instancetype)init { |
| self = [super init]; |
| if (self) { |
| _requestToURLMap = [[NSMutableDictionary alloc] init]; |
| } |
| return self; |
| } |
| |
| + (BOOL)isU2FURL:(const GURL&)URL { |
| std::string isU2F; |
| if (net::GetValueForKeyInQuery(URL, std::string(kIsU2FKey), &isU2F)) { |
| return isU2F == "1"; |
| } |
| return NO; |
| } |
| |
| + (NSString*)tabIDFromResponseURL:(const GURL&)URL { |
| std::string tabID; |
| if (net::GetValueForKeyInQuery(URL, std::string(kTabIDKey), &tabID)) { |
| return base::SysUTF8ToNSString(tabID); |
| } |
| return nil; |
| } |
| |
| - (GURL)XCallbackFromRequestURL:(const GURL&)requestURL |
| originURL:(const GURL&)originURL |
| tabURL:(const GURL&)tabURL |
| tabID:(NSString*)tabID { |
| // Check if origin is secure. |
| if (!originURL.SchemeIsCryptographic()) { |
| return GURL(); |
| } |
| |
| // Check if the webpage has Google or whitelisted test domain. |
| if (![self shouldAllowSourceURL:originURL]) { |
| return GURL(); |
| } |
| |
| // Generate requestUUID and bookkeep it. |
| char randomBytes[16]; |
| crypto::RandBytes(randomBytes, sizeof(randomBytes)); |
| std::string randomUUID(base::HexEncode(randomBytes, sizeof(randomBytes))); |
| NSString* requestUUID = base::SysUTF8ToNSString(randomUUID); |
| NSString* tabURLString = base::SysUTF8ToNSString(tabURL.spec()); |
| [_requestToURLMap setObject:tabURLString forKey:requestUUID]; |
| |
| // Compose callback string. |
| NSString* chromeScheme = |
| [[ChromeAppConstants sharedInstance] getBundleURLScheme]; |
| GURL successOrErrorURL(base::StringPrintf( |
| "%s://%s/", base::SysNSStringToUTF8(chromeScheme).c_str(), |
| kU2FCallbackURL)); |
| successOrErrorURL = net::AppendQueryParameter(successOrErrorURL, kTabIDKey, |
| base::SysNSStringToUTF8(tabID)); |
| successOrErrorURL = |
| net::AppendQueryParameter(successOrErrorURL, kRequestUUIDKey, randomUUID); |
| successOrErrorURL = |
| net::AppendQueryParameter(successOrErrorURL, kIsU2FKey, "1"); |
| |
| std::map<std::string, std::string> params = { |
| {"origin", originURL.spec()}, {"data", requestURL.query()}, |
| }; |
| |
| return CreateXCallbackURLWithParameters("u2f-x-callback", "auth", |
| successOrErrorURL, successOrErrorURL, |
| GURL(), params); |
| } |
| |
| - (void)evaluateU2FResultFromU2FURL:(const GURL&)U2FURL |
| webState:(web::WebState*)webState { |
| if (U2FURL.host() != kU2FCallbackURL) { |
| // If unexpected callback host is in callback, ignore it. |
| return; |
| } |
| |
| std::string requestUUID; |
| if (!net::GetValueForKeyInQuery(U2FURL, std::string(kRequestUUIDKey), |
| &requestUUID)) { |
| // If requestUUID is not in callback, ignore it. |
| return; |
| } |
| |
| NSString* originalTabURL = |
| [_requestToURLMap objectForKey:base::SysUTF8ToNSString(requestUUID)]; |
| if (!originalTabURL) { |
| // If no corresponding original URL, ignore it. |
| return; |
| } |
| |
| // If the URLs match and the page URL is trusted, inject the response JS. |
| web::URLVerificationTrustLevel trustLevel = |
| web::URLVerificationTrustLevel::kNone; |
| GURL currentTabURL = webState->GetCurrentURL(&trustLevel); |
| if (trustLevel == web::URLVerificationTrustLevel::kAbsolute && |
| GURL(base::SysNSStringToUTF8(originalTabURL)) == currentTabURL) { |
| webState->ExecuteJavaScript([self JSStringFromReponseURL:U2FURL]); |
| } |
| |
| // Remove bookkept URL for returned U2F call. |
| [_requestToURLMap removeObjectForKey:base::SysUTF8ToNSString(requestUUID)]; |
| } |
| |
| #pragma mark - Helper method |
| |
| - (base::string16)JSStringFromReponseURL:(const GURL&)URL { |
| std::string requestID; |
| if (!net::GetValueForKeyInQuery(URL, kRequestIDKey, &requestID)) { |
| return base::string16(); |
| } |
| |
| std::string JSString; |
| std::string quotedRequestID = base::GetQuotedJSONString(requestID); |
| |
| std::string errorCode; |
| std::string registrationData; |
| std::string signatureData; |
| if (net::GetValueForKeyInQuery(URL, kErrorKey, &errorCode)) { |
| std::string quotedErrorCodeKey = base::GetQuotedJSONString(kErrorCodeKey); |
| JSString = base::StringPrintf( |
| "u2f.callbackMap_[%s]({%s: %d})", quotedRequestID.c_str(), |
| quotedErrorCodeKey.c_str(), std::stoi(errorCode)); |
| } else if (net::GetValueForKeyInQuery(URL, kRegistrationDataKey, |
| ®istrationData)) { |
| std::string clientData; |
| std::string quotedRegistrationDataKey = |
| base::GetQuotedJSONString(kRegistrationDataKey); |
| std::string quotedRegistrationData = |
| base::GetQuotedJSONString(registrationData); |
| std::string quotedClientDataKey = base::GetQuotedJSONString(kClientDataKey); |
| net::GetValueForKeyInQuery(URL, kClientDataKey, &clientData); |
| std::string quotedClientData = base::GetQuotedJSONString(clientData); |
| JSString = base::StringPrintf( |
| "u2f.callbackMap_[%s]({%s: %s, %s: %s})", quotedRequestID.c_str(), |
| quotedRegistrationDataKey.c_str(), quotedRegistrationData.c_str(), |
| quotedClientDataKey.c_str(), quotedClientData.c_str()); |
| } else if (net::GetValueForKeyInQuery(URL, kSignatureDataKey, |
| &signatureData)) { |
| std::string keyHandle; |
| std::string signatureData; |
| std::string clientData; |
| std::string quotedKeyHandleKey = base::GetQuotedJSONString(kKeyHandleKey); |
| net::GetValueForKeyInQuery(URL, kKeyHandleKey, &keyHandle); |
| std::string quotedKeyHandle = base::GetQuotedJSONString(keyHandle); |
| std::string quotedSignatureDataKey = |
| base::GetQuotedJSONString(kSignatureDataKey); |
| net::GetValueForKeyInQuery(URL, kSignatureDataKey, &signatureData); |
| std::string quotedSignatureData = base::GetQuotedJSONString(signatureData); |
| std::string quotedClientDataKey = base::GetQuotedJSONString(kClientDataKey); |
| net::GetValueForKeyInQuery(URL, kClientDataKey, &clientData); |
| std::string quotedClientData = base::GetQuotedJSONString(clientData); |
| JSString = base::StringPrintf( |
| "u2f.callbackMap_[%s]({%s: %s, %s: %s, %s: %s})", |
| quotedRequestID.c_str(), quotedKeyHandleKey.c_str(), |
| quotedKeyHandle.c_str(), quotedSignatureDataKey.c_str(), |
| quotedSignatureData.c_str(), quotedClientDataKey.c_str(), |
| quotedClientData.c_str()); |
| } |
| return base::UTF8ToUTF16(JSString); |
| } |
| |
| - (BOOL)shouldAllowSourceURL:(const GURL&)sourceURL { |
| if (google_util::IsGoogleDomainUrl( |
| sourceURL, google_util::ALLOW_SUBDOMAIN, |
| google_util::DISALLOW_NON_STANDARD_PORTS)) { |
| return YES; |
| } |
| |
| NSSet* whitelistedDomains = |
| [NSSet setWithObjects:@"u2fdemo.appspot.com", |
| @"chromeiostesting-dot-u2fdemo.appspot.com", nil]; |
| NSString* sourceDomain = base::SysUTF8ToNSString(sourceURL.host()); |
| return [whitelistedDomains containsObject:sourceDomain]; |
| } |
| |
| @end |