MultiFernetKeyRotator.java

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 &copy; 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
Location : <init>
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyTestRejectsTooFewBytes(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
negated conditional → KILLED

2.2
Location : <init>
Killed by : none
negated conditional → NO_COVERAGE

55

1.1
Location : <init>
Killed by : none
removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::setMaxActiveKeys → NO_COVERAGE

76

1.1
Location : createSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateSecretAddsKeyAndRemovesOldest(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
Replaced integer modulus with multiplication → KILLED

2.2
Location : createSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateSecretAddsKeyAndRemovesOldest(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
negated conditional → KILLED

79

1.1
Location : createSecret
Killed by : none
Replaced integer division with multiplication → SURVIVED

80

1.1
Location : createSecret
Killed by : none
Replaced integer addition with subtraction → SURVIVED

81

1.1
Location : createSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateSecretAddsKeyAndRemovesOldest(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
negated conditional → KILLED

88

1.1
Location : createSecret
Killed by : none
removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → SURVIVED

89

1.1
Location : createSecret
Killed by : none
removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → SURVIVED

92

1.1
Location : createSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateSecretAddsKeyAndRemovesOldest(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
removed call to java/util/List::add → KILLED

93

1.1
Location : createSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateSecretAddsKeyAndRemovesOldest(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
Replaced integer addition with subtraction → KILLED

94

1.1
Location : createSecret
Killed by : none
changed conditional boundary → SURVIVED

2.2
Location : createSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateSecretAddsKeyAndRemovesOldest(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
negated conditional → KILLED

98

1.1
Location : createSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateSecretAddsKeyAndRemovesOldest(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
removed call to com/macasaet/fernet/aws/secretsmanager/rotation/SecretsManager::putSecretValue → KILLED

100

1.1
Location : createSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateClearsIntermediateSecret(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → KILLED

110

1.1
Location : testSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyTestClearsIntermediateSecret(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
Replaced integer modulus with multiplication → KILLED

2.2
Location : testSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyTestRejectsTooFewBytes(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
negated conditional → KILLED

119

1.1
Location : testSecret
Killed by : none
removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → SURVIVED

120

1.1
Location : testSecret
Killed by : none
removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → SURVIVED

122

1.1
Location : testSecret
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyTestClearsIntermediateSecret(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
removed call to com/macasaet/fernet/aws/secretsmanager/rotation/MultiFernetKeyRotator::wipe → KILLED

130

1.1
Location : getMaxActiveKeys
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyCreateSecretAddsKeyAndRemovesOldest(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

139

1.1
Location : setMaxActiveKeys
Killed by : none
changed conditional boundary → SURVIVED

2.2
Location : setMaxActiveKeys
Killed by : com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest.verifyTestRejectsTooFewBytes(com.macasaet.fernet.aws.secretsmanager.rotation.MultiFernetKeyRotatorTest)
negated conditional → KILLED

Active mutators

Tests examined


Report generated by PIT 1.4.10