Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto/x509: certificate validation in Windows fails to validate IP in SAN #37176

Open
ihgann opened this issue Feb 11, 2020 · 9 comments
Open

crypto/x509: certificate validation in Windows fails to validate IP in SAN #37176

ihgann opened this issue Feb 11, 2020 · 9 comments

Comments

@ihgann
Copy link

@ihgann ihgann commented Feb 11, 2020

What version of Go are you using (go version)?

$ go version
go version go1.13.5 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/ganni/Library/Caches/go-build"
GOENV="/Users/ganni/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/ganni/.gvm/pkgsets/go1.13.5/global"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/Users/ganni/.gvm/gos/go1.13.5"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/Users/ganni/.gvm/gos/go1.13.5/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/jk/kkbbm14x25l9hj_xj1rb4scm002zdg/T/go-build540477719=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I ran the following quick program to reproduce it:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

func main() {
	client := http.Client{
		Timeout: 5 * time.Second,
	}
	resp, err := client.Get("https://192.168.210.129")
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
		return
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
		return
	}

	fmt.Printf("Response:\n\n%s", string(body))
}

Where 192.168.210.129 is a custom server using a self-signed certificate. The notable aspects of this cert are that I'm connecting via IP (which is not present in the CN) and it is only present in the IP Address field of the SAN, like so:

IP Address=192.168.210.129

When I compile the binary to run on a Windows box, I run the following:

GOOS=windows GOARCH=386 go build -o quicktest.exe main.go

What did you expect to see?

I expect to see a response from the server. This server is healthy and I can access it from my browser, so I expect to see an HTML output from the last line.

What did you see instead?

I see the following output while running:

PS C:\> C:\quicktest.exe
Get https://192.168.210.129: x509: certificate is valid for 192.168.210.129, not 192.168.210.129

The interesting part is that it that's the same IP address in both notes.

