blob: b85e85437eec0bfaefa569045eacf8a2dd61a7c6 [file] [log] [blame]
Alex Chaua76a6e32019-06-26 16:20:011// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/gcm_driver/web_push_sender.h"
6
7#include <limits.h>
8
9#include "base/base64url.h"
10#include "base/bind.h"
Richard Knoll1b846ce2019-07-24 09:37:1211#include "base/no_destructor.h"
Alex Chaua76a6e32019-06-26 16:20:0112#include "base/strings/string_number_conversions.h"
13#include "base/strings/stringprintf.h"
14#include "components/gcm_driver/common/gcm_message.h"
15#include "components/gcm_driver/crypto/json_web_token_util.h"
16#include "components/gcm_driver/crypto/p256_key_util.h"
Alex Chaua76a6e32019-06-26 16:20:0117#include "net/http/http_request_headers.h"
18#include "net/http/http_status_code.h"
19#include "services/network/public/cpp/cors/cors.h"
20#include "services/network/public/cpp/resource_request.h"
21#include "services/network/public/cpp/simple_url_loader.h"
22#include "url/gurl.h"
23
24namespace gcm {
25
26namespace {
27
28// VAPID header constants.
29const char kClaimsKeyAudience[] = "aud";
30const char kFCMServerAudience[] = "https://fcm.googleapis.com";
31
32const char kClaimsKeyExpirationTime[] = "exp";
33
34const char kAuthorizationRequestHeaderFormat[] = "vapid t=%s, k=%s";
35
36// Endpoint constants.
37const char kFCMServerUrlFormat[] = "https://fcm.googleapis.com/fcm/send/%s";
38
39// HTTP header constants.
40const char kTTL[] = "TTL";
Richard Knoll1b846ce2019-07-24 09:37:1241const char kUrgency[] = "Urgency";
Alex Chaua76a6e32019-06-26 16:20:0142
43const char kContentEncodingProperty[] = "content-encoding";
44const char kContentCodingAes128Gcm[] = "aes128gcm";
45
46// Other constants.
47const char kContentEncodingOctetStream[] = "application/octet-stream";
Alex Chaua76a6e32019-06-26 16:20:0148
49base::Optional<std::string> GetAuthHeader(crypto::ECPrivateKey* vapid_key,
50 int time_to_live) {
51 base::Value claims(base::Value::Type::DICTIONARY);
52 claims.SetKey(kClaimsKeyAudience, base::Value(kFCMServerAudience));
53
54 int64_t exp =
55 (base::Time::Now() - base::Time::UnixEpoch()).InSeconds() + time_to_live;
56 // TODO: Year 2038 problem, base::Value does not support int64_t.
57 if (exp > INT_MAX)
58 return base::nullopt;
59
60 claims.SetKey(kClaimsKeyExpirationTime,
61 base::Value(static_cast<int32_t>(exp)));
62
63 base::Optional<std::string> jwt = CreateJSONWebToken(claims, vapid_key);
64 if (!jwt)
65 return base::nullopt;
66
67 std::string public_key;
68 if (!GetRawPublicKey(*vapid_key, &public_key))
69 return base::nullopt;
70
71 std::string base64_public_key;
72 base::Base64UrlEncode(public_key, base::Base64UrlEncodePolicy::OMIT_PADDING,
73 &base64_public_key);
74
75 return base::StringPrintf(kAuthorizationRequestHeaderFormat, jwt->c_str(),
76 base64_public_key.c_str());
77}
78
Richard Knoll1b846ce2019-07-24 09:37:1279std::string GetUrgencyHeader(WebPushMessage::Urgency urgency) {
80 switch (urgency) {
81 case WebPushMessage::Urgency::kVeryLow:
82 return "very-low";
83 case WebPushMessage::Urgency::kLow:
84 return "low";
85 case WebPushMessage::Urgency::kNormal:
86 return "normal";
87 case WebPushMessage::Urgency::kHigh:
88 return "high";
89 }
90}
91
Alex Chaua76a6e32019-06-26 16:20:0192std::unique_ptr<network::SimpleURLLoader> BuildURLLoader(
93 const std::string& fcm_token,
94 int time_to_live,
Richard Knoll1b846ce2019-07-24 09:37:1295 const std::string& urgency_header,
Alex Chaua76a6e32019-06-26 16:20:0196 const std::string& auth_header,
97 const std::string& message) {
98 auto resource_request = std::make_unique<network::ResourceRequest>();
99 std::string server_url =
100 base::StringPrintf(kFCMServerUrlFormat, fcm_token.c_str());
101 resource_request->url = GURL(server_url);
David Benjamin86aa9282019-08-13 22:28:47102 resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
Alex Chaua76a6e32019-06-26 16:20:01103 resource_request->method = "POST";
104 resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
105 auth_header);
106 resource_request->headers.SetHeader(kTTL, base::NumberToString(time_to_live));
107 resource_request->headers.SetHeader(kContentEncodingProperty,
108 kContentCodingAes128Gcm);
Richard Knoll1b846ce2019-07-24 09:37:12109 resource_request->headers.SetHeader(kUrgency, urgency_header);
Alex Chaua76a6e32019-06-26 16:20:01110
111 net::NetworkTrafficAnnotationTag traffic_annotation =
112 net::DefineNetworkTrafficAnnotation("web_push_message", R"(
113 semantics {
114 sender: "GCMDriver WebPushSender"
115 description:
116 "Send a request via Firebase to another device that is signed in"
117 "with the same account."
118 trigger: "Users send data to another owned device."
119 data: "Web push message."
120 destination: GOOGLE_OWNED_SERVICE
121 }
122 policy {
123 cookies_allowed: NO
124 setting:
125 "You can enable or disable this feature in Chrome's settings under"
126 "'Sync and Google services'."
127 policy_exception_justification: "Not implemented."
128 }
129 )");
130 std::unique_ptr<network::SimpleURLLoader> loader =
131 network::SimpleURLLoader::Create(std::move(resource_request),
132 traffic_annotation);
133 loader->AttachStringForUpload(message, kContentEncodingOctetStream);
134 loader->SetRetryOptions(1, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
135 loader->SetAllowHttpErrorResults(true);
136
137 return loader;
138}
139