load SSL CA's from string as opposed to from file - ssl

I'd like to store my CAs in a string inside my binary instead of loading it in via SSL_CTX_load_verify_locations, which takes in file paths and folder paths.
However, I can't find a method that lets me take in a string.

I don't know of a documented way to do this. The only way I know of is to roll your own verification, e.g. create a store using X509_STORE_CTX_new, add the trusted CAs with X509_STORE_CTX_trusted_stack, add the certificate with X509_STORE_CTX_set_cert add some other chain certificates and CRLs with similar function and finally call X509_verify_cert on the X509_STORE_CTX.

OK I figured out how to do it. OpenSSL has a bunch of ways to deal with loading certs, and many of them add the certs to the non-trusted chain. You must use SSL_CTX_get_cert_store and X509_STORE_add_cert in conjunction. These two functions take in X509 pointers, which can be created from a raw c string. Since the documentation is pretty much non-existent for these two functions, so I figured I'd share the code here:
Edit: this is basically Steffen Ulrich's method except using X509_STORE_add_cert.
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/x509.h>
void read_cert_into_ctx(istream &some_stream, SSL_CTX *ctx) {
// Add a stream of PEM formatted certificate strings to the trusted store
// of the ctx.
string line;
string buffer;
while(getline(some_stream, line)) {
buffer.append(line);
buffer.append("\n");
if(line == "-----END CERTIFICATE-----") {
BIO *bio;
X509 *certificate;
bio = BIO_new(BIO_s_mem());
BIO_puts(bio, buffer.c_str());
certificate = PEM_read_bio_X509(bio, NULL, 0, NULL);
if(certificate == NULL)
throw std::runtime_error("could not add certificate to trusted\
CAs");
X509_STORE* store = SSL_CTX_get_cert_store(ctx);
int result = X509_STORE_add_cert(store, certificate);
BIO_free(bio);
buffer = "";
}
}
}

Related

CryptographicEngine::SignHashedData not implemented error

I encountered an error while using cppwinrt. When I use CryptographicEngine::SignHashedData function to sign a hash value, it returns WinRT originate error - 0x80004001 : 'not implemented'.
Here is the code:
#include "pch.h"
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Security::Cryptography;
using namespace Windows::Security::Cryptography::Core;
using namespace Windows::Storage::Streams;
using namespace std;
int main()
{
init_apartment();
IBuffer buffKeyPair;
IBuffer buffPublicKey;
IBuffer nullBuff(nullptr);
uint32_t asymmetricKeyLength = 512;
hstring strMsg = L"zzp yes!";
hstring strAsymmetricAlgName = AsymmetricAlgorithmNames::RsaPkcs1();
hstring strAlgNameH = HashAlgorithmNames::Md5();
AsymmetricKeyAlgorithmProvider objAlgProv = AsymmetricKeyAlgorithmProvider::OpenAlgorithm(strAsymmetricAlgName);
CryptographicKey keyPair = objAlgProv.CreateKeyPair(asymmetricKeyLength);
IBuffer buffUtf8Msg = CryptographicBuffer::ConvertStringToBinary(strMsg, BinaryStringEncoding::Utf8);
HashAlgorithmProvider objAlgH = HashAlgorithmProvider::OpenAlgorithm(strAlgNameH);
IBuffer buffHash = objAlgH.HashData(buffUtf8Msg);
IBuffer signedBuff = CryptographicEngine::SignHashedData(keyPair, buffHash);
hstring strHashHex = CryptographicBuffer::EncodeToHexString(signedBuff);
std::cout << "the strHashHex is: " << winrt::to_string(strHashHex) << std::endl;
}
Could you please help me find the problem? Thanks!
I'm not too experienced in this space, but I think I figured out a couple issues. This is happening because you aren't using an algorithm that can be used for signing, and you aren't using a hash algorithm compatible with it. The docs seem a bit... sparse... in this regard, but the algorithm names that have "sign" in them looked promising.
After a little experimentation, I got this to work.
using namespace Windows::Security::Cryptography;
using namespace Windows::Security::Cryptography::Core;
using namespace Windows::Storage::Streams;
using namespace std;
int main()
{
init_apartment();
auto algo_name = AsymmetricAlgorithmNames::RsaSignPkcs1Sha1();
auto asymmetric_provider = AsymmetricKeyAlgorithmProvider::OpenAlgorithm(algo_name);
auto keyPair = asymmetric_provider.CreateKeyPair(512);
auto md5_hasher = HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha1());
auto buffHash = md5_hasher.HashData(
CryptographicBuffer::ConvertStringToBinary(
L"Hello world", BinaryStringEncoding::Utf8));
auto signature = CryptographicEngine::SignHashedData(keyPair, buffHash);
auto publicKey = asymmetric_provider.ImportPublicKey(keyPair.ExportPublicKey());
auto match = CryptographicEngine::VerifySignatureWithHashInput(
publicKey, buffHash, signature);
}
Notice that I used RsaSignPkcs1Sha1 which seems to indicate that this algorithm can do signing. Also, for the hash algorithm, I used Sha1. Using Md5 gave a different runtime error (and Md5 is pretty weak, anyway). I also added the code to show how to use your key pair's public key to verify the signature.
After try your code and more tests, I have found that RsaSignPkcs1Sha1 is needed when you want to sign a content, the RsaPkcs1 does not support signing. Besides, I found that Windows only support RSA signing with SHA hashes, it does not support sign a md5 hash. Further more, the two names (RsaSignPkcs1Sha1 and Sha1) must match, or you will encounter an error. I don't known how it works in the back. I also found that sign a content with the sign function is equvalent with sign a content's hash with SignHashedData function, It seems that sign function first computes the hash before signing it.

