OLD | NEW |
---|---|
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 origin trial_name | 12 origin trial_name |
12 | 13 |
13 Run "generate_token.py -h" for more help on usage. | 14 Run "generate_token.py -h" for more help on usage. |
14 """ | 15 """ |
15 import argparse | 16 import argparse |
16 import base64 | 17 import base64 |
17 from datetime import datetime | 18 from datetime import datetime |
18 import json | 19 import json |
19 import re | 20 import re |
20 import os | 21 import os |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
78 if not port: | 79 if not port: |
79 port = {"https": 443, "http": 80}[origin.scheme] | 80 port = {"https": 443, "http": 80}[origin.scheme] |
80 # Strip any extra components and return the origin URL: | 81 # Strip any extra components and return the origin URL: |
81 return "{0}://{1}:{2}".format(origin.scheme, origin.hostname, port) | 82 return "{0}://{1}:{2}".format(origin.scheme, origin.hostname, port) |
82 | 83 |
83 def ExpiryFromArgs(args): | 84 def ExpiryFromArgs(args): |
84 if args.expire_timestamp: | 85 if args.expire_timestamp: |
85 return int(args.expire_timestamp) | 86 return int(args.expire_timestamp) |
86 return (int(time.time()) + (int(args.expire_days) * 86400)) | 87 return (int(time.time()) + (int(args.expire_days) * 86400)) |
87 | 88 |
88 def GenerateTokenData(origin, api_name, expiry): | 89 def GenerateTokenData(origin, is_subdomain, feature_name, expiry): |
89 return json.dumps({"origin": origin, | 90 data = {"origin": origin, |
90 "feature": api_name, | 91 "feature": feature_name, |
91 "expiry": expiry}).encode('utf-8') | 92 "expiry": expiry} |
93 if is_subdomain is not None: | |
iclelland
2016/10/13 15:51:23
Could this be just
if is_subdomain:
?
(Is ther
chasej
2016/10/13 16:05:42
There isn't an obvious need - I debated back and f
iclelland
2016/10/13 16:06:46
sgtm
| |
94 data["isSubdomain"] = is_subdomain | |
95 return json.dumps(data).encode('utf-8') | |
92 | 96 |
93 def GenerateDataToSign(version, data): | 97 def GenerateDataToSign(version, data): |
94 return version + struct.pack(">I",len(data)) + data | 98 return version + struct.pack(">I",len(data)) + data |
95 | 99 |
96 def Sign(private_key, data): | 100 def Sign(private_key, data): |
97 return ed25519.signature(data, private_key[:32], private_key[32:]) | 101 return ed25519.signature(data, private_key[:32], private_key[32:]) |
98 | 102 |
99 def FormatToken(version, signature, data): | 103 def FormatToken(version, signature, data): |
100 return base64.b64encode(version + signature + | 104 return base64.b64encode(version + signature + |
101 struct.pack(">I",len(data)) + data) | 105 struct.pack(">I",len(data)) + data) |
102 | 106 |
103 def main(): | 107 def main(): |
104 default_key_file_absolute = os.path.join(script_dir, DEFAULT_KEY_FILE) | 108 default_key_file_absolute = os.path.join(script_dir, DEFAULT_KEY_FILE) |
105 | 109 |
106 parser = argparse.ArgumentParser( | 110 parser = argparse.ArgumentParser( |
107 description="Generate tokens for enabling experimental APIs") | 111 description="Generate tokens for enabling experimental features") |
108 parser.add_argument("origin", | 112 parser.add_argument("origin", |
109 help="Origin for which to enable the API. This can be " | 113 help="Origin for which to enable the feature. This can " |
110 "either a hostname (default scheme HTTPS, default " | 114 "be either a hostname (default scheme HTTPS, " |
111 "port 443) or a URL.", | 115 "default port 443) or a URL.", |
112 type=OriginFromArg) | 116 type=OriginFromArg) |
113 parser.add_argument("trial_name", | 117 parser.add_argument("trial_name", |
114 help="Feature to enable. The current list of " | 118 help="Feature to enable. The current list of " |
115 "experimental feature trials can be found in " | 119 "experimental feature trials can be found in " |
116 "RuntimeFeatures.in") | 120 "RuntimeFeatures.in") |
117 parser.add_argument("--key-file", | 121 parser.add_argument("--key-file", |
118 help="Ed25519 private key file to sign the token with", | 122 help="Ed25519 private key file to sign the token with", |
119 default=default_key_file_absolute) | 123 default=default_key_file_absolute) |
124 | |
125 subdomain_group = parser.add_mutually_exclusive_group() | |
126 subdomain_group.add_argument("--is-subdomain", | |
127 help="Token will enable the feature for all " | |
128 "subdomains that match the origin", | |
129 dest="is_subdomain", | |
130 action="store_true") | |
131 subdomain_group.add_argument("--no-subdomain", | |
132 help="Token will only match the specified " | |
133 "origin (default behavior)", | |
134 dest="is_subdomain", | |
135 action="store_false") | |
136 parser.set_defaults(is_subdomain=None) | |
137 | |
120 expiry_group = parser.add_mutually_exclusive_group() | 138 expiry_group = parser.add_mutually_exclusive_group() |
121 expiry_group.add_argument("--expire-days", | 139 expiry_group.add_argument("--expire-days", |
122 help="Days from now when the token should exipire", | 140 help="Days from now when the token should expire", |
123 type=int, | 141 type=int, |
124 default=42) | 142 default=42) |
125 expiry_group.add_argument("--expire-timestamp", | 143 expiry_group.add_argument("--expire-timestamp", |
126 help="Exact time (seconds since 1970-01-01 " | 144 help="Exact time (seconds since 1970-01-01 " |
127 "00:00:00 UTC) when the token should exipire", | 145 "00:00:00 UTC) when the token should expire", |
128 type=int) | 146 type=int) |
129 | 147 |
130 args = parser.parse_args() | 148 args = parser.parse_args() |
131 expiry = ExpiryFromArgs(args) | 149 expiry = ExpiryFromArgs(args) |
132 | 150 |
133 key_file = open(os.path.expanduser(args.key_file), mode="rb") | 151 key_file = open(os.path.expanduser(args.key_file), mode="rb") |
134 private_key = key_file.read(64) | 152 private_key = key_file.read(64) |
135 | 153 |
136 # Validate that the key file read was a proper Ed25519 key -- running the | 154 # Validate that the key file read was a proper Ed25519 key -- running the |
137 # publickey method on the first half of the key should return the second | 155 # publickey method on the first half of the key should return the second |
138 # half. | 156 # half. |
139 if (len(private_key) < 64 or | 157 if (len(private_key) < 64 or |
140 ed25519.publickey(private_key[:32]) != private_key[32:]): | 158 ed25519.publickey(private_key[:32]) != private_key[32:]): |
141 print("Unable to use the specified private key file.") | 159 print("Unable to use the specified private key file.") |
142 sys.exit(1) | 160 sys.exit(1) |
143 | 161 |
144 token_data = GenerateTokenData(args.origin, args.trial_name, expiry) | 162 token_data = GenerateTokenData(args.origin, args.is_subdomain, |
163 args.trial_name, expiry) | |
145 data_to_sign = GenerateDataToSign(VERSION, token_data) | 164 data_to_sign = GenerateDataToSign(VERSION, token_data) |
146 signature = Sign(private_key, data_to_sign) | 165 signature = Sign(private_key, data_to_sign) |
147 | 166 |
148 # Verify that that the signature is correct before printing it. | 167 # Verify that that the signature is correct before printing it. |
149 try: | 168 try: |
150 ed25519.checkvalid(signature, data_to_sign, private_key[32:]) | 169 ed25519.checkvalid(signature, data_to_sign, private_key[32:]) |
151 except Exception, exc: | 170 except Exception, exc: |
152 print "There was an error generating the signature." | 171 print "There was an error generating the signature." |
153 print "(The original error was: %s)" % exc | 172 print "(The original error was: %s)" % exc |
154 sys.exit(1) | 173 sys.exit(1) |
155 | 174 |
156 | 175 |
157 # Output the token details | 176 # Output the token details |
158 print "Token details:" | 177 print "Token details:" |
159 print " Origin: %s" % args.origin | 178 print " Origin: %s" % args.origin |
179 print " Is Subdomain: %s" % args.is_subdomain | |
160 print " Feature: %s" % args.trial_name | 180 print " Feature: %s" % args.trial_name |
161 print " Expiry: %d (%s UTC)" % (expiry, datetime.utcfromtimestamp(expiry)) | 181 print " Expiry: %d (%s UTC)" % (expiry, datetime.utcfromtimestamp(expiry)) |
162 print | 182 print |
163 | 183 |
164 # Output the properly-formatted token. | 184 # Output the properly-formatted token. |
165 print FormatToken(VERSION, signature, token_data) | 185 print FormatToken(VERSION, signature, token_data) |
166 | 186 |
167 if __name__ == "__main__": | 187 if __name__ == "__main__": |
168 main() | 188 main() |
OLD | NEW |