Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable RSA-PSS signatures in pkcs11-tool #1146

Merged
merged 20 commits into from
Sep 21, 2017

Conversation

Jakuje
Copy link
Member

@Jakuje Jakuje commented Sep 6, 2017

The PSS signatures in PKCS#11 require parameters to mechanism info passed to SignInit(). They need to be adjustable for different hash algorithms (in generic RSA-PSS) and different MGFs. The definitions in pkcs11.h header file come from current PKCS#11 specification. The change also affects pkcs11-spy module, which now lists these parameters, if they are present.

Comments and naming improvements welcomed. This adds support for RSA-PSS into pkcs11-tool to use with other PKCS#11 modules. It does not add a support in the drivers in OpenSC, but it is also something I would like to investigate too.

There are also few more unrelated commits including typos (manual page), adding a few more mechanisms from PKCS#11 specification (SHA224).

Checklist
  • Documentation is added or updated
  • Tested with the following card: PKCS#11 module from yubihsm
    • tested PKCS#11
    • tested Windows Minidriver
    • tested macOS Tokend

@dengert
Copy link
Member

dengert commented Sep 6, 2017

Cool. That addresses one of the problems. (I have only looked at you description and have no way to test it.)

The other part of @mouse07410's problem is using OpenSSL utilities with libp11/engine for devices that do not support CKM_X_509. Unlike other padding methods where the padding can be striped off and the data sent to the card via CKM_RSA_PKCS1, OpenSSL PSS padding can not be stripped as there is not enough information passed to determine the PSS parameters. Libp11 would need to call PKCS#11.

@mouse07410
Copy link
Contributor

mouse07410 commented Sep 6, 2017

Some tokens (I have first-hand experience with YubiHSM2 device in beta-testing) can do RSA-PSS on-board - they often (always?) do not allow raw RSA (aka RSA-X-509). Other tokens (like Yubikey NEO, Yubikey 4, US PIV cards, US DoD CAC) support only raw RSA (aka RSA-X-509) and require that the RSA-PSS padding is done in software prior to requesting token to perform the private key operation. IMHO both of these token classes should be supported.

Also, pkcs11-tool should have a set of defaults to send to the token (or to utilize internally) when all the parameters are not provided by the user. E.g., if invoked with -m SHA384-RSA-PKCS-PSS, it should figure to use SHA384 hash, SHA384-based MGF, salt length equal to the digest size, etc. Please make those changes.

