o
    l>j                     @  s  U d Z ddlm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ddl	Z	ddl
Z
ddlZddlZddlmZ ddlmZmZ ddlmZ ddlmZmZmZmZmZ ddlmZ dd	lmZmZ dd
l m!Z! ddl"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z) ddl*m+Z+m,Z, ddl-m.Z. e/e0Z1dZ2dZ3dZ4dZ5dZ6dZ7dZ8dZ9dZ:dZ;dZ<dZ=dZ>dZ?dZ@eAdBdZCdZDdZEddd ZFd!eGd"< d#ZHd$ZId%ZJd&ZKed'd(G d)d* d*ZLG d+d, d,e#ZMed'd(G d-d. d.ZNeG d/d0 d0ZOed'd(G d1d2 d2ZPed'd(G d3d4 d4ZQed'd(G d5d6 d6ZRed'd(G d7d8 d8ZSed'd(G d9d: d:ZTed'd(G d;d< d<ZUed'd(G d=d> d>ZVG d?d@ d@eZWG dAdB dBeXZYG dCdD dDeZZddHdIZ[ddMdNZ\ddRdSZ]ddVdWZ^ddXdYZ_ddZd[Z`dd\d]Zadd_d`ZbddbdcZcddddeZdddfdgZeddhdiZfddjdkZgddodpZhddqdrZiddudvZjddydzZkdd{d|ZldddZmdddZndddZodddZpdddZqddddddZrdddZsdddZtdddZuG dd dZvG dd dZwdS )z=Internal helpers for Apple's HSA2 trusted-device bridge flow.    )annotationsN)Error)	dataclassfield)
HTMLParser)AnyCallableMappingOptionalProtocol)urlparse)hashesserialization)ec)	BaseModel
ConfigDictField	StrictInt	StrictStrValidationErrorfield_validator)$PyiCloudTrustedDevicePromptException*PyiCloudTrustedDeviceVerificationException)TrustedDeviceBridgeProverz/bridge/step/0z/bridge/step/{step}z/bridge/code/validateiQ       	   
            s   s   doneasciiz$258EAFA5-E914-47DA-95CA-C5AB0DC85B11g      >@zwebsocket.push.apple.comz websocket.sandbox.push.apple.com)prodsandboxdict[str, str]WEBSOCKET_ENVIRONMENT_HOSTS      i  i  T)frozenc                   @  s~   e Zd ZU dZdZded< dZded< dZd	ed
< ee	dZ
ded< ee	dZded< dZded< edddZdddZdS )Hsa2BootContextzABridge-related HSA2 boot data parsed from Apple's HTML bootstrap. strauth_initial_routeFboolhas_trusted_devices tuple[str, ...]auth_factorsdefault_factorydict[str, Any]bridge_initiate_dataphone_number_verificationNOptional[str]source_app_idauth_optionsMapping[str, Any]return'Hsa2BootContext'c                 C  s   | d}t|tsi }| d}t|ts| d}t|ts"i }| d}t|ts.g }| d}|dur;t|}| t| dpCdt| dtd	d
 |D t|t||dS )zBBuild a normalized boot context from Apple's auth-options payload.bridgeInitiateDataphoneNumberVerificationauthFactorssourceAppIdNauthInitialRouter*   hasTrustedDevicesc                 s      | ]
}t |tr|V  qd S N
isinstancer+   .0factorr/   r/   I/home/thesage/.local/lib/python3.10/site-packages/pyicloud/hsa2_bridge.py	<genexpr>j       

z4Hsa2BootContext.from_auth_options.<locals>.<genexpr>r,   r.   r1   r5   r6   r8   )getrF   dictlistr+   r-   tuple)clsr9   r5   r6   r1   r8   r/   r/   rJ   from_auth_optionsP   s4   







z!Hsa2BootContext.from_auth_optionsc                 C  sx   | j | jt| jd}| jrt| j|d< | jr0t| j|d< | jd}t|tr0t||d< | j	dur:| j	|d< |S )z?Return parsed boot data in the shape expected by the auth flow.)rA   rB   r?   r=   r>   trustedPhoneNumberNr@   )
r,   r.   rP   r1   r5   rO   r6   rN   rF   r8   )self	auth_datatrusted_phone_numberr/   r/   rJ   as_auth_datar   s    


zHsa2BootContext.as_auth_data)r9   r:   r;   r<   r;   r4   )__name__
__module____qualname____doc__r,   __annotations__r.   r1   r   rO   r5   r6   r8   classmethodrS   rX   r/   r/   r/   rJ   r)   E   s   
 !r)   c                   @  s.  e Zd ZU dZeddddZedddZded	< edd
dZ	ded< edddZ
ded< edddZded< dZded< dZded< dZded< dZded< dZded< dZded< edddZded< edddZded< ed	ded'd d!Zedddddded'd"d#Zeded(d%d&ZdS ))_BridgePushPayloadModelz7Strict validator for Apple's bridge push JSON envelope.allowT)extrapopulate_by_namearbitrary_types_allowedNsessionUUID)defaultaliaszOptional[StrictStr]session_uuidflowidflow_idnextStepzOptional[StrictStr | StrictInt]	next_step	ruiURLKeyr7   rui_url_keytxnidsaltmididmsdatar   akdatadataencryptedCodeencrypted_coder   zOptional[StrictInt]
error_codevaluer;   c                 C     |dur|  std|S )z(Reject blank bridge session identifiers.Nz$sessionUUID/flowid must not be blankstrip
ValueErrorrR   rx   r/   r/   rJ   _validate_session_uuid   s   z._BridgePushPayloadModel._validate_session_uuidc                 C  ry   )z7Reject present-but-blank optional bridge string fields.Nz(Bridge payload strings must not be blankrz   r}   r/   r/   rJ   $_validate_optional_non_empty_strings   s   z<_BridgePushPayloadModel._validate_optional_non_empty_stringsOptional[str | int]c                 C  s   t |tr| std|S )z>Reject blank next-step markers while allowing ints or strings.znextStep must not be blank)rF   r+   r{   r|   r}   r/   r/   rJ   _validate_next_step   s   z+_BridgePushPayloadModel._validate_next_step)rx   r7   r;   r7   )rx   r   r;   r   )rZ   r[   r\   r]   r   model_configr   rh   r^   rj   rl   rn   ro   rp   rq   rr   rs   rt   rv   rw   r   r_   r~   r   r   r/   r/   r/   rJ   r`      sF   
 r`   c                   @  s   e Zd ZU dZded< ded< dZded< dZded	< dZded
