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 @@