(RANGER-247)Development of Ranger Key Storage Provider Signed-off-by: Velmurugan Periasamy <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/incubator-ranger/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ranger/commit/bb0bdced Tree: http://git-wip-us.apache.org/repos/asf/incubator-ranger/tree/bb0bdced Diff: http://git-wip-us.apache.org/repos/asf/incubator-ranger/diff/bb0bdced Branch: refs/heads/master Commit: bb0bdcedeb134f5aadeef17be11f4df7e8150b71 Parents: ce139e0 Author: Gautam Borad <[email protected]> Authored: Tue Mar 24 12:12:19 2015 +0530 Committer: Velmurugan Periasamy <[email protected]> Committed: Tue Mar 24 13:26:30 2015 -0400 ---------------------------------------------------------------------- kms/config/kms-webapp/dbks-site.xml | 120 ++++ kms/config/kms-webapp/kms-site.xml | 9 +- kms/config/webserver/kms_webserver.properties | 20 +- kms/dev-support/findbugsExcludeFile.xml | 41 ++ kms/pom.xml | 413 +++++++++++++- kms/scripts/db/mysql/kms_core_db.sql | 30 + kms/scripts/db_setup.py | 252 +++++++++ kms/scripts/ranger-kms | 2 +- kms/scripts/ranger-kms-services.sh | 2 +- kms/scripts/setup.sh | 172 ++++++ .../crypto/key/EncryptedPrivateKeyInfo.java | 111 ++++ .../apache/hadoop/crypto/key/KeyProtector.java | 541 +++++++++++++++++++ .../apache/hadoop/crypto/key/RangerKMSDB.java | 85 +++ .../hadoop/crypto/key/RangerKeyStore.java | 537 ++++++++++++++++++ .../crypto/key/RangerKeyStoreProvider.java | 341 ++++++++++++ .../hadoop/crypto/key/RangerMasterKey.java | 210 +++++++ ...rKeyGeneratorKeyProviderCryptoExtension.java | 171 ++++++ .../hadoop/crypto/key/kms/server/KMS.java | 482 +++++++++++++++++ .../hadoop/crypto/key/kms/server/KMSACLs.java | 253 +++++++++ .../crypto/key/kms/server/KMSACLsType.java | 17 + .../hadoop/crypto/key/kms/server/KMSAudit.java | 230 ++++++++ .../key/kms/server/KMSAuthenticationFilter.java | 154 ++++++ .../crypto/key/kms/server/KMSConfiguration.java | 126 +++++ .../key/kms/server/KMSExceptionsProvider.java | 113 ++++ .../crypto/key/kms/server/KMSJMXServlet.java | 36 ++ .../crypto/key/kms/server/KMSJSONReader.java | 54 ++ .../crypto/key/kms/server/KMSJSONWriter.java | 70 +++ .../crypto/key/kms/server/KMSMDCFilter.java | 93 ++++ .../key/kms/server/KMSServerJSONUtils.java | 102 ++++ .../hadoop/crypto/key/kms/server/KMSWebApp.java | 307 +++++++++++ .../kms/server/KeyAuthorizationKeyProvider.java | 299 ++++++++++ .../java/org/apache/ranger/entity/XXDBBase.java | 216 ++++++++ .../apache/ranger/entity/XXRangerKeyStore.java | 121 +++++ .../apache/ranger/entity/XXRangerMasterKey.java | 67 +++ .../apache/ranger/kms/biz/RangerKMSStartUp.java | 35 ++ .../java/org/apache/ranger/kms/dao/BaseDao.java | 261 +++++++++ .../org/apache/ranger/kms/dao/DaoManager.java | 35 ++ .../apache/ranger/kms/dao/DaoManagerBase.java | 31 ++ .../org/apache/ranger/kms/dao/RangerKMSDao.java | 18 + .../ranger/kms/dao/RangerMasterKeyDao.java | 10 + .../META-INF/kms_jpa_named_queries.xml | 32 ++ kms/src/main/resources/META-INF/persistence.xml | 28 + ....apache.hadoop.crypto.key.KeyProviderFactory | 18 + kms/src/main/resources/WEB-INF/web.xml | 62 +++ .../main/resources/log4j-kmsaudit.properties | 25 + kms/src/main/resources/log4j.properties | 31 ++ .../main/resources/mini-kms-acls-default.xml | 135 +++++ kms/src/main/webapp/WEB-INF/web.xml | 62 +++ .../hadoop/crypto/key/kms/server/MiniKMS.java | 238 ++++++++ .../crypto/key/kms/server/TestKMSACLs.java | 52 ++ .../crypto/key/kms/server/TestKMSAudit.java | 135 +++++ .../server/TestKeyAuthorizationKeyProvider.java | 271 ++++++++++ pom.xml | 22 + src/main/assembly/kms.xml | 167 ++++-- 54 files changed, 7418 insertions(+), 47 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/config/kms-webapp/dbks-site.xml ---------------------------------------------------------------------- diff --git a/kms/config/kms-webapp/dbks-site.xml b/kms/config/kms-webapp/dbks-site.xml new file mode 100644 index 0000000..a428c6f --- /dev/null +++ b/kms/config/kms-webapp/dbks-site.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + + <!-- Encryption key Password --> + + <property> + <name>ranger.db.encrypt.key.password</name> + <value>Str0ngPassw0rd</value> + <description> + Password used for encrypting Master Key + </description> + </property> + + <!-- db Details --> + + <property> + <name>ranger.db.ks.database.flavor</name> + <value>MYSQL</value> + <description> + ULR for Database + </description> + </property> + + <property> + <name>ranger.db.ks.core.file</name> + <value>db/mysql/kms_core_db.sql</value> + <description> + ULR for Database + </description> + </property> + + <property> + <name>ranger.db.ks.javax.persistence.jdbc.url</name> + <value>jdbc:log4jdbc:mysql://localhost:3306/rangerkms</value> + <description> + ULR for Database + </description> + </property> + + <property> + <name>ranger.db.root.user</name> + <value>root</value> + <description> + Database root user name used for operation + </description> + </property> + + <property> + <name>ranger.db.root.password</name> + <value>root</value> + <description> + Database root user name used for operation + </description> + </property> + + <property> + <name>ranger.db.host</name> + <value>localhost</value> + <description> + Database root user name used for operation + </description> + </property> + + <property> + <name>ranger.db.ks.name</name> + <value>rangerkms</value> + <description> + Driver used for database + </description> + </property> + + <property> + <name>ranger.db.ks.javax.persistence.jdbc.user</name> + <value>kmsadmin</value> + <description> + Database user name used for operation + </description> + </property> + + <property> + <name>ranger.db.ks.javax.persistence.jdbc.password</name> + <value>kmsadmin</value> + <description> + Database user's password + </description> + </property> + + <property> + <name>ranger.db.ks.javax.persistence.jdbc.dialect</name> + <value>org.eclipse.persistence.platform.database.MySQLPlatform</value> + <description> + Dialect used for database + </description> + </property> + + <property> + <name>ranger.db.ks.javax.persistence.jdbc.driver</name> + <value>net.sf.log4jdbc.DriverSpy</value> + <description> + Driver used for database + </description> + </property> + + <property> + <name>ranger.db.ks.sql.connector.jar</name> + <value>/usr/share/java/mysql-connector-java.jar</value> + <description> + Driver used for database + </description> + </property> + + <property> + <name>ranger.db.ks.java.bin</name> + <value>java</value> + <description> + ULR for Database + </description> + </property> + +</configuration> http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/config/kms-webapp/kms-site.xml ---------------------------------------------------------------------- diff --git a/kms/config/kms-webapp/kms-site.xml b/kms/config/kms-webapp/kms-site.xml index a810ca4..06e7ec4 100644 --- a/kms/config/kms-webapp/kms-site.xml +++ b/kms/config/kms-webapp/kms-site.xml @@ -18,7 +18,7 @@ <property> <name>hadoop.kms.key.provider.uri</name> - <value>jceks://file@/${user.home}/kms.keystore</value> + <value>dbks://http@localhost:9292/kms</value> <description> URI of the backing KeyProvider for the KMS. </description> @@ -31,7 +31,7 @@ If using the JavaKeyStoreProvider, the password for the keystore file. </description> </property> - + <!-- KMS Cache --> <property> @@ -169,5 +169,10 @@ The Kerberos service principal used to connect to Zookeeper. </description> </property> + + <property> + <name>hadoop.kms.security.authorization.manager</name> + <value></value> + </property> </configuration> http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/config/webserver/kms_webserver.properties ---------------------------------------------------------------------- diff --git a/kms/config/webserver/kms_webserver.properties b/kms/config/webserver/kms_webserver.properties index e96b9fc..1d227cf 100644 --- a/kms/config/webserver/kms_webserver.properties +++ b/kms/config/webserver/kms_webserver.properties @@ -44,4 +44,22 @@ accesslog.pattern=%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" # Web Application root folder # xa.webapp.contextName=/kms -xa.webapp.dir=./webapp/root/hadoop-kms-2.6.0.war +xa.webapp.dir=./webapp + +# +# Values required for setup script +# +realScriptPath=`readlink -f $0` +realScriptDir=`dirname $realScriptPath` +RANGER_KMS_DIR=`(cd $realScriptDir/..; pwd)` +RANGER_KMS_EWS_DIR=${RANGER_KMS_DIR}/ews + +app_home=${RANGER_KMS_EWS_DIR}/webapp +SQL_CONNECTOR_JAR=/usr/share/java/mysql-connector-java.jar +PYTHON_COMMAND_INVOKER="python" +JAVA_VERSION_REQUIRED="1.7" +# +# Log file path +# +LOGFILE=${RANGER_KMS_DIR}/logfile +LOGFILES="$LOGFILE" \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/dev-support/findbugsExcludeFile.xml ---------------------------------------------------------------------- diff --git a/kms/dev-support/findbugsExcludeFile.xml b/kms/dev-support/findbugsExcludeFile.xml new file mode 100644 index 0000000..bc92ed7 --- /dev/null +++ b/kms/dev-support/findbugsExcludeFile.xml @@ -0,0 +1,41 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. +--> +<FindBugsFilter> + <!-- + Findbug is complaining about System.out being NULL + --> + <Match> + <Class name="org.apache.hadoop.crypto.key.kms.server.KMSWebApp"/> + <Bug pattern="NP_ALWAYS_NULL"/> + </Match> + <!-- + KMSWebApp is a webapp singleton managed by the servlet container via + ServletContextListener. + --> + <Match> + <Class name="org.apache.hadoop.crypto.key.kms.server.KMSWebApp"/> + <Bug pattern="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD"/> + </Match> + <!-- + KMSWebApp does an exit to kill the servlet container if the initialization + fails. + --> + <Match> + <Class name="org.apache.hadoop.crypto.key.kms.server.KMSWebApp"/> + <Bug pattern="DM_EXIT"/> + </Match> +</FindBugsFilter> http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/pom.xml ---------------------------------------------------------------------- diff --git a/kms/pom.xml b/kms/pom.xml index 4c0f73b..5ce8b11 100644 --- a/kms/pom.xml +++ b/kms/pom.xml @@ -26,14 +26,6 @@ <artifactId>ranger</artifactId> <version>0.5.0</version> </parent> - <build> - <resources> - <resource> - <directory>src/main/resources</directory> - <filtering>true</filtering> - </resource> - </resources> - </build> <dependencies> <dependency> <groupId>org.apache.hadoop</groupId> @@ -46,5 +38,410 @@ <artifactId>hadoop-common</artifactId> <version>${hadoop-common.version}</version> </dependency> + <dependency> + <groupId>org.eclipse.persistence</groupId> + <artifactId>eclipselink</artifactId> + <version>${eclipse.jpa.version}</version> + </dependency> + <dependency> + <groupId>org.eclipse.persistence</groupId> + <artifactId>javax.persistence</artifactId> + <version>${javax.persistence.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-aop</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-asm</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-beans</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-context</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-context-support</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-core</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-expression</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-jdbc</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-orm</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-tx</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + <version>${springframework.version}</version> + </dependency> + <dependency> + <groupId>com.sun.xml.security</groupId> + <artifactId>xml-security-impl</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>com.googlecode.log4jdbc</groupId> + <artifactId>log4jdbc</artifactId> + <version>${googlecode.log4jdbc.version}</version> + </dependency> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + <version>${mysql-connector-java.version}</version> + </dependency> + + <!-- Hadoop KMS dependency --> + <!-- <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-minikdc</artifactId> + <version>${hadoop.minikdc.version}</version> + <scope>test</scope> + </dependency> --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>${mockito.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-auth</artifactId> + <version>${hadoop-auth.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>${guava.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-core</artifactId> + <version>${jersey-server.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-server</artifactId> + <version>${jersey-server.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <version>${servlet.api.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.mortbay.jetty</groupId> + <artifactId>jetty</artifactId> + <version>${mortbay.jetty.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>${log4j.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>${slf4j-api.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <version>${slf4j-api.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jul-to-slf4j</artifactId> + <version>${slf4j-api.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.mortbay.jetty</groupId> + <artifactId>jetty-util</artifactId> + <version>${mortbay.jetty.version}</version> + </dependency> + <dependency> + <groupId>com.codahale.metrics</groupId> + <artifactId>metrics-core</artifactId> + <version>${metrics.core.version}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.curator</groupId> + <artifactId>curator-test</artifactId> + <version>${curator.test.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-bundle</artifactId> + <version>${sun-jersey-bundle.version}</version> + </dependency> + <dependency> + <groupId>asm</groupId> + <artifactId>asm-all</artifactId> + <version>${asm.all.version}</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>${httpcomponents.httpclient.version}</version> + </dependency> + + <!-- change for version variable --> + <dependency> + <groupId>javax.activation</groupId> + <artifactId>activation</artifactId> + <version>1.1</version> + </dependency> + <dependency> + <groupId>org.apache.directory.server</groupId> + <artifactId>apacheds-i18n</artifactId> + <version>2.0.0-M15</version> + </dependency> + <dependency> + <groupId>org.apache.directory.server</groupId> + <artifactId>apacheds-kerberos-codec</artifactId> + <version>2.0.0-M15</version> + </dependency> + <dependency> + <groupId>org.apache.directory.api</groupId> + <artifactId>api-asn1-api</artifactId> + <version>1.0.0-M20</version> + </dependency> + <dependency> + <groupId>org.apache.directory.api</groupId> + <artifactId>api-i18n</artifactId> + <version>1.0.0-M20</version> + </dependency> + <dependency> + <groupId>org.apache.directory.api</groupId> + <artifactId>api-util</artifactId> + <version>1.0.0-M20</version> + </dependency> + <dependency> + <groupId>org.apache.avro</groupId> + <artifactId>avro</artifactId> + <version>1.7.4</version> + </dependency> + <dependency> + <groupId>commons-beanutils</groupId> + <artifactId>commons-beanutils</artifactId> + <version>1.7.0</version> + </dependency> + <dependency> + <groupId>commons-beanutils</groupId> + <artifactId>commons-beanutils-core</artifactId> + <version>1.8.0</version> + </dependency> + <dependency> + <groupId>commons-cli</groupId> + <artifactId>commons-cli</artifactId> + <version>1.2</version> + </dependency> + <dependency> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + <version>1.4</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + <version>1.4.1</version> + </dependency> + <dependency> + <groupId>commons-configuration</groupId> + <artifactId>commons-configuration</artifactId> + <version>1.6</version> + </dependency> + <dependency> + <groupId>commons-digester</groupId> + <artifactId>commons-digester</artifactId> + <version>1.8</version> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.4</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-math3</artifactId> + <version>3.1.1</version> + </dependency> + <dependency> + <groupId>commons-net</groupId> + <artifactId>commons-net</artifactId> + <version>3.1</version> + </dependency> + <dependency> + <groupId>org.apache.curator</groupId> + <artifactId>curator-recipes</artifactId> + <version>2.6.0</version> + </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.2.4</version> + </dependency> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-annotations</artifactId> + <version>${hadoop-auth.version}</version> + </dependency> + <dependency> + <groupId>org.htrace</groupId> + <artifactId>htrace-core</artifactId> + <version>3.0.4</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpcore</artifactId> + <version>4.2.5</version> + </dependency> + <dependency> + <groupId>org.codehaus.jackson</groupId> + <artifactId>jackson-core-asl</artifactId> + <version>${jackson.version}</version> + </dependency> + <dependency> + <groupId>org.codehaus.jackson</groupId> + <artifactId>jackson-jaxrs</artifactId> + <version>${jackson.version}</version> + </dependency> + <dependency> + <groupId>org.codehaus.jackson</groupId> + <artifactId>jackson-mapper-asl</artifactId> + <version>${jackson.version}</version> + </dependency> + <dependency> + <groupId>org.codehaus.jackson</groupId> + <artifactId>jackson-xc</artifactId> + <version>${jackson.version}</version> + </dependency> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + <version>${jaxb-api.version}</version> + </dependency> + <dependency> + <groupId>com.sun.xml.bind</groupId> + <artifactId>jaxb-impl</artifactId> + <version>${jaxb-impl.version}</version> + </dependency> + <dependency> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-json</artifactId> + <version>${jersey-server.version}</version> + </dependency> + <dependency> + <groupId>org.codehaus.jettison</groupId> + <artifactId>jettison</artifactId> + <version>${jettison.version}</version> + </dependency> + <dependency> + <groupId>jline</groupId> + <artifactId>jline</artifactId> + <version>${jline.version}</version> + </dependency> + <dependency> + <groupId>com.jcraft</groupId> + <artifactId>jsch</artifactId> + <version>${jsch.version}</version> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>${jsr305.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>com.thoughtworks.paranamer</groupId> + <artifactId>paranamer</artifactId> + <version>${paranamer.version}</version> + </dependency> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>${protobuf-java.version}</version> + </dependency> + <dependency> + <groupId>org.xerial.snappy</groupId> + <artifactId>snappy-java</artifactId> + <version>${snappy-java.version}</version> + </dependency> + <dependency> + <groupId>xmlenc</groupId> + <artifactId>xmlenc</artifactId> + <version>${xmlenc.version}</version> + </dependency> + <dependency> + <groupId>org.tukaani</groupId> + <artifactId>xz</artifactId> + <version>${xz.version}</version> + </dependency> </dependencies> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <version>2.4</version> + </plugin> + </plugins> + </pluginManagement> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + </resources> + </build> </project> http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/scripts/db/mysql/kms_core_db.sql ---------------------------------------------------------------------- diff --git a/kms/scripts/db/mysql/kms_core_db.sql b/kms/scripts/db/mysql/kms_core_db.sql new file mode 100644 index 0000000..b95a75b --- /dev/null +++ b/kms/scripts/db/mysql/kms_core_db.sql @@ -0,0 +1,30 @@ +DROP TABLE IF EXISTS `ranger_masterkey`; +CREATE TABLE `ranger_masterkey` ( +`id` bigint( 20 ) NOT NULL AUTO_INCREMENT , +`create_time` datetime DEFAULT NULL , +`update_time` datetime DEFAULT NULL , +`added_by_id` bigint( 20 ) DEFAULT NULL , +`upd_by_id` bigint( 20 ) DEFAULT NULL , +`cipher` varchar( 255 ) DEFAULT NULL , +`bitlength` int DEFAULT NULL , +`masterkey` varchar(2048), +PRIMARY KEY ( `id` ) +)ENGINE=InnoDB DEFAULT CHARSET=latin1; + +DROP TABLE IF EXISTS `ranger_keystore`; +CREATE TABLE `ranger_keystore` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `create_time` datetime DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `added_by_id` bigint(20) DEFAULT NULL, + `upd_by_id` bigint(20) DEFAULT NULL, + `kms_alias` varchar(255) NOT NULL, + `kms_createdDate` bigint(20) DEFAULT NULL, + `kms_cipher` varchar(255) DEFAULT NULL, + `kms_bitLength` bigint(20) DEFAULT NULL, + `kms_description` varchar(512) DEFAULT NULL, + `kms_version` bigint(20) DEFAULT NULL, + `kms_attributes` varchar(1024) DEFAULT NULL, + `kms_encoded`varchar(2048), + PRIMARY KEY (`id`) +)ENGINE=InnoDB DEFAULT CHARSET=latin1; http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/scripts/db_setup.py ---------------------------------------------------------------------- diff --git a/kms/scripts/db_setup.py b/kms/scripts/db_setup.py new file mode 100644 index 0000000..301bdcc --- /dev/null +++ b/kms/scripts/db_setup.py @@ -0,0 +1,252 @@ +# +# 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. See accompanying LICENSE file. +# + +import os +import re +import sys +import errno +import shlex +import logging +import subprocess +import fileinput +from os.path import basename +from datetime import date +from xml.etree import ElementTree as ET +globalDict = {} + +def check_output(query): + p = subprocess.Popen(query, stdout=subprocess.PIPE) + output = p.communicate ()[0] + return output + +def log(msg,type): + if type == 'info': + logging.info(" %s",msg) + if type == 'debug': + logging.debug(" %s",msg) + if type == 'warning': + logging.warning(" %s",msg) + if type == 'exception': + logging.exception(" %s",msg) + +def populate_global_dict(): + global globalDict + os.chdir("..") + read_config_file = open(os.path.join(os.getcwd(),'ews/webapp/config/dbks-site.xml')) + dbks_site_properties = import_properties_from_xml(read_config_file, globalDict) + +class BaseDB(object): + + def init_logfiles(self): + FORMAT = '%(asctime)-15s %(message)s' + logging.basicConfig(format=FORMAT, level=logging.DEBUG) + + def create_rangerdb_user(self, root_user, db_user, db_password, db_root_password): + log("---------- Creating User ----------", "info") + + def check_table(self, db_name, root_user, db_root_password, TABLE_NAME): + log("---------- Verifying table ----------", "info") + def import_file_to_db(self, root_user, db_name, db_user, db_password, db_root_password, file_name): + log("---------- Importing db schema ----------", "info") + +class MysqlConf(BaseDB): + # Constructor + def __init__(self, host,SQL_CONNECTOR_JAR,JAVA_BIN): + self.host = host + self.SQL_CONNECTOR_JAR = SQL_CONNECTOR_JAR + self.JAVA_BIN = JAVA_BIN + BaseDB.init_logfiles(self) + + def get_jisql_cmd(self, user, password ,db_name): + #TODO: User array for forming command + jisql_cmd = "%s -cp %s:jisql/lib/* org.apache.util.sql.Jisql -driver mysqlconj -cstring jdbc:mysql://%s/%s -u %s -p %s -noheader -trim -c \;" %(self.JAVA_BIN,self.SQL_CONNECTOR_JAR,self.host,db_name,user,password) + return jisql_cmd + + + def create_rangerdb_user(self, root_user, db_user, db_password, db_root_password): + hosts_arr =["%", "localhost"] + for host in hosts_arr: + get_cmd = self.get_jisql_cmd(root_user, db_root_password ,'mysql') + query = get_cmd + " -query \"select user from mysql.user where user='%s' and host='%s';\"" %(db_user,host) + output = check_output(shlex.split(query)) + if output.strip(db_user + " |"): + log( "\nMYSQL User: " + db_user + " already exists!", "debug") + else: + log("User does not exists", "info") + if db_password == "": + log ("Creating MySQL user: "+ db_user +" with DB password blank\n", "info") + query = get_cmd + " -query \"create user '%s'@'%s';\"" %(db_user, host) + ret = subprocess.check_call(shlex.split(query)) + if ret == 0: + query = get_cmd + " -query \"select user from mysql.user where user='%s' and host='%s';\"" %(db_user,host) + output = check_output(shlex.split(query)) + if output.strip(db_user + " |"): + log("Mysql user " + db_user +" created","info") + else: + log("Creating Mysql user " + db_user +" Failed","info") + sys.exit(1) + else: + log ("Creating MySQL user: "+ db_user +" with DB password\n", "info") + query = get_cmd + " -query \"create user '%s'@'%s' identified by '%s';\"" %(db_user, host, db_password) + ret = subprocess.check_call(shlex.split(query)) + if ret == 0: + log("Mysql user " + db_user +" created","info") + else: + log("Creating Mysql user " + db_user +" Failed","info") + sys.exit(1) + + def verify_db(self, root_user, db_root_password, db_name): + log("\nVerifying Database: " + db_name + "\n", "debug") + get_cmd = self.get_jisql_cmd(root_user, db_root_password, 'mysql') + query = get_cmd + " -query \"show databases like '%s';\"" %(db_name) + output = check_output(shlex.split(query)) + if output.strip(db_name + " |"): + return True + else: + return False + + + def import_file_to_db(self, root_user, db_name, db_user, db_password, db_root_password, file_name): + log ("\nImporting db schema to Database: " + db_name,"debug"); + if self.verify_db(root_user, db_root_password, db_name): + log("\nDatabase: "+db_name + " already exists. Ignoring import_db\n","info") + else: + log("\nDatabase does not exist. Creating database : " + db_name,"info") + get_cmd = self.get_jisql_cmd(root_user, db_root_password, 'mysql') + query = get_cmd + " -query \"create database %s;\"" %(db_name) + ret = subprocess.check_call(shlex.split(query)) + if ret != 0: + log("\nDatabase creation failed!!\n","info") + sys.exit(1) + else: + if self.verify_db(root_user, db_root_password, db_name): + log("Creating database: " + db_name + " succeeded", "info") + self.import_db_file(db_name, root_user , db_user, db_password, db_root_password, file_name) + else: + log("\nDatabase creation failed!!\n","info") + sys.exit(1) + + + def grant_xa_db_user(self, root_user, db_name, db_user, db_password, db_root_password , is_revoke): + hosts_arr =["%", "localhost"] + if is_revoke: + for host in hosts_arr: + get_cmd = self.get_jisql_cmd(root_user, db_root_password, 'mysql') + query = get_cmd + " -query \"REVOKE ALL PRIVILEGES,GRANT OPTION FROM '%s'@'%s';\"" %(db_user, host) + ret = subprocess.check_call(shlex.split(query)) + if ret == 0: + query = get_cmd + " -query \"FLUSH PRIVILEGES;\"" + ret = subprocess.check_call(shlex.split(query)) + if ret != 0: + sys.exit(1) + else: + sys.exit(1) + + for host in hosts_arr: + log ("---------------GRANTING PRIVILEGES TO user '"+db_user+"'@'"+host+"' on db '"+db_name+"'-------------" , "info") + get_cmd = self.get_jisql_cmd(root_user, db_root_password, 'mysql') + query = get_cmd + " -query \"grant all privileges on %s.* to '%s'@'%s' with grant option;\"" %(db_name,db_user, host) + ret = subprocess.check_call(shlex.split(query)) + if ret == 0: + log ("---------------FLUSH PRIVILEGES -------------" , "info") + query = get_cmd + " -query \"FLUSH PRIVILEGES;\"" + ret = subprocess.check_call(shlex.split(query)) + if ret == 0: + log("Privileges granted to '" + db_user + "' on '"+db_name+"'\n", "info") + else: + log("Granting privileges to '" +db_user+"' FAILED on '"+db_name+"'\n", "info") + sys.exit(1) + else: + log("\nGranting privileges to '" +db_user+"' FAILED on '"+db_name+"'\n", "info") + sys.exit(1) + + def import_db_file(self, db_name, root_user, db_user, db_password, db_root_password, file_name): + name = basename(file_name) + if os.path.isfile(file_name): + log("Importing db schema to database : " + db_name + " from file: " + name,"info") + get_cmd = self.get_jisql_cmd(root_user, db_root_password, db_name) + query = get_cmd + " -input %s" %file_name + ret = subprocess.check_call(shlex.split(query)) + if ret == 0: + log(name + " DB schema imported successfully\n","info") + else: + log(name + " DB Schema import failed!\n","info") + sys.exit(1) + else: + log("\nDB Schema file " + name+ " not found\n","exception") + sys.exit(1) + + def check_table(self, db_name, root_user, db_root_password, TABLE_NAME): + if self.verify_db(root_user, db_root_password, db_name): + log("Verifying table " + TABLE_NAME +" in database " + db_name, "debug") + + get_cmd = self.get_jisql_cmd(root_user, db_root_password, db_name) + query = get_cmd + " -query \"show tables like '%s';\"" %(TABLE_NAME) + output = check_output(shlex.split(query)) + if output.strip(TABLE_NAME + " |"): + log("Table " + TABLE_NAME +" already exists in database " + db_name + "\n","info") + return True + else: + log("Table " + TABLE_NAME +" does not exist in database " + db_name + "\n","info") + return False + else: + log("Database " + db_name +" does not exist\n","info") + return False + +def import_properties_from_xml(xml_path, properties_from_xml=None): + xml = ET.parse(xml_path) + root = xml.getroot() + if properties_from_xml is None: + properties_from_xml = dict() + for child in root.findall('property'): + name = child.find("name").text.strip() + value = child.find("value").text.strip() if child.find("value").text is not None else "" + properties_from_xml[name] = value + return properties_from_xml + + +def main(): + populate_global_dict() + JAVA_BIN=globalDict['ranger.db.ks.java.bin'] + XA_DB_FLAVOR=globalDict['ranger.db.ks.database.flavor'] + XA_DB_FLAVOR.upper() + + xa_db_host = globalDict['ranger.db.host'] + + mysql_core_file = globalDict['ranger.db.ks.core.file'] + + db_name = globalDict['ranger.db.ks.name'] + db_user = globalDict['ranger.db.ks.javax.persistence.jdbc.user'] + db_password = globalDict['ranger.db.ks.javax.persistence.jdbc.password'] + xa_db_root_user = globalDict['ranger.db.root.user'] + xa_db_root_password = globalDict['ranger.db.root.password'] + + if XA_DB_FLAVOR == "MYSQL": + MYSQL_CONNECTOR_JAR=globalDict['ranger.db.ks.sql.connector.jar'] + xa_sqlObj = MysqlConf(xa_db_host,MYSQL_CONNECTOR_JAR,JAVA_BIN) + xa_db_core_file_script = os.path.join(os.getcwd(),'scripts') + xa_db_core_file = os.path.join(xa_db_core_file_script,mysql_core_file) + else: + log ("--------- NO SUCH FLAVOUR ---------", "info") + sys.exit(1) + + # Methods Begin + log("\n--------- Creating kms user --------- \n","info") + xa_sqlObj.create_rangerdb_user(xa_db_root_user, db_user, db_password, xa_db_root_password) + log("\n--------- Importing DB Core Database ---------\n","info") + xa_sqlObj.import_file_to_db(xa_db_root_user, db_name, db_user, db_password, xa_db_root_password, xa_db_core_file) + xa_sqlObj.grant_xa_db_user(xa_db_root_user, db_name, db_user, db_password, xa_db_root_password, True) + +main() + http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/scripts/ranger-kms ---------------------------------------------------------------------- diff --git a/kms/scripts/ranger-kms b/kms/scripts/ranger-kms index a7cc20e..f049dd2 100755 --- a/kms/scripts/ranger-kms +++ b/kms/scripts/ranger-kms @@ -39,7 +39,7 @@ stop) restart) echo "Stopping Apache Ranger Kms." ${BIN_PATH}/${MOD_NAME} stop - echo "Stopping Apache Ranger Kms." + echo "Starting Apache Ranger Kms." ${BIN_PATH}/${MOD_NAME} start ;; *) http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/scripts/ranger-kms-services.sh ---------------------------------------------------------------------- diff --git a/kms/scripts/ranger-kms-services.sh b/kms/scripts/ranger-kms-services.sh index 7c80cc8..8677c64 100755 --- a/kms/scripts/ranger-kms-services.sh +++ b/kms/scripts/ranger-kms-services.sh @@ -71,7 +71,7 @@ fi KMS_CONF_DIR=${RANGER_KMS_EWS_DIR}/webapp/config/ -JAVA_OPTS="${JAVA_OPTS} -Dcatalina.base=${RANGER_KMS_EWS_DIR} -Dkms.config.dir=${KMS_CONF_DIR} -Dkms.log.dir=${TOMCAT_LOG_DIR} -cp ${RANGER_KMS_EWS_CONF_DIR}:${RANGER_KMS_EWS_LIB_DIR}/*:${JAVA_HOME}/lib/* " +JAVA_OPTS="${JAVA_OPTS} -Dcatalina.base=${RANGER_KMS_EWS_DIR} -Dkms.config.dir=${KMS_CONF_DIR} -Dkms.log.dir=${TOMCAT_LOG_DIR} -cp ${RANGER_KMS_EWS_CONF_DIR}:${RANGER_KMS_EWS_LIB_DIR}/*:${RANGER_KMS_EWS_DIR}/webapp/lib/*:${JAVA_HOME}/lib/* " if [ "${action^^}" == "START" ]; then echo "+ java -D${PROC_NAME} ${JAVA_OPTS} ${START_CLASS_NAME} ${KMS_CONFIG_FILENAME} " http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/scripts/setup.sh ---------------------------------------------------------------------- diff --git a/kms/scripts/setup.sh b/kms/scripts/setup.sh new file mode 100644 index 0000000..fa88bba --- /dev/null +++ b/kms/scripts/setup.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# ------------------------------------------------------------------------------------- +# +# Ranger KMS Setup Script +# +# This script will install ranger kms webapplication under tomcat and also, initialize the database with ranger kms users/tables. + +realScriptPath=`readlink -f $0` +realScriptDir=`dirname $realScriptPath` +RANGER_KMS_DIR=`(cd $realScriptDir/..; pwd)` +RANGER_KMS_EWS_DIR=${RANGER_KMS_DIR}/ews +RANGER_KMS_EWS_CONF_DIR="${RANGER_KMS_EWS_DIR}/conf" +RANGER_KMS_EWS_LIB_DIR="${RANGER_KMS_EWS_DIR}/lib" + +PROPFILE=${RANGER_KMS_EWS_CONF_DIR}/kms_webserver.properties +propertyValue='' + +. $PROPFILE 1>/dev/null 2>&1 +if [ ! $? = "0" ];then + log "$PROPFILE file not found....!!"; + exit 1; +fi + +usage() { + [ "$*" ] && echo "$0: $*" + sed -n '/^##/,/^$/s/^## \{0,1\}//p' "$0" + exit 2 +} 2>/dev/null + +log() { + local prefix="[$(date +%Y/%m/%d\ %H:%M:%S)]: " + echo "${prefix} $@" >> $LOGFILE + echo "${prefix} $@" +} + +check_ret_status(){ + if [ $1 -ne 0 ]; then + log "[E] $2"; + exit 1; + fi +} + +is_command () { + log "[I] check if command $1 exists" + type "$1" >/dev/null +} + +get_distro(){ + log "[I] Checking distribution name.." + ver=$(cat /etc/*{issues,release,version} 2> /dev/null) + if [[ $(echo $ver | grep DISTRIB_ID) ]]; then + DIST_NAME=$(lsb_release -si) + else + DIST_NAME=$(echo $ver | cut -d ' ' -f 1 | sort -u | head -1) + fi + export $DIST_NAME + log "[I] Found distribution : $DIST_NAME" + +} + +init_logfiles () { + for f in $LOGFILES; do + touch $f + done +} + +init_variables(){ + curDt=`date '+%Y%m%d%H%M%S'` + + INSTALL_DIR=${RANGER_KMS_DIR} + + DB_FLAVOR=`echo $DB_FLAVOR | tr '[:lower:]' '[:upper:]'` + if [ "${DB_FLAVOR}" == "" ] + then + DB_FLAVOR="MYSQL" + fi + log "[I] DB_FLAVOR=${DB_FLAVOR}" +} + +check_python_command() { + if is_command ${PYTHON_COMMAND_INVOKER} ; then + log "[I] '${PYTHON_COMMAND_INVOKER}' command found" + else + log "[E] '${PYTHON_COMMAND_INVOKER}' command not found" + exit 1; + fi +} + +check_java_version() { + #Check for JAVA_HOME + if [ "${JAVA_HOME}" == "" ] + then + log "[E] JAVA_HOME environment property not defined, aborting installation." + exit 1 + fi + + export JAVA_BIN=${JAVA_HOME}/bin/java + + if is_command ${JAVA_BIN} ; then + log "[I] '${JAVA_BIN}' command found" + else + log "[E] '${JAVA_BIN}' command not found" + exit 1; + fi + + $JAVA_BIN -version 2>&1 | grep -q $JAVA_VERSION_REQUIRED + if [ $? != 0 ] ; then + log "[E] Java 1.7 is required" + exit 1; + fi +} + +sanity_check_files() { + + if test -d $app_home; then + log "[I] $app_home folder found" + else + log "[E] $app_home does not exists" ; exit 1; + fi + if [ "${DB_FLAVOR}" == "MYSQL" ] + then + if test -f $mysql_core_file; then + log "[I] $mysql_core_file file found" + else + log "[E] $mysql_core_file does not exists" ; exit 1; + fi + fi +} + +copy_db_connector(){ + log "[I] Copying ${DB_FLAVOR} Connector to $app_home/lib "; + cp -f $SQL_CONNECTOR_JAR $app_home/lib + check_ret_status $? "Copying ${DB_FLAVOR} Connector to $app_home/lib failed" + log "[I] Copying ${DB_FLAVOR} Connector to $app_home/lib DONE"; +} + +setup_kms(){ + #copying ranger kms provider + cd ${RANGER_KMS_EWS_DIR}/webapp + log "[I] Adding ranger kms provider as services in hadoop-common jar" + jar -uf lib/hadoop-common*.jar META-INF/services/org.apache.hadoop.crypto.key.KeyProviderFactory +} + + +init_logfiles +log " --------- Running ranger kms Web Application Install Script --------- " +log "[I] uname=`uname`" +log "[I] hostname=`hostname`" +init_variables +get_distro +check_java_version +sanity_check_files +copy_db_connector +check_python_command +$PYTHON_COMMAND_INVOKER db_setup.py +setup_kms + +echo "Installation of ranger kms is completed." http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/src/main/java/org/apache/hadoop/crypto/key/EncryptedPrivateKeyInfo.java ---------------------------------------------------------------------- diff --git a/kms/src/main/java/org/apache/hadoop/crypto/key/EncryptedPrivateKeyInfo.java b/kms/src/main/java/org/apache/hadoop/crypto/key/EncryptedPrivateKeyInfo.java new file mode 100644 index 0000000..ec8f67d --- /dev/null +++ b/kms/src/main/java/org/apache/hadoop/crypto/key/EncryptedPrivateKeyInfo.java @@ -0,0 +1,111 @@ +package org.apache.hadoop.crypto.key; + +import java.io.*; + +import sun.security.x509.AlgorithmId; +import sun.security.util.*; + +/** + * This class implements the <code>EncryptedPrivateKeyInfo</code> type, + * which is defined in PKCS #8 as follows: + * + * <pre> + * EncryptedPrivateKeyInfo ::= SEQUENCE { + * encryptionAlgorithm AlgorithmIdentifier, + * encryptedData OCTET STRING } + * </pre> + * + * @author Jan Luehe + */ +final class EncryptedPrivateKeyInfo { + + // the "encryptionAlgorithm" field + private AlgorithmId algid; + + // the "encryptedData" field + private byte[] encryptedData; + + // the ASN.1 encoded contents of this class + private byte[] encoded; + + /** + * Constructs (i.e., parses) an <code>EncryptedPrivateKeyInfo</code> from + * its encoding. + */ + @SuppressWarnings("restriction") + EncryptedPrivateKeyInfo(byte[] encoded) throws IOException { + DerValue val = new DerValue(encoded); + + DerValue[] seq = new DerValue[2]; + + seq[0] = val.data.getDerValue(); + seq[1] = val.data.getDerValue(); + + if (val.data.available() != 0) { + throw new IOException("overrun, bytes = " + val.data.available()); + } + + this.algid = AlgorithmId.parse(seq[0]); + if (seq[0].data.available() != 0) { + throw new IOException("encryptionAlgorithm field overrun"); + } + + this.encryptedData = seq[1].getOctetString(); + if (seq[1].data.available() != 0) + throw new IOException("encryptedData field overrun"); + + this.encoded = (byte[])encoded.clone(); + } + + /** + * Constructs an <code>EncryptedPrivateKeyInfo</code> from the + * encryption algorithm and the encrypted data. + */ + @SuppressWarnings("restriction") + EncryptedPrivateKeyInfo(AlgorithmId algid, byte[] encryptedData) { + this.algid = algid; + this.encryptedData = (byte[])encryptedData.clone(); + this.encoded = null; // lazy generation of encoding + } + + /** + * Returns the encryption algorithm. + */ + @SuppressWarnings("restriction") + AlgorithmId getAlgorithm() { + return this.algid; + } + + /** + * Returns the encrypted data. + */ + byte[] getEncryptedData() { + return (byte[])this.encryptedData.clone(); + } + + /** + * Returns the ASN.1 encoding of this class. + */ + @SuppressWarnings("restriction") + byte[] getEncoded() + throws IOException + { + if (this.encoded != null) return (byte[])this.encoded.clone(); + + + DerOutputStream out = new DerOutputStream(); + DerOutputStream tmp = new DerOutputStream(); + + // encode encryption algorithm + algid.encode(tmp); + + // encode encrypted data + tmp.putOctetString(encryptedData); + + // wrap everything into a SEQUENCE + out.write(DerValue.tag_Sequence, tmp); + this.encoded = out.toByteArray(); + + return (byte[])this.encoded.clone(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/src/main/java/org/apache/hadoop/crypto/key/KeyProtector.java ---------------------------------------------------------------------- diff --git a/kms/src/main/java/org/apache/hadoop/crypto/key/KeyProtector.java b/kms/src/main/java/org/apache/hadoop/crypto/key/KeyProtector.java new file mode 100644 index 0000000..5c43301 --- /dev/null +++ b/kms/src/main/java/org/apache/hadoop/crypto/key/KeyProtector.java @@ -0,0 +1,541 @@ +package org.apache.hadoop.crypto.key; + +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.security.KeyRep; +import java.security.Security; +import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.AlgorithmParameters; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.SecretKey; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SealedObject; +import javax.crypto.spec.*; + +import com.sun.crypto.provider.PBEWithMD5AndTripleDESCipher; + +import sun.security.x509.AlgorithmId; +import sun.security.util.DerValue; +import sun.security.util.ObjectIdentifier; + +/** + * This class implements a protection mechanism for private keys. In JCE, we + * use a stronger protection mechanism than in the JDK, because we can use + * the <code>Cipher</code> class. + * Private keys are protected using the JCE mechanism, and are recovered using + * either the JDK or JCE mechanism, depending on how the key has been + * protected. This allows us to parse Sun's keystore implementation that ships + * with JDK 1.2. + * + * @author Jan Luehe + * + * + * @see JceKeyStore + */ + +final class KeyProtector { + + // defined by SunSoft (SKI project) + private static final String PBE_WITH_MD5_AND_DES3_CBC_OID + = "1.3.6.1.4.1.42.2.19.1"; + + // JavaSoft proprietary key-protection algorithm (used to protect private + // keys in the keystore implementation that comes with JDK 1.2) + private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1"; + + private static final int SALT_LEN = 20; // the salt length + private static final int DIGEST_LEN = 20; + + // the password used for protecting/recovering keys passed through this + // key protector + private char[] password; + + private static final Provider PROV = Security.getProvider("SunJCE"); + + KeyProtector(char[] password) { + if (password == null) { + throw new IllegalArgumentException("password can't be null"); + } + this.password = password; + } + + /** + * Protects the given cleartext private key, using the password provided at + * construction time. + */ + byte[] protect(PrivateKey key) + throws Exception + { + // create a random salt (8 bytes) + byte[] salt = new byte[8]; + new SecureRandom().nextBytes(salt); + + // create PBE parameters from salt and iteration count + PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20); + + // create PBE key from password + PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); + SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); + pbeKeySpec.clearPassword(); + + // encrypt private key + Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES"); + cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null); + byte[] plain = (byte[])key.getEncoded(); + byte[] encrKey = cipher.doFinal(plain, 0, plain.length); + + // wrap encrypted private key in EncryptedPrivateKeyInfo + // (as defined in PKCS#8) + AlgorithmParameters pbeParams = AlgorithmParameters.getInstance("PBE", PROV); + pbeParams.init(pbeSpec); + + AlgorithmId encrAlg = new AlgorithmId(new ObjectIdentifier(PBE_WITH_MD5_AND_DES3_CBC_OID), pbeParams); + return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded(); + } + + /* + * Recovers the cleartext version of the given key (in protected format), + * using the password provided at construction time. + */ + Key recover(EncryptedPrivateKeyInfo encrInfo) + throws UnrecoverableKeyException, NoSuchAlgorithmException + { + byte[] plain; + + try { + String encrAlg = encrInfo.getAlgorithm().getOID().toString(); + if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID) + && !encrAlg.equals(KEY_PROTECTOR_OID)) { + throw new UnrecoverableKeyException("Unsupported encryption " + + "algorithm"); + } + + if (encrAlg.equals(KEY_PROTECTOR_OID)) { + // JDK 1.2 style recovery + plain = recover(encrInfo.getEncryptedData()); + } else { + byte[] encodedParams = + encrInfo.getAlgorithm().getEncodedParams(); + + // parse the PBE parameters into the corresponding spec + AlgorithmParameters pbeParams = + AlgorithmParameters.getInstance("PBE"); + pbeParams.init(encodedParams); + PBEParameterSpec pbeSpec = (PBEParameterSpec) + pbeParams.getParameterSpec(PBEParameterSpec.class); + + // create PBE key from password + PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); + SecretKey sKey = + new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); + pbeKeySpec.clearPassword(); + + // decrypt private key + Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES"); + cipher.init(Cipher.DECRYPT_MODE, sKey, pbeSpec, null); + plain= cipher.doFinal(encrInfo.getEncryptedData(), 0, encrInfo.getEncryptedData().length); + } + + // determine the private-key algorithm, and parse private key + // using the appropriate key factory + String oidName = new AlgorithmId(new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName(); + KeyFactory kFac = KeyFactory.getInstance(oidName); + return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain)); + + } catch (NoSuchAlgorithmException ex) { + // Note: this catch needed to be here because of the + // later catch of GeneralSecurityException + throw ex; + } catch (IOException ioe) { + throw new UnrecoverableKeyException(ioe.getMessage()); + } catch (GeneralSecurityException gse) { + throw new UnrecoverableKeyException(gse.getMessage()); + } + } + + /* + * Recovers the cleartext version of the given key (in protected format), + * using the password provided at construction time. This method implements + * the recovery algorithm used by Sun's keystore implementation in + * JDK 1.2. + */ + private byte[] recover(byte[] protectedKey) + throws UnrecoverableKeyException, NoSuchAlgorithmException + { + int i, j; + byte[] digest; + int numRounds; + int xorOffset; // offset in xorKey where next digest will be stored + int encrKeyLen; // the length of the encrpyted key + + MessageDigest md = MessageDigest.getInstance("SHA"); + + // Get the salt associated with this key (the first SALT_LEN bytes of + // <code>protectedKey</code>) + byte[] salt = new byte[SALT_LEN]; + System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN); + + // Determine the number of digest rounds + encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN; + numRounds = encrKeyLen / DIGEST_LEN; + if ((encrKeyLen % DIGEST_LEN) != 0) + numRounds++; + + // Get the encrypted key portion and store it in "encrKey" + byte[] encrKey = new byte[encrKeyLen]; + System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen); + + // Set up the byte array which will be XORed with "encrKey" + byte[] xorKey = new byte[encrKey.length]; + + // Convert password to byte array, so that it can be digested + byte[] passwdBytes = new byte[password.length * 2]; + for (i=0, j=0; i<password.length; i++) { + passwdBytes[j++] = (byte)(password[i] >> 8); + passwdBytes[j++] = (byte)password[i]; + } + + // Compute the digests, and store them in "xorKey" + for (i = 0, xorOffset = 0, digest = salt; + i < numRounds; + i++, xorOffset += DIGEST_LEN) { + md.update(passwdBytes); + md.update(digest); + digest = md.digest(); + md.reset(); + // Copy the digest into "xorKey" + if (i < numRounds - 1) { + System.arraycopy(digest, 0, xorKey, xorOffset, + digest.length); + } else { + System.arraycopy(digest, 0, xorKey, xorOffset, + xorKey.length - xorOffset); + } + } + + // XOR "encrKey" with "xorKey", and store the result in "plainKey" + byte[] plainKey = new byte[encrKey.length]; + for (i = 0; i < plainKey.length; i++) { + plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]); + } + + // Check the integrity of the recovered key by concatenating it with + // the password, digesting the concatenation, and comparing the + // result of the digest operation with the digest provided at the end + // of <code>protectedKey</code>. If the two digest values are + // different, throw an exception. + md.update(passwdBytes); + java.util.Arrays.fill(passwdBytes, (byte)0x00); + passwdBytes = null; + md.update(plainKey); + digest = md.digest(); + md.reset(); + for (i = 0; i < digest.length; i++) { + if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) { + throw new UnrecoverableKeyException("Cannot recover key"); + } + } + return plainKey; + } + + /** + * Seals the given cleartext key, using the password provided at + * construction time + */ + SealedObject seal(Key key) + throws Exception + { + // create a random salt (8 bytes) + byte[] salt = new byte[8]; + new SecureRandom().nextBytes(salt); + + // create PBE parameters from salt and iteration count + PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, 20); + + // create PBE key from password + PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); + SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); + pbeKeySpec.clearPassword(); + + // seal key + Cipher cipher; + + PBEWithMD5AndTripleDESCipher cipherSpi; + cipherSpi = new PBEWithMD5AndTripleDESCipher(); + cipher = new CipherForKeyProtector(cipherSpi, PROV, + "PBEWithMD5AndTripleDES"); + cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec); + return new SealedObjectForKeyProtector(key, cipher); + } + + /** + * Unseals the sealed key. + */ + Key unseal(SealedObject so) + throws NoSuchAlgorithmException, UnrecoverableKeyException + { + try { + // create PBE key from password + PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password); + SecretKey skey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES"); + pbeKeySpec.clearPassword(); + + SealedObjectForKeyProtector soForKeyProtector = null; + if (!(so instanceof SealedObjectForKeyProtector)) { + soForKeyProtector = new SealedObjectForKeyProtector(so); + } else { + soForKeyProtector = (SealedObjectForKeyProtector)so; + } + AlgorithmParameters params = soForKeyProtector.getParameters(); + if (params == null) { + throw new UnrecoverableKeyException("Cannot get " + + "algorithm parameters"); + } + PBEWithMD5AndTripleDESCipher cipherSpi; + cipherSpi = new PBEWithMD5AndTripleDESCipher(); + Cipher cipher = new CipherForKeyProtector(cipherSpi, PROV, + "PBEWithMD5AndTripleDES"); + cipher.init(Cipher.DECRYPT_MODE, skey, params); + return (Key)soForKeyProtector.getObject(cipher); + } catch (NoSuchAlgorithmException ex) { + // Note: this catch needed to be here because of the + // later catch of GeneralSecurityException + throw ex; + } catch (IOException ioe) { + throw new UnrecoverableKeyException(ioe.getMessage()); + } catch (ClassNotFoundException cnfe) { + throw new UnrecoverableKeyException(cnfe.getMessage()); + } catch (GeneralSecurityException gse) { + throw new UnrecoverableKeyException(gse.getMessage()); + } + } +} + + +final class CipherForKeyProtector extends javax.crypto.Cipher { + /** + * Creates a Cipher object. + * + * @param cipherSpi the delegate + * @param provider the provider + * @param transformation the transformation + */ + protected CipherForKeyProtector(CipherSpi cipherSpi, + Provider provider, + String transformation) { + super(cipherSpi, provider, transformation); + } +} + +final class SealedObjectForKeyProtector extends javax.crypto.SealedObject { + + static final long serialVersionUID = -3650226485480866989L; + + SealedObjectForKeyProtector(Serializable object, Cipher c) + throws IOException, IllegalBlockSizeException { + super(object, c); + } + + SealedObjectForKeyProtector(SealedObject so) { + super(so); + } + + AlgorithmParameters getParameters() { + AlgorithmParameters params = null; + if (super.encodedParams != null) { + try { + params = AlgorithmParameters.getInstance("PBE", "SunJCE"); + params.init(super.encodedParams); + } catch (NoSuchProviderException nspe) { + // eat. + } catch (NoSuchAlgorithmException nsae) { + //eat. + } catch (IOException ioe) { + //eat. + } + } + return params; + } +} + +final class PrivateKeyInfo { + + // the version number defined by the PKCS #8 standard + private static final BigInteger VERSION = BigInteger.ZERO; + + // the private-key algorithm + private AlgorithmId algid; + + // the private-key value + private byte[] privkey; + + /** + * Constructs a PKCS#8 PrivateKeyInfo from its ASN.1 encoding. + */ + PrivateKeyInfo(byte[] encoded) throws IOException { + DerValue val = new DerValue(encoded); + + if (val.tag != DerValue.tag_Sequence) + throw new IOException("private key parse error: not a sequence"); + + // version + BigInteger parsedVersion = val.data.getBigInteger(); + if (!parsedVersion.equals(VERSION)) { + throw new IOException("version mismatch: (supported: " + + VERSION + ", parsed: " + parsedVersion); + } + + // privateKeyAlgorithm + this.algid = AlgorithmId.parse(val.data.getDerValue()); + + // privateKey + this.privkey = val.data.getOctetString(); + + // OPTIONAL attributes not supported yet + } + + /** + * Returns the private-key algorithm. + */ + AlgorithmId getAlgorithm() { + return this.algid; + } +} + +/** + * This class represents a PBE key. + * + * @author Jan Luehe + * + */ +final class PBEKey implements SecretKey { + + static final long serialVersionUID = -2234768909660948176L; + + private byte[] key; + + private String type; + + /** + * Creates a PBE key from a given PBE key specification. + * + * @param key the given PBE key specification + */ + PBEKey(PBEKeySpec keySpec, String keytype) throws InvalidKeySpecException { + char[] passwd = keySpec.getPassword(); + if (passwd == null) { + // Should allow an empty password. + passwd = new char[0]; + } + for (int i=0; i<passwd.length; i++) { + if ((passwd[i] < '\u0020') || (passwd[i] > '\u007E')) { + throw new InvalidKeySpecException("Password is not ASCII"); + } + } + this.key = new byte[passwd.length]; + for (int i=0; i<passwd.length; i++) + this.key[i] = (byte) (passwd[i] & 0x7f); + java.util.Arrays.fill(passwd, ' '); + type = keytype; + } + + public byte[] getEncoded() { + return (byte[])this.key.clone(); + } + + public String getAlgorithm() { + return type; + } + + public String getFormat() { + return "RAW"; + } + + /** + * Calculates a hash code value for the object. + * Objects that are equal will also have the same hashcode. + */ + public int hashCode() { + int retval = 0; + for (int i = 1; i < this.key.length; i++) { + retval += this.key[i] * i; + } + return(retval ^= getAlgorithm().toLowerCase().hashCode()); + } + + public boolean equals(Object obj) { + if (obj == this) + return true; + + if (!(obj instanceof SecretKey)) + return false; + + SecretKey that = (SecretKey)obj; + + if (!(that.getAlgorithm().equalsIgnoreCase(type))) + return false; + + byte[] thatEncoded = that.getEncoded(); + boolean ret = java.util.Arrays.equals(this.key, thatEncoded); + java.util.Arrays.fill(thatEncoded, (byte)0x00); + return ret; + } + + /** + * readObject is called to restore the state of this key from + * a stream. + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException + { + s.defaultReadObject(); + key = (byte[])key.clone(); + } + + + /** + * Replace the PBE key to be serialized. + * + * @return the standard KeyRep object to be serialized + * + * @throws java.io.ObjectStreamException if a new object representing + * this PBE key could not be created + */ + private Object writeReplace() throws java.io.ObjectStreamException { + return new KeyRep(KeyRep.Type.SECRET, + getAlgorithm(), + getFormat(), + getEncoded()); + } + + /** + * Ensures that the password bytes of this key are + * set to zero when there are no more references to it. + */ + protected void finalize() throws Throwable { + try { + if (this.key != null) { + java.util.Arrays.fill(this.key, (byte)0x00); + this.key = null; + } + } finally { + super.finalize(); + } + } +} + + http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/src/main/java/org/apache/hadoop/crypto/key/RangerKMSDB.java ---------------------------------------------------------------------- diff --git a/kms/src/main/java/org/apache/hadoop/crypto/key/RangerKMSDB.java b/kms/src/main/java/org/apache/hadoop/crypto/key/RangerKMSDB.java new file mode 100644 index 0000000..649a321 --- /dev/null +++ b/kms/src/main/java/org/apache/hadoop/crypto/key/RangerKMSDB.java @@ -0,0 +1,85 @@ +package org.apache.hadoop.crypto.key; + +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.apache.hadoop.conf.Configuration; +import org.apache.log4j.Logger; +import org.apache.ranger.kms.dao.DaoManager; + +public class RangerKMSDB { + + static final Logger logger = Logger.getLogger(RangerKMSDB.class); + + private EntityManagerFactory entityManagerFactory; + private DaoManager daoManager; + + private static Map<String, String> DB_PROPERTIES = null; + + private static final String PROPERTY_PREFIX = "ranger.db.ks."; + private static final String DB_DIALECT = "javax.persistence.jdbc.dialect"; + private static final String DB_DRIVER = "javax.persistence.jdbc.driver"; + private static final String DB_URL = "javax.persistence.jdbc.url"; + private static final String DB_USER = "javax.persistence.jdbc.user"; + private static final String DB_PASSWORD = "javax.persistence.jdbc.password"; + + private final Configuration conf; + + public RangerKMSDB(){ + conf = new Configuration(); + } + + public RangerKMSDB(Configuration conf){ + this.conf = conf; + initDBConnectivity(); + } + + public DaoManager getDaoManager(){ + return daoManager; + } + + private void initDBConnectivity(){ + try { + DB_PROPERTIES = new HashMap<String, String>(); + DB_PROPERTIES.put(DB_DIALECT, conf.get(PROPERTY_PREFIX+DB_DIALECT)); + DB_PROPERTIES.put(DB_DRIVER, conf.get(PROPERTY_PREFIX+DB_DRIVER)); + DB_PROPERTIES.put(DB_URL, conf.get(PROPERTY_PREFIX+DB_URL)); + DB_PROPERTIES.put(DB_USER, conf.get(PROPERTY_PREFIX+DB_USER)); + DB_PROPERTIES.put(DB_PASSWORD, conf.get(PROPERTY_PREFIX+DB_PASSWORD)); + + entityManagerFactory = Persistence.createEntityManagerFactory("persistence_ranger_server", DB_PROPERTIES); + + daoManager = new DaoManager(); + daoManager.setEntityManagerFactory(entityManagerFactory); + + daoManager.getEntityManager(); // this forces the connection to be made to DB + logger.info("Connected to DB : "+isDbConnected()); + } catch(Exception excp) { + excp.printStackTrace(); + } + } + + private boolean isDbConnected() { + EntityManager em = getEntityManager(); + + return em != null && em.isOpen(); + } + + private EntityManager getEntityManager() { + DaoManager daoMgr = daoManager; + + if(daoMgr != null) { + try { + return daoMgr.getEntityManager(); + } catch(Exception excp) { + excp.printStackTrace(); + } + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/bb0bdced/kms/src/main/java/org/apache/hadoop/crypto/key/RangerKeyStore.java ---------------------------------------------------------------------- diff --git a/kms/src/main/java/org/apache/hadoop/crypto/key/RangerKeyStore.java b/kms/src/main/java/org/apache/hadoop/crypto/key/RangerKeyStore.java new file mode 100644 index 0000000..9b1ff67 --- /dev/null +++ b/kms/src/main/java/org/apache/hadoop/crypto/key/RangerKeyStore.java @@ -0,0 +1,537 @@ +package org.apache.hadoop.crypto.key; + +import java.io.*; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.*; + +import javax.crypto.SealedObject; +import javax.xml.bind.DatatypeConverter; + +import org.apache.log4j.Logger; +import org.apache.ranger.entity.XXRangerKeyStore; +import org.apache.ranger.kms.dao.DaoManager; +import org.apache.ranger.kms.dao.RangerKMSDao; + +/** + * This class provides the Database store implementation. + * + * + * @see KeyProtector + * + */ + +public class RangerKeyStore extends KeyStoreSpi { + + static final Logger logger = Logger.getLogger(RangerKeyStore.class); + + private DaoManager daoManager; + + // keys + private static class KeyEntry { + Date date; // the creation date of this entry + }; + + // Secret key + private static final class SecretKeyEntry { + Date date; // the creation date of this entry + SealedObject sealedKey; + String cipher_field; + int bit_length; + String description; + String attributes; + int version; + } + + /** + * keys are stored in a hashtable. + * Hash entries are keyed by alias names. + */ + private final Hashtable<String, Object> entries; + + RangerKeyStore() { + entries = new Hashtable<String, Object>(); + } + + RangerKeyStore(DaoManager daoManager) { + entries = new Hashtable<String, Object>(); + this.daoManager = daoManager; + } + + // convert an alias to internal form, overridden in subclasses: + String convertAlias(String alias){ + return alias.toLowerCase(); + } + + /** + * Returns the key associated with the given alias, using the given + * password to recover it. + * + * @param alias the alias name + * @param password the password for recovering the key + * + * @return the requested key, or null if the given alias does not exist + * or does not identify a <i>key entry</i>. + * + * @exception NoSuchAlgorithmException if the algorithm for recovering the + * key cannot be found + * @exception UnrecoverableKeyException if the key cannot be recovered + * (e.g., the given password is wrong). + */ + public Key engineGetKey(String alias, char[] password) + throws NoSuchAlgorithmException, UnrecoverableKeyException + { + Key key = null; + + Object entry = entries.get(alias.toLowerCase()); + + if (!(entry instanceof SecretKeyEntry)) { + return null; + } + + KeyProtector keyProtector = new KeyProtector(password); + key = keyProtector.unseal(((SecretKeyEntry)entry).sealedKey); + return key; + } + + /** + * Returns the creation date of the entry identified by the given alias. + * + * @param alias the alias name + * + * @return the creation date of this entry, or null if the given alias does + * not exist + */ + public Date engineGetCreationDate(String alias) { + Object entry = entries.get(convertAlias(alias)); + if (entry != null) { + return new Date(((KeyEntry)entry).date.getTime()); + } else { + return null; + } + } + + /** + * Assigns the given key to the given alias, protecting + * it with the given password as defined in PKCS8. + * + * <p>The given java.security.PrivateKey <code>key</code> must + * be accompanied by a certificate chain certifying the + * corresponding public key. + * + * <p>If the given alias already exists, the keystore information + * associated with it is overridden by the given key and certificate + * chain. + * + * @param alias the alias name + * @param key the key to be associated with the alias + * @param password the password to protect the key + * @param cipher the cipher used for the key + * @param bitLength bit length for the key + * @param description Description for the key + * @param version Key version + * @param attributes key attributes + * + * @exception KeyStoreException if the given key is not a private key, + * cannot be protected, or this operation fails for some other reason + */ + public void engineSetKeyEntry(String alias, Key key, char[] password, String cipher, int bitLength, String description, int version, String attributes) + throws KeyStoreException + { + synchronized(entries) { + try { + KeyProtector keyProtector = new KeyProtector(password); + + SecretKeyEntry entry = new SecretKeyEntry(); + entry.date = new Date(); + // seal and store the key + entry.sealedKey = keyProtector.seal(key); + entry.cipher_field = cipher; + entry.bit_length = bitLength; + entry.description = description; + entry.version = version; + entry.attributes = attributes; + entries.put(alias.toLowerCase(), entry); + } catch (Exception e) { + throw new KeyStoreException(e.getMessage()); + } + } + } + + /** + * Deletes the entry identified by the given alias from this database. + * + * @param alias the alias name + * + * @exception KeyStoreException if the entry cannot be removed. + */ + public void engineDeleteEntry(String alias) + throws KeyStoreException + { + synchronized(entries) { + dbOperationDelete(convertAlias(alias)); + entries.remove(convertAlias(alias)); + } + } + + private void dbOperationDelete(String alias) { + try{ + if(daoManager != null){ + RangerKMSDao rangerKMSDao = new RangerKMSDao(daoManager); + rangerKMSDao.deleteByAlias(alias); + } + }catch(Exception e){ + e.printStackTrace(); + } + } + + /** + * Lists all the alias names of this database. + * + * @return enumeration of the alias names + */ + public Enumeration<String> engineAliases() { + return entries.keys(); + } + + /** + * Checks if the given alias exists in this database. + * + * @param alias the alias name + * + * @return true if the alias exists, false otherwise + */ + public boolean engineContainsAlias(String alias) { + return entries.containsKey(convertAlias(alias)); + } + + /** + * Retrieves the number of entries in this database. + * + * @return the number of entries in this database + */ + public int engineSize() { + return entries.size(); + } + + /** + * Stores this keystore to the provided ranger database, and protects its + * integrity with the given password. + * + * @param stream null. + * @param password the password to generate the keystore integrity check + * + * @exception IOException if there was an I/O problem with data + * @exception NoSuchAlgorithmException if the appropriate data integrity + * algorithm could not be found + * @exception CertificateException if any of the certificates included in + * the keystore data could not be stored + */ + public void engineStore(OutputStream stream, char[] password) + throws IOException, NoSuchAlgorithmException, CertificateException + { + synchronized(entries) { + // password is mandatory when storing + if (password == null) { + throw new IllegalArgumentException("Ranger Master Key can't be null"); + } + + MessageDigest md = getPreKeyedHash(password); + + byte digest[] = md.digest(); + for (Enumeration<String> e = entries.keys(); e.hasMoreElements();) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(new DigestOutputStream(baos, md)); + + ObjectOutputStream oos = null; + try{ + + String alias = e.nextElement(); + Object entry = entries.get(alias); + + // write the sealed key + oos = new ObjectOutputStream(dos); + oos.writeObject(((SecretKeyEntry)entry).sealedKey); + /* + * Write the keyed hash which is used to detect tampering with + * the keystore (such as deleting or modifying key or + * certificate entries). + */ + dos.write(digest); + dos.flush(); + Long creationDate = ((SecretKeyEntry)entry).date.getTime(); + SecretKeyEntry secretKey = (SecretKeyEntry)entry; + XXRangerKeyStore xxRangerKeyStore = mapObjectToEntity(alias,creationDate,baos.toByteArray(), secretKey.cipher_field, secretKey.bit_length, secretKey.description, secretKey.version, secretKey.attributes); + dbOperationStore(xxRangerKeyStore); + }finally { + if (oos != null) { + oos.close(); + } else { + dos.close(); + } + } + } + } + } + + private XXRangerKeyStore mapObjectToEntity(String alias, Long creationDate, + byte[] byteArray, String cipher_field, int bit_length, + String description, int version, String attributes) { + XXRangerKeyStore xxRangerKeyStore = new XXRangerKeyStore(); + xxRangerKeyStore.setAlias(alias); + xxRangerKeyStore.setCreatedDate(creationDate); + xxRangerKeyStore.setEncoded(DatatypeConverter.printBase64Binary(byteArray)); + xxRangerKeyStore.setCipher(cipher_field); + xxRangerKeyStore.setBitLength(bit_length); + xxRangerKeyStore.setDescription(description); + xxRangerKeyStore.setVersion(version); + xxRangerKeyStore.setAttributes(attributes); + return xxRangerKeyStore; + } + + private void dbOperationStore(XXRangerKeyStore rangerKeyStore) { + try{ + if(daoManager != null){ + RangerKMSDao rangerKMSDao = new RangerKMSDao(daoManager); + XXRangerKeyStore xxRangerKeyStore = rangerKMSDao.findByAlias(rangerKeyStore.getAlias()); + boolean keyStoreExists = true; + if (xxRangerKeyStore == null) { + xxRangerKeyStore = new XXRangerKeyStore(); + keyStoreExists = false; + } + + xxRangerKeyStore = mapToEntityBean(rangerKeyStore, xxRangerKeyStore, 0); + if (keyStoreExists) { + xxRangerKeyStore = rangerKMSDao.update(xxRangerKeyStore); + } else { + xxRangerKeyStore = rangerKMSDao.create(xxRangerKeyStore); + } + } + }catch(Exception e){ + e.printStackTrace(); + } + } + + private XXRangerKeyStore mapToEntityBean(XXRangerKeyStore rangerKMSKeyStore, XXRangerKeyStore xxRangerKeyStore,int i) { + xxRangerKeyStore.setAlias(rangerKMSKeyStore.getAlias()); + xxRangerKeyStore.setCreatedDate(rangerKMSKeyStore.getCreatedDate()); + xxRangerKeyStore.setEncoded(rangerKMSKeyStore.getEncoded()); + xxRangerKeyStore.setCipher(rangerKMSKeyStore.getCipher()); + xxRangerKeyStore.setBitLength(rangerKMSKeyStore.getBitLength()); + xxRangerKeyStore.setDescription(rangerKMSKeyStore.getDescription()); + xxRangerKeyStore.setVersion(rangerKMSKeyStore.getVersion()); + xxRangerKeyStore.setAttributes(rangerKMSKeyStore.getAttributes()); + return xxRangerKeyStore; + } + + /** + * Loads the keystore from the given ranger database. + * + * <p>If a password is given, it is used to check the integrity of the + * keystore data. Otherwise, the integrity of the keystore is not checked. + * + * @param stream the input stream from which the keystore is loaded + * @param password the (optional) password used to check the integrity of + * the keystore. + * + * @exception IOException if there is an I/O or format problem with the + * keystore data + * @exception NoSuchAlgorithmException if the algorithm used to check + * the integrity of the keystore cannot be found + * @exception CertificateException if any of the certificates in the + * keystore could not be loaded + */ + public void engineLoad(InputStream stream, char[] password) + throws IOException , NoSuchAlgorithmException, CertificateException + { + synchronized(entries) { + List<XXRangerKeyStore> rangerKeyDetails = dbOperationLoad(); + DataInputStream dis; + MessageDigest md = null; + + if(rangerKeyDetails == null || rangerKeyDetails.size() < 1){ + return; + } + + entries.clear(); + if (password != null) { + md = getPreKeyedHash(password); + } + byte computed[]; + computed = md.digest(); + for(XXRangerKeyStore rangerKey : rangerKeyDetails){ + String encoded = rangerKey.getEncoded(); + byte[] data = DatatypeConverter.parseBase64Binary(encoded); + + if(data != null && data.length > 0){ + stream = new ByteArrayInputStream(data); + }else{ + logger.error("No Key found for alias "+rangerKey.getAlias()); + } + + /* + * If a password has been provided, we check the keyed digest + * at the end. If this check fails, the store has been tampered + * with + */ + if (password != null) { + int counter = 0; + for (int i = computed.length-1; i >= 0; i--) { + if (computed[i] != data[data.length-(1+counter)]) { + Throwable t = new UnrecoverableKeyException + ("Password verification failed"); + throw (IOException)new IOException + ("Keystore was tampered with, or " + + "password was incorrect").initCause(t); + }else{ + counter++; + } + } + } + + if (password != null) { + dis = new DataInputStream(new DigestInputStream(stream, md)); + } else { + dis = new DataInputStream(stream); + } + + ObjectInputStream ois = null; + try{ + String alias; + + SecretKeyEntry entry = new SecretKeyEntry(); + + //read the alias + alias = rangerKey.getAlias(); + + //read the (entry creation) date + entry.date = new Date(rangerKey.getCreatedDate()); + entry.cipher_field = rangerKey.getCipher(); + entry.bit_length = rangerKey.getBitLength(); + entry.description = rangerKey.getDescription(); + entry.version = rangerKey.getVersion(); + entry.attributes = rangerKey.getAttributes(); + + //read the sealed key + try { + ois = new ObjectInputStream(dis); + entry.sealedKey = (SealedObject)ois.readObject(); + } catch (ClassNotFoundException cnfe) { + throw new IOException(cnfe.getMessage()); + } + + //Add the entry to the list + entries.put(alias, entry); + }finally { + if (ois != null) { + ois.close(); + } else { + dis.close(); + } + } + } + } + } + + private List<XXRangerKeyStore> dbOperationLoad() throws IOException { + try{ + if(daoManager != null){ + RangerKMSDao rangerKMSDao = new RangerKMSDao(daoManager); + return rangerKMSDao.getAll(); + } + }catch(Exception e){ + e.printStackTrace(); + } + return null; + } + + /** + * To guard against tampering with the keystore, we append a keyed + * hash with a bit of whitener. + */ + private MessageDigest getPreKeyedHash(char[] password) + throws NoSuchAlgorithmException, UnsupportedEncodingException + { + int i, j; + + MessageDigest md = MessageDigest.getInstance("SHA"); + byte[] passwdBytes = new byte[password.length * 2]; + for (i=0, j=0; i<password.length; i++) { + passwdBytes[j++] = (byte)(password[i] >> 8); + passwdBytes[j++] = (byte)password[i]; + } + md.update(passwdBytes); + for (i=0; i<passwdBytes.length; i++) + passwdBytes[i] = 0; + md.update("Mighty Aphrodite".getBytes("UTF8")); + return md; + } + + @Override + public void engineSetKeyEntry(String arg0, byte[] arg1, Certificate[] arg2) + throws KeyStoreException { + + } + + @Override + public Certificate engineGetCertificate(String alias) { + return null; + } + + @Override + public String engineGetCertificateAlias(Certificate cert) { + return null; + } + + @Override + public Certificate[] engineGetCertificateChain(String alias) { + return null; + } + + @Override + public boolean engineIsCertificateEntry(String alias) { + return false; + } + + @Override + public boolean engineIsKeyEntry(String alias) { + return false; + } + + @Override + public void engineSetCertificateEntry(String alias, Certificate cert) + throws KeyStoreException { + } + + @Override + public void engineSetKeyEntry(String alias, Key key, char[] password, + Certificate[] chain) throws KeyStoreException { + } + + public Map<String, String> getPropertiesWithPrefix(Properties props, String prefix) { + Map<String, String> prefixedProperties = new HashMap<String, String>(); + + if(props != null && prefix != null) { + for(String key : props.stringPropertyNames()) { + if(key == null) { + continue; + } + + String val = props.getProperty(key); + + if(key.startsWith(prefix)) { + key = key.substring(prefix.length()); + + if(key == null) { + continue; + } + + prefixedProperties.put(key, val); + } + } + } + + return prefixedProperties; + } +} \ No newline at end of file
