def generate_jwt(claims, priv_key=None, algorithm='PS512', lifetime=None, expires=None, not_before=None, jti_size=16, other_headers=None): """ Generate a JSON Web Token. :param claims: The claims you want included in the signature. :type claims: dict :param priv_key: The private key to be used to sign the token. Note: if you pass ``None`` then the token will be returned with an empty cryptographic signature and :obj:`algorithm` will be forced to the value ``none``. :type priv_key: `jwcrypto.jwk.JWK <https://jwcrypto.readthedocs.io/en/latest/jwk.html>`_ :param algorithm: The algorithm to use for generating the signature. ``RS256``, ``RS384``, ``RS512``, ``PS256``, ``PS384``, ``PS512``, ``ES256``, ``ES384``, ``ES512``, ``HS256``, ``HS384``, ``HS512`` and ``none`` are supported. :type algorithm: str :param lifetime: How long the token is valid for. :type lifetime: datetime.timedelta :param expires: When the token expires (if :obj:`lifetime` isn't specified) :type expires: datetime.datetime :param not_before: When the token is valid from. Defaults to current time (if ``None`` is passed). :type not_before: datetime.datetime :param jti_size: Size in bytes of the unique token ID to put into the token (can be used to detect replay attacks). Defaults to 16 (128 bits). Specify 0 or ``None`` to omit the JTI from the token. :type jti_size: int :param other_headers: Any headers other than "typ" and "alg" may be specified, they will be included in the header. :type other_headers: dict :rtype: unicode :returns: The JSON Web Token. Note this includes a header, the claims and a cryptographic signature. The following extra claims are added, per the `JWT spec <http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html>`_: - **exp** (*IntDate*) -- The UTC expiry date and time of the token, in number of seconds from 1970-01-01T0:0:0Z UTC. - **iat** (*IntDate*) -- The UTC date and time at which the token was generated. - **nbf** (*IntDate*) -- The UTC valid-from date and time of the token. - **jti** (*str*) -- A unique identifier for the token. :raises: ValueError: If other_headers contains either the "typ" or "alg" header """ header = { 'typ': 'JWT', 'alg': algorithm if priv_key else 'none' } if other_headers is not None: redefined_keys = set(header.keys()) & set(other_headers.keys()) if redefined_keys: raise ValueError('other_headers re-specified the headers: {}'.format(', '.join(redefined_keys))) header.update(other_headers) claims = dict(claims) now = datetime.utcnow() if jti_size: claims['jti'] = base64url_encode(urandom(jti_size)) claims['nbf'] = timegm((not_before or now).utctimetuple()) claims['iat'] = timegm(now.utctimetuple()) if lifetime: claims['exp'] = timegm((now + lifetime).utctimetuple()) elif expires: claims['exp'] = timegm(expires.utctimetuple()) if header['alg'] == 'none': signature = '' else: token = JWS(json_encode(claims)) token.add_signature(priv_key, protected=header) signature = json_decode(token.serialize())['signature'] return u'%s.%s.%s' % ( base64url_encode(json_encode(header)), base64url_encode(json_encode(claims)), signature )
def make_tok(self, key, alg, name): pri_key = JWK(**key) protected = {"typ": "JOSE+JSON", "kid": key["kid"], "alg": alg} plaintext = {"sub": name, "exp": int(time.time()) + (5 * 60)} S = JWS(payload=json_encode(plaintext)) S.add_signature(pri_key, None, json_encode(protected)) return S.serialize()
def make_tok(self, key, alg, name): pri_key = JWK(**key) protected = {"typ": "JOSE+JSON", "kid": key['kid'], "alg": alg} plaintext = {"sub": name, "exp": int(time.time()) + (5 * 60)} jws = JWS(payload=json_encode(plaintext)) jws.add_signature(pri_key, None, json_encode(protected)) return jws.serialize()
def sign_request(payload, nonce, jwk): # type: (dict, str, JWK) -> None header = generate_header(jwk) protected = dict(nonce=nonce) jws = JWS(json_encode(payload).encode()) jws.add_signature(jwk, header['alg'], protected, header) return json_decode(jws.serialize())
def generate(header, payload, priv_pem): priv_pem = json_decode(priv_pem.replace('\n', '\\n')) if priv_pem.startswith("-----BEGIN"): priv_key = JWK.from_pem(to_bytes_2and3(priv_pem)) else: priv_key = JWK(kty='oct', k=base64url_encode(priv_pem)) sig = JWS(payload) sig.add_signature(priv_key, protected=header) sys.stdout.write(sig.serialize(compact=True))
def verify(sjws, pub_pem): sjws = json_decode(sjws) pub_pem = json_decode(pub_pem.replace('\n', '\\n')) if pub_pem.startswith("-----BEGIN"): pub_key = JWK.from_pem(to_bytes_2and3(pub_pem)) else: pub_key = JWK(kty='oct', k=base64url_encode(pub_pem)) sig = JWS() sig.deserialize(sjws, pub_key) sys.stdout.write(base64url_decode(json_decode(sig.serialize())['payload']))
def verify_proof(did_document: list, proof: jws.JWS, signer: str): document_sha256 = hashlib.sha256() document_sha256.update(json.dumps(did_document).encode('utf-8')) document_sha256_b64 = base64url_encode(document_sha256.digest()) payload = json.loads(proof.objects['payload'].decode()) if (document_sha256_b64 != payload['sha-256']): raise Exception("The sha-256 field of the proof payload is not valid") return -1 signer_jwk = did_to_jwk(signer) proof.verify(signer_jwk)
def resolve(self, next, root, info, **args): headers = info.context.headers if "Authorization" in headers: token = JWS() token.deserialize(headers["Authorization"].removeprefix("Bearer ")) byte_payload = token.objects.pop("payload") json_payload = byte_payload.decode("UTF-8") payload = json.loads(json_payload) info.context.token = payload return next(root, info, **args)
def make_signed_token(self, key): """Signs the payload. Creates a JWS token with the header as the JWS protected header and the claims as the payload. See (:class:`jwcrypto.jws.JWS`) for details on the exceptions that may be reaised. :param key: A (:class:`jwcrypto.jwk.JWK`) key. """ t = JWS(self.claims) t.add_signature(key, protected=self.header) self.token = t
def parse(self, msg, name): """Parses the message. We check that the message is properly formatted. :param msg: a json-encoded value containing a JWS or JWE+JWS token :raises InvalidMessage: if the message cannot be parsed or validated :returns: A verified payload """ try: jtok = JWT(jwt=msg) except Exception as e: raise InvalidMessage('Failed to parse message: %s' % str(e)) try: token = jtok.token if isinstance(token, JWE): token.decrypt(self.kkstore.server_keys[KEY_USAGE_ENC]) # If an encrypted payload is received then there must be # a nested signed payload to verify the provenance. payload = token.payload.decode('utf-8') token = JWS() token.deserialize(payload) elif isinstance(token, JWS): pass else: raise TypeError("Invalid Token type: %s" % type(jtok)) # Retrieve client keys for later use self.client_keys = [ JWK(**self._get_key(token.jose_header, KEY_USAGE_SIG)), JWK(**self._get_key(token.jose_header, KEY_USAGE_ENC))] # verify token and get payload token.verify(self.client_keys[KEY_USAGE_SIG]) claims = json_decode(token.payload) except Exception as e: logger.debug('Failed to validate message', exc_info=True) raise InvalidMessage('Failed to validate message: %s' % str(e)) check_kem_claims(claims, name) self.name = name self.payload = claims.get('value') self.msg_type = 'kem' return {'type': self.msg_type, 'value': {'kid': self.client_keys[KEY_USAGE_ENC].key_id, 'claims': claims}}
def sign(message: str, signing_key: JWK, sha1_thumbprint: str, sha256_thumbprint: str) -> str: """Create a signature layer for a message for DCS""" jwstoken = JWS(payload=message) jwstoken.add_signature( key=signing_key, alg=None, protected=json_encode({ "alg": "RS256", "x5t": sha1_thumbprint, "x5t#S256": sha256_thumbprint }), ) return jwstoken.serialize(compact=True)
def deserialize(self, jwt, key=None): """Deserialize a JWT token. NOTE: Destroys any current status and tries to import the raw token provided. :param jwt: a 'raw' JWT token. :param key: A (:class:`jwcrypto.jwk.JWK`) verification or decryption key, or a (:class:`jwcrypto.jwk.JWKSet`) that contains a key indexed by the 'kid' header. """ c = jwt.count('.') if c == 2: self.token = JWS() elif c == 4: self.token = JWE() else: raise ValueError("Token format unrecognized") # Apply algs restrictions if any, before performing any operation if self._algs: self.token.allowed_algs = self._algs # now deserialize and also decrypt/verify (or raise) if we # have a key if key is None: self.token.deserialize(jwt, None) elif isinstance(key, JWK): self.token.deserialize(jwt, key) elif isinstance(key, JWKSet): self.token.deserialize(jwt, None) if 'kid' not in self.token.jose_header: raise JWTMissingKeyID('No key ID in JWT header') token_key = key.get_key(self.token.jose_header['kid']) if not token_key: raise JWTMissingKey('Key ID %s not in key set' % self.token.jose_header['kid']) if isinstance(self.token, JWE): self.token.decrypt(token_key) elif isinstance(self.token, JWS): self.token.verify(token_key) else: raise RuntimeError("Unknown Token Type") else: raise ValueError("Unrecognized Key Type") if key is not None: self.header = self.token.jose_header self.claims = self.token.payload.decode('utf-8') self._check_provided_claims()
def parse(self, msg, name): """Parses the message. We check that the message is properly formatted. :param msg: a json-encoded value containing a JWS or JWE+JWS token :raises InvalidMessage: if the message cannot be parsed or validated :returns: A verified payload """ try: jtok = JWT(jwt=msg) except Exception as e: raise InvalidMessage('Failed to parse message: %s' % str(e)) try: token = jtok.token if isinstance(token, JWE): token.decrypt(self.kkstore.server_keys[KEY_USAGE_ENC]) # If an encrypted payload is received then there must be # a nested signed payload to verify the provenance. payload = token.payload.decode('utf-8') token = JWS() token.deserialize(payload) elif isinstance(token, JWS): pass else: raise TypeError("Invalid Token type: %s" % type(jtok)) # Retrieve client keys for later use self.client_keys = [ JWK(**self._get_key(token.jose_header, KEY_USAGE_SIG)), JWK(**self._get_key(token.jose_header, KEY_USAGE_ENC)) ] # verify token and get payload token.verify(self.client_keys[KEY_USAGE_SIG]) claims = json_decode(token.payload) except Exception as e: logger.debug('Failed to validate message', exc_info=True) raise InvalidMessage('Failed to validate message: %s' % str(e)) check_kem_claims(claims, name) self.name = name self.payload = claims.get('value') self.msg_type = 'kem' return { 'type': self.msg_type, 'value': { 'kid': self.client_keys[KEY_USAGE_ENC].key_id, 'claims': claims } }
def _validateSignature(self, cvs): if cvs.antecedent is None: # Special cased to bootstrap data structures cvs.ratchet(self) jws = JWS() jws.deserialize(self.serialize()) tprint = jws.jose_header["kid"] if (cvs.antecedent.pkt == tprint): key = keystore()[tprint] jws.verify(key) else: # XXX: This case is only revavent on the GodBlock # TODO: support cases where block isn't signed by preceding key # (key recovery, issuer tombstone) raise NotImplementedError("TODO") # pragma: no cover
def _validateSignature(self, cvs): if cvs.antecedent is None: # Special cased to bootstrap data structures cvs.ratchet(self) jws = JWS() jws.deserialize(self.serialize()) tprint = jws.jose_header["kid"] idchain = chainstore()[self.creator] if self.creator not in cvs._recent_thumbprints: raise ChainValidationError("No grants for creator: " + self.creator) creator_print = cvs._recent_thumbprints[self.creator] if idchain.isSameOrSubsequent(tprint, creator_print): key = keystore()[tprint] jws.verify(key) else: raise ChainValidationError("Out of date key.")
def verify_jwt(jwt, pub_key=None, allowed_algs=None, iat_skew=timedelta(), checks_optional=False, ignore_not_implemented=False): """ Verify a JSON Web Token. :param jwt: The JSON Web Token to verify. :type jwt: str or unicode :param pub_key: The public key to be used to verify the token. Note: if you pass ``None`` and **allowed_algs** contains ``none`` then the token's signature will not be verified. :type pub_key: `jwcrypto.jwk.JWK <https://jwcrypto.readthedocs.io/en/latest/jwk.html>`_ :param allowed_algs: Algorithms expected to be used to sign the token. The ``in`` operator is used to test membership. :type allowed_algs: list or NoneType (meaning an empty list) :param iat_skew: The amount of leeway to allow between the issuer's clock and the verifier's clock when verifiying that the token was generated in the past. Defaults to no leeway. :type iat_skew: datetime.timedelta :param checks_optional: If ``False``, then the token must contain the **typ** header property and the **iat**, **nbf** and **exp** claim properties. :type checks_optional: bool :param ignore_not_implemented: If ``False``, then the token must *not* contain the **jku**, **jwk**, **x5u**, **x5c** or **x5t** header properties. :type ignore_not_implemented: bool :rtype: tuple :returns: ``(header, claims)`` if the token was verified successfully. The token must pass the following tests: - Its header must contain a property **alg** with a value in **allowed_algs**. - Its signature must verify using **pub_key** (unless its algorithm is ``none`` and ``none`` is in **allowed_algs**). - If the corresponding property is present or **checks_optional** is ``False``: - Its header must contain a property **typ** with the value ``JWT``. - Its claims must contain a property **iat** which represents a date in the past (taking into account :obj:`iat_skew`). - Its claims must contain a property **nbf** which represents a date in the past. - Its claims must contain a property **exp** which represents a date in the future. :raises: If the token failed to verify. """ if allowed_algs is None: allowed_algs = [] if not isinstance(allowed_algs, list): # jwcrypto only supports list of allowed algorithms raise _JWTError('allowed_algs must be a list') header, claims, _ = jwt.split('.') parsed_header = json_decode(base64url_decode(header)) alg = parsed_header.get('alg') if alg is None: raise _JWTError('alg header not present') if alg not in allowed_algs: raise _JWTError('algorithm not allowed: ' + alg) if not ignore_not_implemented: for k in parsed_header: if k not in JWSHeaderRegistry: raise _JWTError('unknown header: ' + k) if not JWSHeaderRegistry[k].supported: raise _JWTError('header not implemented: ' + k) if pub_key: token = JWS() token.allowed_algs = allowed_algs token.deserialize(jwt, pub_key) elif 'none' not in allowed_algs: raise _JWTError('no key but none alg not allowed') parsed_claims = json_decode(base64url_decode(claims)) utcnow = datetime.utcnow() now = timegm(utcnow.utctimetuple()) typ = parsed_header.get('typ') if typ is None: if not checks_optional: raise _JWTError('typ header not present') elif typ != 'JWT': raise _JWTError('typ header is not JWT') iat = parsed_claims.get('iat') if iat is None: if not checks_optional: raise _JWTError('iat claim not present') elif iat > timegm((utcnow + iat_skew).utctimetuple()): raise _JWTError('issued in the future') nbf = parsed_claims.get('nbf') if nbf is None: if not checks_optional: raise _JWTError('nbf claim not present') elif nbf > now: raise _JWTError('not yet valid') exp = parsed_claims.get('exp') if exp is None: if not checks_optional: raise _JWTError('exp claim not present') elif exp <= now: raise _JWTError('expired') return parsed_header, parsed_claims
def generate_jwt(claims, priv_key=None, algorithm='PS512', lifetime=None, expires=None, not_before=None, jti_size=16, other_headers=None): """ Generate a JSON Web Token. :param claims: The claims you want included in the signature. :type claims: dict :param priv_key: The private key to be used to sign the token. Note: if you pass ``None`` then the token will be returned with an empty cryptographic signature and :obj:`algorithm` will be forced to the value ``none``. :type priv_key: `jwcrypto.jwk.JWK <https://jwcrypto.readthedocs.io/en/latest/jwk.html>`_ :param algorithm: The algorithm to use for generating the signature. ``RS256``, ``RS384``, ``RS512``, ``PS256``, ``PS384``, ``PS512``, ``ES256``, ``ES384``, ``ES512``, ``HS256``, ``HS384``, ``HS512`` and ``none`` are supported. :type algorithm: str :param lifetime: How long the token is valid for. :type lifetime: datetime.timedelta :param expires: When the token expires (if :obj:`lifetime` isn't specified) :type expires: datetime.datetime :param not_before: When the token is valid from. Defaults to current time (if ``None`` is passed). :type not_before: datetime.datetime :param jti_size: Size in bytes of the unique token ID to put into the token (can be used to detect replay attacks). Defaults to 16 (128 bits). Specify 0 or ``None`` to omit the JTI from the token. :type jti_size: int :param other_headers: Any headers other than "typ" and "alg" may be specified, they will be included in the header. :type other_headers: dict :rtype: unicode :returns: The JSON Web Token. Note this includes a header, the claims and a cryptographic signature. The following extra claims are added, per the `JWT spec <http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html>`_: - **exp** (*IntDate*) -- The UTC expiry date and time of the token, in number of seconds from 1970-01-01T0:0:0Z UTC. - **iat** (*IntDate*) -- The UTC date and time at which the token was generated. - **nbf** (*IntDate*) -- The UTC valid-from date and time of the token. - **jti** (*str*) -- A unique identifier for the token. :raises: ValueError: If other_headers contains either the "typ" or "alg" header """ header = {'typ': 'JWT', 'alg': algorithm if priv_key else 'none'} if other_headers is not None: redefined_keys = set(header.keys()) & set(other_headers.keys()) if redefined_keys: raise ValueError( 'other_headers re-specified the headers: {}'.format( ', '.join(redefined_keys))) header.update(other_headers) claims = dict(claims) now = datetime.utcnow() if jti_size: claims['jti'] = base64url_encode(urandom(jti_size)) claims['nbf'] = timegm((not_before or now).utctimetuple()) claims['iat'] = timegm(now.utctimetuple()) if lifetime: claims['exp'] = timegm((now + lifetime).utctimetuple()) elif expires: claims['exp'] = timegm(expires.utctimetuple()) if header['alg'] == 'none': signature = '' else: token = JWS(json_encode(claims)) token.allowed_algs = [header['alg']] token.add_signature(priv_key, protected=header) signature = json_decode(token.serialize())['signature'] return u'%s.%s.%s' % (base64url_encode( json_encode(header)), base64url_encode(json_encode(claims)), signature)
def deserialize(self, jwt, key=None): """Deserialize a JWT token. NOTE: Destroys any current status and tries to import the raw token provided. :param jwt: a 'raw' JWT token. :param key: A (:class:`jwcrypto.jwk.JWK`) verification or decryption key, or a (:class:`jwcrypto.jwk.JWKSet`) that contains a key indexed by the 'kid' header. """ c = jwt.count('.') if c == 2: self.token = JWS() elif c == 4: self.token = JWE() else: raise ValueError("Token format unrecognized") # Apply algs restrictions if any, before performing any operation if self._algs: self.token.allowed_algs = self._algs self.deserializelog = list() # now deserialize and also decrypt/verify (or raise) if we # have a key if key is None: self.token.deserialize(jwt, None) elif isinstance(key, JWK): self.token.deserialize(jwt, key) self.deserializelog.append("Success") elif isinstance(key, JWKSet): self.token.deserialize(jwt, None) if 'kid' in self.token.jose_header: kid_key = key.get_key(self.token.jose_header['kid']) if not kid_key: raise JWTMissingKey('Key ID %s not in key set' % self.token.jose_header['kid']) self.token.deserialize(jwt, kid_key) else: for k in key: try: self.token.deserialize(jwt, k) self.deserializelog.append("Success") break except Exception as e: # pylint: disable=broad-except keyid = k.key_id if keyid is None: keyid = k.thumbprint() self.deserializelog.append('Key [%s] failed: [%s]' % ( keyid, repr(e))) continue if "Success" not in self.deserializelog: raise JWTMissingKey('No working key found in key set') else: raise ValueError("Unrecognized Key Type") if key is not None: self.header = self.token.jose_header self.claims = self.token.payload.decode('utf-8') self._check_provided_claims()
def unwrap_signature(message: str, signing_certificate: JWK) -> str: """Validate and strip a signature from a response from DCS""" jwstoken = JWS() jwstoken.deserialize(raw_jws=message, key=signing_certificate) return jwstoken.payload.decode("utf-8")