hwcrypto / hwcrypto.js

Browser JavaScript library for working with hardware tokens
https://hwcrypto.github.io
MIT License
157 stars 47 forks source link

RSA ID-card signature verification failed #51

Closed vadimkim closed 6 years ago

vadimkim commented 6 years ago

For older ID cards that are using RSA keys signature verification is unsuccessful. Example:

Signing SHA-256: 413140d54372f9baf481d4c54e2d5c7bcf28fd6087000280e07976121dd54af2
Debug: hwcrypto.js 0.0.13 with Chrome native messaging extension 0.0.29/1.0.7.498
Using certificate:
-----BEGIN CERTIFICATE-----
MIIFnTCCA4WgAwIBAgIQHlForrTelKtZCdGezFULQTANBgkqhkiG9w0BAQsFADBj
MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
czEXMBUGA1UEYQwOTlRSRUUtMTA3NDcwMTMxFzAVBgNVBAMMDkVTVEVJRC1TSyAy
MDE1MB4XDTE3MDUwMzEyNDgzMFoXDTE4MTAxNjIwNTk1OVowgY8xCzAJBgNVBAYT
AkVFMQ8wDQYDVQQKDAZFU1RFSUQxFzAVBgNVBAsMDmF1dGhlbnRpY2F0aW9uMSAw
HgYDVQQDDBdURUVNQUEsVE9FTCwzNzkwMTE5Mjc2NTEPMA0GA1UEBAwGVEVFTUFB
MQ0wCwYDVQQqDARUT0VMMRQwEgYDVQQFEwszNzkwMTE5Mjc2NTCCASMwDQYJKoZI
hvcNAQEBBQADggEQADCCAQsCggEBANmKoF5sgFwczcETJNkuNAydn/N7AcJMaYr5
etOz3mEktytXhj8RTAeVt07FyYwlne7fzCe0TuvFcuu4eeKgOBO6WeYZ5dZSZ/T1
Gb2mdrzOB2TnlbJN+OllMlnqtGR59jSE2ffct+hSHeqCUgf/vl2rXiSYGDY9hcrT
YRhM3cTyQ6kwBIFliXghfl5FklMJ/aJ715SeuseZ+f54qrsLCBydpzHjILP2Tzi2
njBeH57kuEkh4VFQEQR7VgV7tbZl1lNOloBwlxK9K4pqYBwIYt3Du1fRnPjNiCYn
lpAsoBontukJ8H8sTDgK63PNzZSLt8Jww/X7uecT+uKT5HiGy7sCBDj0NJ2jggEd
MIIBGTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIEsDA7BgNVHSAENDAyMDAGCSsG
AQQBzh8BATAjMCEGCCsGAQUFBwIBFhVodHRwczovL3d3dy5zay5lZS9jcHMwHwYD
VR0RBBgwFoEUdG9lbC50ZWVtYWFAZWVzdGkuZWUwHQYDVR0OBBYEFP0nv9lrY/m4
kgU1539FDtrOVIw1MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAf
BgNVHSMEGDAWgBSzq4i8mdVipIUqCM20HXI7g3JHUTA8BgNVHR8ENTAzMDGgL6At
hitodHRwOi8vd3d3LnNrLmVlL2NybHMvZXN0ZWlkL2VzdGVpZDIwMTUuY3JsMA0G
CSqGSIb3DQEBCwUAA4ICAQC2A5r8Y6IZrkZJOK7mrCnat231blZrPvEHKZ/0gCrb
CglDrDzFuWQWvmF0cGaWIsa18msI7zx0NJG5mOTH5PjdlvqzzxvhwwKSyMRXmA9H
167aUQZh9wD5gpeitlLqmZkqlWTy9HajUJd8PeRkP4+hcep9LBNwabNlDqD0N23b
OcHuzNZnItrmGgPhE73SEnQUuwVpUfES5suNYcrlwdB3B3lVBPCkwLoToYezwCRD
ZD1w5i9qSRgJNiIe1DAODv3AFdA1B3Jr+TG7hiUI0E5b7m6kjKlh/J49nLFG/Aen
uPyNfPkTdc1kRUxekSFmTLV9w8I0Rp6dyGS36QvGqqS6qcxfSzqenCJaYVRSK4Wo
z8dmmfbzyqym36acYIe8Xmgz6VW0HDkXc/kAPEpAeEANLR917PO7qujvzU7nSbkq
jdY+0dLxbgRYtbgao26swC/Fk0qvzlBIZSWHSYG8ZcclB3hlGoIEwejnD+uVu9QJ
dr1mI5GGT7mRgFh8oGRi6sf3qEjo5WsbnH2lOnrrPc4qypuHvaTA2XCrm4SXM3nz
JWJnAgLEI69IoNlpaVTwFA/295z9VPOqwkQnwCeBcX/IVuSGMeojEkd4rJQAV++Q
T2whWj9KMBW4Hiv9g6U77SyDMKwISf0vQhgjc2aUmPRns9UZQvbC1/ijB6JUfOC8
aA==
-----END CERTIFICATE-----
Generated signature:
5b99ae743378949f7ae831cc12bd4ed4e64de2a4b01388ca93050c6395f1ebdf
37e593c16f0266ca8788832a2f69aa5a2cdcfce7932a693779dc8511878217aa
4500c1397f7818b518e6f7da65edfdb80936253aa2151f8a6e452c181efe63d4
8618ec8be26a522dbd92b1eea7a42bd5928104794ae46b8adfc8380d01a00710
bf0475748bb05100b364a60fad8d40abe1d1029035db29c8b09c8011e63aba66
3881d22a521dd1acb77f5060af7c1db7403ecab8ed5ac9275ac60b3658748c17
e1e2b788c5ef2bb84b2ed5ebe1f1fdeb3a487da617c7722aeb6f462145806a54
9274d3cef53e228a67826f12bf2efb48a02a0f721e94e99878bc69df8628fe6c

