2
2
3
3
import com .connectrpc .ResponseMessage ;
4
4
import com .connectrpc .UnaryBlockingCall ;
5
+ import com .google .protobuf .Struct ;
6
+ import com .google .protobuf .Value ;
5
7
import io .opentdf .platform .policy .KeyAccessServer ;
6
8
import io .opentdf .platform .policy .kasregistry .KeyAccessServerRegistryServiceClient ;
7
9
import io .opentdf .platform .policy .kasregistry .ListKeyAccessServersRequest ;
11
13
12
14
import java .nio .charset .StandardCharsets ;
13
15
16
+ import io .opentdf .platform .wellknownconfiguration .GetWellKnownConfigurationResponse ;
17
+ import io .opentdf .platform .wellknownconfiguration .WellKnownServiceClientInterface ;
14
18
import org .apache .commons .io .output .ByteArrayOutputStream ;
15
19
import org .junit .jupiter .api .BeforeAll ;
16
20
import org .junit .jupiter .api .Test ;
23
27
import java .util .Base64 ;
24
28
import java .util .Collections ;
25
29
import java .util .List ;
30
+ import java .util .Objects ;
26
31
import java .util .Random ;
27
32
28
33
import static org .assertj .core .api .AssertionsForClassTypes .assertThat ;
@@ -45,14 +50,27 @@ public class NanoTDFTest {
45
50
"oVP7Vpcx\n " +
46
51
"-----END PRIVATE KEY-----" ;
47
52
53
+ private static final String BASE_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n " +
54
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/NawR/F7RJfX/odyOLPjl+5Ce1Br\n " +
55
+ "QZ/MBCIerHe26HzlBSbpa7HQHZx9PYVamHTw9+iJCY3dm8Uwp4Ab2uehnA==\n " +
56
+ "-----END PUBLIC KEY-----" ;
57
+
58
+ private static final String BASE_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n " +
59
+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgB3YtAvS7lctHlPsq\n " +
60
+ "bZI8OX1B9W1c4GAIxzwKzD6iPkqhRANCAAT81rBH8XtEl9f+h3I4s+OX7kJ7UGtB\n " +
61
+ "n8wEIh6sd7bofOUFJulrsdAdnH09hVqYdPD36IkJjd2bxTCngBva56Gc\n " +
62
+ "-----END PRIVATE KEY-----" ;
63
+
48
64
private static final String KID = "r1" ;
65
+ private static final String BASE_KID = "basekid" ;
49
66
50
67
protected static KeyAccessServerRegistryServiceClient kasRegistryService ;
51
68
protected static List <String > registeredKases = List .of (
52
69
"https://api.example.com/kas" ,
53
70
"https://other.org/kas2" ,
54
71
"http://localhost:8181/kas" ,
55
- "https://localhost:8383/kas"
72
+ "https://localhost:8383/kas" ,
73
+ "https://api.kaswithbasekey.example.com"
56
74
);
57
75
protected static String platformUrl = "http://localhost:8080" ;
58
76
@@ -70,10 +88,16 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) {
70
88
71
89
@ Override
72
90
public KASInfo getECPublicKey (Config .KASInfo kasInfo , NanoTDFType .ECCurve curve ) {
91
+ var k2 = kasInfo .clone ();
92
+ if (Objects .equals (kasInfo .KID , BASE_KID )) {
93
+ assertThat (kasInfo .URL ).isEqualTo ("https://api.kaswithbasekey.example.com" );
94
+ assertThat (kasInfo .Algorithm ).isEqualTo ("ec:secp384r1" );
95
+ k2 .PublicKey = BASE_PUBLIC_KEY ;
96
+ return k2 ;
97
+ }
73
98
if (kasInfo .Algorithm != null && !"ec:secp256r1" .equals (kasInfo .Algorithm )) {
74
99
throw new IllegalArgumentException ("Unexpected algorithm: " + kasInfo );
75
100
}
76
- var k2 = kasInfo .clone ();
77
101
k2 .KID = KID ;
78
102
k2 .PublicKey = kasPublicKey ;
79
103
k2 .Algorithm = "ec:secp256r1" ;
@@ -82,19 +106,14 @@ public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve)
82
106
83
107
@ Override
84
108
public byte [] unwrap (Manifest .KeyAccess keyAccess , String policy , KeyType sessionKeyType ) {
85
- int index = Integer .parseInt (keyAccess .url );
86
- var decryptor = new AsymDecryption (keypairs .get (index ).getPrivate ());
87
- var bytes = Base64 .getDecoder ().decode (keyAccess .wrappedKey );
88
- try {
89
- return decryptor .decrypt (bytes );
90
- } catch (Exception e ) {
91
- throw new RuntimeException (e );
92
- }
109
+ throw new UnsupportedOperationException ("no unwrapping ZTDFs here" );
93
110
}
94
111
95
112
@ Override
96
113
public byte [] unwrapNanoTDF (NanoTDFType .ECCurve curve , String header , String kasURL ) {
97
-
114
+ String key = Objects .equals (kasURL , "https://api.kaswithbasekey.example.com" )
115
+ ? BASE_PRIVATE_KEY
116
+ : kasPrivateKey ;
98
117
byte [] headerAsBytes = Base64 .getDecoder ().decode (header );
99
118
Header nTDFHeader = new Header (ByteBuffer .wrap (headerAsBytes ));
100
119
byte [] ephemeralKey = nTDFHeader .getEphemeralKey ();
@@ -103,7 +122,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas
103
122
104
123
// Generate symmetric key
105
124
byte [] symmetricKey = ECKeyPair .computeECDHKey (ECKeyPair .publicKeyFromPem (publicKeyAsPem ),
106
- ECKeyPair .privateKeyFromPem (kasPrivateKey ));
125
+ ECKeyPair .privateKeyFromPem (key ));
107
126
108
127
// Generate HKDF key
109
128
MessageDigest digest ;
@@ -113,8 +132,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas
113
132
throw new SDKException ("error creating SHA-256 message digest" , e );
114
133
}
115
134
byte [] hashOfSalt = digest .digest (NanoTDF .MAGIC_NUMBER_AND_VERSION );
116
- byte [] key = ECKeyPair .calculateHKDF (hashOfSalt , symmetricKey );
117
- return key ;
135
+ return ECKeyPair .calculateHKDF (hashOfSalt , symmetricKey );
118
136
}
119
137
120
138
@ Override
@@ -203,6 +221,35 @@ void encryptionAndDecryptionWithValidKey() throws Exception {
203
221
}
204
222
}
205
223
224
+ @ Test
225
+ void encryptionAndDecryptWithBaseKey () throws Exception {
226
+ var baseKeyJson = "{\" kas_url\" :\" https://api.kaswithbasekey.example.com\" ,\" public_key\" :{\" algorithm\" :\" ALGORITHM_EC_P256\" ,\" kid\" :\" " + BASE_KID + "\" ,\" pem\" : \" " + BASE_PUBLIC_KEY + "\" }}" ;
227
+ var val = Value .newBuilder ().setStringValue (baseKeyJson ).build ();
228
+ var config = Struct .newBuilder ().putFields ("base_key" , val ).build ();
229
+ WellKnownServiceClientInterface wellknown = mock (WellKnownServiceClientInterface .class );
230
+ GetWellKnownConfigurationResponse response = GetWellKnownConfigurationResponse .newBuilder ().setConfiguration (config ).build ();
231
+ when (wellknown .getWellKnownConfigurationBlocking (any (), any ())).thenReturn (TestUtil .successfulUnaryCall (response ));
232
+ Config .NanoTDFConfig nanoConfig = Config .newNanoTDFConfig (
233
+ Config .witDataAttributes ("https://example.com/attr/Classification/value/S" ,
234
+ "https://example.com/attr/Classification/value/X" )
235
+ );
236
+
237
+ String plainText = "Virtru!!" ;
238
+ ByteBuffer byteBuffer = ByteBuffer .wrap (plainText .getBytes ());
239
+ ByteArrayOutputStream tdfOutputStream = new ByteArrayOutputStream ();
240
+ NanoTDF nanoTDF = new NanoTDF (new FakeServicesBuilder ().setKas (kas ).setKeyAccessServerRegistryService (kasRegistryService ).setWellknownService (wellknown ).build ());
241
+ nanoTDF .createNanoTDF (byteBuffer , tdfOutputStream , nanoConfig );
242
+
243
+ byte [] nanoTDFBytes = tdfOutputStream .toByteArray ();
244
+ ByteArrayOutputStream plainTextStream = new ByteArrayOutputStream ();
245
+ nanoTDF = new NanoTDF (new FakeServicesBuilder ().setKas (kas ).setKeyAccessServerRegistryService (kasRegistryService ).build ());
246
+ nanoTDF .readNanoTDF (ByteBuffer .wrap (nanoTDFBytes ), plainTextStream , platformUrl );
247
+ String out = new String (plainTextStream .toByteArray (), StandardCharsets .UTF_8 );
248
+ assertThat (out ).isEqualTo (plainText );
249
+ // KAS KID
250
+ assertThat (new String (nanoTDFBytes , StandardCharsets .UTF_8 )).contains (BASE_KID );
251
+ }
252
+
206
253
@ Test
207
254
void testWithDifferentConfigAndKeyValues () throws Exception {
208
255
var kasInfos = new ArrayList <>();
0 commit comments