All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.named_data.jndn.security.identity.FilePrivateKeyStorage Maven / Gradle / Ivy

Go to download

jNDN is a new implementation of a Named Data Networking client library written in Java. It is wire format compatible with the new NDN-TLV encoding, with NDNx and PARC's CCNx.

There is a newer version: 0.25
Show newest version
/**
 * Copyright (C) 2014-2017 Regents of the University of California.
 * @author: Jeff Thompson 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn.security.identity;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.File;
import java.io.FileWriter;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.List;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import net.named_data.jndn.Name;
import net.named_data.jndn.encoding.der.DerDecodingException;
import net.named_data.jndn.encoding.der.DerNode;
import net.named_data.jndn.security.DigestAlgorithm;
import net.named_data.jndn.security.EcdsaKeyParams;
import net.named_data.jndn.security.KeyClass;
import net.named_data.jndn.security.KeyParams;
import net.named_data.jndn.security.KeyType;
import net.named_data.jndn.security.RsaKeyParams;
import net.named_data.jndn.security.SecurityException;
import net.named_data.jndn.security.certificate.PublicKey;
import net.named_data.jndn.util.Blob;
import net.named_data.jndn.util.Common;

/**
 * FilePrivateKeyStorage extends PrivateKeyStorage to implement private key
 * storage using files.
 */
public class FilePrivateKeyStorage extends PrivateKeyStorage {
  /**
   * Create a new FilePrivateKeyStorage to connect to the default directory in
   * System.getProperty("user.home").
   */
  public FilePrivateKeyStorage()
  {
    keyStorePath_ = new File
      (getDefaultDirecoryPath(Common.getHomeDirectory()));
    keyStorePath_.mkdirs();
  }

  /**
   * Create a new FilePrivateKeyStorage to connect to the given directory.
   * @param keyStoreDirectoryPath The full path of the directory holding the
   * private key data. This creates the directory if it doesn't exist.
   * For example, you can get the default directory path from an Android files
   * directory with getDefaultDirecoryPath(context.getFilesDir())
   */
  public FilePrivateKeyStorage(String keyStoreDirectoryPath)
  {
    keyStorePath_ = new File(keyStoreDirectoryPath);
    keyStorePath_.mkdirs();
  }

  /**
   * Get the default directory path for private keys based on the files root.
   * For example if filesRoot is "/data/data/org.example/files", this returns
   * "/data/data/org.example/files/.ndn/ndnsec-tpm-file".
   * @param filesRoot The root file directory. An Android app can use
   * context.getFilesDir()
   * @return The default directory path.
   */
  public static String
  getDefaultDirecoryPath(File filesRoot)
  {
    return getDefaultDirecoryPath(filesRoot.getAbsolutePath());
  }

  /**
   * Get the default directory path for private keys based on the files root.
   * @param filesRoot The root file directory.
   * @return The default directory path.
   */
  public static String
  getDefaultDirecoryPath(String filesRoot)
  {
    // NOTE: Use File because java.nio.file.Path is not available before Java 7.
    return new File(new File(new File(filesRoot), ".ndn"), "ndnsec-tpm-file").getAbsolutePath();
  }

  /**
   * Generate a pair of asymmetric keys.
   * @param keyName The name of the key pair.
   * @param params The parameters of the key.
   * @throws SecurityException
   */
  public final void
  generateKeyPair(Name keyName, KeyParams params) throws SecurityException
  {
    if (doesKeyExist(keyName, KeyClass.PUBLIC))
      throw new SecurityException("Public Key already exists");
    if (doesKeyExist(keyName, KeyClass.PRIVATE))
      throw new SecurityException("Private Key already exists");

    String keyAlgorithm;
    int keySize;
    if (params.getKeyType() == KeyType.RSA) {
      keyAlgorithm = "RSA";
      keySize = ((RsaKeyParams)params).getKeySize();
    }
    else if (params.getKeyType() == KeyType.ECDSA) {
      keyAlgorithm = "EC";
      keySize = ((EcdsaKeyParams)params).getKeySize();
    }
    else
      throw new SecurityException("Cannot generate a key pair of type " + params.getKeyType());

    KeyPairGenerator generator = null;
    try{
      generator = KeyPairGenerator.getInstance(keyAlgorithm);
    }
    catch(NoSuchAlgorithmException e){
      throw new SecurityException
        ("FilePrivateKeyStorage: Could not create the key generator: " + e.getMessage());
    }

    // generate
    generator.initialize(keySize);
    KeyPair pair = generator.generateKeyPair();

    // save
    this.write(keyName, KeyClass.PRIVATE, pair.getPrivate().getEncoded());
    this.write(keyName, KeyClass.PUBLIC, pair.getPublic().getEncoded());
  }

