Refactor key loading to support PEM + fix issue #62
authorSteven Bazyl <sqrrrl@gmail.com>
Fri, 2 Nov 2012 20:56:53 +0000 (13:56 -0700)
committerSteven Bazyl <sqrrrl@gmail.com>
Fri, 2 Nov 2012 20:56:53 +0000 (13:56 -0700)
lib/google/api_client/auth/key_utils.rb [new file with mode: 0644]
lib/google/api_client/auth/pkcs12.rb
lib/google/api_client/service_account.rb
spec/fixtures/files/privatekey.p12 [new file with mode: 0644]
spec/fixtures/files/secret.pem [new file with mode: 0644]
spec/google/api_client/service_account_spec.rb

diff --git a/lib/google/api_client/auth/key_utils.rb b/lib/google/api_client/auth/key_utils.rb
new file mode 100644 (file)
index 0000000..c70de50
--- /dev/null
@@ -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
index 84bcda54ee87b3d8bbf05f7b90bf7a6f0a417dc1..94c43185dbdd971bb7862f8ecd8acad498c0f370 100644 (file)
@@ -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
index 690bd054e47c8bdcee9be54e3d79dc2d8c49f0dc..737ac78cb387acab8c36122969254d1731861e86 100644 (file)
@@ -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 (file)
index 0000000..1e737a9
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 (file)
index 0000000..28b8d12
--- /dev/null
@@ -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: <No 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-----
index 2a2b38cfad1279a650a60a55be7df28c46262331..e338dcf21dae2a11e6c39fd00dca1880c0ddc4d9 100644 (file)
@@ -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