< dZded< dZ	ded< dZ
ded< dZded< dZded< dZded< dZded< edddZdS )BridgePushPayloadzHDecoded bridge push metadata needed to bootstrap trusted-device prompts.r4   payloadr+   rh   Nr7   rl   rn   ro   rp   rq   rr   r   rs   rt   rv   Optional[int]rw   r;   'BridgePushPayload'c                 C  s   zt |}W n ty } ztd|d}~ww |jp|j}|s%td| |||jdur2t|jnd|j|j	|j
|j|j|j|j|j|jdS )z7Validate and normalize one decoded bridge push payload.z-Malformed trusted-device bridge push payload.NzATrusted-device bridge push payload is missing sessionUUID/flowid.)r   rh   rl   rn   ro   rp   rq   rr   rs   rt   rv   rw   )r`   model_validater   r   rh   rj   rl   r+   rn   ro   rp   rq   rr   rs   rt   rv   rw   )rR   r   	validatedexcresolved_session_uuidr/   r/   rJ   from_payload   s:   zBridgePushPayload.from_payload)r   r4   r;   r   )rZ   r[   r\   r]   r^   rl   rn   ro   rp   rq   rr   rs   rt   rv   rw   r_   r   r/   r/   r/   rJ   r      s    
 r   c                   @  s   e Zd ZU dZded< ded< ded< ded< ded< d	ed
< dZded< dZded< dZded< ee	dZ
ded< dZded< dZded< dZded< dZded< dZded< dZded< dZded< dZded< d%d d!Zed&d#d$ZdS )'TrustedDeviceBridgeStatez&Ephemeral trusted-device bridge state.r+   connection_path
push_tokenrh   zOptional[_WebSocketLike]	websockettopicr$   topics_by_hashNr7   r8   rl   rn   r2   r4   push_payloadro   rp   rq   rr   r   rs   rt   rv   r   rw   r   r;   Nonec                 C  sh   t |j| _|j| _|j| _|j| _|j| _|j| _|j| _|j	| _	|j
| _
|j| _|j| _|j| _dS )zCPersist the latest bridge push metadata in the live bridge session.N)rO   r   r   rh   rl   rn   ro   rp   rq   rr   rs   rt   rv   rw   )rU   r   r/   r/   rJ   apply_push_payload  s   z+TrustedDeviceBridgeState.apply_push_payloadr-   c                 C  s   t | jo	| jdS )zIReturn whether Apple routed this bridge challenge to the legacy verifier._W)r-   ro   endswithrU   r/   r/   rJ   #uses_legacy_trusted_device_verifier  s   z<TrustedDeviceBridgeState.uses_legacy_trusted_device_verifier)r   r   r;   r   )r;   r-   )rZ   r[   r\   r]   r^   r8   rl   rn   r   rO   r   ro   rp   rq   rr   rs   rt   rv   rw   r   propertyr   r/   r/   r/   rJ   r      s.   
 
r   c                   @  sT   e Zd ZU dZded< ded< ded< ded< dZd	ed
< dZded< dddZdS )BridgeStepRequestz5Typed request body for Apple's bridge step endpoints.r+   rh   rt   r   intrl   Nr7   rr   r   rs   r;   r4   c                 C  s^   | j | j| j| jd}| jdur| j|d< | jdur-t| jtr(tj	| jddn| j|d< |S )z6Serialize the step request into Apple's JSON envelope.)re   rt   ptknrk   Nrr   ),:)
separatorsrs   )
rh   rt   r   rl   rr   rs   rF   rO   jsondumpsrU   r   r/   r/   rJ   as_json.  s   



zBridgeStepRequest.as_jsonrY   )rZ   r[   r\   r]   r^   rr   rs   r   r/   r/   r/   rJ   r   #  s   
 r   c                   @  s,   e Zd ZU dZded< ded< d
ddZd	S )BridgeCodeValidateRequestzETyped request body for Apple's final bridge code validation endpoint.r+   rh   coder;   r$   c                 C  s   | j | jdS )z8Serialize the final bridge code-validation request body.)re   r   rh   r   r   r/   r/   rJ   r   H  s   z!BridgeCodeValidateRequest.as_jsonN)r;   r$   )rZ   r[   r\   r]   r^   r   r/   r/   r/   rJ   r   A  s
   
 r   c                   @  s6   e Zd ZU dZdZded< dZded< dZd	ed
< dS )_ConnectionResponsez<Decoded server response for the initial websocket bootstrap.r*   r+   push_token_b64r   r   statusNr   server_timestamp_seconds)rZ   r[   r\   r]   r   r^   r   r   r/   r/   r/   rJ   r   P  s
   
 r   c                   @  s*   e Zd ZU dZded< ded< ded< dS )_PushMessagez8Decoded APNS-style push frame from the bridge websocket.bytesr   r   
message_idr   N)rZ   r[   r\   r]   r^   r/   r/   r/   rJ   r   Y  s
   
 r   c                   @  sB   e Zd ZU dZdZded< dZded< dZded< dZded	< d
S )_ChannelSubscriptionResponsez:Decoded response to the bridge topic subscription request.r   r   r   r   retry_interval_secondsr/   r0   topicsN)	rZ   r[   r\   r]   r   r^   r   r   r   r/   r/   r/   rJ   r   b  s   
 r   c                   @  s.   e Zd ZU dZded< ded< dZded< dS )	_AcknowledgementMessagez?Decoded acknowledgment frame emitted by Apple's bridge service.r   r   r   r   r   delivery_statusN)rZ   r[   r\   r]   r^   r   r/   r/   r/   rJ   r   l  s
   
 r   c                   @  sN   e Zd ZU dZdZded< dZded< dZded< dZd	ed
< dZ	ded< dS )_ServerMessagezFOne websocket frame decoded into its known top-level message variants.NzOptional[_ConnectionResponse]connection_responsezOptional[_PushMessage]push_messagez&Optional[_ChannelSubscriptionResponse]channel_subscription_responsez!Optional[_AcknowledgementMessage]push_acknowledgmentr/   ztuple[int, ...]field_numbers)
rZ   r[   r\   r]   r   r^   r   r   r   r   r/   r/   r/   rJ   r   u  s   
 r   c                   @  s.   e Zd ZdZdddZddd	Zdd
dZdS )_WebSocketLikezFProtocol for the minimal websocket operations used by the bridge flow.r   r   r;   r   c                 C     dS )z"Send one binary websocket message.Nr/   r   r/   r/   rJ   send_binary      z_WebSocketLike.send_binaryc                 C  r   )z,Read one complete websocket message payload.Nr/   r   r/   r/   rJ   read_message  r   z_WebSocketLike.read_messagec                 C  r   )zClose the websocket transport.Nr/   r   r/   r/   rJ   close  r   z_WebSocketLike.closeNr   r   r;   r   r;   r   r;   r   )rZ   r[   r\   r]   r   r   r   r/   r/   r/   rJ   r     s
    

r   c                      s"   e Zd ZdZd fddZ  ZS )	_InvalidNonceErrorzFSignal Apple's INVALID_NONCE response along with the server timestamp.server_timestamp_msr   r;   r   c                   s   t  d || _dS )z9Capture the server timestamp returned with INVALID_NONCE.z!Invalid nonce from bridge server.N)super__init__r   )rU   r   	__class__r/   rJ   r     s   
z_InvalidNonceError.__init__)r   r   r;   r   )rZ   r[   r\   r]   r   __classcell__r/   r/   r   rJ   r     s    r   c                      sN   e Zd ZdZd fddZedddZdddZdddZdddZ	  Z
S )_BootArgsHTMLParserz8Extract the JSON body from Apple's boot_args script tag.r;   r   c                   s    t    d| _d| _g | _dS )zDInitialize parser state for the first matching boot_args script tag.FN)r   r   _collecting_found_chunksr   r   r/   rJ   r     s   

z_BootArgsHTMLParser.__init__r+   c                 C  s   d | j S )z)Return the collected boot_args JSON text.r*   )joinr   r{   r   r/   r/   rJ   r     s   z_BootArgsHTMLParser.payloadtagattrslist[tuple[str, Optional[str]]]c                 C  sN   |dks| j r	dS dd |D }|dpd }d|v r%d| _d| _ dS dS )	z=Start collecting data when the boot_args script tag is found.scriptNc                 S  s   i | ]\}}||qS r/   r/   )rH   keyrx   r/   r/   rJ   
<dictcomp>  s    z7_BootArgsHTMLParser.handle_starttag.<locals>.<dictcomp>classr*   	boot_argsT)r   rN   splitr   )rU   r   r   attr_mapclassesr/   r/   rJ   handle_starttag  s   
z#_BootArgsHTMLParser.handle_starttagc                 C  s    |dkr| j rd| _ dS dS dS )z3Stop collecting when the current script tag closes.r   FN)r   )rU   r   r/   r/   rJ   handle_endtag  s   
z!_BootArgsHTMLParser.handle_endtagrt   c                 C  s   | j r| j| dS dS )z9Append script contents while the boot_args tag is active.N)r   r   append)rU   rt   r/   r/   rJ   handle_data  s   z_BootArgsHTMLParser.handle_datar   r;   r+   )r   r+   r   r   r;   r   )r   r+   r;   r   )rt   r+   r;   r   )rZ   r[   r\   r]   r   r   r   r   r   r   r   r/   r/   r   rJ   r     s    


r   	html_textr+   r;   c              
   C  s6  t  }||  |  |j}|stdzt|}W n tjy. } ztd|d}~ww |d}t	|t
s=td|d}t	|t
sIi }|d}t	|t
sUi }|d}t	|t
sai }|d	}	t	|	tsmg }	|d
}
|
durzt|
}
tt|dpdt|dtdd |	D t
|t
||
dS )zEExtract HSA2 boot args from the HTML returned by GET /appleauth/auth.zMissing HSA2 boot args payload.z!Malformed HSA2 boot args payload.NdirectzMissing HSA2 direct boot data.twoSVr=   r>   r?   r@   rA   r*   rB   c                 s  rC   rD   rE   rG   r/   r/   rJ   rK     rL   z'parse_boot_args_html.<locals>.<genexpr>rM   )r   feedr   r   r   r   loadsJSONDecodeErrorrN   rF   rO   rP   r+   r)   r-   rQ   )r   parserpayload_textr   r   r   two_svr5   r6   r1   r8   r/   r/   rJ   parse_boot_args_html  sX   











r   rx   r   r   c                 C  sP   | dk rt dt }	 | d@ }| dL } | r||dB  n	|| t|S q)z#Encode an unsigned protobuf varint.r   z#Negative varints are not supported.T   r       )r|   	bytearrayr   r   )rx   partsto_writer/   r/   rJ   _encode_varint  s   
r   rt   offsettuple[int, int]c                 C  sx   d}d}|}	 |t | krtd| | }|d7 }||d@ |> O }|d@ s)||fS |d7 }|dks7|| d	kr;td
q)zHDecode one protobuf varint from a byte string and return the new offset.r   TzTruncated protobuf varint.r   r   r   r    ?   r   zMalformed protobuf varint.)lenr   )rt   r   rx   shiftstart_offsetbyter/   r/   rJ   _read_varint  s   r   field_number	wire_typec                 C  s   t | d> |B | S )z-Encode one protobuf field header and payload.r   )r   )r   r   rx   r/   r/   rJ   _encode_field     r   c                 C  s   t | dtt|| S )z)Encode a length-delimited protobuf field.r   )r   r   r   r   rx   r/   r/   rJ   _encode_bytes_field     r   c                 C  s   t | |dS )z%Encode a UTF-8 string protobuf field.utf-8)r   encoder   r/   r/   rJ   _encode_string_field     r   c                 C  s   t | dt|S )z*Encode an unsigned integer protobuf field.r   )r   r   r   r/   r/   rJ   _encode_uint32_field!  r   r  dict[int, list[Any]]c           	      C  s   d}i }|t | k r]t| |\}}|d? }|d@ }|dkr%t| |\}}n)|dkrGt| |\}}|| }|t | kr>td| || }|}ntd| ||g | |t | k s
|S )z@Decode a minimal subset of protobuf wire types into field lists.r   r   r    r   zTruncated protobuf field.z Unsupported protobuf wire type: )r   r   r   
setdefaultr   )	rt   r   fieldsr   r   r   rx   length
end_offsetr/   r/   rJ   _decode_fields&  s*   r  messagec              
   C  s   t | }d}|dr(z|d d d}W n ty' } ztd|d}~ww t|ddgd }d}|drBt|d d }t|||d	S )
z1Decode the server's websocket bootstrap response.r*   r   r   r!   z0Malformed bridge connection response push token.Nr   r   )r   r   r   )r  rN   decodeUnicodeDecodeErrorr   r   r   )r  r  r   r   r   r   r/   r/   rJ   _decode_connection_responseC  s,   

r  c                 C  sX   t | }t|ddgd }t|ddgd }t|ddgd }t|||dS )z9Decode one push-delivery frame from the bridge websocket.r       r   r      )r   r   r   )r  r   rN   r   r   )r  r  r   r   r   r/   r/   rJ   _decode_push_messageY  s
   r  c                 C  s   t | }g }|d}|r;t t|d }|dg D ]}t t|}|ddgd }t|tr:||dd qtt|ddgd t|ddgd t|ddgd td	d
 |D dS )z?Decode the server's response to the topic subscription message.r   r   r  r   ignorer   r   r  c                 s  s    | ]}|r|V  qd S rD   r/   rH   r   r/   r/   rJ   rK   v  s    z8_decode_channel_subscription_response.<locals>.<genexpr>)r   r   r   r   )	r  rN   r   rF   r   r	  r   r   rQ   )r  r  r   payload_valuespayload_fieldsapp_response_valueapp_response_fieldstopic_valuer/   r/   rJ   %_decode_channel_subscription_responseb  s"   

r  c                 C  sX   t | }t|ddgd }t|ddgd }t|ddgd }t|||dS )z=Decode a push acknowledgment frame from the bridge websocket.r   r  r   r   r   )r   r   r   )r  r   rN   r   r   )r  r  r   r   r   r/   r/   rJ   _decode_acknowledgement_messagez  s   r  c                 C  s   t | }d}|trtt|t d }d}|tr&tt|t d }d}|tr7tt|t d }d}|t	rHt
t|t	 d }t||||tt|dS )zDDecode all known top-level messages embedded in one websocket frame.Nr   )r   r   r   r   r   )r  rN   "SERVER_MESSAGE_CONNECTION_RESPONSEr  r   SERVER_MESSAGE_PUSHr  ,SERVER_MESSAGE_CHANNEL_SUBSCRIPTION_RESPONSEr  SERVER_MESSAGE_PUSH_ACKr  r   rQ   sorted)r  r  r   r   r   r   r/   r/   rJ   _decode_server_message  s4   




r  
public_keynonce	signaturec              
   C  s>   d td| td|tdt|tdtdtg}td|S )z6Encode the initial bridge websocket bootstrap message.r  r   r   r      )r   r   _encode_bridge_signaturer  !NEW_CONNECTION_EXPIRATION_SECONDS)r  r  r   connection_messager/   r/   rJ   _encode_connection_message  s   


r%  c                 C  s   |  tr| S t|  S )zEWrap the DER ECDSA signature using Apple's bridge signature envelope.)
startswithBRIDGE_SIGNATURE_PREFIX)r   r/   r/   rJ   r"    s   
r"  allowed_topics	list[str]c                 C  s   d dd | D }td|S )z@Encode the topic subscription message sent after bridge connect.r  c                 s  s    | ]}t d |V  qdS )r   N)r   r  r/   r/   rJ   rK     s    

z-_encode_web_filter_message.<locals>.<genexpr>r   )r   r   )r(  filter_payloadr/   r/   rJ   _encode_web_filter_message  s   

r+  r   r   c                 C  s$   d td| td|g}td|S )z?Encode the acknowledgment frame for one delivered push message.r  r   r   )r   r   r  )r   r   ack_payloadr/   r/   rJ   _encode_ack_message  s   
r-  c                 C  s   t | d S )z;Return Apple's websocket topic hash for a named APNS topic.r   )hashlibsha1r   	hexdigest)r   r/   r/   rJ   _topic_hash  r   r1  topic_bytesr   Mapping[str, str]c                 C  s   | |  | ddS )z=Resolve a hashed topic payload back to a readable topic name.r   r  )rN   hexr	  )r2  r   r/   r/   rJ   _topic_name  r   r5  r   r4   c              	   C  s*  z	t | dW S  tt jfy   | dd}Y nw |d}|dkrd}d}d}t||d |dD ]O\}}|rN|r@d}n|dkrGd	}n|d
krMd}q5|d
krUd	}q5|dkr^|d7 }q5|dkr|d8 }|dkrzt |||d  W   S  t jy   Y  nw q5|d|d }|dks%td)z<Extract the JSON object embedded in one bridge push payload.r   r  {r   FN)start\T"r   }z8Could not decode the trusted-device bridge push payload.)r   r   r	  r
  r   find	enumerater   )r   textr7  depth	in_stringescapedindex	characterr/   r/   rJ   _extract_json_payload  sJ   

rC  c              
   C  sB   zt j| ddd W S  ttfy  } ztd|d}~ww )z9Decode base64 bridge data and return it as lowercase hex.r!   Tvalidatez(Malformed base64-encoded bridge payload.N)base64	b64decoder   r4  r|   BinasciiError)rx   r   r/   r/   rJ   _b64_to_hex  s   
rI  c                 C  s   t t| dS )z/Encode hex bridge data as standard base64 text.r!   )rF  	b64encoder   fromhexr	  )rx   r/   r/   rJ   _hex_to_b64  s   rL  timestamp_msc                 C  s   d| j dddd td S )z<Build the nonce format expected by Apple's bridge bootstrap.    r   bigF)signed)to_bytesosurandom)rM  r/   r/   rJ   _build_nonce  s   rT  z<none>)prefixemptyr7   rU  rV  c                C  s*   | s|S t | |kr| S | d|  dS )zAShorten sensitive identifiers before logging them at debug level.Nz...)r   )rx   rU  rV  r/   r/   rJ   _summarize_identifier  s
   rW  boot_contextc                 C  sv   | j }|d}t|tr%|r%d|v rt|}|jr|jS |ddd S |d}t|tr7|tv r7t| S td)z@Resolve the websocket host Apple expects for the bridge session.webSocketUrl:///r   r   apnsEnvironmentz:Missing HSA2 websocket host for the trusted-device bridge.)	r5   rN   rF   r+   r   hostnamer   r%   r   )rX  bridge_dataweb_socket_urlparsedenvironmentr/   r/   rJ   _resolve_websocket_host$  s   

rb  c                 C  s&   | j d}t|tr|r|S td)z<Resolve the APNS topic Apple uses for trusted-device pushes.	apnsTopicz6Missing HSA2 APNS topic for the trusted-device bridge.)r5   rN   rF   r+   r   )rX  r   r/   r/   rJ   _resolve_apns_topic8  s   rd  auth_endpointc                 C  s.   t | }|jr
|jstd|j d|j S )z>Derive the websocket Origin header from the auth endpoint URL.z0Invalid auth endpoint for trusted-device bridge.rZ  )r   schemer]  r   )re  r`  r/   r/   rJ   _derive_originC  s   rg  c                   @  s`   e Zd ZdZd"d
dZd#ddZd$ddZd%ddZd&ddZd'ddZ	d(ddZ
d)dd Zd!S )*_RawWebSocketClientz7Minimal websocket client for Apple's webcourier bridge.urlr+   timeoutfloatorigin
user_agentr;   r   c                 C  s.   || _ || _|| _|| _t | _|  | _dS )z=Open a websocket connection and prepare buffered frame reads.N)_url_timeout_origin_user_agentr   _buffer_open_socket)rU   ri  rj  rl  rm  r/   r/   rJ   r   P  s   z_RawWebSocketClient.__init__ssl.SSLSocketc              	   C  s  t | j}|jdks|jstd| j |jpd}|jpd}|jr*| d|j }t	|j|f| j
}t }|j||jd}|| j
 ttdd}d	| d
d|j ddd| j d| j dd| dg	}|d|d | |}	|	d\}
}}d|
vrtd|
 i }|dD ]}|rd|vrq|dd\}}| ||  < qtt|t  d! d}|"d|krtd|S )z=Perform the websocket HTTP upgrade and return the TLS socket.wsszUnsupported websocket URL: i  r[  ?)server_hostname   r!   zGET z	 HTTP/1.1zHost: zUpgrade: websocketzConnection: UpgradezOrigin: zUser-Agent: zSec-WebSocket-Version: 13zSec-WebSocket-Key: z
z 101 zWebsocket upgrade failed: r   r   zsec-websocket-acceptz3Invalid websocket accept header from bridge server.)#r   rn  rf  r]  r   portpathquerysocketcreate_connectionro  sslcreate_default_contextwrap_socket
settimeoutrF  rJ  rR  rS  r	  rp  rq  sendallr   r   _read_http_response	partitionr   r{   lowerr.  r/  WEBSOCKET_GUIDdigestrN   )rU   r`  rz  resource
raw_socketcontextsecure_socketwebsocket_keyrequest_headersresponsestatus_line_headers_textheadersliner   rx   expected_acceptr/   r/   rJ   rs  _  s^   