I then ran a modified version of go to add a panic near https://sourcegraph.com/github.com/golang/go@release-branch.go1.11/-/blob/src/crypto/x509/root_windows.go#L129, changing:

  if status.Error != 0 {
    switch status.Error {
    case syscall.CERT_E_EXPIRED:
      return CertificateInvalidError{c, Expired, ""}
    case syscall.CERT_E_CN_NO_MATCH:
      return HostnameError{c, opts.DNSName}

to

  if status.Error != 0 {
    switch status.Error {
    case syscall.CERT_E_EXPIRED:
      return CertificateInvalidError{c, Expired, ""}
    case syscall.CERT_E_CN_NO_MATCH:
      panic("failed here")
      // return HostnameError{c, opts.DNSName}

and see the following stack trace to verify this is indeed where it fails, which is Windows-specific.

Windows PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.

PS C:\> & 'C:\quicktestcustom.exe'
panic: failed here

goroutine 21 [running]:
crypto/x509.checkChainSSLServerPolicy(0x128cc2c0, 0xb2d7b8, 0x128adb6c, 0xb14850, 0x128adab8)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/crypto/x509/root_windows.go:130 +0x2c7
crypto/x509.(*Certificate).systemVerify(0x128cc2c0, 0x128adb6c, 0x0, 0x0, 0x0, 0x0, 0x0)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/crypto/x509/root_windows.go:213 +0x652
crypto/x509.(*Certificate).Verify(0x128cc2c0, 0x1289a280, 0xf, 0x128f65c0, 0x0, 0x526d9c8, 0xbf88e6b7, 0x112bdfd, 0x0, 0
x814560, ...)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/crypto/x509/verify.go:750 +0x5b6
crypto/tls.(*Conn).verifyServerCertificate(0x128fc000, 0x12892320, 0x1, 0x1, 0x3eb, 0x0)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/crypto/tls/handshake_client.go:815 +0x1e4
crypto/tls.(*clientHandshakeState).doFullHandshake(0x128adec8, 0x128ba0e0, 0x68)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/crypto/tls/handshake_client.go:452 +0x136c
crypto/tls.(*clientHandshakeState).handshake(0x128adec8, 0x128a26c0, 0x0)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/crypto/tls/handshake_client.go:397 +0x343
crypto/tls.(*Conn).clientHandshake(0x128fc000, 0x0, 0x0)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/crypto/tls/handshake_client.go:206 +0x47a
crypto/tls.(*Conn).Handshake(0x128fc000, 0x0, 0x0)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/crypto/tls/conn.go:1340 +0xe0
net/http.(*persistConn).addTLS.func2(0x0, 0x128fc000, 0x128f4400, 0x128f43c0)
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/net/http/transport.go:1453 +0x34
created by net/http.(*persistConn).addTLS
        /code/github.com/golang/go-darwin-amd64-bootstrap/src/net/http/transport.go:1449 +0x175

I also further validated I do not see such issues on Linux or Mac machines.

For additional debugging help, I should also mention I only have access to one specific Windows machine that I tested with at the time, so I'm not sure if this is apparent on other Windows versions.

My Windows version is: Windows Server 2012 R2 Standard

@ihgann
Copy link
Author

@ihgann ihgann commented Feb 11, 2020

To add - I also verified this behaviour has been present at least since go1.11.

@FiloSottile FiloSottile changed the title x509 certificate validation in Windows fails to validate IP in SAN crypto/x509: certificate validation in Windows fails to validate IP in SAN Feb 11, 2020
@FiloSottile FiloSottile added this to the Backlog milestone Feb 11, 2020
@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Feb 16, 2020

@ihgann

I don't know if there is an error here - I am not a crypto expert.

But I would like to try and reproduce your error here first. You said nothing about your server and how you generated your certificate. Is your server is written in Go? Can I see your code? What are the commands to generate and use your certificate?

Thank you.

Alex

@naoriel
Copy link

@naoriel naoriel commented Sep 9, 2020

Had the same issue here:
x509: certificate is valid for 10.149.145.130, fe80::5efe:a95:9182, fe80:: 100:7f:fffe, not 10.149.145.130

  • Server and client both written in go.
  • Certificate details (unfortunately in German OS):
    cert

Modifying checkChainSSLServerPolicy in root_windows.go as follows would clear up the problem, but I'm not entirely sure, whether any security issues would arise as a result:
line 130 ff.
case syscall.CERT_E_CN_NO_MATCH:
return HostnameError{c, opts.DNSName}
would be modified to:
case syscall.CERT_E_CN_NO_MATCH:
err = c.VerifyHostname(opts.DNSName)
if err != nil {
return err
}
return err

@ihgann
Copy link
Author

@ihgann ihgann commented Sep 9, 2020

@alexbrainman I'm sorry, it's been quite a while - hopefully @naoriel's duplication can help.

I think there is definitely an error here - the error message alone doesn't make sense (it's valid for , but not for ).

You said nothing about your server and how you generated your certificate. Is your server is written in Go?

No, not written in Go. The server is Java-based, though at the time I was able to reproduce it with a Go-based server hosted on a separate port (an echo-server that just returns your request back to you). Again, it worked on both Linux/Mac connections but failed on the Windows connection (client-side failure).

What are the commands to generate and use your certificate?

Unfortunately it's been several months since the filing of this ticket and I had not seen your notification, so I don't have this information, but it was nothing out-of-the-ordinary - I believe this would be reproducible with any certificate request and certificate authority, where an IP is left out of the CN but present in the "IP Address". @naoriel's example looks spot on.

@naoriel
Copy link

@naoriel naoriel commented Sep 10, 2020

I revisited the code in root_windows.go.
Taking into account, that the windows *.dll-method might not yield the CN-mismatch-error as last resort, I extended my error-handling insofar as client- (not server !) verification seems to do all the same steps apart from CN-validation.
So if client-verification succeeds but server-verification does not we can safely use c.VerifyHostname(opts.DNSName) to check, whether there really is a HostnameError to report.

Here is a patch file I prepared:
root_windows.go.zip

@FiloSottile : If you've got a moment to spare, I'd be very interested in your thoughts on the matter.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Sep 12, 2020

@alexbrainman I'm sorry, it's been quite a while - hopefully @naoriel's duplication can help.

No worries. I will let the experts look at @naoriel code.

Alex

@rolandshoemaker
Copy link
Member

@rolandshoemaker rolandshoemaker commented Sep 14, 2020

Looks like this could be a, uh, feature of CryptoAPI. Chromium ran into the same issue (https://bugs.chromium.org/p/chromium/issues/detail?id=72726) which confirmed that CertVerifyCertificateChainPolicy appeared to ignore non-dNSName SAN fields when doing name validation. As far as I can tell this is still the case on most older versions of Windows (but seems to be fixed in 10).

The Chromium approach was to ignore the invalid CN return by setting SECURITY_FLAG_IGNORE_CERT_CN_INVALID and use their own name matching logic, which is in general more conservative (current impl. starts around here). Probably want to follow suit.

@FiloSottile
Copy link
Member

@FiloSottile FiloSottile commented Oct 6, 2020

Well, that's... weird. I guess we should just take over name validation from the OS, after checking that it checks name constraints on all names on the certificate. Since this has been the case forever, and I want to go one cycle without breaking people on crypto/x509, targeting Go 1.17.

@FiloSottile FiloSottile modified the milestones: Backlog, Go1.17 Oct 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants
You can’t perform that action at this time.