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.
Related
I have a USB token which have a X509Certificate/XmlDigSig on it protected by a pin code. (im not exactly sure what is that)
i am currently using the vendor's Library to sign bytes but the vendor code pops up a windows dialog requesting a pin code.
i would like to avoid the pin code popup and supply the code directly as part of my c#.
public byte[] GetSignedByteArr(byte[] request)
{
var signAndVerifyUtil = new GovIL.SignAndVerifySDK.Providers.XmlDigSig();
var signed = signAndVerifyUtil.Sign(request, GetSignParameters());
return signed.ContentInfo.signedContent;
}
public SignParameters GetSignParameters()
{
return new SignParameters(new CryptoSignatureInfo(Certificate.GetSignCertificateBySerialNumber("xxxxxxxxxxxxxxxxxxxxxxx", true)));
}
thanks to all!
X509Certificate is public certificate which used by SSL/TLS. it's based on public key & private key. every one can encrypt the content using the public key but only people who own the private key can decrypt and verify the content.
So we don't know any information about your hardware key and it's SDK.
If you use it to sign something like Cert Authorities the requested pin is private key and by the best practice you really avoid to hardcode it in your code or database, it's very risky. In this scenario maybe SDK developers forcing users to don't store the private key by exposing API which show dialog to user and don't let the developer to load key from memory or file.
You can do the pin input by simulation trick but again it's so forget it ;)
If you use the SDK for encrypting or signing something that verify somewhere else you use public key and it's wise that SDK developers let you to it by API calls, but we don't know anything about your library, so contact the SDK vendor.
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 am trying to figure out a way of authentication between two distributed services.
I don't want to have a shared secret distributed on every service host, because it would mean that once one host has been compromised, all hosts are compromised.
So my scenario is:
Host A knows the public key of Host B
Host A encodes and encryptes the jwt using Host B´s public key
Host B receives and decrypts the jwt using its private key, that it only knows itself.
The jose-jwt package:
https://github.com/dvsekhvalnov/jose-jwt
seems like a good option to me. Beside the signing of the jwt, it also supports encryption using private/public keys.
On the page there are the following examples for encoding and decoding a jwt:
Encode:
var publicKey=new X509Certificate2("my-key.p12", "password").PublicKey.Key as RSACryptoServiceProvider;
string token = Jose.JWT.Encode(payload, publicKey, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM);
Decode:
var privateKey=new X509Certificate2("my-key.p12", "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).PrivateKey as RSACryptoServiceProvider;
string json = Jose.JWT.Decode(token,privateKey);
Now, here is what i don´t understand:
How can I create a .p12 certificate file that only contains the public key information (for the host/service A that encodes the jwt) ?
.. and how can I create a .p12 certificate file that contains both, the public and the private key information (for the host/service B that decodes the jwt) ?
From all the research that I have done, i get the impression that you can either only make a .p12 file that contains both, or one that contains only the public key. But it seems there is no way to create two .p12 files, one with both information and one with only the public key. What am I missing?
Thanks for your answers.
Normally a PKCS12/PFX is not used for public-only, but you can do it if you like.
Assuming that cert.HasPrivateKey is true: cert.Export(X509ContentType.Pkcs12, somePassword) will produce a byte[] that you can write to "publicAndPrivate.p12" (or whatever).
Normally for a public-only certificate you'll write it down just as the X.509 data, either DER-binary or PEM-DER encoded. .NET doesn't make PEM-DER easy, so we'll stick with DER-binary. You can get that data by either cert.RawData, or cert.Export(X509ContentType.Cert) (both will produce identical results, since this export form has no random data in it). (publicOnly.cer)
If you really want a PKCS12 blob which has just the public certificate:
using (X509Certificate2 publicOnly = new X509Certificate2(publicPrivate.RawData))
{
return publicOnly.Export(X509ContentType.Pkcs12, somePassword);
}
The resulting byte[] could then be publicOnly.p12.
I don't really understand how to work with PKCS#7 messages.
I sign some byte array with a X509Certificate2 I have and get also a byte array.
byte[] data = new byte[5] { 110, 111, 112, 113, 114 }, signedData;
X509Certificate2 cert = new X509Certificate2(certPath, password);
ContentInfo content = new ContentInfo(data);
SignedCms envelope = new SignedCms(content);
CmsSigner cmsSigner = new CmsSigner(cert);
envelope.ComputeSignature(cmsSigner);
signedData = envelope.Encode();
The signedData is transmitted to some remote recipient and he gets the SignedCms envelope.
SignedCms envelope = new SignedCms();
envelope.Decode(signedData);
How can he decode the envelope? He doesn't pass my public key as a parameter. There's my public key in the envelope, in SignerInfo property, but is there any reason for that, cause anyone can replace it with the whole signature?
He can the recipient make sure, using my public key that he has, that the actual sender of the envelope is me?
There's method envelope.CheckSignature(new X509Certificate2Collection(certificate), true); but I tried to use wrong certificate and there was no exception thrown.
A PKCS#7 / CMS / S/MIME signed message is a data container which has (in addition to some other metadata):
EncapsulatedContentInfo
ContentInfoType
EncapsulatedContent (the message bytes)
Certificates (Optional)
CRLs (Optional)
SignerInfos
DigestAlgorithm (e.g. SHA-1)
SignedAttributes (Optional, allows other context information to be signed)
SignatureAlgorithm (e.g. RSA, DSA, ECDSA)
SignatureValue (the signature bytes)
UnsignedAttributes (Optional, allows for after-signing information, like counter-signatures)
(This is a summary of RFC 2630 (Cryptographic Message Syntax) Section 5)
SignedCms.Decode reads the encoded message and populates members. Every direct signatory to the message can be read from the SignedCms::SignerInfos property (counter-signers, or entities which have signed that they witnessed the original signature, can be read from SignerInfo::CounterSignerInfos).
When you call SignedCms.CheckSignature, it checks every SigerInfo and verifies that the signature can be successfully verified (or throws an exception), as well as that every counter-signer signature can be worked out.
What it doesn't know is that any of the signers "made sense". For that check you would need to loop over each SignerInfo and look at (for example) the Certificate property; then perform a suitability check:
Perhaps it is a pre-registered public key
Perhaps it chains up to a well-known root or intermediate CA
Perhaps it has some sort of Extension which shows it to be suitable
This part SignedCms cannot realistically do for you, since there's no default notion of "suitable" for messages, unlike the hostname verification of TLS.
If you want to assess the signature of a single signer, you can call SignedInfo::CheckSignature, but that's redundant if you also called SignedCms::CheckSignature.
There's method envelope.CheckSignature(new X509Certificate2Collection(certificate), true); but I tried to use wrong certificate and there was no exception thrown.
The extraCerts overloads provide extra certificates. It's valid to have a SignedCms message which does not embed the signer certificates, leaving it up to the recipient to have known the valid certs ahead of time (e.g. using a per-user database of pre-registered certificates). You didn't get an exception because the correct certificates were found within the provided certificates collection.
You can see what was in the provided certificates collection via the X509Certificate2Collection.Import methods; they can read a PKCS#7 signed-data message and populate the collection with the optional embedded certificates.
A PKCS#7 by itself is just a signature, could it be replaced? sure. envelope.CheckSiganture just validates that pkcs#7 has the right format and length, in other words checks if a pkcs#7 is well constructed.
Broadly putted, you need to implement a PKI (Private Key Infrastructure). Where in one end you construct your pkcs#7 using a public key, and on the other end you must validate that the pkcs#7 you have actually has a valid certificate that you recognize as your own. You must implement an OCSP to validate those certificates and if everything checks out all right you should and must request a timestamp to a third party to vouch for your pkcs#7. Also you will need a vault (database) to keep track of everything: pkcs#7's, data hashes, timestamps, original data, ocsp responses...
But if you are only interested in knowing how to identify a pkcs#7, there are various tools you could use to decode a PKCS#7, this action gives back all the information contained in it. Or you could create your own using c#.
I was recently assigned to task to encrypt some data and send it to our server. I found some valuable resources on the internet but they all require a public key, private key and secret password for the encryption. Please is there any simple way I can encrypt with just pgp key, since that's what I was given?
I assume that you have been given a public OpenPGP key. This one is enough to do encryption of data, which is intended to be decrypted by the person who gave you his public key.
In .NET you can use BouncyCastle or OpenPGPBlackbox package of our SecureBlackbox product. SecureBlackbox comes with extensive samples and support is offered as well (unlike alternatives).
Public PGP keys can encrypt data and verify signatures. Private PGP keys can decrypt data and sign data. If you have someone's public key, just use it, it won't prompt you for a password.
PGP simply works with pairs of private and public keys. The secret password is optional as far as i know.
The standard PGP encryption process in any language works as follows:
Step 1: Generate your private / public key pair
First generate your OpenPGP key pair e.g. with gnupg. The pair consists of a public key, which is used by the sender to encrypt the data and the private key, which is used by the recipient to decrypt the data.
Step 2: Share your public key and collect public keys of other parties
Each side will need to have the public keys of all the other parties. To do this step, you can give your public key using an usb stick or you upload it to a public key server.
Step 3: Encrypt and send your data
You write your data and encrypt it for the recipients.
You might also sign the data, which guarantees that the recipient can verify that the data has been created by you. After the encryption you send the data to the recipients.
Step 4: Authentication of data
You don't have to do this step but another benefit of asymmetric encryption such as PGP is that it allows for authentication. After you have exchanged public keys with your partners, the private keys can be used to digitally sign the encrypted content, allowing the decrypting site to verify the authenticity of the sender.
After data encryption is completed with the private key, you encrypt the session key with the public key that's known to the recipient (and maybe other parties as well). After that you can optionally create a hash of the encrypted data and sign this hash with your private key, this is called a signature.
Save the data in, for example, OpenPGP format.
Step 5: Decrypt data and verify signature
If you receive a data you decrypt it and if the data is signed, you verify the signature to be sure the data is sent by the sender to whom you have the public key.
Recently, I'm doing the PGP Encryption and sending files over to SFTP server. Here's the simple steps I follow with Python:
pip install py-pgp
Keep public_key in the same directory
Get recipients info gpg --list-keys
Script:
import os, gnupg
key = '<public_key>.asc'
src = '<file_to_be_encrypted>'
dst = './' #destination, it could be current directory
def encrypt(key, src):
home = os.path.join(os.getcwd(), '')
gpg = gnupg.GPG(gnupghome=home)
with open(key, "rb") as f:
keys = gpg.import_keys(f.read())
with open(src, "rb") as f:
result = gpg.encrypt_file(f, recipients='<name_retrieved_from_public_key>', output='<file_name>.pgp', always_trust=True)
if not result:
raise RuntimeError(result.status)
encrypt(key, src)
This will provide you the Encrypted file within the same directory.