z_RawWebSocketClient._opensockc                 C  sn   d| j vr|d}|std| j | d| j vs| j dd }t| j d| d}| j d|= |S )zARead the HTTP upgrade response headers from the websocket socket.s   

   z*Unexpected EOF during websocket handshake.r  Nz
iso-8859-1)rr  recvr   extendr;  r   r	  )rU   r  chunkmarkerrt   r/   r/   rJ   r    s   


z'_RawWebSocketClient._read_http_responsesizer   r   c                 C  sr   t | j|k r(| jtd|t | j }|std| j| t | j|k st| jd| }| jd|= |S )z?Read exactly ``size`` buffered bytes from the websocket socket.r  z-Unexpected EOF while reading websocket frame.N)r   rr  rt  r  maxr   r  r   )rU   r  r  rt   r/   r/   rJ   _read_exact  s   z_RawWebSocketClient._read_exactopcoder   c                   s   d|B }t d t|}t|g}|dk r|d|B  n!|dk r1|d |td| n|d |td| t fd	d
t	|D }| j
t|  |  dS )z9Send one masked websocket frame to Apple's bridge server.r   r  ~   i      !H   !Qc                 3  $    | ]\}}| |d   A V  qdS r  Nr/   rH   rA  r   mask_keyr/   rJ   rK         
z2_RawWebSocketClient._send_frame.<locals>.<genexpr>N)rR  rS  r   r   r   r  structpackr   r<  rt  r  )rU   r  r   
first_byter  headermasked_payloadr/   r  rJ   _send_frame  s   



