| /* Copyright (c) 2016, Google Inc. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
| |
| #include <limits.h> |
| |
| #include <algorithm> |
| #include <functional> |
| #include <string> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| #include <openssl/asn1.h> |
| #include <openssl/bio.h> |
| #include <openssl/bytestring.h> |
| #include <openssl/conf.h> |
| #include <openssl/crypto.h> |
| #include <openssl/curve25519.h> |
| #include <openssl/digest.h> |
| #include <openssl/err.h> |
| #include <openssl/nid.h> |
| #include <openssl/pem.h> |
| #include <openssl/pool.h> |
| #include <openssl/x509.h> |
| |
| #include "internal.h" |
| #include "../internal.h" |
| #include "../test/file_util.h" |
| #include "../test/test_data.h" |
| #include "../test/test_util.h" |
| |
| #if defined(OPENSSL_THREADS) |
| #include <thread> |
| #endif |
| |
| |
| static const char kCrossSigningRootPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICcTCCAdqgAwIBAgIIagJHiPvE0MowDQYJKoZIhvcNAQELBQAwPDEaMBgGA1UE |
| ChMRQm9yaW5nU1NMIFRFU1RJTkcxHjAcBgNVBAMTFUNyb3NzLXNpZ25pbmcgUm9v |
| dCBDQTAgFw0xNTAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowPDEaMBgGA1UE |
| ChMRQm9yaW5nU1NMIFRFU1RJTkcxHjAcBgNVBAMTFUNyb3NzLXNpZ25pbmcgUm9v |
| dCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwo3qFvSB9Zmlbpzn9wJp |
| ikI75Rxkatez8VkLqyxbOhPYl2Haz8F5p1gDG96dCI6jcLGgu3AKT9uhEQyyUko5 |
| EKYasazSeA9CQrdyhPg0mkTYVETnPM1W/ebid1YtqQbq1CMWlq2aTDoSGAReGFKP |
| RTdXAbuAXzpCfi/d8LqV13UCAwEAAaN6MHgwDgYDVR0PAQH/BAQDAgIEMB0GA1Ud |
| JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MBkGA1Ud |
| DgQSBBBHKHC7V3Z/3oLvEZx0RZRwMBsGA1UdIwQUMBKAEEcocLtXdn/egu8RnHRF |
| lHAwDQYJKoZIhvcNAQELBQADgYEAnglibsy6mGtpIXivtlcz4zIEnHw/lNW+r/eC |
| CY7evZTmOoOuC/x9SS3MF9vawt1HFUummWM6ZgErqVBOXIB4//ykrcCgf5ZbF5Hr |
| +3EFprKhBqYiXdD8hpBkrBoXwn85LPYWNd2TceCrx0YtLIprE2R5MB2RIq8y4Jk3 |
| YFXvkME= |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kRootCAPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICVTCCAb6gAwIBAgIIAj5CwoHlWuYwDQYJKoZIhvcNAQELBQAwLjEaMBgGA1UE |
| ChMRQm9yaW5nU1NMIFRFU1RJTkcxEDAOBgNVBAMTB1Jvb3QgQ0EwIBcNMTUwMTAx |
| MDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMC4xGjAYBgNVBAoTEUJvcmluZ1NTTCBU |
| RVNUSU5HMRAwDgYDVQQDEwdSb290IENBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB |
| iQKBgQDpDn8RDOZa5oaDcPZRBy4CeBH1siSSOO4mYgLHlPE+oXdqwI/VImi2XeJM |
| 2uCFETXCknJJjYG0iJdrt/yyRFvZTQZw+QzGj+mz36NqhGxDWb6dstB2m8PX+plZ |
| w7jl81MDvUnWs8yiQ/6twgu5AbhWKZQDJKcNKCEpqa6UW0r5nwIDAQABo3oweDAO |
| BgNVHQ8BAf8EBAMCAgQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8G |
| A1UdEwEB/wQFMAMBAf8wGQYDVR0OBBIEEEA31wH7QC+4HH5UBCeMWQEwGwYDVR0j |
| BBQwEoAQQDfXAftAL7gcflQEJ4xZATANBgkqhkiG9w0BAQsFAAOBgQDXylEK77Za |
| kKeY6ZerrScWyZhrjIGtHFu09qVpdJEzrk87k2G7iHHR9CAvSofCgEExKtWNS9dN |
| +9WiZp/U48iHLk7qaYXdEuO07No4BYtXn+lkOykE+FUxmA4wvOF1cTd2tdj3MzX2 |
| kfGIBAYhzGZWhY3JbhIfTEfY1PNM1pWChQ== |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kRootCrossSignedPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICYzCCAcygAwIBAgIIAj5CwoHlWuYwDQYJKoZIhvcNAQELBQAwPDEaMBgGA1UE |
| ChMRQm9yaW5nU1NMIFRFU1RJTkcxHjAcBgNVBAMTFUNyb3NzLXNpZ25pbmcgUm9v |
| dCBDQTAgFw0xNTAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowLjEaMBgGA1UE |
| ChMRQm9yaW5nU1NMIFRFU1RJTkcxEDAOBgNVBAMTB1Jvb3QgQ0EwgZ8wDQYJKoZI |
| hvcNAQEBBQADgY0AMIGJAoGBAOkOfxEM5lrmhoNw9lEHLgJ4EfWyJJI47iZiAseU |
| 8T6hd2rAj9UiaLZd4kza4IURNcKSckmNgbSIl2u3/LJEW9lNBnD5DMaP6bPfo2qE |
| bENZvp2y0Habw9f6mVnDuOXzUwO9SdazzKJD/q3CC7kBuFYplAMkpw0oISmprpRb |
| SvmfAgMBAAGjejB4MA4GA1UdDwEB/wQEAwICBDAdBgNVHSUEFjAUBggrBgEFBQcD |
| AQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQQDfXAftAL7gc |
| flQEJ4xZATAbBgNVHSMEFDASgBBHKHC7V3Z/3oLvEZx0RZRwMA0GCSqGSIb3DQEB |
| CwUAA4GBAErTxYJ0en9HVRHAAr5OO5wuk5Iq3VMc79TMyQLCXVL8YH8Uk7KEwv+q |
| 9MEKZv2eR/Vfm4HlXlUuIqfgUXbwrAYC/YVVX86Wnbpy/jc73NYVCq8FEZeO+0XU |
| 90SWAPDdp+iL7aZdimnMtG1qlM1edmz8AKbrhN/R3IbA2CL0nCWV |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kIntermediatePEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICXjCCAcegAwIBAgIJAKJMH+7rscPcMA0GCSqGSIb3DQEBCwUAMC4xGjAYBgNV |
| BAoTEUJvcmluZ1NTTCBURVNUSU5HMRAwDgYDVQQDEwdSb290IENBMCAXDTE1MDEw |
| MTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjA2MRowGAYDVQQKExFCb3JpbmdTU0wg |
| VEVTVElORzEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMIGfMA0GCSqGSIb3DQEB |
| AQUAA4GNADCBiQKBgQC7YtI0l8ocTYJ0gKyXTtPL4iMJCNY4OcxXl48jkncVG1Hl |
| blicgNUa1r9m9YFtVkxvBinb8dXiUpEGhVg4awRPDcatlsBSEBuJkiZGYbRcAmSu |
| CmZYnf6u3aYQ18SU8WqVERPpE4cwVVs+6kwlzRw0+XDoZAczu8ZezVhCUc6NbQID |
| AQABo3oweDAOBgNVHQ8BAf8EBAMCAgQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG |
| AQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wGQYDVR0OBBIEEIwaaKi1dttdV3sfjRSy |
| BqMwGwYDVR0jBBQwEoAQQDfXAftAL7gcflQEJ4xZATANBgkqhkiG9w0BAQsFAAOB |
| gQCvnolNWEHuQS8PFVVyuLR+FKBeUUdrVbSfHSzTqNAqQGp0C9fk5oCzDq6ZgTfY |
| ESXM4cJhb3IAnW0UM0NFsYSKQJ50JZL2L3z5ZLQhHdbs4RmODGoC40BVdnJ4/qgB |
| aGSh09eQRvAVmbVCviDK2ipkWNegdyI19jFfNP5uIkGlYg== |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kIntermediateSelfSignedPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICZjCCAc+gAwIBAgIJAKJMH+7rscPcMA0GCSqGSIb3DQEBCwUAMDYxGjAYBgNV |
| BAoTEUJvcmluZ1NTTCBURVNUSU5HMRgwFgYDVQQDEw9JbnRlcm1lZGlhdGUgQ0Ew |
| IBcNMTUwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMDYxGjAYBgNVBAoTEUJv |
| cmluZ1NTTCBURVNUSU5HMRgwFgYDVQQDEw9JbnRlcm1lZGlhdGUgQ0EwgZ8wDQYJ |
| KoZIhvcNAQEBBQADgY0AMIGJAoGBALti0jSXyhxNgnSArJdO08viIwkI1jg5zFeX |
| jyOSdxUbUeVuWJyA1RrWv2b1gW1WTG8GKdvx1eJSkQaFWDhrBE8Nxq2WwFIQG4mS |
| JkZhtFwCZK4KZlid/q7dphDXxJTxapURE+kThzBVWz7qTCXNHDT5cOhkBzO7xl7N |
| WEJRzo1tAgMBAAGjejB4MA4GA1UdDwEB/wQEAwICBDAdBgNVHSUEFjAUBggrBgEF |
| BQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQjBpoqLV2 |
| 211Xex+NFLIGozAbBgNVHSMEFDASgBCMGmiotXbbXVd7H40UsgajMA0GCSqGSIb3 |
| DQEBCwUAA4GBALcccSrAQ0/EqQBsx0ZDTUydHXXNP2DrUkpUKmAXIe8McqIVSlkT |
| 6H4xz7z8VRKBo9j+drjjtCw2i0CQc8aOLxRb5WJ8eVLnaW2XRlUqAzhF0CrulfVI |
| E4Vs6ZLU+fra1WAuIj6qFiigRja+3YkZArG8tMA9vtlhTX/g7YBZIkqH |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kLeafPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICXjCCAcegAwIBAgIIWjO48ufpunYwDQYJKoZIhvcNAQELBQAwNjEaMBgGA1UE |
| ChMRQm9yaW5nU1NMIFRFU1RJTkcxGDAWBgNVBAMTD0ludGVybWVkaWF0ZSBDQTAg |
| Fw0xNTAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowMjEaMBgGA1UEChMRQm9y |
| aW5nU1NMIFRFU1RJTkcxFDASBgNVBAMTC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3 |
| DQEBAQUAA4GNADCBiQKBgQDD0U0ZYgqShJ7oOjsyNKyVXEHqeafmk/bAoPqY/h1c |
| oPw2E8KmeqiUSoTPjG5IXSblOxcqpbAXgnjPzo8DI3GNMhAf8SYNYsoH7gc7Uy7j |
| 5x8bUrisGnuTHqkqH6d4/e7ETJ7i3CpR8bvK16DggEvQTudLipz8FBHtYhFakfdh |
| TwIDAQABo3cwdTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG |
| CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwGQYDVR0OBBIEEKN5pvbur7mlXjeMEYA0 |
| 4nUwGwYDVR0jBBQwEoAQjBpoqLV2211Xex+NFLIGozANBgkqhkiG9w0BAQsFAAOB |
| gQBj/p+JChp//LnXWC1k121LM/ii7hFzQzMrt70bny406SGz9jAjaPOX4S3gt38y |
| rhjpPukBlSzgQXFg66y6q5qp1nQTD1Cw6NkKBe9WuBlY3iYfmsf7WT8nhlT1CttU |
| xNCwyMX9mtdXdQicOfNjIGUCD5OLV5PgHFPRKiHHioBAhg== |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kLeafNoKeyUsagePEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICNTCCAZ6gAwIBAgIJAIFQGaLQ0G2mMA0GCSqGSIb3DQEBCwUAMDYxGjAYBgNV |
| BAoTEUJvcmluZ1NTTCBURVNUSU5HMRgwFgYDVQQDEw9JbnRlcm1lZGlhdGUgQ0Ew |
| IBcNMTUwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMDcxGjAYBgNVBAoTEUJv |
| cmluZ1NTTCBURVNUSU5HMRkwFwYDVQQDExBldmlsLmV4YW1wbGUuY29tMIGfMA0G |
| CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOKoZe75NPz77EOaMMl4/0s3PyQw++zJvp |
| ejHAxZiTPCJgMbEHLrSzNoHdopg+CLUH5bE4wTXM8w9Inv5P8OAFJt7gJuPUunmk |
| j+NoU3QfzOR6BroePcz1vXX9jyVHRs087M/sLqWRHu9IR+/A+UTcBaWaFiDVUxtJ |
| YOwFMwjNPQIDAQABo0gwRjAMBgNVHRMBAf8EAjAAMBkGA1UdDgQSBBBJfLEUWHq1 |
| 27rZ1AVx2J5GMBsGA1UdIwQUMBKAEIwaaKi1dttdV3sfjRSyBqMwDQYJKoZIhvcN |
| AQELBQADgYEALVKN2Y3LZJOtu6SxFIYKxbLaXhTGTdIjxipZhmbBRDFjbZjZZOTe |
| 6Oo+VDNPYco4rBexK7umYXJyfTqoY0E8dbiImhTcGTEj7OAB3DbBomgU1AYe+t2D |
| uwBqh4Y3Eto+Zn4pMVsxGEfUpjzjZDel7bN1/oU/9KWPpDfywfUmjgk= |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kForgeryPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICZzCCAdCgAwIBAgIIdTlMzQoKkeMwDQYJKoZIhvcNAQELBQAwNzEaMBgGA1UE |
| ChMRQm9yaW5nU1NMIFRFU1RJTkcxGTAXBgNVBAMTEGV2aWwuZXhhbXBsZS5jb20w |
| IBcNMTUwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMDoxGjAYBgNVBAoTEUJv |
| cmluZ1NTTCBURVNUSU5HMRwwGgYDVQQDExNmb3JnZXJ5LmV4YW1wbGUuY29tMIGf |
| MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDADTwruBQZGb7Ay6s9HiYv5d1lwtEy |
| xQdA2Sy8Rn8uA20Q4KgqwVY7wzIZ+z5Butrsmwb70gdG1XU+yRaDeE7XVoW6jSpm |
| 0sw35/5vJbTcL4THEFbnX0OPZnvpuZDFUkvVtq5kxpDWsVyM24G8EEq7kPih3Sa3 |
| OMhXVXF8kso6UQIDAQABo3cwdTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI |
| KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwGQYDVR0OBBIEEEYJ/WHM |
| 8p64erPWIg4/liwwGwYDVR0jBBQwEoAQSXyxFFh6tdu62dQFcdieRjANBgkqhkiG |
| 9w0BAQsFAAOBgQA+zH7bHPElWRWJvjxDqRexmYLn+D3Aivs8XgXQJsM94W0EzSUf |
| DSLfRgaQwcb2gg2xpDFoG+W0vc6O651uF23WGt5JaFFJJxqjII05IexfCNhuPmp4 |
| 4UZAXPttuJXpn74IY1tuouaM06B3vXKZR+/ityKmfJvSwxacmFcK+2ziAg== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kBadPSSCertPEM is a self-signed RSA-PSS certificate with bad parameters. |
| static const char kBadPSSCertPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIDdjCCAjqgAwIBAgIJANcwZLyfEv7DMD4GCSqGSIb3DQEBCjAxoA0wCwYJYIZI |
| AWUDBAIBoRowGAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIEAgIA3jAnMSUwIwYD |
| VQQDDBxUZXN0IEludmFsaWQgUFNTIGNlcnRpZmljYXRlMB4XDTE1MTEwNDE2MDIz |
| NVoXDTE1MTIwNDE2MDIzNVowJzElMCMGA1UEAwwcVGVzdCBJbnZhbGlkIFBTUyBj |
| ZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTaM7WH |
| qVCAGAIA+zL1KWvvASTrhlq+1ePdO7wsrWX2KiYoTYrJYTnxhLnn0wrHqApt79nL |
| IBG7cfShyZqFHOY/IzlYPMVt+gPo293gw96Fds5JBsjhjkyGnOyr9OUntFqvxDbT |
| IIFU7o9IdxD4edaqjRv+fegVE+B79pDk4s0ujsk6dULtCg9Rst0ucGFo19mr+b7k |
| dbfn8pZ72ZNDJPueVdrUAWw9oll61UcYfk75XdrLk6JlL41GrYHc8KlfXf43gGQq |
| QfrpHkg4Ih2cI6Wt2nhFGAzrlcorzLliQIUJRIhM8h4IgDfpBpaPdVQLqS2pFbXa |
| 5eQjqiyJwak2vJ8CAwEAAaNQME4wHQYDVR0OBBYEFCt180N4oGUt5LbzBwQ4Ia+2 |
| 4V97MB8GA1UdIwQYMBaAFCt180N4oGUt5LbzBwQ4Ia+24V97MAwGA1UdEwQFMAMB |
| Af8wMQYJKoZIhvcNAQEKMCSgDTALBglghkgBZQMEAgGhDTALBgkqhkiG9w0BAQii |
| BAICAN4DggEBAAjBtm90lGxgddjc4Xu/nbXXFHVs2zVcHv/mqOZoQkGB9r/BVgLb |
| xhHrFZ2pHGElbUYPfifdS9ztB73e1d4J+P29o0yBqfd4/wGAc/JA8qgn6AAEO/Xn |
| plhFeTRJQtLZVl75CkHXgUGUd3h+ADvKtcBuW9dSUncaUrgNKR8u/h/2sMG38RWY |
| DzBddC/66YTa3r7KkVUfW7yqRQfELiGKdcm+bjlTEMsvS+EhHup9CzbpoCx2Fx9p |
| NPtFY3yEObQhmL1JyoCRWqBE75GzFPbRaiux5UpEkns+i3trkGssZzsOuVqHNTNZ |
| lC9+9hPHIoc9UMmAQNo1vGIW3NWVoeGbaJ8= |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kRSAKey[] = R"( |
| -----BEGIN RSA PRIVATE KEY----- |
| MIICXgIBAAKBgQDYK8imMuRi/03z0K1Zi0WnvfFHvwlYeyK9Na6XJYaUoIDAtB92 |
| kWdGMdAQhLciHnAjkXLI6W15OoV3gA/ElRZ1xUpxTMhjP6PyY5wqT5r6y8FxbiiF |
| KKAnHmUcrgfVW28tQ+0rkLGMryRtrukXOgXBv7gcrmU7G1jC2a7WqmeI8QIDAQAB |
| AoGBAIBy09Fd4DOq/Ijp8HeKuCMKTHqTW1xGHshLQ6jwVV2vWZIn9aIgmDsvkjCe |
| i6ssZvnbjVcwzSoByhjN8ZCf/i15HECWDFFh6gt0P5z0MnChwzZmvatV/FXCT0j+ |
| WmGNB/gkehKjGXLLcjTb6dRYVJSCZhVuOLLcbWIV10gggJQBAkEA8S8sGe4ezyyZ |
| m4e9r95g6s43kPqtj5rewTsUxt+2n4eVodD+ZUlCULWVNAFLkYRTBCASlSrm9Xhj |
| QpmWAHJUkQJBAOVzQdFUaewLtdOJoPCtpYoY1zd22eae8TQEmpGOR11L6kbxLQsk |
| aMly/DOnOaa82tqAGTdqDEZgSNmCeKKknmECQAvpnY8GUOVAubGR6c+W90iBuQLj |
| LtFp/9ihd2w/PoDwrHZaoUYVcT4VSfJQog/k7kjE4MYXYWL8eEKg3WTWQNECQQDk |
| 104Wi91Umd1PzF0ijd2jXOERJU1wEKe6XLkYYNHWQAe5l4J4MWj9OdxFXAxIuuR/ |
| tfDwbqkta4xcux67//khAkEAvvRXLHTaa6VFzTaiiO8SaFsHV3lQyXOtMrBpB5jd |
| moZWgjHvB2W9Ckn7sDqsPB+U2tyX0joDdQEyuiMECDY8oQ== |
| -----END RSA PRIVATE KEY----- |
| )"; |
| |
| static const char kP256Key[] = R"( |
| -----BEGIN PRIVATE KEY----- |
| MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgBw8IcnrUoEqc3VnJ |
| TYlodwi1b8ldMHcO6NHJzgqLtGqhRANCAATmK2niv2Wfl74vHg2UikzVl2u3qR4N |
| Rvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYaHPUdfvGULUvPciLB |
| -----END PRIVATE KEY----- |
| )"; |
| |
| // kCRLTestRoot is a test root certificate. It has private key: |
| // |
| // -----BEGIN RSA PRIVATE KEY----- |
| // MIIEpAIBAAKCAQEAo16WiLWZuaymsD8n5SKPmxV1y6jjgr3BS/dUBpbrzd1aeFzN |
| // lI8l2jfAnzUyp+I21RQ+nh/MhqjGElkTtK9xMn1Y+S9GMRh+5R/Du0iCb1tCZIPY |
| // 07Tgrb0KMNWe0v2QKVVruuYSgxIWodBfxlKO64Z8AJ5IbnWpuRqO6rctN9qUoMlT |
| // IAB6dL4G0tDJ/PGFWOJYwOMEIX54bly2wgyYJVBKiRRt4f7n8H922qmvPNA9idmX |
| // 9G1VAtgV6x97XXi7ULORIQvn9lVQF6nTYDBJhyuPB+mLThbLP2o9orxGx7aCtnnB |
| // ZUIxUvHNOI0FaSaZH7Fi0xsZ/GkG2HZe7ImPJwIDAQABAoIBAQCJF9MTHfHGkk+/ |
| // DwCXlA0Wg0e6hBuHl10iNobYkMWIl/xXjOknhYiqOqb181py76472SVC5ERprC+r |
| // Lf0PXzqKuA117mnkwT2bYLCL9Skf8WEhoFLQNbVlloF6wYjqXcYgKYKh8HgQbZl4 |
| // aLg2YQl2NADTNABsUWj/4H2WEelsODVviqfFs725lFg9KHDI8zxAZXLzDt/M9uVL |
| // GxJiX12tr0AwaeAFZ1oPM/y+LznM3N3+Ht3jHHw3jZ/u8Z1RdAmdpu3bZ6tbwGBr |
| // 9edsH5rKkm9aBvMrY7eX5VHqaqyRNFyG152ZOJh4XiiFG7EmgTPCpaHo50Y018Re |
| // grVtk+FBAoGBANY3lY+V8ZOwMxSHes+kTnoimHO5Ob7nxrOC71i27x+4HHsYUeAr |
| // /zOOghiDIn+oNkuiX5CIOWZKx159Bp65CPpCbTb/fh+HYnSgXFgCw7XptycO7LXM |
| // 5GwR5jSfpfzBFdYxjxoUzDMFBwTEYRTm0HkUHkH+s+ajjw5wqqbcGLcfAoGBAMM8 |
| // DKW6Tb66xsf708f0jonAjKYTLZ+WOcwsBEWSFHoY8dUjvW5gqx5acHTEsc5ZTeh4 |
| // BCFLa+Mn9cuJWVJNs09k7Xb2PNl92HQ4GN2vbdkJhExbkT6oLDHg1hVD0w8KLfz1 |
| // lTAW6pS+6CdOHMEJpvqx89EgU/1GgIQ1fXYczE75AoGAKeJoXdDFkUjsU+FBhAPu |
| // TDcjc80Nm2QaF9NMFR5/lsYa236f06MGnQAKM9zADBHJu/Qdl1brUjLg1HrBppsr |
| // RDNkw1IlSOjhuUf5hkPUHGd8Jijm440SRIcjabqla8wdBupdvo2+d2NOQgJbsQiI |
| // ToQ+fkzcxAXK3Nnuo/1436UCgYBjLH7UNOZHS8OsVM0I1r8NVKVdu4JCfeJQR8/H |
| // s2P5ffBir+wLRMnH+nMDreMQiibcPxMCArkERAlE4jlgaJ38Z62E76KLbLTmnJRt |
| // EC9Bv+bXjvAiHvWMRMUbOj/ddPNVez7Uld+FvdBaHwDWQlvzHzBWfBCOKSEhh7Z6 |
| // qDhUqQKBgQDPMDx2i5rfmQp3imV9xUcCkIRsyYQVf8Eo7NV07IdUy/otmksgn4Zt |
| // Lbf3v2dvxOpTNTONWjp2c+iUQo8QxJCZr5Sfb21oQ9Ktcrmc/CY7LeBVDibXwxdM |
| // vRG8kBzvslFWh7REzC3u06GSVhyKDfW93kN2cKVwGoahRlhj7oHuZQ== |
| // -----END RSA PRIVATE KEY----- |
| static const char kCRLTestRoot[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIDbzCCAlegAwIBAgIJAODri7v0dDUFMA0GCSqGSIb3DQEBCwUAME4xCzAJBgNV |
| BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW |
| aWV3MRIwEAYDVQQKDAlCb3JpbmdTU0wwHhcNMTYwOTI2MTUwNjI2WhcNMjYwOTI0 |
| MTUwNjI2WjBOMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG |
| A1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJQm9yaW5nU1NMMIIBIjANBgkq |
| hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo16WiLWZuaymsD8n5SKPmxV1y6jjgr3B |
| S/dUBpbrzd1aeFzNlI8l2jfAnzUyp+I21RQ+nh/MhqjGElkTtK9xMn1Y+S9GMRh+ |
| 5R/Du0iCb1tCZIPY07Tgrb0KMNWe0v2QKVVruuYSgxIWodBfxlKO64Z8AJ5IbnWp |
| uRqO6rctN9qUoMlTIAB6dL4G0tDJ/PGFWOJYwOMEIX54bly2wgyYJVBKiRRt4f7n |
| 8H922qmvPNA9idmX9G1VAtgV6x97XXi7ULORIQvn9lVQF6nTYDBJhyuPB+mLThbL |
| P2o9orxGx7aCtnnBZUIxUvHNOI0FaSaZH7Fi0xsZ/GkG2HZe7ImPJwIDAQABo1Aw |
| TjAdBgNVHQ4EFgQUWPt3N5cZ/CRvubbrkqfBnAqhq94wHwYDVR0jBBgwFoAUWPt3 |
| N5cZ/CRvubbrkqfBnAqhq94wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC |
| AQEAORu6M0MOwXy+3VEBwNilfTxyqDfruQsc1jA4PT8Oe8zora1WxE1JB4q2FJOz |
| EAuM3H/NXvEnBuN+ITvKZAJUfm4NKX97qmjMJwLKWe1gVv+VQTr63aR7mgWJReQN |
| XdMztlVeZs2dppV6uEg3ia1X0G7LARxGpA9ETbMyCpb39XxlYuTClcbA5ftDN99B |
| 3Xg9KNdd++Ew22O3HWRDvdDpTO/JkzQfzi3sYwUtzMEonENhczJhGf7bQMmvL/w5 |
| 24Wxj4Z7KzzWIHsNqE/RIs6RV3fcW61j/mRgW2XyoWnMVeBzvcJr9NXp4VQYmFPw |
| amd8GKMZQvP0ufGnUn7D7uartA== |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kCRLTestLeaf[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIDkDCCAnigAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCVVMx |
| EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEjAQ |
| BgNVBAoMCUJvcmluZ1NTTDAeFw0xNjA5MjYxNTA4MzFaFw0xNzA5MjYxNTA4MzFa |
| MEsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQKDAlC |
| b3JpbmdTU0wxEzARBgNVBAMMCmJvcmluZy5zc2wwggEiMA0GCSqGSIb3DQEBAQUA |
| A4IBDwAwggEKAoIBAQDc5v1S1M0W+QWM+raWfO0LH8uvqEwuJQgODqMaGnSlWUx9 |
| 8iQcnWfjyPja3lWg9K62hSOFDuSyEkysKHDxijz5R93CfLcfnVXjWQDJe7EJTTDP |
| ozEvxN6RjAeYv7CF000euYr3QT5iyBjg76+bon1p0jHZBJeNPP1KqGYgyxp+hzpx |
| e0gZmTlGAXd8JQK4v8kpdYwD6PPifFL/jpmQpqOtQmH/6zcLjY4ojmqpEdBqIKIX |
| +saA29hMq0+NK3K+wgg31RU+cVWxu3tLOIiesETkeDgArjWRS1Vkzbi4v9SJxtNu |
| OZuAxWiynRJw3JwH/OFHYZIvQqz68ZBoj96cepjPAgMBAAGjezB5MAkGA1UdEwQC |
| MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl |
| MB0GA1UdDgQWBBTGn0OVVh/aoYt0bvEKG+PIERqnDzAfBgNVHSMEGDAWgBRY+3c3 |
| lxn8JG+5tuuSp8GcCqGr3jANBgkqhkiG9w0BAQsFAAOCAQEAd2nM8gCQN2Dc8QJw |
| XSZXyuI3DBGGCHcay/3iXu0JvTC3EiQo8J6Djv7WLI0N5KH8mkm40u89fJAB2lLZ |
| ShuHVtcC182bOKnePgwp9CNwQ21p0rDEu/P3X46ZvFgdxx82E9xLa0tBB8PiPDWh |
| lV16jbaKTgX5AZqjnsyjR5o9/mbZVupZJXx5Syq+XA8qiJfstSYJs4KyKK9UOjql |
| ICkJVKpi2ahDBqX4MOH4SLfzVk8pqSpviS6yaA1RXqjpkxiN45WWaXDldVHMSkhC |
| 5CNXsXi4b1nAntu89crwSLA3rEwzCWeYj+BX7e1T9rr3oJdwOU/2KQtW1js1yQUG |
| tjJMFw== |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kBasicCRL[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBpzCBkAIBATANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoA4wDDAKBgNV |
| HRQEAwIBATANBgkqhkiG9w0BAQsFAAOCAQEAnrBKKgvd9x9zwK9rtUvVeFeJ7+LN |
| ZEAc+a5oxpPNEsJx6hXoApYEbzXMxuWBQoCs5iEBycSGudct21L+MVf27M38KrWo |
| eOkq0a2siqViQZO2Fb/SUFR0k9zb8xl86Zf65lgPplALun0bV/HT7MJcl04Tc4os |
| dsAReBs5nqTGNEd5AlC1iKHvQZkM//MD51DspKnDpsDiUVi54h9C1SpfZmX8H2Vv |
| diyu0fZ/bPAM3VAGawatf/SyWfBMyKpoPXEG39oAzmjjOj8en82psn7m474IGaho |
| /vBbhl1ms5qQiLYPjm4YELtnXQoFyC72tBjbdFd/ZE9k4CNKDbxFUXFbkw== |
| -----END X509 CRL----- |
| )"; |
| |
| static const char kRevokedCRL[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIB6DCB0QIBATANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEyNDRaFw0xNjEwMjYxNTEyNDRaMD8wEwICEAAX |
| DTE2MDkyNjE1MTIyNlowEwICD/8XDTE2MDkyNjE1MTIyNlowEwICEAEXDTE2MDky |
| NjE1MTIyNlqgDjAMMAoGA1UdFAQDAgECMA0GCSqGSIb3DQEBCwUAA4IBAQAuepA9 |
| byX4PkpJcYKb+Ofn6f/mgB4Sh1BBRp0HMFm7Q3jryXB6yPZYp7UtwAufZUzbV42U |
| kOifOtDyQbu+fcpnpb4D9oWoFlr4DGQ+n23wujHizoTEYhhcZMj4U5yooDfmK4lI |
| GU6zzQZKG+1PaS6Dm4f6+kA+ekVUP+ZVjPqHP/K7Uv6akSKV7gAWs49N5QjrJKMQ |
| 3Igrbg4Et2ipaYgThGj8t1MUZdVY4UPtQ8oltSHkFEvH4PxOW/xKpHT4QQMl/WTT |
| QsQqOlRJBG2ci7pu1fzOylY35YFEAApkND/MkQjQfylNNgCzDWWAPQx0pDvVKQ0v |
| WXdfcGJRL1D3xRXk |
| -----END X509 CRL----- |
| )"; |
| |
| static const char kBadIssuerCRL[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBwjCBqwIBATANBgkqhkiG9w0BAQsFADBSMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEWMBQGA1UECgwN |
| Tm90IEJvcmluZ1NTTBcNMTYwOTI2MTUxMjQ0WhcNMTYxMDI2MTUxMjQ0WjAVMBMC |
| AhAAFw0xNjA5MjYxNTEyMjZaoA4wDDAKBgNVHRQEAwIBAjANBgkqhkiG9w0BAQsF |
| AAOCAQEAlBmjOA3Fs5UCq3GbyPEzHkfAabL0HqOQaCP12btmvIf/z8kcjMGHmjjP |
| t85dHbI4Ak/G9wtRT4E/j9i5KML+6yfhRyUTUJKIK/kbqgkj06uuYWuFipURlpDB |
| cm81RzZaMQvmlN5YKfzZV/clLX6mJiXpNQg6zjhj6wBAMI9ZfwVHmCjRqnwVmCUL |
| TybvuTmkryGBqREwmSbHFFjg5ixG/ztwzON/Ly78aJ8Bql6ktCl9+/gh6VJcoZ0q |
| L8V8aT8+Ghfi+zrXM8S9BmLQ9n0fQe0wzKrDZh14EK4sb7zmOzFHSxm3eEXyS98g |
| Od4cjsc3ymNk88S4jpnLRtIVxZB+SQ== |
| -----END X509 CRL----- |
| )"; |
| |
| // kKnownCriticalCRL is kBasicCRL but with a critical issuing distribution point |
| // extension. |
| static const char kKnownCriticalCRL[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBuDCBoQIBATANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoB8wHTAKBgNV |
| HRQEAwIBATAPBgNVHRwBAf8EBTADgQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAs37Jq |
| 3Htcehm6C2PKXOHekwTqTLOPWsYHfF68kYhdzcopDZBeoKE7jLRkRRGFDaR/tfUs |
| kwLSDNSQ8EwPb9PT1X8kmFn9QmJgWD6f6BzaH5ZZ9iBUwOcvrydlb/jnjdIZHQxs |
| fKOAceW5XX3f7DANC3qwYLsQZR/APkfV8nXjPYVUz1kKj04uq/BbQviInjyUYixN |
| xDx+GDWVVXccehcwAu983kAqP+JDaVQPBVksLuBXz2adrEWwvbLCnZeL3zH1IY9h |
| 6MFO6echpvGbU/H+dRX9UkhdJ7gdwKVD3RjfJl+DRVox9lz8Pbo5H699Tkv9/DQP |
| 9dMWxqhQlv23osLp |
| -----END X509 CRL----- |
| )"; |
| |
| // kUnknownCriticalCRL is kBasicCRL but with an unknown critical extension. |
| static const char kUnknownCriticalCRL[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBvDCBpQIBATANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoCMwITAKBgNV |
| HRQEAwIBATATBgwqhkiG9xIEAYS3CQABAf8EADANBgkqhkiG9w0BAQsFAAOCAQEA |
| GvBP0xqL509InMj/3493YVRV+ldTpBv5uTD6jewzf5XdaxEQ/VjTNe5zKnxbpAib |
| Kf7cwX0PMSkZjx7k7kKdDlEucwVvDoqC+O9aJcqVmM6GDyNb9xENxd0XCXja6MZC |
| yVgP4AwLauB2vSiEprYJyI1APph3iAEeDm60lTXX/wBM/tupQDDujKh2GPyvBRfJ |
| +wEDwGg3ICwvu4gO4zeC5qnFR+bpL9t5tOMAQnVZ0NWv+k7mkd2LbHdD44dxrfXC |
| nhtfERx99SDmC/jtUAJrGhtCO8acr7exCeYcduN7KKCm91OeCJKK6OzWst0Og1DB |
| kwzzU2rL3G65CrZ7H0SZsQ== |
| -----END X509 CRL----- |
| )"; |
| |
| // kUnknownCriticalCRL2 is kBasicCRL but with a critical issuing distribution |
| // point extension followed by an unknown critical extension |
| static const char kUnknownCriticalCRL2[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBzTCBtgIBATANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoDQwMjAKBgNV |
| HRQEAwIBATAPBgNVHRwBAf8EBTADgQH/MBMGDCqGSIb3EgQBhLcJAAEB/wQAMA0G |
| CSqGSIb3DQEBCwUAA4IBAQBgSogsC5kf2wzr+0hmZtmLXYd0itAiYO0Gh9AyaEOO |
| myJFuqICHBSLXXUgwNkTUa2x2I/ivyReVFV756VOlWoaV2wJUs0zeCeVBgC9ZFsq |
| 5a+8OGgXwgoYESFV5Y3QRF2a1Ytzfbw/o6xLXzTngvMsLOs12D4B5SkopyEZibF4 |
| tXlRZyvEudTg3CCrjNP+p/GV07nZ3wcMmKJwQeilgzFUV7NaVCCo9jvPBGp0RxAN |
| KNif7jmjK4hD5mswo/Eq5kxQIc+mTfuUFdgHuAu1hfLYe0YK+Hr4RFf6Qy4hl7Ne |
| YjqkkSVIcr87u+8AznwdstnQzsyD27Jt7SjVORkYRywi |
| -----END X509 CRL----- |
| )"; |
| |
| // kBadExtensionCRL is kBasicCRL but with an incorrectly-encoded issuing |
| // distribution point extension. |
| static const char kBadExtensionCRL[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBujCBowIBATANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoCEwHzAKBgNV |
| HRQEAwIBATARBgNVHRwBAf8EBzAFoQMBAf8wDQYJKoZIhvcNAQELBQADggEBAA+3 |
| i+5e5Ub8sccfgOBs6WVJFI9c8gvJjrJ8/dYfFIAuCyeocs7DFXn1n13CRZ+URR/Q |
| mVWgU28+xeusuSPYFpd9cyYTcVyNUGNTI3lwgcE/yVjPaOmzSZKdPakApRxtpKKQ |
| NN/56aQz3bnT/ZSHQNciRB8U6jiD9V30t0w+FDTpGaG+7bzzUH3UVF9xf9Ctp60A |
| 3mfLe0scas7owSt4AEFuj2SPvcE7yvdOXbu+IEv21cEJUVExJAbhvIweHXh6yRW+ |
| 7VVeiNzdIjkZjyTmAzoXGha4+wbxXyBRbfH+XWcO/H+8nwyG8Gktdu2QB9S9nnIp |
| o/1TpfOMSGhMyMoyPrk= |
| -----END X509 CRL----- |
| )"; |
| |
| // kAlgorithmMismatchCRL is kBasicCRL but with mismatched AlgorithmIdentifiers |
| // in the outer structure and signed portion. The signature reflects the signed |
| // portion. |
| static const char kAlgorithmMismatchCRL[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBpzCBkAIBATANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoA4wDDAKBgNV |
| HRQEAwIBATANBgkqhkiG9w0BAQwFAAOCAQEAnrBKKgvd9x9zwK9rtUvVeFeJ7+LN |
| ZEAc+a5oxpPNEsJx6hXoApYEbzXMxuWBQoCs5iEBycSGudct21L+MVf27M38KrWo |
| eOkq0a2siqViQZO2Fb/SUFR0k9zb8xl86Zf65lgPplALun0bV/HT7MJcl04Tc4os |
| dsAReBs5nqTGNEd5AlC1iKHvQZkM//MD51DspKnDpsDiUVi54h9C1SpfZmX8H2Vv |
| diyu0fZ/bPAM3VAGawatf/SyWfBMyKpoPXEG39oAzmjjOj8en82psn7m474IGaho |
| /vBbhl1ms5qQiLYPjm4YELtnXQoFyC72tBjbdFd/ZE9k4CNKDbxFUXFbkw== |
| -----END X509 CRL----- |
| )"; |
| |
| // kAlgorithmMismatchCRL2 is kBasicCRL but with mismatched AlgorithmIdentifiers |
| // in the outer structure and signed portion. The signature reflects the outer |
| // structure. |
| static const char kAlgorithmMismatchCRL2[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBpzCBkAIBATANBgkqhkiG9w0BAQwFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoA4wDDAKBgNV |
| HRQEAwIBATANBgkqhkiG9w0BAQsFAAOCAQEAjCWtU7AK8nQ5TCFfzvbU04MWNuLp |
| iZfqapRSRyMta4pyRomK773rEmJmYOc/ZNeIphVOlupMgGC2wyv5Z/SD1mxccJbv |
| SlUWciwjskjgvyyU9KnJ5xPgf3e3Fl3G0u9yJEFd4mg6fRavs5pEDX56b0f+SkG+ |
| Vl1FZU94Uylm2kCqk9fRpTxualPGP6dksj3Aitt4x2Vdni4sUfg9vIEEOx2jnisq |
| iLqpT94IdETCWAciE0dgbogdOOsNzMqSASfHM/XPigYLXpYgfaR8fca6OKDwFsVH |
| SrkFz8Se3F6mCHnbDzYElbmA46iKU2J12LTrso3Ewq/qHq0mebfp2z0y6g== |
| -----END X509 CRL----- |
| )"; |
| |
| // kEd25519Cert is a self-signed Ed25519 certificate. |
| static const char kEd25519Cert[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBkTCCAUOgAwIBAgIJAJwooam0UCDmMAUGAytlcDBFMQswCQYDVQQGEwJBVTET |
| MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ |
| dHkgTHRkMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1N1owRTELMAkGA1UE |
| BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp |
| ZGdpdHMgUHR5IEx0ZDAqMAUGAytlcAMhANdamAGCsQq31Uv+08lkBzoO4XLz2qYj |
| Ja8CGmj3B1Eao1AwTjAdBgNVHQ4EFgQUoux7eV+fJK2v3ah6QPU/lj1/+7UwHwYD |
| VR0jBBgwFoAUoux7eV+fJK2v3ah6QPU/lj1/+7UwDAYDVR0TBAUwAwEB/zAFBgMr |
| ZXADQQBuCzqji8VP9xU8mHEMjXGChX7YP5J664UyVKHKH9Z1u4wEbB8dJ3ScaWSL |
| r+VHVKUhsrvcdCelnXRrrSD7xWAL |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kEd25519CertNull is an invalid self-signed Ed25519 with an explicit NULL in |
| // the signature algorithm. |
| static const char kEd25519CertNull[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBlTCCAUWgAwIBAgIJAJwooam0UCDmMAcGAytlcAUAMEUxCzAJBgNVBAYTAkFV |
| MRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRz |
| IFB0eSBMdGQwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjBFMQswCQYD |
| VQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQg |
| V2lkZ2l0cyBQdHkgTHRkMCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPa |
| piMlrwIaaPcHURqjUDBOMB0GA1UdDgQWBBSi7Ht5X58kra/dqHpA9T+WPX/7tTAf |
| BgNVHSMEGDAWgBSi7Ht5X58kra/dqHpA9T+WPX/7tTAMBgNVHRMEBTADAQH/MAcG |
| AytlcAUAA0EA70uefNocdJohkKPNROKVyBuBD3LXMyvmdTklsaxSRY3PcZdOohlr |
| recgVPpVS7B+d9g4EwtZXIh4lodTBDHBBw== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kX25519 is the example X25519 certificate from |
| // https://tools.ietf.org/html/rfc8410#section-10.2 |
| static const char kX25519Cert[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBLDCB36ADAgECAghWAUdKKo3DMDAFBgMrZXAwGTEXMBUGA1UEAwwOSUVURiBUZX |
| N0IERlbW8wHhcNMTYwODAxMTIxOTI0WhcNNDAxMjMxMjM1OTU5WjAZMRcwFQYDVQQD |
| DA5JRVRGIFRlc3QgRGVtbzAqMAUGAytlbgMhAIUg8AmJMKdUdIt93LQ+91oNvzoNJj |
| ga9OukqY6qm05qo0UwQzAPBgNVHRMBAf8EBTADAQEAMA4GA1UdDwEBAAQEAwIDCDAg |
| BgNVHQ4BAQAEFgQUmx9e7e0EM4Xk97xiPFl1uQvIuzswBQYDK2VwA0EAryMB/t3J5v |
| /BzKc9dNZIpDmAgs3babFOTQbs+BolzlDUwsPrdGxO3YNGhW7Ibz3OGhhlxXrCe1Cg |
| w1AH9efZBw== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kSANTypesLeaf is a leaf certificate (signed by |kSANTypesRoot|) which |
| // contains SANS for example.com, test@example.com, 127.0.0.1, and |
| // https://example.com/. (The latter is useless for now since crypto/x509 |
| // doesn't deal with URI SANs directly.) |
| static const char kSANTypesLeaf[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIClzCCAgCgAwIBAgIJAOjwnT/iW+qmMA0GCSqGSIb3DQEBCwUAMCsxFzAVBgNV |
| BAoTDkJvcmluZ1NTTCBUZXN0MRAwDgYDVQQDEwdSb290IENBMB4XDTE1MDEwMTAw |
| MDAwMFoXDTI1MDEwMTAwMDAwMFowLzEXMBUGA1UEChMOQm9yaW5nU1NMIFRlc3Qx |
| FDASBgNVBAMTC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB |
| gQDbRn2TLhInBki8Bighq37EtqJd/h5SRYh6NkelCA2SQlvCgcC+l3mYQPtPbRT9 |
| KxOLwqUuZ9jUCZ7WIji3Sgt0cyvCNPHRk+WW2XR781ifbGE8wLBB1NkrKyQjd1sc |
| O711Xc4gVM+hY4cdHiTE8x0aUIuqthRD7ZendWL0FMhS1wIDAQABo4G+MIG7MA4G |
| A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYD |
| VR0TAQH/BAIwADAZBgNVHQ4EEgQQn5EWH0NDPkmm3m22gNefYDAbBgNVHSMEFDAS |
| gBBAN9cB+0AvuBx+VAQnjFkBMEQGA1UdEQQ9MDuCC2V4YW1wbGUuY29tgRB0ZXN0 |
| QGV4YW1wbGUuY29thwR/AAABhhRodHRwczovL2V4YW1wbGUuY29tLzANBgkqhkiG |
| 9w0BAQsFAAOBgQBtwJvY6+Tk6D6DOtDVaNoJ5y8E25CCuE/Ga4OuIcYJas+yLckf |
| dZwUV3GUG2oBXl2MrpUFxXd4hKBO1CmlBY+hZEeIx0Yp6QWK9P/vnZeydOTP26mk |
| jusJ2PqSmtKNU1Zcaba4d29oFejmOAfeguhR8AHpsc/zHEaS5Q9cJsuJcw== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // -----BEGIN RSA PRIVATE KEY----- |
| // MIICWwIBAAKBgQDbRn2TLhInBki8Bighq37EtqJd/h5SRYh6NkelCA2SQlvCgcC+ |
| // l3mYQPtPbRT9KxOLwqUuZ9jUCZ7WIji3Sgt0cyvCNPHRk+WW2XR781ifbGE8wLBB |
| // 1NkrKyQjd1scO711Xc4gVM+hY4cdHiTE8x0aUIuqthRD7ZendWL0FMhS1wIDAQAB |
| // AoGACwf7z0i1DxOI2zSwFimLghfyCSp8mgT3fbZ3Wj0SebYu6ZUffjceneM/AVrq |
| // gGYHYLOVHcWJqfkl7X3hPo9SDhzLx0mM545/q21ZWCwjhswH7WiCEqV2/zeDO9WU |
| // NIO1VU0VoLm0AQ7ZvwnyB+fpgF9kkkDtbBJW7XWrfNVtlnECQQD97YENpEJ3X1kj |
| // 3rrkrHWDkKAyoWWY1i8Fm7LnganC9Bv6AVwgn5ZlE/479aWHF8vbOFEA3pFPiNZJ |
| // t9FTCfpJAkEA3RCXjGI0Y6GALFLwEs+nL/XZAfJaIpJEZVLCVosYQOSaMS4SchfC |
| // GGYVquT7ZgKk9uvz89Fg87OtBMWS9lrkHwJADGkGLKeBhBoJ3kHtem2fVK3F1pOi |
| // xoR5SdnhNYVVyaxqjZ5xZTrHe+stOrr3uxGDqhQniVZXXb6/Ul0Egv1y2QJAVg/h |
| // kAujba4wIhFf2VLyOZ+yjil1ocPj0LZ5Zgvcs1bMGJ1hHP3W2HzVrqRaowoggui1 |
| // HpTC891dXGA2qKYV7QJAFDmT2A7OVvh3y4AEgzVwHrDmCMwMHKjCIntS7fjxrJnF |
| // YvJUG1zoHwUVrxxbR3DbpTODlktLcl/0b97D0IkH3w== |
| // -----END RSA PRIVATE KEY----- |
| |
| static const char kSANTypesRoot[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICTTCCAbagAwIBAgIIAj5CwoHlWuYwDQYJKoZIhvcNAQELBQAwKzEXMBUGA1UE |
| ChMOQm9yaW5nU1NMIFRlc3QxEDAOBgNVBAMTB1Jvb3QgQ0EwHhcNMTUwMTAxMDAw |
| MDAwWhcNMjUwMTAxMDAwMDAwWjArMRcwFQYDVQQKEw5Cb3JpbmdTU0wgVGVzdDEQ |
| MA4GA1UEAxMHUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6Q5/ |
| EQzmWuaGg3D2UQcuAngR9bIkkjjuJmICx5TxPqF3asCP1SJotl3iTNrghRE1wpJy |
| SY2BtIiXa7f8skRb2U0GcPkMxo/ps9+jaoRsQ1m+nbLQdpvD1/qZWcO45fNTA71J |
| 1rPMokP+rcILuQG4VimUAySnDSghKamulFtK+Z8CAwEAAaN6MHgwDgYDVR0PAQH/ |
| BAQDAgIEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8E |
| BTADAQH/MBkGA1UdDgQSBBBAN9cB+0AvuBx+VAQnjFkBMBsGA1UdIwQUMBKAEEA3 |
| 1wH7QC+4HH5UBCeMWQEwDQYJKoZIhvcNAQELBQADgYEAc4N6hTE62/3gwg+kyc2f |
| c/Jj1mHrOt+0NRaBnmvbmNpsEjHS96Ef4Wt/ZlPXPkkv1C1VosJnOIMF3Q522wRH |
| bqaxARldS12VAa3gcWisDWD+SqSyDxjyojz0XDiJkTrFuCTCUiZO+1GLB7SO10Ms |
| d5YVX0c90VMnUhF/dlrqS9U= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // -----BEGIN RSA PRIVATE KEY----- |
| // MIICXAIBAAKBgQDpDn8RDOZa5oaDcPZRBy4CeBH1siSSOO4mYgLHlPE+oXdqwI/V |
| // Imi2XeJM2uCFETXCknJJjYG0iJdrt/yyRFvZTQZw+QzGj+mz36NqhGxDWb6dstB2 |
| // m8PX+plZw7jl81MDvUnWs8yiQ/6twgu5AbhWKZQDJKcNKCEpqa6UW0r5nwIDAQAB |
| // AoGALEF5daZqc+aEsp8X1yky3nsoheyPL0kqSBWii33IFemZgKcSaRnAoqjPWWLS |
| // 8dHj0I/4rej2MW8iuezVSpDak9tK5boHORC3w4p/wifkizQkLt1DANxTVbzcKvrt |
| // aZ7LjVaKkhjRJbLddniowFHkkWVbUccjvzcUd7Y2VuLbAhECQQDq4FE88aHio8zg |
| // bxSd0PwjEFwLYQTR19u812SoR8PmR6ofIL+pDwOV+fVs+OGcAAOgkhIukOrksQ4A |
| // 1cKtnyhXAkEA/gRI+u3tZ7UE1twIkBfZ6IvCdRodkPqHAYIxMRLzL+MhyZt4MEGc |
| // Ngb/F6U9/WOBFnoR/PI7IwE3ejutzKcL+QJBAKh+6eilk7QKPETZi1m3/dmNt+p1 |
| // 3EZJ65pqjwxmB3Rg/vs7vCMk4TarTdSyKu+F1xRPFfoP/mK3Xctdjj6NyhsCQAYF |
| // 7/0TOzfkUPMPUJyqFB6xgbDpJ55ScnUUsznoqx+NkTWInDb4t02IqO/UmT2y6FKy |
| // Hk8TJ1fTJY+ebqaVp3ECQApx9gQ+n0zIhx97FMUuiRse73xkcW4+pZ8nF+8DmeQL |
| // /JKuuFGmzkG+rUbXFmo/Zg2ozVplw71NnQJ4znPsf7A= |
| // -----END RSA PRIVATE KEY----- |
| |
| // The following four certificates were generated with this Go program, varying |
| // |includeNetscapeExtension| and defining rootKeyPEM and rootCertPEM to be |
| // strings containing the kSANTypesRoot, above. |
| |
| // package main |
| |
| // import ( |
| // "crypto/ecdsa" |
| // "crypto/elliptic" |
| // "crypto/rand" |
| // "crypto/x509" |
| // "crypto/x509/pkix" |
| // "encoding/asn1" |
| // "encoding/pem" |
| // "math/big" |
| // "os" |
| // "time" |
| // ) |
| |
| // const includeNetscapeExtension = true |
| |
| // func main() { |
| // block, _ := pem.Decode([]byte(rootKeyPEM)) |
| // rootPriv, _ := x509.ParsePKCS1PrivateKey(block.Bytes) |
| // block, _ = pem.Decode([]byte(rootCertPEM)) |
| // root, _ := x509.ParseCertificate(block.Bytes) |
| |
| // interTemplate := &x509.Certificate{ |
| // SerialNumber: big.NewInt(2), |
| // Subject: pkix.Name{ |
| // CommonName: "No Basic Constraints (Netscape)", |
| // }, |
| // NotBefore: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), |
| // NotAfter: time.Date(2099, time.January, 1, 0, 0, 0, 0, time.UTC), |
| // } |
| |
| // if includeNetscapeExtension { |
| // interTemplate.ExtraExtensions = []pkix.Extension{ |
| // pkix.Extension{ |
| // Id: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 113730, 1, 1}), |
| // Value: []byte{0x03, 0x02, 2, 0x04}, |
| // }, |
| // } |
| // } else { |
| // interTemplate.KeyUsage = x509.KeyUsageCertSign |
| // } |
| |
| // interKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| |
| // interDER, err := x509.CreateCertificate(rand.Reader, interTemplate, root, &interKey.PublicKey, rootPriv) |
| // if err != nil { |
| // panic(err) |
| // } |
| |
| // pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: interDER}) |
| |
| // inter, _ := x509.ParseCertificate(interDER) |
| |
| // leafTemplate := &x509.Certificate{ |
| // SerialNumber: big.NewInt(3), |
| // Subject: pkix.Name{ |
| // CommonName: "Leaf from CA with no Basic Constraints", |
| // }, |
| // NotBefore: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), |
| // NotAfter: time.Date(2099, time.January, 1, 0, 0, 0, 0, time.UTC), |
| // BasicConstraintsValid: true, |
| // } |
| // leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| |
| // leafDER, err := x509.CreateCertificate(rand.Reader, leafTemplate, inter, &leafKey.PublicKey, interKey) |
| // if err != nil { |
| // panic(err) |
| // } |
| |
| // pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: leafDER}) |
| // } |
| |
| // kNoBasicConstraintsCertSignIntermediate doesn't have isCA set, but contains |
| // certSign in the keyUsage. |
| static const char kNoBasicConstraintsCertSignIntermediate[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBqjCCAROgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowHzEdMBsGA1UEAxMUTm8gQmFzaWMgQ29uc3RyYWludHMw |
| WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASEFMblfxIEDO8My7wHtHWTuDzNyID1 |
| OsPkMGkn32O/pSyXxXuAqDeFoMVffUMTyfm8JcYugSEbrv2qEXXM4bZRoy8wLTAO |
| BgNVHQ8BAf8EBAMCAgQwGwYDVR0jBBQwEoAQQDfXAftAL7gcflQEJ4xZATANBgkq |
| hkiG9w0BAQsFAAOBgQC1Lh6hIAm3K5kRh5iIydU0YAEm7eV6ZSskERDUq3DLJyl9 |
| ZUZCHUzvb464dkwZjeNzaUVS1pdElJslwX3DtGgeJLJGCnk8zUjBjaNrrDm0kzPW |
| xKt/6oif1ci/KCKqKNXJAIFbc4e+IiBpenwpxHk3If4NM+Ek0nKoO8Uj0NkgTQ== |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kNoBasicConstraintsCertSignLeaf[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBUDCB96ADAgECAgEDMAoGCCqGSM49BAMCMB8xHTAbBgNVBAMTFE5vIEJhc2lj |
| IENvbnN0cmFpbnRzMCAXDTAwMDEwMTAwMDAwMFoYDzIwOTkwMTAxMDAwMDAwWjAx |
| MS8wLQYDVQQDEyZMZWFmIGZyb20gQ0Egd2l0aCBubyBCYXNpYyBDb25zdHJhaW50 |
| czBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEsYPMwzdJKjB+2gpC90ib2ilHoB |
| w/arQ6ikUX0CNUDDaKaOu/jF39ogzVlg4lDFrjCKShSfCCcrwgONv70IZGijEDAO |
| MAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0EAwIDSAAwRQIgbV7R99yM+okXSIs6Fp3o |
| eCOXiDL60IBxaTOcLS44ywcCIQDbn87Gj5cFgHBYAkzdHqDsyGXkxQTHDq9jmX24 |
| Djy3Zw== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kNoBasicConstraintsNetscapeCAIntermediate doesn't have isCA set, but contains |
| // a Netscape certificate-type extension that asserts a type of "SSL CA". |
| static const char kNoBasicConstraintsNetscapeCAIntermediate[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBuDCCASGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowKjEoMCYGA1UEAxMfTm8gQmFzaWMgQ29uc3RyYWludHMg |
| KE5ldHNjYXBlKTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCeMbmCaOtMzXBqi |
| PrCdNOH23CkaawUA+pAezitAN4RXS1O2CGK5sJjGPVVeogROU8G7/b+mU+ciZIzH |
| 1PP8FJKjMjAwMBsGA1UdIwQUMBKAEEA31wH7QC+4HH5UBCeMWQEwEQYJYIZIAYb4 |
| QgEBBAQDAgIEMA0GCSqGSIb3DQEBCwUAA4GBAAgNWjh7cfBTClTAk+Ml//5xb9Ju |
| tkBhG6Rm+kkMD+qiSMO6t7xS7CsA0+jIBjkdEYaLZ3oxtQCBdZsVNxUvRxZ0AUfF |
| G3DtRFTsrI1f7IQhpMuqEMF4shPW+5x54hrq0Fo6xMs6XoinJZcTUaaB8EeXRF6M |
| P9p6HuyLrmn0c/F0 |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kNoBasicConstraintsNetscapeCALeaf[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBXDCCAQKgAwIBAgIBAzAKBggqhkjOPQQDAjAqMSgwJgYDVQQDEx9ObyBCYXNp |
| YyBDb25zdHJhaW50cyAoTmV0c2NhcGUpMCAXDTAwMDEwMTAwMDAwMFoYDzIwOTkw |
| MTAxMDAwMDAwWjAxMS8wLQYDVQQDEyZMZWFmIGZyb20gQ0Egd2l0aCBubyBCYXNp |
| YyBDb25zdHJhaW50czBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDlJKolDu3R2 |
| tPqSDycr0QJcWhxdBv76V0EEVflcHRxED6vAioTEcnQszt1OfKtBZvjlo0yp6i6Q |
| DaYit0ZInmWjEDAOMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0EAwIDSAAwRQIhAJsh |
| aZL6BHeEfoUBj1oZ2Ln91qzj3UCVMJ+vrmwAFdYyAiA3wp2JphgchvmoUFuzPXwj |
| XyPwWPbymSTpzKhB4xB7qQ== |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kSelfSignedMismatchAlgorithms[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIFMjCCAxqgAwIBAgIJAL0mG5fOeJ7xMA0GCSqGSIb3DQEBDQUAMC0xCzAJBgNV |
| BAYTAkdCMQ8wDQYDVQQHDAZMb25kb24xDTALBgNVBAoMBFRlc3QwIBcNMTgwOTE3 |
| MTIxNzU3WhgPMjExODA4MjQxMjE3NTdaMC0xCzAJBgNVBAYTAkdCMQ8wDQYDVQQH |
| DAZMb25kb24xDTALBgNVBAoMBFRlc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw |
| ggIKAoICAQDCMhBrRAGGw+n2GdctBr/cEK4FZA6ajiHjihgpCHoSBdyL4R2jGKLS |
| g0WgaMXa1HpkKN7LcIySosEBPlmcRkr1RqbEvQStOSvoFCXYvtx3alM6HTbXMcDR |
| mqoKoABP6LXsPSoMWIgqMtP2X9EOppzHVIK1yFYFfbIlvYUV2Ka+MuMe0Vh5wvD1 |
| 4GanPb+cWSKgdRSVQovCCMY3yWtZKVEaxRpCsk/mYYIFWz0tcgMjIKwDx1XXgiAV |
| nU6NK43xbaw3XhtnaD/pv9lhTTbNrlcln9LjTD097BaK4R+1AEPHnpfxA9Ui3upn |
| kbsNUdGdOB0ksZi/vd7lh833YgquQUIAhYrbfvq/HFCpVV1gljzlS3sqULYpLE// |
| i3OsuL2mE+CYIJGpIi2GeJJWXciNMTJDOqTn+fRDtVb4RPp4Y70DJirp7XzaBi3q |
| H0edANCzPSRCDbZsOhzIXhXshldiXVRX666DDlbMQgLTEnNKrkwv6DmU8o15XQsb |
| 8k1Os2YwXmkEOxUQ7AJZXVTZSf6UK9Znmdq1ZrHjybMfRUkHVxJcnKvrxfryralv |
| gzfvu+D6HuxrCo3Ojqa+nDgIbxKEBtdrcsMhq1jWPFhjwo1fSadAkKOfdCAuXJRD |
| THg3b4Sf+W7Cpc570YHrIpBf7WFl2XsPcEM0mJZ5+yATASCubNozQwIDAQABo1Mw |
| UTAdBgNVHQ4EFgQUES0hupZSqY21JOba10QyZuxm91EwHwYDVR0jBBgwFoAUES0h |
| upZSqY21JOba10QyZuxm91EwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsF |
| AAOCAgEABTN5S30ng/RMpBweDm2N561PdpaCdiRXtAFCRVWR2mkDYC/Xj9Vqe6be |
| PyM7L/5OKYVjzF1yJu67z/dx+ja5o+41g17jdqla7hyPx+9B4uRyDh+1KJTa+duj |
| mw/aA1LCr6O6W4WizDOsChJ6FaB2Y1+GlFnKWb5nUdhVJqXQE1WOX9dZnw8Y4Npd |
| VmAsjWot0BZorJrt3fwfcv3QfA896twkbo7Llv/8qzg4sXZXZ4ZtgAOqnPngiSn+ |
| JT/vYCXZ406VvAFpFqMcVz2dO/VGuL8lGIMHRKNyafrsV81EzH1W/XmRWOgvgj6r |
| yQI63ln/AMY72HQ97xLkE1xKunGz6bK5Ug5+O43Uftc4Mb6MUgzo+ZqEQ3Ob+cAV |
| cvjmtwDaPO/O39O5Xq0tLTlkn2/cKf4OQ6S++GDxzyRVHh5JXgP4j9+jfZY57Woy |
| R1bE7N50JjY4cDermBJKdlBIjL7UPhqmLyaG7V0hBitFlgGBUCcJtJOV0xYd5aF3 |
| pxNkvMXhBmh95fjxJ0cJjpO7tN1RAwtMMNgsl7OUbuVRQCHOPW5DgP5qY21jDeRn |
| BY82382l+9QzykmJLI5MZnmj4BA9uIDCwMtoTTvP++SsvhUAbuvh7MOOUQL0EY4m |
| KStYq7X9PKseN+PvmfeoffIKc5R/Ha39oi7cGMVHCr8aiEhsf94= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kCommonNameWithSANs is a leaf certificate signed by kSANTypesRoot, with |
| // *.host1.test as the common name and a SAN list of *.host2.test and |
| // foo.host3.test. |
| static const char kCommonNameWithSANs[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIB2zCCAUSgAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowNzEeMBwGA1UEChMVQ29tbW9uIG5hbWUgd2l0aCBTQU5z |
| MRUwEwYDVQQDDAwqLmhvc3QxLnRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC |
| AASgWzfnFnpQrokSLIC+LhCKJDUAY/2usfIDpOnafYoYCasbYetkmOslgyY4Nn07 |
| zjvjNROprA/0bdULXAkdL9bNo0gwRjAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQn |
| jFkBMCcGA1UdEQQgMB6CDCouaG9zdDIudGVzdIIOZm9vLmhvc3QzLnRlc3QwDQYJ |
| KoZIhvcNAQELBQADgYEAtv2e3hBhsslXB1HTxgusjoschWOVtvGZUaYlhkKzKTCL |
| 4YpDn50BccnucBU/b9phYvaEZtyzOv4ZXhxTGyLnLrIVB9x5ikfCcfl+LNYNjDwM |
| enm/h1zOfJ7wXLyscD4kU29Wc/zxBd70thIgLYn16CC1S9NtXKsXXDXv5VVH/bg= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kCommonNameWithSANs is a leaf certificate signed by kSANTypesRoot, with |
| // *.host1.test as the common name and no SAN list. |
| static const char kCommonNameWithoutSANs[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBtTCCAR6gAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowOjEhMB8GA1UEChMYQ29tbW9uIG5hbWUgd2l0aG91dCBT |
| QU5zMRUwEwYDVQQDDAwqLmhvc3QxLnRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB |
| BwNCAARt2vjlIrPE+kr11VS1rRP/AYQu4fvf1bNw/K9rwYlVBhmLMPYasEmpCtKE |
| 0bDIFydtDYC3wZDpSS+YiaG40sdAox8wHTAbBgNVHSMEFDASgBBAN9cB+0AvuBx+ |
| VAQnjFkBMA0GCSqGSIb3DQEBCwUAA4GBAHRbIeaCEytOpJpw9O2dlB656AHe1+t5 |
| 4JiS5mvtzoVOLn7fFk5EFQtZS7sG1Uc2XjlSw+iyvFoTFEqfKyU/mIdc2vBuPwA2 |
| +YXT8aE4S+UZ9oz5j0gDpikGnkSCW0cyHD8L8fntNjaQRSaM482JpmtdmuxClmWO |
| pFFXI2B5usgI |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kCommonNameWithEmailSAN is a leaf certificate signed by kSANTypesRoot, with |
| // *.host1.test as the common name and the email address test@host2.test in the |
| // SAN list. |
| static const char kCommonNameWithEmailSAN[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBvDCCASWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowFzEVMBMGA1UEAwwMKi5ob3N0MS50ZXN0MFkwEwYHKoZI |
| zj0CAQYIKoZIzj0DAQcDQgAEtevOxcTjpPzlNGoUMFfZyr1k03/Hiuh+EsnuScDs |
| 8XLKi6fDkvSaDClI99ycabQZRPIrvyT+dglDC6ugQd+CYqNJMEcwDAYDVR0TAQH/ |
| BAIwADAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQnjFkBMBoGA1UdEQQTMBGBD3Rl |
| c3RAaG9zdDIudGVzdDANBgkqhkiG9w0BAQsFAAOBgQCGbqb78OWJWl4zb+qw0Dz2 |
| HJgZZJt6/+nNG/XJKdaYeS4eofsbwsJI4fuuOF6ZvYCJxVNtGqdfZDgycvFA9hjv |
| NGosBF1/spP17cmzTahLjxs71jDvHV/EQJbKGl/Zpta1Em1VrzSrwoOFabPXzZTJ |
| aet/mER21Z/9ZsTUoJQPJw== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kCommonNameWithIPSAN is a leaf certificate signed by kSANTypesRoot, with |
| // *.host1.test as the common name and the IP address 127.0.0.1 in the |
| // SAN list. |
| static const char kCommonNameWithIPSAN[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBsTCCARqgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowFzEVMBMGA1UEAwwMKi5ob3N0MS50ZXN0MFkwEwYHKoZI |
| zj0CAQYIKoZIzj0DAQcDQgAEFKrgkxm8PysXbwnHQeTD3p8YY0+sY4ssnZgmj8wX |
| KTyn893fdBHWlz71GO6t82wMTF5d+ZYwI2XU52pfl4SB2aM+MDwwDAYDVR0TAQH/ |
| BAIwADAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQnjFkBMA8GA1UdEQQIMAaHBH8A |
| AAEwDQYJKoZIhvcNAQELBQADgYEAQWZ8Oj059ZjS109V/ijMYT28xuAN5n6HHxCO |
| DopTP56Zu9+gme5wTETWEfocspZvgecoUOcedTFoKSQ7JafO09NcVLA+D6ddYpju |
| mgfuiLy9dDhqvX/NHaLBMxOBWWbOLwWE+ibyX+pOzjWRCw1L7eUXOr6PhZAOQsmU |
| D0+O6KI= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kConstrainedIntermediate is an intermediate signed by kSANTypesRoot, with |
| // permitted DNS names of permitted1.test and foo.permitted2.test and an |
| // excluded DNS name of excluded.permitted1.test. Its private key is: |
| // |
| // -----BEGIN PRIVATE KEY----- |
| // MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTXUM4tJWM7OzATty |
| // JhNOfIv/d8heWFBeKOfMR+RfaROhRANCAASbbbWYiN6mn+BCpg4XNpibOH0D/DN4 |
| // kZ5C/Ml2YVomC9T83OKk2CzB8fPAabPb4P4Vv+fIabpEfjWS5nzKLY1y |
| // -----END PRIVATE KEY----- |
| static const char kConstrainedIntermediate[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICDjCCAXegAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowKDEmMCQGA1UEAxMdTmFtZSBDb25zdHJhaW50cyBJbnRl |
| cm1lZGlhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASbbbWYiN6mn+BCpg4X |
| NpibOH0D/DN4kZ5C/Ml2YVomC9T83OKk2CzB8fPAabPb4P4Vv+fIabpEfjWS5nzK |
| LY1yo4GJMIGGMA8GA1UdEwEB/wQFMAMBAf8wGwYDVR0jBBQwEoAQQDfXAftAL7gc |
| flQEJ4xZATBWBgNVHR4BAf8ETDBKoCowEYIPcGVybWl0dGVkMS50ZXN0MBWCE2Zv |
| by5wZXJtaXR0ZWQyLnRlc3ShHDAaghhleGNsdWRlZC5wZXJtaXR0ZWQxLnRlc3Qw |
| DQYJKoZIhvcNAQELBQADgYEAFq1Ka05hiKREwRpSceQPzIIH4B5a5IVBg5/EvmQI |
| 9V0fXyAE1GmahPt70sIBxIgzNTEaY8P/IoOuCdlZWe0msmyEO3S6YSAzOWR5Van6 |
| cXmFM1uMd95TlkxUMRdV+jKJTvG6R/BM2zltaV7Xt662k5HtzT5Svw0rZlFaggZz |
| UyM= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kCommonNamePermittedLeaf is a leaf certificate signed by |
| // kConstrainedIntermediate. Its common name is permitted by the name |
| // constraints. |
| static const char kCommonNamePermittedLeaf[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBaDCCAQ2gAwIBAgIBAzAKBggqhkjOPQQDAjAoMSYwJAYDVQQDEx1OYW1lIENv |
| bnN0cmFpbnRzIEludGVybWVkaWF0ZTAgFw0wMDAxMDEwMDAwMDBaGA8yMDk5MDEw |
| MTAwMDAwMFowPjEeMBwGA1UEChMVQ29tbW9uIG5hbWUgcGVybWl0dGVkMRwwGgYD |
| VQQDExNmb28ucGVybWl0dGVkMS50ZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD |
| QgAENX5Ycs8q8MRzPYUz6DqLHhJR3wcmniFRgkiEa7MxE/mRe00y0VGwH7xi7Aoc |
| emXPrtD4JwN5bssbcxWGAKYYzaMQMA4wDAYDVR0TAQH/BAIwADAKBggqhkjOPQQD |
| AgNJADBGAiEAtsnWuRQXtw2xbieC78Y8SVEtTjcZUx8uZyQe1GPLfGICIQDR4fNY |
| yg3PC94ydPNQZVsFxAne32CbonWWsokalTFpUQ== |
| -----END CERTIFICATE----- |
| )"; |
| static const char kCommonNamePermitted[] = "foo.permitted1.test"; |
| |
| // kCommonNameNotPermittedLeaf is a leaf certificate signed by |
| // kConstrainedIntermediate. Its common name is not permitted by the name |
| // constraints. |
| static const char kCommonNameNotPermittedLeaf[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBazCCARCgAwIBAgIBBDAKBggqhkjOPQQDAjAoMSYwJAYDVQQDEx1OYW1lIENv |
| bnN0cmFpbnRzIEludGVybWVkaWF0ZTAgFw0wMDAxMDEwMDAwMDBaGA8yMDk5MDEw |
| MTAwMDAwMFowQTEiMCAGA1UEChMZQ29tbW9uIG5hbWUgbm90IHBlcm1pdHRlZDEb |
| MBkGA1UEAxMSbm90LXBlcm1pdHRlZC50ZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0D |
| AQcDQgAEzfghKuWf0JoXb0Drp09C3yXMSQQ1byt+AUaymvsHOWsxQ9v1Q+vkF/IM |
| HRqGTk2TyxrB2iClVEn/Uu+YtYox1KMQMA4wDAYDVR0TAQH/BAIwADAKBggqhkjO |
| PQQDAgNJADBGAiEAxaUslxmoWL1tIvnDz7gDkto/HcmdU0jHVuUQLXcCG8wCIQCN |
| 5xZjitlCQU8UB5qSu9wH4B+0JcVO3Ss4Az76HEJWMw== |
| -----END CERTIFICATE----- |
| )"; |
| static const char kCommonNameNotPermitted[] = "not-permitted.test"; |
| |
| // kCommonNameNotPermittedWithSANsLeaf is a leaf certificate signed by |
| // kConstrainedIntermediate. Its common name is not permitted by the name |
| // constraints but it has a SAN list. |
| static const char kCommonNameNotPermittedWithSANsLeaf[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBqTCCAU+gAwIBAgIBBjAKBggqhkjOPQQDAjAoMSYwJAYDVQQDEx1OYW1lIENv |
| bnN0cmFpbnRzIEludGVybWVkaWF0ZTAgFw0wMDAxMDEwMDAwMDBaGA8yMDk5MDEw |
| MTAwMDAwMFowSzEsMCoGA1UEChMjQ29tbW9uIG5hbWUgbm90IHBlcm1pdHRlZCB3 |
| aXRoIFNBTlMxGzAZBgNVBAMTEm5vdC1wZXJtaXR0ZWQudGVzdDBZMBMGByqGSM49 |
| AgEGCCqGSM49AwEHA0IABKsn9wOApXFHrqhLdQgbFSeaSoAIbxgO0zVSRZUb5naR |
| 93zoL3MFOvZEF8xiEqh7le+l3XuUig0fwqpcsZzRNJajRTBDMAwGA1UdEwEB/wQC |
| MAAwMwYDVR0RBCwwKoITZm9vLnBlcm1pdHRlZDEudGVzdIITZm9vLnBlcm1pdHRl |
| ZDIudGVzdDAKBggqhkjOPQQDAgNIADBFAiACk+1f184KkKAXuntmrz+Ygcq8MiZl |
| 4delx44FtcNaegIhAIA5nYfzxNcTXxDo3U+x1vSLH6Y7faLvHiFySp7O//q+ |
| -----END CERTIFICATE----- |
| )"; |
| static const char kCommonNameNotPermittedWithSANs[] = "not-permitted.test"; |
| |
| // kCommonNameNotDNSLeaf is a leaf certificate signed by |
| // kConstrainedIntermediate. Its common name is not a DNS name. |
| static const char kCommonNameNotDNSLeaf[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBYTCCAQagAwIBAgIBCDAKBggqhkjOPQQDAjAoMSYwJAYDVQQDEx1OYW1lIENv |
| bnN0cmFpbnRzIEludGVybWVkaWF0ZTAgFw0wMDAxMDEwMDAwMDBaGA8yMDk5MDEw |
| MTAwMDAwMFowNzEcMBoGA1UEChMTQ29tbW9uIG5hbWUgbm90IEROUzEXMBUGA1UE |
| AxMOTm90IGEgRE5TIG5hbWUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASnueyc |
| Zxtnw5ke2J2T0/LwAK37auQP/RSFd9mem+BJVbgviawtAlignJmafp7Zw4/GdYEJ |
| Vm8qlriOJtluvXGcoxAwDjAMBgNVHRMBAf8EAjAAMAoGCCqGSM49BAMCA0kAMEYC |
| IQChUAmVNI39VHe0zemRE09VDcSEgOxr1nTvjLcg/Q8pVQIhAJYZnJI0YZAi05QH |
| RHNlAkTK2TnUaVn3fGSylaLiFS1r |
| -----END CERTIFICATE----- |
| )"; |
| static const char kCommonNameNotDNS[] = "Not a DNS name"; |
| |
| // The following six certificates are issued by |kSANTypesRoot| and have |
| // different extended key usage values. They were created with the following |
| // Go program: |
| // |
| // func main() { |
| // block, _ := pem.Decode([]byte(rootKeyPEM)) |
| // rootPriv, _ := x509.ParsePKCS1PrivateKey(block.Bytes) |
| // block, _ = pem.Decode([]byte(rootCertPEM)) |
| // root, _ := x509.ParseCertificate(block.Bytes) |
| // |
| // leafTemplate := &x509.Certificate{ |
| // SerialNumber: big.NewInt(3), |
| // Subject: pkix.Name{ |
| // CommonName: "EKU msSGC", |
| // }, |
| // NotBefore: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), |
| // NotAfter: time.Date(2099, time.January, 1, 0, 0, 0, 0, time.UTC), |
| // BasicConstraintsValid: true, |
| // ExtKeyUsage: []x509.ExtKeyUsage{FILL IN HERE}, |
| // } |
| // leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| // leafDER, err := x509.CreateCertificate(rand.Reader, leafTemplate, root, &leafKey.PublicKey, rootPriv) |
| // if err != nil { |
| // panic(err) |
| // } |
| // pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: leafDER}) |
| // } |
| |
| static const char kMicrosoftSGCCert[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBtDCCAR2gAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowFDESMBAGA1UEAxMJRUtVIG1zU0dDMFkwEwYHKoZIzj0C |
| AQYIKoZIzj0DAQcDQgAEEn61v3Vs+q6bTyyRnrJvuKBE8PTNVLbXGB52jig4Qse2 |
| mGygNEysS0uzZ0luz+rn2hDRUFL6sHLUs1d8UMbI/6NEMEIwFQYDVR0lBA4wDAYK |
| KwYBBAGCNwoDAzAMBgNVHRMBAf8EAjAAMBsGA1UdIwQUMBKAEEA31wH7QC+4HH5U |
| BCeMWQEwDQYJKoZIhvcNAQELBQADgYEAgDQI9RSo3E3ZVnU71TV/LjG9xwHtfk6I |
| rlNnlJJ0lsTHAuMc1mwCbzhtsmasetwYlIa9G8GFWB9Gh/QqHA7G649iGGmXShqe |
| aVDuWgeSEJxBPE2jILoMm4pEYF7jfonTn7XXX6O78yuSlP+NPIU0gUKHkWZ1sWk0 |
| cC4l0r/6jik= |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kNetscapeSGCCert[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBszCCARygAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowFDESMBAGA1UEAxMJRUtVIG1zU0dDMFkwEwYHKoZIzj0C |
| AQYIKoZIzj0DAQcDQgAE3NbT+TnBfq1DWJCezjaUL52YhDU7cOkI2S2PoWgJ1v7x |
| kKLwBonUFZjppZs69SyBHeJdti+KoJ3qTW+hCG08EaNDMEEwFAYDVR0lBA0wCwYJ |
| YIZIAYb4QgQBMAwGA1UdEwEB/wQCMAAwGwYDVR0jBBQwEoAQQDfXAftAL7gcflQE |
| J4xZATANBgkqhkiG9w0BAQsFAAOBgQBuiyVcfazekHkCWksxdFmjPmMtWCxFjkzc |
| 8VBxFE0CfSHQAfZ8J7tXd1FbAq/eXdZvvo8v0JB4sOM4Ex1ob1fuvDFHdSAHAD7W |
| dhKIjJyzVojoxjCjyue0XMeEPl7RiqbdxoS/R5HFAqAF0T2OeQAqP9gTpOXoau1M |
| RQHX6HQJJg== |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kServerEKUCert[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBsjCCARugAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowFDESMBAGA1UEAxMJRUtVIG1zU0dDMFkwEwYHKoZIzj0C |
| AQYIKoZIzj0DAQcDQgAEDd35i+VWPwIOKLrLWTuP5cqD+yJDB5nujEzPgkXP5LKJ |
| SZRbHTqTdpYZB2jy6y90RY2Bsjx7FfZ7nN5G2g1GOKNCMEAwEwYDVR0lBAwwCgYI |
| KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQn |
| jFkBMA0GCSqGSIb3DQEBCwUAA4GBAIKmbMBjuivL/rxDu7u7Vr3o3cdmEggBJxwL |
| iatNW3x1wg0645aNYOktW/iQ7mAAiziTY73GFyfiJDWqnY+CwA94ZWyQidjHdN/I |
| 6BR52sN/dkYEoInYEbmDNMc/if+T0yqeBQLP4BeKLiT8p0qqaimae6LgibS19hDP |
| 2hoEMdz2 |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kServerEKUPlusMicrosoftSGCCert[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBvjCCASegAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowFDESMBAGA1UEAxMJRUtVIG1zU0dDMFkwEwYHKoZIzj0C |
| AQYIKoZIzj0DAQcDQgAEDO1MYPxq+U4oXMIK8UnsS4C696wpcu4UOmcMJJ5CUd5Z |
| ZpJShN6kYKnrb3GK/6xEgbUGntmrzSRG5FYqk6QgD6NOMEwwHwYDVR0lBBgwFgYI |
| KwYBBQUHAwEGCisGAQQBgjcKAwMwDAYDVR0TAQH/BAIwADAbBgNVHSMEFDASgBBA |
| N9cB+0AvuBx+VAQnjFkBMA0GCSqGSIb3DQEBCwUAA4GBAHOu2IBa4lHzVGS36HxS |
| SejUE87Ji1ysM6BgkYbfxfS9MuV+J3UnqH57JjbH/3CFl4ZDWceF6SGBSCn8LqKa |
| KHpwoNFU3zA99iQzVJgbUyN0PbKwHEanLyKDJZyFk71R39ToxhSNQgaQYjZYCy1H |
| 5V9oXd1bodEqVsOZ/mur24Ku |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kAnyEKU[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBrjCCARegAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowFDESMBAGA1UEAxMJRUtVIG1zU0dDMFkwEwYHKoZIzj0C |
| AQYIKoZIzj0DAQcDQgAE9nsLABDporlTvx1OBUc4Hd5vxfX+8nS/OhbHmKtFLYNu |
| 1CLLrImbwMQYD2G+PgLO6sQHmASq2jmJKp6ZWsRkTqM+MDwwDwYDVR0lBAgwBgYE |
| VR0lADAMBgNVHRMBAf8EAjAAMBsGA1UdIwQUMBKAEEA31wH7QC+4HH5UBCeMWQEw |
| DQYJKoZIhvcNAQELBQADgYEAxgjgn1SAzQ+2GeCicZ5ndvVhKIeFelGCQ989XTVq |
| uUbAYBW6v8GXNuVzoXYxDgNSanF6U+w+INrJ6daKVrIxAxdk9QFgBXqJoupuRAA3 |
| /OqnmYux0EqOTLbTK1P8DhaiaD0KV6dWGUwzqsgBmPkZ0lgNaPjvb1mKV3jhBkjz |
| L6A= |
| -----END CERTIFICATE----- |
| )"; |
| |
| static const char kNoEKU[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBnTCCAQagAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp |
| bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y |
| MDk5MDEwMTAwMDAwMFowFDESMBAGA1UEAxMJRUtVIG1zU0dDMFkwEwYHKoZIzj0C |
| AQYIKoZIzj0DAQcDQgAEpSFSqbYY86ZcMamE606dqdyjWlwhSHKOLUFsUUIzkMPz |
| KHRu/x3Yzi8+Hm8eFK/TnCbkpYsYw4hIw00176dYzaMtMCswDAYDVR0TAQH/BAIw |
| ADAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQnjFkBMA0GCSqGSIb3DQEBCwUAA4GB |
| AHvYzynIkjLThExHRS+385hfv4vgrQSMmCM1SAnEIjSBGsU7RPgiGAstN06XivuF |
| T1fNugRmTu4OtOIbfdYkcjavJufw9hR9zWTt77CNMTy9XmOZLgdS5boFTtLCztr3 |
| TXHOSQQD8Dl4BK0wOet+TP6LBEjHlRFjAqK4bu9xpxV2 |
| -----END CERTIFICATE----- |
| )"; |
| |
| // CertFromPEM parses the given, NUL-terminated PEM block and returns an |
| // |X509*|. |
| static bssl::UniquePtr<X509> CertFromPEM(const char *pem) { |
| bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem))); |
| return bssl::UniquePtr<X509>( |
| PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); |
| } |
| |
| // CRLFromPEM parses the given, NUL-terminated PEM block and returns an |
| // |X509_CRL*|. |
| static bssl::UniquePtr<X509_CRL> CRLFromPEM(const char *pem) { |
| bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem))); |
| return bssl::UniquePtr<X509_CRL>( |
| PEM_read_bio_X509_CRL(bio.get(), nullptr, nullptr, nullptr)); |
| } |
| |
| // CSRFromPEM parses the given, NUL-terminated PEM block and returns an |
| // |X509_REQ*|. |
| static bssl::UniquePtr<X509_REQ> CSRFromPEM(const char *pem) { |
| bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem))); |
| return bssl::UniquePtr<X509_REQ>( |
| PEM_read_bio_X509_REQ(bio.get(), nullptr, nullptr, nullptr)); |
| } |
| |
| // PrivateKeyFromPEM parses the given, NUL-terminated PEM block and returns an |
| // |EVP_PKEY*|. |
| static bssl::UniquePtr<EVP_PKEY> PrivateKeyFromPEM(const char *pem) { |
| bssl::UniquePtr<BIO> bio( |
| BIO_new_mem_buf(const_cast<char *>(pem), strlen(pem))); |
| return bssl::UniquePtr<EVP_PKEY>( |
| PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); |
| } |
| |
| // CertsToStack converts a vector of |X509*| to an OpenSSL STACK_OF(X509), |
| // bumping the reference counts for each certificate in question. |
| static bssl::UniquePtr<STACK_OF(X509)> CertsToStack( |
| const std::vector<X509 *> &certs) { |
| bssl::UniquePtr<STACK_OF(X509)> stack(sk_X509_new_null()); |
| if (!stack) { |
| return nullptr; |
| } |
| for (auto cert : certs) { |
| if (!bssl::PushToStack(stack.get(), bssl::UpRef(cert))) { |
| return nullptr; |
| } |
| } |
| |
| return stack; |
| } |
| |
| // CRLsToStack converts a vector of |X509_CRL*| to an OpenSSL |
| // STACK_OF(X509_CRL), bumping the reference counts for each CRL in question. |
| static bssl::UniquePtr<STACK_OF(X509_CRL)> CRLsToStack( |
| const std::vector<X509_CRL *> &crls) { |
| bssl::UniquePtr<STACK_OF(X509_CRL)> stack(sk_X509_CRL_new_null()); |
| if (!stack) { |
| return nullptr; |
| } |
| for (auto crl : crls) { |
| if (!bssl::PushToStack(stack.get(), bssl::UpRef(crl))) { |
| return nullptr; |
| } |
| } |
| |
| return stack; |
| } |
| |
| static const int64_t kReferenceTime = 1474934400 /* Sep 27th, 2016 */; |
| |
| static int Verify( |
| X509 *leaf, const std::vector<X509 *> &roots, |
| const std::vector<X509 *> &intermediates, |
| const std::vector<X509_CRL *> &crls, unsigned long flags = 0, |
| std::function<void(X509_STORE_CTX *)> configure_callback = nullptr) { |
| bssl::UniquePtr<STACK_OF(X509)> roots_stack(CertsToStack(roots)); |
| bssl::UniquePtr<STACK_OF(X509)> intermediates_stack( |
| CertsToStack(intermediates)); |
| bssl::UniquePtr<STACK_OF(X509_CRL)> crls_stack(CRLsToStack(crls)); |
| |
| if (!roots_stack || |
| !intermediates_stack || |
| !crls_stack) { |
| return X509_V_ERR_UNSPECIFIED; |
| } |
| |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| if (!ctx || |
| !store) { |
| return X509_V_ERR_UNSPECIFIED; |
| } |
| |
| if (!X509_STORE_CTX_init(ctx.get(), store.get(), leaf, |
| intermediates_stack.get())) { |
| return X509_V_ERR_UNSPECIFIED; |
| } |
| |
| X509_STORE_CTX_set0_trusted_stack(ctx.get(), roots_stack.get()); |
| X509_STORE_CTX_set0_crls(ctx.get(), crls_stack.get()); |
| |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx.get()); |
| X509_VERIFY_PARAM_set_time_posix(param, kReferenceTime); |
| if (configure_callback) { |
| configure_callback(ctx.get()); |
| } |
| if (flags) { |
| X509_VERIFY_PARAM_set_flags(param, flags); |
| } |
| |
| ERR_clear_error(); |
| if (X509_verify_cert(ctx.get()) != 1) { |
| return X509_STORE_CTX_get_error(ctx.get()); |
| } |
| |
| return X509_V_OK; |
| } |
| |
| TEST(X509Test, TestVerify) { |
| // cross_signing_root |
| // | |
| // root_cross_signed root |
| // \ / |
| // intermediate |
| // | | |
| // leaf leaf_no_key_usage |
| // | |
| // forgery |
| bssl::UniquePtr<X509> cross_signing_root(CertFromPEM(kCrossSigningRootPEM)); |
| bssl::UniquePtr<X509> root(CertFromPEM(kRootCAPEM)); |
| bssl::UniquePtr<X509> root_cross_signed(CertFromPEM(kRootCrossSignedPEM)); |
| bssl::UniquePtr<X509> intermediate(CertFromPEM(kIntermediatePEM)); |
| bssl::UniquePtr<X509> intermediate_self_signed( |
| CertFromPEM(kIntermediateSelfSignedPEM)); |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kLeafPEM)); |
| bssl::UniquePtr<X509> leaf_no_key_usage(CertFromPEM(kLeafNoKeyUsagePEM)); |
| bssl::UniquePtr<X509> forgery(CertFromPEM(kForgeryPEM)); |
| |
| ASSERT_TRUE(cross_signing_root); |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(root_cross_signed); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(intermediate_self_signed); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(forgery); |
| ASSERT_TRUE(leaf_no_key_usage); |
| |
| // Most of these tests work with or without |X509_V_FLAG_TRUSTED_FIRST|, |
| // though in different ways. |
| for (bool trusted_first : {true, false}) { |
| SCOPED_TRACE(trusted_first); |
| bool override_depth = false; |
| int depth = -1; |
| auto configure_callback = [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| // Note we need the callback to clear the flag. Setting |flags| to zero |
| // only skips setting new flags. |
| if (!trusted_first) { |
| X509_VERIFY_PARAM_clear_flags(param, X509_V_FLAG_TRUSTED_FIRST); |
| } |
| if (override_depth) { |
| X509_VERIFY_PARAM_set_depth(param, depth); |
| } |
| }; |
| |
| // No trust anchors configured. |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(leaf.get(), /*roots=*/{}, /*intermediates=*/{}, |
| /*crls=*/{}, /*flags=*/0, configure_callback)); |
| EXPECT_EQ( |
| X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(leaf.get(), /*roots=*/{}, {intermediate.get()}, /*crls=*/{}, |
| /*flags=*/0, configure_callback)); |
| |
| // Each chain works individually. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{}, /*flags=*/0, configure_callback)); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {cross_signing_root.get()}, |
| {intermediate.get(), root_cross_signed.get()}, |
| /*crls=*/{}, /*flags=*/0, configure_callback)); |
| |
| // When both roots are available, we pick one or the other. |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {cross_signing_root.get(), root.get()}, |
| {intermediate.get(), root_cross_signed.get()}, /*crls=*/{}, |
| /*flags=*/0, configure_callback)); |
| |
| // This is the “altchains” test – we remove the cross-signing CA but include |
| // the cross-sign in the intermediates. With |trusted_first|, we |
| // preferentially stop path-building at |intermediate|. Without |
| // |trusted_first|, the "altchains" logic repairs it. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, |
| {intermediate.get(), root_cross_signed.get()}, |
| /*crls=*/{}, /*flags=*/0, configure_callback)); |
| |
| // If |X509_V_FLAG_NO_ALT_CHAINS| is set and |trusted_first| is disabled, we |
| // get stuck on |root_cross_signed|. If either feature is enabled, we can |
| // build the path. |
| // |
| // This test exists to confirm our current behavior, but these modes are |
| // just workarounds for not having an actual path-building verifier. If we |
| // fix it, this test can be removed. |
| EXPECT_EQ(trusted_first ? X509_V_OK |
| : X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(leaf.get(), {root.get()}, |
| {intermediate.get(), root_cross_signed.get()}, /*crls=*/{}, |
| /*flags=*/X509_V_FLAG_NO_ALT_CHAINS, configure_callback)); |
| |
| // |forgery| is signed by |leaf_no_key_usage|, but is rejected because the |
| // leaf is not a CA. |
| EXPECT_EQ(X509_V_ERR_INVALID_CA, |
| Verify(forgery.get(), {intermediate_self_signed.get()}, |
| {leaf_no_key_usage.get()}, /*crls=*/{}, /*flags=*/0, |
| configure_callback)); |
| |
| // Test that one cannot skip Basic Constraints checking with a contorted set |
| // of roots and intermediates. This is a regression test for CVE-2015-1793. |
| EXPECT_EQ(X509_V_ERR_INVALID_CA, |
| Verify(forgery.get(), |
| {intermediate_self_signed.get(), root_cross_signed.get()}, |
| {leaf_no_key_usage.get(), intermediate.get()}, /*crls=*/{}, |
| /*flags=*/0, configure_callback)); |
| |
| // Test depth limits. |configure_callback| looks at |override_depth| and |
| // |depth|. Negative numbers have historically worked, so test those too. |
| for (int d : {-4, -3, -2, -1, 0, 1, 2, 3, 4, INT_MAX - 3, INT_MAX - 2, |
| INT_MAX - 1, INT_MAX}) { |
| SCOPED_TRACE(d); |
| override_depth = true; |
| depth = d; |
| // A chain with a leaf, two intermediates, and a root is depth two. |
| EXPECT_EQ( |
| depth >= 2 ? X509_V_OK : X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(leaf.get(), {cross_signing_root.get()}, |
| {intermediate.get(), root_cross_signed.get()}, |
| /*crls=*/{}, /*flags=*/0, configure_callback)); |
| |
| // A chain with a leaf, a root, and no intermediates is depth zero. |
| EXPECT_EQ( |
| depth >= 0 ? X509_V_OK : X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(root_cross_signed.get(), {cross_signing_root.get()}, {}, |
| /*crls=*/{}, /*flags=*/0, configure_callback)); |
| |
| // An explicitly trusted self-signed certificate is unaffected by depth |
| // checks. |
| EXPECT_EQ(X509_V_OK, |
| Verify(cross_signing_root.get(), {cross_signing_root.get()}, {}, |
| /*crls=*/{}, /*flags=*/0, configure_callback)); |
| } |
| } |
| } |
| |
| #if defined(OPENSSL_THREADS) |
| // Verifying the same |X509| objects on two threads should be safe. |
| TEST(X509Test, VerifyThreads) { |
| bssl::UniquePtr<X509> root(CertFromPEM(kRootCAPEM)); |
| bssl::UniquePtr<X509> intermediate(CertFromPEM(kIntermediatePEM)); |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kLeafPEM)); |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(leaf); |
| |
| const size_t kNumThreads = 10; |
| std::vector<std::thread> threads; |
| for (size_t i = 0; i < kNumThreads; i++) { |
| threads.emplace_back([&] { |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{})); |
| }); |
| } |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| } |
| |
| // Using the same CRL on two threads should be safe. |
| TEST(X509Test, CRLThreads) { |
| bssl::UniquePtr<X509> root(CertFromPEM(kCRLTestRoot)); |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kCRLTestLeaf)); |
| bssl::UniquePtr<X509_CRL> basic_crl(CRLFromPEM(kBasicCRL)); |
| bssl::UniquePtr<X509_CRL> revoked_crl(CRLFromPEM(kRevokedCRL)); |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(basic_crl); |
| ASSERT_TRUE(revoked_crl); |
| |
| const size_t kNumThreads = 10; |
| std::vector<std::thread> threads; |
| for (size_t i = 0; i < kNumThreads; i++) { |
| threads.emplace_back([&] { |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {root.get()}, |
| {basic_crl.get()}, X509_V_FLAG_CRL_CHECK)); |
| }); |
| threads.emplace_back([&] { |
| EXPECT_EQ(X509_V_ERR_CERT_REVOKED, |
| Verify(leaf.get(), {root.get()}, {root.get()}, |
| {revoked_crl.get()}, X509_V_FLAG_CRL_CHECK)); |
| }); |
| } |
| |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| |
| // TODO(crbug.com/boringssl/600): Add a thread that iterates |
| // |X509_CRL_get_REVOKED| and a thread that calls |X509_CRL_print|. Those |
| // currently do not work correctly. |
| } |
| |
| TEST(X509Test, StoreThreads) { |
| bssl::UniquePtr<X509> root(CertFromPEM(kRootCAPEM)); |
| bssl::UniquePtr<X509> intermediate(CertFromPEM(kIntermediatePEM)); |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kLeafPEM)); |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(leaf); |
| |
| bssl::UniquePtr<STACK_OF(X509)> intermediates = |
| CertsToStack({intermediate.get()}); |
| ASSERT_TRUE(intermediates); |
| |
| // Some unrelated certificates. |
| bssl::UniquePtr<X509> other1(CertFromPEM(kCRLTestRoot)); |
| bssl::UniquePtr<X509> other2(CertFromPEM(kCRLTestLeaf)); |
| ASSERT_TRUE(other1); |
| ASSERT_TRUE(other2); |
| |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| ASSERT_TRUE(store); |
| ASSERT_TRUE(X509_STORE_add_cert(store.get(), root.get())); |
| |
| const size_t kNumThreads = 10; |
| std::vector<std::thread> threads; |
| for (size_t i = 0; i < kNumThreads; i++) { |
| threads.emplace_back([&] { |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), leaf.get(), |
| intermediates.get())); |
| X509_STORE_CTX_set_time_posix(ctx.get(), /*flags=*/0, kReferenceTime); |
| ASSERT_TRUE(X509_verify_cert(ctx.get())); |
| ASSERT_EQ(X509_STORE_CTX_get_error(ctx.get()), X509_V_OK); |
| }); |
| threads.emplace_back([&] { |
| ASSERT_TRUE(X509_STORE_add_cert(store.get(), other1.get())); |
| }); |
| threads.emplace_back([&] { |
| ASSERT_TRUE(X509_STORE_add_cert(store.get(), other2.get())); |
| }); |
| threads.emplace_back([&] { |
| bssl::UniquePtr<STACK_OF(X509_OBJECT)> objs( |
| X509_STORE_get1_objects(store.get())); |
| ASSERT_TRUE(objs); |
| }); |
| } |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| } |
| #endif // OPENSSL_THREADS |
| |
| static const char kHostname[] = "example.com"; |
| static const char kWrongHostname[] = "example2.com"; |
| static const char kEmail[] = "test@example.com"; |
| static const char kWrongEmail[] = "test2@example.com"; |
| static const uint8_t kIP[4] = {127, 0, 0, 1}; |
| static const uint8_t kWrongIP[4] = {127, 0, 0, 2}; |
| static const char kIPString[] = "127.0.0.1"; |
| static const char kWrongIPString[] = "127.0.0.2"; |
| |
| TEST(X509Test, ZeroLengthsWithX509PARAM) { |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kSANTypesLeaf)); |
| bssl::UniquePtr<X509> root(CertFromPEM(kSANTypesRoot)); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(root); |
| |
| std::vector<X509_CRL *> empty_crls; |
| |
| struct X509Test { |
| const char *correct_value; |
| size_t correct_value_len; |
| const char *incorrect_value; |
| size_t incorrect_value_len; |
| int (*func)(X509_VERIFY_PARAM *, const char *, size_t); |
| int mismatch_error; |
| }; |
| const std::vector<X509Test> kTests = { |
| {kHostname, strlen(kHostname), kWrongHostname, strlen(kWrongHostname), |
| X509_VERIFY_PARAM_set1_host, X509_V_ERR_HOSTNAME_MISMATCH}, |
| {kEmail, strlen(kEmail), kWrongEmail, strlen(kWrongEmail), |
| X509_VERIFY_PARAM_set1_email, X509_V_ERR_EMAIL_MISMATCH}, |
| }; |
| |
| for (size_t i = 0; i < kTests.size(); i++) { |
| SCOPED_TRACE(i); |
| const X509Test &test = kTests[i]; |
| |
| // The correct value should work. |
| ASSERT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [&test](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(test.func(param, test.correct_value, |
| test.correct_value_len)); |
| })); |
| |
| // The wrong value should trigger a verification error. |
| ASSERT_EQ(test.mismatch_error, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [&test](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(test.func(param, test.incorrect_value, |
| test.incorrect_value_len)); |
| })); |
| |
| // Passing zero as the length, unlike OpenSSL, should trigger an error and |
| // should cause verification to fail. |
| ASSERT_EQ(X509_V_ERR_INVALID_CALL, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [&test](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_FALSE(test.func(param, test.correct_value, 0)); |
| })); |
| |
| // Passing an empty value should be an error when setting and should cause |
| // verification to fail. |
| ASSERT_EQ(X509_V_ERR_INVALID_CALL, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [&test](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_FALSE(test.func(param, nullptr, 0)); |
| })); |
| |
| // Passing a value with embedded NULs should also be an error and should |
| // also cause verification to fail. |
| ASSERT_EQ(X509_V_ERR_INVALID_CALL, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [&test](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_FALSE(test.func(param, "a", 2)); |
| })); |
| } |
| |
| // IP addresses work slightly differently: |
| |
| // The correct value should still work. |
| ASSERT_EQ( |
| X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1_ip(param, kIP, sizeof(kIP))); |
| })); |
| |
| // Incorrect values should still fail. |
| ASSERT_EQ(X509_V_ERR_IP_ADDRESS_MISMATCH, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1_ip(param, kWrongIP, |
| sizeof(kWrongIP))); |
| })); |
| |
| // Zero length values should trigger an error when setting and cause |
| // verification to always fail. |
| ASSERT_EQ(X509_V_ERR_INVALID_CALL, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| ASSERT_FALSE(X509_VERIFY_PARAM_set1_ip(param, kIP, 0)); |
| })); |
| |
| // ... and so should NULL values. |
| ASSERT_EQ(X509_V_ERR_INVALID_CALL, |
| Verify(leaf.get(), {root.get()}, {}, empty_crls, 0, |
| [](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| ASSERT_FALSE(X509_VERIFY_PARAM_set1_ip(param, nullptr, 0)); |
| })); |
| |
| // Zero bytes in an IP address are, of course, fine. This is tested above |
| // because |kIP| contains zeros. |
| } |
| |
| TEST(X509Test, ZeroLengthsWithCheckFunctions) { |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kSANTypesLeaf)); |
| ASSERT_TRUE(leaf); |
| |
| EXPECT_EQ( |
| 1, X509_check_host(leaf.get(), kHostname, strlen(kHostname), 0, nullptr)); |
| EXPECT_NE(1, X509_check_host(leaf.get(), kWrongHostname, |
| strlen(kWrongHostname), 0, nullptr)); |
| |
| EXPECT_EQ(1, X509_check_email(leaf.get(), kEmail, strlen(kEmail), 0)); |
| EXPECT_NE(1, |
| X509_check_email(leaf.get(), kWrongEmail, strlen(kWrongEmail), 0)); |
| |
| EXPECT_EQ(1, X509_check_ip(leaf.get(), kIP, sizeof(kIP), 0)); |
| EXPECT_NE(1, X509_check_ip(leaf.get(), kWrongIP, sizeof(kWrongIP), 0)); |
| |
| EXPECT_EQ(1, X509_check_ip_asc(leaf.get(), kIPString, 0)); |
| EXPECT_NE(1, X509_check_ip_asc(leaf.get(), kWrongIPString, 0)); |
| |
| // OpenSSL supports passing zero as the length for host and email. We do not |
| // and it should always fail. |
| EXPECT_NE(1, X509_check_host(leaf.get(), kHostname, 0, 0, nullptr)); |
| EXPECT_NE(1, X509_check_host(leaf.get(), kWrongHostname, 0, 0, nullptr)); |
| |
| EXPECT_NE(1, X509_check_email(leaf.get(), kEmail, 0, 0)); |
| EXPECT_NE(1, X509_check_email(leaf.get(), kWrongEmail, 0, 0)); |
| |
| EXPECT_NE(1, X509_check_ip(leaf.get(), kIP, 0, 0)); |
| EXPECT_NE(1, X509_check_ip(leaf.get(), kWrongIP, 0, 0)); |
| |
| // Unlike all the other functions, |X509_check_ip_asc| doesn't take a length, |
| // so it cannot be zero. |
| } |
| |
| TEST(X509Test, TestCRL) { |
| bssl::UniquePtr<X509> root(CertFromPEM(kCRLTestRoot)); |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kCRLTestLeaf)); |
| bssl::UniquePtr<X509_CRL> basic_crl(CRLFromPEM(kBasicCRL)); |
| bssl::UniquePtr<X509_CRL> revoked_crl(CRLFromPEM(kRevokedCRL)); |
| bssl::UniquePtr<X509_CRL> bad_issuer_crl(CRLFromPEM(kBadIssuerCRL)); |
| bssl::UniquePtr<X509_CRL> known_critical_crl(CRLFromPEM(kKnownCriticalCRL)); |
| bssl::UniquePtr<X509_CRL> unknown_critical_crl( |
| CRLFromPEM(kUnknownCriticalCRL)); |
| bssl::UniquePtr<X509_CRL> unknown_critical_crl2( |
| CRLFromPEM(kUnknownCriticalCRL2)); |
| bssl::UniquePtr<X509_CRL> algorithm_mismatch_crl( |
| CRLFromPEM(kAlgorithmMismatchCRL)); |
| bssl::UniquePtr<X509_CRL> algorithm_mismatch_crl2( |
| CRLFromPEM(kAlgorithmMismatchCRL2)); |
| |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(basic_crl); |
| ASSERT_TRUE(revoked_crl); |
| ASSERT_TRUE(bad_issuer_crl); |
| ASSERT_TRUE(known_critical_crl); |
| ASSERT_TRUE(unknown_critical_crl); |
| ASSERT_TRUE(unknown_critical_crl2); |
| ASSERT_TRUE(algorithm_mismatch_crl); |
| ASSERT_TRUE(algorithm_mismatch_crl2); |
| |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {root.get()}, |
| {basic_crl.get()}, X509_V_FLAG_CRL_CHECK)); |
| EXPECT_EQ( |
| X509_V_ERR_CERT_REVOKED, |
| Verify(leaf.get(), {root.get()}, {root.get()}, |
| {basic_crl.get(), revoked_crl.get()}, X509_V_FLAG_CRL_CHECK)); |
| |
| std::vector<X509_CRL *> empty_crls; |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_CRL, |
| Verify(leaf.get(), {root.get()}, {root.get()}, empty_crls, |
| X509_V_FLAG_CRL_CHECK)); |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_CRL, |
| Verify(leaf.get(), {root.get()}, {root.get()}, |
| {bad_issuer_crl.get()}, X509_V_FLAG_CRL_CHECK)); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {root.get()}, |
| {known_critical_crl.get()}, X509_V_FLAG_CRL_CHECK)); |
| EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, |
| Verify(leaf.get(), {root.get()}, {root.get()}, |
| {unknown_critical_crl.get()}, X509_V_FLAG_CRL_CHECK)); |
| EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, |
| Verify(leaf.get(), {root.get()}, {root.get()}, |
| {unknown_critical_crl2.get()}, X509_V_FLAG_CRL_CHECK)); |
| EXPECT_EQ(X509_V_ERR_CRL_SIGNATURE_FAILURE, |
| Verify(leaf.get(), {root.get()}, {root.get()}, |
| {algorithm_mismatch_crl.get()}, X509_V_FLAG_CRL_CHECK)); |
| EXPECT_EQ(X509_V_ERR_CRL_SIGNATURE_FAILURE, |
| Verify(leaf.get(), {root.get()}, {root.get()}, |
| {algorithm_mismatch_crl2.get()}, X509_V_FLAG_CRL_CHECK)); |
| |
| // The CRL is valid for a month. |
| EXPECT_EQ(X509_V_ERR_CRL_HAS_EXPIRED, |
| Verify(leaf.get(), {root.get()}, {root.get()}, {basic_crl.get()}, |
| X509_V_FLAG_CRL_CHECK, [](X509_STORE_CTX *ctx) { |
| X509_STORE_CTX_set_time_posix( |
| ctx, /*flags=*/0, kReferenceTime + 2 * 30 * 24 * 3600); |
| })); |
| |
| // X509_V_FLAG_NO_CHECK_TIME suppresses the validity check. |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {root.get()}, {basic_crl.get()}, |
| X509_V_FLAG_CRL_CHECK | X509_V_FLAG_NO_CHECK_TIME, |
| [](X509_STORE_CTX *ctx) { |
| X509_STORE_CTX_set_time_posix( |
| ctx, /*flags=*/0, kReferenceTime + 2 * 30 * 24 * 3600); |
| })); |
| |
| // We no longer support indirect or delta CRLs. |
| EXPECT_EQ(X509_V_ERR_INVALID_CALL, |
| Verify(leaf.get(), {root.get()}, {root.get()}, {basic_crl.get()}, |
| X509_V_FLAG_CRL_CHECK | X509_V_FLAG_EXTENDED_CRL_SUPPORT)); |
| EXPECT_EQ(X509_V_ERR_INVALID_CALL, |
| Verify(leaf.get(), {root.get()}, {root.get()}, {basic_crl.get()}, |
| X509_V_FLAG_CRL_CHECK | X509_V_FLAG_USE_DELTAS)); |
| |
| // Parsing kBadExtensionCRL should fail. |
| EXPECT_FALSE(CRLFromPEM(kBadExtensionCRL)); |
| } |
| |
| TEST(X509Test, ManyNamesAndConstraints) { |
| bssl::UniquePtr<X509> many_constraints(CertFromPEM( |
| GetTestData("crypto/x509/test/many_constraints.pem").c_str())); |
| ASSERT_TRUE(many_constraints); |
| bssl::UniquePtr<X509> many_names1( |
| CertFromPEM(GetTestData("crypto/x509/test/many_names1.pem").c_str())); |
| ASSERT_TRUE(many_names1); |
| bssl::UniquePtr<X509> many_names2( |
| CertFromPEM(GetTestData("crypto/x509/test/many_names2.pem").c_str())); |
| ASSERT_TRUE(many_names2); |
| bssl::UniquePtr<X509> many_names3( |
| CertFromPEM(GetTestData("crypto/x509/test/many_names3.pem").c_str())); |
| ASSERT_TRUE(many_names3); |
| bssl::UniquePtr<X509> some_names1( |
| CertFromPEM(GetTestData("crypto/x509/test/some_names1.pem").c_str())); |
| ASSERT_TRUE(some_names1); |
| bssl::UniquePtr<X509> some_names2( |
| CertFromPEM(GetTestData("crypto/x509/test/some_names2.pem").c_str())); |
| ASSERT_TRUE(some_names2); |
| bssl::UniquePtr<X509> some_names3( |
| CertFromPEM(GetTestData("crypto/x509/test/some_names3.pem").c_str())); |
| ASSERT_TRUE(some_names3); |
| |
| EXPECT_EQ(X509_V_ERR_UNSPECIFIED, |
| Verify(many_names1.get(), {many_constraints.get()}, |
| {many_constraints.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_UNSPECIFIED, |
| Verify(many_names2.get(), {many_constraints.get()}, |
| {many_constraints.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_UNSPECIFIED, |
| Verify(many_names3.get(), {many_constraints.get()}, |
| {many_constraints.get()}, {})); |
| |
| EXPECT_EQ(X509_V_OK, Verify(some_names1.get(), {many_constraints.get()}, |
| {many_constraints.get()}, {})); |
| EXPECT_EQ(X509_V_OK, Verify(some_names2.get(), {many_constraints.get()}, |
| {many_constraints.get()}, {})); |
| EXPECT_EQ(X509_V_OK, Verify(some_names3.get(), {many_constraints.get()}, |
| {many_constraints.get()}, {})); |
| } |
| |
| static bssl::UniquePtr<GENERAL_NAME> MakeGeneralName(int type, |
| const std::string &value) { |
| if (type != GEN_EMAIL && type != GEN_DNS && type != GEN_URI) { |
| // This function only supports the IA5String types. |
| return nullptr; |
| } |
| bssl::UniquePtr<ASN1_IA5STRING> str(ASN1_IA5STRING_new()); |
| bssl::UniquePtr<GENERAL_NAME> name(GENERAL_NAME_new()); |
| if (!str || !name || |
| !ASN1_STRING_set(str.get(), value.data(), value.size())) { |
| return nullptr; |
| } |
| |
| name->type = type; |
| name->d.ia5 = str.release(); |
| return name; |
| } |
| |
| static bssl::UniquePtr<X509_NAME> MakeTestName(const char *common_name) { |
| bssl::UniquePtr<X509_NAME> name(X509_NAME_new()); |
| if (name == nullptr || |
| !X509_NAME_add_entry_by_txt( |
| name.get(), "CN", MBSTRING_UTF8, |
| reinterpret_cast<const uint8_t *>(common_name), -1, -1, 0)) { |
| return nullptr; |
| } |
| return name; |
| } |
| |
| static bssl::UniquePtr<X509> MakeTestCert(const char *issuer, |
| const char *subject, EVP_PKEY *key, |
| bool is_ca) { |
| bssl::UniquePtr<X509_NAME> issuer_name = MakeTestName(issuer); |
| bssl::UniquePtr<X509_NAME> subject_name = MakeTestName(subject); |
| bssl::UniquePtr<X509> cert(X509_new()); |
| if (issuer_name == nullptr || subject_name == nullptr || cert == nullptr || |
| !X509_set_version(cert.get(), X509_VERSION_3) || |
| !X509_set_issuer_name(cert.get(), issuer_name.get()) || |
| !X509_set_subject_name(cert.get(), subject_name.get()) || |
| !X509_set_pubkey(cert.get(), key) || |
| !ASN1_TIME_adj(X509_getm_notBefore(cert.get()), kReferenceTime, -1, 0) || |
| !ASN1_TIME_adj(X509_getm_notAfter(cert.get()), kReferenceTime, 1, 0)) { |
| return nullptr; |
| } |
| bssl::UniquePtr<BASIC_CONSTRAINTS> bc(BASIC_CONSTRAINTS_new()); |
| if (!bc) { |
| return nullptr; |
| } |
| bc->ca = is_ca ? ASN1_BOOLEAN_TRUE : ASN1_BOOLEAN_FALSE; |
| if (!X509_add1_ext_i2d(cert.get(), NID_basic_constraints, bc.get(), |
| /*crit=*/1, /*flags=*/0)) { |
| return nullptr; |
| } |
| return cert; |
| } |
| |
| static bool AddExtendedKeyUsage(X509 *x509, const std::vector<int> &eku_nids) { |
| bssl::UniquePtr<STACK_OF(ASN1_OBJECT)> objs(sk_ASN1_OBJECT_new_null()); |
| if (objs == nullptr) { |
| return false; |
| } |
| for (int nid : eku_nids) { |
| if (!sk_ASN1_OBJECT_push(objs.get(), OBJ_nid2obj(nid))) { |
| return false; |
| } |
| } |
| return X509_add1_ext_i2d(x509, NID_ext_key_usage, objs.get(), /*crit=*/1, |
| /*flags=*/0); |
| } |
| |
| enum class KeyUsage : int { |
| kDigitalSignature = 0, |
| kNonRepudiation = 1, |
| kKeyEncipherment = 2, |
| kDataEncipherment = 3, |
| kKeyAgreement = 4, |
| kKeyCertSign = 5, |
| kCRLSign = 6, |
| kEncipherOnly = 7, |
| kDecipherOnly = 8, |
| }; |
| |
| static bool AddKeyUsage(X509 *x509, const std::vector<KeyUsage> usages) { |
| bssl::UniquePtr<ASN1_BIT_STRING> str(ASN1_BIT_STRING_new()); |
| if (str == nullptr) { |
| return false; |
| } |
| for (KeyUsage usage : usages) { |
| if (!ASN1_BIT_STRING_set_bit(str.get(), static_cast<int>(usage), 1)) { |
| return false; |
| } |
| } |
| return X509_add1_ext_i2d(x509, NID_key_usage, str.get(), /*crit=*/1, |
| /*flags=*/0); |
| } |
| |
| static bool AddSubjectKeyIdentifier(X509 *x509, |
| bssl::Span<const uint8_t> key_id) { |
| bssl::UniquePtr<ASN1_OCTET_STRING> oct(ASN1_OCTET_STRING_new()); |
| return oct != nullptr && |
| ASN1_STRING_set(oct.get(), key_id.data(), key_id.size()) && |
| X509_add1_ext_i2d(x509, NID_subject_key_identifier, oct.get(), |
| /*crit=*/0, /*flags=*/0); |
| } |
| |
| static bool AddAuthorityKeyIdentifier(X509 *x509, |
| bssl::Span<const uint8_t> key_id) { |
| bssl::UniquePtr<AUTHORITY_KEYID> akid(AUTHORITY_KEYID_new()); |
| if (akid == nullptr) { |
| return false; |
| } |
| akid->keyid = ASN1_OCTET_STRING_new(); |
| if (akid->keyid == nullptr || |
| !ASN1_STRING_set(akid->keyid, key_id.data(), key_id.size()) || |
| !X509_add1_ext_i2d(x509, NID_authority_key_identifier, akid.get(), |
| /*crit=*/0, /*flags=*/0)) { |
| return false; |
| } |
| return true; |
| } |
| |
| static bssl::UniquePtr<X509_CRL> MakeTestCRL(const char *issuer, |
| int this_update_offset_day, |
| int next_update_offset_day) { |
| bssl::UniquePtr<X509_NAME> issuer_name = MakeTestName(issuer); |
| bssl::UniquePtr<X509_CRL> crl(X509_CRL_new()); |
| bssl::UniquePtr<ASN1_TIME> this_update(ASN1_TIME_adj( |
| nullptr, kReferenceTime, this_update_offset_day, /*offset_sec=*/0)); |
| bssl::UniquePtr<ASN1_TIME> next_update(ASN1_TIME_adj( |
| nullptr, kReferenceTime, next_update_offset_day, /*offset_sec=*/0)); |
| if (crl == nullptr || issuer_name == nullptr || this_update == nullptr || |
| next_update == nullptr || |
| !X509_CRL_set_version(crl.get(), X509_CRL_VERSION_2) || |
| !X509_CRL_set_issuer_name(crl.get(), issuer_name.get()) || |
| // OpenSSL's API is named incorrectly. The field is called thisUpdate. |
| !X509_CRL_set1_lastUpdate(crl.get(), this_update.get()) || |
| !X509_CRL_set1_nextUpdate(crl.get(), next_update.get())) { |
| return nullptr; |
| } |
| |
| return crl; |
| } |
| |
| static bool AddRevokedSerialU64(X509_CRL *crl, uint64_t serial, |
| int offset_day) { |
| bssl::UniquePtr<X509_REVOKED> rev(X509_REVOKED_new()); |
| bssl::UniquePtr<ASN1_INTEGER> serial_asn1(ASN1_INTEGER_new()); |
| bssl::UniquePtr<ASN1_TIME> rev_date( |
| ASN1_TIME_adj(nullptr, kReferenceTime, offset_day, /*offset_sec=*/0)); |
| if (rev == nullptr || serial_asn1 == nullptr || rev_date == nullptr || |
| !ASN1_INTEGER_set_uint64(serial_asn1.get(), serial) || |
| !X509_REVOKED_set_serialNumber(rev.get(), serial_asn1.get()) || |
| !X509_REVOKED_set_revocationDate(rev.get(), rev_date.get()) || |
| !X509_CRL_add0_revoked(crl, rev.get())) { |
| return false; |
| } |
| rev.release(); // X509_CRL_add0_revoked takes ownership on success. |
| return true; |
| } |
| |
| static bool AddAuthorityKeyIdentifier(X509_CRL *crl, |
| bssl::Span<const uint8_t> key_id) { |
| bssl::UniquePtr<AUTHORITY_KEYID> akid(AUTHORITY_KEYID_new()); |
| if (akid == nullptr) { |
| return false; |
| } |
| akid->keyid = ASN1_OCTET_STRING_new(); |
| if (akid->keyid == nullptr || |
| !ASN1_STRING_set(akid->keyid, key_id.data(), key_id.size()) || |
| !X509_CRL_add1_ext_i2d(crl, NID_authority_key_identifier, akid.get(), |
| /*crit=*/0, /*flags=*/0)) { |
| return false; |
| } |
| return true; |
| } |
| |
| TEST(X509Test, NameConstraints) { |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| const struct { |
| int type; |
| std::string name; |
| std::string constraint; |
| int result; |
| } kTests[] = { |
| // Empty string matches everything. |
| {GEN_DNS, "foo.example.com", "", X509_V_OK}, |
| // Name constraints match the entire subtree. |
| {GEN_DNS, "foo.example.com", "example.com", X509_V_OK}, |
| {GEN_DNS, "foo.example.com", "EXAMPLE.COM", X509_V_OK}, |
| {GEN_DNS, "foo.example.com", "xample.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_DNS, "foo.example.com", "unrelated.much.longer.name.example", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| // A leading dot means at least one component must be added. |
| {GEN_DNS, "foo.example.com", ".example.com", X509_V_OK}, |
| {GEN_DNS, "foo.example.com", "foo.example.com", X509_V_OK}, |
| {GEN_DNS, "foo.example.com", ".foo.example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_DNS, "foo.example.com", ".xample.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_DNS, "foo.example.com", ".unrelated.much.longer.name.example", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| // NUL bytes, if not rejected, should not confuse the matching logic. |
| {GEN_DNS, std::string({'a', '\0', 'a'}), std::string({'a', '\0', 'b'}), |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| |
| // Names must be emails. |
| {GEN_EMAIL, "not-an-email.example", "not-an-email.example", |
| X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, |
| // A leading dot matches all local names and all subdomains |
| {GEN_EMAIL, "foo@bar.example.com", ".example.com", X509_V_OK}, |
| {GEN_EMAIL, "foo@bar.example.com", ".EXAMPLE.COM", X509_V_OK}, |
| {GEN_EMAIL, "foo@bar.example.com", ".bar.example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| // Without a leading dot, the host must match exactly. |
| {GEN_EMAIL, "foo@example.com", "example.com", X509_V_OK}, |
| {GEN_EMAIL, "foo@example.com", "EXAMPLE.COM", X509_V_OK}, |
| {GEN_EMAIL, "foo@bar.example.com", "example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| // If the constraint specifies a mailbox, it specifies the whole thing. |
| // The halves are compared insensitively. |
| {GEN_EMAIL, "foo@example.com", "foo@example.com", X509_V_OK}, |
| {GEN_EMAIL, "foo@example.com", "foo@EXAMPLE.COM", X509_V_OK}, |
| {GEN_EMAIL, "foo@example.com", "FOO@example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_EMAIL, "foo@example.com", "bar@example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| // OpenSSL ignores a stray leading @. |
| {GEN_EMAIL, "foo@example.com", "@example.com", X509_V_OK}, |
| {GEN_EMAIL, "foo@example.com", "@EXAMPLE.COM", X509_V_OK}, |
| {GEN_EMAIL, "foo@bar.example.com", "@example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| |
| // Basic syntax check. |
| {GEN_URI, "not-a-url", "not-a-url", X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, |
| {GEN_URI, "foo:not-a-url", "not-a-url", |
| X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, |
| {GEN_URI, "foo:/not-a-url", "not-a-url", |
| X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, |
| {GEN_URI, "foo:///not-a-url", "not-a-url", |
| X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, |
| {GEN_URI, "foo://:not-a-url", "not-a-url", |
| X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, |
| {GEN_URI, "foo://", "not-a-url", X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, |
| // Hosts are an exact match. |
| {GEN_URI, "foo://example.com", "example.com", X509_V_OK}, |
| {GEN_URI, "foo://example.com:443", "example.com", X509_V_OK}, |
| {GEN_URI, "foo://example.com/whatever", "example.com", X509_V_OK}, |
| {GEN_URI, "foo://bar.example.com", "example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://bar.example.com:443", "example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://bar.example.com/whatever", "example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://bar.example.com", "xample.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://bar.example.com:443", "xample.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://bar.example.com/whatever", "xample.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com", "some-other-name.example", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com:443", "some-other-name.example", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com/whatever", "some-other-name.example", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| // A leading dot allows components to be added. |
| {GEN_URI, "foo://example.com", ".example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com:443", ".example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com/whatever", ".example.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://bar.example.com", ".example.com", X509_V_OK}, |
| {GEN_URI, "foo://bar.example.com:443", ".example.com", X509_V_OK}, |
| {GEN_URI, "foo://bar.example.com/whatever", ".example.com", X509_V_OK}, |
| {GEN_URI, "foo://example.com", ".some-other-name.example", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com:443", ".some-other-name.example", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com/whatever", ".some-other-name.example", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com", ".xample.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com:443", ".xample.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| {GEN_URI, "foo://example.com/whatever", ".xample.com", |
| X509_V_ERR_PERMITTED_VIOLATION}, |
| }; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(t.type); |
| SCOPED_TRACE(t.name); |
| SCOPED_TRACE(t.constraint); |
| |
| bssl::UniquePtr<GENERAL_NAME> name = MakeGeneralName(t.type, t.name); |
| ASSERT_TRUE(name); |
| bssl::UniquePtr<GENERAL_NAMES> names(GENERAL_NAMES_new()); |
| ASSERT_TRUE(names); |
| ASSERT_TRUE(bssl::PushToStack(names.get(), std::move(name))); |
| |
| bssl::UniquePtr<NAME_CONSTRAINTS> nc(NAME_CONSTRAINTS_new()); |
| ASSERT_TRUE(nc); |
| nc->permittedSubtrees = sk_GENERAL_SUBTREE_new_null(); |
| ASSERT_TRUE(nc->permittedSubtrees); |
| bssl::UniquePtr<GENERAL_SUBTREE> subtree(GENERAL_SUBTREE_new()); |
| ASSERT_TRUE(subtree); |
| GENERAL_NAME_free(subtree->base); |
| subtree->base = MakeGeneralName(t.type, t.constraint).release(); |
| ASSERT_TRUE(subtree->base); |
| ASSERT_TRUE(bssl::PushToStack(nc->permittedSubtrees, std::move(subtree))); |
| |
| bssl::UniquePtr<X509> root = |
| MakeTestCert("Root", "Root", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(X509_add1_ext_i2d(root.get(), NID_name_constraints, nc.get(), |
| /*crit=*/1, /*flags=*/0)); |
| ASSERT_TRUE(X509_sign(root.get(), key.get(), EVP_sha256())); |
| |
| bssl::UniquePtr<X509> leaf = |
| MakeTestCert("Root", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(X509_add1_ext_i2d(leaf.get(), NID_subject_alt_name, names.get(), |
| /*crit=*/0, /*flags=*/0)); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| |
| int ret = Verify(leaf.get(), {root.get()}, {}, {}, 0); |
| EXPECT_EQ(t.result, ret) << X509_verify_cert_error_string(ret); |
| } |
| } |
| |
| TEST(X509Test, PrintGeneralName) { |
| // TODO(https://crbug.com/boringssl/430): Add more tests. Also fix the |
| // external projects that use this to extract the SAN list and unexport. |
| bssl::UniquePtr<GENERAL_NAME> gen = MakeGeneralName(GEN_DNS, "example.com"); |
| ASSERT_TRUE(gen); |
| bssl::UniquePtr<STACK_OF(CONF_VALUE)> values( |
| i2v_GENERAL_NAME(nullptr, gen.get(), nullptr)); |
| ASSERT_TRUE(values); |
| ASSERT_EQ(1u, sk_CONF_VALUE_num(values.get())); |
| const CONF_VALUE *value = sk_CONF_VALUE_value(values.get(), 0); |
| EXPECT_STREQ(value->name, "DNS"); |
| EXPECT_STREQ(value->value, "example.com"); |
| } |
| |
| TEST(X509Test, TestPSS) { |
| static const char *kGoodCerts[] = { |
| "crypto/x509/test/pss_sha256.pem", |
| "crypto/x509/test/pss_sha384.pem", |
| "crypto/x509/test/pss_sha512.pem", |
| // We accept inputs with and without explicit NULLs. See RFC 4055, |
| // section 2.1. |
| "crypto/x509/test/pss_sha256_omit_nulls.pem", |
| // Although invalid, we tolerate an explicit trailerField value. See the |
| // certificates in cl/362617931. |
| "crypto/x509/test/pss_sha256_explicit_trailer.pem", |
| }; |
| for (const char *path : kGoodCerts) { |
| SCOPED_TRACE(path); |
| bssl::UniquePtr<X509> cert = CertFromPEM(GetTestData(path).c_str()); |
| ASSERT_TRUE(cert); |
| bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get())); |
| ASSERT_TRUE(pkey); |
| EXPECT_TRUE(X509_verify(cert.get(), pkey.get())); |
| } |
| |
| static const char *kBadCerts[] = { |
| "crypto/x509/test/pss_sha1_explicit.pem", |
| "crypto/x509/test/pss_sha1_mgf1_syntax_error.pem", |
| "crypto/x509/test/pss_sha1.pem", |
| "crypto/x509/test/pss_sha224.pem", |
| "crypto/x509/test/pss_sha256_mgf1_sha384.pem", |
| "crypto/x509/test/pss_sha256_mgf1_syntax_error.pem", |
| "crypto/x509/test/pss_sha256_salt_overflow.pem", |
| "crypto/x509/test/pss_sha256_salt31.pem", |
| "crypto/x509/test/pss_sha256_unknown_mgf.pem", |
| "crypto/x509/test/pss_sha256_wrong_trailer.pem", |
| }; |
| for (const char *path : kBadCerts) { |
| SCOPED_TRACE(path); |
| bssl::UniquePtr<X509> cert = CertFromPEM(GetTestData(path).c_str()); |
| ASSERT_TRUE(cert); |
| bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get())); |
| ASSERT_TRUE(pkey); |
| EXPECT_FALSE(X509_verify(cert.get(), pkey.get())); |
| } |
| } |
| |
| TEST(X509Test, TestPSSBadParameters) { |
| bssl::UniquePtr<X509> cert(CertFromPEM(kBadPSSCertPEM)); |
| ASSERT_TRUE(cert); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get())); |
| ASSERT_TRUE(pkey); |
| |
| ASSERT_FALSE(X509_verify(cert.get(), pkey.get())); |
| ERR_clear_error(); |
| } |
| |
| TEST(X509Test, TestEd25519) { |
| bssl::UniquePtr<X509> cert(CertFromPEM(kEd25519Cert)); |
| ASSERT_TRUE(cert); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get())); |
| ASSERT_TRUE(pkey); |
| |
| ASSERT_TRUE(X509_verify(cert.get(), pkey.get())); |
| } |
| |
| TEST(X509Test, TestEd25519BadParameters) { |
| bssl::UniquePtr<X509> cert(CertFromPEM(kEd25519CertNull)); |
| ASSERT_TRUE(cert); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get())); |
| ASSERT_TRUE(pkey); |
| |
| ASSERT_FALSE(X509_verify(cert.get(), pkey.get())); |
| |
| EXPECT_TRUE( |
| ErrorEquals(ERR_get_error(), ERR_LIB_X509, X509_R_INVALID_PARAMETER)); |
| ERR_clear_error(); |
| } |
| |
| TEST(X509Test, TestX25519) { |
| bssl::UniquePtr<X509> cert(CertFromPEM(kX25519Cert)); |
| ASSERT_TRUE(cert); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get())); |
| ASSERT_TRUE(pkey); |
| |
| EXPECT_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_X25519); |
| |
| constexpr uint8_t kExpectedPublicValue[] = { |
| 0x85, 0x20, 0xf0, 0x09, 0x89, 0x30, 0xa7, 0x54, 0x74, 0x8b, 0x7d, |
| 0xdc, 0xb4, 0x3e, 0xf7, 0x5a, 0x0d, 0xbf, 0x3a, 0x0d, 0x26, 0x38, |
| 0x1a, 0xf4, 0xeb, 0xa4, 0xa9, 0x8e, 0xaa, 0x9b, 0x4e, 0x6a, |
| }; |
| uint8_t public_value[sizeof(kExpectedPublicValue)]; |
| size_t public_value_size = sizeof(public_value); |
| ASSERT_TRUE(EVP_PKEY_get_raw_public_key(pkey.get(), public_value, |
| &public_value_size)); |
| EXPECT_EQ(Bytes(kExpectedPublicValue), |
| Bytes(public_value, public_value_size)); |
| } |
| |
| static bssl::UniquePtr<X509> ReencodeCertificate(X509 *cert) { |
| uint8_t *der = nullptr; |
| int len = i2d_X509(cert, &der); |
| bssl::UniquePtr<uint8_t> free_der(der); |
| if (len <= 0) { |
| return nullptr; |
| } |
| |
| const uint8_t *inp = der; |
| return bssl::UniquePtr<X509>(d2i_X509(nullptr, &inp, len)); |
| } |
| |
| static bssl::UniquePtr<X509_CRL> ReencodeCRL(X509_CRL *crl) { |
| uint8_t *der = nullptr; |
| int len = i2d_X509_CRL(crl, &der); |
| bssl::UniquePtr<uint8_t> free_der(der); |
| if (len <= 0) { |
| return nullptr; |
| } |
| |
| const uint8_t *inp = der; |
| return bssl::UniquePtr<X509_CRL>(d2i_X509_CRL(nullptr, &inp, len)); |
| } |
| |
| static bssl::UniquePtr<X509_REQ> ReencodeCSR(X509_REQ *req) { |
| uint8_t *der = nullptr; |
| int len = i2d_X509_REQ(req, &der); |
| bssl::UniquePtr<uint8_t> free_der(der); |
| if (len <= 0) { |
| return nullptr; |
| } |
| |
| const uint8_t *inp = der; |
| return bssl::UniquePtr<X509_REQ>(d2i_X509_REQ(nullptr, &inp, len)); |
| } |
| |
| static bool SignatureRoundTrips(EVP_MD_CTX *md_ctx, EVP_PKEY *pkey) { |
| // Make a certificate like signed with |md_ctx|'s settings.' |
| bssl::UniquePtr<X509> cert(CertFromPEM(kLeafPEM)); |
| if (!cert || !X509_sign_ctx(cert.get(), md_ctx)) { |
| return false; |
| } |
| |
| // Ensure that |pkey| may still be used to verify the resulting signature. All |
| // settings in |md_ctx| must have been serialized appropriately. |
| if (!X509_verify(cert.get(), pkey)) { |
| return false; |
| } |
| |
| // Re-encode the certificate. X509 objects contain a cached TBSCertificate |
| // encoding and |X509_sign_ctx| should have dropped that cache. |
| bssl::UniquePtr<X509> copy = ReencodeCertificate(cert.get()); |
| return copy && X509_verify(copy.get(), pkey); |
| } |
| |
| TEST(X509Test, RSASign) { |
| bssl::UniquePtr<EVP_PKEY> pkey(PrivateKeyFromPEM(kRSAKey)); |
| ASSERT_TRUE(pkey); |
| // Test PKCS#1 v1.5. |
| bssl::ScopedEVP_MD_CTX md_ctx; |
| ASSERT_TRUE( |
| EVP_DigestSignInit(md_ctx.get(), NULL, EVP_sha256(), NULL, pkey.get())); |
| ASSERT_TRUE(SignatureRoundTrips(md_ctx.get(), pkey.get())); |
| |
| // RSA-PSS with salt length matching hash length should work when passing in |
| // -1 or the value explicitly. |
| md_ctx.Reset(); |
| EVP_PKEY_CTX *pkey_ctx; |
| ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha256(), NULL, |
| pkey.get())); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING)); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1)); |
| ASSERT_TRUE(SignatureRoundTrips(md_ctx.get(), pkey.get())); |
| |
| md_ctx.Reset(); |
| ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha256(), NULL, |
| pkey.get())); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING)); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, 32)); |
| ASSERT_TRUE(SignatureRoundTrips(md_ctx.get(), pkey.get())); |
| |
| // RSA-PSS with SHA-1 is not supported. |
| md_ctx.Reset(); |
| ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha1(), NULL, |
| pkey.get())); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING)); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1)); |
| bssl::UniquePtr<X509> cert = CertFromPEM(kLeafPEM); |
| ASSERT_TRUE(cert); |
| EXPECT_FALSE(X509_sign_ctx(cert.get(), md_ctx.get())); |
| |
| // RSA-PSS with mismatched hashes is not supported. |
| md_ctx.Reset(); |
| ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha256(), NULL, |
| pkey.get())); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING)); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1)); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, EVP_sha512())); |
| cert = CertFromPEM(kLeafPEM); |
| ASSERT_TRUE(cert); |
| EXPECT_FALSE(X509_sign_ctx(cert.get(), md_ctx.get())); |
| |
| // RSA-PSS with the wrong salt length is not supported. |
| md_ctx.Reset(); |
| ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha256(), NULL, |
| pkey.get())); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING)); |
| ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, 33)); |
| cert = CertFromPEM(kLeafPEM); |
| ASSERT_TRUE(cert); |
| EXPECT_FALSE(X509_sign_ctx(cert.get(), md_ctx.get())); |
| } |
| |
| // Test the APIs for signing a certificate, particularly whether they correctly |
| // handle the TBSCertificate cache. |
| TEST(X509Test, SignCertificate) { |
| const int kSignatureNID = NID_sha384WithRSAEncryption; |
| const EVP_MD *kSignatureHash = EVP_sha384(); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(PrivateKeyFromPEM(kRSAKey)); |
| ASSERT_TRUE(pkey); |
| bssl::UniquePtr<X509_ALGOR> algor(X509_ALGOR_new()); |
| ASSERT_TRUE(algor); |
| ASSERT_TRUE(X509_ALGOR_set0(algor.get(), OBJ_nid2obj(kSignatureNID), |
| V_ASN1_NULL, nullptr)); |
| |
| // Test both signing with |X509_sign| and constructing a signature manually. |
| for (bool sign_manual : {true, false}) { |
| SCOPED_TRACE(sign_manual); |
| |
| // Test certificates made both from other certificates and |X509_new|, in |
| // case there are bugs in filling in fields from different states. (Parsed |
| // certificates contain a TBSCertificate cache, and |X509_new| initializes |
| // fields based on complex ASN.1 template logic.) |
| for (bool new_cert : {true, false}) { |
| SCOPED_TRACE(new_cert); |
| |
| bssl::UniquePtr<X509> cert; |
| if (new_cert) { |
| cert.reset(X509_new()); |
| ASSERT_TRUE(cert); |
| // Fill in some fields for the certificate arbitrarily. |
| EXPECT_TRUE(X509_set_version(cert.get(), X509_VERSION_3)); |
| EXPECT_TRUE( |
| ASN1_INTEGER_set_int64(X509_get_serialNumber(cert.get()), 1)); |
| EXPECT_TRUE(X509_gmtime_adj(X509_getm_notBefore(cert.get()), 0)); |
| EXPECT_TRUE( |
| X509_gmtime_adj(X509_getm_notAfter(cert.get()), 60 * 60 * 24)); |
| X509_NAME *subject = X509_get_subject_name(cert.get()); |
| X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, |
| reinterpret_cast<const uint8_t *>("Test"), |
| -1, -1, 0); |
| EXPECT_TRUE(X509_set_issuer_name(cert.get(), subject)); |
| EXPECT_TRUE(X509_set_pubkey(cert.get(), pkey.get())); |
| } else { |
| // Extract fields from a parsed certificate. |
| cert = CertFromPEM(kLeafPEM); |
| ASSERT_TRUE(cert); |
| |
| // We should test with a different algorithm from what is already in the |
| // certificate. |
| EXPECT_NE(kSignatureNID, X509_get_signature_nid(cert.get())); |
| } |
| |
| if (sign_manual) { |
| // Fill in the signature algorithm. |
| ASSERT_TRUE(X509_set1_signature_algo(cert.get(), algor.get())); |
| |
| // Extract the TBSCertificiate. |
| uint8_t *tbs_cert = nullptr; |
| int tbs_cert_len = i2d_re_X509_tbs(cert.get(), &tbs_cert); |
| bssl::UniquePtr<uint8_t> free_tbs_cert(tbs_cert); |
| ASSERT_GT(tbs_cert_len, 0); |
| |
| // Generate a signature externally and fill it in. |
| bssl::ScopedEVP_MD_CTX md_ctx; |
| ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), nullptr, kSignatureHash, |
| nullptr, pkey.get())); |
| size_t sig_len; |
| ASSERT_TRUE(EVP_DigestSign(md_ctx.get(), nullptr, &sig_len, tbs_cert, |
| tbs_cert_len)); |
| std::vector<uint8_t> sig(sig_len); |
| ASSERT_TRUE(EVP_DigestSign(md_ctx.get(), sig.data(), &sig_len, tbs_cert, |
| tbs_cert_len)); |
| sig.resize(sig_len); |
| ASSERT_TRUE( |
| X509_set1_signature_value(cert.get(), sig.data(), sig.size())); |
| } else { |
| int ret = X509_sign(cert.get(), pkey.get(), EVP_sha384()); |
| ASSERT_GT(ret, 0); |
| // |X509_sign| returns the length of the signature on success. |
| const ASN1_BIT_STRING *sig; |
| X509_get0_signature(&sig, /*out_alg=*/nullptr, cert.get()); |
| EXPECT_EQ(ret, ASN1_STRING_length(sig)); |
| } |
| |
| // Check the signature. |
| EXPECT_TRUE(X509_verify(cert.get(), pkey.get())); |
| |
| // Re-encode the certificate. X509 objects contain a cached TBSCertificate |
| // encoding and re-signing should have dropped that cache. |
| bssl::UniquePtr<X509> copy = ReencodeCertificate(cert.get()); |
| ASSERT_TRUE(copy); |
| EXPECT_TRUE(X509_verify(copy.get(), pkey.get())); |
| } |
| } |
| } |
| |
| // Test the APIs for signing a CRL, particularly whether they correctly handle |
| // the TBSCertList cache. |
| TEST(X509Test, SignCRL) { |
| const int kSignatureNID = NID_sha384WithRSAEncryption; |
| const EVP_MD *kSignatureHash = EVP_sha384(); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(PrivateKeyFromPEM(kRSAKey)); |
| ASSERT_TRUE(pkey); |
| bssl::UniquePtr<X509_ALGOR> algor(X509_ALGOR_new()); |
| ASSERT_TRUE(algor); |
| ASSERT_TRUE(X509_ALGOR_set0(algor.get(), OBJ_nid2obj(kSignatureNID), |
| V_ASN1_NULL, nullptr)); |
| |
| // Test both signing with |X509_CRL_sign| and constructing a signature |
| // manually. |
| for (bool sign_manual : {true, false}) { |
| SCOPED_TRACE(sign_manual); |
| |
| // Test CRLs made both from other CRLs and |X509_CRL_new|, in case there are |
| // bugs in filling in fields from different states. (Parsed CRLs contain a |
| // TBSCertList cache, and |X509_CRL_new| initializes fields based on complex |
| // ASN.1 template logic.) |
| for (bool new_crl : {true, false}) { |
| SCOPED_TRACE(new_crl); |
| |
| bssl::UniquePtr<X509_CRL> crl; |
| if (new_crl) { |
| crl.reset(X509_CRL_new()); |
| ASSERT_TRUE(crl); |
| // Fill in some fields for the certificate arbitrarily. |
| ASSERT_TRUE(X509_CRL_set_version(crl.get(), X509_CRL_VERSION_2)); |
| bssl::UniquePtr<ASN1_TIME> last_update(ASN1_TIME_new()); |
| ASSERT_TRUE(last_update); |
| ASSERT_TRUE(ASN1_TIME_set_posix(last_update.get(), kReferenceTime)); |
| ASSERT_TRUE(X509_CRL_set1_lastUpdate(crl.get(), last_update.get())); |
| bssl::UniquePtr<X509_NAME> issuer(X509_NAME_new()); |
| ASSERT_TRUE(issuer); |
| ASSERT_TRUE(X509_NAME_add_entry_by_txt( |
| issuer.get(), "CN", MBSTRING_ASC, |
| reinterpret_cast<const uint8_t *>("Test"), -1, -1, 0)); |
| EXPECT_TRUE(X509_CRL_set_issuer_name(crl.get(), issuer.get())); |
| } else { |
| // Extract fields from a parsed CRL. |
| crl = CRLFromPEM(kBasicCRL); |
| ASSERT_TRUE(crl); |
| |
| // We should test with a different algorithm from what is already in the |
| // CRL. |
| EXPECT_NE(kSignatureNID, X509_CRL_get_signature_nid(crl.get())); |
| } |
| |
| if (sign_manual) { |
| // Fill in the signature algorithm. |
| ASSERT_TRUE(X509_CRL_set1_signature_algo(crl.get(), algor.get())); |
| |
| // Extract the TBSCertList. |
| uint8_t *tbs = nullptr; |
| int tbs_len = i2d_re_X509_CRL_tbs(crl.get(), &tbs); |
| bssl::UniquePtr<uint8_t> free_tbs(tbs); |
| ASSERT_GT(tbs_len, 0); |
| |
| // Generate a signature externally and fill it in. |
| bssl::ScopedEVP_MD_CTX md_ctx; |
| ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), nullptr, kSignatureHash, |
| nullptr, pkey.get())); |
| size_t sig_len; |
| ASSERT_TRUE( |
| EVP_DigestSign(md_ctx.get(), nullptr, &sig_len, tbs, tbs_len)); |
| std::vector<uint8_t> sig(sig_len); |
| ASSERT_TRUE( |
| EVP_DigestSign(md_ctx.get(), sig.data(), &sig_len, tbs, tbs_len)); |
| sig.resize(sig_len); |
| ASSERT_TRUE( |
| X509_CRL_set1_signature_value(crl.get(), sig.data(), sig.size())); |
| } else { |
| ASSERT_TRUE(X509_CRL_sign(crl.get(), pkey.get(), EVP_sha384())); |
| } |
| |
| // Check the signature. |
| EXPECT_TRUE(X509_CRL_verify(crl.get(), pkey.get())); |
| |
| // Re-encode the CRL. X509_CRL objects contain a cached TBSCertList |
| // encoding and re-signing should have dropped that cache. |
| bssl::UniquePtr<X509_CRL> copy = ReencodeCRL(crl.get()); |
| ASSERT_TRUE(copy); |
| EXPECT_TRUE(X509_CRL_verify(copy.get(), pkey.get())); |
| } |
| } |
| } |
| |
| static const char kTestCSR[] = R"( |
| -----BEGIN CERTIFICATE REQUEST----- |
| MIICVDCCATwCAQAwDzENMAsGA1UEAwwEVGVzdDCCASIwDQYJKoZIhvcNAQEBBQAD |
| ggEPADCCAQoCggEBAK+UkwcNJfRhg5MzIQzxDdrqF9a76jNoK/BwCflKYFX7QEqf |
| rsLkI0J+m60fUD0v50LnKwbGoMFKZ1R/3cBNXLcdXb7ZP/ZJ7A7QwUrL+W9n3sov |
| U8/HSU3rHbg+V5L6egSZYuhDHoXKi33HDOL4DVUzMoU1ykmP4QwF1wUXHLqvqjbU |
| teQBoJWO53/XOGQu8bX04muCFnHZWT2Ubqol70JwPU2PqDU1EBlgUFO79NEmflev |
| b++H8tu42UCDUZXD9k5weftjneO4cud3IsUX6mDsyf7k1e2mxsS4TSZsJcG0iLBX |
| HSr1udXazQsjlAKjJkoI3cWshF6LGRWssAtbGiUCAwEAAaAAMA0GCSqGSIb3DQEB |
| CwUAA4IBAQAniYZL+amXu+wED+AwBZz+zPuxY16bveF27/gxcs/jq6hVpEQvMxfO |
| jfAGeDRtAU7DMxdJPjvWwwNe2JlTMSRoVDMYaiKqB5yxIYa2cjQvp7swSxuFJwbG |
| T8h7/d7yqem6NYYzgYsNOE5QJyNu/PsIEdvzrysfDAnREiT2ituOcVpiqUZq3DTj |
| NaTd1GNG3j4E87ZUmayUJD5nH91UNzKvJbpfo+bLyfy73x4QeU0SRitsZmbSBTAi |
| s9+zmCErxzMlAdJHGzxPkXmtvBnUzGRIsAD5h/DjYNUmQJkB60yplt84ZgThhx54 |
| rZGEJG3+X9OuhczVKGJyg+3gU7oDbecc |
| -----END CERTIFICATE REQUEST----- |
| )"; |
| |
| // Test the APIs for signing a CSR, particularly whether they correctly handle |
| // the CertificationRequestInfo cache. |
| TEST(X509Test, SignCSR) { |
| const int kSignatureNID = NID_sha384WithRSAEncryption; |
| const EVP_MD *kSignatureHash = EVP_sha384(); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(PrivateKeyFromPEM(kRSAKey)); |
| ASSERT_TRUE(pkey); |
| bssl::UniquePtr<X509_ALGOR> algor(X509_ALGOR_new()); |
| ASSERT_TRUE(algor); |
| ASSERT_TRUE(X509_ALGOR_set0(algor.get(), OBJ_nid2obj(kSignatureNID), |
| V_ASN1_NULL, nullptr)); |
| |
| // Test both signing with |X509_REQ_sign| and constructing a signature |
| // manually. |
| for (bool sign_manual : {true, false}) { |
| SCOPED_TRACE(sign_manual); |
| |
| // Test CSRs made both from other CSRs and |X509_REQ_new|, in case there are |
| // bugs in filling in fields from different states. (Parsed CSRs contain a |
| // CertificationRequestInfo cache, and |X509_REQ_new| initializes fields |
| // based on complex ASN.1 template logic.) |
| for (bool new_csr : {true, false}) { |
| SCOPED_TRACE(new_csr); |
| |
| bssl::UniquePtr<X509_REQ> csr; |
| if (new_csr) { |
| csr.reset(X509_REQ_new()); |
| ASSERT_TRUE(csr); |
| bssl::UniquePtr<X509_NAME> subject(X509_NAME_new()); |
| ASSERT_TRUE(subject); |
| ASSERT_TRUE(X509_NAME_add_entry_by_txt( |
| subject.get(), "CN", MBSTRING_ASC, |
| reinterpret_cast<const uint8_t *>("New CSR"), -1, -1, 0)); |
| EXPECT_TRUE(X509_REQ_set_subject_name(csr.get(), subject.get())); |
| } else { |
| // Extract fields from a parsed CSR. |
| csr = CSRFromPEM(kTestCSR); |
| ASSERT_TRUE(csr); |
| } |
| |
| // Override the public key from the CSR unconditionally. Unlike |
| // certificates and CRLs, CSRs do not contain a signed copy of the |
| // signature algorithm, so we use a different field to confirm |
| // |i2d_re_X509_REQ_tbs| clears the cache as expected. |
| EXPECT_TRUE(X509_REQ_set_pubkey(csr.get(), pkey.get())); |
| |
| if (sign_manual) { |
| // Fill in the signature algorithm. |
| ASSERT_TRUE(X509_REQ_set1_signature_algo(csr.get(), algor.get())); |
| |
| // Extract the CertificationRequestInfo. |
| uint8_t *tbs = nullptr; |
| int tbs_len = i2d_re_X509_REQ_tbs(csr.get(), &tbs); |
| bssl::UniquePtr<uint8_t> free_tbs(tbs); |
| ASSERT_GT(tbs_len, 0); |
| |
| // Generate a signature externally and fill it in. |
| bssl::ScopedEVP_MD_CTX md_ctx; |
| ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), nullptr, kSignatureHash, |
| nullptr, pkey.get())); |
| size_t sig_len; |
| ASSERT_TRUE( |
| EVP_DigestSign(md_ctx.get(), nullptr, &sig_len, tbs, tbs_len)); |
| std::vector<uint8_t> sig(sig_len); |
| ASSERT_TRUE( |
| EVP_DigestSign(md_ctx.get(), sig.data(), &sig_len, tbs, tbs_len)); |
| sig.resize(sig_len); |
| ASSERT_TRUE( |
| X509_REQ_set1_signature_value(csr.get(), sig.data(), sig.size())); |
| } else { |
| ASSERT_TRUE(X509_REQ_sign(csr.get(), pkey.get(), EVP_sha384())); |
| } |
| |
| // Check the signature. |
| EXPECT_TRUE(X509_REQ_verify(csr.get(), pkey.get())); |
| |
| // Re-encode the CSR. X509_REQ objects contain a cached |
| // CertificationRequestInfo encoding and re-signing should have dropped |
| // that cache. |
| bssl::UniquePtr<X509_REQ> copy = ReencodeCSR(csr.get()); |
| ASSERT_TRUE(copy); |
| EXPECT_TRUE(X509_REQ_verify(copy.get(), pkey.get())); |
| |
| // Check the signature was over the new public key. |
| bssl::UniquePtr<EVP_PKEY> copy_pubkey(X509_REQ_get_pubkey(copy.get())); |
| ASSERT_TRUE(copy_pubkey); |
| EXPECT_EQ(1, EVP_PKEY_cmp(pkey.get(), copy_pubkey.get())); |
| } |
| } |
| } |
| |
| TEST(X509Test, Ed25519Sign) { |
| uint8_t pub_bytes[32], priv_bytes[64]; |
| ED25519_keypair(pub_bytes, priv_bytes); |
| |
| bssl::UniquePtr<EVP_PKEY> pub( |
| EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, pub_bytes, 32)); |
| ASSERT_TRUE(pub); |
| bssl::UniquePtr<EVP_PKEY> priv( |
| EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, priv_bytes, 32)); |
| ASSERT_TRUE(priv); |
| |
| bssl::ScopedEVP_MD_CTX md_ctx; |
| ASSERT_TRUE( |
| EVP_DigestSignInit(md_ctx.get(), nullptr, nullptr, nullptr, priv.get())); |
| ASSERT_TRUE(SignatureRoundTrips(md_ctx.get(), pub.get())); |
| } |
| |
| static bool PEMToDER(bssl::UniquePtr<uint8_t> *out, size_t *out_len, |
| const char *pem) { |
| bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem))); |
| if (!bio) { |
| return false; |
| } |
| |
| char *name, *header; |
| uint8_t *data; |
| long data_len; |
| if (!PEM_read_bio(bio.get(), &name, &header, &data, &data_len)) { |
| fprintf(stderr, "failed to read PEM data.\n"); |
| return false; |
| } |
| OPENSSL_free(name); |
| OPENSSL_free(header); |
| |
| out->reset(data); |
| *out_len = data_len; |
| |
| return true; |
| } |
| |
| TEST(X509Test, TestFromBuffer) { |
| size_t data_len; |
| bssl::UniquePtr<uint8_t> data; |
| ASSERT_TRUE(PEMToDER(&data, &data_len, kRootCAPEM)); |
| |
| bssl::UniquePtr<CRYPTO_BUFFER> buf( |
| CRYPTO_BUFFER_new(data.get(), data_len, nullptr)); |
| ASSERT_TRUE(buf); |
| bssl::UniquePtr<X509> root(X509_parse_from_buffer(buf.get())); |
| ASSERT_TRUE(root); |
| |
| const uint8_t *enc_pointer = root->cert_info->enc.enc; |
| const uint8_t *buf_pointer = CRYPTO_BUFFER_data(buf.get()); |
| ASSERT_GE(enc_pointer, buf_pointer); |
| ASSERT_LT(enc_pointer, buf_pointer + CRYPTO_BUFFER_len(buf.get())); |
| buf.reset(); |
| |
| /* This ensures the X509 took a reference to |buf|, otherwise this will be a |
| * reference to free memory and ASAN should notice. */ |
| ASSERT_EQ(0x30, enc_pointer[0]); |
| } |
| |
| TEST(X509Test, TestFromBufferWithTrailingData) { |
| size_t data_len; |
| bssl::UniquePtr<uint8_t> data; |
| ASSERT_TRUE(PEMToDER(&data, &data_len, kRootCAPEM)); |
| |
| auto trailing_data = std::make_unique<uint8_t[]>(data_len + 1); |
| OPENSSL_memcpy(trailing_data.get(), data.get(), data_len); |
| |
| bssl::UniquePtr<CRYPTO_BUFFER> buf_trailing_data( |
| CRYPTO_BUFFER_new(trailing_data.get(), data_len + 1, nullptr)); |
| ASSERT_TRUE(buf_trailing_data); |
| |
| bssl::UniquePtr<X509> root_trailing_data( |
| X509_parse_from_buffer(buf_trailing_data.get())); |
| ASSERT_FALSE(root_trailing_data); |
| } |
| |
| TEST(X509Test, TestFromBufferModified) { |
| size_t data_len; |
| bssl::UniquePtr<uint8_t> data; |
| ASSERT_TRUE(PEMToDER(&data, &data_len, kRootCAPEM)); |
| |
| bssl::UniquePtr<CRYPTO_BUFFER> buf( |
| CRYPTO_BUFFER_new(data.get(), data_len, nullptr)); |
| ASSERT_TRUE(buf); |
| |
| bssl::UniquePtr<X509> root(X509_parse_from_buffer(buf.get())); |
| ASSERT_TRUE(root); |
| |
| bssl::UniquePtr<ASN1_INTEGER> fourty_two(ASN1_INTEGER_new()); |
| ASN1_INTEGER_set_int64(fourty_two.get(), 42); |
| X509_set_serialNumber(root.get(), fourty_two.get()); |
| |
| ASSERT_EQ(static_cast<long>(data_len), i2d_X509(root.get(), nullptr)); |
| |
| // Re-encode the TBSCertificate. |
| i2d_re_X509_tbs(root.get(), nullptr); |
| |
| ASSERT_NE(static_cast<long>(data_len), i2d_X509(root.get(), nullptr)); |
| } |
| |
| TEST(X509Test, TestFromBufferReused) { |
| size_t data_len; |
| bssl::UniquePtr<uint8_t> data; |
| ASSERT_TRUE(PEMToDER(&data, &data_len, kRootCAPEM)); |
| |
| bssl::UniquePtr<CRYPTO_BUFFER> buf( |
| CRYPTO_BUFFER_new(data.get(), data_len, nullptr)); |
| ASSERT_TRUE(buf); |
| |
| bssl::UniquePtr<X509> root(X509_parse_from_buffer(buf.get())); |
| ASSERT_TRUE(root); |
| |
| size_t data2_len; |
| bssl::UniquePtr<uint8_t> data2; |
| ASSERT_TRUE(PEMToDER(&data2, &data2_len, kLeafPEM)); |
| EXPECT_TRUE(buffers_alias(root->cert_info->enc.enc, root->cert_info->enc.len, |
| CRYPTO_BUFFER_data(buf.get()), |
| CRYPTO_BUFFER_len(buf.get()))); |
| |
| // Historically, this function tested the interaction betweeen |
| // |X509_parse_from_buffer| and object reuse. We no longer support object |
| // reuse, so |d2i_X509| will replace |raw| with a new object. However, we |
| // retain this test to verify that releasing objects from |d2i_X509| works |
| // correctly. |
| X509 *raw = root.release(); |
| const uint8_t *inp = data2.get(); |
| X509 *ret = d2i_X509(&raw, &inp, data2_len); |
| root.reset(raw); |
| |
| ASSERT_EQ(root.get(), ret); |
| ASSERT_EQ(nullptr, root->cert_info->enc.buf); |
| EXPECT_FALSE(buffers_alias(root->cert_info->enc.enc, root->cert_info->enc.len, |
| CRYPTO_BUFFER_data(buf.get()), |
| CRYPTO_BUFFER_len(buf.get()))); |
| |
| // Free |data2| and ensure that |root| took its own copy. Otherwise the |
| // following will trigger a use-after-free. |
| data2.reset(); |
| |
| uint8_t *i2d = nullptr; |
| int i2d_len = i2d_X509(root.get(), &i2d); |
| ASSERT_GE(i2d_len, 0); |
| bssl::UniquePtr<uint8_t> i2d_storage(i2d); |
| |
| ASSERT_TRUE(PEMToDER(&data2, &data2_len, kLeafPEM)); |
| |
| ASSERT_EQ(static_cast<long>(data2_len), i2d_len); |
| ASSERT_EQ(0, OPENSSL_memcmp(data2.get(), i2d, i2d_len)); |
| ASSERT_EQ(nullptr, root->cert_info->enc.buf); |
| } |
| |
| TEST(X509Test, TestFailedParseFromBuffer) { |
| static const uint8_t kNonsense[] = {1, 2, 3, 4, 5}; |
| |
| bssl::UniquePtr<CRYPTO_BUFFER> buf( |
| CRYPTO_BUFFER_new(kNonsense, sizeof(kNonsense), nullptr)); |
| ASSERT_TRUE(buf); |
| |
| bssl::UniquePtr<X509> cert(X509_parse_from_buffer(buf.get())); |
| ASSERT_FALSE(cert); |
| ERR_clear_error(); |
| |
| // Test a buffer with trailing data. |
| size_t data_len; |
| bssl::UniquePtr<uint8_t> data; |
| ASSERT_TRUE(PEMToDER(&data, &data_len, kRootCAPEM)); |
| |
| auto data_with_trailing_byte = std::make_unique<uint8_t[]>(data_len + 1); |
| OPENSSL_memcpy(data_with_trailing_byte.get(), data.get(), data_len); |
| data_with_trailing_byte[data_len] = 0; |
| |
| bssl::UniquePtr<CRYPTO_BUFFER> buf_with_trailing_byte( |
| CRYPTO_BUFFER_new(data_with_trailing_byte.get(), data_len + 1, nullptr)); |
| ASSERT_TRUE(buf_with_trailing_byte); |
| |
| bssl::UniquePtr<X509> root( |
| X509_parse_from_buffer(buf_with_trailing_byte.get())); |
| ASSERT_FALSE(root); |
| ERR_clear_error(); |
| } |
| |
| TEST(X509Test, TestPrintUTCTIME) { |
| static const struct { |
| const char *val, *want; |
| } asn1_utctime_tests[] = { |
| {"", "Bad time value"}, |
| |
| // Correct RFC 5280 form. Test years < 2000 and > 2000. |
| {"090303125425Z", "Mar 3 12:54:25 2009 GMT"}, |
| {"900303125425Z", "Mar 3 12:54:25 1990 GMT"}, |
| {"000303125425Z", "Mar 3 12:54:25 2000 GMT"}, |
| |
| // Correct form, bad values. |
| {"000000000000Z", "Bad time value"}, |
| {"999999999999Z", "Bad time value"}, |
| |
| // Missing components. |
| {"090303125425", "Bad time value"}, |
| {"9003031254", "Bad time value"}, |
| {"9003031254Z", "Bad time value"}, |
| |
| // GENERALIZEDTIME confused for UTCTIME. |
| {"20090303125425Z", "Bad time value"}, |
| |
| // Legal ASN.1, but not legal RFC 5280. |
| {"9003031254+0800", "Bad time value"}, |
| {"9003031254-0800", "Bad time value"}, |
| |
| // Trailing garbage. |
| {"9003031254Z ", "Bad time value"}, |
| }; |
| |
| for (auto t : asn1_utctime_tests) { |
| SCOPED_TRACE(t.val); |
| bssl::UniquePtr<ASN1_UTCTIME> tm(ASN1_UTCTIME_new()); |
| ASSERT_TRUE(tm); |
| bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem())); |
| ASSERT_TRUE(bio); |
| |
| // Use this instead of ASN1_UTCTIME_set() because some callers get |
| // type-confused and pass ASN1_GENERALIZEDTIME to ASN1_UTCTIME_print(). |
| // ASN1_UTCTIME_set_string() is stricter, and would reject the inputs in |
| // question. |
| ASSERT_TRUE(ASN1_STRING_set(tm.get(), t.val, strlen(t.val))); |
| const int ok = ASN1_UTCTIME_print(bio.get(), tm.get()); |
| |
| const uint8_t *contents; |
| size_t len; |
| ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len)); |
| EXPECT_EQ(ok, (strcmp(t.want, "Bad time value") != 0) ? 1 : 0); |
| EXPECT_EQ(t.want, |
| std::string(reinterpret_cast<const char *>(contents), len)); |
| } |
| } |
| |
| TEST(X509Test, PrettyPrintIntegers) { |
| static const char *kTests[] = { |
| // Small numbers are pretty-printed in decimal. |
| "0", |
| "-1", |
| "1", |
| "42", |
| "-42", |
| "256", |
| "-256", |
| // Large numbers are pretty-printed in hex to avoid taking quadratic time. |
| "0x0123456789", |
| "-0x0123456789", |
| }; |
| for (const char *in : kTests) { |
| SCOPED_TRACE(in); |
| BIGNUM *bn = nullptr; |
| ASSERT_TRUE(BN_asc2bn(&bn, in)); |
| bssl::UniquePtr<BIGNUM> free_bn(bn); |
| |
| { |
| bssl::UniquePtr<ASN1_INTEGER> asn1(BN_to_ASN1_INTEGER(bn, nullptr)); |
| ASSERT_TRUE(asn1); |
| bssl::UniquePtr<char> out(i2s_ASN1_INTEGER(nullptr, asn1.get())); |
| ASSERT_TRUE(out.get()); |
| EXPECT_STREQ(in, out.get()); |
| } |
| |
| { |
| bssl::UniquePtr<ASN1_ENUMERATED> asn1(BN_to_ASN1_ENUMERATED(bn, nullptr)); |
| ASSERT_TRUE(asn1); |
| bssl::UniquePtr<char> out(i2s_ASN1_ENUMERATED(nullptr, asn1.get())); |
| ASSERT_TRUE(out.get()); |
| EXPECT_STREQ(in, out.get()); |
| } |
| } |
| } |
| |
| TEST(X509Test, X509AlgorSetMd) { |
| bssl::UniquePtr<X509_ALGOR> alg(X509_ALGOR_new()); |
| ASSERT_TRUE(alg); |
| EXPECT_TRUE(X509_ALGOR_set_md(alg.get(), EVP_sha256())); |
| const ASN1_OBJECT *obj; |
| const void *pval; |
| int ptype = 0; |
| X509_ALGOR_get0(&obj, &ptype, &pval, alg.get()); |
| EXPECT_TRUE(obj); |
| EXPECT_EQ(OBJ_obj2nid(obj), NID_sha256); |
| EXPECT_EQ(ptype, V_ASN1_NULL); // OpenSSL has V_ASN1_UNDEF |
| EXPECT_EQ(pval, nullptr); |
| EXPECT_TRUE(X509_ALGOR_set_md(alg.get(), EVP_md5())); |
| X509_ALGOR_get0(&obj, &ptype, &pval, alg.get()); |
| EXPECT_EQ(OBJ_obj2nid(obj), NID_md5); |
| EXPECT_EQ(ptype, V_ASN1_NULL); |
| EXPECT_EQ(pval, nullptr); |
| } |
| |
| TEST(X509Test, X509NameSet) { |
| bssl::UniquePtr<X509_NAME> name(X509_NAME_new()); |
| ASSERT_TRUE(name); |
| EXPECT_TRUE(X509_NAME_add_entry_by_txt( |
| name.get(), "C", MBSTRING_ASC, reinterpret_cast<const uint8_t *>("US"), |
| -1, -1, 0)); |
| EXPECT_EQ(X509_NAME_entry_count(name.get()), 1); |
| EXPECT_TRUE(X509_NAME_add_entry_by_txt( |
| name.get(), "C", MBSTRING_ASC, reinterpret_cast<const uint8_t *>("CA"), |
| -1, -1, 0)); |
| EXPECT_EQ(X509_NAME_entry_count(name.get()), 2); |
| EXPECT_TRUE(X509_NAME_add_entry_by_txt( |
| name.get(), "C", MBSTRING_ASC, reinterpret_cast<const uint8_t *>("UK"), |
| -1, -1, 0)); |
| EXPECT_EQ(X509_NAME_entry_count(name.get()), 3); |
| EXPECT_TRUE(X509_NAME_add_entry_by_txt( |
| name.get(), "C", MBSTRING_ASC, reinterpret_cast<const uint8_t *>("JP"), |
| -1, 1, 0)); |
| EXPECT_EQ(X509_NAME_entry_count(name.get()), 4); |
| |
| // Check that the correct entries get incremented when inserting new entry. |
| EXPECT_EQ(X509_NAME_ENTRY_set(X509_NAME_get_entry(name.get(), 1)), 1); |
| EXPECT_EQ(X509_NAME_ENTRY_set(X509_NAME_get_entry(name.get(), 2)), 2); |
| } |
| |
| // Tests that |X509_NAME_hash| and |X509_NAME_hash_old|'s values never change. |
| // These functions figure into |X509_LOOKUP_hash_dir|'s on-disk format, so they |
| // must remain stable. In particular, if we ever remove name canonicalization, |
| // we'll need to preserve it for |X509_NAME_hash|. |
| TEST(X509Test, NameHash) { |
| struct { |
| std::vector<uint8_t> name_der; |
| uint32_t hash; |
| uint32_t hash_old; |
| } kTests[] = { |
| // SEQUENCE { |
| // SET { |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // UTF8String { "Test Name" } |
| // } |
| // } |
| // } |
| {{0x30, 0x14, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, |
| 0x0c, 0x09, 0x54, 0x65, 0x73, 0x74, 0x20, 0x4e, 0x61, 0x6d, 0x65}, |
| 0xc90fba01, |
| 0x8c0d4fea}, |
| |
| // This name canonicalizes to the same value, with OpenSSL's algorithm, as |
| // the above input, so |hash| matches. |hash_old| doesn't use |
| // canonicalization and does not match. |
| // |
| // SEQUENCE { |
| // SET { |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // BMPString { |
| // u"\x09\n\x0b\x0c\x0d tEST\x09\n\x0b\x0c\x0d " |
| // u"\x09\n\x0b\x0c\x0d nAME\x09\n\x0b\x0c\x0d " |
| // } |
| // } |
| // } |
| // } |
| {{0x30, 0x4b, 0x31, 0x49, 0x30, 0x47, 0x06, 0x03, 0x55, 0x04, 0x03, |
| 0x1e, 0x40, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, |
| 0x0d, 0x00, 0x20, 0x00, 0x74, 0x00, 0x45, 0x00, 0x53, 0x00, 0x54, |
| 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, |
| 0x20, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, |
| 0x00, 0x20, 0x00, 0x6e, 0x00, 0x41, 0x00, 0x4d, 0x00, 0x45, 0x00, |
| 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x20}, |
| 0xc90fba01, |
| 0xbe2dd8c8}, |
| }; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(Bytes(t.name_der)); |
| const uint8_t *der = t.name_der.data(); |
| bssl::UniquePtr<X509_NAME> name( |
| d2i_X509_NAME(nullptr, &der, t.name_der.size())); |
| ASSERT_TRUE(name); |
| EXPECT_EQ(t.hash, X509_NAME_hash(name.get())); |
| EXPECT_EQ(t.hash_old, X509_NAME_hash_old(name.get())); |
| } |
| } |
| |
| TEST(X509Test, NoBasicConstraintsCertSign) { |
| bssl::UniquePtr<X509> root(CertFromPEM(kSANTypesRoot)); |
| bssl::UniquePtr<X509> intermediate( |
| CertFromPEM(kNoBasicConstraintsCertSignIntermediate)); |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kNoBasicConstraintsCertSignLeaf)); |
| |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(leaf); |
| |
| // The intermediate has keyUsage certSign, but is not marked as a CA in the |
| // basicConstraints. |
| EXPECT_EQ(X509_V_ERR_INVALID_CA, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0)); |
| |
| // |X509_check_purpose| with |X509_PURPOSE_ANY| and purpose -1 do not check |
| // basicConstraints, but other purpose types do. (This is redundant with the |
| // actual basicConstraints check, but |X509_check_purpose| is public API.) |
| EXPECT_TRUE(X509_check_purpose(intermediate.get(), -1, /*ca=*/1)); |
| EXPECT_TRUE( |
| X509_check_purpose(intermediate.get(), X509_PURPOSE_ANY, /*ca=*/1)); |
| EXPECT_FALSE(X509_check_purpose(intermediate.get(), X509_PURPOSE_SSL_SERVER, |
| /*ca=*/1)); |
| } |
| |
| TEST(X509Test, NoBasicConstraintsNetscapeCA) { |
| bssl::UniquePtr<X509> root(CertFromPEM(kSANTypesRoot)); |
| bssl::UniquePtr<X509> intermediate( |
| CertFromPEM(kNoBasicConstraintsNetscapeCAIntermediate)); |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kNoBasicConstraintsNetscapeCALeaf)); |
| |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(leaf); |
| |
| // The intermediate has a Netscape certificate type of "SSL CA", but is not |
| // marked as a CA in the basicConstraints. |
| EXPECT_EQ(X509_V_ERR_INVALID_CA, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0)); |
| } |
| |
| TEST(X509Test, MismatchAlgorithms) { |
| bssl::UniquePtr<X509> cert(CertFromPEM(kSelfSignedMismatchAlgorithms)); |
| ASSERT_TRUE(cert); |
| |
| bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get())); |
| ASSERT_TRUE(pkey); |
| |
| EXPECT_FALSE(X509_verify(cert.get(), pkey.get())); |
| EXPECT_TRUE(ErrorEquals(ERR_get_error(), ERR_LIB_X509, |
| X509_R_SIGNATURE_ALGORITHM_MISMATCH)); |
| } |
| |
| TEST(X509Test, PEMX509Info) { |
| std::string cert = kRootCAPEM; |
| auto cert_obj = CertFromPEM(kRootCAPEM); |
| ASSERT_TRUE(cert_obj); |
| |
| std::string rsa = kRSAKey; |
| auto rsa_obj = PrivateKeyFromPEM(kRSAKey); |
| ASSERT_TRUE(rsa_obj); |
| |
| std::string crl = kBasicCRL; |
| auto crl_obj = CRLFromPEM(kBasicCRL); |
| ASSERT_TRUE(crl_obj); |
| |
| std::string unknown = |
| "-----BEGIN UNKNOWN-----\n" |
| "AAAA\n" |
| "-----END UNKNOWN-----\n"; |
| |
| std::string invalid = |
| "-----BEGIN CERTIFICATE-----\n" |
| "AAAA\n" |
| "-----END CERTIFICATE-----\n"; |
| |
| // Each X509_INFO contains at most one certificate, CRL, etc. The format |
| // creates a new X509_INFO when a repeated type is seen. |
| std::string pem = |
| // The first few entries have one of everything in different orders. |
| cert + rsa + crl + |
| rsa + crl + cert + |
| // Unknown types are ignored. |
| crl + unknown + cert + rsa + |
| // Seeing a new certificate starts a new entry, so now we have a bunch of |
| // certificate-only entries. |
| cert + cert + cert + |
| // The key folds into the certificate's entry. |
| cert + rsa + |
| // Doubled keys also start new entries. |
| rsa + rsa + rsa + rsa + crl + |
| // As do CRLs. |
| crl + crl; |
| |
| const struct ExpectedInfo { |
| const X509 *cert; |
| const EVP_PKEY *key; |
| const X509_CRL *crl; |
| } kExpected[] = { |
| {cert_obj.get(), rsa_obj.get(), crl_obj.get()}, |
| {cert_obj.get(), rsa_obj.get(), crl_obj.get()}, |
| {cert_obj.get(), rsa_obj.get(), crl_obj.get()}, |
| {cert_obj.get(), nullptr, nullptr}, |
| {cert_obj.get(), nullptr, nullptr}, |
| {cert_obj.get(), nullptr, nullptr}, |
| {cert_obj.get(), rsa_obj.get(), nullptr}, |
| {nullptr, rsa_obj.get(), nullptr}, |
| {nullptr, rsa_obj.get(), nullptr}, |
| {nullptr, rsa_obj.get(), nullptr}, |
| {nullptr, rsa_obj.get(), crl_obj.get()}, |
| {nullptr, nullptr, crl_obj.get()}, |
| {nullptr, nullptr, crl_obj.get()}, |
| }; |
| |
| auto check_info = [](const ExpectedInfo *expected, const X509_INFO *info) { |
| if (expected->cert != nullptr) { |
| EXPECT_EQ(0, X509_cmp(expected->cert, info->x509)); |
| } else { |
| EXPECT_EQ(nullptr, info->x509); |
| } |
| if (expected->crl != nullptr) { |
| EXPECT_EQ(0, X509_CRL_cmp(expected->crl, info->crl)); |
| } else { |
| EXPECT_EQ(nullptr, info->crl); |
| } |
| if (expected->key != nullptr) { |
| ASSERT_NE(nullptr, info->x_pkey); |
| // EVP_PKEY_cmp returns one if the keys are equal. |
| EXPECT_EQ(1, EVP_PKEY_cmp(expected->key, info->x_pkey->dec_pkey)); |
| } else { |
| EXPECT_EQ(nullptr, info->x_pkey); |
| } |
| }; |
| |
| bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem.data(), pem.size())); |
| ASSERT_TRUE(bio); |
| bssl::UniquePtr<STACK_OF(X509_INFO)> infos( |
| PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); |
| ASSERT_TRUE(infos); |
| ASSERT_EQ(OPENSSL_ARRAY_SIZE(kExpected), sk_X509_INFO_num(infos.get())); |
| for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kExpected); i++) { |
| SCOPED_TRACE(i); |
| check_info(&kExpected[i], sk_X509_INFO_value(infos.get(), i)); |
| } |
| |
| // Passing an existing stack appends to it. |
| bio.reset(BIO_new_mem_buf(pem.data(), pem.size())); |
| ASSERT_TRUE(bio); |
| ASSERT_EQ(infos.get(), |
| PEM_X509_INFO_read_bio(bio.get(), infos.get(), nullptr, nullptr)); |
| ASSERT_EQ(2 * OPENSSL_ARRAY_SIZE(kExpected), sk_X509_INFO_num(infos.get())); |
| for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kExpected); i++) { |
| SCOPED_TRACE(i); |
| check_info(&kExpected[i], sk_X509_INFO_value(infos.get(), i)); |
| check_info( |
| &kExpected[i], |
| sk_X509_INFO_value(infos.get(), i + OPENSSL_ARRAY_SIZE(kExpected))); |
| } |
| |
| // Gracefully handle errors in both the append and fresh cases. |
| std::string bad_pem = cert + cert + invalid; |
| |
| bio.reset(BIO_new_mem_buf(bad_pem.data(), bad_pem.size())); |
| ASSERT_TRUE(bio); |
| bssl::UniquePtr<STACK_OF(X509_INFO)> infos2( |
| PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr)); |
| EXPECT_FALSE(infos2); |
| |
| bio.reset(BIO_new_mem_buf(bad_pem.data(), bad_pem.size())); |
| ASSERT_TRUE(bio); |
| EXPECT_FALSE( |
| PEM_X509_INFO_read_bio(bio.get(), infos.get(), nullptr, nullptr)); |
| EXPECT_EQ(2 * OPENSSL_ARRAY_SIZE(kExpected), sk_X509_INFO_num(infos.get())); |
| } |
| |
| TEST(X509Test, ReadBIOEmpty) { |
| bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(nullptr, 0)); |
| ASSERT_TRUE(bio); |
| |
| // CPython expects |ASN1_R_HEADER_TOO_LONG| on EOF, to terminate a series of |
| // certificates. |
| bssl::UniquePtr<X509> x509(d2i_X509_bio(bio.get(), nullptr)); |
| EXPECT_FALSE(x509); |
| EXPECT_TRUE( |
| ErrorEquals(ERR_get_error(), ERR_LIB_ASN1, ASN1_R_HEADER_TOO_LONG)); |
| } |
| |
| TEST(X509Test, ReadBIOOneByte) { |
| bssl::UniquePtr<BIO> bio(BIO_new_mem_buf("\x30", 1)); |
| ASSERT_TRUE(bio); |
| |
| // CPython expects |ASN1_R_HEADER_TOO_LONG| on EOF, to terminate a series of |
| // certificates. This EOF appeared after some data, however, so we do not wish |
| // to signal EOF. |
| bssl::UniquePtr<X509> x509(d2i_X509_bio(bio.get(), nullptr)); |
| EXPECT_FALSE(x509); |
| EXPECT_TRUE( |
| ErrorEquals(ERR_get_error(), ERR_LIB_ASN1, ASN1_R_NOT_ENOUGH_DATA)); |
| } |
| |
| TEST(X509Test, PartialBIOReturn) { |
| // Create a filter BIO that only reads and writes one byte at a time. |
| bssl::UniquePtr<BIO_METHOD> method(BIO_meth_new(0, nullptr)); |
| ASSERT_TRUE(method); |
| ASSERT_TRUE(BIO_meth_set_create(method.get(), [](BIO *b) -> int { |
| BIO_set_init(b, 1); |
| return 1; |
| })); |
| ASSERT_TRUE( |
| BIO_meth_set_read(method.get(), [](BIO *b, char *out, int len) -> int { |
| return BIO_read(BIO_next(b), out, std::min(len, 1)); |
| })); |
| ASSERT_TRUE(BIO_meth_set_write( |
| method.get(), [](BIO *b, const char *in, int len) -> int { |
| return BIO_write(BIO_next(b), in, std::min(len, 1)); |
| })); |
| |
| bssl::UniquePtr<BIO> bio(BIO_new(method.get())); |
| ASSERT_TRUE(bio); |
| BIO *mem_bio = BIO_new(BIO_s_mem()); |
| ASSERT_TRUE(mem_bio); |
| BIO_push(bio.get(), mem_bio); // BIO_push takes ownership. |
| |
| bssl::UniquePtr<X509> cert(CertFromPEM(kLeafPEM)); |
| ASSERT_TRUE(cert); |
| uint8_t *der = nullptr; |
| int der_len = i2d_X509(cert.get(), &der); |
| ASSERT_GT(der_len, 0); |
| bssl::UniquePtr<uint8_t> free_der(der); |
| |
| // Write the certificate into the BIO. Though we only write one byte at a |
| // time, the write should succeed. |
| ASSERT_EQ(1, i2d_X509_bio(bio.get(), cert.get())); |
| const uint8_t *der2; |
| size_t der2_len; |
| ASSERT_TRUE(BIO_mem_contents(mem_bio, &der2, &der2_len)); |
| EXPECT_EQ(Bytes(der, static_cast<size_t>(der_len)), Bytes(der2, der2_len)); |
| |
| // Read the certificate back out of the BIO. Though we only read one byte at a |
| // time, the read should succeed. |
| bssl::UniquePtr<X509> cert2(d2i_X509_bio(bio.get(), nullptr)); |
| ASSERT_TRUE(cert2); |
| EXPECT_EQ(0, X509_cmp(cert.get(), cert2.get())); |
| } |
| |
| TEST(X509Test, CommonNameFallback) { |
| bssl::UniquePtr<X509> root = CertFromPEM(kSANTypesRoot); |
| ASSERT_TRUE(root); |
| bssl::UniquePtr<X509> with_sans = CertFromPEM(kCommonNameWithSANs); |
| ASSERT_TRUE(with_sans); |
| bssl::UniquePtr<X509> without_sans = CertFromPEM(kCommonNameWithoutSANs); |
| ASSERT_TRUE(without_sans); |
| bssl::UniquePtr<X509> with_email = CertFromPEM(kCommonNameWithEmailSAN); |
| ASSERT_TRUE(with_email); |
| bssl::UniquePtr<X509> with_ip = CertFromPEM(kCommonNameWithIPSAN); |
| ASSERT_TRUE(with_ip); |
| |
| auto verify_cert = [&](X509 *leaf, unsigned flags, const char *host) { |
| return Verify(leaf, {root.get()}, {}, {}, 0, [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1_host(param, host, strlen(host))); |
| X509_VERIFY_PARAM_set_hostflags(param, flags); |
| }); |
| }; |
| |
| // By default, the common name is ignored if the SAN list is present but |
| // otherwise is checked. |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_sans.get(), 0 /* no flags */, "foo.host1.test")); |
| EXPECT_EQ(X509_V_OK, |
| verify_cert(with_sans.get(), 0 /* no flags */, "foo.host2.test")); |
| EXPECT_EQ(X509_V_OK, |
| verify_cert(with_sans.get(), 0 /* no flags */, "foo.host3.test")); |
| EXPECT_EQ(X509_V_OK, verify_cert(without_sans.get(), 0 /* no flags */, |
| "foo.host1.test")); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_email.get(), 0 /* no flags */, "foo.host1.test")); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_ip.get(), 0 /* no flags */, "foo.host1.test")); |
| |
| // X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT is ignored. |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_sans.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, |
| "foo.host1.test")); |
| EXPECT_EQ(X509_V_OK, |
| verify_cert(with_sans.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, |
| "foo.host2.test")); |
| EXPECT_EQ(X509_V_OK, |
| verify_cert(with_sans.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, |
| "foo.host3.test")); |
| EXPECT_EQ(X509_V_OK, verify_cert(without_sans.get(), |
| X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, |
| "foo.host1.test")); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_email.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, |
| "foo.host1.test")); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_ip.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, |
| "foo.host1.test")); |
| |
| // X509_CHECK_FLAG_NEVER_CHECK_SUBJECT implements the correct behavior: the |
| // common name is never checked. |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_sans.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, |
| "foo.host1.test")); |
| EXPECT_EQ(X509_V_OK, |
| verify_cert(with_sans.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, |
| "foo.host2.test")); |
| EXPECT_EQ(X509_V_OK, |
| verify_cert(with_sans.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, |
| "foo.host3.test")); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(without_sans.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, |
| "foo.host1.test")); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_email.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, |
| "foo.host1.test")); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(with_ip.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, |
| "foo.host1.test")); |
| } |
| |
| TEST(X509Test, LooksLikeDNSName) { |
| static const char *kValid[] = { |
| "example.com", |
| "eXample123-.com", |
| "*.example.com", |
| "exa_mple.com", |
| "example.com.", |
| "project-dev:us-central1:main", |
| }; |
| static const char *kInvalid[] = { |
| "-eXample123-.com", |
| "", |
| ".", |
| "*", |
| "*.", |
| "example..com", |
| ".example.com", |
| "example.com..", |
| "*foo.example.com", |
| "foo.*.example.com", |
| "foo,bar", |
| }; |
| |
| for (const char *str : kValid) { |
| SCOPED_TRACE(str); |
| EXPECT_TRUE(x509v3_looks_like_dns_name( |
| reinterpret_cast<const uint8_t *>(str), strlen(str))); |
| } |
| for (const char *str : kInvalid) { |
| SCOPED_TRACE(str); |
| EXPECT_FALSE(x509v3_looks_like_dns_name( |
| reinterpret_cast<const uint8_t *>(str), strlen(str))); |
| } |
| } |
| |
| TEST(X509Test, CommonNameAndNameConstraints) { |
| bssl::UniquePtr<X509> root = CertFromPEM(kSANTypesRoot); |
| ASSERT_TRUE(root); |
| bssl::UniquePtr<X509> intermediate = CertFromPEM(kConstrainedIntermediate); |
| ASSERT_TRUE(intermediate); |
| bssl::UniquePtr<X509> permitted = CertFromPEM(kCommonNamePermittedLeaf); |
| ASSERT_TRUE(permitted); |
| bssl::UniquePtr<X509> not_permitted = |
| CertFromPEM(kCommonNameNotPermittedLeaf); |
| ASSERT_TRUE(not_permitted); |
| bssl::UniquePtr<X509> not_permitted_with_sans = |
| CertFromPEM(kCommonNameNotPermittedWithSANsLeaf); |
| ASSERT_TRUE(not_permitted_with_sans); |
| bssl::UniquePtr<X509> not_dns = CertFromPEM(kCommonNameNotDNSLeaf); |
| ASSERT_TRUE(not_dns); |
| |
| auto verify_cert = [&](X509 *leaf, unsigned flags, const char *host) { |
| return Verify( |
| leaf, {root.get()}, {intermediate.get()}, {}, 0, |
| [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1_host(param, host, strlen(host))); |
| X509_VERIFY_PARAM_set_hostflags(param, flags); |
| }); |
| }; |
| |
| // Certificates which would otherwise trigger the common name fallback are |
| // rejected whenever there are name constraints. We do this whether or not |
| // the common name matches the constraints. |
| EXPECT_EQ( |
| X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS, |
| verify_cert(permitted.get(), 0 /* no flags */, kCommonNamePermitted)); |
| EXPECT_EQ(X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS, |
| verify_cert(not_permitted.get(), 0 /* no flags */, |
| kCommonNameNotPermitted)); |
| |
| // This occurs even if the built-in name checks aren't used. The caller may |
| // separately call |X509_check_host|. |
| EXPECT_EQ(X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS, |
| Verify(not_permitted.get(), {root.get()}, {intermediate.get()}, {}, |
| 0 /* no flags */, nullptr)); |
| |
| // If the leaf certificate has SANs, the common name fallback is always |
| // disabled, so the name constraints do not apply. |
| EXPECT_EQ(X509_V_OK, Verify(not_permitted_with_sans.get(), {root.get()}, |
| {intermediate.get()}, {}, 0, nullptr)); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(not_permitted_with_sans.get(), 0 /* no flags */, |
| kCommonNameNotPermittedWithSANs)); |
| |
| // If the common name does not look like a DNS name, we apply neither name |
| // constraints nor common name fallback. |
| EXPECT_EQ(X509_V_OK, Verify(not_dns.get(), {root.get()}, {intermediate.get()}, |
| {}, 0, nullptr)); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| verify_cert(not_dns.get(), 0 /* no flags */, kCommonNameNotDNS)); |
| } |
| |
| TEST(X509Test, ServerGatedCryptoEKUs) { |
| bssl::UniquePtr<X509> root = CertFromPEM(kSANTypesRoot); |
| ASSERT_TRUE(root); |
| bssl::UniquePtr<X509> ms_sgc = CertFromPEM(kMicrosoftSGCCert); |
| ASSERT_TRUE(ms_sgc); |
| bssl::UniquePtr<X509> ns_sgc = CertFromPEM(kNetscapeSGCCert); |
| ASSERT_TRUE(ns_sgc); |
| bssl::UniquePtr<X509> server_eku = CertFromPEM(kServerEKUCert); |
| ASSERT_TRUE(server_eku); |
| bssl::UniquePtr<X509> server_eku_plus_ms_sgc = |
| CertFromPEM(kServerEKUPlusMicrosoftSGCCert); |
| ASSERT_TRUE(server_eku_plus_ms_sgc); |
| bssl::UniquePtr<X509> any_eku = CertFromPEM(kAnyEKU); |
| ASSERT_TRUE(any_eku); |
| bssl::UniquePtr<X509> no_eku = CertFromPEM(kNoEKU); |
| ASSERT_TRUE(no_eku); |
| |
| auto verify_cert = [&root](X509 *leaf) { |
| return Verify(leaf, {root.get()}, /*intermediates=*/{}, /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set_purpose( |
| param, X509_PURPOSE_SSL_SERVER)); |
| }); |
| }; |
| |
| // Neither the Microsoft nor Netscape SGC EKU should be sufficient for |
| // |X509_PURPOSE_SSL_SERVER|. The "any" EKU probably, technically, should be. |
| // However, we've never accepted it and it's not acceptable in leaf |
| // certificates by the Baseline, so perhaps we don't need this complexity. |
| for (X509 *leaf : {ms_sgc.get(), ns_sgc.get(), any_eku.get()}) { |
| EXPECT_EQ(X509_V_ERR_INVALID_PURPOSE, verify_cert(leaf)); |
| } |
| |
| // The server-auth EKU is sufficient, and it doesn't matter if an SGC EKU is |
| // also included. Lastly, not specifying an EKU is also valid. |
| for (X509 *leaf : {server_eku.get(), server_eku_plus_ms_sgc.get(), |
| no_eku.get()}) { |
| EXPECT_EQ(X509_V_OK, verify_cert(leaf)); |
| } |
| } |
| |
| // Test that invalid extensions are rejected by, if not the parser, at least the |
| // verifier. |
| TEST(X509Test, InvalidExtensions) { |
| bssl::UniquePtr<X509> root = CertFromPEM( |
| GetTestData("crypto/x509/test/invalid_extension_root.pem").c_str()); |
| ASSERT_TRUE(root); |
| bssl::UniquePtr<X509> intermediate = CertFromPEM( |
| GetTestData("crypto/x509/test/invalid_extension_intermediate.pem") |
| .c_str()); |
| ASSERT_TRUE(intermediate); |
| bssl::UniquePtr<X509> leaf = CertFromPEM( |
| GetTestData("crypto/x509/test/invalid_extension_leaf.pem").c_str()); |
| ASSERT_TRUE(leaf); |
| |
| // Sanity-check that the baseline chain is accepted. |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {})); |
| |
| static const char *kExtensions[] = { |
| "authority_key_identifier", |
| "basic_constraints", |
| "ext_key_usage", |
| "key_usage", |
| "name_constraints", |
| "subject_alt_name", |
| "subject_key_identifier", |
| }; |
| for (const char *ext : kExtensions) { |
| SCOPED_TRACE(ext); |
| bssl::UniquePtr<X509> invalid_root = CertFromPEM( |
| GetTestData((std::string("crypto/x509/test/invalid_extension_root_") + |
| ext + ".pem") |
| .c_str()) |
| .c_str()); |
| ASSERT_TRUE(invalid_root); |
| |
| bssl::UniquePtr<X509> invalid_intermediate = CertFromPEM( |
| GetTestData( |
| (std::string("crypto/x509/test/invalid_extension_intermediate_") + |
| ext + ".pem") |
| .c_str()) |
| .c_str()); |
| ASSERT_TRUE(invalid_intermediate); |
| |
| bssl::UniquePtr<X509> invalid_leaf = CertFromPEM( |
| GetTestData((std::string("crypto/x509/test/invalid_extension_leaf_") + |
| ext + ".pem") |
| .c_str()) |
| .c_str()); |
| ASSERT_TRUE(invalid_leaf); |
| |
| bssl::UniquePtr<X509> trailing_leaf = CertFromPEM( |
| GetTestData((std::string("crypto/x509/test/trailing_data_leaf_") + |
| ext + ".pem") |
| .c_str()) |
| .c_str()); |
| ASSERT_TRUE(trailing_leaf); |
| |
| EXPECT_EQ( |
| X509_V_ERR_INVALID_EXTENSION, |
| Verify(invalid_leaf.get(), {root.get()}, {intermediate.get()}, {})); |
| |
| EXPECT_EQ( |
| X509_V_ERR_INVALID_EXTENSION, |
| Verify(trailing_leaf.get(), {root.get()}, {intermediate.get()}, {})); |
| |
| // If the invalid extension is on an intermediate or root, |
| // |X509_verify_cert| notices by way of being unable to build a path to |
| // a valid issuer. |
| EXPECT_EQ( |
| X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(leaf.get(), {root.get()}, {invalid_intermediate.get()}, {})); |
| EXPECT_EQ( |
| X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(leaf.get(), {invalid_root.get()}, {intermediate.get()}, {})); |
| } |
| } |
| |
| // kExplicitDefaultVersionPEM is an X.509v1 certificate with the version number |
| // encoded explicitly, rather than omitted as required by DER. |
| static const char kExplicitDefaultVersionPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBfTCCASSgAwIBAAIJANlMBNpJfb/rMAkGByqGSM49BAEwRTELMAkGA1UEBhMC |
| QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp |
| dHMgUHR5IEx0ZDAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMyMzIxNTdaMEUxCzAJ |
| BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l |
| dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2ni |
| v2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYa |
| HPUdfvGULUvPciLBMAkGByqGSM49BAEDSAAwRQIhAPKgNV5ROjbDgnmb7idQhY5w |
| BnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTxy54VXuYfMlJhXnXJ |
| FA== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kNegativeVersionPEM is an X.509 certificate with a negative version number. |
| static const char kNegativeVersionPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBfTCCASSgAwIB/wIJANlMBNpJfb/rMAkGByqGSM49BAEwRTELMAkGA1UEBhMC |
| QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp |
| dHMgUHR5IEx0ZDAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMyMzIxNTdaMEUxCzAJ |
| BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l |
| dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2ni |
| v2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYa |
| HPUdfvGULUvPciLBMAkGByqGSM49BAEDSAAwRQIhAPKgNV5ROjbDgnmb7idQhY5w |
| BnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTxy54VXuYfMlJhXnXJ |
| FA== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kFutureVersionPEM is an X.509 certificate with a version number value of |
| // three, which is not defined. (v3 has value two). |
| static const char kFutureVersionPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBfTCCASSgAwIBAwIJANlMBNpJfb/rMAkGByqGSM49BAEwRTELMAkGA1UEBhMC |
| QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp |
| dHMgUHR5IEx0ZDAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMyMzIxNTdaMEUxCzAJ |
| BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l |
| dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2ni |
| v2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYa |
| HPUdfvGULUvPciLBMAkGByqGSM49BAEDSAAwRQIhAPKgNV5ROjbDgnmb7idQhY5w |
| BnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTxy54VXuYfMlJhXnXJ |
| FA== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kOverflowVersionPEM is an X.509 certificate with a version field which |
| // overflows |uint64_t|. |
| static const char kOverflowVersionPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBoDCCAUegJgIkAP////////////////////////////////////////////// |
| AgkA2UwE2kl9v+swCQYHKoZIzj0EATBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwK |
| U29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4X |
| DTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1N1owRTELMAkGA1UEBhMCQVUxEzAR |
| BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 |
| IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWX |
| a7epHg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsEw |
| CQYHKoZIzj0EAQNIADBFAiEA8qA1XlE6NsOCeZvuJ1CFjnAGdJVX0il0APS+FYdd |
| xAcCIHweeRRqIYPwenRoeV8UmZpotPHLnhVe5h8yUmFedckU |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kV1WithExtensionsPEM is an X.509v1 certificate with extensions. |
| static const char kV1WithExtensionsPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIByjCCAXECCQDZTATaSX2/6zAJBgcqhkjOPQQBMEUxCzAJBgNVBAYTAkFVMRMw |
| EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 |
| eSBMdGQwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjBFMQswCQYDVQQG |
| EwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lk |
| Z2l0cyBQdHkgTHRkMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+ |
| Lx4NlIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7x |
| lC1Lz3IiwaNQME4wHQYDVR0OBBYEFKuE0qyrlfCCThZ4B1VXX+QmjYLRMB8GA1Ud |
| IwQYMBaAFKuE0qyrlfCCThZ4B1VXX+QmjYLRMAwGA1UdEwQFMAMBAf8wCQYHKoZI |
| zj0EAQNIADBFAiEA8qA1XlE6NsOCeZvuJ1CFjnAGdJVX0il0APS+FYddxAcCIHwe |
| eRRqIYPwenRoeV8UmZpotPHLnhVe5h8yUmFedckU |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kV2WithExtensionsPEM is an X.509v2 certificate with extensions. |
| static const char kV2WithExtensionsPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBzzCCAXagAwIBAQIJANlMBNpJfb/rMAkGByqGSM49BAEwRTELMAkGA1UEBhMC |
| QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp |
| dHMgUHR5IEx0ZDAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMyMzIxNTdaMEUxCzAJ |
| BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l |
| dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2ni |
| v2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYa |
| HPUdfvGULUvPciLBo1AwTjAdBgNVHQ4EFgQUq4TSrKuV8IJOFngHVVdf5CaNgtEw |
| HwYDVR0jBBgwFoAUq4TSrKuV8IJOFngHVVdf5CaNgtEwDAYDVR0TBAUwAwEB/zAJ |
| BgcqhkjOPQQBA0gAMEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13E |
| BwIgfB55FGohg/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQ= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kV1WithIssuerUniqueIDPEM is an X.509v1 certificate with an issuerUniqueID. |
| static const char kV1WithIssuerUniqueIDPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBgzCCASoCCQDZTATaSX2/6zAJBgcqhkjOPQQBMEUxCzAJBgNVBAYTAkFVMRMw |
| EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 |
| eSBMdGQwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjBFMQswCQYDVQQG |
| EwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lk |
| Z2l0cyBQdHkgTHRkMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+ |
| Lx4NlIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7x |
| lC1Lz3IiwYEJAAEjRWeJq83vMAkGByqGSM49BAEDSAAwRQIhAPKgNV5ROjbDgnmb |
| 7idQhY5wBnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTxy54VXuYf |
| MlJhXnXJFA== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kV1WithSubjectUniqueIDPEM is an X.509v1 certificate with an issuerUniqueID. |
| static const char kV1WithSubjectUniqueIDPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBgzCCASoCCQDZTATaSX2/6zAJBgcqhkjOPQQBMEUxCzAJBgNVBAYTAkFVMRMw |
| EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 |
| eSBMdGQwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjBFMQswCQYDVQQG |
| EwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lk |
| Z2l0cyBQdHkgTHRkMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+ |
| Lx4NlIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7x |
| lC1Lz3IiwYIJAAEjRWeJq83vMAkGByqGSM49BAEDSAAwRQIhAPKgNV5ROjbDgnmb |
| 7idQhY5wBnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTxy54VXuYf |
| MlJhXnXJFA== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kV1CRLWithExtensionsPEM is a v1 CRL with extensions. |
| static const char kV1CRLWithExtensionsPEM[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBpDCBjTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UECAwK |
| Q2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJQm9y |
| aW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoA4wDDAKBgNVHRQE |
| AwIBATANBgkqhkiG9w0BAQsFAAOCAQEAnrBKKgvd9x9zwK9rtUvVeFeJ7+LNZEAc |
| +a5oxpPNEsJx6hXoApYEbzXMxuWBQoCs5iEBycSGudct21L+MVf27M38KrWoeOkq |
| 0a2siqViQZO2Fb/SUFR0k9zb8xl86Zf65lgPplALun0bV/HT7MJcl04Tc4osdsAR |
| eBs5nqTGNEd5AlC1iKHvQZkM//MD51DspKnDpsDiUVi54h9C1SpfZmX8H2Vvdiyu |
| 0fZ/bPAM3VAGawatf/SyWfBMyKpoPXEG39oAzmjjOj8en82psn7m474IGaho/vBb |
| hl1ms5qQiLYPjm4YELtnXQoFyC72tBjbdFd/ZE9k4CNKDbxFUXFbkw== |
| -----END X509 CRL----- |
| )"; |
| |
| // kExplicitDefaultVersionCRLPEM is a v1 CRL with an explicitly-encoded version |
| // field. |
| static const char kExplicitDefaultVersionCRLPEM[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBlzCBgAIBADANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaMA0GCSqGSIb3 |
| DQEBCwUAA4IBAQCesEoqC933H3PAr2u1S9V4V4nv4s1kQBz5rmjGk80SwnHqFegC |
| lgRvNczG5YFCgKzmIQHJxIa51y3bUv4xV/bszfwqtah46SrRrayKpWJBk7YVv9JQ |
| VHST3NvzGXzpl/rmWA+mUAu6fRtX8dPswlyXThNziix2wBF4GzmepMY0R3kCULWI |
| oe9BmQz/8wPnUOykqcOmwOJRWLniH0LVKl9mZfwfZW92LK7R9n9s8AzdUAZrBq1/ |
| 9LJZ8EzIqmg9cQbf2gDOaOM6Px6fzamyfubjvggZqGj+8FuGXWazmpCItg+ObhgQ |
| u2ddCgXILva0GNt0V39kT2TgI0oNvEVRcVuT |
| -----END X509 CRL----- |
| )"; |
| |
| // kV3CRLPEM is a v3 CRL. CRL versions only go up to v2. |
| static const char kV3CRLPEM[] = R"( |
| -----BEGIN X509 CRL----- |
| MIIBpzCBkAIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzETMBEGA1UE |
| CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzESMBAGA1UECgwJ |
| Qm9yaW5nU1NMFw0xNjA5MjYxNTEwNTVaFw0xNjEwMjYxNTEwNTVaoA4wDDAKBgNV |
| HRQEAwIBATANBgkqhkiG9w0BAQsFAAOCAQEAnrBKKgvd9x9zwK9rtUvVeFeJ7+LN |
| ZEAc+a5oxpPNEsJx6hXoApYEbzXMxuWBQoCs5iEBycSGudct21L+MVf27M38KrWo |
| eOkq0a2siqViQZO2Fb/SUFR0k9zb8xl86Zf65lgPplALun0bV/HT7MJcl04Tc4os |
| dsAReBs5nqTGNEd5AlC1iKHvQZkM//MD51DspKnDpsDiUVi54h9C1SpfZmX8H2Vv |
| diyu0fZ/bPAM3VAGawatf/SyWfBMyKpoPXEG39oAzmjjOj8en82psn7m474IGaho |
| /vBbhl1ms5qQiLYPjm4YELtnXQoFyC72tBjbdFd/ZE9k4CNKDbxFUXFbkw== |
| -----END X509 CRL----- |
| )"; |
| |
| // kV2CSRPEM is a v2 CSR. CSR versions only go up to v1. |
| static const char kV2CSRPEM[] = R"( |
| -----BEGIN CERTIFICATE REQUEST----- |
| MIHJMHECAQEwDzENMAsGA1UEAwwEVGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEH |
| A0IABJjsayyAQod1J7UJYNT8AH4WWxLdKV0ozhrIz6hCzBAze7AqXWOSH8G+1EWC |
| pSfL3oMQNtBdJS0kpXXaUqEAgTSgADAKBggqhkjOPQQDAgNIADBFAiAUXVaEYATg |
| 4Cc917T73KBImxh6xyhsA5pKuYpq1S4m9wIhAK+G93HR4ur7Ghel6+zUTvIAsj9e |
| rsn4lSYsqI4OI4ei |
| -----END CERTIFICATE REQUEST----- |
| )"; |
| |
| // kV3CSRPEM is a v3 CSR. CSR versions only go up to v1. |
| static const char kV3CSRPEM[] = R"( |
| -----BEGIN CERTIFICATE REQUEST----- |
| MIHJMHECAQIwDzENMAsGA1UEAwwEVGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEH |
| A0IABJjsayyAQod1J7UJYNT8AH4WWxLdKV0ozhrIz6hCzBAze7AqXWOSH8G+1EWC |
| pSfL3oMQNtBdJS0kpXXaUqEAgTSgADAKBggqhkjOPQQDAgNIADBFAiAUXVaEYATg |
| 4Cc917T73KBImxh6xyhsA5pKuYpq1S4m9wIhAK+G93HR4ur7Ghel6+zUTvIAsj9e |
| rsn4lSYsqI4OI4ei |
| -----END CERTIFICATE REQUEST----- |
| )"; |
| |
| // Test that the library enforces versions are valid and match the fields |
| // present. |
| TEST(X509Test, InvalidVersion) { |
| // kExplicitDefaultVersionPEM is invalid but, for now, we accept it. See |
| // https://crbug.com/boringssl/364. |
| EXPECT_TRUE(CertFromPEM(kExplicitDefaultVersionPEM)); |
| EXPECT_TRUE(CRLFromPEM(kExplicitDefaultVersionCRLPEM)); |
| |
| EXPECT_FALSE(CertFromPEM(kNegativeVersionPEM)); |
| EXPECT_FALSE(CertFromPEM(kFutureVersionPEM)); |
| EXPECT_FALSE(CertFromPEM(kOverflowVersionPEM)); |
| EXPECT_FALSE(CertFromPEM(kV1WithExtensionsPEM)); |
| EXPECT_FALSE(CertFromPEM(kV2WithExtensionsPEM)); |
| EXPECT_FALSE(CertFromPEM(kV1WithIssuerUniqueIDPEM)); |
| EXPECT_FALSE(CertFromPEM(kV1WithSubjectUniqueIDPEM)); |
| EXPECT_FALSE(CRLFromPEM(kV1CRLWithExtensionsPEM)); |
| EXPECT_FALSE(CRLFromPEM(kV3CRLPEM)); |
| EXPECT_FALSE(CSRFromPEM(kV2CSRPEM)); |
| |
| // kV3CSRPEM is invalid but, for now, we accept it. See |
| // https://github.com/certbot/certbot/pull/9334 |
| EXPECT_TRUE(CSRFromPEM(kV3CSRPEM)); |
| |
| bssl::UniquePtr<X509> x509(X509_new()); |
| ASSERT_TRUE(x509); |
| EXPECT_FALSE(X509_set_version(x509.get(), -1)); |
| EXPECT_FALSE(X509_set_version(x509.get(), X509_VERSION_3 + 1)); |
| EXPECT_FALSE(X509_set_version(x509.get(), 9999)); |
| |
| bssl::UniquePtr<X509_CRL> crl(X509_CRL_new()); |
| ASSERT_TRUE(crl); |
| EXPECT_FALSE(X509_CRL_set_version(crl.get(), -1)); |
| EXPECT_FALSE(X509_CRL_set_version(crl.get(), X509_CRL_VERSION_2 + 1)); |
| EXPECT_FALSE(X509_CRL_set_version(crl.get(), 9999)); |
| |
| bssl::UniquePtr<X509_REQ> req(X509_REQ_new()); |
| ASSERT_TRUE(req); |
| EXPECT_FALSE(X509_REQ_set_version(req.get(), -1)); |
| EXPECT_FALSE(X509_REQ_set_version(req.get(), X509_REQ_VERSION_1 + 1)); |
| EXPECT_FALSE(X509_REQ_set_version(req.get(), 9999)); |
| } |
| |
| // Unlike upstream OpenSSL, we require a non-null store in |
| // |X509_STORE_CTX_init|. |
| TEST(X509Test, NullStore) { |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kLeafPEM)); |
| ASSERT_TRUE(leaf); |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| EXPECT_FALSE(X509_STORE_CTX_init(ctx.get(), nullptr, leaf.get(), nullptr)); |
| } |
| |
| TEST(X509Test, StoreCtxReuse) { |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kLeafPEM)); |
| ASSERT_TRUE(leaf); |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| ASSERT_TRUE(store); |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), leaf.get(), nullptr)); |
| // Re-initializing |ctx| should not leak memory. |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), leaf.get(), nullptr)); |
| } |
| |
| TEST(X509Test, BasicConstraints) { |
| const uint32_t kFlagMask = EXFLAG_CA | EXFLAG_BCONS | EXFLAG_INVALID; |
| |
| static const struct { |
| const char *file; |
| uint32_t flags; |
| int path_len; |
| } kTests[] = { |
| {"basic_constraints_none.pem", 0, -1}, |
| {"basic_constraints_ca.pem", EXFLAG_CA | EXFLAG_BCONS, -1}, |
| {"basic_constraints_ca_pathlen_0.pem", EXFLAG_CA | EXFLAG_BCONS, 0}, |
| {"basic_constraints_ca_pathlen_1.pem", EXFLAG_CA | EXFLAG_BCONS, 1}, |
| {"basic_constraints_ca_pathlen_10.pem", EXFLAG_CA | EXFLAG_BCONS, 10}, |
| {"basic_constraints_leaf.pem", EXFLAG_BCONS, -1}, |
| {"invalid_extension_leaf_basic_constraints.pem", EXFLAG_INVALID, -1}, |
| }; |
| |
| for (const auto &test : kTests) { |
| SCOPED_TRACE(test.file); |
| |
| std::string path = "crypto/x509/test/"; |
| path += test.file; |
| |
| bssl::UniquePtr<X509> cert = CertFromPEM(GetTestData(path.c_str()).c_str()); |
| ASSERT_TRUE(cert); |
| EXPECT_EQ(test.flags, X509_get_extension_flags(cert.get()) & kFlagMask); |
| EXPECT_EQ(test.path_len, X509_get_pathlen(cert.get())); |
| } |
| } |
| |
| // The following strings are test certificates signed by kP256Key and kRSAKey, |
| // with missing, NULL, or invalid algorithm parameters. |
| static const char kP256NoParam[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBIDCBxqADAgECAgIE0jAKBggqhkjOPQQDAjAPMQ0wCwYDVQQDEwRUZXN0MCAX |
| DTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAPMQ0wCwYDVQQDEwRUZXN0 |
| MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+Lx4NlIpM1Zdrt6ke |
| DUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7xlC1Lz3IiwaMQMA4w |
| DAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEAqdIiF+bN9Cl44oUeICpy |
| aXd7HqhpVUaglYKw9ChmNUACIQCpMdL0fNkFNDbRww9dSl/y7kBdk/tp16HiqeSy |
| gGzFYg== |
| -----END CERTIFICATE----- |
| )"; |
| static const char kP256NullParam[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBJDCByKADAgECAgIE0jAMBggqhkjOPQQDAgUAMA8xDTALBgNVBAMTBFRlc3Qw |
| IBcNMDAwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMA8xDTALBgNVBAMTBFRl |
| c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2niv2Wfl74vHg2UikzVl2u3 |
| qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYaHPUdfvGULUvPciLBoxAw |
| DjAMBgNVHRMEBTADAQH/MAwGCCqGSM49BAMCBQADSQAwRgIhAKILHmyo+F3Cn/VX |
| UUeSXOQQKX5aLzsQitwwmNF3ZgH3AiEAsYHcrVj/ftmoQIORARkQ/+PrqntXev8r |
| t6uPxHrmpUY= |
| -----END CERTIFICATE----- |
| )"; |
| static const char kP256InvalidParam[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBMTCBz6ADAgECAgIE0jATBggqhkjOPQQDAgQHZ2FyYmFnZTAPMQ0wCwYDVQQD |
| EwRUZXN0MCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAPMQ0wCwYD |
| VQQDEwRUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+Lx4N |
| lIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7xlC1L |
| z3IiwaMQMA4wDAYDVR0TBAUwAwEB/zATBggqhkjOPQQDAgQHZ2FyYmFnZQNIADBF |
| AiAglpDf/YhN89LeJ2WAs/F0SJIrsuhS4uoInIz6WXUiuQIhAIu5Pwhp5E3Pbo8y |
| fLULTZnynuQUULQkRcF7S7T2WpIL |
| -----END CERTIFICATE----- |
| )"; |
| static const char kRSANoParam[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBWzCBx6ADAgECAgIE0jALBgkqhkiG9w0BAQswDzENMAsGA1UEAxMEVGVzdDAg |
| Fw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowDzENMAsGA1UEAxMEVGVz |
| dDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7ep |
| Hg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsGjEDAO |
| MAwGA1UdEwQFMAMBAf8wCwYJKoZIhvcNAQELA4GBAC1f8W3W0Ao7CPfIBQYDSbPh |
| brZpbxdBU5x27JOS7iSa+Lc9pEH5VCX9vIypHVHXLPEfZ38yIt11eiyrmZB6w62N |
| l9kIeZ6FVPmC30d3sXx70Jjs+ZX9yt7kD1gLyNAQQfeYfa4rORAZT1n2YitD74NY |
| TWUH2ieFP3l+ecj1SeQR |
| -----END CERTIFICATE----- |
| )"; |
| static const char kRSANullParam[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBXzCByaADAgECAgIE0jANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRUZXN0 |
| MCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAPMQ0wCwYDVQQDEwRU |
| ZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+Lx4NlIpM1Zdr |
| t6keDUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7xlC1Lz3IiwaMQ |
| MA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAzVcfIv+Rq1KrMXqIL |
| fPq/cWZjgqFZA1RGaGElNaqp+rkJfamq5tDGzckWpebrK+jjRN7yIlcWDtPpy3Gy |
| seZfvtBDR0TwJm0S/pQl8prKB4wgALcwe3bmi56Rq85nzY5ZLNcP16LQxL+jAAua |
| SwmQUz4bRpckRBj+sIyp1We+pg== |
| -----END CERTIFICATE----- |
| )"; |
| static const char kRSAInvalidParam[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBbTCB0KADAgECAgIE0jAUBgkqhkiG9w0BAQsEB2dhcmJhZ2UwDzENMAsGA1UE |
| AxMEVGVzdDAgFw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowDzENMAsG |
| A1UEAxMEVGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8e |
| DZSKTNWXa7epHg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQt |
| S89yIsGjEDAOMAwGA1UdEwQFMAMBAf8wFAYJKoZIhvcNAQELBAdnYXJiYWdlA4GB |
| AHTJ6cWWjCNrZhqiWWVI3jdK+h5xpRG8jGMXxR4JnjtoYRRusJLOXhmapwCB6fA0 |
| 4vc+66O27v36yDmQX+tIc/hDrTpKNJptU8q3n2VagREvoHhkOTYkcCeS8vmnMtn8 |
| 5OMNZ/ajVwOssw61GcAlScRqEHkZFBoGp7e+QpgB2tf9 |
| -----END CERTIFICATE----- |
| )"; |
| |
| TEST(X509Test, AlgorithmParameters) { |
| // P-256 parameters should be omitted, but we accept NULL ones. |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| bssl::UniquePtr<X509> cert = CertFromPEM(kP256NoParam); |
| ASSERT_TRUE(cert); |
| EXPECT_TRUE(X509_verify(cert.get(), key.get())); |
| |
| cert = CertFromPEM(kP256NullParam); |
| ASSERT_TRUE(cert); |
| EXPECT_TRUE(X509_verify(cert.get(), key.get())); |
| |
| cert = CertFromPEM(kP256InvalidParam); |
| ASSERT_TRUE(cert); |
| EXPECT_FALSE(X509_verify(cert.get(), key.get())); |
| EXPECT_TRUE( |
| ErrorEquals(ERR_get_error(), ERR_LIB_X509, X509_R_INVALID_PARAMETER)); |
| |
| // RSA parameters should be NULL, but we accept omitted ones. |
| key = PrivateKeyFromPEM(kRSAKey); |
| ASSERT_TRUE(key); |
| |
| cert = CertFromPEM(kRSANoParam); |
| ASSERT_TRUE(cert); |
| EXPECT_TRUE(X509_verify(cert.get(), key.get())); |
| |
| cert = CertFromPEM(kRSANullParam); |
| ASSERT_TRUE(cert); |
| EXPECT_TRUE(X509_verify(cert.get(), key.get())); |
| |
| cert = CertFromPEM(kRSAInvalidParam); |
| ASSERT_TRUE(cert); |
| EXPECT_FALSE(X509_verify(cert.get(), key.get())); |
| EXPECT_TRUE( |
| ErrorEquals(ERR_get_error(), ERR_LIB_X509, X509_R_INVALID_PARAMETER)); |
| } |
| |
| TEST(X509Test, GeneralName) { |
| const std::vector<uint8_t> kNames[] = { |
| // [0] { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 } |
| // [0] { |
| // SEQUENCE {} |
| // } |
| // } |
| {0xa0, 0x13, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x02, 0x30, 0x00}, |
| // [0] { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 } |
| // [0] { |
| // [APPLICATION 0] {} |
| // } |
| // } |
| {0xa0, 0x13, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x02, 0x60, 0x00}, |
| // [0] { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 } |
| // [0] { |
| // UTF8String { "a" } |
| // } |
| // } |
| {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x03, 0x0c, 0x01, 0x61}, |
| // [0] { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.2 } |
| // [0] { |
| // UTF8String { "a" } |
| // } |
| // } |
| {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x02, 0xa0, 0x03, 0x0c, 0x01, 0x61}, |
| // [0] { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 } |
| // [0] { |
| // UTF8String { "b" } |
| // } |
| // } |
| {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x03, 0x0c, 0x01, 0x62}, |
| // [0] { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 } |
| // [0] { |
| // BOOLEAN { TRUE } |
| // } |
| // } |
| {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x03, 0x01, 0x01, 0xff}, |
| // [0] { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2.1 } |
| // [0] { |
| // BOOLEAN { FALSE } |
| // } |
| // } |
| {0xa0, 0x14, 0x06, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x01, 0xa0, 0x03, 0x01, 0x01, 0x00}, |
| // [1 PRIMITIVE] { "a" } |
| {0x81, 0x01, 0x61}, |
| // [1 PRIMITIVE] { "b" } |
| {0x81, 0x01, 0x62}, |
| // [2 PRIMITIVE] { "a" } |
| {0x82, 0x01, 0x61}, |
| // [2 PRIMITIVE] { "b" } |
| {0x82, 0x01, 0x62}, |
| // [3] {} |
| {0xa3, 0x00}, |
| // [4] { |
| // SEQUENCE { |
| // SET { |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // UTF8String { "a" } |
| // } |
| // } |
| // } |
| // } |
| {0xa4, 0x0e, 0x30, 0x0c, 0x31, 0x0a, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04, |
| 0x03, 0x0c, 0x01, 0x61}, |
| // [4] { |
| // SEQUENCE { |
| // SET { |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // UTF8String { "b" } |
| // } |
| // } |
| // } |
| // } |
| {0xa4, 0x0e, 0x30, 0x0c, 0x31, 0x0a, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04, |
| 0x03, 0x0c, 0x01, 0x62}, |
| // [5] { |
| // [1] { |
| // UTF8String { "a" } |
| // } |
| // } |
| {0xa5, 0x05, 0xa1, 0x03, 0x0c, 0x01, 0x61}, |
| // [5] { |
| // [1] { |
| // UTF8String { "b" } |
| // } |
| // } |
| {0xa5, 0x05, 0xa1, 0x03, 0x0c, 0x01, 0x62}, |
| // [5] { |
| // [0] { |
| // UTF8String {} |
| // } |
| // [1] { |
| // UTF8String { "a" } |
| // } |
| // } |
| {0xa5, 0x09, 0xa0, 0x02, 0x0c, 0x00, 0xa1, 0x03, 0x0c, 0x01, 0x61}, |
| // [5] { |
| // [0] { |
| // UTF8String { "a" } |
| // } |
| // [1] { |
| // UTF8String { "a" } |
| // } |
| // } |
| {0xa5, 0x0a, 0xa0, 0x03, 0x0c, 0x01, 0x61, 0xa1, 0x03, 0x0c, 0x01, 0x61}, |
| // [5] { |
| // [0] { |
| // UTF8String { "b" } |
| // } |
| // [1] { |
| // UTF8String { "a" } |
| // } |
| // } |
| {0xa5, 0x0a, 0xa0, 0x03, 0x0c, 0x01, 0x62, 0xa1, 0x03, 0x0c, 0x01, 0x61}, |
| // [6 PRIMITIVE] { "a" } |
| {0x86, 0x01, 0x61}, |
| // [6 PRIMITIVE] { "b" } |
| {0x86, 0x01, 0x62}, |
| // [7 PRIMITIVE] { `11111111` } |
| {0x87, 0x04, 0x11, 0x11, 0x11, 0x11}, |
| // [7 PRIMITIVE] { `22222222`} |
| {0x87, 0x04, 0x22, 0x22, 0x22, 0x22}, |
| // [7 PRIMITIVE] { `11111111111111111111111111111111` } |
| {0x87, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, |
| 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, |
| // [7 PRIMITIVE] { `22222222222222222222222222222222` } |
| {0x87, 0x10, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, |
| 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, |
| // [8 PRIMITIVE] { 1.2.840.113554.4.1.72585.2.1 } |
| {0x88, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, |
| 0x09, 0x02, 0x01}, |
| // [8 PRIMITIVE] { 1.2.840.113554.4.1.72585.2.2 } |
| {0x88, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, |
| 0x09, 0x02, 0x02}, |
| }; |
| |
| // Every name should be equal to itself and not equal to any others. |
| for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kNames); i++) { |
| SCOPED_TRACE(Bytes(kNames[i])); |
| |
| const uint8_t *ptr = kNames[i].data(); |
| bssl::UniquePtr<GENERAL_NAME> a( |
| d2i_GENERAL_NAME(nullptr, &ptr, kNames[i].size())); |
| ASSERT_TRUE(a); |
| ASSERT_EQ(ptr, kNames[i].data() + kNames[i].size()); |
| |
| uint8_t *enc = nullptr; |
| int enc_len = i2d_GENERAL_NAME(a.get(), &enc); |
| ASSERT_GE(enc_len, 0); |
| bssl::UniquePtr<uint8_t> free_enc(enc); |
| EXPECT_EQ(Bytes(enc, enc_len), Bytes(kNames[i])); |
| |
| for (size_t j = 0; j < OPENSSL_ARRAY_SIZE(kNames); j++) { |
| SCOPED_TRACE(Bytes(kNames[j])); |
| |
| ptr = kNames[j].data(); |
| bssl::UniquePtr<GENERAL_NAME> b( |
| d2i_GENERAL_NAME(nullptr, &ptr, kNames[j].size())); |
| ASSERT_TRUE(b); |
| ASSERT_EQ(ptr, kNames[j].data() + kNames[j].size()); |
| |
| if (i == j) { |
| EXPECT_EQ(GENERAL_NAME_cmp(a.get(), b.get()), 0); |
| } else { |
| EXPECT_NE(GENERAL_NAME_cmp(a.get(), b.get()), 0); |
| } |
| } |
| } |
| } |
| |
| // Test that extracting fields of an |X509_ALGOR| works correctly. |
| TEST(X509Test, X509AlgorExtract) { |
| static const char kTestOID[] = "1.2.840.113554.4.1.72585.2"; |
| const struct { |
| int param_type; |
| std::vector<uint8_t> param_der; |
| } kTests[] = { |
| // No parameter. |
| {V_ASN1_UNDEF, {}}, |
| // BOOLEAN { TRUE } |
| {V_ASN1_BOOLEAN, {0x01, 0x01, 0xff}}, |
| // BOOLEAN { FALSE } |
| {V_ASN1_BOOLEAN, {0x01, 0x01, 0x00}}, |
| // OCTET_STRING { "a" } |
| {V_ASN1_OCTET_STRING, {0x04, 0x01, 0x61}}, |
| // BIT_STRING { `01` `00` } |
| {V_ASN1_BIT_STRING, {0x03, 0x02, 0x01, 0x00}}, |
| // INTEGER { -1 } |
| {V_ASN1_INTEGER, {0x02, 0x01, 0xff}}, |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2 } |
| {V_ASN1_OBJECT, |
| {0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, |
| 0x09, 0x02}}, |
| // NULL {} |
| {V_ASN1_NULL, {0x05, 0x00}}, |
| // SEQUENCE {} |
| {V_ASN1_SEQUENCE, {0x30, 0x00}}, |
| // SET {} |
| {V_ASN1_SET, {0x31, 0x00}}, |
| // [0] { UTF8String { "a" } } |
| {V_ASN1_OTHER, {0xa0, 0x03, 0x0c, 0x01, 0x61}}, |
| }; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(Bytes(t.param_der)); |
| |
| // Assemble an AlgorithmIdentifier with the parameter. |
| bssl::ScopedCBB cbb; |
| CBB seq, oid; |
| ASSERT_TRUE(CBB_init(cbb.get(), 64)); |
| ASSERT_TRUE(CBB_add_asn1(cbb.get(), &seq, CBS_ASN1_SEQUENCE)); |
| ASSERT_TRUE(CBB_add_asn1(&seq, &oid, CBS_ASN1_OBJECT)); |
| ASSERT_TRUE(CBB_add_asn1_oid_from_text(&oid, kTestOID, strlen(kTestOID))); |
| ASSERT_TRUE(CBB_add_bytes(&seq, t.param_der.data(), t.param_der.size())); |
| ASSERT_TRUE(CBB_flush(cbb.get())); |
| |
| const uint8_t *ptr = CBB_data(cbb.get()); |
| bssl::UniquePtr<X509_ALGOR> alg( |
| d2i_X509_ALGOR(nullptr, &ptr, CBB_len(cbb.get()))); |
| ASSERT_TRUE(alg); |
| |
| const ASN1_OBJECT *obj; |
| int param_type; |
| const void *param_value; |
| X509_ALGOR_get0(&obj, ¶m_type, ¶m_value, alg.get()); |
| |
| EXPECT_EQ(param_type, t.param_type); |
| char oid_buf[sizeof(kTestOID)]; |
| ASSERT_EQ(int(sizeof(oid_buf) - 1), |
| OBJ_obj2txt(oid_buf, sizeof(oid_buf), obj, |
| /*always_return_oid=*/1)); |
| EXPECT_STREQ(oid_buf, kTestOID); |
| |
| // |param_type| and |param_value| must be consistent with |ASN1_TYPE|. |
| if (param_type == V_ASN1_UNDEF) { |
| EXPECT_EQ(nullptr, param_value); |
| } else { |
| bssl::UniquePtr<ASN1_TYPE> param(ASN1_TYPE_new()); |
| ASSERT_TRUE(param); |
| ASSERT_TRUE(ASN1_TYPE_set1(param.get(), param_type, param_value)); |
| |
| uint8_t *param_der = nullptr; |
| int param_len = i2d_ASN1_TYPE(param.get(), ¶m_der); |
| ASSERT_GE(param_len, 0); |
| bssl::UniquePtr<uint8_t> free_param_der(param_der); |
| |
| EXPECT_EQ(Bytes(param_der, param_len), Bytes(t.param_der)); |
| } |
| } |
| } |
| |
| // Test the various |X509_ATTRIBUTE| creation functions. |
| TEST(X509Test, Attribute) { |
| // The expected attribute values are: |
| // 1. BMPString U+2603 |
| // 2. BMPString "test" |
| // 3. INTEGER -1 (not valid for friendlyName) |
| static const uint8_t kTest1[] = {0x26, 0x03}; // U+2603 SNOWMAN |
| static const uint8_t kTest1UTF8[] = {0xe2, 0x98, 0x83}; |
| static const uint8_t kTest2[] = {0, 't', 0, 'e', 0, 's', 0, 't'}; |
| |
| constexpr uint32_t kTest1Mask = 1 << 0; |
| constexpr uint32_t kTest2Mask = 1 << 1; |
| constexpr uint32_t kTest3Mask = 1 << 2; |
| auto check_attribute = [&](X509_ATTRIBUTE *attr, uint32_t mask) { |
| EXPECT_EQ(NID_friendlyName, OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr))); |
| |
| int idx = 0; |
| if (mask & kTest1Mask) { |
| // The first attribute should contain |kTest1|. |
| const ASN1_TYPE *value = X509_ATTRIBUTE_get0_type(attr, idx); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(V_ASN1_BMPSTRING, value->type); |
| EXPECT_EQ(Bytes(kTest1), |
| Bytes(ASN1_STRING_get0_data(value->value.bmpstring), |
| ASN1_STRING_length(value->value.bmpstring))); |
| |
| // |X509_ATTRIBUTE_get0_data| requires the type match. |
| EXPECT_FALSE( |
| X509_ATTRIBUTE_get0_data(attr, idx, V_ASN1_OCTET_STRING, nullptr)); |
| const ASN1_BMPSTRING *bmpstring = static_cast<const ASN1_BMPSTRING *>( |
| X509_ATTRIBUTE_get0_data(attr, idx, V_ASN1_BMPSTRING, nullptr)); |
| ASSERT_TRUE(bmpstring); |
| EXPECT_EQ(Bytes(kTest1), Bytes(ASN1_STRING_get0_data(bmpstring), |
| ASN1_STRING_length(bmpstring))); |
| idx++; |
| } |
| |
| if (mask & kTest2Mask) { |
| const ASN1_TYPE *value = X509_ATTRIBUTE_get0_type(attr, idx); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(V_ASN1_BMPSTRING, value->type); |
| EXPECT_EQ(Bytes(kTest2), |
| Bytes(ASN1_STRING_get0_data(value->value.bmpstring), |
| ASN1_STRING_length(value->value.bmpstring))); |
| idx++; |
| } |
| |
| if (mask & kTest3Mask) { |
| const ASN1_TYPE *value = X509_ATTRIBUTE_get0_type(attr, idx); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(V_ASN1_INTEGER, value->type); |
| int64_t v; |
| ASSERT_TRUE(ASN1_INTEGER_get_int64(&v, value->value.integer)); |
| EXPECT_EQ(v, -1); |
| idx++; |
| } |
| |
| EXPECT_FALSE(X509_ATTRIBUTE_get0_type(attr, idx)); |
| }; |
| |
| bssl::UniquePtr<ASN1_STRING> str(ASN1_STRING_type_new(V_ASN1_BMPSTRING)); |
| ASSERT_TRUE(str); |
| ASSERT_TRUE(ASN1_STRING_set(str.get(), kTest1, sizeof(kTest1))); |
| |
| // Test |X509_ATTRIBUTE_create|. |
| bssl::UniquePtr<X509_ATTRIBUTE> attr( |
| X509_ATTRIBUTE_create(NID_friendlyName, V_ASN1_BMPSTRING, str.get())); |
| ASSERT_TRUE(attr); |
| str.release(); // |X509_ATTRIBUTE_create| takes ownership on success. |
| check_attribute(attr.get(), kTest1Mask); |
| |
| // Test the |MBSTRING_*| form of |X509_ATTRIBUTE_set1_data|. |
| attr.reset(X509_ATTRIBUTE_new()); |
| ASSERT_TRUE(attr); |
| ASSERT_TRUE( |
| X509_ATTRIBUTE_set1_object(attr.get(), OBJ_nid2obj(NID_friendlyName))); |
| ASSERT_TRUE(X509_ATTRIBUTE_set1_data(attr.get(), MBSTRING_UTF8, kTest1UTF8, |
| sizeof(kTest1UTF8))); |
| check_attribute(attr.get(), kTest1Mask); |
| |
| // Test the |ASN1_STRING| form of |X509_ATTRIBUTE_set1_data|. |
| ASSERT_TRUE(X509_ATTRIBUTE_set1_data(attr.get(), V_ASN1_BMPSTRING, kTest2, |
| sizeof(kTest2))); |
| check_attribute(attr.get(), kTest1Mask | kTest2Mask); |
| |
| // The |ASN1_STRING| form of |X509_ATTRIBUTE_set1_data| should correctly |
| // handle negative integers. |
| const uint8_t kOne = 1; |
| ASSERT_TRUE( |
| X509_ATTRIBUTE_set1_data(attr.get(), V_ASN1_NEG_INTEGER, &kOne, 1)); |
| check_attribute(attr.get(), kTest1Mask | kTest2Mask | kTest3Mask); |
| |
| // Test the |ASN1_TYPE| form of |X509_ATTRIBUTE_set1_data|. |
| attr.reset(X509_ATTRIBUTE_new()); |
| ASSERT_TRUE(attr); |
| ASSERT_TRUE( |
| X509_ATTRIBUTE_set1_object(attr.get(), OBJ_nid2obj(NID_friendlyName))); |
| str.reset(ASN1_STRING_type_new(V_ASN1_BMPSTRING)); |
| ASSERT_TRUE(str); |
| ASSERT_TRUE(ASN1_STRING_set(str.get(), kTest1, sizeof(kTest1))); |
| ASSERT_TRUE( |
| X509_ATTRIBUTE_set1_data(attr.get(), V_ASN1_BMPSTRING, str.get(), -1)); |
| check_attribute(attr.get(), kTest1Mask); |
| |
| // An |attrtype| of zero leaves the attribute empty. |
| attr.reset(X509_ATTRIBUTE_create_by_NID( |
| nullptr, NID_friendlyName, /*attrtype=*/0, /*data=*/nullptr, /*len=*/0)); |
| ASSERT_TRUE(attr); |
| check_attribute(attr.get(), 0); |
| } |
| |
| // Test that, by default, |X509_V_FLAG_TRUSTED_FIRST| is set, which means we'll |
| // skip over server-sent expired intermediates when there is a local trust |
| // anchor that works better. |
| TEST(X509Test, TrustedFirst) { |
| // Generate the following certificates: |
| // |
| // Root 2 (in store, expired) |
| // | |
| // Root 1 (in store) Root 1 (cross-sign) |
| // \ / |
| // Intermediate |
| // | |
| // Leaf |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| bssl::UniquePtr<X509> root2 = |
| MakeTestCert("Root 2", "Root 2", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(root2); |
| ASSERT_TRUE(ASN1_TIME_adj(X509_getm_notAfter(root2.get()), kReferenceTime, |
| /*offset_day=*/0, |
| /*offset_sec=*/-1)); |
| ASSERT_TRUE(X509_sign(root2.get(), key.get(), EVP_sha256())); |
| |
| bssl::UniquePtr<X509> root1 = |
| MakeTestCert("Root 1", "Root 1", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(root1); |
| ASSERT_TRUE(X509_sign(root1.get(), key.get(), EVP_sha256())); |
| |
| bssl::UniquePtr<X509> root1_cross = |
| MakeTestCert("Root 2", "Root 1", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(root1_cross); |
| ASSERT_TRUE(X509_sign(root1_cross.get(), key.get(), EVP_sha256())); |
| |
| bssl::UniquePtr<X509> intermediate = |
| MakeTestCert("Root 1", "Intermediate", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(X509_sign(intermediate.get(), key.get(), EVP_sha256())); |
| |
| bssl::UniquePtr<X509> leaf = |
| MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| |
| // As a control, confirm that |leaf| -> |intermediate| -> |root1| is valid, |
| // but the path through |root1_cross| is expired. |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root1.get()}, {intermediate.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_CERT_HAS_EXPIRED, |
| Verify(leaf.get(), {root2.get()}, |
| {intermediate.get(), root1_cross.get()}, {})); |
| |
| // By default, we should find the |leaf| -> |intermediate| -> |root2| chain, |
| // skipping |root1_cross|. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root1.get(), root2.get()}, |
| {intermediate.get(), root1_cross.get()}, {})); |
| |
| // When |X509_V_FLAG_TRUSTED_FIRST| is disabled, we get stuck on the expired |
| // intermediate. Note we need the callback to clear the flag. Setting |flags| |
| // to zero only skips setting new flags. |
| // |
| // This test exists to confirm our current behavior, but these modes are just |
| // workarounds for not having an actual path-building verifier. If we fix it, |
| // this test can be removed. |
| EXPECT_EQ(X509_V_ERR_CERT_HAS_EXPIRED, |
| Verify(leaf.get(), {root1.get(), root2.get()}, |
| {intermediate.get(), root1_cross.get()}, {}, /*flags=*/0, |
| [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| X509_VERIFY_PARAM_clear_flags(param, |
| X509_V_FLAG_TRUSTED_FIRST); |
| })); |
| |
| // Even when |X509_V_FLAG_TRUSTED_FIRST| is disabled, if |root2| is not |
| // trusted, the alt chains logic recovers the path. |
| EXPECT_EQ( |
| X509_V_OK, |
| Verify(leaf.get(), {root1.get()}, {intermediate.get(), root1_cross.get()}, |
| {}, /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| X509_VERIFY_PARAM_clear_flags(param, X509_V_FLAG_TRUSTED_FIRST); |
| })); |
| } |
| |
| // Test that notBefore and notAfter checks work correctly. |
| TEST(X509Test, Expiry) { |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| auto make_cert = [&](const char *issuer, const char *subject, bool is_ca, |
| int not_before_offset, |
| int not_after_offset) -> bssl::UniquePtr<X509> { |
| bssl::UniquePtr<X509> cert = |
| MakeTestCert(issuer, subject, key.get(), is_ca); |
| if (cert == nullptr || |
| !ASN1_TIME_adj(X509_getm_notBefore(cert.get()), kReferenceTime, |
| /*offset_day=*/not_before_offset, |
| /*offset_sec=*/0) || |
| !ASN1_TIME_adj(X509_getm_notAfter(cert.get()), kReferenceTime, |
| /*offset_day=*/not_after_offset, |
| /*offset_sec=*/0) || |
| !X509_sign(cert.get(), key.get(), EVP_sha256())) { |
| return nullptr; |
| } |
| return cert; |
| }; |
| |
| struct Certs { |
| bssl::UniquePtr<X509> not_yet_valid, valid, expired; |
| }; |
| auto make_certs = [&](const char *issuer, const char *subject, |
| bool is_ca) -> Certs { |
| Certs certs; |
| certs.not_yet_valid = |
| make_cert(issuer, subject, is_ca, /*not_before_offset=*/1, |
| /*not_after_offset=*/2); |
| certs.valid = make_cert(issuer, subject, is_ca, /*not_before_offset=*/-1, |
| /*not_after_offset=*/1); |
| certs.expired = make_cert(issuer, subject, is_ca, /*not_before_offset=*/-2, |
| /*not_after_offset=*/-1); |
| if (certs.not_yet_valid == nullptr || certs.valid == nullptr || |
| certs.expired == nullptr) { |
| return Certs{}; |
| } |
| return certs; |
| }; |
| |
| Certs root = make_certs("Root", "Root", /*is_ca=*/true); |
| ASSERT_TRUE(root.valid); |
| Certs root_cross = make_certs("Root 2", "Root", /*is_ca=*/true); |
| ASSERT_TRUE(root_cross.valid); |
| Certs intermediate = make_certs("Root", "Intermediate", /*is_ca=*/true); |
| ASSERT_TRUE(intermediate.valid); |
| Certs leaf = make_certs("Intermediate", "Leaf", /*is_ca=*/false); |
| ASSERT_TRUE(leaf.valid); |
| |
| for (bool check_time : {true, false}) { |
| SCOPED_TRACE(check_time); |
| for (bool partial_chain : {true, false}) { |
| SCOPED_TRACE(partial_chain); |
| unsigned long flags = 0; |
| if (!check_time) { |
| flags |= X509_V_FLAG_NO_CHECK_TIME; |
| } |
| if (partial_chain) { |
| flags |= X509_V_FLAG_PARTIAL_CHAIN; |
| } |
| |
| int not_yet_valid = |
| check_time ? X509_V_ERR_CERT_NOT_YET_VALID : X509_V_OK; |
| int has_expired = check_time ? X509_V_ERR_CERT_HAS_EXPIRED : X509_V_OK; |
| |
| EXPECT_EQ(not_yet_valid, |
| Verify(leaf.not_yet_valid.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| EXPECT_EQ(not_yet_valid, |
| Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.not_yet_valid.get()}, {}, flags)); |
| EXPECT_EQ(not_yet_valid, |
| Verify(leaf.valid.get(), {root.not_yet_valid.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| |
| EXPECT_EQ(X509_V_OK, Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| |
| EXPECT_EQ(has_expired, Verify(leaf.expired.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| EXPECT_EQ(has_expired, Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.expired.get()}, {}, flags)); |
| EXPECT_EQ(has_expired, Verify(leaf.valid.get(), {root.expired.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| |
| if (!partial_chain) { |
| // By default, non-self-signed certificates are not valid trust anchors. |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, |
| Verify(leaf.valid.get(), {root_cross.valid.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| } else { |
| // |X509_V_FLAG_PARTIAL_CHAIN| allows non-self-signed trust anchors. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.valid.get(), {root_cross.valid.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| // Expiry of the trust anchor must still be checked. |
| EXPECT_EQ(not_yet_valid, |
| Verify(leaf.valid.get(), {root_cross.not_yet_valid.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| EXPECT_EQ(has_expired, |
| Verify(leaf.valid.get(), {root_cross.expired.get()}, |
| {intermediate.valid.get()}, {}, flags)); |
| } |
| |
| // When the trust anchor is the target certificate, expiry should also be |
| // checked. |
| EXPECT_EQ(X509_V_OK, |
| Verify(root.valid.get(), {root.valid.get()}, {}, {}, flags)); |
| EXPECT_EQ(not_yet_valid, |
| Verify(root.not_yet_valid.get(), {root.not_yet_valid.get()}, {}, |
| {}, flags)); |
| EXPECT_EQ(has_expired, Verify(root.expired.get(), {root.expired.get()}, |
| {}, {}, flags)); |
| } |
| } |
| |
| // X509_V_FLAG_USE_CHECK_TIME is an internal flag, but one caller relies on |
| // being able to clear it to restore the system time. Using the system time, |
| // all certificates in this test should read as expired. |
| EXPECT_EQ(X509_V_ERR_CERT_HAS_EXPIRED, |
| Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {}, 0, [](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| X509_VERIFY_PARAM_clear_flags(param, |
| X509_V_FLAG_USE_CHECK_TIME); |
| })); |
| } |
| |
| TEST(X509Test, SignatureVerification) { |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| struct Certs { |
| bssl::UniquePtr<X509> valid; |
| bssl::UniquePtr<X509> bad_key_type, bad_key; |
| bssl::UniquePtr<X509> bad_sig_type, bad_sig; |
| }; |
| auto make_certs = [&](const char *issuer, const char *subject, |
| bool is_ca) -> Certs { |
| Certs certs; |
| certs.valid = MakeTestCert(issuer, subject, key.get(), is_ca); |
| if (certs.valid == nullptr || |
| !X509_sign(certs.valid.get(), key.get(), EVP_sha256())) { |
| return Certs{}; |
| } |
| |
| static const uint8_t kInvalid[] = {'i', 'n', 'v', 'a', 'l', 'i', 'd'}; |
| |
| // Extracting the algorithm identifier from |certs.valid|'s SPKI, with |
| // OpenSSL's API, is very tedious. Instead, we'll just rely on knowing it is |
| // ecPublicKey with P-256 as parameters. |
| const ASN1_BIT_STRING *pubkey = X509_get0_pubkey_bitstr(certs.valid.get()); |
| int pubkey_len = ASN1_STRING_length(pubkey); |
| |
| // Sign a copy of the certificate where the key type is an unsupported OID. |
| bssl::UniquePtr<uint8_t> pubkey_data(static_cast<uint8_t *>( |
| OPENSSL_memdup(ASN1_STRING_get0_data(pubkey), pubkey_len))); |
| certs.bad_key_type = MakeTestCert(issuer, subject, key.get(), is_ca); |
| if (pubkey_data == nullptr || certs.bad_key_type == nullptr || |
| !X509_PUBKEY_set0_param(X509_get_X509_PUBKEY(certs.bad_key_type.get()), |
| OBJ_nid2obj(NID_subject_alt_name), V_ASN1_UNDEF, |
| /*param_value=*/nullptr, pubkey_data.release(), |
| pubkey_len) || |
| !X509_sign(certs.bad_key_type.get(), key.get(), EVP_sha256())) { |
| return Certs{}; |
| } |
| |
| // Sign a copy of the certificate where the key data is unparsable. |
| pubkey_data.reset( |
| static_cast<uint8_t *>(OPENSSL_memdup(kInvalid, sizeof(kInvalid)))); |
| certs.bad_key = MakeTestCert(issuer, subject, key.get(), is_ca); |
| if (pubkey_data == nullptr || certs.bad_key == nullptr || |
| !X509_PUBKEY_set0_param(X509_get_X509_PUBKEY(certs.bad_key.get()), |
| OBJ_nid2obj(NID_X9_62_id_ecPublicKey), |
| V_ASN1_OBJECT, |
| OBJ_nid2obj(NID_X9_62_prime256v1), |
| pubkey_data.release(), sizeof(kInvalid)) || |
| !X509_sign(certs.bad_key.get(), key.get(), EVP_sha256())) { |
| return Certs{}; |
| } |
| |
| bssl::UniquePtr<X509_ALGOR> wrong_algo(X509_ALGOR_new()); |
| if (wrong_algo == nullptr || |
| !X509_ALGOR_set0(wrong_algo.get(), OBJ_nid2obj(NID_subject_alt_name), |
| V_ASN1_NULL, nullptr)) { |
| return Certs{}; |
| } |
| |
| certs.bad_sig_type.reset(X509_dup(certs.valid.get())); |
| if (certs.bad_sig_type == nullptr || |
| !X509_set1_signature_algo(certs.bad_sig_type.get(), wrong_algo.get())) { |
| return Certs{}; |
| } |
| |
| certs.bad_sig.reset(X509_dup(certs.valid.get())); |
| if (certs.bad_sig == nullptr || |
| !X509_set1_signature_value(certs.bad_sig.get(), kInvalid, |
| sizeof(kInvalid))) { |
| return Certs{}; |
| } |
| |
| return certs; |
| }; |
| |
| Certs root(make_certs("Root", "Root", /*is_ca=*/true)); |
| ASSERT_TRUE(root.valid); |
| Certs intermediate(make_certs("Root", "Intermediate", /*is_ca=*/true)); |
| ASSERT_TRUE(intermediate.valid); |
| Certs leaf(make_certs("Intermediate", "Leaf", /*is_ca=*/false)); |
| ASSERT_TRUE(leaf.valid); |
| |
| // Check the base chain. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {})); |
| |
| // An invalid or unsupported signature in the leaf or intermediate is noticed. |
| EXPECT_EQ(X509_V_ERR_CERT_SIGNATURE_FAILURE, |
| Verify(leaf.bad_sig.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_CERT_SIGNATURE_FAILURE, |
| Verify(leaf.bad_sig_type.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_CERT_SIGNATURE_FAILURE, |
| Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.bad_sig.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_CERT_SIGNATURE_FAILURE, |
| Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.bad_sig_type.get()}, {})); |
| |
| // By default, the redundant root signature is not checked. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.valid.get(), {root.bad_sig.get()}, |
| {intermediate.valid.get()}, {})); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.valid.get(), {root.bad_sig_type.get()}, |
| {intermediate.valid.get()}, {})); |
| |
| // The caller can request checking it, although it's pointless. |
| EXPECT_EQ( |
| X509_V_ERR_CERT_SIGNATURE_FAILURE, |
| Verify(leaf.valid.get(), {root.bad_sig.get()}, {intermediate.valid.get()}, |
| {}, X509_V_FLAG_CHECK_SS_SIGNATURE)); |
| EXPECT_EQ( |
| X509_V_ERR_CERT_SIGNATURE_FAILURE, |
| Verify(leaf.valid.get(), {root.bad_sig_type.get()}, |
| {intermediate.valid.get()}, {}, X509_V_FLAG_CHECK_SS_SIGNATURE)); |
| |
| // The above also applies when accepting a trusted, self-signed root as the |
| // target certificate. |
| EXPECT_EQ(X509_V_OK, |
| Verify(root.bad_sig.get(), {root.bad_sig.get()}, {}, {})); |
| EXPECT_EQ(X509_V_OK, |
| Verify(root.bad_sig_type.get(), {root.bad_sig_type.get()}, {}, {})); |
| EXPECT_EQ(X509_V_ERR_CERT_SIGNATURE_FAILURE, |
| Verify(root.bad_sig.get(), {root.bad_sig.get()}, {}, {}, |
| X509_V_FLAG_CHECK_SS_SIGNATURE)); |
| EXPECT_EQ(X509_V_ERR_CERT_SIGNATURE_FAILURE, |
| Verify(root.bad_sig_type.get(), {root.bad_sig_type.get()}, {}, {}, |
| X509_V_FLAG_CHECK_SS_SIGNATURE)); |
| |
| // If an intermediate is a trust anchor, the redundant signature is always |
| // ignored, even with |X509_V_FLAG_CHECK_SS_SIGNATURE|. (We cannot check the |
| // signature without the key.) |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.valid.get(), {intermediate.bad_sig.get()}, {}, {}, |
| X509_V_FLAG_CHECK_SS_SIGNATURE | X509_V_FLAG_PARTIAL_CHAIN)); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.valid.get(), {intermediate.bad_sig_type.get()}, {}, {}, |
| X509_V_FLAG_CHECK_SS_SIGNATURE | X509_V_FLAG_PARTIAL_CHAIN)); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.valid.get(), {intermediate.bad_sig.get()}, |
| {}, {}, X509_V_FLAG_PARTIAL_CHAIN)); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.valid.get(), {intermediate.bad_sig_type.get()}, {}, {}, |
| X509_V_FLAG_PARTIAL_CHAIN)); |
| |
| // Bad keys in the root and intermediate are rejected. |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, |
| Verify(leaf.valid.get(), {root.bad_key.get()}, |
| {intermediate.valid.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, |
| Verify(leaf.valid.get(), {root.bad_key_type.get()}, |
| {intermediate.valid.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, |
| Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.bad_key.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, |
| Verify(leaf.valid.get(), {root.valid.get()}, |
| {intermediate.bad_key_type.get()}, {})); |
| |
| // Bad keys in the leaf are ignored. The leaf's key is used by the caller. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.bad_key.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {})); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.bad_key_type.get(), {root.valid.get()}, |
| {intermediate.valid.get()}, {})); |
| |
| // At the time we go to verify signatures, it is possible that we have a |
| // single-element certificate chain with a certificate that isn't self-signed. |
| // This does not seem to be reachable except if missing trust anchors are |
| // suppressed with the verify callback, but exercise this codepath anyway. |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, |
| Verify(leaf.valid.get(), {}, {}, {}, 0, [](X509_STORE_CTX *ctx) { |
| X509_STORE_CTX_set_verify_cb( |
| ctx, [](int ok, X509_STORE_CTX *ctx_inner) -> int { |
| if (ok) { |
| return ok; |
| } |
| // Suppress the missing issuer certificate. |
| int err = X509_STORE_CTX_get_error(ctx_inner); |
| return err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY; |
| }); |
| })); |
| } |
| |
| // kConstructedBitString is an X.509 certificate where the signature is encoded |
| // as a BER constructed BIT STRING. Note that, while OpenSSL's parser accepts |
| // this input, it interprets the value incorrectly. |
| static const char kConstructedBitString[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBJTCBxqADAgECAgIE0jAKBggqhkjOPQQDAjAPMQ0wCwYDVQQDEwRUZXN0MCAX |
| DTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAPMQ0wCwYDVQQDEwRUZXN0 |
| MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+Lx4NlIpM1Zdrt6ke |
| DUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7xlC1Lz3IiwaMQMA4w |
| DAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAiNOAyQAMEYCIQCp0iIX5s30KXjihR4g |
| KnJpd3seqGlVRqCVgrD0KGYDJgA1QAIhAKkx0vR82QU0NtHDD11KX/LuQF2T+2nX |
| oeKp5LKAbMVi |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kConstructedOctetString is an X.509 certificate where an extension is encoded |
| // as a BER constructed OCTET STRING. |
| static const char kConstructedOctetString[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBJDCByqADAgECAgIE0jAKBggqhkjOPQQDAjAPMQ0wCwYDVQQDEwRUZXN0MCAX |
| DTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAPMQ0wCwYDVQQDEwRUZXN0 |
| MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+Lx4NlIpM1Zdrt6ke |
| DUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7xlC1Lz3IiwaMUMBIw |
| EAYDVR0TJAkEAzADAQQCAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKnSIhfmzfQpeOKF |
| HiAqcml3ex6oaVVGoJWCsPQoZjVAAiEAqTHS9HzZBTQ20cMPXUpf8u5AXZP7adeh |
| 4qnksoBsxWI= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kIndefiniteLength is an X.509 certificate where the outermost SEQUENCE uses |
| // BER indefinite-length encoding. |
| static const char kIndefiniteLength[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIAwgcagAwIBAgICBNIwCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEVGVzdDAgFw0w |
| MDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowDzENMAsGA1UEAxMEVGVzdDBZ |
| MBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7epHg1G |
| +92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsGjEDAOMAwG |
| A1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKnSIhfmzfQpeOKFHiAqcml3 |
| ex6oaVVGoJWCsPQoZjVAAiEAqTHS9HzZBTQ20cMPXUpf8u5AXZP7adeh4qnksoBs |
| xWIAAA== |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kNonZeroPadding is an X.09 certificate where the BIT STRING signature field |
| // has non-zero padding values. |
| static const char kNonZeroPadding[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIB0DCCAXagAwIBAgIJANlMBNpJfb/rMAkGByqGSM49BAEwRTELMAkGA1UEBhMC |
| QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp |
| dHMgUHR5IEx0ZDAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMyMzIxNTdaMEUxCzAJ |
| BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l |
| dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2ni |
| v2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYa |
| HPUdfvGULUvPciLBo1AwTjAdBgNVHQ4EFgQUq4TSrKuV8IJOFngHVVdf5CaNgtEw |
| HwYDVR0jBBgwFoAUq4TSrKuV8IJOFngHVVdf5CaNgtEwDAYDVR0TBAUwAwEB/zAJ |
| BgcqhkjOPQQBA0kBMEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13E |
| BwIgfB55FGohg/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQB |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kHighTagNumber is an X.509 certificate where the outermost SEQUENCE tag uses |
| // high tag number form. |
| static const char kHighTagNumber[] = R"( |
| -----BEGIN CERTIFICATE----- |
| PxCCASAwgcagAwIBAgICBNIwCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEVGVzdDAg |
| Fw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowDzENMAsGA1UEAxMEVGVz |
| dDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7ep |
| Hg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsGjEDAO |
| MAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKnSIhfmzfQpeOKFHiAq |
| cml3ex6oaVVGoJWCsPQoZjVAAiEAqTHS9HzZBTQ20cMPXUpf8u5AXZP7adeh4qnk |
| soBsxWI= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kNonMinimalLengthOuter is an X.509 certificate where the outermost SEQUENCE |
| // has a non-minimal length. |
| static const char kNonMinimalLengthOuter[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIMAASAwgcagAwIBAgICBNIwCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEVGVzdDAg |
| Fw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowDzENMAsGA1UEAxMEVGVz |
| dDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7ep |
| Hg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsGjEDAO |
| MAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKnSIhfmzfQpeOKFHiAq |
| cml3ex6oaVVGoJWCsPQoZjVAAiEAqTHS9HzZBTQ20cMPXUpf8u5AXZP7adeh4qnk |
| soBsxWI= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kNonMinimalLengthSignature is an X.509 certificate where the signature has a |
| // non-minimal length. |
| static const char kNonMinimalLengthSignature[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBITCBxqADAgECAgIE0jAKBggqhkjOPQQDAjAPMQ0wCwYDVQQDEwRUZXN0MCAX |
| DTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAPMQ0wCwYDVQQDEwRUZXN0 |
| MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5itp4r9ln5e+Lx4NlIpM1Zdrt6ke |
| DUb73ampHp3culoB59aXqAoY+cPEox5W4nyDSNsWGhz1HX7xlC1Lz3IiwaMQMA4w |
| DAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAgOBSQAwRgIhAKnSIhfmzfQpeOKFHiAq |
| cml3ex6oaVVGoJWCsPQoZjVAAiEAqTHS9HzZBTQ20cMPXUpf8u5AXZP7adeh4qnk |
| soBsxWI= |
| -----END CERTIFICATE----- |
| )"; |
| |
| // kNonMinimalLengthSerial is an X.509 certificate where the serial number has a |
| // non-minimal length. |
| static const char kNonMinimalLengthSerial[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIIBITCBx6ADAgECAoECBNIwCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEVGVzdDAg |
| Fw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowDzENMAsGA1UEAxMEVGVz |
| dDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7ep |
| Hg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsGjEDAO |
| MAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKnSIhfmzfQpeOKFHiAq |
| cml3ex6oaVVGoJWCsPQoZjVAAiEAqTHS9HzZBTQ20cMPXUpf8u5AXZP7adeh4qnk |
| soBsxWI= |
| -----END CERTIFICATE----- |
| )"; |
| |
| TEST(X509Test, BER) { |
| // Constructed strings are forbidden in DER. |
| EXPECT_FALSE(CertFromPEM(kConstructedBitString)); |
| EXPECT_FALSE(CertFromPEM(kConstructedOctetString)); |
| // Indefinite lengths are forbidden in DER. |
| EXPECT_FALSE(CertFromPEM(kIndefiniteLength)); |
| // Padding bits in BIT STRINGs must be zero in BER. |
| EXPECT_FALSE(CertFromPEM(kNonZeroPadding)); |
| // Tags must be minimal in both BER and DER, though many BER decoders |
| // incorrectly support non-minimal tags. |
| EXPECT_FALSE(CertFromPEM(kHighTagNumber)); |
| // Lengths must be minimal in DER. |
| EXPECT_FALSE(CertFromPEM(kNonMinimalLengthOuter)); |
| EXPECT_FALSE(CertFromPEM(kNonMinimalLengthSerial)); |
| // We, for now, accept a non-minimal length in the signature field. See |
| // b/18228011. |
| EXPECT_TRUE(CertFromPEM(kNonMinimalLengthSignature)); |
| } |
| |
| TEST(X509Test, Names) { |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| bssl::UniquePtr<X509> root = |
| MakeTestCert("Root", "Root", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(X509_sign(root.get(), key.get(), EVP_sha256())); |
| |
| struct { |
| std::vector<std::pair<int, std::string>> cert_subject; |
| std::vector<std::string> cert_dns_names; |
| std::vector<std::string> cert_emails; |
| std::vector<std::string> valid_dns_names; |
| std::vector<std::string> invalid_dns_names; |
| std::vector<std::string> valid_emails; |
| std::vector<std::string> invalid_emails; |
| unsigned flags; |
| } kTests[] = { |
| // DNS names only match DNS names and do so case-insensitively. |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/{"example.com", "WWW.EXAMPLE.COM"}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/ |
| {"example.com", "EXAMPLE.COM", "www.example.com", "WWW.EXAMPLE.COM"}, |
| /*invalid_dns_names=*/{"test.example.com", "example.org"}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{"test@example.com", "example.com"}, |
| /*flags=*/0, |
| }, |
| |
| // DNS wildcards match exactly one component. |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/{"*.example.com", "*.EXAMPLE.ORG"}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/ |
| {"www.example.com", "WWW.EXAMPLE.COM", "www.example.org", |
| "WWW.EXAMPLE.ORG"}, |
| /*invalid_dns_names=*/{"example.com", "test.www.example.com"}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{"test@example.com", "www.example.com"}, |
| /*flags=*/0, |
| }, |
| |
| // DNS wildcards can be disabled. |
| // TODO(davidben): Can we remove this feature? Does anyone use it? |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/{"example.com", "*.example.com"}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/{"example.com"}, |
| /*invalid_dns_names=*/{"www.example.com"}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{}, |
| /*flags=*/X509_CHECK_FLAG_NO_WILDCARDS, |
| }, |
| |
| // Invalid DNS wildcards do not match. |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/ |
| {"a.*", "**.b.example", "*c.example", "d*.example", "e*e.example", |
| "*", ".", "..", "*."}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/{}, |
| /*invalid_dns_names=*/ |
| {"a.example", "test.b.example", "cc.example", "dd.example", |
| "eee.example", "f", "g."}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{}, |
| /*flags=*/0, |
| }, |
| |
| // IDNs match like any other DNS labels. |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/ |
| {"xn--rger-koa.a.example", "*.xn--rger-koa.b.example", |
| "www.xn--rger-koa.c.example"}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/ |
| {"xn--rger-koa.a.example", "www.xn--rger-koa.b.example", |
| "www.xn--rger-koa.c.example"}, |
| /*invalid_dns_names=*/ |
| {"www.xn--rger-koa.a.example", "xn--rger-koa.b.example", |
| "www.xn--rger-koa.d.example"}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{}, |
| /*flags=*/0, |
| }, |
| |
| // For now, DNS names are also extracted out of the common name, but only |
| // there is no SAN list. |
| // TODO(https://crbug.com/boringssl/464): Remove this. |
| { |
| /*cert_subject=*/{{NID_commonName, "a.example"}, |
| {NID_commonName, "*.b.example"}}, |
| /*cert_dns_names=*/{}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/ |
| {"a.example", "A.EXAMPLE", "test.b.example", "TEST.B.EXAMPLE"}, |
| /*invalid_dns_names=*/{}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{}, |
| /*flags=*/0, |
| }, |
| { |
| /*cert_subject=*/{{NID_commonName, "a.example"}, |
| {NID_commonName, "*.b.example"}}, |
| /*cert_dns_names=*/{"example.com"}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/{}, |
| /*invalid_dns_names=*/ |
| {"a.example", "A.EXAMPLE", "test.b.example", "TEST.B.EXAMPLE"}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{}, |
| /*flags=*/0, |
| }, |
| |
| // Other subject RDNs do not provide DNS names. |
| { |
| /*cert_subject=*/{{NID_organizationName, "example.com"}}, |
| /*cert_dns_names=*/{}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/{}, |
| /*invalid_dns_names=*/{"example.com"}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{}, |
| /*flags=*/0, |
| }, |
| |
| // Input DNS names cannot have wildcards. |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/{"www.example.com"}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/{}, |
| /*invalid_dns_names=*/{"*.example.com"}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{}, |
| /*flags=*/0, |
| }, |
| |
| // OpenSSL has some non-standard wildcard syntax for input DNS names. We |
| // do not support this. |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/{"www.a.example", "*.b.test"}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/{}, |
| /*invalid_dns_names=*/ |
| {".www.a.example", ".www.b.test", ".a.example", ".b.test", ".example", |
| ".test"}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/{}, |
| /*flags=*/0, |
| }, |
| |
| // Emails match case-sensitively before the '@' and case-insensitively |
| // after. They do not match DNS names. |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/{}, |
| /*cert_emails=*/{"test@a.example", "TEST@B.EXAMPLE"}, |
| /*valid_dns_names=*/{}, |
| /*invalid_dns_names=*/{"a.example", "b.example"}, |
| /*valid_emails=*/ |
| {"test@a.example", "test@A.EXAMPLE", "TEST@b.example", |
| "TEST@B.EXAMPLE"}, |
| /*invalid_emails=*/ |
| {"TEST@a.example", "test@B.EXAMPLE", "another-test@a.example", |
| "est@a.example"}, |
| /*flags=*/0, |
| }, |
| |
| // Emails may also be found in the subject. |
| { |
| /*cert_subject=*/{{NID_pkcs9_emailAddress, "test@a.example"}, |
| {NID_pkcs9_emailAddress, "TEST@B.EXAMPLE"}}, |
| /*cert_dns_names=*/{}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/{}, |
| /*invalid_dns_names=*/{"a.example", "b.example"}, |
| /*valid_emails=*/ |
| {"test@a.example", "test@A.EXAMPLE", "TEST@b.example", |
| "TEST@B.EXAMPLE"}, |
| /*invalid_emails=*/ |
| {"TEST@a.example", "test@B.EXAMPLE", "another-test@a.example", |
| "est@a.example"}, |
| /*flags=*/0, |
| }, |
| |
| // There are no email wildcard names. |
| { |
| /*cert_subject=*/{}, |
| /*cert_dns_names=*/{}, |
| /*cert_emails=*/{"test@*.a.example", "@b.example", "*@c.example"}, |
| /*valid_dns_names=*/{}, |
| /*invalid_dns_names=*/{}, |
| /*valid_emails=*/{}, |
| /*invalid_emails=*/ |
| {"test@test.a.example", "test@b.example", "test@c.example"}, |
| /*flags=*/0, |
| }, |
| |
| // Unrelated RDNs can be skipped when looking in the subject. |
| { |
| /*cert_subject=*/{{NID_organizationName, "Acme Corporation"}, |
| {NID_commonName, "a.example"}, |
| {NID_pkcs9_emailAddress, "test@b.example"}, |
| {NID_countryName, "US"}}, |
| /*cert_dns_names=*/{}, |
| /*cert_emails=*/{}, |
| /*valid_dns_names=*/{"a.example"}, |
| /*invalid_dns_names=*/{}, |
| /*valid_emails=*/{"test@b.example"}, |
| /*invalid_emails=*/{}, |
| /*flags=*/0, |
| }, |
| }; |
| |
| size_t i = 0; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(i++); |
| |
| // Issue a test certificate. |
| bssl::UniquePtr<X509> cert = |
| MakeTestCert("Root", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(cert); |
| if (!t.cert_subject.empty()) { |
| bssl::UniquePtr<X509_NAME> subject(X509_NAME_new()); |
| ASSERT_TRUE(subject); |
| for (const auto &entry : t.cert_subject) { |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| subject.get(), entry.first, MBSTRING_ASC, |
| reinterpret_cast<const unsigned char *>(entry.second.data()), |
| entry.second.size(), /*loc=*/-1, /*set=*/0)); |
| } |
| ASSERT_TRUE(X509_set_subject_name(cert.get(), subject.get())); |
| } |
| bssl::UniquePtr<GENERAL_NAMES> sans(sk_GENERAL_NAME_new_null()); |
| ASSERT_TRUE(sans); |
| for (const auto &dns : t.cert_dns_names) { |
| bssl::UniquePtr<GENERAL_NAME> name(GENERAL_NAME_new()); |
| ASSERT_TRUE(name); |
| name->type = GEN_DNS; |
| name->d.dNSName = ASN1_IA5STRING_new(); |
| ASSERT_TRUE(name->d.dNSName); |
| ASSERT_TRUE(ASN1_STRING_set(name->d.dNSName, dns.data(), dns.size())); |
| ASSERT_TRUE(bssl::PushToStack(sans.get(), std::move(name))); |
| } |
| for (const auto &email : t.cert_emails) { |
| bssl::UniquePtr<GENERAL_NAME> name(GENERAL_NAME_new()); |
| ASSERT_TRUE(name); |
| name->type = GEN_EMAIL; |
| name->d.rfc822Name = ASN1_IA5STRING_new(); |
| ASSERT_TRUE(name->d.rfc822Name); |
| ASSERT_TRUE( |
| ASN1_STRING_set(name->d.rfc822Name, email.data(), email.size())); |
| ASSERT_TRUE(bssl::PushToStack(sans.get(), std::move(name))); |
| } |
| if (sk_GENERAL_NAME_num(sans.get()) != 0) { |
| ASSERT_TRUE(X509_add1_ext_i2d(cert.get(), NID_subject_alt_name, |
| sans.get(), /*crit=*/0, /*flags=*/0)); |
| } |
| ASSERT_TRUE(X509_sign(cert.get(), key.get(), EVP_sha256())); |
| |
| for (const auto &dns : t.valid_dns_names) { |
| SCOPED_TRACE(dns); |
| EXPECT_EQ(1, X509_check_host(cert.get(), dns.data(), dns.size(), t.flags, |
| /*peername=*/nullptr)); |
| EXPECT_EQ(X509_V_OK, |
| Verify(cert.get(), {root.get()}, /*intermediates=*/{}, |
| /*crls=*/{}, /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1_host( |
| param, dns.data(), dns.size())); |
| X509_VERIFY_PARAM_set_hostflags(param, t.flags); |
| })); |
| } |
| |
| for (const auto &dns : t.invalid_dns_names) { |
| SCOPED_TRACE(dns); |
| EXPECT_EQ(0, X509_check_host(cert.get(), dns.data(), dns.size(), t.flags, |
| /*peername=*/nullptr)); |
| EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH, |
| Verify(cert.get(), {root.get()}, /*intermediates=*/{}, |
| /*crls=*/{}, /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1_host( |
| param, dns.data(), dns.size())); |
| X509_VERIFY_PARAM_set_hostflags(param, t.flags); |
| })); |
| } |
| |
| for (const auto &email : t.valid_emails) { |
| SCOPED_TRACE(email); |
| EXPECT_EQ( |
| 1, X509_check_email(cert.get(), email.data(), email.size(), t.flags)); |
| EXPECT_EQ(X509_V_OK, |
| Verify(cert.get(), {root.get()}, /*intermediates=*/{}, |
| /*crls=*/{}, /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1_email( |
| param, email.data(), email.size())); |
| X509_VERIFY_PARAM_set_hostflags(param, t.flags); |
| })); |
| } |
| |
| for (const auto &email : t.invalid_emails) { |
| SCOPED_TRACE(email); |
| EXPECT_EQ( |
| 0, X509_check_email(cert.get(), email.data(), email.size(), t.flags)); |
| EXPECT_EQ(X509_V_ERR_EMAIL_MISMATCH, |
| Verify(cert.get(), {root.get()}, /*intermediates=*/{}, |
| /*crls=*/{}, /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| X509_VERIFY_PARAM *param = |
| X509_STORE_CTX_get0_param(ctx); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1_email( |
| param, email.data(), email.size())); |
| X509_VERIFY_PARAM_set_hostflags(param, t.flags); |
| })); |
| } |
| } |
| } |
| |
| TEST(X509Test, AddDuplicates) { |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| bssl::UniquePtr<X509> a(CertFromPEM(kCrossSigningRootPEM)); |
| bssl::UniquePtr<X509> b(CertFromPEM(kRootCAPEM)); |
| |
| ASSERT_TRUE(store); |
| ASSERT_TRUE(a); |
| ASSERT_TRUE(b); |
| |
| EXPECT_TRUE(X509_STORE_add_cert(store.get(), a.get())); |
| EXPECT_TRUE(X509_STORE_add_cert(store.get(), b.get())); |
| EXPECT_TRUE(X509_STORE_add_cert(store.get(), a.get())); |
| EXPECT_TRUE(X509_STORE_add_cert(store.get(), b.get())); |
| EXPECT_TRUE(X509_STORE_add_cert(store.get(), a.get())); |
| EXPECT_TRUE(X509_STORE_add_cert(store.get(), b.get())); |
| |
| EXPECT_EQ(sk_X509_OBJECT_num(X509_STORE_get0_objects(store.get())), 2u); |
| } |
| |
| TEST(X509Test, BytesToHex) { |
| struct { |
| std::vector<uint8_t> bytes; |
| const char *hex; |
| } kTests[] = { |
| {{}, ""}, |
| {{0x00}, "00"}, |
| {{0x00, 0x11, 0x22}, "00:11:22"}, |
| {{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, |
| "01:23:45:67:89:AB:CD:EF"}, |
| }; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(Bytes(t.bytes)); |
| bssl::UniquePtr<char> hex( |
| x509v3_bytes_to_hex(t.bytes.data(), t.bytes.size())); |
| ASSERT_TRUE(hex); |
| EXPECT_STREQ(hex.get(), t.hex); |
| } |
| } |
| |
| TEST(X509Test, NamePrint) { |
| // kTestName is a DER-encoded X.509 that covers many cases. |
| // |
| // SEQUENCE { |
| // SET { |
| // SEQUENCE { |
| // # countryName |
| // OBJECT_IDENTIFIER { 2.5.4.6 } |
| // PrintableString { "US" } |
| // } |
| // } |
| // # Sets may be multi-valued, with different attributes. Try to keep this |
| // # in DER set order, in case we ever enforce this in the parser. |
| // SET { |
| // SEQUENCE { |
| // # stateOrProvinceName |
| // OBJECT_IDENTIFIER { 2.5.4.8 } |
| // PrintableString { "Some State" } |
| // } |
| // SEQUENCE { |
| // # stateOrProvinceName |
| // OBJECT_IDENTIFIER { 2.5.4.8 } |
| // UTF8String { "Some Other State \xe2\x98\x83" } |
| // } |
| // SEQUENCE { |
| // # stateOrProvinceName |
| // OBJECT_IDENTIFIER { 2.5.4.8 } |
| // BMPString { u"Another State \u2603" } |
| // } |
| // SEQUENCE { |
| // # A custom OID |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2 } |
| // UniversalString { U"\u2603" } |
| // } |
| // } |
| // # Custom OIDs may have non-string values. |
| // SET { |
| // SEQUENCE { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.3 } |
| // SEQUENCE { INTEGER { 1 } INTEGER { 2 } } |
| // } |
| // } |
| // SET { |
| // SEQUENCE { |
| // # organizationName |
| // OBJECT_IDENTIFIER { 2.5.4.10 } |
| // PrintableString { "Org Name" } |
| // } |
| // } |
| // SET { |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // # Embed common delimiter forms to test how well they get escaped. |
| // UTF8String { "Common |
| // Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\nCN=A\n" } |
| // } |
| // } |
| // SET { |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // # Test escaping of leading and trailing spaces. |
| // UTF8String { " spaces " } |
| // } |
| // } |
| static const uint8_t kTestName[] = { |
| 0x30, 0x82, 0x01, 0x00, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, |
| 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x6d, 0x30, 0x11, 0x06, 0x03, 0x55, |
| 0x04, 0x08, 0x13, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x20, 0x53, 0x74, 0x61, |
| 0x74, 0x65, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x14, 0x53, |
| 0x6f, 0x6d, 0x65, 0x20, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x53, 0x74, |
| 0x61, 0x74, 0x65, 0x20, 0xe2, 0x98, 0x83, 0x30, 0x25, 0x06, 0x03, 0x55, |
| 0x04, 0x08, 0x1e, 0x1e, 0x00, 0x41, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x74, |
| 0x00, 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x53, 0x00, 0x74, |
| 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x26, 0x03, 0x30, 0x14, |
| 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, |
| 0x09, 0x02, 0x1c, 0x04, 0x00, 0x00, 0x26, 0x03, 0x31, 0x18, 0x30, 0x16, |
| 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, |
| 0x09, 0x03, 0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x31, 0x11, |
| 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x08, 0x4f, 0x72, 0x67, |
| 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x31, 0x42, 0x30, 0x40, 0x06, 0x03, 0x55, |
| 0x04, 0x03, 0x0c, 0x39, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x20, 0x4e, |
| 0x61, 0x6d, 0x65, 0x2f, 0x43, 0x4e, 0x3d, 0x41, 0x2f, 0x43, 0x4e, 0x3d, |
| 0x42, 0x2c, 0x43, 0x4e, 0x3d, 0x41, 0x2c, 0x43, 0x4e, 0x3d, 0x42, 0x2b, |
| 0x43, 0x4e, 0x3d, 0x41, 0x2b, 0x43, 0x4e, 0x3d, 0x42, 0x3b, 0x43, 0x4e, |
| 0x3d, 0x41, 0x3b, 0x43, 0x4e, 0x3d, 0x42, 0x0a, 0x43, 0x4e, 0x3d, 0x41, |
| 0x0a, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x08, |
| 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x20}; |
| |
| const uint8_t *ptr = kTestName; |
| bssl::UniquePtr<X509_NAME> name( |
| d2i_X509_NAME(nullptr, &ptr, sizeof(kTestName))); |
| ASSERT_TRUE(name); |
| EXPECT_EQ(ptr, kTestName + sizeof(kTestName)); |
| |
| struct { |
| int indent; |
| unsigned long flags; |
| std::string printed; |
| } kTests[] = { |
| // RFC 2253 uses , and + separators and encodes the RDNs in reverse. |
| // OpenSSL's implementation additionally happens to reverse the values |
| // within each RDN. RFC 2253 says any order is permissible. |
| {/*indent=*/0, |
| /*flags=*/XN_FLAG_RFC2253, |
| "CN=\\ spaces\\ ," |
| "CN=Common " |
| "Name/CN=A/CN=B\\,CN=A\\,CN=B\\+CN=A\\+CN=B\\;CN=A\\;CN=B\\0ACN=A\\0A," |
| "O=Org Name," |
| "1.2.840.113554.4.1.72585.3=#3006020101020102," |
| "1.2.840.113554.4.1.72585.2=#1C0400002603+" |
| "ST=Another State \\E2\\98\\83+" |
| "ST=Some Other State \\E2\\98\\83+" |
| "ST=Some State," |
| "C=US"}, |
| {/*indent=*/2, |
| /*flags=*/XN_FLAG_RFC2253, |
| " " |
| "CN=\\ spaces\\ ," |
| "CN=Common " |
| "Name/CN=A/CN=B\\,CN=A\\,CN=B\\+CN=A\\+CN=B\\;CN=A\\;CN=B\\0ACN=A\\0A," |
| "O=Org Name," |
| "1.2.840.113554.4.1.72585.3=#3006020101020102," |
| "1.2.840.113554.4.1.72585.2=#1C0400002603+" |
| "ST=Another State \\E2\\98\\83+" |
| "ST=Some Other State \\E2\\98\\83+" |
| "ST=Some State," |
| "C=US"}, |
| // |XN_FLAG_ONELINE| is an OpenSSL-specific single-line format. It also |
| // omits |XN_FLAG_DUMP_UNKNOWN_FIELDS|, so unknown OIDs that use known |
| // string types will still be decoded. (This may drop important |
| // information if the unknown OID distinguishes between string types.) It |
| // also passes |ASN1_STRFLGS_ESC_QUOTE|. |
| {/*indent=*/0, |
| /*flags=*/XN_FLAG_ONELINE, |
| "C = US, " |
| "ST = Some State + " |
| "ST = Some Other State \\E2\\98\\83 + " |
| "ST = Another State \\E2\\98\\83 + " |
| "1.2.840.113554.4.1.72585.2 = \\E2\\98\\83, " |
| "1.2.840.113554.4.1.72585.3 = #3006020101020102, " |
| "O = Org Name, " |
| "CN = \"Common " |
| "Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\0ACN=A\\0A\", " |
| "CN = \" spaces \""}, |
| // Callers can also customize the output, with both |XN_FLAG_*| and |
| // |ASN1_STRFLGS_*|. |XN_FLAG_SEP_SPLUS_SPC| uses semicolon separators. |
| {/*indent=*/0, |
| /*flags=*/XN_FLAG_SEP_SPLUS_SPC | ASN1_STRFLGS_RFC2253 | |
| ASN1_STRFLGS_ESC_QUOTE, |
| "C=US; " |
| "ST=Some State + " |
| "ST=Some Other State \\E2\\98\\83 + " |
| "ST=Another State \\E2\\98\\83 + " |
| "1.2.840.113554.4.1.72585.2=\\E2\\98\\83; " |
| "1.2.840.113554.4.1.72585.3=#3006020101020102; " |
| "O=Org Name; " |
| "CN=\"Common " |
| "Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\0ACN=A\\0A\"; " |
| "CN=\" spaces \""}, |
| // Node uses these parameters. |
| {/*indent=*/0, |
| /*flags=*/ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | |
| ASN1_STRFLGS_UTF8_CONVERT | XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_SN, |
| "C=US\n" |
| "ST=Some State + " |
| "ST=Some Other State \xE2\x98\x83 + " |
| "ST=Another State \xE2\x98\x83 + " |
| "1.2.840.113554.4.1.72585.2=\xE2\x98\x83\n" |
| "1.2.840.113554.4.1.72585.3=0\\06\\02\\01\\01\\02\\01\\02\n" |
| "O=Org Name\n" |
| "CN=Common " |
| "Name/CN=A/CN=B\\,CN=A\\,CN=B\\+CN=A\\+CN=B\\;CN=A\\;CN=B\\0ACN=A\\0A\n" |
| "CN=\\ spaces\\ "}, |
| // |XN_FLAG_COMPAT| matches |X509_NAME_print|, rather than |
| // |X509_NAME_print_ex|. |
| // |
| // TODO(davidben): This works by post-processing the output of |
| // |X509_NAME_oneline|, which uses "/"" separators, and replacing with |
| // ", ". The escaping is ambiguous and the post-processing is buggy, so |
| // some of the trailing slashes are still present and some internal |
| // slashes are mis-converted. |
| {/*indent=*/0, |
| /*flags=*/XN_FLAG_COMPAT, |
| "C=US, " |
| "ST=Some State, " |
| "ST=Some Other State \\xE2\\x98\\x83, " |
| "ST=\\x00A\\x00n\\x00o\\x00t\\x00h\\x00e\\x00r\\x00 " |
| "\\x00S\\x00t\\x00a\\x00t\\x00e\\x00 &\\x03/" |
| "1.2.840.113554.4.1.72585.2=\\x00\\x00&\\x03/" |
| "1.2.840.113554.4.1.72585.3=0\\x06\\x02\\x01\\x01\\x02\\x01\\x02, " |
| "O=Org Name, " |
| "CN=Common Name, " |
| "CN=A, CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\x0ACN=A\\x0A, " |
| "CN= spaces "}, |
| }; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(t.printed); |
| bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem())); |
| ASSERT_TRUE(bio); |
| int len = X509_NAME_print_ex(bio.get(), name.get(), t.indent, t.flags); |
| ASSERT_GT(len, 0); |
| |
| const uint8_t *printed; |
| size_t printed_len; |
| ASSERT_TRUE(BIO_mem_contents(bio.get(), &printed, &printed_len)); |
| EXPECT_EQ(std::string(printed, printed + printed_len), t.printed); |
| if (t.flags != XN_FLAG_COMPAT) { |
| // TODO(davidben): |XN_FLAG_COMPAT| does not return the length. |
| EXPECT_EQ(static_cast<size_t>(len), printed_len); |
| |
| // Passing a null |BIO| measures the output instead. |
| len = X509_NAME_print_ex(nullptr, name.get(), t.indent, t.flags); |
| EXPECT_GT(len, 0); |
| EXPECT_EQ(static_cast<size_t>(len), printed_len); |
| } |
| } |
| |
| // TODO(davidben): This escapes the underlying bytes in the string, but that |
| // is ambiguous without capturing the type. Should this escape like |
| // |ASN1_STRFLGS_UTF8_CONVERT| instead? |
| static const char *kOnelineComponents[] = { |
| "/C=US", |
| "/ST=Some State", |
| "/ST=Some Other State \\xE2\\x98\\x83", |
| ("/ST=\\x00A\\x00n\\x00o\\x00t\\x00h\\x00e\\x00r\\x00 " |
| "\\x00S\\x00t\\x00a\\x00t\\x00e\\x00 &\\x03"), |
| "/1.2.840.113554.4.1.72585.2=\\x00\\x00&\\x03", |
| "/1.2.840.113554.4.1.72585.3=0\\x06\\x02\\x01\\x01\\x02\\x01\\x02", |
| "/O=Org Name", |
| "/CN=Common Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\x0ACN=A\\x0A", |
| "/CN= spaces ", |
| }; |
| std::string oneline_expected; |
| for (const auto& component : kOnelineComponents) { |
| oneline_expected += component; |
| } |
| |
| // Given null buffer, |X509_NAME_oneline| allocates a new output. |
| bssl::UniquePtr<char> oneline(X509_NAME_oneline(name.get(), nullptr, 0)); |
| ASSERT_TRUE(oneline); |
| EXPECT_EQ(oneline.get(), oneline_expected); |
| |
| // Otherwise it writes to the specified buffer. Note one extra byte is needed |
| // for the trailing NUL. |
| char buf[1024]; |
| ASSERT_GE(sizeof(buf), oneline_expected.size() + 2); |
| ASSERT_EQ(buf, |
| X509_NAME_oneline(name.get(), buf, oneline_expected.size() + 1)); |
| EXPECT_EQ(buf, oneline_expected); |
| |
| memset(buf, 'a', sizeof(buf)); |
| ASSERT_EQ(buf, |
| X509_NAME_oneline(name.get(), buf, oneline_expected.size() + 2)); |
| EXPECT_EQ(buf, oneline_expected); |
| |
| // If the length is too small, |X509_NAME_oneline| truncates at name |
| // entry boundaries. |
| EXPECT_EQ(nullptr, X509_NAME_oneline(name.get(), buf, 0)); |
| for (size_t len = 1; len < oneline_expected.size(); len++) { |
| SCOPED_TRACE(len); |
| memset(buf, 'a', sizeof(buf)); |
| EXPECT_EQ(buf, X509_NAME_oneline(name.get(), buf, len)); |
| |
| std::string truncated; |
| for (const auto& component : kOnelineComponents) { |
| if (truncated.size() + strlen(component) + 1 > len) { |
| break; |
| } |
| truncated += component; |
| } |
| EXPECT_EQ(buf, truncated); |
| } |
| } |
| |
| // kLargeSerialPEM is a certificate with a large serial number. |
| static const char kLargeSerialPEM[] = R"( |
| -----BEGIN CERTIFICATE----- |
| MIICZjCCAc+gAwIBAgIQASNFZ4mrze8BI0VniavN7zANBgkqhkiG9w0BAQsFADA2 |
| MRowGAYDVQQKExFCb3JpbmdTU0wgVEVTVElORzEYMBYGA1UEAxMPSW50ZXJtZWRp |
| YXRlIENBMCAXDTE1MDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAyMRowGAYD |
| VQQKExFCb3JpbmdTU0wgVEVTVElORzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wgZ8w |
| DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMPRTRliCpKEnug6OzI0rJVcQep5p+aT |
| 9sCg+pj+HVyg/DYTwqZ6qJRKhM+MbkhdJuU7FyqlsBeCeM/OjwMjcY0yEB/xJg1i |
| ygfuBztTLuPnHxtSuKwae5MeqSofp3j97sRMnuLcKlHxu8rXoOCAS9BO50uKnPwU |
| Ee1iEVqR92FPAgMBAAGjdzB1MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr |
| BgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAZBgNVHQ4EEgQQo3mm9u6v |
| uaVeN4wRgDTidTAbBgNVHSMEFDASgBCMGmiotXbbXVd7H40UsgajMA0GCSqGSIb3 |
| DQEBCwUAA4GBAGP+n4kKGn/8uddYLWTXbUsz+KLuEXNDMyu3vRufLjTpIbP2MCNo |
| 85fhLeC3fzKuGOk+6QGVLOBBcWDrrLqrmqnWdBMPULDo2QoF71a4GVjeJh+ax/tZ |
| PyeGVPUK21TE0LDIxf2a11d1CJw582MgZQIPk4tXk+AcU9EqIceKgECG |
| -----END CERTIFICATE----- |
| )"; |
| |
| TEST(X509Test, Print) { |
| bssl::UniquePtr<X509> cert(CertFromPEM(kLargeSerialPEM)); |
| ASSERT_TRUE(cert); |
| |
| bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem())); |
| ASSERT_TRUE(bio); |
| EXPECT_TRUE(X509_print_ex(bio.get(), cert.get(), 0, 0)); |
| // Nothing should be left in the error queue. |
| EXPECT_EQ(0u, ERR_peek_error()); |
| |
| // This output is not guaranteed to be stable, but we assert on it to make |
| // sure something is printed. |
| const uint8_t *data; |
| size_t data_len; |
| ASSERT_TRUE(BIO_mem_contents(bio.get(), &data, &data_len)); |
| std::string print(reinterpret_cast<const char*>(data), data_len); |
| EXPECT_EQ(print, R"(Certificate: |
| Data: |
| Version: 3 (0x2) |
| Serial Number: |
| 01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef |
| Signature Algorithm: sha256WithRSAEncryption |
| Issuer: O=BoringSSL TESTING, CN=Intermediate CA |
| Validity |
| Not Before: Jan 1 00:00:00 2015 GMT |
| Not After : Jan 1 00:00:00 2100 GMT |
| Subject: O=BoringSSL TESTING, CN=example.com |
| Subject Public Key Info: |
| Public Key Algorithm: rsaEncryption |
| Public-Key: (1024 bit) |
| Modulus: |
| 00:c3:d1:4d:19:62:0a:92:84:9e:e8:3a:3b:32:34: |
| ac:95:5c:41:ea:79:a7:e6:93:f6:c0:a0:fa:98:fe: |
| 1d:5c:a0:fc:36:13:c2:a6:7a:a8:94:4a:84:cf:8c: |
| 6e:48:5d:26:e5:3b:17:2a:a5:b0:17:82:78:cf:ce: |
| 8f:03:23:71:8d:32:10:1f:f1:26:0d:62:ca:07:ee: |
| 07:3b:53:2e:e3:e7:1f:1b:52:b8:ac:1a:7b:93:1e: |
| a9:2a:1f:a7:78:fd:ee:c4:4c:9e:e2:dc:2a:51:f1: |
| bb:ca:d7:a0:e0:80:4b:d0:4e:e7:4b:8a:9c:fc:14: |
| 11:ed:62:11:5a:91:f7:61:4f |
| Exponent: 65537 (0x10001) |
| X509v3 extensions: |
| X509v3 Key Usage: critical |
| Digital Signature, Key Encipherment |
| X509v3 Extended Key Usage: |
| TLS Web Server Authentication, TLS Web Client Authentication |
| X509v3 Basic Constraints: critical |
| CA:FALSE |
| X509v3 Subject Key Identifier: |
| A3:79:A6:F6:EE:AF:B9:A5:5E:37:8C:11:80:34:E2:75 |
| X509v3 Authority Key Identifier: |
| keyid:8C:1A:68:A8:B5:76:DB:5D:57:7B:1F:8D:14:B2:06:A3 |
| |
| Signature Algorithm: sha256WithRSAEncryption |
| 63:fe:9f:89:0a:1a:7f:fc:b9:d7:58:2d:64:d7:6d:4b:33:f8: |
| a2:ee:11:73:43:33:2b:b7:bd:1b:9f:2e:34:e9:21:b3:f6:30: |
| 23:68:f3:97:e1:2d:e0:b7:7f:32:ae:18:e9:3e:e9:01:95:2c: |
| e0:41:71:60:eb:ac:ba:ab:9a:a9:d6:74:13:0f:50:b0:e8:d9: |
| 0a:05:ef:56:b8:19:58:de:26:1f:9a:c7:fb:59:3f:27:86:54: |
| f5:0a:db:54:c4:d0:b0:c8:c5:fd:9a:d7:57:75:08:9c:39:f3: |
| 63:20:65:02:0f:93:8b:57:93:e0:1c:53:d1:2a:21:c7:8a:80: |
| 40:86 |
| )"); |
| } |
| |
| TEST(X509Test, AddExt) { |
| bssl::UniquePtr<X509> x509(X509_new()); |
| ASSERT_TRUE(x509); |
| |
| struct Extension { |
| int nid; |
| bool critical; |
| std::vector<uint8_t> data; |
| }; |
| auto expect_extensions = [&](const std::vector<Extension> &exts) { |
| ASSERT_EQ(static_cast<size_t>(X509_get_ext_count(x509.get())), exts.size()); |
| for (size_t i = 0; i < exts.size(); i++) { |
| SCOPED_TRACE(i); |
| const X509_EXTENSION *ext = X509_get_ext(x509.get(), static_cast<int>(i)); |
| EXPECT_EQ(OBJ_obj2nid(X509_EXTENSION_get_object(ext)), exts[i].nid); |
| EXPECT_EQ(X509_EXTENSION_get_critical(ext), exts[i].critical ? 1 : 0); |
| const ASN1_OCTET_STRING *data = X509_EXTENSION_get_data(ext); |
| EXPECT_EQ(Bytes(ASN1_STRING_get0_data(data), ASN1_STRING_length(data)), |
| Bytes(exts[i].data)); |
| } |
| }; |
| |
| // Make a few sample extensions. |
| |
| // SEQUENCE {} |
| std::vector<uint8_t> basic1_der = {0x30, 0x00}; |
| const uint8_t *inp = basic1_der.data(); |
| bssl::UniquePtr<BASIC_CONSTRAINTS> basic1_obj( |
| d2i_BASIC_CONSTRAINTS(nullptr, &inp, basic1_der.size())); |
| EXPECT_EQ(inp, basic1_der.data() + basic1_der.size()); |
| |
| // SEQUENCE { BOOLEAN { TRUE } } |
| std::vector<uint8_t> basic2_der = {0x30, 0x03, 0x01, 0x01, 0xff}; |
| inp = basic2_der.data(); |
| bssl::UniquePtr<BASIC_CONSTRAINTS> basic2_obj( |
| d2i_BASIC_CONSTRAINTS(nullptr, &inp, basic2_der.size())); |
| EXPECT_EQ(inp, basic2_der.data() + basic2_der.size()); |
| |
| // OCTET_STRING {} |
| std::vector<uint8_t> skid1_der = {0x04, 0x00}; |
| inp = skid1_der.data(); |
| bssl::UniquePtr<ASN1_OCTET_STRING> skid1_obj( |
| d2i_ASN1_OCTET_STRING(nullptr, &inp, skid1_der.size())); |
| EXPECT_EQ(inp, skid1_der.data() + skid1_der.size()); |
| |
| // OCTET_STRING { "a" } |
| std::vector<uint8_t> skid2_der = {0x04, 0x01, 0x61}; |
| inp = skid2_der.data(); |
| bssl::UniquePtr<ASN1_OCTET_STRING> skid2_obj( |
| d2i_ASN1_OCTET_STRING(nullptr, &inp, skid2_der.size())); |
| EXPECT_EQ(inp, skid2_der.data() + skid2_der.size()); |
| |
| // Initially, the extension list is empty. |
| expect_extensions({}); |
| |
| // Adding extensions works with the default settings. |
| EXPECT_EQ( |
| 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(), |
| /*crit=*/1, X509V3_ADD_DEFAULT)); |
| expect_extensions({{NID_basic_constraints, true, basic1_der}}); |
| EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_subject_key_identifier, |
| skid1_obj.get(), |
| /*crit=*/0, X509V3_ADD_DEFAULT)); |
| expect_extensions({{NID_basic_constraints, true, basic1_der}, |
| {NID_subject_key_identifier, false, skid1_der}}); |
| |
| // By default, we cannot add duplicates. |
| EXPECT_EQ( |
| 0, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic2_obj.get(), |
| /*crit=*/0, X509V3_ADD_DEFAULT)); |
| expect_extensions({{NID_basic_constraints, true, basic1_der}, |
| {NID_subject_key_identifier, false, skid1_der}}); |
| |
| // |X509V3_ADD_KEEP_EXISTING| silently keeps the existing extension if already |
| // present. |
| EXPECT_EQ( |
| 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic2_obj.get(), |
| /*crit=*/0, X509V3_ADD_KEEP_EXISTING)); |
| expect_extensions({{NID_basic_constraints, true, basic1_der}, |
| {NID_subject_key_identifier, false, skid1_der}}); |
| |
| // |X509V3_ADD_REPLACE| replaces it. |
| EXPECT_EQ( |
| 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic2_obj.get(), |
| /*crit=*/0, X509V3_ADD_REPLACE)); |
| expect_extensions({{NID_basic_constraints, false, basic2_der}, |
| {NID_subject_key_identifier, false, skid1_der}}); |
| |
| // |X509V3_ADD_REPLACE_EXISTING| also replaces matches. |
| EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_subject_key_identifier, |
| skid2_obj.get(), |
| /*crit=*/1, X509V3_ADD_REPLACE_EXISTING)); |
| expect_extensions({{NID_basic_constraints, false, basic2_der}, |
| {NID_subject_key_identifier, true, skid2_der}}); |
| |
| // |X509V3_ADD_DELETE| ignores the value and deletes the extension. |
| EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0, |
| X509V3_ADD_DELETE)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}}); |
| |
| // Not finding an extension to delete is an error. |
| EXPECT_EQ(0, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0, |
| X509V3_ADD_DELETE)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}}); |
| |
| // |X509V3_ADD_REPLACE_EXISTING| fails if it cannot find a match. |
| EXPECT_EQ( |
| 0, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(), |
| /*crit=*/1, X509V3_ADD_REPLACE_EXISTING)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}}); |
| |
| // |X509V3_ADD_REPLACE| adds a new extension if not preseent. |
| EXPECT_EQ( |
| 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(), |
| /*crit=*/1, X509V3_ADD_REPLACE)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}, |
| {NID_basic_constraints, true, basic1_der}}); |
| |
| // Delete the extension again. |
| EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0, |
| X509V3_ADD_DELETE)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}}); |
| |
| // |X509V3_ADD_KEEP_EXISTING| adds a new extension if not preseent. |
| EXPECT_EQ( |
| 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(), |
| /*crit=*/1, X509V3_ADD_KEEP_EXISTING)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}, |
| {NID_basic_constraints, true, basic1_der}}); |
| |
| // Delete the extension again. |
| EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0, |
| X509V3_ADD_DELETE)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}}); |
| |
| // |X509V3_ADD_APPEND| adds a new extension if not present. |
| EXPECT_EQ( |
| 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic1_obj.get(), |
| /*crit=*/1, X509V3_ADD_APPEND)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}, |
| {NID_basic_constraints, true, basic1_der}}); |
| |
| // |X509V3_ADD_APPEND| keeps adding duplicates (invalid) even if present. |
| EXPECT_EQ( |
| 1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, basic2_obj.get(), |
| /*crit=*/0, X509V3_ADD_APPEND)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}, |
| {NID_basic_constraints, true, basic1_der}, |
| {NID_basic_constraints, false, basic2_der}}); |
| |
| // |X509V3_ADD_DELETE| only deletes one extension at a time. |
| EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0, |
| X509V3_ADD_DELETE)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}, |
| {NID_basic_constraints, false, basic2_der}}); |
| EXPECT_EQ(1, X509_add1_ext_i2d(x509.get(), NID_basic_constraints, nullptr, 0, |
| X509V3_ADD_DELETE)); |
| expect_extensions({{NID_subject_key_identifier, true, skid2_der}}); |
| } |
| |
| TEST(X509Test, NameEntry) { |
| bssl::UniquePtr<X509_NAME> name(X509_NAME_new()); |
| ASSERT_TRUE(name); |
| |
| auto check_name = [&](const char *expected_rfc2253) { |
| // Check RDN indices are self-consistent. |
| int num = X509_NAME_entry_count(name.get()); |
| if (num > 0) { |
| // RDN indices must start at zero. |
| EXPECT_EQ(0, X509_NAME_ENTRY_set(X509_NAME_get_entry(name.get(), 0))); |
| } |
| for (int i = 1; i < num; i++) { |
| int prev = X509_NAME_ENTRY_set(X509_NAME_get_entry(name.get(), i - 1)); |
| int current = X509_NAME_ENTRY_set(X509_NAME_get_entry(name.get(), i)); |
| // RDN indices must increase consecutively. |
| EXPECT_TRUE(prev == current || prev + 1 == current) |
| << "Entry " << i << " has RDN index " << current |
| << " which is inconsistent with previous index " << prev; |
| } |
| |
| // Check the name based on the RFC 2253 serialization. Note the RFC 2253 |
| // serialization is in reverse. |
| bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem())); |
| ASSERT_TRUE(bio); |
| EXPECT_GE(X509_NAME_print_ex(bio.get(), name.get(), 0, XN_FLAG_RFC2253), 0); |
| const uint8_t *data; |
| size_t len; |
| ASSERT_TRUE(BIO_mem_contents(bio.get(), &data, &len)); |
| EXPECT_EQ(expected_rfc2253, std::string(data, data + len)); |
| }; |
| |
| check_name(""); |
| |
| // |loc| = -1, |set| = 0 appends as new RDNs. |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_organizationName, MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char *>("Org"), /*len=*/-1, /*loc=*/-1, |
| /*set=*/0)); |
| check_name("O=Org"); |
| |
| // |loc| = -1, |set| = 0 appends as new RDNs. |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_commonName, MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char *>("Name"), /*len=*/-1, /*loc=*/-1, |
| /*set=*/0)); |
| check_name("CN=Name,O=Org"); |
| |
| // Inserting in the middle of the set, but with |set| = 0 inserts a new RDN |
| // and fixes the "set" values as needed. |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_organizationalUnitName, MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char *>("Unit"), /*len=*/-1, /*loc=*/1, |
| /*set=*/0)); |
| check_name("CN=Name,OU=Unit,O=Org"); |
| |
| // |set = -1| adds to the previous entry's RDN. (Although putting O and OU at |
| // the same level makes little sense, the test is written this way to check |
| // the function isn't using attribute types to order things.) |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_organizationName, MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char *>("Org2"), /*len=*/-1, /*loc=*/2, |
| /*set=*/-1)); |
| check_name("CN=Name,O=Org2+OU=Unit,O=Org"); |
| |
| // |set| = 1 adds to the next entry's RDN. |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_commonName, MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char *>("Name2"), /*len=*/-1, /*loc=*/2, |
| /*set=*/-1)); |
| check_name("CN=Name,O=Org2+CN=Name2+OU=Unit,O=Org"); |
| |
| // If there is no previous RDN, |set| = -1 makes a new RDN. |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_countryName, MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char *>("US"), /*len=*/-1, /*loc=*/0, |
| /*set=*/-1)); |
| check_name("CN=Name,O=Org2+CN=Name2+OU=Unit,O=Org,C=US"); |
| |
| // Likewise if there is no next RDN. |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_commonName, MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char *>("Name3"), /*len=*/-1, /*loc=*/-1, |
| /*set=*/1)); |
| check_name("CN=Name3,CN=Name,O=Org2+CN=Name2+OU=Unit,O=Org,C=US"); |
| |
| // If |set| = 0 and we insert in the middle of an existing RDN, it adds an |
| // RDN boundary after the entry but not before. This is a quirk of how the |
| // function is implemented and hopefully not something any caller depends on. |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_commonName, MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char *>("Name4"), /*len=*/-1, /*loc=*/3, |
| /*set=*/0)); |
| check_name("CN=Name3,CN=Name,O=Org2+CN=Name2,CN=Name4+OU=Unit,O=Org,C=US"); |
| |
| // Entries may be deleted. |
| X509_NAME_ENTRY_free(X509_NAME_delete_entry(name.get(), 7)); |
| check_name("CN=Name,O=Org2+CN=Name2,CN=Name4+OU=Unit,O=Org,C=US"); |
| |
| // When deleting the only attribute in an RDN, index invariants should still |
| // hold. |
| X509_NAME_ENTRY_free(X509_NAME_delete_entry(name.get(), 0)); |
| check_name("CN=Name,O=Org2+CN=Name2,CN=Name4+OU=Unit,O=Org"); |
| |
| // Index invariants also hold when deleting attributes from non-singular RDNs. |
| X509_NAME_ENTRY_free(X509_NAME_delete_entry(name.get(), 1)); |
| check_name("CN=Name,O=Org2+CN=Name2,CN=Name4,O=Org"); |
| X509_NAME_ENTRY_free(X509_NAME_delete_entry(name.get(), 1)); |
| check_name("CN=Name,O=Org2+CN=Name2,O=Org"); |
| |
| // Same as above, but delete the second attribute first. |
| X509_NAME_ENTRY_free(X509_NAME_delete_entry(name.get(), 2)); |
| check_name("CN=Name,CN=Name2,O=Org"); |
| X509_NAME_ENTRY_free(X509_NAME_delete_entry(name.get(), 1)); |
| check_name("CN=Name,O=Org"); |
| } |
| |
| // Tests that non-integer types are rejected when passed as an argument to |
| // X509_set_serialNumber(). |
| TEST(X509Test, SetSerialNumberChecksASN1StringType) { |
| bssl::UniquePtr<X509> root = CertFromPEM(kRootCAPEM); |
| ASSERT_TRUE(root); |
| |
| // Passing an IA5String to X509_set_serialNumber() should fail. |
| bssl::UniquePtr<ASN1_IA5STRING> str(ASN1_IA5STRING_new()); |
| ASSERT_TRUE(str); |
| EXPECT_FALSE(X509_set_serialNumber(root.get(), str.get())); |
| |
| // Passing a negative serial number is allowed. While invalid, we do accept |
| // them and some callers rely in this for tests. |
| bssl::UniquePtr<ASN1_INTEGER> serial(ASN1_INTEGER_new()); |
| ASSERT_TRUE(serial); |
| ASSERT_TRUE(ASN1_INTEGER_set_int64(serial.get(), -1)); |
| ASSERT_TRUE(X509_set_serialNumber(root.get(), serial.get())); |
| int64_t val; |
| ASSERT_TRUE(ASN1_INTEGER_get_int64(&val, X509_get0_serialNumber(root.get()))); |
| EXPECT_EQ(-1, val); |
| } |
| |
| TEST(X509Test, Policy) { |
| bssl::UniquePtr<ASN1_OBJECT> oid1( |
| OBJ_txt2obj("1.2.840.113554.4.1.72585.2.1", /*dont_search_names=*/1)); |
| ASSERT_TRUE(oid1); |
| bssl::UniquePtr<ASN1_OBJECT> oid2( |
| OBJ_txt2obj("1.2.840.113554.4.1.72585.2.2", /*dont_search_names=*/1)); |
| ASSERT_TRUE(oid2); |
| bssl::UniquePtr<ASN1_OBJECT> oid3( |
| OBJ_txt2obj("1.2.840.113554.4.1.72585.2.3", /*dont_search_names=*/1)); |
| ASSERT_TRUE(oid3); |
| bssl::UniquePtr<ASN1_OBJECT> oid4( |
| OBJ_txt2obj("1.2.840.113554.4.1.72585.2.4", /*dont_search_names=*/1)); |
| ASSERT_TRUE(oid4); |
| bssl::UniquePtr<ASN1_OBJECT> oid5( |
| OBJ_txt2obj("1.2.840.113554.4.1.72585.2.5", /*dont_search_names=*/1)); |
| ASSERT_TRUE(oid5); |
| |
| bssl::UniquePtr<X509> root( |
| CertFromPEM(GetTestData("crypto/x509/test/policy_root.pem").c_str())); |
| ASSERT_TRUE(root); |
| bssl::UniquePtr<X509> root_cross_inhibit_mapping(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_root_cross_inhibit_mapping.pem") |
| .c_str())); |
| ASSERT_TRUE(root_cross_inhibit_mapping); |
| bssl::UniquePtr<X509> root2( |
| CertFromPEM(GetTestData("crypto/x509/test/policy_root2.pem").c_str())); |
| ASSERT_TRUE(root2); |
| bssl::UniquePtr<X509> intermediate(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate.pem").c_str())); |
| ASSERT_TRUE(intermediate); |
| bssl::UniquePtr<X509> intermediate_any(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_any.pem").c_str())); |
| ASSERT_TRUE(intermediate_any); |
| bssl::UniquePtr<X509> intermediate_duplicate(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_duplicate.pem") |
| .c_str())); |
| ASSERT_TRUE(intermediate_duplicate); |
| bssl::UniquePtr<X509> intermediate_invalid(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_invalid.pem").c_str())); |
| ASSERT_TRUE(intermediate_invalid); |
| bssl::UniquePtr<X509> intermediate_mapped(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_mapped.pem") |
| .c_str())); |
| ASSERT_TRUE(intermediate_mapped); |
| bssl::UniquePtr<X509> intermediate_mapped_any(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_mapped_any.pem") |
| .c_str())); |
| ASSERT_TRUE(intermediate_mapped_any); |
| bssl::UniquePtr<X509> intermediate_mapped_oid3(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_mapped_oid3.pem") |
| .c_str())); |
| ASSERT_TRUE(intermediate_mapped_oid3); |
| bssl::UniquePtr<X509> intermediate_require(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_require.pem").c_str())); |
| ASSERT_TRUE(intermediate_require); |
| bssl::UniquePtr<X509> intermediate_require1(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_require1.pem") |
| .c_str())); |
| ASSERT_TRUE(intermediate_require1); |
| bssl::UniquePtr<X509> intermediate_require2(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_require2.pem") |
| .c_str())); |
| ASSERT_TRUE(intermediate_require2); |
| bssl::UniquePtr<X509> intermediate_require_duplicate(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate_require_duplicate.pem") |
| .c_str())); |
| ASSERT_TRUE(intermediate_require_duplicate); |
| bssl::UniquePtr<X509> intermediate_require_no_policies(CertFromPEM( |
| GetTestData( |
| "crypto/x509/test/policy_intermediate_require_no_policies.pem") |
| .c_str())); |
| ASSERT_TRUE(intermediate_require_no_policies); |
| bssl::UniquePtr<X509> leaf( |
| CertFromPEM(GetTestData("crypto/x509/test/policy_leaf.pem").c_str())); |
| ASSERT_TRUE(leaf); |
| bssl::UniquePtr<X509> leaf_any( |
| CertFromPEM(GetTestData("crypto/x509/test/policy_leaf_any.pem").c_str())); |
| ASSERT_TRUE(leaf_any); |
| bssl::UniquePtr<X509> leaf_duplicate(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_duplicate.pem").c_str())); |
| ASSERT_TRUE(leaf_duplicate); |
| bssl::UniquePtr<X509> leaf_invalid(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_invalid.pem").c_str())); |
| ASSERT_TRUE(leaf_invalid); |
| bssl::UniquePtr<X509> leaf_none(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_none.pem").c_str())); |
| ASSERT_TRUE(leaf_none); |
| bssl::UniquePtr<X509> leaf_oid1(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_oid1.pem").c_str())); |
| ASSERT_TRUE(leaf_oid1); |
| bssl::UniquePtr<X509> leaf_oid2(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_oid2.pem").c_str())); |
| ASSERT_TRUE(leaf_oid2); |
| bssl::UniquePtr<X509> leaf_oid3(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_oid3.pem").c_str())); |
| ASSERT_TRUE(leaf_oid3); |
| bssl::UniquePtr<X509> leaf_oid4(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_oid4.pem").c_str())); |
| ASSERT_TRUE(leaf_oid4); |
| bssl::UniquePtr<X509> leaf_oid5(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_oid5.pem").c_str())); |
| ASSERT_TRUE(leaf_oid5); |
| bssl::UniquePtr<X509> leaf_require(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_require.pem").c_str())); |
| ASSERT_TRUE(leaf_require); |
| bssl::UniquePtr<X509> leaf_require1(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_require1.pem").c_str())); |
| ASSERT_TRUE(leaf_require1); |
| |
| auto set_policies = [](X509_STORE_CTX *ctx, |
| std::vector<const ASN1_OBJECT *> oids) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| for (const ASN1_OBJECT *oid : oids) { |
| bssl::UniquePtr<ASN1_OBJECT> copy(OBJ_dup(oid)); |
| ASSERT_TRUE(copy); |
| ASSERT_TRUE(X509_VERIFY_PARAM_add0_policy(param, copy.get())); |
| copy.release(); // |X509_VERIFY_PARAM_add0_policy| takes ownership on |
| // success. |
| } |
| }; |
| |
| // The chain is good for |oid1| and |oid2|, but not |oid3|. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY)); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid2.get()}); |
| })); |
| EXPECT_EQ(X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get(), oid2.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get(), oid3.get()}); |
| })); |
| |
| // The policy extension cannot be parsed. |
| EXPECT_EQ(X509_V_ERR_INVALID_POLICY_EXTENSION, |
| Verify(leaf.get(), {root.get()}, {intermediate_invalid.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ(X509_V_ERR_INVALID_POLICY_EXTENSION, |
| Verify(leaf_invalid.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ(X509_V_ERR_INVALID_POLICY_EXTENSION, |
| Verify(leaf_invalid.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{})); |
| |
| // There is a duplicate policy in the policy extension. |
| EXPECT_EQ(X509_V_ERR_INVALID_POLICY_EXTENSION, |
| Verify(leaf.get(), {root.get()}, {intermediate_duplicate.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| |
| // The policy extension in the leaf cannot be parsed. |
| EXPECT_EQ(X509_V_ERR_INVALID_POLICY_EXTENSION, |
| Verify(leaf_duplicate.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| |
| // Without |X509_V_FLAG_EXPLICIT_POLICY|, the policy tree is built and |
| // intersected with user-specified policies, but it is not required to result |
| // in any valid policies. |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // However, a CA with policy constraints can require an explicit policy. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, |
| {intermediate_require.get()}, /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ(X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf.get(), {root.get()}, {intermediate_require.get()}, |
| /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // requireExplicitPolicy applies even if the application does not configure a |
| // user-initial-policy-set. If the validation results in no policies, the |
| // chain is invalid. |
| EXPECT_EQ(X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_none.get(), {root.get()}, {intermediate_require.get()}, |
| /*crls=*/{})); |
| |
| // A leaf can also set requireExplicitPolicy. |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf_require.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{}, /*flags=*/0)); |
| EXPECT_EQ(X509_V_OK, Verify(leaf_require.get(), {root.get()}, |
| {intermediate.get()}, /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ(X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_require.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // requireExplicitPolicy is a count of certificates to skip. If the value is |
| // not zero by the end of the chain, it doesn't count. |
| EXPECT_EQ(X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf.get(), {root.get()}, {intermediate_require1.get()}, |
| /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate_require2.get()}, |
| /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf_require1.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // If multiple certificates specify the constraint, the more constrained value |
| // wins. |
| EXPECT_EQ( |
| X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_require1.get(), {root.get()}, {intermediate_require1.get()}, |
| /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| EXPECT_EQ( |
| X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_require.get(), {root.get()}, {intermediate_require2.get()}, |
| /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // An intermediate that requires an explicit policy, but then specifies no |
| // policies should fail verification as a result. |
| EXPECT_EQ(X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf.get(), {root.get()}, |
| {intermediate_require_no_policies.get()}, /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| |
| // A constrained intermediate's policy extension has a duplicate policy, which |
| // is invalid. Historically this, and the above case, leaked memory. |
| EXPECT_EQ(X509_V_ERR_INVALID_POLICY_EXTENSION, |
| Verify(leaf.get(), {root.get()}, |
| {intermediate_require_duplicate.get()}, /*crls=*/{}, |
| /*flags=*/0, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| |
| // The leaf asserts anyPolicy, but the intermediate does not. The resulting |
| // valid policies are the intersection. |
| EXPECT_EQ( |
| X509_V_OK, |
| Verify(leaf_any.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ( |
| X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_any.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // The intermediate asserts anyPolicy, but the leaf does not. The resulting |
| // valid policies are the intersection. |
| EXPECT_EQ( |
| X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate_any.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ( |
| X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf.get(), {root.get()}, {intermediate_any.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // Both assert anyPolicy. All policies are valid. |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf_any.get(), {root.get()}, {intermediate_any.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf_any.get(), {root.get()}, {intermediate_any.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // With just a trust anchor, policy checking silently succeeds. |
| EXPECT_EQ(X509_V_OK, Verify(root.get(), {root.get()}, {}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| |
| for (bool use_any : {false, true}) { |
| SCOPED_TRACE(use_any); |
| X509 *cert = |
| use_any ? intermediate_mapped_any.get() : intermediate_mapped.get(); |
| // OID3 is mapped to {OID1, OID2}, which means OID1 and OID2 (or both) are |
| // acceptable for OID3. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, Verify(leaf_oid1.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, Verify(leaf_oid2.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // If the intermediate's policies were anyPolicy, OID3 at the leaf, despite |
| // being mapped, is still acceptable as OID3 at the root. Despite the OID3 |
| // having expected_policy_set = {OID1, OID2}, it can match the anyPolicy |
| // node instead. |
| // |
| // If the intermediate's policies listed OIDs explicitly, OID3 at the leaf |
| // is not acceptable as OID3 at the root. OID3 has expected_polciy_set = |
| // {OID1, OID2} and no other node allows OID3. |
| EXPECT_EQ(use_any ? X509_V_OK : X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_oid3.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| |
| // If the intermediate's policies were anyPolicy, OID1 at the leaf is no |
| // longer acceptable as OID1 at the root because policies only match |
| // anyPolicy when they match no other policy. |
| // |
| // If the intermediate's policies listed OIDs explicitly, OID1 at the leaf |
| // is acceptable as OID1 at the root because it will match both OID1 and |
| // OID3 (mapped) policies. |
| EXPECT_EQ(use_any ? X509_V_ERR_NO_EXPLICIT_POLICY : X509_V_OK, |
| Verify(leaf_oid1.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| |
| // All pairs of OID4 and OID5 are mapped together, so either can stand for |
| // the other. |
| EXPECT_EQ(X509_V_OK, Verify(leaf_oid4.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid4.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, Verify(leaf_oid4.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid5.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, Verify(leaf_oid5.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid4.get()}); |
| })); |
| EXPECT_EQ(X509_V_OK, Verify(leaf_oid5.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid5.get()}); |
| })); |
| |
| EXPECT_EQ(X509_V_OK, Verify(leaf_oid4.get(), {root.get()}, {cert}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid4.get(), oid5.get()}); |
| })); |
| } |
| |
| // Although |intermediate_mapped_oid3| contains many mappings, it only accepts |
| // OID3. Nodes should not be created for the other mappings. |
| EXPECT_EQ(X509_V_OK, Verify(leaf_oid1.get(), {root.get()}, |
| {intermediate_mapped_oid3.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| EXPECT_EQ( |
| X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_oid4.get(), {root.get()}, {intermediate_mapped_oid3.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid4.get()}); |
| })); |
| |
| // Policy mapping can be inhibited, either by the caller or a certificate in |
| // the chain, in which case mapped policies are unassertable (apart from some |
| // anyPolicy edge cases). |
| EXPECT_EQ( |
| X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_oid1.get(), {root.get()}, {intermediate_mapped_oid3.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY | X509_V_FLAG_INHIBIT_MAP, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| EXPECT_EQ( |
| X509_V_ERR_NO_EXPLICIT_POLICY, |
| Verify(leaf_oid1.get(), {root2.get()}, |
| {intermediate_mapped_oid3.get(), root_cross_inhibit_mapping.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid3.get()}); |
| })); |
| } |
| |
| #if defined(OPENSSL_THREADS) |
| // A similar test to the above, but ensures the various bits of intermediate |
| // state are computed safely. |
| TEST(X509Test, PolicyThreads) { |
| const size_t kNumThreads = 10; |
| |
| bssl::UniquePtr<ASN1_OBJECT> oid1( |
| OBJ_txt2obj("1.2.840.113554.4.1.72585.2.1", /*dont_search_names=*/1)); |
| ASSERT_TRUE(oid1); |
| bssl::UniquePtr<ASN1_OBJECT> oid2( |
| OBJ_txt2obj("1.2.840.113554.4.1.72585.2.2", /*dont_search_names=*/1)); |
| ASSERT_TRUE(oid2); |
| bssl::UniquePtr<ASN1_OBJECT> oid3( |
| OBJ_txt2obj("1.2.840.113554.4.1.72585.2.3", /*dont_search_names=*/1)); |
| ASSERT_TRUE(oid3); |
| |
| auto set_policies = [](X509_STORE_CTX *ctx, |
| std::vector<const ASN1_OBJECT *> oids) { |
| X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); |
| for (const ASN1_OBJECT *oid : oids) { |
| bssl::UniquePtr<ASN1_OBJECT> copy(OBJ_dup(oid)); |
| ASSERT_TRUE(copy); |
| ASSERT_TRUE(X509_VERIFY_PARAM_add0_policy(param, copy.get())); |
| copy.release(); // |X509_VERIFY_PARAM_add0_policy| takes ownership on |
| // success. |
| } |
| }; |
| |
| { |
| bssl::UniquePtr<X509> root( |
| CertFromPEM(GetTestData("crypto/x509/test/policy_root.pem").c_str())); |
| ASSERT_TRUE(root); |
| bssl::UniquePtr<X509> intermediate(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate.pem").c_str())); |
| ASSERT_TRUE(intermediate); |
| bssl::UniquePtr<X509> leaf( |
| CertFromPEM(GetTestData("crypto/x509/test/policy_leaf.pem").c_str())); |
| ASSERT_TRUE(leaf); |
| |
| std::vector<std::thread> threads; |
| for (size_t i = 0; i < kNumThreads; i++) { |
| threads.emplace_back([&] { |
| EXPECT_EQ( |
| X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{}, |
| X509_V_FLAG_EXPLICIT_POLICY, [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| }); |
| } |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| } |
| |
| { |
| bssl::UniquePtr<X509> root( |
| CertFromPEM(GetTestData("crypto/x509/test/policy_root.pem").c_str())); |
| ASSERT_TRUE(root); |
| bssl::UniquePtr<X509> intermediate(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_intermediate.pem").c_str())); |
| ASSERT_TRUE(intermediate); |
| bssl::UniquePtr<X509> leaf_invalid(CertFromPEM( |
| GetTestData("crypto/x509/test/policy_leaf_invalid.pem").c_str())); |
| ASSERT_TRUE(leaf_invalid); |
| |
| |
| std::vector<std::thread> threads; |
| for (size_t i = 0; i < kNumThreads; i++) { |
| threads.emplace_back([&] { |
| EXPECT_EQ(X509_V_ERR_INVALID_POLICY_EXTENSION, |
| Verify(leaf_invalid.get(), {root.get()}, {intermediate.get()}, |
| /*crls=*/{}, X509_V_FLAG_EXPLICIT_POLICY, |
| [&](X509_STORE_CTX *ctx) { |
| set_policies(ctx, {oid1.get()}); |
| })); |
| }); |
| } |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| } |
| } |
| #endif // OPENSSL_THREADS |
| |
| TEST(X509Test, ExtensionFromConf) { |
| static const char kTestOID[] = "1.2.840.113554.4.1.72585.2"; |
| const struct { |
| const char *name; |
| std::string value; |
| // conf is the serialized confdb, or nullptr if none is to be provided. |
| const char *conf; |
| // expected is the resulting extension, encoded in DER, or the empty string |
| // if an error is expected. |
| std::vector<uint8_t> expected; |
| } kTests[] = { |
| // Many extensions have built-in syntax. |
| {"basicConstraints", |
| "critical,CA:true", |
| nullptr, |
| {0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, |
| 0x30, 0x03, 0x01, 0x01, 0xff}}, |
| |
| {"basicConstraints", |
| "critical,CA:true,pathlen:1", |
| nullptr, |
| {0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, |
| 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01}}, |
| |
| // key:value tuples can be repeated and just override the previous value. |
| {"basicConstraints", |
| "critical,CA:true,pathlen:100,pathlen:1", |
| nullptr, |
| {0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, |
| 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01}}, |
| |
| // Extension contents may be referenced from a config section. |
| {"basicConstraints", |
| "critical,@section", |
| "[section]\nCA = true\n", |
| {0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, |
| 0x30, 0x03, 0x01, 0x01, 0xff}}, |
| |
| // If no config is provided, this should fail. |
| {"basicConstraints", "critical,@section", nullptr, {}}, |
| |
| // issuingDistributionPoint takes a list of name:value pairs. Omitting the |
| // value is not allowed. |
| {"issuingDistributionPoint", "fullname", nullptr, {}}, |
| |
| {"issuingDistributionPoint", |
| "relativename:name", |
| "[name]\nCN=Hello\n", |
| {0x30, 0x1b, 0x06, 0x03, 0x55, 0x1d, 0x1c, 0x04, 0x14, 0x30, |
| 0x12, 0xa0, 0x10, 0xa1, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, |
| 0x04, 0x03, 0x0c, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}}, |
| |
| // relativename referencing a section which doesn't exist. |
| {"issuingDistributionPoint", |
| "relativename:wrong_section_name", |
| "[name]\nCN=Hello\n", |
| {}}, |
| |
| // relativename must be a single RDN. By default, the section-based name |
| // syntax puts each attribute into its own RDN. |
| {"issuingDistributionPoint", |
| "relativename:name", |
| "[name]\nCN=Hello\nC=US\n", |
| {}}, |
| |
| // A single RDN with multiple attributes is allowed. |
| {"issuingDistributionPoint", |
| "relativename:name", |
| "[name]\nCN=Hello\n+C=US\n", |
| {0x30, 0x26, 0x06, 0x03, 0x55, 0x1d, 0x1c, 0x04, 0x1f, 0x30, |
| 0x1d, 0xa0, 0x1b, 0xa1, 0x19, 0x30, 0x09, 0x06, 0x03, 0x55, |
| 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x30, 0x0c, 0x06, 0x03, |
| 0x55, 0x04, 0x03, 0x0c, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}}, |
| |
| // Duplicate reason keys are an error. Reaching this case is interesting. |
| // The value can a string like "key:value,key:value", or it can be |
| // "@section" and reference a config section. If using a string, duplicate |
| // keys are possible, but then it is impossible to put commas in the |
| // value, as onlysomereasons expects. If using a section reference, it is |
| // impossible to have a duplicate key because the config file parser |
| // overrides the old value. |
| {"issuingDistributionPoint", |
| "onlysomereasons:keyCompromise", |
| nullptr, |
| {0x30, 0x0d, 0x06, 0x03, 0x55, 0x1d, 0x1c, 0x04, 0x06, 0x30, 0x04, 0x83, |
| 0x02, 0x06, 0x40}}, |
| {"issuingDistributionPoint", |
| "onlysomereasons:keyCompromise,onlysomereasons:CACompromise\n", |
| nullptr, |
| {}}, |
| |
| // subjectAltName has a series of string-based inputs for each name type. |
| {"subjectAltName", |
| "email:foo@example.com, URI:https://example.com, DNS:example.com, " |
| "RID:1.2.3.4, IP:127.0.0.1, IP:::1, dirName:section, " |
| "otherName:1.2.3.4;BOOLEAN:TRUE", |
| "[section]\nCN=Test\n", |
| {0x30, 0x78, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x71, 0x30, 0x6f, 0x81, |
| 0x0f, 0x66, 0x6f, 0x6f, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, |
| 0x2e, 0x63, 0x6f, 0x6d, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, |
| 0x2f, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, |
| 0x6d, 0x82, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, |
| 0x6f, 0x6d, 0x88, 0x03, 0x2a, 0x03, 0x04, 0x87, 0x04, 0x7f, 0x00, 0x00, |
| 0x01, 0x87, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xa4, 0x11, 0x30, 0x0f, 0x31, |
| 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x04, 0x54, 0x65, |
| 0x73, 0x74, 0xa0, 0x0a, 0x06, 0x03, 0x2a, 0x03, 0x04, 0xa0, 0x03, 0x01, |
| 0x01, 0xff}}, |
| |
| // Syntax errors in each case, where they exist. (The string types just |
| // copy the string in as-is.) |
| {"subjectAltName", "RID:not_an_oid", nullptr, {}}, |
| {"subjectAltName", "IP:not_an_ip", nullptr, {}}, |
| {"subjectAltName", "dirName:no_conf_db", nullptr, {}}, |
| {"subjectAltName", "dirName:missing_section", "[section]\nCN=Test\n", {}}, |
| {"subjectAltName", "otherName:missing_semicolon", nullptr, {}}, |
| {"subjectAltName", "otherName:1.2.3.4", nullptr, {}}, |
| {"subjectAltName", "otherName:invalid_oid;BOOLEAN:TRUE", nullptr, {}}, |
| {"subjectAltName", "otherName:1.2.3.4;invalid_value", nullptr, {}}, |
| |
| {"policyMappings", |
| "1.1.1.1:2.2.2.2", |
| nullptr, |
| {0x30, 0x15, 0x06, 0x03, 0x55, 0x1d, 0x21, 0x04, 0x0e, 0x30, 0x0c, 0x30, |
| 0x0a, 0x06, 0x03, 0x29, 0x01, 0x01, 0x06, 0x03, 0x52, 0x02, 0x02}}, |
| {"policyMappings", "invalid_oid:2.2.2.2", nullptr, {}}, |
| {"policyMappings", "1.1.1.1:invalid_oid", nullptr, {}}, |
| |
| // The "DER:" prefix just specifies an arbitrary byte string. Colons |
| // separators are ignored. |
| {kTestOID, "DER:0001020304", nullptr, {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, |
| 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, |
| 0x00, 0x01, 0x02, 0x03, 0x04}}, |
| {kTestOID, |
| "DER:00:01:02:03:04", |
| nullptr, |
| {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04}}, |
| {kTestOID, "DER:invalid hex", nullptr, {}}, |
| |
| // The "ASN1:" prefix implements a complex language for describing ASN.1 |
| // structures. See |
| // https://www.openssl.org/docs/man1.1.1/man3/ASN1_generate_nconf.html |
| {kTestOID, "ASN1:invalid", nullptr, {}}, |
| {kTestOID, |
| "ASN1:BOOLEAN:TRUE", |
| nullptr, |
| {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x01, 0x01, 0xff}}, |
| {kTestOID, "ASN1:BOOL:yes", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, |
| 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, |
| 0x01, 0x01, 0xff}}, |
| {kTestOID, |
| "ASN1:BOOLEAN:NO", |
| nullptr, |
| {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x01, 0x01, 0x00}}, |
| {kTestOID, |
| "ASN1:BOOLEAN", // Missing value |
| nullptr, |
| {}}, |
| {kTestOID, "ASN1:BOOLEAN:invalid", nullptr, {}}, |
| {kTestOID, "ASN1:BOOLEAN:TRUE,invalid", nullptr, {}}, |
| |
| {kTestOID, "ASN1:NULL", nullptr, {0x30, 0x12, 0x06, 0x0c, 0x2a, |
| 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, |
| 0x02, 0x04, 0x02, 0x05, 0x00}}, |
| {kTestOID, "ASN1:NULL,invalid", nullptr, {}}, |
| {kTestOID, "ASN1:NULL:invalid", nullptr, {}}, |
| |
| // Missing value. |
| {kTestOID, "ASN1:INTEGER", nullptr, {}}, |
| {kTestOID, "ASN1:INTEGER:", nullptr, {}}, |
| {kTestOID, "ASN1:INTEGER,invalid", nullptr, {}}, |
| |
| // INTEGER may be decimal or hexadecimal. |
| {kTestOID, "ASN1:INT:-0x10", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, |
| 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, |
| 0x02, 0x01, 0xf0}}, |
| {kTestOID, "ASN1:INT:-10", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, |
| 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, |
| 0x02, 0x01, 0xf6}}, |
| {kTestOID, "ASN1:INT:0", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, |
| 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, |
| 0x02, 0x01, 0x00}}, |
| {kTestOID, |
| "ASN1:INTEGER:10", |
| nullptr, |
| {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x02, 0x01, 0x0a}}, |
| {kTestOID, |
| "ASN1:INTEGER:0x10", |
| nullptr, |
| {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x02, 0x01, 0x10}}, |
| |
| {kTestOID, "ASN1:ENUM:0", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, |
| 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, |
| 0x0a, 0x01, 0x00}}, |
| {kTestOID, |
| "ASN1:ENUMERATED:0", |
| nullptr, |
| {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x0a, 0x01, 0x00}}, |
| |
| // OIDs may be spelled out or specified by name. |
| {kTestOID, "ASN1:OBJECT:invalid", nullptr, {}}, |
| {kTestOID, |
| "ASN1:OBJECT:basicConstraints", |
| nullptr, |
| {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x06, 0x03, 0x55, 0x1d, 0x13}}, |
| {kTestOID, |
| "ASN1:OBJECT:2.5.29.19", |
| nullptr, |
| {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x06, 0x03, 0x55, 0x1d, 0x13}}, |
| {kTestOID, |
| "ASN1:OID:2.5.29.19", |
| nullptr, |
| {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x06, 0x03, 0x55, 0x1d, 0x13}}, |
| |
| {kTestOID, "ASN1:UTC:invalid", nullptr, {}}, |
| {kTestOID, "ASN1:UTC:20001231235959Z", nullptr, {}}, |
| {kTestOID, "ASN1:UTCTIME:invalid", nullptr, {}}, |
| {kTestOID, |
| "ASN1:UTC:001231235959Z", |
| nullptr, |
| {0x30, 0x1f, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0f, 0x17, 0x0d, 0x30, 0x30, |
| 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a}}, |
| {kTestOID, |
| "ASN1:UTCTIME:001231235959Z", |
| nullptr, |
| {0x30, 0x1f, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0f, 0x17, 0x0d, 0x30, 0x30, |
| 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a}}, |
| |
| {kTestOID, "ASN1:GENTIME:invalid", nullptr, {}}, |
| {kTestOID, "ASN1:GENTIME:001231235959Z", nullptr, {}}, |
| {kTestOID, "ASN1:GENERALIZEDTIME:invalid", nullptr, {}}, |
| {kTestOID, |
| "ASN1:GENTIME:20001231235959Z", |
| nullptr, |
| {0x30, 0x21, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x11, 0x18, 0x0f, 0x32, 0x30, 0x30, 0x30, |
| 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a}}, |
| {kTestOID, |
| "ASN1:GENERALIZEDTIME:20001231235959Z", |
| nullptr, |
| {0x30, 0x21, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x11, 0x18, 0x0f, 0x32, 0x30, 0x30, 0x30, |
| 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a}}, |
| |
| // The default input format for string types is ASCII, which is then |
| // converted into the target string type. |
| {kTestOID, "ASN1:UTF8:hello", nullptr, {0x30, 0x17, 0x06, 0x0c, 0x2a, |
| 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, |
| 0x02, 0x04, 0x07, 0x0c, 0x05, |
| 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:UTF8String:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x0c, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:UNIV:hello", |
| nullptr, |
| {0x30, 0x26, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x16, 0x1c, 0x14, |
| 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, |
| 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6f}}, |
| {kTestOID, |
| "ASN1:UNIVERSALSTRING:hello", |
| nullptr, |
| {0x30, 0x26, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x16, 0x1c, 0x14, |
| 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, |
| 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6f}}, |
| {kTestOID, |
| "ASN1:BMP:hello", |
| nullptr, |
| {0x30, 0x1c, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0c, 0x1e, 0x0a, |
| 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f}}, |
| {kTestOID, |
| "ASN1:BMPSTRING:hello", |
| nullptr, |
| {0x30, 0x1c, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0c, 0x1e, 0x0a, |
| 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f}}, |
| {kTestOID, "ASN1:IA5:hello", nullptr, {0x30, 0x17, 0x06, 0x0c, 0x2a, |
| 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, |
| 0x02, 0x04, 0x07, 0x16, 0x05, |
| 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:IA5STRING:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x16, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:PRINTABLE:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x13, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:PRINTABLESTRING:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x13, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, "ASN1:T61:hello", nullptr, {0x30, 0x17, 0x06, 0x0c, 0x2a, |
| 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, |
| 0x02, 0x04, 0x07, 0x14, 0x05, |
| 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:T61STRING:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x14, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:TELETEXSTRING:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x14, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| |
| // FORMAT:UTF8 switches the input format to UTF-8. This should be |
| // converted to the destination string, or rejected if invalid. |
| {kTestOID, |
| "ASN1:FORMAT:UTF8,UTF8:\xe2\x98\x83", |
| nullptr, |
| {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x0c, 0x03, 0xe2, 0x98, 0x83}}, |
| {kTestOID, |
| "ASN1:FORMAT:UTF8,UNIV:\xe2\x98\x83", |
| nullptr, |
| {0x30, 0x16, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, |
| 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, |
| 0x04, 0x06, 0x1c, 0x04, 0x00, 0x00, 0x26, 0x03}}, |
| {kTestOID, |
| "ASN1:FORMAT:UTF8,BMP:\xe2\x98\x83", |
| nullptr, |
| {0x30, 0x14, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x04, 0x1e, 0x02, 0x26, 0x03}}, |
| {kTestOID, "ASN1:FORMAT:UTF8,IA5:\xe2\x98\x83", nullptr, {}}, |
| {kTestOID, |
| "ASN1:FORMAT:UTF8,IA5:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x16, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, "ASN1:FORMAT:UTF8,PRINTABLE:\xe2\x98\x83", nullptr, {}}, |
| {kTestOID, |
| "ASN1:FORMAT:UTF8,PRINTABLE:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x13, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, "ASN1:FORMAT:UTF8,T61:\xe2\x98\x83", nullptr, {}}, |
| {kTestOID, |
| "ASN1:FORMAT:UTF8,T61:\xc3\xb7", |
| nullptr, |
| {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x14, 0x01, 0xf7}}, |
| |
| // Invalid UTF-8. |
| {kTestOID, "ASN1:FORMAT:UTF8,UTF8:\xff", nullptr, {}}, |
| |
| // We don't support these string types. |
| {kTestOID, "ASN1:NUMERIC:0", nullptr, {}}, |
| {kTestOID, "ASN1:NUMERICSTRING:0", nullptr, {}}, |
| {kTestOID, "ASN1:VISIBLE:hello", nullptr, {}}, |
| {kTestOID, "ASN1:VISIBLESTRING:hello", nullptr, {}}, |
| {kTestOID, "ASN1:GeneralString:hello", nullptr, {}}, |
| |
| // OCTET STRING and BIT STRING also default to ASCII, but also accept HEX. |
| // BIT STRING interprets OCTET STRING formats by having zero unused bits. |
| {kTestOID, "ASN1:OCT:hello", nullptr, {0x30, 0x17, 0x06, 0x0c, 0x2a, |
| 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, |
| 0x02, 0x04, 0x07, 0x04, 0x05, |
| 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:OCTETSTRING:hello", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x04, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:FORMAT:HEX,OCT:0123abcd", |
| nullptr, |
| {0x30, 0x16, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, |
| 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, |
| 0x04, 0x06, 0x04, 0x04, 0x01, 0x23, 0xab, 0xcd}}, |
| {kTestOID, |
| "ASN1:BITSTR:hello", |
| nullptr, |
| {0x30, 0x18, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x08, |
| 0x03, 0x06, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:BITSTRING:hello", |
| nullptr, |
| {0x30, 0x18, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x08, |
| 0x03, 0x06, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f}}, |
| {kTestOID, |
| "ASN1:FORMAT:HEX,BITSTR:0123abcd", |
| nullptr, |
| {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07, |
| 0x03, 0x05, 0x00, 0x01, 0x23, 0xab, 0xcd}}, |
| |
| {kTestOID, "ASN1:FORMAT:HEX,OCT:invalid hex", nullptr, {}}, |
| |
| // BIT STRING additionally supports a BITLIST type, which specifies a |
| // list of bits to set. |
| {kTestOID, |
| "ASN1:FORMAT:BITLIST,BITSTR:1,5", |
| nullptr, |
| {0x30, 0x14, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x04, 0x03, 0x02, 0x02, 0x44}}, |
| |
| {kTestOID, "ASN1:FORMAT:BITLIST,BITSTR:1,invalid,5", nullptr, {}}, |
| // Negative bit inidices are not allowed. |
| {kTestOID, "ASN1:FORMAT:BITLIST,BITSTR:-1", nullptr, {}}, |
| // We cap bit indices at 256. |
| {kTestOID, "ASN1:FORMAT:BITLIST,BITSTR:257", nullptr, {}}, |
| {kTestOID, |
| "ASN1:FORMAT:BITLIST,BITSTR:256", |
| nullptr, |
| {0x30, 0x34, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x24, 0x03, 0x22, 0x07, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}}, |
| |
| // Unsupported formats for string types. |
| {kTestOID, "ASN1:FORMAT:BITLIST,IA5:abcd", nullptr, {}}, |
| {kTestOID, "ASN1:FORMAT:BITLIST,UTF8:abcd", nullptr, {}}, |
| {kTestOID, "ASN1:FORMAT:BITLIST,OCT:abcd", nullptr, {}}, |
| {kTestOID, "ASN1:FORMAT:BITLIST,UTC:abcd", nullptr, {}}, |
| {kTestOID, "ASN1:FORMAT:HEX,IA5:abcd", nullptr, {}}, |
| {kTestOID, "ASN1:FORMAT:HEX,UTF8:abcd", nullptr, {}}, |
| {kTestOID, "ASN1:FORMAT:HEX,UTC:abcd", nullptr, {}}, |
| {kTestOID, "ASN1:FORMAT:UTF8,OCT:abcd", nullptr, {}}, |
| {kTestOID, "ASN1:FORMAT:UTF8,UTC:abcd", nullptr, {}}, |
| |
| // Invalid format type. |
| {kTestOID, "ASN1:FORMAT:invalid,IA5:abcd", nullptr, {}}, |
| |
| // SEQUENCE and SET encode empty values when there is no value. |
| {kTestOID, "ASN1:SEQ", nullptr, {0x30, 0x12, 0x06, 0x0c, 0x2a, 0x86, 0x48, |
| 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, |
| 0x09, 0x02, 0x04, 0x02, 0x30, 0x00}}, |
| {kTestOID, "ASN1:SET", nullptr, {0x30, 0x12, 0x06, 0x0c, 0x2a, 0x86, 0x48, |
| 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, |
| 0x09, 0x02, 0x04, 0x02, 0x31, 0x00}}, |
| {kTestOID, "ASN1:SEQUENCE", nullptr, {0x30, 0x12, 0x06, 0x0c, 0x2a, |
| 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, |
| 0x02, 0x04, 0x02, 0x30, 0x00}}, |
| |
| // Otherwise, they require a corresponding section in the config database |
| // to encode values. This can be nested recursively. |
| {kTestOID, "ASN1:SEQ:missing_confdb", nullptr, {}}, |
| {kTestOID, "ASN1:SET:missing_confdb", nullptr, {}}, |
| {kTestOID, |
| "ASN1:SEQ:seq", |
| R"( |
| [seq] |
| val1 = NULL |
| val2 = IA5:a |
| val3 = SET:set |
| [set] |
| # Config names do not matter, only the order. |
| val4 = INT:1 |
| val3 = INT:2 |
| val2 = SEQ:empty |
| val1 = INT:3 |
| [empty] |
| )", |
| {0x30, 0x24, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x14, 0x30, 0x12, |
| 0x05, 0x00, 0x16, 0x01, 0x61, 0x31, 0x0b, 0x02, 0x01, 0x01, |
| 0x02, 0x01, 0x02, 0x02, 0x01, 0x03, 0x30, 0x00}}, |
| |
| // There is a recursion limit to stop infinite recursion. |
| {kTestOID, |
| "ASN1:SEQ:seq1", |
| R"( |
| [seq1] |
| val = SEQ:seq2 |
| [seq2] |
| val = SEQ:seq1 |
| )", |
| {}}, |
| |
| // Various modifiers wrap with explicit tagging or universal types. |
| {kTestOID, |
| "ASN1:EXP:0,EXP:16U,EXP:100A,EXP:1000C,OCTWRAP,SEQWRAP,SETWRAP,BITWRAP," |
| "NULL", |
| nullptr, |
| {0x30, 0x26, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x16, 0xa0, 0x14, |
| 0x30, 0x12, 0x7f, 0x64, 0x0f, 0xbf, 0x87, 0x68, 0x0b, 0x04, |
| 0x09, 0x30, 0x07, 0x31, 0x05, 0x03, 0x03, 0x00, 0x05, 0x00}}, |
| |
| // Invalid tag numbers. |
| {kTestOID, "ASN1:EXP:-1,NULL", nullptr, {}}, |
| {kTestOID, "ASN1:EXP:1?,NULL", nullptr, {}}, |
| // Fits in |uint32_t| but exceeds |CBS_ASN1_TAG_NUMBER_MASK|, the largest |
| // tag number we support. |
| {kTestOID, "ASN1:EXP:536870912,NULL", nullptr, {}}, |
| |
| // Implicit tagging may also be applied to the underlying type, or the |
| // wrapping modifiers. |
| {kTestOID, |
| "ASN1:IMP:1A,OCTWRAP,IMP:10,SEQWRAP,IMP:100,SETWRAP,IMP:1000,BITWRAP," |
| "IMP:10000,NULL", |
| nullptr, |
| {0x30, 0x20, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x02, 0x04, 0x10, 0x41, 0x0e, 0xaa, 0x0c, 0xbf, 0x64, |
| 0x09, 0x9f, 0x87, 0x68, 0x05, 0x00, 0x9f, 0xce, 0x10, 0x00}}, |
| |
| // Implicit tagging may not be applied to explicit tagging or itself. |
| // There's no rule against this in ASN.1, but OpenSSL does not allow it |
| // here. |
| {kTestOID, "ASN1:IMP:1,EXP:1,NULL", nullptr, {}}, |
| {kTestOID, "ASN1:IMP:1,IMP:1,NULL", nullptr, {}}, |
| |
| // [UNIVERSAL 0] is reserved. |
| {kTestOID, "ASN1:0U,NULL", nullptr, {}}, |
| |
| // Leading and trailing spaces on name:value pairs are removed. However, |
| // while these pairs are delimited by commas, a type will consumes |
| // everything after it, including commas, and spaces. So this is the |
| // string " a, b ". |
| {kTestOID, |
| "ASN1: EXP:0 , IA5: a, b ", |
| nullptr, |
| {0x30, 0x1a, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0a, 0xa0, 0x08, |
| 0x16, 0x06, 0x20, 0x61, 0x2c, 0x20, 0x62, 0x20}}, |
| |
| // Modifiers without a final type. |
| {kTestOID, "ASN1:EXP:1", nullptr, {}}, |
| |
| // Put it all together to describe a test Ed25519 key (wrapped inside an |
| // X.509 extension). |
| {kTestOID, |
| "ASN1:SEQUENCE:pkcs8", |
| R"( |
| [pkcs8] |
| vers = INT:0 |
| alg = SEQWRAP,OID:1.3.101.112 |
| key = FORMAT:HEX,OCTWRAP,OCT:9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60 |
| )", |
| {0x30, 0x40, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, |
| 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x30, 0x30, 0x2e, 0x02, 0x01, |
| 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, |
| 0x20, 0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, 0xba, 0x84, |
| 0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4, 0x44, 0x49, 0xc5, 0x69, 0x7b, |
| 0x32, 0x69, 0x19, 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60}}, |
| |
| // Sections can be referenced multiple times. |
| {kTestOID, |
| "ASN1:SEQUENCE:seq1", |
| R"( |
| [seq1] |
| val1 = SEQUENCE:seq2 |
| val2 = SEQUENCE:seq2 |
| [seq2] |
| val1 = INT:1 |
| val2 = INT:2 |
| )", |
| {0x30, 0x22, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x12, |
| 0x30, 0x10, 0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, |
| 0x02, 0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02}}, |
| |
| // But we cap this before it blows up exponentially. |
| {kTestOID, |
| "ASN1:SEQ:seq1", |
| R"( |
| [seq1] |
| val1 = SEQ:seq2 |
| val2 = SEQ:seq2 |
| [seq2] |
| val1 = SEQ:seq3 |
| val2 = SEQ:seq3 |
| [seq3] |
| val1 = SEQ:seq4 |
| val2 = SEQ:seq4 |
| [seq4] |
| val1 = SEQ:seq5 |
| val2 = SEQ:seq5 |
| [seq5] |
| val1 = SEQ:seq6 |
| val2 = SEQ:seq6 |
| [seq6] |
| val1 = SEQ:seq7 |
| val2 = SEQ:seq7 |
| [seq7] |
| val1 = SEQ:seq8 |
| val2 = SEQ:seq8 |
| [seq8] |
| val1 = SEQ:seq9 |
| val2 = SEQ:seq9 |
| [seq9] |
| val1 = SEQ:seq10 |
| val2 = SEQ:seq10 |
| [seq10] |
| val1 = SEQ:seq11 |
| val2 = SEQ:seq11 |
| [seq11] |
| val1 = SEQ:seq12 |
| val2 = SEQ:seq12 |
| [seq12] |
| val1 = IA5:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
| val2 = IA5:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB |
| )", |
| {}}, |
| |
| // Integer sizes are capped to mitigate quadratic behavior. |
| {kTestOID, "ASN1:INT:" + std::string(16384, '9'), nullptr, {}}, |
| }; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(t.name); |
| SCOPED_TRACE(t.value); |
| SCOPED_TRACE(t.conf); |
| |
| bssl::UniquePtr<CONF> conf; |
| if (t.conf != nullptr) { |
| conf.reset(NCONF_new(nullptr)); |
| ASSERT_TRUE(conf); |
| bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t.conf, strlen(t.conf))); |
| ASSERT_TRUE(bio); |
| long error_line; |
| ASSERT_TRUE(NCONF_load_bio(conf.get(), bio.get(), &error_line)) |
| << "Failed to load config at line " << error_line; |
| } |
| |
| bssl::UniquePtr<X509_EXTENSION> ext( |
| X509V3_EXT_nconf(conf.get(), nullptr, t.name, t.value.c_str())); |
| if (t.expected.empty()) { |
| EXPECT_FALSE(ext); |
| } else { |
| ASSERT_TRUE(ext); |
| uint8_t *der = nullptr; |
| int len = i2d_X509_EXTENSION(ext.get(), &der); |
| ASSERT_GE(len, 0); |
| bssl::UniquePtr<uint8_t> free_der(der); |
| EXPECT_EQ(Bytes(t.expected), Bytes(der, len)); |
| } |
| |
| // Repeat the test with an explicit |X509V3_CTX|. |
| X509V3_CTX ctx; |
| X509V3_set_ctx(&ctx, nullptr, nullptr, nullptr, nullptr, 0); |
| X509V3_set_nconf(&ctx, conf.get()); |
| ext.reset(X509V3_EXT_nconf(conf.get(), &ctx, t.name, t.value.c_str())); |
| if (t.expected.empty()) { |
| EXPECT_FALSE(ext); |
| } else { |
| ASSERT_TRUE(ext); |
| uint8_t *der = nullptr; |
| int len = i2d_X509_EXTENSION(ext.get(), &der); |
| ASSERT_GE(len, 0); |
| bssl::UniquePtr<uint8_t> free_der(der); |
| EXPECT_EQ(Bytes(t.expected), Bytes(der, len)); |
| } |
| } |
| } |
| |
| TEST(X509Test, AddUnserializableExtension) { |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| bssl::UniquePtr<X509> x509 = |
| MakeTestCert("Issuer", "Subject", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(x509); |
| bssl::UniquePtr<X509_EXTENSION> ext(X509_EXTENSION_new()); |
| ASSERT_TRUE(X509_EXTENSION_set_object(ext.get(), OBJ_get_undef())); |
| EXPECT_FALSE(X509_add_ext(x509.get(), ext.get(), /*loc=*/-1)); |
| } |
| |
| // Test that, when constructing an |X509_NAME|, names are sorted by DER order. |
| TEST(X509Test, SortRDN) { |
| bssl::UniquePtr<X509_NAME> name(X509_NAME_new()); |
| ASSERT_TRUE(name); |
| |
| auto append_entry_new_rdn = [&](const char *str) { |
| return X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_ASC, |
| reinterpret_cast<const uint8_t *>(str), |
| strlen(str), /*loc=*/-1, /*set=*/0); |
| }; |
| auto append_entry_prev_rdn = [&](const char *str) { |
| return X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_ASC, |
| reinterpret_cast<const uint8_t *>(str), |
| strlen(str), /*loc=*/-1, /*set=*/-1); |
| }; |
| |
| // This is the sort order to expect. |
| ASSERT_TRUE(append_entry_new_rdn("A")); |
| ASSERT_TRUE(append_entry_prev_rdn("B")); |
| ASSERT_TRUE(append_entry_prev_rdn("AA")); |
| ASSERT_TRUE(append_entry_prev_rdn("AB")); |
| |
| // The same RDN, with entries added in a different order. |
| ASSERT_TRUE(append_entry_new_rdn("AB")); |
| ASSERT_TRUE(append_entry_prev_rdn("AA")); |
| ASSERT_TRUE(append_entry_prev_rdn("B")); |
| ASSERT_TRUE(append_entry_prev_rdn("A")); |
| |
| // The same RDN, with entries added in a different order. |
| ASSERT_TRUE(append_entry_new_rdn("A")); |
| ASSERT_TRUE(append_entry_prev_rdn("AA")); |
| ASSERT_TRUE(append_entry_prev_rdn("B")); |
| ASSERT_TRUE(append_entry_prev_rdn("AB")); |
| |
| uint8_t *der = nullptr; |
| int der_len = i2d_X509_NAME(name.get(), &der); |
| ASSERT_GT(der_len, 0); |
| bssl::UniquePtr<uint8_t> free_der(der); |
| |
| // SEQUENCE { |
| // SET { |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // UTF8String { "A" } |
| // } |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // UTF8String { "B" } |
| // } |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // UTF8String { "AA" } |
| // } |
| // SEQUENCE { |
| // # commonName |
| // OBJECT_IDENTIFIER { 2.5.4.3 } |
| // UTF8String { "AB" } |
| // } |
| // } |
| // ...two more copies of the above SET... |
| // } |
| static uint8_t kExpected[] = { |
| 0x30, 0x81, 0x84, 0x31, 0x2a, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04, 0x03, |
| 0x0c, 0x01, 0x41, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x01, |
| 0x42, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x02, 0x41, 0x41, |
| 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x02, 0x41, 0x42, 0x31, |
| 0x2a, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x01, 0x41, 0x30, |
| 0x08, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x01, 0x42, 0x30, 0x09, 0x06, |
| 0x03, 0x55, 0x04, 0x03, 0x0c, 0x02, 0x41, 0x41, 0x30, 0x09, 0x06, 0x03, |
| 0x55, 0x04, 0x03, 0x0c, 0x02, 0x41, 0x42, 0x31, 0x2a, 0x30, 0x08, 0x06, |
| 0x03, 0x55, 0x04, 0x03, 0x0c, 0x01, 0x41, 0x30, 0x08, 0x06, 0x03, 0x55, |
| 0x04, 0x03, 0x0c, 0x01, 0x42, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x03, |
| 0x0c, 0x02, 0x41, 0x41, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, |
| 0x02, 0x41, 0x42}; |
| EXPECT_EQ(Bytes(kExpected), Bytes(der, der_len)); |
| } |
| |
| TEST(X509Test, NameAttributeValues) { |
| // 1.2.840.113554.4.1.72585.0. We use an unrecognized OID because using an |
| // arbitrary ASN.1 type as the value for commonName is invalid. Our parser |
| // does not check this, but best to avoid unrelated errors in tests, in case |
| // we decide to later. |
| static const uint8_t kOID[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, |
| 0x04, 0x01, 0x84, 0xb7, 0x09, 0x00}; |
| static const char kOIDText[] = "1.2.840.113554.4.1.72585.0"; |
| |
| auto encode_single_attribute_name = |
| [](CBS_ASN1_TAG tag, |
| const std::string &contents) -> std::vector<uint8_t> { |
| bssl::ScopedCBB cbb; |
| CBB seq, rdn, attr, attr_type, attr_value; |
| if (!CBB_init(cbb.get(), 128) || |
| !CBB_add_asn1(cbb.get(), &seq, CBS_ASN1_SEQUENCE) || |
| !CBB_add_asn1(&seq, &rdn, CBS_ASN1_SET) || |
| !CBB_add_asn1(&rdn, &attr, CBS_ASN1_SEQUENCE) || |
| !CBB_add_asn1(&attr, &attr_type, CBS_ASN1_OBJECT) || |
| !CBB_add_bytes(&attr_type, kOID, sizeof(kOID)) || |
| !CBB_add_asn1(&attr, &attr_value, tag) || |
| !CBB_add_bytes(&attr_value, |
| reinterpret_cast<const uint8_t *>(contents.data()), |
| contents.size()) || |
| !CBB_flush(cbb.get())) { |
| ADD_FAILURE() << "Could not encode name"; |
| return {}; |
| }; |
| return std::vector<uint8_t>(CBB_data(cbb.get()), |
| CBB_data(cbb.get()) + CBB_len(cbb.get())); |
| }; |
| |
| const struct { |
| CBS_ASN1_TAG der_tag; |
| std::string der_contents; |
| int str_type; |
| std::string str_contents; |
| } kTests[] = { |
| // String types are parsed as string types. |
| {CBS_ASN1_BITSTRING, std::string("\0", 1), V_ASN1_BIT_STRING, ""}, |
| {CBS_ASN1_UTF8STRING, "abc", V_ASN1_UTF8STRING, "abc"}, |
| {CBS_ASN1_NUMERICSTRING, "123", V_ASN1_NUMERICSTRING, "123"}, |
| {CBS_ASN1_PRINTABLESTRING, "abc", V_ASN1_PRINTABLESTRING, "abc"}, |
| {CBS_ASN1_T61STRING, "abc", V_ASN1_T61STRING, "abc"}, |
| {CBS_ASN1_IA5STRING, "abc", V_ASN1_IA5STRING, "abc"}, |
| {CBS_ASN1_UNIVERSALSTRING, std::string("\0\0\0a", 4), |
| V_ASN1_UNIVERSALSTRING, std::string("\0\0\0a", 4)}, |
| {CBS_ASN1_BMPSTRING, std::string("\0a", 2), V_ASN1_BMPSTRING, |
| std::string("\0a", 2)}, |
| |
| // ENUMERATED is supported but, currently, INTEGER is not. |
| {CBS_ASN1_ENUMERATED, "\x01", V_ASN1_ENUMERATED, "\x01"}, |
| |
| // Test negative values. These are interesting because, when encoding, the |
| // ASN.1 type must be determined from the string type, but the string type |
| // has an extra |V_ASN1_NEG| bit. |
| {CBS_ASN1_ENUMERATED, "\xff", V_ASN1_NEG_ENUMERATED, "\x01"}, |
| |
| // SEQUENCE is supported but, currently, SET is not. Note the |
| // |ASN1_STRING| representation will include the tag and length. |
| {CBS_ASN1_SEQUENCE, "", V_ASN1_SEQUENCE, std::string("\x30\x00", 2)}, |
| |
| // These types are not actually supported by the library but, |
| // historically, we would parse them, and not other unsupported types, due |
| // to quirks of |ASN1_tag2bit|. |
| {7, "", V_ASN1_OBJECT_DESCRIPTOR, ""}, |
| {8, "", V_ASN1_EXTERNAL, ""}, |
| {9, "", V_ASN1_REAL, ""}, |
| {11, "", 11 /* EMBEDDED PDV */, ""}, |
| {13, "", 13 /* RELATIVE-OID */, ""}, |
| {14, "", 14 /* TIME */, ""}, |
| {15, "", 15 /* not a type; reserved value */, ""}, |
| {29, "", 29 /* CHARACTER STRING */, ""}, |
| |
| // TODO(crbug.com/boringssl/412): Attribute values are an ANY DEFINED BY |
| // type, so we actually shoudl be accepting all ASN.1 types. We currently |
| // do not and only accept the above types. Extend this test when we fix |
| // this. |
| }; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(t.der_tag); |
| SCOPED_TRACE(Bytes(t.der_contents)); |
| |
| // Construct an X.509 name containing a single RDN with a single attribute: |
| // kOID with the specified value. |
| auto encoded = encode_single_attribute_name(t.der_tag, t.der_contents); |
| ASSERT_FALSE(encoded.empty()); |
| SCOPED_TRACE(Bytes(encoded)); |
| |
| // The input should parse. |
| const uint8_t *inp = encoded.data(); |
| bssl::UniquePtr<X509_NAME> name( |
| d2i_X509_NAME(nullptr, &inp, encoded.size())); |
| ASSERT_TRUE(name); |
| EXPECT_EQ(inp, encoded.data() + encoded.size()) |
| << "input was not fully consumed"; |
| |
| // Check there is a single attribute with the expected in-memory |
| // representation. |
| ASSERT_EQ(1, X509_NAME_entry_count(name.get())); |
| const X509_NAME_ENTRY *entry = X509_NAME_get_entry(name.get(), 0); |
| const ASN1_OBJECT *obj = X509_NAME_ENTRY_get_object(entry); |
| EXPECT_EQ(Bytes(OBJ_get0_data(obj), OBJ_length(obj)), Bytes(kOID)); |
| const ASN1_STRING *value = X509_NAME_ENTRY_get_data(entry); |
| EXPECT_EQ(ASN1_STRING_type(value), t.str_type); |
| EXPECT_EQ(Bytes(ASN1_STRING_get0_data(value), ASN1_STRING_length(value)), |
| Bytes(t.str_contents)); |
| |
| // The name should re-encode with the same input. |
| uint8_t *der = nullptr; |
| int der_len = i2d_X509_NAME(name.get(), &der); |
| ASSERT_GE(der_len, 0); |
| bssl::UniquePtr<uint8_t> free_der(der); |
| EXPECT_EQ(Bytes(der, der_len), Bytes(encoded)); |
| |
| // X509_NAME internally caches its encoding, which means the check above |
| // does not fully test re-encoding. Repeat the test by constructing an |
| // |X509_NAME| from the string representation. |
| name.reset(X509_NAME_new()); |
| ASSERT_TRUE(name); |
| ASSERT_TRUE(X509_NAME_add_entry_by_txt( |
| name.get(), kOIDText, t.str_type, |
| reinterpret_cast<const uint8_t *>(t.str_contents.data()), |
| t.str_contents.size(), /*loc=*/-1, /*set=*/0)); |
| |
| // The name should re-encode with the same input. |
| der = nullptr; |
| der_len = i2d_X509_NAME(name.get(), &der); |
| ASSERT_GE(der_len, 0); |
| free_der.reset(der); |
| EXPECT_EQ(Bytes(der, der_len), Bytes(encoded)); |
| } |
| |
| const struct { |
| CBS_ASN1_TAG der_tag; |
| std::string der_contents; |
| } kInvalidTests[] = { |
| // Errors in supported universal types should be handled. |
| {CBS_ASN1_NULL, "not null"}, |
| {CBS_ASN1_BOOLEAN, "not bool"}, |
| {CBS_ASN1_OBJECT, ""}, |
| {CBS_ASN1_INTEGER, std::string("\0\0", 2)}, |
| {CBS_ASN1_ENUMERATED, std::string("\0\0", 2)}, |
| {CBS_ASN1_BITSTRING, ""}, |
| {CBS_ASN1_UTF8STRING, "not utf-8 \xff"}, |
| {CBS_ASN1_BMPSTRING, "not utf-16 "}, |
| {CBS_ASN1_UNIVERSALSTRING, "not utf-32"}, |
| {CBS_ASN1_UTCTIME, "not utctime"}, |
| {CBS_ASN1_GENERALIZEDTIME, "not generalizedtime"}, |
| {CBS_ASN1_UTF8STRING | CBS_ASN1_CONSTRUCTED, ""}, |
| {CBS_ASN1_SEQUENCE & ~CBS_ASN1_CONSTRUCTED, ""}, |
| |
| // TODO(crbug.com/boringssl/412): The following inputs should parse, but |
| // are currently rejected because they cannot be represented in |
| // |ASN1_PRINTABLE|, either because they don't fit in |ASN1_STRING| or |
| // simply in the |B_ASN1_PRINTABLE| bitmask. |
| {CBS_ASN1_NULL, ""}, |
| {CBS_ASN1_BOOLEAN, std::string("\x00", 1)}, |
| {CBS_ASN1_BOOLEAN, "\xff"}, |
| {CBS_ASN1_OBJECT, "\x01\x02\x03\x04"}, |
| {CBS_ASN1_INTEGER, "\x01"}, |
| {CBS_ASN1_INTEGER, "\xff"}, |
| {CBS_ASN1_OCTETSTRING, ""}, |
| {CBS_ASN1_UTCTIME, "700101000000Z"}, |
| {CBS_ASN1_GENERALIZEDTIME, "19700101000000Z"}, |
| {CBS_ASN1_SET, ""}, |
| {CBS_ASN1_APPLICATION | CBS_ASN1_CONSTRUCTED | 42, ""}, |
| {CBS_ASN1_APPLICATION | 42, ""}, |
| }; |
| for (const auto &t : kInvalidTests) { |
| SCOPED_TRACE(t.der_tag); |
| SCOPED_TRACE(Bytes(t.der_contents)); |
| |
| // Construct an X.509 name containing a single RDN with a single attribute: |
| // kOID with the specified value. |
| auto encoded = encode_single_attribute_name(t.der_tag, t.der_contents); |
| ASSERT_FALSE(encoded.empty()); |
| SCOPED_TRACE(Bytes(encoded)); |
| |
| // The input should not parse. |
| const uint8_t *inp = encoded.data(); |
| bssl::UniquePtr<X509_NAME> name( |
| d2i_X509_NAME(nullptr, &inp, encoded.size())); |
| EXPECT_FALSE(name); |
| } |
| } |
| |
| TEST(X509Test, GetTextByOBJ) { |
| struct OBJTestCase { |
| const char *content; |
| int content_type; |
| int len; |
| int expected_result; |
| const char *expected_string; |
| } kTests[] = { |
| {"", V_ASN1_UTF8STRING, 0, 0, ""}, |
| {"derp", V_ASN1_UTF8STRING, 4, 4, "derp"}, |
| {"\x30\x00", // Empty sequence can not be converted to UTF-8 |
| V_ASN1_SEQUENCE, 2, -1, ""}, |
| { |
| "der\0p", |
| V_ASN1_TELETEXSTRING, |
| 5, |
| -1, |
| "", |
| }, |
| { |
| "0123456789ABCDEF", |
| V_ASN1_IA5STRING, |
| 16, |
| 16, |
| "0123456789ABCDEF", |
| }, |
| { |
| "\x07\xff", |
| V_ASN1_BMPSTRING, |
| 2, |
| 2, |
| "\xdf\xbf", |
| }, |
| { |
| "\x00\xc3\x00\xaf", |
| V_ASN1_BMPSTRING, |
| 4, |
| 4, |
| "\xc3\x83\xc2\xaf", |
| }, |
| }; |
| for (const auto &test : kTests) { |
| bssl::UniquePtr<X509_NAME> name(X509_NAME_new()); |
| ASSERT_TRUE(name); |
| ASSERT_TRUE(X509_NAME_add_entry_by_NID( |
| name.get(), NID_commonName, test.content_type, |
| reinterpret_cast<const uint8_t *>(test.content), test.len, /*loc=*/-1, |
| /*set=*/0)); |
| char text[256] = {}; |
| EXPECT_EQ(test.expected_result, |
| X509_NAME_get_text_by_NID(name.get(), NID_commonName, text, |
| sizeof(text))); |
| EXPECT_STREQ(text, test.expected_string); |
| if (test.expected_result > 0) { |
| // Test truncation. The function writes a trailing NUL byte so the |
| // buffer needs to be one bigger than the expected result. |
| char small[2] = "a"; |
| EXPECT_EQ( |
| -1, X509_NAME_get_text_by_NID(name.get(), NID_commonName, small, 1)); |
| // The buffer should be unmodified by truncation failure. |
| EXPECT_STREQ(small, "a"); |
| } |
| } |
| } |
| |
| TEST(X509Test, ParamInheritance) { |
| // |X509_VERIFY_PARAM_inherit| with both unset. |
| { |
| bssl::UniquePtr<X509_VERIFY_PARAM> dest(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(dest); |
| bssl::UniquePtr<X509_VERIFY_PARAM> src(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(src); |
| ASSERT_TRUE(X509_VERIFY_PARAM_inherit(dest.get(), src.get())); |
| EXPECT_EQ(X509_VERIFY_PARAM_get_depth(dest.get()), -1); |
| } |
| |
| // |X509_VERIFY_PARAM_inherit| with source set. |
| { |
| bssl::UniquePtr<X509_VERIFY_PARAM> dest(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(dest); |
| bssl::UniquePtr<X509_VERIFY_PARAM> src(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(src); |
| X509_VERIFY_PARAM_set_depth(src.get(), 5); |
| ASSERT_TRUE(X509_VERIFY_PARAM_inherit(dest.get(), src.get())); |
| EXPECT_EQ(X509_VERIFY_PARAM_get_depth(dest.get()), 5); |
| } |
| |
| // |X509_VERIFY_PARAM_inherit| with destination set. |
| { |
| bssl::UniquePtr<X509_VERIFY_PARAM> dest(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(dest); |
| bssl::UniquePtr<X509_VERIFY_PARAM> src(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(src); |
| X509_VERIFY_PARAM_set_depth(dest.get(), 5); |
| ASSERT_TRUE(X509_VERIFY_PARAM_inherit(dest.get(), src.get())); |
| EXPECT_EQ(X509_VERIFY_PARAM_get_depth(dest.get()), 5); |
| } |
| |
| // |X509_VERIFY_PARAM_inherit| with both set. |
| { |
| bssl::UniquePtr<X509_VERIFY_PARAM> dest(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(dest); |
| bssl::UniquePtr<X509_VERIFY_PARAM> src(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(src); |
| X509_VERIFY_PARAM_set_depth(dest.get(), 5); |
| X509_VERIFY_PARAM_set_depth(src.get(), 10); |
| ASSERT_TRUE(X509_VERIFY_PARAM_inherit(dest.get(), src.get())); |
| // The existing value is used. |
| EXPECT_EQ(X509_VERIFY_PARAM_get_depth(dest.get()), 5); |
| } |
| |
| // |X509_VERIFY_PARAM_set1| with both unset. |
| { |
| bssl::UniquePtr<X509_VERIFY_PARAM> dest(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(dest); |
| bssl::UniquePtr<X509_VERIFY_PARAM> src(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(src); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1(dest.get(), src.get())); |
| EXPECT_EQ(X509_VERIFY_PARAM_get_depth(dest.get()), -1); |
| } |
| |
| // |X509_VERIFY_PARAM_set1| with source set. |
| { |
| bssl::UniquePtr<X509_VERIFY_PARAM> dest(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(dest); |
| bssl::UniquePtr<X509_VERIFY_PARAM> src(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(src); |
| X509_VERIFY_PARAM_set_depth(src.get(), 5); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1(dest.get(), src.get())); |
| EXPECT_EQ(X509_VERIFY_PARAM_get_depth(dest.get()), 5); |
| } |
| |
| // |X509_VERIFY_PARAM_set1| with destination set. |
| { |
| bssl::UniquePtr<X509_VERIFY_PARAM> dest(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(dest); |
| bssl::UniquePtr<X509_VERIFY_PARAM> src(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(src); |
| X509_VERIFY_PARAM_set_depth(dest.get(), 5); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1(dest.get(), src.get())); |
| EXPECT_EQ(X509_VERIFY_PARAM_get_depth(dest.get()), 5); |
| } |
| |
| // |X509_VERIFY_PARAM_set1| with both set. |
| { |
| bssl::UniquePtr<X509_VERIFY_PARAM> dest(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(dest); |
| bssl::UniquePtr<X509_VERIFY_PARAM> src(X509_VERIFY_PARAM_new()); |
| ASSERT_TRUE(src); |
| X509_VERIFY_PARAM_set_depth(dest.get(), 5); |
| X509_VERIFY_PARAM_set_depth(src.get(), 10); |
| ASSERT_TRUE(X509_VERIFY_PARAM_set1(dest.get(), src.get())); |
| // The new value is used. |
| EXPECT_EQ(X509_VERIFY_PARAM_get_depth(dest.get()), 10); |
| } |
| } |
| |
| TEST(X509Test, PublicKeyCache) { |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| X509_PUBKEY *pub = nullptr; |
| ASSERT_TRUE(X509_PUBKEY_set(&pub, key.get())); |
| bssl::UniquePtr<X509_PUBKEY> free_pub(pub); |
| |
| bssl::UniquePtr<EVP_PKEY> key2(X509_PUBKEY_get(pub)); |
| ASSERT_TRUE(key2); |
| EXPECT_EQ(1, EVP_PKEY_cmp(key.get(), key2.get())); |
| |
| // Replace |pub| with different (garbage) values. |
| ASSERT_TRUE(X509_PUBKEY_set0_param(pub, OBJ_nid2obj(NID_subject_alt_name), |
| V_ASN1_NULL, nullptr, nullptr, 0)); |
| |
| // The cached key should no longer be returned. |
| key2.reset(X509_PUBKEY_get(pub)); |
| EXPECT_FALSE(key2); |
| } |
| |
| // Tests some unusual behavior in |X509_STORE_CTX_set_purpose| and |
| // |X509_STORE_CTX_set_trust|. |
| TEST(X509Test, ContextTrustAndPurpose) { |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| ASSERT_TRUE(store); |
| bssl::UniquePtr<X509> leaf(CertFromPEM(kLeafPEM)); |
| ASSERT_TRUE(leaf); |
| |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), leaf.get(), nullptr)); |
| |
| // Initially, neither parameter is set. |
| EXPECT_EQ(ctx->param->purpose, 0); |
| EXPECT_EQ(ctx->param->trust, 0); |
| |
| // Invalid purpose and trust types fail. |
| EXPECT_FALSE(X509_STORE_CTX_set_purpose(ctx.get(), 999)); |
| EXPECT_FALSE(X509_STORE_CTX_set_trust(ctx.get(), 999)); |
| |
| // It is not possible to set |X509_PURPOSE_ANY| with this API, because there |
| // is no corresponding trust. |
| EXPECT_FALSE(X509_STORE_CTX_set_purpose(ctx.get(), X509_PURPOSE_ANY)); |
| |
| // Setting a purpose also sets the corresponding trust. |
| ASSERT_TRUE(X509_STORE_CTX_set_purpose(ctx.get(), X509_PURPOSE_SSL_SERVER)); |
| EXPECT_EQ(ctx->param->purpose, X509_PURPOSE_SSL_SERVER); |
| EXPECT_EQ(ctx->param->trust, X509_TRUST_SSL_SERVER); |
| |
| // Once set, the functions silently do nothing. |
| ASSERT_TRUE(X509_STORE_CTX_set_purpose(ctx.get(), X509_PURPOSE_SSL_CLIENT)); |
| ASSERT_TRUE(X509_STORE_CTX_set_trust(ctx.get(), X509_TRUST_SSL_CLIENT)); |
| EXPECT_EQ(ctx->param->purpose, X509_PURPOSE_SSL_SERVER); |
| EXPECT_EQ(ctx->param->trust, X509_TRUST_SSL_SERVER); |
| |
| // Start over. |
| ctx.reset(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), leaf.get(), nullptr)); |
| EXPECT_EQ(ctx->param->purpose, 0); |
| EXPECT_EQ(ctx->param->trust, 0); |
| |
| // Setting trust leaves purpose unset. |
| ASSERT_TRUE(X509_STORE_CTX_set_trust(ctx.get(), X509_TRUST_SSL_SERVER)); |
| EXPECT_EQ(ctx->param->purpose, 0); |
| EXPECT_EQ(ctx->param->trust, X509_TRUST_SSL_SERVER); |
| |
| // If trust is set, but not purpose, |X509_STORE_CTX_set_purpose| only sets |
| // purpose. |
| ASSERT_TRUE(X509_STORE_CTX_set_purpose(ctx.get(), X509_PURPOSE_SSL_CLIENT)); |
| EXPECT_EQ(ctx->param->purpose, X509_PURPOSE_SSL_CLIENT); |
| EXPECT_EQ(ctx->param->trust, X509_TRUST_SSL_SERVER); |
| |
| // Start over. |
| ctx.reset(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), leaf.get(), nullptr)); |
| EXPECT_EQ(ctx->param->purpose, 0); |
| EXPECT_EQ(ctx->param->trust, 0); |
| |
| // If purpose is set, but not trust, |X509_STORE_CTX_set_purpose| only sets |
| // trust. |
| ASSERT_TRUE(X509_VERIFY_PARAM_set_purpose( |
| X509_STORE_CTX_get0_param(ctx.get()), X509_PURPOSE_SSL_CLIENT)); |
| EXPECT_EQ(ctx->param->purpose, X509_PURPOSE_SSL_CLIENT); |
| EXPECT_EQ(ctx->param->trust, 0); |
| |
| ASSERT_TRUE(X509_STORE_CTX_set_purpose(ctx.get(), X509_PURPOSE_SSL_SERVER)); |
| EXPECT_EQ(ctx->param->purpose, X509_PURPOSE_SSL_CLIENT); |
| EXPECT_EQ(ctx->param->trust, X509_TRUST_SSL_SERVER); |
| } |
| |
| TEST(X509Test, Purpose) { |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| struct { |
| int purpose; |
| int eku_nid; |
| std::vector<KeyUsage> key_usages; |
| } kTests[] = { |
| {X509_PURPOSE_SSL_CLIENT, |
| NID_client_auth, |
| {KeyUsage::kDigitalSignature, KeyUsage::kKeyAgreement}}, |
| {X509_PURPOSE_SSL_SERVER, |
| NID_server_auth, |
| {KeyUsage::kDigitalSignature, KeyUsage::kKeyAgreement, |
| KeyUsage::kKeyEncipherment}}, |
| {X509_PURPOSE_NS_SSL_SERVER, |
| NID_server_auth, |
| {KeyUsage::kKeyEncipherment}}, |
| {X509_PURPOSE_SMIME_SIGN, |
| NID_email_protect, |
| {KeyUsage::kDigitalSignature, KeyUsage::kNonRepudiation}}, |
| {X509_PURPOSE_SMIME_ENCRYPT, |
| NID_email_protect, |
| {KeyUsage::kKeyEncipherment}}, |
| {X509_PURPOSE_CRL_SIGN, NID_undef, {KeyUsage::kCRLSign}}, |
| }; |
| for (const auto &t : kTests) { |
| SCOPED_TRACE(t.purpose); |
| |
| auto configure_callback = [&](X509_STORE_CTX *ctx) { |
| X509_STORE_CTX_set_purpose(ctx, t.purpose); |
| }; |
| |
| // An unconstrained cert chain is valid. |
| bssl::UniquePtr<X509> root = |
| MakeTestCert("Root", "Root", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(X509_sign(root.get(), key.get(), EVP_sha256())); |
| |
| bssl::UniquePtr<X509> intermediate = |
| MakeTestCert("Root", "Intermediate", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(X509_sign(intermediate.get(), key.get(), EVP_sha256())); |
| |
| bssl::UniquePtr<X509> leaf = |
| MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {intermediate.get()}, |
| {}, 0, configure_callback)); |
| |
| // A leaf and intermediate with compatible constraints is valid. |
| intermediate = |
| MakeTestCert("Root", "Intermediate", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(AddKeyUsage(intermediate.get(), {KeyUsage::kKeyCertSign})); |
| if (t.eku_nid != NID_undef) { |
| ASSERT_TRUE(AddExtendedKeyUsage(intermediate.get(), {t.eku_nid})); |
| } |
| ASSERT_TRUE(X509_sign(intermediate.get(), key.get(), EVP_sha256())); |
| |
| leaf = MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| if (t.eku_nid != NID_undef) { |
| ASSERT_TRUE(AddExtendedKeyUsage(leaf.get(), {t.eku_nid})); |
| } |
| ASSERT_TRUE(AddKeyUsage(leaf.get(), t.key_usages)); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {intermediate.get()}, |
| {}, 0, configure_callback)); |
| |
| // Each key usage asserted individually is valid. |
| for (KeyUsage usage : t.key_usages) { |
| SCOPED_TRACE(static_cast<int>(usage)); |
| leaf = MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| if (t.eku_nid != NID_undef) { |
| ASSERT_TRUE(AddExtendedKeyUsage(leaf.get(), {t.eku_nid})); |
| } |
| ASSERT_TRUE(AddKeyUsage(leaf.get(), {usage})); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| EXPECT_EQ(X509_V_OK, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0, |
| configure_callback)); |
| } |
| |
| // A leaf with the wrong EKU is invalid. |
| if (t.eku_nid != NID_undef) { |
| leaf = MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(AddExtendedKeyUsage(leaf.get(), {NID_rsaEncryption})); |
| ASSERT_TRUE(AddKeyUsage(leaf.get(), t.key_usages)); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| EXPECT_EQ(X509_V_ERR_INVALID_PURPOSE, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0, |
| configure_callback)); |
| } |
| |
| // A leaf without any of the requested key usages is invalid. |
| std::vector<KeyUsage> usages; |
| for (int i = 0; i < 10; i++) { |
| auto k = static_cast<KeyUsage>(i); |
| if (std::find(t.key_usages.begin(), t.key_usages.end(), k) == |
| t.key_usages.end()) { |
| usages.push_back(k); |
| } |
| } |
| leaf = MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| if (t.eku_nid != NID_undef) { |
| ASSERT_TRUE(AddExtendedKeyUsage(leaf.get(), {t.eku_nid})); |
| } |
| ASSERT_TRUE(AddKeyUsage(leaf.get(), usages)); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| EXPECT_EQ(X509_V_ERR_INVALID_PURPOSE, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0, |
| configure_callback)); |
| |
| // Extra EKUs and key usages are fine. |
| usages.clear(); |
| for (int i = 0; i < 10; i++) { |
| usages.push_back(static_cast<KeyUsage>(i)); |
| } |
| leaf = MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| if (t.eku_nid != NID_undef) { |
| ASSERT_TRUE( |
| AddExtendedKeyUsage(leaf.get(), {t.eku_nid, NID_rsaEncryption})); |
| } |
| ASSERT_TRUE(AddKeyUsage(leaf.get(), usages)); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {intermediate.get()}, |
| {}, 0, configure_callback)); |
| |
| // anyExtendedKeyUsage is not allowed in place of a concrete EKU. |
| if (t.eku_nid != NID_undef) { |
| leaf = MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(AddExtendedKeyUsage(leaf.get(), {NID_anyExtendedKeyUsage})); |
| ASSERT_TRUE(AddKeyUsage(leaf.get(), t.key_usages)); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| EXPECT_EQ(X509_V_ERR_INVALID_PURPOSE, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0, |
| configure_callback)); |
| } |
| |
| // Restore |leaf| to a valid option. |
| leaf = MakeTestCert("Intermediate", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| |
| // The intermediate must have the keyCertSign bit. This bit is checked in |
| // multiple places. The first place that fails is in looking for candidate |
| // issuers. |
| intermediate = |
| MakeTestCert("Root", "Intermediate", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(AddKeyUsage(intermediate.get(), {KeyUsage::kDigitalSignature})); |
| if (t.eku_nid != NID_undef) { |
| ASSERT_TRUE(AddExtendedKeyUsage(intermediate.get(), {t.eku_nid})); |
| } |
| ASSERT_TRUE(X509_sign(intermediate.get(), key.get(), EVP_sha256())); |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0, |
| configure_callback)); |
| |
| // The intermediate must have the EKU asserted. |
| if (t.eku_nid != NID_undef) { |
| intermediate = |
| MakeTestCert("Root", "Intermediate", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(intermediate); |
| ASSERT_TRUE(AddKeyUsage(intermediate.get(), {KeyUsage::kKeyCertSign})); |
| ASSERT_TRUE(AddExtendedKeyUsage(intermediate.get(), {NID_rsaEncryption})); |
| ASSERT_TRUE(X509_sign(intermediate.get(), key.get(), EVP_sha256())); |
| EXPECT_EQ(X509_V_ERR_INVALID_PURPOSE, |
| Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0, |
| configure_callback)); |
| } |
| } |
| } |
| |
| TEST(X509Test, Trust) { |
| struct Certs { |
| bssl::UniquePtr<X509> normal; |
| bssl::UniquePtr<X509> trusted_server, distrusted_server; |
| bssl::UniquePtr<X509> trusted_any, distrusted_any; |
| }; |
| auto certs_from_pem = [](const char *pem) -> Certs { |
| Certs certs; |
| certs.normal = CertFromPEM(pem); |
| certs.trusted_server = CertFromPEM(pem); |
| certs.distrusted_server = CertFromPEM(pem); |
| certs.trusted_any = CertFromPEM(pem); |
| certs.distrusted_any = CertFromPEM(pem); |
| if (certs.normal == nullptr || certs.trusted_server == nullptr || |
| certs.distrusted_server == nullptr || certs.trusted_any == nullptr || |
| certs.distrusted_any == nullptr || |
| !X509_add1_trust_object(certs.trusted_server.get(), |
| OBJ_nid2obj(NID_server_auth)) || |
| !X509_add1_reject_object(certs.distrusted_server.get(), |
| OBJ_nid2obj(NID_server_auth)) || |
| !X509_add1_trust_object(certs.trusted_any.get(), |
| OBJ_nid2obj(NID_anyExtendedKeyUsage)) || |
| !X509_add1_reject_object(certs.distrusted_any.get(), |
| OBJ_nid2obj(NID_anyExtendedKeyUsage))) { |
| return Certs{}; |
| } |
| return certs; |
| }; |
| |
| Certs root = certs_from_pem(kRootCAPEM); |
| Certs intermediate = certs_from_pem(kIntermediatePEM); |
| Certs leaf = certs_from_pem(kLeafPEM); |
| ASSERT_TRUE(root.normal); |
| ASSERT_TRUE(intermediate.normal); |
| ASSERT_TRUE(leaf.normal); |
| |
| // By default, trust is determined by a combination of self-signedness and |
| // NID_anyExtendedKeyUsage. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.normal.get(), {root.normal.get()}, |
| {intermediate.normal.get()}, {})); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.normal.get(), {root.trusted_any.get()}, |
| {intermediate.normal.get()}, {})); |
| EXPECT_EQ(X509_V_ERR_CERT_REJECTED, |
| Verify(leaf.normal.get(), {root.distrusted_any.get()}, |
| {intermediate.normal.get()}, {})); |
| |
| // Intermediate certificates are not self-signed, so must have an |
| // NID_anyExtendedKeyUsage trust setting. |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, |
| Verify(leaf.normal.get(), {intermediate.normal.get()}, {}, {})); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.normal.get(), |
| {intermediate.trusted_any.get()}, {}, {})); |
| EXPECT_EQ( |
| X509_V_ERR_CERT_REJECTED, |
| Verify(leaf.normal.get(), {intermediate.distrusted_any.get()}, {}, {})); |
| |
| // If a certificate has trust settings, but only for a different OID, the |
| // self-signed rule still takes effect. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.normal.get(), {root.trusted_server.get()}, |
| {intermediate.normal.get()}, {})); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.normal.get(), {root.distrusted_server.get()}, |
| {intermediate.normal.get()}, {})); |
| EXPECT_EQ( |
| X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, |
| Verify(leaf.normal.get(), {intermediate.trusted_server.get()}, {}, {})); |
| |
| // |X509_TRUST_SSL_SERVER| should instead look at self-signedness and |
| // |NID_server_auth|. |
| auto set_server_trust = [](X509_STORE_CTX *ctx) { |
| X509_STORE_CTX_set_trust(ctx, X509_TRUST_SSL_SERVER); |
| }; |
| EXPECT_EQ(X509_V_OK, Verify(leaf.normal.get(), {root.normal.get()}, |
| {intermediate.normal.get()}, {}, /*flags=*/0, |
| set_server_trust)); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.normal.get(), {root.trusted_server.get()}, |
| {intermediate.normal.get()}, {}, /*flags=*/0, |
| set_server_trust)); |
| EXPECT_EQ( |
| X509_V_ERR_CERT_REJECTED, |
| Verify(leaf.normal.get(), {root.distrusted_server.get()}, |
| {intermediate.normal.get()}, {}, /*flags=*/0, set_server_trust)); |
| |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, |
| Verify(leaf.normal.get(), {intermediate.normal.get()}, {}, {}, |
| /*flags=*/0, set_server_trust)); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.normal.get(), |
| {intermediate.trusted_server.get()}, {}, {}, |
| /*flags=*/0, set_server_trust)); |
| EXPECT_EQ(X509_V_ERR_CERT_REJECTED, |
| Verify(leaf.normal.get(), {intermediate.distrusted_server.get()}, |
| {}, {}, /*flags=*/0, set_server_trust)); |
| |
| // NID_anyExtendedKeyUsage is just an unrelated OID to X509_TRUST_SSL_SERVER. |
| // Unlike the default behavior, once a certificate has explicit trust settings |
| // for any OID, the self-signed check is disabled. |
| EXPECT_EQ( |
| X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, |
| Verify(leaf.normal.get(), {root.trusted_any.get()}, |
| {intermediate.normal.get()}, {}, /*flags=*/0, set_server_trust)); |
| |
| // Trust settings on a certificate are ignored if the leaf did not come from |
| // |X509_STORE|. This is important because trust settings may be serialized |
| // via |d2i_X509_AUX|. It is often not obvious which functions may trigger |
| // this, so callers may inadvertently run with attacker-supplied trust |
| // settings on untrusted certificates. |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| Verify(leaf.trusted_server.get(), /*roots=*/{}, |
| /*intermediates=*/{intermediate.trusted_server.get()}, {}, |
| /*flags=*/0, set_server_trust)); |
| EXPECT_EQ( |
| X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, |
| Verify(leaf.trusted_server.get(), /*roots=*/{}, |
| /*intermediates=*/ |
| {intermediate.trusted_server.get(), root.trusted_server.get()}, {}, |
| /*flags=*/0, set_server_trust)); |
| |
| // Likewise, distrusts only take effect from |X509_STORE|. |
| EXPECT_EQ(X509_V_OK, Verify(leaf.distrusted_server.get(), {root.normal.get()}, |
| {intermediate.normal.get()}, {}, |
| /*flags=*/0, set_server_trust)); |
| } |
| |
| // Test some APIs that rust-openssl uses to look up purposes by name. |
| TEST(X509Test, PurposeByShortName) { |
| int idx = X509_PURPOSE_get_by_sname("sslserver"); |
| ASSERT_NE(idx, -1); |
| const X509_PURPOSE *purpose = X509_PURPOSE_get0(idx); |
| ASSERT_TRUE(purpose); |
| EXPECT_EQ(X509_PURPOSE_get_id(purpose), X509_PURPOSE_SSL_SERVER); |
| } |
| |
| TEST(X509Test, CriticalExtension) { |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| bssl::UniquePtr<X509> root = |
| MakeTestCert("Root", "Root", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(X509_sign(root.get(), key.get(), EVP_sha256())); |
| |
| // Issue a certificate with a critical Netscape certificate type extension. We |
| // do not recognize this extension, so this certificate should be rejected. |
| bssl::UniquePtr<X509> leaf = |
| MakeTestCert("Root", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf); |
| bssl::UniquePtr<ASN1_BIT_STRING> cert_type(ASN1_BIT_STRING_new()); |
| ASSERT_TRUE(cert_type); |
| ASSERT_TRUE(ASN1_BIT_STRING_set_bit(cert_type.get(), /*n=*/0, /*value=*/1)); |
| ASSERT_TRUE(X509_add1_ext_i2d(leaf.get(), NID_netscape_cert_type, |
| cert_type.get(), |
| /*crit=*/1, /*flags=*/0)); |
| ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256())); |
| |
| EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, |
| Verify(leaf.get(), {root.get()}, {}, {})); |
| EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {}, {}, |
| X509_V_FLAG_IGNORE_CRITICAL)); |
| } |
| |
| enum NameHash { kOldHash, kNewHash }; |
| |
| // TemporaryHashDir constructs a temporary directory in the format of |
| // |X509_LOOKUP_hash_dir|. |
| class TemporaryHashDir { |
| public: |
| explicit TemporaryHashDir(int type) : type_(type) {} |
| |
| bool Init() { return dir_.Init(); } |
| const std::string &path() const { return dir_.path(); } |
| |
| size_t num_cert_hashes() const { return next_cert_.size(); } |
| size_t num_crl_hashes() const { return next_crl_.size(); } |
| |
| bool AddCert(X509 *x509, NameHash name_hash) { |
| return AddCertWithHash(HashName(name_hash, X509_get_subject_name(x509)), |
| x509); |
| } |
| |
| bool AddCRL(X509_CRL *crl, NameHash name_hash) { |
| return AddCRLWithHash(HashName(name_hash, X509_CRL_get_issuer(crl)), crl); |
| } |
| |
| bool AddCertWithHash(uint32_t hash, X509 *cert) { |
| std::vector<uint8_t> data = EncodeCert(cert); |
| if (data.empty()) { |
| return false; |
| } |
| auto &num = next_cert_[hash]; |
| char path[32]; |
| snprintf(path, sizeof(path), "%08x.%d", hash, num); |
| if (!dir_.AddFile(path, data)) { |
| return false; |
| } |
| num++; |
| return true; |
| } |
| |
| bool AddCRLWithHash(uint32_t hash, X509_CRL *crl) { |
| std::vector<uint8_t> data = EncodeCRL(crl); |
| if (data.empty()) { |
| return false; |
| } |
| auto &num = next_crl_[hash]; |
| char path[32]; |
| snprintf(path, sizeof(path), "%08x.r%d", hash, num); |
| if (!dir_.AddFile(path, data)) { |
| return false; |
| } |
| num++; |
| return true; |
| } |
| |
| bool ReplaceLastCRL(X509_CRL *crl, NameHash name_hash) { |
| uint32_t hash = HashName(name_hash, X509_CRL_get_issuer(crl)); |
| auto iter = next_crl_.find(hash); |
| if (iter == next_crl_.end()) { |
| return false; |
| } |
| std::vector<uint8_t> data = EncodeCRL(crl); |
| if (data.empty()) { |
| return false; |
| } |
| char path[32]; |
| snprintf(path, sizeof(path), "%08x.r%d", hash, iter->second - 1); |
| return dir_.AddFile(path, data); |
| } |
| |
| private: |
| static uint32_t HashName(NameHash name_hash, X509_NAME *name) { |
| return name_hash == kOldHash ? X509_NAME_hash_old(name) |
| : X509_NAME_hash(name); |
| } |
| |
| std::vector<uint8_t> EncodeCert(X509 *cert) { |
| if (type_ == X509_FILETYPE_ASN1) { |
| uint8_t *der = nullptr; |
| int der_len = i2d_X509(cert, &der); |
| if (der_len < 0) { |
| return {}; |
| } |
| bssl::UniquePtr<uint8_t> free_der(der); |
| return std::vector<uint8_t>(der, der + der_len); |
| } |
| |
| bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem())); |
| const uint8_t *pem; |
| size_t pem_len; |
| if (bio == nullptr || // |
| !PEM_write_bio_X509(bio.get(), cert) || |
| !BIO_mem_contents(bio.get(), &pem, &pem_len)) { |
| return {}; |
| } |
| return std::vector<uint8_t>(pem, pem + pem_len); |
| } |
| |
| std::vector<uint8_t> EncodeCRL(X509_CRL *crl) { |
| if (type_ == X509_FILETYPE_ASN1) { |
| uint8_t *der = nullptr; |
| int der_len = i2d_X509_CRL(crl, &der); |
| if (der_len < 0) { |
| return {}; |
| } |
| bssl::UniquePtr<uint8_t> free_der(der); |
| return std::vector<uint8_t>(der, der + der_len); |
| } |
| |
| bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem())); |
| const uint8_t *pem; |
| size_t pem_len; |
| if (bio == nullptr || // |
| !PEM_write_bio_X509_CRL(bio.get(), crl) || |
| !BIO_mem_contents(bio.get(), &pem, &pem_len)) { |
| return {}; |
| } |
| return std::vector<uint8_t>(pem, pem + pem_len); |
| } |
| |
| int type_; |
| TemporaryDirectory dir_; |
| std::map<uint32_t, int> next_cert_; |
| std::map<uint32_t, int> next_crl_; |
| }; |
| |
| // TODO(davidben): Also test CRL handling. There are some interesting behaviors |
| // in here. |
| TEST(X509Test, DirHash) { |
| if (SkipTempFileTests()) { |
| GTEST_SKIP(); |
| } |
| |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| // Test both formats. |
| for (int type : {X509_FILETYPE_PEM, X509_FILETYPE_ASN1}) { |
| SCOPED_TRACE(type); |
| |
| // Generate some roots and fill a directory with OpenSSL's directory hash |
| // format. The hash depends only on the name, so we do not need to |
| // pre-generate the certificates. Test both DER and PEM. |
| TemporaryHashDir dir(type); |
| ASSERT_TRUE(dir.Init()); |
| |
| auto add_root = [&](const std::string &name, NameHash name_hash) -> bool { |
| bssl::UniquePtr<X509> ca = |
| MakeTestCert(name.c_str(), name.c_str(), key.get(), /*is_ca=*/true); |
| if (ca == nullptr || !X509_sign(ca.get(), key.get(), EVP_sha256())) { |
| return false; |
| } |
| return dir.AddCert(ca.get(), name_hash); |
| }; |
| |
| auto issue_crl = |
| [&](const std::string &name, int this_update_offset_day, |
| const std::vector<uint64_t> &serials) -> bssl::UniquePtr<X509_CRL> { |
| bssl::UniquePtr<X509_CRL> crl = MakeTestCRL( |
| name.c_str(), this_update_offset_day, /*next_update_offset_day=*/1); |
| if (crl == nullptr) { |
| return nullptr; |
| } |
| for (uint64_t serial : serials) { |
| // The revocation time does not matter for this test. Pretend the |
| // certificate was just revoked. |
| if (!AddRevokedSerialU64(crl.get(), serial, |
| /*offset_day=*/this_update_offset_day)) { |
| return nullptr; |
| } |
| } |
| if (!X509_CRL_sign(crl.get(), key.get(), EVP_sha256())) { |
| return nullptr; |
| } |
| return crl; |
| }; |
| |
| auto add_crl = [&](const std::string &name, NameHash name_hash, |
| int this_update_offset_day, |
| const std::vector<uint64_t> &serials) -> bool { |
| bssl::UniquePtr<X509_CRL> crl = |
| issue_crl(name, this_update_offset_day, serials); |
| return crl != nullptr && dir.AddCRL(crl.get(), name_hash); |
| }; |
| |
| std::string ca1 = "Test CA 1"; |
| ASSERT_TRUE(add_root(ca1, kNewHash)); |
| |
| std::string ca2 = "Test CA 2"; |
| ASSERT_TRUE(add_root(ca2, kOldHash)); |
| |
| // Install CA 3 at its new hash. CA 3's name is not canonical, but the new |
| // hash runs after canonicalization, so OpenSSL should be able to find it. |
| std::string ca3 = "Test CA 3"; |
| std::string ca3_noncanonical = " test ca 3 "; |
| ASSERT_TRUE(add_root(ca3_noncanonical, kNewHash)); |
| |
| // These two CAs collide under |X509_NAME_hash|. |
| std::string collide_name1 = "Test CA 1191514847"; |
| std::string collide_name2 = "Test CA 1570301806"; |
| size_t num_cert_hashes = dir.num_cert_hashes(); |
| ASSERT_TRUE(add_root(collide_name1, kNewHash)); |
| EXPECT_EQ(dir.num_cert_hashes(), num_cert_hashes + 1); |
| ASSERT_TRUE(add_root(collide_name2, kNewHash)); |
| EXPECT_EQ(dir.num_cert_hashes(), num_cert_hashes + 1); |
| |
| // These two CAs collide under |X509_NAME_hash_old|. |
| std::string old_collide_name1 = "Test CA 1069881739"; |
| std::string old_collide_name2 = "Test CA 940754110"; |
| num_cert_hashes = dir.num_cert_hashes(); |
| ASSERT_TRUE(add_root(old_collide_name1, kOldHash)); |
| EXPECT_EQ(dir.num_cert_hashes(), num_cert_hashes + 1); |
| ASSERT_TRUE(add_root(old_collide_name2, kOldHash)); |
| EXPECT_EQ(dir.num_cert_hashes(), num_cert_hashes + 1); |
| |
| // Make an |X509_STORE| that gets CAs from |dir|. |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| ASSERT_TRUE(store); |
| X509_LOOKUP *lookup = |
| X509_STORE_add_lookup(store.get(), X509_LOOKUP_hash_dir()); |
| ASSERT_TRUE(lookup); |
| ASSERT_TRUE(X509_LOOKUP_add_dir(lookup, dir.path().c_str(), type)); |
| |
| auto test_issuer_flags = [&](const std::string &issuer, uint64_t serial, |
| unsigned long flags) -> int { |
| bssl::UniquePtr<X509> cert = |
| MakeTestCert(issuer.c_str(), "Leaf", key.get(), /*is_ca=*/false); |
| bssl::UniquePtr<ASN1_INTEGER> serial_asn1(ASN1_INTEGER_new()); |
| if (cert == nullptr || serial_asn1 == nullptr || |
| !ASN1_INTEGER_set_uint64(serial_asn1.get(), serial) || |
| !X509_set_serialNumber(cert.get(), serial_asn1.get()) || |
| !X509_sign(cert.get(), key.get(), EVP_sha256())) { |
| return X509_V_ERR_UNSPECIFIED; |
| } |
| |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| if (ctx == nullptr || |
| !X509_STORE_CTX_init(ctx.get(), store.get(), cert.get(), |
| /*chain=*/nullptr)) { |
| return X509_V_ERR_UNSPECIFIED; |
| } |
| X509_STORE_CTX_set_flags(ctx.get(), flags); |
| X509_STORE_CTX_set_time_posix(ctx.get(), /*flags=*/0, kReferenceTime); |
| |
| return X509_verify_cert(ctx.get()) ? X509_V_OK |
| : X509_STORE_CTX_get_error(ctx.get()); |
| }; |
| |
| auto test_issuer = [&](const std::string &issuer) -> int { |
| return test_issuer_flags(issuer, /*serial=*/0, /*flags=*/0); |
| }; |
| auto test_issuer_crl = [&](const std::string &issuer, |
| uint64_t serial) -> int { |
| return test_issuer_flags(issuer, serial, X509_V_FLAG_CRL_CHECK); |
| }; |
| |
| // All these roots are in the store and should be found. Although Test CA |
| // 3 was installed under a non-canonical name, the new hash accounts for |
| // this. |
| EXPECT_EQ(X509_V_OK, test_issuer(ca1)); |
| EXPECT_EQ(X509_V_OK, test_issuer(ca2)); |
| EXPECT_EQ(X509_V_OK, test_issuer(ca3)); |
| EXPECT_EQ(X509_V_OK, test_issuer(collide_name1)); |
| EXPECT_EQ(X509_V_OK, test_issuer(collide_name2)); |
| EXPECT_EQ(X509_V_OK, test_issuer(old_collide_name1)); |
| EXPECT_EQ(X509_V_OK, test_issuer(old_collide_name2)); |
| |
| // Repeat the tests. This time it will pick up the certificate from the |
| // cache. |
| EXPECT_EQ(X509_V_OK, test_issuer(ca1)); |
| EXPECT_EQ(X509_V_OK, test_issuer(ca2)); |
| EXPECT_EQ(X509_V_OK, test_issuer(ca3)); |
| EXPECT_EQ(X509_V_OK, test_issuer(collide_name1)); |
| EXPECT_EQ(X509_V_OK, test_issuer(collide_name2)); |
| EXPECT_EQ(X509_V_OK, test_issuer(old_collide_name1)); |
| EXPECT_EQ(X509_V_OK, test_issuer(old_collide_name2)); |
| |
| // Test a certificate not in the store. |
| ERR_clear_error(); |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, |
| test_issuer("Not In Store")); |
| |
| // Although, internally, this hits the filesystem and finds that a file does |
| // not exist, there should not be anything on the error queue about a |
| // missing file. |X509_verify_cert| generally does not use the error queue, |
| // so it will be empty. See https://crbug.com/boringssl/708. |
| EXPECT_EQ(ERR_get_error(), 0u); |
| |
| // Test CRL handling. First, if we cannot find a CRL, verification will |
| // fail. |
| // |
| // TODO(crbug.com/boringssl/690): We should test both the old and new hash, |
| // but the CRL reloading process does not work for the old hash due to a |
| // bug. It notices the cached old CRL, mistakes it for something loaded via |
| // the new hash, and never bothers checking the old hash. |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_CRL, |
| test_issuer_crl(collide_name1, /*serial=*/1)); |
| |
| // Install an empty CRL. Verification should now succeed. |
| ASSERT_TRUE(add_crl(collide_name1, kNewHash, |
| /*this_update_offset_day=*/-10, /*serials=*/{})); |
| EXPECT_EQ(X509_V_OK, test_issuer_crl(collide_name1, /*serial=*/1)); |
| |
| // Verify again. Unlike roots, which are cached, this will query the |
| // directory again. |
| EXPECT_EQ(X509_V_OK, test_issuer_crl(collide_name1, /*serial=*/1)); |
| |
| // The extra query is so that a newer CRL is picked up, at an incrementing |
| // number. This feature is less useful than it sounds because all CRLs are |
| // persistently cached. |
| ASSERT_TRUE(add_crl(collide_name1, kNewHash, |
| /*this_update_offset_day=*/-9, /*serials=*/{1})); |
| EXPECT_EQ(X509_V_ERR_CERT_REVOKED, |
| test_issuer_crl(collide_name1, /*serial=*/1)); |
| |
| // Serial number 2 is not revoked. |
| EXPECT_EQ(X509_V_OK, test_issuer_crl(collide_name1, /*serial=*/2)); |
| |
| // A new CRL at an already loaded name is ignored because OpenSSL skips |
| // loading the older ones and relies on them being persistently cached in |
| // memory. |
| // |
| // TODO(crbug.com/boringssl/690): This behavior is almost certainly not what |
| // anyone wants. Rework this. |
| bssl::UniquePtr<X509_CRL> crl = issue_crl( |
| collide_name1, /*this_update_offset_day=*/-8, /*serials=*/{1, 2}); |
| ASSERT_TRUE(crl); |
| ASSERT_TRUE(dir.ReplaceLastCRL(crl.get(), kNewHash)); |
| EXPECT_EQ(X509_V_OK, test_issuer_crl(collide_name1, /*serial=*/2)); |
| |
| // If there are many new CRLs, they are all loaded and the newest is wins. |
| ASSERT_TRUE(add_crl(collide_name1, kNewHash, |
| /*this_update_offset_day=*/-7, /*serials=*/{1, 2})); |
| ASSERT_TRUE(add_crl(collide_name1, kNewHash, |
| /*this_update_offset_day=*/-5, /*serials=*/{1, 2, 3})); |
| ASSERT_TRUE(add_crl(collide_name1, kNewHash, |
| /*this_update_offset_day=*/-6, /*serials=*/{1, 2})); |
| |
| // r3 should have won, which revokes all three serials: |
| EXPECT_EQ(X509_V_ERR_CERT_REVOKED, |
| test_issuer_crl(collide_name1, /*serial=*/1)); |
| EXPECT_EQ(X509_V_ERR_CERT_REVOKED, |
| test_issuer_crl(collide_name1, /*serial=*/2)); |
| EXPECT_EQ(X509_V_ERR_CERT_REVOKED, |
| test_issuer_crl(collide_name1, /*serial=*/3)); |
| |
| // If the new CRL is older than a previously loaded one, it is ignored. |
| ASSERT_TRUE(add_crl(collide_name1, kNewHash, |
| /*this_update_offset_day=*/-100, /*serials=*/{})); |
| |
| // Finally, test hash collisions. The internal book-keeping for where to |
| // start loading should be compatible with a second CA whose hash collides. |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_CRL, |
| test_issuer_crl(collide_name2, /*serial=*/1)); |
| EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_CRL, |
| test_issuer_crl(collide_name2, /*serial=*/2)); |
| ASSERT_TRUE(add_crl(collide_name2, kNewHash, |
| /*this_update_offset_day=*/-10, /*serials=*/{1})); |
| EXPECT_EQ(X509_V_ERR_CERT_REVOKED, |
| test_issuer_crl(collide_name2, /*serial=*/1)); |
| EXPECT_EQ(X509_V_OK, test_issuer_crl(collide_name2, /*serial=*/2)); |
| |
| // Confirm all CRLs we added had the same hash. |
| EXPECT_EQ(dir.num_crl_hashes(), 1u); |
| } |
| } |
| |
| // Test that two directory hash paths are treated as a sequence of paths, |
| // separated by a separator. |
| TEST(X509Test, DirHashSeparator) { |
| #if defined(OPENSSL_WINDOWS) |
| const char kSeparator = ';'; |
| #else |
| const char kSeparator = ':'; |
| #endif |
| |
| if (SkipTempFileTests()) { |
| GTEST_SKIP(); |
| } |
| |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| // Make two directories and place one CA in each. |
| TemporaryHashDir dir1(X509_FILETYPE_PEM), dir2(X509_FILETYPE_PEM); |
| ASSERT_TRUE(dir1.Init()); |
| ASSERT_TRUE(dir2.Init()); |
| |
| bssl::UniquePtr<X509> ca1 = |
| MakeTestCert("Test CA 1", "Test CA 1", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(ca1); |
| ASSERT_TRUE(X509_sign(ca1.get(), key.get(), EVP_sha256())); |
| ASSERT_TRUE(dir1.AddCert(ca1.get(), kNewHash)); |
| |
| bssl::UniquePtr<X509> ca2 = |
| MakeTestCert("Test CA 2", "Test CA 2", key.get(), /*is_ca=*/true); |
| ASSERT_TRUE(ca2); |
| ASSERT_TRUE(X509_sign(ca2.get(), key.get(), EVP_sha256())); |
| ASSERT_TRUE(dir1.AddCert(ca2.get(), kNewHash)); |
| |
| // Make an |X509_STORE| that gets CAs from |dir1| and |dir2|. |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| ASSERT_TRUE(store); |
| std::string paths = dir1.path() + kSeparator + dir2.path(); |
| ASSERT_TRUE( |
| X509_STORE_load_locations(store.get(), /*file=*/nullptr, paths.c_str())); |
| |
| // Both CAs should work. |
| { |
| bssl::UniquePtr<X509> cert = |
| MakeTestCert("Test CA 1", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(cert); |
| ASSERT_TRUE(X509_sign(cert.get(), key.get(), EVP_sha256())); |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), cert.get(), |
| /*chain=*/nullptr)); |
| X509_STORE_CTX_set_time_posix(ctx.get(), /*flags=*/0, kReferenceTime); |
| EXPECT_TRUE(X509_verify_cert(ctx.get())) |
| << X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx.get())); |
| } |
| |
| { |
| bssl::UniquePtr<X509> cert = |
| MakeTestCert("Test CA 2", "Leaf", key.get(), /*is_ca=*/false); |
| ASSERT_TRUE(cert); |
| ASSERT_TRUE(X509_sign(cert.get(), key.get(), EVP_sha256())); |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), cert.get(), |
| /*chain=*/nullptr)); |
| X509_STORE_CTX_set_time_posix(ctx.get(), /*flags=*/0, kReferenceTime); |
| EXPECT_TRUE(X509_verify_cert(ctx.get())) |
| << X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx.get())); |
| } |
| } |
| |
| #if defined(OPENSSL_THREADS) |
| // Test that directory hash lookup is thread-safe. |
| TEST(X509Test, DirHashThreads) { |
| if (SkipTempFileTests()) { |
| GTEST_SKIP(); |
| } |
| |
| bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key); |
| |
| // Generate some roots and fill a directory with OpenSSL's directory hash |
| // format. The hash depends only on the name, so we do not need to |
| // pre-generate the certificates. Test both DER and PEM. |
| TemporaryHashDir dir(X509_FILETYPE_PEM); |
| ASSERT_TRUE(dir.Init()); |
| |
| auto add_root = [&](const std::string &name, NameHash name_hash) -> bool { |
| bssl::UniquePtr<X509> ca = |
| MakeTestCert(name.c_str(), name.c_str(), key.get(), /*is_ca=*/true); |
| return ca != nullptr && // |
| X509_sign(ca.get(), key.get(), EVP_sha256()) && |
| dir.AddCert(ca.get(), name_hash); |
| }; |
| |
| auto issue_cert = [&](const std::string &issuer) -> bssl::UniquePtr<X509> { |
| bssl::UniquePtr<X509> cert = |
| MakeTestCert(issuer.c_str(), "Leaf", key.get(), /*is_ca=*/false); |
| if (cert == nullptr || !X509_sign(cert.get(), key.get(), EVP_sha256())) { |
| return nullptr; |
| } |
| return cert; |
| }; |
| |
| auto add_crl = [&](const std::string &name, |
| int this_update_offset_day, NameHash name_hash) -> bool { |
| bssl::UniquePtr<X509_CRL> crl = MakeTestCRL( |
| name.c_str(), this_update_offset_day, /*next_update_offset_day=*/1); |
| return crl != nullptr && |
| X509_CRL_sign(crl.get(), key.get(), EVP_sha256()) && |
| dir.AddCRL(crl.get(), name_hash); |
| }; |
| |
| // These two CAs collide under |X509_NAME_hash|. |
| std::string ca1 = "Test CA 1191514847"; |
| std::string ca2 = "Test CA 1570301806"; |
| ASSERT_TRUE(add_root(ca1, kNewHash)); |
| ASSERT_TRUE(add_root(ca2, kNewHash)); |
| ASSERT_TRUE(add_crl(ca1, -2, kNewHash)); |
| ASSERT_TRUE(add_crl(ca2, -1, kNewHash)); |
| ASSERT_TRUE(add_crl(ca2, -2, kNewHash)); |
| ASSERT_TRUE(add_crl(ca1, -1, kNewHash)); |
| // Verify the hashes collided. |
| ASSERT_EQ(dir.num_cert_hashes(), 1u); |
| ASSERT_EQ(dir.num_crl_hashes(), 1u); |
| bssl::UniquePtr<X509> leaf1 = issue_cert(ca1); |
| ASSERT_TRUE(leaf1); |
| bssl::UniquePtr<X509> leaf2 = issue_cert(ca2); |
| ASSERT_TRUE(leaf2); |
| |
| // These two CAs collide under |X509_NAME_hash_old|. |
| std::string old_ca1 = "Test CA 1069881739"; |
| std::string old_ca2 = "Test CA 940754110"; |
| ASSERT_TRUE(add_root(old_ca1, kOldHash)); |
| ASSERT_TRUE(add_root(old_ca2, kOldHash)); |
| ASSERT_TRUE(add_crl(old_ca1, -2, kOldHash)); |
| ASSERT_TRUE(add_crl(old_ca2, -1, kOldHash)); |
| ASSERT_TRUE(add_crl(old_ca2, -2, kOldHash)); |
| ASSERT_TRUE(add_crl(old_ca1, -1, kOldHash)); |
| // Verify the hashes collided. |
| ASSERT_EQ(dir.num_cert_hashes(), 2u); |
| ASSERT_EQ(dir.num_crl_hashes(), 2u); |
| bssl::UniquePtr<X509> old_leaf1 = issue_cert(old_ca1); |
| ASSERT_TRUE(old_leaf1); |
| bssl::UniquePtr<X509> old_leaf2 = issue_cert(old_ca2); |
| ASSERT_TRUE(old_leaf2); |
| |
| // Make an |X509_STORE| that gets CAs from |dir|. |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| ASSERT_TRUE(store); |
| ASSERT_TRUE(X509_STORE_load_locations(store.get(), /*file=*/nullptr, |
| dir.path().c_str())); |
| |
| auto verify = [&](X509 *cert, bool crl_check) { |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE(X509_STORE_CTX_init(ctx.get(), store.get(), cert, |
| /*chain=*/nullptr)); |
| X509_STORE_CTX_set_flags(ctx.get(), crl_check ? X509_V_FLAG_CRL_CHECK : 0); |
| X509_STORE_CTX_set_time_posix(ctx.get(), /*flags=*/0, kReferenceTime); |
| EXPECT_TRUE(X509_verify_cert(ctx.get())) |
| << X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx.get())); |
| }; |
| |
| const size_t kNumThreads = 10; |
| std::vector<std::thread> threads; |
| for (size_t i = 0; i < kNumThreads; i++) { |
| threads.emplace_back([&] { verify(leaf1.get(), false); }); |
| threads.emplace_back([&] { verify(leaf1.get(), true); }); |
| threads.emplace_back([&] { verify(leaf2.get(), false); }); |
| threads.emplace_back([&] { verify(leaf2.get(), true); }); |
| |
| threads.emplace_back([&] { verify(old_leaf1.get(), false); }); |
| threads.emplace_back([&] { verify(old_leaf1.get(), true); }); |
| threads.emplace_back([&] { verify(old_leaf2.get(), false); }); |
| threads.emplace_back([&] { verify(old_leaf2.get(), true); }); |
| } |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| } |
| #endif // OPENSSL_THREADS |
| |
| // Test that, when there are two CAs with the same name, but different key |
| // identifiers, certificate and CRL lookup can disambiguate correctly. |
| TEST(X509Test, DuplicateName) { |
| // Make two certificate chains and empty CRLs, with the same names but |
| // different keys. |
| bssl::UniquePtr<EVP_PKEY> key1 = PrivateKeyFromPEM(kP256Key); |
| ASSERT_TRUE(key1); |
| uint8_t key_id1[] = {'K', 'e', 'y', '1'}; |
| bssl::UniquePtr<X509> ca1 = |
| MakeTestCert("CA", "CA", key1.get(), /*is_ca=*/true); |
| ASSERT_TRUE(ca1); |
| ASSERT_TRUE(AddSubjectKeyIdentifier(ca1.get(), key_id1)); |
| ASSERT_TRUE(X509_sign(ca1.get(), key1.get(), EVP_sha256())); |
| bssl::UniquePtr<X509> leaf1 = |
| MakeTestCert("CA", "Leaf", key1.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf1); |
| ASSERT_TRUE(AddAuthorityKeyIdentifier(leaf1.get(), key_id1)); |
| ASSERT_TRUE(X509_sign(leaf1.get(), key1.get(), EVP_sha256())); |
| bssl::UniquePtr<X509_CRL> crl1 = MakeTestCRL("CA", -1, 1); |
| ASSERT_TRUE(crl1); |
| ASSERT_TRUE(AddAuthorityKeyIdentifier(crl1.get(), key_id1)); |
| ASSERT_TRUE(X509_CRL_sign(crl1.get(), key1.get(), EVP_sha256())); |
| // TODO(davidben): Some state in CRLs does not get correctly set up unless it |
| // is parsed from data. |X509_CRL_sign| should reset it internally. |
| crl1 = ReencodeCRL(crl1.get()); |
| ASSERT_TRUE(crl1); |
| |
| bssl::UniquePtr<EVP_PKEY> key2 = PrivateKeyFromPEM(kRSAKey); |
| ASSERT_TRUE(key2); |
| uint8_t key_id2[] = {'K', 'e', 'y', '2'}; |
| bssl::UniquePtr<X509> ca2 = |
| MakeTestCert("CA", "CA", key2.get(), /*is_ca=*/true); |
| ASSERT_TRUE(ca2); |
| ASSERT_TRUE(AddSubjectKeyIdentifier(ca2.get(), key_id2)); |
| ASSERT_TRUE(X509_sign(ca2.get(), key2.get(), EVP_sha256())); |
| bssl::UniquePtr<X509> leaf2 = |
| MakeTestCert("CA", "Leaf", key2.get(), /*is_ca=*/false); |
| ASSERT_TRUE(leaf2); |
| ASSERT_TRUE(AddAuthorityKeyIdentifier(leaf2.get(), key_id2)); |
| ASSERT_TRUE(X509_sign(leaf2.get(), key2.get(), EVP_sha256())); |
| bssl::UniquePtr<X509_CRL> crl2 = MakeTestCRL("CA", -2, 2); |
| ASSERT_TRUE(crl2); |
| ASSERT_TRUE(AddAuthorityKeyIdentifier(crl2.get(), key_id2)); |
| ASSERT_TRUE(X509_CRL_sign(crl2.get(), key2.get(), EVP_sha256())); |
| // TODO(davidben): Some state in CRLs does not get correctly set up unless it |
| // is parsed from data. |X509_CRL_sign| should reset it internally. |
| crl2 = ReencodeCRL(crl2.get()); |
| ASSERT_TRUE(crl2); |
| |
| for (bool key1_first : {false, true}) { |
| SCOPED_TRACE(key1_first); |
| X509 *first_leaf = leaf1.get(); |
| X509 *second_leaf = leaf2.get(); |
| if (!key1_first) { |
| std::swap(first_leaf, second_leaf); |
| } |
| |
| for (bool use_dir : {false, true}) { |
| SCOPED_TRACE(use_dir); |
| bssl::UniquePtr<X509_STORE> store(X509_STORE_new()); |
| ASSERT_TRUE(store); |
| TemporaryHashDir dir(X509_FILETYPE_PEM); |
| if (use_dir) { |
| ASSERT_TRUE(dir.Init()); |
| ASSERT_TRUE(dir.AddCert(ca1.get(), kNewHash)); |
| ASSERT_TRUE(dir.AddCert(ca2.get(), kNewHash)); |
| ASSERT_TRUE(dir.AddCRL(crl1.get(), kNewHash)); |
| ASSERT_TRUE(dir.AddCRL(crl2.get(), kNewHash)); |
| ASSERT_EQ(dir.num_cert_hashes(), 1u); |
| ASSERT_EQ(dir.num_crl_hashes(), 1u); |
| ASSERT_TRUE(X509_STORE_load_locations(store.get(), /*file=*/nullptr, |
| dir.path().c_str())); |
| } else { |
| ASSERT_TRUE(X509_STORE_add_cert(store.get(), ca1.get())); |
| ASSERT_TRUE(X509_STORE_add_cert(store.get(), ca2.get())); |
| ASSERT_TRUE(X509_STORE_add_crl(store.get(), crl1.get())); |
| ASSERT_TRUE(X509_STORE_add_crl(store.get(), crl2.get())); |
| } |
| |
| // Verify the two certificates. Whichever comes first, we should |
| // successfully find their CA and CRL. |
| { |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE( |
| X509_STORE_CTX_init(ctx.get(), store.get(), first_leaf, nullptr)); |
| X509_STORE_CTX_set_flags(ctx.get(), X509_V_FLAG_CRL_CHECK); |
| X509_STORE_CTX_set_time_posix(ctx.get(), /*flags=*/0, kReferenceTime); |
| EXPECT_TRUE(X509_verify_cert(ctx.get())) |
| << X509_verify_cert_error_string( |
| X509_STORE_CTX_get_error(ctx.get())); |
| } |
| { |
| bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new()); |
| ASSERT_TRUE(ctx); |
| ASSERT_TRUE( |
| X509_STORE_CTX_init(ctx.get(), store.get(), second_leaf, nullptr)); |
| X509_STORE_CTX_set_flags(ctx.get(), X509_V_FLAG_CRL_CHECK); |
| X509_STORE_CTX_set_time_posix(ctx.get(), /*flags=*/0, kReferenceTime); |
| EXPECT_TRUE(X509_verify_cert(ctx.get())) |
| << X509_verify_cert_error_string( |
| X509_STORE_CTX_get_error(ctx.get())); |
| } |
| } |
| } |
| } |