├── Manifest.txt ├── Rakefile ├── History.txt ├── bin └── osx_keychain ├── test └── test_osx_keychain.rb ├── .autotest ├── README.txt └── lib └── osx_keychain.rb /Manifest.txt: -------------------------------------------------------------------------------- 1 | .autotest 2 | History.txt 3 | Manifest.txt 4 | README.txt 5 | Rakefile 6 | bin/osx_keychain 7 | lib/osx_keychain.rb 8 | test/test_osx_keychain.rb 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require "hoe" 4 | 5 | Hoe.add_include_dirs("../../RubyInline/dev/lib", 6 | "../../ZenTest/dev/lib") 7 | 8 | Hoe.plugin :seattlerb 9 | 10 | Hoe.spec "osx_keychain" do 11 | developer "Ryan Davis", "ryand-ruby@zenspider.com" 12 | 13 | dependency "RubyInline", "~> 3" 14 | 15 | license "MIT" 16 | end 17 | 18 | # vim: syntax=ruby 19 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | === 1.0.2 / 2018-03-16 2 | 3 | * 1 bug fix: 4 | 5 | * Remove warnings when building on 64-bit architectures. (guykogus) 6 | 7 | === 1.0.1 / 2015-05-07 8 | 9 | * 2 bug fixes: 10 | 11 | * Fixed tests to not bother if we're not logged into OSX 12 | * Removed dead rubyforge setting in Rakefile 13 | 14 | === 1.0.0 / 2009-10-16 15 | 16 | * 1 major enhancement 17 | 18 | * Birthday! 19 | -------------------------------------------------------------------------------- /bin/osx_keychain: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'osx_keychain' 4 | 5 | keychain = OSXKeychain.new 6 | 7 | action = ARGV.shift 8 | 9 | abort "usage: #{File.basename $0} (get|set) service [user] [pass]" unless action 10 | 11 | case action 12 | when "get" then 13 | p keychain[ARGV.shift, ARGV.shift] 14 | when "set" then 15 | keychain[ARGV.shift, ARGV.shift] = ARGV.shift 16 | else 17 | warn "unknown command: #{action}" 18 | end 19 | -------------------------------------------------------------------------------- /test/test_osx_keychain.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "osx_keychain" 3 | 4 | CAN_TEST = ENV['SECURITYSESSIONID'] 5 | 6 | class TestOsxKeychain < Minitest::Test 7 | def test_sanity 8 | keychain = OSXKeychain.new 9 | 10 | serv, user, pass = %w[osx_keychain_test username password] 11 | 12 | keychain[serv, user] = pass 13 | 14 | assert_equal pass, keychain[serv, user] 15 | assert_equal pass, keychain[serv] 16 | end if CAN_TEST 17 | end 18 | -------------------------------------------------------------------------------- /.autotest: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require 'autotest/restart' 4 | 5 | Autotest.add_hook :initialize do |at| 6 | at.testlib = "minitest/autorun" 7 | # at.extra_files << "../some/external/dependency.rb" 8 | # 9 | # at.libs << ":../some/external" 10 | # 11 | # at.add_exception 'vendor' 12 | # 13 | # at.add_mapping(/dependency.rb/) do |f, _| 14 | # at.files_matching(/test_.*rb$/) 15 | # end 16 | # 17 | # %w(TestA TestB).each do |klass| 18 | # at.extra_class_map[klass] = "test/test_misc.rb" 19 | # end 20 | end 21 | 22 | # Autotest.add_hook :run_command do |at| 23 | # system "rake build" 24 | # end 25 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | = osx_keychain 2 | 3 | home :: https://github.com/seattlerb/osx_keychain 4 | 5 | == DESCRIPTION: 6 | 7 | Provides API and a command line tool to Access the OS X Keychain. The 8 | command line tool isn't actually useful (use `security` instead), but 9 | demonstrates the usage quite well. 10 | 11 | == FEATURES/PROBLEMS: 12 | 13 | * Very simple hash-like access to the OS X keychain. 14 | 15 | == SYNOPSIS: 16 | 17 | keychain = OSXKeychain.new 18 | keychain[service, username] = password 19 | p keychain[service, username] 20 | 21 | == REQUIREMENTS: 22 | 23 | * RubyInline 24 | 25 | == INSTALL: 26 | 27 | * sudo gem install osx_keychain 28 | 29 | == LICENSE: 30 | 31 | (The MIT License) 32 | 33 | Copyright (c) Ryan Davis, seattle.rb 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining 36 | a copy of this software and associated documentation files (the 37 | 'Software'), to deal in the Software without restriction, including 38 | without limitation the rights to use, copy, modify, merge, publish, 39 | distribute, sublicense, and/or sell copies of the Software, and to 40 | permit persons to whom the Software is furnished to do so, subject to 41 | the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be 44 | included in all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 47 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 48 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 49 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 50 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 51 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 52 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 53 | -------------------------------------------------------------------------------- /lib/osx_keychain.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby -w 2 | 3 | require 'rubygems' 4 | require 'inline' 5 | 6 | class OSXKeychain 7 | VERSION = '1.0.2' 8 | 9 | def []= service, username, password 10 | set(service, username, password) 11 | end 12 | 13 | def [] service, username = nil 14 | get(service, username) 15 | end 16 | 17 | inline :C do |builder| 18 | builder.include '' 19 | 20 | builder.add_link_flags %w[-lc] 21 | 22 | builder.add_link_flags %w[-framework Security 23 | -framework CoreFoundation 24 | -framework CoreServices] 25 | 26 | builder.c <<-EOC 27 | VALUE get(char * service, VALUE _username) { 28 | char *username = RTEST(_username) ? StringValueCStr(_username) : NULL; 29 | OSStatus status; 30 | UInt32 length; 31 | CFArrayRef keychains = NULL; 32 | void *data; 33 | VALUE result = Qnil; 34 | 35 | status = SecKeychainCopySearchList(&keychains); 36 | 37 | if (status) 38 | rb_raise(rb_eRuntimeError, 39 | "can't access keychains, Authorization failed: %d", status); 40 | 41 | status = SecKeychainFindGenericPassword(keychains, 42 | (UInt32)strlen(service), service, 43 | username ? (UInt32)strlen(username) : 0, username, 44 | &length, &data, NULL); 45 | 46 | if (status == errSecItemNotFound) 47 | status = SecKeychainFindInternetPassword(keychains, 48 | (UInt32)strlen(service), service, 49 | 0, NULL, 50 | username ? (UInt32)strlen(username) : 0, username, 51 | 0, NULL, 0, kSecProtocolTypeAny, kSecAuthenticationTypeAny, 52 | &length, &data, NULL); 53 | 54 | switch (status) { 55 | case 0: 56 | result = rb_str_new(data, length); 57 | SecKeychainItemFreeContent(NULL, data); 58 | break; 59 | case errSecItemNotFound: 60 | // do nothing, return nil password 61 | break; 62 | default: 63 | rb_raise(rb_eRuntimeError, "Can't fetch password from system"); 64 | break; 65 | } 66 | 67 | CFRelease(keychains); 68 | 69 | return result; 70 | } 71 | EOC 72 | 73 | builder.c <<-EOC 74 | void set(char * service, char * username, char * password) { 75 | OSStatus status; 76 | SecKeychainRef keychain; 77 | SecKeychainItemRef item; 78 | 79 | status = SecKeychainOpen("login.keychain",&keychain); 80 | 81 | if (status) 82 | rb_raise(rb_eRuntimeError, 83 | "can't access keychains, Authorization failed: %d", status); 84 | 85 | status = SecKeychainFindGenericPassword(keychain, 86 | (UInt32)strlen(service), service, 87 | username == NULL ? 0 : (UInt32)strlen(username), username, 88 | 0, NULL, &item); 89 | 90 | switch (status) { 91 | case 0: 92 | status = SecKeychainItemModifyAttributesAndData(item, NULL, 93 | (UInt32)strlen(password), password); 94 | CFRelease(item); 95 | break; 96 | case errSecItemNotFound: 97 | status = SecKeychainAddGenericPassword(keychain, 98 | (UInt32)strlen(service), service, 99 | username == NULL ? 0 : (UInt32)strlen(username), username, 100 | (UInt32)strlen(password), password, 101 | NULL); 102 | break; 103 | default: 104 | rb_raise(rb_eRuntimeError, "Can't fetch password from system"); 105 | break; 106 | } 107 | 108 | if (status) 109 | rb_raise(rb_eRuntimeError, "Can't store password in Keychain"); 110 | } 111 | EOC 112 | end 113 | end 114 | --------------------------------------------------------------------------------