From 3da489c195a8b73cebcb1b1af55e048e815d3572 Mon Sep 17 00:00:00 2001 From: spencermaxfield Date: Thu, 8 Dec 2022 14:03:10 -0500 Subject: [PATCH 1/2] - Add certificate pinning support with default pinning to Duo's certs - Add methods for pinning to custom root certs and disabling cert pinning - Add new tests for cert pinning - Update project to allow tests to acess internal methods of duo_api_csharp assembly for easier testing --- duo_api_csharp/AssemblyInfo.cs | 36 ++++ duo_api_csharp/CertificatePinnerFactory.cs | 142 ++++++++++++ duo_api_csharp/Duo.cs | 49 ++++- duo_api_csharp/ca_certs.pem | 239 +++++++++++++++++++++ duo_api_csharp/duo_api_csharp.csproj | 5 + test/CertPinningTest.cs | 158 ++++++++++++++ test/DuoApiTest.csproj | 5 +- 7 files changed, 631 insertions(+), 3 deletions(-) create mode 100644 duo_api_csharp/AssemblyInfo.cs create mode 100644 duo_api_csharp/CertificatePinnerFactory.cs create mode 100644 duo_api_csharp/ca_certs.pem create mode 100644 test/CertPinningTest.cs diff --git a/duo_api_csharp/AssemblyInfo.cs b/duo_api_csharp/AssemblyInfo.cs new file mode 100644 index 0000000..75fbbb2 --- /dev/null +++ b/duo_api_csharp/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("duo_api_csharp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("duo_api_csharp")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b15c44a4-74d6-45b7-8a30-a313c2818083")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +// Allow tests to access internal methods for easier testing +[assembly: InternalsVisibleTo("DuoApiTest")] diff --git a/duo_api_csharp/CertificatePinnerFactory.cs b/duo_api_csharp/CertificatePinnerFactory.cs new file mode 100644 index 0000000..8026eb1 --- /dev/null +++ b/duo_api_csharp/CertificatePinnerFactory.cs @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2022 Cisco Systems, Inc. and/or its affiliates + * All rights reserved + */ + +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System; + +namespace Duo +{ + public class CertificatePinnerFactory + { + private readonly X509CertificateCollection _rootCerts; + + public CertificatePinnerFactory(X509CertificateCollection rootCerts) + { + _rootCerts = rootCerts; + } + + /// + /// Get a certificate pinner that ensures only connections to a specific list of root certificates are allowed + /// + /// A Duo certificate pinner for use in an HttpWebRequest + public static RemoteCertificateValidationCallback GetDuoCertificatePinner() + { + return new CertificatePinnerFactory(GetDuoCertCollection()).GetPinner(); + } + /// + /// Get a certificate pinner that ensures only connections to the provided root certificates are allowed + /// + /// A certificate pinner for use in an HttpWebRequest + public static RemoteCertificateValidationCallback GetCustomRootCertificatesPinner(X509CertificateCollection rootCerts) + { + return new CertificatePinnerFactory(rootCerts).GetPinner(); + } + + + /// + /// Get a certificate "pinner" that effectively disables SSL certificate validation + /// + /// + public static RemoteCertificateValidationCallback GetCertificateDisabler() + { + return (httpRequestMessage, certificate, chain, sslPolicyErrors) => true; + } + + internal RemoteCertificateValidationCallback GetPinner() + { + return PinCertificate; + } + + /// + /// Pin only to specified root certificates, and reject connections to any other roots. + /// NB that the certificate and chain have already been checked, and the status of that check is available + /// in the chain ChainStatus and overall SslPolicyErrors. + /// + /// The actual request (unused) + /// The server certificate presented to the connection + /// The full certificate chain presented to the connection + /// The current result of the certificate checks + /// true if the connection should be allowed, false otherwise + internal bool PinCertificate(object request, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + // If there's no server certificate or chain, fail + if (certificate == null || chain == null) + { + return false; + } + + // If the regular certificate checking process failed, fail + // we want everything to be valid, but then just restrict the acceptable root certificates + if (sslPolicyErrors != SslPolicyErrors.None) + { + return false; + } + + // Double check everything's valid and grab the root certificate (and double check it's valid) + if (!chain.ChainStatus.All(status => status.Status == X509ChainStatusFlags.NoError)) + { + return false; + } + var chainLength = chain.ChainElements.Count; + var rootCert = chain.ChainElements[chainLength - 1].Certificate; + if (!rootCert.Verify()) + { + return false; + } + + // Check that the root certificate is in the allowed list + if (!_rootCerts.Contains(rootCert)) + { + return false; + } + + return true; + } + + /// + /// Get the root certificates allowed by Duo in a usable form + /// + /// A X509CertificateCollection of the allowed root certificates + internal static X509CertificateCollection GetDuoCertCollection() + { + var certs = ReadCertsFromFile(); + + X509CertificateCollection coll = new X509CertificateCollection(); + foreach (string oneCert in certs) + { + if (!string.IsNullOrWhiteSpace(oneCert)) + { + var bytes = Encoding.UTF8.GetBytes(oneCert); + coll.Add(new X509Certificate(bytes)); + } + } + return coll; + } + + /// + /// Read the embedded Duo ca_certs.pem certificates file to get an array of certificate strings + /// + /// The Duo root CA certificates as strings + internal static string[] ReadCertsFromFile() + { + var certs = ""; + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("duo_api_csharp.ca_certs.pem")) + using (StreamReader reader = new StreamReader(stream)) + { + certs = reader.ReadToEnd(); + } + var splitOn = "-----DUO_CERT-----"; + return certs.Split(new string[] { splitOn }, int.MaxValue, StringSplitOptions.None); + } + } +} \ No newline at end of file diff --git a/duo_api_csharp/Duo.cs b/duo_api_csharp/Duo.cs index 75de05c..bc38e92 100644 --- a/duo_api_csharp/Duo.cs +++ b/duo_api_csharp/Duo.cs @@ -15,6 +15,8 @@ using System.Globalization; using System.Linq; using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using System.Net.Security; namespace Duo @@ -34,6 +36,8 @@ public class DuoApi private string user_agent; private SleepService sleepService; private RandomService randomService; + private bool sslCertValidation = true; + private X509CertificateCollection customRoots = null; /// Duo integration key /// Duo secret key @@ -71,6 +75,32 @@ protected DuoApi(string ikey, string skey, string host, string user_agent, strin } } + /// + /// Disables SSL certificate validation for the API calls the client makes. + /// Incomptible with UseCustomRootCertificates since certificates will not be checked. + /// + /// THIS SHOULD NEVER BE USED IN A PRODUCTION ENVIRONMENT + /// + /// The DuoApi + public DuoApi DisableSslCertificateValidation() + { + sslCertValidation = false; + return this; + } + + /// + /// Override the set of Duo root certificates used for certificate pinning. Provide a collection of acceptable root certificates. + /// + /// Incompatible with DisableSslCertificateValidation - if that is enabled, certificate pinning is not done at all. + /// + /// The custom set of root certificates to trust + /// The DuoApi + public DuoApi UseCustomRootCertificates(X509CertificateCollection customRoots) + { + this.customRoots = customRoots; + return this; + } + public static string FinishCanonicalize(string p) { // Signatures require upper-case hex digits. @@ -244,6 +274,7 @@ private HttpWebRequest PrepareHttpRequest(String method, String url, String auth String cannonParams, int timeout) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.ServerCertificateValidationCallback = GetCertificatePinner(); request.Method = method; request.Accept = "application/json"; request.Headers.Add("Authorization", auth); @@ -271,6 +302,22 @@ private HttpWebRequest PrepareHttpRequest(String method, String url, String auth return request; } + private RemoteCertificateValidationCallback GetCertificatePinner() + { + if (!sslCertValidation) + { + // Pinner that effectively disables cert pinning by always returning true + return CertificatePinnerFactory.GetCertificateDisabler(); + } + + if (customRoots != null) + { + return CertificatePinnerFactory.GetCustomRootCertificatesPinner(customRoots); + } + + return CertificatePinnerFactory.GetDuoCertificatePinner(); + } + private HttpWebResponse AttemptRetriableHttpRequest( String method, String url, String auth, String date, String cannonParams, int timeout) { @@ -643,7 +690,7 @@ private static extern int WinHttpOpen([MarshalAs(UnmanagedType.LPWStr)] string p private static extern bool WinHttpCloseHandle(int hInternet); #endregion Private DllImport } - + [Serializable] public class DuoException : Exception { diff --git a/duo_api_csharp/ca_certs.pem b/duo_api_csharp/ca_certs.pem new file mode 100644 index 0000000..a809059 --- /dev/null +++ b/duo_api_csharp/ca_certs.pem @@ -0,0 +1,239 @@ +subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root CA +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- +-----DUO_CERT----- +subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- +-----DUO_CERT----- +subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- +-----DUO_CERT----- +subject= /C=US/O=SecureTrust Corporation/CN=SecureTrust CA +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- +-----DUO_CERT----- +subject= /C=US/O=SecureTrust Corporation/CN=Secure Global CA +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- +-----DUO_CERT----- +subject=C = US, O = Amazon, CN = Amazon Root CA 1 +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- +-----DUO_CERT----- +subject=C = US, O = Amazon, CN = Amazon Root CA 2 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- +-----DUO_CERT----- +subject=C = US, O = Amazon, CN = Amazon Root CA 3 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- +-----DUO_CERT----- +subject=C = US, O = Amazon, CN = Amazon Root CA 4 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- +-----DUO_CERT----- +subject=C = BM, O = QuoVadis Limited, CN = QuoVadis Root CA 2 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- \ No newline at end of file diff --git a/duo_api_csharp/duo_api_csharp.csproj b/duo_api_csharp/duo_api_csharp.csproj index 88a1660..e5ab47e 100644 --- a/duo_api_csharp/duo_api_csharp.csproj +++ b/duo_api_csharp/duo_api_csharp.csproj @@ -44,8 +44,13 @@ + + + + + diff --git a/test/CertPinningTest.cs b/test/CertPinningTest.cs new file mode 100644 index 0000000..9c77f86 --- /dev/null +++ b/test/CertPinningTest.cs @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022 Cisco Systems, Inc. and/or its affiliates + * All rights reserved + */ + +using Duo; +using System; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Xunit; + +public class CertPinningTestBase +{ + // Helper methods and some hard-coded certificates + protected static X509Certificate2 DuoApiServerCert() + { + // The leaf certificate for api-*.duosecurity.com + return CertFromString(DUO_API_CERT_SERVER); + } + + protected static X509Chain DuoApiChain() + { + // The certificate chain for api-*.duosecurity.com + var chain = new X509Chain(); + chain.ChainPolicy.ExtraStore.Add(CertFromString(DUO_API_CERT_ROOT)); + chain.ChainPolicy.ExtraStore.Add(CertFromString(DUO_API_CERT_INTER)); + bool valid = chain.Build(DuoApiServerCert()); + Assert.True(valid); + return chain; + } + + protected static X509Chain MicrosoftComChain() + { + // A valid chain, but for www.microsoft.com, not Duo + var chain = new X509Chain(); + chain.ChainPolicy.ExtraStore.Add(CertFromString(MICROSOFT_COM_CERT_ROOT)); + chain.ChainPolicy.ExtraStore.Add(CertFromString(MICROSOFT_COM_CERT_INTER)); + bool valid = chain.Build(CertFromString(MICROSOFT_COM_CERT_SERVER)); + Assert.True(valid); + return chain; + } + + protected static X509Certificate2 CertFromString(string certString) + { + return new X509Certificate2(Convert.FromBase64String(certString)); + } + + // Certificates exported from the web site 2022-03-09 + protected const string DUO_API_CERT_SERVER = "MIIH0zCCBrugAwIBAgIQATqA/dmRlE1FQRYim5kzkDANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNzdXJhbmNlIFNlcnZlciBDQTAeFw0yMjAzMDIwMDAwMDBaFw0yMzA0MDIyMzU5NTlaMGsxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNaWNoaWdhbjESMBAGA1UEBxMJQW5uIEFyYm9yMRkwFwYDVQQKExBEdW8gU2VjdXJpdHkgTExDMRowGAYDVQQDDBEqLmR1b3NlY3VyaXR5LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKGWtT2do8lVflEai4UAdOc019bQyQ4XjUHKVmbHwUxShpmwetLusu4+A0MPLbwZko9kwYCXK8TxrVzABIAWw5CqirJWr80+KucmtFVUgxqFav7vUIJnY/BaWDGkiLSMCLz8HToxi8Fp86rQjTM08AEjPLYMlvU41HlWTvuxQT6HdR0uQovhJ1Qrn4IMlrCoLoPkisPWtVaVX/cMJEqWtT/M89mCiczqNxzE7YMDVwRZowDdmC/T6ujo09mCUkl/uojBlENEiFHKIfjz7SDItDGsFmM0ucRv5Mxfvz80mNkoyRIDK8vL2hq7AzdO3qgjL+ZGT3g9H6YCUqKy16SI/PU6nhS48edpuMB6C1m26dldBszK7foeBqtx59WpztYJAlbClaVDuM88DMYANIiduGn3GY7aTlIXn+lHJh1RoLalCh6aOZs3v/2+ggLeGj75vtqhr91aFJozbgu4OtQZkfSepYt/5dZAHCimuJnNH+euDvWtrj10DQ8ToIXQd0vxQ7QbDY8dMjsGi9vYzL8QPOmF/iTBt5V663K83Kjy1hm/QrKOKbAyak8rsyBYndwEXmCpVEHRI90ECCyVBGzX5pjVEtBP0ZZWem9x6K4kwx1xCQl0mOVdmwro9lBLw3HNAj4/S0R77ZVhgHXgkL2vFozkGSSyYrw6sBfr9685FMSDAgMBAAGjggNsMIIDaDAfBgNVHSMEGDAWgBRRaP+QrwIHdTzM2WVkYqISuFlyOzAdBgNVHQ4EFgQUHANgvmzsw2ofi4cQ9W7b1BJYs20wLQYDVR0RBCYwJIIRKi5kdW9zZWN1cml0eS5jb22CD2R1b3NlY3VyaXR5LmNvbTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHUGA1UdHwRuMGwwNKAyoDCGLmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWhhLXNlcnZlci1nNi5jcmwwNKAyoDCGLmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWhhLXNlcnZlci1nNi5jcmwwPgYDVR0gBDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGDBggrBgEFBQcBAQR3MHUwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBNBggrBgEFBQcwAoZBaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkhpZ2hBc3N1cmFuY2VTZXJ2ZXJDQS5jcnQwCQYDVR0TBAIwADCCAX4GCisGAQQB1nkCBAIEggFuBIIBagFoAHcA6D7Q2j71BjUy51covIlryQPTy9ERa+zraeF3fW0GvW4AAAF/TKU0oAAABAMASDBGAiEAhhSnzx392jnLqxjI4OKypVLQ8DD5GLG06IAV7Ajmr8oCIQCWAtF/QHRDtAenfkplcQ4pzNfPGq0WOwHHHygXFg6bxgB1ALNzdwfhhFD4Y4bWBancEQlKeS2xZwwLh9zwAw55NqWaAAABf0ylNQgAAAQDAEYwRAIgBVuyQ38fIBW+GjBE9PbmMvtlyP9HzaF4XigzUNRkrfsCIGJzhTwCpI7UVXYLOXM0jKA3DBIVah06ohtRQSaG/S0wAHYAtz77JN+cTbp18jnFulj0bF38Qs96nzXEnh0JgSXttJkAAAF/TKU02QAABAMARzBFAiEAyHdWYdIzJUzcvaqOsSThLtBuVtFlGpIWHmzg9gZlf1MCIEUr9IZ7zXAs+6sD/j9T4GMgwJxoKvns7aM+qvRjvh3qMA0GCSqGSIb3DQEBCwUAA4IBAQAp5YyMInyd7dik2lkQ09rugqVY+8idT9QKcEF1OzwcsSNg1RiHJg3lpTjRrG7EBvPghaPhAeWIDnpoeqXroixvp/pIBbWxSJX7a4Zzu7HTGHARbxkN5+wmIsXV+zq0FK/uKi74B5slaeXGGIhUnNpFt9E1IBW8425G0FkVb0A5/paEwEZFqhSOWgxclwqGqMdqIY9jYCTkHdV5YU5hw/yBQPy9eNBwV/jRu92+1iEdwYvQHi6O+Lb+xGQwPHeVEIwbdZ3B1ZeQlIlkMhPYF12R0862VQ8SKLFNdr1i8cgt4PraFKwV2PZ0JWFE9DU/9jfk1ZSyXtEn6miu4GXef4sH"; + + // Certificates exported from the web sites 2021-09-22 + protected const string DUO_API_CERT_INTER = "MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEvMC0GA1UEAxMmRGlnaUNlcnQgU0hBMiBIaWdoIEFzc3VyYW5jZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC24C/CJAbIbQRf1+8KZAayfSImZRauQkCbztyfn3YHPsMwVYcZuU+UDlqUH1VWtMICKq/QmO4LQNfE0DtyyBSe75CxEamu0si4QzrZCwvV1ZX1QK/IHe1NnF9Xt4ZQaJn1itrSxwUfqJfJ3KSxgoQtxq2lnMcZgqaFD15EWCo3j/018QsIJzJa9buLnqS9UdAn4t07QjOjBSjEuyjMmqwrIw14xnvmXnG3Sj4I+4G3FhahnSMSTeXXkgisdaScus0Xsh5ENWV/UyU50RwKmmMbGZJ0aAo3wsJSSMs5WqK24V3B3aAguCGikyZvFEohQcftbZvySC/zA/WiaJJTL17jAgMBAAGjggFJMIIBRTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAdBgNVHQ4EFgQUUWj/kK8CB3U8zNllZGKiErhZcjswHwYDVR0jBBgwFoAUsT7DaQP4v0cB1JgmGggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABiKlYkD5m3fXPwdaOpKj4PWUS+Na0QWnqxj9dJubISZi6qBcYRb7TROsLd5kinMLYBq8I4g4Xmk/gNHE+r1hspZcX30BJZr01lYPf7TMSVcGDiEo+afgv2MW5gxTs14nhr9hctJqvIni5ly/D6q1UEL2tU2ob8cbkdJf17ZSHwD2f2LSaCYJkJA69aSEaRkCldUxPUd1gJea6zuxICaEnL6VpPX/78whQYwvwt/Tv9XBZ0k7YXDK/umdaisLRbvfXknsuvCnQsH6qqF0wGjIChBWUMo0oHjqvbsezt3tkBigAVBRQHvFwY+3sAzm2fTYS5yh+Rp/BIAV0AecPUeybQ="; + protected const string DUO_API_CERT_ROOT = "MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCevEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K"; + + // Certificates exported from the web sites 2022-08-19 + protected const string MICROSOFT_COM_CERT_SERVER = "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"; + protected const string MICROSOFT_COM_CERT_INTER = "MIIFWjCCBEKgAwIBAgIQDxSWXyAgaZlP1ceseIlB4jANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJJRTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTIwMDcyMTIzMDAwMFoXDTI0MTAwODA3MDAwMFowTzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEgMB4GA1UEAxMXTWljcm9zb2Z0IFJTQSBUTFMgQ0EgMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqYnfPmmOyBoTzkDb0mfMUUavqlQo7Rgb9EUEf/lsGWMk4bgj8T0RIzTqk970eouKVuL5RIMW/snBjXXgMQ8ApzWRJCZbar879BV8rKpHoAW4uGJssnNABf2n17j9TiFy6BWy+IhVnFILyLNK+W2M3zK9gheiWa2uACKhuvgCca5Vw/OQYErEdG7LBEzFnMzTmJcliW1iCdXby/vI/OxbfqkKD4zJtm45DJvC9Dh+hpzqvLMiK5uo/+aXSJY+SqhoIEpz+rErHw+uAlKuHFtEjSeeku8eR3+Z5ND9BSqc6JtLqb0bjOHPm5dSRrgt4nnil75bjc9j3lWXpBb9PXP9Sp/nPCK+nTQmZwHGjUnqlO9ebAVQD47ZisFonnDAmjrZNVqEXF3p7laEHrFMxttYuD81BdOzxAbL9Rb/8MeFGQjE2Qx65qgVfhH+RsYuuD9dUw/3wZAhq05yO6nk07AM9c+AbNtRoEcdZcLCHfMDcbkXKNs5DJncCqXAN6LhXVERCw/usG2MmCMLSIx9/kwt8bwhUmitOXc6fpT7SmFvRAtvxg84wUkg4Y/Gx++0j0z6StSeN0EJz150jaHG6WV4HUqaWTb98Tm90IgXAU4AW2GBOlzFPiU5IY9jt+eXC2Q6yC/ZpTL1LAcnL3Qa/OgLrHN0wiw1KFGD51WRPQ0Sh7QIDAQABo4IBJTCCASEwHQYDVR0OBBYEFLV2DDARzseSQk1Mx1wsyKkM6AtkMB8GA1UdIwQYMBaAFOWdWTCCR1jMrPoIVDaGezq1BE3wMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vT21uaXJvb3QyMDI1LmNybDAqBgNVHSAEIzAhMAgGBmeBDAECATAIBgZngQwBAgIwCwYJKwYBBAGCNyoBMA0GCSqGSIb3DQEBCwUAA4IBAQCfK76SZ1vae4qt6P+dTQUO7bYNFUHR5hXcA2D59CJWnEj5na7aKzyowKvQupW4yMH9fGNxtsh6iJswRqOOfZYC4/giBO/gNsBvwr8uDW7t1nYoDYGHPpvnpxCM2mYfQFHq576/TmeYu1RZY29C4w8xYBlkAA8mDJfRhMCmehk7cN5FJtyWRj2cZj/hOoI45TYDBChXpOlLZKIYiG1giY16vhCRi6zmPzEwv+tk156N6cGSVm44jTQ/rs1sa0JSYjzUaYngoFdZC4OfxnIkQvUIA4TOFmPzNPEFdjcZsgbeEz4TcGHTBPK4R28F44qIMCtHRV55VMX53ev6P3hRddJb"; + protected const string MICROSOFT_COM_CERT_ROOT = "MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp"; +} + +public class CertPinningTest : CertPinningTestBase +{ + + private RemoteCertificateValidationCallback duoPinner; + public CertPinningTest() + { + duoPinner = CertificatePinnerFactory.GetDuoCertificatePinner(); + } + + [Fact] + public void TestReadCertFile() + { + Assert.Equal(10, CertificatePinnerFactory.ReadCertsFromFile().Length); + } + + [Fact] + public void TestSuccess() + { + Assert.True(duoPinner(null, DuoApiServerCert(), DuoApiChain(), SslPolicyErrors.None)); + } + + [Fact] + public void TestNullCertificate() + { + Assert.False(duoPinner(null, null, DuoApiChain(), SslPolicyErrors.None)); + } + + [Fact] + public void TestNullChain() + { + Assert.False(duoPinner(null, DuoApiServerCert(), null, SslPolicyErrors.None)); + } + + [Fact] + public void TestFatalSslError() + { + Assert.False(duoPinner(null, DuoApiServerCert(), DuoApiChain(), SslPolicyErrors.RemoteCertificateNameMismatch)); + } + + [Fact] + public void TestUnmatchedRoot() + { + Assert.False(duoPinner(null, DuoApiServerCert(), MicrosoftComChain(), SslPolicyErrors.None)); + } + + [Fact] + public void TestAlternateCertsSuccess() + { + var certCollection = new X509Certificate2Collection + { + CertFromString(MICROSOFT_COM_CERT_ROOT) + }; + + var pinner = new CertificatePinnerFactory(certCollection).GetPinner(); + + Assert.True(pinner(null, CertFromString(MICROSOFT_COM_CERT_SERVER), MicrosoftComChain(), SslPolicyErrors.None)); + } +} + +public class CertDisablingTest : CertPinningTestBase +{ + private RemoteCertificateValidationCallback pinner; + + public CertDisablingTest() + { + pinner = CertificatePinnerFactory.GetCertificateDisabler(); + } + + [Fact] + public void TestSuccess() + { + Assert.True(pinner(null, DuoApiServerCert(), DuoApiChain(), SslPolicyErrors.None)); + } + + [Fact] + public void TestNullCertificate() + { + Assert.True(pinner(null, null, DuoApiChain(), SslPolicyErrors.None)); + } + + [Fact] + public void TestNullChain() + { + Assert.True(pinner(null, DuoApiServerCert(), null, SslPolicyErrors.None)); + } + + [Fact] + public void TestFatalSslError() + { + Assert.True(pinner(null, DuoApiServerCert(), DuoApiChain(), SslPolicyErrors.RemoteCertificateNameMismatch)); + } + + [Fact] + public void TestUnmatchedRoot() + { + Assert.True(pinner(null, DuoApiServerCert(), MicrosoftComChain(), SslPolicyErrors.None)); + } +} \ No newline at end of file diff --git a/test/DuoApiTest.csproj b/test/DuoApiTest.csproj index 96608cc..9aaa12f 100644 --- a/test/DuoApiTest.csproj +++ b/test/DuoApiTest.csproj @@ -9,8 +9,8 @@ {6B97B9FB-E553-494C-BD50-4BF7DB5C2184} Library Properties - test - test + DuoApiTest + DuoApiTest v4.8 512 @@ -85,6 +85,7 @@ + From 3fdc1e75e4283cd4738dddf542e283bf38397f08 Mon Sep 17 00:00:00 2001 From: spencermaxfield Date: Thu, 8 Dec 2022 15:27:28 -0500 Subject: [PATCH 2/2] Update test workflow to reflect new test assembly name --- .github/workflows/net-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/net-ci.yml b/.github/workflows/net-ci.yml index 07a524b..2c88397 100644 --- a/.github/workflows/net-ci.yml +++ b/.github/workflows/net-ci.yml @@ -53,4 +53,4 @@ jobs: run: msbuild.exe duo_api_csharp.sln - name: Run Tests dll - run: vstest.console.exe .\test\bin\Debug\test.dll + run: vstest.console.exe .\test\bin\Debug\DuoApiTest.dll