22
33import com .connectrpc .ResponseMessage ;
44import com .connectrpc .UnaryBlockingCall ;
5+ import com .google .protobuf .Struct ;
6+ import com .google .protobuf .Value ;
57import io .opentdf .platform .policy .KeyAccessServer ;
68import io .opentdf .platform .policy .kasregistry .KeyAccessServerRegistryServiceClient ;
79import io .opentdf .platform .policy .kasregistry .ListKeyAccessServersRequest ;
1113
1214import java .nio .charset .StandardCharsets ;
1315
16+ import io .opentdf .platform .wellknownconfiguration .GetWellKnownConfigurationResponse ;
17+ import io .opentdf .platform .wellknownconfiguration .WellKnownServiceClientInterface ;
1418import org .apache .commons .io .output .ByteArrayOutputStream ;
1519import org .junit .jupiter .api .BeforeAll ;
1620import org .junit .jupiter .api .Test ;
2327import java .util .Base64 ;
2428import java .util .Collections ;
2529import java .util .List ;
30+ import java .util .Objects ;
2631import java .util .Random ;
2732
2833import static org .assertj .core .api .AssertionsForClassTypes .assertThat ;
@@ -45,14 +50,27 @@ public class NanoTDFTest {
4550 "oVP7Vpcx\n " +
4651 "-----END PRIVATE KEY-----" ;
4752
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+
4864 private static final String KID = "r1" ;
65+ private static final String BASE_KID = "basekid" ;
4966
5067 protected static KeyAccessServerRegistryServiceClient kasRegistryService ;
5168 protected static List <String > registeredKases = List .of (
5269 "https://api.example.com/kas" ,
5370 "https://other.org/kas2" ,
5471 "http://localhost:8181/kas" ,
55- "https://localhost:8383/kas"
72+ "https://localhost:8383/kas" ,
73+ "https://api.kaswithbasekey.example.com"
5674 );
5775 protected static String platformUrl = "http://localhost:8080" ;
5876
@@ -70,10 +88,16 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) {
7088
7189 @ Override
7290 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+ }
7398 if (kasInfo .Algorithm != null && !"ec:secp256r1" .equals (kasInfo .Algorithm )) {
7499 throw new IllegalArgumentException ("Unexpected algorithm: " + kasInfo );
75100 }
76- var k2 = kasInfo .clone ();
77101 k2 .KID = KID ;
78102 k2 .PublicKey = kasPublicKey ;
79103 k2 .Algorithm = "ec:secp256r1" ;
@@ -82,19 +106,14 @@ public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve)
82106
83107 @ Override
84108 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" );
93110 }
94111
95112 @ Override
96113 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 ;
98117 byte [] headerAsBytes = Base64 .getDecoder ().decode (header );
99118 Header nTDFHeader = new Header (ByteBuffer .wrap (headerAsBytes ));
100119 byte [] ephemeralKey = nTDFHeader .getEphemeralKey ();
@@ -103,7 +122,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas
103122
104123 // Generate symmetric key
105124 byte [] symmetricKey = ECKeyPair .computeECDHKey (ECKeyPair .publicKeyFromPem (publicKeyAsPem ),
106- ECKeyPair .privateKeyFromPem (kasPrivateKey ));
125+ ECKeyPair .privateKeyFromPem (key ));
107126
108127 // Generate HKDF key
109128 MessageDigest digest ;
@@ -113,8 +132,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas
113132 throw new SDKException ("error creating SHA-256 message digest" , e );
114133 }
115134 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 );
118136 }
119137
120138 @ Override
@@ -203,6 +221,35 @@ void encryptionAndDecryptionWithValidKey() throws Exception {
203221 }
204222 }
205223
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+
206253 @ Test
207254 void testWithDifferentConfigAndKeyValues () throws Exception {
208255 var kasInfos = new ArrayList <>();
0 commit comments