Skip to content

Commit 36fd6cf

Browse files
mkleeneclaude
andauthored
feat: verify that BC works with approved_only mode set (#375)
The bouncycastle FIPS provider happily does things that are not FIPS-compliant when the `org.bouncycastle.fips.approved_only` system property is not set to `true`. The following problems were observed: 1. using OAEP to `ENCRYPT` rather than `WRAP`. We are indeed using OEAP for key wrapping so we just had to change some cipher parameters. `WRAP` and `ENCRYPT` perform the same operation; the only difference is in the semantics and the JCA API associated with each operation. This should not affect compatibility with other parts of the platform in any way. 1. Without `approved_only` BC would be fine using a non-FIPS truststore. Now we add an empty truststore (with an empty password) of a type that can be loaded by the BC FIPS truststore. 1. Our home grown implementation of HKDF didn't work because our usage of `SHA-265/HMAC` within the HKDF implementation triggers the error: `ECKeyPairTest.createSymmetricKeysWithOtherCurves:129 » IllegalKey Key size for HMAC must be at least 112 bits in approved mode: SHA-256/HMAC`. By using a FIPS compliant provider that implements HKDF directly we bypass this error. We include the FIPS implementation of HKDF in a separate JAR due to its dependency on the bouncycastle FIPS provider. If we included it in the same JAR we would have a dependency on `bc-fips`, which would conflict with usage of the regular bouncycastle provider. Since bouncycastle is popular it seems best to allow non-FIPS usage. The major change from a customer POV is that customers that want to use EC crypto in a FIPS context will now need to pull in the `sdk-fips-bouncycastle` jar and depend on `bc-fips` since HKDF [only got a JCA name in JDK 24](https://openjdk.org/jeps/478) and [bc-fips does not expose HKDF through a provider interface](https://downloads.bouncycastle.org/fips-java/docs/BC-FJA-UserGuide-2.1.1.pdf). If using `bc-fips` is not appropriate in a particular application it would be quite easy to implement an alternative JAR that implements a similar provider pattern and uses whichever FIPS-compliant library one wishes to implement HKDF. Also: * pull the test scripts in `checks.yaml` in to bash files so they can be run locally more easily * add a FIPS mode `cmdline` platform integration phase * publish the new FIPS provider JAR to maven central * remove double addition of generated sources that was generating a warning * overwrite the `java.security` file in its entirety so that we know exactly which providers we are using in FIPS tests --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 63b49af commit 36fd6cf

23 files changed

Lines changed: 542 additions & 156 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
echo "basic assertions"
5+
printf 'here is some data to encrypt' > data
6+
7+
ASSERTIONS='[{"id":"assertion1","type":"handling","scope":"tdo","appliesToState":"encrypted","statement":{"format":"json+stanag5636","schema":"urn:nato:stanag:5636:A:1:elements:json","value":"{\"ocl\":\"2024-10-21T20:47:36Z\"}"}}]'
8+
9+
java -jar target/cmdline.jar \
10+
--client-id=opentdf-sdk \
11+
--client-secret=secret \
12+
--platform-endpoint=http://localhost:8080 \
13+
-h \
14+
encrypt \
15+
--kas-url=http://localhost:8080 \
16+
--mime-type=text/plain \
17+
--with-assertions="$ASSERTIONS" \
18+
--autoconfigure=false \
19+
-f data \
20+
-m 'here is some metadata' > test.tdf
21+
22+
java -jar target/cmdline.jar \
23+
--client-id=opentdf-sdk \
24+
--client-secret=secret \
25+
--platform-endpoint=http://localhost:8080 \
26+
-h \
27+
decrypt \
28+
-f test.tdf > decrypted
29+
30+
if ! diff -q data decrypted; then
31+
printf 'decrypted data is incorrect [%s]\n' "$(< decrypted)"
32+
exit 1
33+
fi
34+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
echo "hs256 assertions"
5+
printf 'here is some data to encrypt' > data
6+
7+
HS256_KEY=$(openssl rand -base64 32)
8+
SIGNED_ASSERTIONS_HS256='[{"id":"assertion1","type":"handling","scope":"tdo","appliesToState":"encrypted","statement":{"format":"json+stanag5636","schema":"urn:nato:stanag:5636:A:1:elements:json","value":"{\"ocl\":\"2024-10-21T20:47:36Z\"}"},"signingKey":{"alg":"HS256","key":"'"$HS256_KEY"'"}}]'
9+
SIGNED_ASSERTION_VERIFICATON_HS256='{"keys":{"assertion1":{"alg":"HS256","key":"'"$HS256_KEY"'"}}}'
10+
11+
java -jar target/cmdline.jar \
12+
--client-id=opentdf-sdk \
13+
--client-secret=secret \
14+
--platform-endpoint=http://localhost:8080 \
15+
-h \
16+
encrypt \
17+
--kas-url=http://localhost:8080 \
18+
--mime-type=text/plain \
19+
--with-assertions="$SIGNED_ASSERTIONS_HS256" \
20+
--autoconfigure=false \
21+
-f data \
22+
-m 'here is some metadata' > test.tdf
23+
24+
java -jar target/cmdline.jar \
25+
--client-id=opentdf-sdk \
26+
--client-secret=secret \
27+
--platform-endpoint=http://localhost:8080 \
28+
-h \
29+
decrypt \
30+
--with-assertion-verification-keys="$SIGNED_ASSERTION_VERIFICATON_HS256" \
31+
-f test.tdf > decrypted
32+
33+
if ! diff -q data decrypted; then
34+
printf 'decrypted data is incorrect [%s]\n' "$(< decrypted)"
35+
exit 1
36+
fi
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
echo "rs256 assertions"
5+
printf 'here is some data to encrypt' > data
6+
7+
openssl genpkey -algorithm RSA -out rs_private_key.pem -pkeyopt rsa_keygen_bits:2048
8+
openssl rsa -pubout -in rs_private_key.pem -out rs_public_key.pem
9+
10+
RS256_PRIVATE_KEY=$(awk '{printf "%s\\n", $0}' rs_private_key.pem)
11+
RS256_PUBLIC_KEY=$(awk '{printf "%s\\n", $0}' rs_public_key.pem)
12+
SIGNED_ASSERTIONS_RS256='[{"id":"assertion1","type":"handling","scope":"tdo","appliesToState":"encrypted","statement":{"format":"json+stanag5636","schema":"urn:nato:stanag:5636:A:1:elements:json","value":"{\"ocl\":\"2024-10-21T20:47:36Z\"}"},"signingKey":{"alg":"RS256","key":"'"$RS256_PRIVATE_KEY"'"}}]'
13+
SIGNED_ASSERTION_VERIFICATON_RS256='{"keys":{"assertion1":{"alg":"RS256","key":"'"$RS256_PUBLIC_KEY"'"}}}'
14+
15+
java -jar target/cmdline.jar \
16+
--client-id=opentdf-sdk \
17+
--client-secret=secret \
18+
--platform-endpoint=http://localhost:8080 \
19+
-h \
20+
encrypt \
21+
--kas-url=http://localhost:8080 \
22+
--mime-type=text/plain \
23+
--with-assertions "$SIGNED_ASSERTIONS_RS256" \
24+
--autoconfigure=false \
25+
-f data \
26+
-m 'here is some metadata' > test.tdf
27+
28+
java -jar target/cmdline.jar \
29+
--client-id=opentdf-sdk \
30+
--client-secret=secret \
31+
--platform-endpoint=http://localhost:8080 \
32+
-h \
33+
decrypt \
34+
--with-assertion-verification-keys "$SIGNED_ASSERTION_VERIFICATON_RS256" \
35+
-f test.tdf > decrypted
36+
37+
if ! diff -q data decrypted; then
38+
printf 'decrypted data is incorrect [%s]\n' "$(< decrypted)"
39+
exit 1
40+
fi
41+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
printf 'here is some data to encrypt' > data
5+
6+
java -jar target/cmdline.jar \
7+
--client-id=opentdf-sdk \
8+
--client-secret=secret \
9+
--platform-endpoint=http://localhost:8080 \
10+
-h \
11+
encrypt \
12+
--kas-url=http://localhost:8080 \
13+
--mime-type=text/plain \
14+
--attr https://example.com/attr/attr1/value/value1 \
15+
--autoconfigure=false \
16+
-f data \
17+
-m 'here is some metadata' > test.tdf
18+
19+
java -jar target/cmdline.jar \
20+
--client-id=opentdf-sdk \
21+
--client-secret=secret \
22+
--platform-endpoint=http://localhost:8080 \
23+
-h \
24+
decrypt \
25+
-f test.tdf > decrypted
26+
27+
java -jar target/cmdline.jar \
28+
--client-id=opentdf-sdk \
29+
--client-secret=secret \
30+
--platform-endpoint=http://localhost:8080 \
31+
-h \
32+
metadata \
33+
-f test.tdf > metadata
34+
35+
if ! diff -q data decrypted; then
36+
printf 'decrypted data is incorrect [%s]\n' "$(< decrypted)"
37+
exit 1
38+
fi
39+
40+
if [ "$(< metadata)" != 'here is some metadata' ]; then
41+
printf 'metadata is incorrect [%s]\n' "$(< metadata)"
42+
exit 1
43+
fi
44+

.github/workflows/checks.yaml

Lines changed: 28 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ jobs:
9292
BUF_INPUT_HTTPS_PASSWORD: ${{ secrets.PERSONAL_ACCESS_TOKEN_OPENTDF }}
9393
run: mvn clean --batch-mode clean generate-sources
9494
- name: Tests and enforcer (fips)
95-
run: mvn --batch-mode test enforcer:enforce -P 'fips,!non-fips' -Dmaven.antrun.skip
95+
run: |
96+
# install the sdk-fips-bouncycastle jar so that FIPS mode tests work
97+
mvn --batch-mode install -pl sdk-fips-bouncycastle -am \
98+
-Dmaven.antrun.skip \
99+
-Dmaven.test.skip
100+
mvn --batch-mode test enforcer:enforce -P 'fips,!non-fips' \
101+
-Dmaven.antrun.skip
96102
- name: Tests with coverage and javadoc (non-fips)
97103
env:
98104
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -104,7 +110,15 @@ jobs:
104110
-P 'coverage,non-fips,!fips'
105111
106112
platform-integration:
113+
name: "Platform Integration ${{ matrix.label }}"
107114
runs-on: ubuntu-22.04
115+
strategy:
116+
matrix:
117+
include:
118+
- label: ""
119+
maven_profile: ""
120+
- label: " (FIPS)"
121+
maven_profile: "-P 'fips,!non-fips'"
108122
steps:
109123
- name: Checkout Java SDK
110124
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -117,12 +131,6 @@ jobs:
117131
java-version: "17"
118132
distribution: "temurin"
119133
server-id: github
120-
- name: Build java SDK
121-
run: |
122-
mvn --batch-mode clean install -DskipTests
123-
env:
124-
BUF_INPUT_HTTPS_USERNAME: opentdf-bot
125-
BUF_INPUT_HTTPS_PASSWORD: ${{ secrets.PERSONAL_ACCESS_TOKEN_OPENTDF }}
126134

127135
- name: Check out and start up platform with deps/containers
128136
id: run-platform
@@ -137,121 +145,26 @@ jobs:
137145
grpcurl -plaintext localhost:8080 list && \
138146
grpcurl -plaintext localhost:8080 kas.AccessService/PublicKey
139147
140-
- name: Validate the SDK through the command line interface
148+
- name: Build java SDK${{ matrix.label }}
141149
run: |
142-
printf 'here is some data to encrypt' > data
143-
144-
java -jar target/cmdline.jar \
145-
--client-id=opentdf-sdk \
146-
--client-secret=secret \
147-
--platform-endpoint=http://localhost:8080 \
148-
-h\
149-
encrypt --kas-url=http://localhost:8080 --mime-type=text/plain --attr https://example.com/attr/attr1/value/value1 --autoconfigure=false -f data -m 'here is some metadata' > test.tdf
150-
151-
java -jar target/cmdline.jar \
152-
--client-id=opentdf-sdk \
153-
--client-secret=secret \
154-
--platform-endpoint=http://localhost:8080 \
155-
-h\
156-
decrypt -f test.tdf > decrypted
157-
158-
java -jar target/cmdline.jar \
159-
--client-id=opentdf-sdk \
160-
--client-secret=secret \
161-
--platform-endpoint=http://localhost:8080 \
162-
-h\
163-
metadata -f test.tdf > metadata
164-
165-
if ! diff -q data decrypted; then
166-
printf 'decrypted data is incorrect [%s]' "$(< decrypted)"
167-
exit 1
168-
fi
150+
mvn --batch-mode clean install -pl cmdline -am ${{ matrix.maven_profile }} -DskipTests
151+
env:
152+
BUF_INPUT_HTTPS_USERNAME: opentdf-bot
153+
BUF_INPUT_HTTPS_PASSWORD: ${{ secrets.PERSONAL_ACCESS_TOKEN_OPENTDF }}
169154

170-
if [ "$(< metadata)" != 'here is some metadata' ]; then
171-
printf 'metadata is incorrect [%s]\n' "$(< metadata)"
172-
exit 1
173-
fi
155+
- name: Validate the SDK through the command line interface${{ matrix.label }}
156+
run: |
157+
../.github/scripts/verify_cmdline_roundtrip.sh
174158
working-directory: cmdline
175159

176-
- name: Encrypt/Decrypt Assertions
160+
- name: Encrypt/Decrypt Assertions${{ matrix.label }}
177161
run: |
178-
echo "basic assertions"
179-
echo 'here is some data to encrypt' > data
180-
181-
ASSERTIONS='[{"id":"assertion1","type":"handling","scope":"tdo","appliesToState":"encrypted","statement":{"format":"json+stanag5636","schema":"urn:nato:stanag:5636:A:1:elements:json","value":"{\"ocl\":\"2024-10-21T20:47:36Z\"}"}}]'
182-
183-
java -jar target/cmdline.jar \
184-
--client-id=opentdf-sdk \
185-
--client-secret=secret \
186-
--platform-endpoint=http://localhost:8080 \
187-
-h\
188-
encrypt --kas-url=http://localhost:8080 --mime-type=text/plain --with-assertions=$ASSERTIONS --autoconfigure=false -f data -m 'here is some metadata' > test.tdf
189-
190-
java -jar target/cmdline.jar \
191-
--client-id=opentdf-sdk \
192-
--client-secret=secret \
193-
--platform-endpoint=http://localhost:8080 \
194-
-h\
195-
decrypt -f test.tdf > decrypted
196-
197-
if ! diff -q data decrypted; then
198-
printf 'decrypted data is incorrect [%s]' "$(< decrypted)"
199-
exit 1
200-
fi
201-
202-
HS256_KEY=$(openssl rand -base64 32)
203-
openssl genpkey -algorithm RSA -out rs_private_key.pem -pkeyopt rsa_keygen_bits:2048
204-
openssl rsa -pubout -in rs_private_key.pem -out rs_public_key.pem
205-
RS256_PRIVATE_KEY=$(awk '{printf "%s\\n", $0}' rs_private_key.pem)
206-
RS256_PUBLIC_KEY=$(awk '{printf "%s\\n", $0}' rs_public_key.pem)
207-
SIGNED_ASSERTIONS_HS256='[{"id":"assertion1","type":"handling","scope":"tdo","appliesToState":"encrypted","statement":{"format":"json+stanag5636","schema":"urn:nato:stanag:5636:A:1:elements:json","value":"{\"ocl\":\"2024-10-21T20:47:36Z\"}"},"signingKey":{"alg":"HS256","key":"'$HS256_KEY'"}}]'
208-
SIGNED_ASSERTION_VERIFICATON_HS256='{"keys":{"assertion1":{"alg":"HS256","key":"'$HS256_KEY'"}}}'
209-
SIGNED_ASSERTIONS_RS256='[{"id":"assertion1","type":"handling","scope":"tdo","appliesToState":"encrypted","statement":{"format":"json+stanag5636","schema":"urn:nato:stanag:5636:A:1:elements:json","value":"{\"ocl\":\"2024-10-21T20:47:36Z\"}"},"signingKey":{"alg":"RS256","key":"'$RS256_PRIVATE_KEY'"}}]'
210-
SIGNED_ASSERTION_VERIFICATON_RS256='{"keys":{"assertion1":{"alg":"RS256","key":"'$RS256_PUBLIC_KEY'"}}}'
211-
212-
echo "hs256 assertions"
213-
214-
java -jar target/cmdline.jar \
215-
--client-id=opentdf-sdk \
216-
--client-secret=secret \
217-
--platform-endpoint=http://localhost:8080 \
218-
-h\
219-
encrypt --kas-url=http://localhost:8080 --mime-type=text/plain --with-assertions="$SIGNED_ASSERTIONS_HS256" --autoconfigure=false -f data -m 'here is some metadata' > test.tdf
220-
221-
java -jar target/cmdline.jar \
222-
--client-id=opentdf-sdk \
223-
--client-secret=secret \
224-
--platform-endpoint=http://localhost:8080 \
225-
-h\
226-
decrypt --with-assertion-verification-keys="$SIGNED_ASSERTION_VERIFICATON_HS256" -f test.tdf > decrypted
227-
228-
if ! diff -q data decrypted; then
229-
printf 'decrypted data is incorrect [%s]' "$(< decrypted)"
230-
exit 1
231-
fi
232-
233-
echo "rs256 assertions"
234-
235-
java -jar target/cmdline.jar \
236-
--client-id=opentdf-sdk \
237-
--client-secret=secret \
238-
--platform-endpoint=http://localhost:8080 \
239-
-h\
240-
encrypt --kas-url=http://localhost:8080 --mime-type=text/plain --with-assertions "$SIGNED_ASSERTIONS_RS256" --autoconfigure=false -f data -m 'here is some metadata' > test.tdf
241-
242-
java -jar target/cmdline.jar \
243-
--client-id=opentdf-sdk \
244-
--client-secret=secret \
245-
--platform-endpoint=http://localhost:8080 \
246-
-h\
247-
decrypt --with-assertion-verification-keys "$SIGNED_ASSERTION_VERIFICATON_RS256" -f test.tdf > decrypted
248-
249-
if ! diff -q data decrypted; then
250-
printf 'decrypted data is incorrect [%s]' "$(< decrypted)"
251-
exit 1
252-
fi
162+
../.github/scripts/verify_assertions_basic.sh
163+
../.github/scripts/verify_assertions_hs256.sh
164+
../.github/scripts/verify_assertions_rs256.sh
253165
working-directory: cmdline
254166

167+
255168
platform-xtest:
256169
permissions:
257170
contents: read

cmdline/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,19 @@
7878
<version>${project.version}</version>
7979
</dependency>
8080
</dependencies>
81+
<profiles>
82+
<profile>
83+
<id>fips</id>
84+
<activation>
85+
<activeByDefault>false</activeByDefault>
86+
</activation>
87+
<dependencies>
88+
<dependency>
89+
<groupId>io.opentdf.platform</groupId>
90+
<artifactId>sdk-fips-bouncycastle</artifactId>
91+
<version>${project.version}</version>
92+
</dependency>
93+
</dependencies>
94+
</profile>
95+
</profiles>
8196
</project>

cmdline/src/main/java/io/opentdf/platform/Command.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import com.google.gson.GsonBuilder;
1111
import com.google.gson.reflect.TypeToken;
1212

13-
import java.security.cert.X509Certificate;
1413
import java.text.ParseException;
1514
import com.google.gson.JsonSyntaxException;
1615
import io.opentdf.platform.sdk.AssertionConfig;
@@ -23,7 +22,6 @@
2322
import picocli.CommandLine.HelpCommand;
2423
import picocli.CommandLine.Option;
2524

26-
import javax.net.ssl.X509TrustManager;
2725
import java.io.BufferedInputStream;
2826
import java.io.BufferedOutputStream;
2927
import java.io.File;
@@ -63,7 +61,6 @@ class Versions {
6361
@CommandLine.Command(name = "tdf", subcommands = { HelpCommand.class }, version = "{\"version\":\"" + Versions.SDK
6462
+ "\",\"tdfSpecVersion\":\"" + Versions.TDF_SPEC + "\"}")
6563
class Command {
66-
6764
@Option(names = { "-V", "--version" }, versionHelp = true, description = "display version info")
6865
boolean versionInfoRequested;
6966

cmdline/src/main/resources/log4j2.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
</Console>
77
</appenders>
88
<loggers>
9-
<root level="trace">
10-
<appender-ref ref="Console"/>
11-
</root>
9+
<Root level="trace">
10+
<AppenderRef ref="Console"/>
11+
</Root>
1212
<Logger name="io.grpc.netty" level="error" additivity="false">
1313
<AppenderRef ref="Console" />
1414
</Logger>

0 commit comments

Comments
 (0)