@@ -4,10 +4,13 @@ import (
44 "archive/zip"
55 "bytes"
66 "context"
7+ "crypto/ecdsa"
78 "crypto/rand"
89 "crypto/rsa"
910 "crypto/sha256"
11+ "crypto/x509"
1012 "encoding/json"
13+ "encoding/pem"
1114 "fmt"
1215 "io"
1316 "log/slog"
@@ -204,6 +207,36 @@ Iuxu2zA7cGQNhhUi6MKr5cUWl6tBprAghzdwEH1cZQsBiV3ki7fCCiDURIJaTlNq
204207uOnQR2c7Dix39LZQCiEfPSUnTAKJCyMpolky7Vq31PsPKk+gK19XftfH/Aul21vt
205208ZwVW7fLwZ2SSmC9cOjSkzZw/eDwwIRNgo94OL4mw5cXSPOuMeO8Tugc6LO4v91SO
206209yg==
210+ -----END CERTIFICATE-----`
211+ mockECPrivateKey1 = `-----BEGIN PRIVATE KEY-----
212+ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgokydHKV9HW88nqn9
213+ 2U2J1AqvcjrLDRCH6NBdNVqYLJOhRANCAASu1haeL6ckVfALALUlJKsehW8xomA9
214+ dcWMuYTECCukuGCklqiD0ofQAo+stVTRjen+zxM7C6MJaHdsbE4Pf088
215+ -----END PRIVATE KEY-----`
216+ mockECPublicKey1 = `-----BEGIN CERTIFICATE-----
217+ MIIBcTCCARegAwIBAgIURFydDqs4150ytI73sMRmya2fvTMwCgYIKoZIzj0EAwIw
218+ DjEMMAoGA1UEAwwDa2FzMB4XDTI0MDYxMTAxNTU0N1oXDTI1MDYxMTAxNTU0N1ow
219+ DjEMMAoGA1UEAwwDa2FzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErtYWni+n
220+ JFXwCwC1JSSrHoVvMaJgPXXFjLmExAgrpLhgpJaog9KH0AKPrLVU0Y3p/s8TOwuj
221+ CWh3bGxOD39PPKNTMFEwHQYDVR0OBBYEFLg9mMeD25ZGvmjSYaunIPoeekzlMB8G
222+ A1UdIwQYMBaAFLg9mMeD25ZGvmjSYaunIPoeekzlMA8GA1UdEwEB/wQFMAMBAf8w
223+ CgYIKoZIzj0EAwIDSAAwRQIhALYXC70t37RlmIkRDlUTehiVEHpSQXz04wQ9Ivw+
224+ 4h4hAiBNR3rD3KieiJaiJrCfM6TPJL7TIch7pAhMHdG6IPJMoQ==
225+ -----END CERTIFICATE-----`
226+ mockECPrivateKey2 = `-----BEGIN PRIVATE KEY-----
227+ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgokydHKV9HW88nqn9
228+ 2U2J1AqvcjrLDRCH6NBdNVqYLJOhRANCAASu1haeL6ckVfALALUlJKsehW8xomA9
229+ dcWMuYTECCukuGCklqiD0ofQAo+stVTRjen+zxM7C6MJaHdsbE4Pf088
230+ -----END PRIVATE KEY-----`
231+ mockECPublicKey2 = `-----BEGIN CERTIFICATE-----
232+ MIIBcTCCARegAwIBAgIURFydDqs4150ytI73sMRmya2fvTMwCgYIKoZIzj0EAwIw
233+ DjEMMAoGA1UEAwwDa2FzMB4XDTI0MDYxMTAxNTU0N1oXDTI1MDYxMTAxNTU0N1ow
234+ DjEMMAoGA1UEAwwDa2FzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErtYWni+n
235+ JFXwCwC1JSSrHoVvMaJgPXXFjLmExAgrpLhgpJaog9KH0AKPrLVU0Y3p/s8TOwuj
236+ CWh3bGxOD39PPKNTMFEwHQYDVR0OBBYEFLg9mMeD25ZGvmjSYaunIPoeekzlMB8G
237+ A1UdIwQYMBaAFLg9mMeD25ZGvmjSYaunIPoeekzlMA8GA1UdEwEB/wQFMAMBAf8w
238+ CgYIKoZIzj0EAwIDSAAwRQIhALYXC70t37RlmIkRDlUTehiVEHpSQXz04wQ9Ivw+
239+ 4h4hAiBNR3rD3KieiJaiJrCfM6TPJL7TIch7pAhMHdG6IPJMoQ==
207240-----END CERTIFICATE-----`
208241)
209242
@@ -263,6 +296,11 @@ func TestTDF(t *testing.T) {
263296}
264297
265298func (s * TDFSuite ) Test_SimpleTDF () {
299+ type TestConfig struct {
300+ tdfOptions []TDFOption
301+ tdfReadOptions []TDFReaderOption
302+ }
303+
266304 metaData := []byte (`{"displayName" : "openTDF go sdk"}` )
267305 attributes := []string {
268306 "https://example.com/attr/Classification/value/S" ,
@@ -272,14 +310,37 @@ func (s *TDFSuite) Test_SimpleTDF() {
272310 expectedTdfSize := int64 (2058 )
273311 tdfFilename := "secure-text.tdf"
274312 plainText := "Virtru"
275- {
276- kasURLs := []KASInfo {
277- {
278- URL : "https://a.kas/" ,
279- PublicKey : "" ,
313+
314+ // add opts ...TDFOption to TestConfig
315+ testConfigs := []TestConfig {
316+ {
317+ tdfOptions : []TDFOption {
318+ WithKasInformation (KASInfo {
319+ URL : "https://a.kas/" ,
320+ PublicKey : "" ,
321+ }),
322+ WithMetaData (string (metaData )),
323+ WithDataAttributes (attributes ... ),
280324 },
281- }
325+ tdfReadOptions : []TDFReaderOption {},
326+ },
327+ {
328+ tdfOptions : []TDFOption {
329+ WithKasInformation (KASInfo {
330+ URL : "https://d.kas/" ,
331+ PublicKey : "" ,
332+ }),
333+ WithMetaData (string (metaData )),
334+ WithDataAttributes (attributes ... ),
335+ WithWrappingKeyAlg (ocrypto .EC256Key ),
336+ },
337+ tdfReadOptions : []TDFReaderOption {
338+ WithSessionKeyType (ocrypto .EC256Key ),
339+ },
340+ },
341+ }
282342
343+ for _ , config := range testConfigs {
283344 inBuf := bytes .NewBufferString (plainText )
284345 bufReader := bytes .NewReader (inBuf .Bytes ())
285346
@@ -291,18 +352,12 @@ func (s *TDFSuite) Test_SimpleTDF() {
291352 s .Require ().NoError (err )
292353 }(fileWriter )
293354
294- tdfObj , err := s .sdk .CreateTDF (fileWriter , bufReader ,
295- WithKasInformation (kasURLs ... ),
296- WithMetaData (string (metaData )),
297- WithDataAttributes (attributes ... ),
298- )
355+ tdfObj , err := s .sdk .CreateTDF (fileWriter , bufReader , config .tdfOptions ... )
299356
300357 s .Require ().NoError (err )
301- s .InDelta (float64 (expectedTdfSize ), float64 (tdfObj .size ), 32.0 )
302- }
358+ s .InDelta (float64 (expectedTdfSize ), float64 (tdfObj .size ), 36.0 )
303359
304- // test meta data and build meta data
305- {
360+ // test meta data and build meta data
306361 readSeeker , err := os .Open (tdfFilename )
307362 s .Require ().NoError (err )
308363
@@ -311,28 +366,23 @@ func (s *TDFSuite) Test_SimpleTDF() {
311366 s .Require ().NoError (err )
312367 }(readSeeker )
313368
314- r , err := s .sdk .LoadTDF (readSeeker )
315-
369+ r , err := s .sdk .LoadTDF (readSeeker , config .tdfReadOptions ... )
316370 s .Require ().NoError (err )
317371
318372 unencryptedMetaData , err := r .UnencryptedMetadata ()
319373 s .Require ().NoError (err )
320-
321374 s .EqualValues (metaData , unencryptedMetaData )
322375
323376 dataAttributes , err := r .DataAttributes ()
324377 s .Require ().NoError (err )
325-
326378 s .Equal (attributes , dataAttributes )
327379
328380 payloadKey , err := r .UnsafePayloadKeyRetrieval ()
329381 s .Require ().NoError (err )
330382 s .Len (payloadKey , kKeySize )
331- }
332383
333- // test reader
334- {
335- readSeeker , err := os .Open (tdfFilename )
384+ // test reader
385+ readSeeker , err = os .Open (tdfFilename )
336386 s .Require ().NoError (err )
337387
338388 defer func (readSeeker * os.File ) {
@@ -341,8 +391,7 @@ func (s *TDFSuite) Test_SimpleTDF() {
341391 }(readSeeker )
342392
343393 buf := make ([]byte , 8 )
344-
345- r , err := s .sdk .LoadTDF (readSeeker )
394+ r , err = s .sdk .LoadTDF (readSeeker , config .tdfReadOptions ... )
346395 s .Require ().NoError (err )
347396
348397 offset := 2
@@ -353,9 +402,9 @@ func (s *TDFSuite) Test_SimpleTDF() {
353402
354403 expectedPlainTxt := plainText [offset : offset + n ]
355404 s .Equal (expectedPlainTxt , string (buf [:n ]))
356- }
357405
358- _ = os .Remove (tdfFilename )
406+ _ = os .Remove (tdfFilename )
407+ }
359408}
360409
361410func (s * TDFSuite ) Test_TDFWithAssertion () {
@@ -1613,7 +1662,7 @@ func (s *TDFSuite) startBackend() {
16131662 return l .Dial ()
16141663 }
16151664
1616- s .kases = make ([]FakeKas , 10 )
1665+ s .kases = make ([]FakeKas , 12 )
16171666
16181667 for i , ki := range []struct {
16191668 url , private , public string
@@ -1623,6 +1672,8 @@ func (s *TDFSuite) startBackend() {
16231672 {"https://a.kas/" , mockRSAPrivateKey1 , mockRSAPublicKey1 },
16241673 {"https://b.kas/" , mockRSAPrivateKey2 , mockRSAPublicKey2 },
16251674 {"https://c.kas/" , mockRSAPrivateKey3 , mockRSAPublicKey3 },
1675+ {"https://d.kas/" , mockECPrivateKey1 , mockECPublicKey1 },
1676+ {"https://e.kas/" , mockECPrivateKey2 , mockECPublicKey2 },
16261677 {kasAu , mockRSAPrivateKey1 , mockRSAPublicKey1 },
16271678 {kasCa , mockRSAPrivateKey2 , mockRSAPublicKey2 },
16281679 {kasUk , mockRSAPrivateKey2 , mockRSAPublicKey2 },
@@ -1757,22 +1808,83 @@ func (f *FakeKas) getRewrapResponse(rewrapRequest string) *kaspb.RewrapResponse
17571808 kao := kaoReq .GetKeyAccessObject ()
17581809 wrappedKey := kaoReq .GetKeyAccessObject ().GetWrappedKey ()
17591810
1760- kasPrivateKey := strings .ReplaceAll (f .privateKey , "\n \t " , "\n " )
1761- if kao .GetKid () != "" && kao .GetKid () != f .KID {
1762- // old kid
1763- lk , ok := f .legakeys [kaoReq .GetKeyAccessObject ().GetKid ()]
1764- f .s .Require ().True (ok , "unable to find key [%s]" , kao .GetKid ())
1765- kasPrivateKey = strings .ReplaceAll (lk .private , "\n \t " , "\n " )
1811+ var entityWrappedKey []byte
1812+ switch kaoReq .GetKeyAccessObject ().GetKeyType () {
1813+ case "ec-wrapped" :
1814+ // Get the ephemeral public key in PEM format
1815+ ephemeralPubKeyPEM := kaoReq .GetKeyAccessObject ().GetEphemeralPublicKey ()
1816+
1817+ // Get EC key size and convert to mode
1818+ keySize , err := ocrypto .GetECKeySize ([]byte (ephemeralPubKeyPEM ))
1819+ f .s .Require ().NoError (err , "failed to get EC key size" )
1820+
1821+ mode , err := ocrypto .ECSizeToMode (keySize )
1822+ f .s .Require ().NoError (err , "failed to convert key size to mode" )
1823+
1824+ // Parse the PEM public key
1825+ block , _ := pem .Decode ([]byte (ephemeralPubKeyPEM ))
1826+ f .s .Require ().NoError (err , "failed to decode PEM block" )
1827+
1828+ pub , err := x509 .ParsePKIXPublicKey (block .Bytes )
1829+ f .s .Require ().NoError (err , "failed to parse public key" )
1830+
1831+ ecPub , ok := pub .(* ecdsa.PublicKey )
1832+ if ! ok {
1833+ f .s .Require ().Error (err , "not an EC public key" )
1834+ }
1835+
1836+ // Compress the public key
1837+ compressedKey , err := ocrypto .CompressedECPublicKey (mode , * ecPub )
1838+ f .s .Require ().NoError (err , "failed to compress public key" )
1839+
1840+ kasPrivateKey := strings .ReplaceAll (f .privateKey , "\n \t " , "\n " )
1841+ if kao .GetKid () != "" && kao .GetKid () != f .KID {
1842+ // old kid
1843+ lk , found := f .legakeys [kaoReq .GetKeyAccessObject ().GetKid ()]
1844+ f .s .Require ().True (found , "unable to find key [%s]" , kao .GetKid ())
1845+ kasPrivateKey = strings .ReplaceAll (lk .private , "\n \t " , "\n " )
1846+ }
1847+
1848+ privateKey , err := ocrypto .ECPrivateKeyFromPem ([]byte (kasPrivateKey ))
1849+ f .s .Require ().NoError (err , "failed to extract private key from PEM" )
1850+
1851+ ed , err := ocrypto .NewECDecryptor (privateKey )
1852+ f .s .Require ().NoError (err , "failed to create EC decryptor" )
1853+
1854+ symmetricKey , err := ed .DecryptWithEphemeralKey (wrappedKey , compressedKey )
1855+ f .s .Require ().NoError (err , "failed to decrypt" )
1856+
1857+ asymEncrypt , err := ocrypto .FromPublicPEM (bodyData .GetClientPublicKey ())
1858+ f .s .Require ().NoError (err , "ocrypto.FromPublicPEM failed" )
1859+
1860+ var sessionKey string
1861+ if e , found := asymEncrypt .(ocrypto.ECEncryptor ); found {
1862+ sessionKey , err = e .PublicKeyInPemFormat ()
1863+ f .s .Require ().NoError (err , "unable to serialize ephemeral key" )
1864+ }
1865+ resp .SessionPublicKey = sessionKey
1866+ entityWrappedKey , err = asymEncrypt .Encrypt (symmetricKey )
1867+ f .s .Require ().NoError (err , "ocrypto.AsymEncryption.encrypt failed" )
1868+
1869+ case "wrapped" :
1870+ kasPrivateKey := strings .ReplaceAll (f .privateKey , "\n \t " , "\n " )
1871+ if kao .GetKid () != "" && kao .GetKid () != f .KID {
1872+ // old kid
1873+ lk , ok := f .legakeys [kaoReq .GetKeyAccessObject ().GetKid ()]
1874+ f .s .Require ().True (ok , "unable to find key [%s]" , kao .GetKid ())
1875+ kasPrivateKey = strings .ReplaceAll (lk .private , "\n \t " , "\n " )
1876+ }
1877+
1878+ asymDecrypt , err := ocrypto .NewAsymDecryption (kasPrivateKey )
1879+ f .s .Require ().NoError (err , "ocrypto.NewAsymDecryption failed" )
1880+ symmetricKey , err := asymDecrypt .Decrypt (wrappedKey )
1881+ f .s .Require ().NoError (err , "ocrypto.Decrypt failed" )
1882+ asymEncrypt , err := ocrypto .NewAsymEncryption (bodyData .GetClientPublicKey ())
1883+ f .s .Require ().NoError (err , "ocrypto.NewAsymEncryption failed" )
1884+ entityWrappedKey , err = asymEncrypt .Encrypt (symmetricKey )
1885+ f .s .Require ().NoError (err , "ocrypto.encrypt failed" )
17661886 }
17671887
1768- asymDecrypt , err := ocrypto .NewAsymDecryption (kasPrivateKey )
1769- f .s .Require ().NoError (err , "ocrypto.NewAsymDecryption failed" )
1770- symmetricKey , err := asymDecrypt .Decrypt (wrappedKey )
1771- f .s .Require ().NoError (err , "ocrypto.Decrypt failed" )
1772- asymEncrypt , err := ocrypto .NewAsymEncryption (bodyData .GetClientPublicKey ())
1773- f .s .Require ().NoError (err , "ocrypto.NewAsymEncryption failed" )
1774- entityWrappedKey , err := asymEncrypt .Encrypt (symmetricKey )
1775- f .s .Require ().NoError (err , "ocrypto.encrypt failed" )
17761888 kaoResult := & kaspb.KeyAccessRewrapResult {
17771889 Result : & kaspb.KeyAccessRewrapResult_KasWrappedKey {KasWrappedKey : entityWrappedKey },
17781890 Status : "permit" ,
0 commit comments