  /**
   * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing.
   * @param keyName The name of the key pair.
   */
  public void
  deleteKeyPair(Name keyName) throws SecurityException
  {
    try {
      // deleteKeyPair is required by an older API which will be changed.
      // For now, call deleteKey.
      deleteKey(keyName);
    } catch (SecurityException ex) {
      // In the deleteKeyPair API, do nothing if the key doesn't exist.
    }
  }

  /**
   * Get the public key
   * @param keyName The name of public key.
   * @return The public key.
   * @throws SecurityException
   */
  public final PublicKey
  getPublicKey(Name keyName) throws SecurityException
  {
    if (!doesKeyExist(keyName, KeyClass.PUBLIC))
      throw new SecurityException("Public key does not exist.");

    // Read the file contents.
    byte[] der = this.read(keyName, KeyClass.PUBLIC);

    return new PublicKey(new Blob(der, false));
  }

  /**
   * Get the private key for this name; internal helper method
   * @param keyName The name of the key.
   * @param keyType Set keyType[0] to the KeyType.
   * @return The java.security.PrivateKey.
   * @throws SecurityException
   */
  private PrivateKey
  getPrivateKey(Name keyName, KeyType[] keyType) throws SecurityException
  {
    if (!doesKeyExist(keyName, KeyClass.PRIVATE))
      throw new SecurityException
        ("FilePrivateKeyStorage: Private key does not exist.");

    // Read the file contents.
    byte[] der = this.read(keyName, KeyClass.PRIVATE);

    // Decode the PKCS #8 DER to find the algorithm OID.
    String oidString = null;
    try {
      DerNode parsedNode = DerNode.parse(ByteBuffer.wrap(der), 0);
      List pkcs8Children = parsedNode.getChildren();
      List algorithmIdChildren = DerNode.getSequence(pkcs8Children, 1).getChildren();
      oidString = "" + ((DerNode.DerOid)algorithmIdChildren.get(0)).toVal();
    }
    catch (DerDecodingException ex) {
      throw new SecurityException("Cannot decode the PKCS #8 private key: " + ex);
    }

    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der);
    if (oidString.equals(RSA_ENCRYPTION_OID)) {
      keyType[0] = KeyType.RSA;

      try {
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
      }
      catch(InvalidKeySpecException e){
        // Don't expect this to happen.
        throw new SecurityException
          ("FilePrivateKeyStorage: RSA is not supported: " + e.getMessage());
      }
      catch(NoSuchAlgorithmException e){
        // Don't expect this to happen.
        throw new SecurityException
          ("FilePrivateKeyStorage: PKCS8EncodedKeySpec is not supported for RSA: "
                  + e.getMessage());
      }
    }
    else if (oidString.equals(EC_ENCRYPTION_OID)) {
      keyType[0] = KeyType.ECDSA;

      try {
        KeyFactory kf = KeyFactory.getInstance("EC");
        return kf.generatePrivate(spec);
      }
      catch(InvalidKeySpecException e){
        // Don't expect this to happen.
        throw new SecurityException
          ("FilePrivateKeyStorage: EC is not supported: " + e.getMessage());
      }
      catch(NoSuchAlgorithmException e){
        // Don't expect this to happen.
        throw new SecurityException
          ("FilePrivateKeyStorage: PKCS8EncodedKeySpec is not supported for EC: "
                  + e.getMessage());
      }
    }
    else
      throw new SecurityException
        ("FilePrivateKeyStorage.sign: Unrecognized private key OID: " + oidString);
  }

  /**
   * Get the symmetric key for this name; internal helper method
   * @param keyName
   * @return The symmetric key.
   * @throws SecurityException
   */
  private final SecretKey
  getSymmetricKey(Name keyName) throws SecurityException
  {
    if (!doesKeyExist(keyName, KeyClass.SYMMETRIC))
      throw new SecurityException
        ("FilePrivateKeyStorage: Symmetric key does not exist.");

    // Read the file contents.
    byte[] encoded = this.read(keyName, KeyClass.SYMMETRIC);
    // TODO: Check the key type. Don't assume AES.
    return new SecretKeySpec(encoded, "AES");
  }


  /**
   * Fetch the private key for keyName and sign the data, returning a signature
   * Blob.
   * @param data Pointer the input byte buffer to sign.
   * @param keyName The name of the signing key.
   * @param digestAlgorithm the digest algorithm.
   * @return The signature Blob.
   * @throws SecurityException
   */
  public final Blob
  sign(ByteBuffer data, Name keyName, DigestAlgorithm digestAlgorithm)
      throws SecurityException
  {
    if (!doesKeyExist(keyName, KeyClass.PRIVATE))
      throw new SecurityException
        ("FilePrivateKeyStorage.sign: private key doesn't exist");

    if (digestAlgorithm != DigestAlgorithm.SHA256)
      throw new SecurityException
        ("FilePrivateKeyStorage.sign: Unsupported digest algorithm");

    // Retrieve the private key.
    KeyType[] keyType = new KeyType[1];
    PrivateKey privateKey = getPrivateKey(keyName, keyType);

    // Sign.
    java.security.Signature signature = null;
    if (keyType[0] == KeyType.RSA) {
      try {
        signature = java.security.Signature.getInstance("SHA256withRSA");
      }
      catch (NoSuchAlgorithmException e) {
        // Don't expect this to happen.
        throw new SecurityException
          ("FilePrivateKeyStorage: The SHA256withRSA algorithm is not supported");
      }
    }
    else if (keyType[0] == KeyType.ECDSA) {
      try {
        signature = java.security.Signature.getInstance("SHA256withECDSA");
      }
      catch (NoSuchAlgorithmException e) {
        // Don't expect this to happen.
        throw new SecurityException
          ("FilePrivateKeyStorage: The SHA256withECDSA algorithm is not supported");
      }
    }
    else
      // We don't expect this to happen since getPrivateKey checked it.
      throw new SecurityException
        ("FilePrivateKeyStorage: Unsupported signature key type " + keyType[0]);

    try {
      signature.initSign(privateKey);
    }
    catch (InvalidKeyException exception) {
      throw new SecurityException
        ("FilePrivateKeyStorage: InvalidKeyException: " + exception.getMessage());
    }
    try {
      signature.update(data);
      return new Blob(signature.sign(), false);
    }
    catch (SignatureException exception) {
      throw new SecurityException
        ("FilePrivateKeyStorage: SignatureException: " + exception.getMessage());
    }
  }

  /**
   * Decrypt data.
   * @param keyName The name of the decrypting key.
   * @param data The byte buffer to be decrypted, from its position to its
   * limit.
   * @param isSymmetric If true symmetric encryption is used, otherwise
   * asymmetric encryption is used.
   * @return The decrypted data.
   * @throws SecurityException
   */
  public final Blob
  decrypt(Name keyName, ByteBuffer data, boolean isSymmetric)
         throws SecurityException
  {
    throw new UnsupportedOperationException
      ("FilePrivateKeyStorage.decrypt is not implemented");
  }

  /**
   * Encrypt data.
   * @param keyName The name of the encrypting key.
   * @param data The byte buffer to be encrypted, from its position to its
   * limit.
   * @param isSymmetric If true symmetric encryption is used, otherwise
   * asymmetric encryption is used.
   * @return The encrypted data.
   * @throws SecurityException
   */
  public final Blob
  encrypt(Name keyName, ByteBuffer data, boolean isSymmetric)
          throws SecurityException
  {
    throw new UnsupportedOperationException
      ("FilePrivateKeyStorage.encrypt is not implemented");
  }

  /**
   * Generate a symmetric key.
   * @param keyName The name of the key.
   * @param params The parameters of the key.
   * @throws SecurityException
   */
  public final void
  generateKey(Name keyName, KeyParams params) throws SecurityException
  {
    throw new UnsupportedOperationException
      ("FilePrivateKeyStorage.generateKey is not implemented");
  }

  /**
   * Delete a key by name; checks all KeyClass types
   * @param keyName
   * @throws SecurityException
   */
  public final void
  deleteKey(Name keyName) throws SecurityException
  {
    int deletedFiles = 0;
    for(KeyClass keyClass : KeyClass.values()){
      if (doesKeyExist(keyName, keyClass)){
        String extension = (String) keyTypeMap_.get(keyClass);
        File file = nameTransform(keyName.toUri(), extension);
        file.delete();
        deletedFiles++;
      }
    }
    if(deletedFiles == 0){
      throw new SecurityException("No key files found to delete");
    }
  }

  /**
   * Check if a particular key exists.
   * @param keyName The name of the key.
   * @param keyClass The class of the key, e.g. KeyClass.PUBLIC,
   * KeyClass.PRIVATE, or KeyClass.SYMMETRIC.
   * @return True if the key exists, otherwise false.
   */
  public final boolean
  doesKeyExist(Name keyName, KeyClass keyClass) throws SecurityException
  {
    String keyURI = keyName.toUri();
    String extension = (String) keyTypeMap_.get(keyClass);
    if(extension == null) throw new SecurityException("Unrecognized key class");
    else return nameTransform(keyURI, extension).exists();
  }

  /**
   * Transform a key name to its hashed file path
   * @param keyName
   * @param extension
   * @return The hashed file path.
   * @throws SecurityException
   */
  private File
  nameTransform(String keyName, String extension) throws SecurityException
  {
    byte[] hash;
    try {
      hash = Common.digestSha256(keyName.getBytes("UTF-8"));
    } catch (UnsupportedEncodingException ex) {
      // We don't expect this to happen.
      throw new Error("UTF-8 encoder not supported: " + ex.getMessage());
    }
    String digest = Common.base64Encode(hash);
    digest = digest.replace('/', '%');

    return new File(keyStorePath_, digest + extension);
  }

  /**
   * Use nameTransform to get the file path for keyName (without the extension)
   * and also add to the mapping.txt file.
   * @param keyName The key name which is transformed to a file path.
   * @return The key file path without the extension.
   */
  private String
  maintainMapping(String keyName) throws SecurityException
  {
    String keyFilePathNoExtension = nameTransform(keyName, "").getAbsolutePath();

    File mappingFilePath = new File(keyStorePath_, "mapping.txt");

    try{
      BufferedWriter writer = new BufferedWriter
        (new FileWriter(mappingFilePath.getAbsolutePath(), true));
      try {
        writer.write(keyName + ' ' + keyFilePathNoExtension + '\n');
        writer.flush();
      }
      finally{
        writer.close();
      }
    }
    catch(IOException e){
      throw new SecurityException
        ("FilePrivateKeyStorage: Failed to write to mapping.txt: " + e.getMessage());
    }

    return keyFilePathNoExtension;
  }

  /**
   * Write to a key file. If keyClass is PRIVATE, then also update mapping.txt.
   * @param keyName
   * @param keyClass [PUBLIC, PRIVATE, SYMMETRIC]
   * @param data
   * @throws IOException
   * @throws SecurityException
   */
  private void
  write(Name keyName, KeyClass keyClass, byte[] data) throws SecurityException{
    String extension = (String) keyTypeMap_.get(keyClass);
    try{
      String filePath;
      if (keyClass == KeyClass.PRIVATE)
        filePath = maintainMapping(keyName.toUri()) + extension;
      else
        filePath = nameTransform(keyName.toUri(), extension).getAbsolutePath();

      BufferedWriter writer = new BufferedWriter(new FileWriter(filePath));
      try{
        String base64Data = Common.base64Encode(data, true);
        writer.write(base64Data, 0, base64Data.length());
        writer.flush();
      }
      finally{
        writer.close();
      }
    }
    catch(SecurityException e){
      throw new SecurityException
        ("FilePrivateKeyStorage: Failed to write key: " + e.getMessage());
    }
    catch(IOException e){
      throw new SecurityException
        ("FilePrivateKeyStorage: Failed to write key: " + e.getMessage());
    }
  }

  /**
   * Read from a key file
   * @param keyName
   * @param keyClass [PUBLIC, PRIVATE, SYMMETRIC]
   * @return The key bytes.
   * @throws IOException
   * @throws SecurityException
   */
  private byte[]
  read(Name keyName, KeyClass keyClass) throws SecurityException{
    String extension = (String) keyTypeMap_.get(keyClass);
    StringBuilder contents = new StringBuilder();
    try{
      BufferedReader reader = new BufferedReader
        (new FileReader(nameTransform(keyName.toUri(), extension).getAbsolutePath()));
      // Use "try/finally instead of "try-with-resources" or "using"
      // which are not supported before Java 7.
      try {
        String line = null;
        while ((line = reader.readLine()) != null)
          contents.append(line);
      } finally {
        reader.close();
      }
    }
    catch(SecurityException e) {
      throw new SecurityException
        ("FilePrivateKeyStorage: Failed to read key: " + e.getMessage());
    }
    catch(IOException e) {
      throw new SecurityException
        ("FilePrivateKeyStorage: Failed to read key: " + e.getMessage());
    }

    return Common.base64Decode(contents.toString());
  }

  static private String RSA_ENCRYPTION_OID = "1.2.840.113549.1.1.1";
  static private String EC_ENCRYPTION_OID = "1.2.840.10045.2.1";

  private final File keyStorePath_;
  // Use HashMap without generics so it works with older Java compilers.
  private static final HashMap keyTypeMap_;
  static{
    keyTypeMap_ = new HashMap();
    keyTypeMap_.put(KeyClass.PUBLIC, ".pub");
    keyTypeMap_.put(KeyClass.PRIVATE, ".pri");
    keyTypeMap_.put(KeyClass.SYMMETRIC, ".key");
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy