From 1c849c7e7a135f61c71d4b619a0ff1040b7beac4 Mon Sep 17 00:00:00 2001 From: Steven Bazyl Date: Fri, 2 Nov 2012 13:56:53 -0700 Subject: [PATCH] Refactor key loading to support PEM + fix issue #62 --- lib/google/api_client/auth/key_utils.rb | 93 ++++++++++++++++++ lib/google/api_client/auth/pkcs12.rb | 15 +-- lib/google/api_client/service_account.rb | 1 + spec/fixtures/files/privatekey.p12 | Bin 0 -> 1732 bytes spec/fixtures/files/secret.pem | 19 ++++ .../google/api_client/service_account_spec.rb | 31 ++++++ 6 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 lib/google/api_client/auth/key_utils.rb create mode 100644 spec/fixtures/files/privatekey.p12 create mode 100644 spec/fixtures/files/secret.pem 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 0000000000000000000000000000000000000000..1e737a93a7a5c7a6f2dd34a8d5ee88a9b135e004 GIT binary patch literal 1732 zcmY+DdpHvcAIEnylS>=hghOZxmDFXY#rC!a$>92#3*X+nOJMN z^@fw;Wo}`mdQCPCMdfnh9JWqLZm*u_J?A{Cm%dx5jK}Vl^4zF?xgJ z<&vsZxsB|2`7^j*onI2z@um}z?jpGteAbHc{@6$o;fu;fQAS2gjp=ZMq~EAwB)gos zT4irn7=2aOZK9TsIj1rDp?;y}=w{x&&~9ZLMwt0B9UOx```gW0!JHQ1U8#j2<2Yew zult+J&dYrp===o3rRovyiH~N~^~EF+Rl4#b$DORYv(5k)^xNreUTc9)d zxxkD}zTZFCq+q}}B`r%%;&a!*fZy_o24?3|i3R;4KW&)h`Vicz;KC3}M;31F;neDF z!U)!nSx=|7XBkO8!2*VA%9B(TBt!XWcStd=)@eQq{oK{BVN7d35OM^utc^c$cRQ`GB>Cz$d`AeB_xF?~dE` z*N3Yi@n|C?AQli0hyo-5LI8red??c>m zFbY*@t*)n-p*8m^AB3`m?Wcpe=_C9EZN$plZHZ1^T*Jr0rN@TySQRTB%1BrvKZY8C zBTj?j?;Lmz62<@M7M$%3>LRtFL%T4;zb{MKyNrs(m`7h5%pY zn5l#ok9Vz%^XjSyCY@ZuIz^nG7j^dt?w!()8I?Q<9=aB+zP#B_S_<$C@hVBf_M^=Q z6xe_qioK&5DKx&S9cYlrS8cC6u~4!-HMc9YZasmfi`oq{F`FB<2T<~+l^c=gW}chI zt{%Z)!xH9S@YJvNFkc>#$}F5C@UZ>X3`sA_=QxET^_V3#%I7$X6H6UrgJ}g%?qAp3 zQY!uVi<97fbgSRdGiSmHFJrwgHnbM$z%k>Ba#%TDQ=%glBTy%m;cXwB#!Z{Qq56p+ zqZU2BW@e+1(^m+|Wt`AwGxtz{>u#sLRw0XQz8BWV?aJcLW?(B9i*xpSUYia-I;hrGbKyCK>ZDsGO)q7z}aaGD2u zqcT7n+zf8V^UygO^Je zTjI8E!LbVVU5)AhV)Y%ilK!uj3k=@5 z$80Nnc&o*DtooSAbk}81esJu9}9Jh6uzZe-zheK{zGGRX)XjA1zXAYWs~yPaLl-; OBFnCrSANW|i2M(Cw+V*; literal 0 HcmV?d00001 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