z_RawWebSocketClient._send_framec                 C  s   |  t| dS )z*Send one binary websocket message payload.N)r  OPCODE_BINARYr   r/   r/   rJ   r     r   z_RawWebSocketClient.send_binaryc           
        s(  g }d}	 |  d\}}|d@ }t|d@ }t|d@ }|d@ }|dkr0td|  dd	 }n|dkr?td
|  dd	 }|rF|  dnd |  |}	|r\t fddt|	D }	|tkrdtd|tkro| 	t
|	 q|t
krtq|d	krz|}||	 |r|dtfvrtd| d|S q)zDRead one complete websocket message, handling control frames inline.NTr      r   r   r  r  r   r  r   r  r  c                 3  r  r  r/   r  r  r/   rJ   rK     r  z3_RawWebSocketClient.read_message.<locals>.<genexpr>z3Bridge websocket closed before delivering a prompt.r   zUnsupported websocket opcode: )r  r-   r  unpackr   r<  OPCODE_CLOSEr   OPCODE_PINGr  OPCODE_PONGr   r  r   )
rU   	fragmentsr  r  second_byteframe_opcodefinishedmaskedpayload_lengthr   r/   r  rJ   r     sJ   


z _RawWebSocketClient.read_messagec                 C  s   t | dddu r
dS z&z| td W n	 ty   Y nw W z| j  W dS  ty0   Y dS w z| j  W w  tyA   Y w w )zCAttempt a clean websocket close and always close the socket object.rt  Nr  )getattrr  r  OSErrorrt  r   r   r/   r/   rJ   r     s$   z_RawWebSocketClient.closeN)
ri  r+   rj  rk  rl  r+   rm  r+   r;   r   )r;   ru  )r  ru  r;   r+   )r  r   r;   r   )r  r   r   r   r;   r   r   r   r   )rZ   r[   r\   r]   r   rs  r  r  r  r   r   r   r/   r/   r/   rJ   rh  M  s    


