Bug 64831 - Create a certificate alias listing/sampler
Summary: Create a certificate alias listing/sampler
Status: NEEDINFO
Alias: None
Product: JMeter
Classification: Unclassified
Component: Main (show other bugs)
Version: 5.2
Hardware: PC All
: P2 enhancement (vote)
Target Milestone: JMETER_5.5
Assignee: JMeter issues mailing list
URL:
Keywords: FixedInTrunk
Depends on:
Blocks:
 
Reported: 2020-10-20 10:08 UTC by Michael Osipov
Modified: 2021-04-10 12:42 UTC (History)
0 users



Attachments
Log debug information about loaded certificates (4.58 KB, patch)
2021-01-02 13:08 UTC, Felix Schumacher
Details | Diff
Log debug information about loaded certificates (6.77 KB, patch)
2021-03-06 10:49 UTC, Felix Schumacher
Details | Diff
Log debug information about loaded certificates (7.23 KB, patch)
2021-03-22 16:29 UTC, Felix Schumacher
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Michael Osipov 2020-10-20 10:08:31 UTC
While trying to use Windows-MY with JMeter one does not know the format of the aliases generated unless you read the C/Java code of the MSCAPI module in the JDK. Yes, I have looked into the code which format is used and an index is appended if common names collide.

At the end you have to write a simple Java program:
> KeyStore ks = KeyStore.getInstance("Windows-MY");
> ks.load(null, null);
> Enumeration<String> aliases = ks.aliases();
> 
> int i = 1;
> while(aliases.hasMoreElements()) {
> 	String alias = aliases.nextElement();
> 	System.out.printf("#%03d: %s%n", i++, alias);
> 	X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
> 	System.out.println(cert.getNotAfter().toInstant());
> }

Output:
> #001: *.siemens.com
> 2022-11-23T16:34:58Z
> #002: Osipov Michael (1)
> 2021-10-16T08:01:04Z
> #003: Osipov Michael (2)
> 2021-10-16T08:17:34Z
> #004: Osipov Michael
> 2014-07-05T07:57:48Z
> #005: *.ad001.siemens.net
> 2022-09-16T23:01:13Z
> #006: DO_NOT_TRUST_FiddlerRoot-CE
> 2022-09-16T23:00:11Z
> #007: Osipov Michael (3)
> 2017-06-30T08:44:39Z
> #008: Osipov Michael (4)
> 2019-10-19T08:52:59Z

Or use KeyStore Explorer to open Windows-MY. Since a smartcard stores all old certificates you cannot really guess the index. At the end it was "Osipov Michael (1)"

Note that I always have two valid vertificates: Identify verification and email encryption. Chosing the wrong gives an exception.

The enhancement request is to either create a new sampler of extend the debug sampler will can print out aliases along with basic certificate information, especially with extended key usages to identify the proper certificate. The required OID for Client Cert Auth is 1.3.6.1.5.5.7.3.2.
Here is a selection of OIDs I do process:
> EXTENDED_KEY_USAGES.put("1.3.6.1.5.5.7.3.9",
> 		"Signing Online Certificate Status Protocol (OCSP) responses");
> EXTENDED_KEY_USAGES.put("1.3.6.1.4.1.311.20.2.2", "Smartcard logon to Microsoft Windows");
> EXTENDED_KEY_USAGES.put("1.3.6.1.4.1.311.10.3.4",
> 		"Can use encrypted file systems (EFS) (EFS_CRYPTO)");
> EXTENDED_KEY_USAGES.put("1.3.6.1.4.1.311.10.3.4.1",
> 		"Can use encrypted file systems (EFS) (EFS_RECOVERY)");
> EXTENDED_KEY_USAGES.put("1.3.6.1.5.5.7.3.1",
> 		"Transport Layer Security (TLS) World Wide Web (WWW) server authentication");
> EXTENDED_KEY_USAGES.put("1.3.6.1.5.5.7.3.2",
> 		"Transport Layer Security (TLS) World Wide Web (WWW) client authentication");
> EXTENDED_KEY_USAGES.put("1.3.6.1.5.5.7.3.3", "Signing of downloadable executable code");
> EXTENDED_KEY_USAGES.put("1.3.6.1.5.5.7.3.4", "Email protection");
> EXTENDED_KEY_USAGES.put("1.3.6.1.5.5.7.3.8", "Time stamping");

JMeter works with Windows-MY by setting the keyStoreType=Windows-MY at startup. My PIN dialog pops up and Apache HTTPd accepts my cert from my smartcard.
Comment 1 Felix Schumacher 2021-01-02 13:08:33 UTC
Created attachment 37679 [details]
Log debug information about loaded certificates

Would this debug logging be enough for your use case?
Comment 2 Michael Osipov 2021-01-03 01:28:38 UTC
I will check this when I get back to work next week.
Comment 3 Michael Osipov 2021-01-08 11:18:01 UTC
Just tried, this doesn't scale at all, unfortunately. To have this printed you need some sampler accessing it and since one has to be in debug mode HttpClient logs so much that those log statements are long gone from the pane buffer in JMeter. It would require additional effort to configure some fake sampler or configure a log file.

I understand that this is the easiest way to implement, but from a user's POV not useful.

I would also rearrage the log statements for consistency too:

1:
>  log.debug("{}: {}", i++, alias);

This give virtually no context about the printed information.

2:
Next:
Subject DN: ...
Issuer DN: ...
Not valid before: ...
Not valid after: ...

2:
SAN can be empty, probe for it. (Javadoc: an immutable Collection of subject alternative names (or null))

3: Printing SAN as-is isn't really helpful. The ASN.1 defition is:
 GeneralName ::= CHOICE {
      otherName                       [0]     OtherName,
      rfc822Name                      [1]     IA5String,
      dNSName                         [2]     IA5String,
      x400Address                     [3]     ORAddress,
      directoryName                   [4]     Name,
      ediPartyName                    [5]     EDIPartyName,
      uniformResourceIdentifier       [6]     IA5String,
      iPAddress                       [7]     OCTET STRING,
      registeredID                    [8]     OBJECT IDENTIFIER}

So a switch on the first member of the nested list to a string literal would be helpful. In case of otherName it would be very helpful to dumb the ASN.1 blob with Apache Kerby ASN.1. I have used it myself, very nice. Many enterprise certs like mine will contain MS UserPrincipalName (1.3.6.1.4.1.311.20.2.3) which Java does not know.

Maybe the debug sampler could be extended for this?
Comment 4 Felix Schumacher 2021-01-08 11:23:19 UTC
The debug sampler has currently no knowledge of the keystore and is therefore not my preferred sampler to extend :) but ... we could store the information that is currently logged into a variable, which then would be reported by the debug sampler. What do you think?

The information about the SAN can (and should) be enhanced. Thanks for the OIDs.

Could you explain, what you mean by comment 1)?
Comment 5 Michael Osipov 2021-01-08 12:21:45 UTC
(In reply to Felix Schumacher from comment #4)
> The debug sampler has currently no knowledge of the keystore and is
> therefore not my preferred sampler to extend :) but ... we could store the
> information that is currently logged into a variable, which then would be
> reported by the debug sampler. What do you think?

OK, let's leave the debug sampler stupid. Hmm, while storing into a variable seems reasonable, it still requires to invoke a real sampler to have this kind of information. Sometimes I do invoke debug sampler to see JMeter internal information w/o any other sampler involved. Do you see a way to store this kind of information when the keystore is first loaded by JMeter? This would of course require the keystore to be static, means JMeter won't notice changes until re-reads it or restarts, but that's would be acceptable.

> The information about the SAN can (and should) be enhanced. Thanks for the
> OIDs.
> 
> Could you explain, what you mean by comment 1)?

I guess you mean this one:
>  log.debug("{}: {}", i++, alias);

It will merly print a number and an alias. More context means:
log.debug("certitifate at index {} with alias {}", i++, alias);

Subsequent log messages could be indented to establish visual context.
Comment 6 Felix Schumacher 2021-03-06 10:49:08 UTC
Created attachment 37756 [details]
Log debug information about loaded certificates

Add handling of SAN entries.

Still not sure, where to put the information other then in the logs.
Comment 7 Michael Osipov 2021-03-06 13:19:09 UTC
Let me run this again next at work...
Comment 8 Michael Osipov 2021-03-20 14:04:01 UTC
+    private static final List<String> SAN_OIDS = Arrays.asList("otherName", "rfc822Name", "dNSName", "x400Address",
+            "directoryName", "ediPartyName", "uniformResourceIdentifier", "iPAddress", "registeredID");