How to read RSA public key from file/string in java/kotlin using BouncyCastle

I am trying to parse a public key in the RSA format from kotlin. I have the key in a string whose contents are like this:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwAzOKC8d0o0dcv1KqILLehASGgOWyjlAc+adazix6ThhX7QeD3Qw
HzxPpbwsJrVPIEMEIN383awIqnCfIL+AbCQPL13XaUCCS74wC5a84X1r6hcI5XO1
9CPAn+jBKmTr4hPaHWKxuhfO3PcXxGfQdXyqNT96bCYnAYaeSECohFjqDbe+RFcL
1lIns2GtQPMh1/uDyhPA+8HSguREWn+Ac3I2c0wtrzZa6R4nruPgIi6XbWRqAskr
tzbO2Xy6O1DcERH9tg+es/pbrWHRHrwEmLXorj3iGqkJJBUmLeW6B5EjmIgiukdJ
dw7bLTNcwf2n0BLJy/bgnhcw4TMOzUadSQIDAQAB
-----END RSA PUBLIC KEY-----
I found a lot of code examples to do this that involve trimming out the BEGIN and END, using String.replace() but that seemed hacky to me. The BouncyCastle code seems to handle this already including the ability to create a parsers for the different types of "files" it encounters. I'm trying this:
try {
val parser = PEMParser(StringReader(publicKeyString))
val pemObject = parser.readPemObject()
val pemContent = pemObject.content
val key = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(pemContent)
serviceLogger.info("Key object: {}", key)
} catch (e: Exception) {
serviceLogger.error("Could not generate key from keyspec", e)
}
I get as far as a pemContent (an array of bytes) without a problem, but when I try to actually parse that into an RSAPublicKey I get this:
java.lang.IllegalArgumentException: failed to construct sequence from byte[]: DEF length 3 object truncated by 2
What I can't figure out is if I'm calling RSAPublicKey.getInstance() correctly - with the content of the entire PemObject - or is this exception telling me that there's something wrong with my key.
The examples I have been able to find on this are pretty old, and the APIs seem to have evolved to the point that I shouldn't have to be chopping up strings manually.
I doubt this really helps the matter, but I'm generating this file in go from an rsa keypair:
func PublicKeyToPemBytes(prvkey *rsa.PrivateKey) ([]byte, error) {
var pubkey *rsa.PublicKey
pubkey = &prvkey.PublicKey
pubkey_bytes := x509.MarshalPKCS1PublicKey(pubkey)
if pubkey_bytes == nil {
return nil, errors.New("Public key could not be serialized")
}
pubkey_pem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubkey_bytes,
},
)
return pubkey_pem, nil
}
The go rsa.PublicKey object contains an N and an E. It gives me a file like listed above, and the base64 decoding results in the exact same length, 270 bytes.
dave_thompson_085 was right. This code actually does work. He asked me if I altered the data, which made me look more closely at it and I realized I was doing a .toUpperCase() on the base64. The result was still perfectly valid base64 with some bits flipped on here and there. I shouldn't have been doing that .toUpperCase() at all, I just didn't see it until he said that. Bouncy does work.

