diff --git a/lib/google/api_client/auth/key_utils.rb b/lib/google/api_client/auth/key_utils.rb new file mode 100644 index 000000000..c70de50cc --- /dev/null +++ b/lib/google/api_client/auth/key_utils.rb @@ -0,0 +1,93 @@ +# Copyright 2010 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Google + class APIClient + ## + # Helper for loading keys from the PKCS12 files downloaded when + # setting up service accounts at the APIs Console. + # + module KeyUtils + ## + # Loads a key from PKCS12 file, assuming a single private key + # is present. + # + # @param [String] keyfile + # Path of the PKCS12 file to load. If not a path to an actual file, + # assumes the string is the content of the file itself. + # @param [String] passphrase + # Passphrase for unlocking the private key + # + # @return [OpenSSL::PKey] The private key for signing assertions. + def self.load_from_pkcs12(keyfile, passphrase) + load_key(keyfile, passphrase) do |content, passphrase| + OpenSSL::PKCS12.new(content, passphrase).key + end + end + + + ## + # Loads a key from a PEM file. + # + # @param [String] keyfile + # Path of the PEM file to load. If not a path to an actual file, + # assumes the string is the content of the file itself. + # @param [String] passphrase + # Passphrase for unlocking the private key + # + # @return [OpenSSL::PKey] The private key for signing assertions. + # + def self.load_from_pem(keyfile, passphrase) + load_key(keyfile, passphrase) do | content, passphrase| + OpenSSL::PKey::RSA.new(content, passphrase) + end + end + + private + + ## + # Helper for loading keys from file or memory. Accepts a block + # to handle the specific file format. + # + # @param [String] keyfile + # Path of thefile to load. If not a path to an actual file, + # assumes the string is the content of the file itself. + # @param [String] passphrase + # Passphrase for unlocking the private key + # + # @yield [String, String] + # Key file & passphrase to extract key from + # @yieldparam [String] keyfile + # Contents of the file + # @yieldparam [String] passphrase + # Passphrase to unlock key + # @yieldreturn [OpenSSL::PKey] + # Private key + # + # @return [OpenSSL::PKey] The private key for signing assertions. + def self.load_key(keyfile, passphrase, &block) + begin + begin + content = File.read(keyfile) + rescue + content = keyfile + end + block.call(content, passphrase) + rescue OpenSSL::OpenSSLError + raise ArgumentError.new("Invalid keyfile or passphrase") + end + end + end + end +end diff --git a/lib/google/api_client/auth/pkcs12.rb b/lib/google/api_client/auth/pkcs12.rb index 84bcda54e..94c43185d 100644 --- a/lib/google/api_client/auth/pkcs12.rb +++ b/lib/google/api_client/auth/pkcs12.rb @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'google/api_client/auth/key_utils' module Google class APIClient ## @@ -30,18 +31,10 @@ module Google # Passphrase for unlocking the private key # # @return [OpenSSL::PKey] The private key for signing assertions. + # @deprecated + # Use {Google::APIClient::KeyUtils} instead def self.load_key(keyfile, passphrase) - begin - if File.exists?(keyfile) - content = File.read(keyfile) - else - content = keyfile - end - pkcs12 = OpenSSL::PKCS12.new(content, passphrase) - return pkcs12.key - rescue OpenSSL::PKCS12::PKCS12Error - raise ArgumentError.new("Invalid keyfile or passphrase") - end + KeyUtils.load_from_pkcs12(keyfile, passphrase) end end end diff --git a/lib/google/api_client/service_account.rb b/lib/google/api_client/service_account.rb index 690bd054e..737ac78cb 100644 --- a/lib/google/api_client/service_account.rb +++ b/lib/google/api_client/service_account.rb @@ -14,3 +14,4 @@ require 'google/api_client/auth/pkcs12' require 'google/api_client/auth/jwt_asserter' +require 'google/api_client/auth/key_utils' diff --git a/spec/fixtures/files/privatekey.p12 b/spec/fixtures/files/privatekey.p12 new file mode 100644 index 000000000..1e737a93a Binary files /dev/null and b/spec/fixtures/files/privatekey.p12 differ diff --git a/spec/fixtures/files/secret.pem b/spec/fixtures/files/secret.pem new file mode 100644 index 000000000..28b8d1205 --- /dev/null +++ b/spec/fixtures/files/secret.pem @@ -0,0 +1,19 @@ +Bag Attributes + friendlyName: privatekey + localKeyID: 54 69 6D 65 20 31 33 35 31 38 38 38 31 37 38 36 39 36 +Key Attributes: +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDYDyPb3GhyFx5i/wxS/jFsO6wSLys1ehAk6QZoBXGlg7ETVrIJ +HYh9gXQUno4tJiQoaO8wOvleIRrqI0LkiftCXKWVSrzOiV+O9GkKx1byw1yAIZus +QdwMT7X0O9hrZLZwhICWC9s6cGhnlCVxLIP/+JkVK7hxEq/LxoSszNV77wIDAQAB +AoGAa2G69L7quil7VMBmI6lqbtyJfNAsrXtpIq8eG/z4qsZ076ObAKTI/XeldcoH +57CZL+xXVKU64umZMt0rleJuGXdlauEUbsSx+biGewRfGTgC4rUSjmE539rBvmRW +gaKliorepPMp/+B9CcG/2YfDPRvG/2cgTXJHVvneo+xHL4ECQQD2Jx5Mvs8z7s2E +jY1mkpRKqh4Z7rlitkAwe1NXcVC8hz5ASu7ORyTl8EPpKAfRMYl1ofK/ozT1URXf +kL5nChPfAkEA4LPUJ6cqrY4xrrtdGaM4iGIxzen5aZlKz/YNlq5LuQKbnLLHMuXU +ohp/ynpqNWbcAFbmtGSMayxGKW5+fJgZ8QJAUBOZv82zCmn9YcnK3juBEmkVMcp/ +dKVlbGAyVJgAc9RrY+78kQ6D6mmnLgpfwKYk2ae9mKo3aDbgrsIfrtWQcQJAfFGi +CEpJp3orbLQG319ZsMM7MOTJdC42oPZOMFbAWFzkAX88DKHx0bn9h+XQizkccSej +Ppz+v3DgZJ3YZ1Cz0QJBALiqIokZ+oa3AY6oT0aiec6txrGvNPPbwOsrBpFqGNbu +AByzWWBoBi40eKMSIR30LqN9H8YnJ91Aoy1njGYyQaw= +-----END RSA PRIVATE KEY----- diff --git a/spec/google/api_client/service_account_spec.rb b/spec/google/api_client/service_account_spec.rb index 2a2b38cfa..e338dcf21 100644 --- a/spec/google/api_client/service_account_spec.rb +++ b/spec/google/api_client/service_account_spec.rb @@ -16,6 +16,37 @@ require 'spec_helper' require 'google/api_client' +fixtures_path = File.expand_path('../../../fixtures', __FILE__) + +describe Google::APIClient::KeyUtils do + it 'should read PKCS12 files from the filesystem' do + path = File.expand_path('files/privatekey.p12', fixtures_path) + key = Google::APIClient::KeyUtils.load_from_pkcs12(path, 'notasecret') + key.should_not == nil + end + + it 'should read PKCS12 files from loaded files' do + path = File.expand_path('files/privatekey.p12', fixtures_path) + content = File.read(path) + key = Google::APIClient::KeyUtils.load_from_pkcs12(content, 'notasecret') + key.should_not == nil + end + + it 'should read PEM files from the filesystem' do + path = File.expand_path('files/secret.pem', fixtures_path) + key = Google::APIClient::KeyUtils.load_from_pem(path, 'notasecret') + key.should_not == nil + end + + it 'should read PEM files from loaded files' do + path = File.expand_path('files/secret.pem', fixtures_path) + content = File.read(path) + key = Google::APIClient::KeyUtils.load_from_pem(content, 'notasecret') + key.should_not == nil + end + +end + describe Google::APIClient::JWTAsserter do include ConnectionHelpers