@@ -227,6 +233,9 @@ static const char *option_help[] = {
"Derive a secret key using another key and some data",
"Derive ECDHpass DER encoded pubkey for compatibility with some PKCS#11 implementations",
"Specify mechanism (use -M for a list of supported mechanisms)",
"Specify hash algorithm used with generic RSA-PSS signature",
"Specify MGF (Message Generation Function) used for RSA-PSS signatures (possible values are MGF1-SHA1 to MGF1-SHA512)",
"Specify how many bytes should be used for salt in RSA-PSS signatures (default 0)",
Copy link
Contributor

@mouse07410 mouse07410 Sep 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think default here should be the same as OpenSSL. And OpenSSL uses salt length equal to the digest size (rsa_pss_saltlen:-1) as its RSA-PSS default.

@mouse07410
Copy link
Contributor

mouse07410 commented Sep 7, 2017

In the meanwhile, I've extended your PR with saltlen default to digest size (see https://github.com/mouse07410/OpenSC.git branch 1146), and confirm that your PR works OK:

$ ~/bin/yhsm2-rsa-pss-sign-demo2
Generating ephemeral file /tmp/derive.28727.text to test RSA-PSS signature...

openssl rand -hex -out /tmp/derive.28727.text 5120

Signing file /tmp/derive.28727.text...
pkcs11-tool --module /usr/local/lib/yubihsm_pkcs11.dylib --login --sign -m SHA384-RSA-PKCS-PSS -d 0301 -i /tmp/derive.28727.text -o /tmp/derive.28727.text.sig
Using slot 0 with a present token (0x0)
Logging in to "YubiHSM".
Please enter User PIN: 
Using signature algorithm SHA384-RSA-PKCS-PSS
PSS parameters: hashAlg=SHA384-RSA-PKCS-PSS, mgf=MGF1-SHA384, salt=48 B
Signature for /tmp/derive.28727.text is stored in /tmp/derive.28727.text.sig

Verifying signature:
openssl dgst -engine pkcs11 -keyform engine -verify "pkcs11:token=YubiHSM;id=%03%01;type=public" -sha384 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -signature /tmp/derive.28727.text.sig  /tmp/derive.28727.text
engine "pkcs11" set.
Enter PKCS#11 token PIN for YubiHSM:
Verified OK

$ 

Are you considering making a similar PR for RSA-OAEP encryption?

Update
Not specifying OpenSSL saltlen parameter proves the compatibility of the defaults I set:

$ pkcs11-tool --module ${YUBIHSM_PKCS11_MODULE} --login --sign -m SHA384-RSA-PKCS-PSS --id 0301 -i ~/src/t256.dat -o ~/t.sig
Using slot 0 with a present token (0x0)
Logging in to "YubiHSM".
Please enter User PIN: 
Using signature algorithm SHA384-RSA-PKCS-PSS
PSS parameters: hashAlg=SHA384, mgf=MGF1-SHA384, salt=48 B
$ openssl dgst -engine pkcs11 -keyform engine -sha384 -verify "pkcs11:token=YubiHSM;id=%03%01;type=public" -sigopt rsa_padding_mode:pss -signature  ~/t.sig ~/src/t256.dat
engine "pkcs11" set.
Enter PKCS#11 token PIN for YubiHSM:
Verified OK
$ 

@dengert
Copy link
Member

dengert commented Sep 7, 2017

In the above example it has:
PSS parameters: hashAlg=SHA384-RSA-PKCS-PSS, mgf=MGF1-SHA384, salt=48 B

Since this is listing the PSS parameters, I would assume the hashAlg should be SHA384
Not SHA384-RSA-PKCS-PSS as PKCS#11 V2.40 says:

"hashAlg: hash algorithm used in the PSS encoding; if the signature
mechanism does not include message hashing, then this value must
be the mechanism used by the application to generate the message
hash
; if the signature mechanism includes hashing, then this value
must match the hash algorithm indicated by the signature
mechanism"

hashAlg is only the HASH mechanism, not the signature mechanism.

@mouse07410
Copy link
Contributor

@dengert thank you - you're right. Fixed:

$ src/tools/pkcs11-tool --module ${YUBIHSM_PKCS11_MODULE} --login --sign -m SHA384-RSA-PKCS-PSS --id 0301 -i ~/src/t256.dat -o ~/t.sig
Using slot 0 with a present token (0x0)
Logging in to "YubiHSM".
Please enter User PIN: 
Using signature algorithm SHA384-RSA-PKCS-PSS
PSS parameters: hashAlg=SHA384, mgf=MGF1-SHA384, salt=48 B
$ openssl dgst -engine pkcs11 -keyform engine -sha384 -verify "pkcs11:token=YubiHSM;id=%03%01;type=public" -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -signature  ~/t.sig ~/src/t256.dat
engine "pkcs11" set.
Enter PKCS#11 token PIN for YubiHSM:
Verified OK
$ 

@Jakuje please take a look at my branch. I think addresses the issues I brought up, and fixes the problem @dengert pointed out.

@mouse07410
Copy link
Contributor

It does not add a support in the drivers in OpenSC, but it is also something I would like to investigate too.

I'd like to see that. Such a code would issue calls to libyubihsm.dylib, as shown in the YubiHSM SDK examples.

What's currently missing is:

  • RSA-PSS (in HSM device driver)
  • RSA-OAEP (in pkcs11-tool and HSM device driver)
  • ECDH (pkcs11-tool and HSM device driver) - complicated by the fact that YubiHSM does not seem to advertise any ECDH mechanism
  • ECDSA-SHAxxx (in HSM device driver)

@dengert
Copy link
Member

dengert commented Sep 8, 2017

I would also like to see the terms salt and opt_salt be replaced with something that denotes it is not the value of a salt, but the length of a salt that will be randomly generated. slen would be a good choice as PKCS#11 v2.40 says:

"sLen length, in bytes, of the salt value used in the PSS encoding; typical
values are the length of the message hash and zero"

The convention used by OpenSL of -1 to use the length of the message hash could be the default.
But static unsigned long opt_salt = 0; is unsigned. So something needs to be changed.

So if user does not specify anything, slen = hlen. would be used. User can provide 0 or any other length if they want to specify a specific length or no salt at all.

RFC 8017 Section 9.1 Note 4 says:
"4. Typical salt lengths in octets are hLen (the length of the output
of the hash function Hash) and 0."

@Jakuje
Copy link
Member Author

Jakuje commented Sep 8, 2017

Thank you for ideas and comments. I will try to address them during the next week. I am traveling at this moment.

@mouse07410
Copy link
Contributor

@dengert, I took care of the issues you pointed out in my fork (branch 1146). Hopefully @Jakuje can pick my changes up.

@Jakuje
Copy link
Member Author

Jakuje commented Sep 11, 2017