8



,rh  c                   @  s   e Zd ZdZeddddLddZdMddZdNddZdOddZdPd!d"Z	dQd%d&Z
dRd*d+ZdSd/d0ZdTd2d3ZdTd4d5ZdTd6d7ZdUd9d:ZdVdBdCZdWdDdEZdXdHdIZdYdJdKZdS )ZTrustedDeviceBridgeBootstrapperzLBootstrap the trusted-device bridge flow captured in Apple's browser client.N)rj  websocket_factoryprover_factoryrj  rk  r  :Optional[Callable[[str, float, str, str], _WebSocketLike]]r  1Optional[Callable[[], TrustedDeviceBridgeProver]]r;   r   c                C  s   || _ |pt| _|pt| _dS )z?Configure websocket and prover factories for bridge operations.N)rj  rh  _websocket_factoryr   _prover_factory)rU   rj  r  r  r/   r/   rJ   r     s   

z(TrustedDeviceBridgeBootstrapper.__init__sessionr   re  r+   r  r3  rX  r)   rm  r   c                C  s  t |}t|}t|}t||i}	|j}
|  \}}td||||
 d}d}tdD ]I}t	|p9t
t d }||tt }t|||}| }d| d| }td|t| | || j||}d}z z| |}| }td	t| |t|g td
| |  }t|}|
r|
|d< tdt|t| | j|||||d | |||	}|jddur|j |krt!dtdt|j |j"|j# t$|||j ||t|	|
d}|%| d}|W W |s|&    S   S  t'y } z|j(}|}td| W Y d}~nRd}~w t)t*jt+j,fy@ } z|}tjddd W Y d}~W |s:|&   n;d}~w t!yd } z|}tjddd W Y d}~W |s^|&   nd}~ww W |sm|&  q,|sv|&  w w t!d|)zOBootstrap Apple's trusted-device bridge until the first prompt payload arrives.zaBootstrapping trusted-device bridge: auth_endpoint=%s websocket_host=%s topic=%s source_app_id=%sNr     zwss://z/v2/z@Opening trusted-device websocket: host=%s bootstrapPayloadLen=%dFz?Trusted-device bridge connected; received push token (%d bytes)z1Sent trusted-device webFilterMessage for topic=%sX-Apple-App-IdzCPosting trusted-device bridge step 0 with sessionUUID=%s ptknLen=%d)r  re  r  rh   r   re   9Trusted-device bridge returned a mismatched session UUID.zOReceived trusted-device bridge payload: sessionUUID=%s nextStep=%s ruiURLKey=%s)r   r   rh   r   r   r   r8   TzOTrusted-device bridge received INVALID_NONCE; retrying with server timestamp %sz:Trusted-device websocket transport error during bootstrap.exc_infoz9Trusted-device bridge bootstrap failed before completion.z5Failed to bootstrap the trusted-device bridge prompt.)-rd  rb  rg  r1  r8   _generate_keypairLOGGERdebugrangerT  r   timesignr   ECDSAr   SHA256r%  r4  r   r  rj  _wait_for_push_tokenr   r+  _generate_session_uuidrO   rW  _post_bridge_step0_wait_for_bridge_pushr   rN   rh   r   rl   rn   r   r   r   r   r   r  r}  r  SSLError)rU   r  re  r  rX  rm  r   websocket_hostrl  r   r8   r  private_keyrM  
last_errorr  r  r   r$  r   websocket_urlr   keep_websocket_openr   push_token_hexrh   bridge_headersr   bridge_stater   r/   r/   rJ   r7    s  



		
z%TrustedDeviceBridgeBootstrapper.start(tuple[bytes, ec.EllipticCurvePrivateKey]c                 C  s0   t t  }| jtjjtjj	d}||fS )zBGenerate the ephemeral P-256 keypair used for websocket bootstrap.)encodingformat)
r   generate_private_key	SECP256R1r  public_bytesr   EncodingX962PublicFormatUncompressedPoint)rU   r  r  r/   r/   rJ   r    s   z1TrustedDeviceBridgeBootstrapper._generate_keypairc                 C  s   t   dtt  S )z6Generate the browser-style bridge session UUID string.-)uuiduuid4r   r  r   r/   r/   rJ   r    s   z6TrustedDeviceBridgeBootstrapper._generate_session_uuidr  "Optional[TrustedDeviceBridgeState]c                 C  sV   |du rdS |j }d|_ |du rdS z|  W dS  ty*   tjddd Y dS w )zHClose and detach the websocket associated with an active bridge session.Nz-Trusted-device bridge websocket close failed.Tr  )r   r   r  r  r  )rU   r  r   r/   r/   rJ   r     s   z%TrustedDeviceBridgeBootstrapper.closer   r-   c             
   C  sj  |j }|du rtd|jrtd|jdvrtd|js"td|  }| ||}zzWtdt	|j
