when i use this code block about TlsCipherSuite, i get this error "SSL routines:tls_post_process_client_hello:no shared cipher". can you give some advice?
public static KestrelServerOptions ListenSera(this KestrelServerOptions options, SeraSettings seraSettings)
{
options.Listen(IPAddress.Parse(seraSettings.ListenIP), seraSettings.Port, listenOptions =>
{
listenOptions.UseConnectionLimits(veraSettings.ConnectionLimit);
listenOptions.UseHttps(adapterOptions =>
{
adapterOptions.OnAuthenticate = (context, authenticationOptions) =>
{
authenticationOptions.CipherSuitesPolicy = new CipherSuitesPolicy(new[]
{
TlsCipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
});
};
adapterOptions.SslProtocols = SslProtocols.Tls12;
adapterOptions.CheckCertificateRevocation = false;
adapterOptions.HandshakeTimeout = TimeSpan.FromSeconds(veraSettings.TlsHandshakeTimeout);
adapterOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
adapterOptions.ServerCertificate =
new X509Certificate2(Path.Combine("certs", veraSettings.ServerCertificateFilename),
veraSettings.ServerCertificatePassword);
adapterOptions.AllowAnyClientCertificate();
});
listenOptions.UseConnectionLogging();
listenOptions.UseConnectionHandler<VeraKecManager>();
});
return options;
}
}
This means that the ciphers you offered to the server are not available in the server. For some unknown reason you only offered these two ciphers:
TlsCipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
The first one is using plain DHE is key exchange, which is slow and thus often not enabled in the server (too much load on the server). In the second cipher you offer ECDHE as key exchange which is much faster and usually available. But you offer it only in connection with ECDSA which means that the server needs an ECC certificate and not the more common RSA certificate.
In general, it is not a good idea to change the offered ciphers from the defaults. It is even worse if these are restricted to only a few ones for a reason you cannot explain. In general, you should never change security settings without understanding what these are actually doing and what implications the change has, since this might not only make your code not working but it might actually work but in an insecure way. Thus, better leave any security settings at their default and change only these, were the default is not sufficient.
I guess there are a few more thing you can do to diagnose the problem.
try to run Wireshark and listen to the TLS handshake packets. If you take a close look you should see which cipher suites are being offered by the client and server.
If you are using windows, check the registry (Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010002\Functions). This key should list all cipher suites on your machine.
If you are using a certificate, check what sort of cipher suite is mentioned and if any elliptic curves are used. In my case, the certificate mentioned NistP521 curve (Public key parameters ECDSA_P521) which is not enabled by default in windows. I had to modify the registry to enable it (I changed registry value Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010002\Functions\EccCurves from:
curve25519
NistP256
NistP384
to
curve25519
NistP256
NistP384
NistP521
Hope any of the above will put you on the right track.
Related
DKIM is set up for a domain in Office365. A .Net application (currently MVC 4) sends Email through an O365 connector to external parties.
We'd like to sign these using DKIM as well.
I'm not quite clear about the entire process.
MimeKit's Documentation is reasonably clear. I suppose I can use any pub/priv key generator such as Putty to generate a keypair? I would then store the private key in a way that the C# application can read it into
var signer = new DkimSigner ("privatekey.pem") {
SignatureAlgorithm = DkimSignatureAlgorithm.RsaSha1,
AgentOrUserIdentifier = "#eng.example.com",
QueryMethod = "dns/txt",
};
The public key will be published as a DNS record for my domain. Unfortunately, the Office 365 documentation isn't all too clear on the exact how.
Summary Questions
What exactly goes into AgentOrUserIdentifier, if my system sends with the address application#example.org?
How exactly would I publish my generated public key to Office 365?
Any enlightening summary would be greatly appreciated, thanks.
I'll accept #jstedfast's answer (although without really understanding it).
Just in case anyone else is struggling with this, here's the complete walk-through:
Get a public/private key pair. You can use Puttygen or openssl directly, but it's easier to use (oh had I only known beforehand) sth like https://port25.com/dkim-wizard/
Specify your domain name (example.org here) and a "selector" - this could be your application name ("greatapp"). This selector will be the TXT record for the public key in DNS.
Create an additional DNS (TXT) record; leave the Office365 ones intact. Since they rotate keys regularly you want an additional record that you can control.
greatapp._domainkey.example.org IN TXT
"k=rsa\; p=here goes the stuff between -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY-----", so e.g.
"k=rsa\; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhvIwVBomj+dx2CEBbY/ZpSdnQK2Omx6ZNyHsuvC3MMJYNLQ069ajuJo5FP......."
Copy the private key to a file, or use it in your code directly. MimeKit either expects a file or a stream, so for the quick & dirty example here I'm using a string:
var mail = new MimeMessage();
mail.From.Add(new MailboxAddress("Justin Case", "justin#example.org"));
mail.To.Add(new MailboxAddress("Candy Barr", "candy#example.org"));
... subject etc
var privateKey = #"-----BEGIN RSA PRIVATE KEY-----......";
var privateKeyStream = new MemoryStream(Encoding.Default.GetBytes(privateKey));
mail.Sign(new DkimSigner(privateKeyStream, "example.org", "greatapp", DkimSignatureAlgorithm.RsaSha256), new HeaderId[] { HeaderId.From, HeaderId.Subject }, DkimCanonicalizationAlgorithm.Simple, DkimCanonicalizationAlgorithm.Simple);
... Connect client and send.
Thanks to jstedfast something as awesome as MailKit/MimeKit exists, don't forget to donate.
From rfc6376, section 2.6:
2.6. Agent or User Identifier (AUID)
A single identifier that refers to the agent or user on behalf of
whom the Signing Domain Identifier (SDID) has taken responsibility.
The AUID comprises a domain name and an optional <local-part>. The
domain name is the same as that used for the SDID or is a subdomain
of it. For DKIM processing, the domain name portion of the AUID has
only basic domain name semantics; any possible owner-specific
semantics are outside the scope of DKIM. It is specified in
Section 3.5.
Note that acceptable values for the AUID may be constrained via a
flag in the public-key record. (See Section 3.6.1.)
I have to exchange encrypted & signed e-mails with some business partners. Specific algorithms are required, such as :
for signature, RSASSA-PSS as the signature algorithm,
for encryption, RSAES-OAEP for key encryption & AES-128 CBC for content encryption
I am having troubles setting this up with Mailkit, and actually behind it MailKit & BouncyCastle.
Here is where I am so far :
For decryption & signature verification
Decrypting the body is ok, I do it by using a WindowsSecureMimeContext, after setting up my private key in the windows store
Verifying the signature is not ok
case MultipartSigned signedBody:
try
{
using (var ctx = new WindowsSecureMimeContext(StoreLocation.LocalMachine))
{
var verifiedData = signedBody.Verify(ctx);
return verifiedData.All(o => o.Verify());
}
}
catch (Exception e)
{
throw new Exception("Error during signature verification.", e);
}
Certificate of the sender is signed by a common CA, so I'm using again a WindowsSecureMimeContext, but verifiedData.All(o => o.Verify()) throws a DigitalSignatureVerifyException ("Failed to verify digital signature: Unknown error "-1073700864".")
For signature and encryption
Well, that looks tough...
For signature, it seems that I need somewhere a BouncyCastle's PssSigner, which I can get by overriding DkimSigner, and especially the DigestSigner property
class TestSigner : DkimSigner
{
protected TestSigner(string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base(domain, selector, algorithm)
{
}
public TestSigner(AsymmetricKeyParameter key, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base(key, domain, selector, algorithm)
{
}
public TestSigner(string fileName, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base(fileName, domain, selector, algorithm)
{
}
public TestSigner(Stream stream, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base(stream, domain, selector, algorithm)
{
}
public override ISigner DigestSigner => SignerUtilities.GetSigner(PkcsObjectIdentifiers.IdRsassaPss);
}
However I don't know exactly where to use it. Maybe when using MimeMessage.Sign(), however I am a bit lost with the required parameters in the signature of the method
For encryption, I could find my way up to a RsaesOaepParameters in BouncyCastle's library, by I can't figure out how to use it.
Any help by a mail expert would be much appreciated !
A DkimSigner is used for generating DKIM signatures which is not what you want to do. DKIM signatures have nothing to do with S/MIME.
S/MIME Signing using RSASSA-PSS
Currently, the WindowsSecureMimeContext (which uses System.Security as the backend) does NOT support RSASSA-PSS, so you'll need to use the Bouncy Castle backend.
To use the Bouncy Castle backend, you will need to use one of the BouncyCastleSecureMimeContext derivatives (or create your own). As a temporary solution for playing around with things, I might suggest using the TemporarySecureMimeContext, but for long-term use, I would suggest looking at the DefaultSecureMimeContext - although you will still probably want to subclass that to get it working.
Now that you are using a Bouncy Castle S/MIME context, in order to specify that you want to use RSASSA-PSS padding, you'll need to use the APIs that take a CmsSigner parameter such as MultipartSigned.Create() or ApplicationPkcs7Mime.Sign().
Here's an example code snippet:
var signer = new CmsSigner ("certificate.pfx", "password");
// Specify that we want to use RSASSA-PSS
signer.RsaSignaturePaddingScheme = RsaSignaturePaddingScheme.Pss;
// Sign the message body
var signed = MultipartSigned.Create (ctx, signer, message.Body);
// replace the message body with the signed body
message.Body = signed;
S/MIME Encryption Using AES-128 CBC (or any other specific algorithm) with RSAES-OAEP
First, to encrypt using S/MIME, you'll want to use one of the ApplicationPkcs7Mime.Encrypt() methods.[2]
The Encrypt() methods that take a MailboxAddress will automatically create the CmsRecipients and CmsRecipientCollection for you by doing certificate lookups based on the email address provided (or, if any of those mailboxes are actually a SecureMailboxAddress, the Fingerprint is used instead, which is useful if that user has more than 1 certificate in your database or you want to be extra sure that MimeKit picks the right one).
The other thing that MimeKit will do for you when you feed it a list of MailboxAddresses, is that it will look up the supported encryption algorithms that are stored in the database for said user.
For the WindowsSecureMimeContext, this involves looking at the S/MIME Capabilities X509 Certificate Extension attribute and decoding the supported encryption algorithms. In my experience, however, many times this extension is not present on X509 Certificates in the Windows certificate store and so MimeKit will have to assume that only 3DES CBC is supported.
For the DefaultSecureMimeContext, if you have verified any S/MIME signed message by said recipient, then that user's certificate (chain) and advertised encryption algorithms will be stored in MimeKit's custom SQL database (when you sign a message using S/MIME, it's fairly common practice for clients to include the S/MIME Capabilities attribute in the S/MIME signature data).
Now that you understand how that works, if you want to force the use of AES-128 CBC, the way to do that is to manually construct the CmsRecipientCollection yourself.
Naturally, this involves creating a new CmsRecipient for each recipient. To create this class, all you really need is the X509 certificate for that recipient.
var recipient = new CmsRecipient (certificate);
Since you want to force the use of AES-128 CBC, now you just need to override the encryption algorithms that this recipient supports:
recipient.EncryptionAlgorithms = new EncryptionAlgorithm[] {
EncryptionAlgorithm.Aes128
};
(By default, the EncryptionAlgorithms property will be set to the algorithms listed in the certificate's S/MIME Capabilities Extension attribute (in preferential order), if present, otherwise it'll just contain 3DES CBC).
If you also want to force RSAES-OAEP, you'll need to set:
recipient.RsaEncryptionPadding = RsaEncryptionPadding.OaepSha1;
Add each CmsRecipient to your CmsRecipientCollection and then pass that off to your preferred Encrypt() method and whallah, it will be encrypted using AES-128 CBC.
Notes:
MultipartSigned.Create() will produce a multipart/signed MIME part while ApplicationPkcs7Mime.Sign() will create an application/pkcs7-mime MIME part. Whichever one you want to use is up to you to decide, just keep in mind that your choice may impact compatibility with whatever client your recipients are using (I think most clients support both forms, but you might want to check to make sure).
If you've registered your custom SecureMimeContext class with MimeKit (as briefly described in the README), then you can feel free to use the various Encrypt/Decrypt/Sign/Verify/etc methods that do not take a cryptography context argument as MimeKit will instantiate the default context for you. Otherwise you will need to pass them a context.
I am able to successfully identify client certificates in a .NET thick client app, and the user is able to successfully select one.
X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var fcollection = store.Certificates.Find(X509FindType.FindByApplicationPolicy, "1.3.6.1.5.5.7.3.2", true);
// other stuff where user selects one of them
Now how do I ask the user to answer the challenge (e.g. PIN in this case)?
I see there's a SignedXML.ComputeSignature() class, but it takes a byte stream, and I'm not sure where that comes from (perhaps in certificate.RawData[]?).
I'm not really as interested in getting the actual pin as I am that the card/pin match.
EDIT:
I tried using the private key from the smart card (and even encrypted from it), but I don't get asked for my PIN.
RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)certificate.PrivateKey;
UnicodeEncoding ByteConverter = new UnicodeEncoding();
byte[] dataToEncrypt = ByteConverter.GetBytes("Data to Encrypt");
var encryptedData = RSAEncrypt(dataToEncrypt, rsacsp.ExportParameters(false), false);
Looks like the PIN request happens when I call RSACryptoServiceProvidersa.Decrypt.
Here's example code that worked perfectly for me in a Console app:
http://blog.aggregatedintelligence.com/2010/02/encryptingdecrypting-using.html
Much simpler in ASP.NET (aside from all the IIS config hassles/mysteries ...).
If this is a smartcard, the pin prompt will happen when you try to use the private key of the certificate.
You need to use the certificate somehow, and validate the result. For example, you might use the certificate to sign something. Once that signature operation happens, the pin prompt will appear.
If you don't really need to "use" the certificate, just want to validate that it's there and the user knows the pin, then you need some sort of proof of work. The certificate could be used to sign a challenge, and a remote server could validate the signature uses a key that belongs to a trusted root. Keep in mind this is difficult to get right, such as making sure you aren't open to a replay attack, etc.
We like to enable some hidden features of our software only if it is run inside of the company network. The key requirements are:
no need for a third party library outside of DotNet 4.5.1
easy to implement (should not be more than some dozens of lines .. I don't want to reimplement a crypto library)
It should be reasonable safe:
at least: hard to reverse engineer
at best: "impossible" to break even with read-access to the source code
low maintenance overhead
Win2012-Server is available for installation of additional software (open source or own implementation prefered - server can be assumed to be safe)
What I have thought about:
Check if a specific PC is available with a known MAC or IP (current implementation, not really secure and some other flaws)
Test, if a service is available on a specific response (i.e. I send 'Hello' to MyServer:12345 - server responses with 'World')
Similar to 2nd but a more complex challenge (i.e. send a seed for a RNG to the server, verify the response)
Set up an apache with HTTPS and verify the certificate
If you use ActiveDirectory, you could add a reference to the System.DirectoryServices namespace and check
ActiveDirectorySite currentSite = ActiveDirectorySite.GetComputerSite();
then you can get a bit of information from the currentSite object and check against that. That's how I enable/disable features of an application I'm developing currently.
I also grab:
var client = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in client.AddressList)
{
if(ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
ipAddress = ip;
}
}
Which you can check to make sure the client is connected with the proper protocol.
I've choosen the last option: Set up a webserver in the intranet and verify the certificate.
It was easier than expected. There are enough tutorials for setting up an apache with https for every supported OS. The self-signed certificate have a lifetime of 9999 days - should be okay until 2042. The C#-part is also reasonable small:
private static bool m_isHomeLocation = false;
public static bool IsHomeLocation
{
get
{
if (m_isHomeLocation)
return true;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://yourLicenseServer:yourConfiguredPort");
request.ServerCertificateValidationCallback += ((s, certificate, chain, sslPolicyErrors) => true);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
response.Close();
var thumbprint = new X509Certificate2(request.ServicePoint.Certificate).Thumbprint;
m_isHomeLocation = (thumbprint == "WhateverThumbprintYourCertificateHave");
}
catch
{
// pass - maybe next time
}
return m_isHomeLocation;
}
}
I am doing an SSL3 handshake using an SslStream, but, in spite of my best efforts, the SslStream never sends a client certificate on my behalf. Here is the code:
SSLConnection = new System.Net.Security.SslStream(SSLInOutStream, false, new System.Net.Security.RemoteCertificateValidationCallback(AlwaysValidRemoteCertificate), new System.Net.Security.LocalCertificateSelectionCallback(ChooseLocalCertificate));
X509CertificateCollection CC = new X509CertificateCollection();
CC.Add(Org.BouncyCastle.Security.DotNetUtilities.ToX509Certificate(MyLocalCertificate));
SSLConnection.AuthenticateAsClient("test", CC, System.Security.Authentication.SslProtocols.Ssl3, false);
and then I have AlwaysValidRemoteCertificate just returning true, and ChooseLocalCertificate returning the zeroth element of the array.
The code probably looks a little weird because the project is a little weird, but I think that is beside the point here. The SSL handshake completes. The issue is that instead of sending a certificate message on my behalf (in the handshake process), with the ASN.1 encoded certificate (MyLocalCertificate), the SslStream sends an SSL alert number 41 (no certificate) and then carries on. I know this from packet sniffing. After the handshake is completed, the SslStream marks IsAuthenticated as true, IsMutuallyAuthenticated as false, and its LocalCertificate member is null.
I feel like I'm probably missing something pretty obvious here, so any ideas would be appreciated. I am a novice with SSL, and this project is off the beaten path, so I am kind of at a loss.
P.S. 1: My ChooseLocalCertificate routine is called twice during the handshake, and returns a valid (as far as I can tell), non-null certificate both times.
P.S. 2: SSLInOutStream is my own class, not a NetworkStream. Like I said, though, the handshake proceeds mostly normally, so I doubt this is the culprit... but who knows?
For some reason the site won't let me leave a comment on the solution posted above, but it solved my problem. Here's my comment:
Hey, that was a great guess, and it fixed my issue. Thank you very much. The C# X509Certificate class doesn't seem to support setting a private key, but the daughter class X509Certificate2 does. I had to monkey around a bit with BouncyCastle stuff, but the implementation (if anyone needs it in the future) is this:
X509CertificateCollection CC = new X509CertificateCollection();
X509Certificate2 C2 = new X509Certificate2(MyLocalCertificate.GetEncoded());
C2.PrivateKey = Org.BouncyCastle.Security.DotNetUtilities.ToRSA((Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters)MyPrivateKey.Private);
CC.Add(C2);
MyLocalCertificate is an instance of bouncycastle's X509Certificate class, and MyPrivateKey is an instance of bouncycastle's AsymmetricCipherKeyPair.
I'm not familiar with the Bouncycastle .NET API for SSL, but at first look, I'm guessing that you aren't supplying the private key to the API.
Even though the private key itself is never sent to the server, it is required to digitally sign some data in order to prove to the server that you hold it. There should be some API to provide the private key for this signature operation.