Signing Configuration Profiles with OpenSSL In Pure Ruby

In one of our recent projects, we needed to implement the ability to sign automatically generated configuration profiles for iOS and OS X [1] in the backend.

If a configuration profile is signed and iOS / OS X successfully verifies its signature, the profile looks like the following:

openssl-in-pure-ruby

The user sees immediately that the profile is signed and thus can be trusted, because it was signed by a trusted authority (in this example COMODO). The requirements of the project dictated that the generated profile must be signed with a valid signature. However, when we initially tried to sign it with various approaches the signature verification always failed. After a little research, we found that the intermediate certificates necessary to perform a successful verification were not present in the configuration profile. Dick Visser’s article “Sign Apple mobileconfig files” [2] led us to the right direction. Signing the configuration profile on the command line integrates the intermediate certificates and allows the profile to be verified successfully.

bashopenssl smime -sign -signer certificate.pem -inkey private_key.pem -certfile intermediate_certificates.pem -nodetach -outform der -in profile.mobileconfig -out profile_signed.mobileconfig

Our first solution looked like this:

rubydef sign_mobileconfig(mobileconfig)
  return `echo "#{mobileconfig}" | openssl smime -sign -signer "#{Rails.root}/config/keys/certificate.pem" -inkey "#{Rails.root}/config/keys/private_key.pem" -nodetach -outform der -certfile "#{Rails.root}/config/keys/intermediate_certificates.pem" -binary`
end

This spawns a separate process to invoke openssl. But we wanted to be able to sign the profiles with the OpenSSL API directly in Ruby, not by invoking an external program. Unfortunately, the documentation of OpenSSL itself and the documentation of the OpenSSL API in Ruby is very poor. Brian Campbell’s answer to the question “Digital signature verification with OpenSSL” on Stack Overflow [3] was the best explanation about how to sign a file with Ruby’s OpenSSL API we could find. However, the answer did not led us to a successful signature, because either the intermediate certificates were not present in the signed configuration profile or the signature creation failed (depending on how we configured the parameters of OpenSSL::PKCS7::sign).

The road to success is to bundle the (PEM-encoded) intermediate certificate and the root certificate in separate files, each certificate in its own file. Before invoking OpenSSL::PKCS7::sign, read all certificates and create OpenSSL::X509::Certificate instances. Create an Array with the certificate instances and provide it to OpenSSL::PKCS7::sign as the fourth parameter. The fifth parameter should be OpenSSL::PKCS7::BINARY. The following listing outlines the solution that fulfills our project’s requirements in a nice and clean manner.

rubysigning_cert_data =# Read from file.
signing_cert = OpenSSL::X509::Certificate.new(signing_cert_data)

private_key_data =# Read from file.
private_key = OpenSSL::PKey::RSA.new(private_key_data)

configuration_profile_data =# Read from file.

intermediate_cert1_data =# Read from file.
intermediate_cert1 = OpenSSL::X509::Certificate.new(intermediate_cert1_data)
intermediate_cert2_data =# Read from file.
intermediate_cert2 = OpenSSL::X509::Certificate.new(intermediate_cert2_data)
intermediate_certs = [ intermediate_cert1, intermediate_cert2 ]

signed_file = OpenSSL::PKCS7.sign(signing_cert, private_key, configuration_profile_data, intermediate_certs, OpenSSL::PKCS7::BINARY)

The full source code is available on GitHub [4]. We welcome any comments or suggestions to further improve it.

Let's talk about