Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 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 | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Utility for generating experimental API tokens | |
| 7 | |
| 8 usage: generate_token.py [-h] [--key-file KEY_FILE] | |
| 9 [--expire-days EXPIRE_DAYS | | |
| 10 --expire-timestamp EXPIRE_TIMESTAMP] | |
| 11 hostname api_name | |
|
chasej
2016/01/22 18:14:00
Should we use something other than "hostname" for
chasej
2016/01/22 18:14:00
I would suggest "trial_name" to replace "api_name"
iclelland
2016/01/22 20:31:57
Done.
iclelland
2016/01/22 20:31:57
Done.
| |
| 12 | |
| 13 Run "generate_token.py -h" for more help on usage. | |
| 14 """ | |
| 15 import argparse | |
| 16 import base64 | |
| 17 import re | |
| 18 import os | |
| 19 import sys | |
| 20 import time | |
| 21 import urlparse | |
| 22 | |
| 23 from third_party import ed25519 | |
| 24 | |
| 25 HOSTNAME_REGEX = re.compile( | |
| 26 r"""^ | |
| 27 # Zero or more components, where each one is either a single alphanumeric | |
| 28 (([a-z0-9]| | |
| 29 # Or multiple alphanumerics with internal hyphens | |
| 30 [a-z0-9][a-z0-9-]*[a-z0-9]) | |
| 31 # Separated by periods | |
| 32 \.)* | |
| 33 # And followed by a final component. | |
| 34 ([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$""", | |
| 35 re.IGNORECASE | re.VERBOSE | |
| 36 ) | |
| 37 | |
| 38 def OriginFromArgs(arg): | |
| 39 """Constructs the origin for the token from the command line arguments. | |
| 40 | |
| 41 Returns None if this is not possible (neither a valid hostname nor a | |
| 42 valid origin URL was provided.) | |
| 43 """ | |
| 44 # Does it look like a hostname? | |
| 45 if HOSTNAME_REGEX.match(arg): | |
| 46 return 'https://'+arg | |
| 47 # Try to construct an origin URL from the argument | |
| 48 origin = urlparse.urlparse(arg) | |
| 49 if not origin or not origin.scheme or not origin.netloc: | |
| 50 raise argparse.ArgumentTypeError("%s is not a hostname or a URL" % arg) | |
| 51 # Strip any other components and return the URL: | |
| 52 return urlparse.urlunparse(origin[:2] + ('', )*4) | |
| 53 | |
| 54 def ExpiryFromArgs(args): | |
| 55 if args.expire_timestamp: | |
| 56 return int(args.expire_timestamp) | |
| 57 return ((int(time.time()) + (int(args.expire_days) * 86400)) * 1000) | |
| 58 | |
| 59 def GenerateTokenData(origin, apiName, expiry): | |
| 60 return "{0}|{1}|{2}".format(origin, apiName, expiry) | |
| 61 | |
| 62 def Sign(private_key, data): | |
| 63 return ed25519.signature(data, private_key[:32], private_key[32:]) | |
| 64 | |
| 65 def FormatToken(signature, data): | |
| 66 return base64.b64encode(signature) + "|" + data | |
| 67 | |
| 68 def main(): | |
| 69 parser = argparse.ArgumentParser( | |
| 70 description="Generate tokens for enabling experimental APIs") | |
| 71 parser.add_argument('hostname', | |
| 72 help="Host for which to enable the API. This can be a " | |
| 73 "bare hostname (in which case https will be " | |
| 74 "assumed,) or a valid origin URL (hostname, " | |
| 75 "protocol and port can all be included.)", | |
| 76 type=OriginFromArgs) | |
| 77 parser.add_argument('api_name', | |
| 78 help="API to enable. The current list of experimental " | |
| 79 "APIs can be found in RuntimeFeatures.in") | |
| 80 parser.add_argument('--key-file', | |
| 81 help='Ed25519 private key file to sign the token with', | |
| 82 default="eftest.key") | |
| 83 expiry_group = parser.add_mutually_exclusive_group() | |
| 84 expiry_group.add_argument('--expire-days', | |
| 85 help='Days from now when the token should exipire', | |
| 86 type=int, | |
| 87 default=42) | |
| 88 expiry_group.add_argument('--expire-timestamp', | |
| 89 help="Exact time (milliseconds since 1970-01-01 " | |
| 90 "00:00:00 UTC) when the token should exipire", | |
| 91 type=int) | |
| 92 | |
| 93 args = parser.parse_args() | |
| 94 expiry = ExpiryFromArgs(args) | |
| 95 | |
| 96 key_file = open(os.path.expanduser(args.key_file)) | |
| 97 private_key = key_file.read() | |
| 98 | |
| 99 # Validate that the key file read was a proper Ed25519 key -- running the | |
| 100 # publickey method on the first half of the key should return the second | |
| 101 # half. | |
| 102 if (len(private_key) != 64 or | |
| 103 ed25519.publickey(private_key[:32]) != private_key[32:]): | |
| 104 print("Unable to use the specified private key file.") | |
| 105 sys.exit(1) | |
| 106 | |
| 107 token_data = GenerateTokenData(args.hostname, args.api_name, expiry) | |
| 108 signature = Sign(private_key, token_data) | |
| 109 | |
| 110 # Verify that that the signature is correct before printing it. | |
| 111 try: | |
| 112 ed25519.checkvalid(signature, token_data, private_key[32:]) | |
| 113 except Exception: | |
| 114 print "There was an error generating the signature." | |
| 115 sys.exit(1) | |
| 116 | |
| 117 print FormatToken(signature, token_data) | |
| 118 | |
| 119 if __name__ == "__main__": | |
| 120 main() | |
| OLD | NEW |