How to enumerate through multiple certificates in a bundle? - ruby

I can create OpenSSL::X509::Certificate objects from files containing certificates:
blob = IO.binread path
cert = OpenSSL::X509::Certificate.new blob
However, this creates the certificate-object from the first certificate found in the file. When dealing with certificate-bundles the subsequent certificates in the same file are quietly ignored.
I suppose, I can split the blob on "\n-----END CERTIFICATE-----\n" and feed each part to OpenSSL::X509::Certificate.new, but I'm not sure, that will work with all file-formats, that my script might encounter and be expected to operate on.
Is there a better way?

Did some digging, it seems that the Ruby openssl bindings doesn't support this. It also seems like this is not something that is common and/or easy to do with the raw C bindings. All the code I could find online shows doing stuff with a single cert, or using a X509 store to verify other certs. I've not seen any examples of having a single cert "object" refer to multiple certs, and/or iterating the certs in a store.
So I think the short answer is: no. At least nothing provided by the Ruby bindings can help you do this.
If your use case is to verify a certificate against a bundle of certificates, there's always OpenSSL::X509::Store, which has a method store.add_file that adds all the certs in that file to the store, and has methods to verify whether or not a cert is valid based on the bundle contents.

Ok, so with the assumption that only PEM-encoded certificates can be packed into a bundle, I wrote my code as below. If the assumption is invalid -- or if you can offer a better way, please let me know!
DELIMITER = "\n-----END CERTIFICATE-----\n"
blob = IO.binread path
blobs = blob.split(DELIMITER)
blobs.each do |blob|
blob += DELIMITER # Does not break DER
cert = OpenSSL::X509::Certificate.new blob
.... process the cert ....
end
Instead of splitting and then re-adding the delimiter, one could use scan, but that requires regular-expression use, which is probably more expensive, than what I'm doing...
This code works with DER-encoded certificates too -- but only one per file, because there is no obvious way to split them.

