/*
* Copyright 2012-2013 Mathias Herberts
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.geoxp.oss;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
public class DirectoryHierarchyKeyStore extends KeyStore {
private final File directory;
public DirectoryHierarchyKeyStore(String directory) {
try {
this.directory = new File(directory).getCanonicalFile();
} catch (IOException ioe) {
throw new RuntimeException("Unable to determine canonical path.");
}
if (!this.directory.exists() || !this.directory.isDirectory()) {
throw new RuntimeException("Invalid directory '" + this.directory.getAbsolutePath() + "'");
}
}
@Override
public byte[] getSecret(String name, String fingerprint) throws OSSException {
try {
//
// Sanitize name
//
name = sanitizeSecretName(name);
File root = getSecretFile(name);
File secretFile = new File(root.getAbsolutePath() + ".secret");
File aclFile = findACLFile(name);
//
// Check if secret exists
//
if (!secretFile.exists() || !secretFile.isFile() || null == aclFile || !aclFile.exists() || !aclFile.isFile()) {
throw new OSSException("Missing secret or ACL file.");
}
//
// Check ACLs
//
// Sanitize fingerprint
if (null == fingerprint) {
fingerprint = "";
}
fingerprint = fingerprint.toLowerCase().replaceAll("[^0-9a-f]","");
boolean authorized = false;
Reader reader;
if (!OSS.hasSecureACLs()) {
reader = new FileReader(aclFile);
} else {
//
// Read ACL blob and unwrap it
//
InputStream in = new FileInputStream(aclFile);
byte[] buf = new byte[1024];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while(true) {
int len = in.read(buf);
if (len < 0) {
break;
}
baos.write(buf, 0, len);
}
in.close();
byte[] k = OSS.getMasterSecret();
reader = new StringReader(new String(CryptoHelper.unwrapBlob(k, baos.toByteArray()), "UTF-8"));
Arrays.fill(k, (byte) 0);
}
try {
BufferedReader br = new BufferedReader(reader);
while(true) {
String line = br.readLine();
if (null == line) {
break;
}
String acl = line.toLowerCase().replaceAll("[^0-9a-f#*]", "");
if ("*".equals(acl) || fingerprint.equals(acl)) {
authorized = true;
break;
}
}
br.close();
} catch (IOException ioe) {
throw new OSSException(ioe);
}
if (!authorized) {
throw new OSSException("Access denied.");
}
//
// Read secret
//
ByteArrayOutputStream baos = new ByteArrayOutputStream((int) secretFile.length());
byte[] buf = new byte[1024];
try {
InputStream is = new FileInputStream(secretFile);
do {
int len = is.read(buf);
if (-1 == len) {
break;
}
baos.write(buf, 0, len);
} while(true);
is.close();
} catch (IOException ioe) {
throw new OSSException(ioe);
}
return baos.toByteArray();
} catch (IOException ioe) {
throw new OSSException(ioe);
}
}
@Override
public void putSecret(String name, byte[] secret) throws OSSException {
//
// Sanitize name
//
name = sanitizeSecretName(name);
File root = getSecretFile(name);
File secretFile = new File(root.getAbsolutePath() + ".secret");
if (secretFile.exists()) {
throw new OSSException("Secret '" + name + "' already exists.");
}
//
// Create hierarchy
//
if (secretFile.getParentFile().exists() && !secretFile.getParentFile().isDirectory()) {
throw new OSSException("Secret path already exists and is not a directory.");
}
if (!secretFile.getParentFile().exists() && !secretFile.getParentFile().mkdirs()) {
throw new OSSException("Unable to create path to secret file.");
}
try {
OutputStream os = new FileOutputStream(secretFile);
os.write(secret);
os.close();
} catch (IOException ioe) {
throw new OSSException(ioe);
}
}
/**
* Retrieve secret file from secret name
*
* @param name
* @return
*/
private File getSecretFile(String name) throws OSSException {
//
// Sanitize name
//
name = sanitizeSecretName(name);
//
// Replace '.' with '/'
//
String[] tokens = name.split("\\.");
File f;
f = this.directory.getAbsoluteFile();
for (int i = 0; i < tokens.length; i++) {
f = new File(f, tokens[i]);
}
return f;
}
@Override
public File getACLFile(String name) throws IOException, OSSException {
File path = getSecretFile(name);
return new File(path.getCanonicalPath() + ".acl");
}
/**
* Determine the ACL file to use for a given secret
*
* @param name Name of secret
* @return File of ACLs or null if none is suitable
*/
private File findACLFile(String name) throws IOException, OSSException {
File path = getSecretFile(name);
while (!path.equals(this.directory)) {
File acl = new File(path.getCanonicalPath() + ".acl");
if (acl.exists() && acl.isFile()) {
return acl;
}
path = path.getParentFile();
}
return null;
}
}