|jt	|jdd	 ||j| | }	td
t	|j
 | j||||dt|	|j|jd | ||j|j}
| ||
 |js|tdz tj|jdddd}|dd\}}t|}t|}W n tttfy } ztd|d}~ww tdt	|j
 z| |}W n ty } ztd|d}~ww z|!| W n ty   tdt	|j
 Y W W | "| dS w tdt	|j
 | j||||dt||j|jd | ||j|j}| #|| |j$s&tdtdt	|j
 z|%|j$}W n tyI } ztd|d}~ww | j&|||||d}|j't(k}|jdv radnd}td |t	|j
|j' | j|||||t)|j|jd |W W | "| S  t*y } ztd!|d}~w t+t,j-t.j/fy } ztd"|d}~ww | "| w )#z=Run Apple's bridge-specific trusted-device verification flow.Nz,Trusted-device bridge session is not active.zELegacy trusted-device verification should bypass the bridge verifier.>   2r   z;Trusted-device bridge is not ready for step 2 verification.z9Trusted-device bridge payload is missing the step-2 salt.zUStarting trusted-device bridge code verification: sessionUUID=%s nextStep=%s txnid=%s   rU  z8Posting trusted-device bridge step 2 with sessionUUID=%sr   )r  re  r  r  rl   rt   rr   rs   z<Trusted-device bridge step 4 payload is missing prover data.r!   TrD  r   r  r   z2Trusted-device bridge step 4 payload is malformed.zBProcessing trusted-device bridge step 4 payload for sessionUUID=%szPTrusted-device bridge prover rejected the step-4 confirmation for sessionUUID=%sFz8Posting trusted-device bridge step 4 with sessionUUID=%sr  z=Trusted-device bridge final payload is missing encryptedCode.z8Decrypting trusted-device bridge code for sessionUUID=%sz<Failed to decrypt the trusted-device bridge validation code.)r  re  r  r  r   >   6   r  zTPosting trusted-device bridge completion step %s with sessionUUID=%s verifyStatus=%szQTrusted-device bridge verification failed while waiting for the next bridge push.zMTrusted-device bridge verification failed due to a websocket transport error.)0r   r   r   rl   rp   r  _bridge_headersr  r  rW  rh   ro   init_with_saltget_message1_post_bridge_steprL  rr   rs   r  r   r   _apply_expected_step4_pushrt   rF  rG  r   r	  r   rI  r|   r
  rH  process_message1process_message2r   _apply_final_bridge_pushrv   decrypt_message_post_bridge_code_validatestatus_codeHTTP_STATUS_PRECONDITION_FAILEDBRIDGE_DONE_DATA_B64r   r  r}  rj  r  r  )rU   r  re  r  r  r   r   proverr  message1step4_payload
