Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: tools/origin_trials/generate_token.py

Issue 2456053004: Validate origins when generating subdomain tokens (Closed)
Patch Set: Address comments Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2016 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2016 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Utility for generating experimental API tokens 6 """Utility for generating experimental API tokens
7 7
8 usage: generate_token.py [-h] [--key-file KEY_FILE] 8 usage: generate_token.py [-h] [--key-file KEY_FILE]
9 [--expire-days EXPIRE_DAYS | 9 [--expire-days EXPIRE_DAYS |
10 --expire-timestamp EXPIRE_TIMESTAMP] 10 --expire-timestamp EXPIRE_TIMESTAMP]
11 [--is_subdomain | --no-subdomain] 11 [--is_subdomain | --no-subdomain]
12 origin trial_name 12 origin trial_name
13 13
14 Run "generate_token.py -h" for more help on usage. 14 Run "generate_token.py -h" for more help on usage.
15 """ 15 """
16 import argparse 16 import argparse
17 import base64 17 import base64
18 from datetime import datetime 18 from datetime import datetime
19 import json 19 import json
20 import re 20 import re
21 import os 21 import os
22 import struct 22 import struct
23 import subprocess
23 import sys 24 import sys
24 import time 25 import time
25 import urlparse 26 import urlparse
26 27
27 script_dir = os.path.dirname(os.path.realpath(__file__)) 28 script_dir = os.path.dirname(os.path.realpath(__file__))
28 sys.path.insert(0, os.path.join(script_dir, 'third_party', 'ed25519')) 29 sys.path.insert(0, os.path.join(script_dir, 'third_party', 'ed25519'))
29 import ed25519 30 import ed25519
30 31
31 32
32 # Matches a valid DNS name label (alphanumeric plus hyphens, except at the ends, 33 # Matches a valid DNS name label (alphanumeric plus hyphens, except at the ends,
33 # no longer than 63 ASCII characters) 34 # no longer than 63 ASCII characters)
34 DNS_LABEL_REGEX = re.compile(r"^(?!-)[a-z\d-]{1,63}(?<!-)$", re.IGNORECASE) 35 DNS_LABEL_REGEX = re.compile(r"^(?!-)[a-z\d-]{1,63}(?<!-)$", re.IGNORECASE)
35 36
36 # This script generates Version 2 tokens. 37 # This script generates Version 2 tokens.
37 VERSION = "\x02" 38 VERSION = "\x02"
38 39
39 # Default key file, relative to script_dir. 40 # Default key file, relative to script_dir.
40 DEFAULT_KEY_FILE = 'eftest.key' 41 DEFAULT_KEY_FILE = 'eftest.key'
41 42
43 # Default location of validate subdomain utility, relative to script_dir.
44 DEFAULT_TARGET_PATH = '../../out/Default/'
45
42 def HostnameFromArg(arg): 46 def HostnameFromArg(arg):
43 """Determines whether a string represents a valid hostname. 47 """Determines whether a string represents a valid hostname.
44 48
45 Returns the canonical hostname if its argument is valid, or None otherwise. 49 Returns the canonical hostname if its argument is valid, or None otherwise.
46 """ 50 """
47 if not arg or len(arg) > 255: 51 if not arg or len(arg) > 255:
48 return None 52 return None
49 if arg[-1] == ".": 53 if arg[-1] == ".":
50 arg = arg[:-1] 54 arg = arg[:-1]
51 if "." not in arg and arg != "localhost": 55 if "." not in arg and arg != "localhost":
(...skipping 27 matching lines...) Expand all
79 if not port: 83 if not port:
80 port = {"https": 443, "http": 80}[origin.scheme] 84 port = {"https": 443, "http": 80}[origin.scheme]
81 # Strip any extra components and return the origin URL: 85 # Strip any extra components and return the origin URL:
82 return "{0}://{1}:{2}".format(origin.scheme, origin.hostname, port) 86 return "{0}://{1}:{2}".format(origin.scheme, origin.hostname, port)
83 87
84 def ExpiryFromArgs(args): 88 def ExpiryFromArgs(args):
85 if args.expire_timestamp: 89 if args.expire_timestamp:
86 return int(args.expire_timestamp) 90 return int(args.expire_timestamp)
87 return (int(time.time()) + (int(args.expire_days) * 86400)) 91 return (int(time.time()) + (int(args.expire_days) * 86400))
88 92
93 def ValidateSubdomainTokenOrigin(origin, target_path):
94 """ Calls validate_subdomain_origin utility to check the origin
95
96 If the utility is not found, prints a warning for manual validation, and
97 returns True
98 """
99 utility_path = "%s/validate_subdomain_origin" % target_path
100 if not os.path.exists(utility_path):
101 print "WARNING!"
102 print "Origin not validated for use in subdomain token"
103 print " (missing '%s' utility)" % utility_path
104 print "Must manually check origin against the Public Suffix List"
105 print
106 return True
107
108 rc = subprocess.call([utility_path, "--quiet", origin])
109 if (rc < 0 or rc > 3):
110 print("Unexpected return code from validate subdomain utility: %d" % rc)
iclelland 2016/11/03 19:47:55 May as well use |utility_path| here instead
chasej 2016/11/03 21:13:10 Done.
111 sys.exit(1)
112
113 return rc == 0
114
89 def GenerateTokenData(origin, is_subdomain, feature_name, expiry): 115 def GenerateTokenData(origin, is_subdomain, feature_name, expiry):
90 data = {"origin": origin, 116 data = {"origin": origin,
91 "feature": feature_name, 117 "feature": feature_name,
92 "expiry": expiry} 118 "expiry": expiry}
93 if is_subdomain is not None: 119 if is_subdomain is not None:
94 data["isSubdomain"] = is_subdomain 120 data["isSubdomain"] = is_subdomain
95 return json.dumps(data).encode('utf-8') 121 return json.dumps(data).encode('utf-8')
96 122
97 def GenerateDataToSign(version, data): 123 def GenerateDataToSign(version, data):
98 return version + struct.pack(">I",len(data)) + data 124 return version + struct.pack(">I",len(data)) + data
99 125
100 def Sign(private_key, data): 126 def Sign(private_key, data):
101 return ed25519.signature(data, private_key[:32], private_key[32:]) 127 return ed25519.signature(data, private_key[:32], private_key[32:])
102 128
103 def FormatToken(version, signature, data): 129 def FormatToken(version, signature, data):
104 return base64.b64encode(version + signature + 130 return base64.b64encode(version + signature +
105 struct.pack(">I",len(data)) + data) 131 struct.pack(">I",len(data)) + data)
106 132
107 def main(): 133 def main():
108 default_key_file_absolute = os.path.join(script_dir, DEFAULT_KEY_FILE) 134 default_key_file_absolute = os.path.join(script_dir, DEFAULT_KEY_FILE)
135 default_target_path_absolute = os.path.join(script_dir, DEFAULT_TARGET_PATH)
109 136
110 parser = argparse.ArgumentParser( 137 parser = argparse.ArgumentParser(
111 description="Generate tokens for enabling experimental features") 138 description="Generate tokens for enabling experimental features")
112 parser.add_argument("origin", 139 parser.add_argument("origin",
113 help="Origin for which to enable the feature. This can " 140 help="Origin for which to enable the feature. This can "
114 "be either a hostname (default scheme HTTPS, " 141 "be either a hostname (default scheme HTTPS, "
115 "default port 443) or a URL.", 142 "default port 443) or a URL.",
116 type=OriginFromArg) 143 type=OriginFromArg)
117 parser.add_argument("trial_name", 144 parser.add_argument("trial_name",
118 help="Feature to enable. The current list of " 145 help="Feature to enable. The current list of "
(...skipping 19 matching lines...) Expand all
138 expiry_group = parser.add_mutually_exclusive_group() 165 expiry_group = parser.add_mutually_exclusive_group()
139 expiry_group.add_argument("--expire-days", 166 expiry_group.add_argument("--expire-days",
140 help="Days from now when the token should expire", 167 help="Days from now when the token should expire",
141 type=int, 168 type=int,
142 default=42) 169 default=42)
143 expiry_group.add_argument("--expire-timestamp", 170 expiry_group.add_argument("--expire-timestamp",
144 help="Exact time (seconds since 1970-01-01 " 171 help="Exact time (seconds since 1970-01-01 "
145 "00:00:00 UTC) when the token should expire", 172 "00:00:00 UTC) when the token should expire",
146 type=int) 173 type=int)
147 174
175 parser.add_argument("--target",
176 help="Path to the output directory for compiled resources"
177 ", relative to the script directory",
iclelland 2016/11/03 19:47:55 Is this correct? I think that since you're just us
chasej 2016/11/03 21:13:10 Done. Just removed the part about "relative to ...
178 default=default_target_path_absolute)
179
148 args = parser.parse_args() 180 args = parser.parse_args()
149 expiry = ExpiryFromArgs(args) 181 expiry = ExpiryFromArgs(args)
150 182
151 key_file = open(os.path.expanduser(args.key_file), mode="rb") 183 key_file = open(os.path.expanduser(args.key_file), mode="rb")
152 private_key = key_file.read(64) 184 private_key = key_file.read(64)
153 185
154 # Validate that the key file read was a proper Ed25519 key -- running the 186 # Validate that the key file read was a proper Ed25519 key -- running the
155 # publickey method on the first half of the key should return the second 187 # publickey method on the first half of the key should return the second
156 # half. 188 # half.
157 if (len(private_key) < 64 or 189 if (len(private_key) < 64 or
158 ed25519.publickey(private_key[:32]) != private_key[32:]): 190 ed25519.publickey(private_key[:32]) != private_key[32:]):
159 print("Unable to use the specified private key file.") 191 print("Unable to use the specified private key file.")
160 sys.exit(1) 192 sys.exit(1)
161 193
194 # For subdomain tokens, validate that the origin is allowed
195 if args.is_subdomain:
196 target_path = os.path.expanduser(args.target)
197 if not ValidateSubdomainTokenOrigin(args.origin, target_path):
198 print "The specified origin is not valid for use in a subdomain token."
199 sys.exit(1)
200
162 token_data = GenerateTokenData(args.origin, args.is_subdomain, 201 token_data = GenerateTokenData(args.origin, args.is_subdomain,
163 args.trial_name, expiry) 202 args.trial_name, expiry)
164 data_to_sign = GenerateDataToSign(VERSION, token_data) 203 data_to_sign = GenerateDataToSign(VERSION, token_data)
165 signature = Sign(private_key, data_to_sign) 204 signature = Sign(private_key, data_to_sign)
166 205
167 # Verify that that the signature is correct before printing it. 206 # Verify that that the signature is correct before printing it.
168 try: 207 try:
169 ed25519.checkvalid(signature, data_to_sign, private_key[32:]) 208 ed25519.checkvalid(signature, data_to_sign, private_key[32:])
170 except Exception, exc: 209 except Exception, exc:
171 print "There was an error generating the signature." 210 print "There was an error generating the signature."
172 print "(The original error was: %s)" % exc 211 print "(The original error was: %s)" % exc
173 sys.exit(1) 212 sys.exit(1)
174 213
175 214
176 # Output the token details 215 # Output the token details
177 print "Token details:" 216 print "Token details:"
178 print " Origin: %s" % args.origin 217 print " Origin: %s" % args.origin
179 print " Is Subdomain: %s" % args.is_subdomain 218 print " Is Subdomain: %s" % args.is_subdomain
180 print " Feature: %s" % args.trial_name 219 print " Feature: %s" % args.trial_name
181 print " Expiry: %d (%s UTC)" % (expiry, datetime.utcfromtimestamp(expiry)) 220 print " Expiry: %d (%s UTC)" % (expiry, datetime.utcfromtimestamp(expiry))
182 print 221 print
183 222
184 # Output the properly-formatted token. 223 # Output the properly-formatted token.
185 print FormatToken(VERSION, signature, token_data) 224 print FormatToken(VERSION, signature, token_data)
186 225
187 if __name__ == "__main__": 226 if __name__ == "__main__":
188 main() 227 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698