Thank you for the comments and especially the fixes and changes made by @mouse07410 in the meantime. I cherry-picked them into this branch, but I cleaned up the spaces into tabs to be consistent with the rest of the code. I didn't squash the changes yet to be clear what was fixed where.
Unfortunately I don't have new-enough OpenSSL which would support PSS verification (so again, thank you for verification it does what it is supposed to do).

It does not add a support in the drivers in OpenSC, but it is also something I would like to investigate too.

I'd like to see that. Such a code would issue calls to libyubihsm.dylib, as shown in the YubiHSM SDK examples.

I don't think so. Using PSS in drivers (or rather libopensc directly, for cards with RSA-X-509 support) would need to do the "padding" manually and then issue raw RSA-X-509 mechanism to the non-PSS capable card. I will certainly have a look into that in coming weeks (hopefully).

What's currently missing is:
RSA-OAEP (in pkcs11-tool and HSM device driver)

That should not be so hard once we have PSS around, at least for pkcs11-tool. And YubiHSM2 should have support for these too. But unlike the PSS, I don't have a real use case for that at this point.

break;
default:
sLen = 0;
break;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we should silently fall back to 0 length salt in case we got something unknown. It can bite us later (SHA224, SHA3 ?). We should probably exit here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. But I'm not sure how we'd do a clean exit here without overly complicating things on the caller side.

As for SHA-3 and SHA224 - why don't we add them right now and be done with it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the way around pkcs11-tool, the we call util_fatal(), which calls exit. It is not nice, but it does its job for these unexpected situations. I

We can add CKM_SHA224 straight away, but there is no CKM_ for SHA3 yet in latest PKCS#11 standard. And when it will be there, we will forget about this switch.

I will add a commits addressing this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks. Makes sense to me. For an executable like pkcs11-tool using util_fatal() should be OK.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Darn... We must be using different PKCS#11 include files?!

. . . . .
  CC       cardos-tool.o
pkcs11-tool.c:1637:8: error: use of undeclared identifier 'CKM_SHA224'
        case  CKM_SHA224:
              ^
1 error generated.
make[3]: *** [pkcs11-tool.o] Error 1
. . . . .

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. I just did not check it compiles. I checked only the PKCS#11 specification, which defines them:

http:https://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/cs01/pkcs11-curr-v2.40-cs01.html#_Toc399398977

I added them in b051d3b from the above source so it should build fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, thanks. Now everything compiles OK. ;-)

@mouse07410
Copy link
Contributor

Thank you for the comments and especially the fixes and changes made by @mouse07410 in the meantime. I cherry-picked them into this branch, but I cleaned up the spaces into tabs to be consistent with the rest of the code.

Thank you! And I appreciate you taking care of spaces-vs-tabs, as I'm not keen on setting Emacs configuration for the appropriate indentation styles that different project have :-(.

It does not add a support in the drivers in OpenSC, but it is also something I would like to investigate too.

I'd like to see that. Such a code would issue calls to libyubihsm.dylib, as shown in the YubiHSM SDK examples.

I don't think so. Using PSS in drivers (or rather libopensc directly, for cards with RSA-X-509 support) would need to do the "padding" manually and then issue raw RSA-X-509 mechanism to the non-PSS capable card. I will certainly have a look into that in coming weeks (hopefully).

I'm not sure what you're disagreeing with here. As far as I can see, there are two classes of devices:
a. those like YubiHSM2 that support -PSS and -OAEP on the device itself; and
b. those like Yubikey that support only raw RSA on device and require software to provide PSS or OAEP before calling on the device.

As long as we're limiting our discussion to the pkcs11-tool itself, we seem to concur.

Once the YubiHSM2 driver gets involved - it wouldn't be feasible (IMHO) to call yubihsm_pkcs11.dylib from it. To me that says that the driver would have to talk to the closest HSM2 interface library, which is libyubihsm.dylib.

What's currently missing is: RSA-OAEP (in pkcs11-tool and HSM device driver)

That should not be so hard once we have PSS around, at least for pkcs11-tool.

That was my thinking too.

And YubiHSM2 should have support for these too.

AFAIK, it does.

But unlike the PSS, I don't have a real use case for that at this point.

:-) Come on, doesn't the eager community count? :-)

@mouse07410
Copy link
Contributor

@frankmorgner could I ask to merge this PR rather sooner than later please? Thanks!

@frankmorgner
Copy link
Member

@Jakuje
Copy link
Member Author

Jakuje commented Sep 13, 2017

Sorry, I always have a hard time to find the errors in the VS builds. That was obviously related to the noreturn, which would require some other keywords in VS, but it is not related to this change. I dropped now so the builds should pass.

