P12 certificates with "Basic constraints" extension and "PathLen" constraint are ignored #122054
Labels
bug
Fixes for quality problems that affect the customer experience
impact:needs-assessment
Product and/or Engineering needs to evaluate the impact of the change.
loe:small
Small Level of Effort
Team:Security
Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more!
Kibana version: 7.9? - 7.16.2
Describe the bug:
Kibana can be configured to read/parse certificates in P12 files (configured via
server.ssl.keystore.path
,server.ssl.truststore.path
,elasticsearch.ssl.keystore.path
, orelasticesearch.ssl.truststore.path
) to facilitate Browser-to-Kibana or Kibana-to-Elasticsearch TLS communication.This bug causes certificates that contain the "Basic constraints" X509 extension (OID 2.5.29.19) and a defined "PathLen" constraint to be completely and silently ignored. The symptoms can vary, but this generally causes some sort of TLS communication to fail because Kibana cannot establish trust without the missing certificate(s). Depending on the configuration of the other network entities, errors can include messages like "unable to verify the first certificate" or "self signed certificate in certificate chain".
We have confirmed internal reports of customers upgrading from <= 7.8 to >= 7.14 that experience this bug, where a P12 key store or trust store was working before, suddenly doesn't work after the upgrade.
Steps to reproduce:
Not so straightforward, you need:
console.log
statements in Kibana where the certificate(s) are getting consumed to verify that the expected certificate(s) are missing.I've provided such a P12 file here:
localhost.p12.zip
Click to see certificate contents
You can start using the following config:
Kibana should crash with the following error:
Expected behavior:
Kibana should be able to read all contents of P12 files.
Any additional context:
I tracked down the root cause to this. Given the sample/problematic P12 file,
node-forge
returned the certificate from the P12 just fine in unit tests, but when actually starting Kibana,node-forge
did not find a certificate.When Kibana parses a P12 file, it uses
node-forge
to read the file in base64 format, then converts that to DER format, then converts that to ASN.1 format, then decrypts/reads the contents of each contained SafeBag into anode-forge
internal Certificate structure, and finally converts them to PEM format for the consumer.Through debugging, I discovered that if the aforementioned extension was used, an error condition would be encountered in
node-forge
while converting the SafeBags from ASN.1 to the internal Certificate structure: https://github.com/digitalbazaar/forge/blob/c0bb359afca73bb0f3ba6feb3f93bbcb9166af2e/lib/pkcs12.js#L700-L706The error is "bytes.getSignedInt is not a function", which originates in the
derToInteger
function here: https://github.com/digitalbazaar/forge/blob/c0bb359afca73bb0f3ba6feb3f93bbcb9166af2e/lib/asn1.js#L1110Additional debugging determined that the prototype of the
bytes
object value is different when this error scenario is encountered. In unit tests this is object has a prototype ofByteStringBuffer
, but when running the Kibana server this is object instead has a prototype ofDataBuffer
that has different properties:Click to expand and see properties
__defineGetter__
__defineGetter__
__defineSetter__
__defineSetter__
__lookupGetter__
__lookupGetter__
__lookupSetter__
__lookupSetter__
__proto__
__proto__
accommodate
_constructedStringLength
_optimizeConstructedString
at
at
available
buffer
bytes
bytes
clear
clear
compact
compact
constructor
constructor
copy
copy
data
data
equals
fillWithByte
fillWithByte
getBuffer
getByte
getByte
getBytes
getBytes
getInt
getInt
getInt16
getInt16
getInt16Le
getInt16Le
getInt24
getInt24
getInt24Le
getInt24Le
getInt32
getInt32
getInt32Le
getInt32Le
getNative
getSignedInt
growSize
hasOwnProperty
hasOwnProperty
isEmpty
isEmpty
isPrototypeOf
isPrototypeOf
last
last
length
length
native
propertyIsEnumerable
propertyIsEnumerable
putBuffer
putBuffer
putByte
putByte
putBytes
putBytes
putInt
putInt
putInt16
putInt16
putInt16Le
putInt16Le
putInt24
putInt24
putInt24Le
putInt24Le
putInt32
putInt32
putInt32Le
putInt32Le
putNative
putSignedInt
putSignedInt
putString
putString
read
read
setAt
setAt
toHex
toHex
toLocaleString
toLocaleString
toString
toString
truncate
truncate
valueOf
valueOf
write
That led me to discover that the version of
node-jose
we are using (1.1.0) actually replaces thenode-forge
implementation ofByteStringBuffer
(viaByteBuffer
) with its ownDataBuffer
prototype: https://github.com/cisco/node-jose/blame/4014f5c2ceb7bae8b516749f6a329132a115bc24/lib/util/databuffer.js#L474This
DataBuffer
is not a 1:1 replacement forByteStringBuffer
, and that is what is causing this error scenario. Only certain certificates with the "Basic constraints" extension trigger this code path, and when that happens,node-forge
swallows the error and doesn't return the contents of that certificate.A few things to note:
node-jose
that was fixed in the 1.1.4 release but was not mentioned in the changelog.node-jose
as a transitive dependency of@elastic/request-crypto
.node-jose
changes the behavior of thenode-forge
package before ElasticsearchConfig parses the P12 file. Why are we only starting to see this bug in 7.9+? This isn't a new transitive dependency, we've had it for years. My guess is that one the innumerable changes in our build system caused thenode-jose
import to happen at a different (earlier) time during the startup process. It is probably a race condition of some sort.That calls the
derToInteger
function, which throws the aforementioned error.The text was updated successfully, but these errors were encountered: