B
    Ø‹dõ7  ã            
   @   sž  d Z ddlZddlZddlZddlZddlZddlZye d¡d jZ	W n( ej
k
rp Z zdZ	W ddZ[X Y nX yddlmZ W n ek
rž   ddlZY nX yddlZW n  ek
rÌ   ddlmZ Y nX ddlZddlmZmZmZ ddlm  mZ ddlm  m  m  mZ ddlm  m  m  m Z  ddl!m"Z"m#Z#m$Z$m%Z%m&Z& ddl'm(Z( ddl)m*  m+Z+ dZ,da-G d	d
„ d
e"ƒZ.G dd„ de/ƒZ0dS )zU
A module for effectively caching the public keys of various token issuer endpoints.
é    NÚ	scitokensz1.0.0)ÚEncodingÚPublicFormatÚload_pem_public_key)ÚSciTokensExceptionÚMissingKeyExceptionÚNonHTTPSIssuerÚUnableToCreateCacheÚUnsupportedKeyException)Úlong_from_byteszscitokens_keycache.sqllitec               @   s   e Zd ZdZdS )ÚUnableToWriteKeyCachez?
    For whatever reason, unable to write to the Key Cache
    N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© r   r   úe/work/yifan.wang/ringdown/master-ringdown-env/lib/python3.7/site-packages/scitokens/utils/keycache.pyr   +   s   r   c               @   s„   e Zd ZdZdd„ Zedd„ ƒZddd„Zedd	d
„ƒZdd„ Z	dd„ Z
ddd„Zedd„ ƒZeddd„ƒZdd„ Zedd„ ƒZdS )ÚKeyCachez_
    Object that persistently caches signing keys associated with a token issuer endpoint.
    c             C   s   |   ¡ | _d S )N)Ú_get_cache_fileÚcache_location)Úselfr   r   r   Ú__init__6   s    zKeyCache.__init__c               C   s   t dkrtƒ a t S )z@
        Return the singleton instance of the KeyCache.
        N)ÚKEYCACHE_INSTANCEr   r   r   r   r   Úgetinstance:   s    zKeyCache.getinstancer   c             C   sd   |dkrd}t  | j¡}t j|_| ¡ }| d ||¡¡ tj	||||||d | 
¡  | ¡  dS )aP  
        Add a single, known public key to the cache.

        :param str issuer: URI of the issuer
        :param str key_id: Key Identifier
        :param public_key: Cryptography public_key object
        :param int cache_timer: Cache lifetime of the public_key
        :param int next_update: Seconds until next update time
        r   i  z:DELETE FROM keycache WHERE issuer = '{}' AND key_id = '{}')Úcache_timerÚnext_updateN)Úsqlite3Úconnectr   ÚRowÚrow_factoryÚcursorÚexecuteÚformatr   Ú_addkeyinfoÚcommitÚclose)r   ÚissuerÚkey_idÚ