Trying to verify the signature with openssl failed:

openssl x509 -in cert.pem -inform pem -pubkey -noout > pubkey.pem
echo '5b99ae743378949f7ae831cc12bd4ed4e64de2a4b01388ca93050c6395f1ebdf37e593c16f0266ca8788832a2f69aa5a2cdcfce7932a693779dc8511878217aa4500c1397f7818b518e6f7da65edfdb80936253aa2151f8a6e452c181efe63d48618ec8be26a522dbd92b1eea7a42bd5928104794ae46b8adfc8380d01a00710bf0475748bb05100b364a60fad8d40abe1d1029035db29c8b09c8011e63aba663881d22a521dd1acb77f5060af7c1db7403ecab8ed5ac9275ac60b3658748c17e1e2b788c5ef2bb84b2ed5ebe1f1fdeb3a487da617c7722aeb6f462145806a549274d3cef53e228a67826f12bf2efb48a02a0f721e94e99878bc69df8628fe6c' | xxd -r -p > sigfile
openssl rsautl -verify -in sigfile -pkcs -inkey pubkey.pem -pubin > challenge.rsautil-decrypted

The resulting challenge.rsautil-decrypted is 51bytes instead of 32 bytes of incoming data. I have tried to do the similar thing in JAVA and the result of verification is also "false".

vadimkim commented 6 years ago

Further investigation showed that correct SHA256 hash is prefix-ed with 19 bytes of data. I.e it is possible to get hash value 413140d54372f9baf481d4c54e2d5c7bcf28fd6087000280e07976121dd54af2 if first 19 bytes are stripped-off

metsma commented 6 years ago

19 bytes prefix is ASN1 SHA OID https://github.com/open-eid/chrome-token-signing/blob/master/host-shared/PKCS11CardManager.h#L224

vadimkim commented 6 years ago

Thank you for response, Raul. This is OID for SHA256 hash indeed. Unfortunately I was not able to correctly configure JAVA signature. Used something like this:

Signature signature = Signature.getInstance("NONEwithRSAandMGF1");
signature.setParameter(new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 0, 1));

