From b429f6908d0e4e8c3ba944b414f33fc1db6c32ab Mon Sep 17 00:00:00 2001 From: Chad Scharf <3904944+cscharf@users.noreply.github.com> Date: Wed, 9 Sep 2020 11:32:33 -0400 Subject: [PATCH] Added X.509 cert validation copy value buttons (#923) --- .../Portal/Models/SsoConfigDataViewModel.cs | 43 +++++++- bitwarden_license/src/Portal/Sass/site.scss | 5 + .../src/Portal/Views/Sso/Index.cshtml | 100 +++++++++++++----- src/Core/Resources/SharedResources.en.resx | 39 +++++++ 4 files changed, 160 insertions(+), 27 deletions(-) diff --git a/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs b/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs index bb7bdb6792..a9a7edb876 100644 --- a/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs +++ b/bitwarden_license/src/Portal/Models/SsoConfigDataViewModel.cs @@ -6,6 +6,10 @@ using Bit.Core; using Bit.Core.Models.Data; using Bit.Core.Enums; using Bit.Core.Sso; +using U2F.Core.Utils; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text.RegularExpressions; namespace Bit.Portal.Models { @@ -144,6 +148,35 @@ namespace Bit.Portal.Models yield return new ValidationResult(i18nService.GetLocalizedHtmlString("IdpSingleSignOnServiceUrlValidationError"), new[] { nameof(model.IdpSingleSignOnServiceUrl) }); } + if (!string.IsNullOrWhiteSpace(model.IdpX509PublicCert)) + { + // Validate the certificate is in a valid format + ValidationResult failedResult = null; + try + { + var certData = StripPemCertificateElements(model.IdpX509PublicCert).Base64StringToByteArray(); + new X509Certificate2(certData); + } + catch (FormatException) + { + failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertInvalidFormatValidationError"), + new[] { nameof(model.IdpX509PublicCert) }); + } + catch (CryptographicException cryptoEx) + { + failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertCryptographicExceptionValidationError", cryptoEx.Message), + new[] { nameof(model.IdpX509PublicCert) }); + } + catch (Exception ex) + { + failedResult = new ValidationResult(i18nService.GetLocalizedHtmlString("IdpX509PublicCertValidationError", ex.Message), + new[] { nameof(model.IdpX509PublicCert) }); + } + if (failedResult != null) + { + yield return failedResult; + } + } } } @@ -162,7 +195,7 @@ namespace Bit.Portal.Models IdpSingleSignOnServiceUrl = IdpSingleSignOnServiceUrl, IdpSingleLogoutServiceUrl = IdpSingleLogoutServiceUrl, IdpArtifactResolutionServiceUrl = IdpArtifactResolutionServiceUrl, - IdpX509PublicCert = IdpX509PublicCert, + IdpX509PublicCert = StripPemCertificateElements(IdpX509PublicCert), IdpOutboundSigningAlgorithm = IdpOutboundSigningAlgorithm, IdpAllowUnsolicitedAuthnResponse = IdpAllowUnsolicitedAuthnResponse, IdpDisableOutboundLogoutRequests = IdpDisableOutboundLogoutRequests, @@ -174,5 +207,13 @@ namespace Bit.Portal.Models SpValidateCertificates = SpValidateCertificates, }; } + + private string StripPemCertificateElements(string certificateText) + { + return Regex.Replace(certificateText, + @"(((BEGIN|END) CERTIFICATE)|([\-\n\r\t\s\f]))", + string.Empty, + RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } } } diff --git a/bitwarden_license/src/Portal/Sass/site.scss b/bitwarden_license/src/Portal/Sass/site.scss index a6f821056a..6c09fd92aa 100644 --- a/bitwarden_license/src/Portal/Sass/site.scss +++ b/bitwarden_license/src/Portal/Sass/site.scss @@ -224,3 +224,8 @@ input, select, textarea { .alert.validation-summary-errors > ul { margin-bottom: 0; } + +.input-validation-error { + border: solid 1px $danger; + border-color: $danger; +} \ No newline at end of file diff --git a/bitwarden_license/src/Portal/Views/Sso/Index.cshtml b/bitwarden_license/src/Portal/Views/Sso/Index.cshtml index 97e2fc1ec5..a1cc17b44a 100644 --- a/bitwarden_license/src/Portal/Views/Sso/Index.cshtml +++ b/bitwarden_license/src/Portal/Views/Sso/Index.cshtml @@ -25,6 +25,16 @@ $('#Data_ConfigType').change(function () { toggleVisibility(); }); + + $('.copy-button').on('click', function () { + const $control = $(this).closest('.form-group').find('input[type="text"], textarea'); + if ($control.length > 0) { + const elem = $control[0]; + elem.select(); + elem.setSelectionRange(0, $control.val().length); + document.execCommand('copy'); + } + }); }); } @@ -42,7 +52,7 @@
-
+
@@ -53,47 +63,64 @@

@i18nService.T("OpenIdConnectConfig")

-
+
- +
+ +
+ +
+
-
+
- +
+ +
+ +
+
-
-
+
-
+
-
+
-
+
-
+
@@ -108,33 +135,53 @@

@i18nService.T("SamlSpConfig")

-
+
- +
+ +
+ +
+
-
+
- +
+ +
+ +
+
+
+
-
+
-
+
-
+
@@ -159,46 +206,47 @@

@i18nService.T("SamlIdpConfig")

-
+
-
+
-
+
-
+
-
+
-
+
- + +
-
+
diff --git a/src/Core/Resources/SharedResources.en.resx b/src/Core/Resources/SharedResources.en.resx index 7acb16b51b..bec85190ee 100644 --- a/src/Core/Resources/SharedResources.en.resx +++ b/src/Core/Resources/SharedResources.en.resx @@ -276,15 +276,19 @@ Not Configured + A SAML Name ID format Unspecified + A SAML Name ID format Email Address + A SAML Name ID format X.509 Subject Name + A SAML Name ID format Windows Domain Qualified Name @@ -297,9 +301,11 @@ Persistent + A SAML Name ID format Transient + A SAML Name ID format Private Key @@ -448,4 +454,37 @@ Welcome to the Bitwarden Business Portal + + The IdP public certificate provided is invalid: {0} + + + The IdP public certificate provided is not a valid Base64 encoded string, contains illegal characters or whitespace, or is incomplete. + + + The IdP public certificate provided does not appear to be a valid certificate, please ensure this is a valid, Base64 encoded PEM or CER format public certificate valid for signing: {0} + + + Copy the OIDC callback path to your clipboard + + + Copy the OIDC signed out callback path to your clipboard + + + Copy the SP Entity Id to your clipboard + + + Copy the Assertion Consumer Service (ACS) URL to your clipboard + + + Redirect + A SAML binding type, Redirect + + + HTTP POST + A SAML binding type, HTTP POST + + + Artifact + A SAML binding type, Artifact +