com.github.javakeyring.internal.osx.OsxKeychainBackend Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-keyring Show documentation
Show all versions of java-keyring Show documentation
A Java library to store password securely
The newest version!
/*
* Copyright © 2019, Java Keyring
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.github.javakeyring.internal.osx;
import java.nio.charset.Charset;
import com.github.javakeyring.BackendNotSupportedException;
import com.github.javakeyring.PasswordAccessException;
import com.github.javakeyring.internal.KeyringBackend;
import com.sun.jna.Pointer;
/**
* Keyring backend which uses legacy OS X Keychain.
*/
public class OsxKeychainBackend implements KeyringBackend {
private final NativeLibraryManager nativeLibraries;
public OsxKeychainBackend() throws BackendNotSupportedException {
if(System.getProperty("os.name", "").toLowerCase().contains("mac os")) {
nativeLibraries = new NativeLibraryManager();
}
else
throw new BackendNotSupportedException("Not running on Mac OS.");
}
/**
* Gets password from key store.
*
* @param service
* Service name
* @param account
* Account name
*
* @return Password related to specified service and account
*
* @throws PasswordAccessException
* Thrown when an error happened while getting password
*/
@Override
public String getPassword(String service, String account) throws PasswordAccessException {
byte[] serviceBytes;
byte[] accountBytes;
Charset charset = Charset.forName("UTF-8");
serviceBytes = service.getBytes(charset);
accountBytes = account.getBytes(charset);
int[] dataLength = new int[1];
Pointer[] data = new Pointer[1];
int status = nativeLibraries.getSecurity().SecKeychainFindGenericPassword(null, serviceBytes.length, serviceBytes,
accountBytes.length, accountBytes, dataLength, data, null);
if (status != 0) {
throw new PasswordAccessException(convertErrorCodeToMessage(status));
}
byte[] passwordBytes = data[0].getByteArray(0, dataLength[0]);
nativeLibraries.getSecurity().SecKeychainItemFreeContent(null, data[0]);
return new String(passwordBytes, charset);
}
/**
* Sets password to key store.
*
* @param service
* Service name
* @param account
* Account name
* @param password
* Password
*
* @throws PasswordAccessException
* Thrown when an error happened while saving the password
*/
@Override
public void setPassword(String service, String account, String password) throws PasswordAccessException {
byte[] serviceBytes;
byte[] accountBytes;
byte[] passwordBytes;
Charset charset = Charset.forName("UTF-8");
serviceBytes = service.getBytes(charset);
accountBytes = account.getBytes(charset);
passwordBytes = password.getBytes(charset);
Pointer[] itemRef = new Pointer[1];
int status = nativeLibraries.getSecurity().SecKeychainFindGenericPassword(null, serviceBytes.length, serviceBytes,
accountBytes.length, accountBytes, null, null, itemRef);
if (status != SecurityLibrary.ERR_SEC_SUCCESS && status != SecurityLibrary.ERR_SEC_ITEM_NOT_FOUND) {
throw new PasswordAccessException(convertErrorCodeToMessage(status));
}
if (itemRef[0] != null) {
try {
status = nativeLibraries.getSecurity().SecKeychainItemModifyContent(itemRef[0], null, passwordBytes.length,
passwordBytes);
} finally {
nativeLibraries.getCoreFoundation().CFRelease(itemRef[0]);
}
} else {
status = nativeLibraries.getSecurity().SecKeychainAddGenericPassword(Pointer.NULL, serviceBytes.length,
serviceBytes, accountBytes.length, accountBytes, passwordBytes.length, passwordBytes, null);
}
if (status != 0) {
throw new PasswordAccessException(convertErrorCodeToMessage(status));
}
}
/**
* Delete a password from the key store.
*
* @param service
* Service name
* @param account
* Account name
*
* @throws PasswordAccessException
* Thrown when an error happened while saving the password
*/
public void deletePassword(String service, String account) throws PasswordAccessException {
byte[] serviceBytes;
byte[] accountBytes;
Charset charset = Charset.forName("UTF-8");
serviceBytes = service.getBytes(charset);
accountBytes = account.getBytes(charset);
Pointer[] itemRef = new Pointer[1];
int status = nativeLibraries.getSecurity().SecKeychainFindGenericPassword(null, serviceBytes.length, serviceBytes,
accountBytes.length, accountBytes, null, null, itemRef);
if (status != SecurityLibrary.ERR_SEC_SUCCESS && status != SecurityLibrary.ERR_SEC_ITEM_NOT_FOUND) {
throw new PasswordAccessException(convertErrorCodeToMessage(status));
}
if (itemRef[0] != null) {
status = nativeLibraries.getSecurity().SecKeychainItemDelete(itemRef[0]);
}
if (status != 0) {
throw new PasswordAccessException(convertErrorCodeToMessage(status));
}
}
/**
* Converts OSStat to error message.
*
* @param errorCode
* OSStat to be converted
*/
private String convertErrorCodeToMessage(int errorCode) {
Pointer msgPtr = nativeLibraries.getSecurity().SecCopyErrorMessageString(errorCode, null);
if (msgPtr == null) {
return null;
}
int bufSize = (int) nativeLibraries.getCoreFoundation().CFStringGetLength(msgPtr);
char[] buf = new char[bufSize];
for (int i = 0; i < buf.length; i++) {
buf[i] = nativeLibraries.getCoreFoundation().CFStringGetCharacterAtIndex(msgPtr, i);
}
nativeLibraries.getCoreFoundation().CFRelease(msgPtr);
return new String(buf);
}
@Override
public void close() throws Exception {
// nothing to close
}
}