step4_databridge_message1_b64bridge_message2_b64bridge_message1_hexbridge_message2_hexr   message2final_payloadderived_codeverify_responseverification_succeededcompletion_stepr/   r/   rJ   validate_code  s^  
N

z-TrustedDeviceBridgeBootstrapper.validate_coder   r   r   c              
   C  s   t  | j }t  |k rm| }t|}|j}|du r$td|j q|j	t
krL|jrLztj|jdddW S  ttfyK } ztd|d}~ww |j	tkr]|jdur]t|jd td|j	 td	|j	 d
td)zDWait for the bridge connection response that carries the push token.NzOIgnoring non-connection websocket frame while waiting for push token; fields=%sr!   TrD  zMalformed bridge push token.r  z<Trusted-device bridge connection response returned status=%szBridge server returned status .z,Timed out waiting for the bridge push token.)r  	monotonicrj  r   r  r   r  r  r   r   	STATUS_OKr   rF  rG  r   r|   rH  r   STATUS_INVALID_NONCEr   r   )rU   r   deadliner  server_messager   r   r/   r/   rJ   r  p  sT   



z4TrustedDeviceBridgeBootstrapper._wait_for_push_tokenr   r   r   c                 C  s  t  | j }t  |k r| }t|}|jdur8|j}td|j|j	|j
|j |j	tkr8td|j	 d|jdurN|j}td|j|jt|j| |j}	|	du r]td|j q|t|	j|	j td|	jt|	j| t|	j||kr|qt|	j}
t|
S td)	z@Wait for, acknowledge, and decode the next relevant bridge push.NzxReceived channel subscription response during bridge bootstrap: messageId=%s status=%s retryIntervalSeconds=%s topics=%sz8Trusted-device bridge topic subscription failed (status ).z]Received bridge push acknowledgment during bootstrap: messageId=%s deliveryStatus=%s topic=%szLIgnoring non-push websocket frame during trusted-device bootstrap; fields=%sz7Acknowledged trusted-device push message id=%s topic=%sz8Timed out waiting for the trusted-device bridge payload.)r  r  rj  r   r  r   r  r  r   r   r   r   r  r   r   r   r5  r   r   r   r   r-  rC  r   r   r   )rU   r   r   r   r  r  r  channel_responsepush_ackr   r   r/   r/   rJ   r    sb   






z5TrustedDeviceBridgeBootstrapper._wait_for_bridge_pushr   c              
   C  s|   |j |j kr
tdtdt|j |jt|jdd|jt|j	t|j
 |jdvr7td|jd|j d|| d	S )
