{"id":22614547,"url":"https://github.com/salrashid123/go_mtls_scratchpad","last_synced_at":"2025-03-29T00:26:37.940Z","repository":{"id":266352375,"uuid":"629001079","full_name":"salrashid123/go_mtls_scratchpad","owner":"salrashid123","description":"go mTLS with multiple certificate issuers and OCSP verification","archived":false,"fork":false,"pushed_at":"2024-12-05T02:47:39.000Z","size":3266,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-03T10:34:05.837Z","etag":null,"topics":["golang","mtls-authentication","tls"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salrashid123.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-17T12:23:00.000Z","updated_at":"2024-12-05T02:47:43.000Z","dependencies_parsed_at":"2024-12-03T21:34:00.412Z","dependency_job_id":null,"html_url":"https://github.com/salrashid123/go_mtls_scratchpad","commit_stats":null,"previous_names":["salrashid123/go_mtls_scratchpad"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salrashid123%2Fgo_mtls_scratchpad","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salrashid123%2Fgo_mtls_scratchpad/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salrashid123%2Fgo_mtls_scratchpad/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salrashid123%2Fgo_mtls_scratchpad/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salrashid123","download_url":"https://codeload.github.com/salrashid123/go_mtls_scratchpad/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246119741,"owners_count":20726439,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","mtls-authentication","tls"],"created_at":"2024-12-08T18:10:42.265Z","updated_at":"2025-03-29T00:26:37.916Z","avatar_url":"https://github.com/salrashid123.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n## go mTLS with multiple certificate issuers and OCSP verification\n\nSample client/server application which does the following:\n\n1. Suppose there are two CAs that issue client certificates (`ca1`, `ca2`).\n\n2. These two CAs also issue server a certificate each with different SNIs (`tee.collaborator1.com`, `tee.collaborator2.com`)\n\n3. The server will load the two server certificates from these two CAs.\n\n4. The server will enforce mTLS where the client certificates are issued by the corresponding CAs.\n\n5. The client will load the client certificates and server CA certificate for _one_ of the two issuers and connect to the server while specifying an appropriate `SNI` value\n\n6. During TLS negotiation, the server will return the server cert that maps to `SNI` sent by the client\n\n7. During TLS negotiation, the server will return the set of client certificate CA's it accepts.\n\n8. Client will verify the server's cert CA issuer from the loaded CA in step 5.\n\n9. Client will negotiate mTLS using the client certs in step 5.\n\n10. Server will verify the client certificate using the server certs from step 3.\n\n11. Server will optionally invoke an external OCSP request to verify live that the client certificate presented has not been revoked\n\n\n---\n\nFor gRPC, the tests here only verifies against ca1 but demonstrates the [AdvancedTLS](https://pkg.go.dev/google.golang.org/grpc/security/advancedtls) package.   You can find the examples under the `grpc` folder\n\n\n---\n\nalso see\n\n* [mTLS with TPM bound private key](https://github.com/salrashid123/go_tpm_https_embed)\n* [mTLS with PKCS11](https://github.com/salrashid123/mtls_pkcs11)\n\n---\n\n### Setup\n\nRun Server\n\n```bash\ngo run server/server.go \n```\n\nTry to make a connection to the server without specifying any certificates:\n\nThe response will show a 'default' server certificate `tee.operator.com` and the list of CA's it accepts.\n\nThe default certificate was shown because we did not specify an SNI value (eg `-servername tee.collaborator1.com`)\n\n```bash\n$ openssl s_client --connect localhost:8081\n\n---\nCertificate chain\n 0 s:C = US, O = Google, OU = Enterprise, CN = tee.operator.com             \u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\n   i:C = US, O = Operator, OU = Enterprise, CN = Enterprise Root CA\n\n---\nServer certificate\n\nsubject=C = US, O = Google, OU = Enterprise, CN = tee.operator.com\nissuer=C = US, O = Operator, OU = Enterprise, CN = Enterprise Root CA\n---\nAcceptable client certificate CA names\nC = US, O = Collaborator 1, OU = Enterprise, CN = Collaborator 1 Root CA\nC = US, O = Collaborator 2, OU = Enterprise, CN = Collaborator 2 Root CA\n```\n\n### Run Client\n\nNow run the clients:\n\n```bash\n## collaborator 1\ngo run client/client.go --serverName tee.collaborator1.com --serverCertRootCA ca1/root-ca.crt --clientCert ca1/client.crt --clientKey ca1/client.key\n## collaborator 2\ngo run client/client.go --serverName tee.collaborator2.com --serverCertRootCA ca2/root-ca.crt --clientCert ca2/client.crt --clientKey ca2/client.key\n```\n\nWhat you should see on the server config is a pair of connections from the clients:\n\n```\n$ go run server/server.go \nSubject CN=client.collaborator1.com,OU=Enterprise,O=Google,L=US\nClient Certificate hash KK8-CThe_W9i0FWTX7L-DMbvsuCvv5K10m6fWJO--MA\n\nSubject CN=client.collaborator2.com,OU=Enterprise,O=Google,C=US\nClient Certificate hash Pkapc_CwEq12asjxrJ5mdn5_1MV2NNDosQ44hxbcwG0\n```\n\n### Emit client certificate hash\n\nThe sever also generates and prints the client certificate hash for use with [Token Binding](https://connect2id.com/learn/token-binding)\n\n```bash\n$ echo `openssl x509 -in ca1/client.crt -outform DER | openssl dgst -sha256 | cut -d\" \" -f2` | xxd -r -p - | openssl enc -a | tr -d '=' | tr '/+' '_-'\nKK8-CThe_W9i0FWTX7L-DMbvsuCvv5K10m6fWJO--MA\n```\n\nalso see\n\n* [Certificate Bound Tokens using Security Token Exchange Server (STS)](https://github.com/salrashid123/cert_bound_sts_server)\n* [Envoy WASM and LUA filters for Certificate Bound Tokens](https://github.com/salrashid123/envoy_cert_bound_token)\n\n### Exported Key Material\n\nBoth the client and server will print the common [Exported Key Material rfc5705](https://www.rfc-editor.org/rfc/rfc5705.html) from the established [ConnectionState.ExportKeyingMaterial](https://pkg.go.dev/crypto/tls#ConnectionState.ExportKeyingMaterial).  This can be used for binding.\n\n\nThe net result is you should get the same per-conn data on both ends of the connection\n\n```bash\n$ go run server/server.go \nStarting Server..\nEKM my_nonce: 1efe1f9d73fcaefb59d10a898592759c6a1bae792f8fbfce80ca7db72202e06b   \u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\n\n$ go run client/client.go --serverName tee.collaborator1.com --serverCertRootCA ca1/root-ca.crt --clientCert ca1/client.crt --clientKey ca1/client.key\nEKM my_nonce: 1efe1f9d73fcaefb59d10a898592759c6a1bae792f8fbfce80ca7db72202e06b   \u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\n```\n\n### SSL KeyLog\n\nIf you want to see the decrypted tls traces using wireshark, enable the `SSLKEYLOGFILE` writer within the `TLSConfig`\n\n```\nsudo tcpdump -s0 -iany -w trace.cap port 8081\n```\n\n```bash\nexport SSLKEYLOGFILE=/tmp/keylog.log\ngo run server/server.go\n```\n\nrun client\n\n```bash\ncd tcpdump/\nwireshark trace.cap --log-level=debug -otls.keylog_file:keylog.log\n```\n\n* Client-\u003eServer SNI\n\n![images/sni.png](images/sni.png)\n\n* Server-\u003eClient Client CA issuers\n\n![images/ca_requested.png](images/ca_requested.png)\n\n* Server-\u003eClient HTTP Decrypted\n\n![images/decrypted.png](images/decrypted.png)\n\n\nalso see\n\n* [OpenSSL 3.0.0 docker with TLS trace enabled (enable-ssl-trace) and FIPS (enable-fips)](https://github.com/salrashid123/openssl_trace)\n* [Using Wireshark to decrypt TLS gRPC Client-Server protobuf messages](https://github.com/salrashid123/grpc_sslkeylog)\n\n#### OCSP Check\n\nThe server  can also make an OCSP API call to verify the client' certificates revocation status at runtime.\n\nTo use this, you need to run an ocsp server which in the following is trough `openssl`\n\n```bash\ncd ca1/ca_scratchpad\n\n## start OCSP Server\nopenssl ocsp -index ca/root-ca/db/root-ca.db -port 9999 -rsigner ca/root-ca.crt -rkey ca/root-ca/private/root-ca.key -CA ca/root-ca.crt -text -ndays 10\n\n## in new windows, test ocsp server with revoked cert \nopenssl ocsp -CA ca/root-ca.crt -CAfile ca/root-ca.crt -issuer ca/root-ca.crt  -cert certs/revoked.crt -url http://localhost:9999 -resp_text\n### note Cert Status: revoked\n\n### test ocsp server with valid cert\nopenssl ocsp -CA ca/root-ca.crt -CAfile ca/root-ca.crt -issuer ca/root-ca.crt  -cert certs/tee.crt -url http://localhost:9999 -resp_text\n### note Cert Status: good\n\n```\n\nnow run the server with ocsp enabled\n\n```bash\n## run server\n$ go run server/server.go --check_ocsp\n\n## run client\n$ go run client/client.go --serverName tee.collaborator1.com --serverCertRootCA ca1/root-ca.crt --clientCert ca1/client.crt --clientKey ca1/client.key\n\n```\n\nThe ocsp server will show the request, validation and response\n\n```text\n$ openssl ocsp -index ca/root-ca/db/root-ca.db -port 9999 -rsigner ca/root-ca.crt -rkey ca/root-ca/private/root-ca.key -CA ca/root-ca.crt -text -ndays 10\n\nACCEPT 0.0.0.0:9999 PID=187589\nocsp: waiting for OCSP client connections...\nocsp: Received request, 1st line: POST / HTTP/1.1\nOCSP Request Data:\n    Version: 1 (0x0)\n    Requestor List:\n        Certificate ID:\n          Hash Algorithm: sha1\n          Issuer Name Hash: 10CA8300F670BDF813C03C0CD3DACE5EA8AAB355\n          Issuer Key Hash: 750D12CCDB33ED58068CADED0E9E2F00E96FC165\n          Serial Number: 01\nOCSP Response Data:\n    OCSP Response Status: successful (0x0)\n    Response Type: Basic OCSP Response\n    Version: 1 (0x0)\n    Responder Id: C = US, O = Collaborator 1, OU = Enterprise, CN = Collaborator 1 Root CA\n    Produced At: Apr 17 12:20:54 2023 GMT\n    Responses:\n    Certificate ID:\n      Hash Algorithm: sha1\n      Issuer Name Hash: 10CA8300F670BDF813C03C0CD3DACE5EA8AAB355\n      Issuer Key Hash: 750D12CCDB33ED58068CADED0E9E2F00E96FC165\n      Serial Number: 01\n    Cert Status: good                             \u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\u003c\n    This Update: Apr 17 12:20:54 2023 GMT\n    Next Update: Apr 27 12:20:54 2023 GMT\n\n```\n\n#### OCSP Staple and CRL\n\nIf you would rather see a self-contained end-to-end example for *BOTH* the client and server ocsp staple and CRL checks, see `ocsp_full/` folder\n\nUnlike the other examples, this folder uses its own client/server and CA:\n\nto use\n\n- (optional) start and OCSP Server\n\n```bash\ncd ocsp_full/\nopenssl ocsp -index ca_scratchpad/ca/root-ca/db/root-ca.db -port 9999 \\\n -rsigner ca_scratchpad/certs/ocsp.crt -rkey ca_scratchpad/certs/ocsp.key \\\n  -CA ca_scratchpad/ca/root-ca.crt -text -ndays 3500\n```\n\nNow run the server with a valid OCSP response and CRL checks.  THe following will read the server key and cert, will read fhe following defaults\n\n```golang\n\tserverCert         = flag.String(\"serverCert\", \"ca_scratchpad/certs/http.crt\", \"Server TLS Cert\")\n\tserverKey          = flag.String(\"serverKey\", \"ca_scratchpad/certs/http.key\", \"Server TLS KEY\")\n\trootCA             = flag.String(\"rootCA\", \"ca_scratchpad/ca/root-ca.crt\", \"RootCA\")\n\tocspSigner         = flag.String(\"ocspSigner\", \"ca_scratchpad/certs/ocsp.crt\", \"OCSP SIgner\")\n\tocspResponseStatic = flag.String(\"ocspResponseStatic\", \"ca_scratchpad/http_server_ocsp_resp_valid.bin\", \"OCSP Response Bytes\")\n\n\tcrlFile = flag.String(\"crlFile\", \"ca_scratchpad/crl/root-ca-empty-valid.crl\", \"CRLFile to read\")\n```\n\nNote, the default mode is where the server will actually contact the openssl ocsp server for the response.  I've left it commented out a mode where the server will read a static response from the file.\n\nThe server will also read the CRL value to ensure that the client cert it sends is still valid\n\n```bash\ngo run server/server.go\n```\n\nNow run the client.\n\n```golang\n\tclientCert         = flag.String(\"clientCert\", \"ca_scratchpad/certs/client.crt\", \"Client TLS Cert\")\n\tclientKey          = flag.String(\"clientKey\", \"ca_scratchpad/certs/client.key\", \"Client TLS KEY\")\n\trootCA             = flag.String(\"rootCA\", \"ca_scratchpad/ca/root-ca.crt\", \"RootCA\")\n\tocspSigner         = flag.String(\"ocspSigner\", \"ca_scratchpad/certs/ocsp.crt\", \"OCSP SIgner\")\n\tocspResponseStatic = flag.String(\"ocspResponseStatic\", \"ca_scratchpad/client_ocsp_resp_valid.bin\", \"OCSP Response Bytes\")\n```\n\nThe client will readk its keypair, then read the _static_ ocsp response from file (it could read from the ocsp server too)\n\nIn the end *both* client and server will validate the certificated presented by using OCSP Stapling\n\n![images/server_ocsp.png](images/server_ocsp.png)\n\n![images/client_ocsp.png](images/client_ocsp.png)\n\n---\n\n\n#### TLS Config without SNI specific client certificates\n\nNotice that the initial TLS negotiation sends back **BOTH** the client cert CA's it accepts:\n\n```bash\nAcceptable client certificate CA names\nC = US, O = Collaborator 1, OU = Enterprise, CN = Collaborator 1 Root CA\nC = US, O = Collaborator 2, OU = Enterprise, CN = Collaborator 2 Root CA\n```\n\nif you would rather only send back the Client Cert CA that is appropriate for the SNI requested, then some more modifications are required for the `TLSConfig`:\n\n\n```golang\n\ttlsConfig := \u0026tls.Config{\n\t\tGetConfigForClient: func(ci *tls.ClientHelloInfo) (*tls.Config, error) {\n\t\t\tif ci.ServerName == \"tee.collaborator1.com\" {\n\t\t\t\treturn \u0026tls.Config{\n\t\t\t\t\tMinVersion:   tls.VersionTLS13,\n\t\t\t\t\tClientAuth:   tls.RequireAndVerifyClientCert,\n\t\t\t\t\t// set client1 root pool\n\t\t\t\t\tClientCAs:    client1_root_pool,\n\t\t\t\t\tGetCertificate: func(ci *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\t\t\t\t\t// set server1_ cert\n\t\t\t\t\t\treturn \u0026server1_cert, nil\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tif ci.ServerName == \"tee.collaborator2.com\" {\n\t\t\t\treturn \u0026tls.Config{\n\t\t\t\t\tMinVersion:   tls.VersionTLS13,\n\t\t\t\t\tClientAuth:   tls.RequireAndVerifyClientCert,\n\t\t\t\t\t// set client1 root pool\n\t\t\t\t\tClientCAs:    client2_root_pool,\n\t\t\t\t\tGetCertificate: func(ci *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\t\t\t\t\t// set server2 cert\n\t\t\t\t\t\treturn \u0026server2_cert, nil\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"SNI not recognized %s\", ci.ServerName)\n\t\t},\n\t}\n  ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalrashid123%2Fgo_mtls_scratchpad","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalrashid123%2Fgo_mtls_scratchpad","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalrashid123%2Fgo_mtls_scratchpad/lists"}