files: """ Implements the App Identity API. """
  
  import json
  import logging
  import time
  import urllib
  import urllib2
  
  from kazoo.exceptions import KazooException
  from kazoo.exceptions import NoNodeError
  from kazoo.exceptions import NodeExistsError
  from tornado.ioloop import IOLoop
  
  from appscale.api_server import app_identity_service_pb2 as service_pb
  from appscale.api_server import crypto
  from appscale.api_server.base_service import BaseService
  from appscale.api_server.constants import ApplicationError
  from appscale.api_server.constants import CallNotFound
  from appscale.api_server.crypto import (
      AccessToken, PrivateKey, PublicCertificate)
  from appscale.common.async_retrying import retry_children_watch_coroutine
  
  logger = logging.getLogger(__name__)
  
  
  class UnknownError(Exception):
      """ Indicates that the request cannot be completed at this time. """
      pass
  
  
  class AppIdentityService(BaseService):
      """ Implements the App Identity API. """
      SERVICE_NAME = 'app_identity_service'
  
      # A dummy bucket name for satisfying calls.
      DEFAULT_GCS_BUCKET_NAME = 'app_default_bucket'
  
      # The appropriate messages for each API call.
      METHODS = {'SignForApp': (service_pb.SignForAppRequest,
                                service_pb.SignForAppResponse),
                 'GetPublicCertificatesForApp': (
                     service_pb.GetPublicCertificateForAppRequest,
                     service_pb.GetPublicCertificateForAppResponse),
                 'GetServiceAccountName': (
                     service_pb.GetServiceAccountNameRequest,
                     service_pb.GetServiceAccountNameResponse),
                 'GetAccessToken': (service_pb.GetAccessTokenRequest,
                                    service_pb.GetAccessTokenResponse),
                 'GetDefaultGcsBucketName': (
                     service_pb.GetDefaultGcsBucketNameRequest,
                     service_pb.GetDefaultGcsBucketNameResponse)}
  
      def __init__(self, project_id, zk_client):
          """ Creates a new AppIdentityService.
  
          Args:
              project_id: A string specifying the project ID.
              zk_client: A KazooClient.
          """
          super(AppIdentityService, self).__init__(self.SERVICE_NAME)
  
          self.project_id = project_id
          project_node = '/appscale/projects/{}'.format(self.project_id)
  
          self._zk_client = zk_client
          self._key_node = '{}/private_key'.format(project_node)
          self._key = None
          self._ensure_private_key()
          self._zk_client.DataWatch(self._key_node, self._update_key)
  
          self._certs_node = '{}/certificates'.format(project_node)
          self._zk_client.ensure_path(self._certs_node)
          self._certs = []
          self._zk_client.ChildrenWatch(self._certs_node, self._update_certs)
  
          self._service_accounts_node = '{}/service_accounts'.format(
              project_node)
  
      def get_public_certificates(self):
          """ Retrieves a list of valid public certificates for the project.
  
          Returns:
              A list of PublicCertificate objects.
          Raises:
              UnknownError if unable to retrieve any certificates.
          """
          valid_certs = []
          for cert in self._certs:
              if cert.expired:
                  try:
                      self._remove_cert(cert)
                  except (KazooException, UnknownError):
                      pass
              else:
                  valid_certs.append(cert)
  
          if valid_certs:
              return valid_certs
  
          # If there are no valid certificates, try to generate a a new one.
          if self._key is None:
              raise UnknownError('A private key is not configured')
  
          new_cert = PublicCertificate.from_key(self._key, self.project_id)
          try:
              self._zk_client.create('{}/cert'.format(self._certs_node),
                                     new_cert.to_json(), sequence=True)
          except KazooException:
              raise UnknownError('Unable to create new certificate')
  
          return [new_cert]
  
      def get_service_account_name(self):
          """ Retrieves the default service account name.
  
          Returns:
              A string specifying the service account name.
          Raises:
              UnknownError if no service account is configured.
          """
          if self._key is None:
              raise UnknownError('A private key is not configured')
  
          return self._key.key_name
  
      def get_access_token(self, scopes, service_account_id=None,
                           service_account_name=None):
          """ Generates an access token from a service account.
  
          Args:
              scopes: A list of strings specifying scopes.
              service_account_id: An integer specifying a service account ID.
              service_account_name: A string specifying a service account name.
          Returns:
              An AccessToken.
          Raises:
              UnknownError if the service account is not configured.
          """
          # TODO: Check if it makes sense to store the audience with the service
          # account definition.
          default_audience = 'https://www.googleapis.com/oauth2/v4/token'
  
          if service_account_id is not None:
              raise UnknownError(
                  '{} is not configured'.format(service_account_id))
  
          if (service_account_name is not None and
              service_account_name != self._key.key_name):
              service_account_node = '/'.join([self._service_accounts_node,
                                               service_account_name])
              try:
                  account_details = self._zk_client.get(service_account_node)[0]
              except NoNodeError:
                  raise UnknownError(
                      '{} is not configured'.format(service_account_name))
  
              try:
                  account_details = json.loads(account_details)
              except ValueError:
                  raise UnknownError(
                      '{} has invalid data'.format(service_account_node))
  
              pem = account_details['private_key'].encode('utf-8')
              key = PrivateKey.from_pem(service_account_name, pem)
              assertion = key.generate_assertion(default_audience, scopes)
  
              grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
              payload = urllib.urlencode({'grant_type': grant_type,
                                          'assertion': assertion})
              try:
                  response = urllib2.urlopen(default_audience, payload)
              except urllib2.HTTPError as error:
                  raise UnknownError(error.msg)
              except urllib2.URLError as error:
                  raise UnknownError(error.reason)
  
              token_details = json.loads(response.read())
              logging.info('Generated access token: {}'.format(token_details))
              expiration_time = int(time.time()) + token_details['expires_in']
              return AccessToken(token_details['access_token'], expiration_time)
  
          if self._key is None:
              raise UnknownError('A private key is not configured')
  
          assertion = self._key.generate_assertion(default_audience, scopes)
          # TODO: Generate access token from assertion.
          return AccessToken(assertion, int(time.time() + 3600))
  
      def sign(self, blob):
          """ Signs a message with the project's key.
  
          Args:
              blob: A binary type containing an arbitrary payload.
          Returns:
              A binary type containing the signed payload.
          Raises:
              UnknownError if the payload cannot be signed.
          """
          if self._key is None:
              raise UnknownError('A private key is not configured')
  
          return self._key.sign(blob)
  
      def make_call(self, method, encoded_request):
          """ Makes the appropriate API call for a given request.
  
          Args:
              method: A string specifying the API method.
              encoded_request: A binary type containing the request details.
          Returns:
              A binary type containing the response details.
          """
          if method not in self.METHODS:
              raise CallNotFound(
                  '{}.{} does not exist'.format(self.SERVICE_NAME, method))
  
          request = self.METHODS[method][0]()
          request.ParseFromString(encoded_request)
  
          response = self.METHODS[method][1]()
  
          if method == 'SignForApp':
              response.key_name = self._key.key_name
              try:
                  response.signature_bytes = self.sign(request.bytes_to_sign)
              except UnknownError as error:
                  logger.exception('Unable to sign bytes')
                  raise ApplicationError(service_pb.UNKNOWN_ERROR, str(error))
          elif method == 'GetPublicCertificatesForApp':
              try:
                  public_certs = self.get_public_certificates()
              except UnknownError as error:
                  logger.exception('Unable to get public certificates')
                  raise ApplicationError(service_pb.UNKNOWN_ERROR, str(error))
  
              for public_cert in public_certs:
                  cert = response.public_certificate_list.add()
                  cert.key_name = cert.key_name = public_cert.key_name
                  cert.x509_certificate_pem = public_cert.pem
          elif method == 'GetServiceAccountName':
              try:
                  response.service_account_name = self.get_service_account_name()
              except UnknownError as error:
                  logger.exception('Unable to get service account name')
                  raise ApplicationError(service_pb.UNKNOWN_ERROR, str(error))
          elif method == 'GetAccessToken':
              service_account_id = None
              if request.HasField('service_account_id'):
                  service_account_id = request.service_account_id
  
              service_account_name = None
              if request
  
  [... the rest is omitted ...]
