关于SSL verify_fun中UserState变量及Cowboy证书存储的技术问询
Great question! I’ve dealt with this exact scenario when setting up mutual TLS in Cowboy, so let’s break down your options clearly.
Option 1: Using UserState to Pass Cert Info
You absolutely can use the UserState parameter in verify_fun to store certificate details and retrieve them later in your Cowboy server. Here’s how it works:
- The
UserStateis an arbitrary term you can initialize (e.g., an empty map) when configuring your TLS listener. - During the certificate verification process in
verify_fun, you can extract the data you need (like subject DN, public key, or custom extensions) from the client certificate, update theUserStatewith this data, and return it alongside thevalidstatus. - Once verification passes, Cowboy stores this updated
UserStatein the request’s metadata, which you can access in your request handlers.
Example Code Snippet
First, define your verify_fun to extract and store cert info:
verify_fun(Cert, _Event, UserState) -> % Decode the certificate to extract fields OtpCert = public_key:pkix_decode_cert(Cert, otp), SubjectDN = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subject, PublicKey = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subjectPublicKeyInfo, % Update UserState with the extracted data UpdatedState = maps:merge(UserState, #{ cert_subject => SubjectDN, cert_public_key => PublicKey }), {valid, UpdatedState}.
Then, configure your Cowboy listener to use this verify_fun and initialize UserState:
start() -> SslOpts = [ {certfile, "path/to/server.crt"}, {keyfile, "path/to/server.key"}, {verify, verify_peer}, {fail_if_no_peer_cert, true}, {verify_fun, {fun verify_fun/3, #{}}} % Initialize with empty map ], cowboy:start_tls(https_listener, SslOpts, #{env => #{dispatch => dispatch()}}).
Finally, retrieve the UserState in your request handler:
init(Req0, State) -> % Fetch the updated UserState from request metadata case cowboy_req:meta(ssl_verify_user_state, Req0) of undefined -> % Handle unexpected case where verification didn't run Req = cowboy_req:reply(500, #{}, "Internal error", Req0), {ok, Req, State}; CertData -> % Use CertData for client authentication logic io:format("Client cert subject: ~p~n", [maps:get(cert_subject, CertData)]), Req = cowboy_req:reply(200, #{}, "Authenticated", Req0), {ok, Req, State} end.
Option 2: Directly Fetching Cert Info in Cowboy Handlers
If you don’t need to tie the cert data extraction to the verification process, you can skip modifying verify_fun entirely and fetch the client certificate directly in your request handlers using cowboy_req:peer_cert/1.
Example Code Snippet
init(Req0, State) -> case cowboy_req:peer_cert(Req0) of {ok, Cert} -> % Decode and extract the info you need OtpCert = public_key:pkix_decode_cert(Cert, otp), SubjectDN = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subject, % Proceed with authentication using SubjectDN Req = cowboy_req:reply(200, #{}, "Authenticated", Req0), {ok, Req, State}; undefined -> % No client certificate provided Req = cowboy_req:reply(401, #{}, "Client certificate required", Req0), {ok, Req, State} end.
Which Approach Should You Use?
- Use
UserStateif:- You need to validate specific cert fields during the verification process (e.g., checking if the subject matches an allowed list) and want to pass the validated data forward.
- You want to avoid re-decoding the certificate multiple times (since you already process it in
verify_fun).
- Use direct
peer_certfetch if:- Your certificate verification is handled by standard CA checks (no custom
verify_funlogic). - You only need cert info for post-verification authentication (like checking if the subject is in a database) and prefer simpler, more focused code.
- Your certificate verification is handled by standard CA checks (no custom
Both methods are fully supported by Cowboy—pick the one that aligns with your workflow!
内容的提问来源于stack exchange,提问作者ITChap