public_keyr   r   ÚconnÚcursr   r   r   Ú
addkeyinfoD   s    zKeyCache.addkeyinfoc          	   C   sd   d}d|  tjtj¡ d¡i}|  |j|t ¡ | |t	 
|¡t ¡ | d¡ | jdkr`tdƒ‚dS )zM
        Given an open database cursor to a key cache, insert a key.
        z€INSERT INTO keycache VALUES('{issuer}', '{expiration}', '{key_id}',                                '{keydata}', '{next_update}')Úpub_keyÚascii)r&   Ú
expirationr'   Úkeydatar   é   zUnable to insert into key cacheN)Zpublic_bytesr   ZPEMr   ZSubjectPublicKeyInfoÚdecoder!   r"   ÚtimeÚjsonÚdumpsZrowcountr   )r*   r&   r'   r(   r   r   Zinsert_key_statementr/   r   r   r   r#   [   s    
zKeyCache._addkeyinfoc             C   s>   yt  |¡d S  tk
r8   t d¡ |  ||¡ dS X dS )a|  
        Keydata is stored as a JSON object inside the DB.  Therefore, we must extract it.

        :param str issuer: Token Issuer in keydata
        :param str kid: Key ID
        :param str keydata: Raw JSON key data (at least, it should be)
        :param curs: SQLite cursor, in case it has to delete the row

        :returns str: encoded public key, otherwise None
        r,   z†Unable to parse JSON stored in keycache.  This likely means the database format needsto be updated, which we will now do automaticallyN)r3   ÚloadsÚ
ValueErrorÚloggingÚ	exceptionÚ_delete_cache_entry)r   r&   Úkidr/   r   r   r   Ú_parse_key_datal   s    
zKeyCache._parse_key_datac             C   s:   t  | j¡}| ¡ }| d ||¡¡ | ¡  | ¡  dS )z&
        Delete a cache entry
        z:DELETE FROM keycache WHERE issuer = '{}' AND key_id = '{}'N)r   r   r   r    r!   r"   r$   r%   )r   r&   r'   r)   r*   r   r   r   r9   „   s    
zKeyCache._delete_cache_entryNFc          
   C   sÈ  d}|dkr|d7 }t  | j¡}t j|_| ¡ }| |j||d¡ | ¡ }| 	¡  | 
¡  |dkr¢t|d ƒt ¡ k r(|  |¡r(y&|  |||¡\}}	|  ||||	¡ |S  tk
r$ }
 zVt d¡}| d t|
ƒ¡¡ |  |d |d	 |d
 ¡}|rt| ¡ t ¡ dS W dd}
~
X Y nX nz|  |¡rŽ|  |d |d	 |d
 ¡}|rht| ¡ t ¡ dS |  |||¡\}}	|  ||||	¡ |S |  |d |d	 ¡ |  |||¡\}}	|  ||||	¡ |S )a3  
        Get the key information

        :param str issuer: The issuer URI
        :param str key_id: Text key id to identify the key
        :param bool insecure: Whether insecure methods are acceptable (defaults to False).
        :returns: None if no key is found.  Else, returns the public key
        z0SELECT * FROM keycache WHERE issuer = '{issuer}'Nz AND key_id = '{key_id}')r&   r'   r   r   z/Unable to get key triggered by next update: {0}r&   r'   r/   )Úbackend)r   r   r   r   r   r    r!   r"   Zfetchoner$   r%   Úintr2   Ú_check_validityÚ_get_issuer_publickeyr+   Ú	Exceptionr7   Ú	getLoggerÚwarningÚstrr;   r   ÚencodeÚbackendsÚdefault_backendr9   )r   r&   r'   ÚinsecureZ	key_queryr)   r*   Úrowr(   r   ÚexÚloggerr/   r   r   r   Ú
getkeyinfo‘   sB    

"
(	zKeyCache.getkeyinfoc             C   s   |d t   ¡ krdS dS dS )z8
        Check the key to see if it has expired
        r.   FTN)r2   )ÚclsZkey_infor   r   r   r>   Õ   s    zKeyCache._check_validityc             C   sL  dd  t¡i}d}|  d¡s$| d } t | ¡}t |j|¡}t|ƒ}||d< t |¡}|svt |¡}|jdkrvt	dƒ‚t
 t
j||d¡}	t |	 ¡  d	¡¡}
|
d
 }|sÆt |¡}|jdkrÆt	dƒ‚t
 t
j||d¡}	d}|	 ¡ }d|kr"d|d kr"t d|d ¡}|r"t| d¡ƒ}t|t d¡ƒ}t |	 ¡  d	¡¡}d}d}|dkr‚t|d ƒdkrttdƒ‚n|d d }n(x&|d D ]}|d |krŒ|}P qŒW |dkrÂtd  |¡ƒ‚|d dkrüt t|d ƒt|d ƒ¡}| t  !¡ ¡}nH|d dkr<t" #t|d ƒt|d ƒt" $¡ ¡}| t  !¡ ¡}nt%dƒ‚||fS ) z
        :return: Tuple containing (public_key, cache_lifetime).  Cache_lifetime how
            the public key is valid
        z
User-AgentzSciTokens/{}z .well-known/openid-configurationú/é   Úhttpsz;Issuer is not over HTTPS.  RFC requires it to be over HTTPS)Úheaderszutf-8Újwks_uriz%jwks_uri is not over HTTPS, insecure!r   zCache-Controlzmax-agez.*max-age=(\d+)r0   Zcache_lifetimeÚ NÚkeysz`No kid in header, but multiple keys in response from certs server.  Don't know which key to use!r:   zUnable to find key at issuer {}ZktyZRSAÚeÚnZECÚxÚyz,SciToken signed with an unsupported key type)&r"   ÚPKG_VERSIONÚendswithÚurlparseÚurljoinÚpathÚlistÚ
urlunparseÚschemer   ÚrequestÚurlopenÚRequestr3   r5   Úreadr1   ÚinfoÚreÚsearchr=   ÚgroupÚmaxÚconfigZget_intÚlenÚNotImplementedErrorr   ÚrsaZRSAPublicNumbersr   r(   rE   rF   ÚecZEllipticCurvePublicNumbersZ	SECP256R1r
   )r&   r'   rG   rP   Zwell_known_uriÚ