iText 7 PDF signing with GlobalSign DSS

I'm digitally signing a PDF using iText 7 in an asp.net app. I'm using vb but I could figure out a c# answer, too. We're using GlobalSign DSS in order to have the signature not come up as UNKNOWN (AATL).
I created a pfx file from our company SSL certificate and I am able to sign the PDF files using that file but I don't understand how to use the things I get from GlobalSign to accomplish the same thing. This service requires to send a hex encoded SHA256 digest and in return I receive a hex encoded signatureValue. Besides that I also receive a PEM encoded X509 signing certificate, intermediate certificate (PEM encoded X509 certificate that represents the issuer of the signing certificate), Base64 encoded DER representation of the OCSP response for signing_certificate, a Base64 encoded DER representation of timestamp token. It includes the TSA signing certificate in SignedData.CertificatesSet.
I created the code below as best as I could with limited examples from the internet for itext 7 and my even more limited knowledge of PDF signing and certificates in general. I know now that there are some things I make that I don't even use properly such as the tsaClient and the crlList. I didn't quite understand what those were until I had to switch to the DSS. I actually still don't know what the crlList would be although I think it has something to do with a chain, although I don't quite know what a chain is in this context.
Dim outputFileStream As New FileStream(Server.MapPath(strDest), IO.FileMode.Create, IO.FileAccess.ReadWrite)
Dim signer As New PdfSigner(New PdfReader(Server.MapPath(strTemp)), outputFileStream, True)
signer.SetCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS)
Dim appearance As PdfSignatureAppearance = signer.GetSignatureAppearance
Dim rect As New iText.Kernel.Geom.Rectangle(50, 425, 500, 200)
appearance.SetContact("Contact Name - Phone")
appearance.SetSignatureCreator("Company - New Program")
appearance.SetReason(vbCrLf + "eDoc Consent Agreement: " + CType(Session("eDocConsentDateTime"), Date).ToString + vbCrLf _
+ "Terms and Conditions Agreement: " + objInstall.AgreementSigned.ToString + vbCrLf _
+ "Identifier: " + Session("Ident"))
appearance.SetLocation(vbCrLf + strInstallationAddress)
appearance.SetReuseAppearance(False)
appearance.SetPageRect(rect)
appearance.SetPageNumber(2) 'Signature will appear on Page 2
appearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION)
signer.SetFieldName(signer.GetNewSigFieldName())
Dim cert As New X509Certificate(Server.MapPath("CompSSL.pfx"), "password")
Dim tsaUrl As String = CertificateUtil.GetTSAURL(DotNetUtilities.FromX509Certificate(cert))
tsaClient = New TSAClientBouncyCastle(tsaUrl)
Dim crlList As IList(Of ICrlClient) = New List(Of ICrlClient)()
Dim fsCertFile As New FileStream(Server.MapPath("CompSSL.pfx"), FileMode.Open, FileAccess.Read)
Dim pk12 As New Pkcs12Store(fsCertFile, "password")
fsCertFile.Dispose()
'then Iterate throught certificate entries to find the private key entry
Dim [alias] As String = Nothing
For Each tAlias As String In pk12.Aliases
If pk12.IsKeyEntry(tAlias) Then
[alias] = tAlias
Exit For
End If
Next
Dim pk = pk12.GetKey([alias]).Key
Dim myExternalSignature As IExternalSignature = New PrivateKeySignature(pk, DigestAlgorithms.SHA1)
signer.SignDetached(myExternalSignature, New Org.BouncyCastle.X509.X509Certificate() {pk12.GetCertificate([alias]).Certificate}, Nothing, Nothing, Nothing, 0, PdfSigner.CryptoStandard.CMS)
So I'd like to know how I can use the GlobalSign pieces in order to sign the PDF. It almost seems like they are giving me everything I need to plug into the SignDetached method at the very end but I specifically don't know these things:
I believe that I'm supposed to do the hash to create the digest AFTER I've added the appearance stuff but I don't know what I would pass those bytes to something like Dim hash() As Byte = s.ComputeHash(fileBytes) at that point in my code (right after signer.SetFieldName(signer.GetNewSigFieldName()) I assume)
I don't know how or if I use the two certificates I get from the DSS. I even saw an example that suggests I may put them together to get something.
I don't know if the Signature Value I get is the equivalent of the IExternalSignature. If so, I don't know how to convert it to type "Org.BouncyCastle.Crypto.ICipherParameters" or even if I'm supposed to be trying to.
I plug the timestamp token string I get back from the DSS into the line tsaClient = New TSAClientBouncyCastle(tsaUrl) in the place of "tsaUrl" and there is no error but I don't know if that's right.
I don't know what to do with the OCSP response that I get from the DSS to put it into an IOcspClient object.
If I don't even try to put in the timestamp or the OCSP response in, as apparently I don't in the .pfx version of my code, what will that mean?
What are the data type conversions I'll have to do, if any, to make the DSS responses work?
I've tried to follow the iTextsharp examples, even this one (itextsharp signing pdf with signed hash) which is exactly what I'm trying to do but it doesn't seem to give me the info I need to finish it off. Thanks for your help.
EDIT:
Thanks for the help, dsandoval. I went ahead and hash the unchanged file and that's what I send to GlobalSign:
Dim s As New SHA256Managed
Dim fileBytes() As Byte = IO.File.ReadAllBytes(Server.MapPath(strSrc))
Dim hash() As Byte = s.ComputeHash(fileBytes)
Dim calculatedHash As String = ByteArrayToString(hash)
With this too:
Private Function ByteArrayToString(ByVal arrInput() As Byte) As String
Dim sb As New System.Text.StringBuilder(arrInput.Length * 2)
For i As Integer = 0 To arrInput.Length - 1
sb.Append(arrInput(i).ToString("X2"))
Next
Return sb.ToString().ToUpper
End Function
Then I wrote the two certificates I get from GS to files and then use BouncyCastle to read them into certificates and put them into an array (signing cert first). I pick up where I am writing the second cert file from their 'certificate_path' string here:
Using sw As New StreamWriter(File.Open(strFile2, FileMode.OpenOrCreate))
sw.WriteLine(strTempCert2)
End Using
Dim fileStream2 = System.IO.File.OpenText(strFile2)
Dim pemReader2 = New Org.BouncyCastle.OpenSsl.PemReader(fileStream2)
Dim myCertificateChain() As Org.BouncyCastle.X509.X509Certificate = New Org.BouncyCastle.X509.X509Certificate() {pemReader.ReadObject, pemReader2.ReadObject}
I did the same thing you did with IExternalSignature using this to convert the string from GS:
Private Function StringToByteArray(s As String) As Byte()
' remove any spaces from, e.g. "A0 20 34 34"
s = s.Replace(" "c, "")
' make sure we have an even number of digits
If (s.Length And 1) = 1 Then
Throw New FormatException("Odd string length when even string length is required.")
End If
' calculate the length of the byte array and dim an array to that
Dim nBytes = s.Length \ 2
Dim a(nBytes - 1) As Byte
' pick out every two bytes and convert them from hex representation
For i = 0 To nBytes - 1
a(i) = Convert.ToByte(s.Substring(i * 2, 2), 16)
Next
Return a
End Function
For now I'm just including the certificate chain and the signature and not the timestamp or the OCSP to see if I can just get it signed. With the certificate chain and the implementation of IExternalSignature, I'm able to get SignDetached to run:
signer.SignDetached(myExternalSignature, myCertificateChain, Nothing, Nothing, Nothing, 0, PdfSigner.CryptoStandard.CMS)
The PDF is signed but the signature panel says "Certification by is invalid", "Certified by %s". The details say "There are errors in the formatting or information contained in this signature". When clicking on "Certificate details", nothing happens. I found another person online with this error (Error signing pdf with PHP's openssl_pkcs7_sign) and it was due to the signature container being too big. The zero in my SignDetached parameters is supposed to be "estimate the size" so I changed it to 8192 which was a magic number of 8k I found in some other examples online. That didn't help. Any suggestions at this point?
I'm using c# for my implementation. I'm not too familiar with VB but hopefully this helps. Most of the examples I've seen are using Java and there were some differences in how they implement this, so I had to work around them. The only things I had to implement for this work was the IExternalSignature, IOcspClient, and ITsaClient.
The digest that is sent to GlobalSign are the contents of the document prior to applying to the signature, so the appearance can be left out if applied with signature. The document is not modified until you apply the signature using the PdfSigner.SignDetached, I believe. To compute the digest, you need to use the SHA256 algorithm to hash the message and hexencode it. This is the digest that will be sent to GS.
You need to put both the certificates from GlobalSign to create the certificate chain. First add the signing certificate then the certificate you can retrieve from their 'certificate_path' endpoint.
I'm not sure about the conversion to that type, but with the c# library, the Sign method of IExternalSignature returns a byte[]. With my own implementation of the interface, I simply converted the SignatureValue we received from GS to a byte[].
For the timestamp, we also implemented our own ITsaClient that calls GS for the timestamp. The digest has to be calculated in the GetTimeStampToken method and sent to GS. The response is a hexencoded string that we convert to a byte[].
For the OCSP, we also implemented our own IOcspClient. Since we get the ocsp_response when we fetch the identity, this client simply uses that, rather than calling GS again. I had issues enabling LTV when returning the response by only converting it from a Base64 string to a byte[]. BouncyCastle had trouble reading the response in this format. I had to convert it to the following BouncyCastle objects for this to work properly.
public byte[] GetEncoded( X509Certificate checkCert, X509Certificate issuerCert, string url )
{
var decoded = Base64.Decode(_ocspResponse); //_ocspResponse is same one that we received when fecthing the identity
var response = new OcspResp(decoded);
var basicResponse = (BasicOcspResp)response.GetResponseObject();
return basicResponse.GetEncoded();
}
If no timestamp is attached, the signature will not be timestamped. It will display the time as something along the lines of "the time is time on the signer's computer's system clock" when you open the document in Adobe. If no ocsp response is attatched, the person validating will not have a way to validate that the certificate has/hasn't been revoked at the time of the signature.
I believe the only conversions I had to make were the one mentioned above.
Let me know if you need any more code examples or any other questions.
EDIT
Here is the IExternalSignature implementation. The hash and encryption algorithm has to match what GS will use. They are using SHA256 and RSA.
public class ExternalSignature : IExternalSignature
{
private readonly SigningIdentity _identity;
private readonly GlobalSignClient _client;
public ExternalSignature( GlobalSignClient client, SigningIdentity identity )
{
_client = client;
_identity = identity;
}
public string GetEncryptionAlgorithm() => "RSA";
public string GetHashAlgorithm() => DigestAlgorithms.SHA256;
public byte[] Sign( byte[] message )
{
// Using the System.Security.Cryptography implementation of SHA256
using (var sha = SHA256.Create())
{
// Hash the message, containing the PDF
var bytes = sha.ComputeHash(message);
// HexEconde the hashed message to send to GlobalSign
var digest = Helpers.ByteArrayToString(bytes);
// Send the digest to GlobalSign
var signature = _client.GetSignatureAsync(_identity.Id, digest).Result;
// Return the returned signature as a byte[]
return Helpers.StringToByteArray(signature);
}
}
}
To add the signature, just create an instance and pass it to SignDetached method.
var signature = new ExternalSignature(_client, signingIdentity);
...
...
//After creating the PDF signer, adding appearance, creating the OCSP and TSA clients.
signer.SignDetached(signature, certChain, null, ocsp, tsa, 0, PdfSigner.CryptoStandard.CMS);
I did the same with the OCSP and TSA clients.

How to build a RFC 3161 time stamp using Bouncy Castle

we need to implement our own PDF timestamping mechanism based on X509 certificate (including private key of course) and RFC 3161. I've googled and asked here on SO and proper solution would be to re-implement TSAClient class to do timestamping locally for us (without online TSA). However I didn't find any implementation of RFC 3161 except SecureBlackbox components. It should be possible with Bouncy Castle libraries but I don't know how.
Can you please point me to right direction?
It is possible to generate a RFC3161 timestamp token with Bouncycastle libraries.
First create a TimestampRequest. For your case it is only a wrapper for the digest algorithm and the digest value.
byte[] document = /* ... */
byte[] digest = MessageDigest.getInstance("SHA256").digest(document);
TimeStampRequestGenerator tsReqGen = new TimeStampRequestGenerator();
TimeStampRequest tsReq = tsReqGen.generate(CMSAlgorithm.SHA256, digest);
Then generate the token
DigestCalculator dgCalc = new JcaDigestCalculatorProviderBuilder().build();
ContentSigner signer = new JcaContentSignerBuilder().build(getPrivateKey());
SignerInfoGenerator siGen = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder()).build(signer, getCertificate());
ASN1ObjectIdentifier policy = new ASN1ObjectIdentifier("1.2.3.4.5.6"); // Replace by your timestamping policy OID
TimeStampTokenGenerator tstGen = new TimeStampTokenGenerator(siGen, dgCalc, policy);
/* Set the parameters e.g. set the accuracy or include the signing certificate */
TimeStampToken tst = tstGen.generate(tsReq, generateSerialNumber(), new Date());
byte[] encoding = tst.getEncoded();