zBValidate a generic bridge push and merge it into the active state.r  zqDecoded trusted-device bridge payload: sessionUUID=%s nextStep=%s txnid=%s ec=%s has_data=%s has_encryptedCode=%sr  r  )Nr   z7Trusted-device bridge returned an error push (nextStep=z, ec=r  N)rh   r   r  r  rW  rl   ro   rw   r-   rt   rv   r   rU   r  r   r/   r/   rJ   _apply_bridge_push  s,   
	z2TrustedDeviceBridgeBootstrapper._apply_bridge_pushc              	   C  sJ   |  || |jdks|jstdtdt|j|jt|jdd dS )zBRequire the post-step-2 bridge push to contain step-4 prover data.4zATrusted-device bridge returned an unexpected post-step-2 payload.zKReceived trusted-device bridge payload: sessionUUID=%s nextStep=%s txnid=%sr  r  N)	r  rl   rt   r   r  r  rW  rh   ro   r  r/   r/   rJ   r    s   z:TrustedDeviceBridgeBootstrapper._apply_expected_step4_pushc              	   C  sJ   |  || |jdvs|jstdtdt|j|jt|jdd dS )zGRequire the final bridge push to contain the encrypted validation code.>   r  r  r  r  z;Trusted-device bridge returned an unexpected final payload.zQReceived trusted-device bridge final payload: sessionUUID=%s nextStep=%s txnid=%sr  r  N)	r  rl   rv   r   r  r  rW  rh   ro   r  r/   r/   rJ   r  
  s   
z8TrustedDeviceBridgeBootstrapper._apply_final_bridge_pushr$   c                 C  s   t |}|jr|j|d< |S )z>Build the auth headers used for bridge-specific HTTP requests.r  )rO   r8   )rU   r  r  r  r/   r/   rJ   r  #  s   
z/TrustedDeviceBridgeBootstrapper._bridge_headersrl   r   rt   rr   r7   rs   r4   c                C  s   t |j||j|||d S )z0Build the JSON payload for one bridge step POST.)rh   rt   r   rl   rr   rs   )r   rh   r   r   )rU   r  rl   rt   rr   rs   r/   r/   rJ   _bridge_step_json.  s   
z1TrustedDeviceBridgeBootstrapper._bridge_step_jsonc          
   
   C  s^   |j d| tj|d | j|||||d|d}	|	jttthvr-td| d|	j d|	S )zAPOST one bridge step and enforce the small set of valid statuses.POST)step)r  rl   rt   rr   rs   r   r  zTrusted-device bridge step z failed with status r  )	request_rawBRIDGE_STEP_PATH_TEMPLATEr  r  r  HTTP_STATUS_OKHTTP_STATUS_NO_CONTENTHTTP_STATUS_CONFLICTr   )
rU   r  re  r  r  rl   rt   rr   rs   r  r/   r/   rJ   r  A  s2   z1TrustedDeviceBridgeBootstrapper._post_bridge_steprh   r   c                C  sF   |j d| t ||d|d}|jttthvr!td|j d|S )z>POST bridge step 0 immediately after obtaining the push token.r  )re   r   r  z0Trusted-device bridge step 0 failed with status r  )r   BRIDGE_STEP_PATHr  r"  r#  r$  r   )rU   r  re  r  rh   r   r  r/   r/   rJ   r  e  s&   

	z2TrustedDeviceBridgeBootstrapper._post_bridge_step0c                C  sP   |j d| t t|j|d |d}|jtttt	hvr&t
d|j d|S )zDPOST the decrypted bridge code to Apple's final validation endpoint.r  r   r  z9Trusted-device bridge code validation failed with status r  )r   BRIDGE_CODE_VALIDATE_PATHr   rh   r   r  r"  r#  r$  r  r   )rU   r  re  r  r  r   r  r/   r/   rJ   r    s,   

	z:TrustedDeviceBridgeBootstrapper._post_bridge_code_validate)rj  rk  r  r  r  r  r;   r   )r  r   re  r+   r  r3  rX  r)   rm  r+   r;   r   )r;   r  r   )r  r  r;   r   )r  r   re  r+   r  r3  r  r   r   r+   r;   r-   )r   r   r;   r   )r   r   r   r+   r   r3  r;   r   )r  r   r   r   r;   r   )r  r3  r  r   r;   r$   )r  r   rl   r   rt   r+   rr   r7   rs   r   r;   r4   )r  r   re  r+   r  r3  r  r   rl   r   rt   r+   rr   r7   rs   r   r;   r   )r  r   re  r+   r  r3  rh   r+   r   r+   r;   r   )r  r   re  r+   r  r3  r  r   r   r+   r;   r   )rZ   r[   r\   r]   WEBSOCKET_TIMEOUT_SECONDSr   r7  r  r  r   r  r  r  r  r  r  r  r  r  r  r  r/   r/   r/   rJ   r    s.    
 
	
	

 
2
0
>





$r  )r   r+   r;   r)   )rx   r   r;   r   )rt   r   r   r   r;   r   )r   r   r   r   rx   r   r;   r   )r   r   rx   r   r;   r   )r   r   rx   r+   r;   r   )r   r   rx   r   r;   r   )rt   r   r;   r  )r  r   r;   r   )r  r   r;   r   )r  r   r;   r   )r  r   r;   r   )r  r   r;   r   )r  r   r  r   r   r   r;   r   )r   r   r;   r   )r(  r)  r;   r   )r   r   r   r   r;   r   )r   r+   r;   r+   )r2  r   r   r3  r;   r+   )r   r   r;   r4   )rx   r+   r;   r+   )rM  r   r;   r   )rx   r7   rU  r   rV  r+   r;   r+   )rX  r)   r;   r+   )re  r+   r;   r+   )xr]   
__future__r   rF  r.  r   loggingrR  r}  r  r  r  r  binasciir   rH  dataclassesr   r   html.parserr   typingr   r   r	   r
   r   urllib.parser   cryptography.hazmat.primitivesr   r   )cryptography.hazmat.primitives.asymmetricr   pydanticr   r   r   r   r   r   r   pyicloud.exceptionsr   r   pyicloud.hsa2_bridge_proverr   	getLoggerrZ   r  r%  r!  r&  r#  r  r  r  r  r  r  r  r  r  r  r'  rJ  r	  r   r  r'  r%   r^   r"  r#  r$  r  r)   r`   r   r   r   r   r   r   r   r   r   r   	Exceptionr   r   r   r   r   r   r   r   r  r  r  r  r  r  r  r%  r"  r+  r-  r1  r5  rC  rI  rL  rT  rW  rb  rd  rg  rh  r  r/   r/   r/   rJ   <module>   s    $

B93-	
	
$
5








	


#






'





 <