and resolved the issue by manually decrypting signature and stripping out padding like this:

// decrypt signature value using public certificate
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, clientCertificate.getPublicKey());
byte[] decryptedSignature = cipher.doFinal(DatatypeConverter.parseHexBinary(signatureString));
byte[] hash = Arrays.copyOfRange(decryptedSignature, decryptedSignature.length-32, decryptedSignature.length);
...

Not very nice, but working. Hope I can find better solution some time...

metsma commented 6 years ago

It should be Signature signature = Signature.getInstance("SHA256withRSA"); and if you use over 1024 RSA keys you need configure java http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

vadimkim commented 6 years ago

@metsma this was the first thing I have tried and the reason why I have opened this thread. With or without unlimited JCE verification of "SHA256withRSA" and "NONEwithRSA" (because we already sign SHA256 hash, not pure data) is failed. Code below gives "false".


        Security.addProvider(new BouncyCastleProvider());
        String certificate = "-----BEGIN CERTIFICATE----- MIIFnTCCA4WgAwIBAgIQHlForrTelKtZCdGezFULQTANBgkqhkiG9w0BAQsFADBj MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 czEXMBUGA1UEYQwOTlRSRUUtMTA3NDcwMTMxFzAVBgNVBAMMDkVTVEVJRC1TSyAy MDE1MB4XDTE3MDUwMzEyNDgzMFoXDTE4MTAxNjIwNTk1OVowgY8xCzAJBgNVBAYT AkVFMQ8wDQYDVQQKDAZFU1RFSUQxFzAVBgNVBAsMDmF1dGhlbnRpY2F0aW9uMSAw HgYDVQQDDBdURUVNQUEsVE9FTCwzNzkwMTE5Mjc2NTEPMA0GA1UEBAwGVEVFTUFB MQ0wCwYDVQQqDARUT0VMMRQwEgYDVQQFEwszNzkwMTE5Mjc2NTCCASMwDQYJKoZI hvcNAQEBBQADggEQADCCAQsCggEBANmKoF5sgFwczcETJNkuNAydn/N7AcJMaYr5 etOz3mEktytXhj8RTAeVt07FyYwlne7fzCe0TuvFcuu4eeKgOBO6WeYZ5dZSZ/T1 Gb2mdrzOB2TnlbJN+OllMlnqtGR59jSE2ffct+hSHeqCUgf/vl2rXiSYGDY9hcrT YRhM3cTyQ6kwBIFliXghfl5FklMJ/aJ715SeuseZ+f54qrsLCBydpzHjILP2Tzi2 njBeH57kuEkh4VFQEQR7VgV7tbZl1lNOloBwlxK9K4pqYBwIYt3Du1fRnPjNiCYn lpAsoBontukJ8H8sTDgK63PNzZSLt8Jww/X7uecT+uKT5HiGy7sCBDj0NJ2jggEd MIIBGTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIEsDA7BgNVHSAENDAyMDAGCSsG AQQBzh8BATAjMCEGCCsGAQUFBwIBFhVodHRwczovL3d3dy5zay5lZS9jcHMwHwYD VR0RBBgwFoEUdG9lbC50ZWVtYWFAZWVzdGkuZWUwHQYDVR0OBBYEFP0nv9lrY/m4 kgU1539FDtrOVIw1MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAf BgNVHSMEGDAWgBSzq4i8mdVipIUqCM20HXI7g3JHUTA8BgNVHR8ENTAzMDGgL6At hitodHRwOi8vd3d3LnNrLmVlL2NybHMvZXN0ZWlkL2VzdGVpZDIwMTUuY3JsMA0G CSqGSIb3DQEBCwUAA4ICAQC2A5r8Y6IZrkZJOK7mrCnat231blZrPvEHKZ/0gCrb CglDrDzFuWQWvmF0cGaWIsa18msI7zx0NJG5mOTH5PjdlvqzzxvhwwKSyMRXmA9H 167aUQZh9wD5gpeitlLqmZkqlWTy9HajUJd8PeRkP4+hcep9LBNwabNlDqD0N23b OcHuzNZnItrmGgPhE73SEnQUuwVpUfES5suNYcrlwdB3B3lVBPCkwLoToYezwCRD ZD1w5i9qSRgJNiIe1DAODv3AFdA1B3Jr+TG7hiUI0E5b7m6kjKlh/J49nLFG/Aen uPyNfPkTdc1kRUxekSFmTLV9w8I0Rp6dyGS36QvGqqS6qcxfSzqenCJaYVRSK4Wo z8dmmfbzyqym36acYIe8Xmgz6VW0HDkXc/kAPEpAeEANLR917PO7qujvzU7nSbkq jdY+0dLxbgRYtbgao26swC/Fk0qvzlBIZSWHSYG8ZcclB3hlGoIEwejnD+uVu9QJ dr1mI5GGT7mRgFh8oGRi6sf3qEjo5WsbnH2lOnrrPc4qypuHvaTA2XCrm4SXM3nz JWJnAgLEI69IoNlpaVTwFA/295z9VPOqwkQnwCeBcX/IVuSGMeojEkd4rJQAV++Q T2whWj9KMBW4Hiv9g6U77SyDMKwISf0vQhgjc2aUmPRns9UZQvbC1/ijB6JUfOC8 aA== -----END CERTIFICATE-----";
        X509Certificate x509 = CertificateUtil.toX509Certificate(certificate);
        Signature signature = Signature.getInstance("NONEwithRSA");
        signature.initVerify(x509);

        byte[] data = DatatypeConverter.parseHexBinary("413140d54372f9baf481d4c54e2d5c7bcf28fd6087000280e07976121dd54af2");
        byte[] sig =  DatatypeConverter.parseHexBinary("5b99ae743378949f7ae831cc12bd4ed4e64de2a4b01388ca93050c6395f1ebdf37e593c16f0266ca8788832a2f69aa5a2cdcfce7932a693779dc8511878217aa4500c1397f7818b518e6f7da65edfdb80936253aa2151f8a6e452c181efe63d48618ec8be26a522dbd92b1eea7a42bd5928104794ae46b8adfc8380d01a00710bf0475748bb05100b364a60fad8d40abe1d1029035db29c8b09c8011e63aba663881d22a521dd1acb77f5060af7c1db7403ecab8ed5ac9275ac60b3658748c17e1e2b788c5ef2bb84b2ed5ebe1f1fdeb3a487da617c7722aeb6f462145806a549274d3cef53e228a67826f12bf2efb48a02a0f721e94e99878bc69df8628fe6c");

        signature.update(data);
        boolean verify = signature.verify(sig);
        assertTrue(verify); // Is "false"