Adding trust for a X509 CA certificate imported into keychain on OS X

Recently I wrote a little chunk of code that grabs a CA certificate from a SCEP server, turns it into a SecCertificateRef and adds it to a keychain (either System or login). Now I'm wondering how I can get the system to trust that certificate. I've been playing around with Trust Policies but I haven't had much luck yet.
On top of this, I understand that the system may not allow you to automatically trust a certificate without user interaction. If that's the case, how do you kick off the interaction? Using "SecCertificateAddToKeychain" puts the certificate into the keychain silently.
Side note: I'm trying to support 10.5 with this code as well.
Thanks for any help!
Edit:
After playing around with the code on the Citrix page I came up with my own function. From what I gathered from the Citix page, this method is destructive. So if the certificate is already in the keychain and already has policies (iChat, etc) this will overwrite those. Since I don't care about that in my project, here's a simpler version I came up with.
-(OSStatus) addCertificate: (CertificateWrapper *) cert trust:(BOOL) shouldTrust {
//keychain is a SecKeychainRef created with SecKeychainOpen
OSStatus result = SecCertificateAddToKeychain([cert certificate], keychain);
if((result == noErr || result == errKCDuplicateItem) && shouldTrust){
SecTrustSettingsDomain domains[3] = { kSecTrustSettingsDomainSystem, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainUser};
for(int i = 0; i < 3; i++){
CFMutableArrayRef trustSettingMutArray = NULL;
trustSettingMutArray = CFArrayCreateMutable (NULL, 0, &kCFTypeArrayCallBacks);
result = SecTrustSettingsSetTrustSettings([cert certificate], domains[i], trustSettingMutArray );
if(result == noErr){
break;
}
}
}
return result;
}
There is a great example of how to do this on the Citrix web site with a ton of sample code.