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

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

Issue 1858763003: Change origin trial token format (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update parser unit tests, improve error detection Created 4 years, 8 months 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
chasej 2016/04/07 15:52:30 Nit: There are a couple usages of "API" instead of
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 origin trial_name 11 origin trial_name
12 12
13 Run "generate_token.py -h" for more help on usage. 13 Run "generate_token.py -h" for more help on usage.
14 """ 14 """
15 import argparse 15 import argparse
16 import base64 16 import base64
17 import json
17 import re 18 import re
18 import os 19 import os
20 import struct
19 import sys 21 import sys
20 import time 22 import time
21 import urlparse 23 import urlparse
22 24
23 script_dir = os.path.dirname(os.path.realpath(__file__)) 25 script_dir = os.path.dirname(os.path.realpath(__file__))
24 sys.path.insert(0, os.path.join(script_dir, 'third_party', 'ed25519')) 26 sys.path.insert(0, os.path.join(script_dir, 'third_party', 'ed25519'))
25 import ed25519 27 import ed25519
26 28
27 29
28 # Matches a valid DNS name label (alphanumeric plus hyphens, except at the ends, 30 # Matches a valid DNS name label (alphanumeric plus hyphens, except at the ends,
29 # no longer than 63 ASCII characters) 31 # no longer than 63 ASCII characters)
30 DNS_LABEL_REGEX = re.compile(r"^(?!-)[a-z\d-]{1,63}(?<!-)$", re.IGNORECASE) 32 DNS_LABEL_REGEX = re.compile(r"^(?!-)[a-z\d-]{1,63}(?<!-)$", re.IGNORECASE)
31 33
34 # This script generates Version 1 tokens
35 VERSION = "\x01"
36
32 def HostnameFromArg(arg): 37 def HostnameFromArg(arg):
33 """Determines whether a string represents a valid hostname. 38 """Determines whether a string represents a valid hostname.
34 39
35 Returns the canonical hostname if its argument is valid, or None otherwise. 40 Returns the canonical hostname if its argument is valid, or None otherwise.
36 """ 41 """
37 if not arg or len(arg) > 255: 42 if not arg or len(arg) > 255:
38 return None 43 return None
39 if arg[-1] == ".": 44 if arg[-1] == ".":
40 arg = arg[:-1] 45 arg = arg[:-1]
41 if all(DNS_LABEL_REGEX.match(label) for label in arg.split(".")): 46 if all(DNS_LABEL_REGEX.match(label) for label in arg.split(".")):
(...skipping 25 matching lines...) Expand all
67 if not port: 72 if not port:
68 port = {"https": 443, "http": 80}[origin.scheme] 73 port = {"https": 443, "http": 80}[origin.scheme]
69 # Strip any extra components and return the origin URL: 74 # Strip any extra components and return the origin URL:
70 return "{0}://{1}:{2}".format(origin.scheme, origin.hostname, port) 75 return "{0}://{1}:{2}".format(origin.scheme, origin.hostname, port)
71 76
72 def ExpiryFromArgs(args): 77 def ExpiryFromArgs(args):
73 if args.expire_timestamp: 78 if args.expire_timestamp:
74 return int(args.expire_timestamp) 79 return int(args.expire_timestamp)
75 return (int(time.time()) + (int(args.expire_days) * 86400)) 80 return (int(time.time()) + (int(args.expire_days) * 86400))
76 81
77 def GenerateTokenData(origin, api_name, expiry): 82 def GenerateTokenData(origin, api_name, expiry):
chasej 2016/04/07 15:52:30 Ditto for "API" vs "feature".
78 return "{0}|{1}|{2}".format(origin, api_name, expiry) 83 return json.dumps({"origin": origin,
84 "feature": api_name,
85 "expiry": expiry}).encode('utf-8')
86
87 def GenerateDataToSign(version, data):
88 return version + struct.pack(">I",len(data)) + data
79 89
80 def Sign(private_key, data): 90 def Sign(private_key, data):
81 return ed25519.signature(data, private_key[:32], private_key[32:]) 91 return ed25519.signature(data, private_key[:32], private_key[32:])
82 92
83 def FormatToken(version, signature, data): 93 def FormatToken(version, signature, data):
84 return version + "|" + base64.b64encode(signature) + "|" + data 94 return base64.b64encode(version + signature +
95 struct.pack(">I",len(data)) + data)
chasej 2016/04/07 15:52:30 Nit: Could pull out the logic to pack the length +
85 96
86 def main(): 97 def main():
87 parser = argparse.ArgumentParser( 98 parser = argparse.ArgumentParser(
88 description="Generate tokens for enabling experimental APIs") 99 description="Generate tokens for enabling experimental APIs")
chasej 2016/04/07 15:52:30 Ditto for "API" vs "feature".
89 parser.add_argument("origin", 100 parser.add_argument("origin",
90 help="Origin for which to enable the API. This can be " 101 help="Origin for which to enable the API. This can be "
chasej 2016/04/07 15:52:30 Ditto for "API" vs "feature".
91 "either a hostname (default scheme HTTPS, default " 102 "either a hostname (default scheme HTTPS, default "
92 "port 443) or a URL.", 103 "port 443) or a URL.",
93 type=OriginFromArg) 104 type=OriginFromArg)
94 parser.add_argument("trial_name", 105 parser.add_argument("trial_name",
95 help="Feature to enable. The current list of " 106 help="Feature to enable. The current list of "
96 "experimental feature trials can be found in " 107 "experimental feature trials can be found in "
97 "RuntimeFeatures.in") 108 "RuntimeFeatures.in")
98 parser.add_argument("--key-file", 109 parser.add_argument("--key-file",
99 help="Ed25519 private key file to sign the token with", 110 help="Ed25519 private key file to sign the token with",
100 default="eftest.key") 111 default="eftest.key")
(...skipping 15 matching lines...) Expand all
116 127
117 # Validate that the key file read was a proper Ed25519 key -- running the 128 # Validate that the key file read was a proper Ed25519 key -- running the
118 # publickey method on the first half of the key should return the second 129 # publickey method on the first half of the key should return the second
119 # half. 130 # half.
120 if (len(private_key) < 64 or 131 if (len(private_key) < 64 or
121 ed25519.publickey(private_key[:32]) != private_key[32:]): 132 ed25519.publickey(private_key[:32]) != private_key[32:]):
122 print("Unable to use the specified private key file.") 133 print("Unable to use the specified private key file.")
123 sys.exit(1) 134 sys.exit(1)
124 135
125 token_data = GenerateTokenData(args.origin, args.trial_name, expiry) 136 token_data = GenerateTokenData(args.origin, args.trial_name, expiry)
126 signature = Sign(private_key, token_data) 137 data_to_sign = GenerateDataToSign(VERSION, token_data)
138 signature = Sign(private_key, data_to_sign)
chasej 2016/04/07 15:52:30 Nit: Add a comment linking to the design doc somew
127 139
128 # Verify that that the signature is correct before printing it. 140 # Verify that that the signature is correct before printing it.
129 try: 141 try:
130 ed25519.checkvalid(signature, token_data, private_key[32:]) 142 ed25519.checkvalid(signature, data_to_sign, private_key[32:])
131 except Exception, exc: 143 except Exception, exc:
132 print "There was an error generating the signature." 144 print "There was an error generating the signature."
133 print "(The original error was: %s)" % exc 145 print "(The original error was: %s)" % exc
134 sys.exit(1) 146 sys.exit(1)
135 147
136 # Output a properly-formatted token. Version 1 is hard-coded, as it is 148 # Output a properly-formatted token. Version 1 is hard-coded, as it is
137 # the only defined token version. 149 # the only defined token version.
138 print FormatToken("1", signature, token_data) 150 print FormatToken(VERSION, signature, token_data)
139 151
140 if __name__ == "__main__": 152 if __name__ == "__main__":
141 main() 153 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698