vadimkim commented 6 years ago

Got it working with encoding digest to ASN1 format like this:

Signature signature = Signature.getInstance("NONEwithRSA");
....
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.1"), DERNull.INSTANCE);
DigestInfo di = new DigestInfo(algorithmIdentifier, data);
byte[] digest = di.getEncoded();
signature.update(digest);
boolean verify = signature.verify(sig);
assertTrue(verify); // Is "true" now
persuader commented 5 years ago

Got it working with encoding digest to ASN1 format like this:

Signature signature = Signature.getInstance("NONEwithRSA");
....
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.1"), DERNull.INSTANCE);
DigestInfo di = new DigestInfo(algorithmIdentifier, data);
byte[] digest = di.getEncoded();
signature.update(digest);
boolean verify = signature.verify(sig);
assertTrue(verify); // Is "true" now

Anyway to achieve this in command line with openssl? Thanks in advance.

vadimkim commented 5 years ago

@persuader I have provided openssl command in request. It is correct, just the result of decrypted challenge is prefixed with 19 bytes of SHA256 OID like @metsma has replied.

persuader commented 5 years ago

@persuader I have provided openssl command in request. It is correct, just the result of decrypted challenge is prefixed with 19 bytes of SHA256 OID like @metsma has replied.

Got it . Thanks again.