Those aren't OID, but GeneralNames.

+            log.debug("Subject DN: {}", cert.getSubjectDN());
+            log.debug("issuer DN: {}", cert.getIssuerDN());
+            log.debug("not valid before: {}", cert.getNotBefore().toInstant());
+            log.debug("not valid after: {}", cert.getNotAfter().toInstant());

* Please start each log message consistently with uppercase
* Use getSubjectX500Principal() and getIssuerX500Principal()

+            if (oidData instanceof Integer) {
+                Integer oid = (Integer) oidData;
+                String description = sanOidToName(oid);
+                String valueString = sanDataToString(data);
+                decodedEntries.add(Pair.of(description, valueString));
+            }

* According to the Javadoc it is always an Integer. One can blindly cast, can't one?
* OIDs cannot be Integers. This is logically wrong.

+                        log.debug("Extended Key Usage {} ({})", EXTENDED_KEY_USAGES.getOrDefault(keyUsage, keyUsage), keyUsage);

Add a colon after 'Usage'. This makes it easier to read the output.

I just have found another EKU OID on my smartcard: Any Purpose (2.5.29.37.0).

Except for the comments above, I have tried this with a soft store as well as my smartcard. Looks good to me for now.
Comment 9 Felix Schumacher 2021-03-22 16:29:57 UTC
Created attachment 37782 [details]
Log debug information about loaded certificates

Change names of variables and capitalization of Log messages as suggested by Michael.

While testing this feature, I came to the conclusion, that something like a Cert-Debug-Sampler would be a really good idea. Maybe we could add a getCertsInfo method to the JMeterKeyStore, that gets initialized on first loading of the keystore?
Comment 10 Michael Osipov 2021-03-26 08:31:21 UTC
Testing..

> log.debug("Certificate at index {} with alias {}:", i++, alias);
Seems to contain a redudant colon at the end.

> log.debug("Extended Key Usage: {} ({})", EXTENDED_KEY_USAGES.getOrDefault(keyUsage, keyUsage),
and
> log.debug("Can't get extendedKeyUsage for alias {}", alias, e);

Can you make them consistent? Both either three words or camelCase? Since used an acronym for SAN, I would do that here too.
Comment 11 Michael Osipov 2021-03-26 18:20:58 UTC
(In reply to Felix Schumacher from comment #9)
> Created attachment 37782 [details]
> Log debug information about loaded certificates
> 
> Change names of variables and capitalization of Log messages as suggested by
> Michael.

Changes work for me except for the last remaining comments.

> While testing this feature, I came to the conclusion, that something like a
> Cert-Debug-Sampler would be a really good idea. Maybe we could add a
> getCertsInfo method to the JMeterKeyStore, that gets initialized on first
> loading of the keystore?

This sounds like a plan, but I see that the "SSL Manager" needs to be addressed first. It is not really possible to select the KeyStore type. For instance, I do use Windows-MY with my smartcard but have to launch JMeter via terminal and a system property which is ugly.
SSL Manager should either be a configuration element at top level or least a dialog which provides three fields:
(1) KeyStore type
(2) optional path (file picker)
(3) optional password (input box)
instead of a mere file picker

Windows-MY does not require (2) and (3).

The reason why both JKS and PKCS 12 work is this line:
> keystore.type.compat=true
in/usr/local/openjdk8/jre/lib/security/java.security
Comment 12 Felix Schumacher 2021-04-10 12:42:40 UTC
The logging of those entries has been integrated.

For the other parts of this (adding a real sampler instead of logging; make SSL Manager more configurable), I think we should open a new ticket.

commit 75f5396f80b3a6f6c8ca9a87dd25734b0e2df0f8
Author: Felix Schumacher <felix.schumacher@internetallee.de>
AuthorDate: Sat Jan 2 14:07:00 2021 +0100

    Log debug information about loaded certs from keystore
    
    Bugzilla Id: 64831
---
 .../jmeter/util/keystore/JmeterKeyStore.java       | 110 +++++++++++++++++++++
 xdocs/changes.xml                                  |   1 +
 2 files changed, 111 insertions(+)

diff --git a/src/core/src/main/java/org/apache/jmeter/util/keystore/JmeterKeyStore.java b/src/core/src/main/java/org/apache/jmeter/util/keystore/JmeterKeyStore.java