I do not know about ruby, but with C you can load a CRT (and CRL) bundle like
X509_STORE *store = X509_STORE_new();
X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
X509_LOOKUP_load_file(lookup, argv[i], X509_LU_X509);
and iterate over its elements with
for (int i = 0; i < sk_X509_OBJECT_num(store->objs); ++i) {
X509_OBJECT *o = sk_X509_OBJECT_value(store->objs, i);
switch (o->type) {
case X509_LU_X509:
handle_crt(o->data.x509);

Related

How to store the random key and iv created when trying to encrypt in ruby?

I'm currently working on a project where I will be storing data of users in text files. I would like to encrypt these files using the OpenSSL library within Ruby. I am able to encrypt files using randomly created key and iv values. As the program won't be running on a server but locally I would like to export these values with a password so that a different method could decrypt the information.
require "openssl"
cipher = OpenSSL::Cipher::AES256.new(:CBC)
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
encrypted = cipher.update("test.txt") + cipher.final
File.open("temp.txt", "w+") do |file|
file.write(encrypted)
end
If I don't use a random key it won't encrypt the information or returns errors.
'update': key not set (OpenSSL::Cipther::CipherError)
When I try to use the export commands (with random key) I also get an error saying that it isn't a method.
'export' for #<string:0x00000000065659b8> (NoMethodError)
It looks like you're not experienced with developing encryption schemes, so I'm going to suggest you not implement this yourself. The built-in OpenSSL library does not protect you from making security mistakes. As you've noticed, it provides no guidance on how to securely handle the key and iv (and doing it wrong would be insecure).
I recommend using the rbnacl gem, which has:
The RbNaCl::SimpleBox class provides a simple, easy-to-use cryptographic API where all of the hard decisions have been made for you in advance. If you're looking to encrypt something, and you don't know much about cryptography, this is probably what you want to be using.
Here's a full end-to-end example with outputting to & loading from disk:
require 'rbnacl'
generated_key = RbNaCl::Random.random_bytes(RbNaCl::SecretBox.key_bytes)
box = RbNaCl::SimpleBox.from_secret_key(generated_key)
ciphertext = box.encrypt('plaintext')
# save both artifacts to disk
File.open('cipher.bin', 'w') { |f| f.write(ciphertext) }
File.open('key.bin', 'w') { |f| f.write(generated_key) }
# decrypt by reading artifacts from disk
# this could be done in separate program
ciphertext_from_file = File.read('cipher.bin', mode: 'rb') # r -> "read", b -> "binary"
key_from_file = File.read('key.bin', mode: 'rb')
regenerated_box = RbNaCl::SimpleBox.from_secret_key(key_from_file)
regenerated_box.decrypt(ciphertext_from_file)
# => 'plaintext'
# cleanup
File.delete('cipher.bin') if File.exist?('cipher.bin')
File.delete('key.bin') if File.exist?('key.bin')
Note that generated_key and ciphertext are both binary strings. If you don't want to save raw binary files, then convert them using something like Base64.encode64 or Digest.hexencode, converting them to printable characters. That way, they'll be printable, copy/paste-able, and you won't need to do a "read binary" with 'rb'.

Generating Google2fa secrets based on a fixed string

We're building a system to validate mobile phone numbers.
To achieve this, when a user adds his number, we are sending him a text message with a 6 digit code.
We don't want this code to go in our database as we don't like to clutter our database with fields that have no business meaning.
So we came up with the idea to reuse pragmarx/google2falibrary, have it generate an OTP code, dispatch it to the user by a text message, and then the circle would be round.
So basically we wanted to use the phone number, prefixed by somehting secret as the "secret" for the pragmarx/google2fa library:
$secret = '1263' . $mobile->country_code . $mobile->subscriber;
$google2fa = new Google2FA();
$google2fa->setEnforceGoogleAuthenticatorCompatibility(false);
$google2fa->getCurrentOtp($secret);
The above results in a secretsimilar to 12633232970987. However, the call to getCurrentOtp throws an exception Invalid characters in the base32 string. which is obviously not what I was hoping for.
So, I tried adding
$secret = base_convert($secret, 10, 32)
and pass that to the getCurrentOtpmethod, but that returned the same error. Checking into the library code, I see the following constant:
const VALID_FOR_B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
My base_convert returns a string that has other characters in there though. Are these really the only valid characters?
My alternative would be to just have the library generate a random secret, but we don't really want to do that, as that would require us to keep that secret somewhere in the database (or at least in cache), which we would like to avoid. The idea is that we could generate the code for a mobile number using the number as the secret, and let the OTP mechanism deal with expiring codes.
Anyone has any suggestion how I can resolve this?

Ruby RSA public key encryption to Golang

I'm currently working on a project where I have to "convert" some code from Ruby(version 1.9.3p194) to Golang(version 1.7). There is this part where Ruby uses RSA public key encryption and I always get a consistent result every time it gets executed. This is the function used:
Edit: I overlooked that after the public key encryption, there is a base 64 encoding as well
public_key = OpenSSL::PKey::RSA.new(public_encryption_key)
public_encrypted_text = public_key.public_encrypt(text, OpenSSL::PKey::RSA::NO_PADDING)
base64_encrypted_text = Base64.encode64(public_encrypted_text).gsub("\n", "")
escaped_encrypted_text = URI.escape(encrypted_key, "/+=")
However in Golang, due to the rsa library I can't get a consistent result since the function to encrypt takes a random parameter to generate different result each time. I understand why it needs to be different every time, but i can't get anything remotely similar to what ruby generates. These are the functions used in Golang:
//keyBytes is the public key as []byte
block, _ := pem.Decode(keyBytes)
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
pubKey, ok := key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("Cannot convert to rsa.PublicKey")
}
result, err := rsa.EncryptPKCS1v15(cryptorand.Reader, pubKey, text)
encryptedText := base64.URLEncoding.EncodeToString(result)
encryptedText = strings.TrimRight(encryptedText, "=")
One of the problems is that ruby can encrypt the text with no problem, and in golang I'm getting an error that the key is too short to encrypt everything.
If I encrypt something else, like "Hello". When decrypting I get from ruby the error "padding check failed". The decryption is being handle like follows:
private_key.private_decrypt(Base64.decode64(text))
EDIT: Thanks to the answer of gusto2 I now know better what is going on since I didn't have much understanding of RSA.
Now in Golang I was able to encrypt the text using PKCS1 v1.5, and just to be sure I tried to decrypt that as well, also in Golang, with no problem.
However in Ruby I still wasn't able to decrypt using the private key. So I figured that the base64 encoding used in Golang was the issue. So I changed that part to this:
encryptedText := base64.StdEncoding.EncodeToString(result)
And also I removed the last line were the equal sign was being trimmed.
With that done it worked like a charm.
I am no knowledgeable about golang, however I may know something about RSA.
The difference seems to be in the padding.
For ruby - no padding is used
For golang - PKCS1v15 padding is used
In the rubyexample you use OpenSSL::PKey::RSA::NO_PADDING is used which is VERY VERY unsafe. It is called textbook RSA and is not inteded in real-life use as it has many weaknesses and dangerous traps. So the ruby example is very dangerously unsafe because of using the textbook RSA. As well it is limited to encrypting small messages (much smaller than the keyspace).
There are two padding types used with RSA:
PKCS1 v1 (commonly referred as PKCS1) - this is a deterministic padding (the output is always the same), many cryptographers consider this option obsolete as some weaknesses has been found when not used properly, but it is still in use and not considered broken.
PKCS1 v2 (commonly refered as OAEP or PSS) which is stochastic (randomized) padding. You can distinguish the last two as the output of OAEP is always different.
One of the problems is that ruby can encrypt the text with no problem, and in golang I'm getting an error that the key is too short to encrypt everything
You've provided only a small part of the golang example, so here I may only assume many things.
As you claim the golang example outputs randomized output and according to the parameters PKCS1 v1.5 is used, I'd assume the implementation is doing hybrid encryption which is good and much safer way to encrypt data with RSA (using symmetric encryption with random key and wrap/encrypt the key with RSA).

Sporadic errSecAuthFailed (-25293) when accessing KeyChain with SecKeychainFindGenericPassword?

i'm writing an app that stores passwords on the keychain, and later obtains them with SecKeychainFindGenericPassword(). this works file 90% of the time, but every once in a while, the call to SecKeychainFindGenericPassword() will fail with errSecAuthFailed (-25293). when it does, just trying again, or restarting the app fixes it.
does anyone have an idea what could be causing this? general Google search on this error points to keychain corruption or the keychain being locked - neither of which is the case here, since subsequent calls succeed again...
This link suggests the passphrase you entered is not correct. See Here
Is it possible that sometimes you are sending a null object as a passphrase just by chance?
Alternatively, you could try out EMKeychain. I have a more up-to-date version on GitHub here: http://github.com/ctshryock/EMKeychain
You haven't shared the code around your problem, So I'll just guess your problem is not with dysfunctional keychain, but rather some coding error.
Here is a common pitfall: Since KeyChain APIs are 'C', and they only accept C-style null terminated string buffers, you'll usually need to convert your CFString/NSString objects to C buffers before handing them to the API.
Many use things like:
const char *usernameCStr = [username UTF8String];
For an NSString, or its CFString companion...
const char *CFStringGetCStringPtr(CFStringRef theString, CFStringEncoding encoding); /* May return NULL at any time; be prepared for NULL */
Dismissing the fact these APIs may return NULL. Either because the internal buffer of the CF/NSString is non-contiguous, or not in the encoding you asked for, or otherwise not-c-compatible.
Such issue can behave in runtime exactly like what you describe.
In such cases you should catch the problem and use different API to copy the CF/NS string into a C-buffer:
Boolean CFStringGetCString(CFStringRef theString, char *buffer, CFIndex bufferSize, CFStringEncoding encoding);
or
- (BOOL)getCString:(char *)buffer maxLength:(NSUInteger)maxBufferCount encoding:(NSStringEncoding)encoding;
I'm not sure this was the problem (i don't see how it could have been) but i recently changed my code to properly pass the strlen() of the cStrings, rather then the length of the NSStrings into the call. Technically this is more correct (since the string length might differ from the cString, if UTF-8 dual-byte characters are involved.
BUT, none of the usernames/passwords i tested with contained non-ASCII characters, so i don;t see how this problem could have actually affected the errors I was seeing. My new code is as follows, and i have not seen the error with it:
UInt32 length;
void *data;
const char *account = [[BC_HOST stringByAppendingFormat:#":%#", login] cStringUsingEncoding:NSUTF8StringEncoding];
NSLog(#"Getting password from keychain.");
OSStatus s = SecKeychainFindGenericPassword (nil,
strlen(BC_APPNAME), BC_APPNAME,
strlen(account), account,
&length, &data, &keychainItem);
if (s != 0) NSLog(#"Error %d obtaining password from keychain.", s);
if (s == 0)
{
password = [[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding];
}
I was having the same issue and in my case this turned out to be the cause: Cannot access keychain item after SMJobBless update
One possible reason that this issue can occur is if the executable making the call does not have access to the keychain item. In Keychain Access you can see a list of the apps that have permission to access the item under the Access Control tab for the item in question.
If your app is running from a different location, you will get this error. For example, I have a Privileged Helper Tool that on my dev machine I typically run through Xcode as root. The path to this executable is the path at which Xcode creates it which is a path in ~/Library/Developer/Xcode/DerivedData//myexecutable. When I run it as a user would, it is being run from /Library/PrivilegedHelperTools/myexecutable. So if the password was initially created by one version of the app and I try to read it using the other path, I will see the errSecAuthFailed error.
This isn't the only reason. Someone else mentioned the upgrade in place problem that SMJobBless has. That can also result in the same error code, but for sure I see it for both reasons - although I programmatically solved the upgrade in place issue by ordering the helper tool to move itself to a different location before I do the upgrade.

Opening an RSA private key from Ruby

I think I know how to create custom encrypted RSA keys, but how can I read one encrypted like ssh-keygen does?
I know I can do this:
OpenSSL::PKey::RSA.new(File.read('private_key'))
But then OpenSSL asks me for the passphrase... How can I pass it to OpenSSL as a parameter?
And, how can I create one compatible to the ones generated by ssh-keygen?
I do something like this to create private encrypted keys:
pass = '123456'
key = OpenSSL::PKey::RSA.new(1024)
key = "0000000000000000#{key.to_der}"
c = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
c.encrypt
c.key = Digest::SHA1.hexdigest(pass).unpack('a2' * 32).map {|x| x.hex}.pack('c' * 32)
c.iv = iv
encrypted_key = c.update(key)
encrypted_key << c.final
Also, keys generated by OpenSSL::PKey::RSA.new(1024) (without encryption), don't work when I try password-less logins (i.e., I copy the public key to the server and use the private one to login).
Also, when I open an ssh-keygen file via OpenSSL and then check its contents, it appears to have additional characters at the beginning and end of the key. Is this normal?
I don't really understand some of this security stuff, but I'm trying to learn. What is it that I'm doing wrong?
According to the blog post here:
http://stuff-things.net/2008/02/05/encrypting-lots-of-sensitive-data-with-ruby-on-rails/
You can simply do:
OpenSSL::PKey::RSA.new(File.read('private_key'), 'passphrase')
Best of luck.
I've made some progress on this. If I use the Net::SSH library, I can do this:
Net::SSH::KeyFactory.load_private_key 'keyfile', 'passphrase'
By reading the source code I have yet to figure out what the library does to OpenSSL's PKey::RSA.new to accomplish this... And then I go and test again, and sure enough, OpenSSL can open the private key just fine without Net::SSH... I've made so much tests that somehow I didn't test this correctly before.
But I still have the issue of creating an SSH compatible key pair... and maybe I'll go test again and have the answer :P ... nah, I'm not that interested in that part

Resources