• pki::x509::verify_cert not working?

    From Jerry O.@jerry@thselucreh.com to comp.lang.tcl on Tue Mar 17 02:22:23 2026
    From Newsgroup: comp.lang.tcl

    Hello everyone,

    I have a question for those who have written code using pki: have any of you had pki::x509::verify_cert suddenly begin failing certs, even when the trust has indeed been established, the certs are current, and-- most importantly--
    an independent check using "openssl verify -CAfile..." (or some other similar utility) shows that the cert _is_ valid?

    I've condensed what I'm experiencing into the test example code below. I've commented the code with _MY_ understanding of what is going on-- but my understanding may be wrong, which may itself be the problem.

    But... this _used_ to work...

    1) Set up my own Root CA:
    -Generate the CA keys (pki::rsa::generate);
    -Use those keys to generate a Cert Signing Request for a cert
    (pki::pkcs::create_csr/parse_csr);
    -Apply those same keys to the CCR to (Self-)sign and create the x509
    cert for the CA (::pki::x509::create_cert);
    -Save the Root CA x509 cert to the file "example_root_ca.crt"

    2) Provision a user certificate signed by the Root CA:
    -Generate the user keys (pki::rsa::generate);
    -Use the user keys to generate a Cert Signing Request for the user cert
    (pki::pkcs::create_csr/parse_csr);
    -Sign the user CCR with the CA keys and create the x506 cert for the
    user (::pki::x509::create_cert);
    -Save the user x509 cert to the file "server.example.com.crt"

    3) Check the trust of the user cert against the CA cert
    (pki::x509::verify_cert). It should return _true_. But it doesn't, not
    anymore! (returns false).

    At this point, I look to see what OpenSSL has to say about the x509 certs:
    --> openssl verify -CAfile example_root_ca.crt server.example.com.crt
    --> server.example.com.crt: OK

    These results are identical on my Xubuntu 24.04.4 LTS and MacOS 15.7.4 (Sequoia) workstations, and Ubuntu Server 24.04.4 LTS, all with Tcl8.6 and pki at version 0.20. I have not yet had the opportunity to try the below test example code on Windows, but it may be significant that the original pki code on which the test is based is still functioning on Windows 10, but has since failed on Windows 11 (where it had previously functioned properly).

    Anyone else experienced or is experiencing something like this? Or is it just happening to me?

    With Gratitude,
    -Jerry O.
    (Please first reverse the letters in my address domain if replying direct.)

    ###################### TEST EXAMPLE TCL CODE FOLLOWS #######################

    #!/bin/bash
    # Run tclsh from user PATH \
    exec tclsh "$0" ${1+"$@"}

    # name of program is $argv0
    # number of arguments is $argc
    # argument LIST is $argv; can access using lindex

    #
    # refer to the paper:
    #
    # Public Key Infrastructure and the Tool Command Language
    # Roy S. Keene
    # Jemimah Ruhala
    # September 23rd, 2013
    # #https://tca1.tcl-lang.org/tcl2013/Proceedings-2013/RoyKeene/pki-and-tcl-v2.pdf #


    package require pki


    # CREATE THE ROOT CA
    # generate the CA private and public keys
    #set ca_key [::pki::rsa::generate 2048]
    set ca_keys [::pki::rsa::generate 512] ; # for faster testing
    # ::pki::rsa::generate returns a dict with dictionary keys:
    # p, l, d, q, e, type, n
    #
    # l : key length as was specified by caller (2048)
    # type : rsa
    #
    # p, q : are the two prime numbers
    #
    # n : is the product ( p * q )
    # e : is the exponent, default is 0x10001 (65537) unless specified in call-- # ADVISED NOT TO CHANGE.
    #
    # *** PRESENTED TOGETHER n AND e MAKE THE PUBLIC KEY ***
    #
    # d : *** THE PRIVATE KEY ***

    # save the CA keys to a file
    set the_file [open "example_root_ca.keys" w]
    puts $the_file $ca_keys
    close $the_file

    # define the CA cert attributes
    set subject [list C "US" O "Example Org" CN "Example Root CA"]
    set serial_number [clock seconds] ; # acts as a simple unique id
    set not_before [clock seconds]
    set not_after [clock add [clock seconds] 10 years]

    # right now no extensions (this is taken care of by ::pki, see man page), so
    # leave as empty list
    set extensions [list]


    # create a Certificate Signing Request for the CA using ::pki::pkcs::create_csr # a cert signing request made by an entity requires both that entity's public # and private keys. in this case, the entity is the CA itself, so use the
    # $ca_keys generated earlier. Also, a cert requires the subject attribute, and # ::pki::pkcs::create_csr expects a list for this as was defined in the
    # CA cert attributes code above.
    set ca_csr [::pki::pkcs::create_csr $ca_keys $subject 1]

    # ::pki::x509::create_cert expects the signing request to be parsed.
    # ::pki::pkcs::parse_csr returns a dict containing l, subject, e, n, type,
    # BUT DOES NOT INCLUDE d (THE PRIVATE KEY)!
    set parsed_ca_csr [::pki::pkcs::parse_csr $ca_csr]

    # ::pki::x509::create_cert also expects the CA "cert" doing the signing to be
    # parsed into a dict with keys l, subject, e, n, type, AND d (the private key). #
    # $ca_keys already has l, e, n, type, d, but no subject
    #
    # $parsed_ca_csr has l, subject, e, n, type, but no d
    #
    # there are two choices then:
    # choice 1: take ca_keys and set a new key named "subject" with value
    # { C="US" O="Example Org" CN="Example Root CA" ...etc }
    # and use that,
    # OR
    # choice 2: take parsed_ca_csr and set a new key named "d" with value d from
    # $ca_keys and use that instead
    #
    # I prefer choice 2:
    set parsed_ca_keys $parsed_ca_csr
    dict set parsed_ca_keys d [dict get $ca_keys d]

    # create the certificate (an actual x509) for the CA, signed by the CA itself
    # (see the tcllib pki man page for the explanation of the first "1" and the
    # internal extensions setting that results from it)
    set ca_cert [::pki::x509::create_cert \
    $parsed_ca_csr \
    $parsed_ca_keys\
    $serial_number \
    $not_before \
    $not_after \
    1 \
    $extensions \
    1
    ]
    # save the CA cert to a file for later analysis with OpenSSL verify command
    set the_file [open "example_root_ca.crt" w]
    puts $the_file $ca_cert
    close $the_file


    # CREATE A USER CERT AND SIGN IT WITH THE ROOT CA CERT
    # generate user Private Key
    #set user_keys [::pki::rsa::generate 2048]
    set user_keys [::pki::rsa::generate 512] ; # for faster testing

    # save the user keys to a file
    set the_file [open "server.example.com.keys" w]
    puts $the_file $user_keys
    close $the_file

    # define the user cert attributes
    set user_subject [list C "US" O "Example Org" CN "server.example.com"]
    set user_serial_number [clock seconds] ; # acts as a simple unique id
    set user_not_before [clock seconds]
    set user_not_after [clock add [clock seconds] 1 year]

    # create a Certificate Signing Request for the user, and parse it
    set user_csr [pki::pkcs::create_csr $user_keys $user_subject 1]
    set parsed_user_csr [::pki::pkcs::parse_csr $user_csr]

    # create the user certificate (an actual x509) signed by the CA
    # (note the "0" here instead of "1"-- see the tcllib pki man page)
    set user_cert [::pki::x509::create_cert \
    $parsed_user_csr \
    $parsed_ca_keys\
    $user_serial_number \
    $user_not_before \
    $user_not_after \
    0 \
    [list] \
    1
    ]

    # save the user cert to a file for later analysis with OpenSSL verify command set the_file [open "server.example.com.crt" w]
    puts $the_file $user_cert


    # check that the user cert validates against the CA
    set trusted_cert [pki::x509::parse_cert $ca_cert]
    set supplicant_cert [pki::x509::parse_cert $user_cert]
    set status [pki::x509::verify_cert $supplicant_cert [list $trusted_cert]]
    puts "pki::x509::verify_cert return value (true = pass, false = fail): $status" exit

    # compare the result to the command line output of:
    # openssl verify -CAfile example_root_ca.crt server.example.com.crt
    # if different, WHY?

    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Jerry O.@jerry@thselucreh.com to comp.lang.tcl on Fri Mar 20 03:19:53 2026
    From Newsgroup: comp.lang.tcl

    On Tue, 17 Mar 2026 02:22:23 -0000 (UTC), Jerry O. wrote:

    Hello everyone,

    I have a question for those who have written code using pki: have any of you had pki::x509::verify_cert suddenly begin failing certs, even when the trust has indeed been established, the certs are current, and-- most importantly-- an independent check using "openssl verify -CAfile..." (or some other similar utility) shows that the cert _is_ valid?

    Hello again,

    I've been studying the pki.tcl module source code and I believe I may have found the
    reason for why verify_cert is not passing an otherwise trusted cert chain: a missing case
    in a switch statement in the proc ::pki::x509::validate_cert. I believe this to be so
    based on the following reasoning (see the code excerpts below):

    1) ::pki::x509::verify_cert invokes ::pki::x509::validate_cert on the supplicant cert
    first, and then on the trusted cert(s).

    2) The supplicant cert passes ::pki::x509::validate_cert.

    3) The trusted cert fails ::pki::x509::validate_cert.

    4) Therefore, ::pki::x509::verify_cert fails.

    5) The CA cert (the trusted cert) generated by ::pki::x509::create_cert includes the
    extension "id-ce-basicConstraints". The ::pki::x509::validate_cert code doesn't
    recognize this extension-- it only recognizes "basicConstraints"-- and so returns
    false as the default response.

    6) It looks like "id-ce-basicConstraints" and "basicConstraints" are otherwise treated
    equivalently elsewhere in pki.tcl. See the code for ::pki::x509::create_cert, and
    the code (and comments) for ::pki::x509::_parse_extensions as comparison.

    7) It appears to me that the switch statement in ::pki::x509::validate_cert is simply
    missing the "id-ce-basicConstraints" case that would fix the issue.

    I humbly ask that those readers who are, or who are in contact with, code maintainers
    please review what I have submitted here, and if it is confirmed, that a correction to
    pki.tcl be proposed.

    With Gratitude,
    -Jerry O.
    Please first reverse the letters in my address domain when replying direct.

    modules/pki/pki.tcl as retrieved on March 19th, 2026:


    In the proc ::pki::x509::validate_cert (starting at line 2222):

    2294 # Check for extensions and process them. However v1 certs have no extensions
    2295 if {$cert_arr(version) == 0} {
    2296 # Do not permit V1 certificates for signing.
    2297 set CA 0
    2298 } else {
    2299 ## Critical extensions must be understood, non-critical extensions may be ignored if not understood
    2300 set CA 0
    2301 set CAdepth -1
    2302 foreach {ext_id ext_val} $cert_arr(extensions) {
    2303 set critical [lindex $ext_val 0]
    2304
    2305 switch -- $ext_id {
    2306 ------> basicConstraints {
    2307 set CA [lindex $ext_val 1 0]
    2308 set CAdepth [lindex $ext_val 1 1]
    2309 }
    2310 default {
    2311 ### If this extensions is critical and not understood, we must reject it
    2312 if {$critical} {
    2313 return false
    2314 }
    2315 }
    2316 }
    2317 }
    2318 }

    Shouldn't "id-ce-basicConstraints" be included, as follows:

    switch -- $ext_id {
    ---ADD?---> id-ce-basicConstraints -
    basicConstraints {
    set CA [lindex $ext_val 1 0]
    set CAdepth [lindex $ext_val 1 1]
    }

    ...etc...

    Compare to the code of proc ::pki::x509::create_cert (starting at line 2584):

    2686 ## Insert extensions
    2687 if {[array get extensions] ne {}} {
    2688 set extensionslist [list]
    2689
    2690 foreach {extension extvalue} [array get extensions] {
    2691 set critical 0
    2692
    2693 switch -- $extension {
    2694 ------> id-ce-basicConstraints -
    2695 basicConstraints {
    2696 set critical [lindex $extvalue 0]
    2697 set allowCA [lindex $extvalue 1]
    2698 set caDepth [lindex $extvalue 2]
    2699
    2700 if {$caDepth < 0} {
    2701 set extvalue [::asn::asnSequence [::asn::asnBoolean $allowCA]]
    2702 } else {
    2703 set extvalue [::asn::asnSequence [
    2704 ::asn::asnBoolean $allowCA
    2705 ] [
    2706 ::asn::asnInteger $caDepth
    2707 ]]
    2708 }
    2709 }


    Look at proc ::pki::x509::_parse_extensions (starting at line 2010), lines 2089 thru 2098,
    where accommodation was made for both forms "basicConstraints" and "id-ce-basicConstraints"
    --
    Please first reverse the letters in my address domain when replying direct.
    --- Synchronet 3.21d-Linux NewsLink 1.2