├── README.md ├── test ├── c │ ├── test.c │ ├── fakejni.h │ └── fakejni.c └── java │ └── com │ └── mcdermottroe │ └── apple │ └── OSXKeychainTest.java └── src ├── java └── com │ └── mcdermottroe │ └── apple │ ├── OSXKeychainException.java │ └── OSXKeychain.java └── c ├── codegen └── generate_enums.c └── com_mcdermottroe_apple_OSXKeychain.c /README.md: -------------------------------------------------------------------------------- 1 | Example: 2 | 3 | OSXKeychain keychain = OSXKeychain.getInstance(); 4 | String password = keychain.findInternetPassword("github.com", null, "conormcd", "/login", 0); 5 | -------------------------------------------------------------------------------- /test/c/test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Conor McDermottroe 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | /* A very simple set of tests for the C portion of this library. */ 28 | 29 | #include "fakejni.h" 30 | #include "../../src/c/com_mcdermottroe_apple_OSXKeychain.c" 31 | 32 | #define SERVICE_NAME "Test OS X Keychain from Java" 33 | #define USERNAME "Test OS X Keychain User" 34 | #define PASSWORD "Test OS X Keychain Password" 35 | 36 | int main() { 37 | JNIEnv env; 38 | fakejni_env fakejni; 39 | jstring genericPassword; 40 | 41 | fakejni_init(&fakejni); 42 | env = &fakejni; 43 | 44 | /* Test a round-trip for a generic password. */ 45 | Java_com_mcdermottroe_apple_OSXKeychain__1addGenericPassword(&env, NULL, SERVICE_NAME, USERNAME, PASSWORD); 46 | genericPassword = Java_com_mcdermottroe_apple_OSXKeychain__1findGenericPassword(&env, NULL, SERVICE_NAME, USERNAME); 47 | if (strncmp(genericPassword, PASSWORD, strlen(PASSWORD)) != 0) { 48 | printf("Failed to round-trip the generic password.\n"); 49 | return 1; 50 | } 51 | Java_com_mcdermottroe_apple_OSXKeychain__1deleteGenericPassword(&env, NULL, SERVICE_NAME, USERNAME); 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /test/c/fakejni.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Conor McDermottroe 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | /* Prevent the real jni.h from being included. */ 28 | #define _JAVASOFT_JNI_H_ 29 | 30 | /* Map the JNI types we use to some useful fakes. */ 31 | #define JNIEXPORT 32 | #define JNICALL 33 | #define JNIEnv fakejni_env* 34 | #define jclass void* 35 | #define jobject void* 36 | #define jstring char* 37 | #define jint int 38 | #define jbyte char 39 | #define jboolean int 40 | #define jsize int 41 | 42 | /* Something to use as an env* for JNI functions. */ 43 | typedef struct { 44 | void (*DeleteLocalRef)(void *env, jobject lref); 45 | void* (*FindClass)(void*, const char*); 46 | int (*GetStringLength)(void*, jstring); 47 | const jbyte * (*GetStringUTFChars)(void*, jstring, jboolean *); 48 | jsize (*GetStringUTFLength)(void *env, jstring string); 49 | void (*GetStringUTFRegion)(void*, jstring, int, int, char*); 50 | char* (*NewStringUTF)(void*, char*); 51 | void (*ReleaseStringUTFChars)(void *env, jstring string, const char *utf); 52 | void (*ThrowNew)(void*, jclass, const char*); 53 | } fakejni_env; 54 | 55 | /* Use this to initialise a fakejni_env. */ 56 | void fakejni_init(fakejni_env*); 57 | -------------------------------------------------------------------------------- /src/java/com/mcdermottroe/apple/OSXKeychainException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Conor McDermottroe 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package com.mcdermottroe.apple; 28 | 29 | /** An exception to be thrown if the native code interacting with the keychain 30 | * encounters an error. 31 | * 32 | * @author Conor McDermottroe 33 | */ 34 | public class OSXKeychainException 35 | extends Exception 36 | { 37 | /** Create a blank exception with no message. */ 38 | public OSXKeychainException() { 39 | super(); 40 | } 41 | 42 | /** Create an exception with a message. 43 | * 44 | * @param message A message explaining why this exception must be thrown. 45 | */ 46 | public OSXKeychainException(String message) { 47 | super(message); 48 | } 49 | 50 | /** Create an exception with no message but with a link to the exception 51 | * which caused this one to be thrown. 52 | * 53 | * @param cause The reason this exception is being created and thrown. 54 | */ 55 | public OSXKeychainException(Throwable cause) { 56 | super(cause); 57 | } 58 | 59 | /** Create an exception both with a message and a link to the exceptino 60 | * which caused this one to be thrown. 61 | * 62 | * @param message A message explaining why this exception must be thrown. 63 | * @param cause The reason this exception is being created and thrown. 64 | */ 65 | public OSXKeychainException(String message, Throwable cause) { 66 | super(message, cause); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/java/com/mcdermottroe/apple/OSXKeychainTest.java: -------------------------------------------------------------------------------- 1 | package com.mcdermottroe.apple; 2 | 3 | import junit.framework.TestCase; 4 | 5 | /** Test the OSXKeychainTest class. 6 | * 7 | * @author Conor McDermottroe 8 | */ 9 | public class OSXKeychainTest 10 | extends TestCase 11 | { 12 | 13 | /** The keychain instance to test. */ 14 | private OSXKeychain keychain; 15 | 16 | /** Try to insert, read and delete a generic password. */ 17 | public void testRoundTripGenericPassword() { 18 | initKeychain(); 19 | 20 | final String serviceName = "testRoundTripGenericPassword_service"; 21 | final String userName = "testRoundTripGenericPassword_username"; 22 | final String password = "testRoundTripGenericPassword_password"; 23 | 24 | // Add it to the keychain. 25 | try { 26 | keychain.addGenericPassword(serviceName, userName, password); 27 | } catch (OSXKeychainException e) { 28 | fail("Failed to add a generic password."); 29 | } 30 | 31 | // Retrieve it from the keychain 32 | try { 33 | String pass = keychain.findGenericPassword(serviceName, userName); 34 | assertEquals("Retrieved password did not match.", password, pass); 35 | } catch (OSXKeychainException e) { 36 | fail("Failed to retrieve generic password"); 37 | } 38 | 39 | // Delete it from the keychain. 40 | try { 41 | keychain.deleteGenericPassword(serviceName, userName); 42 | } catch (OSXKeychainException e) { 43 | fail("Failed to delete generic password"); 44 | } 45 | } 46 | 47 | /** Try to insert, read and delete a generic password. */ 48 | public void testUpdateGenericPassword() { 49 | initKeychain(); 50 | 51 | final String serviceName = "testUpdateGenericPassword_service"; 52 | final String userName = "testUpdateGenericPassword_username"; 53 | final String password1 = "testUpdateGenericPassword_pw1"; 54 | final String password2 = "testUpdateGenericPassword_pw2"; 55 | 56 | // Add it to the keychain. 57 | try { 58 | keychain.addGenericPassword(serviceName, userName, password1); 59 | } catch (OSXKeychainException e) { 60 | fail("Failed to add a generic password."); 61 | } 62 | 63 | // Retrieve it from the keychain 64 | try { 65 | String pass = keychain.findGenericPassword(serviceName, userName); 66 | assertEquals("Retrieved password did not match.", password1, pass); 67 | } catch (OSXKeychainException e) { 68 | e.printStackTrace(); 69 | fail("Failed to retrieve generic password"); 70 | } 71 | 72 | // Modify the existing item in the keychain. 73 | try { 74 | keychain.modifyGenericPassword(serviceName, userName, password2); 75 | } catch (OSXKeychainException e) { 76 | fail("Failed to update a generic password."); 77 | } 78 | 79 | // Retrieve it from the keychain, expect the updated password now 80 | try { 81 | String pass = keychain.findGenericPassword(serviceName, userName); 82 | assertEquals("Retrieved password did not match.", password2, pass); 83 | } catch (OSXKeychainException e) { 84 | fail("Failed to retrieve generic password"); 85 | } 86 | 87 | // Delete it from the keychain. 88 | try { 89 | keychain.deleteGenericPassword(serviceName, userName); 90 | } catch (OSXKeychainException e) { 91 | fail("Failed to delete generic password"); 92 | } 93 | } 94 | 95 | /** Initialize the keychain for testing. */ 96 | private void initKeychain() { 97 | try { 98 | if (keychain == null) { 99 | keychain = OSXKeychain.getInstance(); 100 | } 101 | } catch (OSXKeychainException e) { 102 | fail("Failed to initialize keychain"); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/c/fakejni.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Conor McDermottroe 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "fakejni.h" 32 | 33 | void fakejni_DeleteLocalRef(void *env, jobject lref) { 34 | } 35 | 36 | 37 | /* A replacement for JNI's (*env)->FindClass. Don't use the result of this 38 | * function for anything, bad things will happen if you do. 39 | */ 40 | void* fakejni_FindClass(void* env, const char* exceptionClass) { 41 | return (void*)"Don't use this"; 42 | } 43 | 44 | /* A replacement for JNI's (*env)->GetStringLength. */ 45 | int fakejni_GetStringLength(void* env, jstring str) { 46 | return strlen(str); 47 | } 48 | 49 | const jbyte * fakejni_GetStringUTFChars(void*env, jstring str, jboolean *isCopy) { 50 | int len = strlen(str); 51 | char *utf = (char *) calloc(len+1, sizeof(char)); 52 | memcpy(utf, str, len*sizeof(char)); 53 | utf[len] = '\0'; 54 | if (isCopy != NULL) { 55 | *isCopy = 1; 56 | } 57 | return utf; 58 | } 59 | 60 | jsize fakejni_GetStringUTFLength(void *env, jstring string) { 61 | return strlen(string); 62 | } 63 | 64 | /* A replacement for JNI's (*env)->GetStringUTFRegion. */ 65 | void fakejni_GetStringUTFRegion(void* env, jstring src, int offset, int length, char* dst) { 66 | int dstidx; 67 | int srcidx; 68 | for (dstidx = 0, srcidx = offset; dstidx < length; srcidx++, dstidx++) { 69 | dst[dstidx] = src[srcidx]; 70 | } 71 | } 72 | 73 | /* A replacement for JNI's (*env)->NewStringUTF. */ 74 | char* fakejni_NewStringUTF(void* env, char* str) { 75 | int len = strlen(str); 76 | char *utf = (char *) calloc(len+1, sizeof(char)); 77 | memcpy(utf, str, len*sizeof(char)); 78 | utf[len] = '\0'; 79 | return utf; 80 | } 81 | 82 | void fakejni_ReleaseStringUTFChars(void *env, jstring string, const char *utf) { 83 | free((void *) utf); 84 | } 85 | 86 | /* A replacement for JNI's (*env)->ThrowNew. */ 87 | void fakejni_ThrowNew(void* env, jclass cls, const char* message) { 88 | printf("Exception: %s\n", message); 89 | exit(1); 90 | } 91 | 92 | /* Initialise a fakejni_env. */ 93 | void fakejni_init(fakejni_env* env) { 94 | env->DeleteLocalRef = &fakejni_DeleteLocalRef; 95 | env->FindClass = &fakejni_FindClass; 96 | env->GetStringLength = &fakejni_GetStringLength; 97 | env->GetStringUTFRegion = &fakejni_GetStringUTFRegion; 98 | env->GetStringUTFChars = &fakejni_GetStringUTFChars; 99 | env->GetStringUTFLength = &fakejni_GetStringUTFLength; 100 | env->NewStringUTF = &fakejni_NewStringUTF; 101 | env->ReleaseStringUTFChars = fakejni_ReleaseStringUTFChars; 102 | env->ThrowNew = &fakejni_ThrowNew; 103 | } 104 | -------------------------------------------------------------------------------- /src/c/codegen/generate_enums.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Conor McDermottroe 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | /* This short program is used to generate OSXKeychainAuthenticationType.java 28 | * and OSXKeychainProtocolType.java which are simply mirrors of 29 | * kSecAuthenticationType* and kSecProtocolType*. 30 | */ 31 | 32 | #include 33 | 34 | #define ENUM_CLASS_HEAD(file, classname) fprintf(file, "\ 35 | package com.mcdermottroe.apple;\n\ 36 | \n\ 37 | /** Auto-generated from Security.h, see the Keychain Services Reference for\n\ 38 | * descriptions of what these constants mean.\n\ 39 | */\n\ 40 | public enum %s {\n", classname); 41 | #define ENUM_VALUE(file, name, value) fprintf(file, "\t/** " #value " */\n\t" name "(\"" name "\", %d)", value) 42 | #define ENUM_VALUE_DEF(file, name, value) ENUM_VALUE(file, name, value); fprintf(file, ",\n\n"); 43 | #define ENUM_VALUE_LAST(file, name, value) ENUM_VALUE(file, name, value); fprintf(file, ";\n"); 44 | #define ENUM_CLASS_TAIL(file, classname) fprintf(file, "\ 45 | \n\ 46 | \t/** The name of the constant. */\n\ 47 | \tprivate final String symbol;\n\ 48 | \n\ 49 | \t/** The value of the constant. */\n\ 50 | \tprivate final int value;\n\ 51 | \n\ 52 | \t/** Create the constant. \n\ 53 | \t *\n\ 54 | \t *\t@param sym The name of the constant.\n\ 55 | \t *\t@param val The value of the constant.\n\ 56 | \t */\n\ 57 | \t" classname "(String sym, int val) {\n\ 58 | \t\tsymbol = sym;\n\ 59 | \t\tvalue = val;\n\ 60 | \t}\n\ 61 | \n\ 62 | \t/** Get the value of the constant.\n\ 63 | \t *\n\ 64 | \t *\t@return The value of the constant.\n\ 65 | \t */\n\ 66 | \tpublic int getValue() {\n\ 67 | \t\treturn value;\n\ 68 | \t}\n\ 69 | \n\ 70 | \t/** {@inheritDoc} */\n\ 71 | \t@Override\n\ 72 | \tpublic String toString() {\n\ 73 | \t\treturn symbol;\n\ 74 | \t}\n\ 75 | }\n\ 76 | "); 77 | 78 | /* Create OSXKeychainAuthenticationType.java. */ 79 | void generateOSXKeychainAuthenticationType(const char* filename) { 80 | FILE* file; 81 | 82 | file = fopen(filename, "w"); 83 | 84 | ENUM_CLASS_HEAD(file, "OSXKeychainAuthenticationType"); 85 | ENUM_VALUE_DEF(file, "Any", kSecAuthenticationTypeAny); 86 | ENUM_VALUE_DEF(file, "DPA", kSecAuthenticationTypeDPA); 87 | ENUM_VALUE_DEF(file, "Default", kSecAuthenticationTypeDefault); 88 | ENUM_VALUE_DEF(file, "HTMLForm", kSecAuthenticationTypeHTMLForm); 89 | ENUM_VALUE_DEF(file, "HTTPBasic", kSecAuthenticationTypeHTTPBasic); 90 | ENUM_VALUE_DEF(file, "HTTPDigest", kSecAuthenticationTypeHTTPDigest); 91 | ENUM_VALUE_DEF(file, "MSN", kSecAuthenticationTypeMSN); 92 | ENUM_VALUE_DEF(file, "NTLM", kSecAuthenticationTypeNTLM); 93 | ENUM_VALUE_LAST(file, "RPA", kSecAuthenticationTypeRPA); 94 | ENUM_CLASS_TAIL(file, "OSXKeychainAuthenticationType"); 95 | 96 | fclose(file); 97 | } 98 | 99 | /* Create OSXKeychainProtocolType.java. */ 100 | void generateOSXKeychainProtocolType(const char* filename) { 101 | FILE* file; 102 | 103 | file = fopen(filename, "w"); 104 | 105 | ENUM_CLASS_HEAD(file, "OSXKeychainProtocolType"); 106 | ENUM_VALUE_DEF(file, "AFP", kSecProtocolTypeAFP); 107 | ENUM_VALUE_DEF(file, "Any", kSecProtocolTypeAny); 108 | ENUM_VALUE_DEF(file, "AppleTalk", kSecProtocolTypeAppleTalk); 109 | ENUM_VALUE_DEF(file, "CIFS", kSecProtocolTypeCIFS); 110 | ENUM_VALUE_DEF(file, "CVSpserver", kSecProtocolTypeCVSpserver); 111 | ENUM_VALUE_DEF(file, "DAAP", kSecProtocolTypeDAAP); 112 | ENUM_VALUE_DEF(file, "EPPC", kSecProtocolTypeEPPC); 113 | ENUM_VALUE_DEF(file, "FTP", kSecProtocolTypeFTP); 114 | ENUM_VALUE_DEF(file, "FTPAccount", kSecProtocolTypeFTPAccount); 115 | ENUM_VALUE_DEF(file, "FTPProxy", kSecProtocolTypeFTPProxy); 116 | ENUM_VALUE_DEF(file, "FTPS", kSecProtocolTypeFTPS); 117 | ENUM_VALUE_DEF(file, "HTTP", kSecProtocolTypeHTTP); 118 | ENUM_VALUE_DEF(file, "HTTPProxy", kSecProtocolTypeHTTPProxy); 119 | ENUM_VALUE_DEF(file, "HTTPS", kSecProtocolTypeHTTPS); 120 | ENUM_VALUE_DEF(file, "HTTPSProxy", kSecProtocolTypeHTTPSProxy); 121 | ENUM_VALUE_DEF(file, "IMAP", kSecProtocolTypeIMAP); 122 | ENUM_VALUE_DEF(file, "IMAPS", kSecProtocolTypeIMAPS); 123 | ENUM_VALUE_DEF(file, "IPP", kSecProtocolTypeIPP); 124 | ENUM_VALUE_DEF(file, "IRC", kSecProtocolTypeIRC); 125 | ENUM_VALUE_DEF(file, "IRCS", kSecProtocolTypeIRCS); 126 | ENUM_VALUE_DEF(file, "LDAP", kSecProtocolTypeLDAP); 127 | ENUM_VALUE_DEF(file, "LDAPS", kSecProtocolTypeLDAPS); 128 | ENUM_VALUE_DEF(file, "NNTP", kSecProtocolTypeNNTP); 129 | ENUM_VALUE_DEF(file, "NNTPS", kSecProtocolTypeNNTPS); 130 | ENUM_VALUE_DEF(file, "POP3", kSecProtocolTypePOP3); 131 | ENUM_VALUE_DEF(file, "POP3S", kSecProtocolTypePOP3S); 132 | ENUM_VALUE_DEF(file, "RTSP", kSecProtocolTypeRTSP); 133 | ENUM_VALUE_DEF(file, "RTSPProxy", kSecProtocolTypeRTSPProxy); 134 | ENUM_VALUE_DEF(file, "SMB", kSecProtocolTypeSMB); 135 | ENUM_VALUE_DEF(file, "SMTP", kSecProtocolTypeSMTP); 136 | ENUM_VALUE_DEF(file, "SOCKS", kSecProtocolTypeSOCKS); 137 | ENUM_VALUE_DEF(file, "SSH", kSecProtocolTypeSSH); 138 | ENUM_VALUE_DEF(file, "SVN", kSecProtocolTypeSVN); 139 | ENUM_VALUE_DEF(file, "Telnet", kSecProtocolTypeTelnet); 140 | ENUM_VALUE_LAST(file, "TelnetS", kSecProtocolTypeTelnetS); 141 | ENUM_CLASS_TAIL(file, "OSXKeychainProtocolType"); 142 | 143 | fclose(file); 144 | } 145 | 146 | /* The program takes two arguments, the first is the path to 147 | * OSXKeychainAuthenticationType.java, the second is the path to 148 | * OSXKeychainProtocolType.java. 149 | */ 150 | int main(int argc, char** argv, char** envp) { 151 | if (argc != 3) { 152 | printf("Usage: %s /path/to/OSXKeychainAuthenticationType.java /path/to/OSXKeychainProtocolType.java\n", argv[0]); 153 | return 1; 154 | } 155 | 156 | generateOSXKeychainAuthenticationType(argv[1]); 157 | generateOSXKeychainProtocolType(argv[2]); 158 | 159 | return 0; 160 | } 161 | -------------------------------------------------------------------------------- /src/c/com_mcdermottroe_apple_OSXKeychain.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Conor McDermottroe 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #include "com_mcdermottroe_apple_OSXKeychain.h" 30 | #include 31 | #include 32 | 33 | #define OSXKeychainException "com/mcdermottroe/apple/OSXKeychainException" 34 | 35 | /* A simplified structure for dealing with jstring objects. Use jstring_unpack 36 | * and jstring_unpacked_free to manage these. 37 | */ 38 | typedef struct { 39 | int len; 40 | const char* str; 41 | } jstring_unpacked; 42 | 43 | /* Throw an exception. 44 | * 45 | * Parameters: 46 | * env The JNI environment. 47 | * exceptionClass The name of the exception class. 48 | * message The message to pass to the Exception. 49 | */ 50 | void throw_exception(JNIEnv* env, const char* exceptionClass, const char* message) { 51 | jclass cls = (*env)->FindClass(env, exceptionClass); 52 | /* if cls is NULL, an exception has already been thrown */ 53 | if (cls != NULL) { 54 | (*env)->ThrowNew(env, cls, message); 55 | } 56 | /* free the local ref, utility funcs must delete local refs. */ 57 | (*env)->DeleteLocalRef(env, cls); 58 | } 59 | 60 | /* Shorthand for throwing an OSXKeychainException from an OSStatus. 61 | * 62 | * Parameters: 63 | * env The JNI environment. 64 | * status The non-error status returned from a keychain call. 65 | */ 66 | void throw_osxkeychainexception(JNIEnv* env, OSStatus status) { 67 | CFStringRef errorMessage = SecCopyErrorMessageString(status, NULL); 68 | throw_exception( 69 | env, 70 | OSXKeychainException, 71 | CFStringGetCStringPtr(errorMessage, kCFStringEncodingMacRoman) 72 | ); 73 | CFRelease(errorMessage); 74 | } 75 | 76 | /* Unpack the data from a jstring and put it in a jstring_unpacked. 77 | * 78 | * Parameters: 79 | * env The JNI environment. 80 | * js The jstring to unpack. 81 | * ret The jstring_unpacked in which to store the result. 82 | */ 83 | void jstring_unpack(JNIEnv* env, jstring js, jstring_unpacked* ret) { 84 | if (ret == NULL) { 85 | return; 86 | } 87 | if (env == NULL || js == NULL) { 88 | ret->len = 0; 89 | ret->str = NULL; 90 | return; 91 | } 92 | 93 | /* Get the length of the string. */ 94 | ret->len = (int)((*env)->GetStringUTFLength(env, js)); 95 | if (ret->len <= 0) { 96 | ret->len = 0; 97 | ret->str = NULL; 98 | return; 99 | } 100 | ret->str = (*env)->GetStringUTFChars(env, js, NULL); 101 | } 102 | 103 | /* Clean up a jstring_unpacked after it's no longer needed. 104 | * 105 | * Parameters: 106 | * jsu A jstring_unpacked structure to clean up. 107 | */ 108 | void jstring_unpacked_free(JNIEnv *env, jstring js, jstring_unpacked* jsu) { 109 | if (jsu != NULL && jsu->str != NULL) { 110 | (*env)->ReleaseStringUTFChars(env, js, jsu->str); 111 | jsu->len = 0; 112 | jsu->str = NULL; 113 | } 114 | } 115 | 116 | /* Implementation of OSXKeychain.addGenericPassword(). See the Java docs for 117 | * explanations of the parameters. 118 | */ 119 | JNIEXPORT void JNICALL Java_com_mcdermottroe_apple_OSXKeychain__1addGenericPassword(JNIEnv* env, jobject obj, jstring serviceName, jstring accountName, jstring password) { 120 | OSStatus status; 121 | jstring_unpacked service_name; 122 | jstring_unpacked account_name; 123 | jstring_unpacked service_password; 124 | 125 | /* Unpack the params */ 126 | jstring_unpack(env, serviceName, &service_name); 127 | jstring_unpack(env, accountName, &account_name); 128 | jstring_unpack(env, password, &service_password); 129 | /* check for allocation failures */ 130 | if (service_name.str == NULL || 131 | account_name.str == NULL || 132 | service_password.str == NULL) { 133 | jstring_unpacked_free(env, serviceName, &service_name); 134 | jstring_unpacked_free(env, accountName, &account_name); 135 | jstring_unpacked_free(env, password, &service_password); 136 | return; 137 | } 138 | 139 | /* Add the details to the keychain. */ 140 | status = SecKeychainAddGenericPassword( 141 | NULL, 142 | service_name.len, 143 | service_name.str, 144 | account_name.len, 145 | account_name.str, 146 | service_password.len, 147 | service_password.str, 148 | NULL 149 | ); 150 | if (status != errSecSuccess) { 151 | throw_osxkeychainexception(env, status); 152 | } 153 | 154 | /* Clean up. */ 155 | jstring_unpacked_free(env, serviceName, &service_name); 156 | jstring_unpacked_free(env, accountName, &account_name); 157 | jstring_unpacked_free(env, password, &service_password); 158 | } 159 | 160 | JNIEXPORT void JNICALL Java_com_mcdermottroe_apple_OSXKeychain__1modifyGenericPassword(JNIEnv *env, jobject obj, jstring serviceName, jstring accountName, jstring password) { 161 | OSStatus status; 162 | jstring_unpacked service_name; 163 | jstring_unpacked account_name; 164 | jstring_unpacked service_password; 165 | SecKeychainItemRef existingItem; 166 | 167 | /* Unpack the params */ 168 | jstring_unpack(env, serviceName, &service_name); 169 | jstring_unpack(env, accountName, &account_name); 170 | jstring_unpack(env, password, &service_password); 171 | /* check for allocation failures */ 172 | if (service_name.str == NULL || 173 | account_name.str == NULL || 174 | service_password.str == NULL) { 175 | jstring_unpacked_free(env, serviceName, &service_name); 176 | jstring_unpacked_free(env, accountName, &account_name); 177 | jstring_unpacked_free(env, password, &service_password); 178 | return; 179 | } 180 | 181 | status = SecKeychainFindGenericPassword( 182 | NULL, 183 | service_name.len, 184 | service_name.str, 185 | account_name.len, 186 | account_name.str, 187 | NULL, 188 | NULL, 189 | &existingItem 190 | ); 191 | if (status != errSecSuccess) { 192 | throw_osxkeychainexception(env, status); 193 | } 194 | else { 195 | /* Update the details in the keychain. */ 196 | status = SecKeychainItemModifyContent( 197 | existingItem, 198 | NULL, 199 | service_password.len, 200 | service_password.str 201 | ); 202 | if (status != errSecSuccess) { 203 | throw_osxkeychainexception(env, status); 204 | } 205 | } 206 | 207 | /* Clean up. */ 208 | jstring_unpacked_free(env, serviceName, &service_name); 209 | jstring_unpacked_free(env, accountName, &account_name); 210 | jstring_unpacked_free(env, password, &service_password); 211 | } 212 | 213 | 214 | /* Implementation of OSXKeychain.addInternetPassword(). See the Java docs for 215 | * explanation of the parameters. 216 | */ 217 | JNIEXPORT void JNICALL Java_com_mcdermottroe_apple_OSXKeychain__1addInternetPassword(JNIEnv* env, jobject obj, jstring serverName, jstring securityDomain, jstring accountName, jstring path, jint port, jint protocol, jint authenticationType, jstring password) { 218 | OSStatus status; 219 | jstring_unpacked server_name; 220 | jstring_unpacked security_domain; 221 | jstring_unpacked account_name; 222 | jstring_unpacked server_path; 223 | jstring_unpacked server_password; 224 | 225 | /* Unpack the string params. */ 226 | jstring_unpack(env, serverName, &server_name); 227 | jstring_unpack(env, securityDomain, &security_domain); 228 | jstring_unpack(env, accountName, &account_name); 229 | jstring_unpack(env, path, &server_path); 230 | jstring_unpack(env, password, &server_password); 231 | /* check for allocation failures */ 232 | if (server_name.str == NULL || 233 | security_domain.str == NULL || 234 | account_name.str == NULL || 235 | server_path.str == NULL || 236 | server_password.str == NULL) { 237 | jstring_unpacked_free(env, serverName, &server_name); 238 | jstring_unpacked_free(env, securityDomain, &security_domain); 239 | jstring_unpacked_free(env, accountName, &account_name); 240 | jstring_unpacked_free(env, path, &server_path); 241 | jstring_unpacked_free(env, password, &server_password); 242 | return; 243 | } 244 | 245 | /* Add the details to the keychain. */ 246 | status = SecKeychainAddInternetPassword( 247 | NULL, 248 | server_name.len, 249 | server_name.str, 250 | security_domain.len, 251 | security_domain.str, 252 | account_name.len, 253 | account_name.str, 254 | server_path.len, 255 | server_path.str, 256 | port, 257 | protocol, 258 | authenticationType, 259 | server_password.len, 260 | server_password.str, 261 | NULL 262 | ); 263 | if (status != errSecSuccess) { 264 | throw_osxkeychainexception(env, status); 265 | } 266 | 267 | /* Clean up. */ 268 | jstring_unpacked_free(env, serverName, &server_name); 269 | jstring_unpacked_free(env, securityDomain, &security_domain); 270 | jstring_unpacked_free(env, accountName, &account_name); 271 | jstring_unpacked_free(env, path, &server_path); 272 | jstring_unpacked_free(env, password, &server_password); 273 | } 274 | 275 | /* Implementation of OSXKeychain.findGenericPassword(). See the Java docs for 276 | * explanations of the parameters. 277 | */ 278 | JNIEXPORT jstring JNICALL Java_com_mcdermottroe_apple_OSXKeychain__1findGenericPassword(JNIEnv* env, jobject obj, jstring serviceName, jstring accountName) { 279 | OSStatus status; 280 | jstring_unpacked service_name; 281 | jstring_unpacked account_name; 282 | jstring result = NULL; 283 | 284 | /* Buffer for the return from SecKeychainFindGenericPassword. */ 285 | void* password; 286 | UInt32 password_length; 287 | 288 | /* Query the keychain. */ 289 | status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainUser); 290 | if (status != errSecSuccess) { 291 | throw_osxkeychainexception(env, status); 292 | return NULL; 293 | } 294 | 295 | /* Unpack the params. */ 296 | jstring_unpack(env, serviceName, &service_name); 297 | jstring_unpack(env, accountName, &account_name); 298 | if (service_name.str == NULL || 299 | account_name.str == NULL) { 300 | jstring_unpacked_free(env, serviceName, &service_name); 301 | jstring_unpacked_free(env, accountName, &account_name); 302 | return NULL; 303 | } 304 | 305 | status = SecKeychainFindGenericPassword( 306 | NULL, 307 | service_name.len, 308 | service_name.str, 309 | account_name.len, 310 | account_name.str, 311 | &password_length, 312 | &password, 313 | NULL 314 | ); 315 | if (status != errSecSuccess) { 316 | throw_osxkeychainexception(env, status); 317 | } 318 | else { 319 | // the returned value from keychain is not 320 | // null terminated, so a copy is created. 321 | char *password_buffer = malloc(password_length+1); 322 | memcpy(password_buffer, password, password_length); 323 | password_buffer[password_length] = 0; 324 | 325 | /* Create the return value. */ 326 | result = (*env)->NewStringUTF(env, password_buffer); 327 | 328 | /* Clean up. */ 329 | bzero(password_buffer, password_length); 330 | free(password_buffer); 331 | SecKeychainItemFreeContent(NULL, password); 332 | } 333 | jstring_unpacked_free(env, serviceName, &service_name); 334 | jstring_unpacked_free(env, accountName, &account_name); 335 | 336 | return result; 337 | } 338 | 339 | /* Implementation of OSXKeychain.findInternetPassword(). See the Java docs for 340 | * explanations of the parameters. 341 | */ 342 | JNIEXPORT jstring JNICALL Java_com_mcdermottroe_apple_OSXKeychain__1findInternetPassword(JNIEnv* env, jobject obj, jstring serverName, jstring securityDomain, jstring accountName, jstring path, jint port) { 343 | OSStatus status; 344 | jstring_unpacked server_name; 345 | jstring_unpacked security_domain; 346 | jstring_unpacked account_name; 347 | jstring_unpacked server_path; 348 | jstring result = NULL; 349 | 350 | /* This is the password buffer which will be used by 351 | * SecKeychainFindInternetPassword 352 | */ 353 | void* password; 354 | UInt32 password_length; 355 | 356 | /* Query the keychain */ 357 | status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainUser); 358 | if (status != errSecSuccess) { 359 | throw_osxkeychainexception(env, status); 360 | return NULL; 361 | } 362 | 363 | /* Unpack all the jstrings into useful structures. */ 364 | jstring_unpack(env, serverName, &server_name); 365 | jstring_unpack(env, securityDomain, &security_domain); 366 | jstring_unpack(env, accountName, &account_name); 367 | jstring_unpack(env, path, &server_path); 368 | if (server_name.str == NULL || 369 | security_domain.str == NULL || 370 | account_name.str == NULL || 371 | server_path.str == NULL) { 372 | jstring_unpacked_free(env, serverName, &server_name); 373 | jstring_unpacked_free(env, securityDomain, &security_domain); 374 | jstring_unpacked_free(env, accountName, &account_name); 375 | jstring_unpacked_free(env, path, &server_path); 376 | return NULL; 377 | } 378 | 379 | status = SecKeychainFindInternetPassword( 380 | NULL, 381 | server_name.len, 382 | server_name.str, 383 | security_domain.len, 384 | security_domain.str, 385 | account_name.len, 386 | account_name.str, 387 | server_path.len, 388 | server_path.str, 389 | port, 390 | kSecProtocolTypeAny, 391 | kSecAuthenticationTypeAny, 392 | &password_length, 393 | &password, 394 | NULL 395 | ); 396 | if (status != errSecSuccess) { 397 | throw_osxkeychainexception(env, status); 398 | } 399 | else { 400 | // the returned value from keychain is not 401 | // null terminated, so a copy is created. 402 | char* password_buffer = (char *) malloc(password_length+1); 403 | memcpy(password_buffer, password, password_length); 404 | password_buffer[password_length] = 0; 405 | 406 | /* Create the return value. */ 407 | result = (*env)->NewStringUTF(env, password_buffer); 408 | 409 | /* Clean up. */ 410 | bzero(password_buffer, password_length); 411 | free(password_buffer); 412 | SecKeychainItemFreeContent(NULL, password); 413 | } 414 | 415 | jstring_unpacked_free(env, serverName, &server_name); 416 | jstring_unpacked_free(env, securityDomain, &security_domain); 417 | jstring_unpacked_free(env, accountName, &account_name); 418 | jstring_unpacked_free(env, path, &server_path); 419 | 420 | return result; 421 | } 422 | 423 | /* Implementation of OSXKeychain.deleteGenericPassword(). See the Java docs for 424 | * explanations of the parameters. 425 | */ 426 | JNIEXPORT void JNICALL Java_com_mcdermottroe_apple_OSXKeychain__1deleteGenericPassword(JNIEnv* env, jobject obj, jstring serviceName, jstring accountName) { 427 | OSStatus status; 428 | jstring_unpacked service_name; 429 | jstring_unpacked account_name; 430 | SecKeychainItemRef itemToDelete; 431 | 432 | /* Query the keychain. */ 433 | status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainUser); 434 | if (status != errSecSuccess) { 435 | throw_osxkeychainexception(env, status); 436 | return; 437 | } 438 | 439 | /* Unpack the params. */ 440 | jstring_unpack(env, serviceName, &service_name); 441 | jstring_unpack(env, accountName, &account_name); 442 | if (service_name.str == NULL || 443 | account_name.str == NULL) { 444 | jstring_unpacked_free(env, serviceName, &service_name); 445 | jstring_unpacked_free(env, accountName, &account_name); 446 | return; 447 | } 448 | status = SecKeychainFindGenericPassword( 449 | NULL, 450 | service_name.len, 451 | service_name.str, 452 | account_name.len, 453 | account_name.str, 454 | NULL, 455 | NULL, 456 | &itemToDelete 457 | ); 458 | if (status != errSecSuccess) { 459 | throw_osxkeychainexception(env, status); 460 | } 461 | else { 462 | status = SecKeychainItemDelete(itemToDelete); 463 | if (status != errSecSuccess) { 464 | throw_osxkeychainexception(env, status); 465 | } 466 | } 467 | 468 | /* Clean up. */ 469 | jstring_unpacked_free(env, serviceName, &service_name); 470 | jstring_unpacked_free(env, accountName, &account_name); 471 | } 472 | -------------------------------------------------------------------------------- /src/java/com/mcdermottroe/apple/OSXKeychain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Conor McDermottroe 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package com.mcdermottroe.apple; 28 | 29 | import java.io.File; 30 | import java.io.FileOutputStream; 31 | import java.io.IOException; 32 | import java.io.InputStream; 33 | import java.io.OutputStream; 34 | import java.net.URL; 35 | import java.util.HashMap; 36 | import java.util.Map; 37 | 38 | /** An interface to the OS X Keychain. The names of functions and parameters 39 | * will mostly match the functions listed in the Keychain Services Reference. 40 | * 41 | * @author Conor McDermottroe 42 | */ 43 | public class OSXKeychain { 44 | /** The singleton instance of the keychain. Lazily loaded in 45 | * {@link #getInstance()}. 46 | */ 47 | private static OSXKeychain instance; 48 | 49 | /** Prevent this class from being instantiated directly. */ 50 | private OSXKeychain() { 51 | } 52 | 53 | /** Get an instance of the keychain. 54 | * 55 | * @return An instance of this class. 56 | * @throws OSXKeychainException If it's not possible to connect to the 57 | * keychain. 58 | */ 59 | public static OSXKeychain getInstance() 60 | throws OSXKeychainException 61 | { 62 | if (instance == null) { 63 | try { 64 | loadSharedObject(); 65 | } catch (IOException e) { 66 | throw new OSXKeychainException("Failed to load osxkeychain.so", e); 67 | } 68 | instance = new OSXKeychain(); 69 | } 70 | return instance; 71 | } 72 | 73 | /** Add a non-internet password to the keychain. 74 | * 75 | * @param serviceName The name of the service the password is 76 | * for. 77 | * @param accountName The account name/username for the 78 | * service. 79 | * @param password The password for the service. 80 | * @throws OSXKeychainException If an error occurs when communicating 81 | * with the OS X keychain. 82 | */ 83 | public void addGenericPassword(String serviceName, String accountName, String password) 84 | throws OSXKeychainException 85 | { 86 | _addGenericPassword(serviceName, accountName, password); 87 | } 88 | 89 | /** Update an existing non-internet password to the keychain. 90 | * 91 | * @param serviceName The name of the service the password is 92 | * for. 93 | * @param accountName The account name/username for the 94 | * service. 95 | * @param password The password for the service. 96 | * @throws OSXKeychainException If an error occurs when communicating 97 | * with the OS X keychain. 98 | */ 99 | public void modifyGenericPassword(String serviceName, String accountName, String password) 100 | throws OSXKeychainException 101 | { 102 | _modifyGenericPassword(serviceName, accountName, password); 103 | } 104 | 105 | /** Add an internet password to the keychain. 106 | * 107 | * @param url The URL to associate the password with. 108 | * @param accountName The username for the password. Pass 109 | * null here if the username is provided 110 | * in the URL. 111 | * @param password The password to add to the keychain. 112 | * @throws OSXKeychainException If an error occurs when communicating 113 | * with the OS X keychain. 114 | */ 115 | public void addInternetPassword(URL url, String accountName, String password) 116 | throws OSXKeychainException 117 | { 118 | // Work out the username if we can. 119 | String username = getUsername(accountName, url); 120 | if (username == null) { 121 | throw new OSXKeychainException("No account name supplied."); 122 | } 123 | 124 | // Figure out the protocol and port 125 | int port = url.getPort(); 126 | OSXKeychainProtocolType protocol = getProtocol(port, url.getProtocol()); 127 | if (port <= 0) { 128 | port = getPort(port, protocol); 129 | } 130 | 131 | // Now add the password 132 | addInternetPassword( 133 | url.getHost(), 134 | null, 135 | username, 136 | url.getPath(), 137 | port, 138 | protocol, 139 | OSXKeychainAuthenticationType.Any, 140 | password 141 | ); 142 | } 143 | 144 | /** Add an intenet password to the keychain. 145 | * 146 | * @param serverName The name of the server which the 147 | * password is for. 148 | * @param securityDomain The security domain which some 149 | * protocols need. 150 | * @param accountName The account name/username for the 151 | * password. 152 | * @param path The path on the server for which the 153 | * credentials should be used. 154 | * @param port Only return the password if connecting 155 | * to this port. 156 | * @param protocol Only return the password for this 157 | * protocol. 158 | * @param authenticationType The type of authentication the password 159 | * is for. 160 | * @param password The password to add. 161 | * @throws OSXKeychainException If an error occurs when communicating 162 | * with the OS X keychain. 163 | */ 164 | public void addInternetPassword(String serverName, String securityDomain, String accountName, String path, int port, OSXKeychainProtocolType protocol, OSXKeychainAuthenticationType authenticationType, String password) 165 | throws OSXKeychainException 166 | { 167 | _addInternetPassword(serverName, securityDomain, accountName, path, port, protocol.getValue(), authenticationType.getValue(), password); 168 | } 169 | 170 | /** Find a password in the keychain which is not an Internet Password. 171 | * 172 | * @param serviceName The name of the service the password is 173 | * for. 174 | * @param accountName The account name/username for the 175 | * service. 176 | * @return The password which matches the details 177 | * supplied. 178 | * @throws OSXKeychainException If an error occurs when communicating 179 | * with the OS X keychain. 180 | */ 181 | public String findGenericPassword(String serviceName, String accountName) 182 | throws OSXKeychainException 183 | { 184 | return _findGenericPassword(serviceName, accountName); 185 | } 186 | 187 | /** Find an Internet Password in the keychain. This is a convenience method 188 | * wrapping {@link #findInternetPassword(String,String,String,String,int)} 189 | * for one of the most common cases. 190 | * 191 | * @param url The URL of which requires the password. 192 | * @param accountName The account name/username. e.g. 193 | * "conormcd". 194 | * @return The first password which matches the 195 | * details supplied. 196 | * @throws OSXKeychainException If an error occurs when communicating 197 | * with the OS X keychain. 198 | */ 199 | public String findInternetPassword(URL url, String accountName) 200 | throws OSXKeychainException 201 | { 202 | String username = getUsername(accountName, url); 203 | if (username == null) { 204 | throw new OSXKeychainException("No account name supplied."); 205 | } 206 | return findInternetPassword(url.getHost(), username, url.getPath()); 207 | } 208 | 209 | /** Find an Internet Password in the keychain. This is a convenience method 210 | * wrapping {@link #findInternetPassword(String,String,String,String,int)} 211 | * for one of the most common cases. 212 | * 213 | * @param serverName The name of the server. e.g. 214 | * "github.com". 215 | * @param accountName The account name/username. e.g. 216 | * "conormcd". 217 | * @param path The path to the password protected 218 | * resource on the server. e.g. "/login". 219 | * @return The first password which matches the 220 | * details supplied. 221 | * @throws OSXKeychainException If an error occurs when communicating 222 | * with the OS X keychain. 223 | */ 224 | public String findInternetPassword(String serverName, String accountName, String path) 225 | throws OSXKeychainException 226 | { 227 | return _findInternetPassword(serverName, null, accountName, path, 0); 228 | } 229 | 230 | /** Find an Internet Password in the keychain. 231 | * 232 | * @param serverName The name of the server. e.g. 233 | * "github.com". 234 | * @param securityDomain The security domain which is needed for 235 | * some protocols. Pass null if not 236 | * needed. 237 | * @param accountName The account name/username. e.g. 238 | * "conormcd". 239 | * @param path The path to the password protected 240 | * resource on the server. e.g. "/login". 241 | * @param port The port to connect to. Pass 0 if you 242 | * want the first result for any entry 243 | * matching the rest of the criteria. 244 | * @return The first password which matches the 245 | * details supplied. 246 | * @throws OSXKeychainException If an error occurs when communicating 247 | * with the OS X keychain. 248 | */ 249 | public String findInternetPassword(String serverName, String securityDomain, String accountName, String path, int port) 250 | throws OSXKeychainException 251 | { 252 | return _findInternetPassword(serverName, securityDomain, accountName, path, port); 253 | } 254 | 255 | /** Delete a generic password from the keychain. 256 | * 257 | * @param serviceName The name of the service the password is 258 | * for. 259 | * @param accountName The account name/username for the 260 | * service. 261 | * @throws OSXKeychainException If an error occurs when communicating 262 | * with the OS X keychain. 263 | */ 264 | public void deleteGenericPassword(String serviceName, String accountName) 265 | throws OSXKeychainException 266 | { 267 | _deleteGenericPassword(serviceName, accountName); 268 | } 269 | 270 | /* ************************* */ 271 | /* JNI stuff from here down. */ 272 | /* ************************* */ 273 | 274 | /** See Java_com_mcdermottroe_apple_OSXKeychain__1addGenericPassword for 275 | * the implementation of this and use {@link #addGenericPassword(String, 276 | * String, String)} to call this. 277 | * 278 | * @param serviceName The value which should be passed as the 279 | * serviceName parameter to 280 | * SecKeychainAddGenericPassword. 281 | * @param accountName The value which should be passed as the 282 | * accountName parameter to 283 | * SecKeychainAddGenericPassword. 284 | * @param password The value which should be passed as the 285 | * password parameter to 286 | * SecKeychainAddGenericPassword. 287 | * @throws OSXKeychainException If an error occurs when communicating 288 | * with the OS X keychain. 289 | */ 290 | private native void _addGenericPassword(String serviceName, String accountName, String password) 291 | throws OSXKeychainException; 292 | 293 | /** See Java_com_mcdermottroe_apple_OSXKeychain__1modifyGenericPassword for 294 | * the implementation of this and use {@link #modifyGenericPassword(String, 295 | * String, String)} to call this. 296 | * 297 | * @param serviceName The value which should be passed as the 298 | * serviceName parameter to 299 | * SecKeychainAddGenericPassword. 300 | * @param accountName The value which should be passed as the 301 | * accountName parameter to 302 | * SecKeychainAddGenericPassword. 303 | * @param password The value which should be passed as the 304 | * password parameter to 305 | * SecKeychainAddGenericPassword. 306 | * @throws OSXKeychainException If an error occurs when communicating 307 | * with the OS X keychain. 308 | */ 309 | private native void _modifyGenericPassword(String serviceName, String accountName, String password) 310 | throws OSXKeychainException; 311 | 312 | /** See Java_com_mcdermottroe_apple_OSXKeychain__1addInternetPassword for 313 | * the implementation of this and use {@link #addInternetPassword(String, 314 | * String, String, String, int, OSXKeychainProtocolType, 315 | * OSXKeychainAuthenticationType, String)} to call this. 316 | */ 317 | private native void _addInternetPassword(String serverName, String securityDomain, String accountName, String path, int port, int protocol, int authenticationType, String password) 318 | throws OSXKeychainException; 319 | 320 | /** See Java_com_mcdermottroe_apple_OSXKeychain__1findGenericPassword for 321 | * the implementation of this and use {@link #findGenericPassword(String, 322 | * String)} to call this. 323 | * 324 | * @param serviceName The value which should be passed as the 325 | * serviceName parameter to 326 | * SecKeychainFindGenericPassword. 327 | * @param accountName The value for the accountName parameter 328 | * to SecKeychainFindGenericPassword. 329 | * @return The first password which matches the 330 | * details supplied. 331 | * @throws OSXKeychainException If an error occurs when communicating 332 | * with the OS X keychain. 333 | */ 334 | private native String _findGenericPassword(String serviceName, String accountName) 335 | throws OSXKeychainException; 336 | 337 | /** See Java_com_mcdermottroe_apple_OSXKeychain__1findInternetPassword for 338 | * the implementation of this and use {@link #findInternetPassword(String, 339 | * String, String, String, int)} to call this. 340 | * 341 | * @param serverName The value which should be passed as the 342 | * serverName parameter to 343 | * SecKeychainFindInternetPassword. 344 | * @param securityDomain This will be passed for the 345 | * securityDomain parameter to 346 | * SecKeychainFindInternetPassword. 347 | * @param accountName The value for the accountName parameter 348 | * to SecKeychainFindInternetPassword. 349 | * @param path This will be passed as the path 350 | * parameter to 351 | * SecKeychainFindInternetPassword. 352 | * @param port The port parameter value for 353 | * SecKeychainFindInternetPassword. 354 | * @return The first password which matches the 355 | * details supplied. 356 | * @throws OSXKeychainException If an error occurs when communicating 357 | * with the OS X keychain. 358 | */ 359 | private native String _findInternetPassword(String serverName, String securityDomain, String accountName, String path, int port) 360 | throws OSXKeychainException; 361 | 362 | /** See Java_com_mcdermottroe_apple_OSXKeychain__1deleteGenericPassword for 363 | * the implementation of this and use {@link #deleteGenericPassword(String, 364 | * String)} to call this. 365 | * 366 | * @param serviceName The value which should be passed as the 367 | * serviceName parameter to 368 | * SecKeychainFindGenericPassword in order 369 | * to find the password to delete it. 370 | * @param accountName The value for the accountName parameter 371 | * to SecKeychainFindGenericPassword in 372 | * order to find the password to delete it. 373 | * @throws OSXKeychainException If an error occurs when communicating 374 | * with the OS X keychain. 375 | */ 376 | private native void _deleteGenericPassword(String serviceName, String accountName) 377 | throws OSXKeychainException; 378 | 379 | /** Load the shared object which contains the implementations for the native 380 | * methods in this class. 381 | * 382 | * @throws IOException If the shared object could not be loaded. 383 | */ 384 | private static void loadSharedObject() 385 | throws IOException 386 | { 387 | // Stream the library out of the JAR 388 | InputStream soInJarStream = OSXKeychain.class.getResourceAsStream("osxkeychain.so"); 389 | 390 | // Put the library in a temp file. 391 | File soInTmp = File.createTempFile("osxkeychain", ".so"); 392 | soInTmp.deleteOnExit(); 393 | OutputStream soInTmpStream = new FileOutputStream(soInTmp); 394 | 395 | // Copy the .so 396 | byte[] buffer = new byte[4096]; 397 | int bytesRead; 398 | while ((bytesRead = soInJarStream.read(buffer)) > 0) { 399 | soInTmpStream.write(buffer, 0, bytesRead); 400 | } 401 | 402 | // Clean up 403 | soInJarStream.close(); 404 | soInTmpStream.close(); 405 | 406 | // Now load the library 407 | System.load(soInTmp.getAbsolutePath()); 408 | } 409 | 410 | /* ********************************* */ 411 | /* Private utilities from here down. */ 412 | /* ********************************* */ 413 | 414 | /** A fixed mapping of ports to known protocols. */ 415 | private static final Map PROTOCOLS; 416 | static { 417 | PROTOCOLS = new HashMap(32); 418 | PROTOCOLS.put(548, OSXKeychainProtocolType.AFP); 419 | PROTOCOLS.put(3020, OSXKeychainProtocolType.CIFS); 420 | PROTOCOLS.put(2401, OSXKeychainProtocolType.CVSpserver); 421 | PROTOCOLS.put(3689, OSXKeychainProtocolType.DAAP); 422 | PROTOCOLS.put(3031, OSXKeychainProtocolType.EPPC); 423 | PROTOCOLS.put(21, OSXKeychainProtocolType.FTP); 424 | PROTOCOLS.put(990, OSXKeychainProtocolType.FTPS); 425 | PROTOCOLS.put(80, OSXKeychainProtocolType.HTTP); 426 | PROTOCOLS.put(443, OSXKeychainProtocolType.HTTPS); 427 | PROTOCOLS.put(143, OSXKeychainProtocolType.IMAP); 428 | PROTOCOLS.put(993, OSXKeychainProtocolType.IMAPS); 429 | PROTOCOLS.put(631, OSXKeychainProtocolType.IPP); 430 | PROTOCOLS.put(6667, OSXKeychainProtocolType.IRC); 431 | PROTOCOLS.put(994, OSXKeychainProtocolType.IRCS); 432 | PROTOCOLS.put(389, OSXKeychainProtocolType.LDAP); 433 | PROTOCOLS.put(636, OSXKeychainProtocolType.LDAPS); 434 | PROTOCOLS.put(119, OSXKeychainProtocolType.NNTP); 435 | PROTOCOLS.put(563, OSXKeychainProtocolType.NNTPS); 436 | PROTOCOLS.put(110, OSXKeychainProtocolType.POP3); 437 | PROTOCOLS.put(995, OSXKeychainProtocolType.POP3S); 438 | PROTOCOLS.put(554, OSXKeychainProtocolType.RTSP); 439 | PROTOCOLS.put(25, OSXKeychainProtocolType.SMTP); 440 | PROTOCOLS.put(1080, OSXKeychainProtocolType.SOCKS); 441 | PROTOCOLS.put(22, OSXKeychainProtocolType.SSH); 442 | PROTOCOLS.put(3690, OSXKeychainProtocolType.SVN); 443 | PROTOCOLS.put(23, OSXKeychainProtocolType.Telnet); 444 | PROTOCOLS.put(992, OSXKeychainProtocolType.TelnetS); 445 | } 446 | 447 | /** Resolve a username from either a supplied username or from the username 448 | * portion of a URL. 449 | * 450 | * @param username The username to return if it's not 451 | * null. 452 | * @param url The URL to check iff username is null. 453 | * @return If the username is not null, the 454 | * username will be returned. If the 455 | * username is null and the url contains a 456 | * username then the username from the URL 457 | * will be returned. 458 | * @throws OSXKeychainException If username is null and the URL 459 | * contains no username. 460 | */ 461 | private static String getUsername(String username, URL url) 462 | throws OSXKeychainException 463 | { 464 | if (username != null) { 465 | return username; 466 | } 467 | 468 | String urlUsername = url.getUserInfo(); 469 | if (urlUsername != null) { 470 | if (urlUsername.indexOf(':') > 0) { 471 | return urlUsername.substring(0, urlUsername.indexOf(':')); 472 | } else { 473 | return urlUsername; 474 | } 475 | } else { 476 | throw new OSXKeychainException("Could not return a username."); 477 | } 478 | } 479 | 480 | /** Resolve a port from either a supplied port or from a protocol. 481 | * 482 | * @param port The port the protocol runs on. 483 | * @param protocol A protocol constant for the given 484 | * protocol. 485 | * @return If the supplied port is a valid port 486 | * number, then that number is returned. 487 | * If the port is not a valid number, but 488 | * the protocol is valid, then the port 489 | * for that protocol is returned. 490 | * @throws OSXKeychainException If port is not valid and protocol is not 491 | * known to use a particular port. 492 | */ 493 | private static int getPort(int port, OSXKeychainProtocolType protocol) 494 | throws OSXKeychainException 495 | { 496 | if (port > 0 && port < 65536) { 497 | return port; 498 | } 499 | for (Map.Entry entry : PROTOCOLS.entrySet()) { 500 | if (entry.getValue() == protocol) { 501 | return entry.getKey(); 502 | } 503 | } 504 | throw new OSXKeychainException("Could not determine port."); 505 | } 506 | 507 | /** Figure out the protocol given the port and or the protocol portion of 508 | * the URL. 509 | * 510 | * @param port An IP port number. 511 | * @param protocol The protocol part of a URL. 512 | * @return The protocol type which matches either 513 | * the port or the protocol prefix from 514 | * the URL. 515 | * @throws OSXKeychainException If there's no known protocol which 516 | * matches the port or protocol prefix. 517 | */ 518 | private static OSXKeychainProtocolType getProtocol(int port, String protocol) 519 | throws OSXKeychainException 520 | { 521 | // Try to figure it out from the port, if available. 522 | for (Map.Entry pp : PROTOCOLS.entrySet()) { 523 | if (pp.getKey() == port) { 524 | return pp.getValue(); 525 | } 526 | } 527 | 528 | // See if we got a protocol prefix. 529 | if (protocol != null) { 530 | // Try to match the name of the protocol 531 | for (OSXKeychainProtocolType pt : OSXKeychainProtocolType.values()) { 532 | if (pt.toString().toLowerCase().equals(protocol.toLowerCase())) { 533 | return pt; 534 | } 535 | } 536 | 537 | // A few aliases/ones we couldn't handle in the map above. 538 | if ("smb".equals(protocol)) { 539 | return OSXKeychainProtocolType.SMB; 540 | } 541 | } 542 | 543 | // Give up. 544 | throw new OSXKeychainException("Could not determine protocol."); 545 | } 546 | } 547 | --------------------------------------------------------------------------------