Skip to content

Commit a95e4e6

Browse files
chore(sdk): EC-wrapped unit tests (#1924)
### Proposed Changes *Updated FakeKas to support ec-wrapped ### Checklist - [ ] I have added or updated unit tests - [ ] I have added or updated integration tests (if appropriate) - [ ] I have added or updated documentation ### Testing Instructions --------- Co-authored-by: David Mihalcik <[email protected]>
1 parent d7b430b commit a95e4e6

File tree

1 file changed

+154
-42
lines changed

1 file changed

+154
-42
lines changed

sdk/tdf_test.go

Lines changed: 154 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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
204207
uOnQR2c7Dix39LZQCiEfPSUnTAKJCyMpolky7Vq31PsPKk+gK19XftfH/Aul21vt
205208
ZwVW7fLwZ2SSmC9cOjSkzZw/eDwwIRNgo94OL4mw5cXSPOuMeO8Tugc6LO4v91SO
206209
yg==
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

265298
func (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

361410
func (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

Comments
 (0)