@Jakuje
Copy link
Member Author

Jakuje commented Sep 13, 2017

OK, I put that back because the gcc complains about non-returning functions after the fatal calls. I took the opportunity to implement it in the way it should suit most of the todays compilers (I hope it will go through the VS too).

@dengert
Copy link
Member

dengert commented Sep 14, 2017

Looks like an engine problem.
See my comments about OpenSSL engine and OAEP and pkcs11_pkey_rsa_encrypt that needs to be written.

OpenSC/libp11#176 (comment)

@mouse07410
Copy link
Contributor

@Jakuje I'm having a strange problem. The fault is probably with the yubihsm_pkcs11.dylib, but it's worth checking:

$ alias yhsm2-tool='pkcs11-tool --module /usr/local/lib/yubihsm_pkcs11.dylib'
$ yhsm2-tool -s -m RSA-PKCS-PSS --hash-algorithm SHA256 --mgf MGF1-SHA256 --salt -2 --id 0301 -i t6400.dat -o t6400.dat.sig5.pss
Using slot 0 with a present token (0x0)
Logging in to "YubiHSM".
Please enter User PIN: 
Using signature algorithm RSA-PKCS-PSS
PSS parameters: hashAlg=SHA256, mgf=MGF1-SHA256, salt=350 B
error: PKCS11 function C_SignFinal failed: rv = CKR_FUNCTION_FAILED (0x6)
Aborting.
$ yhsm2-tool -s -m SHA256-RSA-PKCS-PSS --salt -2 --id 0301 -i t6400.dat -o t6400.dat.sig5.pss
Using slot 0 with a present token (0x0)
Logging in to "YubiHSM".
Please enter User PIN: 
Using signature algorithm SHA256-RSA-PKCS-PSS
PSS parameters: hashAlg=SHA256, mgf=MGF1-SHA256, salt=350 B
$ openssl dgst -engine pkcs11 -keyform engine -sha256 -verify "pkcs11:token=YubiHSM;id=%03%01;type=public" -sigopt rsa_padding_mode:pss -signature t6400.dat.sig5.pss t6400.dat
engine "pkcs11" set.
Enter PKCS#11 token PIN for YubiHSM:
Verified OK
$

So it looks like the "generic" mechanism RSA-PKCS-PSS is rejected no matter what, and only specific ones (like SHA256-RSA-PKCS-PSS) are honored...?

@dengert
Copy link
Member

dengert commented Sep 15, 2017

I believe the "generic" mechanism RSA-PKCS-PSS is expecting a hash of the message that matches the size of the hashAlg=SHA256 A SPY trace would show what is getting passed.

PKCS|#11 v2.40 says:
hashAlg
hash algorithm used in the PSS encoding; if the signature
mechanism does not include message hashing, then this value must
be the mechanism used by the application to generate the message
hash; if the signature mechanism includes hashing, then this value
must match the hash algorithm indicated by the signature
mechanism

RSA-PKCS-PSS does not include message hashing.

What is the size of t6400.dat?

The salt=350 also looks strange. with --salt -2 I would expect it would be the size of the hash (SHA256) so would be salt=32.

@Jakuje
Copy link
Member Author

Jakuje commented Sep 15, 2017

The -2 means maximum possible, so according to the code modlen - hashlen -2. Therefore using RSA key with 4096 bits (512 B) and around 150B of data, we can get salts of this size to my understanding. This looks correct to me (or at least the similar way how it is implemented in OpenSSL).

@mouse07410 for the OEAP please, open a separate pull request. It is getting confusing here.

Anyway, about the RSA-PKCS-PSS I am seeing the same behavior if I provide bogus data. If I provide something that looks like SHA256 hash (32B of random data), I am able to create signature (I have smaller keys than @mouse07410 ):

$ yhsm2-tool -s -m RSA-PKCS-PSS --hash-algorithm SHA256 --mgf MGF1-SHA256 --salt -2 --id c31d -i data -o data.sig5.pss
Using slot 0 with a present token (0x0)
Using signature algorithm RSA-PKCS-PSS
PSS parameters: hashAlg=SHA256, mgf=MGF1-SHA256, salt=222 B

@mouse07410
Copy link
Contributor

I believe the "generic" mechanism RSA-PKCS-PSS is expecting a hash of the message that matches the size of the hashAlg...
RSA-PKCS-PSS does not include message hashing.

Spot on! Thank you!

Correcting this, pkcs11-tool proceeds to sign using RSA-PKCS-PSS.

