Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

How to use Keychain to store Perl variables UNIX
Suppose You want to write a Perl script that takes advantage of Keychain Access as a place to read settings from. It can be done. But what about manipulating those Keychain data? AppleScript can do that. As a generic example, "MyProgram" uses Keychain for:
  • storing service name for MyProgram (which is actually the real identity for the program, while the system call security uses -s as an argument)
  • username for MyProgram
  • password for MyProgram
  • URI for MyProgram
  • runstate for MyProgram
The corresponding Keychain Access key fields would be:
  • Name for RUNSTATE
  • Account for PROGRAMID
  • Where for SERVICENAME
  • Comments for PROGRAMURI
  • Password for PROGRAMPW
Read on for the AppleScript part for MyProgram, where you set up the field values, start or stop the program, etc. -- kind of the GUI side of the program -- as well as additional AppleScript and perl examples.

[robg adds: This is a long, detailed hint that was first posted on the author's blog. I'm recreating it here in case the original ever vanishes. You can read the fully entry here or on the author's blog.]

Here's the setup AppleScript:
(* MyProgram © kroko GNU GPL*)

global servvicename
set servvicename to "MyProgram"
if getrun() is servvicename then
    -- we need to constantly call AppleScript Runner to activate, because user can
    -- focus on other windows during this script exec proces
    -- (this is a trimmed script from that I wrote as personalized iTunes script plugin- there calling
    -- AppleScript Runner to activate was critical for itunes not to get in front of this window)
    tell application "AppleScript Runner"
        activate
        display dialog servvicename & " is running" with title 
            servvicename buttons {"Preferences", "Good to know", "Stop"} default button 2 with icon 1
    end tell
    if the button returned of the result is "Stop" then
        setrun(servvicename & "Stopped")
    else if button returned of the result is "Preferences" then
        setprefs()
    end if
else if getrun() is (servvicename & "Stopped") then
    tell application "AppleScript Runner"
        activate
        display dialog servvicename & " is stopped" with title 
            servvicename buttons {"Preferences", "Good to know", "Start"} default button 2 with icon 1
    end tell
    if the button returned of the result is "Start" then
        setrun(servvicename)
    else if button returned of the result is "Preferences" then
        setprefs()
    end if
else if getrun() is "cannotgetrun" then
    tell application "AppleScript Runner"
        activate
        display dialog "Could't read " & servvicename & " Preferences.
Have You set them up?" with title 
            servvicename buttons {"*censored* this", "Edit Preferences"} default button 2 with icon 0
    end tell
    if button returned of the result is "Edit Preferences" then
        setprefs()
    end if
else
    tell application "AppleScript Runner"
        activate
        display dialog "Unknown error!
Couldn't talk to  " & servvicename with title servvicename buttons {"Damn!"} default button 1 with icon 2
    end tell
end if

on getrun()
    tell application "Keychain Scripting"
        try
            set myKey to first key of current keychain 
                whose service is servvicename
            return name of myKey
        on error
            return "cannotgetrun"
        end try
    end tell
end getrun

on setrun(runstate)
    tell application "Keychain Scripting"
        try
            set myKey to first key of current keychain 
                whose service is servvicename
            set programid to account of myKey
            set programuri to comment of myKey
            set programpw to password of myKey
            delay 1
            delete myKey
            delay 1
            make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
        on error
            tell application "AppleScript Runner"
                activate
                display dialog "Error!" with title servvicename buttons {"Damn!"} default button 1 with icon 2
            end tell
        end try
    end tell
end setrun

on setprefs()
    tell application "AppleScript Runner"
        activate
        display dialog "Here You can (re)set " & servvicename & " account data " with title 
            servvicename buttons {"Exit", "Continue"} default button 2 with icon 1
    end tell
    if the button returned of the result is "Continue" then
        tell application "AppleScript Runner"
            activate
            display dialog "Enter new ID" with title 
                servvicename default answer ""
        end tell
        set programid to (text returned of result)
        tell application "AppleScript Runner"
            activate
            display dialog "Enter new Password" with title 
                servvicename default answer "" with hidden answer
        end tell
        set programpw to (text returned of result)
        tell application "AppleScript Runner"
            activate
            display dialog "Enter new URI" with title 
                servvicename default answer "http://www.foo.com/"
        end tell
        set programuri to (text returned of result)
        tell application "AppleScript Runner"
            activate
            display dialog "Do You want to start " & servvicename & "?" with title 
                servvicename buttons {"No", "Yes"} default button 2 with icon 1
        end tell
        if the button returned of the result is "Yes" then
            set runstate to servvicename
        else
            set runstate to (servvicename & "Stopped")
        end if
        tell application "Keychain Scripting"
            try
                set myKey to first key of current keychain 
                    whose service is servvicename
                try
                    delete myKey
                    delay 0.2
                    make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
                on error
                    tell application "AppleScript Runner"
                        activate
                        display dialog "Error!" with title servvicename buttons {"Damn!"} default button 1 with icon 2
                    end tell
                end try
            on error
                -- This will exec on "Could't read MyProgram Preferences", because set myKey has already failed once on getrun()
                try
                    make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
                end try
            end try
        end tell
    else
        quit me
    end if
end setprefs
Here is the Perl script that reads values stored in Keychain and uses them in the program (here the usage is simple printout of those values).
#!/usr/bin/perl

# MyProgram © kroko GNU GLP

    use strict;
    use warnings;
    use diagnostics;
    use vars qw { $PROGRAMID $PROGRAMPW $RUNSTATE $PROGRAMURI $SERVICENAME };

    $PROGRAMID       = "";
    $PROGRAMPW       = "";
    $RUNSTATE        = "";
    $PROGRAMURI      = "";
    $SERVICENAME     = "MyProgram";

if (getuserinfo() == -3) {
print("Exiting ${SERVICENAME}. Key does not exist.\n");
exit; }
elsif (getuserinfo() == -2) {
print("Exiting ${SERVICENAME}. You have set up ID, PASSWORD, URI incorectly; reconfigure!\n");
exit; }
elsif (getuserinfo() == -1) {
print("Keychain info read successfully!\n${SERVICENAME} is stopped.\n");
exit; }

# The program comes below
# Here simply print out all the info stored in Keychain
print "Keychain info read successfully!\n";
print "Program - ${SERVICENAME}, ID - ${PROGRAMID}, URI - ${PROGRAMURI}, PASSWORD - ${PROGRAMPW}, Program running.\n";
exit;

# Get user info from keychain, check it, get the runstate
sub getuserinfo {
    my(@alluserdata);
    # Read the MyProgram from Keychain Access, redirect all stderr to stdout
    open(USERINFO, "security 2>&1 find-generic-password -g -s $SERVICENAME |") || return -3;
    @alluserdata = <USERINFO>; 
    close(USERINFO);
    # If MyProgram key does not exsist return -3
    if ($alluserdata[0] =~ m/SecKeychainFindGenericPassword/i) {return -3;}
    # Read out the needed info
    $alluserdata[0] =~ /^password: "(.*)"$/ ;
    $PROGRAMPW = $1;
    $alluserdata[4] =~ /0x00000007 <blob>="${SERVICENAME}(.*)"/ ;
    $RUNSTATE = $1;
    $alluserdata[6] =~ /"acct"<blob>="(.*)"/ ;
    $PROGRAMID = $1;
    $alluserdata[12] =~ /"icmt"<blob>="(.*)"/ ;
    $PROGRAMURI = $1;
    # Check if the fields follow the format rules rules, else return -2
    if ($PROGRAMID !~ /^([A-Za-z0-9\_]*)$/i || $PROGRAMPW !~ /^([A-Za-z0-9\_]*)$/i || $PROGRAMURI !~ /^([A-Za-z0-9\.\/\-\~\:\_]*)$/i) {return -2;}
    # Check if the Runstate is running eles return -1
    if ($RUNSTATE eq "Stopped" || $RUNSTATE ne "") {return -1;}
    return 0;
}
Note that the AppleScript actually doesn't start or stop the Perl script. It just writes the RUNSTATE into Keychain. Gluing the two parts together can be accomplished in different ways. For me, the Perl script is a launchd job, thus the solution is to restart the corresponding launchd job after changing MyProgram preferences in Keychain.

Configure com.foo.MyProgram.plist file as follows and put under one of the launchd-monitored places; I chose ~/Library/LaunchAgents: And the modified AppleScript simply restarts the launchd job - meaning it will restart Perl script to read the new preferences:
(* MyProgram © kroko GNU GPL*)

global servvicename
set servvicename to "MyProgram"
if getrun() is servvicename then
    -- we need to constantly call AppleScript Runner to activate, because user can
    -- focus on other windows during this script exec proces
    -- (this is a trimmed script from that I wrote as personalized iTunes script plugin- there calling
    -- AppleScript Runner to activate was critical for itunes not to get in front of this window)
    tell application "AppleScript Runner"
        activate
        display dialog servvicename & " is running" with title 
            servvicename buttons {"Preferences", "Good to know", "Stop"} default button 2 with icon 1
    end tell
    if the button returned of the result is "Stop" then
        setrun(servvicename & "Stopped")
    else if button returned of the result is "Preferences" then
        setprefs()
    end if
else if getrun() is (servvicename & "Stopped") then
    tell application "AppleScript Runner"
        activate
        display dialog servvicename & " is stopped" with title 
            servvicename buttons {"Preferences", "Good to know", "Start"} default button 2 with icon 1
    end tell
    if the button returned of the result is "Start" then
        setrun(servvicename)
    else if button returned of the result is "Preferences" then
        setprefs()
    end if
else if getrun() is "cannotgetrun" then
    tell application "AppleScript Runner"
        activate
        display dialog "Could't read " & servvicename & " Preferences.
Have You set them up?" with title 
            servvicename buttons {"*censored* this", "Edit Preferences"} default button 2 with icon 0
    end tell
    if button returned of the result is "Edit Preferences" then
        setprefs()
    end if
else
    tell application "AppleScript Runner"
        activate
        display dialog "Unknown error!
Couldn't talk to  " & servvicename with title servvicename buttons {"Damn!"} default button 1 with icon 2
    end tell
end if



on getrun()
    tell application "Keychain Scripting"
        try
            set myKey to first key of current keychain 
                whose service is servvicename
            return name of myKey
        on error
            return "cannotgetrun"
        end try
    end tell
end getrun

on setrun(runstate)
    tell application "Keychain Scripting"
        try
            set myKey to first key of current keychain 
                whose service is servvicename
            set programid to account of myKey
            set programuri to comment of myKey
            set programpw to password of myKey
            delay 1
            delete myKey
            delay 1
            make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
            try
                do shell script "launchctl stop com.foo.MyProgram && launchctl start com.foo.MyProgram"
                tell application "AppleScript Runner"
                    activate
                    display dialog "DONE!
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
                end tell
            on error
                do shell script "launchctl start com.foo.MyProgram"
                tell application "AppleScript Runner"
                    activate
                    display dialog "DONE! (But with unknown error)
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
                end tell
            end try
            
        on error
            tell application "AppleScript Runner"
                activate
                display dialog "Error!" with title servvicename buttons {"Damn!"} default button 1 with icon 2
            end tell
        end try
    end tell
end setrun

on setprefs()
    tell application "AppleScript Runner"
        activate
        display dialog "Here You can (re)set " & servvicename & " account data " with title 
            servvicename buttons {"Exit", "Continue"} default button 2 with icon 1
    end tell
    if the button returned of the result is "Continue" then
        tell application "AppleScript Runner"
            activate
            display dialog "Enter new ID" with title 
                servvicename default answer ""
        end tell
        set programid to (text returned of result)
        tell application "AppleScript Runner"
            activate
            display dialog "Enter new Password" with title 
                servvicename default answer "" with hidden answer
        end tell
        set programpw to (text returned of result)
        tell application "AppleScript Runner"
            activate
            display dialog "Enter new URI" with title 
                servvicename default answer "http://www.foo.com/"
        end tell
        set programuri to (text returned of result)
        tell application "AppleScript Runner"
            activate
            display dialog "Do You want to start " & servvicename & "?" with title 
                servvicename buttons {"No", "Yes"} default button 2 with icon 1
        end tell
        if the button returned of the result is "Yes" then
            set runstate to servvicename
        else
            set runstate to (servvicename & "Stopped")
        end if
        tell application "Keychain Scripting"
            try
                set myKey to first key of current keychain 
                    whose service is servvicename
                try
                    delete myKey
                    delay 0.2
                    make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
                    try
                        do shell script "launchctl stop com.foo.MyProgram && launchctl start com.foo.MyProgram"
                        tell application "AppleScript Runner"
                            activate
                            display dialog "DONE!
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
                        end tell
                    on error
                        do shell script "launchctl start com.foo.MyProgram"
                        tell application "AppleScript Runner"
                            activate
                            display dialog "DONE! (But with unknown error)
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
                        end tell
                    end try
                on error
                    tell application "AppleScript Runner"
                        activate
                        display dialog "Error!" with title servvicename buttons {"Damn!"} default button 1 with icon 2
                    end tell
                end try
            on error
                -- This will exec on "Could't read MyProgram Preferences", because set myKey has already failed once on getrun()
                try
                    make new generic key with properties {name:runstate, kind:"application password", account:programid, service:servvicename, comment:programuri, password:programpw}
                    try
                        do shell script "launchctl stop com.foo.MyProgram && launchctl start com.foo.MyProgram"
                        tell application "AppleScript Runner"
                            activate
                            display dialog "DONE!
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
                        end tell
                    on error
                        do shell script "launchctl start com.foo.MyProgram"
                        tell application "AppleScript Runner"
                            activate
                            display dialog "DONE! (But with unknown error)
" & servvicename & " will restart in new state
after 30 seconds" with title servvicename buttons {"Yeah!"} default button 1 with icon 1
                        end tell
                    end try
                end try
            end try
        end tell
    else
        quit me
    end if
end setprefs
Hopefully it's apparent that the fields can contain anything that your Perl script uses. Store whatever varibles you want. And each key field can contain many paremeters, divided by some predefined char/string; reading them out in Perl script simply takes some modification the regex part. i.e., we have a key that's Service Name whose value is "MyProgram," and we want to store all the variables for Perl to use in the comments field, each value divided by colon (valueone:valuetwo:valuethree:valuefour:valuefive):
Then the possible Perl script would be:
#!/usr/bin/perl

# MyProgram (all variables stored in Keychain Access comments field) © kroko GNU GLP

    use strict;
    use warnings;
    use diagnostics;
    use vars qw { @VALUESTORAGE $SERVICENAME };

    $SERVICENAME     = "MyProgram";

# If getuserinfo returns -3 it means that the key does not exist. Return the info.
if (getuserinfo() == -3) {
print("Exiting ${SERVICENAME}. Key does not exist.\n");
exit; }

# Else do anything with those values stored in @VALUESTORAGE
# Here we simply print them out each in its own line
    foreach my $outputter (@VALUESTORAGE) {
        print $outputter . "\n";
    }
exit;

# Get info from keychain
sub getuserinfo {
    my(@alluserdata);

    # Read the MyProgram from Keychain Access, redirect all stderr to stdout
    open(USERINFO, "security 2>&1 find-generic-password -g -s $SERVICENAME |") || return -3;
    @alluserdata = <USERINFO>; 
    close(USERINFO);

    # If MyProgram key does not exsist return -3
    if ($alluserdata[0] =~ m/SecKeychainFindGenericPassword/i) {return -3;}

    # Read all the values stored in key "comments" field
    $alluserdata[12] =~ /"icmt"<blob>="(.*)"/ ;
    # Put them in array @VALUESTORAGE, assuming that values are divided by ":"
    @VALUESTORAGE = split(/:/, $1);
    return 0;
}
Finally, some basic AppleScripts to work with Keychain Access:
 (* This AppleScript makes a key*)
tell application "Keychain Scripting"
    set namename to "MyProgram"
    set servicename to "MyProgram"
    set accountname to "myuser"
    set commentname to "http://www.foo.com"
    set passwordname to "uuberpassword"
    try
        make new generic key with properties {name:namename, kind:"application password", account:accountname, service:servicename, comment:commentname, password:passwordname}
        return "Done"
    on error
        return "Failed"
    end try
end tell

(* This AppleScript checks if a key exists, arguments- service and account*)
tell application "Keychain Scripting"
    set servicename to "MyProgram"
    set accountname to "myuser"
    try
        set myKey to first key of current keychain 
            whose service is servicename and account is accountname
        return "Key Exists"
    on error
        return "Failed"
    end try
end tell

(* This AppleScript gets password from a key, arguments- service and account*)
tell application "Keychain Scripting"
    set servicename to "MyProgram"
    set accountname to "myuser"
    try
        set myKey to first key of current keychain 
            whose name is servicename and account is accountname
        return password of myKey
    on error
        return "Failed"
    end try
end tell

(* This AppleScript deletes a key, arguments- service and account*)
tell application "Keychain Scripting"
    set servicename to "MyProgram"
    set accountname to "myuser"
    try
        set myKey to first key of current keychain 
            whose service is servicename and account is accountname
        delete myKey
        return "Key Deleted"
    on error
        return "Failed"
    end try
end tell
[robg adds: Any errors in the above are my fault; please check the author's blog if this version doesn't work.]
    •    
  • Currently 1.60 / 5
  • 1
  • 2
  • 3
  • 4
  • 5
  (5 votes cast)
 
[9,915 views]  

How to use Keychain to store Perl variables | 3 comments | Create New Account
Click here to return to the 'How to use Keychain to store Perl variables' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
Somewhat unclear purpose
Authored by: momerath on Sep 26, '08 09:19:42AM

Does this script require you to store a keychain or other password inside one of the script files, or does it use Keychain Access to prompt you for it if needed and use a keychain if it's already unlocked?



[ Reply to This | # ]
Somewhat unclear purpose
Authored by: kroko on Oct 02, '08 11:17:42AM
the only info stored in the script is keychain keys service name, that isn't a sensitive info. the keychain access does the job itself - when your script tries to access sensitive data (through security) in the key, it asks for user password. meanwhile it is easy rewrite for the service name value to be passed to the script through command line argument ARGV, which offers different possibilities

[ Reply to This | # ]
THe short version
Authored by: mjg on Nov 23, '08 06:12:39PM
So stripped of all the Perl, all we're doing is using the "security" command to manipulate the keychain, plus using the Keychain Scripting extension for AppleScript. You can just as easily use "security" in any language that can call Unix commands - Perl, Python, Ruby, whatever. In Perl you can even call Keychain Scripting using the Mac::Glue module - see Tatsuhiko Miyagawa's LWP::UserAgent::Keychain for a trivial example.

[ Reply to This | # ]