const {P,H2,H3,Callout,Code,Ul,Related,PostHead}=window;
function Article(){
return <>
<PostHead cat="/ANDROID" date="2026-08-21" title="SSL pinning is a client opinion" lede="The app refuses TLS unless the cert or SPKI matches a list. That is a trust boundary we name. It is not a server control. We do not ship unpin kits."/>
<P>Pinning means the client will not complete TLS unless the peer certificate (or its SPKI hash) is on a list the app shipped. Casual intercept on a stock build fails. That is useful. It is also only a statement about <em>this binary, as shipped, on a device that still runs it</em>.</P>
<Callout>Authorized APK and a lab device named in the letter. We map where the pin lives. We do not publish Frida unpin scripts, TrustManager replacements, or “objection recipes.”</Callout>
<H2>Where it actually lives</H2>
<H3>network_security_config.xml</H3>
<P>Android’s own file. apktool shows it. Pins, debug overrides, cleartext. If <span style={{fontFamily:'var(--font-mono)',fontSize:13}}>debug-overrides</span> still allow user CAs in the release variant, the pin is a suggestion. Write the filename and the domain list. That is the finding shape.</P>
<H3>OkHttp CertificatePinner / similar Java</H3>
<P>jadx: a list of SPKI hashes next to hostnames. Honest, easy to screenshot, easy to go stale when the CDN rotates. We copy the hosts, not a bypass.</P>
<H3>Custom TrustManager / HostnameVerifier</H3>
<P>Often “pinning” that is actually <span style={{fontFamily:'var(--font-mono)',fontSize:13}}>return true</span> in debug leftover. Read it. If it accepts anything, say so. If it compares to a baked-in cert in assets, name the asset.</P>
<H3>Native</H3>
<P>BoringSSL verify hooks, a .so that never uses the Java stack. jadx will look unpinned. The traffic still dies in Charles. Then Ghidra on the .so — same native desk as everything else. Do not declare “no pinning” from Java alone.</P>
<H2>Combination on the desk</H2>
<Code lang="flow.txt">{`apktool    →  res/xml/network_security_config.xml
jadx       →  CertificatePinner, TrustManager, hostname lists
lib/*.so   →  only if Java has no TLS and traffic still refuses intercept
lab device →  confirm: stock build fails intercept, or it does not
server     →  does a build without the pin still get tokens?`}</Code>
<P>The last line is the one that matters. Pinning that the API does not notice is theater. If the engagement asks “does the broker still run when the Java layer is lied to,” that test stays in the report. The method does not go on this site.</P>
<H2>What pinning is not</H2>
<Ul items={[
'Not attestation. A pinned client on a rooted phone is still that client. See keybox / Play Integrity.',
'Not request signing. A pin does not prove the request body came from unmodified code.',
'Not a substitute for the server refusing old, debug, or sideloaded builds.'
]}/>
<Related items={[
['post-android-desk.html','Android on the authorized desk'],
['post-re-dynamic.html','Static vs dynamic']
]}/>
</>;
}
window.Article=Article;
mountPost();