parsed_urlZupdated_urlZparsed_url_listZmeta_uriÚresponseÚdatarQ   r   ÚmatchZ	keys_datar(   Zraw_keyÚkeyZpublic_key_numbersr   r   r   r?   à   sn    














zKeyCache._get_issuer_publickeyc          
   C   sò   t  d¡}tj dd¡}tj d¡}|dkr2|}n$|dkr@|}n|dkrVtj |d¡}tj |¡s¦yt |¡ W n4 t	k
r¤ } zt
d t|ƒ¡ƒ‚W dd}~X Y nX tj |d¡}tj |¡sÊt |¡ tj |t¡}tj |¡sî|  |¡ |S )	zÇ
        Get the Cache file location

        1. Configuration cache location
        2. $XDG_CACHE_HOME
        3. .cache subdirectory of home directory as returned by the password database
        r   ÚXDG_CACHE_HOMENú~rR   z.cachezUnable to create cache: {}r   )ri   ÚgetÚosÚenvironr\   Ú
expanduserÚjoinÚexistsÚmakedirsÚOSErrorr	   r"   rC   ÚCACHE_FILENAMEÚ_initialize_cachedb)r   Zconfig_cache_locationZxdg_cache_homeZhome_dirÚ	cache_dirZoseZkeycache_dirZkeycache_filer   r   r   r   <  s*    	
$

zKeyCache._get_cache_filec             C   s0   t  | ¡}| ¡ }| d¡ | ¡  | ¡  dS )z4
        Create a simple flat sqllite cache
        z¤CREATE TABLE keycache (issuer text NOT NULL,expiration integer NOT NULL,key_id text,keydata text NOT NULL,next_update integer NOT NULL,PRIMARY KEY (issuer, key_id))N)r   r   r    r!   r$   r%   )Zsql_filer)   r*   r   r   r   r~   `  s
    

zKeyCache._initialize_cachedb)r   r   )r   r   )NF)NF)r   r   r   r   r   Ústaticmethodr   r+   r#   r;   r9   rK   Úclassmethodr>   r?   r   r~   r   r   r   r   r   1   s   


D[$r   )1r   rv   r   r2   Úpkg_resourcesre   r7   ÚrequireÚversionrX   ÚDistributionNotFoundÚerrorÚurllib.requestr`   ÚImportErrorÚurllib2rZ   Úurllib.parseÚparser3   Z,cryptography.hazmat.primitives.serializationr   r   r   Zcryptography.hazmat.backendsZhazmatrE   Z,cryptography.hazmat.primitives.asymmetric.ecZ
primitivesZ
asymmetricrm   Z-cryptography.hazmat.primitives.asymmetric.rsarl   Zscitokens.utils.errorsr   r   r   r	   r
   Zscitokens.utilsr   Zscitokens.utils.configÚutilsri   r}   r   r   Úobjectr   r   r   r   r   Ú<module>   s<   