blob: be760f5099db1f884ae2564553d727ba1b4dd0ac [file] [log] [blame]
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package com.google.crypto.tink.tinkey;
import com.google.crypto.tink.Aead;
import com.google.crypto.tink.BinaryKeysetReader;
import com.google.crypto.tink.BinaryKeysetWriter;
import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.JsonKeysetReader;
import com.google.crypto.tink.JsonKeysetWriter;
import com.google.crypto.tink.KeyTemplate;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.KeysetManager;
import com.google.crypto.tink.KeysetReader;
import com.google.crypto.tink.KeysetWriter;
import com.google.crypto.tink.KmsClients;
import com.google.crypto.tink.proto.OutputPrefixType;
import com.google.protobuf.ByteString;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
/** Various helpers. */
final class TinkeyUtil {
public enum CommandType {
ADD_KEY,
CONVERT_KEYSET,
CREATE_KEYSET,
CREATE_PUBLIC_KEYSET,
LIST_KEY_TEMPLATES,
DELETE_KEY,
DESTROY_KEY,
DISABLE_KEY,
ENABLE_KEY,
LIST_KEYSET,
ROTATE_KEYSET,
PROMOTE_KEY
}
/** Creates a {@code KeysetReader} that can read the keyset in the right {@code inFormat}. */
public static KeysetReader createKeysetReader(InputStream inputStream, String inFormat)
throws IOException {
if (inFormat == null || inFormat.toLowerCase().equals("json")) {
return JsonKeysetReader.withInputStream(inputStream);
}
return BinaryKeysetReader.withInputStream(inputStream);
}
/** Creates a {@code KeysetWriter} that can write the keyset in the right {@code outFormat}. */
public static KeysetWriter createKeysetWriter(OutputStream outputStream, String outFormat)
throws IOException {
if (outFormat == null || outFormat.toLowerCase().equals("json")) {
return JsonKeysetWriter.withOutputStream(outputStream);
}
return BinaryKeysetWriter.withOutputStream(outputStream);
}
/**
* Creates a keyset that contains a single key of template {@code keyTemplate}, encrypts it using
* {@code credentialPath} and {@code masterKeyUri}, then encodes it in {@code outFormat}.
*
* @return an input stream containing the resulting encrypted keyset.
* @throws GeneralSecurityException if failed to encrypt keyset.
*/
public static final InputStream createKeyset(
KeyTemplate keyTemplate, String outFormat, String masterKeyUri, String credentialPath)
throws GeneralSecurityException, IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
createKeyset(outputStream, outFormat, masterKeyUri, credentialPath, keyTemplate);
return new ByteArrayInputStream(outputStream.toByteArray());
}
/**
* Creates a keyset that contains a single key of template {@code keyTemplate}, encrypts it using
* {@code credentialPath} and {@code masterKeyUri}, then writes it to {@code writer}.
*/
public static void createKeyset(
OutputStream outputStream,
String outFormat,
String masterKeyUri,
String credentialPath,
KeyTemplate keyTemplate)
throws GeneralSecurityException, IOException {
@SuppressWarnings("deprecation") // Need to maintain backward-compatibility
KeysetHandle handle =
KeysetManager.withEmptyKeyset().rotate(toProto(keyTemplate)).getKeysetHandle();
writeKeyset(handle, outputStream, outFormat, masterKeyUri, credentialPath);
}
/** Manipulates a key within a keyset. */
public static void manipulateKey(
CommandType type,
OutputStream outputStream,
String outFormat,
InputStream inputStream,
String inFormat,
String masterKeyUri,
String credentialPath,
int keyId)
throws GeneralSecurityException, IOException {
KeysetManager manager =
KeysetManager.withKeysetHandle(
getKeysetHandle(inputStream, inFormat, masterKeyUri, credentialPath));
switch (type) {
case DELETE_KEY:
manager = manager.delete(keyId);
break;
case DESTROY_KEY:
manager = manager.destroy(keyId);
break;
case DISABLE_KEY:
manager = manager.disable(keyId);
break;
case ENABLE_KEY:
manager = manager.enable(keyId);
break;
case PROMOTE_KEY:
manager = manager.setPrimary(keyId);
break;
default:
throw new GeneralSecurityException("invalid command");
}
writeKeyset(manager.getKeysetHandle(), outputStream, outFormat, masterKeyUri, credentialPath);
}
/**
* Creates and adds a new key to an existing keyset. The new key becomes the primary key if {@code
* type} is {@link CommandType#ROTATE}.
*/
public static void createKey(
CommandType type,
OutputStream outputStream,
String outFormat,
InputStream inputStream,
String inFormat,
String masterKeyUri,
String credentialPath,
KeyTemplate keyTemplate)
throws GeneralSecurityException, IOException {
KeysetManager manager =
KeysetManager.withKeysetHandle(
getKeysetHandle(inputStream, inFormat, masterKeyUri, credentialPath));
switch (type) {
case ADD_KEY:
manager.add(keyTemplate);
break;
case ROTATE_KEYSET:
manager.rotate(toProto(keyTemplate));
break;
default:
throw new GeneralSecurityException("invalid command");
}
writeKeyset(manager.getKeysetHandle(), outputStream, outFormat, masterKeyUri, credentialPath);
}
// TODO(b/153937575): remove this once KeysetManager allows to directly work with KeyTemplate
// POJO.
private static com.google.crypto.tink.proto.KeyTemplate toProto(KeyTemplate template) {
OutputPrefixType prefixType;
switch (template.getOutputPrefixType()) {
case TINK:
prefixType = OutputPrefixType.TINK;
break;
case LEGACY:
prefixType = OutputPrefixType.LEGACY;
break;
case RAW:
prefixType = OutputPrefixType.RAW;
break;
case CRUNCHY:
prefixType = OutputPrefixType.CRUNCHY;
break;
default:
throw new IllegalArgumentException("Unknown output prefix type");
}
return com.google.crypto.tink.proto.KeyTemplate.newBuilder()
.setTypeUrl(template.getTypeUrl())
.setValue(ByteString.copyFrom(template.getValue()))
.setOutputPrefixType(prefixType)
.build();
}
/**
* Writes the keyset managed by {@code handle} to {@code outputStream} with format {@code
* outFormat}. Maybe encrypt it with {@code masterKeyUri} and {@code credentialPath}.
*/
public static void writeKeyset(
KeysetHandle handle,
OutputStream outputStream,
String outFormat,
String masterKeyUri,
String credentialPath)
throws GeneralSecurityException, IOException {
KeysetWriter writer = createKeysetWriter(outputStream, outFormat);
if (masterKeyUri != null) {
Aead masterKey =
KmsClients.getAutoLoaded(masterKeyUri)
.withCredentials(credentialPath)
.getAead(masterKeyUri);
handle.write(writer, masterKey);
} else {
CleartextKeysetHandle.write(handle, writer);
}
}
/** Manipulates a keyset */
public static void manipulateEncryptedKeyset(
CommandType type,
OutputStream outputStream,
String outFormat,
InputStream inputStream,
String inFormat,
String masterKeyUri,
String credentialPath,
String keyId)
throws GeneralSecurityException, IOException {}
/**
* Returns a {@code KeysetHandle} from either a cleartext {@code Keyset} or a {@code
* EncryptedKeyset}, read from {@code inputStream}.
*/
public static KeysetHandle getKeysetHandle(
InputStream inputStream, String inFormat, String masterKeyUri, String credentialPath)
throws IOException, GeneralSecurityException {
KeysetReader reader = createKeysetReader(inputStream, inFormat);
if (masterKeyUri != null) {
Aead masterKey =
KmsClients.getAutoLoaded(masterKeyUri)
.withCredentials(credentialPath)
.getAead(masterKeyUri);
return KeysetHandle.read(reader, masterKey);
}
return CleartextKeysetHandle.read(reader);
}
/**
* Checks that input or output format is valid. Only supported formats are {@code json} and {@code
* binary} (case-insensitive).
*
* @throws IllegalArgumentException iff format is invalid
*/
public static void validateFormat(String format) throws IllegalArgumentException {
if (format != null
&& !format.toLowerCase().equals("json")
&& !format.toLowerCase().equals("binary")) {
throw new IllegalArgumentException("invalid format: " + format);
}
}
/** Prints an error then exits. */
public static void die(String error) {
System.err.print(String.format("Error: %s\n", error));
System.exit(1);
}
private TinkeyUtil() {}
}