I am using https://developer.linkedin.com/oauth-test-console to determine why I am getting a 401 unauthorised message.
For some reason, the signature produced in my code is not the same as the signature generated on this console. For testing purposes, the nonce, timestamp and baseString in the code have identical values to those used in the console. Yet the signatures still do not match?
I am using the following code to generate a signature:
HMACSHA1 hmacsha1 = new HMACSHA1();
hmacsha1.Key = Encoding.ASCII.GetBytes(string.Format("{0}&{1}", UrlEncode(), UrlEncode()));
string signature = Convert.ToBase64String(hashAlgorithm.ComputeHash(System.Text.Encoding.ASCII.GetBytes(baseString)));
UrlEncode() and UrlEncode() are the consumer secret and token secret, respectively, with url encoding applied.
I solved it. The logic above was incorrect.
I swapped around the token secret and consumer secret so it was LinkedinOAuthTokenSecret & LinkedinOAuthConsumerSecret ({1}{0}) rather than the other way around.
This created the correct key which could be used to make a legal signature.
Related
I'm trying to create a JWT token by signing it with a private key stored in an Azure key vault. The code I have works fine and it generates the token, but the token just wasn't working with the API I'm trying to log into. Generating the token via Python worked fine. I used jwt.io to decode the payload data and compared the tokens generated via C# and Python. I noticed that that the Issued At claim ("iat") is not surrounded by double-quotes in the Python generated token, but it had the quotes in the other one.
Is there a way to create a System.Security.Claim with an integer as the claim value? I tried including "Integer64" as the ValueType parameter, but it didn't work. In the code below I'm simply removing the quotes around the "iat" claim value and it works. Is this what I'm supposed to do?
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), "Integer64")
};
var payload = JsonSerializer.Serialize(new JwtPayload(claims));
payload = payload.Replace("iat\":\"", "iat\":").Replace("\"}", "}");
var headerAndPayload = $"{Base64UrlEncoder.Encode(header)}.{Base64UrlEncoder.Encode(payload)}";
Here is what the API expects (no double-quotes):
jwt.io decoded payload
In C# I tried removing the double-quotes around the "iat" claim value and it worked. This wasn't necessary when using jwt.encode in Python.
A better approach is to use the build in .NET Libraries to generate your JWT tokens, just to make sure you get everything right.
There are two different libraries in .NET for generating tokens and you can read about this here:
Why we have two classes for JWT tokens JwtSecurityTokenHandler vs JsonWebTokenHandler?
Here's a resource to explore for manually creating a token:
https://www.c-sharpcorner.com/article/jwt-validation-and-authorization-in-net-5-0/
I'm trying to validate a hmac sha256 key that an API sends to me in a header. I don't fully understand how to validate this, can anoyone point me in the right direction?
From the API reference:
Every central webhook POST contains a header field
"X-Signature-SHA256" with the signature value of the signed body's
payload. The JSON body is signed with the HMAC SHA256 algorithm based
on RFC2104, with the "Client Secret" as the signing key.
The following awnser is pretty clear & helpfull:
https://stackoverflow.com/a/12253723/4179234
However I feel like I'm missing a string/message that I need to encrypt to get the same hmac sha256 key, as I only have the api client secret to use as a key for the hasing but no message.
Following part is taken from the above stackoverlow awnser, so I convert the api client secret to byte[] and use it for the first input var. But what should message be then in this case?
private static byte[] HashHMAC(byte[] key, byte[] message)
{
var hash = new HMACSHA256(key);
return hash.ComputeHash(message);
}
I'm working on an implementation of EWP (Erasmus Without Papers), a set of API's for communication between universities world wide. Every call must be signed with RSA-SHA256. Every partner has a private and public key, the public keys are available in a registry with a keyId. A request has some headers: a digest of the body (sha256 hash), the host, date, x-request-id (random guid). These headers and their values are concatenated in a signing string, that is signed with the private key, the result being the signature that is also sent in the headers, along with the keyId and the other headers used for the signature.
The server needs to check if the signature is valid, by creating the same signing string, looking up the public key in the registry and then checking if the signature in the request is indeed correct.
I have both the API as a client application that I use to test. I have access to 109 different partners to test, some of them are not ready yet (they show an error both in my client and the online validator, so I assume there is an error in that API server side, as it is a development environment), others are working fine. There is also a test API of EWP and there is an online validator you can use to send requests to any of these API's.
Strange thing is, my client works with almost all of the partner API's, while some of them give me an error my signature is wrong (when the online validator works fine). Using my client on my own API, validation works fine. Using the online validator, my validation does not work (invalid signature error). There are 2 partners where my client does not work and the online validator does, and 3 API's where my client works but the online validator doesn't (including my own API)
Can anyone think of anything that could cause these issues? Strange thing is, 2 weeks ago, the online validator did work on my API and the only thing that changed according to the EWP guy, is the key pair used by the online validator, but he claims I'm using the correct one now (I sent him my logs)
Could it be some kind of character set issue? An error only happening when some character is used in the public key or something like that?
Here is my code for the RSA-SHA256 part:
Create a signature in the client request, with the private key:
public string CreateSignature(string SigningString)
{
byte[] message = Encoding.UTF8.GetBytes(SigningString);
PemReader pr = new PemReader(new StringReader(privateKeyString));
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyPair.Private);
RSACng rsaCng = new RSACng();
rsaCng.ImportParameters(rsaParams);
byte[] signatureBytes = rsaCng.SignData(message, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signatureBytes);
}
Check if the received signature is correct:
public bool IsRsaHashCorrect(string originalMessage, string hash, string stringPublicKey)
{
string x509Pem = #"-----BEGIN PUBLIC KEY-----" + stringPublicKey + "-----END PUBLIC KEY-----";
byte[] message = Encoding.UTF8.GetBytes(originalMessage);
byte[] signature = Convert.FromBase64String(hash);
byte[] Sha256Message = SHA256.Create().ComputeHash(message);
PemReader pr = new PemReader(new StringReader(x509Pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey);
RSACng rsaCng = new RSACng();
rsaCng.ImportParameters(rsaParams);
var result = rsaCng.VerifyHash(Sha256Message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
log.Info("check rsa-sha256 hash: " + result);
return result;
}
So, long story short: above code is working fine sometimes, failing other times. Why oh why?
Problem is fixed! Turned out there was an issue with the format of the dates in the request headers. This date is also used in the signing string for the signature. Different date formats means invalid signature.
The request sent by the online validator had the date Thu, 7 Jan 2021 14:01:58 GMT
When fetching this date in my code, it was transformed into Thu, 07 Jan 2021 14:01:58 GMT
(with a leading 0 in the day)
requestContext.Date = request.Headers.GetValues("date").Last();
Now when first validation fails, I try again with an altered date where the leading zero is deleted. Only if that fails too, the signature validation fails. Turns out some other partners are using leading zero's in their dates too, that's why my client did work with some API's and not with others...
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 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#.