$ yhsm2-tool -s -m RSA-PKCS-PSS --hash-algorithm SHA256 --salt -2 --id 0301 -i t256.dat -o t256.dat.sig.pss
Using slot 0 with a present token (0x0)
Logging in to "YubiHSM".
Please enter User PIN: 
Using signature algorithm RSA-PKCS-PSS
PSS parameters: hashAlg=SHA256, mgf=MGF1-SHA256, salt=350 B
$ openssl pkeyutl -engine pkcs11 -keyform engine -verify -in t256.dat -pubin -inkey "pkcs11:token=YubiHSM;id=%03%01;type=public" -pkeyopt rsa_padding_mode:pss -pkeyopt digest:sha256 -sigfile t256.dat.sig.pss
engine "pkcs11" set.
Enter PKCS#11 token PIN for YubiHSM:
Signature Verified Successfully
$ 

I am editing the man page to reflect this.

@dengert
Copy link
Member

dengert commented Sep 15, 2017

@Jakuje You are right on the -2. His earlier examples used -1 and had saltlLen = 48. I mixed them up.
PSS parameters: hashAlg=SHA384-RSA-PKCS-PSS, mgf=MGF1-SHA384, salt=48 B

His latest does not say what key size. But with:
PSS parameters: hashAlg=SHA256, mgf=MGF1-SHA256, salt=350 B

saltlen = modlen - hashlen -2
modlen = saltlen + hashlen +2
modlen = 350 + 32 + 2
modlen = 384.
Or a 3072 bit RSA key.

I see @mouse07410 has it working now.

The RSA-PKCS-PSS is not really "generic". It is used to pass in the hash of the message which must match in size of a hash from the PKCS#11 hashAlg parameter.

@Jakuje
Copy link
Member Author

Jakuje commented Sep 15, 2017

Yes, sorry. That was my initial confusion, that it should be something like generic algoritm. Also for example RFC 4055 talks about default hash and mgf functions (SHA-1 based) so I took the opportunity to add also this while improving the documentation and removing the bogus "generic".

I also used --salt-len rather than the previous --salt to make it also clear that we are not providing salt on command-line.

See latest commit.

@@ -969,6 +969,15 @@ C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HA
enter("C_SignInit");
spy_dump_ulong_in("hSession", hSession);
fprintf(spy_output, "pMechanism->type=%s\n", lookup_enum(MEC_T, pMechanism->mechanism));
if (pMechanism->pParameter != NULL) { /* XXX assuming PSS parameter */
CK_RSA_PKCS_PSS_PARAMS *param =
(CK_RSA_PKCS_PSS_PARAMS *) pMechanism->pParameter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we test for the actual parameter type? casting here and dereferencing later may lead to unexpected behavior...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should. But since this was the first mechanism using parameter, I wanted to avoid listing all the PSS mechanisms. But as we will have OEAP too, we should certainly do that. I will submit an update tomorrow. It is quite late here in Europe.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What other parameter types are possible in C_SignInit? Certainly not OAEP (which belongs to C_DecryptInit).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh ... my bad. It was very late yesterday. But reading through the PKCS#11 mechanisms specification, we can see that for example 2.8.9 General-length AES-MAC is a mechanism that has parameters and is using/can use the C_Sign interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks. Understood. Made the same change for OAEP in ...spy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI done. All checks passed.

@frankmorgner frankmorgner merged commit bdb1961 into OpenSC:master Sep 21, 2017
@frankmorgner
Copy link
Member

thanks

@Jakuje
Copy link
Member Author

Jakuje commented Oct 6, 2017

@mouse07410 do you have any progress with the OAEP? I saw no changes in your branch for some time nor I see the PR here. I would be happy to review and test it as it will be done.

@mouse07410
Copy link
Contributor

mouse07410 commented Oct 6, 2017

...any progress with the OAEP?

Of course ;-)

Everything's finished and working in my fork of OpenSC. The relevant commits went in before the merge with yours showed up, as I wanted to keep my master in sync with the upstream, but not at the cost of holding down the necessary features or fixes.

libp11 now also supports PSS and OAEP - check this branch.

@Jakuje please try it, and report if you can. Also, feel free to make a PR to the master. ;)

metsma pushed a commit to metsma/OpenSC that referenced this pull request Dec 6, 2017
* Add missing SHA224 RSA algorithms

* Fix wrong replacement in pkcs11-tool manual page

* Add MGF and PSS_PARAMS definitions in PKCS#11 header file

* Inspect PSS signature parameters in pkcs11-spy

* Enable RSA-PSS signatures in pkcs11-tool

* Added short names to RSA-PSS methods

* Reintroduce portable NORETURN indication for functions and use it to avoid compilers complaining
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants