mirror of
https://github.com/NationalSecurityAgency/PACE.git
synced 2026-11-07 06:05:59 -10:00
Initial commit of the PACE library.
This commit is contained in:
commit
ccf94c8203
266 files changed
+24194
No files matched your search
+27
@@ -0,0 +1,27 @@
|
||||
# Copyright 2016 MIT Lincoln Laboratory
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Maven ignores
|
||||
/target/
|
||||
|
||||
# IDE ignores
|
||||
/.settings/
|
||||
/.project
|
||||
/.classpath
|
||||
/.pydevproject
|
||||
/.idea
|
||||
/*.iml
|
||||
/nbproject/
|
||||
/nbactions.xml
|
||||
/nb-configuration.xml
|
||||
@@ -0,0 +1,20 @@
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
PACE depends on artifacts which can be found in Maven Central.
|
||||
|
||||
Each module has its own dependencies. Please refer to the individual
|
||||
modules' pom.xml files for a comprehensive listing.
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
Confidentiality
|
||||
---------------
|
||||
|
||||
The encryption code allows clients to encrypt data before storing it at the
|
||||
server. This prevents that data from being read by the server administrator.
|
||||
Additionally, it can be used to cryptographically enforce visibility labels.
|
||||
|
||||
The encryption code is designed as a drop-in replacement for Accumulo's
|
||||
BatchWriter and BatchScanner classes. It can be configured programmatically
|
||||
or through the use of configuration files (preferred). Cryptographic keys
|
||||
are provided by the developer (see [key management][key-management]).
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Classes for encryption functionality are found in `edu.mit.ll.pace.encryption`.
|
||||
|
||||
#### `EncryptedBatchWriter`
|
||||
|
||||
The encrypted batch writer is created similarly to a `BatchWriter`. After
|
||||
creation, it is used identically to the Accumulo `BatchWriter`.
|
||||
|
||||
```java
|
||||
BatchWriter writer = new EncryptedBatchWriter(
|
||||
instance.getConnector(),
|
||||
TABLE_NAME,
|
||||
batchWriterConfig,
|
||||
encryptionConfig,
|
||||
encryptionKeys);
|
||||
```
|
||||
|
||||
#### `EncryptedBatchScanner`
|
||||
|
||||
The encrypted batch scanner is created similarly to a `BatchScanner`. After
|
||||
creation, it is used identically to the Accumulo `BatchScanner`.
|
||||
|
||||
```java
|
||||
BatchScanner scanner = new EncryptedBatchScanner(
|
||||
instance.getConnector(),
|
||||
TABLE_NAME,
|
||||
authorizations,
|
||||
numThreads,
|
||||
encryptionConfig,
|
||||
encryptionKeys);
|
||||
```
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The encryption configuration defines which fields will be encrypted, and with
|
||||
which modes of operation. The encryption configuration of each field can be set
|
||||
separately from the other fields. This configuration is given by an
|
||||
`EncryptionConfig` object. These configuration objects can be created
|
||||
programmatically or loaded from a configuration file.
|
||||
|
||||
The configuration file is split into sections, one for each field that will
|
||||
store encrypted data. The layout of these sections is as follows:
|
||||
|
||||
```ini
|
||||
[{row, columnFamily, columnQualifier, value}]
|
||||
cipher = {AES_CTR, AES_CFB, AES_CBC, AES_OFB, AES_GCM, AES_SIV_DETERMINISTIC}
|
||||
provider = {SunJCE,BC,...}
|
||||
useVisibility = {true,false}
|
||||
keyId = {keyId}
|
||||
keyLength = {16, 24, 32, ...}
|
||||
sources = {row, columnFamily, columnQualifier, columnVisibility, value}
|
||||
```
|
||||
|
||||
##### Section Header
|
||||
The section header defines which field will store the encrypted data.
|
||||
|
||||
##### cipher
|
||||
Dictates the algorithm, mode, and padding to use for encryption. The various
|
||||
modes fall into the following
|
||||
three categories:
|
||||
|
||||
1. Semantically secure encryption—Encrypted data reveals no partial information
|
||||
about the plaintext data (other than length). In particular, encryption does not
|
||||
reveal equalities of plaintexts; for a given key, encrypting the same value
|
||||
repeatedly will produce different ciphertexts. This prevents a server
|
||||
administrator from detecting patterns in the data.
|
||||
2. Authenticated encryption—In addition to providing confidentiality with
|
||||
semantic security, authenticated encryption provides integrity and authenticity,
|
||||
meaning that any unauthorized changes to the data will be detected.
|
||||
3. Deterministic encryption—For a given key, encrypting the same value
|
||||
repeatedly will always produce the same ciphertext. This allows for encrypted
|
||||
data to be searched for server-side but also allows the server administrator to
|
||||
detect patterns in the data (see https://cs.brown.edu/~seny/pubs/edb.pdf).
|
||||
**For this reason, care should be taken when using this type of encryption**,
|
||||
and it should only be used when the performance gains are more important than
|
||||
the loss of security.
|
||||
|
||||
The following ciphers are available for use in encryption.
|
||||
|
||||
* **AES_CTR**—Semantically secure—Encrypts data using AES in CTR mode with no
|
||||
padding.
|
||||
* **AES_CFB**—Semantically secure—Encrypts data using AES in CFB mode with no
|
||||
padding.
|
||||
* **AES_CBC**—Semantically secure—Encrypts data using AES in CBC mode with PKCS5
|
||||
padding.
|
||||
* **AES_OFB**—Semantically secure—Encrypts data using AES in OFB mode with no
|
||||
padding.
|
||||
* **AES_GCM**—Authenticated—Encrypts data using AES in GCM mode with no padding.
|
||||
* **AES_SIV_DETERMINISTIC**—Deterministic—Encrypts data using AES in SIV mode
|
||||
with no padding.
|
||||
|
||||
If unsure, we recommend the use of AES_GCM. It is very important to note that
|
||||
the GCM encryption key must be refreshed to limit the number of GCM encryptions
|
||||
with a given key in order to prevent IV reuse; similarly for CTR mode. The NIST
|
||||
recommendation (SP 800-38 D) is to limit the number of invocations of encryption
|
||||
with GCM for any given key to 2^32.
|
||||
|
||||
##### provider (Optional)
|
||||
The Java cryptographic provider to use. If not set, will let the system decide
|
||||
the appropriate cryptographic provider.
|
||||
|
||||
##### useVisibility (Optional, default=false)
|
||||
Whether to use encryption to enforce the column visibility field. If this is set
|
||||
to true, only users that have encryption keys corresponding to the appropriate
|
||||
labels in the column visibility field will be able to decrypt the data. For
|
||||
example, if the visibility is `doctor | (nurse & admin)`, a user with only the
|
||||
doctor attribute key will be able to decrypt the row, but a user with only the
|
||||
nurse key (and neither the doctor key nor the admin key) will not be able to
|
||||
decrypt.
|
||||
|
||||
When writing data, there is currently a limitation that requires the client to
|
||||
have keys for all labels in the visibility field. In the above example, the
|
||||
user would need to have the doctor, nurse, **and** admin attribute keys to
|
||||
encrypt the row. We are looking to address this limitation in future versions of
|
||||
the code base.
|
||||
|
||||
##### keyId (Optional, default=cipher)
|
||||
An identifier for the keys used to encrypt the field. This value is passed to
|
||||
the key management interface to specify which key to retrieve. If not set, will
|
||||
use the value given in the cipher field.
|
||||
|
||||
##### keyLength (Optional, default=16|32)
|
||||
The length of the key to use for encryption, in bytes. This is one of {16, 24,
|
||||
32} for non-deterministic encryption ciphers, and {32, 48, 64} for deterministic
|
||||
encryption ciphers. If not set, will use the smallest appropriate key length.
|
||||
|
||||
##### sources (Optional, default=section header)
|
||||
The source of the data that should be encrypted into the field. If not set, the
|
||||
encrypted data will be drawn from the same field as the encrypted.
|
||||
|
||||
Example configurations
|
||||
----------------------
|
||||
|
||||
Within the core library's jar resources there are three example configurations
|
||||
(`edu/mit/ll/place/encryption`).
|
||||
These demonstrate some configurations that should support a large number of use
|
||||
cases.
|
||||
|
||||
* **encrypt-value**—Encrypts the value using AES in GCM mode, with the
|
||||
encryption cryptographically enforcing the column visibility.
|
||||
* **encrypt-entry**—Encrypts each field in the entry (row, columnFamily,
|
||||
columnQualifier, value) individually using AES in GCM mode, with the encryption
|
||||
cryptographically enforcing the column visibility.
|
||||
* **searchable**—Encrypts each field in the entry key (row, columnFamily,
|
||||
columnQualifier) individually using AES in SIV mode (deterministic encryption),
|
||||
with the encryption not being used to enforce column visibility. The value is
|
||||
encrypted using AES in GCM mode, with the encryption cryptographically enforcing
|
||||
the column visibility. Since the key is encrypted deterministically, the system
|
||||
can still search for records server-side.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
1. If any field in the key is encrypted with non-deterministic encryption,
|
||||
Accumulo versioning on the table will be broken.
|
||||
1. If any field in the key is encrypted with non-deterministic encryption, it is
|
||||
not possible for the `EncryptedBatchWriter` to delete values. Instead it is
|
||||
necessary to delete keys using an `ItemProcessingIterator`:
|
||||
```java
|
||||
EncryptedBatchScanner encryptedBatchScanner = ...;
|
||||
BatchWriter writer = ...;
|
||||
ItemProcessingIterator<Entry<Key,Value>> iterator = encryptedBatchScanner.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
if (isKeyToDelete(iterator.next())) {
|
||||
Entry<Key,Value> unprocessed = iterator.unprocessed();
|
||||
Mutation delete = new Mutation(unprocessed.getKey().getRow());
|
||||
delete.putDelete(unprocessed.getKey().getColumnFamily(), unprocessed.getKey().getColumnQualifier(), unprocessed.getKey().getColumnVisibilityParsed(),
|
||||
unprocessed.getKey().getTimestamp());
|
||||
}
|
||||
}
|
||||
writer.close();
|
||||
```
|
||||
1. If the entry key is encrypted deterministically and one of the encryption
|
||||
keys is revoked—resulting in a new version of the encryption key—then entries
|
||||
created with the new key will not version correctly with entries created with
|
||||
the old key. Both sets of entries will show up in the scanner.
|
||||
1. When attempting to filter results returned by `EncryptedBatchScanner`, if
|
||||
possible, the filtering will happen server side. In several situations it is
|
||||
necessary to still filter some of the data client side:
|
||||
1. If any of the search fields (e.g., row, column family) are encrypted
|
||||
non-deterministially, than those field must be filtered client side.
|
||||
2. If a contents of a search field are encrypted into another field (e.g.,
|
||||
row encrypts both the row and column family, leaving the column family
|
||||
zeroed out), then the field can only be filtered server side if the
|
||||
encrypted field containing it is also part of the search (e.g., searching
|
||||
using a range will work, but not searching using `fetchColumnFamily`).
|
||||
3. For a deterministically encrypted field, the search must be a single
|
||||
value and not a range of values. Similarly, fields preceding the encrypted
|
||||
field must be a single value and not a range of values. Fields after the
|
||||
encrypted field can be searched over a range. (e.g., if the column family is
|
||||
encrypted, you can only create ranges that have the same row and column
|
||||
family, but the column qualifier can be multiple values.)
|
||||
|
||||
[key-management]: KEY_MANAGEMENT.md
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
Examples
|
||||
--------
|
||||
In the `examples/simple` module we have included several examples that show
|
||||
how to use the PACE library for encryption and signatures:
|
||||
|
||||
* Encryption
|
||||
* GenerateEncryptionKeys—Demonstrates how encryption keys can be managed from
|
||||
the command line.
|
||||
* EncryptedReadWriteExample—Modified
|
||||
`org.apache.accumulo.examples.simple.client.ReadWriteExample`; demonstrates
|
||||
how to write, then read, encrypted data.
|
||||
* EncryptedConverterExample—Converts an existing unencrypted table into an
|
||||
encrypted table.
|
||||
* Signature
|
||||
* GenerateSignatureKeys—Demonstrates how signature keys can be managed from
|
||||
the command line.
|
||||
* SignedReadWriteExample—Modified
|
||||
`org.apache.accumulo.examples.simple.client.ReadWriteExample`; demonstrates
|
||||
how to write, then, read, signed data.
|
||||
* SignedConverterExample—Converts an existing unsigned table into a signed
|
||||
table.
|
||||
|
||||
We describe below how to use these samples to generate keys, convert an existing
|
||||
table to one that is signed/encrypted, and read data from that table.
|
||||
|
||||
To prepare to run the following examples, first set up the environment as
|
||||
follows:
|
||||
|
||||
```bash
|
||||
cd examples/simple
|
||||
mvn clean compile
|
||||
mkdir -p target/keys
|
||||
```
|
||||
|
||||
Encryption
|
||||
----------
|
||||
|
||||
### Key generation
|
||||
|
||||
First we will generate three encryption keys and put them into a key store for
|
||||
later use:
|
||||
|
||||
1. An encryption key for AES_GCM.
|
||||
2. An encryption key for AES_GCM when the visibility label is `secret`.
|
||||
3. An encryption key for AES_GCM when the visibility label is `default`.
|
||||
|
||||
```
|
||||
mvn exec:java -Dexec.mainClass=edu.mit.ll.pace.examples.simple.GenerateEncryptionKeys -Dexec.args="--master-key-file target/keys/master.keys --write-master-key --key AES_GCM --key AES_GCM|secret --key AES_GCM|default --store target/keys/enc.keys"
|
||||
```
|
||||
|
||||
The master secret file stores a randomly generated master secret that is used
|
||||
in the generation of all encryption keys. The master secret file is needed in
|
||||
order to generate more keys later. For example, the following command creates a
|
||||
second encryption key store that has keys 1 and 3 from the first key store, but
|
||||
not key 2.
|
||||
|
||||
```
|
||||
mvn exec:java -Dexec.mainClass=edu.mit.ll.pace.examples.simple.GenerateEncryptionKeys -Dexec.args="--master-key-file target/keys/master.keys --read-master-key --key AES_GCM --key AES_GCM|default --store target/keys/enc3.keys"
|
||||
```
|
||||
|
||||
### Converting Data
|
||||
First we will use an example from Accumulo to create data in a table:
|
||||
|
||||
```bash
|
||||
$ACCUMULO_HOME/bin/accumulo org.apache.accumulo.examples.simple.client.ReadWriteExample -i instance -z zookeepers -u user -p password --table test --createtable --create --auths secret,default
|
||||
```
|
||||
|
||||
Now we will convert that data into encrypted form:
|
||||
```
|
||||
mvn exec:java -Dexec.mainClass=edu.mit.ll.pace.examples.simple.EncryptedConverterExample -Dexec.args="-i instance -z zookeepers -u user -p password --source test --destination test2 --createtable --encryption-config ../../core/src/main/resources/edu/mit/ll/pace/encryption/encrypt-value.ini --encryption-keys target/keys/enc.keys --auths secret,default"
|
||||
```
|
||||
|
||||
In the Accumulo shell you can view the encrypted data:
|
||||
```bash
|
||||
table test2
|
||||
scan
|
||||
```
|
||||
|
||||
### Reading Data
|
||||
Next we will read the data in its unencrypted form:
|
||||
|
||||
```
|
||||
mvn exec:java -Dexec.mainClass=edu.mit.ll.pace.examples.simple.EncryptedReadWriteExample -Dexec.args="-i instance -z zookeepers -u user -p password --table test2 --read --encryption-config ../../core/src/main/resources/edu/mit/ll/pace/encryption/encrypt-value.ini --encryption-keys target/keys/enc.keys --auths secret,default"
|
||||
```
|
||||
|
||||
|
||||
Signatures
|
||||
----------
|
||||
|
||||
### Key generation
|
||||
|
||||
First we will generate an RSA signature and verification keys for two users,
|
||||
Alice and Bob.
|
||||
|
||||
The following command will create a key store for Alice (containing Alice's
|
||||
generated private and public RSA key):
|
||||
|
||||
```
|
||||
mvn exec:java -Dexec.mainClass=edu.mit.ll.pace.examples.simple.GenerateSignatureKeys -Dexec.args="--key Alice|RSA|2048 --key-dir target/keys --public-key-file target/keys/public.keys"
|
||||
```
|
||||
|
||||
Later Bob's key store is created (containing Bob's generated public and private
|
||||
keys, and Alice's public key). Alice's key store is also updated to included
|
||||
Bob's public key:
|
||||
```
|
||||
mvn exec:java -Dexec.mainClass=edu.mit.ll.pace.examples.simple.GenerateSignatureKeys -Dexec.args="--key Bob|RSA|2048 --key-dir target/keys --public-key-file target/keys/public.keys --update"
|
||||
```
|
||||
|
||||
|
||||
### Converting Data
|
||||
First we will use an example from Accumulo to create data in a table:
|
||||
|
||||
```bash
|
||||
$ACCUMULO_HOME/bin/accumulo org.apache.accumulo.examples.simple.client.ReadWriteExample -i instance -z zookeepers -u user -p password --table test3 --createtable --create --auths secret,default
|
||||
```
|
||||
|
||||
Now we will convert that data into signed form:
|
||||
```
|
||||
mvn exec:java -Dexec.mainClass=edu.mit.ll.pace.examples.simple.SignatureConverterExample -Dexec.args="-i instance -z zookeepers -u user -p password --source test3 --destination test4 --createtable --signature-config ../../core/src/main/resources/edu/mit/ll/pace/signature/rsa.ini --signature-keys target/keys/Alice_RSA.keys --auths secret,default"
|
||||
```
|
||||
|
||||
In the Accumulo shell you can check that the data is signed:
|
||||
```bash
|
||||
table test4
|
||||
scan
|
||||
```
|
||||
|
||||
### Reading Data
|
||||
Next we will read the data, verifying its signature. We use Bob's key store to
|
||||
demonstrate that different users can verify signatures, as long as they have the
|
||||
appropriate public key.
|
||||
|
||||
```
|
||||
mvn exec:java -Dexec.mainClass=edu.mit.ll.pace.examples.simple.SignedReadWriteExample -Dexec.args="-i instance -z zookeepers -u user -p password --table test4 --read --signature-config ../../core/src/main/resources/edu/mit/ll/pace/signature/rsa.ini --signature-keys target/keys/Bob_RSA.keys --auths secret,default"
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
Key Management
|
||||
--------------
|
||||
In PACE there are two types of keys, symmetric encryption keys (e.g., AES) and
|
||||
asymmetric signature keys (e.g., RSA). While PACE defines the structure of these
|
||||
keys (e.g., key length) it is left to the developers to generate, distribute,
|
||||
and manage the keys.
|
||||
|
||||
While we plan to provide an example key management server
|
||||
and client in the future, these are not currently implemented. In the meantime,
|
||||
the [examples][examples], which provide basic key management operations, can be
|
||||
used as templates to define your own key management framework. These
|
||||
examples make use of locally stored key containers, which can be used
|
||||
by importing the following artifact:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>edu.mit.ll.pace</groupId>
|
||||
<artifactId>pace-keymanagement-common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
[examples]: EXAMPLES.md
|
||||
@@ -0,0 +1,212 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
|
||||
**********
|
||||
|
||||
PACE SUBCOMPONENTS:
|
||||
|
||||
The PACE project contains subcomponents with separate copyright
|
||||
notices and license terms. Your use of the source code for the these
|
||||
subcomponents is subject to the terms and conditions of the following licenses.
|
||||
|
||||
**********
|
||||
@@ -0,0 +1,177 @@
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
[Proactively-secure Accumulo with Cryptographic Enforcement (PACE)][pace]
|
||||
--
|
||||
|
||||
The [PACE][pace] library adds cryptographic protections to
|
||||
[Apache Accumulo][accumulo], with the goal of protecting data from a potentially
|
||||
malicious Accumulo server administrator. These include encrypting data
|
||||
([encryption]) and signing data ([signature]) stored in Accumulo.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
To use PACE, include the core library as a maven dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>edu.mit.ll.pace</groupId>
|
||||
<artifactId>pace-core</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
There are three main components to PACE:
|
||||
|
||||
1. [Encryption][encryption]—Ensuring that data can be read by authorized
|
||||
parties.
|
||||
2. [Signatures][signature]—Ensuring that data can only be modified by authorized
|
||||
parties.
|
||||
3. [Key management][key-management]—Managing the cryptographic keys necessary
|
||||
for encryption and signatures to function properly.
|
||||
|
||||
Currently, developers can choose to either encrypt **or** sign data. In the
|
||||
future, we plan to allow users to do both simultaneously.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The public PACE API is composed of all public types in the following packages:
|
||||
|
||||
* edu.mit.ll.pace
|
||||
* edu.mit.ll.pace.encryption
|
||||
* edu.mit.ll.pace.signature
|
||||
* edu.mit.ll.pace.keymanagement.common
|
||||
|
||||
A type is a class, interface, or enum. Anything with public or protected
|
||||
acccess in an API type is in the API. This includes, but is not limited to:
|
||||
methods, members, classes, interfaces, and enums. Package-private types in
|
||||
the above packages are *not* considered public API.
|
||||
|
||||
The PACE project maintains binary compatibility across this API within a
|
||||
major release, as defined in the Java Language Specification 3rd ed. All
|
||||
API changes will follow [semver 2.0][semver].
|
||||
|
||||
Examples
|
||||
--------
|
||||
Example code can be found in the examples/simple project. The examples
|
||||
demonstrate how to generate keys, read and write encrypted/signed data, and
|
||||
migrate existing tables to use PACE. Descriptions of these examples can be found
|
||||
in the [examples README][examples].
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Accumulo uses [Maven] to compile, test, and package its source.
|
||||
|
||||
In addition to the standard Maven lifecycle, the following commands are
|
||||
supported:
|
||||
|
||||
* **apilyzer:analyze**—Analyze the public PACE API, ensuring that it is
|
||||
[well structured][apilyzer].
|
||||
* **cobertura:cobertura**–Generates test coverage report in the site directory.
|
||||
* **javadoc:javadoc**–Generates JavaDoc for the PACE project.
|
||||
|
||||
Future Work
|
||||
-----------
|
||||
* Key Management—While the [example code][examples] demonstrates how to manually
|
||||
maintain cryptographic keys, it is not automated, nor does it scale well.
|
||||
In the future, we plan to add a key management client and server that will
|
||||
simplify the process of key management.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
If you get an `InvalidKeyException: Illegal key size or default parameters`
|
||||
message, you most likely need to install the Java Cryptography Extension
|
||||
(JCE) Unlimited Strength Jurisdiction Policy Files. They can be found on
|
||||
Oracle's website.
|
||||
|
||||
Export Control
|
||||
--------------
|
||||
|
||||
This distribution includes cryptographic software. The country in which you
|
||||
currently reside may have restrictions on the import, possession, use, and/or
|
||||
re-export to another country, of encryption software. BEFORE using any
|
||||
encryption software, please check your country's laws, regulations and
|
||||
policies concerning the import, possession, or use, and re-export of encryption
|
||||
software, to see if this is permitted. See <http://www.wassenaar.org/> for more
|
||||
information.
|
||||
|
||||
<!--
|
||||
The U.S. Government Department of Commerce, Bureau of Industry and Security
|
||||
(BIS), has classified this software as Export Commodity Control Number (ECCN)
|
||||
5D002.C.1, which includes information security software using or performing
|
||||
cryptographic functions with asymmetric algorithms. The form and manner of this
|
||||
Apache Software Foundation distribution makes it eligible for export under the
|
||||
License Exception ENC Technology Software Unrestricted (TSU) exception (see the
|
||||
BIS Export Administration Regulations, Section 740.13) for both object code and
|
||||
source code.
|
||||
-->
|
||||
|
||||
The following provides more details on the included cryptographic software:
|
||||
|
||||
PACE uses the built-in Java cryptography libraries in its
|
||||
encryption implementation. See [Oracle's export-regulations doc][java-export]
|
||||
for more details on Java's cryptography features. Apache Accumulo also uses
|
||||
the Bouncy Castle library for some cryptographic technology as well. See
|
||||
[the Bouncy Castle FAQ][bouncy-faq] for
|
||||
more details on Bouncy Castle's cryptography features.
|
||||
|
||||
Distribution Statement
|
||||
----------------------
|
||||
|
||||
DISTRIBUTION STATEMENT A. Approved for public release: distribution unlimited.
|
||||
|
||||
This material is based upon work supported by the Department of Defense under Air Force Contract No. FA8721-05-C-0002 and/or FA8702-15-D-0001. Any opinions, findings, conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the Department of Defense.
|
||||
|
||||
© 2017 Massachusetts Institute of Technology.
|
||||
|
||||
The software/firmware is provided to you on an As-Is basis
|
||||
|
||||
Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. Government rights in this work are defined by DFARS 252.227-7013 or DFARS 252.227-7014 as detailed above.
|
||||
|
||||
License
|
||||
-------
|
||||
Copyright 2017 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
|
||||
[pace]: https://github.com/mit-ll/pace
|
||||
[accumulo]: https://accumulo.apache.org
|
||||
[encryption]: ENCRYPTION.md
|
||||
[signature]: SIGNATURE.md
|
||||
[examples]: EXAMPLES.md
|
||||
[key-management]: KEY_MANAGEMENT.md
|
||||
[Maven]: https://maven.apache.org
|
||||
[semver]: http://semver.org/spec/v2.0.0
|
||||
[java-export]: http://www.oracle.com/us/products/export/export-regulations-345813.html
|
||||
[bouncy-faq]: http://www.bouncycastle.org/wiki/display/JA1/Frequently+Asked+Questions
|
||||
[apache]: http://www.apache.org/licenses/
|
||||
[apilyzer]: https://github.com/revelc/apilyzer-maven-plugin
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
Integrity
|
||||
---------
|
||||
|
||||
The signature code allows clients to sign data before storing it at the server.
|
||||
This prevents data from being modified by the server administrator.
|
||||
|
||||
The signature code is designed as a drop-in replacement for Accumulo's
|
||||
BatchWriter, BatchScanner, and Scanner classes. It can be configured
|
||||
programmatically or through the use of configuration files (preferred).
|
||||
Cryptographic keys are provided by the developer (see
|
||||
[key management][key-management]).
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Classes for signature functionality are found in
|
||||
`edu.mit.ll.pace.signature`.
|
||||
|
||||
#### `SignedBatchWriter`
|
||||
|
||||
The signed batch writer is created similarly to a `BatchWriter`. After creation,
|
||||
it is used identically to the Accumulo `BatchWriter`.
|
||||
|
||||
```java
|
||||
BatchWriter writer = new SignedBatchWriter(
|
||||
instance.getConnector(),
|
||||
TABLE_NAME,
|
||||
batchWriterConfig,
|
||||
signatureConfig,
|
||||
signatureKey);
|
||||
```
|
||||
|
||||
#### `SignedBatchScanner`
|
||||
|
||||
The signed batch scanner is created similarly to a `BatchScanner`. After
|
||||
creation, it is used identically to the Accumulo `BatchScanner`.
|
||||
|
||||
```java
|
||||
BatchScanner scanner = new SignedBatchScanner(
|
||||
instance.getConnector(),
|
||||
TABLE_NAME,
|
||||
authorizations,
|
||||
numThreads,
|
||||
signatureConfig,
|
||||
verificationKeys);
|
||||
```
|
||||
|
||||
#### `SignedScanner`
|
||||
|
||||
The signed batch scanner is created similarly to a `Scanner`. After creation,
|
||||
it is used identically to the Accumulo `Scanner`.
|
||||
|
||||
```java
|
||||
Scanner scanner = new SignedScanner(
|
||||
instance.getConnector(),
|
||||
TABLE_NAME,
|
||||
authorizations,
|
||||
signatureConfig,
|
||||
verificationKeys);
|
||||
```
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The configuration for the signature code is given by a `SignatureConfig` object.
|
||||
This configuration object can be created programmatically or loaded from a
|
||||
configuration file.
|
||||
|
||||
The configuration files specify the signature algorithm that will be used to
|
||||
sign entries.
|
||||
|
||||
```ini
|
||||
[Signature]
|
||||
algorithm = {RSA-PKCS1,RSA-PSS,DSA,ECDSA}
|
||||
provider = {SunJCE,BC,...}
|
||||
```
|
||||
|
||||
Example config files can be found in the resources folder
|
||||
(`edu.mit.ll.pace.signature`).
|
||||
|
||||
#### algorithm
|
||||
Dictates the algorithm to use for signing and verifying records. The following
|
||||
algorithms are available for signing and verifying data.
|
||||
|
||||
* **RSA-PKCS1**–RSA signature using the old PKCS #1 v1.5 padding.
|
||||
* **RSA-PSS**–RSA signature using the more modern PSS padding scheme.
|
||||
* **DSA**—DSA signature.
|
||||
* **ECDSA**—ECDSA signature.
|
||||
|
||||
All algorithms sign the SHA256 hash of the data. BouncyCastle is required for
|
||||
using RSA-PSS and DSA.
|
||||
|
||||
##### provider (Optional)
|
||||
The Java cryptographic provider to use. If not set, will let the system decide
|
||||
the appropriate cryptographic provider.
|
||||
|
||||
Compatibility Options (Use with Care)
|
||||
-------------------------------------
|
||||
|
||||
By default, the signature is stored in the value field. When using the PACE
|
||||
classes, this signature is verified and stripped before the value is returned.
|
||||
In some cases, it may be necessary to sign data, while still allowing older,
|
||||
non-PACE aware clients to continue accessing the data. In this case, we have
|
||||
provided two alternative locations to store the signature.
|
||||
|
||||
##### Storing signatures in the columnVisibility field
|
||||
The signature can be stored in the column visibility field by adding it as a
|
||||
disjunctive clause (i.e., `(originalVisibility)|encodedSignature`). This is
|
||||
done by adding the following two lines to the configuration file:
|
||||
|
||||
```ini
|
||||
[Signature]
|
||||
destination = colVis
|
||||
defaultVisibility = ...
|
||||
```
|
||||
|
||||
As with the default behavior, PACE clients will strip the signature from the
|
||||
column visibility field before returning the data. Non-PACE aware clients will
|
||||
see the signature, but will ignore it as it will be a visibility label for which
|
||||
no user will have access.
|
||||
|
||||
The `defaultVisibility` option specifies what the default visibility field is
|
||||
for the table where signatures will be stored. This is used when the entry being
|
||||
signed has an empty visibility field.
|
||||
|
||||
**Limitations:**
|
||||
|
||||
1. Storing signatures in the column visibility field breaks versioning. As such,
|
||||
it should only be used with tables that do not rely on Accumulo to version
|
||||
entries.
|
||||
2. The `SignedBatchWriter` is unable to delete entries when the signature is
|
||||
stored in the column visibility field.
|
||||
|
||||
##### Storing signatures in a separate table
|
||||
The signature can also be stored in a separate table. This is done by adding the
|
||||
following two lines to the configuration file:
|
||||
|
||||
```ini
|
||||
destination = table
|
||||
table = ...
|
||||
```
|
||||
|
||||
In this case, signatures are stored in a second, duplicate table, where the
|
||||
values are replaced with signatures. The name of this second table is given in
|
||||
the configuration.
|
||||
|
||||
**Limitations:**
|
||||
|
||||
1. This approach is much slower. First, it requires two tables to be scanned.
|
||||
Second, if the data in the two tables is retrieved out of order
|
||||
(i.e., batch scanning), an internal copy of all signatures that have not yet
|
||||
been matched must be kept, increasing the memory requirements.
|
||||
2. This option requires that both tables are versioned, and cannot be used if
|
||||
versioning is disabled.
|
||||
|
||||
|
||||
[key-management]: KEY_MANAGEMENT.md
|
||||
@@ -0,0 +1,306 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
<profiles version="12">
|
||||
<profile kind="CodeFormatterProfile" name="integration" version="12">
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.source" value="1.7"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="160"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.7"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="160"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.7"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
<templates>
|
||||
<template autoinsert="true" context="gettercomment_context" deleted="false" description="Comment for getter method" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name="gettercomment">/**
|
||||
* @return the ${bare_field_name}
|
||||
*/
|
||||
</template>
|
||||
<template autoinsert="true" context="settercomment_context" deleted="false" description="Comment for setter method" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.settercomment" name="settercomment">/**
|
||||
* @param ${param} the ${bare_field_name} to set
|
||||
*/
|
||||
</template>
|
||||
<template autoinsert="true" context="constructorcomment_context" deleted="false" description="Comment for created constructors" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name="constructorcomment">/**
|
||||
* ${tags}
|
||||
*/
|
||||
</template>
|
||||
<template autoinsert="false" context="filecomment_context" deleted="false" description="Comment for created Java files" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.filecomment" name="filecomment">/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
</template>
|
||||
<template autoinsert="false" context="typecomment_context" deleted="false" description="Comment for created types" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.typecomment" name="typecomment">/**
|
||||
* ${tags}
|
||||
*/
|
||||
</template>
|
||||
<template autoinsert="false" context="fieldcomment_context" deleted="false" description="Comment for fields" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name="fieldcomment"/>
|
||||
<template autoinsert="true" context="methodcomment_context" deleted="false" description="Comment for non-overriding methods" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name="methodcomment">/**
|
||||
* ${tags}
|
||||
*/
|
||||
</template>
|
||||
<template autoinsert="false" context="overridecomment_context" deleted="false" description="Comment for overriding methods" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name="overridecomment"/>
|
||||
<template autoinsert="true" context="delegatecomment_context" deleted="false" description="Comment for delegate methods" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name="delegatecomment">/**
|
||||
* ${tags}
|
||||
* ${see_to_target}
|
||||
*/
|
||||
</template>
|
||||
<template autoinsert="true" context="newtype_context" deleted="false" description="Newly created files" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.newtype" name="newtype">${filecomment}
|
||||
${package_declaration}
|
||||
|
||||
${typecomment}
|
||||
${type_declaration}
|
||||
</template>
|
||||
<template autoinsert="true" context="classbody_context" deleted="false" description="Code in new class type bodies" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.classbody" name="classbody">
|
||||
</template>
|
||||
<template autoinsert="true" context="interfacebody_context" deleted="false" description="Code in new interface type bodies" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name="interfacebody">
|
||||
</template>
|
||||
<template autoinsert="true" context="enumbody_context" deleted="false" description="Code in new enum type bodies" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.enumbody" name="enumbody">
|
||||
</template>
|
||||
<template autoinsert="true" context="annotationbody_context" deleted="false" description="Code in new annotation type bodies" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name="annotationbody">
|
||||
</template>
|
||||
<template autoinsert="true" context="catchblock_context" deleted="false" description="Code in new catch blocks" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.catchblock" name="catchblock">// ${todo} Auto-generated catch block
|
||||
${exception_var}.printStackTrace();
|
||||
</template>
|
||||
<template autoinsert="true" context="methodbody_context" deleted="false" description="Code in created method stubs" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.methodbody" name="methodbody">// ${todo} Auto-generated method stub
|
||||
${body_statement}
|
||||
</template>
|
||||
<template autoinsert="true" context="constructorbody_context" deleted="false" description="Code in created constructor stubs" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name="constructorbody">${body_statement}
|
||||
// ${todo} Auto-generated constructor stub
|
||||
</template>
|
||||
<template autoinsert="true" context="getterbody_context" deleted="false" description="Code in created getters" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.getterbody" name="getterbody">return ${field};
|
||||
</template>
|
||||
<template autoinsert="true" context="setterbody_context" deleted="false" description="Code in created setters" enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.setterbody" name="setterbody">${field} = ${param};
|
||||
</template>
|
||||
</templates>
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright 2016 MIT Lincoln Laboratory
|
||||
#
|
||||
# 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.
|
||||
|
||||
To generate a Findbugs report, invoke the following Maven command from the root
|
||||
of the project:
|
||||
|
||||
`mvn clean package site site:stage`
|
||||
|
||||
Activating the package lifecycle phase is necessary as recompilation of the
|
||||
classes is necessary to get instrumentation from Findbugs. This will ultimately
|
||||
generate a local HTML site in `target/staging` which you can point your web
|
||||
browser to. Each submodule of the project will contain its own Findbugs report.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright 2016 MIT Lincoln Laboratory
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Maven ignores
|
||||
/target/
|
||||
|
||||
# IDE ignores
|
||||
/.settings/
|
||||
/.project
|
||||
/.classpath
|
||||
/.pydevproject
|
||||
/.idea
|
||||
/*.iml
|
||||
/nbproject/
|
||||
/nbactions.xml
|
||||
/nb-configuration.xml
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>edu.mit.ll.pace</groupId>
|
||||
<artifactId>pace-project</artifactId>
|
||||
<version>1.0-beta</version>
|
||||
</parent>
|
||||
<artifactId>pace-core</artifactId>
|
||||
<name>Core</name>
|
||||
<description>Core libraries.</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.accumulo</groupId>
|
||||
<artifactId>accumulo-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>siv-mode</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ini4j</groupId>
|
||||
<artifactId>ini4j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-library</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>net.revelc.code</groupId>
|
||||
<artifactId>apilyzer-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>edu[.]mit[.]ll[.]pace[.].*</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>.*Impl</exclude>
|
||||
<exclude>.*[.]impl[.].*</exclude>
|
||||
<eclude>edu[.]mit[.]ll[.]pace[.]internal[.].*</eclude>
|
||||
</excludes>
|
||||
<allows>
|
||||
<allow>org[.]apache[.]accumulo[.].*</allow>
|
||||
<allow>org[.]apache[.]hadoop[.]io[.]Text</allow>
|
||||
</allows>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>apilyzer</id>
|
||||
<goals>
|
||||
<goal>analyze</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>cobertura-maven-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<configuration>
|
||||
<aggregate>true</aggregate>
|
||||
<formats>
|
||||
<format>xml</format>
|
||||
<format>html</format>
|
||||
</formats>
|
||||
<instrumentation>
|
||||
<excludes>
|
||||
<exclude>edu/mit/ll/pace/IllegalKeyRequestException.class</exclude>
|
||||
<exclude>edu/mit/ll/pace/encryption/EncryptionException.class</exclude>
|
||||
<exclude>edu/mit/ll/pace/signature/SignatureException.class</exclude>
|
||||
</excludes>
|
||||
<ignoreTrivial>true</ignoreTrivial>
|
||||
</instrumentation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,46 @@
|
||||
<!--
|
||||
Copyright 2016 MIT Lincoln Laboratory
|
||||
|
||||
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.
|
||||
-->
|
||||
<FindBugsFilter>
|
||||
<Match>
|
||||
<!-- The purpose of the method is to test a null argument -->
|
||||
<Or>
|
||||
<Class name="edu.mit.ll.pace.encryption.EncryptedBatchScannerTest"/>
|
||||
<Class name="edu.mit.ll.pace.encryption.EntryEncryptorTest"/>
|
||||
<Class name="edu.mit.ll.pace.signature.EntrySignerTest"/>
|
||||
</Or>
|
||||
<Bug code="NP" pattern="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- The purpose of the method is to test a null argument -->
|
||||
<Or>
|
||||
<Class name="edu.mit.ll.pace.encryption.EncryptedBatchScannerTest"/>
|
||||
<Class name="edu.mit.ll.pace.encryption.EncryptedBatchWriterTest"/>
|
||||
<Class name="edu.mit.ll.pace.encryption.EntryEncryptorTest"/>
|
||||
<Class name="edu.mit.ll.pace.encryption.FieldEncryptorTest"/>
|
||||
<Class name="edu.mit.ll.pace.signature.SignedBatchScannerTest"/>
|
||||
<Class name="edu.mit.ll.pace.signature.SignedBatchWriterTest"/>
|
||||
<Class name="edu.mit.ll.pace.signature.SignedScannerTest"/>
|
||||
<Class name="edu.mit.ll.pace.signature.ValueSignerTest"/>
|
||||
<Class name="edu.mit.ll.pace.internal.UtilsTest"/>
|
||||
</Or>
|
||||
<Bug code="NP" pattern="NP_NULL_PARAM_DEREF_NONVIRTUAL"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- We are intending to search for an array on a single byte[] -->
|
||||
<Class name="edu.mit.ll.pace.encryption.EntryEncryptorTest"/>
|
||||
<Bug code="VA" pattern="VA_PRIMITIVE_ARRAY_PASSED_TO_OBJECT_VARARG"/>
|
||||
</Match>
|
||||
</FindBugsFilter>
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Enumeration for the fields in an entry.
|
||||
*/
|
||||
public enum EntryField {
|
||||
ROW("row"),
|
||||
COLUMN_FAMILY("colFamily"),
|
||||
COLUMN_QUALIFIER("colQualifier"),
|
||||
COLUMN_VISIBILITY("colVisibility"),
|
||||
TIMESTAMP("timestamp"),
|
||||
DELETE("delete"),
|
||||
VALUE("value");
|
||||
|
||||
/**
|
||||
* Set of EntryField names mapped to the EntryField enum object. Used to speed up name lookups.
|
||||
*/
|
||||
private static final Map<String,EntryField> fields;
|
||||
static {
|
||||
fields = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (EntryField field : values()) {
|
||||
fields.put(field.name, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A string representation of the EntryField.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Create an EntryField with the given name.
|
||||
*
|
||||
* @param name
|
||||
* Name of the entry field. Used in the configuration INI.
|
||||
*/
|
||||
EntryField(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entry from its {@link String} representation.
|
||||
* <p>
|
||||
* This method is paired with {@link #toString()}, and will parse strings generated by that method.
|
||||
*
|
||||
* @param value
|
||||
* Value to parse.
|
||||
* @return EntryField for the value.
|
||||
*/
|
||||
public static EntryField fromString(String value) {
|
||||
EntryField field = fields.get(value);
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("invalid name");
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace;
|
||||
|
||||
/**
|
||||
* Exception for when a user lacks the permissions to request a key from the key infrastructure.
|
||||
*/
|
||||
public class IllegalKeyRequestException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 6652704411535999467L;
|
||||
|
||||
/**
|
||||
* Constructs a new illegal key request exception with null as its detail message.
|
||||
*/
|
||||
public IllegalKeyRequestException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new illegal key request exception with the specified detail message.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later retrieval by the {@link Throwable#getMessage()} method.
|
||||
*/
|
||||
public IllegalKeyRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new illegal key request exception with the specified cause and a detail message of (cause==null ? null : cause.toString()) (which typically
|
||||
* contains the class and detail message of cause). This constructor is useful for runtime exceptions that are little more than wrappers for other throwables.
|
||||
*
|
||||
* @param cause
|
||||
* the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). (A null value is permitted, and indicates that the
|
||||
* cause is nonexistent or unknown.)
|
||||
*/
|
||||
public IllegalKeyRequestException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new illegal key request exception with the specified detail message and cause.
|
||||
* <p>
|
||||
* Note that the detail message associated with cause is not automatically incorporated in this runtime exception's detail message.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later retrieval by the {@link Throwable#getMessage()} method.
|
||||
* @param cause
|
||||
* the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). (A null value is permitted, and indicates that the
|
||||
* cause is nonexistent or unknown.)
|
||||
*/
|
||||
public IllegalKeyRequestException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* An iterator that iterates over items both post- and pre- processing.
|
||||
* <p>
|
||||
* More concretely, {@link #next()} returns the processed entry, and {@link #unprocessed()} returns the unprocessed entry that resulted in the value returned by
|
||||
* {@link #next()}.
|
||||
*
|
||||
* @param <E>
|
||||
* Type parameter.
|
||||
*/
|
||||
public interface ItemProcessingIterator<E> extends Iterator<E> {
|
||||
|
||||
/**
|
||||
* Get the unprocessed element that returned the next item.
|
||||
* <p>
|
||||
* Requires that {@link #next()} has been successfully called.
|
||||
*
|
||||
* @return Returns the next item.
|
||||
*/
|
||||
E unprocessed();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.AlgorithmParameters;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||
import org.apache.hadoop.io.WritableUtils;
|
||||
|
||||
/**
|
||||
* Defines the contract for a {@link ValueEncryptorBase} that performs semantic or authenticated encryption using AES.
|
||||
*/
|
||||
final class AESValueEncryptor extends ValueEncryptorBase {
|
||||
|
||||
/**
|
||||
* The algorithm string to use when creating a key spec.
|
||||
*/
|
||||
private final static String AES = "AES";
|
||||
|
||||
/**
|
||||
* The cipher to use for encryption.
|
||||
*/
|
||||
private final Cipher cipher;
|
||||
|
||||
/**
|
||||
* Tracks whether this encryptor is a SunJCE provided instance of GCM. This is necessary as SunJCE uses "GCM" instead of "AES" for the algorithm name of this
|
||||
* cipher.
|
||||
*/
|
||||
private final boolean isInstanceOfSunProvidedGCM;
|
||||
|
||||
/**
|
||||
* Create the value encryptor.
|
||||
*
|
||||
* @param transformation
|
||||
* ValueEncryptor transformation. Passed to {@link Cipher#getInstance(String, String)}.
|
||||
* <p>
|
||||
* The transformation defines the algorithm used, the mode it is used in, and any padding.
|
||||
* @param provider
|
||||
* ValueEncryptor provider. Passed to {@link Cipher#getInstance(String, String)}.
|
||||
* @throws EncryptionException
|
||||
* Thrown when the encryptor can't be instantiated.
|
||||
*/
|
||||
AESValueEncryptor(String transformation, String provider) {
|
||||
try {
|
||||
if (provider == null) {
|
||||
cipher = Cipher.getInstance(transformation);
|
||||
} else {
|
||||
cipher = Cipher.getInstance(transformation, provider);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) {
|
||||
throw new EncryptionException(e);
|
||||
}
|
||||
|
||||
isInstanceOfSunProvidedGCM = transformation.toUpperCase().startsWith("AES/GCM") && cipher.getProvider().getName().equals("SunJCE");
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] encrypt(byte[] key, byte[] data) {
|
||||
try {
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, AES);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
|
||||
byte[] ciphertext = cipher.doFinal(data);
|
||||
|
||||
// Write out the metadata.
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
DataOutput out = new DataOutputStream(stream);
|
||||
|
||||
byte[] params = cipher.getParameters().getEncoded();
|
||||
WritableUtils.writeVInt(out, params.length);
|
||||
out.write(params);
|
||||
|
||||
// Write the original ciphertext and return the new ciphertext.
|
||||
out.write(ciphertext);
|
||||
return stream.toByteArray();
|
||||
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | IOException e) {
|
||||
throw new EncryptionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] decrypt(byte[] key, byte[] data) {
|
||||
try {
|
||||
// Sun's impelemntation of GCM uses a custom algorithm name. We handle this odd (potentially incorrect) behavior here.
|
||||
AlgorithmParameters params = AlgorithmParameters.getInstance(isInstanceOfSunProvidedGCM ? "GCM" : "AES");
|
||||
|
||||
// Read the metadata.
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(data);
|
||||
DataInput in = new DataInputStream(stream);
|
||||
|
||||
int metadataLength = WritableUtils.readVInt(in);
|
||||
byte[] metadata = new byte[metadataLength];
|
||||
in.readFully(metadata);
|
||||
params.init(metadata);
|
||||
|
||||
// Decrypt the remaining data.
|
||||
SecretKeySpec keySpec = new SecretKeySpec(key, AES);
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, params);
|
||||
byte[] ciphertext = new byte[stream.available()];
|
||||
in.readFully(ciphertext);
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, params);
|
||||
return cipher.doFinal(ciphertext);
|
||||
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException
|
||||
| IOException e) {
|
||||
throw new EncryptionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
|
||||
import org.cryptomator.siv.SivMode;
|
||||
import org.cryptomator.siv.UnauthenticCiphertextException;
|
||||
|
||||
/**
|
||||
* Defines the contract for classes that supports symmetric encryption and decryption of data.
|
||||
*/
|
||||
final class DeterministicSIVValueEncryptor extends ValueEncryptorBase {
|
||||
|
||||
/**
|
||||
* SIV cipher to use.
|
||||
*/
|
||||
private final SivMode siv;
|
||||
|
||||
/**
|
||||
* Creates an SIV encryptor to use for deterministic encryption.
|
||||
*/
|
||||
DeterministicSIVValueEncryptor() {
|
||||
siv = new SivMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] encrypt(byte[] key, byte[] data) {
|
||||
// The key passed here contains both the AES and IV generation keys.
|
||||
byte[] encKey = Arrays.copyOfRange(key, 0, key.length / 2);
|
||||
byte[] macKey = Arrays.copyOfRange(key, key.length / 2, key.length);
|
||||
|
||||
return siv.encrypt(encKey, macKey, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] decrypt(byte[] key, byte[] data) {
|
||||
// The key passed here contains both the AES and IV generation keys.
|
||||
byte[] encKey = Arrays.copyOfRange(key, 0, key.length / 2);
|
||||
byte[] macKey = Arrays.copyOfRange(key, key.length / 2, key.length);
|
||||
|
||||
try {
|
||||
return siv.decrypt(encKey, macKey, data);
|
||||
} catch (IllegalBlockSizeException | UnauthenticCiphertextException e) {
|
||||
throw new EncryptionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchScanner;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.client.TableNotFoundException;
|
||||
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
|
||||
import org.apache.accumulo.core.data.Column;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.accumulo.core.util.TextUtil;
|
||||
import org.apache.hadoop.io.Text;
|
||||
|
||||
import edu.mit.ll.pace.ItemProcessingIterator;
|
||||
import edu.mit.ll.pace.encryption.EntryEncryptor.ColumnFilterResult;
|
||||
|
||||
/**
|
||||
* Reads encrypted entries from Accumulo. As is the case for all {@link BatchScanner} instances, data is not guaranteed to be sorted.
|
||||
* <p>
|
||||
* It should be noted that while server side scan iterators can be set from this scanner ({@link #addScanIterator(IteratorSetting)},
|
||||
* {@link #clearScanIterators()}, {@link #removeScanIterator(String)}, and {@link #updateScanIteratorOption(String, String, String)}), the data will not be
|
||||
* decrypted on the server side. As such these server side will only be able to operate on unencrypted data and ciphertext, not decrypted plaintext.
|
||||
*/
|
||||
public final class EncryptedBatchScanner implements BatchScanner {
|
||||
|
||||
/**
|
||||
* The underlying {@link BatchScanner} used to retrieve data from Accumulo.
|
||||
*/
|
||||
private final BatchScanner scanner;
|
||||
|
||||
/**
|
||||
* The {@link EntryEncryptor} that will handle decryption for this instance.
|
||||
*/
|
||||
private final EntryEncryptor encryptor;
|
||||
|
||||
/**
|
||||
* The set of ranges that an entry must match to be returned.
|
||||
* <p>
|
||||
* These are handled client side, as it was not possible to push this filtering to the server.
|
||||
*/
|
||||
private List<Range> clientSideRanges = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The set of columns that an entry must match to be returned.
|
||||
* <p>
|
||||
* These are handled client side, as it was not possible to push this filtering to the server.
|
||||
*/
|
||||
private SortedSet<Column> clientSideColumnFilters = new TreeSet<>();
|
||||
|
||||
/**
|
||||
* Create an encrypted batch scanner.
|
||||
*
|
||||
* @param connector
|
||||
* The connector for the Accumulo instance.
|
||||
* @param tableName
|
||||
* Name of the table to write to.
|
||||
* @param authorizations
|
||||
* The authorizations this user has for querying Accumulo.
|
||||
* @param numQueryThreads
|
||||
* Maximimum number of query threads to use for this scanner.
|
||||
* @param cryptoConfig
|
||||
* Configuration for the decryption.
|
||||
* @param keys
|
||||
* Container with the keys to use for decryption.
|
||||
* @throws TableNotFoundException
|
||||
* Thrown if the table name is not found in the Accumulo instance.
|
||||
*/
|
||||
public EncryptedBatchScanner(Connector connector, String tableName, Authorizations authorizations, int numQueryThreads, EncryptionConfig cryptoConfig,
|
||||
EncryptionKeyContainer keys) throws TableNotFoundException {
|
||||
checkArgument(connector != null, "connection is null");
|
||||
checkArgument(tableName != null, "tableName is null");
|
||||
checkArgument(authorizations != null, "authorizations is null");
|
||||
checkArgument(cryptoConfig != null, "config is null");
|
||||
checkArgument(keys != null, "keys is null");
|
||||
|
||||
this.scanner = connector.createBatchScanner(tableName, authorizations, numQueryThreads);
|
||||
this.encryptor = new EntryEncryptor(cryptoConfig, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Automatically creates a set of server and client side ranges that most efficiently filter for the given ranges.
|
||||
*
|
||||
* @throws EncryptionException
|
||||
* The reason for the failure can be retrieved by calling {@link EncryptionException#getCause()}.
|
||||
*/
|
||||
@Override
|
||||
public void setRanges(Collection<Range> collection) {
|
||||
checkArgument(collection != null && collection.size() > 0, "ranges must be non null and contain at least 1 range");
|
||||
|
||||
List<Range> serverSideRanges = new ArrayList<>();
|
||||
clientSideRanges = new ArrayList<>();
|
||||
|
||||
// Transform the ranges as needed to deal with different encryption configurations.
|
||||
for (Range range : collection) {
|
||||
if (encryptor.transformRange(range, serverSideRanges)) {
|
||||
clientSideRanges.add(range);
|
||||
}
|
||||
}
|
||||
|
||||
scanner.setRanges(serverSideRanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Handles encryption of the search values as needed, filtering for the given column family on the server or client side as appropriate.
|
||||
*
|
||||
* @throws EncryptionException
|
||||
* The reason for the failure can be retrieved by calling {@link EncryptionException#getCause()}.
|
||||
*/
|
||||
@Override
|
||||
public void fetchColumnFamily(Text col) {
|
||||
checkArgument(col != null, "col is null");
|
||||
|
||||
ColumnFilterResult search = encryptor.getColumnFamilyFilter(col);
|
||||
for (Column filter : search.serverSideFilters) {
|
||||
scanner.fetchColumnFamily(new Text(filter.getColumnFamily()));
|
||||
}
|
||||
if (search.needsClientSideFiltering) {
|
||||
clientSideColumnFilters.add(new Column(TextUtil.getBytes(col), null, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Handles encryption of the search values as needed, filtering for the given column on the server or client side as appropriate.
|
||||
*
|
||||
* @throws EncryptionException
|
||||
* The reason for the failure can be retrieved by calling {@link EncryptionException#getCause()}.
|
||||
*/
|
||||
@Override
|
||||
public void fetchColumn(Text colFam, Text colQual) {
|
||||
checkArgument(colFam != null, "colFam is null");
|
||||
checkArgument(colQual != null, "colQual is null");
|
||||
|
||||
ColumnFilterResult search = encryptor.getColumnFilter(colFam, colQual);
|
||||
for (Column column : search.serverSideFilters) {
|
||||
if (column.getColumnQualifier() == null) {
|
||||
scanner.fetchColumnFamily(new Text(column.getColumnFamily()));
|
||||
} else {
|
||||
scanner.fetchColumn(new Text(column.getColumnFamily()), new Text(column.getColumnQualifier()));
|
||||
}
|
||||
}
|
||||
if (search.needsClientSideFiltering) {
|
||||
clientSideColumnFilters.add(new Column(TextUtil.getBytes(colFam), TextUtil.getBytes(colQual), null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @throws EncryptionException
|
||||
* The reason for the failure can be retrieved by calling {@link EncryptionException#getCause()}.
|
||||
*/
|
||||
@Override
|
||||
public void fetchColumn(IteratorSetting.Column column) {
|
||||
checkArgument(column != null, "Column is null");
|
||||
fetchColumn(column.getColumnFamily(), column.getColumnQualifier());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearColumns() {
|
||||
scanner.clearColumns();
|
||||
clientSideColumnFilters.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Returns a {@link edu.mit.ll.pace.ItemProcessingIterator}. This can be used to get the unprocessed items, allowing them to be deleted by a regular accumulo
|
||||
* writer.
|
||||
*/
|
||||
@Override
|
||||
public ItemProcessingIterator<Entry<Key,Value>> iterator() {
|
||||
return new EncryptedScannerIterator(scanner.iterator(), encryptor, clientSideRanges, clientSideColumnFilters);
|
||||
}
|
||||
|
||||
/* What remains are simply wrappers around BatchScanner. */
|
||||
|
||||
@Override
|
||||
public void clearScanIterators() {
|
||||
scanner.clearScanIterators();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
scanner.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authorizations getAuthorizations() {
|
||||
return scanner.getAuthorizations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSamplerConfiguration(SamplerConfiguration samplerConfiguration) {
|
||||
scanner.setSamplerConfiguration(samplerConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamplerConfiguration getSamplerConfiguration() {
|
||||
return scanner.getSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSamplerConfiguration() {
|
||||
scanner.clearSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchTimeout(long l, TimeUnit timeUnit) {
|
||||
scanner.setBatchTimeout(l, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBatchTimeout(TimeUnit timeUnit) {
|
||||
return scanner.getBatchTimeout(timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClassLoaderContext(String s) {
|
||||
scanner.setClassLoaderContext(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearClassLoaderContext() {
|
||||
scanner.clearClassLoaderContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassLoaderContext() {
|
||||
return scanner.getClassLoaderContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScanIterator(IteratorSetting iteratorSetting) {
|
||||
scanner.addScanIterator(iteratorSetting);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScanIterator(String s) {
|
||||
scanner.removeScanIterator(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScanIteratorOption(String s, String s1, String s2) {
|
||||
scanner.updateScanIteratorOption(s, s1, s2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeout(long l, TimeUnit timeUnit) {
|
||||
scanner.setTimeout(l, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeout(TimeUnit timeUnit) {
|
||||
return scanner.getTimeout(timeUnit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchWriter;
|
||||
import org.apache.accumulo.core.client.BatchWriterConfig;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.MutationsRejectedException;
|
||||
import org.apache.accumulo.core.client.TableNotFoundException;
|
||||
import org.apache.accumulo.core.data.ColumnUpdate;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Mutation;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility;
|
||||
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
|
||||
/**
|
||||
* Writes encrypted entries to Accumulo.
|
||||
*/
|
||||
public final class EncryptedBatchWriter implements BatchWriter {
|
||||
|
||||
/**
|
||||
* A {@link BatchWriter} to use to write the data to Accumulo.
|
||||
*/
|
||||
private final BatchWriter writer;
|
||||
|
||||
/**
|
||||
* The {@link EntryEncryptor} that will handle encryption for this instance.
|
||||
*/
|
||||
private final EntryEncryptor encryptor;
|
||||
|
||||
/**
|
||||
* Tracks whether entries written by this encrypted batch writer can be updated (i.e., deleted or changed).
|
||||
*/
|
||||
private final boolean supportsDelete;
|
||||
|
||||
/**
|
||||
* Create an encrypted batch writer.
|
||||
*
|
||||
* @param connector
|
||||
* The connector for the Accumulo instance.
|
||||
* @param tableName
|
||||
* Name of the table to write to.
|
||||
* @param batchConfig
|
||||
* Configuration for a {@link BatchWriter}.
|
||||
* @param cryptoConfig
|
||||
* Configuration for the encryption.
|
||||
* @param keys
|
||||
* Container with the keys to use for encryption.
|
||||
* @throws TableNotFoundException
|
||||
* Thrown if the table name is not found in the Accumulo instance.
|
||||
*/
|
||||
public EncryptedBatchWriter(Connector connector, String tableName, BatchWriterConfig batchConfig, EncryptionConfig cryptoConfig, EncryptionKeyContainer keys)
|
||||
throws TableNotFoundException {
|
||||
checkArgument(connector != null, "connector is null");
|
||||
checkArgument(tableName != null, "tableName is null");
|
||||
checkArgument(cryptoConfig != null, "config is null");
|
||||
checkArgument(keys != null, "keys is null");
|
||||
|
||||
this.writer = connector.createBatchWriter(tableName, batchConfig);
|
||||
this.encryptor = new EntryEncryptor(cryptoConfig, keys);
|
||||
this.supportsDelete = this.encryptor.canBeDeleteServerSide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given mutation and then write it to Accumulo.
|
||||
*
|
||||
* @param mutation
|
||||
* The mutation to encrypt.
|
||||
* @throws EncryptionException
|
||||
* The reason for the failure can be retrieved by calling {@link EncryptionException#getCause()}.
|
||||
*/
|
||||
@Override
|
||||
public void addMutation(Mutation mutation) throws MutationsRejectedException {
|
||||
for (ColumnUpdate update : mutation.getUpdates()) {
|
||||
MutableEntry entry = new MutableEntry(mutation.getRow(), update);
|
||||
|
||||
if (update.isDeleted()) {
|
||||
if (!supportsDelete) {
|
||||
throw new IllegalArgumentException("cannot delete entries when there are fields encrypted using non-deterministic encryption");
|
||||
}
|
||||
|
||||
// Many keys might need to be deleted, one for each combination of key versions.
|
||||
for (Key deleteKey : encryptor.getDeleteKeys(entry.toKey())) {
|
||||
MutableEntry encryptedKey = new MutableEntry(deleteKey);
|
||||
|
||||
Mutation encryptedMutation = new Mutation(encryptedKey.row);
|
||||
if (update.hasTimestamp()) {
|
||||
encryptedMutation.putDelete(encryptedKey.colF, encryptedKey.colQ, new ColumnVisibility(encryptedKey.colVis), encryptedKey.timestamp);
|
||||
} else {
|
||||
encryptedMutation.putDelete(encryptedKey.colF, encryptedKey.colQ, new ColumnVisibility(encryptedKey.colVis));
|
||||
}
|
||||
writer.addMutation(encryptedMutation);
|
||||
}
|
||||
} else {
|
||||
MutableEntry encryptedEntry = new MutableEntry(encryptor.encrypt(new MutableEntry(mutation.getRow(), update).toEntry()));
|
||||
|
||||
Mutation encryptedMutation = new Mutation(encryptedEntry.row);
|
||||
if (update.hasTimestamp()) {
|
||||
encryptedMutation.put(encryptedEntry.colF, encryptedEntry.colQ, new ColumnVisibility(encryptedEntry.colVis), encryptedEntry.timestamp,
|
||||
encryptedEntry.value);
|
||||
} else {
|
||||
encryptedMutation.put(encryptedEntry.colF, encryptedEntry.colQ, new ColumnVisibility(encryptedEntry.colVis), encryptedEntry.value);
|
||||
}
|
||||
writer.addMutation(encryptedMutation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMutations(Iterable<Mutation> iterable) throws MutationsRejectedException {
|
||||
for (Mutation mutation : iterable) {
|
||||
addMutation(mutation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws MutationsRejectedException {
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws MutationsRejectedException {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import org.apache.accumulo.core.data.Column;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
|
||||
import edu.mit.ll.pace.ItemProcessingIterator;
|
||||
|
||||
/**
|
||||
* Reads encrypted entries from Accumulo.
|
||||
*/
|
||||
final class EncryptedScannerIterator implements ItemProcessingIterator<Entry<Key,Value>> {
|
||||
|
||||
/**
|
||||
* The underlying iterator that contains the data that will be decrypted.
|
||||
*/
|
||||
private final Iterator<Entry<Key,Value>> iterator;
|
||||
|
||||
/**
|
||||
* The {@link EntryEncryptor} to use to decrypt values.
|
||||
*/
|
||||
private final EntryEncryptor encryptor;
|
||||
|
||||
/**
|
||||
* The set of ranges that an entry must match to be returned.
|
||||
* <p>
|
||||
* These are handled client side, as it was not possible to push this filtering to the server.
|
||||
*/
|
||||
private final List<Range> clientSideRanges;
|
||||
|
||||
/**
|
||||
* The set of columns that an entry must match to be returned.
|
||||
* <p>
|
||||
* These are handled client side, as it was not possible to push this filtering to the server.
|
||||
*/
|
||||
private final SortedSet<Column> clientSideColumnFilters;
|
||||
|
||||
/**
|
||||
* Used to track the next element to return. Needed as the underlying iterator's {@link Iterator#hasNext()} doesn't match this iterator's {@link #hasNext()}.
|
||||
*/
|
||||
private Entry<Key,Value> next = null;
|
||||
|
||||
/**
|
||||
* Keep track of the encrypted entry that resulted in
|
||||
*/
|
||||
private Entry<Key,Value> unprocessedEntry = null;
|
||||
|
||||
/**
|
||||
* Wrap the given iterator, decrypting and filtering entries as appropriate.
|
||||
*
|
||||
* @param iterator
|
||||
* The underlying iterator that contains the data that will be decrypted.
|
||||
* @param encryptor
|
||||
* The {@link EntryEncryptor} to use to decrypt values.
|
||||
* @param clientSideRanges
|
||||
* The set of ranges that an entry must match to be returned.
|
||||
* @param clientSideColumnFilters
|
||||
* The set of columns that an entry must match to be returned.
|
||||
*/
|
||||
EncryptedScannerIterator(Iterator<Entry<Key,Value>> iterator, EntryEncryptor encryptor, List<Range> clientSideRanges,
|
||||
SortedSet<Column> clientSideColumnFilters) {
|
||||
this.iterator = iterator;
|
||||
this.encryptor = encryptor;
|
||||
this.clientSideRanges = clientSideRanges;
|
||||
this.clientSideColumnFilters = clientSideColumnFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (next == null) {
|
||||
advance();
|
||||
}
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Key,Value> next() {
|
||||
if (next == null) {
|
||||
advance();
|
||||
}
|
||||
|
||||
if (next == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
// Clear next, as it has now been consumed.
|
||||
Entry<Key,Value> value = next;
|
||||
next = null;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance to the next entry that can be decrypted. This element is stored in next. If no more entries are left, next is set to null.
|
||||
*/
|
||||
private void advance() {
|
||||
next = null;
|
||||
unprocessedEntry = null;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Entry<Key,Value> original = iterator.next();
|
||||
Entry<Key,Value> entry = encryptor.decrypt(original);
|
||||
|
||||
// Check to see if the decrypted entry matches one of the set clientSideRanges.
|
||||
if (clientSideRanges.size() > 0) {
|
||||
boolean matches = false;
|
||||
for (Range range : clientSideRanges) {
|
||||
if (range.contains(entry.getKey())) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matches) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if the decrypted entry matches one of the filtered columns.
|
||||
if (clientSideColumnFilters.size() > 0) {
|
||||
boolean matches = false;
|
||||
for (Column column : clientSideColumnFilters) {
|
||||
// Column family always set and must be checked. Column qualifier may not be set.
|
||||
if (Arrays.equals(column.getColumnFamily(), entry.getKey().getColumnFamilyData().getBackingArray())
|
||||
&& (column.getColumnQualifier() == null || Arrays.equals(column.getColumnQualifier(), entry.getKey().getColumnQualifierData().getBackingArray()))) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matches) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// We've found the next entry and can end the loop.
|
||||
next = entry;
|
||||
unprocessedEntry = original;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Key,Value> unprocessed() {
|
||||
if (next != null || unprocessedEntry == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return unprocessedEntry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.ini4j.Ini;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
|
||||
/**
|
||||
* Holds configuration data for encrypting Accumulo entries.
|
||||
*
|
||||
* <p>
|
||||
* The actual configuration is a list of {@link FieldEncryptorConfig} objects, that specify how data will be encrypted and written to a given field. As having
|
||||
* two {@link FieldEncryptorConfig} with the same destination field would lead to conflicts, only a single {@link FieldEncryptorConfig} is allowed for a
|
||||
* destination field.
|
||||
*/
|
||||
public final class EncryptionConfig {
|
||||
|
||||
/**
|
||||
* Read-only of {@link FieldEncryptorConfig} that define how encryption of an entry will happen.
|
||||
*/
|
||||
final List<FieldEncryptorConfig> fieldEncryptorConfigs;
|
||||
|
||||
/**
|
||||
* Creates an EncryptionConfig from a set of field encryptor configurations.
|
||||
*
|
||||
* @param fieldEncryptorConfigs
|
||||
* The encryptor configs that define how encryption of an entry will happen.
|
||||
*/
|
||||
EncryptionConfig(List<FieldEncryptorConfig> fieldEncryptorConfigs) {
|
||||
checkArgument(fieldEncryptorConfigs != null, "fieldEncryptorConfigs is null");
|
||||
checkArgument(fieldEncryptorConfigs.size() != 0, "fieldEncryptorConfigs is empty");
|
||||
|
||||
// Scan for duplicate destinations.
|
||||
Set<EntryField> destinations = new HashSet<>();
|
||||
Set<EntryField> sources = new HashSet<>();
|
||||
for (FieldEncryptorConfig config : fieldEncryptorConfigs) {
|
||||
checkArgument(!destinations.contains(config.destination), "duplicate destination in fieldEncryptorConfigs");
|
||||
destinations.add(config.destination);
|
||||
sources.addAll(config.sources);
|
||||
}
|
||||
|
||||
// Scan for duplicate
|
||||
for (EntryField destination : destinations) {
|
||||
checkArgument(sources.contains(destination), destination.toString() + " was a destination, but it was not a source.");
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Behavior with duplicate sources is undefined. This could lead to problems on decrypt. Consider the following situations: Encrypt with config:
|
||||
* colFamily = E(row) Update table: row = "new" Encrypt with config: colQualifier = E(row) Decrypt with config: row = D(colFamily), row = D(colQualifier) In
|
||||
* this case, row will have different values encrypted into it. This can only arise if different configs are allowed on a single table, but right now there
|
||||
* is nothing that stops that.
|
||||
*/
|
||||
this.fieldEncryptorConfigs = Collections.unmodifiableList(fieldEncryptorConfigs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the object to a Writer.
|
||||
*
|
||||
* @param out
|
||||
* Stream to write object out to.
|
||||
*/
|
||||
public void write(Writer out) throws IOException {
|
||||
Ini configIni = new Ini();
|
||||
|
||||
for (FieldEncryptorConfig config : fieldEncryptorConfigs) {
|
||||
config.write(configIni);
|
||||
}
|
||||
|
||||
configIni.store(out);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile.Section;
|
||||
|
||||
/**
|
||||
* Builder for configuration of entry encryptor objects.
|
||||
*/
|
||||
public class EncryptionConfigBuilder {
|
||||
|
||||
// For definitions of these values see FieldEncryptorConfig.
|
||||
private List<FieldEncryptorConfig> fieldEncryptorConfigs = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Add a field encryptor config.
|
||||
*
|
||||
* @param fieldEncryptorConfig
|
||||
* Field encryptor config to add.
|
||||
* @return Builder.
|
||||
*/
|
||||
public EncryptionConfigBuilder addFieldEncryptorConfig(FieldEncryptorConfig fieldEncryptorConfig) {
|
||||
this.fieldEncryptorConfigs.add(fieldEncryptorConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the field encryptor configs.
|
||||
*
|
||||
* @param fieldEncryptorConfigs
|
||||
* Field encryptor config to use.
|
||||
* @return Builder.
|
||||
*/
|
||||
|
||||
public EncryptionConfigBuilder setFieldEncryptorConfigs(Collection<FieldEncryptorConfig> fieldEncryptorConfigs) {
|
||||
this.fieldEncryptorConfigs.clear();
|
||||
if (fieldEncryptorConfigs != null) {
|
||||
this.fieldEncryptorConfigs.addAll(fieldEncryptorConfigs);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the configuration from a Reader.
|
||||
*
|
||||
* @param in
|
||||
* Stream to read from.
|
||||
* @return Builder.
|
||||
*/
|
||||
public EncryptionConfigBuilder readFromFile(Reader in) throws IOException {
|
||||
Ini configIni = new Ini(in);
|
||||
|
||||
for (Section section : configIni.values()) {
|
||||
addFieldEncryptorConfig(new FieldEncryptorConfigBuilder().readFromIni(section).build());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link EncryptionConfig}.
|
||||
*
|
||||
* @return Built config.
|
||||
*/
|
||||
public EncryptionConfig build() {
|
||||
return new EncryptionConfig(fieldEncryptorConfigs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
/**
|
||||
* Runtime wrapper for exceptions that occur during encryption.
|
||||
*/
|
||||
public class EncryptionException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -6473669625720101487L;
|
||||
|
||||
/**
|
||||
* Constructs a new encryption exception with null as its detail message.
|
||||
*/
|
||||
public EncryptionException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new encryption exception with the specified detail message.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later retrieval by the {@link Throwable#getMessage()} method.
|
||||
*/
|
||||
public EncryptionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new encryption exception with the specified cause and a detail message of (cause==null ? null : cause.toString()) (which typically contains
|
||||
* the class and detail message of cause). This constructor is useful for runtime exceptions that are little more than wrappers for other throwables.
|
||||
*
|
||||
* @param cause
|
||||
* the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). (A null value is permitted, and indicates that the
|
||||
* cause is nonexistent or unknown.)
|
||||
*/
|
||||
public EncryptionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new encryption exception with the specified detail message and cause.
|
||||
* <p>
|
||||
* Note that the detail message associated with cause is not automatically incorporated in this runtime exception's detail message.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later retrieval by the {@link Throwable#getMessage()} method.
|
||||
* @param cause
|
||||
* the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). (A null value is permitted, and indicates that the
|
||||
* cause is nonexistent or unknown.)
|
||||
*/
|
||||
public EncryptionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import edu.mit.ll.pace.IllegalKeyRequestException;
|
||||
|
||||
/**
|
||||
* Interface defining operations that must be supported by a key container for it to work with this module.
|
||||
*/
|
||||
public interface EncryptionKeyContainer {
|
||||
|
||||
/**
|
||||
* Key with its version.
|
||||
*/
|
||||
final class KeyWithVersion {
|
||||
/**
|
||||
* Encryption key.
|
||||
*/
|
||||
public final byte[] key;
|
||||
|
||||
/**
|
||||
* Version of the key.
|
||||
*/
|
||||
public final int version;
|
||||
|
||||
/**
|
||||
* Key with its version.
|
||||
*
|
||||
* @param key
|
||||
* Key.
|
||||
* @param version
|
||||
* Version.
|
||||
*/
|
||||
public KeyWithVersion(byte[] key, int version) {
|
||||
checkArgument(key != null, "key is null");
|
||||
checkArgument(key.length != 0, "key is empty");
|
||||
checkArgument(version >= 0, "version is negative");
|
||||
|
||||
this.key = key;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (null == obj || !(obj instanceof KeyWithVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyWithVersion other = (KeyWithVersion) obj;
|
||||
return Arrays.equals(key, other.key) && version == other.version;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the versioned keys for the given id.
|
||||
*
|
||||
* @param id
|
||||
* Id of the key to retrieve.
|
||||
* @param length
|
||||
* Length of the key to return in bits.
|
||||
* @return Requested key of the desired length and the version of that key.
|
||||
* @throws IllegalKeyRequestException
|
||||
* User lacks the permissions to obtain the desired key.
|
||||
*/
|
||||
Collection<KeyWithVersion> getKeys(String id, int length) throws IllegalKeyRequestException;
|
||||
|
||||
/**
|
||||
* Get the key for the given id.
|
||||
*
|
||||
* @param id
|
||||
* Id of the key to retrieve.
|
||||
* @param length
|
||||
* Length of the key to return in bits.
|
||||
* @return Requested key of the desired length and the version of that key.
|
||||
* @throws IllegalKeyRequestException
|
||||
* User lacks the permissions to obtain the desired key.
|
||||
*/
|
||||
KeyWithVersion getKey(String id, int length) throws IllegalKeyRequestException;
|
||||
|
||||
/**
|
||||
* Get the key for the given id and version.
|
||||
*
|
||||
* @param id
|
||||
* Id of the key to retrieve.
|
||||
* @param version
|
||||
* Version of the key to retrieve.
|
||||
* @param length
|
||||
* Length of the key to return in bits.
|
||||
* @return byte[] with requested key.
|
||||
* @throws IllegalKeyRequestException
|
||||
* User lacks the permissions to obtain the desired key.
|
||||
*/
|
||||
byte[] getKey(String id, int version, int length) throws IllegalKeyRequestException;
|
||||
|
||||
/**
|
||||
* Get the encryption key for the given attribute.
|
||||
*
|
||||
* @param attribute
|
||||
* Name of the attribute whose key is being retrieved.
|
||||
* @param id
|
||||
* Id of the key to retrieve.
|
||||
* @param length
|
||||
* Length of the key to return in bits.
|
||||
* @return Requested encryption key of the desired length and the version of those keys.
|
||||
* @throws IllegalKeyRequestException
|
||||
* User lacks the permissions to obtain the desired key.
|
||||
*/
|
||||
KeyWithVersion getAttributeKey(String attribute, String id, int length) throws IllegalKeyRequestException;
|
||||
|
||||
/**
|
||||
* Get the encryption key for the given attribute.
|
||||
*
|
||||
* @param attribute
|
||||
* Name of the attribute whose key is being retrieved.
|
||||
* @param id
|
||||
* Id of the key to retrieve.
|
||||
* @param version
|
||||
* Version of the key to retrieve.
|
||||
* @param length
|
||||
* Length of the key to return in bits.
|
||||
* @return byte[] with the requested encryption key.
|
||||
* @throws IllegalKeyRequestException
|
||||
* User lacks the permissions to obtain the desired key.
|
||||
*/
|
||||
byte[] getAttributeKey(String attribute, String id, int version, int length) throws IllegalKeyRequestException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static edu.mit.ll.pace.internal.Utils.EMPTY;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.accumulo.core.data.Column;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.PartialKey;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility;
|
||||
import org.apache.accumulo.core.util.TextUtil;
|
||||
import org.apache.hadoop.io.Text;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
|
||||
/**
|
||||
* Encrypts an Accumulo {@literal Entry<Key,Value>} based on the supplied configuration.
|
||||
*/
|
||||
public final class EntryEncryptor {
|
||||
|
||||
/**
|
||||
* Result of encrypted filter operation.
|
||||
*/
|
||||
public static final class ColumnFilterResult {
|
||||
|
||||
/**
|
||||
* Server side filters.
|
||||
*/
|
||||
public final Collection<Column> serverSideFilters;
|
||||
|
||||
/**
|
||||
* Whether filtering also needs to happen client side.
|
||||
*/
|
||||
public final boolean needsClientSideFiltering;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param serverSideFilters
|
||||
* Server side filters.
|
||||
* @param needsClientSideFiltering
|
||||
* Whether filtering also needs to happen client side.
|
||||
*/
|
||||
private ColumnFilterResult(Collection<Column> serverSideFilters, boolean needsClientSideFiltering) {
|
||||
this.serverSideFilters = serverSideFilters;
|
||||
this.needsClientSideFiltering = needsClientSideFiltering;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of the {@link FieldEncryptor} objects that make up this entry encryptor.
|
||||
*/
|
||||
private final List<FieldEncryptor> encryptors;
|
||||
|
||||
/**
|
||||
* A map of destination fields to their respective {@link FieldEncryptor}.
|
||||
*/
|
||||
private final Map<EntryField,FieldEncryptor> destinationMap;
|
||||
|
||||
/**
|
||||
* A set of the fields used as sources by this encryptor.
|
||||
*/
|
||||
private final Set<EntryField> sources;
|
||||
|
||||
/**
|
||||
* Create an entry encryptor.
|
||||
*
|
||||
* @param config
|
||||
* Configuration for the encryption.
|
||||
* @param keys
|
||||
* Container with the keys to use for encryption.
|
||||
*/
|
||||
public EntryEncryptor(EncryptionConfig config, EncryptionKeyContainer keys) {
|
||||
checkArgument(config != null, "config is null");
|
||||
checkArgument(keys != null, "keys is null");
|
||||
|
||||
// Create the encryptors and their reverse-lookup.
|
||||
encryptors = new ArrayList<>(config.fieldEncryptorConfigs.size());
|
||||
destinationMap = new HashMap<>(config.fieldEncryptorConfigs.size());
|
||||
sources = new HashSet<>();
|
||||
|
||||
for (FieldEncryptorConfig fieldEncryptorConfig : config.fieldEncryptorConfigs) {
|
||||
FieldEncryptor fieldEncryptor = new FieldEncryptor(fieldEncryptorConfig, keys);
|
||||
encryptors.add(fieldEncryptor);
|
||||
destinationMap.put(fieldEncryptorConfig.destination, fieldEncryptor);
|
||||
sources.addAll(fieldEncryptorConfig.sources);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given entry.
|
||||
*
|
||||
* @param entry
|
||||
* Entry to encrypt.
|
||||
* @return Encrypted entry.
|
||||
*/
|
||||
public Entry<Key,Value> encrypt(Entry<Key,Value> entry) {
|
||||
checkArgument(entry != null, "entry is null");
|
||||
|
||||
MutableEntry wrapped = new MutableEntry(entry);
|
||||
MutableEntry result = new MutableEntry(entry);
|
||||
ColumnVisibility visibility = entry.getKey().getColumnVisibilityParsed();
|
||||
|
||||
// Remove source fields that are encrypted but not replaced with encrypted data.
|
||||
// Ignore column visibility, timestamp, and delete as these are needed for correct parsing by Accumulo.
|
||||
for (EntryField source : sources) {
|
||||
switch (source) {
|
||||
case ROW:
|
||||
case COLUMN_FAMILY:
|
||||
case COLUMN_QUALIFIER:
|
||||
case VALUE:
|
||||
result.setBytes(source, EMPTY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt the various fields.
|
||||
try {
|
||||
for (FieldEncryptor fieldEncryptor : encryptors) {
|
||||
fieldEncryptor.encrypt(wrapped, result, visibility);
|
||||
}
|
||||
} catch (IOException e) { // IO exceptions won't be thrown in practice as we are operating on in-memory streams.
|
||||
throw new EncryptionException(e);
|
||||
}
|
||||
|
||||
return result.toEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the given entry.
|
||||
*
|
||||
* @param entry
|
||||
* entry to decrypt.
|
||||
* @return Decrypted entry.
|
||||
*/
|
||||
public Entry<Key,Value> decrypt(Entry<Key,Value> entry) {
|
||||
checkArgument(entry != null, "entry is null");
|
||||
|
||||
MutableEntry wrapped = new MutableEntry(entry);
|
||||
MutableEntry result = new MutableEntry(entry);
|
||||
ColumnVisibility visibility = entry.getKey().getColumnVisibilityParsed();
|
||||
|
||||
// Decrypt the various fields.
|
||||
try {
|
||||
for (FieldEncryptor fieldEncryptor : encryptors) {
|
||||
fieldEncryptor.decrypt(wrapped, result, visibility);
|
||||
}
|
||||
} catch (IOException e) { // IO exceptions won't be thrown in practice as we are operating on in-memory streams.
|
||||
throw new EncryptionException(e);
|
||||
}
|
||||
|
||||
return result.toEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this entry encryptor results in an encrypted key that is searchable server-side.
|
||||
*
|
||||
* @return Whether entried can be updated.
|
||||
*/
|
||||
public boolean canBeDeleteServerSide() {
|
||||
for (EntryField field : FieldEncryptorConfig.KEY_DESTINATION_FIELDS) {
|
||||
if (destinationMap.containsKey(field) && !destinationMap.get(field).canBeFilteredServerSide(FieldEncryptorConfig.KEY_SOURCE_FIELDS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a key for deletion, generate the appropriate server side keys to delete.
|
||||
*
|
||||
* @param key
|
||||
* Key to delete.
|
||||
* @return Set of encrypted keys to delete.
|
||||
*/
|
||||
public Collection<Key> getDeleteKeys(Key key) {
|
||||
checkArgument(canBeDeleteServerSide(), "canBeDeleteServerSide is false");
|
||||
checkArgument(key != null, "key is null");
|
||||
MutableEntry searchKey = new MutableEntry(key);
|
||||
|
||||
// Pair up the correct answers.
|
||||
Collection<byte[]> rowValues = getDeleteValues(searchKey, EntryField.ROW);
|
||||
Collection<byte[]> colFValues = getDeleteValues(searchKey, EntryField.COLUMN_FAMILY);
|
||||
Collection<byte[]> colQValues = getDeleteValues(searchKey, EntryField.COLUMN_QUALIFIER);
|
||||
|
||||
return rowValues
|
||||
.stream()
|
||||
.flatMap(
|
||||
r -> colFValues.stream().flatMap(
|
||||
cf -> colQValues.stream().map(cq -> new Key(r, cf, cq, key.getColumnVisibilityData().getBackingArray(), key.getTimestamp(), true, true))))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the delete values for the given field.
|
||||
*
|
||||
* @param key
|
||||
* Key to pull values from.
|
||||
* @param field
|
||||
* Field to get delete values for.
|
||||
* @return Values that needed to be deleted server side to delete the given key.
|
||||
*/
|
||||
private Collection<byte[]> getDeleteValues(MutableEntry key, EntryField field) {
|
||||
if (!destinationMap.containsKey(field)) {
|
||||
if (!sources.contains(field)) {
|
||||
return Collections.singletonList(key.getBytes(field));
|
||||
} else {
|
||||
return Collections.singletonList(EMPTY);
|
||||
}
|
||||
} else {
|
||||
return destinationMap.get(field).getServerSideFilterValues(key, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter for the given value.
|
||||
*
|
||||
* @param col
|
||||
* Column family to filter.
|
||||
* @return Results with columns to filter server ide and whether client side filtering is needed.
|
||||
*/
|
||||
public ColumnFilterResult getColumnFamilyFilter(Text col) {
|
||||
checkArgument(col != null, "col is null");
|
||||
|
||||
MutableEntry key = new MutableEntry();
|
||||
key.colF = TextUtil.getBytes(col);
|
||||
|
||||
return getColumnFamilyFilter(key, ImmutableSet.of(EntryField.COLUMN_FAMILY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter for the given value.
|
||||
*
|
||||
* @param key
|
||||
* Data to use to generate the possibly encrypted column family.
|
||||
* @param fields
|
||||
* Set of fields available for encryption.
|
||||
* @return Results with columns to filter server ide and whether client side filtering is needed.
|
||||
*/
|
||||
private ColumnFilterResult getColumnFamilyFilter(MutableEntry key, Set<EntryField> fields) {
|
||||
if (!destinationMap.containsKey(EntryField.COLUMN_FAMILY)) {
|
||||
if (!sources.contains(EntryField.COLUMN_FAMILY)) {
|
||||
return new ColumnFilterResult(Collections.singletonList(new Column(key.colF, null, null)), false);
|
||||
} else {
|
||||
return new ColumnFilterResult(Collections.singletonList(new Column(EMPTY, null, null)), true);
|
||||
}
|
||||
} else {
|
||||
FieldEncryptor encryptor = destinationMap.get(EntryField.COLUMN_FAMILY);
|
||||
if (encryptor.canBeFilteredServerSide(fields)) {
|
||||
return new ColumnFilterResult(encryptor.getServerSideFilterValues(key).stream().map(f -> new Column(f, null, null)).collect(Collectors.toList()), false);
|
||||
} else {
|
||||
return new ColumnFilterResult(new ArrayList<>(0), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter for the given value.
|
||||
*
|
||||
* @param colFam
|
||||
* Column family to filter.
|
||||
* @param colQual
|
||||
* Column qualifier to filter.
|
||||
* @return A pair, with the left side being the set of columns to filter on the server side, and the right whether it is necessary to also filter on the
|
||||
* client side.
|
||||
*/
|
||||
public ColumnFilterResult getColumnFilter(Text colFam, Text colQual) {
|
||||
checkArgument(colFam != null, "colFam is null");
|
||||
checkArgument(colQual != null, "colQual is null");
|
||||
|
||||
Set<EntryField> fields = ImmutableSet.of(EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER);
|
||||
MutableEntry key = new MutableEntry();
|
||||
key.colF = TextUtil.getBytes(colFam);
|
||||
key.colQ = TextUtil.getBytes(colQual);
|
||||
|
||||
ColumnFilterResult familySearch = getColumnFamilyFilter(key, fields);
|
||||
|
||||
if (!destinationMap.containsKey(EntryField.COLUMN_QUALIFIER)) {
|
||||
if (!sources.contains(EntryField.COLUMN_QUALIFIER)) {
|
||||
return new ColumnFilterResult(familySearch.serverSideFilters.stream().map(f -> new Column(f.getColumnFamily(), key.colQ, null))
|
||||
.collect(Collectors.toList()), familySearch.needsClientSideFiltering);
|
||||
} else {
|
||||
boolean serverSideOnly = destinationMap.containsKey(EntryField.COLUMN_FAMILY)
|
||||
&& destinationMap.get(EntryField.COLUMN_FAMILY).canSearchFor(ImmutableSet.of(EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER));
|
||||
return new ColumnFilterResult(familySearch.serverSideFilters.stream().map(f -> new Column(f.getColumnFamily(), EMPTY, null))
|
||||
.collect(Collectors.toList()), !serverSideOnly);
|
||||
}
|
||||
} else {
|
||||
FieldEncryptor encryptor = destinationMap.get(EntryField.COLUMN_QUALIFIER);
|
||||
if (encryptor.canBeFilteredServerSide(fields)) {
|
||||
boolean serverSideOnly = !familySearch.needsClientSideFiltering
|
||||
|| (!destinationMap.containsKey(EntryField.COLUMN_FAMILY) && sources.contains(EntryField.COLUMN_FAMILY) && encryptor.canSearchFor(ImmutableSet.of(
|
||||
EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER)));
|
||||
|
||||
return new ColumnFilterResult(familySearch.serverSideFilters.stream()
|
||||
.flatMap(f -> encryptor.getServerSideFilterValues(key).stream().map(v -> new Column(f.getColumnFamily(), v, null))).collect(Collectors.toList()),
|
||||
!serverSideOnly);
|
||||
} else {
|
||||
boolean serverSideOnly = destinationMap.containsKey(EntryField.COLUMN_FAMILY)
|
||||
&& destinationMap.get(EntryField.COLUMN_FAMILY).canSearchFor(ImmutableSet.of(EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER));
|
||||
|
||||
return new ColumnFilterResult(familySearch.serverSideFilters.stream().map(f -> new Column(f.getColumnFamily(), null, null))
|
||||
.collect(Collectors.toList()), !serverSideOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the range to a set of encrypted server side range queries.
|
||||
*
|
||||
* @param range
|
||||
* Range to convert.
|
||||
* @param serverSideRanges
|
||||
* List of server side filters.
|
||||
* @return Whether this range still needs to be searched for client side.
|
||||
*/
|
||||
public boolean transformRange(Range range, Collection<Range> serverSideRanges) {
|
||||
checkArgument(range != null, "range is null");
|
||||
checkArgument(serverSideRanges != null, "serverSideRanges is null");
|
||||
|
||||
// If only the value is encrypted, then the key can be searched server side.
|
||||
if (destinationMap.size() == 1 && destinationMap.containsKey(EntryField.VALUE)) {
|
||||
serverSideRanges.add(range);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the range includes everything, it can be sent server side.
|
||||
if (range.isInfiniteStartKey() && range.isInfiniteStopKey()) {
|
||||
serverSideRanges.add(range);
|
||||
return false;
|
||||
}
|
||||
|
||||
MutableEntry startKey = range.isInfiniteStartKey() ? new MutableEntry() : new MutableEntry(range.getStartKey());
|
||||
MutableEntry endKey = range.isInfiniteStopKey() ? new MutableEntry() : new MutableEntry(range.getEndKey());
|
||||
|
||||
// Determine which set of fields are equal in both the start and end keys; these keys can be used as source values for deterministically encrypted fields.
|
||||
Set<EntryField> equalFields = new HashSet<>();
|
||||
EntryField followingKey = null;
|
||||
|
||||
if (!range.isInfiniteStartKey() && !range.isInfiniteStopKey()) {
|
||||
for (EntryField field : FieldEncryptorConfig.KEY_SOURCE_FIELDS) {
|
||||
byte[] start = startKey.getBytes(field);
|
||||
byte[] end = endKey.getBytes(field);
|
||||
|
||||
if (Arrays.equals(start, end)) {
|
||||
equalFields.add(field);
|
||||
} else if (!range.isEndKeyInclusive() && range.getStartKey().followingKey(toPartialKey(field)).equals(range.getEndKey())) {
|
||||
// TODO: This may actually be buggy. I'm not sure if it is possible to have this true twice, and if so how I should handle that.
|
||||
followingKey = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (followingKey != null) {
|
||||
equalFields.add(followingKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate server side filters.
|
||||
List<MutableEntry> startKeys = new ArrayList<>(), endKeys = new ArrayList<>();
|
||||
startKeys.add(new MutableEntry());
|
||||
endKeys.add(new MutableEntry());
|
||||
|
||||
PartialKey prefix = null;
|
||||
boolean prefixHasVariance = range.isInfiniteStartKey() || range.isInfiniteStopKey(); // Infinite keys always give variance.
|
||||
|
||||
for (EntryField field : EntryField.values()) {
|
||||
if (field == EntryField.VALUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!destinationMap.containsKey(field)) {
|
||||
if (!sources.contains(field)) {
|
||||
// Only plaintext fields that are not wiped (i.e., used as a source) will contribute to the filter.
|
||||
if (range.isInfiniteStartKey()) {
|
||||
for (MutableEntry entry : endKeys) {
|
||||
entry.setBytes(field, endKey.getBytes(field));
|
||||
}
|
||||
} else if (range.isInfiniteStopKey()) {
|
||||
for (MutableEntry entry : startKeys) {
|
||||
entry.setBytes(field, startKey.getBytes(field));
|
||||
}
|
||||
} else {
|
||||
if (!equalFields.contains(field)) {
|
||||
prefixHasVariance = true;
|
||||
}
|
||||
|
||||
Iterator<MutableEntry> startKeyIterator = startKeys.iterator();
|
||||
Iterator<MutableEntry> endKeyIterator = endKeys.iterator();
|
||||
while (startKeyIterator.hasNext()) {
|
||||
switch (field) {
|
||||
case TIMESTAMP:
|
||||
startKeyIterator.next().timestamp = startKey.timestamp;
|
||||
endKeyIterator.next().timestamp = endKey.timestamp;
|
||||
break;
|
||||
|
||||
case DELETE:
|
||||
startKeyIterator.next().delete = startKey.delete;
|
||||
endKeyIterator.next().delete = endKey.delete;
|
||||
break;
|
||||
|
||||
default:
|
||||
startKeyIterator.next().setBytes(field, startKey.getBytes(field));
|
||||
endKeyIterator.next().setBytes(field, endKey.getBytes(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Encrypted field.
|
||||
if (prefixHasVariance || !destinationMap.get(field).canBeFilteredServerSide(equalFields)) {
|
||||
appendServerSideRanges(serverSideRanges, range, startKeys, endKeys, prefix);
|
||||
return true;
|
||||
} else {
|
||||
startKeys = getServerSideFilterKeys(startKeys, field, startKey, false);
|
||||
endKeys = getServerSideFilterKeys(endKeys, field, startKey, field == followingKey);
|
||||
}
|
||||
}
|
||||
|
||||
prefix = toPartialKey(field);
|
||||
}
|
||||
|
||||
// Doesn't need a client side filter, as everything can be done server-side.
|
||||
Iterator<MutableEntry> startKeyIterator = startKeys.iterator();
|
||||
Iterator<MutableEntry> endKeyIterator = endKeys.iterator();
|
||||
while (startKeyIterator.hasNext()) {
|
||||
serverSideRanges.add(new Range(startKeyIterator.next().toKey(), range.isStartKeyInclusive(), endKeyIterator.next().toKey(), range.isEndKeyInclusive()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the server side filter keys for the given field to the existing filters.
|
||||
*
|
||||
* @param existingFilters
|
||||
* Filters that are being added to.
|
||||
* @param field
|
||||
* Field to add encrypted search for.
|
||||
* @param key
|
||||
* Data to use for generated encrypted search.
|
||||
* @param followingKey
|
||||
* Should the returned value be generated as in {@link Key#followingArray(byte[])}.
|
||||
* @return New list of server side filters.
|
||||
*/
|
||||
private List<MutableEntry> getServerSideFilterKeys(List<MutableEntry> existingFilters, EntryField field, MutableEntry key, boolean followingKey) {
|
||||
List<MutableEntry> entries = new ArrayList<>();
|
||||
List<byte[]> values = destinationMap.get(field).getServerSideFilterValues(key, followingKey);
|
||||
|
||||
for (MutableEntry entry : existingFilters) {
|
||||
for (byte[] value : values) {
|
||||
MutableEntry newItem = entry.cloneEntry();
|
||||
newItem.setBytes(field, value);
|
||||
entries.add(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serverSideRanges
|
||||
* List of server side filters.
|
||||
* @param originalRange
|
||||
* Range to convert.
|
||||
* @param startKeys
|
||||
* Set of start key filters.
|
||||
* @param endKeys
|
||||
* Set of end key filters.
|
||||
* @param prefix
|
||||
* What portion of the key are we filtering?
|
||||
*/
|
||||
private void appendServerSideRanges(Collection<Range> serverSideRanges, Range originalRange, List<MutableEntry> startKeys, List<MutableEntry> endKeys,
|
||||
PartialKey prefix) {
|
||||
if (prefix == null) {
|
||||
serverSideRanges.add(new Range());
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalRange.isInfiniteStartKey()) {
|
||||
for (MutableEntry endKey : endKeys) {
|
||||
serverSideRanges.add(new Range(null, true, endKey.toKey().followingKey(prefix), false));
|
||||
}
|
||||
} else if (originalRange.isInfiniteStopKey()) {
|
||||
for (MutableEntry startKey : startKeys) {
|
||||
serverSideRanges.add(new Range(startKey.toKey(), true, null, true));
|
||||
}
|
||||
} else {
|
||||
Iterator<MutableEntry> startKeyIterator = startKeys.iterator();
|
||||
Iterator<MutableEntry> endKeyIterator = endKeys.iterator();
|
||||
while (startKeyIterator.hasNext()) {
|
||||
serverSideRanges.add(new Range(startKeyIterator.next().toKey(), true, endKeyIterator.next().toKey().followingKey(prefix), false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the field to a partial key.
|
||||
*
|
||||
* @param field
|
||||
* Field to transform.
|
||||
* @return The partial key that ends with this field.
|
||||
*/
|
||||
private static PartialKey toPartialKey(EntryField field) {
|
||||
switch (field) {
|
||||
case ROW:
|
||||
return PartialKey.ROW;
|
||||
case COLUMN_FAMILY:
|
||||
return PartialKey.ROW_COLFAM;
|
||||
case COLUMN_QUALIFIER:
|
||||
return PartialKey.ROW_COLFAM_COLQUAL;
|
||||
case COLUMN_VISIBILITY:
|
||||
return PartialKey.ROW_COLFAM_COLQUAL_COLVIS;
|
||||
case TIMESTAMP:
|
||||
return PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME;
|
||||
case DELETE:
|
||||
return PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static edu.mit.ll.pace.internal.Utils.xor;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility.Node;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility.NodeType;
|
||||
import org.apache.hadoop.io.WritableUtils;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
import edu.mit.ll.pace.IllegalKeyRequestException;
|
||||
import edu.mit.ll.pace.encryption.EncryptionKeyContainer.KeyWithVersion;
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
|
||||
/**
|
||||
* Encrypts an Accumulo field based on the supplied configuration.
|
||||
*/
|
||||
final class FieldEncryptor {
|
||||
|
||||
/**
|
||||
* Secure random number generator.
|
||||
*/
|
||||
private static final SecureRandom random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Configuration for this encryptor.
|
||||
*/
|
||||
private final FieldEncryptorConfig config;
|
||||
|
||||
/**
|
||||
* The keys available to this encryptor.
|
||||
*/
|
||||
private final EncryptionKeyContainer keys;
|
||||
|
||||
/**
|
||||
* The value encryptor to use to encrypt data.
|
||||
*/
|
||||
private final ValueEncryptorBase encryptor;
|
||||
|
||||
/**
|
||||
* Creates a field encryptor.
|
||||
*
|
||||
* @param config
|
||||
* Configuration for the encryption.
|
||||
* @param keys
|
||||
* Container with the keys to use for encryption.
|
||||
*/
|
||||
FieldEncryptor(FieldEncryptorConfig config, EncryptionKeyContainer keys) {
|
||||
checkArgument(config != null, "config is null");
|
||||
checkArgument(keys != null, "keys is null");
|
||||
|
||||
this.config = config;
|
||||
this.keys = keys;
|
||||
|
||||
this.encryptor = config.valueEncryptor.getInstance(config.provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given entry.
|
||||
*
|
||||
* @param entry
|
||||
* {@link MutableEntry} to encrypt.
|
||||
* @param result
|
||||
* {@link MutableEntry} to write result to.
|
||||
* @param columnVisibility
|
||||
* The parsed column visibility.
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
void encrypt(MutableEntry entry, MutableEntry result, ColumnVisibility columnVisibility) throws IOException {
|
||||
// Put the data in a single byte array.
|
||||
byte[] data = concatData(entry);
|
||||
|
||||
// Gets the key used to encrypt the data. Also write to the front of the ciphertext the metadata necessary to retrieve the key.
|
||||
ByteArrayOutputStream ciphertextStream = new ByteArrayOutputStream();
|
||||
DataOutput ciphertextOut = new DataOutputStream(ciphertextStream);
|
||||
|
||||
// Encrypt the data, and write it to the result (first wrapping it if writing to the column visibility field).
|
||||
byte[] key = getKey(columnVisibility, ciphertextOut);
|
||||
ciphertextOut.write(encryptor.encrypt(key, data));
|
||||
byte[] ciphertext = ciphertextStream.toByteArray();
|
||||
result.setBytes(config.destination, ciphertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concat the data in preparation for it to be encrypted.
|
||||
*
|
||||
* @param entry
|
||||
* Entry to pull data from.
|
||||
* @return Concatenated data.
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
private byte[] concatData(MutableEntry entry) throws IOException {
|
||||
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
|
||||
DataOutput dataOut = new DataOutputStream(dataStream);
|
||||
|
||||
for (EntryField source : config.sources) {
|
||||
switch (source) {
|
||||
case ROW:
|
||||
case COLUMN_FAMILY:
|
||||
case COLUMN_QUALIFIER:
|
||||
case COLUMN_VISIBILITY:
|
||||
case VALUE:
|
||||
byte[] bytes = entry.getBytes(source);
|
||||
WritableUtils.writeVInt(dataOut, bytes.length);
|
||||
dataOut.write(bytes);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
return dataStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the given entry.
|
||||
*
|
||||
* @param entry
|
||||
* {@link MutableEntry} to encrypt.
|
||||
* @param result
|
||||
* {@link MutableEntry} to write result to.
|
||||
* @param columnVisibility
|
||||
* The parsed column visibility.
|
||||
*
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
void decrypt(MutableEntry entry, MutableEntry result, ColumnVisibility columnVisibility) throws IOException {
|
||||
ByteArrayInputStream ciphertextStream = new ByteArrayInputStream(entry.getBytes(config.destination));
|
||||
DataInput ciphertextIn = new DataInputStream(ciphertextStream);
|
||||
|
||||
byte[] key = getKey(columnVisibility, ciphertextIn);
|
||||
byte[] ciphertext = new byte[ciphertextStream.available()];
|
||||
ciphertextIn.readFully(ciphertext);
|
||||
byte[] decryptedData = encryptor.decrypt(key, ciphertext);
|
||||
|
||||
// Break apart the decrypted data.
|
||||
ByteArrayInputStream dataStream = new ByteArrayInputStream(decryptedData);
|
||||
DataInput dataIn = new DataInputStream(dataStream);
|
||||
|
||||
for (EntryField source : config.sources) {
|
||||
switch (source) {
|
||||
case ROW:
|
||||
case COLUMN_FAMILY:
|
||||
case COLUMN_QUALIFIER:
|
||||
case COLUMN_VISIBILITY:
|
||||
case VALUE:
|
||||
int length = WritableUtils.readVInt(dataIn);
|
||||
byte[] bytes = new byte[length];
|
||||
dataIn.readFully(bytes);
|
||||
result.setBytes(source, bytes);
|
||||
break;
|
||||
|
||||
// case TIMESTAMP:
|
||||
// result.timestamp = WritableUtils.readVLong(dataIn);
|
||||
// break;
|
||||
|
||||
// case DELETE:
|
||||
// result.delete = dataIn.readBoolean();
|
||||
// break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a field encryption key for use in <strong>encrypting</strong> the field.
|
||||
* <p>
|
||||
* Any metadata needed to retrieve this key later should be written to the DataOuput object.
|
||||
*
|
||||
* @param visibility
|
||||
* Visibility expression for the field.
|
||||
* @param out
|
||||
* DataOutput object to write metadata to.
|
||||
* @return Field encryption key.
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
private byte[] getKey(ColumnVisibility visibility, DataOutput out) throws IOException {
|
||||
if (config.encryptUsingVisibility) {
|
||||
byte[] key = new byte[config.keyLength];
|
||||
|
||||
if (visibility.getParseTree().getType() != NodeType.EMPTY) {
|
||||
random.nextBytes(key);
|
||||
writeVisibilityShare(key, visibility.getParseTree(), visibility.getExpression(), out);
|
||||
}
|
||||
|
||||
return key;
|
||||
} else {
|
||||
KeyWithVersion keyData = keys.getKey(config.keyId, config.keyLength);
|
||||
WritableUtils.writeVInt(out, keyData.version); // Write the version of the key being used as meta-data.
|
||||
return keyData.key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field encryption key to use in <strong>decrypting</strong> the field.
|
||||
* <p>
|
||||
* Metadata can be read from the DataInput object. All meta-data that was written to the stream should be read out, regardless if it is used.
|
||||
*
|
||||
* @param visibility
|
||||
* Visibility expression for the field.
|
||||
* @param in
|
||||
* Stream from which metadata is read.
|
||||
* @return Field encryption key.
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
private byte[] getKey(ColumnVisibility visibility, DataInput in) throws IOException {
|
||||
if (config.encryptUsingVisibility) {
|
||||
if (visibility.getParseTree().getType() != NodeType.EMPTY) {
|
||||
// Rebuild the key from the shares created based on the visibility expression.
|
||||
byte[] key = readVisibilityShare(visibility.getParseTree(), visibility.getExpression(), in, false);
|
||||
|
||||
if (key == null) {
|
||||
throw new IllegalKeyRequestException();
|
||||
}
|
||||
return key;
|
||||
} else {
|
||||
return new byte[config.keyLength];
|
||||
}
|
||||
} else {
|
||||
int version = WritableUtils.readVInt(in);
|
||||
return keys.getKey(config.keyId, version, config.keyLength);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given share based on the current visibility node.
|
||||
* <p>
|
||||
* Metadata needed to later decrypt the share and the encrypted share are written to the DataOutput object.
|
||||
*
|
||||
* @param share
|
||||
* Share of an encryption key to encrypt based on the visilbity expression.
|
||||
* @param node
|
||||
* Visibility node to use in encrypting the share.
|
||||
* @param expression
|
||||
* Visibility expression.
|
||||
* @param out
|
||||
* Stream to which metadata is written.
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
private void writeVisibilityShare(byte[] share, Node node, byte[] expression, DataOutput out) throws IOException {
|
||||
switch (node.getType()) {
|
||||
case TERM:
|
||||
// This is the only case we actually write to the stream. Encrypt the share with the attribute share.
|
||||
// The output format is "version || length || encrypted data"
|
||||
KeyWithVersion keyData = keys.getAttributeKey(new String(node.getTerm(expression).toArray(), VISIBILITY_CHARSET), config.keyId, config.keyLength);
|
||||
WritableUtils.writeVInt(out, keyData.version); // Key version is written to the metadata.
|
||||
|
||||
byte[] encrypted;
|
||||
encrypted = encryptor.encrypt(keyData.key, share);
|
||||
WritableUtils.writeVInt(out, encrypted.length);
|
||||
out.write(encrypted);
|
||||
break;
|
||||
|
||||
case AND:
|
||||
// Create random shares, with the final share being the original share xor'ed with each of the random shares.
|
||||
byte[] mask = new byte[share.length];
|
||||
for (int i = 0; i < node.getChildren().size(); i++) {
|
||||
if (i == (node.getChildren().size() - 1)) {
|
||||
writeVisibilityShare(xor(mask, share), node.getChildren().get(i), expression, out);
|
||||
} else {
|
||||
byte[] randomMask = new byte[share.length];
|
||||
random.nextBytes(randomMask);
|
||||
writeVisibilityShare(randomMask, node.getChildren().get(i), expression, out);
|
||||
xor(mask, randomMask);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OR:
|
||||
// Write the same share with each attribute share.
|
||||
for (Node child : node.getChildren()) {
|
||||
writeVisibilityShare(share, child, expression, out);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the given share based on the current visibility node.
|
||||
* <p>
|
||||
* This method must read all metadata that the corresponding call to {@link #writeVisibilityShare(byte[], Node, byte[], DataOutput)} wrote. This is necessary
|
||||
* regardless of whether the key retrieval is ongoing, successful, or failed.
|
||||
*
|
||||
* @param node
|
||||
* Visibility node to use in decrypting the share.
|
||||
* @param expression
|
||||
* Visibility expression.
|
||||
* @param in
|
||||
* Stream from which metadata is read.
|
||||
* @param skipDecryption
|
||||
* Tracks whether the call to this method is trying to regenerate the share.
|
||||
* <p>
|
||||
* This will be false if the share was already generated (i.e., from a different branch in an OR expression) or if there was an error in this branch
|
||||
* (i.e., lacked the attribute key to decrypt a different share of an AND expression).
|
||||
* @return The decrypted share, or null if it could not be obtained.
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
private byte[] readVisibilityShare(Node node, byte[] expression, DataInput in, boolean skipDecryption) throws IOException {
|
||||
byte[] share = null;
|
||||
|
||||
switch (node.getType()) {
|
||||
case TERM:
|
||||
// This is the only case we actually read from the stream. Decrypt the share with the attribute share.
|
||||
// The input format is "version || length || encrypted data"
|
||||
int version = WritableUtils.readVInt(in);
|
||||
byte[] encrypted = new byte[WritableUtils.readVInt(in)];
|
||||
in.readFully(encrypted);
|
||||
|
||||
if (!skipDecryption) {
|
||||
try {
|
||||
byte[] key = keys.getAttributeKey(new String(node.getTerm(expression).toArray(), VISIBILITY_CHARSET), config.keyId, version, config.keyLength);
|
||||
share = encryptor.decrypt(key, encrypted);
|
||||
} catch (IllegalKeyRequestException e) {
|
||||
// Swallow this error. The user does not have access to decrypt this sub-share, but it still may be possible for the user to decrypt another
|
||||
// sub-share that will give the same data. This error will be re-thrown if at the end of reading all shares the users does not have enough
|
||||
// data to reconstruct the original share.
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case AND:
|
||||
// Read random shares, with the final share being the original share xor'ed with each of the random shares.
|
||||
for (Node child : node.getChildren()) {
|
||||
byte[] mask = readVisibilityShare(child, expression, in, skipDecryption);
|
||||
|
||||
// A single failure means this whole AND is a failure.
|
||||
if (!skipDecryption) {
|
||||
if (mask == null) {
|
||||
share = null;
|
||||
skipDecryption = true;
|
||||
} else {
|
||||
if (share == null) {
|
||||
share = mask;
|
||||
} else {
|
||||
xor(share, mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OR:
|
||||
// Read the share from multiple possible attribute shares. Only one share is needed.
|
||||
for (Node child : node.getChildren()) {
|
||||
byte[] tempKey = readVisibilityShare(child, expression, in, skipDecryption);
|
||||
if (!skipDecryption && tempKey != null) {
|
||||
share = tempKey;
|
||||
skipDecryption = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
return share;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the field can be filtered server side.
|
||||
* <p>
|
||||
* A field can be filtered server side if two things hold:
|
||||
* <ol>
|
||||
* <li>It is encrypted deterministically.</li>
|
||||
* <li>All of this field encryptor's field sources are available for use.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param availableSources
|
||||
* Fields available during encryption to pull data from.
|
||||
* @return Whether it is possible to generate a server side encryption for this item.
|
||||
*/
|
||||
boolean canBeFilteredServerSide(Collection<EntryField> availableSources) {
|
||||
return config.valueEncryptor.isDeterministic() && availableSources.containsAll(config.sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the encrypted value created by this encryptor can be used to search for the given fields.
|
||||
* <p>
|
||||
* The fields can only be searched for if they are the same set as the sources for this field encryptor.
|
||||
*
|
||||
* @param fields
|
||||
* Fields that need to be searched.
|
||||
* @return Whether this field's encrypted value can be used to search for the given fields.
|
||||
*/
|
||||
boolean canSearchFor(Set<EntryField> fields) {
|
||||
return config.valueEncryptor.isDeterministic() && fields.equals(config.sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a value into a set of server side search terms. This method assumes that {@link #canBeFilteredServerSide(Collection)} was called, and that this
|
||||
* field is suitable for server side filtering.
|
||||
* <p>
|
||||
* This method will encrypt the data in the given key using ever version of that key available. For example, if key "ROW_KEY" has 3 versions, than this method
|
||||
* will encrypt the key with all 3 versions, resulting in 3 possible server side filter values for this encrypted field.
|
||||
*
|
||||
* @param key
|
||||
* Data used to create the filter.
|
||||
* @return Keys containing the data for the server side filters.
|
||||
*/
|
||||
List<byte[]> getServerSideFilterValues(MutableEntry key) {
|
||||
return getServerSideFilterValues(key, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a value into a set of server side search terms. This method assumes that {@link #canBeFilteredServerSide(Collection)} was called, and that this
|
||||
* field is suitable for server side filtering.
|
||||
* <p>
|
||||
* This method will encrypt the data in the given key using ever version of that key available. For example, if key "ROW_KEY" has 3 versions, than this method
|
||||
* will encrypt the key with all 3 versions, resulting in 3 possible server side filter values for this encrypted field.
|
||||
*
|
||||
* @param key
|
||||
* Data used to create the filter.
|
||||
* @param followingKey
|
||||
* Should the returned value be generated as in {@link Key#followingArray(byte[])}.
|
||||
* @return Keys containing the data for the server side filters.
|
||||
*/
|
||||
List<byte[]> getServerSideFilterValues(MutableEntry key, boolean followingKey) {
|
||||
List<byte[]> filterValues = new ArrayList<>();
|
||||
|
||||
for (KeyWithVersion keyData : keys.getKeys(config.keyId, config.keyLength)) {
|
||||
ByteArrayOutputStream ciphertextStream = new ByteArrayOutputStream();
|
||||
DataOutput ciphertextOut = new DataOutputStream(ciphertextStream);
|
||||
|
||||
try {
|
||||
WritableUtils.writeVInt(ciphertextOut, keyData.version);
|
||||
ciphertextOut.write(encryptor.encrypt(keyData.key, concatData(key)));
|
||||
if (followingKey) {
|
||||
ciphertextOut.writeByte(0);
|
||||
}
|
||||
} catch (IOException e) { // IO exceptions won't be thrown in practice as we are operating on in-memory streams.
|
||||
throw new EncryptionException(e);
|
||||
}
|
||||
|
||||
filterValues.add(ciphertextStream.toByteArray());
|
||||
}
|
||||
|
||||
return filterValues;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile.Section;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
|
||||
/**
|
||||
* Holds configuration data for encrypting an Accumulo field.
|
||||
* <p>
|
||||
* This class defines how data will be encrypted to a specific field. This includes defining which encryption scheme will be used, where the the data to be
|
||||
* encrypted is pulled from, and where it is to be written.
|
||||
*/
|
||||
public final class FieldEncryptorConfig {
|
||||
|
||||
/**
|
||||
* The set of fields that can be the destination of encrypted content in the key.
|
||||
*/
|
||||
static final List<EntryField> KEY_DESTINATION_FIELDS = ImmutableList.of(EntryField.ROW, EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER);
|
||||
|
||||
/**
|
||||
* Set of fields that can be sources for data when encrypting a field in the key.
|
||||
*/
|
||||
static final List<EntryField> KEY_SOURCE_FIELDS = ImmutableList.of(EntryField.ROW, EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER,
|
||||
EntryField.COLUMN_VISIBILITY);
|
||||
|
||||
/**
|
||||
* Set of fields that can be sources for data when encrypting the value.
|
||||
*/
|
||||
static final List<EntryField> VALUE_SOURCE_FIELDS = ImmutableList.of(EntryField.VALUE);
|
||||
|
||||
/**
|
||||
* The {@link ValueEncryptor} used to encrypt the field.
|
||||
*/
|
||||
final ValueEncryptor valueEncryptor;
|
||||
|
||||
/**
|
||||
* The provider to use when getting an instance of the {@link ValueEncryptor}.
|
||||
*/
|
||||
final String provider;
|
||||
|
||||
/**
|
||||
* Will this encryption encrypt the field using the visibility.
|
||||
*/
|
||||
final boolean encryptUsingVisibility;
|
||||
|
||||
/**
|
||||
* Id of the key that will be used for encryption.
|
||||
*/
|
||||
final String keyId;
|
||||
|
||||
/**
|
||||
* The length of keys to use for encryption.
|
||||
*/
|
||||
final int keyLength;
|
||||
|
||||
/**
|
||||
* The field that the encrypted data will be written to.
|
||||
*/
|
||||
final EntryField destination;
|
||||
|
||||
/**
|
||||
* The fields that plaintext data is drawn from.
|
||||
*/
|
||||
final SortedSet<EntryField> sources;
|
||||
|
||||
/**
|
||||
* Creates a configuration for an {@link FieldEncryptor}.
|
||||
*
|
||||
* @param valueEncryptor
|
||||
* The {@link ValueEncryptor} used to encrypt the field.
|
||||
* @param provider
|
||||
* The provider to use when getting an instance of the {@link ValueEncryptor}. If this is null, will search for the appropriate providers as defined
|
||||
* by the system.
|
||||
* @param encryptUsingVisibility
|
||||
* Will this encryption encrypt the field using the visibility.
|
||||
* @param keyId
|
||||
* Id of the key that will be used for encryption.
|
||||
* @param keyLength
|
||||
* The length of the key to use.
|
||||
* @param destination
|
||||
* The field where encrypted data will be written to by the encryptor.
|
||||
* @param sources
|
||||
* The fields that plaintext data is drawn from.
|
||||
*/
|
||||
public FieldEncryptorConfig(ValueEncryptor valueEncryptor, String provider, boolean encryptUsingVisibility, String keyId, int keyLength,
|
||||
EntryField destination, SortedSet<EntryField> sources) {
|
||||
checkArgument(valueEncryptor != null, "valueEncryptor is null");
|
||||
checkArgument(!encryptUsingVisibility || !valueEncryptor.isDeterministic(),
|
||||
"Cannot deterministically encrypt when encrypting fields using the visibility expression");
|
||||
checkArgument(!Strings.isNullOrEmpty(keyId), "keyId is null or empty");
|
||||
checkArgument(valueEncryptor.isValidKeyLength(keyLength), "invalid key length for the value encryptor");
|
||||
checkArgument(destination != null, "destination is null");
|
||||
checkArgument(KEY_DESTINATION_FIELDS.contains(destination) || destination == EntryField.VALUE, "invalid destination");
|
||||
checkArgument(sources != null, "sources is null");
|
||||
checkArgument(sources.size() > 0, "sources is empty");
|
||||
|
||||
if (KEY_SOURCE_FIELDS.contains(destination)) {
|
||||
for (EntryField source : sources) {
|
||||
checkArgument(KEY_SOURCE_FIELDS.contains(source), source.toString() + " cannot be encrypted into a key field");
|
||||
}
|
||||
} else {
|
||||
for (EntryField source : sources) {
|
||||
checkArgument(VALUE_SOURCE_FIELDS.contains(source), source.toString() + " cannot be encrypted into the value field");
|
||||
}
|
||||
}
|
||||
|
||||
this.valueEncryptor = valueEncryptor;
|
||||
this.provider = provider;
|
||||
this.encryptUsingVisibility = encryptUsingVisibility;
|
||||
this.keyLength = keyLength;
|
||||
this.keyId = keyId;
|
||||
this.destination = destination;
|
||||
this.sources = Collections.unmodifiableSortedSet(sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write's this FieldEncryptorBase configuration to the given INI.
|
||||
*
|
||||
* @param configIni
|
||||
* INI to write to.
|
||||
*/
|
||||
void write(Ini configIni) {
|
||||
Section section = configIni.add(destination.toString());
|
||||
|
||||
section.put("cipher", valueEncryptor.toString());
|
||||
section.put("provider", provider);
|
||||
section.put("useVisibility", encryptUsingVisibility);
|
||||
section.put("keyId", keyId);
|
||||
section.put("keyLength", Integer.toString(keyLength));
|
||||
section.put("sources", StringUtils.join(sources, ','));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ini4j.Profile.Section;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
|
||||
/**
|
||||
* Builder for configuration of field encryptor objects.
|
||||
*/
|
||||
public class FieldEncryptorConfigBuilder {
|
||||
|
||||
// For definitions of these values see FieldEncryptorConfig.
|
||||
private ValueEncryptor valueEncryptor;
|
||||
private String provider;
|
||||
private boolean encryptUsingVisibility;
|
||||
private int keyLength;
|
||||
private String keyId;
|
||||
private EntryField destination;
|
||||
private SortedSet<EntryField> sources = new TreeSet<>();
|
||||
|
||||
/**
|
||||
* Set the value encryptor.
|
||||
*
|
||||
* @param valueEncryptor
|
||||
* The {@link ValueEncryptor} used to encrypt the field.
|
||||
* @return Builder.
|
||||
*/
|
||||
public FieldEncryptorConfigBuilder setValueEncryptor(ValueEncryptor valueEncryptor) {
|
||||
this.valueEncryptor = valueEncryptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the provider to use.
|
||||
*
|
||||
* @param provider
|
||||
* The provider to use when getting an instance of the {@link ValueEncryptor}. If this is null, will search for the appropriate providers as defined
|
||||
* by the system.
|
||||
* @return Builder.
|
||||
*/
|
||||
public FieldEncryptorConfigBuilder setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whether to encrypt using the visibility.
|
||||
*
|
||||
* @param encryptUsingVisibility
|
||||
* Will this encryption encrypt the field using the visibility.
|
||||
* @return Builder.
|
||||
*/
|
||||
public FieldEncryptorConfigBuilder setEncryptUsingVisibility(boolean encryptUsingVisibility) {
|
||||
this.encryptUsingVisibility = encryptUsingVisibility;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the key length.
|
||||
*
|
||||
* @param keyLength
|
||||
* The length of the key to use.
|
||||
* @return Builder.
|
||||
*/
|
||||
public FieldEncryptorConfigBuilder setKeyLength(int keyLength) {
|
||||
this.keyLength = keyLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the key ID.
|
||||
*
|
||||
* @param keyId
|
||||
* Id of the key that will be used for encryption.
|
||||
* @return Builder.
|
||||
*/
|
||||
public FieldEncryptorConfigBuilder setKeyId(String keyId) {
|
||||
this.keyId = keyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination field.
|
||||
*
|
||||
* @param destination
|
||||
* The field where encrypted data will be written to by the encryptor.
|
||||
* @return Builder.
|
||||
*/
|
||||
public FieldEncryptorConfigBuilder setDestination(EntryField destination) {
|
||||
this.destination = destination;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a source to the list of sources for this encryptor.
|
||||
*
|
||||
* @param source
|
||||
* A field that will be encrypted and stored in the destination.
|
||||
* @return Builder.
|
||||
*/
|
||||
public FieldEncryptorConfigBuilder addSource(EntryField source) {
|
||||
this.sources.add(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sources.
|
||||
*
|
||||
* @param sources
|
||||
* The fields that will be encrypted and stored in the destination.
|
||||
* @return Builder.
|
||||
*/
|
||||
public FieldEncryptorConfigBuilder setSources(Collection<EntryField> sources) {
|
||||
this.sources.clear();
|
||||
if (sources != null) {
|
||||
this.sources.addAll(sources);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the configuration from a Reader.
|
||||
*
|
||||
* @param section
|
||||
* Ini section to read from.
|
||||
* @return BUilder
|
||||
*/
|
||||
FieldEncryptorConfigBuilder readFromIni(Section section) {
|
||||
ValueEncryptor valueEncryptor = ValueEncryptor.fromString(section.get("cipher"));
|
||||
|
||||
setValueEncryptor(valueEncryptor);
|
||||
setProvider(section.get("provider"));
|
||||
setEncryptUsingVisibility(section.containsKey("useVisibility") ? Boolean.parseBoolean(section.get("useVisibility")) : false);
|
||||
setKeyId(section.getOrDefault("keyId", valueEncryptor.toString()));
|
||||
setKeyLength(section.containsKey("keyLength") ? Integer.parseInt(section.get("keyLength")) : valueEncryptor.getDefaultKeyLength());
|
||||
setDestination(EntryField.fromString(section.getName()));
|
||||
|
||||
if (!section.containsKey("sources")) {
|
||||
addSource(EntryField.fromString(section.getName()));
|
||||
} else {
|
||||
for (String source : StringUtils.split(section.get("sources"), ',')) {
|
||||
addSource(EntryField.fromString(source.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link FieldEncryptorConfig}.
|
||||
*
|
||||
* @return Built config.
|
||||
*/
|
||||
public FieldEncryptorConfig build() {
|
||||
return new FieldEncryptorConfig(valueEncryptor, provider, encryptUsingVisibility, keyId, keyLength, destination, sources);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Defines the set of supported instantiations for {@link ValueEncryptorBase}.
|
||||
*/
|
||||
public enum ValueEncryptor {
|
||||
AES_SIV_DETERMINISTIC("AES_SIV_DETERMINISTIC", true, true, null), AES_CTR("AES_CTR", false, false, "AES/CTR/NoPadding"), AES_CFB("AES_CFB", false, false,
|
||||
"AES/CFB/NoPadding"), AES_CBC("AES_CBC", false, false, "AES/CBC/PKCS5Padding"), AES_OFB("AES_OFB", false, false, "AES/OFB/NoPadding"), AES_GCM("AES_GCM",
|
||||
false, false, "AES/GCM/NoPadding");
|
||||
|
||||
/**
|
||||
* Valid key sizes for generic AES.
|
||||
*/
|
||||
final static ImmutableList<Integer> VALID_KEY_LENGTHS = ImmutableList.of(16, 24, 32);
|
||||
|
||||
/**
|
||||
* Valid key sizes for AES algorithms that need both an aes and an IV-generation key.
|
||||
*/
|
||||
final static ImmutableList<Integer> VALID_DOUBLE_KEY_LENGTHS = ImmutableList.of(32, 48, 64);
|
||||
|
||||
/**
|
||||
* Set of ValueEncryptor names mapped to the ValueEncryptor enum object. Used to speed up name lookups.
|
||||
*/
|
||||
private static final Map<String,ValueEncryptor> encryptors;
|
||||
static {
|
||||
encryptors = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (ValueEncryptor encryptor : values()) {
|
||||
encryptors.put(encryptor.name, encryptor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the encryptor.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Whether the encryptor is deterministic in its encryption of ciphertext (i.e., same value produces the same encrypted ciphertext).
|
||||
*/
|
||||
private final boolean deterministic;
|
||||
|
||||
/**
|
||||
* Whether the encryptor needs two keys, an encryption and iv-generation key.
|
||||
*/
|
||||
private final boolean needsTwoKeys;
|
||||
|
||||
/**
|
||||
* Transformation used by this encryptor, or null if the encryptor's implementation does not rely on {@link javax.crypto.Cipher#getInstance(String, String)}.
|
||||
* <p>
|
||||
* The transformation defines the algorithm used, the mode it is used in, and any padding.
|
||||
*/
|
||||
private final String transformation;
|
||||
|
||||
/**
|
||||
* Create the value encryptor.
|
||||
*
|
||||
* @param name
|
||||
* Name of the encryptor.
|
||||
* @param deterministic
|
||||
* Whether the encryptor is deterministic in its encryption of ciphertext (i.e., same value produces the same encrypted ciphertext).
|
||||
* @param needsTwoKeys
|
||||
* Whether the encryptor needs two keys, an encryption and iv-generation key.
|
||||
* @param transformation
|
||||
* Transformation used by this encryptor, or null if the encryptor's implementation does not rely on
|
||||
* {@link javax.crypto.Cipher#getInstance(String, String)}.
|
||||
*/
|
||||
ValueEncryptor(String name, boolean deterministic, boolean needsTwoKeys, String transformation) {
|
||||
this.name = name;
|
||||
this.deterministic = deterministic;
|
||||
this.needsTwoKeys = needsTwoKeys;
|
||||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value encryptor from its {@link String} representation.
|
||||
* <p>
|
||||
* This method is paired with {@link #toString()}, and will parse strings generated by that method.
|
||||
*
|
||||
* @param value
|
||||
* Value to parse.
|
||||
* @return ValueEncryptor for the value.
|
||||
*/
|
||||
static ValueEncryptor fromString(String value) {
|
||||
ValueEncryptor encryptor = encryptors.get(value);
|
||||
if (encryptor == null) {
|
||||
throw new IllegalArgumentException("invalid name");
|
||||
}
|
||||
return encryptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this encryptor is deterministic.
|
||||
*
|
||||
* @return Whether this encryptor is deterministic.
|
||||
*/
|
||||
boolean isDeterministic() {
|
||||
return deterministic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default key length for this encryptor.
|
||||
*
|
||||
* @return The default key length of this encryptor.
|
||||
*/
|
||||
int getDefaultKeyLength() {
|
||||
return (needsTwoKeys ? VALID_DOUBLE_KEY_LENGTHS : VALID_KEY_LENGTHS).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the key length is valid for this encryptor.
|
||||
*
|
||||
* @param keyLength
|
||||
* Length of the key to check for validity.
|
||||
* @return Whether the given key length is valid for this encryptor.
|
||||
*/
|
||||
boolean isValidKeyLength(int keyLength) {
|
||||
return (needsTwoKeys ? VALID_DOUBLE_KEY_LENGTHS : VALID_KEY_LENGTHS).contains(keyLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new {@link ValueEncryptorBase} instance for the encryptor.
|
||||
*
|
||||
* @param provider
|
||||
* Provider to use when creating the instance.
|
||||
* @return The created instance.
|
||||
*/
|
||||
ValueEncryptorBase getInstance(String provider) {
|
||||
switch (this) {
|
||||
case AES_SIV_DETERMINISTIC:
|
||||
return new DeterministicSIVValueEncryptor();
|
||||
|
||||
case AES_CTR:
|
||||
case AES_CFB:
|
||||
case AES_CBC:
|
||||
case AES_OFB:
|
||||
case AES_GCM:
|
||||
return new AESValueEncryptor(transformation, provider);
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Defines the contract for classes that supports symmetric encryption and decryption of data.
|
||||
*/
|
||||
abstract class ValueEncryptorBase {
|
||||
|
||||
/**
|
||||
* Encrypt the given data with the given key.
|
||||
*
|
||||
* @param key
|
||||
* Key to encrypt with.
|
||||
* @param data
|
||||
* Data to encrypt.
|
||||
* @return The encrypted data, encoded with any metadata needed to decrypt the data.
|
||||
* @throws EncryptionException
|
||||
* Thrown if an error happens during encryption.
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
abstract byte[] encrypt(byte[] key, byte[] data) throws EncryptionException, IOException;
|
||||
|
||||
/**
|
||||
* Decrypt the given data with the given key.
|
||||
*
|
||||
* @param key
|
||||
* Key to decrypt with.
|
||||
* @param data
|
||||
* Data to decrypt, including the encoded metadata needed to decrypt the data.
|
||||
* @return The decrypted data.
|
||||
* @throws EncryptionException
|
||||
* Thrown if an error happens during encryption
|
||||
* @throws IOException
|
||||
* Not actually thrown.
|
||||
*/
|
||||
abstract byte[] decrypt(byte[] key, byte[] data) throws EncryptionException, IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.internal;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.EMPTY;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.accumulo.core.data.ColumnUpdate;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
|
||||
/**
|
||||
* Mutable container representing an Accumulo entry.
|
||||
* <p>
|
||||
* This class is not part of the PACE API, and should not be used.
|
||||
* </p>
|
||||
*/
|
||||
public final class MutableEntry implements Cloneable {
|
||||
|
||||
/**
|
||||
* The byte-based values in an entry.
|
||||
*/
|
||||
public byte[] row = EMPTY, colF = EMPTY, colQ = EMPTY, colVis = EMPTY, value = EMPTY;
|
||||
|
||||
/**
|
||||
* The timestamp for the entry.
|
||||
*/
|
||||
public long timestamp = 0L;
|
||||
|
||||
/**
|
||||
* Whether the entry has been deleted.
|
||||
*/
|
||||
public boolean delete = false;
|
||||
|
||||
/**
|
||||
* Creates an empty mutable entry.
|
||||
*/
|
||||
public MutableEntry() {}
|
||||
|
||||
/**
|
||||
* Creates a mutable entry for the given key.
|
||||
*
|
||||
* @param key
|
||||
* Key to wrap.
|
||||
*/
|
||||
public MutableEntry(Key key) {
|
||||
row = key.getRowData().getBackingArray();
|
||||
colF = key.getColumnFamilyData().getBackingArray();
|
||||
colQ = key.getColumnQualifierData().getBackingArray();
|
||||
colVis = key.getColumnVisibilityData().getBackingArray();
|
||||
timestamp = key.getTimestamp();
|
||||
delete = key.isDeleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mutable entry for the given entry.
|
||||
*
|
||||
* @param entry
|
||||
* Entry to wrap.
|
||||
*/
|
||||
public MutableEntry(Map.Entry<Key,Value> entry) {
|
||||
this(entry.getKey());
|
||||
value = entry.getValue().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mutable entry for the given update.
|
||||
*
|
||||
* @param row
|
||||
* The row that is being updated.
|
||||
* @param update
|
||||
* Update to wrap.
|
||||
*/
|
||||
public MutableEntry(byte[] row, ColumnUpdate update) {
|
||||
this.row = row;
|
||||
this.colF = update.getColumnFamily();
|
||||
this.colQ = update.getColumnQualifier();
|
||||
this.colVis = update.getColumnVisibility();
|
||||
this.timestamp = update.getTimestamp();
|
||||
this.delete = update.isDeleted();
|
||||
this.value = update.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the data in this class in a cannonical {@link Key}.
|
||||
*
|
||||
* @return An Accumulo key.
|
||||
*/
|
||||
public Key toKey() {
|
||||
return new Key(row, colF, colQ, colVis, timestamp, delete, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the data in this class in a cannonical {@literal Entry<Key,Value>}.
|
||||
*
|
||||
* @return An Accumulo entry.
|
||||
*/
|
||||
public Map.Entry<Key,Value> toEntry() {
|
||||
return new AbstractMap.SimpleImmutableEntry<>(toKey(), new Value(value, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this object.
|
||||
* <p>
|
||||
* Cloning this object will never throw an exception.
|
||||
*
|
||||
* @return Shallow clone of this object.
|
||||
*/
|
||||
public MutableEntry cloneEntry() {
|
||||
try {
|
||||
return (MutableEntry) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bytes for the given field.
|
||||
*
|
||||
* @param field
|
||||
* Field for which to retrieve data.
|
||||
* @return Requested bytes.
|
||||
*/
|
||||
public byte[] getBytes(EntryField field) {
|
||||
switch (field) {
|
||||
case ROW:
|
||||
return row;
|
||||
case COLUMN_FAMILY:
|
||||
return colF;
|
||||
case COLUMN_QUALIFIER:
|
||||
return colQ;
|
||||
case COLUMN_VISIBILITY:
|
||||
return colVis;
|
||||
case VALUE:
|
||||
return value;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid field");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bytes for the given field.
|
||||
*
|
||||
* @param field
|
||||
* Field for which to set data.
|
||||
* @param bytes
|
||||
* Bytes to set.
|
||||
*/
|
||||
public void setBytes(EntryField field, byte[] bytes) {
|
||||
switch (field) {
|
||||
case ROW:
|
||||
row = bytes;
|
||||
break;
|
||||
case COLUMN_FAMILY:
|
||||
colF = bytes;
|
||||
break;
|
||||
case COLUMN_QUALIFIER:
|
||||
colQ = bytes;
|
||||
break;
|
||||
case COLUMN_VISIBILITY:
|
||||
colVis = bytes;
|
||||
break;
|
||||
case VALUE:
|
||||
value = bytes;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid field");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Utility functions used by PACE.
|
||||
*/
|
||||
public final class Utils {
|
||||
|
||||
/**
|
||||
* The empty byte array.
|
||||
*/
|
||||
public static final byte[] EMPTY = new byte[0];
|
||||
|
||||
/**
|
||||
* Charset to use when changing column visibility bytes into a String and vice versa.
|
||||
*/
|
||||
public static final Charset VISIBILITY_CHARSET = StandardCharsets.US_ASCII;
|
||||
|
||||
/**
|
||||
* Static class.
|
||||
*/
|
||||
private Utils() {}
|
||||
|
||||
/**
|
||||
* XOR first and second, storing the result in first.
|
||||
*
|
||||
* @param first
|
||||
* First argument.
|
||||
* @param second
|
||||
* Second argument.
|
||||
* @return XOR'ed first argument.
|
||||
*/
|
||||
public static byte[] xor(byte[] first, byte[] second) {
|
||||
checkArgument(first != null, "first is null");
|
||||
checkArgument(second != null, "second is null");
|
||||
checkArgument(first.length == second.length, "first and second must be the same length");
|
||||
|
||||
for (int i = 0; i < first.length; i++) {
|
||||
first[i] = (byte) (first[i] ^ second[i]);
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Signature;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.accumulo.core.data.ArrayByteSequence;
|
||||
import org.apache.accumulo.core.data.ByteSequence;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility;
|
||||
import org.apache.accumulo.core.security.VisibilityEvaluator;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.hadoop.io.WritableUtils;
|
||||
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
import edu.mit.ll.pace.signature.SignatureConfig.Destination;
|
||||
import edu.mit.ll.pace.signature.SignatureKeyContainer.PrivateKeyWithId;
|
||||
|
||||
/**
|
||||
* Signs an Accumulo {@literal Entry<Key,Value>} based on the supplied configuration.
|
||||
*/
|
||||
public final class EntrySigner {
|
||||
|
||||
/**
|
||||
* Signature configuration.
|
||||
*/
|
||||
private final SignatureConfig config;
|
||||
|
||||
/**
|
||||
* Keys to use for signing and verifying.
|
||||
*/
|
||||
private final SignatureKeyContainer keys;
|
||||
|
||||
/**
|
||||
* Verifier for this instance.
|
||||
*/
|
||||
private Signature signer;
|
||||
|
||||
/**
|
||||
* The id of the signer.
|
||||
*/
|
||||
private byte[] signerId;
|
||||
|
||||
/**
|
||||
* Signer for this instance.
|
||||
*/
|
||||
private Signature verifier;
|
||||
|
||||
/**
|
||||
* Create an entry singer.
|
||||
*
|
||||
* @param config
|
||||
* Configuration for the signing.
|
||||
* @param keys
|
||||
* Container with the keys to use for signing.
|
||||
*/
|
||||
public EntrySigner(SignatureConfig config, SignatureKeyContainer keys) {
|
||||
checkArgument(config != null, "config is null");
|
||||
checkArgument(keys != null, "keys is null");
|
||||
|
||||
this.config = config;
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the given entry.
|
||||
*
|
||||
* @param entry
|
||||
* Entry to sign.
|
||||
* @return Signed update.
|
||||
* @throws SignatureException
|
||||
* Thrown in signature creation fails.
|
||||
*/
|
||||
public Entry<Key,Value> sign(Entry<Key,Value> entry, boolean hasTimestamp) {
|
||||
checkArgument(entry != null, "entry is null");
|
||||
checkArgument(!entry.getKey().isDeleted(), "cannot sign deleted entries");
|
||||
|
||||
// Defer initialization of the signing algorithm until needed.
|
||||
if (signer == null) {
|
||||
signer = config.algorithm.getInstance(config.provider);
|
||||
|
||||
try {
|
||||
PrivateKeyWithId keyData = keys.getSigningKey();
|
||||
signer.initSign(keyData.key);
|
||||
signerId = keyData.id;
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are writing to the visibility field, we will need to replace the empty visibility with the default visibility.
|
||||
MutableEntry wrapped = new MutableEntry(entry);
|
||||
if (config.destination == Destination.COLUMN_VISIBILITY && wrapped.colVis.length == 0) {
|
||||
wrapped.colVis = config.defaultVisibility;
|
||||
}
|
||||
|
||||
// Sign the entry.
|
||||
byte[] signature;
|
||||
try {
|
||||
signer.update(wrapped.row);
|
||||
signer.update(wrapped.colF);
|
||||
signer.update(wrapped.colQ);
|
||||
signer.update(wrapped.colVis);
|
||||
signer.update(hasTimestamp ? (byte) 0 : (byte) 1);
|
||||
if (hasTimestamp) {
|
||||
signer.update(Longs.toByteArray(wrapped.timestamp));
|
||||
}
|
||||
signer.update(wrapped.value);
|
||||
signature = signer.sign();
|
||||
} catch (java.security.SignatureException e) {
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
|
||||
// Create a stream with the signature data.
|
||||
try {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
DataOutput out = new DataOutputStream(stream);
|
||||
|
||||
WritableUtils.writeVInt(out, signerId.length);
|
||||
out.write(signerId);
|
||||
|
||||
out.writeBoolean(hasTimestamp);
|
||||
|
||||
WritableUtils.writeVInt(out, signature.length);
|
||||
out.write(signature);
|
||||
|
||||
switch (config.destination) {
|
||||
case VALUE:
|
||||
out.write(wrapped.value);
|
||||
wrapped.value = stream.toByteArray();
|
||||
break;
|
||||
|
||||
case COLUMN_VISIBILITY:
|
||||
wrapped.colVis = wrapVisibility(wrapped.colVis, stream.toByteArray());
|
||||
break;
|
||||
|
||||
case SEPARATE_TABLE:
|
||||
wrapped.value = stream.toByteArray();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
} catch (IOException e) { // IO exceptions won't be thrown in practice as we are operating on in-memory streams.
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
|
||||
return wrapped.toEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the given entry.
|
||||
*
|
||||
* @param entry
|
||||
* entry with signature to verify.
|
||||
* @return Verified entry with signature data removed.
|
||||
* @throws SignatureException
|
||||
* Thrown if signature verification fails.
|
||||
*/
|
||||
public Entry<Key,Value> verify(Entry<Key,Value> entry) {
|
||||
return verify(entry, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the given entry.
|
||||
*
|
||||
* @param entry
|
||||
* entry to verify, potentially with the signature too.
|
||||
* @param signedEntry
|
||||
* If the signature is stored in an entry in another table, then this entry is that signature entry, otherwise it is null.
|
||||
* @return Verified entry, with signature data removed as necessary.
|
||||
* @throws SignatureException
|
||||
* Thrown if signature verification fails.
|
||||
*/
|
||||
public Entry<Key,Value> verify(Entry<Key,Value> entry, Entry<Key,Value> signedEntry) {
|
||||
checkArgument(entry != null, "entry is null");
|
||||
checkArgument(!entry.getKey().isDeleted(), "cannot verify deleted entries");
|
||||
|
||||
if (config.destination == SignatureConfig.Destination.SEPARATE_TABLE) {
|
||||
checkArgument(signedEntry != null, "signature is in a separate table, but signature entry is null");
|
||||
} else {
|
||||
checkArgument(signedEntry == null, "signature is not in a separate table, but separate signature entry given");
|
||||
}
|
||||
|
||||
if (verifier == null) {
|
||||
verifier = config.algorithm.getInstance(config.provider);
|
||||
}
|
||||
|
||||
MutableEntry wrapped = new MutableEntry(entry);
|
||||
ByteArrayInputStream stream;
|
||||
|
||||
switch (config.destination) {
|
||||
case VALUE:
|
||||
stream = new ByteArrayInputStream(wrapped.value);
|
||||
break;
|
||||
|
||||
case COLUMN_VISIBILITY:
|
||||
Pair<byte[],byte[]> signatureData = unwrapVisibility(new ColumnVisibility(wrapped.colVis));
|
||||
wrapped.colVis = signatureData.getLeft();
|
||||
stream = new ByteArrayInputStream(signatureData.getRight());
|
||||
break;
|
||||
|
||||
case SEPARATE_TABLE:
|
||||
stream = new ByteArrayInputStream(signedEntry.getValue().get());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// Read the signature from the stream.
|
||||
byte[] signature;
|
||||
boolean hasTimestamp;
|
||||
try {
|
||||
DataInput in = new DataInputStream(stream);
|
||||
byte[] signerId = new byte[WritableUtils.readVInt(in)];
|
||||
in.readFully(signerId);
|
||||
verifier.initVerify(keys.getVerifyingKey(signerId));
|
||||
|
||||
hasTimestamp = in.readBoolean();
|
||||
|
||||
signature = new byte[WritableUtils.readVInt(in)];
|
||||
in.readFully(signature);
|
||||
|
||||
// If we wrote to the value, extract the original value.
|
||||
if (config.destination == Destination.VALUE) {
|
||||
wrapped.value = new byte[stream.available()];
|
||||
in.readFully(wrapped.value);
|
||||
}
|
||||
} catch (IOException | InvalidKeyException e) { // IO exceptions won't be thrown in practice as we are operating on in-memory streams.
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
|
||||
// Generate and verify the signature.
|
||||
try {
|
||||
verifier.update(wrapped.row);
|
||||
verifier.update(wrapped.colF);
|
||||
verifier.update(wrapped.colQ);
|
||||
verifier.update(wrapped.colVis);
|
||||
verifier.update(hasTimestamp ? (byte) 0 : (byte) 1);
|
||||
if (hasTimestamp) {
|
||||
verifier.update(Longs.toByteArray(wrapped.timestamp));
|
||||
}
|
||||
verifier.update(wrapped.value);
|
||||
|
||||
if (!verifier.verify(signature)) {
|
||||
throw new SignatureException("invalid signature found");
|
||||
}
|
||||
} catch (java.security.SignatureException e) {
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
|
||||
return wrapped.toEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the visibility field.
|
||||
* <p>
|
||||
* The resulting field will have the value {@literal colVis = (<originalColVis>)|data}. If the original column visibility value is empty, then the deafult
|
||||
* visibility value will be used in place of originalColVis.
|
||||
*
|
||||
* @param visibility
|
||||
* The visibility data being wrapped.
|
||||
* @param data
|
||||
* data to wrap into visibility.
|
||||
* @return Wrapped visibility.
|
||||
*/
|
||||
private static byte[] wrapVisibility(byte[] visibility, byte[] data) throws IOException {
|
||||
ByteArrayOutputStream colVisStream = new ByteArrayOutputStream();
|
||||
DataOutput colVisOut = new DataOutputStream(colVisStream);
|
||||
|
||||
colVisOut.writeByte((byte) '(');
|
||||
colVisOut.write(visibility);
|
||||
colVisOut.writeByte((byte) ')');
|
||||
colVisOut.writeByte((byte) '|');
|
||||
colVisOut.write(VisibilityEvaluator.escape(data, true));
|
||||
|
||||
return colVisStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap the visibility field.
|
||||
* <p>
|
||||
* Assumes the visibility field had previously been wrapped by a call to {@link #wrapVisibility(byte[], byte[])}. If the visibility is not in the form
|
||||
*
|
||||
*
|
||||
* @param visibility
|
||||
* The visibility data.
|
||||
* @return The unwrapped visibility expression and data.
|
||||
*/
|
||||
private static Pair<byte[],byte[]> unwrapVisibility(ColumnVisibility visibility) {
|
||||
ColumnVisibility.Node node = visibility.getParseTree();
|
||||
|
||||
if (node.getType() != ColumnVisibility.NodeType.OR || node.getChildren().size() != 2) {
|
||||
throw new SignatureException("Invalid signature in the column visibility");
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
ColumnVisibility.stringify(node.getChildren().get(0), visibility.getExpression(), builder);
|
||||
byte[] visibilityData = builder.toString().getBytes(VISIBILITY_CHARSET);
|
||||
|
||||
byte[] wrappedData = unescape(node.getChildren().get(1).getTerm(visibility.getExpression())).toArray();
|
||||
return Pair.of(visibilityData, wrappedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescape a visibility term.
|
||||
* <p>
|
||||
* Pulled from {@link VisibilityEvaluator} (line 49). This method is not public and could not be used directly.
|
||||
*
|
||||
* @param auth
|
||||
* Term to transform.
|
||||
* @return The unescaped term.
|
||||
*/
|
||||
private static ByteSequence unescape(ByteSequence auth) {
|
||||
int escapeCharCount = 0;
|
||||
for (int i = 0; i < auth.length(); i++) {
|
||||
byte b = auth.byteAt(i);
|
||||
if (b == '"' || b == '\\') {
|
||||
escapeCharCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (escapeCharCount > 0) {
|
||||
if (escapeCharCount % 2 == 1) {
|
||||
throw new IllegalArgumentException("Illegal escape sequence in auth : " + auth);
|
||||
}
|
||||
|
||||
byte[] unescapedCopy = new byte[auth.length() - escapeCharCount / 2];
|
||||
int pos = 0;
|
||||
for (int i = 0; i < auth.length(); i++) {
|
||||
byte b = auth.byteAt(i);
|
||||
if (b == '\\') {
|
||||
i++;
|
||||
b = auth.byteAt(i);
|
||||
if (b != '"' && b != '\\') {
|
||||
throw new IllegalArgumentException("Illegal escape sequence in auth : " + auth);
|
||||
}
|
||||
} else if (b == '"') {
|
||||
// should only see quote after a slash
|
||||
throw new IllegalArgumentException("Illegal escape sequence in auth : " + auth);
|
||||
}
|
||||
|
||||
unescapedCopy[pos++] = b;
|
||||
}
|
||||
|
||||
return new ArrayByteSequence(unescapedCopy);
|
||||
} else {
|
||||
return auth;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile.Section;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
/**
|
||||
* Configuration for signing entries.
|
||||
*/
|
||||
public final class SignatureConfig {
|
||||
|
||||
/**
|
||||
* INI section to write data to.
|
||||
*/
|
||||
static final String SECTION_NAME = "Signature";
|
||||
|
||||
/**
|
||||
* Where the signature should be written.
|
||||
*/
|
||||
public enum Destination {
|
||||
VALUE("value"), COLUMN_VISIBILITY("colVis"), SEPARATE_TABLE("table");
|
||||
|
||||
/**
|
||||
* Set of EntryField names mapped to the EntryField enum object. Used to speed up name lookups.
|
||||
*/
|
||||
private static final Map<String,Destination> destinations;
|
||||
static {
|
||||
destinations = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (Destination destination : values()) {
|
||||
destinations.put(destination.name, destination);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A string representation of the Destination.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Create an Destination with the given name.
|
||||
*
|
||||
* @param name
|
||||
* Name of the entry field. Used in the configuration INI.
|
||||
*/
|
||||
Destination(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination from its {@link String} representation.
|
||||
* <p>
|
||||
* This method is paired with {@link #toString()}, and will parse strings generated by that method.
|
||||
*
|
||||
* @param value
|
||||
* Value to parse.
|
||||
* @return Destination for the value.
|
||||
*/
|
||||
static Destination fromString(String value) {
|
||||
Destination destination = destinations.get(value);
|
||||
if (destination == null) {
|
||||
throw new IllegalArgumentException("invalid name");
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The algorithm to use to sign entries.
|
||||
*/
|
||||
final ValueSigner algorithm;
|
||||
|
||||
/**
|
||||
* The provider to use for creating the {@link java.security.Signature}.
|
||||
*/
|
||||
final String provider;
|
||||
|
||||
/**
|
||||
* The destination where the signature is written to or read from.
|
||||
*/
|
||||
final Destination destination;
|
||||
|
||||
/**
|
||||
* The table that will store the signatures.
|
||||
*/
|
||||
final String destinationTable;
|
||||
|
||||
/**
|
||||
* Default visibility to use when wrapping data in the column visibility field.
|
||||
*/
|
||||
final byte[] defaultVisibility;
|
||||
|
||||
/**
|
||||
* Create a configuration for signing entries.
|
||||
*
|
||||
* @param signer
|
||||
* The algorithm to use to sign entries.
|
||||
* @param provider
|
||||
* The provider to use for creating the {@link java.security.Signature}.
|
||||
* @param destination
|
||||
* The destination where the signature is written to or read from.
|
||||
* @param destinationTable
|
||||
* The table that will store the signatures.
|
||||
* @param defaultVisibility
|
||||
* Default visibility to use when wrapping signature in a visibility field.
|
||||
*/
|
||||
SignatureConfig(ValueSigner signer, String provider, Destination destination, String destinationTable, byte[] defaultVisibility) {
|
||||
checkArgument(signer != null, "signer is null");
|
||||
checkArgument(destination != null, "destination is null");
|
||||
|
||||
if (destination == Destination.SEPARATE_TABLE) {
|
||||
checkArgument(!Strings.isNullOrEmpty(destinationTable), "destination table is null or empty");
|
||||
} else {
|
||||
checkArgument(destinationTable == null, "destination table is not null, but not needed");
|
||||
}
|
||||
|
||||
if (destination == Destination.COLUMN_VISIBILITY) {
|
||||
checkArgument(defaultVisibility != null && defaultVisibility.length != 0, "defaultVisibility is null or empty, but needed");
|
||||
} else {
|
||||
checkArgument(defaultVisibility == null, "default is not null, but should be");
|
||||
}
|
||||
|
||||
this.algorithm = signer;
|
||||
this.provider = provider;
|
||||
this.destination = destination;
|
||||
this.destinationTable = destinationTable;
|
||||
this.defaultVisibility = defaultVisibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the object to a Writer.
|
||||
*
|
||||
* @param out
|
||||
* Stream to write object out to.
|
||||
*/
|
||||
public void write(Writer out) throws IOException {
|
||||
Ini configIni = new Ini();
|
||||
Section section = configIni.add(SECTION_NAME);
|
||||
|
||||
section.put("algorithm", algorithm.toString());
|
||||
section.put("provider", provider);
|
||||
section.put("destination", destination.toString());
|
||||
if (destinationTable != null) {
|
||||
section.put("table", destinationTable);
|
||||
}
|
||||
if (defaultVisibility != null) {
|
||||
section.put("defaultVisibility", new String(defaultVisibility, VISIBILITY_CHARSET));
|
||||
}
|
||||
|
||||
configIni.store(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this config write to a separate signature table.
|
||||
*/
|
||||
public boolean isSignatureInSeparateTable() {
|
||||
return this.destination == Destination.SEPARATE_TABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the algorithm for this signer.
|
||||
* <p>
|
||||
* The key supplied to the {@link EntrySigner} must match this algorithm.
|
||||
*
|
||||
* @return Algorithm Algorithm defined by this config.
|
||||
*/
|
||||
public ValueSigner getAlgorithm() {
|
||||
return this.algorithm;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile.Section;
|
||||
|
||||
import edu.mit.ll.pace.signature.SignatureConfig.Destination;
|
||||
|
||||
/**
|
||||
* Builder for configuration of signer objects.
|
||||
*/
|
||||
public class SignatureConfigBuilder {
|
||||
|
||||
// Variables from SignatureConfig.
|
||||
private ValueSigner signer;
|
||||
private String provider;
|
||||
private Destination destination;
|
||||
private String destinationTable;
|
||||
private byte[] defaultVisibility;
|
||||
|
||||
/**
|
||||
* Set the algorithm to use to sign entries.
|
||||
*
|
||||
* @param signer
|
||||
* The algorithm to use to sign entries.
|
||||
* @return Builder.
|
||||
*/
|
||||
public SignatureConfigBuilder setSigner(ValueSigner signer) {
|
||||
this.signer = signer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the provider to use when creating the signature.
|
||||
*
|
||||
* @param provider
|
||||
* The provider to use for creating the {@link java.security.Signature}. If null, will allow the system to select the appropriate provider.
|
||||
* @return Builder.
|
||||
*/
|
||||
public SignatureConfigBuilder setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination for the signature.
|
||||
*
|
||||
* @param destination
|
||||
* The destination where the signature is written to or read from.
|
||||
* @return Builder.
|
||||
*/
|
||||
public SignatureConfigBuilder setDestination(Destination destination) {
|
||||
this.destination = destination;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination table.
|
||||
*
|
||||
* @param destinationTable
|
||||
* The table that will store the signatures.
|
||||
* @return Builder.
|
||||
*/
|
||||
public SignatureConfigBuilder setDestinationTable(String destinationTable) {
|
||||
this.destinationTable = destinationTable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default visibility.
|
||||
*
|
||||
* @param defaultVisibility
|
||||
* Default visibility to use when wrapping data in the column visibility field.
|
||||
* @return Builder.
|
||||
*/
|
||||
public SignatureConfigBuilder setDefaultVisibility(byte[] defaultVisibility) {
|
||||
this.defaultVisibility = defaultVisibility;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the configuration from a Reader.
|
||||
*
|
||||
* @param in
|
||||
* Stream to read from.
|
||||
* @return builder.
|
||||
*/
|
||||
public SignatureConfigBuilder readFromFile(Reader in) throws IOException {
|
||||
Ini configIni = new Ini(in);
|
||||
Section section = configIni.get(SignatureConfig.SECTION_NAME);
|
||||
|
||||
setSigner(ValueSigner.fromString(section.get("algorithm")));
|
||||
setDestination(section.containsKey("destination") ? Destination.fromString(section.get("destination")) : Destination.VALUE);
|
||||
setProvider(section.get("provider")).setDestinationTable(section.get("table"));
|
||||
setDefaultVisibility(section.containsKey("defaultVisibility") ? section.get("defaultVisibility").getBytes(VISIBILITY_CHARSET) : null);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the signature config.
|
||||
*
|
||||
* @return The build signature config.
|
||||
*/
|
||||
public SignatureConfig build() {
|
||||
return new SignatureConfig(signer, provider, destination, destinationTable, defaultVisibility);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
/**
|
||||
* Runtime wrapper for exceptions that occur during signing.
|
||||
*/
|
||||
public class SignatureException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 3546537933578657569L;
|
||||
|
||||
/**
|
||||
* Constructs a new signature exception with null as its detail message.
|
||||
*/
|
||||
public SignatureException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new signature exception with the specified detail message.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later retrieval by the {@link Throwable#getMessage()} method.
|
||||
*/
|
||||
public SignatureException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new signature exception with the specified cause and a detail message of (cause==null ? null : cause.toString()) (which typically contains the
|
||||
* class and detail message of cause). This constructor is useful for runtime exceptions that are little more than wrappers for other throwables.
|
||||
*
|
||||
* @param cause
|
||||
* the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). (A null value is permitted, and indicates that the
|
||||
* cause is nonexistent or unknown.)
|
||||
*/
|
||||
public SignatureException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new signature exception with the specified detail message and cause.
|
||||
* <p>
|
||||
* Note that the detail message associated with cause is not automatically incorporated in this runtime exception's detail message.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later retrieval by the {@link Throwable#getMessage()} method.
|
||||
* @param cause
|
||||
* the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). (A null value is permitted, and indicates that the
|
||||
* cause is nonexistent or unknown.)
|
||||
*/
|
||||
public SignatureException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import edu.mit.ll.pace.IllegalKeyRequestException;
|
||||
|
||||
/**
|
||||
* Interface for the key container.
|
||||
*/
|
||||
public interface SignatureKeyContainer {
|
||||
|
||||
/**
|
||||
* Private key with an id.
|
||||
*/
|
||||
final class PrivateKeyWithId {
|
||||
|
||||
/**
|
||||
* Private key.
|
||||
*/
|
||||
public final PrivateKey key;
|
||||
|
||||
/**
|
||||
* Id associated with the private key.
|
||||
*/
|
||||
public final byte[] id;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param privateKey
|
||||
* Private key.
|
||||
* @param id
|
||||
* Id.
|
||||
*/
|
||||
public PrivateKeyWithId(PrivateKey privateKey, byte[] id) {
|
||||
checkArgument(privateKey != null, "key is null");
|
||||
checkArgument(id != null, "id ßis null");
|
||||
checkArgument(id.length != 0, "id is empty");
|
||||
|
||||
this.key = privateKey;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (null == obj || !(obj instanceof PrivateKeyWithId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PrivateKeyWithId other = (PrivateKeyWithId) obj;
|
||||
return key.equals(other.key) && Arrays.equals(id, other.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signing key to use in signing entries.
|
||||
*
|
||||
* @return The signing key and an identifier used to retrieve the appropriate verifier key.
|
||||
* @throws IllegalKeyRequestException
|
||||
* User lacks the permissions to obtain the desired key.
|
||||
*/
|
||||
PrivateKeyWithId getSigningKey();
|
||||
|
||||
/**
|
||||
* Get the verifier key for the given signature key.
|
||||
*
|
||||
* @param id
|
||||
* Id of the signing key used to sign the entry.
|
||||
* @return The verifier key to use to verify data signed by the identified signing key.
|
||||
* @throws IllegalKeyRequestException
|
||||
* User lacks the permissions to obtain the desired key.
|
||||
*/
|
||||
PublicKey getVerifyingKey(byte[] id);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchScanner;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.client.IteratorSetting.Column;
|
||||
import org.apache.accumulo.core.client.TableNotFoundException;
|
||||
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.hadoop.io.Text;
|
||||
|
||||
import edu.mit.ll.pace.ItemProcessingIterator;
|
||||
|
||||
/**
|
||||
* Scanner that checks signatures on entries.
|
||||
*/
|
||||
public final class SignedBatchScanner implements BatchScanner {
|
||||
|
||||
/**
|
||||
* The verifier to use.
|
||||
*/
|
||||
private final EntrySigner verifier;
|
||||
|
||||
/**
|
||||
* The underlying scanner used to retrieve data from Accumulo.
|
||||
*/
|
||||
private final BatchScanner valueScanner;
|
||||
|
||||
/**
|
||||
* The scanner to use to retrieve signatures.
|
||||
*/
|
||||
private final BatchScanner signatureScanner;
|
||||
|
||||
/**
|
||||
* Create an encrypted batch scanner.
|
||||
*
|
||||
* @param connector
|
||||
* The connector for the Accumulo instance.
|
||||
* @param tableName
|
||||
* Name of the table to write to.
|
||||
* @param authorizations
|
||||
* The authorizations this user has for querying Accumulo.
|
||||
* @param numQueryThreads
|
||||
* Maximimum number of query threads to use for this scanner.
|
||||
* @param signatureConfig
|
||||
* Configuration for the decryption.
|
||||
* @param keys
|
||||
* Container with the keys to use for decryption.
|
||||
*/
|
||||
public SignedBatchScanner(Connector connector, String tableName, Authorizations authorizations, int numQueryThreads, SignatureConfig signatureConfig,
|
||||
SignatureKeyContainer keys) throws TableNotFoundException {
|
||||
checkArgument(connector != null, "connection is null");
|
||||
checkArgument(tableName != null, "tableName is null");
|
||||
checkArgument(authorizations != null, "authorizations is null");
|
||||
checkArgument(signatureConfig != null, "config is null");
|
||||
checkArgument(keys != null, "keys is null");
|
||||
|
||||
this.valueScanner = connector.createBatchScanner(tableName, authorizations, numQueryThreads);
|
||||
this.verifier = new EntrySigner(signatureConfig, keys);
|
||||
|
||||
if (signatureConfig.destination == SignatureConfig.Destination.SEPARATE_TABLE) {
|
||||
this.signatureScanner = connector.createBatchScanner(signatureConfig.destinationTable, authorizations, numQueryThreads);
|
||||
} else {
|
||||
this.signatureScanner = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Returns a {@link edu.mit.ll.pace.ItemProcessingIterator}. This can be used to get the unprocessed items, allowing them to be deleted by a regular accumulo
|
||||
* writer.
|
||||
*/
|
||||
@Override
|
||||
public ItemProcessingIterator<Entry<Key,Value>> iterator() {
|
||||
if (signatureScanner != null) {
|
||||
return new SignedExternalScannerIterator(valueScanner.iterator(), signatureScanner.iterator(), verifier, false);
|
||||
} else {
|
||||
return new SignedInlineScannerIterator(valueScanner.iterator(), verifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This iterator is applied to both the table and the signature table. Changing the value will also affect the signature.
|
||||
*/
|
||||
@Override
|
||||
public void addScanIterator(IteratorSetting cfg) {
|
||||
valueScanner.addScanIterator(cfg);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.addScanIterator(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearColumns() {
|
||||
valueScanner.clearColumns();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.clearColumns();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearScanIterators() {
|
||||
valueScanner.clearScanIterators();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.clearScanIterators();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRanges(Collection<Range> collection) {
|
||||
valueScanner.setRanges(collection);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setRanges(collection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
valueScanner.close();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumn(Column column) {
|
||||
valueScanner.fetchColumn(column);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.fetchColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumn(Text colFam, Text colQual) {
|
||||
valueScanner.fetchColumn(colFam, colQual);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.fetchColumn(colFam, colQual);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumnFamily(Text col) {
|
||||
valueScanner.fetchColumnFamily(col);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.fetchColumnFamily(col);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authorizations getAuthorizations() {
|
||||
return valueScanner.getAuthorizations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSamplerConfiguration(SamplerConfiguration samplerConfiguration) {
|
||||
valueScanner.setSamplerConfiguration(samplerConfiguration);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setSamplerConfiguration(samplerConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamplerConfiguration getSamplerConfiguration() {
|
||||
return valueScanner.getSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSamplerConfiguration() {
|
||||
valueScanner.clearSamplerConfiguration();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.clearSamplerConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchTimeout(long l, TimeUnit timeUnit) {
|
||||
valueScanner.setBatchTimeout(l, timeUnit);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setBatchTimeout(l, timeUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBatchTimeout(TimeUnit timeUnit) {
|
||||
return valueScanner.getBatchTimeout(timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClassLoaderContext(String s) {
|
||||
valueScanner.setClassLoaderContext(s);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setClassLoaderContext(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearClassLoaderContext() {
|
||||
valueScanner.clearClassLoaderContext();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.clearClassLoaderContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassLoaderContext() {
|
||||
return valueScanner.getClassLoaderContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeout(TimeUnit timeUnit) {
|
||||
return valueScanner.getTimeout(timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScanIterator(String iteratorName) {
|
||||
valueScanner.removeScanIterator(iteratorName);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.removeScanIterator(iteratorName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScanIteratorOption(String iteratorName, String key, String value) {
|
||||
valueScanner.updateScanIteratorOption(iteratorName, key, value);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.updateScanIteratorOption(iteratorName, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeout(long timeout, TimeUnit timeUnit) {
|
||||
valueScanner.setTimeout(timeout, timeUnit);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setTimeout(timeout, timeUnit);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchWriter;
|
||||
import org.apache.accumulo.core.client.BatchWriterConfig;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.MutationsRejectedException;
|
||||
import org.apache.accumulo.core.client.TableNotFoundException;
|
||||
import org.apache.accumulo.core.data.ColumnUpdate;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Mutation;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility;
|
||||
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
import edu.mit.ll.pace.signature.SignatureConfig.Destination;
|
||||
|
||||
/**
|
||||
* Writes signed entries to Accumulo.
|
||||
*/
|
||||
public final class SignedBatchWriter implements BatchWriter {
|
||||
|
||||
/**
|
||||
* A {@link BatchWriter} to use to write the data to Accumulo.
|
||||
*/
|
||||
private final BatchWriter tableWriter;
|
||||
|
||||
/**
|
||||
* A {@link BatchWriter} to use to write the data to Accumulo when signatures are stored in a separate table.
|
||||
*/
|
||||
private final BatchWriter signatureTableWriter;
|
||||
|
||||
/**
|
||||
* The {@link EntrySigner} that will handle signing for this instance.
|
||||
*/
|
||||
private final EntrySigner signer;
|
||||
|
||||
/**
|
||||
* The {@link EntrySigner} that will handle signing for this instance.
|
||||
*/
|
||||
private final SignatureConfig signatureConfig;
|
||||
|
||||
/**
|
||||
* Create an signed batch tableWriter.
|
||||
*
|
||||
* @param connector
|
||||
* The connector for the Accumulo instance.
|
||||
* @param tableName
|
||||
* Name of the table to write to.
|
||||
* @param batchConfig
|
||||
* Configuration for a {@link BatchWriter}.
|
||||
* @param signatureConfig
|
||||
* Configuration for the signatures.
|
||||
* @param keys
|
||||
* Container with the keys to use for signatures.
|
||||
*/
|
||||
public SignedBatchWriter(Connector connector, String tableName, BatchWriterConfig batchConfig, SignatureConfig signatureConfig, SignatureKeyContainer keys)
|
||||
throws TableNotFoundException {
|
||||
checkArgument(connector != null, "connector is null");
|
||||
checkArgument(tableName != null, "tableName is null");
|
||||
checkArgument(signatureConfig != null, "signatureConfig is null");
|
||||
checkArgument(keys != null, "keys is null");
|
||||
|
||||
this.tableWriter = connector.createBatchWriter(tableName, batchConfig);
|
||||
this.signer = new EntrySigner(signatureConfig, keys);
|
||||
this.signatureConfig = signatureConfig;
|
||||
|
||||
if (signatureConfig.destination == SignatureConfig.Destination.SEPARATE_TABLE) {
|
||||
this.signatureTableWriter = connector.createBatchWriter(signatureConfig.destinationTable, batchConfig);
|
||||
} else {
|
||||
this.signatureTableWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the given mutation and then write it to Accumulo.
|
||||
*
|
||||
* @param mutation
|
||||
* The mutation to sign.
|
||||
*/
|
||||
@Override
|
||||
public void addMutation(Mutation mutation) throws MutationsRejectedException {
|
||||
Mutation signedMutation = new Mutation(mutation.getRow());
|
||||
|
||||
// Sign the entries.
|
||||
for (ColumnUpdate update : mutation.getUpdates()) {
|
||||
if (update.isDeleted()) {
|
||||
if (signatureConfig.destination == Destination.COLUMN_VISIBILITY) {
|
||||
throw new IllegalArgumentException("cannot delete entries when the signature is stored in the column visibility");
|
||||
}
|
||||
|
||||
if (update.hasTimestamp()) {
|
||||
signedMutation.putDelete(update.getColumnFamily(), update.getColumnQualifier(), new ColumnVisibility(update.getColumnVisibility()),
|
||||
update.getTimestamp());
|
||||
} else {
|
||||
signedMutation.putDelete(update.getColumnFamily(), update.getColumnQualifier(), new ColumnVisibility(update.getColumnVisibility()));
|
||||
}
|
||||
} else {
|
||||
Entry<Key,Value> signedEntry = signer.sign(new MutableEntry(mutation.getRow(), update).toEntry(), update.hasTimestamp());
|
||||
Key signedKey = signedEntry.getKey();
|
||||
|
||||
if (update.hasTimestamp()) {
|
||||
signedMutation.put(signedKey.getColumnFamily(), signedKey.getColumnQualifier(), signedKey.getColumnVisibilityParsed(), signedKey.getTimestamp(),
|
||||
signedEntry.getValue());
|
||||
} else {
|
||||
signedMutation.put(signedKey.getColumnFamily(), signedKey.getColumnQualifier(), signedKey.getColumnVisibilityParsed(), signedEntry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the signed mutations.
|
||||
if (signatureTableWriter != null) {
|
||||
tableWriter.addMutation(mutation);
|
||||
signatureTableWriter.addMutation(signedMutation);
|
||||
} else {
|
||||
tableWriter.addMutation(signedMutation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMutations(Iterable<Mutation> iterable) throws MutationsRejectedException {
|
||||
for (Mutation mutation : iterable) {
|
||||
addMutation(mutation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws MutationsRejectedException {
|
||||
tableWriter.flush();
|
||||
if (signatureTableWriter != null) {
|
||||
signatureTableWriter.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws MutationsRejectedException {
|
||||
tableWriter.close();
|
||||
if (signatureTableWriter != null) {
|
||||
signatureTableWriter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.PartialKey;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
|
||||
import edu.mit.ll.pace.ItemProcessingIterator;
|
||||
|
||||
/**
|
||||
* Read signed Accumulo entries.
|
||||
*/
|
||||
final class SignedExternalScannerIterator implements ItemProcessingIterator<Entry<Key,Value>> {
|
||||
|
||||
/**
|
||||
* The iterator with the Accumulo data.
|
||||
*/
|
||||
private final Iterator<Entry<Key,Value>> valueIterator;
|
||||
|
||||
/**
|
||||
* The iterator with the signature data.
|
||||
*/
|
||||
private final Iterator<Entry<Key,Value>> signatureIterator;
|
||||
|
||||
/**
|
||||
* The verifier to use.
|
||||
*/
|
||||
private final EntrySigner verifier;
|
||||
|
||||
/**
|
||||
* Whether the signatures are in the same order as the values.
|
||||
*/
|
||||
private final boolean inOrder;
|
||||
|
||||
/**
|
||||
* If we are processing out of order, then this map will store signatures that we have consumed, but for which the appropriate value is yet to be consumed.
|
||||
*/
|
||||
private final SortedMap<Key,Entry<Key,Value>> bufferedSignatures = new TreeMap<>();
|
||||
|
||||
/**
|
||||
* Keep track of the encrypted entry that resulted in
|
||||
*/
|
||||
private Entry<Key,Value> unprocessedEntry = null;
|
||||
|
||||
/**
|
||||
* Read signed Accumulo data where the signature is stored in a different entry than the value.
|
||||
*
|
||||
* @param valueIterator
|
||||
* The iterator with the accumulo data.
|
||||
* @param signatureIterator
|
||||
* The iterator with the signature data.
|
||||
* @param verifier
|
||||
* The signer to use in verifying signatures.
|
||||
* @param inOrder
|
||||
* Whether the signatures are in the same order as the values.
|
||||
*/
|
||||
SignedExternalScannerIterator(Iterator<Entry<Key,Value>> valueIterator, Iterator<Entry<Key,Value>> signatureIterator, EntrySigner verifier, boolean inOrder) {
|
||||
this.valueIterator = valueIterator;
|
||||
this.signatureIterator = signatureIterator;
|
||||
this.verifier = verifier;
|
||||
this.inOrder = inOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return valueIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Key,Value> next() {
|
||||
unprocessedEntry = null;
|
||||
|
||||
// Get the next signature.
|
||||
Entry<Key,Value> entry = valueIterator.next();
|
||||
Entry<Key,Value> signature = bufferedSignatures.get(entry.getKey());
|
||||
if (signature == null) {
|
||||
while (true) {
|
||||
if (!signatureIterator.hasNext()) {
|
||||
throw new SignatureException("no signature found");
|
||||
}
|
||||
|
||||
signature = signatureIterator.next();
|
||||
|
||||
// Timestamps might be slightly inconsistent, so don't include them in the compare. This requires that both of these tables be versioned.
|
||||
// TODO: Check that the tables are versioned, and if not throw an exception.
|
||||
int cmp = entry.getKey().compareTo(signature.getKey(), PartialKey.ROW_COLFAM_COLQUAL_COLVIS);
|
||||
|
||||
if (cmp == 0) { // Found the signature
|
||||
break;
|
||||
} else if (cmp < 0) { // Entry is before the next signature.
|
||||
if (inOrder) {
|
||||
throw new SignatureException("no signature found for entry");
|
||||
} else {
|
||||
bufferedSignatures.put(signature.getKey(), signature);
|
||||
}
|
||||
} else { // Entry is after the next signature.
|
||||
if (!inOrder) {
|
||||
bufferedSignatures.put(signature.getKey(), signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Entry<Key,Value> processedEntry = verifier.verify(entry, signature);
|
||||
unprocessedEntry = entry;
|
||||
return processedEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Key,Value> unprocessed() {
|
||||
if (unprocessedEntry == null) {
|
||||
throw new NoSuchElementException("next() has not been called");
|
||||
}
|
||||
return unprocessedEntry;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
|
||||
import edu.mit.ll.pace.ItemProcessingIterator;
|
||||
|
||||
/**
|
||||
* Read signed Accumulo entries.
|
||||
*/
|
||||
final class SignedInlineScannerIterator implements ItemProcessingIterator<Entry<Key,Value>> {
|
||||
|
||||
/**
|
||||
* The iterator with the Accumulo data.
|
||||
*/
|
||||
private final Iterator<Entry<Key,Value>> iterator;
|
||||
|
||||
/**
|
||||
* The verifier to use.
|
||||
*/
|
||||
private final EntrySigner verifier;
|
||||
|
||||
/**
|
||||
* Keep track of the encrypted entry that resulted in
|
||||
*/
|
||||
private Entry<Key,Value> unprocessedEntry = null;
|
||||
|
||||
/**
|
||||
* Read signed Accumulo data.
|
||||
* <p>
|
||||
* This only works when the signature is stored in the same entry as the value.
|
||||
*
|
||||
* @param iterator
|
||||
* The iterator with the accumulo data.
|
||||
* @param verifier
|
||||
* The signer to use in verifying signatures.
|
||||
*/
|
||||
SignedInlineScannerIterator(Iterator<Entry<Key,Value>> iterator, EntrySigner verifier) {
|
||||
this.iterator = iterator;
|
||||
this.verifier = verifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Key,Value> next() {
|
||||
Entry<Key,Value> original = iterator.next();
|
||||
Entry<Key,Value> processedEntry = verifier.verify(original);
|
||||
unprocessedEntry = original;
|
||||
return processedEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Key,Value> unprocessed() {
|
||||
if (unprocessedEntry == null) {
|
||||
throw new NoSuchElementException("next() has not been called");
|
||||
}
|
||||
return unprocessedEntry;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.client.IteratorSetting.Column;
|
||||
import org.apache.accumulo.core.client.Scanner;
|
||||
import org.apache.accumulo.core.client.TableNotFoundException;
|
||||
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.hadoop.io.Text;
|
||||
|
||||
import edu.mit.ll.pace.ItemProcessingIterator;
|
||||
|
||||
/**
|
||||
* Accumulo Scanner that also checks signatures on entries.
|
||||
*/
|
||||
public final class SignedScanner implements Scanner {
|
||||
|
||||
/**
|
||||
* The signer to use to verify signatures.
|
||||
*/
|
||||
private final EntrySigner verifier;
|
||||
|
||||
/**
|
||||
* The underlying scanner used to retrieve data from Accumulo.
|
||||
*/
|
||||
private final Scanner valueScanner;
|
||||
|
||||
/**
|
||||
* The scanner to use to retrieve signatures.
|
||||
*/
|
||||
private final Scanner signatureScanner;
|
||||
|
||||
/**
|
||||
* Create an signed scanner.
|
||||
*
|
||||
* @param connector
|
||||
* The connector for the Accumulo instance.
|
||||
* @param tableName
|
||||
* Name of the table to write to.
|
||||
* @param authorizations
|
||||
* The authorizations this user has for querying Accumulo.
|
||||
* @param signatureConfig
|
||||
* Configuration for the verification.
|
||||
* @param keys
|
||||
* Container with the keys to use for signatures.
|
||||
*/
|
||||
public SignedScanner(Connector connector, String tableName, Authorizations authorizations, SignatureConfig signatureConfig, SignatureKeyContainer keys)
|
||||
throws TableNotFoundException {
|
||||
checkArgument(connector != null, "connection is null");
|
||||
checkArgument(tableName != null, "tableName is null");
|
||||
checkArgument(authorizations != null, "authorizations is null");
|
||||
checkArgument(signatureConfig != null, "config is null");
|
||||
checkArgument(keys != null, "keys is null");
|
||||
|
||||
this.valueScanner = connector.createScanner(tableName, authorizations);
|
||||
this.verifier = new EntrySigner(signatureConfig, keys);
|
||||
|
||||
if (signatureConfig.destination == SignatureConfig.Destination.SEPARATE_TABLE) {
|
||||
this.signatureScanner = connector.createScanner(signatureConfig.destinationTable, authorizations);
|
||||
} else {
|
||||
this.signatureScanner = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Returns a {@link edu.mit.ll.pace.ItemProcessingIterator}. This can be used to get the unprocessed items, allowing them to be deleted by a regular accumulo
|
||||
* writer.
|
||||
*/
|
||||
@Override
|
||||
public ItemProcessingIterator<Entry<Key,Value>> iterator() {
|
||||
if (signatureScanner != null) {
|
||||
return new SignedExternalScannerIterator(valueScanner.iterator(), signatureScanner.iterator(), verifier, true);
|
||||
} else {
|
||||
return new SignedInlineScannerIterator(valueScanner.iterator(), verifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This iterator is applied to both the table and the signature table. Changing the value will also affect the signature.
|
||||
*/
|
||||
@Override
|
||||
public void addScanIterator(IteratorSetting cfg) {
|
||||
valueScanner.addScanIterator(cfg);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.addScanIterator(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearColumns() {
|
||||
valueScanner.clearColumns();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.clearColumns();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearScanIterators() {
|
||||
valueScanner.clearScanIterators();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.clearScanIterators();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
valueScanner.close();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumn(Column column) {
|
||||
valueScanner.fetchColumn(column);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.fetchColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumn(Text colFam, Text colQual) {
|
||||
valueScanner.fetchColumn(colFam, colQual);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.fetchColumn(colFam, colQual);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchColumnFamily(Text col) {
|
||||
valueScanner.fetchColumnFamily(col);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.fetchColumnFamily(col);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authorizations getAuthorizations() {
|
||||
return valueScanner.getAuthorizations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSamplerConfiguration(SamplerConfiguration samplerConfiguration) {
|
||||
valueScanner.setSamplerConfiguration(samplerConfiguration);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setSamplerConfiguration(samplerConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamplerConfiguration getSamplerConfiguration() {
|
||||
return valueScanner.getSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSamplerConfiguration() {
|
||||
valueScanner.clearSamplerConfiguration();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.clearSamplerConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchTimeout(long l, TimeUnit timeUnit) {
|
||||
valueScanner.setBatchTimeout(l, timeUnit);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setBatchTimeout(l, timeUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBatchTimeout(TimeUnit timeUnit) {
|
||||
return valueScanner.getBatchTimeout(timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClassLoaderContext(String s) {
|
||||
valueScanner.setClassLoaderContext(s);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setClassLoaderContext(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearClassLoaderContext() {
|
||||
valueScanner.clearClassLoaderContext();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.clearClassLoaderContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassLoaderContext() {
|
||||
return valueScanner.getClassLoaderContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeout(TimeUnit timeUnit) {
|
||||
return valueScanner.getTimeout(timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScanIterator(String iteratorName) {
|
||||
valueScanner.removeScanIterator(iteratorName);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.removeScanIterator(iteratorName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScanIteratorOption(String iteratorName, String key, String value) {
|
||||
valueScanner.updateScanIteratorOption(iteratorName, key, value);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.updateScanIteratorOption(iteratorName, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeout(long timeout, TimeUnit timeUnit) {
|
||||
valueScanner.setTimeout(timeout, timeUnit);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setTimeout(timeout, timeUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableIsolation() {
|
||||
valueScanner.disableIsolation();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.disableIsolation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableIsolation() {
|
||||
valueScanner.enableIsolation();
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.enableIsolation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBatchSize() {
|
||||
return valueScanner.getBatchSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range getRange() {
|
||||
return valueScanner.getRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getReadaheadThreshold() {
|
||||
return valueScanner.getReadaheadThreshold();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public int getTimeOut() {
|
||||
return valueScanner.getTimeOut();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchSize(int size) {
|
||||
valueScanner.setBatchSize(size);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setBatchSize(size);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRange(Range range) {
|
||||
valueScanner.setRange(range);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setRange(range);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadaheadThreshold(long batches) {
|
||||
valueScanner.setReadaheadThreshold(batches);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setReadaheadThreshold(batches);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setTimeOut(int timeOut) {
|
||||
valueScanner.setTimeOut(timeOut);
|
||||
if (signatureScanner != null) {
|
||||
signatureScanner.setTimeOut(timeOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Signature;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Set of objects that can sign values.
|
||||
*/
|
||||
public enum ValueSigner {
|
||||
RSA_PKCS1("RSA-PKCS1", "SHA256withRSA", "RSA"), RSA_PSS("RSA-PSS", "SHA256withRSA/PSS", "RSA"), DSA("DSA", "SHA256withDSA", "DSA"), ECDSA("ECDSA",
|
||||
"SHA256withECDSA", "ECDSA");
|
||||
|
||||
/**
|
||||
* Set of ValueSigner names mapped to the ValueSigner enum object. Used to speed up name lookups.
|
||||
*/
|
||||
private static final Map<String,ValueSigner> signers;
|
||||
static {
|
||||
signers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (ValueSigner signer : values()) {
|
||||
signers.put(signer.name, signer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the signer.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Algorithm used for the singer.
|
||||
*/
|
||||
private final String algorithm;
|
||||
|
||||
/**
|
||||
* Algorithm that can be used to generate keys for use with this cipher.
|
||||
*/
|
||||
private final String keyGenerationAlgorithm;
|
||||
|
||||
/**
|
||||
* Create the value signer.
|
||||
*
|
||||
* @param name
|
||||
* Name of the algorithm.
|
||||
* @param algorithm
|
||||
* Algorithm used by this signer.
|
||||
* @param keyGenerationAlgorithm
|
||||
* Algorithm that can be used to generate keys for use with this cipher.
|
||||
*/
|
||||
ValueSigner(String name, String algorithm, String keyGenerationAlgorithm) {
|
||||
this.name = name;
|
||||
this.algorithm = algorithm;
|
||||
this.keyGenerationAlgorithm = keyGenerationAlgorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value signer from its {@link String} representation.
|
||||
* <p>
|
||||
* This method is paired with {@link #toString()}, and will parse strings generated by that method.
|
||||
*
|
||||
* @param value
|
||||
* Value to parse.
|
||||
* @return ValueSigner for the value.
|
||||
*/
|
||||
static ValueSigner fromString(String value) {
|
||||
ValueSigner signer = signers.get(value);
|
||||
if (signer == null) {
|
||||
throw new IllegalArgumentException("invalid name");
|
||||
}
|
||||
return signer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the algorithm that can be used to generate keys for use with this cipher.
|
||||
*
|
||||
* @return Key generation algorithm.
|
||||
*/
|
||||
public String getKeyGenerationAlgorithm() {
|
||||
return keyGenerationAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new {@link Signature} instance for the signer.
|
||||
*
|
||||
* @param provider
|
||||
* Provider to use when creating the instance, or null to allow the system to decide which provider to use.
|
||||
* @return The created instance.
|
||||
*/
|
||||
Signature getInstance(String provider) {
|
||||
try {
|
||||
if (provider == null) {
|
||||
return Signature.getInstance(algorithm);
|
||||
} else {
|
||||
return Signature.getInstance(algorithm, provider);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
|
||||
[value]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
@@ -0,0 +1,17 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[value]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
@@ -0,0 +1,29 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_row
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colFamily
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
|
||||
[value]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
@@ -0,0 +1,16 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[Signature]
|
||||
algorithm = DSA
|
||||
@@ -0,0 +1,16 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[Signature]
|
||||
algorithm = ECDSA
|
||||
@@ -0,0 +1,16 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[Signature]
|
||||
algorithm = RSA-PSS
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test {@link EntryField}.
|
||||
*/
|
||||
public final class EntryFieldTest {
|
||||
|
||||
@Test
|
||||
public void validEnumTest() {
|
||||
assertThat("should have seven values", 7, is(EntryField.values().length));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringTest() {
|
||||
assertThat("toString should return correct value", "row", is(EntryField.ROW.toString()));
|
||||
assertThat("toString should return correct value", "colFamily", is(EntryField.COLUMN_FAMILY.toString()));
|
||||
assertThat("toString should return correct value", "colQualifier", is(EntryField.COLUMN_QUALIFIER.toString()));
|
||||
assertThat("toString should return correct value", "colVisibility", is(EntryField.COLUMN_VISIBILITY.toString()));
|
||||
assertThat("toString should return correct value", "timestamp", is(EntryField.TIMESTAMP.toString()));
|
||||
assertThat("toString should return correct value", "delete", is(EntryField.DELETE.toString()));
|
||||
assertThat("toString should return correct value", "value", is(EntryField.VALUE.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromStringTest() {
|
||||
assertThat("fromString should return correct enum value", EntryField.fromString("row"), is(EntryField.ROW));
|
||||
assertThat("fromString should return correct enum value", EntryField.fromString("colFamily"), is(EntryField.COLUMN_FAMILY));
|
||||
assertThat("fromString should return correct enum value", EntryField.fromString("colQualifier"), is(EntryField.COLUMN_QUALIFIER));
|
||||
assertThat("fromString should return correct enum value", EntryField.fromString("colVisibility"), is(EntryField.COLUMN_VISIBILITY));
|
||||
assertThat("fromString should return correct enum value", EntryField.fromString("timestamp"), is(EntryField.TIMESTAMP));
|
||||
assertThat("fromString should return correct enum value", EntryField.fromString("delete"), is(EntryField.DELETE));
|
||||
assertThat("fromString should return correct enum value", EntryField.fromString("value"), is(EntryField.VALUE));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void fromStringFailureTest() {
|
||||
EntryField.fromString("bad");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static edu.mit.ll.pace.test.TestUtils.wrap;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.isA;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
import javax.crypto.AEADBadTagException;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
/**
|
||||
* Test {@link AESValueEncryptor}.
|
||||
*/
|
||||
public class AESValueEncryptorTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@BeforeClass
|
||||
public static void registerBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void semanticEncryptionTest() throws Exception {
|
||||
ValueEncryptorBase encryptor = ValueEncryptor.AES_CBC.getInstance(null);
|
||||
byte[] plaintext = "1234".getBytes(VISIBILITY_CHARSET);
|
||||
byte[] key = "aabbccddaabbccdd".getBytes(VISIBILITY_CHARSET);
|
||||
|
||||
byte[] ciphertext1 = encryptor.encrypt(key, plaintext);
|
||||
assertThat("ciphertext should not contain the original plaintext", wrap(ciphertext1), is(not(arrayContaining(wrap(plaintext)))));
|
||||
|
||||
byte[] plaintext1 = encryptor.decrypt(key, ciphertext1);
|
||||
assertThat("decrypting the ciphertext should give the plaintext", plaintext1, is(equalTo(plaintext)));
|
||||
|
||||
byte[] ciphertext2 = encryptor.encrypt(key, plaintext);
|
||||
assertThat("ciphertext should be different when identical values are encrypted", ciphertext2, is(not(equalTo(ciphertext1))));
|
||||
|
||||
byte[] ciphertext3 = encryptor.encrypt(key, "1235".getBytes(VISIBILITY_CHARSET));
|
||||
assertThat("ciphertext should be not be the same when different values are encrypted", ciphertext3, is(not(equalTo(ciphertext1))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticatedEncryptionTest() throws Exception {
|
||||
for (String provider : new String[] {null, "BC", "SunJCE"}) {
|
||||
ValueEncryptorBase encryptor = ValueEncryptor.AES_GCM.getInstance(provider);
|
||||
byte[] plaintext = "1234".getBytes(VISIBILITY_CHARSET);
|
||||
byte[] key = "aabbccddaabbccddaabbccddaabbccdd".getBytes(VISIBILITY_CHARSET);
|
||||
|
||||
byte[] ciphertext1 = encryptor.encrypt(key, plaintext);
|
||||
assertThat("ciphertext should not contain the original plaintext", wrap(ciphertext1), is(not(arrayContaining(wrap(plaintext)))));
|
||||
|
||||
byte[] plaintext1 = encryptor.decrypt(key, ciphertext1);
|
||||
assertThat("decrypting the ciphertext should give the plaintext", plaintext1, is(equalTo(plaintext)));
|
||||
|
||||
byte[] ciphertext2 = encryptor.encrypt(key, plaintext);
|
||||
assertThat("ciphertext should be different when identical values are encrypted", ciphertext2, is(not(equalTo(ciphertext1))));
|
||||
|
||||
byte[] ciphertext3 = encryptor.encrypt(key, "1235".getBytes(VISIBILITY_CHARSET));
|
||||
assertThat("ciphertext should be not be the same when different values are encrypted", ciphertext3, is(not(equalTo(ciphertext1))));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticatedEncryptionMacFailureTest() throws Exception {
|
||||
for (String provider : new String[] {null, "BC", "SunJCE"}) {
|
||||
ValueEncryptorBase encryptor = ValueEncryptor.AES_GCM.getInstance(provider);
|
||||
byte[] plaintext = "1234567890123457".getBytes(VISIBILITY_CHARSET);
|
||||
byte[] key = "aabbccddaabbccddaabbccddaabbccdd".getBytes(VISIBILITY_CHARSET);
|
||||
|
||||
byte[] ciphertext = encryptor.encrypt(key, plaintext);
|
||||
if (ciphertext[20] == 0) {
|
||||
ciphertext[20] = (byte) 1;
|
||||
} else {
|
||||
ciphertext[20] = (byte) 0;
|
||||
}
|
||||
|
||||
thrown.expect(EncryptionException.class);
|
||||
thrown.expectCause(isA(AEADBadTagException.class));
|
||||
encryptor.decrypt(key, ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static edu.mit.ll.pace.test.TestUtils.wrap;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.isA;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.cryptomator.siv.UnauthenticCiphertextException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
/**
|
||||
* Test {@link DeterministicSIVValueEncryptor}.
|
||||
*/
|
||||
public class DeterministicSIVValueEncryptorTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void deterministicSivEncryptionTest() throws Exception {
|
||||
ValueEncryptorBase encryptor = ValueEncryptor.AES_SIV_DETERMINISTIC.getInstance(null);
|
||||
byte[] plaintext = "1234".getBytes(VISIBILITY_CHARSET);
|
||||
byte[] key = "aabbccddaabbccddaabbccddaabbccdd".getBytes(VISIBILITY_CHARSET);
|
||||
|
||||
byte[] ciphertext1 = encryptor.encrypt(key, plaintext);
|
||||
assertThat("ciphertext should not contain the original plaintext", wrap(ciphertext1), is(not(arrayContaining(wrap(plaintext)))));
|
||||
|
||||
byte[] plaintext1 = encryptor.decrypt(key, ciphertext1);
|
||||
assertThat("decrypting the ciphertext should give the plaintext", plaintext1, is(equalTo(plaintext)));
|
||||
|
||||
byte[] ciphertext2 = encryptor.encrypt(key, plaintext);
|
||||
assertThat("ciphertext should be the same when identical values are encrypted", ciphertext2, is(equalTo(ciphertext1)));
|
||||
|
||||
byte[] ciphertext3 = encryptor.encrypt(key, "1235".getBytes(VISIBILITY_CHARSET));
|
||||
assertThat("ciphertext should be not be the same when different values are encrypted", ciphertext3, is(not(equalTo(ciphertext1))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticatedEncryptionMacFailureTest() throws Exception {
|
||||
ValueEncryptorBase encryptor = ValueEncryptor.AES_SIV_DETERMINISTIC.getInstance(null);
|
||||
byte[] plaintext = "1234567890123457".getBytes(VISIBILITY_CHARSET);
|
||||
byte[] key = "aabbccddaabbccddaabbccddaabbccdd".getBytes(VISIBILITY_CHARSET);
|
||||
|
||||
byte[] ciphertext = encryptor.encrypt(key, plaintext);
|
||||
ciphertext[20] = (byte) 0;
|
||||
|
||||
thrown.expect(EncryptionException.class);
|
||||
thrown.expectCause(isA(UnauthenticCiphertextException.class));
|
||||
encryptor.decrypt(key, ciphertext);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchScanner;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.hadoop.io.Text;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import edu.mit.ll.pace.internal.Utils;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test {@link EncryptedBatchScanner}.
|
||||
*/
|
||||
public class EncryptedBatchScannerTest {
|
||||
|
||||
/**
|
||||
* The default testing table.
|
||||
*/
|
||||
private final static String TEST_TABLE = "test";
|
||||
|
||||
/**
|
||||
* Authorizations.
|
||||
*/
|
||||
private final static Authorizations authorizations = new Authorizations("admin", "secret", "top secret");
|
||||
|
||||
/**
|
||||
* Encryption keys.
|
||||
*/
|
||||
private final static MockEncryptionKeyContainer KEYS = new MockEncryptionKeyContainer(Pair.of("AES_GCM", 1), Pair.of("searchable_row", 2), Pair.of(
|
||||
"searchable_colFamily", 2), Pair.of("searchable_colQualifier", 2), Pair.of("secret", 1));
|
||||
|
||||
@Mock
|
||||
private Connector mockConnector;
|
||||
|
||||
@Mock
|
||||
private BatchScanner mockScanner;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Captor
|
||||
public ArgumentCaptor<Collection<Range>> captor;
|
||||
|
||||
@Test
|
||||
public void badConstructorTest() throws Exception {
|
||||
try {
|
||||
new EncryptedBatchScanner(null, TEST_TABLE, authorizations, 0, getConfig("encrypt-value.ini"), KEYS);
|
||||
fail("null connector not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptedBatchScanner(mockConnector, null, authorizations, 0, getConfig("encrypt-value.ini"), KEYS);
|
||||
fail("null table name not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, null, 0, getConfig("encrypt-value.ini"), KEYS);
|
||||
fail("null authorizations not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 0, null, KEYS);
|
||||
fail("null crypto config not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 0, getConfig("encrypt-value.ini"), null);
|
||||
fail("null key container not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest() throws Exception {
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS);
|
||||
verify(mockConnector).createBatchScanner(TEST_TABLE, authorizations, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRangesSemanticEncryptionTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("encrypt-key.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-key.ini"), KEYS);
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
scanner.setRanges(Collections.singletonList(new Range(new Text(new byte[] {1}))));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(1));
|
||||
|
||||
scanner.setRanges(Collections.singletonList(new Range(new Text(new byte[] {1}), new Text(new byte[] {5}))));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
// Should not have been handled server side.
|
||||
verify(mockScanner, times(2)).setRanges(captor.capture());
|
||||
|
||||
for (Collection<Range> ranges : captor.getAllValues()) {
|
||||
assertThat("has the infinite range", ranges, hasSize(1));
|
||||
assertThat("has the infinite range", ranges.iterator().next(), equalTo(new Range()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRangesSearchableTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("searchable-row.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("searchable-row.ini"), KEYS);
|
||||
scanner.setRanges(Collections.singletonList(new Range(new Text(new byte[] {1}))));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
verify(mockScanner).setRanges(captor.capture());
|
||||
assertThat("correct number of ranges", captor.getValue(), hasSize(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRangesException() throws Exception {
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-key.ini"), KEYS);
|
||||
|
||||
try {
|
||||
scanner.setRanges(null);
|
||||
fail("null ranges not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
scanner.setRanges(new ArrayList<>());
|
||||
fail("empty ranges not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnFamilySemanticEncryptionTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("encrypt-key.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-key.ini"), KEYS);
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
scanner.fetchColumnFamily(new Text(new byte[] {2}));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(1));
|
||||
|
||||
scanner.fetchColumnFamily(new Text(new byte[] {6}));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
// Should not have been handled server side.
|
||||
verify(mockScanner, never()).fetchColumn(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnFamilySearchableTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("searchable.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("searchable.ini"), KEYS);
|
||||
scanner.fetchColumnFamily(new Text(new byte[] {2}));
|
||||
assertThat("filtering is not happening client-side", scanner, iterableWithSize(2));
|
||||
verify(mockScanner, times(2)).fetchColumnFamily(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnFamilyException() throws Exception {
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-key.ini"), KEYS);
|
||||
|
||||
try {
|
||||
scanner.fetchColumnFamily(null);
|
||||
fail("null column family not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnSemanticEncryptionTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("encrypt-key.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-key.ini"), KEYS);
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
scanner.fetchColumn(new IteratorSetting.Column(new Text(new byte[] {2}), new Text(new byte[] {3})));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(1));
|
||||
|
||||
scanner.fetchColumn(new IteratorSetting.Column(new Text(new byte[] {6}), new Text(new byte[] {7})));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
// Should not have been handled server side.
|
||||
verify(mockScanner, never()).fetchColumn(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnSearchableTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("searchable.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("searchable.ini"), KEYS);
|
||||
scanner.fetchColumn(new Text(new byte[] {2}), new Text(new byte[] {3}));
|
||||
assertThat("filtering is not happening client-side", scanner, iterableWithSize(2));
|
||||
verify(mockScanner, times(4)).fetchColumn(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnPartiallySearchableTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("partially-searchable.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("partially-searchable.ini"), KEYS);
|
||||
scanner.fetchColumn(new Text(new byte[] {2}), new Text(new byte[] {3}));
|
||||
assertThat("filtering is partially happening client-side", scanner, iterableWithSize(1));
|
||||
verify(mockScanner, times(2)).fetchColumnFamily(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnException() throws Exception {
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-key.ini"), KEYS);
|
||||
|
||||
try {
|
||||
scanner.fetchColumn(null);
|
||||
fail("null column not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
scanner.fetchColumn(null, new Text(Utils.EMPTY));
|
||||
fail("null column family not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
scanner.fetchColumn(new Text(Utils.EMPTY), null);
|
||||
fail("null column qualifier not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearColumnsTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("encrypt-key.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-key.ini"), KEYS);
|
||||
scanner.fetchColumn(new Text(new byte[] {2}), new Text(new byte[] {2}));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(0));
|
||||
|
||||
scanner.clearColumns();
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("encrypt-value.ini"), KEYS);
|
||||
List<Map.Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS);
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
Iterator<Entry<Key,Value>> iterator = scanner.iterator();
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry));
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearScanIteratorsTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).clearScanIterators();
|
||||
verify(mockScanner).clearScanIterators();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).close();
|
||||
verify(mockScanner).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAuthorizationsTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockScanner.getAuthorizations()).thenReturn(authorizations);
|
||||
Authorizations auths = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).getAuthorizations();
|
||||
verify(mockScanner).getAuthorizations();
|
||||
assertThat("correct authorizations returned", auths, equalTo(authorizations));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
SamplerConfiguration config = new SamplerConfiguration("test");
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).setSamplerConfiguration(config);
|
||||
verify(mockScanner).setSamplerConfiguration(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
SamplerConfiguration config = new SamplerConfiguration("test");
|
||||
when(mockScanner.getSamplerConfiguration()).thenReturn(config);
|
||||
SamplerConfiguration value = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS)
|
||||
.getSamplerConfiguration();
|
||||
verify(mockScanner).getSamplerConfiguration();
|
||||
assertThat("correct config returned", value, equalTo(config));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).clearSamplerConfiguration();
|
||||
verify(mockScanner).clearSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBatchTimeoutTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
verify(mockScanner).setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatchTimeoutTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockScanner.getBatchTimeout(TimeUnit.DAYS)).thenReturn(5L);
|
||||
long value = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).getBatchTimeout(TimeUnit.DAYS);
|
||||
verify(mockScanner).getBatchTimeout(TimeUnit.DAYS);
|
||||
assertThat("correct timeout returned", value, is(5L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).setClassLoaderContext("test");
|
||||
verify(mockScanner).setClassLoaderContext("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).clearClassLoaderContext();
|
||||
verify(mockScanner).clearClassLoaderContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockScanner.getClassLoaderContext()).thenReturn("test");
|
||||
String value = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).getClassLoaderContext();
|
||||
verify(mockScanner).getClassLoaderContext();
|
||||
assertThat("correct class loader context returned", value, is("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addScanIteratorTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
IteratorSetting test = new IteratorSetting(10, "test", "test2");
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).addScanIterator(test);
|
||||
verify(mockScanner).addScanIterator(test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeScanIteratorTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).removeScanIterator("test");
|
||||
verify(mockScanner).removeScanIterator("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateScanIteratorOptionTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).updateScanIteratorOption("test", "a", "b");
|
||||
verify(mockScanner).updateScanIteratorOption("test", "a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTimeoutTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).setTimeout(5L, TimeUnit.DAYS);
|
||||
verify(mockScanner).setTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTimeoutTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockScanner.getTimeout(TimeUnit.DAYS)).thenReturn(5L);
|
||||
Long value = new EncryptedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("encrypt-value.ini"), KEYS).getTimeout(TimeUnit.DAYS);
|
||||
verify(mockScanner).getTimeout(TimeUnit.DAYS);
|
||||
assertThat("correct timeout returned", value, is(5L));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an encryptor config.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @return EncryptionConfig.
|
||||
*/
|
||||
private EncryptionConfig getConfig(String resource) throws Exception {
|
||||
return new EncryptionConfigBuilder().readFromFile(new InputStreamReader(TestUtils.getResourceAsStream(this.getClass(), resource))).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchWriter;
|
||||
import org.apache.accumulo.core.client.BatchWriterConfig;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.data.ColumnUpdate;
|
||||
import org.apache.accumulo.core.data.Mutation;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test {@link EncryptedBatchWriter}.
|
||||
*/
|
||||
public class EncryptedBatchWriterTest {
|
||||
|
||||
/**
|
||||
* The default testing table.
|
||||
*/
|
||||
private final static String TEST_TABLE = "table";
|
||||
|
||||
/**
|
||||
* Encryption keys.
|
||||
*/
|
||||
private final static MockEncryptionKeyContainer KEYS = new MockEncryptionKeyContainer(Pair.of("AES_GCM", 1), Pair.of("deterministic", 2));
|
||||
|
||||
@Mock
|
||||
private Connector mockConnector;
|
||||
|
||||
@Mock
|
||||
private BatchWriter mockWriter;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Mutation> captor;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Test
|
||||
public void badConstructorTest() throws Exception {
|
||||
try {
|
||||
new EncryptedBatchWriter(null, TEST_TABLE, null, getConfig("encrypt-value.ini"), KEYS);
|
||||
fail("null connector not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptedBatchWriter(mockConnector, null, null, getConfig("encrypt-value.ini"), KEYS);
|
||||
fail("null table name not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, null, KEYS);
|
||||
fail("null crypto config not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("encrypt-value.ini"), null);
|
||||
fail("null key container not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest() throws Exception {
|
||||
BatchWriterConfig config = new BatchWriterConfig();
|
||||
new EncryptedBatchWriter(mockConnector, TEST_TABLE, config, getConfig("encrypt-value.ini"), KEYS);
|
||||
verify(mockConnector).createBatchWriter(TEST_TABLE, config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMutationTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("encrypt-value.ini"), KEYS);
|
||||
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("encrypt-value.ini"), KEYS);
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.put("colF".getBytes(), "colQ".getBytes(), "val1".getBytes());
|
||||
mutation.put("colF".getBytes(), "colQ".getBytes(), 0, "val2".getBytes());
|
||||
writer.addMutation(mutation);
|
||||
|
||||
verify(mockWriter, times(2)).addMutation(captor.capture());
|
||||
List<Mutation> mutations = captor.getAllValues();
|
||||
assertThat("two mutations", mutations, iterableWithSize(2));
|
||||
|
||||
Mutation encrypted = mutations.get(0);
|
||||
assertThat("row is unchanged", encrypted.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
List<ColumnUpdate> updates = encrypted.getUpdates();
|
||||
assertThat("has 1 update", updates, hasSize(1));
|
||||
|
||||
ColumnUpdate update = updates.get(0);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnQualifier(), equalTo("colQ".getBytes()));
|
||||
assertThat("timestamp not set", update.hasTimestamp(), is(false));
|
||||
assertThat("value is encrypted", update.getColumnFamily(), not(equalTo("val1".getBytes())));
|
||||
|
||||
MutableEntry decrypted = new MutableEntry(encryptor.decrypt(new MutableEntry("row".getBytes(), update).toEntry()));
|
||||
assertThat("value was encrypted correctly", decrypted.value, equalTo("val1".getBytes()));
|
||||
|
||||
encrypted = mutations.get(1);
|
||||
assertThat("row is unchanged", encrypted.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
updates = encrypted.getUpdates();
|
||||
assertThat("has 1 update", updates, hasSize(1));
|
||||
|
||||
update = updates.get(0);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnQualifier(), equalTo("colQ".getBytes()));
|
||||
assertThat("timestamp is set", update.hasTimestamp(), is(true));
|
||||
assertThat("timestamp is correct", update.getTimestamp(), is(0L));
|
||||
assertThat("value is encrypted", update.getColumnFamily(), not(equalTo("val2".getBytes())));
|
||||
|
||||
decrypted = new MutableEntry(encryptor.decrypt(new MutableEntry("row".getBytes(), update).toEntry()));
|
||||
assertThat("value was encrypted correctly", decrypted.value, equalTo("val2".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMutationDeterministicTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("deterministic.ini"), KEYS);
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.put("colF1".getBytes(), "colQ".getBytes(), "val".getBytes());
|
||||
mutation.put("colF2".getBytes(), "colQ".getBytes(), 0, "val".getBytes());
|
||||
mutation.put("colF1".getBytes(), "colQ".getBytes(), 0, "val2".getBytes());
|
||||
writer.addMutation(mutation);
|
||||
|
||||
verify(mockWriter, times(3)).addMutation(captor.capture());
|
||||
Iterable<Mutation> mutations = captor.getAllValues();
|
||||
assertThat("should have 2 mutations", mutations, iterableWithSize(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("encrypt-entry.ini"), KEYS);
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.putDelete("colF1".getBytes(), "colQ".getBytes());
|
||||
|
||||
try {
|
||||
writer.addMutation(mutation);
|
||||
fail("encrypted deletes are not allowed on non-deterministically encrypted data");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteDeterministicTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("deterministic.ini"), KEYS);
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.putDelete("colF1".getBytes(), "colQ".getBytes());
|
||||
mutation.putDelete("colF2".getBytes(), "colQ".getBytes(), 0);
|
||||
writer.addMutation(mutation);
|
||||
|
||||
verify(mockWriter, times(4)).addMutation(captor.capture());
|
||||
Iterable<Mutation> mutations = captor.getAllValues();
|
||||
assertThat("should have 4 mutations", mutations, iterableWithSize(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMutationsTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("encrypt-value.ini"), KEYS);
|
||||
|
||||
List<Mutation> mutations = new ArrayList<>();
|
||||
Mutation mutation = new Mutation("row");
|
||||
mutation.put("colF", "colQ", "val");
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(1)).addMutation(any()); // 1 time
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(3)).addMutation(any()); // 1 + 2 times
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(6)).addMutation(any()); // 1 + 2 + 3 times
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("encrypt-value.ini"), KEYS).flush();
|
||||
verify(mockWriter).flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
new EncryptedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("encrypt-value.ini"), KEYS).close();
|
||||
verify(mockWriter).close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an encryptor config.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @return EncryptionConfig.
|
||||
*/
|
||||
private EncryptionConfig getConfig(String resource) throws Exception {
|
||||
return new EncryptionConfigBuilder().readFromFile(new InputStreamReader(TestUtils.getResourceAsStream(this.getClass(), resource))).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.accumulo.core.data.Column;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.hadoop.io.Text;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import edu.mit.ll.pace.internal.Utils;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test {@link EncryptedScannerIterator}.
|
||||
*/
|
||||
public class EncryptedScannerIteratorTest {
|
||||
|
||||
/**
|
||||
* Encryption keys.
|
||||
*/
|
||||
private final static MockEncryptionKeyContainer KEYS = new MockEncryptionKeyContainer(Pair.of("AES_GCM", 1));
|
||||
|
||||
@Test
|
||||
public void hasNextTest() throws Exception {
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("config.ini"), KEYS);
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<Key,Value>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
|
||||
EncryptedScannerIterator iterator = new EncryptedScannerIterator(entries.iterator(), encryptor, Collections.singletonList(new Range()),
|
||||
new TreeSet<Column>());
|
||||
|
||||
assertThat("has next item", iterator.hasNext(), is(true));
|
||||
assertThat("has next item", iterator.hasNext(), is(true));
|
||||
iterator.next();
|
||||
assertThat("does not have a next item", iterator.hasNext(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nextTest() throws Exception {
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("config.ini"), KEYS);
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<Key,Value>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
|
||||
EncryptedScannerIterator iterator = new EncryptedScannerIterator(entries.iterator(), encryptor, Collections.singletonList(new Range()),
|
||||
new TreeSet<Column>());
|
||||
assertThat("next item is correct", iterator.next(), Matchers.equalTo(entry));
|
||||
|
||||
try {
|
||||
iterator.next();
|
||||
fail("no items should be left");
|
||||
} catch (NoSuchElementException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasNextThenNextTest() throws Exception {
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("config.ini"), KEYS);
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<Key,Value>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
|
||||
EncryptedScannerIterator iterator = new EncryptedScannerIterator(entries.iterator(), encryptor, Collections.singletonList(new Range()),
|
||||
new TreeSet<Column>());
|
||||
assertThat("hasNext is true", iterator.hasNext(), is(true));
|
||||
assertThat("next item is correct", iterator.next(), Matchers.equalTo(entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchRangeTest() throws Exception {
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("config.ini"), KEYS);
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(Utils.VISIBILITY_CHARSET), 0,
|
||||
false, false), new Value(new byte[] {4}));
|
||||
Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7}, "secret".getBytes(Utils.VISIBILITY_CHARSET),
|
||||
0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
|
||||
EncryptedScannerIterator iterator = new EncryptedScannerIterator(entries.iterator(), new EntryEncryptor(getConfig("config.ini"), KEYS),
|
||||
Collections.singletonList(new Range()), new TreeSet<>());
|
||||
assertThat("correct number of items", Lists.newArrayList(iterator), hasSize(2));
|
||||
|
||||
iterator = new EncryptedScannerIterator(entries.iterator(), new EntryEncryptor(getConfig("config.ini"), KEYS), new ArrayList<Range>(),
|
||||
new TreeSet<Column>());
|
||||
assertThat("correct number of items", Lists.newArrayList(iterator), hasSize(2));
|
||||
|
||||
iterator = getIteratorForRange(entries, (byte) 1);
|
||||
assertThat("correct number of items", Lists.newArrayList(iterator), hasSize(1));
|
||||
|
||||
iterator = getIteratorForRange(entries, (byte) 3);
|
||||
assertThat("correct number of items", Lists.newArrayList(iterator), hasSize(0));
|
||||
}
|
||||
|
||||
private EncryptedScannerIterator getIteratorForRange(List<Entry<Key,Value>> entries, byte row) throws Exception {
|
||||
return new EncryptedScannerIterator(entries.iterator(), new EntryEncryptor(getConfig("config.ini"), KEYS), Collections.singletonList(new Range(new Text(
|
||||
new byte[] {row}))), new TreeSet<Column>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchColumnFilters() throws Exception {
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("config.ini"), KEYS);
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(Utils.VISIBILITY_CHARSET), 0,
|
||||
false, false), new Value(new byte[] {4}));
|
||||
Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {2}, new byte[] {7}, "secret".getBytes(Utils.VISIBILITY_CHARSET),
|
||||
0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
entries.add(encryptor.encrypt(entry2));
|
||||
|
||||
EncryptedScannerIterator iterator = new EncryptedScannerIterator(entries.iterator(), new EntryEncryptor(getConfig("config.ini"), KEYS),
|
||||
Collections.singletonList(new Range()), new TreeSet<>());
|
||||
assertThat("correct number of items", Lists.newArrayList(iterator), hasSize(2));
|
||||
|
||||
iterator = getIteratorForColumn(entries, new byte[] {2}, null);
|
||||
assertThat("correct number of items", Lists.newArrayList(iterator), hasSize(2));
|
||||
|
||||
iterator = getIteratorForColumn(entries, new byte[] {3}, null);
|
||||
assertThat("correct number of items", Lists.newArrayList(iterator), hasSize(0));
|
||||
|
||||
iterator = getIteratorForColumn(entries, new byte[] {2}, new byte[] {7});
|
||||
assertThat("correct number of items", Lists.newArrayList(iterator), hasSize(1));
|
||||
}
|
||||
|
||||
private EncryptedScannerIterator getIteratorForColumn(List<Entry<Key,Value>> entries, byte[] colF, byte[] colQ) throws Exception {
|
||||
TreeSet<Column> columns = new TreeSet<>();
|
||||
columns.add(new Column(colF, colQ, null));
|
||||
return new EncryptedScannerIterator(entries.iterator(), new EntryEncryptor(getConfig("config.ini"), KEYS), Collections.singletonList(new Range()), columns);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unprocessedTest() throws Exception {
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("config.ini"), KEYS);
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<Key,Value>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
|
||||
EncryptedScannerIterator iterator = new EncryptedScannerIterator(entries.iterator(), encryptor, Collections.singletonList(new Range()),
|
||||
new TreeSet<Column>());
|
||||
iterator.next();
|
||||
assertThat("unprocessed item is correct", iterator.unprocessed(), Matchers.equalTo(entries.get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unprocessedException() throws Exception {
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("config.ini"), KEYS);
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<Key,Value>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
|
||||
EncryptedScannerIterator iterator = new EncryptedScannerIterator(entries.iterator(), encryptor, Collections.singletonList(new Range()), new TreeSet<>());
|
||||
|
||||
try {
|
||||
iterator.unprocessed();
|
||||
fail("cannot call unprocessed before calling next()");
|
||||
} catch (NoSuchElementException e) { /* expected */}
|
||||
|
||||
try {
|
||||
iterator.hasNext();
|
||||
iterator.unprocessed();
|
||||
fail("cannot call unprocessed before calling next()");
|
||||
} catch (NoSuchElementException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeException() throws Exception {
|
||||
EntryEncryptor encryptor = new EntryEncryptor(getConfig("config.ini"), KEYS);
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<Key,Value>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
entries.add(encryptor.encrypt(entry));
|
||||
|
||||
EncryptedScannerIterator iterator = new EncryptedScannerIterator(entries.iterator(), encryptor, Collections.singletonList(new Range()), new TreeSet<>());
|
||||
|
||||
try {
|
||||
iterator.remove();
|
||||
fail("remove not supported");
|
||||
} catch (UnsupportedOperationException e) { /* expected */}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an encryptor config.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @return EncryptionConfig.
|
||||
*/
|
||||
private EncryptionConfig getConfig(String resource) throws Exception {
|
||||
return new EncryptionConfigBuilder().readFromFile(new InputStreamReader(TestUtils.getResourceAsStream(this.getClass(), resource))).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static edu.mit.ll.pace.test.Matchers.hasSameFieldsAs;
|
||||
import static edu.mit.ll.pace.test.TestUtils.getResourceAsStream;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Factory;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.ini4j.Ini;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
|
||||
/**
|
||||
* Test {@link EncryptionConfig}.
|
||||
*/
|
||||
public class EncryptionConfigTest {
|
||||
|
||||
/**
|
||||
* In memory representation of the config.
|
||||
*/
|
||||
private static final EncryptionConfig config1 = new EncryptionConfigBuilder()
|
||||
.addFieldEncryptorConfig(
|
||||
new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_CBC).setProvider("BC").setKeyLength(16).setEncryptUsingVisibility(false)
|
||||
.setKeyId("keyId").setDestination(EntryField.COLUMN_FAMILY)
|
||||
.setSources(ImmutableSortedSet.of(EntryField.ROW, EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER)).build())
|
||||
.addFieldEncryptorConfig(
|
||||
new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_SIV_DETERMINISTIC).setKeyLength(32).setEncryptUsingVisibility(false)
|
||||
.setKeyId("keyId2").setDestination(EntryField.COLUMN_QUALIFIER)
|
||||
.setSources(ImmutableSortedSet.of(EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER)).build())
|
||||
.addFieldEncryptorConfig(
|
||||
new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_GCM).setKeyLength(24).setEncryptUsingVisibility(true).setKeyId("AES_GCM")
|
||||
.setDestination(EntryField.VALUE).setSources(ImmutableSortedSet.of(EntryField.VALUE)).build()).build();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder testFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void constructorExceptionTests() throws IOException {
|
||||
try {
|
||||
new EncryptionConfigBuilder().setFieldEncryptorConfigs(null).build();
|
||||
fail("null fieldEncryptionConfigs should not be allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptionConfigBuilder().readFromFile(getReader("emptyConfig.ini")).build();
|
||||
fail("empty fieldEncryptionConfigs should not be allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new EncryptionConfigBuilder().setFieldEncryptorConfigs(
|
||||
ImmutableList.of(
|
||||
new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_GCM).setProvider("BC").setKeyLength(32).setEncryptUsingVisibility(true)
|
||||
.setKeyId("AES_GCM").setDestination(EntryField.VALUE).setSources(ImmutableSortedSet.of(EntryField.VALUE)).build(),
|
||||
new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_GCM).setProvider("BC").setKeyLength(32).setEncryptUsingVisibility(true)
|
||||
.setKeyId("AES_GCM").setDestination(EntryField.VALUE).setSources(ImmutableSortedSet.of(EntryField.VALUE)).build())).build();
|
||||
fail("duplicate fieldEncryptionConfigs should not be allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTest() throws IOException {
|
||||
EncryptionConfig actual = new EncryptionConfigBuilder().readFromFile(getReader("config1.ini")).build();
|
||||
assertThat("reading the ini produces the correct configuration", actual, is(equalTo(config1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeTest() throws IOException {
|
||||
File iniFile = testFolder.newFile();
|
||||
FileWriter out = new FileWriter(iniFile);
|
||||
config1.write(out);
|
||||
out.close();
|
||||
|
||||
Ini actual = new Ini(new FileReader(iniFile));
|
||||
Ini expected = getIni("config1.ini");
|
||||
assertThat("reading the ini produces the correct configuration", actual, is(Matchers.equalTo(expected)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReadTest() throws IOException {
|
||||
File ini = testFolder.newFile();
|
||||
FileWriter out = new FileWriter(ini);
|
||||
config1.write(out);
|
||||
out.close();
|
||||
|
||||
FileReader in = new FileReader(ini);
|
||||
EncryptionConfig config2 = new EncryptionConfigBuilder().readFromFile(in).build();
|
||||
in.close();
|
||||
|
||||
assertThat("writing then reading the config should produce an equivalent configuration", config2, is(equalTo(config1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hamcrest matcher that tests whether two {@link EncryptionConfig} are equal.
|
||||
*
|
||||
* @param expectedValue
|
||||
* The expected value to core against.
|
||||
* @return Method safe matcher that will core field equality.
|
||||
*/
|
||||
@Factory
|
||||
private static TypeSafeMatcher<EncryptionConfig> equalTo(final EncryptionConfig expectedValue) {
|
||||
final TreeMap<EntryField,TypeSafeMatcher<FieldEncryptorConfig>> configMatchers = new TreeMap<>();
|
||||
for (FieldEncryptorConfig config : expectedValue.fieldEncryptorConfigs) {
|
||||
configMatchers.put(config.destination, hasSameFieldsAs(config));
|
||||
}
|
||||
|
||||
return new TypeSafeMatcher<EncryptionConfig>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(EncryptionConfig actualValue) {
|
||||
if (actualValue.fieldEncryptorConfigs.size() != configMatchers.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (FieldEncryptorConfig config : actualValue.fieldEncryptorConfigs) {
|
||||
TypeSafeMatcher<FieldEncryptorConfig> matcher = configMatchers.get(config.destination);
|
||||
if (matcher == null || !matcher.matches(config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void describeMismatchSafely(EncryptionConfig actualValue, Description description) {
|
||||
description.appendText("was [\n");
|
||||
for (FieldEncryptorConfig config : actualValue.fieldEncryptorConfigs) {
|
||||
description.appendText("\t\t{").appendText(ReflectionToStringBuilder.toString(config, ToStringStyle.SIMPLE_STYLE)).appendText("}\n");
|
||||
}
|
||||
description.appendText("\t]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("[\n");
|
||||
for (FieldEncryptorConfig config : expectedValue.fieldEncryptorConfigs) {
|
||||
description.appendText("\t\t{").appendText(ReflectionToStringBuilder.toString(config, ToStringStyle.SIMPLE_STYLE)).appendText("}\n");
|
||||
}
|
||||
description.appendText("\t]");
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested INI.
|
||||
*
|
||||
* @param resource
|
||||
* INI file to get.
|
||||
* @return The INI file.
|
||||
*/
|
||||
private Ini getIni(String resource) throws IOException {
|
||||
return new Ini(getResourceAsStream(this.getClass(), resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested INI.
|
||||
*
|
||||
* @param resource
|
||||
* INI file to get.
|
||||
* @return The INI file.
|
||||
*/
|
||||
private Reader getReader(String resource) throws IOException {
|
||||
return new InputStreamReader(getResourceAsStream(this.getClass(), resource));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.EMPTY;
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static edu.mit.ll.pace.test.TestUtils.getResourceAsStream;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.Security;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.accumulo.core.data.Column;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.hadoop.io.Text;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.mit.ll.pace.encryption.EntryEncryptor.ColumnFilterResult;
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test {@link EntryEncryptor}.
|
||||
*/
|
||||
public class EntryEncryptorTest {
|
||||
|
||||
/**
|
||||
* The mock key container, filled with several keys.
|
||||
*/
|
||||
private static EncryptionKeyContainer keys = new MockEncryptionKeyContainer(Pair.of("searchable_row", 2), Pair.of("searchable_colFamily", 2), Pair.of(
|
||||
"searchable_colQualifier", 2), Pair.of("searchable_row", 2), Pair.of("secret", 2), Pair.of("top secret", 1), Pair.of("admin", 1), Pair.of("AES_GCM", 2),
|
||||
Pair.of("deterministic", 2));
|
||||
|
||||
/**
|
||||
* Ensure that BouncyCastleProvider is registered.
|
||||
*/
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorExceptionTests() throws Exception {
|
||||
try {
|
||||
new EntryEncryptor(null, keys);
|
||||
fail("config must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getEncryptor("encrypt-value.ini", null); // Implicitly tests a good config, but a null key set.
|
||||
fail("keys must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullEntryTest() throws Exception {
|
||||
EntryEncryptor encryptor = getEncryptor("encrypt-entry.ini");
|
||||
|
||||
try {
|
||||
encryptor.encrypt(null);
|
||||
fail("cannot call encrypt with a null value");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
encryptor.decrypt(null);
|
||||
fail("cannot call decrypt with a null value");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptDecryptTest() throws Exception {
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET),
|
||||
(long) 5, false), new Value(new byte[] {6}));
|
||||
Entry<Key,Value> encrypted, decrypted;
|
||||
|
||||
for (EntryEncryptor encryptor : getEncryptors()) { // Test all the various configs.
|
||||
encrypted = encryptor.encrypt(entry);
|
||||
assertThat("encrypting should change the data", encrypted, not(Matchers.equalTo(entry)));
|
||||
decrypted = encryptor.decrypt(encrypted);
|
||||
assertThat("encrypting than decrypting should return the original value", decrypted, Matchers.equalTo(entry));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBeDeletedServerSideTest() throws Exception {
|
||||
assertThat("non-encrypted key can be updated", getEncryptor("encrypt-value.ini").canBeDeleteServerSide(), is(true));
|
||||
assertThat("non-deterministically encrypted key cannot be updated", getEncryptor("encrypt-entry.ini").canBeDeleteServerSide(), is(false));
|
||||
assertThat("searchable key can be updated", getEncryptor("searchable.ini").canBeDeleteServerSide(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeleteKeysTest() throws Exception {
|
||||
EntryEncryptor encryptor = getEncryptor("deterministic.ini");
|
||||
MutableEntry searchKey = new MutableEntry(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), (long) 5, false));
|
||||
|
||||
Collection<Key> keys = encryptor.getDeleteKeys(searchKey.toKey());
|
||||
assertThat("has correct number of delete keys", keys, hasSize(2));
|
||||
|
||||
for (Key deleteKey : keys) {
|
||||
MutableEntry key = new MutableEntry(deleteKey);
|
||||
assertThat("row is encrypted", key.row, not(equalTo(searchKey.row)));
|
||||
assertThat("colF is zeroes", key.colF, equalTo(EMPTY));
|
||||
assertThat("colQ is plaintext", key.colQ, equalTo(searchKey.colQ));
|
||||
assertThat("colQ is plaintext", key.colVis, equalTo(searchKey.colVis));
|
||||
assertThat("colQ is plaintext", key.timestamp, equalTo(searchKey.timestamp));
|
||||
assertThat("delete is true", key.delete, is(true));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeleteKeysExceptionTest() throws Exception {
|
||||
EntryEncryptor encryptor = getEncryptor("encrypt-entry.ini");
|
||||
|
||||
try {
|
||||
encryptor.getDeleteKeys(null);
|
||||
fail("cannot call delete keys with a null value");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
encryptor = getEncryptor("encrypt-entry.ini");
|
||||
|
||||
try {
|
||||
encryptor.getDeleteKeys(new Key());
|
||||
fail("cannot call delete keys when the encryptor does not allow server side delete");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getColumnFamilyFilterTest() throws Exception {
|
||||
byte[] columnFamily = new byte[] {2};
|
||||
|
||||
ColumnFilterResult result = getEncryptor("encrypt-value.ini").getColumnFamilyFilter(new Text(columnFamily));
|
||||
assertThat("unencrypted column is searchable server side", result.serverSideFilters, hasSize(1));
|
||||
assertThat("unencrypted column is searchable server side", result.serverSideFilters, containsInAnyOrder(new Column(columnFamily, null, null)));
|
||||
assertThat("unencrypted column does not need client-side search", result.needsClientSideFiltering, is(false));
|
||||
|
||||
result = getEncryptor("zeroed3.ini").getColumnFamilyFilter(new Text(columnFamily));
|
||||
assertThat("zeroed column is searchable server side", result.serverSideFilters, hasSize(1));
|
||||
assertThat("zeroed column is searchable server side", result.serverSideFilters, containsInAnyOrder(new Column(EMPTY, null, null)));
|
||||
assertThat("zeroed column needs client-side search", result.needsClientSideFiltering, is(true));
|
||||
|
||||
result = getEncryptor("encrypt-entry.ini").getColumnFamilyFilter(new Text(columnFamily));
|
||||
assertThat("non-deterministically encrypted column is not searchable server side", result.serverSideFilters, hasSize(0));
|
||||
assertThat("non-deterministically encrypted column needs client-side search", result.needsClientSideFiltering, is(true));
|
||||
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(EMPTY, columnFamily, EMPTY, EMPTY, (long) 0, false), new Value(EMPTY));
|
||||
MutableEntry encrypted1 = new MutableEntry(getEncryptor("filterable.ini",
|
||||
new MockEncryptionKeyContainer(Pair.of("searchable_colFamily", 1), Pair.of("searchable_colQualifier", 1))).encrypt(entry));
|
||||
MutableEntry encrypted2 = new MutableEntry(getEncryptor("filterable.ini",
|
||||
new MockEncryptionKeyContainer(Pair.of("searchable_colFamily", 2), Pair.of("searchable_colQualifier", 2))).encrypt(entry));
|
||||
|
||||
result = getEncryptor("filterable.ini").getColumnFamilyFilter(new Text(columnFamily));
|
||||
assertThat("deterministically encrypted column is searchable server side", result.serverSideFilters, hasSize(2));
|
||||
assertThat("deterministically encrypted column is searchable server side", result.serverSideFilters,
|
||||
containsInAnyOrder(new Column(encrypted1.colF, null, null), new Column(encrypted2.colF, null, null)));
|
||||
assertThat("deterministically column does not needs client-side search", result.needsClientSideFiltering, is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getColumnFamilyFilterExceptionTest() throws Exception {
|
||||
EntryEncryptor encryptor = getEncryptor("encrypt-entry.ini");
|
||||
|
||||
try {
|
||||
encryptor.getColumnFamilyFilter(null);
|
||||
fail("cannot call filter with a null value");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getColumnFilterTest() throws Exception {
|
||||
byte[] columnFamily = new byte[] {2};
|
||||
byte[] columnQualifier = new byte[] {3};
|
||||
|
||||
// Unencrypted column qualifier
|
||||
ColumnFilterResult result = getEncryptor("encrypt-value.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("unencrypted column is searchable server side", result.serverSideFilters, hasSize(1));
|
||||
assertThat("unencrypted column is searchable server side", result.serverSideFilters, containsInAnyOrder(new Column(columnFamily, columnQualifier, null)));
|
||||
assertThat("unencrypted column does not need client-side search", result.needsClientSideFiltering, is(false));
|
||||
|
||||
// Zeroed column qualifier
|
||||
result = getEncryptor("zeroed1.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("zeroed column is searchable server side", result.serverSideFilters, hasSize(2));
|
||||
assertThat("zeroed column needs client-side search", result.needsClientSideFiltering, is(false));
|
||||
|
||||
result = getEncryptor("zeroed2.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("zeroed column is searchable server side", result.serverSideFilters, hasSize(2));
|
||||
assertThat("zeroed column needs client-side search", result.needsClientSideFiltering, is(true));
|
||||
|
||||
result = getEncryptor("zeroed3.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("zeroed column is searchable server side", result.serverSideFilters, hasSize(2));
|
||||
assertThat("zeroed column needs client-side search", result.needsClientSideFiltering, is(false));
|
||||
|
||||
result = getEncryptor("zeroed4.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("zeroed column is searchable server side", result.serverSideFilters, hasSize(2));
|
||||
assertThat("zeroed column needs client-side search", result.needsClientSideFiltering, is(true));
|
||||
|
||||
result = getEncryptor("zeroed5.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("zeroed column is searchable server side", result.serverSideFilters, hasSize(1));
|
||||
assertThat("zeroed column needs client-side search", result.needsClientSideFiltering, is(true));
|
||||
|
||||
// Non-deterministic encrypted column qualifier
|
||||
result = getEncryptor("encrypt-entry.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("non-deterministically encrypted column is not searchable server side", result.serverSideFilters, hasSize(0));
|
||||
assertThat("non-deterministically encrypted column needs client-side search", result.needsClientSideFiltering, is(true));
|
||||
|
||||
result = getEncryptor("filterable2.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("non-deterministically encrypted column is not searchable server side", result.serverSideFilters, hasSize(2));
|
||||
assertThat("non-deterministically encrypted column needs client-side search", result.needsClientSideFiltering, is(false));
|
||||
|
||||
// Deterministically encrypted column qualifier
|
||||
result = getEncryptor("filterable.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("deterministically encrypted column is searchable server side", result.serverSideFilters, hasSize(4));
|
||||
assertThat("deterministically column does not needs client-side search", result.needsClientSideFiltering, is(false));
|
||||
|
||||
result = getEncryptor("filterable3.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("deterministically encrypted column is searchable server side", result.serverSideFilters, hasSize(2));
|
||||
assertThat("deterministically column does not needs client-side search", result.needsClientSideFiltering, is(false));
|
||||
|
||||
result = getEncryptor("filterable4.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("deterministically encrypted column is searchable server side", result.serverSideFilters, hasSize(2));
|
||||
assertThat("deterministically column does not needs client-side search", result.needsClientSideFiltering, is(true));
|
||||
|
||||
result = getEncryptor("filterable5.ini").getColumnFilter(new Text(columnFamily), new Text(columnQualifier));
|
||||
assertThat("deterministically encrypted column is searchable server side", result.serverSideFilters, hasSize(0));
|
||||
assertThat("deterministically column does not needs client-side search", result.needsClientSideFiltering, is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getColumnFilterExceptionTest() throws Exception {
|
||||
EntryEncryptor encryptor = getEncryptor("encrypt-entry.ini");
|
||||
|
||||
try {
|
||||
encryptor.getColumnFilter(null, new Text());
|
||||
fail("cannot call filter with a null value");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
encryptor.getColumnFilter(new Text(), null);
|
||||
fail("cannot call filter with a null value");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformRangeTest() throws Exception {
|
||||
// only value encrypted
|
||||
Range range = new Range("A");
|
||||
Collection<Range> serverSideRanges = new ArrayList<>();
|
||||
|
||||
boolean result = getEncryptor("encrypt-value.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(1));
|
||||
assertThat("should be filtered server side", serverSideRanges, contains(range));
|
||||
assertThat("should not be filtered client side", result, is(false));
|
||||
|
||||
// all fields encrypted
|
||||
range = new Range("A");
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("encrypt-entry.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should not be filtered server side", serverSideRanges, hasSize(1));
|
||||
assertThat("should not be filtered server side", serverSideRanges, contains(new Range()));
|
||||
assertThat("should be filtered client side", result, is(true));
|
||||
|
||||
// infinite range
|
||||
range = new Range();
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("searchable.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(1));
|
||||
assertThat("should be filtered server side", serverSideRanges, contains(range));
|
||||
assertThat("should not be filtered client side", result, is(false));
|
||||
|
||||
// single value search
|
||||
range = new Range(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 0, false), new Key(new byte[] {1},
|
||||
new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 0, false));
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("searchable.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(8)); // 2 different key versions for each of the three search fields.
|
||||
assertThat("should not be filtered client side", result, is(false));
|
||||
|
||||
// single value search, change unimportant variables
|
||||
range = new Range(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 1, false), new Key(new byte[] {1},
|
||||
new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 0, true));
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("searchable.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(8)); // 2 different key versions for each of the three search fields.
|
||||
assertThat("should not be filtered client side", result, is(false));
|
||||
|
||||
// range search, one searchable field
|
||||
range = new Range(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 0, false), new Key(new byte[] {1},
|
||||
new byte[] {4}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 0, false));
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("searchable.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(2)); // 2 different key versions for the one searchable field.
|
||||
assertThat("should not be filtered client side", result, is(true));
|
||||
|
||||
// range search, two searchable fields
|
||||
range = new Range(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 0, false), new Key(new byte[] {1},
|
||||
new byte[] {2}, new byte[] {4}, "secret".getBytes(VISIBILITY_CHARSET), 0, false));
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("searchable.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(4)); // 2 different key versions for the two searchable field.
|
||||
assertThat("should not be filtered client side", result, is(true));
|
||||
|
||||
// range search, involves infinite key
|
||||
range = new Range(null, new Key(new byte[] {1}, new byte[] {2}, new byte[] {4}, "secret".getBytes(VISIBILITY_CHARSET), 0, false));
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("searchable2.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(1)); // a range that is the first part of the search.
|
||||
assertThat("should not be filtered client side", result, is(true));
|
||||
|
||||
range = new Range(new Key(new byte[] {1}, new byte[] {2}, new byte[] {4}, "secret".getBytes(VISIBILITY_CHARSET), 0, false), null);
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("searchable2.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(1)); // a range that is the first part of the search.
|
||||
assertThat("should not be filtered client side", result, is(true));
|
||||
|
||||
// single point search, non-deterministic encryption
|
||||
range = new Range(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 0, false), new Key(new byte[] {1},
|
||||
new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET), 0, false));
|
||||
serverSideRanges.clear();
|
||||
|
||||
result = getEncryptor("searchable3.ini", keys).transformRange(range, serverSideRanges);
|
||||
assertThat("should be filtered server side", serverSideRanges, hasSize(2)); // 2 different key versions for the one searchable field.
|
||||
assertThat("should not be filtered client side", result, is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformRangeExceptionTest() throws Exception {
|
||||
Range range = new Range();
|
||||
Collection<Range> serverSideRanges = new ArrayList<>();
|
||||
EntryEncryptor encryptor = getEncryptor("searchable.ini");
|
||||
|
||||
try {
|
||||
encryptor.transformRange(null, serverSideRanges);
|
||||
fail("cannot call with a null range");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
encryptor.transformRange(range, null);
|
||||
fail("cannot call with a null serverSideRanges");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an encryptor. The default set of keys will be used.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @return EntryEncryptor.
|
||||
*/
|
||||
private EntryEncryptor getEncryptor(String resource) throws Exception {
|
||||
return getEncryptor(resource, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an encryptor.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @param keys
|
||||
* Keys to use for the encryptor.
|
||||
* @return EntryEncryptor.
|
||||
*/
|
||||
private EntryEncryptor getEncryptor(String resource, EncryptionKeyContainer keys) throws Exception {
|
||||
return new EntryEncryptor(new EncryptionConfigBuilder().readFromFile(new InputStreamReader(getResourceAsStream(this.getClass(), resource))).build(), keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value encryptors for this test class. The default set of keys will be used.
|
||||
*
|
||||
* @return Entry encryptors.
|
||||
*/
|
||||
private Collection<EntryEncryptor> getEncryptors() throws Exception {
|
||||
List<EntryEncryptor> encryptors = new ArrayList<>();
|
||||
for (String line : IOUtils.readLines(this.getClass().getResourceAsStream(this.getClass().getSimpleName()))) {
|
||||
encryptors.add(new EntryEncryptor(new EncryptionConfigBuilder().readFromFile(new InputStreamReader(TestUtils.getResourceAsStream(this.getClass(), line)))
|
||||
.build(), keys));
|
||||
}
|
||||
return encryptors;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static edu.mit.ll.pace.test.Matchers.hasSameFieldsAs;
|
||||
import static edu.mit.ll.pace.test.TestUtils.getResourceAsStream;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.ini4j.Ini;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
|
||||
/**
|
||||
* Test {@link FieldEncryptorConfig}
|
||||
*/
|
||||
public class FieldEncryptorConfigTest {
|
||||
|
||||
@Test
|
||||
public void constructorExceptionTests() throws IOException {
|
||||
|
||||
try {
|
||||
getValidBuilder().setValueEncryptor(null).build();
|
||||
fail("valueEnctypor must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setKeyLength(1000).build();
|
||||
fail("keyLength must be appropriate for the encryptor");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setKeyId(null).build();
|
||||
fail("keyId must be set");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setKeyId("").build();
|
||||
fail("keyId must be set");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setValueEncryptor(ValueEncryptor.AES_SIV_DETERMINISTIC).setEncryptUsingVisibility(true).build();
|
||||
fail("deterministic encryption cannot be used when keys are created using the visibility");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(null).build();
|
||||
fail("destination must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(EntryField.COLUMN_VISIBILITY).build();
|
||||
fail("destination must not be column visibility");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(EntryField.TIMESTAMP).build();
|
||||
fail("destination must not be timestamp");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(EntryField.DELETE).build();
|
||||
fail("destination must not be delete");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setSources(null).build();
|
||||
fail("sources must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setSources(new TreeSet<>()).build();
|
||||
fail("sources must not be empty");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTest() throws IOException {
|
||||
FieldEncryptorConfig expectedConfig = new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_CBC).setProvider("SunJCE").setKeyLength(24)
|
||||
.setEncryptUsingVisibility(false).setKeyId("keyId").setDestination(EntryField.COLUMN_FAMILY)
|
||||
.setSources(ImmutableSortedSet.of(EntryField.ROW, EntryField.COLUMN_QUALIFIER, EntryField.COLUMN_FAMILY)).build();
|
||||
FieldEncryptorConfig actualConfig = new FieldEncryptorConfigBuilder().readFromIni(getIni("config1.ini").get(EntryField.COLUMN_FAMILY.toString())).build();
|
||||
assertThat("reading the ini file produces the correct configuration", actualConfig, hasSameFieldsAs(expectedConfig));
|
||||
|
||||
expectedConfig = new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_CBC).setProvider("BC").setKeyLength(16)
|
||||
.setEncryptUsingVisibility(true).setKeyId("AES_CBC").setDestination(EntryField.ROW).setSources(ImmutableSortedSet.of(EntryField.ROW)).build();
|
||||
actualConfig = new FieldEncryptorConfigBuilder().readFromIni(getIni("config2.ini").get(EntryField.ROW.toString())).build();
|
||||
assertThat("reading the ini file produces the correct configuration", actualConfig, hasSameFieldsAs(expectedConfig));
|
||||
|
||||
expectedConfig = new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_CBC).setKeyLength(16).setEncryptUsingVisibility(true)
|
||||
.setKeyId("AES_CBC").setDestination(EntryField.ROW).setSources(ImmutableSortedSet.of(EntryField.ROW)).build();
|
||||
actualConfig = new FieldEncryptorConfigBuilder().readFromIni(getIni("config2WithoutDefaults.ini").get(EntryField.ROW.toString())).build();
|
||||
assertThat("default values are correctly set", actualConfig, hasSameFieldsAs(expectedConfig));
|
||||
|
||||
expectedConfig = new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_CBC).setProvider(null).setKeyLength(16)
|
||||
.setEncryptUsingVisibility(false).setKeyId(ValueEncryptor.AES_CBC.toString()).setDestination(EntryField.VALUE)
|
||||
.setSources(ImmutableSortedSet.of(EntryField.VALUE)).build();
|
||||
actualConfig = new FieldEncryptorConfigBuilder().readFromIni(getIni("config3.ini").get(EntryField.VALUE.toString())).build();
|
||||
assertThat("reading the ini file produces the correct configuration", actualConfig, hasSameFieldsAs(expectedConfig));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeTest() throws IOException {
|
||||
FieldEncryptorConfig config = new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_CBC).setProvider("SunJCE").setKeyLength(24)
|
||||
.setEncryptUsingVisibility(false).setKeyId("keyId").setDestination(EntryField.COLUMN_FAMILY)
|
||||
.setSources(ImmutableSortedSet.of(EntryField.ROW, EntryField.COLUMN_QUALIFIER, EntryField.COLUMN_FAMILY)).build();
|
||||
|
||||
Ini ini = new Ini();
|
||||
config.write(ini);
|
||||
assertThat("the configuration is properly written to an ini", ini, is(Matchers.equalTo(getIni("config1.ini"))));
|
||||
|
||||
config = new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_CBC).setProvider("BC").setKeyLength(16).setEncryptUsingVisibility(true)
|
||||
.setKeyId("AES_CBC").setDestination(EntryField.ROW).setSources(ImmutableSortedSet.of(EntryField.ROW)).build();
|
||||
ini = new Ini();
|
||||
config.write(ini);
|
||||
assertThat("empty fields are not included in the output ini", ini, is(Matchers.equalTo(getIni("config2.ini"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReadTest() {
|
||||
FieldEncryptorConfig config = new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_CBC).setProvider("BC").setKeyLength(32)
|
||||
.setEncryptUsingVisibility(false).setKeyId("keyId").setDestination(EntryField.COLUMN_FAMILY)
|
||||
.setSources(ImmutableSortedSet.of(EntryField.ROW, EntryField.COLUMN_FAMILY, EntryField.COLUMN_QUALIFIER)).build();
|
||||
|
||||
Ini ini = new Ini();
|
||||
config.write(ini);
|
||||
|
||||
FieldEncryptorConfig config2 = new FieldEncryptorConfigBuilder().readFromIni(ini.values().iterator().next()).build();
|
||||
assertThat("writing then reading the config should produce an equivalent configuration", config2, hasSameFieldsAs(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a valid config builder.
|
||||
*
|
||||
* @return Valid builder.
|
||||
*/
|
||||
private static FieldEncryptorConfigBuilder getValidBuilder() {
|
||||
return new FieldEncryptorConfigBuilder().setValueEncryptor(ValueEncryptor.AES_GCM).setKeyLength(ValueEncryptor.AES_GCM.getDefaultKeyLength())
|
||||
.setEncryptUsingVisibility(true).setDestination(EntryField.VALUE).setSources(ImmutableSortedSet.of(EntryField.VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested INI.
|
||||
*
|
||||
* @param resource
|
||||
* INI file to get.
|
||||
* @return The INI file.
|
||||
*/
|
||||
private Ini getIni(String resource) throws IOException {
|
||||
return new Ini(getResourceAsStream(this.getClass(), resource));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static edu.mit.ll.pace.test.TestUtils.getResourceAsStream;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.ColumnVisibility;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.ini4j.Ini;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
import edu.mit.ll.pace.IllegalKeyRequestException;
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
|
||||
/**
|
||||
* Test {@link FieldEncryptor}.
|
||||
*/
|
||||
public class FieldEncryptorTest {
|
||||
|
||||
/**
|
||||
* The mock key container, filled with several keys.
|
||||
*/
|
||||
private static EncryptionKeyContainer keys = new MockEncryptionKeyContainer(Pair.of("SIV", 2), Pair.of("GCM", 2), Pair.of("identity1", 1), Pair.of(
|
||||
"identity2", 2), Pair.of("secret", 2), Pair.of("top secret", 1), Pair.of("admin", 1));
|
||||
|
||||
@BeforeClass
|
||||
public static void registerBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorExceptionTests() throws Exception {
|
||||
try {
|
||||
new FieldEncryptor(null, keys);
|
||||
fail("config must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getEncryptor("gcm.ini", null); // Implicitly tests a good config, but a null key set.
|
||||
fail("keys must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptTest() throws Exception {
|
||||
MutableEntry original = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6})));
|
||||
MutableEntry empty = new MutableEntry();
|
||||
ColumnVisibility visibility = new ColumnVisibility("secret");
|
||||
|
||||
// gcmEncryptor
|
||||
MutableEntry encrypted = new MutableEntry();
|
||||
FieldEncryptor encryptor = getEncryptor("gcm.ini");
|
||||
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
assertThat("row was not encrypted and should not be set", encrypted.row, is(empty.row));
|
||||
assertThat("colFamily was not encrypted and should not be set", encrypted.colF, is(empty.colF));
|
||||
assertThat("colQualifier was not encrypted and should not be set", encrypted.colQ, is(empty.colQ));
|
||||
assertThat("colVisibility was not encrypted and should not be set", encrypted.colVis, is(empty.colVis));
|
||||
assertThat("timestamp was not encrypted and should not be set", encrypted.timestamp, is(empty.timestamp));
|
||||
assertThat("delete was not encrypted and should not be set", encrypted.delete, is(empty.delete));
|
||||
assertThat("value was encrypted and should be different", encrypted.value, is(not(original.value)));
|
||||
|
||||
// gcmCEABACEncryptor
|
||||
encrypted = new MutableEntry();
|
||||
encryptor = getEncryptor("gcmCEABAC.ini");
|
||||
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
assertThat("row was not encrypted and should not be set", encrypted.row, is(empty.row));
|
||||
assertThat("colFamily was not encrypted and should not be set", encrypted.colF, is(empty.colF));
|
||||
assertThat("colQualifier was not encrypted and should not be set", encrypted.colQ, is(empty.colQ));
|
||||
assertThat("colVisibility was not encrypted and should not be set", encrypted.colVis, is(empty.colVis));
|
||||
assertThat("timestamp was not encrypted and should not be set", encrypted.timestamp, is(empty.timestamp));
|
||||
assertThat("delete was not encrypted and should not be set", encrypted.delete, is(empty.delete));
|
||||
assertThat("value was encrypted and should be different", encrypted.value, is(not(original.value)));
|
||||
|
||||
// sivEncryptor
|
||||
encrypted = new MutableEntry();
|
||||
encryptor = getEncryptor("siv.ini");
|
||||
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
assertThat("row was encrypted and should be different", encrypted.row, is(not(original.row)));
|
||||
assertThat("colFamily was not encrypted and should not be set", encrypted.colF, is(empty.colF));
|
||||
assertThat("colQualifier was not encrypted and should not be set", encrypted.colQ, is(empty.colQ));
|
||||
assertThat("colVisibility was not encrypted and should not be set", encrypted.colVis, is(empty.colVis));
|
||||
assertThat("timestamp was not encrypted and should not be set", encrypted.timestamp, is(empty.timestamp));
|
||||
assertThat("delete was not encrypted and should not be set", encrypted.delete, is(empty.delete));
|
||||
assertThat("value was not encrypted and should not be set", encrypted.value, is(empty.value));
|
||||
|
||||
// allEncryptor
|
||||
encrypted = new MutableEntry();
|
||||
encryptor = getEncryptor("all.ini");
|
||||
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
assertThat("row was encrypted and should not be different", encrypted.row, is(not(original.row)));
|
||||
assertThat("colFamily was encrypted and should not be different", encrypted.colF, is(not(original.colF)));
|
||||
assertThat("colQualifier was encrypted and should not be different", encrypted.colQ, is(not(original.colQ)));
|
||||
assertThat("colVisibility was encrypted and should be different", encrypted.colVis, is(not(original.colVis)));
|
||||
assertThat("timestamp was encrypted and should not be different", encrypted.timestamp, is(empty.timestamp));
|
||||
assertThat("delete was encrypted, but and should not be different", encrypted.delete, is(empty.delete));
|
||||
assertThat("value was encrypted and should not be different", encrypted.value, is(not(original.value)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptedFieldStructureTest() throws Exception {
|
||||
MutableEntry original1 = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6, 7, 8})));
|
||||
MutableEntry original2 = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {9}, new byte[] {10, 11}, new byte[] {12, 13, 14},
|
||||
"abcde".getBytes(VISIBILITY_CHARSET), (long) 15, true), new Value(new byte[] {16, 17})));
|
||||
ColumnVisibility visibility = new ColumnVisibility("secret");
|
||||
|
||||
// Encrypt value with version 1 key.
|
||||
MutableEntry encrypted = new MutableEntry();
|
||||
FieldEncryptor encryptor = getEncryptor("identity1.ini"); // Uses a key with version = 1.
|
||||
IdentityEncryptor.replaceValueEncryptorWithIdentityFunction(encryptor);
|
||||
|
||||
encryptor.encrypt(original1, encrypted, visibility);
|
||||
assertThat("encrypted structure is (key version, length, data)", encrypted.value, is(new byte[] {1, 3, 6, 7, 8}));
|
||||
|
||||
encryptor.encrypt(original2, encrypted, visibility);
|
||||
assertThat("encrypted structure is (key version, length, data)", encrypted.value, is(new byte[] {1, 2, 16, 17}));
|
||||
|
||||
// Encrypt value with version 2 key.
|
||||
encrypted = new MutableEntry();
|
||||
encryptor = getEncryptor("identity2.ini"); // Uses a key with version = 2.
|
||||
IdentityEncryptor.replaceValueEncryptorWithIdentityFunction(encryptor);
|
||||
|
||||
encryptor.encrypt(original1, encrypted, visibility);
|
||||
assertThat("encrypted structure is (key version, length, data)", encrypted.value, is(new byte[] {2, 3, 6, 7, 8}));
|
||||
|
||||
// Encrypt all values into single field.
|
||||
encrypted = new MutableEntry();
|
||||
encryptor = getEncryptor("identityAll.ini");
|
||||
IdentityEncryptor.replaceValueEncryptorWithIdentityFunction(encryptor);
|
||||
|
||||
encryptor.encrypt(original1, encrypted, visibility);
|
||||
assertThat("encrypted structure is (key version, foreach field [length, data])", encrypted.row, is(new byte[] {2, 1, 1, 1, 2, 1, 3, 6, 115, 101, 99, 114,
|
||||
101, 116}));
|
||||
|
||||
encryptor.encrypt(original2, encrypted, visibility);
|
||||
assertThat("encrypted structure is (key version, foreach field [length, data])", encrypted.row, is(new byte[] {2, 1, 9, 2, 10, 11, 3, 12, 13, 14, 5, 97,
|
||||
98, 99, 100, 101}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptDecryptTest() throws Exception {
|
||||
MutableEntry original = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6})));
|
||||
MutableEntry empty = new MutableEntry();
|
||||
ColumnVisibility visibility = new ColumnVisibility("secret");
|
||||
|
||||
// gcmEncryptor
|
||||
MutableEntry encrypted = new MutableEntry();
|
||||
MutableEntry decrypted = new MutableEntry();
|
||||
FieldEncryptor encryptor = getEncryptor("gcm.ini");
|
||||
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
encryptor.decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("row was not encrypted and should not be set", decrypted.row, is(empty.row));
|
||||
assertThat("colFamily was not encrypted and should not be set", decrypted.colF, is(empty.colF));
|
||||
assertThat("colQualifier was not encrypted and should not be set", decrypted.colQ, is(empty.colQ));
|
||||
assertThat("colVisibility was not encrypted and should not be set", decrypted.colVis, is(empty.colVis));
|
||||
assertThat("timestamp was not encrypted and should not be set", decrypted.timestamp, is(empty.timestamp));
|
||||
assertThat("delete was not encrypted and should not be set", decrypted.delete, is(empty.delete));
|
||||
assertThat("value was encrypted and should be set", decrypted.value, is(original.value));
|
||||
|
||||
// gcmCEABACEncryptor
|
||||
encrypted = new MutableEntry();
|
||||
decrypted = new MutableEntry();
|
||||
encryptor = getEncryptor("gcmCEABAC.ini");
|
||||
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
encryptor.decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("row was not encrypted and should not be set", decrypted.row, is(empty.row));
|
||||
assertThat("colFamily was not encrypted and should not be set", decrypted.colF, is(empty.colF));
|
||||
assertThat("colQualifier was not encrypted and should not be set", decrypted.colQ, is(empty.colQ));
|
||||
assertThat("colVisibility was not encrypted and should not be set", decrypted.colVis, is(empty.colVis));
|
||||
assertThat("timestamp was not encrypted and should not be set", decrypted.timestamp, is(empty.timestamp));
|
||||
assertThat("delete was not encrypted and should not be set", decrypted.delete, is(empty.delete));
|
||||
assertThat("value was encrypted and should be set", decrypted.value, is(original.value));
|
||||
|
||||
// sivEncryptor
|
||||
encrypted = new MutableEntry();
|
||||
decrypted = new MutableEntry();
|
||||
encryptor = getEncryptor("siv.ini");
|
||||
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
encryptor.decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("row was encrypted and should be set", decrypted.row, is(original.row));
|
||||
assertThat("colFamily was not encrypted and should not be set", decrypted.colF, is(empty.colF));
|
||||
assertThat("colQualifier was not encrypted and should not be set", decrypted.colQ, is(empty.colQ));
|
||||
assertThat("colVisibility was not encrypted and should not be set", decrypted.colVis, is(empty.colVis));
|
||||
assertThat("timestamp was not encrypted and should not be set", decrypted.timestamp, is(empty.timestamp));
|
||||
assertThat("delete was not encrypted and should not be set", decrypted.delete, is(empty.delete));
|
||||
assertThat("value was not encrypted and should not be set", decrypted.value, is(empty.value));
|
||||
|
||||
// allEncryptor
|
||||
encrypted = new MutableEntry();
|
||||
decrypted = new MutableEntry();
|
||||
encryptor = getEncryptor("all.ini");
|
||||
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
encryptor.decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("row was encrypted and should be set", decrypted.row, is(original.row));
|
||||
assertThat("colFamily was encrypted and should be set", decrypted.colF, is(original.colF));
|
||||
assertThat("colQualifier was encrypted and should be set", decrypted.colQ, is(original.colQ));
|
||||
assertThat("colVisibility was encrypted and should be set", decrypted.colVis, is(original.colVis));
|
||||
assertThat("timestamp was not encrypted and should not be set", decrypted.timestamp, is(empty.timestamp));
|
||||
assertThat("delete was not encrypted and should not be set", decrypted.delete, is(empty.delete));
|
||||
assertThat("value was not encrypted and should not be set", decrypted.value, is(empty.value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptDecryptCEABACTest() throws Exception {
|
||||
MutableEntry original = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6})));
|
||||
MutableEntry encrypted = new MutableEntry();
|
||||
MutableEntry decrypted = new MutableEntry();
|
||||
|
||||
FieldEncryptor encryptor = getEncryptor("gcmCEABAC.ini");
|
||||
MockEncryptionKeyContainer empty = new MockEncryptionKeyContainer();
|
||||
MockEncryptionKeyContainer secretOnly = new MockEncryptionKeyContainer(Pair.of("secret", 2));
|
||||
MockEncryptionKeyContainer topSecretOnly = new MockEncryptionKeyContainer(Pair.of("top secret", 1));
|
||||
MockEncryptionKeyContainer secretAndAdminOnly = new MockEncryptionKeyContainer(Pair.of("secret", 2), Pair.of("admin", 1));
|
||||
|
||||
// empty
|
||||
ColumnVisibility visibility = new ColumnVisibility("");
|
||||
original.colVis = visibility.getExpression();
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
|
||||
getEncryptor("gcmCEABAC.ini", empty).decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("no keys should be needed to decrypt the value", decrypted.value, is(original.value));
|
||||
|
||||
// secret | top secret
|
||||
visibility = new ColumnVisibility("secret|\"top secret\"");
|
||||
original.colVis = visibility.getExpression();
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
|
||||
try {
|
||||
getEncryptor("gcmCEABAC.ini", empty).decrypt(encrypted, decrypted, visibility);
|
||||
fail("empty keys should not decrypt data");
|
||||
} catch (IllegalKeyRequestException e) { /* expected */}
|
||||
|
||||
getEncryptor("gcmCEABAC.ini", secretOnly).decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("secret should be sufficient", decrypted.value, is(original.value));
|
||||
|
||||
getEncryptor("gcmCEABAC.ini", topSecretOnly).decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("top secret should be sufficient", decrypted.value, is(original.value));
|
||||
|
||||
// secret & top secret
|
||||
visibility = new ColumnVisibility("secret&\"top secret\"");
|
||||
original.colVis = visibility.getExpression();
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
|
||||
try {
|
||||
getEncryptor("gcmCEABAC.ini", empty).decrypt(encrypted, decrypted, visibility);
|
||||
fail("empty keys should not decrypt data");
|
||||
} catch (IllegalKeyRequestException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getEncryptor("gcmCEABAC.ini", secretOnly).decrypt(encrypted, decrypted, visibility);
|
||||
fail("secret keys should not decrypt data");
|
||||
} catch (IllegalKeyRequestException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getEncryptor("gcmCEABAC.ini", topSecretOnly).decrypt(encrypted, decrypted, visibility);
|
||||
fail("top secret keys should not decrypt data");
|
||||
} catch (IllegalKeyRequestException e) { /* expected */}
|
||||
|
||||
getEncryptor("gcmCEABAC.ini", keys).decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("secret and topSecret should be sufficient", decrypted.value, is(original.value));
|
||||
|
||||
// top secret | (secret & admin)
|
||||
visibility = new ColumnVisibility("\"top secret\"|(secret&admin)");
|
||||
original.colVis = visibility.getExpression();
|
||||
encryptor.encrypt(original, encrypted, visibility);
|
||||
|
||||
getEncryptor("gcmCEABAC.ini", topSecretOnly).decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("topSecret should be sufficient", decrypted.value, is(original.value));
|
||||
|
||||
try {
|
||||
getEncryptor("gcmCEABAC.ini", secretOnly).decrypt(encrypted, decrypted, visibility);
|
||||
fail("secret keys should not decrypt data");
|
||||
} catch (IllegalKeyRequestException e) { /* expected */}
|
||||
|
||||
getEncryptor("gcmCEABAC.ini", secretAndAdminOnly).decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("secret and admin should be sufficient", decrypted.value, is(original.value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyVersioningTest() throws Exception {
|
||||
MutableEntry original = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6})));
|
||||
ColumnVisibility visibility = new ColumnVisibility("secret");
|
||||
MutableEntry encrypted = new MutableEntry();
|
||||
MutableEntry decrypted = new MutableEntry();
|
||||
|
||||
getEncryptor("gcm.ini", new MockEncryptionKeyContainer(Pair.of("GCM", 1))).encrypt(original, encrypted, visibility);
|
||||
getEncryptor("gcm.ini", new MockEncryptionKeyContainer(Pair.of("GCM", 2))).decrypt(encrypted, decrypted, visibility);
|
||||
assertThat("value was decrypted with proper key", decrypted.value, is(original.value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBeFilteredServerSideTest() throws Exception {
|
||||
assertThat("non-deterministic algorithms can't be filtered server side", getEncryptor("gcm.ini").canBeFilteredServerSide(ImmutableSet.of(EntryField.ROW)),
|
||||
is(false));
|
||||
assertThat("Visibility can't be filtered server side", getEncryptor("gcmCEABAC.ini").canBeFilteredServerSide(ImmutableSet.of(EntryField.ROW)), is(false));
|
||||
assertThat("appropriate columns must be available", getEncryptor("siv.ini").canBeFilteredServerSide(ImmutableSet.of(EntryField.COLUMN_FAMILY)), is(false));
|
||||
assertThat("deterministic encryption with correct fields should work", getEncryptor("siv.ini").canBeFilteredServerSide(ImmutableSet.of(EntryField.ROW)),
|
||||
is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSearchForTest() throws Exception {
|
||||
assertThat("non-deterministic algorithms can't be searched server side", getEncryptor("gcm.ini").canSearchFor(ImmutableSet.of(EntryField.ROW)), is(false));
|
||||
assertThat("Visibility can't be filtered server side", getEncryptor("gcmCEABAC.ini").canSearchFor(ImmutableSet.of(EntryField.ROW)), is(false));
|
||||
assertThat("appropriate columns must be available", getEncryptor("siv.ini").canSearchFor(ImmutableSet.of(EntryField.ROW, EntryField.COLUMN_FAMILY)),
|
||||
is(false));
|
||||
assertThat("deterministic encryption with correct fields should work", getEncryptor("siv.ini").canSearchFor(ImmutableSet.of(EntryField.ROW)), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServerSideFilterValuesTest() throws Exception {
|
||||
MutableEntry original = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6})));
|
||||
ColumnVisibility visibility = new ColumnVisibility("secret");
|
||||
|
||||
MutableEntry encryptedV1 = new MutableEntry();
|
||||
getEncryptor("siv.ini", new MockEncryptionKeyContainer(Pair.of("SIV", 1))).encrypt(original, encryptedV1, visibility);
|
||||
|
||||
MutableEntry encryptedV2 = new MutableEntry();
|
||||
getEncryptor("siv.ini", new MockEncryptionKeyContainer(Pair.of("SIV", 2))).encrypt(original, encryptedV2, visibility);
|
||||
|
||||
List<byte[]> searchKeys = getEncryptor("siv.ini", new MockEncryptionKeyContainer(Pair.of("SIV", 2))).getServerSideFilterValues(original);
|
||||
assertThat("encrypted values for each version available", searchKeys, containsInAnyOrder(encryptedV1.row, encryptedV2.row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an encryptor. The default set of keys will be used.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @return FieldEncryptor.
|
||||
*/
|
||||
private FieldEncryptor getEncryptor(String resource) throws Exception {
|
||||
return getEncryptor(resource, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an encryptor.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @param keys
|
||||
* Keys to use for the encryptor.
|
||||
* @return FieldEncryptor.
|
||||
*/
|
||||
private FieldEncryptor getEncryptor(String resource, EncryptionKeyContainer keys) throws Exception {
|
||||
Ini ini = new Ini(getResourceAsStream(this.getClass(), resource));
|
||||
return new FieldEncryptor(new FieldEncryptorConfigBuilder().readFromIni(ini.values().iterator().next()).build(), keys);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Encryptor that returns data instead of encrypting it.
|
||||
*/
|
||||
public class IdentityEncryptor extends ValueEncryptorBase {
|
||||
|
||||
/**
|
||||
* Field accesser for {@link EntryEncryptor#encryptors}.
|
||||
*/
|
||||
private static Field encryptorsField;
|
||||
|
||||
/**
|
||||
* Field accesser for {@link FieldEncryptor#encryptor}.
|
||||
*/
|
||||
private static Field encryptorField;
|
||||
|
||||
static {
|
||||
try {
|
||||
encryptorsField = EntryEncryptor.class.getDeclaredField("encryptors");
|
||||
encryptorsField.setAccessible(true);
|
||||
|
||||
encryptorField = FieldEncryptor.class.getDeclaredField("encryptor");
|
||||
encryptorField.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the internal {@link ValueEncryptorBase} for each field encryptor that makes up this entry encryptor.
|
||||
* <p>
|
||||
* Useful for testing or demoes.
|
||||
*
|
||||
* @param entryEncryptor
|
||||
* Entry encryptor to modify.
|
||||
*/
|
||||
static void replaceValueEncryptorsWithIdentityFunction(EntryEncryptor entryEncryptor) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<FieldEncryptor> fieldEncryptors = (Collection<FieldEncryptor>) encryptorsField.get(entryEncryptor);
|
||||
for (FieldEncryptor fieldEncryptor : fieldEncryptors) {
|
||||
replaceValueEncryptorWithIdentityFunction(fieldEncryptor);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace field encryptors internal {@link ValueEncryptorBase} to use an identity function instead of actual encryption.
|
||||
* <p>
|
||||
* Useful for testing or demoes.
|
||||
*
|
||||
* @param fieldEncryptor
|
||||
* Field encryptor to modify.
|
||||
*/
|
||||
static void replaceValueEncryptorWithIdentityFunction(FieldEncryptor fieldEncryptor) {
|
||||
try {
|
||||
encryptorField.set(fieldEncryptor, new IdentityEncryptor());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] encrypt(byte[] key, byte[] data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] decrypt(byte[] key, byte[] data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
|
||||
import org.bouncycastle.crypto.params.HKDFParameters;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import edu.mit.ll.pace.IllegalKeyRequestException;
|
||||
|
||||
/**
|
||||
* Mock {@link EncryptionKeyContainer} to use for testing the encryption code.
|
||||
*/
|
||||
public final class MockEncryptionKeyContainer implements EncryptionKeyContainer {
|
||||
|
||||
/**
|
||||
* Key to use in the HKDF function.
|
||||
*/
|
||||
private static byte[] HKDF_KEY = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
|
||||
/**
|
||||
* The keys that will be issued by this container, along with their current version.
|
||||
*/
|
||||
private Map<String,Integer> keys = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
/**
|
||||
* An HKDF to use for key generation.
|
||||
*/
|
||||
private HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA512Digest());
|
||||
|
||||
/**
|
||||
* Create the mock container with the supplied keys and versions.
|
||||
*
|
||||
* @param keys
|
||||
* Keys to add to the mock container.
|
||||
*/
|
||||
@SafeVarargs
|
||||
MockEncryptionKeyContainer(Pair<String,Integer>... keys) {
|
||||
for (Pair<String,Integer> pair : keys) {
|
||||
this.keys.put(pair.getLeft(), pair.getRight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<KeyWithVersion> getKeys(String id, int length) throws IllegalKeyRequestException {
|
||||
if (!keys.containsKey(id)) {
|
||||
throw new IllegalKeyRequestException("getKey: " + id);
|
||||
}
|
||||
|
||||
List<KeyWithVersion> versionedKeys = new ArrayList<>();
|
||||
int maxVersion = keys.get(id);
|
||||
|
||||
// Get a key for each version, past and future.
|
||||
for (int i = 1; i <= maxVersion; i++) {
|
||||
versionedKeys.add(new KeyWithVersion(generateKey(id, i, length), i));
|
||||
}
|
||||
|
||||
return versionedKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyWithVersion getKey(String id, int length) throws IllegalKeyRequestException {
|
||||
if (!keys.containsKey(id)) {
|
||||
throw new IllegalKeyRequestException("getKey: " + id);
|
||||
}
|
||||
int version = keys.get(id);
|
||||
return new KeyWithVersion(generateKey(id, version, length), version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getKey(String id, int version, int length) throws IllegalKeyRequestException {
|
||||
if (!keys.containsKey(id)) {
|
||||
throw new IllegalKeyRequestException("getKey: " + id);
|
||||
}
|
||||
int maxVersion = keys.get(id);
|
||||
if (version > maxVersion) {
|
||||
throw new IllegalArgumentException("invalid version");
|
||||
}
|
||||
|
||||
return generateKey(id, version, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyWithVersion getAttributeKey(String attribute, String id, int length) throws IllegalKeyRequestException {
|
||||
if (!keys.containsKey(attribute)) {
|
||||
throw new IllegalKeyRequestException("getAttributeKey: " + attribute);
|
||||
}
|
||||
int version = keys.get(attribute);
|
||||
return new KeyWithVersion(generateKey(attribute, id, version, length), version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAttributeKey(String attribute, String id, int version, int length) throws IllegalKeyRequestException {
|
||||
|
||||
if (!keys.containsKey(attribute)) {
|
||||
throw new IllegalKeyRequestException("getAttributeKey: " + attribute);
|
||||
}
|
||||
int maxVersion = keys.get(attribute);
|
||||
if (version > maxVersion) {
|
||||
throw new IllegalArgumentException("invalid version");
|
||||
}
|
||||
|
||||
return generateKey(attribute, id, version, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a key from the given data.
|
||||
*
|
||||
* @param id
|
||||
* Id of the key to generate.
|
||||
* @param version
|
||||
* Version of the key to generate.
|
||||
* @param length
|
||||
* Length of the key to generate.
|
||||
* @return Generated key.
|
||||
*/
|
||||
private byte[] generateKey(String id, int version, int length) {
|
||||
return generateKey("keyIdOnly", id, version, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a key from the given data.
|
||||
*
|
||||
* @param attribute
|
||||
* Attribute of the key to generate.
|
||||
* @param id
|
||||
* Id of the key to generate.
|
||||
* @param version
|
||||
* Version of the key to generate.
|
||||
* @param length
|
||||
* Length of the key to generate.
|
||||
* @return Generated key.
|
||||
*/
|
||||
private byte[] generateKey(String attribute, String id, int version, int length) {
|
||||
ByteArrayOutputStream metadata = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
metadata.write(id.getBytes(StandardCharsets.UTF_8));
|
||||
metadata.write(Ints.toByteArray(version));
|
||||
metadata.write(Ints.toByteArray(length));
|
||||
} catch (IOException e) { /* won't be thrown */}
|
||||
|
||||
byte[] key = new byte[length];
|
||||
hkdf.init(new HKDFParameters(HKDF_KEY, attribute.getBytes(StandardCharsets.UTF_8), metadata.toByteArray()));
|
||||
hkdf.generateBytes(key, 0, key.length);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.encryption;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.arrayWithSize;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test {@link ValueEncryptor}.
|
||||
*/
|
||||
public final class ValueEncryptorTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void registerBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validEnumTest() {
|
||||
assertThat("should have six values", ValueEncryptor.values(), is(arrayWithSize(6)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringTest() {
|
||||
assertThat("toString should return correct value", ValueEncryptor.AES_SIV_DETERMINISTIC.toString(), is("AES_SIV_DETERMINISTIC"));
|
||||
assertThat("toString should return correct value", ValueEncryptor.AES_CTR.toString(), is("AES_CTR"));
|
||||
assertThat("toString should return correct value", ValueEncryptor.AES_CFB.toString(), is("AES_CFB"));
|
||||
assertThat("toString should return correct value", ValueEncryptor.AES_CBC.toString(), is("AES_CBC"));
|
||||
assertThat("toString should return correct value", ValueEncryptor.AES_OFB.toString(), is("AES_OFB"));
|
||||
assertThat("toString should return correct value", ValueEncryptor.AES_GCM.toString(), is("AES_GCM"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromStringTest() {
|
||||
assertThat("fromString should return correct enum value", ValueEncryptor.fromString("AES_SIV_DETERMINISTIC"), is(ValueEncryptor.AES_SIV_DETERMINISTIC));
|
||||
assertThat("fromString should return correct enum value", ValueEncryptor.fromString("AES_CTR"), is(ValueEncryptor.AES_CTR));
|
||||
assertThat("fromString should return correct enum value", ValueEncryptor.fromString("AES_CFB"), is(ValueEncryptor.AES_CFB));
|
||||
assertThat("fromString should return correct enum value", ValueEncryptor.fromString("AES_CBC"), is(ValueEncryptor.AES_CBC));
|
||||
assertThat("fromString should return correct enum value", ValueEncryptor.fromString("AES_OFB"), is(ValueEncryptor.AES_OFB));
|
||||
assertThat("fromString should return correct enum value", ValueEncryptor.fromString("AES_GCM"), is(ValueEncryptor.AES_GCM));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void fromStringFailureTest() {
|
||||
ValueEncryptor.fromString("bad");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isDeterministicTest() {
|
||||
assertThat("isDeterministic is true", ValueEncryptor.AES_SIV_DETERMINISTIC.isDeterministic(), is(true));
|
||||
assertThat("isDeterministic is false", ValueEncryptor.AES_CTR.isDeterministic(), is(false));
|
||||
assertThat("isDeterministic is false", ValueEncryptor.AES_CFB.isDeterministic(), is(false));
|
||||
assertThat("isDeterministic is false", ValueEncryptor.AES_CBC.isDeterministic(), is(false));
|
||||
assertThat("isDeterministic is false", ValueEncryptor.AES_OFB.isDeterministic(), is(false));
|
||||
assertThat("isDeterministic is false", ValueEncryptor.AES_GCM.isDeterministic(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDefaultKeyLengthTest() {
|
||||
assertThat("correct default key (32)", ValueEncryptor.AES_SIV_DETERMINISTIC.getDefaultKeyLength(), is(32));
|
||||
assertThat("correct default key (16)", ValueEncryptor.AES_CTR.getDefaultKeyLength(), is(16));
|
||||
assertThat("correct default key (16)", ValueEncryptor.AES_CFB.getDefaultKeyLength(), is(16));
|
||||
assertThat("correct default key (16)", ValueEncryptor.AES_CBC.getDefaultKeyLength(), is(16));
|
||||
assertThat("correct default key (16)", ValueEncryptor.AES_OFB.getDefaultKeyLength(), is(16));
|
||||
assertThat("correct default key (16)", ValueEncryptor.AES_GCM.getDefaultKeyLength(), is(16));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidKeyLengthTest() {
|
||||
for (int keyLength : Arrays.asList(16, 24, 32)) {
|
||||
for (ValueEncryptor encryptor : ValueEncryptor.values()) {
|
||||
switch (encryptor) {
|
||||
case AES_SIV_DETERMINISTIC:
|
||||
assertThat("isValidKeyLength should accept good key lengths", encryptor.isValidKeyLength(keyLength * 2), is(true));
|
||||
assertThat("isValidKeyLength should accept good key lengths", encryptor.isValidKeyLength(keyLength * 2 + 1), is(false));
|
||||
break;
|
||||
|
||||
default:
|
||||
assertThat("isValidKeyLength should accept good key lengths", encryptor.isValidKeyLength(keyLength), is(true));
|
||||
assertThat("isValidKeyLength should accept good key lengths", encryptor.isValidKeyLength(keyLength + 1), is(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInstanceTest() throws Exception {
|
||||
assertThat("should return a valid ValueEncryptorBase instance", ValueEncryptor.AES_SIV_DETERMINISTIC.getInstance(null),
|
||||
is(instanceOf(ValueEncryptorBase.class)));
|
||||
assertThat("should return a valid ValueEncryptorBase instance", ValueEncryptor.AES_CTR.getInstance(null), is(instanceOf(ValueEncryptorBase.class)));
|
||||
assertThat("should return a valid ValueEncryptorBase instance", ValueEncryptor.AES_CFB.getInstance("BC"), is(instanceOf(ValueEncryptorBase.class)));
|
||||
assertThat("should return a valid ValueEncryptorBase instance", ValueEncryptor.AES_CBC.getInstance("BC"), is(instanceOf(ValueEncryptorBase.class)));
|
||||
assertThat("should return a valid ValueEncryptorBase instance", ValueEncryptor.AES_OFB.getInstance(null), is(instanceOf(ValueEncryptorBase.class)));
|
||||
assertThat("should return a valid ValueEncryptorBase instance", ValueEncryptor.AES_GCM.getInstance(null), is(instanceOf(ValueEncryptorBase.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptionTest() throws Exception {
|
||||
// Test each enum value and ensure it can properly encrypt data.
|
||||
for (ValueEncryptor encryptorEnum : ValueEncryptor.values()) {
|
||||
ValueEncryptorBase encryptor = encryptorEnum.getInstance(null);
|
||||
byte[] plaintext = "1234".getBytes(VISIBILITY_CHARSET);
|
||||
byte[] key = "aabbccddaabbccddaabbccddaabbccdd".getBytes(VISIBILITY_CHARSET); // 256-bit key valid for all encryptors.
|
||||
|
||||
byte[] ciphertext1 = encryptor.encrypt(key, plaintext);
|
||||
assertThat("ciphertext should not contain the original plaintext", TestUtils.wrap(ciphertext1), is(not(arrayContaining(TestUtils.wrap(plaintext)))));
|
||||
|
||||
byte[] plaintext1 = encryptor.decrypt(key, ciphertext1);
|
||||
assertThat("decrypting the ciphertext should give the plaintext", plaintext1, is(equalTo(plaintext)));
|
||||
|
||||
byte[] ciphertext2 = encryptor.encrypt(key, "1235".getBytes(VISIBILITY_CHARSET));
|
||||
assertThat("ciphertext should be not be the same when different values are encrypted", ciphertext2, is(not(equalTo(ciphertext1))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.internal;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.EMPTY;
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.accumulo.core.data.ColumnUpdate;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.mit.ll.pace.EntryField;
|
||||
|
||||
/**
|
||||
* Test {@link MutableEntry}.
|
||||
*/
|
||||
public final class MutableEntryTest {
|
||||
|
||||
/**
|
||||
* Testing values.
|
||||
*/
|
||||
private byte[] row, colF, colQ, colVis, value;
|
||||
private long timestamp;
|
||||
private boolean delete;
|
||||
private Key key;
|
||||
private Entry<Key,Value> entry;
|
||||
|
||||
/**
|
||||
* Ensure that the core values are always in a good state.
|
||||
*/
|
||||
@Before
|
||||
public void setupTestValues() {
|
||||
row = new byte[] {(byte) 1};
|
||||
colF = new byte[] {(byte) 2};
|
||||
colQ = new byte[] {(byte) 3};
|
||||
colVis = "A".getBytes(VISIBILITY_CHARSET);
|
||||
value = new byte[] {(byte) 4};
|
||||
|
||||
timestamp = 4;
|
||||
delete = true;
|
||||
|
||||
key = new Key(row, colF, colQ, colVis, timestamp, delete);
|
||||
entry = new SimpleImmutableEntry<>(key, new Value(value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWithKeyTest() {
|
||||
MutableEntry mutableKey = new MutableEntry(key);
|
||||
|
||||
assertThat("row should be unmodified", mutableKey.row, is(equalTo(row)));
|
||||
assertThat("colF should be unmodified", mutableKey.colF, is(equalTo(colF)));
|
||||
assertThat("colQ should be unmodified", mutableKey.colQ, is(equalTo(colQ)));
|
||||
assertThat("colVis should be unmodified", mutableKey.colVis, is(equalTo(colVis)));
|
||||
assertThat("timestamp should be unmodified", mutableKey.timestamp, is(equalTo(timestamp)));
|
||||
assertThat("delete should be unmodified", mutableKey.delete, is(equalTo(delete)));
|
||||
assertThat("value should not be set", mutableKey.value, is(equalTo(EMPTY)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWithEntryTest() {
|
||||
MutableEntry mutableEntry = new MutableEntry(entry);
|
||||
|
||||
assertThat("row should be unmodified", mutableEntry.row, is(equalTo(row)));
|
||||
assertThat("colF should be unmodified", mutableEntry.colF, is(equalTo(colF)));
|
||||
assertThat("colQ should be unmodified", mutableEntry.colQ, is(equalTo(colQ)));
|
||||
assertThat("colVis should be unmodified", mutableEntry.colVis, is(equalTo(colVis)));
|
||||
assertThat("timestamp should be unmodified", mutableEntry.timestamp, is(equalTo(timestamp)));
|
||||
assertThat("delete should be unmodified", mutableEntry.delete, is(equalTo(delete)));
|
||||
assertThat("value should be unmodified", mutableEntry.value, is(equalTo(value)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWithUpdateTest() {
|
||||
ColumnUpdate update = new ColumnUpdate(colF, colQ, colVis, true, timestamp, delete, value);
|
||||
MutableEntry mutableEntry = new MutableEntry(row, update);
|
||||
|
||||
assertThat("row should be unmodified", mutableEntry.row, is(equalTo(row)));
|
||||
assertThat("colF should be unmodified", mutableEntry.colF, is(equalTo(colF)));
|
||||
assertThat("colQ should be unmodified", mutableEntry.colQ, is(equalTo(colQ)));
|
||||
assertThat("colVis should be unmodified", mutableEntry.colVis, is(equalTo(colVis)));
|
||||
assertThat("timestamp should be unmodified", mutableEntry.timestamp, is(equalTo(timestamp)));
|
||||
assertThat("delete should be unmodified", mutableEntry.delete, is(equalTo(delete)));
|
||||
assertThat("value should be unmodified", mutableEntry.value, is(equalTo(value)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toKeyTest() {
|
||||
MutableEntry mutableKey = new MutableEntry(key);
|
||||
assertThat("returned key should be the same as the original key", mutableKey.toKey(), is(equalTo(key)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toEntryTest() {
|
||||
MutableEntry mutableEntry = new MutableEntry(entry);
|
||||
assertThat("returned entry should be the same as the original entry", mutableEntry.toEntry(), is(equalTo(entry)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloneTest() {
|
||||
MutableEntry mutableEntry = new MutableEntry(entry);
|
||||
MutableEntry mutableEntry2 = mutableEntry.cloneEntry();
|
||||
assertThat("returned entry should be the same as the original entry", mutableEntry2.toEntry(), is(equalTo(mutableEntry.toEntry())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBytesTest() {
|
||||
MutableEntry mutableEntry = new MutableEntry(entry);
|
||||
|
||||
assertThat("correct bytes returned", mutableEntry.getBytes(EntryField.ROW), is(equalTo(row)));
|
||||
assertThat("correct bytes returned", mutableEntry.getBytes(EntryField.COLUMN_FAMILY), is(equalTo(colF)));
|
||||
assertThat("correct bytes returned", mutableEntry.getBytes(EntryField.COLUMN_QUALIFIER), is(equalTo(colQ)));
|
||||
assertThat("correct bytes returned", mutableEntry.getBytes(EntryField.COLUMN_VISIBILITY), is(equalTo(colVis)));
|
||||
assertThat("correct bytes returned", mutableEntry.getBytes(EntryField.VALUE), is(equalTo(value)));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getBytesException() {
|
||||
MutableEntry mutableEntry = new MutableEntry(entry);
|
||||
mutableEntry.getBytes(EntryField.TIMESTAMP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBytesTest() {
|
||||
MutableEntry mutableEntry = new MutableEntry(new Key()); // Create an empty mutable entry.
|
||||
|
||||
mutableEntry.setBytes(EntryField.ROW, row);
|
||||
mutableEntry.setBytes(EntryField.COLUMN_FAMILY, colF);
|
||||
mutableEntry.setBytes(EntryField.COLUMN_QUALIFIER, colQ);
|
||||
mutableEntry.setBytes(EntryField.COLUMN_VISIBILITY, colVis);
|
||||
mutableEntry.setBytes(EntryField.VALUE, value);
|
||||
mutableEntry.timestamp = timestamp;
|
||||
mutableEntry.delete = delete;
|
||||
|
||||
assertThat("constructed entry should be the same as the testing entry", mutableEntry.toEntry(), is(equalTo(entry)));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setBytesException() {
|
||||
MutableEntry mutableEntry = new MutableEntry(entry);
|
||||
mutableEntry.setBytes(EntryField.TIMESTAMP, EMPTY);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.internal;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test {@link Utils}.
|
||||
*/
|
||||
public final class UtilsTest {
|
||||
|
||||
@Test
|
||||
public void visibilityCharsetTest() {
|
||||
assertThat("correct visibility charset", Utils.VISIBILITY_CHARSET, is(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyTest() {
|
||||
assertThat("correct empty array", Utils.EMPTY, is(equalTo(new Key().getRow().getBytes())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xorTest() {
|
||||
byte[] first = new byte[] {0, 1, 2};
|
||||
byte[] second = new byte[] {0, 1, 1};
|
||||
|
||||
byte[] result = Utils.xor(first, second);
|
||||
assertThat("the return value is the same as the first argument", result == first, is(true));
|
||||
assertThat("the correct result was achieved", result, is(equalTo(new byte[] {0, 0, 3})));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xorExceptionTest() {
|
||||
byte[] first = new byte[] {0, 1, 2};
|
||||
byte[] second = new byte[] {0, 1, 2, 3};
|
||||
|
||||
try {
|
||||
Utils.xor(null, second);
|
||||
fail("null first variable not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
Utils.xor(first, null);
|
||||
fail("null second variable not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
Utils.xor(first, second);
|
||||
fail("arrays with unequal length not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.EMPTY;
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static edu.mit.ll.pace.test.TestUtils.getResourceAsStream;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
import edu.mit.ll.pace.internal.Utils;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
|
||||
/**
|
||||
* Tests for {@link EntrySigner}
|
||||
*/
|
||||
public class EntrySignerTest {
|
||||
|
||||
private Map<ValueSigner,SignatureKeyContainer> aliceKeyContainers = new HashMap<>();
|
||||
private Map<ValueSigner,SignatureKeyContainer> bobKeyContainers = new HashMap<>();
|
||||
|
||||
public EntrySignerTest() throws NoSuchAlgorithmException {
|
||||
aliceKeyContainers = MockSignatureKeyContainer.getContainers("alice", "alice", "bob");
|
||||
bobKeyContainers = MockSignatureKeyContainer.getContainers("bob", "alice", "bob");
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setupBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorExceptionTests() throws Exception {
|
||||
try {
|
||||
new EntrySigner(null, aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("config must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getSigner("config1.ini", null); // Implicitly tests a good config, but a null key set.
|
||||
fail("keys must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullEntryTest() throws Exception {
|
||||
EntrySigner signer = getSigner("config1.ini", aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
try {
|
||||
signer.sign(null, true);
|
||||
fail("cannot call sign with a null entry");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
signer.verify(null);
|
||||
fail("cannot call verify with a null value");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
signer.verify(null, null);
|
||||
fail("cannot call verify with a null value");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(VISIBILITY_CHARSET),
|
||||
(long) 5, false), new Value(new byte[] {6}));
|
||||
|
||||
try {
|
||||
getSigner("config2.ini", aliceKeyContainers.get(ValueSigner.RSA_PKCS1)).verify(entry, entry);
|
||||
fail("cannot call verify with a non-null signature entry if not needed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getSigner("config3.ini", aliceKeyContainers.get(ValueSigner.ECDSA)).verify(entry, null);
|
||||
fail("cannot call verify with a null signature value when needed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signVerifyInValueTest() throws Exception {
|
||||
MutableEntry entry = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6})));
|
||||
|
||||
MutableEntry signed;
|
||||
Entry<Key,Value> verified;
|
||||
|
||||
EntrySigner signer = getSigner("config1.ini", aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
EntrySigner verifier = getSigner("config1.ini", bobKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
signed = new MutableEntry(signer.sign(entry.toEntry(), true));
|
||||
assertThat("row should not have changed", signed.row, is(entry.row));
|
||||
assertThat("colFamily should not have changed", signed.colF, is(entry.colF));
|
||||
assertThat("colQualifier should not have changed", signed.colQ, is(entry.colQ));
|
||||
assertThat("colVisibility should not have changed", signed.colVis, is(entry.colVis));
|
||||
assertThat("timestamp should not have changed", signed.timestamp, is(entry.timestamp));
|
||||
assertThat("delete should not have changed", signed.delete, is(entry.delete));
|
||||
assertThat("value should have changed", signed.value, is(not(entry.value)));
|
||||
|
||||
verified = verifier.verify(signed.toEntry());
|
||||
assertThat("original and verified records are the same.", verified, Matchers.equalTo(entry.toEntry()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signVerifyInVisibilityTest() throws Exception {
|
||||
MutableEntry entry = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6})));
|
||||
|
||||
MutableEntry signed;
|
||||
Entry<Key,Value> verified;
|
||||
|
||||
// Sign to column visibility
|
||||
EntrySigner signer = getSigner("config2.ini", aliceKeyContainers.get(ValueSigner.RSA_PKCS1));
|
||||
EntrySigner verifier = getSigner("config2.ini", bobKeyContainers.get(ValueSigner.RSA_PKCS1));
|
||||
|
||||
signed = new MutableEntry(signer.sign(entry.toEntry(), true));
|
||||
assertThat("row should not have changed", signed.row, is(entry.row));
|
||||
assertThat("colFamily should not have changed", signed.colF, is(entry.colF));
|
||||
assertThat("colQualifier should not have changed", signed.colQ, is(entry.colQ));
|
||||
assertThat("colVisibility should have changed", signed.colVis, is(not(entry.colVis)));
|
||||
assertThat("colVisibility is wrapped", new String(signed.colVis, Utils.VISIBILITY_CHARSET), startsWith("(secret)"));
|
||||
assertThat("timestamp should not have changed", signed.timestamp, is(entry.timestamp));
|
||||
assertThat("delete should not have changed", signed.delete, is(entry.delete));
|
||||
assertThat("value should not have changed", signed.value, is(entry.value));
|
||||
|
||||
verified = verifier.verify(signed.toEntry());
|
||||
assertThat("original and verified records are the same.", verified, Matchers.equalTo(entry.toEntry()));
|
||||
|
||||
// Sign to column visibility with default visibility
|
||||
MutableEntry entry2 = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, EMPTY, (long) 5, false),
|
||||
new Value(new byte[] {6})));
|
||||
|
||||
signed = new MutableEntry(signer.sign(entry2.toEntry(), true));
|
||||
assertThat("row should not have changed", signed.row, is(entry2.row));
|
||||
assertThat("colFamily should not have changed", signed.colF, is(entry.colF));
|
||||
assertThat("colQualifier should not have changed", signed.colQ, is(entry.colQ));
|
||||
assertThat("colVisibility should have changed", signed.colVis, is(not(entry.colVis)));
|
||||
assertThat("colVisibility uses the default visibility", new String(signed.colVis, Utils.VISIBILITY_CHARSET), startsWith("(default)"));
|
||||
assertThat("timestamp should not have changed", signed.timestamp, is(entry.timestamp));
|
||||
assertThat("delete should not have changed", signed.delete, is(entry.delete));
|
||||
assertThat("value should not have changed", signed.value, is(entry.value));
|
||||
|
||||
MutableEntry verifiedEntry = new MutableEntry(verifier.verify(signed.toEntry()));
|
||||
assertThat("row should not have changed", verifiedEntry.row, is(entry2.row));
|
||||
assertThat("colFamily should not have changed", verifiedEntry.colF, is(entry.colF));
|
||||
assertThat("colQualifier should not have changed", verifiedEntry.colQ, is(entry.colQ));
|
||||
assertThat("colVisibility should have changed to the default visibility", verifiedEntry.colVis, is("default".getBytes(Utils.VISIBILITY_CHARSET)));
|
||||
assertThat("timestamp should not have changed", verifiedEntry.timestamp, is(entry.timestamp));
|
||||
assertThat("delete should not have changed", verifiedEntry.delete, is(entry.delete));
|
||||
assertThat("value should not have changed", verifiedEntry.value, is(entry.value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signVerifyInSeparateTableTest() throws Exception {
|
||||
MutableEntry entry = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 5, false), new Value(new byte[] {6})));
|
||||
|
||||
MutableEntry signed;
|
||||
Entry<Key,Value> verified;
|
||||
|
||||
EntrySigner signer = getSigner("config3.ini", aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
EntrySigner verifier = getSigner("config3.ini", bobKeyContainers.get(ValueSigner.ECDSA));
|
||||
|
||||
signed = new MutableEntry(signer.sign(entry.toEntry(), true));
|
||||
assertThat("row should not have changed", signed.row, is(entry.row));
|
||||
assertThat("colFamily should not have changed", signed.colF, is(entry.colF));
|
||||
assertThat("colQualifier should not have changed", signed.colQ, is(entry.colQ));
|
||||
assertThat("colVisibility should not have changed", signed.colVis, is(entry.colVis));
|
||||
assertThat("timestamp should not have changed", signed.timestamp, is(entry.timestamp));
|
||||
assertThat("delete should not have changed", signed.delete, is(entry.delete));
|
||||
assertThat("value should have changed", signed.value, is(not(entry.value)));
|
||||
|
||||
verified = verifier.verify(entry.toEntry(), signed.toEntry());
|
||||
assertThat("original and verified records are the same.", verified, Matchers.equalTo(entry.toEntry()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasNoTimestampTest() throws Exception {
|
||||
MutableEntry entry = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 0, false), new Value(new byte[] {6})));
|
||||
MutableEntry signed;
|
||||
|
||||
EntrySigner signer = getSigner("config1.ini", aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
EntrySigner verifier = getSigner("config1.ini", bobKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
try {
|
||||
signed = new MutableEntry(signer.sign(entry.toEntry(), true));
|
||||
signed.timestamp = 1000L;
|
||||
verifier.verify(signed.toEntry());
|
||||
fail("changing the timestamp should cause the signature to fail");
|
||||
} catch (SignatureException e) { /* expected */}
|
||||
|
||||
signed = new MutableEntry(signer.sign(entry.toEntry(), false));
|
||||
signed.timestamp = 1000L;
|
||||
verifier.verify(signed.toEntry());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badSignatureTest() throws Exception {
|
||||
MutableEntry entry = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 0, false), new Value(new byte[] {6})));
|
||||
Entry<Key,Value> signed;
|
||||
|
||||
// Sign to value;
|
||||
EntrySigner signer = getSigner("config3.ini", aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
EntrySigner verifier = getSigner("config3.ini", bobKeyContainers.get(ValueSigner.ECDSA));
|
||||
|
||||
signed = signer.sign(entry.toEntry(), true);
|
||||
entry.value = new byte[] {7};
|
||||
|
||||
try {
|
||||
verifier.verify(entry.toEntry(), signed);
|
||||
fail("bad signature should thrown an exception");
|
||||
} catch (SignatureException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signVerifyDeleteException() throws Exception {
|
||||
MutableEntry entry = new MutableEntry(new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 0, true), new Value(new byte[] {6})));
|
||||
|
||||
EntrySigner signer = getSigner("config1.ini", aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
EntrySigner verifier = getSigner("config1.ini", bobKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
try {
|
||||
signer.sign(entry.toEntry(), true);
|
||||
fail("cannot sign deleted entries");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
verifier.verify(entry.toEntry());
|
||||
fail("cannot verify deleted entries");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a signer.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @param keys
|
||||
* Keys to use for the signer.
|
||||
* @return EntrySigner.
|
||||
*/
|
||||
private EntrySigner getSigner(String resource, SignatureKeyContainer keys) throws Exception {
|
||||
return new EntrySigner(new SignatureConfigBuilder().readFromFile(new InputStreamReader(getResourceAsStream(this.getClass(), resource))).build(), keys);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Mock {@link SignatureKeyContainer} to use for testing the signature code.
|
||||
*/
|
||||
public class MockSignatureKeyContainer implements SignatureKeyContainer {
|
||||
|
||||
/**
|
||||
* Class for key lookup in key container map.
|
||||
*/
|
||||
private static final class KeyLookup {
|
||||
private final ValueSigner signer;
|
||||
private final int keyLength;
|
||||
private final String id;
|
||||
|
||||
KeyLookup(ValueSigner signer, int keyLength, String id) {
|
||||
this.signer = signer;
|
||||
this.keyLength = keyLength;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(signer, keyLength, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (null == obj || !(obj instanceof KeyLookup)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyLookup other = (KeyLookup) obj;
|
||||
return signer.equals(other.signer) && keyLength == other.keyLength && id.equals(other.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of keys generated by this class.
|
||||
*/
|
||||
private static final Map<KeyLookup,KeyPair> generatedKeys = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The signing key id.
|
||||
*/
|
||||
private final String signingKeyId;
|
||||
|
||||
/**
|
||||
* The signing key wrapped by this container.
|
||||
*/
|
||||
private final PrivateKey signingKey;
|
||||
|
||||
/**
|
||||
* The public key wrapped by this container.
|
||||
*/
|
||||
private final Map<String,PublicKey> verifyingKeys = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
/**
|
||||
* Create a mock container with the given signature and verifying keys.
|
||||
*
|
||||
* @param signer
|
||||
* Signer to generate keys for.
|
||||
* @param keyLength
|
||||
* Length of the keys.
|
||||
* @param signingKeyId
|
||||
* Id of the user the signing key is for.
|
||||
* @param verifyingKeyIds
|
||||
* Id of the users that we will verify signatures for.
|
||||
*/
|
||||
private MockSignatureKeyContainer(ValueSigner signer, int keyLength, String signingKeyId, String... verifyingKeyIds) throws NoSuchAlgorithmException {
|
||||
this.signingKeyId = String.format("%s_%d_%s", signer.getKeyGenerationAlgorithm(), keyLength, signingKeyId);
|
||||
this.signingKey = getKeyPair(signer, keyLength, signingKeyId).getPrivate();
|
||||
|
||||
for (String keyId : verifyingKeyIds) {
|
||||
this.verifyingKeys.put(String.format("%s_%d_%s", signer.getKeyGenerationAlgorithm(), keyLength, keyId), getKeyPair(signer, keyLength, keyId).getPublic());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKeyWithId getSigningKey() {
|
||||
return new PrivateKeyWithId(signingKey, signingKeyId.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getVerifyingKey(byte[] id) {
|
||||
String keyId = new String(id, StandardCharsets.UTF_8);
|
||||
return verifyingKeys.get(keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a key pair of given length for the given ID.
|
||||
*
|
||||
* @param signer
|
||||
* Signer that the key is being generated for.
|
||||
* @param keyLength
|
||||
* Length of the key.
|
||||
* @param id
|
||||
* Id of the user the key is for.
|
||||
* @return KeyPair for the requested key.
|
||||
*/
|
||||
private static KeyPair getKeyPair(ValueSigner signer, int keyLength, String id) throws NoSuchAlgorithmException {
|
||||
KeyLookup mapKey = new KeyLookup(signer, keyLength, id);
|
||||
if (!generatedKeys.containsKey(mapKey)) {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(signer.getKeyGenerationAlgorithm());
|
||||
keyGen.initialize(keyLength);
|
||||
generatedKeys.put(mapKey, keyGen.generateKeyPair());
|
||||
}
|
||||
|
||||
return generatedKeys.get(mapKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key length for a given signer.
|
||||
*
|
||||
* @param signer
|
||||
* The singer to get the length for.
|
||||
* @return The length for generating keys.
|
||||
*/
|
||||
private static int getKeyGenLength(ValueSigner signer) {
|
||||
switch (signer) {
|
||||
case RSA_PKCS1:
|
||||
case RSA_PSS:
|
||||
case DSA:
|
||||
return 1024;
|
||||
|
||||
case ECDSA:
|
||||
return 256;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create mock containers for each value signer for the given signing key id and verifying key id.
|
||||
*
|
||||
* @param signingKeyId
|
||||
* Id of the user the signing key is for.
|
||||
* @param verifyingKeyIds
|
||||
* Id of the users that we will verify signatures for.
|
||||
*/
|
||||
static Map<ValueSigner,SignatureKeyContainer> getContainers(String signingKeyId, String... verifyingKeyIds) throws NoSuchAlgorithmException {
|
||||
Map<ValueSigner,SignatureKeyContainer> containers = new HashMap<>();
|
||||
for (ValueSigner signer : ValueSigner.values()) {
|
||||
containers.put(signer, new MockSignatureKeyContainer(signer, getKeyGenLength(signer), signingKeyId, verifyingKeyIds));
|
||||
}
|
||||
return containers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.arrayWithSize;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.mit.ll.pace.signature.SignatureConfig.Destination;
|
||||
|
||||
/**
|
||||
* Test for {@link Destination}.
|
||||
*/
|
||||
public class SignatureConfigDestinationTest {
|
||||
|
||||
@Test
|
||||
public void validEnumTest() {
|
||||
assertThat("should have three values", Destination.values(), is(arrayWithSize(3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringTest() {
|
||||
assertThat("toString should return correct value", Destination.VALUE.toString(), is("value"));
|
||||
assertThat("toString should return correct value", Destination.COLUMN_VISIBILITY.toString(), is("colVis"));
|
||||
assertThat("toString should return correct value", Destination.SEPARATE_TABLE.toString(), is("table"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromStringTest() {
|
||||
assertThat("fromString should return correct enum value", Destination.fromString("value"), is(Destination.VALUE));
|
||||
assertThat("fromString should return correct enum value", Destination.fromString("colVis"), is(Destination.COLUMN_VISIBILITY));
|
||||
assertThat("fromString should return correct enum value", Destination.fromString("table"), is(Destination.SEPARATE_TABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromStringExceptionTest() {
|
||||
try {
|
||||
Destination.fromString("bad");
|
||||
fail("only valid names should be allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static edu.mit.ll.pace.test.Matchers.equalTo;
|
||||
import static edu.mit.ll.pace.test.Matchers.hasSameFieldsAs;
|
||||
import static edu.mit.ll.pace.test.TestUtils.getResourceAsStream;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.ini4j.Ini;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import edu.mit.ll.pace.internal.Utils;
|
||||
import edu.mit.ll.pace.signature.SignatureConfig.Destination;
|
||||
|
||||
/**
|
||||
* Test for {@link SignatureConfig} and {@link SignatureConfigBuilder}.
|
||||
*/
|
||||
public class SignatureConfigTest {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
// Configurations matching the ini files in the resource directory.
|
||||
private static final SignatureConfig config1 = new SignatureConfigBuilder().setSigner(ValueSigner.RSA_PSS).setProvider("BC")
|
||||
.setDestination(Destination.VALUE).build();
|
||||
private static final SignatureConfig config2 = new SignatureConfigBuilder().setSigner(ValueSigner.RSA_PKCS1).setDestination(Destination.COLUMN_VISIBILITY)
|
||||
.setDefaultVisibility("default".getBytes(Utils.VISIBILITY_CHARSET)).build();
|
||||
private static final SignatureConfig config3 = new SignatureConfigBuilder().setSigner(ValueSigner.ECDSA).setDestination(Destination.SEPARATE_TABLE)
|
||||
.setDestinationTable("sigs").build();
|
||||
|
||||
@Test
|
||||
public void constructorExceptionTest() {
|
||||
try {
|
||||
getValidBuilder().setSigner(null).build();
|
||||
fail("signer must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(null).build();
|
||||
fail("destination must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(Destination.SEPARATE_TABLE).setDestinationTable(null).build();
|
||||
fail("if storing to a separate table, destination table must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(Destination.SEPARATE_TABLE).setDestinationTable("").build();
|
||||
fail("if storing to a separate table, destination table must not be empty");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(Destination.VALUE).setDestinationTable("something").build();
|
||||
fail("if not storing to a separate table, destination table must not be set");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(Destination.COLUMN_VISIBILITY).setDefaultVisibility(null).build();
|
||||
fail("if storing to the column visibility field, default visibility must not be null");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(Destination.COLUMN_VISIBILITY).setDefaultVisibility(Utils.EMPTY).build();
|
||||
fail("if storing to the column visibility field, default visibility must not be empty");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
getValidBuilder().setDestination(Destination.VALUE).setDefaultVisibility("something".getBytes(Utils.VISIBILITY_CHARSET)).build();
|
||||
fail("if not storing to the column visibility field, default visibility must not be set");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTest() throws Exception {
|
||||
SignatureConfig actualConfig = new SignatureConfigBuilder().readFromFile(getReader("config1.ini")).build();
|
||||
assertThat("reading the ini file produces the correct configuration", actualConfig, hasSameFieldsAs(config1));
|
||||
|
||||
actualConfig = new SignatureConfigBuilder().readFromFile(getReader("config1WithoutDefault.ini")).build();
|
||||
assertThat("reading the ini file produces the correct configuration", actualConfig, hasSameFieldsAs(config1));
|
||||
|
||||
actualConfig = new SignatureConfigBuilder().readFromFile(getReader("config2.ini")).build();
|
||||
assertThat("reading the ini file produces the correct configuration", actualConfig, hasSameFieldsAs(config2));
|
||||
|
||||
actualConfig = new SignatureConfigBuilder().readFromFile(getReader("config3.ini")).build();
|
||||
assertThat("reading the ini file produces the correct configuration", actualConfig, hasSameFieldsAs(config3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeTest() throws Exception {
|
||||
File file = writeConfigToFile(config1);
|
||||
Ini expectedIni = getIni("config1.ini");
|
||||
Ini actualIni = new Ini(file);
|
||||
assertThat("writing the ini file produces the correct configuration", expectedIni, equalTo(actualIni));
|
||||
|
||||
file = writeConfigToFile(config2);
|
||||
expectedIni = getIni("config2.ini");
|
||||
actualIni = new Ini(file);
|
||||
assertThat("writing the ini file produces the correct configuration", expectedIni, equalTo(actualIni));
|
||||
|
||||
file = writeConfigToFile(config3);
|
||||
expectedIni = getIni("config3.ini");
|
||||
actualIni = new Ini(file);
|
||||
assertThat("writing the ini file produces the correct configuration", expectedIni, equalTo(actualIni));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReadTest() throws Exception {
|
||||
File file = writeConfigToFile(config1);
|
||||
SignatureConfig actualConfig = new SignatureConfigBuilder().readFromFile(new FileReader(file)).build();
|
||||
assertThat("writing then reading the config produces an equivalent configuration", actualConfig, hasSameFieldsAs(config1));
|
||||
|
||||
file = writeConfigToFile(config2);
|
||||
actualConfig = new SignatureConfigBuilder().readFromFile(new FileReader(file)).build();
|
||||
assertThat("writing then reading the config produces an equivalent configuration", actualConfig, hasSameFieldsAs(config2));
|
||||
|
||||
file = writeConfigToFile(config3);
|
||||
actualConfig = new SignatureConfigBuilder().readFromFile(new FileReader(file)).build();
|
||||
assertThat("writing then reading the config produces an equivalent configuration", actualConfig, hasSameFieldsAs(config3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAlgorithmTest() {
|
||||
assertThat("correct algorithm", config1.getAlgorithm(), Matchers.equalTo(config1.algorithm));
|
||||
assertThat("correct algorithm", config2.getAlgorithm(), Matchers.equalTo(config2.algorithm));
|
||||
assertThat("correct algorithm", config3.getAlgorithm(), Matchers.equalTo(config3.algorithm));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSignatureInSeparateTableTest() {
|
||||
assertThat("correct return value", config1.isSignatureInSeparateTable(), is(false));
|
||||
assertThat("correct return value", config2.isSignatureInSeparateTable(), is(false));
|
||||
assertThat("correct return value", config3.isSignatureInSeparateTable(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given configuration to a file.
|
||||
*
|
||||
* @param config
|
||||
* Configuration to write.
|
||||
* @return The file that was written to.
|
||||
*/
|
||||
private File writeConfigToFile(SignatureConfig config) throws IOException {
|
||||
File file = temp.newFile();
|
||||
FileWriter writer = new FileWriter(file);
|
||||
config.write(writer);
|
||||
writer.close();
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a builder wrapping a valid configuration.
|
||||
*
|
||||
* @return Valid builder.
|
||||
*/
|
||||
private SignatureConfigBuilder getValidBuilder() {
|
||||
return new SignatureConfigBuilder().setSigner(ValueSigner.RSA_PKCS1).setDestination(Destination.VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested INI.
|
||||
*
|
||||
* @param resource
|
||||
* INI file to get.
|
||||
* @return The INI file.
|
||||
*/
|
||||
private Ini getIni(String resource) throws IOException {
|
||||
return new Ini(getResourceAsStream(this.getClass(), resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested INI.
|
||||
*
|
||||
* @param resource
|
||||
* INI file to get.
|
||||
* @return The INI file.
|
||||
*/
|
||||
private Reader getReader(String resource) throws IOException {
|
||||
return new InputStreamReader(getResourceAsStream(this.getClass(), resource));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchScanner;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.client.IteratorSetting.Column;
|
||||
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.hadoop.io.Text;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import edu.mit.ll.pace.internal.Utils;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test {@link SignedBatchScanner}.
|
||||
*/
|
||||
public class SignedBatchScannerTest {
|
||||
|
||||
private final static String TEST_TABLE = "test";
|
||||
private final static String SIG_TABLE = "sigs";
|
||||
|
||||
private final static Authorizations authorizations = new Authorizations("admin", "secret", "top secret");
|
||||
|
||||
private Map<ValueSigner,SignatureKeyContainer> aliceKeyContainers = new HashMap<>();
|
||||
|
||||
@Mock
|
||||
private Connector mockConnector;
|
||||
|
||||
@Mock
|
||||
private BatchScanner mockScanner;
|
||||
|
||||
@Mock
|
||||
private BatchScanner mockSignatureScanner;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||
|
||||
public SignedBatchScannerTest() throws NoSuchAlgorithmException {
|
||||
aliceKeyContainers = MockSignatureKeyContainer.getContainers("alice", "alice");
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setupBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badConstructorTest() throws Exception {
|
||||
try {
|
||||
new SignedBatchScanner(null, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null connector not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedBatchScanner(mockConnector, null, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null table name not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, null, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null authorizations not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, null, aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null crypto config not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), null);
|
||||
fail("null key container not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest() throws Exception {
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
verify(mockConnector).createBatchScanner(TEST_TABLE, authorizations, 1);
|
||||
verify(mockConnector, never()).createBatchScanner(SIG_TABLE, authorizations, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorExternalTest() throws Exception {
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
verify(mockConnector).createBatchScanner(TEST_TABLE, authorizations, 1);
|
||||
verify(mockConnector).createBatchScanner(SIG_TABLE, authorizations, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
|
||||
EntrySigner signer = new EntrySigner(getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(Utils.VISIBILITY_CHARSET), 0,
|
||||
false, false), new Value(new byte[] {4}));
|
||||
Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7}, "secret".getBytes(Utils.VISIBILITY_CHARSET),
|
||||
0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(signer.sign(entry, true));
|
||||
entries.add(signer.sign(entry2, true));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
BatchScanner scanner = new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"),
|
||||
aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
Iterator<Entry<Key,Value>> iterator = scanner.iterator();
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry));
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
|
||||
EntrySigner signer = new EntrySigner(getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
List<Entry<Key,Value>> signedEntries = new ArrayList<>();
|
||||
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3}, "secret".getBytes(Utils.VISIBILITY_CHARSET), 0,
|
||||
false, false), new Value(new byte[] {4}));
|
||||
Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7}, "secret".getBytes(Utils.VISIBILITY_CHARSET),
|
||||
0, false, false), new Value(new byte[] {8}));
|
||||
|
||||
entries.add(entry);
|
||||
entries.add(entry2);
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
signedEntries.add(signer.sign(entry2, true));
|
||||
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
when(mockSignatureScanner.iterator()).thenReturn(signedEntries.iterator()).thenReturn(signedEntries.iterator());
|
||||
|
||||
BatchScanner scanner = new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"),
|
||||
aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
Iterator<Entry<Key,Value>> iterator = scanner.iterator();
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry));
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addScanIteratorTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
IteratorSetting test = new IteratorSetting(10, "test", "test2");
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.addScanIterator(test);
|
||||
verify(mockScanner).addScanIterator(test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addScanIteratorExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
IteratorSetting test = new IteratorSetting(10, "test", "test2");
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.addScanIterator(test);
|
||||
verify(mockScanner).addScanIterator(test);
|
||||
verify(mockSignatureScanner).addScanIterator(test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearColumns() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).clearColumns();
|
||||
verify(mockScanner).clearColumns();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearExternalColumns() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).clearColumns();
|
||||
verify(mockScanner).clearColumns();
|
||||
verify(mockSignatureScanner).clearColumns();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearScanIteratorsTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearScanIterators();
|
||||
verify(mockScanner).clearScanIterators();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearScanIteratorsExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearScanIterators();
|
||||
verify(mockScanner).clearScanIterators();
|
||||
verify(mockSignatureScanner).clearScanIterators();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRangesTest() throws Exception {
|
||||
Collection<Range> ranges = Collections.singletonList(new Range("test"));
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setRanges(ranges);
|
||||
verify(mockScanner).setRanges(ranges);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRangesExternalTest() throws Exception {
|
||||
Collection<Range> ranges = Collections.singletonList(new Range("test"));
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setRanges(ranges);
|
||||
verify(mockScanner).setRanges(ranges);
|
||||
verify(mockSignatureScanner).setRanges(ranges);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).close();
|
||||
verify(mockScanner).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).close();
|
||||
verify(mockScanner).close();
|
||||
verify(mockSignatureScanner).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnTest() throws Exception {
|
||||
Column column = new Column(new Text(new byte[] {1}), new Text(new byte[] {2}));
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.fetchColumn(column);
|
||||
verify(mockScanner).fetchColumn(column);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnExternalTest() throws Exception {
|
||||
Column column = new Column(new Text(new byte[] {1}), new Text(new byte[] {2}));
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.fetchColumn(column);
|
||||
verify(mockScanner).fetchColumn(column);
|
||||
verify(mockSignatureScanner).fetchColumn(column);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumn2Test() throws Exception {
|
||||
Text colF = new Text(new byte[] {1}), colQ = new Text(new byte[] {2});
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).fetchColumn(
|
||||
colF, colQ);
|
||||
verify(mockScanner).fetchColumn(colF, colQ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumn2ExternalTest() throws Exception {
|
||||
Text colF = new Text(new byte[] {1}), colQ = new Text(new byte[] {2});
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).fetchColumn(
|
||||
colF, colQ);
|
||||
verify(mockScanner).fetchColumn(colF, colQ);
|
||||
verify(mockSignatureScanner).fetchColumn(colF, colQ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnFamilyTest() throws Exception {
|
||||
Text colF = new Text(new byte[] {1});
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.fetchColumnFamily(colF);
|
||||
verify(mockScanner).fetchColumnFamily(colF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnFamilyExternalTest() throws Exception {
|
||||
Text colF = new Text(new byte[] {1});
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.fetchColumnFamily(colF);
|
||||
verify(mockScanner).fetchColumnFamily(colF);
|
||||
verify(mockSignatureScanner).fetchColumnFamily(colF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAuthorizationsTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockScanner.getAuthorizations()).thenReturn(authorizations);
|
||||
Authorizations auths = new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"),
|
||||
aliceKeyContainers.get(ValueSigner.RSA_PSS)).getAuthorizations();
|
||||
verify(mockScanner).getAuthorizations();
|
||||
assertThat("correct authorizations returned", auths, equalTo(authorizations));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
SamplerConfiguration config = new SamplerConfiguration("test");
|
||||
when(mockScanner.getSamplerConfiguration()).thenReturn(config);
|
||||
SamplerConfiguration value = new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"),
|
||||
aliceKeyContainers.get(ValueSigner.RSA_PSS)).getSamplerConfiguration();
|
||||
verify(mockScanner).getSamplerConfiguration();
|
||||
assertThat("correct config returned", value, equalTo(config));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
SamplerConfiguration config = new SamplerConfiguration("test");
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setSamplerConfiguration(config);
|
||||
verify(mockScanner).setSamplerConfiguration(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSamplerConfigurationExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
SamplerConfiguration config = new SamplerConfiguration("test");
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setSamplerConfiguration(config);
|
||||
verify(mockScanner).setSamplerConfiguration(config);
|
||||
verify(mockSignatureScanner).setSamplerConfiguration(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearSamplerConfiguration();
|
||||
verify(mockScanner).clearSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearSamplerConfigurationExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearSamplerConfiguration();
|
||||
verify(mockScanner).clearSamplerConfiguration();
|
||||
verify(mockSignatureScanner).clearSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBatchTimeoutTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
verify(mockScanner).setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBatchTimeoutExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
verify(mockScanner).setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
verify(mockSignatureScanner).setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatchTimeoutTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockScanner.getBatchTimeout(TimeUnit.DAYS)).thenReturn(5L);
|
||||
long value = new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getBatchTimeout(TimeUnit.DAYS);
|
||||
verify(mockScanner).getBatchTimeout(TimeUnit.DAYS);
|
||||
assertThat("correct timeout returned", value, is(5L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setClassLoaderContext("test");
|
||||
verify(mockScanner).setClassLoaderContext("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClassLoaderContextExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setClassLoaderContext("test");
|
||||
verify(mockScanner).setClassLoaderContext("test");
|
||||
verify(mockSignatureScanner).setClassLoaderContext("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearClassLoaderContext();
|
||||
verify(mockScanner).clearClassLoaderContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearClassLoaderContextExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearClassLoaderContext();
|
||||
verify(mockScanner).clearClassLoaderContext();
|
||||
verify(mockSignatureScanner).clearClassLoaderContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockScanner.getClassLoaderContext()).thenReturn("test");
|
||||
String value = new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getClassLoaderContext();
|
||||
verify(mockScanner).getClassLoaderContext();
|
||||
assertThat("correct class loader context returned", value, is("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTimeoutTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockScanner.getTimeout(TimeUnit.DAYS)).thenReturn(5L);
|
||||
Long value = new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getTimeout(TimeUnit.DAYS);
|
||||
verify(mockScanner).getTimeout(TimeUnit.DAYS);
|
||||
assertThat("correct timeout returned", value, is(5L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeScanIteratorTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.removeScanIterator("test");
|
||||
verify(mockScanner).removeScanIterator("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeScanIteratorExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.removeScanIterator("test");
|
||||
verify(mockScanner).removeScanIterator("test");
|
||||
verify(mockSignatureScanner).removeScanIterator("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateScanIteratorOptionTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.updateScanIteratorOption("test", "a", "b");
|
||||
verify(mockScanner).updateScanIteratorOption("test", "a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateScanIteratorOptionExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.updateScanIteratorOption("test", "a", "b");
|
||||
verify(mockScanner).updateScanIteratorOption("test", "a", "b");
|
||||
verify(mockSignatureScanner).updateScanIteratorOption("test", "a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTimeoutTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setTimeout(5L,
|
||||
TimeUnit.DAYS);
|
||||
verify(mockScanner).setTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTimeoutExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchScanner(TEST_TABLE, authorizations, 1)).thenReturn(mockScanner);
|
||||
when(mockConnector.createBatchScanner(SIG_TABLE, authorizations, 1)).thenReturn(mockSignatureScanner);
|
||||
new SignedBatchScanner(mockConnector, TEST_TABLE, authorizations, 1, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setTimeout(5L,
|
||||
TimeUnit.DAYS);
|
||||
verify(mockScanner).setTimeout(5L, TimeUnit.DAYS);
|
||||
verify(mockSignatureScanner).setTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an signor config.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @return EncryptionConfig.
|
||||
*/
|
||||
private SignatureConfig getConfig(String resource) throws Exception {
|
||||
return new SignatureConfigBuilder().readFromFile(new InputStreamReader(TestUtils.getResourceAsStream(this.getClass(), resource))).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.accumulo.core.client.BatchWriter;
|
||||
import org.apache.accumulo.core.client.BatchWriterConfig;
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.data.ColumnUpdate;
|
||||
import org.apache.accumulo.core.data.Mutation;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import edu.mit.ll.pace.internal.MutableEntry;
|
||||
import edu.mit.ll.pace.internal.Utils;
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test {@link SignedBatchWriter}.
|
||||
*/
|
||||
public class SignedBatchWriterTest {
|
||||
|
||||
private final static String TEST_TABLE = "test";
|
||||
private final static String SIG_TABLE = "sigs";
|
||||
|
||||
private Map<ValueSigner,SignatureKeyContainer> aliceKeyContainers = new HashMap<>();
|
||||
private Map<ValueSigner,SignatureKeyContainer> bobKeyContainers = new HashMap<>();
|
||||
|
||||
@Mock
|
||||
private Connector mockConnector;
|
||||
|
||||
@Mock
|
||||
private BatchWriter mockWriter;
|
||||
|
||||
@Mock
|
||||
private BatchWriter mockSignatureWriter;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Mutation> captor;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Mutation> signatureCaptor;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||
|
||||
public SignedBatchWriterTest() throws NoSuchAlgorithmException {
|
||||
aliceKeyContainers = MockSignatureKeyContainer.getContainers("alice", "alice", "bob");
|
||||
bobKeyContainers = MockSignatureKeyContainer.getContainers("bob", "alice", "bob");
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setupBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badConstructorTest() throws Exception {
|
||||
try {
|
||||
new SignedBatchWriter(null, TEST_TABLE, null, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null connector not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedBatchWriter(mockConnector, null, null, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null table name not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedBatchWriter(mockConnector, TEST_TABLE, null, null, aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null crypto config not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config1.ini"), null);
|
||||
fail("null key container not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest() throws Exception {
|
||||
BatchWriterConfig config = new BatchWriterConfig();
|
||||
new SignedBatchWriter(mockConnector, TEST_TABLE, config, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
verify(mockConnector).createBatchWriter(TEST_TABLE, config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorExternalTest() throws Exception {
|
||||
BatchWriterConfig config = new BatchWriterConfig();
|
||||
new SignedBatchWriter(mockConnector, TEST_TABLE, config, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
verify(mockConnector).createBatchWriter(TEST_TABLE, config);
|
||||
verify(mockConnector).createBatchWriter(SIG_TABLE, config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMutationValueTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
EntrySigner signer = new EntrySigner(getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.put("colF".getBytes(), "colQ".getBytes(), "val1".getBytes());
|
||||
mutation.put("colF".getBytes(), "colQ".getBytes(), 0, "val2".getBytes());
|
||||
writer.addMutation(mutation);
|
||||
|
||||
verify(mockWriter).addMutation(captor.capture());
|
||||
verify(mockSignatureWriter, never()).addMutation(any());
|
||||
|
||||
List<Mutation> mutations = captor.getAllValues();
|
||||
assertThat("only a single mutation", mutations, hasSize(1));
|
||||
|
||||
Mutation signed = mutations.get(0);
|
||||
assertThat("row is unchanged", signed.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
List<ColumnUpdate> updates = signed.getUpdates();
|
||||
assertThat("has 2 updates", updates, hasSize(2));
|
||||
|
||||
ColumnUpdate update = updates.get(0);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp not set", update.hasTimestamp(), is(false));
|
||||
assertThat("signature in value", update.getColumnFamily(), not(equalTo("val1".getBytes())));
|
||||
|
||||
MutableEntry verified = new MutableEntry(signer.verify(new MutableEntry("row".getBytes(), update).toEntry()));
|
||||
assertThat("value was correctly unwrapped", verified.value, equalTo("val1".getBytes()));
|
||||
|
||||
update = updates.get(1);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp is set", update.hasTimestamp(), is(true));
|
||||
assertThat("timestamp is correct", update.getTimestamp(), is(0L));
|
||||
assertThat("signature in value", update.getColumnFamily(), not(equalTo("val2".getBytes())));
|
||||
|
||||
verified = new MutableEntry(signer.verify(new MutableEntry("row".getBytes(), update).toEntry()));
|
||||
assertThat("value was correctly unwrapped", verified.value, equalTo("val2".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMutationColVisTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config2.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
EntrySigner signer = new EntrySigner(getConfig("config2.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.put("colF".getBytes(), "colQ".getBytes(), "val1".getBytes());
|
||||
mutation.put("colF".getBytes(), "colQ".getBytes(), 0, "val2".getBytes());
|
||||
writer.addMutation(mutation);
|
||||
|
||||
verify(mockWriter).addMutation(captor.capture());
|
||||
verify(mockSignatureWriter, never()).addMutation(any());
|
||||
|
||||
List<Mutation> mutations = captor.getAllValues();
|
||||
assertThat("only a single mutation", mutations, iterableWithSize(1));
|
||||
|
||||
Mutation signed = mutations.get(0);
|
||||
assertThat("row is unchanged", signed.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
List<ColumnUpdate> updates = signed.getUpdates();
|
||||
assertThat("has 2 updates", updates, hasSize(2));
|
||||
|
||||
ColumnUpdate update = updates.get(0);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnQualifier(), equalTo("colQ".getBytes()));
|
||||
assertThat("colQualifier has the default visibility", new String(update.getColumnVisibility(), Utils.VISIBILITY_CHARSET), startsWith("(default)"));
|
||||
assertThat("timestamp not set", update.hasTimestamp(), is(false));
|
||||
assertThat("value is unchanged", update.getValue(), equalTo("val1".getBytes()));
|
||||
|
||||
MutableEntry verified = new MutableEntry(signer.verify(new MutableEntry("row".getBytes(), update).toEntry()));
|
||||
assertThat("value was correctly unwrapped", verified.value, equalTo("val1".getBytes()));
|
||||
|
||||
update = updates.get(1);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnQualifier(), equalTo("colQ".getBytes()));
|
||||
assertThat("colQualifier has the default visibility", new String(update.getColumnVisibility(), Utils.VISIBILITY_CHARSET), startsWith("(default)"));
|
||||
assertThat("timestamp is set", update.hasTimestamp(), is(true));
|
||||
assertThat("timestamp is correct", update.getTimestamp(), is(0L));
|
||||
assertThat("value is unchanged", update.getValue(), equalTo("val2".getBytes()));
|
||||
|
||||
verified = new MutableEntry(signer.verify(new MutableEntry("row".getBytes(), update).toEntry()));
|
||||
assertThat("value was correctly unwrapped", verified.value, equalTo("val2".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMutationSeparateTableTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
when(mockConnector.createBatchWriter(SIG_TABLE, null)).thenReturn(mockSignatureWriter);
|
||||
|
||||
BatchWriter writer = new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
EntrySigner signer = new EntrySigner(getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.put("colF".getBytes(), "colQ".getBytes(), "val1".getBytes());
|
||||
mutation.put("colF".getBytes(), "colQ".getBytes(), 0, "val2".getBytes());
|
||||
writer.addMutation(mutation);
|
||||
|
||||
verify(mockWriter).addMutation(captor.capture());
|
||||
verify(mockSignatureWriter).addMutation(signatureCaptor.capture());
|
||||
|
||||
// Check entry
|
||||
List<Mutation> mutations = captor.getAllValues();
|
||||
assertThat("only a single mutation", mutations, hasSize(1));
|
||||
|
||||
Mutation signed = mutations.get(0);
|
||||
assertThat("row is unchanged", signed.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
List<ColumnUpdate> updates = signed.getUpdates();
|
||||
assertThat("has 2 updates", updates, hasSize(2));
|
||||
|
||||
ColumnUpdate update = updates.get(0);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp not set", update.hasTimestamp(), is(false));
|
||||
assertThat("value is unchanged", update.getValue(), equalTo("val1".getBytes()));
|
||||
|
||||
update = updates.get(1);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp is set", update.hasTimestamp(), is(true));
|
||||
assertThat("timestamp is correct", update.getTimestamp(), is(0L));
|
||||
assertThat("value is unchanged", update.getValue(), equalTo("val2".getBytes()));
|
||||
|
||||
// Check signature.
|
||||
mutations = signatureCaptor.getAllValues();
|
||||
assertThat("only a single mutation", mutations, hasSize(1));
|
||||
|
||||
signed = mutations.get(0);
|
||||
assertThat("row is unchanged", signed.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
updates = signed.getUpdates();
|
||||
assertThat("has 2 updates", updates, hasSize(2));
|
||||
|
||||
update = updates.get(0);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp not set", update.hasTimestamp(), is(false));
|
||||
assertThat("signature in value", update.getColumnFamily(), not(equalTo("val1".getBytes())));
|
||||
|
||||
update = updates.get(1);
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp is set", update.hasTimestamp(), is(true));
|
||||
assertThat("timestamp is correct", update.getTimestamp(), is(0L));
|
||||
assertThat("signature in value", update.getColumnFamily(), not(equalTo("val2".getBytes())));
|
||||
|
||||
// Verify unwrapping of entry.
|
||||
MutableEntry verified = new MutableEntry(signer.verify(new MutableEntry("row".getBytes(), captor.getAllValues().get(0).getUpdates().get(0)).toEntry(),
|
||||
new MutableEntry("row".getBytes(), signatureCaptor.getAllValues().get(0).getUpdates().get(0)).toEntry()));
|
||||
assertThat("value was correctly unwrapped", verified.value, equalTo("val1".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.putDelete("colF".getBytes(), "colQ".getBytes());
|
||||
mutation.putDelete("colF".getBytes(), "colQ".getBytes(), 0L);
|
||||
writer.addMutation(mutation);
|
||||
|
||||
verify(mockWriter).addMutation(captor.capture());
|
||||
verify(mockSignatureWriter, never()).addMutation(any());
|
||||
|
||||
List<Mutation> mutations = captor.getAllValues();
|
||||
assertThat("only a single mutation", mutations, iterableWithSize(1));
|
||||
|
||||
Mutation signed = mutations.get(0);
|
||||
assertThat("row is unchanged", signed.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
List<ColumnUpdate> updates = signed.getUpdates();
|
||||
assertThat("has 2 updates", updates, hasSize(2));
|
||||
|
||||
ColumnUpdate update = updates.get(0);
|
||||
assertThat("is delete operation", update.isDeleted(), is(true));
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnQualifier(), equalTo("colQ".getBytes()));
|
||||
assertThat("timestamp is not set", update.hasTimestamp(), is(false));
|
||||
|
||||
update = updates.get(1);
|
||||
assertThat("is delete operation", update.isDeleted(), is(true));
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnQualifier(), equalTo("colQ".getBytes()));
|
||||
assertThat("timestamp is set", update.hasTimestamp(), is(true));
|
||||
assertThat("timestamp is correct", update.getTimestamp(), is(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteColVisTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
BatchWriter writer = new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config2.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.putDelete("colF1".getBytes(), "colQ".getBytes());
|
||||
mutation.putDelete("colF1".getBytes(), "colQ".getBytes(), 0L);
|
||||
|
||||
try {
|
||||
writer.addMutation(mutation);
|
||||
fail("signed deletes are not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
when(mockConnector.createBatchWriter(SIG_TABLE, null)).thenReturn(mockSignatureWriter);
|
||||
|
||||
BatchWriter writer = new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
|
||||
Mutation mutation = new Mutation("row".getBytes());
|
||||
mutation.putDelete("colF".getBytes(), "colQ".getBytes());
|
||||
mutation.putDelete("colF".getBytes(), "colQ".getBytes(), 0);
|
||||
writer.addMutation(mutation);
|
||||
|
||||
verify(mockWriter).addMutation(captor.capture());
|
||||
verify(mockSignatureWriter).addMutation(signatureCaptor.capture());
|
||||
|
||||
// Check entry
|
||||
List<Mutation> mutations = captor.getAllValues();
|
||||
assertThat("only a single mutation", mutations, hasSize(1));
|
||||
|
||||
Mutation signed = mutations.get(0);
|
||||
assertThat("row is unchanged", signed.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
List<ColumnUpdate> updates = signed.getUpdates();
|
||||
assertThat("has 2 updates", updates, hasSize(2));
|
||||
|
||||
ColumnUpdate update = updates.get(0);
|
||||
assertThat("is delete operation", update.isDeleted(), is(true));
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp not set", update.hasTimestamp(), is(false));
|
||||
|
||||
update = updates.get(1);
|
||||
assertThat("is delete operation", update.isDeleted(), is(true));
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp is set", update.hasTimestamp(), is(true));
|
||||
assertThat("timestamp is correct", update.getTimestamp(), is(0L));
|
||||
|
||||
// Check signature.
|
||||
mutations = signatureCaptor.getAllValues();
|
||||
assertThat("only a single mutation", mutations, hasSize(1));
|
||||
|
||||
signed = mutations.get(0);
|
||||
assertThat("row is unchanged", signed.getRow(), equalTo("row".getBytes()));
|
||||
|
||||
updates = signed.getUpdates();
|
||||
assertThat("has 2 updates", updates, hasSize(2));
|
||||
|
||||
update = updates.get(0);
|
||||
assertThat("is delete operation", update.isDeleted(), is(true));
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp not set", update.hasTimestamp(), is(false));
|
||||
|
||||
update = updates.get(1);
|
||||
assertThat("is delete operation", update.isDeleted(), is(true));
|
||||
assertThat("column family is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("column qualifier is unchanged", update.getColumnFamily(), equalTo("colF".getBytes()));
|
||||
assertThat("timestamp is set", update.hasTimestamp(), is(true));
|
||||
assertThat("timestamp is correct", update.getTimestamp(), is(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMutationsTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
|
||||
BatchWriter writer = new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
List<Mutation> mutations = new ArrayList<>();
|
||||
Mutation mutation = new Mutation("row");
|
||||
mutation.put("colF", "colQ", "val");
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(1)).addMutation(any()); // 1 time
|
||||
verify(mockSignatureWriter, never()).addMutation(any());
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(3)).addMutation(any()); // 1 + 2 times
|
||||
verify(mockSignatureWriter, never()).addMutation(any());
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(6)).addMutation(any()); // 1 + 2 + 3 times
|
||||
verify(mockSignatureWriter, never()).addMutation(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMutationsExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
when(mockConnector.createBatchWriter(SIG_TABLE, null)).thenReturn(mockSignatureWriter);
|
||||
|
||||
BatchWriter writer = new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
|
||||
List<Mutation> mutations = new ArrayList<>();
|
||||
Mutation mutation = new Mutation("row");
|
||||
mutation.put("colF", "colQ", "val");
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(1)).addMutation(any()); // 1 time
|
||||
verify(mockSignatureWriter, times(1)).addMutation(any()); // 1 time
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(3)).addMutation(any()); // 1 + 2 times
|
||||
verify(mockSignatureWriter, times(3)).addMutation(any()); // 1 + 2 times
|
||||
|
||||
mutations.add(mutation);
|
||||
writer.addMutations(mutations);
|
||||
verify(mockWriter, times(6)).addMutation(any()); // 1 + 2 + 3 times
|
||||
verify(mockSignatureWriter, times(6)).addMutation(any()); // 1 + 2 + 3 times
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
|
||||
new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).flush();
|
||||
verify(mockWriter).flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
when(mockConnector.createBatchWriter(SIG_TABLE, null)).thenReturn(mockSignatureWriter);
|
||||
|
||||
new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA)).flush();
|
||||
verify(mockWriter).flush();
|
||||
verify(mockSignatureWriter).flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
|
||||
new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).close();
|
||||
verify(mockWriter).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeExternalTest() throws Exception {
|
||||
when(mockConnector.createBatchWriter(TEST_TABLE, null)).thenReturn(mockWriter);
|
||||
when(mockConnector.createBatchWriter(SIG_TABLE, null)).thenReturn(mockSignatureWriter);
|
||||
|
||||
new SignedBatchWriter(mockConnector, TEST_TABLE, null, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA)).close();
|
||||
verify(mockWriter).close();
|
||||
verify(mockSignatureWriter).close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an encryptor config.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @return EncryptionConfig.
|
||||
*/
|
||||
private SignatureConfig getConfig(String resource) throws Exception {
|
||||
return new SignatureConfigBuilder().readFromFile(new InputStreamReader(TestUtils.getResourceAsStream(this.getClass(), resource))).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static edu.mit.ll.pace.internal.Utils.VISIBILITY_CHARSET;
|
||||
import static edu.mit.ll.pace.test.Matchers.equalTo;
|
||||
import static edu.mit.ll.pace.test.TestUtils.getResourceAsStream;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.mit.ll.pace.internal.Utils;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
|
||||
/**
|
||||
* Tests for {@link SignedInlineScannerIterator}
|
||||
*/
|
||||
public class SignedIteratorTest {
|
||||
|
||||
private Map<ValueSigner,SignatureKeyContainer> aliceKeyContainers = new HashMap<>();
|
||||
private Map<ValueSigner,SignatureKeyContainer> bobKeyContainers = new HashMap<>();
|
||||
|
||||
public SignedIteratorTest() throws NoSuchAlgorithmException {
|
||||
aliceKeyContainers = MockSignatureKeyContainer.getContainers("alice", "alice", "bob");
|
||||
bobKeyContainers = MockSignatureKeyContainer.getContainers("bob", "alice", "bob");
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setupBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorValueTest() throws Exception {
|
||||
Random random = new Random();
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] bytes = new byte[32 * 4];
|
||||
random.nextBytes(bytes);
|
||||
entries.add(new SimpleImmutableEntry<>(new Key(Arrays.copyOfRange(bytes, 0, 32), Arrays.copyOfRange(bytes, 32, 64), Arrays.copyOfRange(bytes, 64, 96),
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 0, false), new Value(Arrays.copyOfRange(bytes, 96, 128))));
|
||||
}
|
||||
|
||||
EntrySigner signer = getSigner("config1.ini", aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
List<Entry<Key,Value>> signedEntries = new ArrayList<>(entries.size());
|
||||
for (Entry<Key,Value> entry : entries) {
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
}
|
||||
|
||||
EntrySigner verifier = getSigner("config1.ini", bobKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
Iterator<Entry<Key,Value>> entriesIterator = entries.iterator();
|
||||
Iterator<Entry<Key,Value>> signedIterator = new SignedInlineScannerIterator(signedEntries.iterator(), verifier);
|
||||
|
||||
while (entriesIterator.hasNext()) {
|
||||
assertThat("should return all entries", signedIterator.hasNext(), is(true));
|
||||
assertThat("should return same entry", entriesIterator.next(), equalTo(signedIterator.next()));
|
||||
}
|
||||
assertThat("should not return any more entries", signedIterator.hasNext(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorColVisTest() throws Exception {
|
||||
Random random = new Random();
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] bytes = new byte[32 * 4];
|
||||
random.nextBytes(bytes);
|
||||
entries.add(new SimpleImmutableEntry<>(new Key(Arrays.copyOfRange(bytes, 0, 32), Arrays.copyOfRange(bytes, 32, 64), Arrays.copyOfRange(bytes, 64, 96),
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 0, false), new Value(Arrays.copyOfRange(bytes, 96, 128))));
|
||||
}
|
||||
|
||||
EntrySigner signer = getSigner("config2.ini", aliceKeyContainers.get(ValueSigner.RSA_PKCS1));
|
||||
List<Entry<Key,Value>> signedEntries = new ArrayList<>(entries.size());
|
||||
for (Entry<Key,Value> entry : entries) {
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
}
|
||||
|
||||
EntrySigner verifier = getSigner("config2.ini", bobKeyContainers.get(ValueSigner.RSA_PKCS1));
|
||||
Iterator<Entry<Key,Value>> entriesIterator = entries.iterator();
|
||||
SignedInlineScannerIterator signedIterator = new SignedInlineScannerIterator(signedEntries.iterator(), verifier);
|
||||
|
||||
while (entriesIterator.hasNext()) {
|
||||
assertThat("should return all entries", signedIterator.hasNext(), is(true));
|
||||
assertThat("should return same entry", entriesIterator.next(), equalTo(signedIterator.next()));
|
||||
}
|
||||
assertThat("should not return any more entries", signedIterator.hasNext(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorTableTest() throws Exception {
|
||||
Random random = new Random();
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] bytes = new byte[32 * 4];
|
||||
random.nextBytes(bytes);
|
||||
entries.add(new SimpleImmutableEntry<>(new Key(Arrays.copyOfRange(bytes, 0, 32), Arrays.copyOfRange(bytes, 32, 64), Arrays.copyOfRange(bytes, 64, 96),
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 0, false), new Value(Arrays.copyOfRange(bytes, 96, 128))));
|
||||
}
|
||||
|
||||
EntrySigner signer = getSigner("config3.ini", aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
List<Entry<Key,Value>> signedEntries = new ArrayList<>(entries.size());
|
||||
for (Entry<Key,Value> entry : entries) {
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
}
|
||||
|
||||
EntrySigner verifier = getSigner("config3.ini", bobKeyContainers.get(ValueSigner.ECDSA));
|
||||
Iterator<Entry<Key,Value>> entriesIterator = entries.iterator();
|
||||
SignedExternalScannerIterator signedIterator = new SignedExternalScannerIterator(entries.iterator(), signedEntries.iterator(), verifier, true);
|
||||
|
||||
while (entriesIterator.hasNext()) {
|
||||
assertThat("should return all entries", signedIterator.hasNext(), is(true));
|
||||
assertThat("should return same entry", entriesIterator.next(), equalTo(signedIterator.next()));
|
||||
}
|
||||
assertThat("should not return any more entries", signedIterator.hasNext(), is(false));
|
||||
|
||||
// Sign to separate table, with signatures in a different order than the entries.
|
||||
signedEntries = new ArrayList<>(entries.size());
|
||||
for (Entry<Key,Value> entry : entries) {
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
}
|
||||
Collections.shuffle(signedEntries);
|
||||
|
||||
entriesIterator = entries.iterator();
|
||||
signedIterator = new SignedExternalScannerIterator(entries.iterator(), signedEntries.iterator(), verifier, false);
|
||||
|
||||
while (entriesIterator.hasNext()) {
|
||||
assertThat("should return all entries", signedIterator.hasNext(), is(true));
|
||||
assertThat("should return same entry", entriesIterator.next(), equalTo(signedIterator.next()));
|
||||
}
|
||||
assertThat("should not return any more entries", signedIterator.hasNext(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void internalUnprocessedTest() throws Exception {
|
||||
EntrySigner signer = getSigner("config1.ini", aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
EntrySigner verifier = getSigner("config1.ini", bobKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
byte[] row = new byte[] {1};
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(row, new byte[] {2}, new byte[] {3}, "secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false,
|
||||
false), new Value(new byte[] {4}));
|
||||
|
||||
entries.add(signer.sign(entry, true));
|
||||
SignedInlineScannerIterator iterator = new SignedInlineScannerIterator(entries.iterator(), verifier);
|
||||
|
||||
iterator.next();
|
||||
assertThat("unprocessed item is correct", iterator.unprocessed(), Matchers.equalTo(entries.get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void internalUnprocessedException() throws Exception {
|
||||
EntrySigner signer = getSigner("config1.ini", aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
EntrySigner verifier = getSigner("config1.ini", bobKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
byte[] row = new byte[] {1};
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(row, new byte[] {2}, new byte[] {3}, "secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false,
|
||||
false), new Value(new byte[] {4}));
|
||||
|
||||
entries.add(signer.sign(entry, true));
|
||||
SignedInlineScannerIterator iterator = new SignedInlineScannerIterator(entries.iterator(), verifier);
|
||||
|
||||
try {
|
||||
iterator.unprocessed();
|
||||
fail("cannot call unprocessed before calling next()");
|
||||
} catch (NoSuchElementException e) { /* expected */}
|
||||
|
||||
try {
|
||||
iterator.hasNext();
|
||||
iterator.unprocessed();
|
||||
fail("cannot call unprocessed before calling next()");
|
||||
} catch (NoSuchElementException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void externalUnprocessedTest() throws Exception {
|
||||
EntrySigner signer = getSigner("config3.ini", aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
EntrySigner verifier = getSigner("config3.ini", bobKeyContainers.get(ValueSigner.ECDSA));
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
List<Entry<Key,Value>> signedEntries = new ArrayList<>();
|
||||
|
||||
byte[] row = new byte[] {1};
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(row, new byte[] {2}, new byte[] {3}, "secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false,
|
||||
false), new Value(new byte[] {4}));
|
||||
|
||||
entries.add(entry);
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
|
||||
SignedExternalScannerIterator iterator = new SignedExternalScannerIterator(entries.iterator(), signedEntries.iterator(), verifier, true);
|
||||
|
||||
iterator.next();
|
||||
assertThat("unprocessed item is correct", iterator.unprocessed(), Matchers.equalTo(entries.get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void externalUnprocessedException() throws Exception {
|
||||
EntrySigner signer = getSigner("config3.ini", aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
EntrySigner verifier = getSigner("config3.ini", bobKeyContainers.get(ValueSigner.ECDSA));
|
||||
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
List<Entry<Key,Value>> signedEntries = new ArrayList<>();
|
||||
|
||||
byte[] row = new byte[] {1};
|
||||
Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(row, new byte[] {2}, new byte[] {3}, "secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false,
|
||||
false), new Value(new byte[] {4}));
|
||||
|
||||
entries.add(entry);
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
|
||||
SignedExternalScannerIterator iterator = new SignedExternalScannerIterator(entries.iterator(), signedEntries.iterator(), verifier, true);
|
||||
|
||||
try {
|
||||
iterator.unprocessed();
|
||||
fail("cannot call unprocessed before calling next()");
|
||||
} catch (NoSuchElementException e) { /* expected */}
|
||||
|
||||
try {
|
||||
iterator.hasNext();
|
||||
iterator.unprocessed();
|
||||
fail("cannot call unprocessed before calling next()");
|
||||
} catch (NoSuchElementException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorRemoveFailsTest() {
|
||||
try {
|
||||
new SignedInlineScannerIterator(null, null).remove();
|
||||
fail("removal is not allowed");
|
||||
} catch (UnsupportedOperationException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedExternalScannerIterator(null, null, null, true).remove();
|
||||
fail("removal is not allowed");
|
||||
} catch (UnsupportedOperationException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingSignatureTest() throws Exception {
|
||||
Random random = new Random();
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] bytes = new byte[32 * 4];
|
||||
random.nextBytes(bytes);
|
||||
entries.add(new SimpleImmutableEntry<>(new Key(Arrays.copyOfRange(bytes, 0, 32), Arrays.copyOfRange(bytes, 32, 64), Arrays.copyOfRange(bytes, 64, 96),
|
||||
"secret".getBytes(VISIBILITY_CHARSET), (long) 0, false), new Value(Arrays.copyOfRange(bytes, 96, 128))));
|
||||
}
|
||||
|
||||
EntrySigner signer = getSigner("config3.ini", aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
List<Entry<Key,Value>> signedEntries = new ArrayList<>(entries.size());
|
||||
for (Entry<Key,Value> entry : entries) {
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
}
|
||||
|
||||
EntrySigner verifier = getSigner("config3.ini", bobKeyContainers.get(ValueSigner.ECDSA));
|
||||
signedEntries.remove(9);
|
||||
SignedExternalScannerIterator signedIterator = new SignedExternalScannerIterator(entries.iterator(), signedEntries.iterator(), verifier, true);
|
||||
|
||||
try {
|
||||
while (signedIterator.hasNext()) {
|
||||
signedIterator.next();
|
||||
}
|
||||
fail("missing signature should cause an exception");
|
||||
} catch (SignatureException e) { /* expected */}
|
||||
|
||||
// Missing signature is the first signature.
|
||||
signedEntries = new ArrayList<>(entries.size());
|
||||
for (Entry<Key,Value> entry : entries) {
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
}
|
||||
signedEntries.remove(0);
|
||||
signedIterator = new SignedExternalScannerIterator(entries.iterator(), signedEntries.iterator(), verifier, true);
|
||||
|
||||
try {
|
||||
while (signedIterator.hasNext()) {
|
||||
signedIterator.next();
|
||||
}
|
||||
fail("missing signature should cause an exception");
|
||||
} catch (SignatureException e) { /* expected */}
|
||||
|
||||
// Missing signature is in the middle of the table.
|
||||
signedEntries = new ArrayList<>(entries.size());
|
||||
for (Entry<Key,Value> entry : entries) {
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
}
|
||||
signedEntries.remove(5);
|
||||
Collections.shuffle(signedEntries);
|
||||
signedIterator = new SignedExternalScannerIterator(entries.iterator(), signedEntries.iterator(), verifier, false);
|
||||
|
||||
try {
|
||||
while (signedIterator.hasNext()) {
|
||||
signedIterator.next();
|
||||
}
|
||||
fail("missing signature should cause an exception");
|
||||
} catch (SignatureException e) { /* expected */}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a signer.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @param keys
|
||||
* Keys to use for the signer.
|
||||
* @return EntrySigner.
|
||||
*/
|
||||
private EntrySigner getSigner(String resource, SignatureKeyContainer keys) throws Exception {
|
||||
return new EntrySigner(new SignatureConfigBuilder().readFromFile(new InputStreamReader(getResourceAsStream(this.getClass(), resource))).build(), keys);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.accumulo.core.client.Connector;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.client.IteratorSetting.Column;
|
||||
import org.apache.accumulo.core.client.Scanner;
|
||||
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.hadoop.io.Text;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import edu.mit.ll.pace.internal.Utils;
|
||||
import edu.mit.ll.pace.test.Matchers;
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test {@link SignedScanner}.
|
||||
*/
|
||||
public class SignedScannerTest {
|
||||
|
||||
private final static String TEST_TABLE = "test";
|
||||
private final static String SIG_TABLE = "sigs";
|
||||
|
||||
private final static Authorizations authorizations = new Authorizations("admin", "secret", "top secret");
|
||||
|
||||
private Map<ValueSigner,SignatureKeyContainer> aliceKeyContainers = new HashMap<>();
|
||||
|
||||
@Mock
|
||||
private Connector mockConnector;
|
||||
|
||||
@Mock
|
||||
private Scanner mockScanner;
|
||||
|
||||
@Mock
|
||||
private Scanner mockSignatureScanner;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||
|
||||
public SignedScannerTest() throws NoSuchAlgorithmException {
|
||||
aliceKeyContainers = MockSignatureKeyContainer.getContainers("alice", "alice");
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setupBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badConstructorTest() throws Exception {
|
||||
try {
|
||||
new SignedScanner(null, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null connector not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedScanner(mockConnector, null, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null table name not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedScanner(mockConnector, TEST_TABLE, null, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null authorizations not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, null, aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
fail("null crypto config not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
|
||||
try {
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), null);
|
||||
fail("null key container not allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest() throws Exception {
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
verify(mockConnector).createScanner(TEST_TABLE, authorizations);
|
||||
verify(mockConnector, never()).createScanner(SIG_TABLE, authorizations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorExternalTest() throws Exception {
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
verify(mockConnector).createScanner(TEST_TABLE, authorizations);
|
||||
verify(mockConnector).createScanner(SIG_TABLE, authorizations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
|
||||
EntrySigner signer = new EntrySigner(getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
entries.add(signer.sign(entry, true));
|
||||
entries.add(signer.sign(entry2, true));
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
|
||||
Scanner scanner = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
Iterator<Entry<Key,Value>> iterator = scanner.iterator();
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry));
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
|
||||
EntrySigner signer = new EntrySigner(getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
List<Entry<Key,Value>> entries = new ArrayList<>();
|
||||
List<Entry<Key,Value>> signedEntries = new ArrayList<>();
|
||||
|
||||
Map.Entry<Key,Value> entry = new SimpleImmutableEntry<>(new Key(new byte[] {1}, new byte[] {2}, new byte[] {3},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {4}));
|
||||
Map.Entry<Key,Value> entry2 = new SimpleImmutableEntry<>(new Key(new byte[] {5}, new byte[] {6}, new byte[] {7},
|
||||
"secret".getBytes(Utils.VISIBILITY_CHARSET), 0, false, false), new Value(new byte[] {8}));
|
||||
|
||||
entries.add(entry);
|
||||
entries.add(entry2);
|
||||
signedEntries.add(signer.sign(entry, true));
|
||||
signedEntries.add(signer.sign(entry2, true));
|
||||
|
||||
when(mockScanner.iterator()).thenReturn(entries.iterator()).thenReturn(entries.iterator());
|
||||
when(mockSignatureScanner.iterator()).thenReturn(signedEntries.iterator()).thenReturn(signedEntries.iterator());
|
||||
|
||||
Scanner scanner = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.ECDSA));
|
||||
assertThat("has correct number of elements", scanner, iterableWithSize(2));
|
||||
|
||||
Iterator<Entry<Key,Value>> iterator = scanner.iterator();
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry));
|
||||
assertThat("correct item", iterator.next(), Matchers.equalTo(entry2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addScanIteratorTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
IteratorSetting test = new IteratorSetting(10, "test", "test2");
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).addScanIterator(test);
|
||||
verify(mockScanner).addScanIterator(test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addScanIteratorExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
IteratorSetting test = new IteratorSetting(10, "test", "test2");
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).addScanIterator(test);
|
||||
verify(mockScanner).addScanIterator(test);
|
||||
verify(mockSignatureScanner).addScanIterator(test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearColumns() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).clearColumns();
|
||||
verify(mockScanner).clearColumns();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearExternalColumns() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).clearColumns();
|
||||
verify(mockScanner).clearColumns();
|
||||
verify(mockSignatureScanner).clearColumns();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearScanIteratorsTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).clearScanIterators();
|
||||
verify(mockScanner).clearScanIterators();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearScanIteratorsExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).clearScanIterators();
|
||||
verify(mockScanner).clearScanIterators();
|
||||
verify(mockSignatureScanner).clearScanIterators();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).close();
|
||||
verify(mockScanner).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).close();
|
||||
verify(mockScanner).close();
|
||||
verify(mockSignatureScanner).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnTest() throws Exception {
|
||||
Column column = new Column(new Text(new byte[] {1}), new Text(new byte[] {2}));
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).fetchColumn(column);
|
||||
verify(mockScanner).fetchColumn(column);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnExternalTest() throws Exception {
|
||||
Column column = new Column(new Text(new byte[] {1}), new Text(new byte[] {2}));
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).fetchColumn(column);
|
||||
verify(mockScanner).fetchColumn(column);
|
||||
verify(mockSignatureScanner).fetchColumn(column);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumn2Test() throws Exception {
|
||||
Text colF = new Text(new byte[] {1}), colQ = new Text(new byte[] {2});
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).fetchColumn(colF, colQ);
|
||||
verify(mockScanner).fetchColumn(colF, colQ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumn2ExternalTest() throws Exception {
|
||||
Text colF = new Text(new byte[] {1}), colQ = new Text(new byte[] {2});
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).fetchColumn(colF, colQ);
|
||||
verify(mockScanner).fetchColumn(colF, colQ);
|
||||
verify(mockSignatureScanner).fetchColumn(colF, colQ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnFamilyTest() throws Exception {
|
||||
Text colF = new Text(new byte[] {1});
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).fetchColumnFamily(colF);
|
||||
verify(mockScanner).fetchColumnFamily(colF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchColumnFamilyExternalTest() throws Exception {
|
||||
Text colF = new Text(new byte[] {1});
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).fetchColumnFamily(colF);
|
||||
verify(mockScanner).fetchColumnFamily(colF);
|
||||
verify(mockSignatureScanner).fetchColumnFamily(colF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAuthorizationsTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockScanner.getAuthorizations()).thenReturn(authorizations);
|
||||
Authorizations auths = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getAuthorizations();
|
||||
verify(mockScanner).getAuthorizations();
|
||||
assertThat("correct authorizations returned", auths, equalTo(authorizations));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
SamplerConfiguration config = new SamplerConfiguration("test");
|
||||
when(mockScanner.getSamplerConfiguration()).thenReturn(config);
|
||||
SamplerConfiguration value = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"),
|
||||
aliceKeyContainers.get(ValueSigner.RSA_PSS)).getSamplerConfiguration();
|
||||
verify(mockScanner).getSamplerConfiguration();
|
||||
assertThat("correct config returned", value, equalTo(config));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
SamplerConfiguration config = new SamplerConfiguration("test");
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setSamplerConfiguration(config);
|
||||
verify(mockScanner).setSamplerConfiguration(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSamplerConfigurationExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
SamplerConfiguration config = new SamplerConfiguration("test");
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setSamplerConfiguration(config);
|
||||
verify(mockScanner).setSamplerConfiguration(config);
|
||||
verify(mockSignatureScanner).setSamplerConfiguration(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearSamplerConfigurationTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearSamplerConfiguration();
|
||||
verify(mockScanner).clearSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearSamplerConfigurationExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearSamplerConfiguration();
|
||||
verify(mockScanner).clearSamplerConfiguration();
|
||||
verify(mockSignatureScanner).clearSamplerConfiguration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBatchTimeoutTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setBatchTimeout(5L,
|
||||
TimeUnit.DAYS);
|
||||
verify(mockScanner).setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBatchTimeoutExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setBatchTimeout(5L,
|
||||
TimeUnit.DAYS);
|
||||
verify(mockScanner).setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
verify(mockSignatureScanner).setBatchTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatchTimeoutTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockScanner.getBatchTimeout(TimeUnit.DAYS)).thenReturn(5L);
|
||||
long value = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getBatchTimeout(TimeUnit.DAYS);
|
||||
verify(mockScanner).getBatchTimeout(TimeUnit.DAYS);
|
||||
assertThat("correct timeout returned", value, is(5L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setClassLoaderContext("test");
|
||||
verify(mockScanner).setClassLoaderContext("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClassLoaderContextExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setClassLoaderContext("test");
|
||||
verify(mockScanner).setClassLoaderContext("test");
|
||||
verify(mockSignatureScanner).setClassLoaderContext("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearClassLoaderContext();
|
||||
verify(mockScanner).clearClassLoaderContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearClassLoaderContextExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.clearClassLoaderContext();
|
||||
verify(mockScanner).clearClassLoaderContext();
|
||||
verify(mockSignatureScanner).clearClassLoaderContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getClassLoaderContextTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockScanner.getClassLoaderContext()).thenReturn("test");
|
||||
String value = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getClassLoaderContext();
|
||||
verify(mockScanner).getClassLoaderContext();
|
||||
assertThat("correct class loader context returned", value, is("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTimeoutTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockScanner.getTimeout(TimeUnit.DAYS)).thenReturn(5L);
|
||||
Long value = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getTimeout(TimeUnit.DAYS);
|
||||
verify(mockScanner).getTimeout(TimeUnit.DAYS);
|
||||
assertThat("correct timeout returned", value, is(5L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeScanIteratorTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.removeScanIterator("test");
|
||||
verify(mockScanner).removeScanIterator("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeScanIteratorExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.removeScanIterator("test");
|
||||
verify(mockScanner).removeScanIterator("test");
|
||||
verify(mockSignatureScanner).removeScanIterator("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateScanIteratorOptionTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.updateScanIteratorOption("test", "a", "b");
|
||||
verify(mockScanner).updateScanIteratorOption("test", "a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateScanIteratorOptionExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.updateScanIteratorOption("test", "a", "b");
|
||||
verify(mockScanner).updateScanIteratorOption("test", "a", "b");
|
||||
verify(mockSignatureScanner).updateScanIteratorOption("test", "a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTimeoutTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setTimeout(5L,
|
||||
TimeUnit.DAYS);
|
||||
verify(mockScanner).setTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTimeoutExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setTimeout(5L,
|
||||
TimeUnit.DAYS);
|
||||
verify(mockScanner).setTimeout(5L, TimeUnit.DAYS);
|
||||
verify(mockSignatureScanner).setTimeout(5L, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableIsolationTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).disableIsolation();
|
||||
verify(mockScanner).disableIsolation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableIsolationExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).disableIsolation();
|
||||
verify(mockScanner).disableIsolation();
|
||||
verify(mockSignatureScanner).disableIsolation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableIsolationTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).enableIsolation();
|
||||
verify(mockScanner).enableIsolation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableIsolationExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).enableIsolation();
|
||||
verify(mockScanner).enableIsolation();
|
||||
verify(mockSignatureScanner).enableIsolation();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBatchSizeTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setBatchSize(5);
|
||||
verify(mockScanner).setBatchSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBatchSizeExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setBatchSize(5);
|
||||
verify(mockScanner).setBatchSize(5);
|
||||
verify(mockSignatureScanner).setBatchSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatchSizeTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockScanner.getBatchSize()).thenReturn(5);
|
||||
int value = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getBatchSize();
|
||||
verify(mockScanner).getBatchSize();
|
||||
assertThat("correct batch size returned", value, is(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRangeTest() throws Exception {
|
||||
Range range = new Range("test");
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setRange(range);
|
||||
verify(mockScanner).setRange(range);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRangeExternalTest() throws Exception {
|
||||
Range range = new Range("test");
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setRange(range);
|
||||
verify(mockScanner).setRange(range);
|
||||
verify(mockSignatureScanner).setRange(range);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRangeTest() throws Exception {
|
||||
Range range = new Range("test");
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockScanner.getRange()).thenReturn(range);
|
||||
Range value = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getRange();
|
||||
verify(mockScanner).getRange();
|
||||
assertThat("correct range returned", value, equalTo(range));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setReadaheadThresholdTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setReadaheadThreshold(5L);
|
||||
verify(mockScanner).setReadaheadThreshold(5L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setReadaheadThresholdExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.setReadaheadThreshold(5L);
|
||||
verify(mockScanner).setReadaheadThreshold(5L);
|
||||
verify(mockSignatureScanner).setReadaheadThreshold(5L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReadaheadThresholdTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockScanner.getReadaheadThreshold()).thenReturn(5L);
|
||||
long value = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getReadaheadThreshold();
|
||||
verify(mockScanner).getReadaheadThreshold();
|
||||
assertThat("correct threshold returned", value, is(5L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setTimeOutTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setTimeOut(5);
|
||||
verify(mockScanner).setTimeOut(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setTimeOutExternalTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockConnector.createScanner(SIG_TABLE, authorizations)).thenReturn(mockSignatureScanner);
|
||||
new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config3.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS)).setTimeOut(5);
|
||||
verify(mockScanner).setTimeOut(5);
|
||||
verify(mockSignatureScanner).setTimeOut(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void getTimeOutTest() throws Exception {
|
||||
when(mockConnector.createScanner(TEST_TABLE, authorizations)).thenReturn(mockScanner);
|
||||
when(mockScanner.getTimeOut()).thenReturn(5);
|
||||
int value = new SignedScanner(mockConnector, TEST_TABLE, authorizations, getConfig("config1.ini"), aliceKeyContainers.get(ValueSigner.RSA_PSS))
|
||||
.getTimeOut();
|
||||
verify(mockScanner).getTimeOut();
|
||||
assertThat("correct timeout returned", value, is(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an signor config.
|
||||
*
|
||||
* @param resource
|
||||
* Resource file containing the configuration.
|
||||
* @return EncryptionConfig.
|
||||
*/
|
||||
private SignatureConfig getConfig(String resource) throws Exception {
|
||||
return new SignatureConfigBuilder().readFromFile(new InputStreamReader(TestUtils.getResourceAsStream(this.getClass(), resource))).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.signature;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.arrayWithSize;
|
||||
import static org.hamcrest.Matchers.emptyArray;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.mit.ll.pace.test.TestUtils;
|
||||
|
||||
/**
|
||||
* Test for {@link ValueSigner}.
|
||||
*/
|
||||
public class ValueSignerTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setupBouncyCastle() {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validEnumTest() {
|
||||
assertThat("should have four values", ValueSigner.values(), is(arrayWithSize(4)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringTest() {
|
||||
assertThat("toString should return correct value", ValueSigner.RSA_PKCS1.toString(), is("RSA-PKCS1"));
|
||||
assertThat("toString should return correct value", ValueSigner.RSA_PSS.toString(), is("RSA-PSS"));
|
||||
assertThat("toString should return correct value", ValueSigner.DSA.toString(), is("DSA"));
|
||||
assertThat("toString should return correct value", ValueSigner.ECDSA.toString(), is("ECDSA"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromStringTest() {
|
||||
assertThat("fromString should return correct enum value", ValueSigner.fromString("RSA-PKCS1"), is(ValueSigner.RSA_PKCS1));
|
||||
assertThat("fromString should return correct enum value", ValueSigner.fromString("RSA-PSS"), is(ValueSigner.RSA_PSS));
|
||||
assertThat("fromString should return correct enum value", ValueSigner.fromString("DSA"), is(ValueSigner.DSA));
|
||||
assertThat("fromString should return correct enum value", ValueSigner.fromString("ECDSA"), is(ValueSigner.ECDSA));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromStringExceptionTest() {
|
||||
try {
|
||||
ValueSigner.fromString("bad");
|
||||
fail("only valid names should be allowed");
|
||||
} catch (IllegalArgumentException e) { /* expected */}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getKeyGenerationAlgorithmTest() throws Exception {
|
||||
assertThat("should return correct algorithm", ValueSigner.RSA_PKCS1.getKeyGenerationAlgorithm(), is("RSA"));
|
||||
assertThat("should return correct algorithm", ValueSigner.RSA_PSS.getKeyGenerationAlgorithm(), is("RSA"));
|
||||
assertThat("should return correct algorithm", ValueSigner.DSA.getKeyGenerationAlgorithm(), is("DSA"));
|
||||
assertThat("should return correct algorithm", ValueSigner.ECDSA.getKeyGenerationAlgorithm(), is("ECDSA"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInstanceTest() throws Exception {
|
||||
assertThat("should return a Signature instance", ValueSigner.RSA_PKCS1.getInstance(null), is(instanceOf(Signature.class)));
|
||||
assertThat("should return a Signature instance", ValueSigner.RSA_PSS.getInstance("BC"), is(instanceOf(Signature.class)));
|
||||
assertThat("should return a Signature instance", ValueSigner.DSA.getInstance("BC"), is(instanceOf(Signature.class)));
|
||||
assertThat("should return a Signature instance", ValueSigner.ECDSA.getInstance(null), is(instanceOf(Signature.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signatureTest() throws Exception {
|
||||
for (ValueSigner signer : ValueSigner.values()) {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(signer.getKeyGenerationAlgorithm());
|
||||
switch (signer) {
|
||||
case RSA_PKCS1:
|
||||
case RSA_PSS:
|
||||
keyGen.initialize(1024);
|
||||
break;
|
||||
|
||||
case DSA:
|
||||
keyGen.initialize(1024);
|
||||
break;
|
||||
|
||||
case ECDSA:
|
||||
keyGen.initialize(256);
|
||||
break;
|
||||
}
|
||||
|
||||
signatureTest(signer, keyGen);
|
||||
}
|
||||
}
|
||||
|
||||
private void signatureTest(ValueSigner signer, KeyPairGenerator generator) throws Exception {
|
||||
byte[] data = "HELLO".getBytes();
|
||||
Signature signature = signer.getInstance(null);
|
||||
KeyPair pair = generator.generateKeyPair();
|
||||
|
||||
signature.initSign(pair.getPrivate());
|
||||
signature.update(data);
|
||||
byte[] digest = signature.sign();
|
||||
|
||||
assertThat("digest has a value", TestUtils.wrap(digest), is(not(emptyArray())));
|
||||
|
||||
signature.initVerify(pair.getPublic());
|
||||
signature.update(data);
|
||||
assertThat("verification succeeds", signature.verify(digest), is(true));
|
||||
|
||||
switch (signer) {
|
||||
case RSA_PKCS1:
|
||||
break;
|
||||
|
||||
default:
|
||||
signature.initSign(pair.getPrivate());
|
||||
signature.update(data);
|
||||
assertThat("signatures correctly use random padding", signature.sign(), not(equalTo(digest)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.test;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Factory;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile.Section;
|
||||
|
||||
/**
|
||||
* Hamcrest matchers for use in testing.
|
||||
*/
|
||||
public class Matchers {
|
||||
/**
|
||||
* Gets a hamcrest matcher that tests whether two objects have the same fields.
|
||||
* <p>
|
||||
* This check uses reflection.
|
||||
*
|
||||
* @param expectedValue
|
||||
* The expected value to core against.
|
||||
* @return Method safe matcher that will core field equality.
|
||||
*/
|
||||
@Factory
|
||||
public static <T> TypeSafeMatcher<T> hasSameFieldsAs(final T expectedValue) {
|
||||
return new TypeSafeMatcher<T>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(T actualValue) {
|
||||
return EqualsBuilder.reflectionEquals(expectedValue, actualValue, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void describeMismatchSafely(T actualValue, Description description) {
|
||||
description.appendText("was {").appendText(ReflectionToStringBuilder.toString(actualValue, ToStringStyle.SIMPLE_STYLE)).appendText("}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText(" {").appendText(ReflectionToStringBuilder.toString(expectedValue, ToStringStyle.SIMPLE_STYLE)).appendText("}");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hamcrest matcher that tests whether two Ini objects are equal.
|
||||
*
|
||||
* @param expectedValue
|
||||
* The expected value to core against.
|
||||
* @return Method safe matcher that will core field equality.
|
||||
*/
|
||||
@Factory
|
||||
public static TypeSafeMatcher<Ini> equalTo(final Ini expectedValue) {
|
||||
return new TypeSafeMatcher<Ini>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(Ini actualValue) {
|
||||
if (expectedValue.size() != actualValue.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Section expectedSection : expectedValue.values()) {
|
||||
Section actualSection = actualValue.get(expectedSection.getName());
|
||||
if (actualSection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expectedSection.size() != actualSection.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Entry<String,String> expectedEntry : expectedSection.entrySet()) {
|
||||
if (!actualSection.containsKey(expectedEntry.getKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String actual = actualSection.get(expectedEntry.getKey());
|
||||
String expected = expectedEntry.getValue();
|
||||
if ((actual == null && expected != null) || (actual != null && !actual.equals(expected))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendValue(expectedValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hamcrest matcher that tests whether two entires are the same.
|
||||
*
|
||||
* @param expectedValue
|
||||
* The expected value to core against.
|
||||
* @return Method safe matcher that will core equality.
|
||||
*/
|
||||
@Factory
|
||||
public static TypeSafeMatcher<Entry<Key,Value>> equalTo(final Entry<Key,Value> expectedValue) {
|
||||
return new TypeSafeMatcher<Entry<Key,Value>>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(Entry<Key,Value> actualValue) {
|
||||
return expectedValue.getKey().equals(actualValue.getKey()) && expectedValue.getValue().equals(actualValue.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void describeMismatchSafely(Entry<Key,Value> actualValue, Description description) {
|
||||
description.appendText("was {key=").appendValue(actualValue.getKey()).appendText(", value=").appendValue(actualValue.getValue()).appendText("}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText(" {key=").appendValue(expectedValue.getKey()).appendText(", value=").appendValue(expectedValue.getValue()).appendText("}");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2016 MIT Lincoln Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package edu.mit.ll.pace.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Functions utilized across the tests.
|
||||
*/
|
||||
public final class TestUtils {
|
||||
|
||||
/**
|
||||
* Get a resource for the given class.
|
||||
*
|
||||
* @param clazz
|
||||
* Class to get the resource for.
|
||||
* @param resourceName
|
||||
* THe name of the resource.
|
||||
* @return An stream to the resource.
|
||||
* @throws IOException
|
||||
* Thrown if the resource name does not exist.
|
||||
*/
|
||||
public static InputStream getResourceAsStream(Class<?> clazz, String resourceName) throws IOException {
|
||||
if (resourceName.startsWith("/")) {
|
||||
return clazz.getResourceAsStream(resourceName);
|
||||
}
|
||||
|
||||
InputStream resource = clazz.getResourceAsStream(clazz.getSimpleName() + "/" + resourceName);
|
||||
if (resource == null) {
|
||||
resource = clazz.getResourceAsStream(resourceName);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a byte[] in a Byte[] so that it can be used with {@link org.hamcrest.Matchers#arrayContaining(Object[])} )}.
|
||||
*
|
||||
* @param data
|
||||
* Data to be wrapped.
|
||||
* @return Wrapped array.
|
||||
*/
|
||||
public static Byte[] wrap(byte[] data) {
|
||||
Byte[] newData = new Byte[data.length];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
newData[i] = data[i];
|
||||
}
|
||||
return newData;
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_GCM
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_GCM
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_GCM
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_row
|
||||
sources = row
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colFamily
|
||||
sources = colFamily
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_GCM
|
||||
|
||||
[value]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_row
|
||||
sources = row
|
||||
|
||||
[value]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = deterministic
|
||||
sources = row,colFamily
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[value]
|
||||
cipher = AES_GCM
|
||||
@@ -0,0 +1,35 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_CBC
|
||||
provider = BC
|
||||
useVisibility = false
|
||||
keyId = keyId
|
||||
keyLength = 16
|
||||
sources = row,colFamily,colQualifier
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
useVisibility = false
|
||||
keyId = keyId2
|
||||
keyLength = 32
|
||||
sources = colFamily,colQualifier
|
||||
|
||||
[value]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
keyId = AES_GCM
|
||||
keyLength = 24
|
||||
sources = value
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = deterministic
|
||||
sources = row,colFamily
|
||||
@@ -0,0 +1,23 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colFamily
|
||||
sources = colFamily
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
sources = colQualifier
|
||||
@@ -0,0 +1,18 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_GCM
|
||||
keyId = searchable_colFamily
|
||||
sources = colFamily
|
||||
@@ -0,0 +1,23 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colFamily
|
||||
sources = colFamily,colQualifier
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_GCM
|
||||
keyId = AES_GCM
|
||||
sources = colQualifier
|
||||
@@ -0,0 +1,23 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_GCM
|
||||
keyId = AES_GCM
|
||||
sources = row,colFamily
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
sources = colFamily,colQualifier
|
||||
@@ -0,0 +1,23 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_GCM
|
||||
keyId = AES_GCM
|
||||
sources = row,colFamily
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
sources = colQualifier
|
||||
@@ -0,0 +1,23 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_GCM
|
||||
keyId = AES_GCM
|
||||
sources = colFamily
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
sources = colQualifier
|
||||
@@ -0,0 +1,18 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
sources = colQualifier
|
||||
@@ -0,0 +1,29 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_row
|
||||
sources = row
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_GCM
|
||||
useVisibility = true
|
||||
keyId = AES_GCM
|
||||
sources = colFamily
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
sources = colQualifier
|
||||
@@ -0,0 +1,18 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colFamily
|
||||
sources = colFamily,colQualifier
|
||||
@@ -0,0 +1,23 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_row
|
||||
sources = row,colQualifier
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colFamily
|
||||
sources = colFamily
|
||||
@@ -0,0 +1,18 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
sources = colFamily,colQualifier
|
||||
@@ -0,0 +1,23 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_row
|
||||
sources = row,colFamily
|
||||
|
||||
[colQualifier]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_colQualifier
|
||||
sources = colQualifier
|
||||
@@ -0,0 +1,18 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[row]
|
||||
cipher = AES_SIV_DETERMINISTIC
|
||||
keyId = searchable_row
|
||||
sources = row,colFamily,colQualifier
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
; Copyright 2016 MIT Lincoln Laboratory
|
||||
;
|
||||
; 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.
|
||||
|
||||
[colFamily]
|
||||
cipher = AES_CBC
|
||||
provider = SunJCE
|
||||
useVisibility = false
|
||||
keyId = keyId
|
||||
keyLength = 24
|
||||
sources = row,colFamily,colQualifier
|
||||
Loaded 100 of 266 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user