From cf60bceb44a5625772108a998ab56d23c92c3730 Mon Sep 17 00:00:00 2001 From: Joe Shook Date: Fri, 26 Dec 2025 15:11:15 -0800 Subject: [PATCH 1/5] Adding a BcSMIMECryptographer This is a first push. Some clean up included. I am testing this on a Windows 11 machine and the expected PKCS#1 v1.5 padding was expected with the original SMIMECryptographer. But it is not. There is a suspicion that new Windows fixes the NIST 800-131A rev 2 recomendation. I am going to now test this on an older Windows version. --- csharp/agent/agent.csproj | 2 +- .../Cryptography/BcSMIMECryptographer.cs | 178 ++++++++++++++ csharp/common/common.csproj | 2 +- csharp/config/console/config.console.csproj | 2 +- csharp/context.receiver.tests/App.config | 21 -- .../BadContext/ContextMissing.eml | 21 -- .../BadContext/ContextUnparsablePatient.eml | 34 --- .../BadContext/ContextUnparsableType.eml | 34 --- .../ContextTestFiles/BadContext/DSN.eml | 31 --- .../ContextTestFiles/BadContext/MDN.eml | 29 --- .../BadContext/NoDirectContextHeader.eml | 9 - .../ContextHL7.Default.txtBase64 | 37 --- .../ContextSimple.PatienIdOnly.txtDefault | 32 --- .../ContextTestFiles/ContextSimple1.txtBase64 | 34 --- .../ContextTestFiles/ContextSimple1.txtBinary | 35 --- .../ContextSimple1.txtDefault | 34 --- .../ContextSimple1.txtEightBit | 35 --- .../ContextSimple1.txtQuotedPrintable | 36 --- .../ContextSimple1.txtSevenBit | 35 --- .../ContextSimple1.txtUUEncode | 38 --- .../Properties/AssemblyInfo.cs | 36 --- csharp/context.receiver.tests/TestReceiver.cs | 223 ------------------ .../context.loopback.receiver.tests.csproj | 85 ------- csharp/context.receiver/Extensions.cs | 17 -- csharp/context.receiver/LoopBackContext.cs | 186 --------------- .../context.receiver/PongContextSettings.cs | 46 ---- .../Properties/AssemblyInfo.cs | 18 -- csharp/context.receiver/README.md | 56 ----- .../context.loopback.receiver.csproj | 29 --- .../hsmCryptographer/HsmCryptographer.csproj | 2 +- csharp/policy/policy.csproj | 2 +- .../trust.bundler/bundle/trust.bundle.csproj | 2 +- .../bundle.tests/trust.bundle.tests.csproj | 2 +- .../Cryptography/OaepEncryptionFacts.cs | 162 +++++++++++++ csharp/unittests/common/common.tests.csproj | 2 +- .../hsmCryptographer.tests.csproj | 2 +- 36 files changed, 349 insertions(+), 1200 deletions(-) create mode 100644 csharp/common/Cryptography/BcSMIMECryptographer.cs delete mode 100644 csharp/context.receiver.tests/App.config delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextMissing.eml delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextUnparsablePatient.eml delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextUnparsableType.eml delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/BadContext/DSN.eml delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/BadContext/MDN.eml delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/BadContext/NoDirectContextHeader.eml delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextHL7.Default.txtBase64 delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextSimple.PatienIdOnly.txtDefault delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtBase64 delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtBinary delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtDefault delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtEightBit delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtQuotedPrintable delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtSevenBit delete mode 100644 csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtUUEncode delete mode 100644 csharp/context.receiver.tests/Properties/AssemblyInfo.cs delete mode 100644 csharp/context.receiver.tests/TestReceiver.cs delete mode 100644 csharp/context.receiver.tests/context.loopback.receiver.tests.csproj delete mode 100644 csharp/context.receiver/Extensions.cs delete mode 100644 csharp/context.receiver/LoopBackContext.cs delete mode 100644 csharp/context.receiver/PongContextSettings.cs delete mode 100644 csharp/context.receiver/Properties/AssemblyInfo.cs delete mode 100644 csharp/context.receiver/README.md delete mode 100644 csharp/context.receiver/context.loopback.receiver.csproj create mode 100644 csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs diff --git a/csharp/agent/agent.csproj b/csharp/agent/agent.csproj index f8ccad4..66ed7a7 100644 --- a/csharp/agent/agent.csproj +++ b/csharp/agent/agent.csproj @@ -45,6 +45,6 @@ - + \ No newline at end of file diff --git a/csharp/common/Cryptography/BcSMIMECryptographer.cs b/csharp/common/Cryptography/BcSMIMECryptographer.cs new file mode 100644 index 0000000..f74261b --- /dev/null +++ b/csharp/common/Cryptography/BcSMIMECryptographer.cs @@ -0,0 +1,178 @@ +/* + Copyright (c) 2025, Direct Project + All rights reserved. + + Authors: + GitHub Copilot + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +using System; +using System.Net.Mime; +using System.Security.Cryptography.X509Certificates; +using Health.Direct.Common.Mail; +using Health.Direct.Common.Mime; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Cms; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Security; + +namespace Health.Direct.Common.Cryptography +{ + /// + /// S/MIME cryptographer using BouncyCastle for CMS operations, enabling RSAES-OAEP on .NET Framework 4.8. + /// + public class BcSMIMECryptographer : ISmimeCryptographer + { + public event Action Error; + public event Action Warning + { + add { } + remove { } + } + + /// + /// For non-critical operations, delegate to the default software cryptographer. + /// + public ISmimeCryptographer DefaultCryptographer { get; set; } = SMIMECryptographer.Default; + + public EncryptionAlgorithm EncryptionAlgorithm { get; set; } = EncryptionAlgorithm.AES128; + public DigestAlgorithm DigestAlgorithm { get; set; } = DigestAlgorithm.SHA256; + public bool IncludeMultipartEpilogueInSignature { get; set; } = true; + public X509IncludeOption IncludeCertChainInSignature { get; set; } = X509IncludeOption.EndCertOnly; + + public MimeEntity Encrypt(MimeEntity entity, X509Certificate2 encryptingCertificate) + { + return Encrypt(entity, new X509Certificate2Collection(encryptingCertificate)); + } + + public MimeEntity Encrypt(MimeEntity entity, X509Certificate2Collection encryptingCertificates) + { + if (entity == null) + { + throw new EncryptionException(EncryptionError.NullEntity); + } + if (encryptingCertificates == null || encryptingCertificates.Count == 0) + { + throw new EncryptionException(EncryptionError.NoCertificates); + } + + try + { + byte[] messageBytes = DefaultSerializer.Default.SerializeToBytes(entity); + byte[] encryptedBytes = EncryptBytesWithBc(messageBytes, encryptingCertificates); + + MimeEntity encryptedEntity = new MimeEntity + { + ContentType = SMIMEStandard.EncryptedEnvelopeContentTypeHeaderValue, + ContentTransferEncoding = TransferEncoding.Base64.AsString(), + Body = new Body(Convert.ToBase64String(encryptedBytes, Base64FormattingOptions.InsertLineBreaks)) + }; + encryptedEntity.ContentDisposition = SMIMEStandard.EncryptedEnvelopeDisposition; + return encryptedEntity; + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + public MimeEntity DecryptEntity(byte[] encryptedBytes, X509Certificate2 decryptingCertificate) + { + try + { + return DefaultCryptographer.DecryptEntity(encryptedBytes, decryptingCertificate); + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + public SignedEntity Sign(Message message, X509Certificate2Collection signingCertificates) + { + return DefaultCryptographer.Sign(message, signingCertificates); + } + + public SignedEntity Sign(MimeEntity entity, X509Certificate2 signingCertificate) + { + return DefaultCryptographer.Sign(entity, signingCertificate); + } + + public SignedEntity Sign(MimeEntity entity, X509Certificate2Collection signingCertificates) + { + return DefaultCryptographer.Sign(entity, signingCertificates); + } + + public System.Security.Cryptography.Pkcs.SignedCms DeserializeDetachedSignature(SignedEntity entity) + { + return DefaultCryptographer.DeserializeDetachedSignature(entity); + } + + public System.Security.Cryptography.Pkcs.SignedCms DeserializeEnvelopedSignature(MimeEntity envelopeEntity) + { + return DefaultCryptographer.DeserializeEnvelopedSignature(envelopeEntity); + } + + public byte[] GetEncryptedBytes(MimeEntity encryptedEntity) + { + return DefaultCryptographer.GetEncryptedBytes(encryptedEntity); + } + + private byte[] EncryptBytesWithBc(byte[] content, X509Certificate2Collection encryptingCertificates) + { + CmsEnvelopedDataGenerator gen = new CmsEnvelopedDataGenerator(); + + // OAEP parameters for RSA (SHA-256, MGF1 with SHA-256, pSource=PSpecified with empty string) + AlgorithmIdentifier oaepParams = new AlgorithmIdentifier( + PkcsObjectIdentifiers.IdRsaesOaep, + new RsaesOaepParameters( + new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256), + new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256)), + new AlgorithmIdentifier(PkcsObjectIdentifiers.IdPSpecified, new DerOctetString(new byte[0])) + ) + ); + + foreach (X509Certificate2 cert in encryptingCertificates) + { + var bcCert = DotNetUtilities.FromX509Certificate(cert); + // Use Asn1KeyWrapper with OAEP algorithm and public key + var keyWrapper = new Asn1KeyWrapper(oaepParams, bcCert.GetPublicKey()); + var recipGen = new KeyTransRecipientInfoGenerator(bcCert, keyWrapper); + gen.AddRecipientInfoGenerator(recipGen); + } + + string symAlgTag = GetSymmetricEncryptionTag(EncryptionAlgorithm); + CmsProcessableByteArray data = new CmsProcessableByteArray(content); + var encrypted = gen.Generate(data, symAlgTag); + return encrypted.GetEncoded(); + } + + private static string GetSymmetricEncryptionTag(EncryptionAlgorithm alg) + { + switch (alg) + { + case EncryptionAlgorithm.AES128: + return CmsEnvelopedGenerator.Aes128Cbc; + case EncryptionAlgorithm.AES192: + return CmsEnvelopedGenerator.Aes192Cbc; + case EncryptionAlgorithm.AES256: + return CmsEnvelopedGenerator.Aes256Cbc; + default: + return CmsEnvelopedGenerator.Aes128Cbc; + } + } + } +} diff --git a/csharp/common/common.csproj b/csharp/common/common.csproj index 890c7e3..7f87695 100644 --- a/csharp/common/common.csproj +++ b/csharp/common/common.csproj @@ -49,7 +49,7 @@ - + diff --git a/csharp/config/console/config.console.csproj b/csharp/config/console/config.console.csproj index a6e4a4c..82aae26 100644 --- a/csharp/config/console/config.console.csproj +++ b/csharp/config/console/config.console.csproj @@ -49,7 +49,7 @@ - + diff --git a/csharp/context.receiver.tests/App.config b/csharp/context.receiver.tests/App.config deleted file mode 100644 index 122c732..0000000 --- a/csharp/context.receiver.tests/App.config +++ /dev/null @@ -1,21 +0,0 @@ - - - -
-
-
- - - - - - - - - - - - - - - diff --git a/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextMissing.eml b/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextMissing.eml deleted file mode 100644 index ea130f5..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextMissing.eml +++ /dev/null @@ -1,21 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 10:55:39 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: "Jean" -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-zYT9GDXFXEDRaBnZ8nRxKw==" - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-zYT9GDXFXEDRaBnZ8nRxKw==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextUnparsablePatient.eml b/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextUnparsablePatient.eml deleted file mode 100644 index 972ca9e..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextUnparsablePatient.eml +++ /dev/null @@ -1,34 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 10:55:39 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: "Jean" -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-zYT9GDXFXEDRaBnZ8nRxKw==" - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -patient-id: 2.16.840.1.113883.19.999999:123456; - 2.16.840.1.113883.19.888888:75774 -type: radiology/report -purpose: research -patient: Name=John; surname=Doe; middleName=Jacob; dateOfBirth=1961-12-31; gender=M; postalCode=12345 - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-zYT9GDXFXEDRaBnZ8nRxKw==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextUnparsableType.eml b/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextUnparsableType.eml deleted file mode 100644 index 8f561b7..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/BadContext/ContextUnparsableType.eml +++ /dev/null @@ -1,34 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 10:55:39 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: "Jean" -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-zYT9GDXFXEDRaBnZ8nRxKw==" - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -patient-id: 2.16.840.1.113883.19.999999:123456; - 2.16.840.1.113883.19.888888:75774 -type: bad/report -purpose: research -patient: givenName=John; surname=Doe; middleName=Jacob; dateOfBirth=1961-12-31; gender=M; postalCode=12345 - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-zYT9GDXFXEDRaBnZ8nRxKw==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/BadContext/DSN.eml b/csharp/context.receiver.tests/ContextTestFiles/BadContext/DSN.eml deleted file mode 100644 index 8b1a9e1..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/BadContext/DSN.eml +++ /dev/null @@ -1,31 +0,0 @@ -MIME-Version: 1.0 -To: Joe -From: "Jean" -Content-Transfer-Encoding: 7bit -Content-Type: multipart/report; - boundary="fb90c8abb88a4d37b6f7f600b3512ac5"; - report-type=disposition-notification -Subject: dispatched:Test Subject 9e273dc5-bc56-4720-8232-021d981a9788 -Message-ID: <1b3f199374444a39948dc7b22b13b709@direct.west.hobo.lab> -Content-Class: urn:content-classes:message -Date: Sat, 14 Oct 2017 04:02:24 -0700 - -This is a multi-part message in MIME format. - ---fb90c8abb88a4d37b6f7f600b3512ac5 -Content-Transfer-Encoding: 7bit -Content-Type: text/plain; - charset="iso-8859-1" - -Message Delivery Notification ---fb90c8abb88a4d37b6f7f600b3512ac5 -Content-Transfer-Encoding: 7bit -Content-Type: message/disposition-notification - -Disposition:automatic-action/MDN-sent-automatically;dispatched -X-DIRECT-FINAL-DESTINATION-DELIVERY: -Reporting-UA:direct.west.hobo.lab;Direct Security Agent -Original-Message-ID: -Final-Recipient:rfc822;"Jean" - ---fb90c8abb88a4d37b6f7f600b3512ac5-- diff --git a/csharp/context.receiver.tests/ContextTestFiles/BadContext/MDN.eml b/csharp/context.receiver.tests/ContextTestFiles/BadContext/MDN.eml deleted file mode 100644 index 50c27e6..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/BadContext/MDN.eml +++ /dev/null @@ -1,29 +0,0 @@ -MIME-Version: 1.0 -To: Joe -From: "Jean" -Content-Transfer-Encoding: 7bit -Content-Type: multipart/report; - boundary="1c1983cc46bd440eaa7aebab441cf876"; - report-type=disposition-notification -Subject: processed:Test Subject 40bec126-77d3-4364-9e63-b98c282209ba -Message-ID: -Date: Sat, 14 Oct 2017 04:02:25 -0700 - -This is a multi-part message in MIME format. - ---1c1983cc46bd440eaa7aebab441cf876 -Content-Transfer-Encoding: 7bit -Content-Type: text/plain; - charset="iso-8859-1" - -Message Delivery Notification ---1c1983cc46bd440eaa7aebab441cf876 -Content-Transfer-Encoding: 7bit -Content-Type: message/disposition-notification - -Disposition:automatic-action/MDN-sent-automatically;processed -Reporting-UA:Direct.West.Hobo.Lab;Direct Security Agent -Original-Message-ID: -Final-Recipient:rfc822;Jean@opsstation.lab - ---1c1983cc46bd440eaa7aebab441cf876-- diff --git a/csharp/context.receiver.tests/ContextTestFiles/BadContext/NoDirectContextHeader.eml b/csharp/context.receiver.tests/ContextTestFiles/BadContext/NoDirectContextHeader.eml deleted file mode 100644 index 7c18ff2..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/BadContext/NoDirectContextHeader.eml +++ /dev/null @@ -1,9 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 10:55:39 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: "Jean" -MIME-Version: 1.0 -Content-Type: text/plain - -Simple Body diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextHL7.Default.txtBase64 b/csharp/context.receiver.tests/ContextTestFiles/ContextHL7.Default.txtBase64 deleted file mode 100644 index cb0546d..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextHL7.Default.txtBase64 +++ /dev/null @@ -1,37 +0,0 @@ -From: Joe -Date: Wed, 30 Aug 2017 22:17:46 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: Jean -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-UM9iEJGuFrn47U1X1JORSw==" - ---=-UM9iEJGuFrn47U1X1JORSw== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-UM9iEJGuFrn47U1X1JORSw== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -encapsulation: hl7v2 - ---=-UM9iEJGuFrn47U1X1JORSw== -Content-Type: application/x-direct-encapsulated+hl7v2; name=adt-sample.hl7 -Content-Disposition: attachment; filename=adt-sample.hl7 -Content-Transfer-Encoding: base64 - -TVNIIHxeIH5cJnwgU0VORElOR19BUFBMSUNBVElPTiB8IFNFTkRJTkdfRkFDSUxJVFkgfCBS -RUNFSVZJTkdfQVBQTElDQVRJT04gfCBSRUNFSVZJTkdfRkFDSUxJVFkgfCAyMDExMDYxMzA4 -MzYxNyB8fCBBRFQgXiBBMDEgfCA5MzQ1NzYxMjAxMTA2MTMwODM2MTcgfCBQIHwgMi4zIHx8 -fHwNCkVWTiB8IEEwMSB8IDIwMTEwNjEzMDgzNjE3IHx8fA0KUElEIHwgMSB8fCAxMzU3Njkg -fHwgTU9VU0UgXiBNSUNLRVkgXnx8IDE5MjgxMTE4IHwgTSB8fHwgMTIzIE1haW4gU3QuXl4g -TGFrZSBCdWVuYSBWaXN0YSBeIEZMIF4gMzI4MzAgfHwgKDQwNyk5MzkgLSAxMjg5IF5eXiB0 -aGVNYWluTW91c2VAZGlzbmV5LmNvbSB8fHx8fCAxNzE5IHwgOTk5OTk5OTkgfHx8fHx8fHx8 -fHx8fHx8fHx8fHwNClBWMSB8IDEgfCBPIHx8fHx8Xl5eXl5eXl58Xl5eXl5eXl4= - ---=-UM9iEJGuFrn47U1X1JORSw==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple.PatienIdOnly.txtDefault b/csharp/context.receiver.tests/ContextTestFiles/ContextSimple.PatienIdOnly.txtDefault deleted file mode 100644 index e20da1c..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple.PatienIdOnly.txtDefault +++ /dev/null @@ -1,32 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 10:55:39 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: Jean -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-zYT9GDXFXEDRaBnZ8nRxKw==" - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -patient-id: 2.16.840.1.113883.19.999999:123456 -type: radiology/report -purpose: research - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-zYT9GDXFXEDRaBnZ8nRxKw==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtBase64 b/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtBase64 deleted file mode 100644 index 3fcc03c..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtBase64 +++ /dev/null @@ -1,34 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 11:26:18 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: Jean -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-+EBGwupd+08aVN7a5xg65w==" - ---=-+EBGwupd+08aVN7a5xg65w== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-+EBGwupd+08aVN7a5xg65w== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-Transfer-Encoding: base64 -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -dmVyc2lvbjogMS4wDQppZDogPDIxNDI4NDhAZGlyZWN0LmV4YW1wbGUuY29tPg0KcGF0aWVu -dC1pZDogMi4xNi44NDAuMS4xMTM4ODMuMTkuOTk5OTk5OjEyMzQ1NjsNCiAyLjE2Ljg0MC4x -LjExMzg4My4xOS44ODg4ODg6NzU3NzQNCnR5cGU6IHJhZGlvbG9neS9yZXBvcnQNCnB1cnBv -c2U6IHJlc2VhcmNoDQpwYXRpZW50OiBnaXZlbk5hbWU9Sm9objsgc3VybmFtZT1Eb2U7IG1p -ZGRsZU5hbWU9SmFjb2I7IGRhdGVPZkJpcnRoPTE5NjEtMTItMzE7IGdlbmRlcj1NOyBwb3N0 -YWxDb2RlPTEyMzQ1DQo= - ---=-+EBGwupd+08aVN7a5xg65w== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-+EBGwupd+08aVN7a5xg65w==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtBinary b/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtBinary deleted file mode 100644 index 9034b26..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtBinary +++ /dev/null @@ -1,35 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 11:30:57 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: Jean -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-qOObRauiSrZluuB5HiV5sQ==" - ---=-qOObRauiSrZluuB5HiV5sQ== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-qOObRauiSrZluuB5HiV5sQ== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-Transfer-Encoding: binary -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -patient-id: 2.16.840.1.113883.19.999999:123456; - 2.16.840.1.113883.19.888888:75774 -type: radiology/report -purpose: research -patient: givenName=John; surname=Doe; middleName=Jacob; dateOfBirth=1961-12-31; gender=M; postalCode=12345 - ---=-qOObRauiSrZluuB5HiV5sQ== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-qOObRauiSrZluuB5HiV5sQ==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtDefault b/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtDefault deleted file mode 100644 index 0a13407..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtDefault +++ /dev/null @@ -1,34 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 10:55:39 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: "Jean" -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-zYT9GDXFXEDRaBnZ8nRxKw==" - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -patient-id: 2.16.840.1.113883.19.999999:123456; - 2.16.840.1.113883.19.888888:75774 -type: radiology/report -purpose: research -patient: givenName=John; surname=Doe; middleName=Jacob; dateOfBirth=1961-12-31; gender=M; postalCode=12345 - ---=-zYT9GDXFXEDRaBnZ8nRxKw== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-zYT9GDXFXEDRaBnZ8nRxKw==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtEightBit b/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtEightBit deleted file mode 100644 index 96b82d4..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtEightBit +++ /dev/null @@ -1,35 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 11:27:24 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: Jean -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-ewU/G6Fvh7cR9G7PSTn7PA==" - ---=-ewU/G6Fvh7cR9G7PSTn7PA== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-ewU/G6Fvh7cR9G7PSTn7PA== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-Transfer-Encoding: 8bit -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -patient-id: 2.16.840.1.113883.19.999999:123456; - 2.16.840.1.113883.19.888888:75774 -type: radiology/report -purpose: research -patient: givenName=John; surname=Doe; middleName=Jacob; dateOfBirth=1961-12-31; gender=M; postalCode=12345 - ---=-ewU/G6Fvh7cR9G7PSTn7PA== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-ewU/G6Fvh7cR9G7PSTn7PA==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtQuotedPrintable b/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtQuotedPrintable deleted file mode 100644 index 5d5d51e..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtQuotedPrintable +++ /dev/null @@ -1,36 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 11:30:20 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: Jean -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-1GehjmVTXFcPRv+tjzABIA==" - ---=-1GehjmVTXFcPRv+tjzABIA== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-1GehjmVTXFcPRv+tjzABIA== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-Transfer-Encoding: quoted-printable -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -patient-id: 2.16.840.1.113883.19.999999:123456; - 2.16.840.1.113883.19.888888:75774 -type: radiology/report -purpose: research -patient: givenName=3DJohn; surname=3DDoe; middleName=3DJacob; dateOfBi= -rth=3D1961-12-31; gender=3DM; postalCode=3D12345 - ---=-1GehjmVTXFcPRv+tjzABIA== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-1GehjmVTXFcPRv+tjzABIA==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtSevenBit b/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtSevenBit deleted file mode 100644 index 0a84e73..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtSevenBit +++ /dev/null @@ -1,35 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 11:28:08 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: Jean -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-jXtmR+ujFqamxwK5xzs6tA==" - ---=-jXtmR+ujFqamxwK5xzs6tA== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-jXtmR+ujFqamxwK5xzs6tA== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-Transfer-Encoding: 7bit -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -version: 1.0 -id: <2142848@direct.example.com> -patient-id: 2.16.840.1.113883.19.999999:123456; - 2.16.840.1.113883.19.888888:75774 -type: radiology/report -purpose: research -patient: givenName=John; surname=Doe; middleName=Jacob; dateOfBirth=1961-12-31; gender=M; postalCode=12345 - ---=-jXtmR+ujFqamxwK5xzs6tA== -Content-Type: application/pdf; name=report.pdf -Content-Disposition: attachment; filename=report.pdf -Content-Transfer-Encoding: base64 - -RmFrZSBQREY= - ---=-jXtmR+ujFqamxwK5xzs6tA==-- \ No newline at end of file diff --git a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtUUEncode b/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtUUEncode deleted file mode 100644 index e536a22..0000000 --- a/csharp/context.receiver.tests/ContextTestFiles/ContextSimple1.txtUUEncode +++ /dev/null @@ -1,38 +0,0 @@ -From: Joe -Date: Sat, 08 Jul 2017 11:29:39 -0700 -Subject: Need more memory -Message-Id: <34C73MNYE0U4.6M64WULRWE982@SSORDSK-JSHO02> -To: Jean -X-Direct-Context: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="=-8Oju9BPJiN86XvhLX7iekQ==" - ---=-8Oju9BPJiN86XvhLX7iekQ== -Content-Type: text/plain; charset=utf-8 - -Simple Body ---=-8Oju9BPJiN86XvhLX7iekQ== -Content-Type: text/plain -Content-Disposition: attachment; filename=metadata.txt -Content-Transfer-Encoding: x-uuencode -Content-ID: <2ff6eaec83894520bbb872e5671ff49e@hobo.lab> - -begin 0644 metadata.txt -M=F5R2]R97!O", mimeMessage.Headers["X-Direct-Context"]); - } - - [Theory] - [InlineData("ContextTestFiles\\BadContext\\MDN.eml")] - public void TestConstructContextIgnoreMDN(string file) - { - var smtpMessage = new CDOSmtpMessage(SmtpAgent.Extensions.LoadCDOMessage(file)); - var testFileName = SmtpAgent.Extensions.CreateUniqueFileName(); - - var receiver = new LoopBackContext - { - TestFilename = testFileName - }; - - var settings = new PongContextSettings() - { - PickupFolder = TestPickupFolder - }; - - receiver.Settings = settings; - Assert.True(receiver.Receive(smtpMessage)); - - Assert.False(File.Exists(Path.Combine( - settings.PickupFolder, - testFileName))); - - } - - [Theory] - [InlineData("ContextTestFiles\\BadContext\\DSN.eml")] - public void TestConstructContextIgnorDSN(string file) - { - var smtpMessage = new CDOSmtpMessage(SmtpAgent.Extensions.LoadCDOMessage(file)); - var testFileName = SmtpAgent.Extensions.CreateUniqueFileName(); - - var receiver = new LoopBackContext - { - TestFilename = testFileName - }; - - var settings = new PongContextSettings() - { - PickupFolder = TestPickupFolder - }; - - receiver.Settings = settings; - Assert.True(receiver.Receive(smtpMessage)); - - Assert.False(File.Exists(Path.Combine( - settings.PickupFolder, - testFileName))); - - } - - - [Theory] - [InlineData("ContextTestFiles\\BadContext\\ContextMissing.eml")] - public void TestConstructContextNoContext(string file) - { - var smtpMessage = new CDOSmtpMessage(SmtpAgent.Extensions.LoadCDOMessage(file)); - var testFileName = SmtpAgent.Extensions.CreateUniqueFileName(); - - var receiver = new LoopBackContext - { - TestFilename = testFileName - }; - - var settings = new PongContextSettings() - { - PickupFolder = TestPickupFolder - }; - - receiver.Settings = settings; - Assert.True(receiver.Receive(smtpMessage)); - - var resultMessage = MimeMessage.Load( - Path.Combine( - settings.PickupFolder, - testFileName)); - - Assert.Equal("Object reference not set to an instance of an object.", resultMessage.TextBody); - - } - - [Theory] - [InlineData("ContextTestFiles\\BadContext\\NoDirectContextHeader.eml")] - public void TestConstructContextNoContextId(string file) - { - var smtpMessage = new CDOSmtpMessage(SmtpAgent.Extensions.LoadCDOMessage(file)); - var testFileName = SmtpAgent.Extensions.CreateUniqueFileName(); - - var receiver = new LoopBackContext - { - TestFilename = testFileName - }; - - var settings = new PongContextSettings() - { - PickupFolder = TestPickupFolder - }; - - receiver.Settings = settings; - Assert.True(receiver.Receive(smtpMessage)); - - var resultMessage = MimeMessage.Load( - Path.Combine( - settings.PickupFolder, - testFileName)); - - Assert.Equal("No Context found", resultMessage.TextBody); - } - - - [Theory] - [InlineData("ContextTestFiles\\BadContext\\ContextUnparsableType.eml")] - public void TestConstructContextUnParsableType(string file) - { - var smtpMessage = new CDOSmtpMessage(SmtpAgent.Extensions.LoadCDOMessage(file)); - var testFileName = SmtpAgent.Extensions.CreateUniqueFileName(); - - var receiver = new LoopBackContext - { - TestFilename = testFileName - }; - - var settings = new PongContextSettings() - { - PickupFolder = TestPickupFolder - }; - - receiver.Settings = settings; - Assert.True(receiver.Receive(smtpMessage)); - - var resultMessage = MimeMessage.Load( - Path.Combine( - settings.PickupFolder, - testFileName)); - - Assert.Equal("Context Error=InvalidType", resultMessage.TextBody); - } - - [Theory] - [InlineData("ContextTestFiles\\BadContext\\ContextUnparsablePatient.eml")] - public void TestConstructContextUnParsablePatient(string file) - { - var smtpMessage = new CDOSmtpMessage(SmtpAgent.Extensions.LoadCDOMessage(file)); - var testFileName = SmtpAgent.Extensions.CreateUniqueFileName(); - - var receiver = new LoopBackContext - { - TestFilename = testFileName - }; - - var settings = new PongContextSettings() - { - PickupFolder = TestPickupFolder - }; - - receiver.Settings = settings; - Assert.True(receiver.Receive(smtpMessage)); - - var resultMessage = MimeMessage.Load( - Path.Combine( - settings.PickupFolder, - testFileName)); - - Assert.Equal("Context Error=InvalidPatient", resultMessage.TextBody); - } - } -} diff --git a/csharp/context.receiver.tests/context.loopback.receiver.tests.csproj b/csharp/context.receiver.tests/context.loopback.receiver.tests.csproj deleted file mode 100644 index 34ec897..0000000 --- a/csharp/context.receiver.tests/context.loopback.receiver.tests.csproj +++ /dev/null @@ -1,85 +0,0 @@ - - - net48 - Library - Health.Direct.Context.Loopback.Receiver.tests - Health.Direct.Context.Loopback.Receiver.tests - false - - - - ..\build\libraries\smtpagent_com_interop\Interop.CDO.dll - True - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/csharp/context.receiver/Extensions.cs b/csharp/context.receiver/Extensions.cs deleted file mode 100644 index 5d07b97..0000000 --- a/csharp/context.receiver/Extensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; - -namespace Health.Direct.Context.Loopback.Receiver -{ - public static class Extensions - { - public static Stream ToStream(this string str) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(str); - writer.Flush(); - stream.Position = 0; - return stream; - } - } -} \ No newline at end of file diff --git a/csharp/context.receiver/LoopBackContext.cs b/csharp/context.receiver/LoopBackContext.cs deleted file mode 100644 index aff925f..0000000 --- a/csharp/context.receiver/LoopBackContext.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Mail; -using Health.Direct.Common.Container; -using Health.Direct.Common.Diagnostics; -using Health.Direct.Common.Mail.DSN; -using Health.Direct.Common.Mail.Notifications; -using Health.Direct.Common.Routing; -using Health.Direct.SmtpAgent; -using MimeKit; - -namespace Health.Direct.Context.Loopback.Receiver -{ - /// - /// Test only receiver. - /// The LoopBack receiver receives messages to a specific email address and parses context if it exists. Then is rebuilds the context and sends a reply to the sender with the same context. - /// The value of such a receiver is to allow testing partners to test a context implementation with with the .net RI context implemenation. - /// - /* - - - LoopBackContext - - Health.Direct.Context.Loopback.Receiver.LoopBackContext, Health.Direct.Context.Loopback.Receiver - - c:\inetpub\mailroot\pickup - - - - - ... - - - */ - public class LoopBackContext : IReceiver, IPlugin - { - PongContextSettings m_settings; - - public PongContextSettings Settings - { - get - { - return m_settings; - } - set - { - if (value == null) - { - throw new SmtpAgentException(SmtpAgentError.InvalidPluginRoute); - } - - value.Validate(); - m_settings = value; - } - } - - public void Init(PluginDefinition pluginDef) - { - Settings = pluginDef.DeserializeSettings(); - } - - public bool Receive(ISmtpMessage data) - { - var message = data.GetEnvelope().Message; - - if (message.IsDSN()) - { - Log.For().Debug("Ignore DSN"); - - return true; - } - - if (message.IsMDN()) - { - Log.For().Debug("Ignore MDN"); - - return true; - } - - var directMessage = MimeMessage.Load(message.ToString().ToStream()); - - try - { - if (! directMessage.ContainsDirectContext()) - { - Log.For() - .Warn($"Message does not contain context. MessageId={directMessage.MessageId}"); - - var dsnMessage = ReturnNoContextMessage(directMessage, @"No Context found"); - DropMessage(dsnMessage); - - return true; - } - - var pongMessage = EchoContext.Process(directMessage); - DropMessage(pongMessage); - - Log.For() - .Info($"Message context pong response. MessageId={pongMessage.MessageId} RelatesToMessageId={message.ID}"); - } - catch (Exception ex) - { - Log.For() - .Error($"Message exception. MessageId={message.ID} :: Exception: {ex}"); - - var dsnMessage = ReturnNoContextMessage(directMessage, ex.Message); - DropMessage(dsnMessage); - - } - - return true; - } - - private void DropMessage(MimeMessage pongMessage) - { - using (Stream stream = File.OpenWrite( - Path.Combine( - Settings.PickupFolder, - TestFilename))) - { - pongMessage.WriteTo(stream); - } - } - - private void DropMessage(DSNMessage dsnMessage) - { - using (Stream stream = File.OpenWrite( - Path.Combine( - Settings.PickupFolder, - TestFilename))) - { - dsnMessage.Save(stream); - } - } - - private DSNMessage ReturnNoContextMessage(MimeMessage directMessage, string bodyMessage) - { - var to = new MailAddress(directMessage.From.Mailboxes.Single().ToString()); - - var perMessage = new DSNPerMessage( - to.Host, - directMessage.MessageId); - - var dsnPerRecipients = new List(); - - foreach (var mailboxAddress in directMessage.To.Mailboxes) - { - var dsnPerRecipient = new DSNPerRecipient( - DSNStandard.DSNAction.Failed, - DSNStandard.DSNStatus.Permanent, - "3.3", - new MailAddress(mailboxAddress.ToString()) - ); - - dsnPerRecipients.Add(dsnPerRecipient); - } - - var dsn = new DSN(perMessage, dsnPerRecipients); - dsn.Explanation = bodyMessage; - var postMaster = new MailAddress("Postmaster@" + to.Host); - - var statusMessage = new DSNMessage(to.Address, postMaster.Address, dsn ); - - return statusMessage; - } - - private string m_fileName; - - /// - /// Set a known filename per test - /// - public string TestFilename - { - get - { - var fileName = m_fileName ?? SmtpAgent.Extensions.CreateUniqueFileName(); - m_fileName = null; - - return fileName; - } - set { m_fileName = value; } - } - } -} diff --git a/csharp/context.receiver/PongContextSettings.cs b/csharp/context.receiver/PongContextSettings.cs deleted file mode 100644 index 0dc34fe..0000000 --- a/csharp/context.receiver/PongContextSettings.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.IO; -using System.Xml.Serialization; -using Health.Direct.SmtpAgent; - -namespace Health.Direct.Context.Loopback.Receiver -{ - [XmlType("PongContextSettings")] - public class PongContextSettings - { - public PongContextSettings() - { - } - - /// - /// Smtp pickup folder - /// - [XmlElement("PickupFolder")] - public string PickupFolder - { - get; - set; - } - - [XmlIgnore] - internal bool HasPickupFolder - { - get - { - return !(string.IsNullOrEmpty(PickupFolder)); - } - } - - public void Validate() - { - if (!this.HasPickupFolder) - { - return; - } - - if (!Directory.Exists(PickupFolder)) - { - throw new SmtpAgentException(SmtpAgentError.MailPickupFolderDoesNotExist); - } - } - } -} \ No newline at end of file diff --git a/csharp/context.receiver/Properties/AssemblyInfo.cs b/csharp/context.receiver/Properties/AssemblyInfo.cs deleted file mode 100644 index 8338130..0000000 --- a/csharp/context.receiver/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -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("context.receiver")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] - -// 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("53ff4765-58b8-4747-93e7-6ee9416e2036")] - diff --git a/csharp/context.receiver/README.md b/csharp/context.receiver/README.md deleted file mode 100644 index 365b2be..0000000 --- a/csharp/context.receiver/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Health.Direct.Context.Loopback.Receiver - -## What is Health.Direct.Context.Loopback.Receiver -This is a plugin receiver test receiver. -The LoopBack receiver receives messages and parses context if it exists. Then is rebuilds the context and sends a reply to the sender with the same context. The value of such a receiver is to allow testing partners to test a context implementation with with the .net RI context implemenation. If no context exists a failure DSN with a description is returned. All other failures will result failure DSN's with the best attempt to indicated the failure. - - -## Licesnse Information - -Copyright (c) 2010-2017, Direct Project - All rights reserved. - - Authors: - Joseph Shook Joseph.Shook@Surescripts.com - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -## Installing -Included with Direct-1.3.0.7-NET45_Beta installer. - -### Configuring -Ensure the following two assemblies are in the Direct Project .NET Gateway folder, typically in C:\Program Files\Direct Project .NET Gateway - -- Health.Direct.Context.Loopback.Receiver.dll -- MimeKitLite.dll - -In SmtpAgentConfig.xml add the Health.Direct.Context.Loopback.Receiver plugin reciever to the IncomingRoutes section. Notice the AddressType is LoopBackContext. - -```xml - - - SMTP - C:\inetpub\mailroot\Gateway\incoming - - - LoopBackContext - - Health.Direct.Context.Loopback.Receiver.LoopBackContext, Health.Direct.Context.Loopback.Receiver - - c:\inetpub\mailroot\pickup - - - - -``` - -Now run the cli tool, "ConfigConsole.exe". The following two commands will add a route for only the PingPong@Direct.North.Hobo.Lab email address to the Health.Direct.Context.Loopback.Receiver plugin receiver and set it's status to enabled. After this restart with IISReset.
- -Address_Add PingPong@Direct.North.Hobo.Lab LoopBackContext
-Address_Status_Set PingPong@Direct.North.Hobo.Lab Enabled
diff --git a/csharp/context.receiver/context.loopback.receiver.csproj b/csharp/context.receiver/context.loopback.receiver.csproj deleted file mode 100644 index f8ea153..0000000 --- a/csharp/context.receiver/context.loopback.receiver.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - net48 - Library - Health.Direct.Context.Loopback.Receiver - Health.Direct.Context.Loopback.Receiver - false - - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - \ No newline at end of file diff --git a/csharp/hsmCryptographer/HsmCryptographer.csproj b/csharp/hsmCryptographer/HsmCryptographer.csproj index 39133dc..222509e 100644 --- a/csharp/hsmCryptographer/HsmCryptographer.csproj +++ b/csharp/hsmCryptographer/HsmCryptographer.csproj @@ -24,7 +24,7 @@ - + \ No newline at end of file diff --git a/csharp/policy/policy.csproj b/csharp/policy/policy.csproj index 80135b0..c015c72 100644 --- a/csharp/policy/policy.csproj +++ b/csharp/policy/policy.csproj @@ -28,6 +28,6 @@ - + \ No newline at end of file diff --git a/csharp/tools/trust.bundler/bundle/trust.bundle.csproj b/csharp/tools/trust.bundler/bundle/trust.bundle.csproj index fb87a35..7f5395d 100644 --- a/csharp/tools/trust.bundler/bundle/trust.bundle.csproj +++ b/csharp/tools/trust.bundler/bundle/trust.bundle.csproj @@ -23,7 +23,7 @@ - + diff --git a/csharp/tools/trust.bundler/unittests/bundle.tests/trust.bundle.tests.csproj b/csharp/tools/trust.bundler/unittests/bundle.tests/trust.bundle.tests.csproj index 8f2ab53..823c2bd 100644 --- a/csharp/tools/trust.bundler/unittests/bundle.tests/trust.bundle.tests.csproj +++ b/csharp/tools/trust.bundler/unittests/bundle.tests/trust.bundle.tests.csproj @@ -32,7 +32,7 @@ - + diff --git a/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs b/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs new file mode 100644 index 0000000..b81450a --- /dev/null +++ b/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs @@ -0,0 +1,162 @@ +/* + Copyright (c) 2025, Direct Project + All rights reserved. + + Authors: + GitHub Copilot + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.IO; +using System.Net.Mime; +using System.Security.Cryptography; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; +using Health.Direct.Common.Cryptography; +using Health.Direct.Common.Mail; +using Health.Direct.Common.Mime; +using Org.BouncyCastle.Asn1.Pkcs; +using Xunit; +using Xunit.Abstractions; + +namespace Health.Direct.Common.Tests.Cryptography +{ + public class OaepEncryptionFacts : TestingBase + { + private readonly ITestOutputHelper _output; + + public OaepEncryptionFacts(ITestOutputHelper output) + { + _output = output; + } + + private static X509Certificate2 GenerateSelfSignedEnciphermentCert(string subjectName) + { + using (var rsa = RSA.Create(2048)) + { + var req = new CertificateRequest( + new X500DistinguishedName($"CN={subjectName}"), + rsa, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + var keyUsage = new X509KeyUsageExtension( + X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DataEncipherment, + critical: true); + req.CertificateExtensions.Add(keyUsage); + + var basicConstraints = new X509BasicConstraintsExtension(false, false, 0, true); + req.CertificateExtensions.Add(basicConstraints); + + var notBefore = DateTimeOffset.UtcNow.AddDays(-1); + var notAfter = DateTimeOffset.UtcNow.AddYears(3); + var cert = req.CreateSelfSigned(notBefore, notAfter); + + return cert; + } + } + + private static X509Certificate2 LoadRecipientCert() + { + var baseDir = AppDomain.CurrentDomain.BaseDirectory; + var certPath = Path.Combine(baseDir, "DnsResolver", "DnsTestCerts", "umesh.cer"); + Assert.True(File.Exists(certPath), $"Test recipient certificate not found at {certPath}"); + return new X509Certificate2(certPath); + } + + private static MimeEntity CreatePlainTextEntity(string text) + { + return new MimeEntity + { + ContentType = MimeStandard.MediaType.TextPlain, + ContentTransferEncoding = TransferEncoding.SevenBit.AsString(), + Body = new Body(text) + }; + } + + [Fact] + public void BcCryptographer_Encrypts_With_RSAES_OAEP() + { + var recipient = LoadRecipientCert(); + var entity = CreatePlainTextEntity("Hello OAEP"); + + var crypt = new BcSMIMECryptographer(); + var encryptedEntity = crypt.Encrypt(entity, recipient); + var encryptedBytes = crypt.GetEncryptedBytes(encryptedEntity); + + var cms = new EnvelopedCms(); + cms.Decode(encryptedBytes); + Assert.True(cms.RecipientInfos.Count > 0, "No recipient infos present"); + + var keyAlgOid = cms.RecipientInfos[0].KeyEncryptionAlgorithm.Oid.Value; + _output.WriteLine($"BcSMIMECryptographer KeyEncryptionAlgorithm OID: {keyAlgOid}"); + _output.WriteLine($"Expected RSAES-OAEP OID: {PkcsObjectIdentifiers.IdRsaesOaep.Id}"); + Assert.Equal(PkcsObjectIdentifiers.IdRsaesOaep.Id, keyAlgOid); + } + + [Fact] + public void DefaultSmimeCryptographer_Reports_Key_Encryption_Algorithm() + { + // This test documents what algorithm the default SMIMECryptographer uses. + // On modern .NET Framework 4.8 with recent Windows updates, EnvelopedCms may use OAEP. + // The BcSMIMECryptographer explicitly uses OAEP regardless of Windows policy. + var recipient = LoadRecipientCert(); + var entity = CreatePlainTextEntity("Hello Default"); + + var crypt = SMIMECryptographer.Default; + var encryptedEntity = crypt.Encrypt(entity, recipient); + var encryptedBytes = crypt.GetEncryptedBytes(encryptedEntity); + + var cms = new EnvelopedCms(); + cms.Decode(encryptedBytes); + Assert.True(cms.RecipientInfos.Count > 0, "No recipient infos present"); + + var keyAlgOid = cms.RecipientInfos[0].KeyEncryptionAlgorithm.Oid.Value; + _output.WriteLine($"SMIMECryptographer.Default KeyEncryptionAlgorithm OID: {keyAlgOid}"); + _output.WriteLine($"rsaEncryption (PKCS#1 v1.5) OID: {PkcsObjectIdentifiers.RsaEncryption.Id}"); + _output.WriteLine($"RSAES-OAEP OID: {PkcsObjectIdentifiers.IdRsaesOaep.Id}"); + Assert.NotNull(keyAlgOid); + } + + [Fact] + public void BcEncrypt_OAEP_SHA256_DefaultDecrypt_Roundtrip() + { + var recipient = GenerateSelfSignedEnciphermentCert("OAEP-SHA256-Roundtrip"); + var entity = CreatePlainTextEntity("Hello OAEP Roundtrip"); + + var bc = new BcSMIMECryptographer(); + var encryptedEntity = bc.Encrypt(entity, recipient); + var encryptedBytes = bc.GetEncryptedBytes(encryptedEntity); + + var cms = new EnvelopedCms(); + cms.Decode(encryptedBytes); + Assert.True(cms.RecipientInfos.Count > 0, "No recipient infos present"); + var keyAlgOid = cms.RecipientInfos[0].KeyEncryptionAlgorithm.Oid.Value; + _output.WriteLine($"Encrypted with Bc; KeyEncAlg OID: {keyAlgOid} (OAEP expected)"); + + Exception ex = Record.Exception(() => + { + var decrypted = SMIMECryptographer.Default.DecryptEntity(encryptedBytes, recipient); + Assert.NotNull(decrypted); + Assert.Equal("Hello OAEP Roundtrip", decrypted.Body.Text); + }); + + if (ex != null) + { + _output.WriteLine($"Default Decrypt failed: {ex.GetType().Name} - {ex.Message}"); + Assert.Fail("Default SMIMECryptographer could not decrypt OAEP-SHA256 wrapped content key."); + } + else + { + _output.WriteLine("Default Decrypt succeeded with OAEP-SHA256."); + } + } + } +} diff --git a/csharp/unittests/common/common.tests.csproj b/csharp/unittests/common/common.tests.csproj index b8156d8..639d38d 100644 --- a/csharp/unittests/common/common.tests.csproj +++ b/csharp/unittests/common/common.tests.csproj @@ -93,7 +93,7 @@ - + diff --git a/csharp/unittests/hsmCryptographer.tests/hsmCryptographer.tests.csproj b/csharp/unittests/hsmCryptographer.tests/hsmCryptographer.tests.csproj index eb443a0..18b2b56 100644 --- a/csharp/unittests/hsmCryptographer.tests/hsmCryptographer.tests.csproj +++ b/csharp/unittests/hsmCryptographer.tests/hsmCryptographer.tests.csproj @@ -42,7 +42,7 @@ - + From d222c2f2b906b7097a2628b3ecdee304842c650b Mon Sep 17 00:00:00 2001 From: Joe Shook Date: Sat, 27 Dec 2025 08:33:09 -0800 Subject: [PATCH 2/5] Adding some tests and logging in tests Now I need to decide if I use the new BcSMIMECryptographer, or I change the SMIMECryptographer and then produce a LegacySMIMECryptographer, so I have something to show that I can accept SHA-1 for some time. --- .../Cryptography/OaepEncryptionFacts.cs | 142 +++++++++++++----- 1 file changed, 102 insertions(+), 40 deletions(-) diff --git a/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs b/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs index b81450a..6ce0c22 100644 --- a/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs +++ b/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs @@ -22,7 +22,11 @@ Neither the name of The Direct Project (directproject.org) nor the names of its using Health.Direct.Common.Cryptography; using Health.Direct.Common.Mail; using Health.Direct.Common.Mime; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; using Xunit; using Xunit.Abstractions; @@ -62,15 +66,7 @@ private static X509Certificate2 GenerateSelfSignedEnciphermentCert(string subjec return cert; } } - - private static X509Certificate2 LoadRecipientCert() - { - var baseDir = AppDomain.CurrentDomain.BaseDirectory; - var certPath = Path.Combine(baseDir, "DnsResolver", "DnsTestCerts", "umesh.cer"); - Assert.True(File.Exists(certPath), $"Test recipient certificate not found at {certPath}"); - return new X509Certificate2(certPath); - } - + private static MimeEntity CreatePlainTextEntity(string text) { return new MimeEntity @@ -81,50 +77,79 @@ private static MimeEntity CreatePlainTextEntity(string text) }; } + [Fact] - public void BcCryptographer_Encrypts_With_RSAES_OAEP() + public void BcCryptographer_OAEP_Uses_SHA256_Params() { - var recipient = LoadRecipientCert(); - var entity = CreatePlainTextEntity("Hello OAEP"); + var recipient = GenerateSelfSignedEnciphermentCert("OAEP-SHA256-Params"); + var entity = CreatePlainTextEntity("Hello OAEP SHA256"); - var crypt = new BcSMIMECryptographer(); - var encryptedEntity = crypt.Encrypt(entity, recipient); - var encryptedBytes = crypt.GetEncryptedBytes(encryptedEntity); + var bc = new BcSMIMECryptographer(); + var encryptedEntity = bc.Encrypt(entity, recipient); + var encryptedBytes = bc.GetEncryptedBytes(encryptedEntity); - var cms = new EnvelopedCms(); - cms.Decode(encryptedBytes); - Assert.True(cms.RecipientInfos.Count > 0, "No recipient infos present"); + var asn1 = Org.BouncyCastle.Asn1.Asn1Object.FromByteArray(encryptedBytes); + var contentInfo = Org.BouncyCastle.Asn1.Cms.ContentInfo.GetInstance(asn1); + var envelopedData = Org.BouncyCastle.Asn1.Cms.EnvelopedData.GetInstance(contentInfo.Content); + var recipientInfo = Org.BouncyCastle.Asn1.Cms.RecipientInfo.GetInstance(envelopedData.RecipientInfos[0]); + var ktri = Org.BouncyCastle.Asn1.Cms.KeyTransRecipientInfo.GetInstance(recipientInfo.Info); + var algId = Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier.GetInstance(ktri.KeyEncryptionAlgorithm); - var keyAlgOid = cms.RecipientInfos[0].KeyEncryptionAlgorithm.Oid.Value; - _output.WriteLine($"BcSMIMECryptographer KeyEncryptionAlgorithm OID: {keyAlgOid}"); - _output.WriteLine($"Expected RSAES-OAEP OID: {PkcsObjectIdentifiers.IdRsaesOaep.Id}"); - Assert.Equal(PkcsObjectIdentifiers.IdRsaesOaep.Id, keyAlgOid); + Assert.Equal(PkcsObjectIdentifiers.IdRsaesOaep.Id, algId.Algorithm.Id); + + var oaep = Org.BouncyCastle.Asn1.Pkcs.RsaesOaepParameters.GetInstance(algId.Parameters); + var hashAlg = Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier.GetInstance(oaep.HashAlgorithm); + var mgfAlg = Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier.GetInstance(oaep.MaskGenAlgorithm); + + _output.WriteLine($"Bc OAEP Hash: {hashAlg.Algorithm.Id}"); + _output.WriteLine($"Bc OAEP MGF: {mgfAlg.Algorithm.Id}"); + + Assert.Equal(NistObjectIdentifiers.IdSha256.Id, hashAlg.Algorithm.Id); + // MGF1 OID with SHA-256 parameter + Assert.Equal(PkcsObjectIdentifiers.IdMgf1.Id, mgfAlg.Algorithm.Id); + var mgfParam = Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier.GetInstance(mgfAlg.Parameters); + Assert.Equal(NistObjectIdentifiers.IdSha256.Id, mgfParam.Algorithm.Id); } [Fact] - public void DefaultSmimeCryptographer_Reports_Key_Encryption_Algorithm() + public void DefaultSmimeCryptographer_OAEP_Uses_SHA1() { - // This test documents what algorithm the default SMIMECryptographer uses. - // On modern .NET Framework 4.8 with recent Windows updates, EnvelopedCms may use OAEP. - // The BcSMIMECryptographer explicitly uses OAEP regardless of Windows policy. - var recipient = LoadRecipientCert(); - var entity = CreatePlainTextEntity("Hello Default"); + var recipient = GenerateSelfSignedEnciphermentCert("Default-OAEP-Params"); + var entity = CreatePlainTextEntity("Hello Default OAEP Params"); - var crypt = SMIMECryptographer.Default; - var encryptedEntity = crypt.Encrypt(entity, recipient); - var encryptedBytes = crypt.GetEncryptedBytes(encryptedEntity); + var def = SMIMECryptographer.Default; + var encryptedEntity = def.Encrypt(entity, recipient); + var encryptedBytes = def.GetEncryptedBytes(encryptedEntity); - var cms = new EnvelopedCms(); - cms.Decode(encryptedBytes); - Assert.True(cms.RecipientInfos.Count > 0, "No recipient infos present"); + var asn1 = Org.BouncyCastle.Asn1.Asn1Object.FromByteArray(encryptedBytes); + var contentInfo = Org.BouncyCastle.Asn1.Cms.ContentInfo.GetInstance(asn1); + var envelopedData = Org.BouncyCastle.Asn1.Cms.EnvelopedData.GetInstance(contentInfo.Content); + var recipientInfo = Org.BouncyCastle.Asn1.Cms.RecipientInfo.GetInstance(envelopedData.RecipientInfos[0]); + var ktri = Org.BouncyCastle.Asn1.Cms.KeyTransRecipientInfo.GetInstance(recipientInfo.Info); + var algId = Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier.GetInstance(ktri.KeyEncryptionAlgorithm); - var keyAlgOid = cms.RecipientInfos[0].KeyEncryptionAlgorithm.Oid.Value; - _output.WriteLine($"SMIMECryptographer.Default KeyEncryptionAlgorithm OID: {keyAlgOid}"); - _output.WriteLine($"rsaEncryption (PKCS#1 v1.5) OID: {PkcsObjectIdentifiers.RsaEncryption.Id}"); - _output.WriteLine($"RSAES-OAEP OID: {PkcsObjectIdentifiers.IdRsaesOaep.Id}"); - Assert.NotNull(keyAlgOid); + var keyAlgOid = algId.Algorithm.Id; + _output.WriteLine($"Default KeyEncAlg OID: {keyAlgOid}"); + + + Assert.Equal(PkcsObjectIdentifiers.IdRsaesOaep.Id, keyAlgOid); + var oaep = Org.BouncyCastle.Asn1.Pkcs.RsaesOaepParameters.GetInstance(algId.Parameters); + var hashAlg = Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier.GetInstance(oaep.HashAlgorithm); + var mgfAlg = Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier.GetInstance(oaep.MaskGenAlgorithm); + + _output.WriteLine($"Default OAEP Hash: {hashAlg.Algorithm.Id}"); + _output.WriteLine($"Default OAEP MGF: {mgfAlg.Algorithm.Id}"); + + // Expect legacy SHA-1-based OAEP if using OAEP + Assert.Equal("1.3.14.3.2.26", hashAlg.Algorithm.Id); // SHA-1 + Assert.Equal(PkcsObjectIdentifiers.IdMgf1.Id, mgfAlg.Algorithm.Id); + var mgfParam = Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier.GetInstance(mgfAlg.Parameters); + Assert.Equal("1.3.14.3.2.26", mgfParam.Algorithm.Id); // SHA-1 } + /// + /// Encrypt with BouncyCastle OAEP-SHA256, decrypt with Default SMIMECryptographer + /// [Fact] public void BcEncrypt_OAEP_SHA256_DefaultDecrypt_Roundtrip() { @@ -141,7 +166,7 @@ public void BcEncrypt_OAEP_SHA256_DefaultDecrypt_Roundtrip() var keyAlgOid = cms.RecipientInfos[0].KeyEncryptionAlgorithm.Oid.Value; _output.WriteLine($"Encrypted with Bc; KeyEncAlg OID: {keyAlgOid} (OAEP expected)"); - Exception ex = Record.Exception(() => + var ex = Record.Exception(() => { var decrypted = SMIMECryptographer.Default.DecryptEntity(encryptedBytes, recipient); Assert.NotNull(decrypted); @@ -158,5 +183,42 @@ public void BcEncrypt_OAEP_SHA256_DefaultDecrypt_Roundtrip() _output.WriteLine("Default Decrypt succeeded with OAEP-SHA256."); } } + + /// + /// Encrypt with Default SMIMECryptographer OAEP-SHA1, decrypt with BouncyCastle + /// + [Fact] + public void SMIMECryptographer_OAEP_SHA1_DefaultDecrypt_BCSMIMECryptographer_Roundtrip() + { + var recipient = GenerateSelfSignedEnciphermentCert("OAEP-SHA256-Roundtrip"); + var entity = CreatePlainTextEntity("Hello OAEP Roundtrip"); + + var sc = SMIMECryptographer.Default; + var encryptedEntity = sc.Encrypt(entity, recipient); + var encryptedBytes = sc.GetEncryptedBytes(encryptedEntity); + + var cms = new EnvelopedCms(); + cms.Decode(encryptedBytes); + Assert.True(cms.RecipientInfos.Count > 0, "No recipient infos present"); + var keyAlgOid = cms.RecipientInfos[0].KeyEncryptionAlgorithm.Oid.Value; + _output.WriteLine($"Encrypted with Sc; KeyEncAlg OID: {keyAlgOid} (OAEP expected)"); + + var ex = Record.Exception(() => + { + var decrypted = new BcSMIMECryptographer().DecryptEntity(encryptedBytes, recipient); + Assert.NotNull(decrypted); + Assert.Equal("Hello OAEP Roundtrip", decrypted.Body.Text); + }); + + if (ex != null) + { + _output.WriteLine($"Default Decrypt failed: {ex.GetType().Name} - {ex.Message}"); + Assert.Fail("Default SMIMECryptographer could not decrypt OAEP-SHA256 wrapped content key."); + } + else + { + _output.WriteLine("Default Decrypt succeeded with OAEP-SHA1."); + } + } } } From 339d062875df28f3f1a0a9a246a014402fe8e06d Mon Sep 17 00:00:00 2001 From: Joe Shook Date: Sat, 27 Dec 2025 14:27:20 -0800 Subject: [PATCH 3/5] Obsolete SHA1 as a default digest for signing CMS The previous guidance was to set the default in the smtpagentconfig.xml file but you could still use SHA1. That has now been removed and receiving a SHA1 signed CMS will fail. --- .../common/Cryptography/SMIMECryptographer.cs | 2 +- .../Cryptography/SMIMECryptographerBase.cs | 8 +++--- csharp/unittests/agent/BasicAgentTests.cs | 25 ++++++++++++++++--- csharp/unittests/agent/CryptographerTests.cs | 16 +++++++++--- .../common/Certificates/CertificateFacts.cs | 2 +- .../TestSmtpAgentAuditConfig.xml | 2 +- ...tpAgentAuditConfig_BadAuditor_Defaults.xml | 2 +- .../TestSmtpAgentConfig.xml | 2 +- .../TestSmtpAgentConfigWithCertPolicy.xml | 2 +- .../TestSmtpAgentConfig_InternalRelay.xml | 2 +- 10 files changed, 45 insertions(+), 18 deletions(-) diff --git a/csharp/common/Cryptography/SMIMECryptographer.cs b/csharp/common/Cryptography/SMIMECryptographer.cs index f3f92e2..bc5c7d7 100644 --- a/csharp/common/Cryptography/SMIMECryptographer.cs +++ b/csharp/common/Cryptography/SMIMECryptographer.cs @@ -54,7 +54,7 @@ public event Action Warning /// Initializes an instance with the default set of encryption and digest algorithms. /// public SMIMECryptographer() - : this(EncryptionAlgorithm.AES128, DigestAlgorithm.SHA1) + : this(EncryptionAlgorithm.AES128, DigestAlgorithm.SHA256) { } diff --git a/csharp/common/Cryptography/SMIMECryptographerBase.cs b/csharp/common/Cryptography/SMIMECryptographerBase.cs index 726018f..79d5025 100644 --- a/csharp/common/Cryptography/SMIMECryptographerBase.cs +++ b/csharp/common/Cryptography/SMIMECryptographerBase.cs @@ -88,7 +88,7 @@ public static class CryptoOids { // documentation for these is silly. #pragma warning disable 1591 - public static readonly Oid SHA1 = new Oid("1.3.14.3.2.26"); + // public static readonly Oid SHA1 = new Oid("1.3.14.3.2.26"); // Obsolete public static readonly Oid SHA256 = new Oid("2.16.840.1.101.3.4.2.1"); public static readonly Oid SHA384 = new Oid("2.16.840.1.101.3.4.2.2"); public static readonly Oid SHA512 = new Oid("2.16.840.1.101.3.4.2.3"); @@ -123,10 +123,10 @@ public static Oid ToDigestAlgorithmOid(DigestAlgorithm type) switch (type) { default: - throw new NotSupportedException(); + throw new NotSupportedException($"{type.ToString()} DigestAlgorithm is not supported."); - case DigestAlgorithm.SHA1: - return CryptoOids.SHA1; + // case DigestAlgorithm.SHA1: // obsolete + // return CryptoOids.SHA1; case DigestAlgorithm.SHA256: return CryptoOids.SHA256; diff --git a/csharp/unittests/agent/BasicAgentTests.cs b/csharp/unittests/agent/BasicAgentTests.cs index ab2bbce..85dc657 100644 --- a/csharp/unittests/agent/BasicAgentTests.cs +++ b/csharp/unittests/agent/BasicAgentTests.cs @@ -90,7 +90,7 @@ public void BuildOutgoingFile() public void TestOutgoing(string fileName) { m_tester.AgentA.Cryptographer.EncryptionAlgorithm = EncryptionAlgorithm.AES128; - m_tester.AgentA.Cryptographer.DigestAlgorithm = DigestAlgorithm.SHA1; + m_tester.AgentA.Cryptographer.DigestAlgorithm = DigestAlgorithm.SHA256; OutgoingMessage message = null; Assert.Null(Record.Exception(() => message = m_tester.ProcessOutgoingFile(fileName))); @@ -121,7 +121,8 @@ public void TestOutgoingUntrustedFully(string fileName) // // All recipients are untrusted. The agent should reject the message completely // - Assert.Throws(() => m_tester.ProcessOutgoingFileToString(fileName)); + var ex = Assert.Throws(() => m_tester.ProcessOutgoingFileToString(fileName)); + Assert.False(string.IsNullOrEmpty(ex.Message)); } // @@ -131,6 +132,7 @@ public void TestOutgoingUntrustedFully(string fileName) [MemberData("OutgoingUntrustedFiles")] public void OutgoingUntrusted(string fileName) { + m_tester.AgentA.Cryptographer.DigestAlgorithm = DigestAlgorithm.SHA256; OutgoingMessage outgoing = m_tester.ProcessOutgoingFile(fileName); Assert.True(outgoing.RejectedRecipients.Count > 0); } @@ -143,7 +145,8 @@ public void TestWithMessageObjects() { var message = MimeSerializer.Default.Deserialize(m_tester.ReadMessageText("simple.eml")); - var outgoing = new OutgoingMessage(message); + var outgoing = new OutgoingMessage(message); + m_tester.AgentA.Cryptographer.DigestAlgorithm = DigestAlgorithm.SHA256; outgoing = m_tester.AgentA.ProcessOutgoing(outgoing); Assert.True(outgoing.Message.HasHeader(MailStandard.Headers.Date)); @@ -192,6 +195,14 @@ public void TestEndToEnd(string fileName, EncryptionAlgorithm encryptionAlgorith m_tester.AgentA.Cryptographer.DigestAlgorithm = digestAlgorithm; m_tester.AgentB.Cryptographer.EncryptionAlgorithm = encryptionAlgorithm; m_tester.AgentB.Cryptographer.DigestAlgorithm = digestAlgorithm; + + if (digestAlgorithm == DigestAlgorithm.SHA1) + { + var ex = Assert.Throws(() => m_tester.TestEndToEndFile(fileName)); + Assert.Equal("SHA1 DigestAlgorithm is not supported.", ex.Message); + return; + } + m_tester.TestEndToEndFile(fileName); } @@ -204,6 +215,14 @@ public void TestEndToEndCompat(string fileName, EncryptionAlgorithm encryptionAl { m_tester.AgentA.Cryptographer.EncryptionAlgorithm = encryptionAlgorithm; m_tester.AgentA.Cryptographer.DigestAlgorithm = digestAlgorithm; + + if (digestAlgorithm == DigestAlgorithm.SHA1) + { + var ex = Assert.Throws(() => m_tester.TestEndToEndFile(fileName)); + Assert.Equal("SHA1 DigestAlgorithm is not supported.", ex.Message); + return; + } + m_tester.TestEndToEndFile(fileName); } diff --git a/csharp/unittests/agent/CryptographerTests.cs b/csharp/unittests/agent/CryptographerTests.cs index 01b7b0d..01158ef 100644 --- a/csharp/unittests/agent/CryptographerTests.cs +++ b/csharp/unittests/agent/CryptographerTests.cs @@ -82,10 +82,17 @@ public void TestSignatureOIDs(DigestAlgorithm algo) { string messageText = m_tester.ReadMessageText("simple.eml"); m_cryptographer.DigestAlgorithm = algo; - SignedCms signedData = null; - - signedData = m_cryptographer.CreateSignature(Encoding.ASCII.GetBytes(messageText), new X509Certificate2Collection(m_cert)); - + + if (algo == DigestAlgorithm.SHA1) + { + var ex = Assert.Throws(() => + m_cryptographer.CreateSignature(Encoding.ASCII.GetBytes(messageText), new X509Certificate2Collection(m_cert)) + ); + Assert.Equal("SHA1 DigestAlgorithm is not supported.", ex.Message); + return; + } + + SignedCms signedData = m_cryptographer.CreateSignature(Encoding.ASCII.GetBytes(messageText), new X509Certificate2Collection(m_cert)); Assert.True(signedData.SignerInfos.Count == 1); Assert.True(signedData.SignerInfos[0].DigestAlgorithm.Value == SMIMECryptographer.ToDigestAlgorithmOid(algo).Value); } @@ -96,6 +103,7 @@ public void TestDispositionHeaders() string messageText = m_tester.ReadMessageText("simple.eml"); Message message = MimeSerializer.Default.Deserialize(messageText); + m_cryptographer.DigestAlgorithm = DigestAlgorithm.SHA256; SignedEntity signedEntity = m_cryptographer.Sign(message, m_cert); string disposition = signedEntity.Signature.ContentDisposition; Assert.True(!string.IsNullOrEmpty(disposition)); diff --git a/csharp/unittests/common/Certificates/CertificateFacts.cs b/csharp/unittests/common/Certificates/CertificateFacts.cs index 4dd74e3..69a4686 100644 --- a/csharp/unittests/common/Certificates/CertificateFacts.cs +++ b/csharp/unittests/common/Certificates/CertificateFacts.cs @@ -29,7 +29,7 @@ public void AnchorMetadataSerialize() { AnchorMetadata metadata = new AnchorMetadata(); metadata.RequiredOids = new Oid[] { - new Oid(SMIMECryptographer.CryptoOids.SHA1.Value, "SHA1 Digest Algorithm"), + // new Oid(SMIMECryptographer.CryptoOids.SHA1.Value, "SHA1 Digest Algorithm"), new Oid(SMIMECryptographer.CryptoOids.SHA256.Value, "SHA256 Digest Algorithm"), }; metadata.BundleSource = "Toby's bundle"; diff --git a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentAuditConfig.xml b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentAuditConfig.xml index 3fcfa7b..1ded06c 100644 --- a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentAuditConfig.xml +++ b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentAuditConfig.xml @@ -22,7 +22,7 @@ AES128 - SHA1 + SHA256 diff --git a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentAuditConfig_BadAuditor_Defaults.xml b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentAuditConfig_BadAuditor_Defaults.xml index 6e001a1..b2eddf9 100644 --- a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentAuditConfig_BadAuditor_Defaults.xml +++ b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentAuditConfig_BadAuditor_Defaults.xml @@ -22,7 +22,7 @@ AES128 - SHA1 + SHA256 diff --git a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfig.xml b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfig.xml index 6f0a40f..32c8a29 100644 --- a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfig.xml +++ b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfig.xml @@ -22,7 +22,7 @@ AES128 - SHA1 + SHA256 diff --git a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfigWithCertPolicy.xml b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfigWithCertPolicy.xml index 8042acf..172e79d 100644 --- a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfigWithCertPolicy.xml +++ b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfigWithCertPolicy.xml @@ -22,7 +22,7 @@ AES128 - SHA1 + SHA256 diff --git a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfig_InternalRelay.xml b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfig_InternalRelay.xml index 3e56cb7..1540714 100644 --- a/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfig_InternalRelay.xml +++ b/csharp/unittests/smtpAgent/SmtpAgentTestFiles/TestSmtpAgentConfig_InternalRelay.xml @@ -16,7 +16,7 @@ AES128 - SHA1 + SHA256 From 708191b4d30017889081163f9f2e9af227c0ecc9 Mon Sep 17 00:00:00 2001 From: Joe Shook Date: Sat, 27 Dec 2025 15:24:53 -0800 Subject: [PATCH 4/5] Fixup AdminMvc UI web app. It runs again. --- csharp/admin/AdminMvc/AdminMvc.csproj | 239 +++++++++++++++--- .../Models/Repositories/RepositoryModule.cs | 1 + .../AdminMvc/Properties/launchSettings.json | 19 -- .../AdminMvc/Views/Addresses/AddressList.ascx | 71 ++++-- .../admin/AdminMvc/Views/Addresses/Index.aspx | 3 - .../AdminMvc/Views/Anchors/AnchorList.ascx | 73 ++++-- .../admin/AdminMvc/Views/Anchors/Index.aspx | 7 +- .../Views/Certificates/CertificateList.ascx | 77 +++--- .../AdminMvc/Views/Certificates/Index.aspx | 10 +- .../Views/DnsRecords/DnsRecordList.ascx | 64 +++-- .../AdminMvc/Views/DnsRecords/Index.aspx | 17 +- .../AdminMvc/Views/Domains/DomainList.ascx | 82 ++++-- .../AdminMvc/Views/MdnRecords/Index.aspx | 12 +- .../AdminMvc/Views/MdnRecords/MdnList.ascx | 73 ++++-- csharp/build/DirectProject.sln | 12 +- csharp/config/client/PasswordHash.partial.cs | 13 +- csharp/config/store/PasswordHash.cs | 2 +- csharp/tools/admin.console/App.config | 2 +- 18 files changed, 531 insertions(+), 246 deletions(-) delete mode 100644 csharp/admin/AdminMvc/Properties/launchSettings.json diff --git a/csharp/admin/AdminMvc/AdminMvc.csproj b/csharp/admin/AdminMvc/AdminMvc.csproj index f75840f..0dfa5f7 100644 --- a/csharp/admin/AdminMvc/AdminMvc.csproj +++ b/csharp/admin/AdminMvc/AdminMvc.csproj @@ -1,17 +1,63 @@ - + + + - net48 + Debug + AnyCPU + + + 2.0 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} Library + Properties Health.Direct.Admin.Console Health.Direct.Admin.Console - false - true - true - false + v4.8 true - false + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + true + pdbonly + true + bin\ + TRACE + prompt + 4 - + + + + + + + + + + + + + + + + + + @@ -22,32 +68,163 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + Global.asax + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + PreserveNewest + + + + + + + + + + + + - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {6bc0ce68-3bcf-4be8-8b04-ef2e9f786db3} + common + + + {8b0fea15-5031-4616-89ad-cafd39c70e63} + config.client + + + {a0f8ba3a-ae78-4c89-9d86-c925e53deef5} + diagnostics.nlog + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 8080 + / + http://localhost:8080/ + False + False + + + False + + + + + diff --git a/csharp/admin/AdminMvc/Models/Repositories/RepositoryModule.cs b/csharp/admin/AdminMvc/Models/Repositories/RepositoryModule.cs index 14d0d2d..ae62527 100644 --- a/csharp/admin/AdminMvc/Models/Repositories/RepositoryModule.cs +++ b/csharp/admin/AdminMvc/Models/Repositories/RepositoryModule.cs @@ -36,6 +36,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof (DomainManagerClient))) .Where(t => t.Name.EndsWith("Client")) + .AsSelf() .AsImplementedInterfaces(); } } diff --git a/csharp/admin/AdminMvc/Properties/launchSettings.json b/csharp/admin/AdminMvc/Properties/launchSettings.json deleted file mode 100644 index 52b3e19..0000000 --- a/csharp/admin/AdminMvc/Properties/launchSettings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:8080", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/csharp/admin/AdminMvc/Views/Addresses/AddressList.ascx b/csharp/admin/AdminMvc/Views/Addresses/AddressList.ascx index 906817e..e7956b2 100644 --- a/csharp/admin/AdminMvc/Views/Addresses/AddressList.ascx +++ b/csharp/admin/AdminMvc/Views/Addresses/AddressList.ascx @@ -1,31 +1,56 @@ <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Common"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%= Html.Grid(Model) - .Attributes(@class => "grid ui-widget ui-widget-content") - .HeaderRowAttributes(new Dictionary { { "class", "ui-widget-header" } }) - .Columns( - column => - { - column.For(a => a.DomainID).Visible(ViewData["Domain"] == null).Named("Domain ID"); - column.For(a => a.EmailAddress); - column.For(a => a.DisplayName); - column.For(a => a.Status).Attributes(@class => "status"); - column.For(d => Html.Span(Formatter.Format(d.CreateDate), new { title = d.CreateDate })).Named("Created On"); - column.For(d => Html.Span(Formatter.Format(d.UpdateDate), new { @class="update-date", title = d.UpdateDate })).Named("Updated On"); - column.For(a => Html.ActionLink("View", "Details", new { id = a.ID }, new { @class = "view-details" })); - column.For(a => Html.ActionLink("Edit", "Edit", new { id = a.ID })); - column.For(a => a.IsEnabled - ? Html.ActionLink("Disable", "Disable", new { id = a.ID }, new { @class = "enable-disable-action" }) - : Html.ActionLink("Enable", "Enable", new { id = a.ID }, new { @class = "enable-disable-action" })); - column.For(d => Html.ActionLink("Delete", "Delete", new { id = d.ID }, new { @class = "toolbar-button delete-action" })); - })%> + + + + + + + + + + + + + + <% foreach (var a in Model) { %> + + + + + + + + + + <% } %> + +
Domain IDEmail AddressDisplay NameStatusCreated OnUpdated OnActions
<%= a.DomainID %><%= a.EmailAddress %><%= a.DisplayName %><%= a.Status %><%= Html.Span(Formatter.Format(a.CreateDate), new { title = a.CreateDate }) %><%= Html.Span(Formatter.Format(a.UpdateDate), new { @class = "update-date", title = a.UpdateDate }) %> + <%= Html.ActionLink("View", "Details", new { id = a.ID }, new { @class = "view-details" }) %> | + <%= Html.ActionLink("Edit", "Edit", new { id = a.ID }) %> | + <% if (a.IsEnabled) { %> + <%= Html.ActionLink("Disable", "Disable", new { id = a.ID }, new { @class = "enable-disable-action" }) %> + <% } else { %> + <%= Html.ActionLink("Enable", "Enable", new { id = a.ID }, new { @class = "enable-disable-action" }) %> + <% } %> + | + <%= Html.ActionLink("Delete", "Delete", new { id = a.ID }, new { @class = "toolbar-button delete-action" }) %> +
-<%= Html.Pager((IPagination)Model) %> +<% var paged = Model as Health.Direct.Admin.Console.Models.Pagination.PaginatedList; %> +<% if (paged != null) { %> +
+ <% if (paged.HasPreviousPage) { %> + <%= Html.ActionLink("Prev", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber - 1 }) %> + <% } %> + Page <%= paged.PageNumber %> of <%= paged.PageCount %> + <% if (paged.HasNextPage) { %> + <%= Html.ActionLink("Next", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber + 1 }) %> + <% } %> +
+<% } %> diff --git a/csharp/admin/AdminMvc/Views/Addresses/Index.aspx b/csharp/admin/AdminMvc/Views/Addresses/Index.aspx index 277e870..bc76e52 100644 --- a/csharp/admin/AdminMvc/Views/Addresses/Index.aspx +++ b/csharp/admin/AdminMvc/Views/Addresses/Index.aspx @@ -1,8 +1,5 @@ <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> <%@ Import Namespace="Health.Direct.Admin.Console.Controllers"%> diff --git a/csharp/admin/AdminMvc/Views/Anchors/AnchorList.ascx b/csharp/admin/AdminMvc/Views/Anchors/AnchorList.ascx index 5c107a6..b0e561e 100644 --- a/csharp/admin/AdminMvc/Views/Anchors/AnchorList.ascx +++ b/csharp/admin/AdminMvc/Views/Anchors/AnchorList.ascx @@ -1,32 +1,57 @@ <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Common"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%= Html.Grid(Model) - .Attributes(@class => "grid ui-widget ui-widget-content") - .HeaderRowAttributes(new Dictionary { { "class", "ui-widget-header" } }) - .Columns( - column => - { - column.For(c => c.Owner); - column.For(c => Html.P(c.Thumbprint, new { title = c.Thumbprint, @class = "thumbprint" })) - .Named("Thumbprint"); - column.For(a => a.Status).Attributes(@class => "status"); - column.For(a => Html.Span(Formatter.Format(a.CreateDate), new { title = a.CreateDate })).Named("Created On"); - column.For(a => Html.Span(Formatter.Format(a.ValidStartDate), new { title = a.ValidStartDate })).Named("Valid From"); - column.For(a => Html.Span(Formatter.Format(a.ValidEndDate), new { title = a.ValidEndDate })).Named("Valid Until"); - column.For(a => a.Purpose); - column.For(d => Html.ActionLink("View", "Details", new { d.ID }, new { @class = "view-details" })); - column.For(d => d.IsEnabled - ? Html.ActionLink("Disable", "Disable", new { d.ID }, new { @class = "enable-disable-action" }) - : Html.ActionLink("Enable", "Enable", new { d.ID }, new { @class = "enable-disable-action" })); - column.For(d => Html.ActionLink("Delete", "Delete", new { d.ID }, new { @class = "toolbar-button delete-action" })); - })%> + + + + + + + + + + + + + + + <% foreach (var a in Model) { %> + + + + + + + + + + + <% } %> + +
OwnerThumbprintStatusCreated OnValid FromValid UntilPurposeActions
<%= a.Owner %><%= Html.P(a.Thumbprint, new { title = a.Thumbprint, @class = "thumbprint" }) %><%= a.Status %><%= Html.Span(Formatter.Format(a.CreateDate), new { title = a.CreateDate }) %><%= Html.Span(Formatter.Format(a.ValidStartDate), new { title = a.ValidStartDate }) %><%= Html.Span(Formatter.Format(a.ValidEndDate), new { title = a.ValidEndDate }) %><%= a.Purpose %> + <%= Html.ActionLink("View", "Details", new { a.ID }, new { @class = "view-details" }) %> | + <% if (a.IsEnabled) { %> + <%= Html.ActionLink("Disable", "Disable", new { a.ID }, new { @class = "enable-disable-action" }) %> + <% } else { %> + <%= Html.ActionLink("Enable", "Enable", new { a.ID }, new { @class = "enable-disable-action" }) %> + <% } %> + | + <%= Html.ActionLink("Delete", "Delete", new { a.ID }, new { @class = "toolbar-button delete-action" }) %> +
-<%= Html.Pager((IPagination)Model) %> +<% var paged = Model as Health.Direct.Admin.Console.Models.Pagination.PaginatedList; %> +<% if (paged != null) { %> +
+ <% if (paged.HasPreviousPage) { %> + <%= Html.ActionLink("Prev", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber - 1 }) %> + <% } %> + Page <%= paged.PageNumber %> of <%= paged.PageCount %> + <% if (paged.HasNextPage) { %> + <%= Html.ActionLink("Next", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber + 1 }) %> + <% } %> +
+<% } %> diff --git a/csharp/admin/AdminMvc/Views/Anchors/Index.aspx b/csharp/admin/AdminMvc/Views/Anchors/Index.aspx index 1348407..70bd65d 100644 --- a/csharp/admin/AdminMvc/Views/Anchors/Index.aspx +++ b/csharp/admin/AdminMvc/Views/Anchors/Index.aspx @@ -1,8 +1,5 @@ <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> <%@ Import Namespace="Health.Direct.Admin.Console.Controllers"%> @@ -11,11 +8,11 @@ - <%= Html.Partial(ViewData["Domain"] == null? "AllItemsReminder" : "FilterReminder", "anchors")%> + <%= Html.Partial(ViewData["Domain"] == null ? "AllItemsReminder" : "FilterReminder", "anchors")%>
<%= Html.ActionLink("Add Anchor", "Add", new { domainID = ((DomainModel)ViewData["Domain"] ?? new DomainModel()).ID }, new { @class = "action ui-priority-primary" })%>
- + <%= Html.Partial("AnchorList", Model, ViewData) %> <%= Html.Partial("AnchorDetailsDialog") %> diff --git a/csharp/admin/AdminMvc/Views/Certificates/CertificateList.ascx b/csharp/admin/AdminMvc/Views/Certificates/CertificateList.ascx index 3e4b6ad..50054c3 100644 --- a/csharp/admin/AdminMvc/Views/Certificates/CertificateList.ascx +++ b/csharp/admin/AdminMvc/Views/Certificates/CertificateList.ascx @@ -1,38 +1,55 @@ <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Common"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%= Html.Grid(Model) - .Attributes(@class => "grid ui-widget ui-widget-content") - .HeaderRowAttributes(new Dictionary { { "class", "ui-widget-header" } }) - .Columns( - column => - { - column.For(c => c.Owner); - column.For(c => Html.P(c.Thumbprint, new {title = c.Thumbprint, @class = "thumbprint"})) - .Named("Thumbprint"); - column.For(c => c.Status).Attributes(@class => "status"); - column.For(c => Html.Span(Formatter.Format(c.CreateDate), new {title = c.CreateDate})) - .Named("Created On"); - column.For(c => Html.Span(Formatter.Format(c.ValidStartDate), new {title = c.ValidStartDate})) - .Named("Valid From"); - column.For(c => Html.Span(Formatter.Format(c.ValidEndDate), new {title = c.ValidEndDate})) - .Named("Valid Until"); - column.For(c => Html.ActionLink("View", "Details", new {c.ID}, new {@class = "view-details"})); - column.For(c => c.IsEnabled - ? Html.ActionLink("Disable", "Disable", new {c.ID}, - new {@class = "enable-disable-action"}) - : Html.ActionLink("Enable", "Enable", new {c.ID}, - new {@class = "enable-disable-action"})); + + + + + + + + + + + + + + <% foreach (var c in Model) { %> + + + + + + + + + + <% } %> + +
OwnerThumbprintStatusCreated OnValid FromValid UntilActions
<%= c.Owner %><%= Html.P(c.Thumbprint, new { title = c.Thumbprint, @class = "thumbprint" }) %><%= c.Status %><%= Html.Span(Formatter.Format(c.CreateDate), new { title = c.CreateDate }) %><%= Html.Span(Formatter.Format(c.ValidStartDate), new { title = c.ValidStartDate }) %><%= Html.Span(Formatter.Format(c.ValidEndDate), new { title = c.ValidEndDate }) %> + <%= Html.ActionLink("View", "Details", new { c.ID }, new { @class = "view-details" }) %> | + <% if (c.IsEnabled) { %> + <%= Html.ActionLink("Disable", "Disable", new { c.ID }, new { @class = "enable-disable-action" }) %> + <% } else { %> + <%= Html.ActionLink("Enable", "Enable", new { c.ID }, new { @class = "enable-disable-action" }) %> + <% } %> + | + <%= Html.ActionLink("Delete", "Delete", new { c.ID }, new { @class = "toolbar-button delete-action" }) %> +
- column.For( - c => Html.ActionLink("Delete", "Delete", new {c.ID}, new {@class = "toolbar-button delete-action"})); - })%> - -<%= Html.Pager((IPagination)Model) %> +<% var paged = Model as Health.Direct.Admin.Console.Models.Pagination.PaginatedList; %> +<% if (paged != null) { %> +
+ <% if (paged.HasPreviousPage) { %> + <%= Html.ActionLink("Prev", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber - 1 }) %> + <% } %> + Page <%= paged.PageNumber %> of <%= paged.PageCount %> + <% if (paged.HasNextPage) { %> + <%= Html.ActionLink("Next", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber + 1 }) %> + <% } %> +
+<% } %> diff --git a/csharp/admin/AdminMvc/Views/Certificates/Index.aspx b/csharp/admin/AdminMvc/Views/Certificates/Index.aspx index ee1665b..e730c4b 100644 --- a/csharp/admin/AdminMvc/Views/Certificates/Index.aspx +++ b/csharp/admin/AdminMvc/Views/Certificates/Index.aspx @@ -1,8 +1,5 @@ <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> <%@ Import Namespace="Health.Direct.Admin.Console.Controllers"%> @@ -11,12 +8,11 @@ - <%= Html.Partial(ViewData["Domain"] == null? "AllItemsReminder" : "FilterReminder", "certificates")%> + <%= Html.Partial(ViewData["Domain"] == null ? "AllItemsReminder" : "FilterReminder", "certificates")%>
- <%= Html.ActionLink("Add Certificate", "Add", new { domainID=((DomainModel)ViewData["Domain"] ?? new DomainModel()).ID }, new { @class = "action ui-priority-primary" })%> - <%= Html.ActionLink("Resolve", "Resolve", new { domainID=((DomainModel)ViewData["Domain"] ?? new DomainModel()).ID }, new { @class = "action ui-priority-secondary" })%> + <%= Html.ActionLink("Add Certificate", "Add", new { domainID = ((DomainModel)ViewData["Domain"] ?? new DomainModel()).ID }, new { @class = "action ui-priority-primary" })%>
- + <%= Html.Partial("CertificateList", Model, ViewData) %> <%= Html.Partial("CertificateDetailsDialog") %> diff --git a/csharp/admin/AdminMvc/Views/DnsRecords/DnsRecordList.ascx b/csharp/admin/AdminMvc/Views/DnsRecords/DnsRecordList.ascx index 852721e..2fa5ea0 100644 --- a/csharp/admin/AdminMvc/Views/DnsRecords/DnsRecordList.ascx +++ b/csharp/admin/AdminMvc/Views/DnsRecords/DnsRecordList.ascx @@ -1,28 +1,50 @@ <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Common"%> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> -<%= Html.Grid(Model) - .Attributes(@class => "grid ui-widget ui-widget-content") - .HeaderRowAttributes(new Dictionary { { "class", "ui-widget-header" } }) - .Columns( - column => - { - column.For(d => d.ID).Visible(false); - column.For(d => d.DomainName); - column.For(d => d.TypeString).Named("Type"); - column.For(d => d.Notes); - column.For(d => Html.Span(Formatter.Format(d.CreateDate), new { title = d.CreateDate })).Named("Created On"); - column.For(d => Html.Span(Formatter.Format(d.UpdateDate), new { @class = "update-date", title = d.UpdateDate })).Named("Updated On"); - column.For(d => Html.ActionLink("View", d.TypeString + "Details", new {id = d.ID}, new { @class = "view-details"})); - column.For(d => Html.ActionLink("Edit", "Edit" + d.TypeString, new { id = d.ID })); - column.For(d => Html.ActionLink("Delete", "Delete", new { id = d.ID }, new { @class = "toolbar-button delete-action" })); - })%> - -<%= Html.Pager((IPagination)Model) %> + + + + + + + + + + + + + + <% foreach (var d in Model) { %> + + + + + + + + + + <% } %> + +
IDDomainTypeNotesCreated OnUpdated OnActions
<%= d.ID %><%= d.DomainName %><%= d.TypeString %><%= d.Notes %><%= Html.Span(Formatter.Format(d.CreateDate), new { title = d.CreateDate }) %><%= Html.Span(Formatter.Format(d.UpdateDate), new { @class = "update-date", title = d.UpdateDate }) %> + <%= Html.ActionLink("View", d.TypeString + "Details", new { id = d.ID }, new { @class = "view-details" }) %> | + <%= Html.ActionLink("Edit", "Edit" + d.TypeString, new { id = d.ID }) %> | + <%= Html.ActionLink("Delete", "Delete", new { id = d.ID }, new { @class = "toolbar-button delete-action" }) %> +
+ +<% var paged = Model as Health.Direct.Admin.Console.Models.Pagination.PaginatedList; %> +<% if (paged != null) { %> +
+ <% if (paged.HasPreviousPage) { %> + <%= Html.ActionLink("Prev", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber - 1 }) %> + <% } %> + Page <%= paged.PageNumber %> of <%= paged.PageCount %> + <% if (paged.HasNextPage) { %> + <%= Html.ActionLink("Next", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber + 1 }) %> + <% } %> +
+<% } %> diff --git a/csharp/admin/AdminMvc/Views/DnsRecords/Index.aspx b/csharp/admin/AdminMvc/Views/DnsRecords/Index.aspx index ab2f37b..edc9ac8 100644 --- a/csharp/admin/AdminMvc/Views/DnsRecords/Index.aspx +++ b/csharp/admin/AdminMvc/Views/DnsRecords/Index.aspx @@ -3,18 +3,19 @@ <%@ Import Namespace="Health.Direct.Admin.Console.Controllers"%> - DNS Record + DNS Records -
- <%= Html.ActionLink("Add ANAME Record", "AddAname", null, new { @class = "action ui-priority-primary"})%> - <%= Html.ActionLink("Add MX Record", "AddMx", null, new { @class = "action ui-priority-primary"})%> - <%= Html.ActionLink("Add SOA Record", "AddSoa", null, new { @class = "action ui-priority-primary"})%> + <%= Html.Partial(ViewData["Domain"] == null ? "AllItemsReminder" : "FilterReminder", "dnsrecords")%> +
+ <%= Html.ActionLink("Add A Record", "AddAname", null, new { @class = "action ui-priority-primary" })%> + <%= Html.ActionLink("Add MX Record", "AddMx", null, new { @class = "action ui-priority-primary" })%> + <%= Html.ActionLink("Add SOA Record", "AddSoa", null, new { @class = "action ui-priority-primary" })%>
- <%= Html.Partial("DnsRecordList", Model) %> - <%= Html.Partial("DnsRecordDetailsDialog", Model) %> - + <%= Html.Partial("DnsRecordList", Model, ViewData) %> + <%= Html.Partial("DnsRecordDetailsDialog") %> + diff --git a/csharp/admin/AdminMvc/Views/Domains/DomainList.ascx b/csharp/admin/AdminMvc/Views/Domains/DomainList.ascx index 71eacb4..ec332c2 100644 --- a/csharp/admin/AdminMvc/Views/Domains/DomainList.ascx +++ b/csharp/admin/AdminMvc/Views/Domains/DomainList.ascx @@ -1,34 +1,62 @@ <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Common"%> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> -<%= Html.Grid(Model) - .Attributes(@class => "grid ui-widget ui-widget-content") - .HeaderRowAttributes(new Dictionary { { "class", "ui-widget-header" } }) - .Columns( - column => - { - column.For(d => d.ID).Attributes(style => "display: none;").HeaderAttributes(style => "display: none;"); - column.For(d => d.Name); - column.For(d => d.Status).Attributes(@class => "status"); - column.For((d => d.SecurityStandard)); - column.For(d => Html.Span(Formatter.Format(d.CreateDate), new { title = d.CreateDate })).Named("Created On"); - column.For(d => Html.Span(Formatter.Format(d.UpdateDate), new { @class = "update-date", title = d.UpdateDate })).Named("Updated On"); - - column.For(d => Html.ActionLink("View", "Details", new {id = d.ID}, new { @class = "view-details"})); - column.For(d => Html.ActionLink("Addresses", "Addresses", new { id = d.ID })); - column.For(d => Html.ActionLink("Anchors", "Anchors", new { id = d.ID })); - column.For(d => Html.ActionLink("Certificates", "Certificates", new { id = d.ID })); - column.For(d => d.IsEnabled - ? Html.ActionLink("Disable", "Disable", new {id = d.ID}, new {@class = "enable-disable-action"}) - : Html.ActionLink("Enable", "Enable", new { id = d.ID }, new { @class = "enable-disable-action" })); - column.For(d => Html.ActionLink("Delete", "Delete", new { id = d.ID }, new { @class = "toolbar-button delete-action" })); - })%> - -<%= Html.Pager((IPagination)Model) %> + + + + + + + + + + + + + + <% foreach (var d in Model) { %> + + + + + + + + + + <% } %> + +
IDNameStatusSecurity StandardCreated OnUpdated OnActions
<%= d.ID %><%= d.Name %><%= d.Status %><%= d.SecurityStandard %><%= Html.Span(Formatter.Format(d.CreateDate), new { title = d.CreateDate }) %><%= Html.Span(Formatter.Format(d.UpdateDate), new { @class = "update-date", title = d.UpdateDate }) %> + <%= Html.ActionLink("View", "Details", new { id = d.ID }, new { @class = "view-details" }) %> + | + <%= Html.ActionLink("Addresses", "Addresses", new { id = d.ID }) %> + | + <%= Html.ActionLink("Anchors", "Anchors", new { id = d.ID }) %> + | + <%= Html.ActionLink("Certificates", "Certificates", new { id = d.ID }) %> + | + <% if (d.IsEnabled) { %> + <%= Html.ActionLink("Disable", "Disable", new { id = d.ID }, new { @class = "enable-disable-action" }) %> + <% } else { %> + <%= Html.ActionLink("Enable", "Enable", new { id = d.ID }, new { @class = "enable-disable-action" }) %> + <% } %> + | + <%= Html.ActionLink("Delete", "Delete", new { id = d.ID }, new { @class = "toolbar-button delete-action" }) %> +
+ +<% var paged = Model as Health.Direct.Admin.Console.Models.Pagination.PaginatedList; %> +<% if (paged != null) { %> +
+ <% if (paged.HasPreviousPage) { %> + <%= Html.ActionLink("Prev", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber - 1 }) %> + <% } %> + Page <%= paged.PageNumber %> of <%= paged.PageCount %> + <% if (paged.HasNextPage) { %> + <%= Html.ActionLink("Next", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber + 1 }) %> + <% } %> +
+<% } %> diff --git a/csharp/admin/AdminMvc/Views/MdnRecords/Index.aspx b/csharp/admin/AdminMvc/Views/MdnRecords/Index.aspx index 94a45ec..549f814 100644 --- a/csharp/admin/AdminMvc/Views/MdnRecords/Index.aspx +++ b/csharp/admin/AdminMvc/Views/MdnRecords/Index.aspx @@ -1,19 +1,15 @@ -<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage>" %> +<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Models"%> -<%@ Import Namespace="MvcContrib.UI.Pager"%> -<%@ Import Namespace="MvcContrib.Pagination"%> -<%@ Import Namespace="MvcContrib.UI.Grid"%> <%@ Import Namespace="Health.Direct.Admin.Console.Controllers"%> - Certificates + MDN Records -
-
- + <%= Html.Partial(ViewData["Domain"] == null ? "AllItemsReminder" : "FilterReminder", "mdnrecords")%> + <%= Html.Partial("MdnList", Model, ViewData) %>
diff --git a/csharp/admin/AdminMvc/Views/MdnRecords/MdnList.ascx b/csharp/admin/AdminMvc/Views/MdnRecords/MdnList.ascx index 2c9cf9a..718a1e5 100644 --- a/csharp/admin/AdminMvc/Views/MdnRecords/MdnList.ascx +++ b/csharp/admin/AdminMvc/Views/MdnRecords/MdnList.ascx @@ -1,27 +1,52 @@ <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl>" %> <%@ Import Namespace="Health.Direct.Admin.Console.Common" %> -<%@ Import Namespace="MvcContrib.Pagination" %> -<%@ Import Namespace="MvcContrib.UI.Grid" %> -<%@ Import Namespace="MvcContrib.UI.Pager" %> -<%= Html.Grid(Model) - .Attributes(@class => "grid ui-widget ui-widget-content") - .HeaderRowAttributes(new Dictionary { { "class", "ui-widget-header" } }) - .Columns( - column => - { - column.For(d => d.Id).Attributes(style => "display: none;").HeaderAttributes(style => "display: none;"); - column.For(d => d.MdnIdentifier); - column.For(d => d.MessageId); - column.For(d => d.SubjectValue).Named("Subject"); - column.For(d => d.Sender); - column.For(d => d.Recipient); - column.For(d => d.Status).Attributes(@class => "status"); - column.For(d => d.NotifyDispatched); - column.For(d => d.Timedout); - column.For(d => d.MdnProcessedDate).Named("Processed On"); - column.For(d => d.CreateDate).Named("Created On"); - column.For(d => d.UpdateDate).Named("Updated On"); - })%> - -<%= Html.Pager((IPagination)Model) %> \ No newline at end of file + + + + + + + + + + + + + + + + + + + <% foreach (var d in Model) { %> + + + + + + + + + + + + + + + <% } %> + +
IDMDN IdentifierMessage IDSubjectSenderRecipientStatusNotify DispatchedTimed OutProcessed OnCreated OnUpdated On
<%= d.Id %><%= d.MdnIdentifier %><%= d.MessageId %><%= d.SubjectValue %><%= d.Sender %><%= d.Recipient %><%= d.Status %><%= d.NotifyDispatched %><%= d.Timedout %><%= d.MdnProcessedDate %><%= d.CreateDate %><%= d.UpdateDate %>
+ +<% var paged = Model as Health.Direct.Admin.Console.Models.Pagination.PaginatedList; %> +<% if (paged != null) { %> +
+ <% if (paged.HasPreviousPage) { %> + <%= Html.ActionLink("Prev", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber - 1 }) %> + <% } %> + Page <%= paged.PageNumber %> of <%= paged.PageCount %> + <% if (paged.HasNextPage) { %> + <%= Html.ActionLink("Next", ViewContext.RouteData.Values["action"].ToString(), new { page = paged.PageNumber + 1 }) %> + <% } %> +
+<% } %> \ No newline at end of file diff --git a/csharp/build/DirectProject.sln b/csharp/build/DirectProject.sln index abc30cf..ffd4f72 100644 --- a/csharp/build/DirectProject.sln +++ b/csharp/build/DirectProject.sln @@ -84,8 +84,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "hsmCryptographer.tests", ". EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "smtpEventHandler", "..\gateway\smtpEventHandler\smtpEventHandler.vcxproj", "{1C4EFA2C-943E-4ABA-9EDC-F92BD561EAC2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdminMvc", "..\admin\AdminMvc\AdminMvc.csproj", "{8E284E54-B6E7-4F16-B362-719C34E95762}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xd.tests", "..\unittests\xdtests\xd.tests.csproj", "{C6AF9782-1F93-4B72-9281-754456D053A8}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xdm.tests", "..\unittests\xdm.tests\xdm.tests.csproj", "{2C3A196B-AA5A-4D35-8DE3-953F8DD3501F}" @@ -122,6 +120,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "external", "external", "{FD ..\external\microsoft\vcredist\vcredist_x86.exe = ..\external\microsoft\vcredist\vcredist_x86.exe EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdminMvc", "..\admin\AdminMvc\AdminMvc.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -256,10 +256,6 @@ Global {1C4EFA2C-943E-4ABA-9EDC-F92BD561EAC2}.Debug|Any CPU.Build.0 = Debug|x64 {1C4EFA2C-943E-4ABA-9EDC-F92BD561EAC2}.Release|Any CPU.ActiveCfg = Release|x64 {1C4EFA2C-943E-4ABA-9EDC-F92BD561EAC2}.Release|Any CPU.Build.0 = Release|x64 - {8E284E54-B6E7-4F16-B362-719C34E95762}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E284E54-B6E7-4F16-B362-719C34E95762}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E284E54-B6E7-4F16-B362-719C34E95762}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E284E54-B6E7-4F16-B362-719C34E95762}.Release|Any CPU.Build.0 = Release|Any CPU {C6AF9782-1F93-4B72-9281-754456D053A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C6AF9782-1F93-4B72-9281-754456D053A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {C6AF9782-1F93-4B72-9281-754456D053A8}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -320,6 +316,10 @@ Global {533AA11A-7C3C-4F3D-9901-534301D3F036}.Debug|Any CPU.Build.0 = Debug|Any CPU {533AA11A-7C3C-4F3D-9901-534301D3F036}.Release|Any CPU.ActiveCfg = Release|Any CPU {533AA11A-7C3C-4F3D-9901-534301D3F036}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/csharp/config/client/PasswordHash.partial.cs b/csharp/config/client/PasswordHash.partial.cs index bb86428..ebbc65d 100644 --- a/csharp/config/client/PasswordHash.partial.cs +++ b/csharp/config/client/PasswordHash.partial.cs @@ -36,17 +36,14 @@ public PasswordHash(Administrator user, string password) { if (user == null) throw new ArgumentNullException(nameof(user)); if (password == null) throw new ArgumentNullException(nameof(password)); - this.HashedPassword = ComputeHash(user.Username, password); + this.HashedPassword = ComputeHash(user, password); } - static string ComputeHash(string username, string password) + static string ComputeHash(Administrator user, string password) { - // Replace with the exact original server algorithm if it differs. - using (var sha = SHA256.Create()) - { - var material = Encoding.UTF8.GetBytes((username ?? string.Empty).ToLowerInvariant() + ":" + password); - return Convert.ToBase64String(sha.ComputeHash(material)); - } + // Must match the server-side algorithm in config.store\PasswordHash.cs + var source = user.Username.ToLower() + "|" + user.CreateDate.ToString("yyyyMMdd'T'HHmmss") + "|" + password; + return Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(source))); } } } \ No newline at end of file diff --git a/csharp/config/store/PasswordHash.cs b/csharp/config/store/PasswordHash.cs index 3304268..3a44f74 100644 --- a/csharp/config/store/PasswordHash.cs +++ b/csharp/config/store/PasswordHash.cs @@ -33,7 +33,7 @@ public PasswordHash(Administrator user, string password) private static string HashPassword(Administrator user, string password) { var source = user.Username.ToLower() + "|" + user.CreateDate.ToString("yyyyMMdd'T'HHmmss") + "|" + password; - return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(source))); + return Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(source))); } [DataMember(IsRequired = true)] diff --git a/csharp/tools/admin.console/App.config b/csharp/tools/admin.console/App.config index 0a9c00b..041c3cc 100644 --- a/csharp/tools/admin.console/App.config +++ b/csharp/tools/admin.console/App.config @@ -17,7 +17,7 @@ - + From 2a5332bd8b90bdd147849c5cd2834b013e84e0e0 Mon Sep 17 00:00:00 2001 From: Joe Shook Date: Sat, 27 Dec 2025 15:58:33 -0800 Subject: [PATCH 5/5] Decided to update SMIMECryptographer to use BouncyCastle for encryption Just copied the old SMIMECryptographer to the LegacySMIMECryptographer class, to aid in unit tests. This will have the least effect on the existing install base. --- .../Cryptography/BcSMIMECryptographer.cs | 178 ----- .../Cryptography/LegacySMIMECryptographer.cs | 647 ++++++++++++++++++ .../common/Cryptography/SMIMECryptographer.cs | 84 ++- .../Cryptography/OaepEncryptionFacts.cs | 17 +- 4 files changed, 706 insertions(+), 220 deletions(-) delete mode 100644 csharp/common/Cryptography/BcSMIMECryptographer.cs create mode 100644 csharp/common/Cryptography/LegacySMIMECryptographer.cs diff --git a/csharp/common/Cryptography/BcSMIMECryptographer.cs b/csharp/common/Cryptography/BcSMIMECryptographer.cs deleted file mode 100644 index f74261b..0000000 --- a/csharp/common/Cryptography/BcSMIMECryptographer.cs +++ /dev/null @@ -1,178 +0,0 @@ -/* - Copyright (c) 2025, Direct Project - All rights reserved. - - Authors: - GitHub Copilot - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -*/ - -using System; -using System.Net.Mime; -using System.Security.Cryptography.X509Certificates; -using Health.Direct.Common.Mail; -using Health.Direct.Common.Mime; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Nist; -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Cms; -using Org.BouncyCastle.Crypto.Operators; -using Org.BouncyCastle.Security; - -namespace Health.Direct.Common.Cryptography -{ - /// - /// S/MIME cryptographer using BouncyCastle for CMS operations, enabling RSAES-OAEP on .NET Framework 4.8. - /// - public class BcSMIMECryptographer : ISmimeCryptographer - { - public event Action Error; - public event Action Warning - { - add { } - remove { } - } - - /// - /// For non-critical operations, delegate to the default software cryptographer. - /// - public ISmimeCryptographer DefaultCryptographer { get; set; } = SMIMECryptographer.Default; - - public EncryptionAlgorithm EncryptionAlgorithm { get; set; } = EncryptionAlgorithm.AES128; - public DigestAlgorithm DigestAlgorithm { get; set; } = DigestAlgorithm.SHA256; - public bool IncludeMultipartEpilogueInSignature { get; set; } = true; - public X509IncludeOption IncludeCertChainInSignature { get; set; } = X509IncludeOption.EndCertOnly; - - public MimeEntity Encrypt(MimeEntity entity, X509Certificate2 encryptingCertificate) - { - return Encrypt(entity, new X509Certificate2Collection(encryptingCertificate)); - } - - public MimeEntity Encrypt(MimeEntity entity, X509Certificate2Collection encryptingCertificates) - { - if (entity == null) - { - throw new EncryptionException(EncryptionError.NullEntity); - } - if (encryptingCertificates == null || encryptingCertificates.Count == 0) - { - throw new EncryptionException(EncryptionError.NoCertificates); - } - - try - { - byte[] messageBytes = DefaultSerializer.Default.SerializeToBytes(entity); - byte[] encryptedBytes = EncryptBytesWithBc(messageBytes, encryptingCertificates); - - MimeEntity encryptedEntity = new MimeEntity - { - ContentType = SMIMEStandard.EncryptedEnvelopeContentTypeHeaderValue, - ContentTransferEncoding = TransferEncoding.Base64.AsString(), - Body = new Body(Convert.ToBase64String(encryptedBytes, Base64FormattingOptions.InsertLineBreaks)) - }; - encryptedEntity.ContentDisposition = SMIMEStandard.EncryptedEnvelopeDisposition; - return encryptedEntity; - } - catch (Exception ex) - { - this.Error.NotifyEvent(this, ex); - throw; - } - } - - public MimeEntity DecryptEntity(byte[] encryptedBytes, X509Certificate2 decryptingCertificate) - { - try - { - return DefaultCryptographer.DecryptEntity(encryptedBytes, decryptingCertificate); - } - catch (Exception ex) - { - this.Error.NotifyEvent(this, ex); - throw; - } - } - - public SignedEntity Sign(Message message, X509Certificate2Collection signingCertificates) - { - return DefaultCryptographer.Sign(message, signingCertificates); - } - - public SignedEntity Sign(MimeEntity entity, X509Certificate2 signingCertificate) - { - return DefaultCryptographer.Sign(entity, signingCertificate); - } - - public SignedEntity Sign(MimeEntity entity, X509Certificate2Collection signingCertificates) - { - return DefaultCryptographer.Sign(entity, signingCertificates); - } - - public System.Security.Cryptography.Pkcs.SignedCms DeserializeDetachedSignature(SignedEntity entity) - { - return DefaultCryptographer.DeserializeDetachedSignature(entity); - } - - public System.Security.Cryptography.Pkcs.SignedCms DeserializeEnvelopedSignature(MimeEntity envelopeEntity) - { - return DefaultCryptographer.DeserializeEnvelopedSignature(envelopeEntity); - } - - public byte[] GetEncryptedBytes(MimeEntity encryptedEntity) - { - return DefaultCryptographer.GetEncryptedBytes(encryptedEntity); - } - - private byte[] EncryptBytesWithBc(byte[] content, X509Certificate2Collection encryptingCertificates) - { - CmsEnvelopedDataGenerator gen = new CmsEnvelopedDataGenerator(); - - // OAEP parameters for RSA (SHA-256, MGF1 with SHA-256, pSource=PSpecified with empty string) - AlgorithmIdentifier oaepParams = new AlgorithmIdentifier( - PkcsObjectIdentifiers.IdRsaesOaep, - new RsaesOaepParameters( - new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256), - new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256)), - new AlgorithmIdentifier(PkcsObjectIdentifiers.IdPSpecified, new DerOctetString(new byte[0])) - ) - ); - - foreach (X509Certificate2 cert in encryptingCertificates) - { - var bcCert = DotNetUtilities.FromX509Certificate(cert); - // Use Asn1KeyWrapper with OAEP algorithm and public key - var keyWrapper = new Asn1KeyWrapper(oaepParams, bcCert.GetPublicKey()); - var recipGen = new KeyTransRecipientInfoGenerator(bcCert, keyWrapper); - gen.AddRecipientInfoGenerator(recipGen); - } - - string symAlgTag = GetSymmetricEncryptionTag(EncryptionAlgorithm); - CmsProcessableByteArray data = new CmsProcessableByteArray(content); - var encrypted = gen.Generate(data, symAlgTag); - return encrypted.GetEncoded(); - } - - private static string GetSymmetricEncryptionTag(EncryptionAlgorithm alg) - { - switch (alg) - { - case EncryptionAlgorithm.AES128: - return CmsEnvelopedGenerator.Aes128Cbc; - case EncryptionAlgorithm.AES192: - return CmsEnvelopedGenerator.Aes192Cbc; - case EncryptionAlgorithm.AES256: - return CmsEnvelopedGenerator.Aes256Cbc; - default: - return CmsEnvelopedGenerator.Aes128Cbc; - } - } - } -} diff --git a/csharp/common/Cryptography/LegacySMIMECryptographer.cs b/csharp/common/Cryptography/LegacySMIMECryptographer.cs new file mode 100644 index 0000000..2f959a5 --- /dev/null +++ b/csharp/common/Cryptography/LegacySMIMECryptographer.cs @@ -0,0 +1,647 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Joe Shook Joseph.Shook@Surescripts.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +using System; +using System.Net.Mime; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.X509Certificates; +using Health.Direct.Common.Extensions; +using Health.Direct.Common.Mail; +using Health.Direct.Common.Mime; + +namespace Health.Direct.Common.Cryptography +{ + /// + /// Encapsulates use of S/MIME PKCS7 (CMS) cryptography + /// + public class LegacySMIMECryptographer : SMIMECryptographerBase, ISmimeCryptographer + { + /// + /// The default set of cryptographic algorithms. + /// + public static readonly ISmimeCryptographer Default = new LegacySMIMECryptographer(); + + /// + public event Action Error; + + /// + public event Action Warning + { + add { } + remove { } + } + + /// + /// This is the default cryptographer so always return self. + /// + public ISmimeCryptographer DefaultCryptographer { get { return this; } set { } } + + /// + /// Initializes an instance with the default set of encryption and digest algorithms. + /// + public LegacySMIMECryptographer() + : this(EncryptionAlgorithm.AES128, DigestAlgorithm.SHA256) + { + } + + /// + /// Initializes an instance, specifying the encryption and digest algorithm to use. + /// + /// The to use in this cryptographer + /// The to use in this cryptographer + public LegacySMIMECryptographer(EncryptionAlgorithm encryptionAlgorithm, DigestAlgorithm digestAlgorithm) + { + EncryptionAlgorithm = encryptionAlgorithm; + DigestAlgorithm = digestAlgorithm; + IncludeMultipartEpilogueInSignature = true; + IncludeCertChainInSignature = X509IncludeOption.EndCertOnly; + } + + //----------------------------------------------------- + // + // Encryption + // + //----------------------------------------------------- + /// + /// Takes a MIME entity and returns a new encrypted MIME entity. + /// + /// The including content headers + /// The certificate used for encrytion + /// The encrypted + public MimeEntity Encrypt(MimeEntity entity, X509Certificate2 encryptingCertificate) + { + try + { + return Encrypt(entity, new X509Certificate2Collection(encryptingCertificate)); + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + /// + /// Takes a MIME entity and returns a new encrypted MIME entity. + /// + /// + /// As specified in the S/MIME and CMS RFCs, encryption uses symetric encryption to encrypt the body, and + /// certificate-based asymetric encryption to encrypt the encryption key used. With multiple certificates, + /// there will be multiple copies of the encrypted encryption key. + /// + /// The including content headers + /// The certificates used for encryption + /// The encrypted + public MimeEntity Encrypt(MimeEntity entity, X509Certificate2Collection encryptingCertificates) + { + if (entity == null) + { + throw new EncryptionException(EncryptionError.NullEntity); + } + + try + { + byte[] messageBytes = DefaultSerializer.Default.SerializeToBytes(entity); // Serialize message out as ASCII encoded... + + byte[] encryptedBytes = Encrypt(messageBytes, encryptingCertificates); + + MimeEntity encryptedEntity = new MimeEntity + { + ContentType = SMIMEStandard.EncryptedEnvelopeContentTypeHeaderValue, + ContentTransferEncoding = TransferEncoding.Base64.AsString(), + Body = new Body(Convert.ToBase64String(encryptedBytes, + Base64FormattingOptions.InsertLineBreaks)) + }; + + encryptedEntity.ContentDisposition = SMIMEStandard.EncryptedEnvelopeDisposition; + return encryptedEntity; + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + /// + /// Encrypts raw data and returns the encrypted raw data (without content headers) + /// + /// The content to encrypt + /// The certificate used for encrytion + /// The encrypted raw data. + private byte[] Encrypt(byte[] content, X509Certificate2 encryptingCertificate) + { + return Encrypt(content, new X509Certificate2Collection(encryptingCertificate)); + } + + /// + /// Encrypts raw data and returns the encrypted raw data (without content headers) + /// + /// The content to encrypt + /// The collection of certificate used for encrytion + /// The encrypted raw data. + private byte[] Encrypt(byte[] content, X509Certificate2Collection encryptingCertificates) + { + EnvelopedCms envelope = CreateEncryptedEnvelope(content, encryptingCertificates); + return envelope.Encode(); + } + + + /// + /// Encrypts raw data and returns a instance with the encrypted data. + /// + /// The content to encrypt + /// The collection of certificate used for encrytion + /// The encrypted instance. + private EnvelopedCms CreateEncryptedEnvelope(byte[] content, X509Certificate2Collection encryptingCertificates) + { + if (content == null) + { + throw new EncryptionException(EncryptionError.NullContent); + } + if (encryptingCertificates == null || encryptingCertificates.Count == 0) + { + throw new EncryptionException(EncryptionError.NoCertificates); + } + + CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, encryptingCertificates); + EnvelopedCms dataEnvelope = new EnvelopedCms(CreateDataContainer(content), ToAlgorithmID(EncryptionAlgorithm)); + dataEnvelope.Encrypt(recipients); + + return dataEnvelope; + } + + //----------------------------------------------------- + // + // Decryption + // + //----------------------------------------------------- + // + // Cryptography is performed only on the Mime portions of the message, not the RFC822 headers + // + + /// + /// Decrypts a with a certificate + /// + /// The to decrypt + /// The that encrypted the message + /// A holding the decrypted message + private MimeEntity Decrypt(Message message, X509Certificate2 decryptingCertificate) + { + return Decrypt(message.ExtractMimeEntity(), decryptingCertificate); + } + + /// + /// Decrypts a with a certificate + /// + /// The to decrypt + /// The that encrypted the message + /// A holding the decrypted message + private MimeEntity Decrypt(MimeEntity encryptedEntity, X509Certificate2 decryptingCertificate) + { + return this.DecryptEntity(this.GetEncryptedBytes(encryptedEntity), decryptingCertificate); + } + + /// + /// Decrypt the given encryptedByte array into a MimeEntity + /// + /// source encrypted bytes + /// The that encrypted the message + /// A holding the decrypted message + public MimeEntity DecryptEntity(byte[] encryptedBytes, X509Certificate2 decryptingCertificate) + { + if (encryptedBytes.IsNullOrEmpty()) + { + throw new ArgumentException("encryptedBytes"); + } + + if (decryptingCertificate == null) + { + throw new EncryptionException(EncryptionError.NoCertificates); + } + + if (!decryptingCertificate.HasPrivateKey) + { + throw new EncryptionException(EncryptionError.NoPrivateKey); + } + + try + { + byte[] decryptedBytes = Decrypt(encryptedBytes, decryptingCertificate); + + // + // And turn the encrypted bytes back into an entity + // + return DefaultSerializer.Default.Deserialize(decryptedBytes); + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + /// + /// Decrypts encrypted raw content with a certificate + /// + /// The raw data to decrypt + /// The that encrypted the message + /// A byte array holding the decrypted content + private byte[] Decrypt(byte[] encryptedContent, X509Certificate2 decryptingCertificate) + { + return Decrypt(encryptedContent, new X509Certificate2Collection(decryptingCertificate)); + } + + /// + /// Decrypts encrypted raw content with a collection of certificates, at least one of which can decrypt the encryption key. + /// + /// + /// See for more information on underlying processing. + /// + /// The raw data to decrypt + /// The of certificates, at least one of which encrypted the message + /// A byte array holding the decrypted content + private byte[] Decrypt(byte[] encryptedContent, X509Certificate2Collection decryptingCertificates) + { + if (decryptingCertificates == null || decryptingCertificates.Count == 0) + { + throw new EncryptionException(EncryptionError.NoCertificates); + } + + EnvelopedCms dataEnvelope = DeserializeEncryptionEnvelope(encryptedContent); + + dataEnvelope.Decrypt(decryptingCertificates); + + ContentInfo contentInfo = dataEnvelope.ContentInfo; + + if (!IsDataContainer(contentInfo)) + { + throw new EncryptionException(EncryptionError.ContentNotDataContainer); + } + + return contentInfo.Content; + } + + /// + /// Deserializes an encrypted byte array of encrypted data as a instance + /// + /// The raw encrypted data + /// An instance of containing the encrypted data + private EnvelopedCms DeserializeEncryptionEnvelope(byte[] encryptedContent) + { + if (encryptedContent == null) + { + throw new EncryptionException(EncryptionError.NullContent); + } + + EnvelopedCms dataEnvelope = new EnvelopedCms(); + dataEnvelope.Decode(encryptedContent); + return dataEnvelope; + } + + internal ContentInfo CreateDataContainer(byte[] content) + { + return new ContentInfo(CryptoOids.ContentType_Data, content); + } + + internal bool IsDataContainer(ContentInfo contentInfo) + { + return (contentInfo.ContentType.Value == CryptoOids.ContentType_Data.Value); + } + + //----------------------------------------------------- + // + // Signatures + // + //----------------------------------------------------- + // + // Cryptography is performed only on the Mime portions of the message, not the RFC822 headers + // Some mail readers ignore the epilogue when calculating signatures! + // + + + /// + /// Creates a detached signed entity from a message and a collection of signing certificate + /// + /// + /// Cryptography is performed only on the Mime portions of the message, not the RFC822 headers + /// Some mail readers ignore the epilogue when calculating signatures! + /// + /// The entity to sign + /// The certificates with which to sign. + /// A instance holding the signatures. + public SignedEntity Sign(Message message, X509Certificate2Collection signingCertificates) + { + try + { + return Sign(message.ExtractEntityForSignature(IncludeMultipartEpilogueInSignature), signingCertificates); + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + /// + /// Creates a detached signed entity from a and a signing certificate + /// + /// + /// Cryptography is performed only on the Mime portions of the message, not the RFC822 headers + /// Some mail readers ignore the epilogue when calculating signatures! + /// + /// The to sign + /// The certificate with which to sign. + /// A instance holding the signature. + public SignedEntity Sign(MimeEntity entity, X509Certificate2 signingCertificate) + { + try + { + return Sign(entity, new X509Certificate2Collection(signingCertificate)); + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + /// + /// Creates a detatched signed entity from a and collection of a signing certificates + /// + /// + /// Cryptography is performed only on the Mime portions of the message, not the RFC822 headers + /// Some mail readers ignore the epilogue when calculating signatures! + /// + /// The to sign + /// The certificates with which to sign. + /// A instance holding the signatures. + public SignedEntity Sign(MimeEntity entity, X509Certificate2Collection signingCertificates) + { + if (entity == null) + { + throw new SignatureException(SignatureError.NullEntity); + } + + try + { + byte[] entityBytes = DefaultSerializer.Default.SerializeToBytes(entity); + MimeEntity signature = CreateSignatureEntity(entityBytes, signingCertificates); + + return new SignedEntity(DigestAlgorithm, entity, signature); + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + + /// + /// Creates a signed data from source raw data and a collection of signing certificates + /// + /// + /// Cryptography is performed only on the Mime portions of the message, not the RFC822 headers + /// Some mail readers ignore the epilogue when calculating signatures! + /// + /// The byte array to sign + /// The certificates with which to sign. + /// Raw data holding the signatures. + private byte[] Sign(byte[] content, X509Certificate2Collection signingCertificates) + { + SignedCms signature = CreateSignature(content, signingCertificates); + return signature.Encode(); + } + + + /// + /// Creates a signed entity from source raw data and a collection of signing certificates + /// + /// + /// Cryptography is performed only on the Mime portions of the message, not the RFC822 headers + /// Some mail readers ignore the epilogue when calculating signatures! + /// + /// The byte array to sign + /// The certificates with which to sign. + /// The holding the signatures. + private MimeEntity CreateSignatureEntity(byte[] content, X509Certificate2Collection signingCertificates) + { + byte[] signatureBytes = Sign(content, signingCertificates); + // + // We create an entity to hold a detached signature + // + MimeEntity signature = new MimeEntity + { + ContentType = SMIMEStandard.SignatureContentTypeHeaderValue, + ContentTransferEncoding = TransferEncoding.Base64.AsString(), + Body = new Body(Convert.ToBase64String(signatureBytes)) + }; + signature.ContentDisposition = SMIMEStandard.SignatureDisposition; + return signature; + } + + /// + /// Creates for raw content and a collection of signing certificate + /// + /// The byte array to sign + /// The certificates with which to sign. + /// An instance of holdling the signatures + public SignedCms CreateSignature(byte[] content, X509Certificate2Collection signingCertificates) + { + if (content == null) + { + throw new SignatureException(SignatureError.NullContent); + } + + if (signingCertificates == null) + { + throw new SignatureException(SignatureError.NoCertificates); + } + + try + { + SignedCms signature = new SignedCms(CreateDataContainer(content), true); + // true: Detached Signature + + for (int i = 0, count = signingCertificates.Count; i < count; ++i) + { + CmsSigner signer = CreateSigner(signingCertificates[i]); + signature.ComputeSignature(signer, true); // true: don't prompt the user + } + + return signature; + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + //----------------------------------------------------- + // + // Signature Validation + // + //----------------------------------------------------- + + /// + /// Checks that a signature was signed by the signer certificate. + /// + /// The signed entity to check + /// The signer certificaet that purports to sign the entity + /// If the entity was not signed by the claimed certificate + private void CheckSignature(SignedEntity signedEntity, X509Certificate2 signerCertificate) + { + SignedCms signatureEnvelope = DeserializeDetachedSignature(signedEntity); + CheckSignature(signatureEnvelope.SignerInfos, signerCertificate); + } + + /// + /// Checks that a collection of signature was signed by the signer certificate. + /// + /// The collection of to check + /// The signer certificate that purports to sign the entity + /// If the entity was not signed by the claimed certificate + private void CheckSignature(SignerInfoCollection signers, X509Certificate2 signerCertificate) + { + if (signerCertificate == null) + { + throw new SignatureException(SignatureError.NoCertificates); + } + if (signers == null || signers.Count == 0) + { + throw new SignatureException(SignatureError.NoSigners); + } + // + // Find the signer + // + SignerInfo signer = signers.FindByThumbprint(signerCertificate.Thumbprint); + if (signer == null) + { + throw new SignatureException(SignatureError.NoSigners); + } + + signer.CheckSignature(true); + } + + /// + /// Transforms a to the associated instance + /// + /// The to deserialize + /// The corresponding + public SignedCms DeserializeDetachedSignature(SignedEntity entity) + { + if (entity == null) + { + throw new SignatureException(SignatureError.NullEntity); + } + + try + { + // Serialize entity out as ASCII encoded... + byte[] contentBytes = DefaultSerializer.Default.SerializeToBytes(entity.Content); + byte[] signatureBytes = Convert.FromBase64String(entity.Signature.Body.Text); + + return DeserializeDetachedSignature(contentBytes, signatureBytes); + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + /// + /// Transforms a detached signature, represented as the entity and the signature raw data + /// to the associated instance + /// + /// The raw content of the entity + /// the raw content of the signature + /// The corresponding + private SignedCms DeserializeDetachedSignature(byte[] content, byte[] signatureBytes) + { + SignedCms signature = new SignedCms(CreateDataContainer(content), true); + signature.Decode(signatureBytes); + + return signature; + } + + /// + /// Tranforms an enveloped signature to the corresponding + /// + /// The entity containing the enveloped signature + /// the corresponding + public SignedCms DeserializeEnvelopedSignature(MimeEntity envelopeEntity) + { + if (envelopeEntity == null) + { + throw new SignatureException(SignatureError.NullEntity); + } + + if (!SMIMEStandard.IsSignedEnvelope(envelopeEntity)) + { + throw new SignatureException(SignatureError.NotSignatureEnvelope); + } + + try + { + byte[] envelopeBytes = Convert.FromBase64String(envelopeEntity.Body.Text); + + return DeserializeEnvelopedSignature(envelopeBytes); + } + catch (Exception ex) + { + this.Error.NotifyEvent(this, ex); + throw; + } + } + + /// + /// Tranforms an enveloped signature to the corresponding + /// + /// The raw data containing the enveloped signature + /// the corresponding + private SignedCms DeserializeEnvelopedSignature(byte[] envelopeBytes) + { + SignedCms signature = new SignedCms(); + signature.Decode(envelopeBytes); + + if (!IsDataContainer(signature.ContentInfo)) + { + throw new SignatureException(SignatureError.ContentNotDataContainer); + } + + return signature; + } + + private CmsSigner CreateSigner(X509Certificate2 cert) + { + CmsSigner signer = new CmsSigner(cert) + { + IncludeOption = IncludeCertChainInSignature, + DigestAlgorithm = ToDigestAlgorithmOid(DigestAlgorithm) + }; + + Pkcs9SigningTime signingTime = new Pkcs9SigningTime(); + signer.SignedAttributes.Add(signingTime); + + return signer; + } + + } +} \ No newline at end of file diff --git a/csharp/common/Cryptography/SMIMECryptographer.cs b/csharp/common/Cryptography/SMIMECryptographer.cs index bc5c7d7..42aa6ca 100644 --- a/csharp/common/Cryptography/SMIMECryptographer.cs +++ b/csharp/common/Cryptography/SMIMECryptographer.cs @@ -1,5 +1,5 @@ -/* - Copyright (c) 2010, Direct Project +/* + Copyright (c) 2010 -2025, Direct Project All rights reserved. Authors: @@ -12,9 +12,10 @@ Joe Shook Joseph.Shook@Surescripts.com Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + */ + using System; using System.Net.Mime; using System.Security.Cryptography.Pkcs; @@ -22,6 +23,14 @@ Neither the name of The Direct Project (directproject.org) nor the names of its using Health.Direct.Common.Extensions; using Health.Direct.Common.Mail; using Health.Direct.Common.Mime; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Cms; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Security; +using ContentInfo = System.Security.Cryptography.Pkcs.ContentInfo; + namespace Health.Direct.Common.Cryptography { @@ -156,33 +165,46 @@ private byte[] Encrypt(byte[] content, X509Certificate2 encryptingCertificate) /// The encrypted raw data. private byte[] Encrypt(byte[] content, X509Certificate2Collection encryptingCertificates) { - EnvelopedCms envelope = CreateEncryptedEnvelope(content, encryptingCertificates); - return envelope.Encode(); + CmsEnvelopedDataGenerator gen = new CmsEnvelopedDataGenerator(); + + // OAEP parameters for RSA (SHA-256, MGF1 with SHA-256, pSource=PSpecified with empty string) + Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier oaepParams = new Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier( + PkcsObjectIdentifiers.IdRsaesOaep, + new RsaesOaepParameters( + new Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(NistObjectIdentifiers.IdSha256), + new Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, new Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(NistObjectIdentifiers.IdSha256)), + new Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(PkcsObjectIdentifiers.IdPSpecified, new DerOctetString(new byte[0])) + ) + ); + + foreach (X509Certificate2 cert in encryptingCertificates) + { + var bcCert = DotNetUtilities.FromX509Certificate(cert); + // Use Asn1KeyWrapper with OAEP algorithm and public key + var keyWrapper = new Asn1KeyWrapper(oaepParams, bcCert.GetPublicKey()); + var recipGen = new KeyTransRecipientInfoGenerator(bcCert, keyWrapper); + gen.AddRecipientInfoGenerator(recipGen); + } + + string symAlgTag = GetSymmetricEncryptionTag(EncryptionAlgorithm); + CmsProcessableByteArray data = new CmsProcessableByteArray(content); + var encrypted = gen.Generate(data, symAlgTag); + return encrypted.GetEncoded(); } - - /// - /// Encrypts raw data and returns a instance with the encrypted data. - /// - /// The content to encrypt - /// The collection of certificate used for encrytion - /// The encrypted instance. - private EnvelopedCms CreateEncryptedEnvelope(byte[] content, X509Certificate2Collection encryptingCertificates) + private static string GetSymmetricEncryptionTag(EncryptionAlgorithm alg) { - if (content == null) - { - throw new EncryptionException(EncryptionError.NullContent); - } - if (encryptingCertificates == null || encryptingCertificates.Count == 0) + switch (alg) { - throw new EncryptionException(EncryptionError.NoCertificates); + case EncryptionAlgorithm.AES128: + return CmsEnvelopedGenerator.Aes128Cbc; + case EncryptionAlgorithm.AES192: + return CmsEnvelopedGenerator.Aes192Cbc; + case EncryptionAlgorithm.AES256: + return CmsEnvelopedGenerator.Aes256Cbc; + default: + return CmsEnvelopedGenerator.Aes128Cbc; } - - CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, encryptingCertificates); - EnvelopedCms dataEnvelope = new EnvelopedCms(CreateDataContainer(content), ToAlgorithmID(EncryptionAlgorithm)); - dataEnvelope.Encrypt(recipients); - - return dataEnvelope; } //----------------------------------------------------- @@ -270,7 +292,7 @@ private byte[] Decrypt(byte[] encryptedContent, X509Certificate2 decryptingCerti /// Decrypts encrypted raw content with a collection of certificates, at least one of which can decrypt the encryption key. /// /// - /// See for more information on underlying processing. + /// See for more information on underlying processing. /// /// The raw data to decrypt /// The of certificates, at least one of which encrypted the message @@ -313,12 +335,12 @@ private EnvelopedCms DeserializeEncryptionEnvelope(byte[] encryptedContent) return dataEnvelope; } - internal ContentInfo CreateDataContainer(byte[] content) + internal System.Security.Cryptography.Pkcs.ContentInfo CreateDataContainer(byte[] content) { - return new ContentInfo(CryptoOids.ContentType_Data, content); + return new System.Security.Cryptography.Pkcs.ContentInfo(CryptoOids.ContentType_Data, content); } - internal bool IsDataContainer(ContentInfo contentInfo) + internal bool IsDataContainer(System.Security.Cryptography.Pkcs.ContentInfo contentInfo) { return (contentInfo.ContentType.Value == CryptoOids.ContentType_Data.Value); } @@ -514,7 +536,7 @@ private void CheckSignature(SignedEntity signedEntity, X509Certificate2 signerCe /// /// Checks that a collection of signature was signed by the signer certificate. /// - /// The collection of to check + /// The collection of to check /// The signer certificate that purports to sign the entity /// If the entity was not signed by the claimed certificate private void CheckSignature(SignerInfoCollection signers, X509Certificate2 signerCertificate) @@ -530,7 +552,7 @@ private void CheckSignature(SignerInfoCollection signers, X509Certificate2 signe // // Find the signer // - SignerInfo signer = signers.FindByThumbprint(signerCertificate.Thumbprint); + System.Security.Cryptography.Pkcs.SignerInfo signer = signers.FindByThumbprint(signerCertificate.Thumbprint); if (signer == null) { throw new SignatureException(SignatureError.NoSigners); diff --git a/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs b/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs index 6ce0c22..333d57f 100644 --- a/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs +++ b/csharp/unittests/common/Cryptography/OaepEncryptionFacts.cs @@ -14,19 +14,14 @@ Neither the name of The Direct Project (directproject.org) nor the names of its */ using System; -using System.IO; using System.Net.Mime; using System.Security.Cryptography; using System.Security.Cryptography.Pkcs; using System.Security.Cryptography.X509Certificates; using Health.Direct.Common.Cryptography; -using Health.Direct.Common.Mail; using Health.Direct.Common.Mime; using Org.BouncyCastle.Asn1.Nist; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Cms; using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Asn1.X509; using Xunit; using Xunit.Abstractions; @@ -84,7 +79,7 @@ public void BcCryptographer_OAEP_Uses_SHA256_Params() var recipient = GenerateSelfSignedEnciphermentCert("OAEP-SHA256-Params"); var entity = CreatePlainTextEntity("Hello OAEP SHA256"); - var bc = new BcSMIMECryptographer(); + var bc = new SMIMECryptographer(); var encryptedEntity = bc.Encrypt(entity, recipient); var encryptedBytes = bc.GetEncryptedBytes(encryptedEntity); @@ -117,7 +112,7 @@ public void DefaultSmimeCryptographer_OAEP_Uses_SHA1() var recipient = GenerateSelfSignedEnciphermentCert("Default-OAEP-Params"); var entity = CreatePlainTextEntity("Hello Default OAEP Params"); - var def = SMIMECryptographer.Default; + var def = LegacySMIMECryptographer.Default; var encryptedEntity = def.Encrypt(entity, recipient); var encryptedBytes = def.GetEncryptedBytes(encryptedEntity); @@ -156,7 +151,7 @@ public void BcEncrypt_OAEP_SHA256_DefaultDecrypt_Roundtrip() var recipient = GenerateSelfSignedEnciphermentCert("OAEP-SHA256-Roundtrip"); var entity = CreatePlainTextEntity("Hello OAEP Roundtrip"); - var bc = new BcSMIMECryptographer(); + var bc = new SMIMECryptographer(); var encryptedEntity = bc.Encrypt(entity, recipient); var encryptedBytes = bc.GetEncryptedBytes(encryptedEntity); @@ -168,7 +163,7 @@ public void BcEncrypt_OAEP_SHA256_DefaultDecrypt_Roundtrip() var ex = Record.Exception(() => { - var decrypted = SMIMECryptographer.Default.DecryptEntity(encryptedBytes, recipient); + var decrypted = LegacySMIMECryptographer.Default.DecryptEntity(encryptedBytes, recipient); Assert.NotNull(decrypted); Assert.Equal("Hello OAEP Roundtrip", decrypted.Body.Text); }); @@ -193,7 +188,7 @@ public void SMIMECryptographer_OAEP_SHA1_DefaultDecrypt_BCSMIMECryptographer_Rou var recipient = GenerateSelfSignedEnciphermentCert("OAEP-SHA256-Roundtrip"); var entity = CreatePlainTextEntity("Hello OAEP Roundtrip"); - var sc = SMIMECryptographer.Default; + var sc = LegacySMIMECryptographer.Default; var encryptedEntity = sc.Encrypt(entity, recipient); var encryptedBytes = sc.GetEncryptedBytes(encryptedEntity); @@ -205,7 +200,7 @@ public void SMIMECryptographer_OAEP_SHA1_DefaultDecrypt_BCSMIMECryptographer_Rou var ex = Record.Exception(() => { - var decrypted = new BcSMIMECryptographer().DecryptEntity(encryptedBytes, recipient); + var decrypted = new SMIMECryptographer().DecryptEntity(encryptedBytes, recipient); Assert.NotNull(decrypted); Assert.Equal("Hello OAEP Roundtrip", decrypted.Body.Text); });