files: APIServer/appscale/api_server/app_identity.py
files: """ Implements the App Identity API. """
  
  import logging
  
  from kazoo.exceptions import KazooException
  from kazoo.exceptions import NoNodeError
  from kazoo.exceptions import NodeExistsError
  from tornado.ioloop import IOLoop
  
  from appscale.api_server import app_identity_service_pb2 as service_pb
  from appscale.api_server import crypto
  from appscale.api_server.base_service import BaseService
  from appscale.api_server.constants import ApplicationError
  from appscale.api_server.constants import CallNotFound
  from appscale.api_server.crypto import PrivateKey
  from appscale.api_server.crypto import PublicCertificate
  from appscale.common.async_retrying import retry_children_watch_coroutine
  
  logger = logging.getLogger(__name__)
  
  
  class UnknownError(Exception):
      """ Indicates that the request cannot be completed at this time. """
      pass
  
  
  class AppIdentityService(BaseService):
      """ Implements the App Identity API. """
      SERVICE_NAME = 'app_identity_service'
  
      # A dummy bucket name for satisfying calls.
      DEFAULT_GCS_BUCKET_NAME = 'app_default_bucket'
  
      # The appropriate messages for each API call.
      METHODS = {'SignForApp': (service_pb.SignForAppRequest,
                                service_pb.SignForAppResponse),
                 'GetPublicCertificatesForApp': (
                     service_pb.GetPublicCertificateForAppRequest,
                     service_pb.GetPublicCertificateForAppResponse),
                 'GetServiceAccountName': (
                     service_pb.GetServiceAccountNameRequest,
                     service_pb.GetServiceAccountNameResponse),
                 'GetAccessToken': (service_pb.GetAccessTokenRequest,
                                    service_pb.GetAccessTokenResponse),
                 'GetDefaultGcsBucketName': (
                     service_pb.GetDefaultGcsBucketNameRequest,
                     service_pb.GetDefaultGcsBucketNameResponse)}
  
      def __init__(self, project_id, zk_client):
          """ Creates a new AppIdentityService.
  
          Args:
              project_id: A string specifying the project ID.
              zk_client: A KazooClient.
          """
          super(AppIdentityService, self).__init__(self.SERVICE_NAME)
  
          self.project_id = project_id
  
          self._zk_client = zk_client
          self._key_node = '/appscale/projects/{}/private_key'.format(
              self.project_id)
          self._key = None
          self._ensure_private_key()
          self._zk_client.DataWatch(self._key_node, self._update_key)
  
          self._certs_node = '/appscale/projects/{}/certificates'.format(
              self.project_id)
          self._zk_client.ensure_path(self._certs_node)
          self._certs = []
          self._zk_client.ChildrenWatch(self._certs_node, self._update_certs)
  
      def get_public_certificates(self):
          """ Retrieves a list of valid public certificates for the project.
  
          Returns:
              A list of PublicCertificate objects.
          Raises:
              UnknownError if unable to retrieve any certificates.
          """
          valid_certs = []
          for cert in self._certs:
              if cert.expired:
                  try:
                      self._remove_cert(cert)
                  except (KazooException, UnknownError):
                      pass
              else:
                  valid_certs.append(cert)
  
          if valid_certs:
              return valid_certs
  
          # If there are no valid certificates, try to generate a a new one.
          if self._key is None:
              raise UnknownError('A private key is not configured')
  
          new_cert = PublicCertificate.from_key(self._key, self.project_id)
          try:
              self._zk_client.create('{}/cert'.format(self._certs_node),
                                     new_cert.to_json(), sequence=True)
          except KazooException:
              raise UnknownError('Unable to create new certificate')
  
          return [new_cert]
  
      def get_service_account_name(self):
          """ Retrieves the default service account name.
  
          Returns:
              A string specifying the service account name.
          Raises:
              UnknownError if no service account is configured.
          """
          if self._key is None:
              raise UnknownError('A private key is not configured')
  
          return self._key.key_name
  
      def get_access_token(self, scopes, service_account_id=None,
                           service_account_name=None):
          """ Generates an access token from a service account.
  
          Args:
              scopes: A list of strings specifying scopes.
              service_account_id: An integer specifying a service account ID.
              service_account_name: A string specifying a service account name.
          Returns:
              An AccessToken.
          Raises:
              UnknownError if the service account is not configured.
          """
          if self._key is None:
              raise UnknownError('A private key is not configured')
  
          if service_account_id is not None:
              raise UnknownError(
                  '{} is not configured'.format(service_account_id))
  
          if (service_account_name is not None and
              service_account_name != self._key.key_name):
              raise UnknownError(
                  '{} is not configured'.format(service_account_name))
  
          return self._key.generate_access_token(self.project_id, scopes)
  
      def sign(self, blob):
          """ Signs a message with the project's key.
  
          Args:
              blob: A binary type containing an arbitrary payload.
          Returns:
              A binary type containing the signed payload.
          Raises:
              UnknownError if the payload cannot be signed.
          """
          if self._key is None:
              raise UnknownError('A private key is not configured')
  
          return self._key.sign(blob)
  
      def make_call(self, method, encoded_request):
          """ Makes the appropriate API call for a given request.
  
          Args:
              method: A string specifying the API method.
              encoded_request: A binary type containing the request details.
          Returns:
              A binary type containing the response details.
          """
          if method not in self.METHODS:
              raise CallNotFound(
                  '{}.{} does not exist'.format(self.SERVICE_NAME, method))
  
          request = self.METHODS[method][0]()
          request.ParseFromString(encoded_request)
  
          response = self.METHODS[method][1]()
  
          if method == 'SignForApp':
              response.key_name = self._key.key_name
              try:
                  response.signature_bytes = self.sign(request.bytes_to_sign)
              except UnknownError as error:
                  logger.exception('Unable to sign bytes')
                  raise ApplicationError(service_pb.UNKNOWN_ERROR, str(error))
          elif method == 'GetPublicCertificatesForApp':
              try:
                  public_certs = self.get_public_certificates()
              except UnknownError as error:
                  logger.exception('Unable to get public certificates')
                  raise ApplicationError(service_pb.UNKNOWN_ERROR, str(error))
  
              for public_cert in public_certs:
                  cert = response.public_certificate_list.add()
                  cert.key_name = cert.key_name = public_cert.key_name
                  cert.x509_certificate_pem = public_cert.pem
          elif method == 'GetServiceAccountName':
              try:
                  response.service_account_name = self.get_service_account_name()
              except UnknownError as error:
                  logger.exception('Unable to get service account name')
                  raise ApplicationError(service_pb.UNKNOWN_ERROR, str(error))
          elif method == 'GetAccessToken':
              service_account_id = None
              if request.HasField('service_account_id'):
                  service_account_id = request.service_account_id
  
              service_account_name = None
              if request.HasField('service_account_name'):
                  service_account_name = request.service_account_name
  
              try:
                  token = self.get_access_token(
                      list(request.scope), service_account_id,
                      service_account_name)
              except UnknownError as error:
                  logger.exception('Unable to get access token')
                  raise ApplicationError(service_pb.UNKNOWN_ERROR, str(error))
  
              response.access_token = token.token
              response.expiration_time = token.expiration_time
          elif method == 'GetDefaultGcsBucketName':
              response.default_gcs_bucket_name = self.DEFAULT_GCS_BUCKET_NAME
  
          return response.SerializeToString()
  
      def _remove_cert(self, cert):
          """ Removes a certificate node.
  
          Args:
              cert: A PublicCertificate.
          Raises:
              UnknownError if the given certificate does not have node info.
          """
          if cert.node_name is None:
              raise UnknownError('Certificate has no ZooKeeper location')
  
          full_path = '/'.join([self._certs_node, cert.node_name])
          self._zk_client.delete(full_path)
  
      def _ensure_private_key(self):
          """ Ensures the project has a private key. """
          key_exists = self._zk_client.exists(self._key_node) is not None
          if key_exists:
              return
  
          key = PrivateKey.generate('{}_1'.format(self.project_id))
          try:
              self._zk_client.create(self._key_node, key.to_json())
          except NodeExistsError:
              pass
  
      def _update_key(self, new_data, _):
          """ Updates the private key.
  
          Args:
              new_data: A JSON string containing the new key details.
          """
          try:
              self._key = PrivateKey.from_json(new_data)
          except crypto.InvalidKey:
              logger.error('Invalid private key at {}'.format(self
  
  [... the rest is omitted ...]
files: APIServer/appscale/api_server/app_identity.py
files: """ Manages cryptography-related data and operations. """
  
  import base64
  import datetime
  import json
  import time
  
  import six
  from cryptography import x509
  from cryptography.hazmat.backends import default_backend
  from cryptography.hazmat.primitives import hashes
  from cryptography.hazmat.primitives import serialization
  from cryptography.hazmat.primitives.asymmetric import padding
  from cryptography.hazmat.primitives.asymmetric import rsa
  from cryptography.x509.oid import NameOID
  
  # The length of time a certificate should remain valid for.
  CERTIFICATE_TTL = datetime.timedelta(days=10)
  
  # The generated key length in bits.
  KEY_SIZE = 2048
  
  # The public exponent to use when generating keys.
  PUBLIC_EXPONENT = 65537
  
  
  class InvalidKey(Exception):
      """ Indicates that a private key is invalid. """
      pass
  
  
  class InvalidCertificate(Exception):
      """ Indicates that a certificate is invalid. """
      pass
  
  
  class AccessToken(object):
      """ Holds an access token and its metadata. """
      def __init__(self, token, expiration_time):
          """ Creates a new AccessToken.
  
          Args:
              token: A string containing an access token.
              expiration_time: An integer specifying a unix timestamp.
          """
          self.token = token
          self.expiration_time = expiration_time
  
  
  class PrivateKey(object):
      """ Holds a private key and its metadata. """
      ENCODING = serialization.Encoding.PEM
      ENCRYPTION = serialization.NoEncryption()
      FORMAT = serialization.PrivateFormat.PKCS8
      PADDING = padding.PKCS1v15()
      TOKEN_LIFETIME = 3600
  
      def __init__(self, key_name, key):
          """ Creates a new PrivateKey.
  
          Args:
              key_name: A string specifying the key name.
              key: An RSAPrivateKey.
          """
          self.key_name = key_name
          self.key = key
  
      @property
      def pem(self):
          """ Serializes the private key.
  
          Returns:
              A binary type containing a PEM-formatted key.
          """
          return self.key.private_bytes(self.ENCODING, self.FORMAT,
                                        self.ENCRYPTION)
  
      def generate_assertion(self, audience, scopes):
          """ Creates an access token signed by the key.
  
          Args:
              project_id: A string specifying the project ID.
              scopes: A list of strings specifying scopes.
          Returns:
              An AccessToken.
          """
          def encode_part(part):
              if isinstance(part, dict):
                  part = json.dumps(part, separators=(',', ':')).encode('utf-8')
  
              return base64.urlsafe_b64encode(part).rstrip('=')
  
          header = encode_part({'typ': 'JWT', 'alg': 'RS256'})
          current_time = int(time.time())
          metadata = encode_part({'iss': self.key_name,
                                  'aud': audience,
                                  'scope': ' '.join(scopes),
                                  'iat': current_time,
                                  'exp': current_time + self.TOKEN_LIFETIME})
  
          signature = self.sign('.'.join([header, metadata]))
          return '.'.join([header, metadata, encode_part(signature)])
  
      def sign(self, blob):
          """ Signs a given payload.
  
          Args:
              blob: A binary type containing an arbitrary payload.
          Returns:
              A binary type containing the signed payload.
          """
          return self.key.sign(blob, self.PADDING, hashes.SHA256())
  
      def to_json(self):
          """ Serializes the key details as JSON.
  
          Returns:
              A string containing the JSON-encoded key details.
          """
          return json.dumps({'key_name': self.key_name, 'pem': self.pem})
  
      @classmethod
      def generate(cls, key_name):
          """ Creates a new key with the given name.
  
          Args:
              key_name: A string specifying the key name.
          Returns:
              A PrivateKey.
          """
          key = rsa.generate_private_key(PUBLIC_EXPONENT, KEY_SIZE,
                                         default_backend())
          return cls(key_name, key)
  
      @classmethod
      def from_json(cls, key_data):
          """ Creates a new key from a JSON-encoded string.
  
          Args:
              key_data: A JSON string containing key details.
          Returns:
              A PrivateKey.
          """
          try:
              key_details = json.loads(key_data)
          except (TypeError, ValueError):
              raise InvalidKey('Invalid JSON')
  
          try:
              key_name = key_details['key_name']
              pem = key_details['pem'].encode('utf-8')
          except KeyError as error:
              raise InvalidKey(str(error))
  
          return cls.from_pem(key_name, pem)
  
      @classmethod
      def from_pem(cls, key_name, pem):
          try:
              key = serialization.load_pem_private_key(
                  pem, password=None, backend=default_backend())
          except ValueError as error:
              raise InvalidKey(str(error))
  
          return cls(key_name, key)
  
  
  class PublicCertificate(object):
      """ Holds a certificate and its metadata. """
      ENCODING = serialization.Encoding.PEM
  
      def __init__(self, key_name, cert, node_name=None):
          """ Creates a new PublicCertificate.
  
          Args:
              key_name: A string specifying the key name.
              cert: A PEM-formatted binary type specifying the certificate.
              node_name: A string specifying the ZooKeeper node name.
          """
          self.key_name = key_name
          self.cert = cert
          self.node_name = node_name
  
      @property
      def expired(self):
          """ Indicates whether or not the certificate has expired.
  
          Returns:
              A boolean.
          """
          return datetime.datetime.utcnow() > self.cert.not_valid_after
  
      @property
      def pem(self):
          """ Serializes the certificate.
  
          Returns:
              A binary type containing a PEM-formatted certificate. """
          return self.cert.public_bytes(self.ENCODING)
  
      def to_json(self):
          """ Serializes the certificate details as JSON.
  
          Returns:
              A string containing the JSON-encoded certificate details.
          """
          return json.dumps({'key_name': self.key_name, 'pem': self.pem})
  
      @classmethod
      def from_key(cls, private_key, project_id):
          """ Creates a new certificate from a private key.
  
          Args:
              private_key: An RSAPrivateKey.
              project_id: A string specifying the project ID.
          Returns:
              A PublicCertificate.
          """
          # NameAttribute requires unicode objects in Python 2.
          if not isinstance(project_id, six.text_type):
              project_id = six.u(project_id)
  
          subject = x509.Name(
              [x509.NameAttribute(NameOID.COMMON_NAME, project_id)])
          issuer = x509.Name(
              [x509.NameAttribute(NameOID.COMMON_NAME, u'AppScale')])
  
          cert = x509.CertificateBuilder().\
              subject_name(subject).\
              issuer_name(issuer).\
              public_key(private_key.key.public_key()).\
              serial_number(x509.random_serial_number()).\
              not_valid_before(datetime.datetime.utcnow()).\
              not_valid_after(datetime.datetime.utcnow() + CERTIFICATE_TTL).\
              sign(private_key.key, hashes.SHA256(), default_backend())
  
          return cls(private_key.key_name, cert)
  
      @classmethod
      def from_node(cls, node_name, cert_data):
          """ Creates a new certificate from a ZooKeeper node.
  
          Args:
              node_name: The name of the ZooKeeper node.
              cert_data: A JSON string containing the certificate details.
          Returns:
              A PublicCertificate.
          """
          try:
              cert_details = json.loads(cert_data)
          except ValueError:
              raise InvalidCertificate('Invalid JSON')
  
          try:
              key_name = cert_details['key_name']
              pem = cert_details['pem'].encode('utf-8')
          except KeyError as error:
              raise InvalidCertificate(str(error))
  
          try:
              cert = x509.load_pem_x509_certificate(pem, default_backend())
          except ValueError as error:
              raise InvalidCertificate(str(error))
  
          return cls(key_name, cert, node_name)
  
  
  [... the rest is omitted ...]
files: APIServer/appscale/api_server/crypto.py
files: """ Manages cryptography-related data and operations. """
  
  import base64
  import datetime
  import json
  import time
  
  import six
  from cryptography import x509
  from cryptography.hazmat.backends import default_backend
  from cryptography.hazmat.primitives import hashes
  from cryptography.hazmat.primitives import serialization
  from cryptography.hazmat.primitives.asymmetric import padding
  from cryptography.hazmat.primitives.asymmetric import rsa
  from cryptography.x509.oid import NameOID
  
  # The length of time a certificate should remain valid for.
  CERTIFICATE_TTL = datetime.timedelta(days=10)
  
  # The generated key length in bits.
  KEY_SIZE = 2048
  
  # The public exponent to use when generating keys.
  PUBLIC_EXPONENT = 65537
  
  
  class InvalidKey(Exception):
      """ Indicates that a private key is invalid. """
      pass
  
  
  class InvalidCertificate(Exception):
      """ Indicates that a certificate is invalid. """
      pass
  
  
  class AccessToken(object):
      """ Holds an access token and its metadata. """
      def __init__(self, token, expiration_time):
          """ Creates a new AccessToken.
  
          Args:
              token: A string containing an access token.
              expiration_time: An integer specifying a unix timestamp.
          """
          self.token = token
          self.expiration_time = expiration_time
  
  
  class PrivateKey(object):
      """ Holds a private key and its metadata. """
      ENCODING = serialization.Encoding.PEM
      ENCRYPTION = serialization.NoEncryption()
      FORMAT = serialization.PrivateFormat.PKCS8
      PADDING = padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                            salt_length=padding.PSS.MAX_LENGTH)
      TOKEN_LIFETIME = 1800
  
      def __init__(self, key_name, key):
          """ Creates a new PrivateKey.
  
          Args:
              key_name: A string specifying the key name.
              key: An RSAPrivateKey.
          """
          self.key_name = key_name
          self.key = key
  
      @property
      def pem(self):
          """ Serializes the private key.
  
          Returns:
              A binary type containing a PEM-formatted key.
          """
          return self.key.private_bytes(self.ENCODING, self.FORMAT,
                                        self.ENCRYPTION)
  
      def generate_access_token(self, project_id, scopes):
          """ Creates an access token signed by the key.
  
          Args:
              project_id: A string specifying the project ID.
              scopes: A list of strings specifying scopes.
          Returns:
              An AccessToken.
          """
          def encode_part(part):
              serialized = json.dumps(part, separators=(',', ':'))
              return base64.urlsafe_b64encode(serialized).rstrip('=')
  
          header = encode_part({'typ': 'JWT', 'alg': 'RS256'})
          expiration_time = int(time.time() + self.TOKEN_LIFETIME)
          metadata = encode_part({'project_id': project_id,
                                  'exp': expiration_time,
                                  'scopes': scopes})
  
          signature = self.sign('.'.join([header, metadata]))
          encoded_sig = base64.urlsafe_b64encode(signature).rstrip('=')
          token = '.'.join([header, metadata, encoded_sig])
          return AccessToken(token, expiration_time)
  
      def sign(self, blob):
          """ Signs a given payload.
  
          Args:
              blob: A binary type containing an arbitrary payload.
          Returns:
              A binary type containing the signed payload.
          """
          return self.key.sign(blob, self.PADDING, hashes.SHA256())
  
      def to_json(self):
          """ Serializes the key details as JSON.
  
          Returns:
              A string containing the JSON-encoded key details.
          """
          return json.dumps({'key_name': self.key_name, 'pem': self.pem})
  
      @classmethod
      def generate(cls, key_name):
          """ Creates a new key with the given name.
  
          Args:
              key_name: A string specifying the key name.
          Returns:
              A PrivateKey.
          """
          key = rsa.generate_private_key(PUBLIC_EXPONENT, KEY_SIZE,
                                         default_backend())
          return cls(key_name, key)
  
      @classmethod
      def from_json(cls, key_data):
          """ Creates a new key from a JSON-encoded string.
  
          Args:
              key_data: A JSON string containing key details.
          Returns:
              A PrivateKey.
          """
          try:
              key_details = json.loads(key_data)
          except (TypeError, ValueError):
              raise InvalidKey('Invalid JSON')
  
          try:
              key_name = key_details['key_name']
              pem = key_details['pem'].encode('utf-8')
          except KeyError as error:
              raise InvalidKey(str(error))
  
          try:
              key = serialization.load_pem_private_key(
                  pem, password=None, backend=default_backend())
          except ValueError as error:
              raise InvalidKey(str(error))
  
          return cls(key_name, key)
  
  
  class PublicCertificate(object):
      """ Holds a certificate and its metadata. """
      ENCODING = serialization.Encoding.PEM
  
      def __init__(self, key_name, cert, node_name=None):
          """ Creates a new PublicCertificate.
  
          Args:
              key_name: A string specifying the key name.
              cert: A PEM-formatted binary type specifying the certificate.
              node_name: A string specifying the ZooKeeper node name.
          """
          self.key_name = key_name
          self.cert = cert
          self.node_name = node_name
  
      @property
      def expired(self):
          """ Indicates whether or not the certificate has expired.
  
          Returns:
              A boolean.
          """
          return datetime.datetime.utcnow() > self.cert.not_valid_after
  
      @property
      def pem(self):
          """ Serializes the certificate.
  
          Returns:
              A binary type containing a PEM-formatted certificate. """
          return self.cert.public_bytes(self.ENCODING)
  
      def to_json(self):
          """ Serializes the certificate details as JSON.
  
          Returns:
              A string containing the JSON-encoded certificate details.
          """
          return json.dumps({'key_name': self.key_name, 'pem': self.pem})
  
      @classmethod
      def from_key(cls, private_key, project_id):
          """ Creates a new certificate from a private key.
  
          Args:
              private_key: An RSAPrivateKey.
              project_id: A string specifying the project ID.
          Returns:
              A PublicCertificate.
          """
          # NameAttribute requires unicode objects in Python 2.
          if not isinstance(project_id, six.text_type):
              project_id = six.u(project_id)
  
          subject = x509.Name(
              [x509.NameAttribute(NameOID.COMMON_NAME, project_id)])
          issuer = x509.Name(
              [x509.NameAttribute(NameOID.COMMON_NAME, u'AppScale')])
  
          cert = x509.CertificateBuilder().\
              subject_name(subject).\
              issuer_name(issuer).\
              public_key(private_key.key.public_key()).\
              serial_number(x509.random_serial_number()).\
              not_valid_before(datetime.datetime.utcnow()).\
              not_valid_after(datetime.datetime.utcnow() + CERTIFICATE_TTL).\
              sign(private_key.key, hashes.SHA256(), default_backend())
  
          return cls(private_key.key_name, cert)
  
      @classmethod
      def from_node(cls, node_name, cert_data):
          """ Creates a new certificate from a ZooKeeper node.
  
          Args:
              node_name: The name of the ZooKeeper node.
              cert_data: A JSON string containing the certificate details.
          Returns:
              A PublicCertificate.
          """
          try:
              cert_details = json.loads(cert_data)
          except ValueError:
              raise InvalidCertificate('Invalid JSON')
  
          try:
              key_name = cert_details['key_name']
              pem = cert_details['pem'].encode('utf-8')
          except KeyError as error:
              raise InvalidCertificate(str(error))
  
          try:
              cert = x509.load_pem_x509_certificate(pem, default_backend())
          except ValueError as error:
              raise InvalidCertificate(str(error))
  
          return cls(key_name, cert, node_name)
  
  
  [... the rest is omitted ...]
files: APIServer/appscale/api_server/crypto.py