blob: 45703f08dd095db5291dee9309857ddd0d7432aa [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;
import com.google.crypto.tink.proto.KeyData;
import com.google.crypto.tink.proto.KeyStatusType;
import com.google.crypto.tink.proto.KeyTemplate;
import com.google.crypto.tink.proto.Keyset;
import com.google.crypto.tink.proto.OutputPrefixType;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.annotation.concurrent.GuardedBy;
/**
* Manages a {@link Keyset} proto, with convenience methods that rotate, disable, enable or destroy
* keys.
*
* @since 1.0.0
*/
public final class KeysetManager {
@GuardedBy("this")
private final Keyset.Builder keysetBuilder;
private KeysetManager(Keyset.Builder val) {
keysetBuilder = val;
}
/** @return a {@link KeysetManager} for the keyset manged by {@code val} */
public static KeysetManager withKeysetHandle(KeysetHandle val) {
return new KeysetManager(val.getKeyset().toBuilder());
}
/** @return a {@link KeysetManager} for an empty keyset. */
public static KeysetManager withEmptyKeyset() {
return new KeysetManager(Keyset.newBuilder());
}
/** @return a {@link KeysetHandle} of the managed keyset */
@GuardedBy("this")
public synchronized KeysetHandle getKeysetHandle() throws GeneralSecurityException {
return KeysetHandle.fromKeyset(keysetBuilder.build());
}
/**
* Generates and adds a fresh key generated using {@code keyTemplate}, and sets the new key as the
* primary key.
*
* @throws GeneralSecurityException if cannot find any {@link KeyManager} that can handle {@code
* keyTemplate}
*/
@GuardedBy("this")
public synchronized KeysetManager rotate(KeyTemplate keyTemplate)
throws GeneralSecurityException {
addNewKey(keyTemplate, true);
return this;
}
/**
* Generates and adds a fresh key generated using {@code keyTemplate}.
*
* @throws GeneralSecurityException if cannot find any {@link KeyManager} that can handle {@code
* keyTemplate}
*/
@GuardedBy("this")
public synchronized KeysetManager add(KeyTemplate keyTemplate) throws GeneralSecurityException {
addNewKey(keyTemplate, false);
return this;
}
/**
* Generates a fresh key using {@code keyTemplate} and returns the {@code keyId} of it. In case
* {@isPrimary} is true the generated key will be the new primary.
*/
@GuardedBy("this")
public synchronized int addNewKey(KeyTemplate keyTemplate, boolean asPrimary)
throws GeneralSecurityException {
Keyset.Key key = newKey(keyTemplate);
keysetBuilder.addKey(key);
if (asPrimary) {
keysetBuilder.setPrimaryKeyId(key.getKeyId());
}
return key.getKeyId();
}
/**
* Sets the key with {@code keyId} as primary.
*
* @throws GeneralSecurityException if the key is not found or not enabled
*/
@GuardedBy("this")
public synchronized KeysetManager setPrimary(int keyId) throws GeneralSecurityException {
for (int i = 0; i < keysetBuilder.getKeyCount(); i++) {
Keyset.Key key = keysetBuilder.getKey(i);
if (key.getKeyId() == keyId) {
if (!key.getStatus().equals(KeyStatusType.ENABLED)) {
throw new GeneralSecurityException(
"cannot set key as primary because it's not enabled: " + keyId);
}
keysetBuilder.setPrimaryKeyId(keyId);
return this;
}
}
throw new GeneralSecurityException("key not found: " + keyId);
}
/**
* Sets the key with {@code keyId} as primary.
*
* @throws GeneralSecurityException if the key is not found or not enabled
* @deprecated use {@link setPrimary}
*/
@GuardedBy("this")
@Deprecated
public synchronized KeysetManager promote(int keyId) throws GeneralSecurityException {
return setPrimary(keyId);
}
/**
* Enables the key with {@code keyId}.
*
* @throws GeneralSecurityException if the key is not found
*/
@GuardedBy("this")
public synchronized KeysetManager enable(int keyId) throws GeneralSecurityException {
for (int i = 0; i < keysetBuilder.getKeyCount(); i++) {
Keyset.Key key = keysetBuilder.getKey(i);
if (key.getKeyId() == keyId) {
if (key.getStatus() != KeyStatusType.ENABLED
&& key.getStatus() != KeyStatusType.DISABLED) {
throw new GeneralSecurityException(
"cannot enable key with id " + keyId + " and status " + key.getStatus());
}
keysetBuilder.setKey(i, key.toBuilder().setStatus(KeyStatusType.ENABLED).build());
return this;
}
}
throw new GeneralSecurityException("key not found: " + keyId);
}
/**
* Disables the key with {@code keyId}.
*
* @throws GeneralSecurityException if the key is not found or it is the primary key
*/
@GuardedBy("this")
public synchronized KeysetManager disable(int keyId) throws GeneralSecurityException {
if (keyId == keysetBuilder.getPrimaryKeyId()) {
throw new GeneralSecurityException("cannot disable the primary key");
}
for (int i = 0; i < keysetBuilder.getKeyCount(); i++) {
Keyset.Key key = keysetBuilder.getKey(i);
if (key.getKeyId() == keyId) {
if (key.getStatus() != KeyStatusType.ENABLED
&& key.getStatus() != KeyStatusType.DISABLED) {
throw new GeneralSecurityException(
"cannot disable key with id " + keyId + " and status " + key.getStatus());
}
keysetBuilder.setKey(i, key.toBuilder().setStatus(KeyStatusType.DISABLED).build());
return this;
}
}
throw new GeneralSecurityException("key not found: " + keyId);
}
/**
* Deletes the key with {@code keyId}.
*
* @throws GeneralSecurityException if the key is not found or it is the primary key
*/
@GuardedBy("this")
public synchronized KeysetManager delete(int keyId) throws GeneralSecurityException {
if (keyId == keysetBuilder.getPrimaryKeyId()) {
throw new GeneralSecurityException("cannot delete the primary key");
}
for (int i = 0; i < keysetBuilder.getKeyCount(); i++) {
Keyset.Key key = keysetBuilder.getKey(i);
if (key.getKeyId() == keyId) {
keysetBuilder.removeKey(i);
return this;
}
}
throw new GeneralSecurityException("key not found: " + keyId);
}
/**
* Destroys the key material associated with the {@code keyId}.
*
* @throws GeneralSecurityException if the key is not found or it is the primary key
*/
@GuardedBy("this")
public synchronized KeysetManager destroy(int keyId) throws GeneralSecurityException {
if (keyId == keysetBuilder.getPrimaryKeyId()) {
throw new GeneralSecurityException("cannot destroy the primary key");
}
for (int i = 0; i < keysetBuilder.getKeyCount(); i++) {
Keyset.Key key = keysetBuilder.getKey(i);
if (key.getKeyId() == keyId) {
if (key.getStatus() != KeyStatusType.ENABLED
&& key.getStatus() != KeyStatusType.DISABLED
&& key.getStatus() != KeyStatusType.DESTROYED) {
throw new GeneralSecurityException(
"cannot destroy key with id " + keyId + " and status " + key.getStatus());
}
keysetBuilder.setKey(
i, key.toBuilder().setStatus(KeyStatusType.DESTROYED).clearKeyData().build());
return this;
}
}
throw new GeneralSecurityException("key not found: " + keyId);
}
@GuardedBy("this")
private synchronized Keyset.Key newKey(KeyTemplate keyTemplate) throws GeneralSecurityException {
KeyData keyData = Registry.newKeyData(keyTemplate);
int keyId = newKeyId();
OutputPrefixType outputPrefixType = keyTemplate.getOutputPrefixType();
if (outputPrefixType == OutputPrefixType.UNKNOWN_PREFIX) {
outputPrefixType = OutputPrefixType.TINK;
}
return Keyset.Key.newBuilder()
.setKeyData(keyData)
.setKeyId(keyId)
.setStatus(KeyStatusType.ENABLED)
.setOutputPrefixType(outputPrefixType)
.build();
}
@GuardedBy("this")
private synchronized int newKeyId() {
int keyId = randPositiveInt();
while (true) {
for (Keyset.Key key : keysetBuilder.getKeyList()) {
if (key.getKeyId() == keyId) {
keyId = randPositiveInt();
continue;
}
}
break;
}
return keyId;
}
/** @return positive random int */
private static int randPositiveInt() {
SecureRandom secureRandom = new SecureRandom();
byte[] rand = new byte[4];
int result = 0;
while (result == 0) {
secureRandom.nextBytes(rand);
result =
((rand[0] & 0x7f) << 24)
| ((rand[1] & 0xff) << 16)
| ((rand[2] & 0xff) << 8)
| (rand[3] & 0xff);
}
return result;
}
}