| 1 | /** | |
| 2 | Copyright 2018 Carlos Macasaet | |
| 3 | ||
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | you may not use this file except in compliance with the License. | |
| 6 | You may obtain a copy of the License at | |
| 7 | ||
| 8 | http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | ||
| 10 | Unless required by applicable law or agreed to in writing, software | |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | See the License for the specific language governing permissions and | |
| 14 | limitations under the License. | |
| 15 | */ | |
| 16 | package com.macasaet.fernet.aws.secretsmanager.rotation; | |
| 17 | ||
| 18 | import static com.macasaet.fernet.aws.secretsmanager.rotation.Stage.CURRENT; | |
| 19 | import static com.macasaet.fernet.aws.secretsmanager.rotation.Stage.PENDING; | |
| 20 | ||
| 21 | import java.nio.ByteBuffer; | |
| 22 | import java.security.SecureRandom; | |
| 23 | import java.util.ArrayList; | |
| 24 | import java.util.List; | |
| 25 | ||
| 26 | import com.amazonaws.services.kms.AWSKMS; | |
| 27 | import com.amazonaws.services.kms.AWSKMSClientBuilder; | |
| 28 | import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder; | |
| 29 | import com.macasaet.fernet.Key; | |
| 30 | ||
| 31 | /** | |
| 32 | * <p>This rotator can be used when an array of Fernet keys is stored in AWSCURRENT.</p> | |
| 33 | * | |
| 34 | * <p>Grant AWS Secrets Manager permission to execute the Lambda using this:</p> | |
| 35 | * <pre>aws lambda add-permission --function-name arn:aws:lambda:{region}:{accountId}:function:{functionName} --principal secretsmanager.amazonaws.com --action lambda:InvokeFunction --statement-id SecretsManagerAccess</pre> | |
| 36 | * | |
| 37 | * <p>Copyright © 2018 Carlos Macasaet.</p> | |
| 38 | * @author Carlos Macasaet | |
| 39 | */ | |
| 40 | @SuppressWarnings("PMD.LawOfDemeter") | |
| 41 | public class MultiFernetKeyRotator extends AbstractFernetKeyRotator { | |
| 42 | ||
| 43 | private static final int fernetKeySize = 32; | |
| 44 | private int maxActiveKeys = 3; | |
| 45 | ||
| 46 | /** | |
| 47 | * @param secretsManager a utility for interacting with AWS Secrets Manager | |
| 48 | * @param kms a KMS client for seeding the random number generator | |
| 49 | * @param random an entropy source | |
| 50 | */ | |
| 51 | protected MultiFernetKeyRotator(final SecretsManager secretsManager, final AWSKMS kms, final SecureRandom random) { | |
| 52 | super(secretsManager, kms, random); | |
| 53 | final String maxActiveKeysString = System.getenv("MAX_ACTIVE_KEYS"); | |
| 54 |
2
1. <init> : negated conditional → NO_COVERAGE 2. <init> : negated conditional → KILLED |
if (maxActiveKeysString != null && !"".equals(maxActiveKeysString)) { |
| 55 |
1
1. <init> : removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::setMaxActiveKeys → NO_COVERAGE |
setMaxActiveKeys(Integer.parseInt(maxActiveKeysString)); |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | /** | |
| 60 | * @param random an entropy source | |
| 61 | */ | |
| 62 | protected MultiFernetKeyRotator(final SecureRandom random) { | |
| 63 | this(new SecretsManager(AWSSecretsManagerClientBuilder.standard() | |
| 64 | .withRequestHandlers(new MemoryOverwritingRequestHandler(random)).build()), | |
| 65 | AWSKMSClientBuilder.defaultClient(), random); | |
| 66 | } | |
| 67 | ||
| 68 | public MultiFernetKeyRotator() { | |
| 69 | this(new SecureRandom()); | |
| 70 | } | |
| 71 | ||
| 72 | @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") | |
| 73 | protected void createSecret(final String secretId, final String clientRequestToken) { | |
| 74 | final ByteBuffer currentSecret = getSecretsManager().getSecretStage(secretId, CURRENT); | |
| 75 | try { | |
| 76 |
2
1. createSecret : Replaced integer modulus with multiplication → KILLED 2. createSecret : negated conditional → KILLED |
if (currentSecret.remaining() % fernetKeySize != 0) { |
| 77 | throw new IllegalStateException("There must be a multiple of 32 bytes."); | |
| 78 | } | |
| 79 |
1
1. createSecret : Replaced integer division with multiplication → SURVIVED |
final int numKeys = currentSecret.remaining() / fernetKeySize; |
| 80 |
1
1. createSecret : Replaced integer addition with subtraction → SURVIVED |
List<Key> keys = new ArrayList<>(numKeys + 1); |
| 81 |
1
1. createSecret : negated conditional → KILLED |
while (currentSecret.hasRemaining()) { |
| 82 | final byte[] signingKey = new byte[16]; | |
| 83 | currentSecret.get(signingKey); | |
| 84 | final byte[] encryptionKey = new byte[16]; | |
| 85 | currentSecret.get(encryptionKey); | |
| 86 | final Key key = new Key(signingKey, encryptionKey); | |
| 87 | keys.add(key); | |
| 88 |
1
1. createSecret : removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → SURVIVED |
wipe(signingKey); |
| 89 |
1
1. createSecret : removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → SURVIVED |
wipe(encryptionKey); |
| 90 | } | |
| 91 | final Key keyToStage = Key.generateKey(getRandom()); | |
| 92 |
1
1. createSecret : removed call to java/util/List::add → KILLED |
keys.add(0, keyToStage); |
| 93 |
1
1. createSecret : Replaced integer addition with subtraction → KILLED |
final int desiredSize = getMaxActiveKeys() + 1; // max active keys + one pending |
| 94 |
2
1. createSecret : changed conditional boundary → SURVIVED 2. createSecret : negated conditional → KILLED |
if (keys.size() > desiredSize) { |
| 95 | keys = keys.subList(0, desiredSize); | |
| 96 | } | |
| 97 | ||
| 98 |
1
1. createSecret : removed call to com/macasaet/fernet/aws/secretsmanager/rotation/SecretsManager::putSecretValue → KILLED |
getSecretsManager().putSecretValue(secretId, clientRequestToken, keys, PENDING); |
| 99 | } finally { | |
| 100 |
1
1. createSecret : removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → KILLED |
wipe(currentSecret); |
| 101 | } | |
| 102 | getLogger().info("createSecret: Successfully put secret for ARN {} and version {}.", secretId, | |
| 103 | clientRequestToken); | |
| 104 | } | |
| 105 | ||
| 106 | protected void testSecret(final String secretId, final String clientRequestToken) { | |
| 107 | final ByteBuffer currentSecret = getSecretsManager().getSecretVersion(secretId, | |
| 108 | clientRequestToken); | |
| 109 | try { | |
| 110 |
2
1. testSecret : Replaced integer modulus with multiplication → KILLED 2. testSecret : negated conditional → KILLED |
if (currentSecret.remaining() % fernetKeySize != 0) { |
| 111 | throw new IllegalStateException("There must be a multiple of " + fernetKeySize + " bytes."); | |
| 112 | } | |
| 113 | // first key will become the staged key | |
| 114 | final byte[] signingKey = new byte[16]; | |
| 115 | currentSecret.get(signingKey); | |
| 116 | final byte[] encryptionKey = new byte[16]; | |
| 117 | currentSecret.get(encryptionKey); | |
| 118 | new Key(signingKey, encryptionKey); | |
| 119 |
1
1. testSecret : removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → SURVIVED |
wipe(signingKey); |
| 120 |
1
1. testSecret : removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → SURVIVED |
wipe(encryptionKey); |
| 121 | } finally { | |
| 122 |
1
1. testSecret : removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → KILLED |
wipe(currentSecret); |
| 123 | } | |
| 124 | } | |
| 125 | ||
| 126 | /** | |
| 127 | * @return the total number of keys that can be used for decryption. The actual number of keys stored will be this value plus one. | |
| 128 | */ | |
| 129 | protected int getMaxActiveKeys() { | |
| 130 |
1
1. getMaxActiveKeys : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return maxActiveKeys; |
| 131 | } | |
| 132 | ||
| 133 | /** | |
| 134 | * @param maxActiveKeys the total number of keys that can be used for decryption. The actual number of keys stored will be this value plus one. | |
| 135 | */ | |
| 136 | @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") | |
| 137 | protected void setMaxActiveKeys(final int maxActiveKeys) { | |
| 138 | getLogger().info("Setting the maximum number of active keys to: {}.", maxActiveKeys); | |
| 139 |
2
1. setMaxActiveKeys : changed conditional boundary → SURVIVED 2. setMaxActiveKeys : negated conditional → KILLED |
if (maxActiveKeys < 1) { |
| 140 | throw new IllegalArgumentException("The maximum number of active keys must be at least 1."); | |
| 141 | } | |
| 142 | this.maxActiveKeys = maxActiveKeys; | |
| 143 | } | |
| 144 | ||
| 145 | } | |
Mutations | ||
| 54 |
1.1 2.2 |
|
| 55 |
1.1 |
|
| 76 |
1.1 2.2 |
|
| 79 |
1.1 |
|
| 80 |
1.1 |
|
| 81 |
1.1 |
|
| 88 |
1.1 |
|
| 89 |
1.1 |
|
| 92 |
1.1 |
|
| 93 |
1.1 |
|
| 94 |
1.1 2.2 |
|
| 98 |
1.1 |
|
| 100 |
1.1 |
|
| 110 |
1.1 2.2 |
|
| 119 |
1.1 |
|
| 120 |
1.1 |
|
| 122 |
1.1 |
|
| 130 |
1.1 |
|
| 139 |
1.1 2.2 |