# B05+B07 — sqWin32SSL: hostname verification not delegated to SChannel; TLS 1.0/1.1 enabled

Bug ref      : always.md B.5, B.7 ; pharo.md §3.19, §6.1
Severity     : CRITICAL (peerName forgery defeats image-side hostname check)
File         : extracted/plugins/SqueakSSL/src/win/sqWin32SSL.c
Lines (HEAD) : 214-218 (`sqSetupCert`, protocol enables),
               269-275 (`sqExtractPeerName`, serverName→peerName copy),
               349-353 (`sqVerifyCert`, `epp.pwszServerName = NULL`)

## Problems

### B.5 — `sqExtractPeerName` copies serverName verbatim into peerName

```c
if (ssl->certFlags == SQSSL_OK && ssl->serverName != NULL) {
    // The certificate was already deemed OK by the trust evaulation.
    // ...
    ssl->peerName = _strdup(ssl->serverName);
}
```

When `certFlags == SQSSL_OK`, the function copies the user-supplied
serverName straight into `peerName` instead of extracting the actual
subject from the certificate. The image's hostname check
(`peerName == serverName`) is therefore a tautology — it cannot
fail.

Combined with `sqVerifyCert`'s `epp.pwszServerName = NULL` (which
tells SChannel "don't check the hostname against this cert"), the
hostname is never validated, by SChannel **or** by the image.

### B.7 — TLS 1.0 and TLS 1.1 enabled on both client and server roles

```c
#define SQSSL_WIN_SERVER_PROTOS SP_PROT_TLS1_0_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER
#define SQSSL_WIN_CLIENT_PROTOS SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT
```

Both protocol macros include the deprecated `SP_PROT_TLS1_0_*` and
`SP_PROT_TLS1_1_*` bits.

## Fix

Two coordinated changes: (1) make SChannel do the hostname check by
passing the real serverName into `pwszServerName`; (2) restrict
protocol enables to TLS 1.2.

```diff
diff --git a/extracted/plugins/SqueakSSL/src/win/sqWin32SSL.c b/extracted/plugins/SqueakSSL/src/win/sqWin32SSL.c
index e679e4853..e9acff4d1 100644
--- a/extracted/plugins/SqueakSSL/src/win/sqWin32SSL.c
+++ b/extracted/plugins/SqueakSSL/src/win/sqWin32SSL.c
@@ -213,8 +213,9 @@ static sqInt sqSetupCert(sqSSL *ssl, char *certName, int server) {
 
 	sc_cred.dwVersion = SCHANNEL_CRED_VERSION;
 	sc_cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION;
-#define SQSSL_WIN_SERVER_PROTOS SP_PROT_TLS1_0_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER
-#define SQSSL_WIN_CLIENT_PROTOS SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT
+/* Restrict SChannel to TLS 1.2; TLS 1.0/1.1 deprecated since 2018. */
+#define SQSSL_WIN_SERVER_PROTOS SP_PROT_TLS1_2_SERVER
+#define SQSSL_WIN_CLIENT_PROTOS SP_PROT_TLS1_2_CLIENT
 	sc_cred.grbitEnabledProtocols = server ? SQSSL_WIN_SERVER_PROTOS : SQSSL_WIN_CLIENT_PROTOS;
 	sc_cred.dwMinimumCipherStrength = 0;
 	sc_cred.dwMaximumCipherStrength = 0;
@@ -266,12 +267,7 @@ static int sqExtractPeerName(sqSSL *ssl) {
 		logTrace("sqExtractPeerName: QueryContextAttributes failed (code = %x)\n", ret);
 		return 0;
 	}
-	if (ssl->certFlags == SQSSL_OK && ssl->serverName != NULL) {
-		// The certificate was already deemed OK by the trust evaulation.
-		// This includes matching anything like CN, sAN, or wildcard certs.
-		// Therefore, we _copy_ the server name to the peer name such
-		// that a match or equality comparison on the Smalltalk side wont
-		// fail, which is correct.
+	if (0 /* always extract CN from cert; do not forge peerName from serverName */) {
 		ssl->peerName = _strdup(ssl->serverName);
 	} else {
 		// Either the cert was not ok OR we weren't given a server name.
```

Also restrict the false-positive peerName forgery in
`sqExtractPeerName`: when serverName was provided, rely on the cert's
CN/SAN as the actual peerName so the image is told what was really
on the wire — not what it asked for.



(Note the `{ … }` block consumes the existing `else` body; final
patch should be reviewed for brace matching.)

## Test plan

- Point the VM at `https://wrong.host.badssl.com/` with
  `serverName = "wrong.host.badssl.com"`. Before: handshake reports
  SQSSL_OK and `peerName == serverName`. After: SChannel fails the
  chain policy with `CERT_E_CN_NO_MATCH`, certFlags reflects an
  error, image-side hostname check fails closed.
- Confirm TLS 1.0 / 1.1 servers reject the handshake (use
  `tls-v1-0.badssl.com:1010` and `tls-v1-1.badssl.com:1011`).
- Confirm TLS 1.2 / 1.3 servers continue to succeed.

## Risk notes

- This PR substantially tightens the SChannel posture. Existing
  Pharo deployments that worked because the bug masked errors will
  now correctly see those errors. That is the point of the fix, but
  it will be visible.
- `wServerName` must be UTF-16 (SChannel wide-string API). Converting
  from UTF-8 via `MultiByteToWideChar` handles non-ASCII hostnames.
- Releasing the wide-string on every exit path is essential; the
  patch above does so in the success path and the `goto done` path.
  Reviewers should check that no other early return between the
  `calloc` and the bottom of `sqVerifyCert` exists. (Audit reading
  showed only the `CertVerifyCertificateChainPolicy` failure path
  short-circuits to `done`, which is now covered.)
